artalk 2.8.0 → 2.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Artalk.mjs CHANGED
@@ -381,6 +381,29 @@ let Api$1 = class Api extends HttpClient {
381
381
  type: "application/json",
382
382
  format: "json"
383
383
  }, params)),
384
+ /**
385
+ * @description Get the detail of a comment by comment id
386
+ *
387
+ * @tags Comment
388
+ * @name GetComment
389
+ * @summary Get a comment
390
+ * @request GET:/comments/{id}
391
+ * @response `200` `HandlerResponseCommentGet` OK
392
+ * @response `404` `(HandlerMap & {
393
+ msg?: string,
394
+
395
+ })` Not Found
396
+ * @response `500` `(HandlerMap & {
397
+ msg?: string,
398
+
399
+ })` Internal Server Error
400
+ */
401
+ getComment: (id, params = {}) => this.request(__spreadValues({
402
+ path: `/comments/${id}`,
403
+ method: "GET",
404
+ type: "application/json",
405
+ format: "json"
406
+ }, params)),
384
407
  /**
385
408
  * @description Update a specific comment
386
409
  *
@@ -4470,7 +4493,7 @@ const en = {
4470
4493
  link: "Website",
4471
4494
  emoticon: "Emoji",
4472
4495
  preview: "Preview",
4473
- image: "Image",
4496
+ uploadImage: "Upload Image",
4474
4497
  uploadFail: "Upload Failed",
4475
4498
  commentFail: "Failed to comment",
4476
4499
  restoredMsg: "Content has been restored",
@@ -4543,14 +4566,14 @@ const zhCN = {
4543
4566
  /* Editor */
4544
4567
  placeholder: "键入内容...",
4545
4568
  noComment: "「此时无声胜有声」",
4546
- send: "发送评论",
4547
- save: "保存评论",
4569
+ send: "发送",
4570
+ save: "保存",
4548
4571
  nick: "昵称",
4549
4572
  email: "邮箱",
4550
4573
  link: "网址",
4551
4574
  emoticon: "表情",
4552
4575
  preview: "预览",
4553
- image: "图片",
4576
+ uploadImage: "上传图片",
4554
4577
  uploadFail: "上传失败",
4555
4578
  commentFail: "评论失败",
4556
4579
  restoredMsg: "内容已自动恢复",
