Fix Comment Problem when Using PJAX or InstantClick on Typecho

0.Problem description

After introducing asynchronously loaded scripts such as PJAX / InstantClick, the following exception occurs when switching pages:

  1. Unable to click the "Reply" button;
  2. The comment fails. Return (refresh the page) and comment again normally.

The problem with Question 2 is that Typecho's "check whether the comment source page URL is consistent with the article link" and "anti-spam protection" are not necessarily compatible with the theme, PJAX, and InstantClick settings. The problem with Question 1 is that Typecho's settings in <head> The content of TypechoComment is output in TypechoComment, while PJAX/InstantClick generally only overloads <body>.

1.Solution

1.1, Question 1

First, turn off "Check whether the comment source page URL is consistent with the article link" and "Anti-Spam Protection" in the Typecho backend under Settings-Comments-Comment Submission.

I know that after closing, robots will spam comments, so I recommend using comment verification codes or comment anti-spam plug-ins, such as the reCAPTCHAv3 plug-in that I have used for a long time.
Recently, I discovered that there was something wrong with the ReCaptcha mirror site, either "Unable to connect to ReCaptcha" or "You have been judged to be a robot", making it almost unusable, so I changed it to Cloudflare Turnstile. For application and configuration, please refer to the article on this site:

(not written yet)

Next up is PJAX/InstantClick compatible:

If using PJAX, edit line 50 of /var/Widget/Security.php to read:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function getTokenUrl($path)
{
     $parts = parse_url($path);
     $params = array();

     if (!empty($parts['query'])) {
         parse_str($parts['query'], $params);
     }

     $params['_'] = $this->getToken(preg_replace('/\?_pjax=%23body/', '', $this->request->getRequestUrl()));
     $parts['query'] = http_build_query($params);

     return Typecho_Common::buildUrl($parts);
}

Then add PJAX/InstantClick's refresh function (the function executed after the page changes):

JavaScript
1
2
var now_url_comment = location.href + ' ';
history.replaceState(null,'',now_url_comment.replace('/comment ',''));

That will solve the problem.

1.2, Question 2

There are two ideas to solve the JavaScript loading problem (reply button problem):

  1. Reload function when updating the page;
  2. Directly delete the comment function that comes with Typecho and modify it to a function that adapts to your own template.

1.2.1, Idea 1

The first method is more general, just add when outputting the comment:

PHP
1
<script>respondId = '<?php $this->respondId(); ?>';</script>

If the template doesn't have $this here (like mine), it can be obtained with JS. For example the comment form is:

PHP
1
2
3
<div id="<?php $this->respondId(); ?>" class="response-box">
...
</div>

The above output code can be changed to:

PHP
1
<script>respondId = document.getElementsByClassName("response-box")[0].id;</script>

No matter which method is used, the responseId in the page will be loaded into a JS variable after output, so just add it to the refresh function of PJAX / InstantClick:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
window.TypechoComment = {
     dom : function (id) {
         return document.getElementById(id);
     },

     create : function (tag, attr) {
         var el = document.createElement(tag);

         for (var key in attr) {
             el.setAttribute(key, attr[key]);
         }

         return el;
     },

     reply : function (cid, coid) {
         var comment = this.dom(cid), parent = comment.parentNode,
             response = this.dom(respondId()), input = this.dom('comment-parent'),
             form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0],
             textarea = response.getElementsByTagName('textarea')[0];

         if (null == input) {
             input = this.create('input', {
                 'type' : 'hidden',
                 'name' : 'parent',
                 'id' : 'comment-parent'
             });

             form.appendChild(input);
         }

         input.setAttribute('value', coid);

         if (null == this.dom('comment-form-place-holder')) {
             var holder = this.create('div', {
                 'id' : 'comment-form-place-holder'
             });

             response.parentNode.insertBefore(holder, response);
         }

         comment.appendChild(response);
         this.dom('cancel-comment-reply-link').style.display = '';

         if (null != textarea && 'text' == textarea.name) {
             textarea.focus();
         }

         return false;
     },

     cancelReply : function () {
         var response = this.dom(respondId()),
         holder = this.dom('comment-form-place-holder'), input = this.dom('comment-parent');

         if (null != input) {
             input.parentNode.removeChild(input);
         }

         if (null == holder) {
             return true;
         }

         this.dom('cancel-comment-reply-link').style.display = 'none';
         holder.parentNode.insertBefore(response, holder);
         return false;
     }
};

If it still doesn't work, you can modify it slightly according to your own template content.

1.2.2, Idea 2

I don’t really like that when Typecho replies to a comment, it appends the comment box below the topic to be replied to, so I directly delete the built-in comment function, that is, put the <head> part:

PHP
1
$this->header('');

Change to:

PHP
1
$this->header('commentReply=');

Of course, I recommend getting rid of a lot of clutter in the head as well:

PHP
1
$this->header('generator=&template=&pingback=&xmlrpc=&wlw=&atom=&rss1=&rss2=&commentReply=');

Then add JS to the comment output code:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<script type="text/javascript">
(function () {
     window.TypechoComment = {
         dom : function (id) {
             return document.getElementById(id);
         },
         create : function (tag, attr) {
             var el = document.createElement(tag);
             for (var key in attr) {
                 el.setAttribute(key, attr[key]);
             }
             return el;
         },
         reply : function (cid, coid) {
             var comment = this.dom(cid), parent = comment.parentNode,
                 response = this.dom('<?php echo $this->respondId(); ?>'),
                 input = this.dom('comment-parent'),
                 form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0],
                 textarea = response.getElementsByTagName('textarea')[0];
             if (null == input) {
                 input = this.create('input', {
                     'type' : 'hidden',
                     'name' : 'parent',
                     'id'   : 'comment-parent'
                });
                form.appendChild(input);
            }
            input.setAttribute('value', coid);
            if (null == this.dom('comment-form-place-holder')) {
                var holder = this.create('div', {
                    'id' : 'comment-form-place-holder'
                });
                response.parentNode.insertBefore(holder, response);
            }
            comment.appendChild(response);
            this.dom('cancel-comment-reply-link').style.display = '';
            if (null != textarea && 'text' == textarea.name) {
                textarea.focus();
            }
            return false;
        },
        cancelReply : function () {
            var response = this.dom('<?php echo $this->respondId(); ?>'),
            holder = this.dom('comment-form-place-holder'),
            input = this.dom('comment-parent');
            if (null != input) {
                input.parentNode.removeChild(input);
            }
            if (null == holder) {
                return true;
            }
            this.dom('cancel-comment-reply-link').style.display = 'none';
            holder.parentNode.insertBefore(response, holder);
            return false;
        }
    };
})();
</script>

如果不需要上述的评论框移动,只需删去comment.appendChild(response);即可。

这里使用了$this->responseId,如果没有这个值同样可以用 JS 动态获取:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script type="text/javascript">  
(function () {
    responseId = document.getElementsByClassName("response-box")[0].id;
    window.TypechoComment = {
        dom : function (id) {
            return document.getElementById(id);
        },
        create : function (tag, attr) {
            var el = document.createElement(tag);
            for (var key in attr) {
                el.setAttribute(key, attr[key]);
            }
            return el;
        },
        reply : function (cid, coid) {
            var comment = this.dom(cid), parent = comment.parentNode,
                response = this.dom('<?php echo $this->respondId(); ?>'),
                input = this.dom('comment-parent'),
                form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0],
                textarea = response.getElementsByTagName('textarea')[0];
            if (null == input) {
                input = this.create('input', {
                    'type' : 'hidden',
                    'name' : 'parent',
                    'id'   : 'comment-parent'
                });
                form.appendChild(input);
            }
           input.setAttribute('value', coid);
            this.dom('cancel-comment-reply-link').style.display = '';
            if (null != textarea && 'text' == textarea.name) {
                textarea.focus();
            }
            return false;
        },
        cancelReply : function () {
            var response = this.dom(responseId),
            input = this.dom('comment-parent');
            if (null != input) {
                input.parentNode.removeChild(input);
            }
            this.dom('cancel-comment-reply-link').style.display = 'none';
            return false;
        }
    };
})();
</script>

Solved perfectly.

Fix Comment Problem when Using PJAX or InstantClick on Typecho

https://blog.tsinbei.com/en/archives/765/

Author
Hsukqi Lee
Posted on

2023-11-19

Edited on

2023-11-19

Licensed under

CC BY-NC-ND 4.0

Comments

Name
Mail
Site
None yet