@@ -5152,7 +5175,7 @@ function showLoading(parentElem, conf) {
5152
5175
  let $loading = parentElem.querySelector(":scope > .atk-loading");
5153
5176
  if (!$loading) {
5154
5177
  $loading = createElement(
5155
- `<div class="atk-loading atk-fade-in" style="display: none;">
5178
+ `<div class="atk-loading" style="display: none;">
5156
5179
  <div class="atk-loading-spinner">
5157
5180
  <svg viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none" stroke-width="2" stroke-miterlimit="10"></circle></svg>
5158
5181
  </div>
@@ -5167,7 +5190,8 @@ function showLoading(parentElem, conf) {
5167
5190
  if ($spinner) {
5168
5191
  $spinner.style.display = "none";
5169
5192
  window.setTimeout(() => {
5170
- $spinner.style.display = "";
5193
+ if ($spinner.isConnected)
5194
+ $spinner.style.display = "";
5171
5195
  }, 500);
5172
5196
  }
5173
5197
  }
@@ -5187,8 +5211,7 @@ function scrollIntoView(elem, enableAnim = true, relativeTo) {
5187
5211
  if (relativeTo) {
5188
5212
  const containerRect = relativeTo.getBoundingClientRect();
5189
5213
  const elementRect = elem.getBoundingClientRect();
5190
- top = elementRect.top - containerRect.top + relativeTo.scrollTop;
5191
- top += getHeight(relativeTo) / 2 - getHeight(elem) / 2;
5214
+ top = elementRect.top - containerRect.top + relativeTo.scrollTop - relativeTo.clientHeight / 2 + elem.clientHeight / 2;
5192
5215
  } else {
5193
5216
  const rect = elem.getBoundingClientRect();
5194
5217
  const elemTop = rect.top + window.scrollY;
@@ -5522,8 +5545,11 @@ class Component {
5522
5545
  get conf() {
5523
5546
  return this.ctx.conf;
5524
5547
  }
5548
+ getEl() {
5549
+ return this.$el;
5550
+ }
5525
5551
  }
5526
- const EditorHTML = '<div class="atk-main-editor">\n <div class="atk-header">\n <input name="nick" class="atk-nick" type="text" required="required">\n <input name="email" class="atk-email" type="email" required="required">\n <input name="link" class="atk-link" type="url">\n </div>\n <div class="atk-textarea-wrap">\n <textarea class="atk-textarea"></textarea>\n </div>\n <div class="atk-plug-panel-wrap" style="display: none;"></div>\n <div class="atk-bottom">\n <div class="atk-item atk-plug-btn-wrap"></div>\n <div class="atk-item">\n <button type="button" class="atk-send-btn"></button>\n </div>\n </div>\n <div class="atk-notify-wrap"></div>\n</div>\n';
5552
+ const EditorHTML = '<div class="atk-main-editor">\n <div class="atk-header">\n <input name="nick" class="atk-nick" type="text" required="required">\n <input name="email" class="atk-email" type="email" required="required">\n <input name="link" class="atk-link" type="url">\n </div>\n <div class="atk-textarea-wrap">\n <textarea class="atk-textarea"></textarea>\n </div>\n <div class="atk-plug-panel-wrap" style="display: none;"></div>\n <div class="atk-bottom">\n <div class="atk-item atk-bottom-left">\n <span class="atk-state-wrap"></span>\n <span class="atk-plug-btn-wrap"></span>\n </div>\n <div class="atk-item">\n <button type="button" class="atk-send-btn"></button>\n </div>\n </div>\n <div class="atk-notify-wrap"></div>\n</div>\n';
5527
5553
  const Sel = {
5528
5554
  $header: ".atk-header",
5529
5555
  $nick: '.atk-header [name="nick"]',
@@ -5534,6 +5560,8 @@ const Sel = {
5534
5560
  $bottom: ".atk-bottom",
5535
5561
  $submitBtn: ".atk-send-btn",
5536
5562
  $notifyWrap: ".atk-notify-wrap",
5563
+ $bottomLeft: ".atk-bottom-left",
5564
+ $stateWrap: ".atk-state-wrap",
5537
5565
  $plugBtnWrap: ".atk-plug-btn-wrap",
5538
5566
  $plugPanelWrap: ".atk-plug-panel-wrap"
5539
5567
  };
@@ -5906,39 +5934,18 @@ function makeNestCommentNodeList(srcData, sortBy = "DATE_DESC", nestMax = 2) {
5906
5934
  })(nodeList);
5907
5935
  return nodeList;
5908
5936
  }
5909
- class ListLayout {
5910
- constructor(options) {
5911
- this.options = options;
5912
- }
5913
- // TODO: refactor `if syntax` to strategy pattern
5914
- import(comments) {
5915
- if (this.options.flatMode) {
5916
- comments.forEach((commentData) => {
5917
- this.putCommentFlatMode(commentData, comments, "append");
5918
- });
5919
- } else {
5920
- this.importCommentsNestMode(comments);
5921
- }
5922
- }
5923
- insert(comment) {
5924
- if (!this.options.flatMode) {
5925
- this.insertCommentNest(comment);
5926
- } else {
5927
- this.insertCommentFlatMode(comment);
5928
- }
5929
- }
5930
- // 导入评论 · 嵌套模式
5931
- importCommentsNestMode(srcData) {
5932
- const rootNodes = makeNestCommentNodeList(srcData, this.options.nestSortBy, this.options.nestMax);
5937
+ const createNestStrategy = (opts) => ({
5938
+ import: (comments) => {
5939
+ const rootNodes = makeNestCommentNodeList(comments, opts.nestSortBy, opts.nestMax);
5933
5940
  rootNodes.forEach((rootNode) => {
5934
5941
  var _a;
5935
- const rootC = this.options.createCommentNode(rootNode.comment, srcData);
5936
- (_a = this.options.$commentsWrap) == null ? void 0 : _a.appendChild(rootC.getEl());
5942
+ const rootC = opts.createCommentNode(rootNode.comment);
5943
+ (_a = opts.$commentsWrap) == null ? void 0 : _a.appendChild(rootC.getEl());
5937
5944
  rootC.getRender().playFadeAnim();
5938
5945
  const loadChildren = (parentC, parentNode) => {
5939
5946
  parentNode.children.forEach((node) => {
5940
5947
  const childD = node.comment;
5941
- const childC = this.options.createCommentNode(childD, srcData);
5948
+ const childC = opts.createCommentNode(childD, parentC.getData());
5942
5949
  parentC.putChild(childC);
5943
5950
  loadChildren(childC, node);
5944
5951
  });
@@ -5946,44 +5953,66 @@ class ListLayout {
5946
5953
  loadChildren(rootC, rootNode);
5947
5954
  rootC.getRender().checkHeightLimit();
5948
5955
  });
5949
- }
5950
- /** 导入评论 · 平铺模式 */
5951
- putCommentFlatMode(cData, ctxData, insertMode) {
5952
- var _a, _b;
5953
- if (cData.is_collapsed)
5954
- cData.is_allow_reply = false;
5955
- const comment = this.options.createCommentNode(cData, ctxData);
5956
- if (cData.visible) {
5957
- if (insertMode === "append")
5958
- (_a = this.options.$commentsWrap) == null ? void 0 : _a.append(comment.getEl());
5959
- if (insertMode === "prepend")
5960
- (_b = this.options.$commentsWrap) == null ? void 0 : _b.prepend(comment.getEl());
5961
- comment.getRender().playFadeAnim();
5962
- }
5963
- comment.getRender().checkHeightLimit();
5964
- return comment;
5965
- }
5966
- insertCommentNest(commentData) {
5956
+ },
5957
+ insert: (comment, replyComment) => {
5967
5958
  var _a;
5968
- const comment = this.options.createCommentNode(commentData, this.options.getCommentDataList());
5969
- if (commentData.rid === 0) {
5970
- (_a = this.options.$commentsWrap) == null ? void 0 : _a.prepend(comment.getEl());
5959
+ const node = opts.createCommentNode(comment, replyComment);
5960
+ if (comment.rid === 0) {
5961
+ (_a = opts.$commentsWrap) == null ? void 0 : _a.prepend(node.getEl());
5971
5962
  } else {
5972
- const parent = this.options.findCommentNode(commentData.rid);
5963
+ const parent = opts.findCommentNode(comment.rid);
5973
5964
  if (parent) {
5974
- parent.putChild(comment, this.options.nestSortBy === "DATE_ASC" ? "append" : "prepend");
5975
- comment.getParents().forEach((p) => {
5965
+ parent.putChild(node, opts.nestSortBy === "DATE_ASC" ? "append" : "prepend");
5966
+ node.getParents().forEach((p) => {
5976
5967
  p.getRender().heightLimitRemoveForChildren();
5977
5968
  });
5978
5969
  }
5979
5970
  }
5980
- comment.getRender().checkHeightLimit();
5981
- scrollIntoView(comment.getEl());
5982
- comment.getRender().playFadeAnim();
5971
+ node.getRender().checkHeightLimit();
5972
+ node.scrollIntoView();
5973
+ node.getRender().playFadeAnim();
5974
+ }
5975
+ });
5976
+ const createFlatStrategy = (opts) => ({
5977
+ import: (comments) => {
5978
+ comments.forEach((comment) => {
5979
+ const replyComment = comment.rid === 0 ? void 0 : comments.find((c) => c.id === comment.rid);
5980
+ insertComment(opts, "append", comment, replyComment);
5981
+ });
5982
+ },
5983
+ insert: (comment, replyComment) => {
5984
+ const node = insertComment(opts, "prepend", comment, replyComment);
5985
+ node.scrollIntoView();
5986
+ }
5987
+ });
5988
+ function insertComment(opts, insertMode, comment, replyComment) {
5989
+ if (comment.is_collapsed)
5990
+ comment.is_allow_reply = false;
5991
+ const node = opts.createCommentNode(comment, replyComment);
5992
+ if (comment.visible) {
5993
+ const $comment = node.getEl();
5994
+ const $listCommentsWrap = opts.$commentsWrap;
5995
+ if (insertMode === "append")
5996
+ $listCommentsWrap == null ? void 0 : $listCommentsWrap.append($comment);
5997
+ if (insertMode === "prepend")
5998
+ $listCommentsWrap == null ? void 0 : $listCommentsWrap.prepend($comment);
5999
+ node.getRender().playFadeAnim();
6000
+ }
6001
+ node.getRender().checkHeightLimit();
6002
+ return node;
6003
+ }
6004
+ class ListLayout {
6005
+ constructor(options) {
6006
+ this.options = options;
6007
+ }
6008
+ getStrategy() {
6009
+ return this.options.flatMode ? createFlatStrategy(this.options) : createNestStrategy(this.options);
5983
6010
  }
5984
- insertCommentFlatMode(commentData) {
5985
- const comment = this.putCommentFlatMode(commentData, this.options.getCommentDataList(), "prepend");
5986
- scrollIntoView(comment.getEl());
6011
+ import(comments) {
6012
+ this.getStrategy().import(comments);
6013
+ }
6014
+ insert(comment, replyComment) {
6015
+ this.getStrategy().insert(comment, replyComment);
5987
6016
  }
5988
6017
  }
5989
6018
  function Detect(userAgent) {
@@ -6246,7 +6275,7 @@ const CommentHTML = '<div class="atk-comment-wrap">\n <div class="atk-comment">
6246
6275
  function renderAvatar(r) {
6247
6276
  const $avatar = r.$el.querySelector(".atk-avatar");
6248
6277
  const $avatarImg = createElement("<img />");
6249
- const avatarURLBuilder = r.conf.avatarURLBuilder;
6278
+ const avatarURLBuilder = r.opts.avatarURLBuilder;
6250
6279
  $avatarImg.src = avatarURLBuilder ? avatarURLBuilder(r.data) : r.comment.getGravatarURL();
6251
6280
  if (r.data.link) {
6252
6281
  const $avatarA = createElement('<a target="_blank" rel="noreferrer noopener nofollow"></a>');
@@ -6285,12 +6314,12 @@ function renderVerifyBadge(ctx) {
6285
6314
  const badgeColor = ctx.data.badge_color;
6286
6315
  if (badgeText) {
6287
6316
  const $badge = createElement(`<span class="atk-badge"></span>`);
6288
- $badge.innerText = badgeText.replace("管理员", ctx.ctx.$t("admin"));
6317
+ $badge.innerText = badgeText.replace("管理员", t("admin"));
6289
6318
  $badge.style.backgroundColor = badgeColor || "";
6290
6319
  ctx.$headerBadgeWrap.append($badge);
6291
6320
  }
6292
6321
  if (ctx.data.is_pinned) {
6293
- const $pinnedBadge = createElement(`<span class="atk-pinned-badge">${ctx.ctx.$t("pin")}</span>`);
6322
+ const $pinnedBadge = createElement(`<span class="atk-pinned-badge">${t("pin")}</span>`);
6294
6323
  ctx.$headerBadgeWrap.append($pinnedBadge);
6295
6324
  }
6296
6325
  }
@@ -6300,7 +6329,7 @@ function renderDate(ctx) {
6300
6329
  $date.setAttribute("data-atk-comment-date", String(+new Date(ctx.data.date)));
6301
6330
  }
6302
6331
  function renderUABadge(ctx) {
6303
- if (!ctx.ctx.conf.uaBadge && !ctx.data.ip_region)
6332
+ if (!ctx.opts.uaBadge && !ctx.data.ip_region)
6304
6333
  return;
6305
6334
  let $uaWrap = ctx.$header.querySelector("atk-ua-wrap");
6306
6335
  if (!$uaWrap) {
@@ -6313,7 +6342,7 @@ function renderUABadge(ctx) {
6313
6342
  $regionBadge.innerText = ctx.data.ip_region;
6314
6343
  $uaWrap.append($regionBadge);
6315
6344
  }
6316
- if (ctx.ctx.conf.uaBadge) {
6345
+ if (ctx.opts.uaBadge) {
6317
6346
  const { browser, os } = ctx.comment.getUserUA();
6318
6347
  if (String(browser).trim()) {
6319
6348
  const $uaBrowser = createElement(`<span class="atk-ua ua-browser"></span>`);
@@ -6336,8 +6365,8 @@ function renderContent(r) {
6336
6365
  r.$content.classList.add("atk-hide", "atk-type-collapsed");
6337
6366
  const collapsedInfoEl = createElement(`
6338
6367
  <div class="atk-collapsed">
6339
- <span class="atk-text">${r.ctx.$t("collapsedMsg")}</span>
6340
- <span class="atk-show-btn">${r.ctx.$t("expand")}</span>
6368
+ <span class="atk-text">${t("collapsedMsg")}</span>
6369
+ <span class="atk-show-btn">${t("expand")}</span>
6341
6370
  </div>`);
6342
6371
  r.$body.insertAdjacentElement("beforeend", collapsedInfoEl);
6343
6372
  const contentShowBtn = collapsedInfoEl.querySelector(".atk-show-btn");
@@ -6347,51 +6376,51 @@ function renderContent(r) {
6347
6376
  r.$content.innerHTML = r.comment.getContentMarked();
6348
6377
  r.$content.classList.remove("atk-hide");
6349
6378
  playFadeInAnim(r.$content);
6350
- contentShowBtn.innerText = r.ctx.$t("collapse");
6379
+ contentShowBtn.innerText = t("collapse");
6351
6380
  } else {
6352
6381
  r.$content.innerHTML = "";
6353
6382
  r.$content.classList.add("atk-hide");
6354
- contentShowBtn.innerText = r.ctx.$t("expand");
6383
+ contentShowBtn.innerText = t("expand");
6355
6384
  }
6356
6385
  });
6357
6386
  }
6358
6387
  function renderReplyAt(r) {
6359
- if (r.cConf.isFlatMode || r.data.rid === 0)
6388
+ if (r.opts.flatMode || r.data.rid === 0)
6360
6389
  return;
6361
- if (!r.cConf.replyTo)
6390
+ if (!r.opts.replyTo)
6362
6391
  return;
6363
6392
  r.$replyAt = createElement(`<span class="atk-item atk-reply-at"><span class="atk-arrow"></span><span class="atk-nick"></span></span>`);
6364
- r.$replyAt.querySelector(".atk-nick").innerText = `${r.cConf.replyTo.nick}`;
6393
+ r.$replyAt.querySelector(".atk-nick").innerText = `${r.opts.replyTo.nick}`;
6365
6394
  r.$replyAt.onclick = () => {
6366
6395
  r.comment.getActions().goToReplyComment();
6367
6396
  };
6368
6397
  r.$headerBadgeWrap.insertAdjacentElement("afterend", r.$replyAt);
6369
6398
  }
6370
6399
  function renderReplyTo(r) {
6371
- if (!r.cConf.isFlatMode)
6400
+ if (!r.opts.flatMode)
6372
6401
  return;
6373
- if (!r.cConf.replyTo)
6402
+ if (!r.opts.replyTo)
6374
6403
  return;
6375
6404
  r.$replyTo = createElement(`
6376
6405
  <div class="atk-reply-to">
6377
- <div class="atk-meta">${r.ctx.$t("reply")} <span class="atk-nick"></span>:</div>
6406
+ <div class="atk-meta">${t("reply")} <span class="atk-nick"></span>:</div>
6378
6407
  <div class="atk-content"></div>
6379
6408
  </div>`);
6380
6409
  const $nick = r.$replyTo.querySelector(".atk-nick");
6381
- $nick.innerText = `@${r.cConf.replyTo.nick}`;
6410
+ $nick.innerText = `@${r.opts.replyTo.nick}`;
6382
6411
  $nick.onclick = () => {
6383
6412
  r.comment.getActions().goToReplyComment();
6384
6413
  };
6385
- let replyContent = marked(r.cConf.replyTo.content);
6386
- if (r.cConf.replyTo.is_collapsed)
6387
- replyContent = `[${r.ctx.$t("collapsed")}]`;
6414
+ let replyContent = marked(r.opts.replyTo.content);
6415
+ if (r.opts.replyTo.is_collapsed)
6416
+ replyContent = `[${t("collapsed")}]`;
6388
6417
  r.$replyTo.querySelector(".atk-content").innerHTML = replyContent;
6389
6418
  r.$body.prepend(r.$replyTo);
6390
6419
  }
6391
6420
  function renderPending(r) {
6392
6421
  if (!r.data.is_pending)
6393
6422
  return;
6394
- const pendingEl = createElement(`<div class="atk-pending">${r.ctx.$t("pendingMsg")}</div>`);
6423
+ const pendingEl = createElement(`<div class="atk-pending">${t("pendingMsg")}</div>`);
6395
6424
  r.$body.prepend(pendingEl);
6396
6425
  }
6397
6426
  class ActionBtn {
@@ -6544,14 +6573,14 @@ function renderActions(r) {
6544
6573
  });
6545
6574
  }
6546
6575
  function renderVote(r) {
6547
- if (!r.ctx.conf.vote)
6576
+ if (!r.opts.vote)
6548
6577
  return;
6549
- r.voteBtnUp = new ActionBtn(() => `${r.ctx.$t("voteUp")} (${r.data.vote_up || 0})`).appendTo(r.$actions);
6578
+ r.voteBtnUp = new ActionBtn(() => `${t("voteUp")} (${r.data.vote_up || 0})`).appendTo(r.$actions);
6550
6579
  r.voteBtnUp.setClick(() => {
6551
6580
  r.comment.getActions().vote("up");
6552
6581
  });
6553
- if (r.ctx.conf.voteDown) {
6554
- r.voteBtnDown = new ActionBtn(() => `${r.ctx.$t("voteDown")} (${r.data.vote_down || 0})`).appendTo(r.$actions);
6582
+ if (r.opts.voteDown) {
6583
+ r.voteBtnDown = new ActionBtn(() => `${t("voteDown")} (${r.data.vote_down || 0})`).appendTo(r.$actions);
6555
6584
  r.voteBtnDown.setClick(() => {
6556
6585
  r.comment.getActions().vote("down");
6557
6586
  });
@@ -6560,20 +6589,16 @@ function renderVote(r) {
6560
6589
  function renderReply(r) {
6561
6590
  if (!r.data.is_allow_reply)
6562
6591
  return;
6563
- const replyBtn = createElement(`<span>${r.ctx.$t("reply")}</span>`);
6592
+ const replyBtn = createElement(`<span>${t("reply")}</span>`);
6564
6593
  r.$actions.append(replyBtn);
6565
6594
  replyBtn.addEventListener("click", (e) => {
6566
6595
  e.stopPropagation();
6567
- if (!r.cConf.onReplyBtnClick) {
6568
- r.ctx.replyComment(r.data, r.$el);
6569
- } else {
6570
- r.cConf.onReplyBtnClick();
6571
- }
6596
+ r.opts.replyComment(r.data, r.$el);
6572
6597
  });
6573
6598
  }
6574
6599
  function renderCollapse(r) {
6575
6600
  const collapseBtn = new ActionBtn({
6576
- text: () => r.data.is_collapsed ? r.ctx.$t("expand") : r.ctx.$t("collapse"),
6601
+ text: () => r.data.is_collapsed ? t("expand") : t("collapse"),
6577
6602
  adminOnly: true
6578
6603
  });
6579
6604
  collapseBtn.appendTo(r.$actions);
@@ -6583,7 +6608,7 @@ function renderCollapse(r) {
6583
6608
  }
6584
6609
  function renderModerator(r) {
6585
6610
  const pendingBtn = new ActionBtn({
6586
- text: () => r.data.is_pending ? r.ctx.$t("pending") : r.ctx.$t("approved"),
6611
+ text: () => r.data.is_pending ? t("pending") : t("approved"),
6587
6612
  adminOnly: true
6588
6613
  });
6589
6614
  pendingBtn.appendTo(r.$actions);
@@ -6593,7 +6618,7 @@ function renderModerator(r) {
6593
6618
  }
6594
6619
  function renderPin(r) {
6595
6620
  const pinnedBtn = new ActionBtn({
6596
- text: () => r.data.is_pinned ? r.ctx.$t("unpin") : r.ctx.$t("pin"),
6621
+ text: () => r.data.is_pinned ? t("unpin") : t("pin"),
6597
6622
  adminOnly: true
6598
6623
  });
6599
6624
  pinnedBtn.appendTo(r.$actions);
@@ -6603,19 +6628,19 @@ function renderPin(r) {
6603
6628
  }
6604
6629
  function renderEdit(r) {
6605
6630
  const editBtn = new ActionBtn({
6606
- text: r.ctx.$t("edit"),
6631
+ text: t("edit"),
6607
6632
  adminOnly: true
6608
6633
  });
6609
6634
  editBtn.appendTo(r.$actions);
6610
6635
  editBtn.setClick(() => {
6611
- r.ctx.editComment(r.data, r.$el);
6636
+ r.opts.editComment(r.data, r.$el);
6612
6637
  });
6613
6638
  }
6614
6639
  function renderDel(r) {
6615
6640
  const delBtn = new ActionBtn({
6616
- text: r.ctx.$t("delete"),
6641
+ text: t("delete"),
6617
6642
  confirm: true,
6618
- confirmText: r.ctx.$t("deleteConfirm"),
6643
+ confirmText: t("deleteConfirm"),
6619
6644
  adminOnly: true
6620
6645
  });
6621
6646
  delBtn.appendTo(r.$actions);
@@ -6657,18 +6682,21 @@ class Render {
6657
6682
  __publicField(this, "$replyAt");
6658
6683
  this.comment = comment;
6659
6684
  }
6660
- get ctx() {
6661
- return this.comment.ctx;
6662
- }
6663
6685
  get data() {
6664
6686
  return this.comment.getData();
6665
6687
  }
6666
- get conf() {
6667
- return this.comment.conf;
6668
- }
6669
- get cConf() {
6670
- return this.comment.getConf();
6688
+ get opts() {
6689
+ return this.comment.getOpts();
6671
6690
  }
6691
+ /**
6692
+ * Render the comment ui
6693
+ *
6694
+ * If comment data is updated, call this method to re-render the comment ui.
6695
+ * The method will be called multiple times, so it should be idempotent.
6696
+ *
6697
+ * Renders may add event listeners to the comment ui, so it should be called only once or override the original.
6698
+ * Please be aware of the memory leak caused by the event listener.
6699
+ */
6672
6700
  render() {
6673
6701
  this.$el = createElement(CommentHTML);
6674
6702
  this.$main = this.$el.querySelector(".atk-main");
@@ -6678,12 +6706,14 @@ class Render {
6678
6706
  this.$actions = this.$el.querySelector(".atk-actions");
6679
6707
  this.$el.setAttribute("id", `atk-comment-${this.data.id}`);
6680
6708
  loadRenders(this);
6681
- this.recoveryChildrenWrap();
6709
+ if (this.$childrenWrap) {
6710
+ this.$main.append(this.$childrenWrap);
6711
+ }
6682
6712
  return this.$el;
6683
6713
  }
6684
6714
  /** 内容限高检测 */
6685
6715
  checkHeightLimit() {
6686
- const conf = this.ctx.conf.heightLimit;
6716
+ const conf = this.opts.heightLimit;
6687
6717
  if (!conf || !conf.content || !conf.children)
6688
6718
  return;
6689
6719
  const contentMaxH = conf.content;
@@ -6717,24 +6747,21 @@ class Render {
6717
6747
  playFadeAnimForBody() {
6718
6748
  playFadeInAnim(this.comment.getRender().$body);
6719
6749
  }
6750
+ /** Perform the flash animation */
6751
+ playFlashAnim() {
6752
+ this.$el.classList.remove("atk-flash-once");
6753
+ window.setTimeout(() => {
6754
+ this.$el.classList.add("atk-flash-once");
6755
+ }, 150);
6756
+ }
6720
6757
  /** 获取子评论 Wrap */
6721
6758
  getChildrenWrap() {
6722
- return this.$childrenWrap;
6723
- }
6724
- /** 初始化子评论区域 Wrap */
6725
- renderChildrenWrap() {
6726
6759
  if (!this.$childrenWrap) {
6727
6760
  this.$childrenWrap = createElement('<div class="atk-comment-children"></div>');
6728
6761
  this.$main.append(this.$childrenWrap);
6729
6762
  }
6730
6763
  return this.$childrenWrap;
6731
6764
  }
6732
- /** 恢复原有的子评论区域 Wrap */
6733
- recoveryChildrenWrap() {
6734
- if (this.$childrenWrap) {
6735
- this.$main.append(this.$childrenWrap);
6736
- }
6737
- }
6738
6765
  /** 设置已读 */
6739
6766
  setUnread(val) {
6740
6767
  if (val)
@@ -6753,17 +6780,15 @@ class Render {
6753
6780
  setOpenURL(url) {
6754
6781
  this.setOpenable(true);
6755
6782
  this.$el.onclick = (evt) => {
6756
- evt.preventDefault();
6783
+ evt.stopPropagation();
6757
6784
  window.open(url);
6758
- if (this.cConf.openEvt)
6759
- this.cConf.openEvt();
6760
6785
  };
6761
6786
  }
6762
6787
  /** 设置点击评论时的操作 */
6763
6788
  setOpenAction(action) {
6764
6789
  this.setOpenable(true);
6765
6790
  this.$el.onclick = (evt) => {
6766
- evt.preventDefault();
6791
+ evt.stopPropagation();
6767
6792
  action();
6768
6793
  };
6769
6794
  }
@@ -6773,26 +6798,26 @@ class CommentActions {
6773
6798
  __publicField(this, "comment");
6774
6799
  this.comment = comment;
6775
6800
  }
6776
- get ctx() {
6777
- return this.comment.ctx;
6778
- }
6779
6801
  get data() {
6780
6802
  return this.comment.getData();
6781
6803
  }
6782
- get cConf() {
6783
- return this.comment.getConf();
6804
+ get opts() {
6805
+ return this.comment.getOpts();
6806
+ }
6807
+ getApi() {
6808
+ return this.comment.getOpts().getApi();
6784
6809
  }
6785
6810
  /** 投票操作 */
6786
6811
  vote(type) {
6787
6812
  const actionBtn = type === "up" ? this.comment.getRender().voteBtnUp : this.comment.getRender().voteBtnDown;
6788
- this.ctx.getApi().votes.vote(`comment_${type}`, this.data.id, __spreadValues({}, this.ctx.getApi().getUserFields())).then((res) => {
6813
+ this.getApi().votes.vote(`comment_${type}`, this.data.id, __spreadValues({}, this.getApi().getUserFields())).then((res) => {
6789
6814
  var _a, _b;
6790
6815
  this.data.vote_up = res.data.up;
6791
6816
  this.data.vote_down = res.data.down;
6792
6817
  (_a = this.comment.getRender().voteBtnUp) == null ? void 0 : _a.updateText();
6793
6818
  (_b = this.comment.getRender().voteBtnDown) == null ? void 0 : _b.updateText();
6794
6819
  }).catch((err) => {
6795
- actionBtn == null ? void 0 : actionBtn.setError(this.ctx.$t("voteFail"));
6820
+ actionBtn == null ? void 0 : actionBtn.setError(t("voteFail"));
6796
6821
  console.log(err);
6797
6822
  });
6798
6823
  }
@@ -6800,7 +6825,7 @@ class CommentActions {
6800
6825
  adminEdit(type, btnElem) {
6801
6826
  if (btnElem.isLoading)
6802
6827
  return;
6803
- btnElem.setLoading(true, `${this.ctx.$t("editing")}...`);
6828
+ btnElem.setLoading(true, `${t("editing")}...`);
6804
6829
  const modify = __spreadValues({}, this.data);
6805
6830
  if (type === "collapsed") {
6806
6831
  modify.is_collapsed = !modify.is_collapsed;
@@ -6809,26 +6834,26 @@ class CommentActions {
6809
6834
  } else if (type === "pinned") {
6810
6835
  modify.is_pinned = !modify.is_pinned;
6811
6836
  }
6812
- this.ctx.getApi().comments.updateComment(this.data.id, __spreadValues({}, modify)).then((res) => {
6837
+ this.getApi().comments.updateComment(this.data.id, __spreadValues({}, modify)).then((res) => {
6813
6838
  btnElem.setLoading(false);
6814
6839
  this.comment.setData(res.data);
6815
6840
  }).catch((err) => {
6816
6841
  console.error(err);
6817
- btnElem.setError(this.ctx.$t("editFail"));
6842
+ btnElem.setError(t("editFail"));
6818
6843
  });
6819
6844
  }
6820
6845
  /** 管理员 - 评论删除 */
6821
6846
  adminDelete(btnElem) {
6822
6847
  if (btnElem.isLoading)
6823
6848
  return;
6824
- btnElem.setLoading(true, `${this.ctx.$t("deleting")}...`);
6825
- this.ctx.getApi().comments.deleteComment(this.data.id).then(() => {
6849
+ btnElem.setLoading(true, `${t("deleting")}...`);
6850
+ this.getApi().comments.deleteComment(this.data.id).then(() => {
6826
6851
  btnElem.setLoading(false);
6827
- if (this.cConf.onDelete)
6828
- this.cConf.onDelete(this.comment);
6852
+ if (this.opts.onDelete)
6853
+ this.opts.onDelete(this.comment);
6829
6854
  }).catch((e) => {
6830
6855
  console.error(e);
6831
- btnElem.setError(this.ctx.$t("deleteFail"));
6856
+ btnElem.setError(t("deleteFail"));
6832
6857
  });
6833
6858
  }
6834
6859
  /** 快速跳转到该评论 */
@@ -6840,21 +6865,18 @@ class CommentActions {
6840
6865
  window.dispatchEvent(new Event("hashchange"));
6841
6866
  }
6842
6867
  }
6843
- class CommentNode extends Component {
6844
- // 最大嵌套层数
6845
- constructor(ctx, data, conf) {
6846
- super(ctx);
6868
+ class CommentNode {
6869
+ // 当前嵌套层数
6870
+ constructor(data, opts) {
6871
+ __publicField(this, "$el");
6847
6872
  __publicField(this, "renderInstance");
6848
6873
  __publicField(this, "actionInstance");
6849
6874
  __publicField(this, "data");
6850
- __publicField(this, "cConf");
6875
+ __publicField(this, "opts");
6851
6876
  __publicField(this, "parent");
6852
6877
  __publicField(this, "children", []);
6853
6878
  __publicField(this, "nestCurt");
6854
- // 当前嵌套层数
6855
- __publicField(this, "nestMax");
6856
- this.nestMax = ctx.conf.nestMax || 3;
6857
- this.cConf = conf;
6879
+ this.opts = opts;
6858
6880
  this.data = __spreadValues({}, data);
6859
6881
  this.data.date = this.data.date.replace(/-/g, "/");
6860
6882
  this.parent = null;
@@ -6868,8 +6890,8 @@ class CommentNode extends Component {
6868
6890
  if (this.$el)
6869
6891
  this.$el.replaceWith(newEl);
6870
6892
  this.$el = newEl;
6871
- if (this.cConf.afterRender)
6872
- this.cConf.afterRender();
6893
+ if (this.opts.onAfterRender)
6894
+ this.opts.onAfterRender();
6873
6895
  }
6874
6896
  /** 获取评论操作实例对象 */
6875
6897
  getActions() {
@@ -6910,51 +6932,76 @@ class CommentNode extends Component {
6910
6932
  return this.data.id;
6911
6933
  }
6912
6934
  /** 置入子评论 */
6913
- putChild(childC, insertMode = "append") {
6914
- childC.parent = this;
6915
- childC.nestCurt = this.nestCurt + 1;
6916
- this.children.push(childC);
6917
- const $children = this.getChildrenEl();
6935
+ putChild(childNode, insertMode = "append") {
6936
+ childNode.parent = this;
6937
+ childNode.nestCurt = this.nestCurt + 1;
6938
+ this.children.push(childNode);
6939
+ const $childrenWrap = this.getChildrenWrapEl();
6940
+ const $childComment = childNode.getEl();
6918
6941
  if (insertMode === "append")
6919
- $children.append(childC.getEl());
6942
+ $childrenWrap.append($childComment);
6920
6943
  else if (insertMode === "prepend")
6921
- $children.prepend(childC.getEl());
6922
- childC.getRender().playFadeAnim();
6923
- childC.getRender().checkHeightLimit();
6944
+ $childrenWrap.prepend($childComment);
6945
+ childNode.getRender().playFadeAnim();
6946
+ childNode.getRender().checkHeightLimit();
6924
6947
  }
6925
6948
  /** 获取存放子评论的元素对象 */
6926
- getChildrenEl() {
6927
- let $children = this.getRender().getChildrenWrap();
6928
- if (!$children) {
6929
- if (this.nestCurt < this.nestMax) {
6930
- $children = this.getRender().renderChildrenWrap();
6931
- } else {
6932
- $children = this.parent.getChildrenEl();
6933
- }
6949
+ getChildrenWrapEl() {
6950
+ if (this.nestCurt >= this.opts.nestMax) {
6951
+ return this.parent.getChildrenWrapEl();
6934
6952
  }
6935
- return $children;
6953
+ return this.getRender().getChildrenWrap();
6936
6954
  }
6937
6955
  /** 获取所有父评论 */
6938
6956
  getParents() {
6939
- const parents = [];
6940
- const once = (c) => {
6941
- if (c.parent) {
6942
- parents.push(c.parent);
6943
- once(c.parent);
6944
- }
6945
- };
6946
- once(this);
6947
- return parents;
6957
+ const flattenParents = [];
6958
+ let parent = this.parent;
6959
+ while (parent) {
6960
+ flattenParents.push(parent);
6961
+ parent = parent.getParent();
6962
+ }
6963
+ return flattenParents;
6948
6964
  }
6949
- /** 获取评论元素对象 */
6965
+ /**
6966
+ * Get the element of the comment
6967
+ *
6968
+ * The `getEl()` will always return the latest $el after calling `render()`.
6969
+ * Please be aware of the memory leak if you use the $el reference directly.
6970
+ */
6950
6971
  getEl() {
6972
+ if (!this.$el)
6973
+ throw new Error("comment element not initialized before `getEl()`");
6951
6974
  return this.$el;
6952
6975
  }
6976
+ /**
6977
+ * Focus on the comment
6978
+ *
6979
+ * Scroll to the comment and perform flash animation
6980
+ */
6981
+ focus() {
6982
+ if (!this.$el)
6983
+ throw new Error("comment element not initialized before `focus()`");
6984
+ this.getParents().forEach((p) => {
6985
+ p.getRender().heightLimitRemoveForChildren();
6986
+ });
6987
+ this.scrollIntoView();
6988
+ this.getRender().playFlashAnim();
6989
+ }
6990
+ scrollIntoView() {
6991
+ this.$el && scrollIntoView(this.$el, false, this.opts.scrollRelativeTo && this.opts.scrollRelativeTo());
6992
+ }
6993
+ /**
6994
+ * Remove the comment node
6995
+ */
6996
+ remove() {
6997
+ var _a;
6998
+ (_a = this.$el) == null ? void 0 : _a.remove();
6999
+ }
6953
7000
  /** 获取 Gravatar 头像 URL */
6954
7001
  getGravatarURL() {
6955
7002
  return getGravatarURL({
6956
- mirror: this.ctx.conf.gravatar.mirror,
6957
- params: this.ctx.conf.gravatar.params,
7003
+ mirror: this.opts.gravatar.mirror,
7004
+ params: this.opts.gravatar.params,
6958
7005
  emailMD5: this.data.email_encrypted
6959
7006
  });
6960
7007
  }
@@ -6964,7 +7011,7 @@ class CommentNode extends Component {
6964
7011
  }
6965
7012
  /** 获取格式化后的日期 */
6966
7013
  getDateFormatted() {
6967
- return timeAgo(new Date(this.data.date), this.ctx.$t);
7014
+ return timeAgo(new Date(this.data.date), t);
6968
7015
  }
6969
7016
  /** 获取用户 UserAgent 信息 */
6970
7017
  getUserUA() {
@@ -6975,21 +7022,33 @@ class CommentNode extends Component {
6975
7022
  };
6976
7023
  }
6977
7024
  /** 获取配置 */
6978
- getConf() {
6979
- return this.cConf;
7025
+ getOpts() {
7026
+ return this.opts;
6980
7027
  }
6981
7028
  }
6982
- function createComment(ctx, comment, ctxComments) {
6983
- var _a;
6984
- const instance2 = new CommentNode(ctx, comment, {
6985
- isFlatMode: (_a = ctx.getData().getListLastFetch()) == null ? void 0 : _a.params.flatMode,
6986
- afterRender: () => {
7029
+ function createCommentNode(ctx, comment, replyComment, opts) {
7030
+ const instance2 = new CommentNode(comment, {
7031
+ onAfterRender: () => {
6987
7032
  ctx.trigger("comment-rendered", instance2);
6988
7033
  },
6989
7034
  onDelete: (c) => {
6990
7035
  ctx.getData().deleteComment(c.getID());
6991
7036
  },
6992
- replyTo: comment.rid ? ctxComments.find((c) => c.id === comment.rid) : void 0
7037
+ replyTo: replyComment,
7038
+ // TODO simplify reference
7039
+ flatMode: typeof (opts == null ? void 0 : opts.forceFlatMode) === "boolean" ? opts == null ? void 0 : opts.forceFlatMode : ctx.conf.flatMode,
7040
+ gravatar: ctx.conf.gravatar,
7041
+ nestMax: ctx.conf.nestMax,
7042
+ heightLimit: ctx.conf.heightLimit,
7043
+ avatarURLBuilder: ctx.conf.avatarURLBuilder,
7044
+ scrollRelativeTo: ctx.conf.scrollRelativeTo,
7045
+ vote: ctx.conf.vote,
7046
+ voteDown: ctx.conf.voteDown,
7047
+ uaBadge: ctx.conf.uaBadge,
7048
+ // TODO: move to plugin folder and remove from core
7049
+ getApi: () => ctx.getApi(),
7050
+ replyComment: (c, $el) => ctx.replyComment(c, $el),
7051
+ editComment: (c, $el) => ctx.editComment(c, $el)
6993
7052
  });
6994
7053
  instance2.render();
6995
7054
  return instance2;
@@ -7365,20 +7424,19 @@ class List extends Component {
7365
7424
  getCommentNodes() {
7366
7425
  return this.commentNodes;
7367
7426
  }
7368
- getListLayout() {
7427
+ getListLayout({ forceFlatMode } = {}) {
7369
7428
  return new ListLayout({
7370
7429
  $commentsWrap: this.$commentsWrap,
7371
7430
  nestSortBy: this.ctx.conf.nestSort,
7372
7431
  nestMax: this.ctx.conf.nestMax,
7373
- flatMode: this.ctx.conf.flatMode,
7432
+ flatMode: typeof forceFlatMode === "boolean" ? forceFlatMode : this.ctx.conf.flatMode,
7374
7433
  // flatMode must be boolean because it had been handled when Artalk.init
7375
- createCommentNode: (d, c) => {
7376
- const node = createComment(this.ctx, d, c);
7434
+ createCommentNode: (d, r) => {
7435
+ const node = createCommentNode(this.ctx, d, r, { forceFlatMode });
7377
7436
  this.commentNodes.push(node);
7378
7437
  return node;
7379
7438
  },
7380
- findCommentNode: (id) => this.commentNodes.find((c) => c.getID() === id),
7381
- getCommentDataList: () => this.ctx.getData().getComments()
7439
+ findCommentNode: (id) => this.commentNodes.find((c) => c.getID() === id)
7382
7440
  });
7383
7441
  }
7384
7442
  initCrudEvents() {
@@ -7386,8 +7444,10 @@ class List extends Component {
7386
7444
  this.getListLayout().import(comments);
7387
7445
  });
7388
7446
  this.ctx.on("list-loaded", (comments) => {
7389
- if (comments.length === 0)
7447
+ if (comments.length === 0) {
7448
+ this.commentNodes = [];
7390
7449
  this.$commentsWrap.innerHTML = "";
7450
+ }
7391
7451
  });
7392
7452
  this.ctx.on("comment-inserted", (comment) => {
7393
7453
  this.getListLayout().insert(comment);
@@ -7398,7 +7458,7 @@ class List extends Component {
7398
7458
  console.error(`comment node id=${comment.id} not found`);
7399
7459
  return;
7400
7460
  }
7401
- node.getEl().remove();
7461
+ node.remove();
7402
7462
  this.commentNodes = this.commentNodes.filter((c) => c.getID() !== comment.id);
7403
7463
  });
7404
7464
  this.ctx.on("comment-updated", (comment) => {
@@ -7915,15 +7975,16 @@ class StateReply extends EditorPlug {
7915
7975
  }
7916
7976
  setReply(commentData) {
7917
7977
  const ui = this.kit.useUI();
7918
- if (!ui.$sendReply) {
7919
- ui.$sendReply = createElement(
7920
- `<div class="atk-send-reply">${t("reply")} <span class="atk-text"></span><span class="atk-cancel">×</span></div>`
7978
+ if (!ui.$sendReplyBtn) {
7979
+ const $btn = createElement(
7980
+ `<span class="atk-state-btn"><span class="atk-text-wrap">${t("reply")} <span class="atk-text"></span></span><span class="atk-cancel">×</span></span>`
7921
7981
  );
7922
- ui.$sendReply.querySelector(".atk-text").innerText = `@${commentData.nick}`;
7923
- ui.$sendReply.addEventListener("click", () => {
7982
+ $btn.querySelector(".atk-text").innerText = `@${commentData.nick}`;
7983
+ $btn.addEventListener("click", () => {
7924
7984
  this.kit.useEditor().resetState();
7925
7985
  });
7926
- ui.$textareaWrap.append(ui.$sendReply);
7986
+ ui.$stateWrap.append($btn);
7987
+ ui.$sendReplyBtn = $btn;
7927
7988
  }
7928
7989
  this.comment = commentData;
7929
7990
  ui.$textarea.focus();
@@ -7932,9 +7993,9 @@ class StateReply extends EditorPlug {
7932
7993
  if (!this.comment)
7933
7994
  return;
7934
7995
  const ui = this.kit.useUI();
7935
- if (ui.$sendReply) {
7936
- ui.$sendReply.remove();
7937
- ui.$sendReply = void 0;
7996
+ if (ui.$sendReplyBtn) {
7997
+ ui.$sendReplyBtn.remove();
7998
+ ui.$sendReplyBtn = void 0;
7938
7999
  }
7939
8000
  this.comment = void 0;
7940
8001
  }
@@ -7981,12 +8042,12 @@ class StateEdit extends EditorPlug {
7981
8042
  const ui = this.kit.useUI();
7982
8043
  if (!ui.$editCancelBtn) {
7983
8044
  const $btn = createElement(
7984
- `<div class="atk-send-reply">${t("editCancel")} <span class="atk-cancel">×</span></div>`
8045
+ `<span class="atk-state-btn"><span class="atk-text-wrap">${t("editCancel")}</span><span class="atk-cancel">×</span></span>`
7985
8046
  );
7986
8047
  $btn.onclick = () => {
7987
8048
  this.kit.useEditor().resetState();
7988
8049
  };
7989
- ui.$textareaWrap.append($btn);
8050
+ ui.$stateWrap.append($btn);
7990
8051
  ui.$editCancelBtn = $btn;
7991
8052
  }
7992
8053
  this.comment = comment;
@@ -8183,7 +8244,7 @@ class Emoticons extends EditorPlug {
8183
8244
  __publicField(this, "isImgLoaded", false);
8184
8245
  this.kit.useMounted(() => {
8185
8246
  this.usePanel(`<div class="atk-editor-plug-emoticons"></div>`);
8186
- this.useBtn(t("emoticon"));
8247
+ this.useBtn(`<i aria-label="${t("emoticon")}"><svg fill="currentColor" aria-hidden="true" height="14" viewBox="0 0 14 14" width="14"><path d="m4.26829 5.29294c0-.94317.45893-1.7074 1.02439-1.7074.56547 0 1.02439.76423 1.02439 1.7074s-.45892 1.7074-1.02439 1.7074c-.56546 0-1.02439-.76423-1.02439-1.7074zm4.43903 1.7074c.56546 0 1.02439-.76423 1.02439-1.7074s-.45893-1.7074-1.02439-1.7074c-.56547 0-1.02439.76423-1.02439 1.7074s.45892 1.7074 1.02439 1.7074zm-1.70732 2.73184c-1.51883 0-2.06312-1.52095-2.08361-1.58173l-1.29551.43231c.03414.10244.868 2.51604 3.3798 2.51604 2.51181 0 3.34502-2.41291 3.37982-2.51604l-1.29484-.43573c-.02254.06488-.56683 1.58583-2.08498 1.58583zm7-2.73252c0 3.86004-3.1401 7.00034-7 7.00034s-7-3.1396-7-6.99966c0-3.86009 3.1401-7.00034 7-7.00034s7 3.14025 7 7.00034zm-1.3659 0c0-3.10679-2.5275-5.63442-5.6341-5.63442-3.10663 0-5.63415 2.52832-5.63415 5.6351 0 3.10676 2.52752 5.63446 5.63415 5.63446 3.1066 0 5.6341-2.5277 5.6341-5.63446z"/></svg></i>`);
8187
8248
  });
8188
8249
  this.kit.useUnmounted(() => {
8189
8250
  });
@@ -8434,7 +8495,7 @@ class Upload extends EditorPlug {
8434
8495
  this.$imgUploadInput.type = "file";
8435
8496
  this.$imgUploadInput.style.display = "none";
8436
8497
  this.$imgUploadInput.accept = AllowImgExts.map((o) => `.${o}`).join(",");
8437
- const $btn = this.useBtn(`${t("image")}`);
8498
+ const $btn = this.useBtn(`<i aria-label="${t("uploadImage")}"><svg fill="currentColor" aria-hidden="true" height="14" viewBox="0 0 14 14" width="14"><path d="m0 1.94444c0-1.074107.870333-1.94444 1.94444-1.94444h10.11116c1.0741 0 1.9444.870333 1.9444 1.94444v10.11116c0 1.0741-.8703 1.9444-1.9444 1.9444h-10.11116c-1.074107 0-1.94444-.8703-1.94444-1.9444zm1.94444-.38888c-.21466 0-.38888.17422-.38888.38888v7.06689l2.33333-2.33333 2.33333 2.33333 3.88888-3.88889 2.3333 2.33334v-5.51134c0-.21466-.1742-.38888-.3888-.38888zm10.49996 8.09977-2.3333-2.33333-3.88888 3.8889-2.33333-2.33334-2.33333 2.33334v.8447c0 .2146.17422.3888.38888.3888h10.11116c.2146 0 .3888-.1742.3888-.3888zm-7.1944-6.54422c-.75133 0-1.36111.60978-1.36111 1.36111 0 .75134.60978 1.36111 1.36111 1.36111s1.36111-.60977 1.36111-1.36111c0-.75133-.60978-1.36111-1.36111-1.36111z"/></svg></i>`);
8438
8499
  $btn.after(this.$imgUploadInput);
8439
8500
  $btn.onclick = () => {
8440
8501
  const $input = this.$imgUploadInput;
@@ -8538,10 +8599,7 @@ class Preview extends EditorPlug {
8538
8599
  __publicField(this, "isPlugPanelShow", false);
8539
8600
  this.kit.useMounted(() => {
8540
8601
  this.usePanel(`<div class="atk-editor-plug-preview"></div>`);
8541
- let btnText = t("preview");
8542
- if (getInstance())
8543
- btnText += ` <i title="Markdown is supported"><svg class="markdown" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path></svg></i>`;
8544
- this.useBtn(btnText);
8602
+ this.useBtn(`<i aria-label="${t("preview")}"><svg fill="currentColor" aria-hidden="true" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path></svg></i>`);
8545
8603
  });
8546
8604
  this.kit.useUnmounted(() => {
8547
8605
  });
@@ -8883,62 +8941,50 @@ const UnreadBadge = (ctx) => {
8883
8941
  showUnreadBadge(notifies.length || 0);
8884
8942
  });
8885
8943
  };
8886
- const Goto = (ctx) => {
8887
- let delayGoto = true;
8888
- const check2 = () => {
8944
+ const GotoDispatcher = (ctx) => {
8945
+ let lastID = 0;
8946
+ const check2 = ({ locker }) => {
8889
8947
  const commentID = extractCommentID();
8890
8948
  if (!commentID)
8891
8949
  return;
8950
+ if (locker && lastID === commentID)
8951
+ return;
8952
+ lastID = commentID;
8892
8953
  ctx.trigger("list-goto", commentID);
8893
- delayGoto = true;
8894
- };
8895
- const hashChangeHandler = () => {
8896
- delayGoto = false;
8897
- check2();
8898
8954
  };
8955
+ const hashChangeHandler = () => check2({ locker: false });
8956
+ const listLoadedHandler = () => check2({ locker: true });
8899
8957
  ctx.on("mounted", () => {
8900
8958
  window.addEventListener("hashchange", hashChangeHandler);
8901
- ctx.on("list-loaded", check2);
8959
+ ctx.on("list-loaded", listLoadedHandler);
8902
8960
  });
8903
8961
  ctx.on("unmounted", () => {
8904
8962
  window.removeEventListener("hashchange", hashChangeHandler);
8905
- ctx.off("list-loaded", check2);
8906
- });
8907
- let foundID = 0;
8908
- ctx.on("list-goto", (commentID) => {
8909
- if (foundID === commentID)
8910
- return;
8911
- const comment = ctx.get("list").getCommentNodes().find((c) => c.getID() === commentID);
8912
- if (!comment)
8913
- return;
8914
- foundID = commentID;
8915
- comment.getParents().forEach((p) => {
8916
- p.getRender().heightLimitRemoveForChildren();
8917
- });
8918
- const goTo = () => {
8919
- scrollIntoView(comment.getEl(), false);
8920
- comment.getEl().classList.remove("atk-flash-once");
8921
- window.setTimeout(() => {
8922
- comment.getEl().classList.add("atk-flash-once");
8923
- }, 150);
8924
- };
8925
- if (!delayGoto)
8926
- goTo();
8927
- else
8928
- window.setTimeout(() => goTo(), 350);
8963
+ ctx.off("list-loaded", listLoadedHandler);
8929
8964
  });
8930
8965
  };
8931
8966
  function extractCommentID() {
8932
- let commentId = Number(getQueryParam("atk_comment"));
8967
+ const match = window.location.hash.match(/#atk-comment-([0-9]+)/);
8968
+ let commentId = match && match[1] && !Number.isNaN(parseFloat(match[1])) ? parseFloat(match[1]) : null;
8933
8969
  if (!commentId) {
8934
- const match = window.location.hash.match(/#atk-comment-([0-9]+)/);
8935
- if (!match || !match[1] || Number.isNaN(Number(match[1])))
8936
- return null;
8937
- commentId = Number(match[1]);
8970
+ commentId = Number(getQueryParam("atk_comment"));
8938
8971
  }
8939
8972
  return commentId || null;
8940
8973
  }
8941
- const version = "2.8.0";
8974
+ const GotoFocus = (ctx) => {
8975
+ ctx.on("list-goto", (commentID) => __async(void 0, null, function* () {
8976
+ let comment = ctx.getCommentNodes().find((c) => c.getID() === commentID);
8977
+ if (!comment) {
8978
+ const data = (yield ctx.getApi().comments.getComment(commentID)).data;
8979
+ ctx.get("list").getListLayout({ forceFlatMode: true }).insert(data.comment, data.reply_comment);
8980
+ comment = ctx.getCommentNodes().find((c) => c.getID() === commentID);
8981
+ }
8982
+ if (!comment)
8983
+ return;
8984
+ comment.focus();
8985
+ }));
8986
+ };
8987
+ const version = "2.8.2";
8942
8988
  const Copyright = (ctx) => {
8943
8989
  ctx.on("mounted", () => {
8944
8990
  const list2 = ctx.get("list");
@@ -8949,14 +8995,7 @@ const Copyright = (ctx) => {
8949
8995
  });
8950
8996
  };
8951
8997
  const NoComment = (ctx) => {
8952
- let isFetching = false;
8953
- ctx.on("list-fetch", (params) => {
8954
- isFetching = true;
8955
- });
8956
8998
  ctx.on("list-loaded", (comments) => {
8957
- if (!isFetching)
8958
- return;
8959
- isFetching = false;
8960
8999
  const list2 = ctx.get("list");
8961
9000
  const isNoComment = comments.length <= 0;
8962
9001
  let $noComment = list2.getCommentsWrapEl().querySelector(".atk-list-no-comment");
@@ -9208,7 +9247,8 @@ const ListPlugins = [
9208
9247
  SidebarBtn,
9209
9248
  UnreadBadge,
9210
9249
  Dropdown,
9211
- Goto,
9250
+ GotoDispatcher,
9251
+ GotoFocus,
9212
9252
  NoComment,
9213
9253
  Copyright,
9214
9254
  TimeTicking,
@@ -9232,6 +9272,7 @@ const PvCountWidget = (ctx) => {
9232
9272
  ctx.watchConf([
9233
9273
  "site",
9234
9274
  "pageKey",
9275
+ "pageTitle",
9235
9276
  "countEl",
9236
9277
  "pvEl"
9237
9278
  ], (conf) => {
@@ -9239,6 +9280,7 @@ const PvCountWidget = (ctx) => {
9239
9280
  getApi: () => ctx.getApi(),
9240
9281
  siteName: conf.site,
9241
9282
  pageKey: conf.pageKey,
9283
+ pageTitle: conf.pageTitle,
9242
9284
  countEl: conf.countEl,
9243
9285
  pvEl: conf.pvEl,
9244
9286
  pvAdd: typeof ctx.conf.pvAdd === "boolean" ? ctx.conf.pvAdd : true
@@ -9250,10 +9292,10 @@ function initCountWidget(opt) {
9250
9292
  if (opt.countEl && document.querySelector(opt.countEl)) {
9251
9293
  refreshStatCount(opt, { query: "page_comment", numEl: opt.countEl });
9252
9294
  }
9253
- const initialData = opt.pvAdd ? {
9295
+ const initialData = opt.pvAdd && opt.pageKey ? {
9254
9296
  [opt.pageKey]: (yield opt.getApi().pages.logPv({
9255
9297
  page_key: opt.pageKey,
9256
- page_title: opt.pageKey,
9298
+ page_title: opt.pageTitle,
9257
9299
  site_name: opt.siteName
9258
9300
  })).data.pv
9259
9301
  // pv+1 and get pv count
@@ -9270,7 +9312,7 @@ function initCountWidget(opt) {
9270
9312
  function refreshStatCount(opt, args) {
9271
9313
  return __async(this, null, function* () {
9272
9314
  let data = args.data || {};
9273
- let queryPageKeys = Array.from(document.querySelectorAll(args.numEl)).map((e) => e.getAttribute("data-page-key") || opt.pageKey).filter((k) => typeof data[k] !== "number");
9315
+ let queryPageKeys = Array.from(document.querySelectorAll(args.numEl)).map((e) => e.getAttribute("data-page-key") || opt.pageKey).filter((k) => k && typeof data[k] !== "number");
9274
9316
  queryPageKeys = [...new Set(queryPageKeys)];
9275
9317
  if (queryPageKeys.length > 0) {
9276
9318
  const res = (yield opt.getApi().stats.getStats(args.query, {
@@ -9279,7 +9321,8 @@ function refreshStatCount(opt, args) {
9279
9321
  })).data.data;
9280
9322
  data = __spreadValues(__spreadValues({}, data), res);
9281
9323
  }
9282
- applyCountData(args.numEl, data, data[opt.pageKey]);
9324
+ const defaultCount = opt.pageKey ? data[opt.pageKey] : 0;
9325
+ applyCountData(args.numEl, data, defaultCount);
9283
9326
  });
9284
9327
  }
9285
9328
  function applyCountData(selector, data, defaultCount) {
@@ -9468,7 +9511,6 @@ class Artalk {
9468
9511
  initCountWidget({
9469
9512
  getApi: () => new Api2(convertApiOptions(conf)),
9470
9513
  siteName: conf.site,
9471
- pageKey: conf.pageKey,
9472
9514
  countEl: conf.countEl,
9473
9515
  pvEl: conf.pvEl,
9474
9516
  pvAdd: false