@xpadev-net/niconicomments 0.2.56 → 0.2.58

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/bundle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- niconicomments.js v0.2.56
2
+ niconicomments.js v0.2.58
3
3
  (c) 2021 xpadev-net https://xpadev.net
4
4
  Released under the MIT License.
5
5
  */
@@ -48,6 +48,11 @@
48
48
  setPlugins: setPlugins
49
49
  });
50
50
 
51
+ let isDebug = false;
52
+ const setIsDebug = (val) => {
53
+ isDebug = val;
54
+ };
55
+
51
56
  let defaultConfig;
52
57
  const updateConfig = (config) => {
53
58
  defaultConfig = config;
@@ -147,7 +152,7 @@
147
152
  };
148
153
 
149
154
  const hex2rgb = (hex) => {
150
- if (hex.slice(0, 1) === "#")
155
+ if (hex.startsWith("#"))
151
156
  hex = hex.slice(1);
152
157
  if (hex.length === 3)
153
158
  hex =
@@ -162,7 +167,7 @@
162
167
  });
163
168
  };
164
169
  const hex2rgba = (hex) => {
165
- if (hex.slice(0, 1) === "#")
170
+ if (hex.startsWith("#"))
166
171
  hex = hex.slice(1);
167
172
  if (hex.length === 4)
168
173
  hex =
@@ -252,6 +257,7 @@
252
257
  "mail",
253
258
  "user_id",
254
259
  "layer",
260
+ "is_my_post",
255
261
  ]),
256
262
  comments: (i) => {
257
263
  if (typeof i !== "object")
@@ -390,27 +396,30 @@
390
396
  },
391
397
  nicoScript: {
392
398
  range: {
393
- target: (i) => typeof i === "string" && !!i.match(/^(?:\u6295?\u30b3\u30e1|\u5168)$/),
399
+ target: (i) => typeof i === "string" &&
400
+ !!RegExp(/^(?:\u6295?\u30b3\u30e1|\u5168)$/).exec(i),
394
401
  },
395
402
  replace: {
396
- range: (i) => typeof i === "string" && !!i.match(/^[\u5358\u5168]$/),
403
+ range: (i) => typeof i === "string" && !!RegExp(/^[\u5358\u5168]$/).exec(i),
397
404
  target: (i) => typeof i === "string" &&
398
- !!i.match(/^(?:\u30b3\u30e1|\u6295\u30b3\u30e1|\u5168|\u542b\u3080|\u542b\u307e\u306a\u3044)$/),
405
+ !!RegExp(/^(?:\u30b3\u30e1|\u6295\u30b3\u30e1|\u5168|\u542b\u3080|\u542b\u307e\u306a\u3044)$/).exec(i),
399
406
  condition: (i) => typeof i === "string" &&
400
- !!i.match(/^(?:\u90e8\u5206\u4e00\u81f4|\u5b8c\u5168\u4e00\u81f4)$/),
407
+ !!RegExp(/^(?:\u90e8\u5206\u4e00\u81f4|\u5b8c\u5168\u4e00\u81f4)$/).exec(i),
401
408
  },
402
409
  },
403
410
  comment: {
404
- font: (i) => typeof i === "string" && !!i.match(/^(?:gothic|mincho|defont)$/),
405
- loc: (i) => typeof i === "string" && !!i.match(/^(?:ue|naka|shita)$/),
406
- size: (i) => typeof i === "string" && !!i.match(/^(?:big|medium|small)$/),
411
+ font: (i) => typeof i === "string" && !!RegExp(/^(?:gothic|mincho|defont)$/).exec(i),
412
+ loc: (i) => typeof i === "string" && !!RegExp(/^(?:ue|naka|shita)$/).exec(i),
413
+ size: (i) => typeof i === "string" && !!RegExp(/^(?:big|medium|small)$/).exec(i),
407
414
  command: {
408
- key: (i) => typeof i === "string" && !!i.match(/^(?:full|ender|_live|invisible)$/),
415
+ key: (i) => typeof i === "string" &&
416
+ !!RegExp(/^(?:full|ender|_live|invisible)$/).exec(i),
409
417
  },
410
418
  color: (i) => typeof i === "string" && Object.keys(colors).includes(i),
411
- colorCode: (i) => typeof i === "string" && !!i.match(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/),
419
+ colorCode: (i) => typeof i === "string" &&
420
+ !!RegExp(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/).exec(i),
412
421
  colorCodeAllowAlpha: (i) => typeof i === "string" &&
413
- !!i.match(/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/),
422
+ !!RegExp(/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/).exec(i),
414
423
  },
415
424
  config: {
416
425
  initOptions: (item) => {
@@ -429,7 +438,7 @@
429
438
  scale: isNumber,
430
439
  config: isObject,
431
440
  format: (i) => typeof i === "string" &&
432
- !!i.match(/^(XMLDocument|niconicome|formatted|legacy|legacyOwner|owner|v1|default|empty)$/),
441
+ !!RegExp(/^(XMLDocument|niconicome|formatted|legacy|legacyOwner|owner|v1|default|empty)$/).exec(i),
433
442
  video: (i) => typeof i === "object" && i.nodeName === "VIDEO",
434
443
  };
435
444
  for (const key of Object.keys(keys)) {
@@ -546,12 +555,12 @@
546
555
  processNicoscript(comment, commands);
547
556
  const defaultCommand = getDefaultCommand(comment.vpos);
548
557
  applyNicoScriptReplace(comment, commands);
549
- const size = commands.size || defaultCommand.size || "medium";
558
+ const size = commands.size ?? defaultCommand.size ?? "medium";
550
559
  return {
551
560
  size: size,
552
- loc: commands.loc || defaultCommand.loc || "naka",
553
- color: commands.color || defaultCommand.color || "#FFFFFF",
554
- font: commands.font || defaultCommand.font || "defont",
561
+ loc: commands.loc ?? defaultCommand.loc ?? "naka",
562
+ color: commands.color ?? defaultCommand.color ?? "#FFFFFF",
563
+ font: commands.font ?? defaultCommand.font ?? "defont",
555
564
  fontSize: getConfig(config.fontSize, isFlash)[size].default,
556
565
  long: commands.long ? Math.floor(Number(commands.long) * 100) : 300,
557
566
  flash: isFlash,
@@ -562,26 +571,27 @@
562
571
  strokeColor: commands.strokeColor,
563
572
  wakuColor: commands.wakuColor,
564
573
  fillColor: commands.fillColor,
574
+ button: commands.button,
565
575
  };
566
576
  };
567
577
  const parseBrackets = (input) => {
568
578
  const content = input.split(""), result = [];
569
579
  let quote = "", last_i = "", string = "";
570
580
  for (const i of content) {
571
- if (i.match(/["'\u300c]/) && quote === "") {
581
+ if (RegExp(/^["'\u300c]$/).exec(i) && quote === "") {
572
582
  quote = i;
573
583
  }
574
- else if (i.match(/["']/) && quote === i && last_i !== "\\") {
584
+ else if (RegExp(/^["']$/).exec(i) && quote === i && last_i !== "\\") {
575
585
  result.push(string.replaceAll("\\n", "\n"));
576
586
  quote = "";
577
587
  string = "";
578
588
  }
579
- else if (i.match(/\u300d/) && quote === "\u300c") {
589
+ else if (i === "\u300d" && quote === "\u300c") {
580
590
  result.push(string);
581
591
  quote = "";
582
592
  string = "";
583
593
  }
584
- else if (quote === "" && i.match(/\s+/)) {
594
+ else if (quote === "" && RegExp(/^\s+$/).exec(i)) {
585
595
  if (string) {
586
596
  result.push(string);
587
597
  string = "";
@@ -609,10 +619,10 @@
609
619
  start: comment.vpos,
610
620
  long: commands.long === undefined ? undefined : Math.floor(commands.long * 100),
611
621
  keyword: result[0],
612
- replace: result[1] || "",
613
- range: result[2] || "\u5358",
614
- target: result[3] || "\u30b3\u30e1",
615
- condition: result[4] || "\u90e8\u5206\u4e00\u81f4",
622
+ replace: result[1] ?? "",
623
+ range: result[2] ?? "\u5358",
624
+ target: result[3] ?? "\u30b3\u30e1",
625
+ condition: result[4] ?? "\u90e8\u5206\u4e00\u81f4",
616
626
  color: commands.color,
617
627
  size: commands.size,
618
628
  font: commands.font,
@@ -635,11 +645,11 @@
635
645
  });
636
646
  };
637
647
  const processNicoscript = (comment, commands) => {
638
- const nicoscript = comment.content.match(/^[@\uff20](\u30c7\u30d5\u30a9\u30eb\u30c8|\u7f6e\u63db|\u9006|\u30b3\u30e1\u30f3\u30c8\u7981\u6b62|\u30b7\u30fc\u30af\u7981\u6b62|\u30b8\u30e3\u30f3\u30d7|\u30dc\u30bf\u30f3)(?:\s(.+))?/);
648
+ const nicoscript = RegExp(/^[@\uff20](\u30c7\u30d5\u30a9\u30eb\u30c8|\u7f6e\u63db|\u9006|\u30b3\u30e1\u30f3\u30c8\u7981\u6b62|\u30b7\u30fc\u30af\u7981\u6b62|\u30b8\u30e3\u30f3\u30d7|\u30dc\u30bf\u30f3)(?:\s(.+))?/).exec(comment.content);
639
649
  if (!nicoscript)
640
650
  return;
641
651
  if (nicoscript[1] === "\u30dc\u30bf\u30f3" && nicoscript[2]) {
642
- processAtButton(commands);
652
+ processAtButton(comment, commands);
643
653
  return;
644
654
  }
645
655
  if (!comment.owner)
@@ -680,8 +690,8 @@
680
690
  });
681
691
  };
682
692
  const processReverseScript = (comment, commands) => {
683
- const reverse = comment.content.match(/^[@\uff20]\u9006(?:\s+)?(\u5168|\u30b3\u30e1|\u6295\u30b3\u30e1)?/);
684
- if (!reverse || !reverse[1] || !typeGuard.nicoScript.range.target(reverse[1]))
693
+ const reverse = RegExp(/^[@\uff20]\u9006(?:\s+)?(\u5168|\u30b3\u30e1|\u6295\u30b3\u30e1)?/).exec(comment.content);
694
+ if (!reverse?.[1] || !typeGuard.nicoScript.range.target(reverse[1]))
685
695
  return;
686
696
  if (commands.long === undefined) {
687
697
  commands.long = 30;
@@ -711,8 +721,8 @@
711
721
  });
712
722
  };
713
723
  const processJumpScript$1 = (comment, commands, input) => {
714
- const options = input.match(/\s*((?:sm|so|nm|\uff53\uff4d|\uff53\uff4f|\uff4e\uff4d)?[1-9\uff11-\uff19][0-9\uff11-\uff19]*|#[0-9]+:[0-9]+(?:\.[0-9]+)?)\s+(.*)/);
715
- if (!options || !options[1])
724
+ const options = RegExp(/\s*((?:sm|so|nm|\uff53\uff4d|\uff53\uff4f|\uff4e\uff4d)?[1-9\uff11-\uff19][0-9\uff11-\uff19]*|#[0-9]+:[0-9]+(?:\.[0-9]+)?)\s+(.*)/).exec(input);
725
+ if (!options?.[1])
716
726
  return;
717
727
  nicoScripts.jump.unshift({
718
728
  start: comment.vpos,
@@ -721,9 +731,26 @@
721
731
  message: options[2],
722
732
  });
723
733
  };
724
- const processAtButton = (commands) => {
734
+ const processAtButton = (comment, commands) => {
735
+ const args = parseBrackets(comment.content);
736
+ if (args[1] === undefined)
737
+ return;
725
738
  commands.invisible = false;
726
- commands.button = true;
739
+ const content = RegExp(/^(?:(?<before>.*?)\[)?(?<body>.*?)(?:\](?<after>[^\]]*?))?$/su).exec(args[1]);
740
+ const message = {
741
+ before: content.groups?.before ?? "",
742
+ body: content.groups?.body ?? "",
743
+ after: content.groups?.after ?? "",
744
+ };
745
+ commands.button = {
746
+ message,
747
+ commentMessage: args[2] ?? `${message.before}${message.body}${message.after}`,
748
+ commentVisible: args[3] !== "\u975e\u8868\u793a",
749
+ commentMail: args[4]?.split(",") ?? [],
750
+ limit: Number(args[5] ?? 1),
751
+ local: comment.mail.includes("local"),
752
+ hidden: comment.mail.includes("hidden"),
753
+ };
727
754
  };
728
755
  const parseCommands = (comment) => {
729
756
  const commands = comment.mail, isFlash = isFlashComment(comment);
@@ -740,7 +767,6 @@
740
767
  _live: false,
741
768
  invisible: false,
742
769
  long: undefined,
743
- button: false,
744
770
  };
745
771
  for (const command of commands) {
746
772
  parseCommand(comment, command, result, isFlash);
@@ -752,22 +778,22 @@
752
778
  };
753
779
  const parseCommand = (comment, _command, result, isFlash) => {
754
780
  const command = _command.toLowerCase();
755
- const long = command.match(/^[@\uff20]([0-9.]+)/);
781
+ const long = RegExp(/^[@\uff20]([0-9.]+)/).exec(command);
756
782
  if (long) {
757
783
  result.long = Number(long[1]);
758
784
  return;
759
785
  }
760
- const strokeColor = getColor(command.match(/^nico:stroke:(.+)$/));
786
+ const strokeColor = getColor(RegExp(/^nico:stroke:(.+)$/).exec(command));
761
787
  if (strokeColor) {
762
788
  result.strokeColor ??= strokeColor;
763
789
  return;
764
790
  }
765
- const rectColor = getColor(command.match(/^nico:waku:(.+)$/));
791
+ const rectColor = getColor(RegExp(/^nico:waku:(.+)$/).exec(command));
766
792
  if (rectColor) {
767
793
  result.wakuColor ??= rectColor;
768
794
  return;
769
795
  }
770
- const fillColor = getColor(command.match(/^nico:fill:(.+)$/));
796
+ const fillColor = getColor(RegExp(/^nico:fill:(.+)$/).exec(command));
771
797
  if (fillColor) {
772
798
  result.fillColor ??= fillColor;
773
799
  return;
@@ -785,7 +811,7 @@
785
811
  result.color ??= config.colors[command];
786
812
  return;
787
813
  }
788
- const colorCode = command.match(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/);
814
+ const colorCode = RegExp(/^#(?:[0-9a-z]{3}|[0-9a-z]{6})$/).exec(command);
789
815
  if (colorCode && comment.premium) {
790
816
  result.color ??= colorCode[0].toUpperCase();
791
817
  return;
@@ -970,7 +996,7 @@
970
996
  const changeCALayer = (rawData) => {
971
997
  const userScoreList = getUsersScore(rawData);
972
998
  const filteredComments = removeDuplicateCommentArt(rawData);
973
- const commentArts = filteredComments.filter((comment) => (userScoreList[comment.user_id] || 0) >= config.sameCAMinScore &&
999
+ const commentArts = filteredComments.filter((comment) => (userScoreList[comment.user_id] ?? 0) >= config.sameCAMinScore &&
974
1000
  !comment.owner);
975
1001
  const commentArtsGroupedByUser = groupCommentsByUser(commentArts);
976
1002
  const commentArtsGroupedByTimes = groupCommentsByTime(commentArtsGroupedByUser);
@@ -989,7 +1015,7 @@
989
1015
  comment.mail.includes("full")) {
990
1016
  userScoreList[comment.user_id] += 5;
991
1017
  }
992
- const lineCount = (comment.content.match(/\r\n|\n|\r/g) || []).length;
1018
+ const lineCount = (comment.content.match(/\r\n|\n|\r/g) ?? []).length;
993
1019
  if (lineCount > 2) {
994
1020
  userScoreList[comment.user_id] += lineCount / 2;
995
1021
  }
@@ -1001,7 +1027,7 @@
1001
1027
  return comments.filter((comment) => {
1002
1028
  const key = `${comment.content}@@${[...comment.mail]
1003
1029
  .sort()
1004
- .filter((e) => !e.match(/@[\d.]+|184|device:.+|patissier|ca/))
1030
+ .filter((e) => !RegExp(/@[\d.]+|184|device:.+|patissier|ca/).exec(e))
1005
1031
  .join("")}`, lastComment = index[key];
1006
1032
  if (lastComment === undefined) {
1007
1033
  index[key] = comment;
@@ -1121,11 +1147,11 @@
1121
1147
  };
1122
1148
  const parseContent = (content) => {
1123
1149
  const results = [];
1124
- const lines = (content.match(/\n|[^\n]+/g) || []).map((val) => Array.from(val.match(/[ -~。-゚]+|[^ -~。-゚]+/g) || []));
1150
+ const lines = Array.from(content.match(/\n|[^\n]+/g) ?? []);
1125
1151
  for (const line of lines) {
1126
1152
  const lineContent = parseLine(line);
1127
1153
  const firstContent = lineContent[0];
1128
- if (firstContent && firstContent.font) {
1154
+ if (firstContent?.font) {
1129
1155
  results.push(...lineContent.map((val) => {
1130
1156
  if (!val.font) {
1131
1157
  val.font = firstContent.font;
@@ -1140,8 +1166,9 @@
1140
1166
  return results;
1141
1167
  };
1142
1168
  const parseLine = (line) => {
1169
+ const parts = Array.from(line.match(/[ -~。-゚]+|[^ -~。-゚]+/g) ?? []);
1143
1170
  const lineContent = [];
1144
- for (const part of line) {
1171
+ for (const part of parts) {
1145
1172
  if (part.match(/[ -~。-゚]+/g) !== null) {
1146
1173
  lineContent.push({ content: part, slicedContent: part.split("\n") });
1147
1174
  continue;
@@ -1222,6 +1249,108 @@
1222
1249
  font: getFlashFontName(secondVal.font),
1223
1250
  });
1224
1251
  };
1252
+ const getButtonParts = (comment) => {
1253
+ let leftParts = undefined;
1254
+ const parts = [];
1255
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
1256
+ const lineOffset = comment.lineOffset;
1257
+ const lineHeight = comment.fontSize * comment.lineHeight;
1258
+ const offsetKey = comment.resizedY ? "resized" : "default";
1259
+ const offsetY = config.commentYPaddingTop[offsetKey] +
1260
+ comment.fontSize *
1261
+ comment.lineHeight *
1262
+ config.commentYOffset[comment.size][offsetKey];
1263
+ let leftOffset = 0, lineCount = 0, isLastButton = false;
1264
+ for (const item of comment.content) {
1265
+ const lines = item.slicedContent;
1266
+ for (let j = 0, n = lines.length; j < n; j++) {
1267
+ const line = lines[j];
1268
+ if (line === undefined)
1269
+ continue;
1270
+ const posY = (lineOffset + lineCount + 1) * lineHeight + offsetY;
1271
+ const partWidth = item.width[j] ?? 0;
1272
+ if (comment.button && !comment.button.hidden) {
1273
+ if (!isLastButton && item.isButton) {
1274
+ leftParts = {
1275
+ type: "left",
1276
+ left: leftOffset + atButtonPadding,
1277
+ top: posY - lineHeight + atButtonPadding,
1278
+ width: partWidth + atButtonPadding,
1279
+ height: lineHeight,
1280
+ };
1281
+ leftOffset += atButtonPadding * 2;
1282
+ }
1283
+ else if (isLastButton && item.isButton) {
1284
+ parts.push({
1285
+ type: "middle",
1286
+ left: leftOffset,
1287
+ top: posY - lineHeight + atButtonPadding,
1288
+ width: partWidth,
1289
+ height: lineHeight,
1290
+ });
1291
+ }
1292
+ else if (isLastButton && !item.isButton) {
1293
+ if (leftParts) {
1294
+ comment.buttonObjects = {
1295
+ left: leftParts,
1296
+ middle: parts,
1297
+ right: {
1298
+ type: "right",
1299
+ right: leftOffset + atButtonPadding,
1300
+ top: posY - lineHeight + atButtonPadding,
1301
+ height: lineHeight,
1302
+ },
1303
+ };
1304
+ }
1305
+ return comment;
1306
+ }
1307
+ }
1308
+ if (j < n - 1) {
1309
+ leftOffset = 0;
1310
+ lineCount += 1;
1311
+ continue;
1312
+ }
1313
+ leftOffset += partWidth;
1314
+ }
1315
+ isLastButton = !!item.isButton;
1316
+ }
1317
+ if (comment.button && !comment.button.hidden && isLastButton && leftParts) {
1318
+ const posY = (lineOffset + lineCount + 1) * lineHeight + offsetY;
1319
+ comment.buttonObjects = {
1320
+ left: leftParts,
1321
+ middle: parts,
1322
+ right: {
1323
+ type: "right",
1324
+ right: leftOffset + atButtonPadding,
1325
+ top: posY - lineHeight + atButtonPadding,
1326
+ height: lineHeight,
1327
+ },
1328
+ };
1329
+ }
1330
+ return comment;
1331
+ };
1332
+ const buildAtButtonComment = (comment, vpos) => {
1333
+ if (!comment.button || comment.button.limit <= 0)
1334
+ return;
1335
+ comment.button.limit -= 1;
1336
+ const mail = [...comment.button.commentMail];
1337
+ if (!comment.button.commentVisible) {
1338
+ mail.push("invisible");
1339
+ }
1340
+ return {
1341
+ id: -1,
1342
+ vpos,
1343
+ content: comment.button.commentMessage,
1344
+ date: -1,
1345
+ date_usec: -1,
1346
+ owner: false,
1347
+ premium: true,
1348
+ mail,
1349
+ user_id: -10,
1350
+ layer: -1,
1351
+ is_my_post: true,
1352
+ };
1353
+ };
1225
1354
 
1226
1355
  class TypeGuardError extends Error {
1227
1356
  constructor(options = {}) {
@@ -1257,7 +1386,7 @@
1257
1386
  let currentWidth = 0;
1258
1387
  for (const item of comment.content) {
1259
1388
  const lines = item.content.split("\n");
1260
- context.font = parseFont(item.font || comment.font, fontSize);
1389
+ context.font = parseFont(item.font ?? comment.font, fontSize);
1261
1390
  const width = [];
1262
1391
  for (let j = 0, n = lines.length; j < n; j++) {
1263
1392
  const line = lines[j];
@@ -1300,7 +1429,9 @@
1300
1429
  __proto__: null,
1301
1430
  ArrayEqual: ArrayEqual,
1302
1431
  ArrayPush: ArrayPush,
1432
+ buildAtButtonComment: buildAtButtonComment,
1303
1433
  changeCALayer: changeCALayer,
1434
+ getButtonParts: getButtonParts,
1304
1435
  getCharSize: getCharSize,
1305
1436
  getConfig: getConfig,
1306
1437
  getDefaultCommand: getDefaultCommand,
@@ -1343,12 +1474,15 @@
1343
1474
  context;
1344
1475
  cacheKey;
1345
1476
  comment;
1477
+ pos;
1346
1478
  posY;
1347
1479
  pluginName = "BaseComment";
1348
1480
  image;
1481
+ buttonImage;
1349
1482
  constructor(comment, context) {
1350
1483
  this.context = context;
1351
1484
  this.posY = 0;
1485
+ this.pos = { x: 0, y: 0 };
1352
1486
  comment.content = comment.content.replace(/\t/g, "\u2003\u2003");
1353
1487
  this.comment = this.convertComment(comment);
1354
1488
  this.cacheKey =
@@ -1412,7 +1546,7 @@
1412
1546
  console.error("convertComment method is not implemented", comment);
1413
1547
  throw new NotImplementedError(this.pluginName, "convertComment");
1414
1548
  }
1415
- draw(vpos, showCollision, debug) {
1549
+ draw(vpos, showCollision, cursor) {
1416
1550
  if (isBanActive(vpos))
1417
1551
  return;
1418
1552
  const reverse = isReverseActive(vpos, this.comment.owner);
@@ -1420,13 +1554,17 @@
1420
1554
  const posY = this.comment.loc === "shita"
1421
1555
  ? config.canvasHeight - this.posY - this.comment.height
1422
1556
  : this.posY;
1557
+ this.pos = {
1558
+ x: posX,
1559
+ y: posY,
1560
+ };
1423
1561
  this._drawBackgroundColor(posX, posY);
1424
- this._draw(posX, posY);
1562
+ this._draw(posX, posY, cursor);
1425
1563
  this._drawRectColor(posX, posY);
1426
1564
  this._drawCollision(posX, posY, showCollision);
1427
- this._drawDebugInfo(posX, posY, debug);
1565
+ this._drawDebugInfo(posX, posY);
1428
1566
  }
1429
- _draw(posX, posY) {
1567
+ _draw(posX, posY, cursor) {
1430
1568
  if (this.image === undefined) {
1431
1569
  this.image = this.getTextImage();
1432
1570
  }
@@ -1438,6 +1576,10 @@
1438
1576
  else {
1439
1577
  this.context.globalAlpha = 1;
1440
1578
  }
1579
+ if (this.comment.button && !this.comment.button.hidden) {
1580
+ const button = this.getButtonImage(posX, posY, cursor);
1581
+ button && drawImage(this.context, button, posX, posY);
1582
+ }
1441
1583
  drawImage(this.context, this.image, posX, posY);
1442
1584
  this.context.restore();
1443
1585
  }
@@ -1458,8 +1600,8 @@
1458
1600
  this.context.restore();
1459
1601
  }
1460
1602
  }
1461
- _drawDebugInfo(posX, posY, debug) {
1462
- if (debug) {
1603
+ _drawDebugInfo(posX, posY) {
1604
+ if (isDebug) {
1463
1605
  this.context.save();
1464
1606
  const font = this.context.font;
1465
1607
  const fillStyle = this.context.fillStyle;
@@ -1523,14 +1665,60 @@
1523
1665
  context,
1524
1666
  };
1525
1667
  }
1668
+ getButtonImage(posX, posY, cursor) {
1669
+ console.error("getButtonImage method is not implemented", posX, posY, cursor);
1670
+ throw new NotImplementedError(this.pluginName, "getButtonImage");
1671
+ }
1672
+ isHovered(cursor, posX, posY) {
1673
+ console.error("isHovered method is not implemented", posX, posY, cursor);
1674
+ throw new NotImplementedError(this.pluginName, "getButtonImage");
1675
+ }
1526
1676
  }
1527
1677
 
1678
+ const drawLeftBorder = (context, left, top, width, height, radius) => {
1679
+ context.save();
1680
+ context.beginPath();
1681
+ context.moveTo(left + width, top);
1682
+ context.lineTo(left + radius, top);
1683
+ context.quadraticCurveTo(left, top, left, top + radius);
1684
+ context.lineTo(left, top + height - radius);
1685
+ context.quadraticCurveTo(left, top + height, left + radius, top + height);
1686
+ context.lineTo(left + width, top + height);
1687
+ context.stroke();
1688
+ context.restore();
1689
+ };
1690
+ const drawMiddleBorder = (context, left, top, width, height) => {
1691
+ context.save();
1692
+ context.beginPath();
1693
+ context.moveTo(left + width, top);
1694
+ context.lineTo(left, top);
1695
+ context.moveTo(left + width, top + height);
1696
+ context.lineTo(left, top + height);
1697
+ context.stroke();
1698
+ context.restore();
1699
+ };
1700
+ const drawRightBorder = (context, right, top, height, radius) => {
1701
+ context.save();
1702
+ context.beginPath();
1703
+ context.moveTo(right - radius, top);
1704
+ context.quadraticCurveTo(right, top, right, top + radius);
1705
+ context.lineTo(right, top + height - radius);
1706
+ context.quadraticCurveTo(right, top + height, right - radius, top + height);
1707
+ context.stroke();
1708
+ context.restore();
1709
+ };
1710
+
1528
1711
  class FlashComment extends BaseComment {
1529
1712
  _globalScale;
1530
1713
  pluginName = "FlashComment";
1714
+ buttonImage;
1715
+ buttonContext;
1531
1716
  constructor(comment, context) {
1532
1717
  super(comment, context);
1533
1718
  this._globalScale ??= getConfig(config.commentScale, true);
1719
+ const button = this.createCanvas();
1720
+ this.buttonImage = button.image;
1721
+ this.buttonContext = button.context;
1534
1722
  }
1535
1723
  get content() {
1536
1724
  return this.comment.rawContent;
@@ -1545,7 +1733,7 @@
1545
1733
  lineOffset,
1546
1734
  };
1547
1735
  const val = content[0];
1548
- if (val && val.font) {
1736
+ if (val?.font) {
1549
1737
  comment.font = val.font;
1550
1738
  }
1551
1739
  this.comment = this.getCommentSize(comment);
@@ -1557,7 +1745,7 @@
1557
1745
  }
1558
1746
  convertComment(comment) {
1559
1747
  this._globalScale = getConfig(config.commentScale, true);
1560
- return this.getCommentSize(this.parseCommandAndNicoscript(comment));
1748
+ return getButtonParts(this.getCommentSize(this.parseCommandAndNicoscript(comment)));
1561
1749
  }
1562
1750
  getCommentSize(parsedData) {
1563
1751
  if (parsedData.invisible) {
@@ -1584,6 +1772,9 @@
1584
1772
  measure.width *= options.scale;
1585
1773
  }
1586
1774
  this.context.restore();
1775
+ if (parsedData.button && !parsedData.button.hidden) {
1776
+ measure.width += getConfig(config.atButtonPadding, true) * 4;
1777
+ }
1587
1778
  return {
1588
1779
  ...parsedData,
1589
1780
  height: measure.height * this._globalScale,
@@ -1601,9 +1792,9 @@
1601
1792
  }
1602
1793
  parseCommandAndNicoscript(comment) {
1603
1794
  const data = parseCommandAndNicoScript(comment);
1604
- const { content, lineCount, lineOffset } = this.parseContent(comment.content);
1795
+ const { content, lineCount, lineOffset } = this.parseContent(comment.content, data.button);
1605
1796
  const val = content[0];
1606
- if (val && val.font) {
1797
+ if (val?.font) {
1607
1798
  data.font = val.font;
1608
1799
  }
1609
1800
  return {
@@ -1615,16 +1806,23 @@
1615
1806
  lineOffset,
1616
1807
  };
1617
1808
  }
1618
- parseContent(input) {
1619
- const content = parseContent(input);
1620
- const lineCount = content.reduce((pv, val) => {
1621
- return pv + (val.content.match(/\n/g)?.length || 0);
1622
- }, 1);
1623
- const lineOffset = (input.match(new RegExp(config.FlashScriptChar.super, "g"))?.length ||
1809
+ parseContent(input, button) {
1810
+ const content = button
1811
+ ? [
1812
+ ...parseContent(button.message.before),
1813
+ ...parseContent(button.message.body).map((val) => {
1814
+ val.isButton = true;
1815
+ return val;
1816
+ }),
1817
+ ...parseContent(button.message.after),
1818
+ ]
1819
+ : parseContent(input);
1820
+ const lineCount = (input.match(/\n/g)?.length ?? 0) + 1;
1821
+ const lineOffset = (input.match(new RegExp(config.FlashScriptChar.super, "g"))?.length ??
1624
1822
  0) *
1625
1823
  -1 *
1626
1824
  config.scriptCharOffset +
1627
- (input.match(new RegExp(config.FlashScriptChar.sub, "g"))?.length || 0) *
1825
+ (input.match(new RegExp(config.FlashScriptChar.sub, "g"))?.length ?? 0) *
1628
1826
  config.scriptCharOffset;
1629
1827
  return {
1630
1828
  content,
@@ -1694,7 +1892,7 @@
1694
1892
  for (const item of comment.content) {
1695
1893
  const lines = item.content.split("\n");
1696
1894
  const widths = [];
1697
- this.context.font = parseFont(item.font || comment.font, comment.fontSize);
1895
+ this.context.font = parseFont(item.font ?? comment.font, comment.fontSize);
1698
1896
  for (let i = 0, n = lines.length; i < n; i++) {
1699
1897
  const value = lines[i];
1700
1898
  if (value === undefined)
@@ -1739,27 +1937,19 @@
1739
1937
  }
1740
1938
  _generateTextImage() {
1741
1939
  const { image, context } = this.createCanvas();
1742
- image.width = this.comment.width;
1743
- image.height = this.comment.height;
1744
- context.strokeStyle = getStrokeColor(this.comment);
1745
- context.fillStyle = this.comment.color;
1746
- context.textAlign = "start";
1747
- context.textBaseline = "alphabetic";
1748
- context.lineWidth = 4;
1749
- context.font = parseFont(this.comment.font, this.comment.fontSize);
1750
- const scale = this._globalScale *
1751
- this.comment.scale *
1752
- (this.comment.layer === -1 ? options.scale : 1);
1753
- context.scale(scale * this.comment.scaleX, scale);
1940
+ this._setupCanvas(image, context);
1941
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
1754
1942
  const lineOffset = this.comment.lineOffset;
1943
+ const lineHeight = this.comment.fontSize * this.comment.lineHeight;
1755
1944
  const offsetKey = this.comment.resizedY ? "resized" : "default";
1756
1945
  const offsetY = config.commentYPaddingTop[offsetKey] +
1757
1946
  this.comment.fontSize *
1758
1947
  this.comment.lineHeight *
1759
1948
  config.commentYOffset[this.comment.size][offsetKey];
1760
- let lastFont = this.comment.font, leftOffset = 0, lineCount = 0;
1949
+ let lastFont = this.comment.font, leftOffset = 0, lineCount = 0, isLastButton = false;
1950
+ console.log(this.comment);
1761
1951
  for (const item of this.comment.content) {
1762
- const font = item.font || this.comment.font;
1952
+ const font = item.font ?? this.comment.font;
1763
1953
  if (lastFont !== font) {
1764
1954
  lastFont = font;
1765
1955
  context.font = parseFont(font, this.comment.fontSize);
@@ -1769,9 +1959,16 @@
1769
1959
  const line = lines[j];
1770
1960
  if (line === undefined)
1771
1961
  continue;
1772
- const posY = (lineOffset + lineCount + 1) *
1773
- (this.comment.fontSize * this.comment.lineHeight) +
1774
- offsetY;
1962
+ const posY = (lineOffset + lineCount + 1) * lineHeight + offsetY;
1963
+ const partWidth = item.width[j] ?? 0;
1964
+ if (this.comment.button && !this.comment.button.hidden) {
1965
+ if (!isLastButton && item.isButton) {
1966
+ leftOffset += atButtonPadding * 2;
1967
+ }
1968
+ else if (isLastButton && !item.isButton) {
1969
+ leftOffset += atButtonPadding * 2;
1970
+ }
1971
+ }
1775
1972
  context.strokeText(line, leftOffset, posY);
1776
1973
  context.fillText(line, leftOffset, posY);
1777
1974
  if (j < n - 1) {
@@ -1779,11 +1976,79 @@
1779
1976
  lineCount += 1;
1780
1977
  continue;
1781
1978
  }
1782
- leftOffset += item.width[j] || 0;
1979
+ leftOffset += partWidth;
1783
1980
  }
1981
+ isLastButton = !!item.isButton;
1784
1982
  }
1785
1983
  return image;
1786
1984
  }
1985
+ getButtonImage(posX, posY, cursor) {
1986
+ if (!this.comment.button || this.comment.button.hidden)
1987
+ return undefined;
1988
+ const { image, context } = this._setupCanvas(this.buttonImage, this.buttonContext);
1989
+ const parts = this.comment.buttonObjects;
1990
+ if (!parts)
1991
+ return undefined;
1992
+ const atButtonRadius = getConfig(config.atButtonRadius, true);
1993
+ const isHover = this.isHovered(cursor, posX, posY);
1994
+ context.save();
1995
+ context.strokeStyle = isHover ? this.comment.color : "white";
1996
+ drawLeftBorder(context, parts.left.left, parts.left.top, parts.left.width, parts.left.height, atButtonRadius);
1997
+ for (const part of parts.middle) {
1998
+ drawMiddleBorder(context, part.left, part.top, part.width, part.height);
1999
+ }
2000
+ drawRightBorder(context, parts.right.right, parts.right.top, parts.right.height, atButtonRadius);
2001
+ context.restore();
2002
+ return image;
2003
+ }
2004
+ isHovered(_cursor, _posX, _posY) {
2005
+ if (!_cursor || !this.comment.buttonObjects)
2006
+ return false;
2007
+ const { left, middle, right } = this.comment.buttonObjects;
2008
+ const scale = this._globalScale *
2009
+ this.comment.scale *
2010
+ (this.comment.layer === -1 ? options.scale : 1);
2011
+ const posX = (_posX ?? this.pos.x) / scale;
2012
+ const posY = (_posY ?? this.pos.y) / scale;
2013
+ const cursor = {
2014
+ x: _cursor.x / scale,
2015
+ y: _cursor.y / scale,
2016
+ };
2017
+ if (cursor.x < posX ||
2018
+ posX + this.comment.width < cursor.x ||
2019
+ cursor.y < posY + left.top ||
2020
+ posY + right.top + right.height < cursor.y) {
2021
+ return false;
2022
+ }
2023
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
2024
+ const between = (val, min, max) => {
2025
+ return min < val && val < max;
2026
+ };
2027
+ for (const part of [left, ...middle]) {
2028
+ if (between(cursor.x, posX + part.left, posX + part.left + part.width) &&
2029
+ between(cursor.y, posY + part.top, posY + part.top + part.height)) {
2030
+ return true;
2031
+ }
2032
+ }
2033
+ return (between(cursor.x, posX + right.right - atButtonPadding, posX + right.right + atButtonPadding * 2) && between(cursor.y, posY + right.top, posY + right.top + right.height));
2034
+ }
2035
+ _setupCanvas(image, context) {
2036
+ const atButtonPadding = getConfig(config.atButtonPadding, true);
2037
+ image.width = this.comment.width;
2038
+ image.height =
2039
+ this.comment.height + (this.comment.button ? atButtonPadding * 2 : 0);
2040
+ context.strokeStyle = getStrokeColor(this.comment);
2041
+ context.fillStyle = this.comment.color;
2042
+ context.textAlign = "start";
2043
+ context.textBaseline = "alphabetic";
2044
+ context.lineWidth = 4;
2045
+ context.font = parseFont(this.comment.font, this.comment.fontSize);
2046
+ const scale = this._globalScale *
2047
+ this.comment.scale *
2048
+ (this.comment.layer === -1 ? options.scale : 1);
2049
+ context.scale(scale * this.comment.scaleX, scale);
2050
+ return { image, context };
2051
+ }
1787
2052
  }
1788
2053
 
1789
2054
  class HTML5Comment extends BaseComment {
@@ -1875,7 +2140,7 @@
1875
2140
  slicedContent: input.split("\n"),
1876
2141
  });
1877
2142
  const lineCount = content.reduce((pv, val) => {
1878
- return pv + (val.content.match(/\n/g)?.length || 0);
2143
+ return pv + (val.content.match(/\n/g)?.length ?? 0);
1879
2144
  }, 1);
1880
2145
  const lineOffset = 0;
1881
2146
  return {
@@ -2044,6 +2309,12 @@
2044
2309
  }
2045
2310
  return image;
2046
2311
  }
2312
+ getButtonImage() {
2313
+ return undefined;
2314
+ }
2315
+ isHovered() {
2316
+ return false;
2317
+ }
2047
2318
  }
2048
2319
 
2049
2320
  var index = /*#__PURE__*/Object.freeze({
@@ -2199,17 +2470,17 @@
2199
2470
 
2200
2471
  const initConfig = () => {
2201
2472
  const platform = (function (ua) {
2202
- if (ua.match(/windows nt 6\.[12]/i))
2473
+ if (RegExp(/windows nt 6\.[12]/i).exec(ua))
2203
2474
  return "win7";
2204
- else if (ua.match(/windows nt (6\.3|10\.\d+)|win32/i))
2475
+ else if (RegExp(/windows nt (6\.3|10\.\d+)|win32/i).exec(ua))
2205
2476
  return "win8_1";
2206
- else if (ua.match(/windows nt/i))
2477
+ else if (RegExp(/windows nt/i).exec(ua))
2207
2478
  return "win";
2208
- else if (ua.match(/mac os x 10(.|_)(9|10)/i))
2479
+ else if (RegExp(/mac os x 10(.|_)(9|10)/i).exec(ua))
2209
2480
  return "mac10_9";
2210
- else if (ua.match(/mac os x 10(.|_)\d{2}|darwin/i))
2481
+ else if (RegExp(/mac os x 10(.|_)\d{2}|darwin/i).exec(ua))
2211
2482
  return "mac10_11";
2212
- else if (ua.match(/mac os x/i))
2483
+ else if (RegExp(/mac os x/i).exec(ua))
2213
2484
  return "mac";
2214
2485
  return "other";
2215
2486
  })(typeof navigator !== "undefined" ? navigator.userAgent : process.platform);
@@ -2365,6 +2636,8 @@
2365
2636
  },
2366
2637
  ],
2367
2638
  nakaCommentSpeedOffset: 0.95,
2639
+ atButtonPadding: 5,
2640
+ atButtonRadius: 7,
2368
2641
  };
2369
2642
  updateConfig(defaultConfig);
2370
2643
  };
@@ -2520,14 +2793,15 @@
2520
2793
  mail: [],
2521
2794
  user_id: -1,
2522
2795
  layer: -1,
2796
+ is_my_post: false,
2523
2797
  };
2524
2798
  if (item.getAttribute("mail")) {
2525
- tmpParam.mail = item.getAttribute("mail")?.split(/\s+/g) || [];
2799
+ tmpParam.mail = item.getAttribute("mail")?.split(/\s+/g) ?? [];
2526
2800
  }
2527
2801
  if (tmpParam.content.startsWith("/") && tmpParam.owner) {
2528
2802
  tmpParam.mail.push("invisible");
2529
2803
  }
2530
- const userId = item.getAttribute("user_id") || "";
2804
+ const userId = item.getAttribute("user_id") ?? "";
2531
2805
  const isUserExist = userList.indexOf(userId);
2532
2806
  if (isUserExist === -1) {
2533
2807
  tmpParam.user_id = userList.length;
@@ -2542,11 +2816,12 @@
2542
2816
  };
2543
2817
  const fromFormatted = (data) => {
2544
2818
  return data.map((comment) => {
2545
- if (typeGuard.formatted.legacyComment(comment)) {
2819
+ if (!typeGuard.formatted.comment(comment)) {
2546
2820
  return {
2547
2821
  ...comment,
2548
2822
  layer: -1,
2549
2823
  user_id: 0,
2824
+ is_my_post: false,
2550
2825
  };
2551
2826
  }
2552
2827
  return comment;
@@ -2570,6 +2845,7 @@
2570
2845
  mail: [],
2571
2846
  user_id: -1,
2572
2847
  layer: -1,
2848
+ is_my_post: false,
2573
2849
  };
2574
2850
  if (value.mail) {
2575
2851
  tmpParam.mail = value.mail.split(/\s+/g);
@@ -2608,7 +2884,7 @@
2608
2884
  const tmpParam = {
2609
2885
  id: i,
2610
2886
  vpos: Number(commentData[0]) * 100,
2611
- content: commentData[2] || "",
2887
+ content: commentData[2] ?? "",
2612
2888
  date: i,
2613
2889
  date_usec: 0,
2614
2890
  owner: true,
@@ -2616,6 +2892,7 @@
2616
2892
  mail: [],
2617
2893
  user_id: -1,
2618
2894
  layer: -1,
2895
+ is_my_post: false,
2619
2896
  };
2620
2897
  if (commentData[1]) {
2621
2898
  tmpParam.mail = commentData[1].split(/[\s+]/g);
@@ -2644,6 +2921,7 @@
2644
2921
  mail: [],
2645
2922
  user_id: -1,
2646
2923
  layer: -1,
2924
+ is_my_post: false,
2647
2925
  };
2648
2926
  if (value.command) {
2649
2927
  tmpParam.mail = value.command.split(/\s+/g);
@@ -2671,6 +2949,7 @@
2671
2949
  mail: value.commands,
2672
2950
  user_id: -1,
2673
2951
  layer: -1,
2952
+ is_my_post: value.isMyPost,
2674
2953
  };
2675
2954
  if (tmpParam.content.startsWith("/") && tmpParam.owner) {
2676
2955
  tmpParam.mail.push("invisible");
@@ -2707,7 +2986,7 @@
2707
2986
  return data;
2708
2987
  };
2709
2988
  const time2vpos = (time_str) => {
2710
- const time = time_str.match(/^(?:(\d+):(\d+)\.(\d+)|(\d+):(\d+)|(\d+)\.(\d+)|(\d+))$/);
2989
+ const time = RegExp(/^(?:(\d+):(\d+)\.(\d+)|(\d+):(\d+)|(\d+)\.(\d+)|(\d+))$/).exec(time_str);
2711
2990
  if (time) {
2712
2991
  if (time[1] !== undefined &&
2713
2992
  time[2] !== undefined &&
@@ -2763,7 +3042,6 @@
2763
3042
  utils: index$1
2764
3043
  });
2765
3044
 
2766
- let isDebug = false;
2767
3045
  class NiconiComments {
2768
3046
  enableLegacyPiP;
2769
3047
  showCollision;
@@ -2789,7 +3067,7 @@
2789
3067
  throw new InvalidOptionError();
2790
3068
  setOptions(Object.assign(defaultOptions, initOptions));
2791
3069
  setConfig(Object.assign(defaultConfig, options.config));
2792
- isDebug = options.debug;
3070
+ setIsDebug(options.debug);
2793
3071
  resetImageCache();
2794
3072
  resetNicoScripts();
2795
3073
  this.canvas = canvas;
@@ -2811,7 +3089,7 @@
2811
3089
  options.mode = "html5";
2812
3090
  }
2813
3091
  const parsedData = convert2formattedComment(data, formatType);
2814
- this.video = options.video || undefined;
3092
+ this.video = options.video ?? undefined;
2815
3093
  this.showCollision = options.showCollision;
2816
3094
  this.showFPS = options.showFPS;
2817
3095
  this.showCommentCount = options.showCommentCount;
@@ -2832,7 +3110,7 @@
2832
3110
  if (options.keepCA) {
2833
3111
  rawData = changeCALayer(rawData);
2834
3112
  }
2835
- const instances = rawData.reduce((pv, val) => {
3113
+ let instances = rawData.reduce((pv, val) => {
2836
3114
  pv.push(createCommentInstance(val, this.context));
2837
3115
  return pv;
2838
3116
  }, []);
@@ -2842,10 +3120,14 @@
2842
3120
  for (const plugin of config.plugins) {
2843
3121
  try {
2844
3122
  const canvas = generateCanvas();
3123
+ const pluginInstance = new plugin(canvas, instances);
2845
3124
  plugins.push({
2846
3125
  canvas,
2847
- instance: new plugin(canvas, instances),
3126
+ instance: pluginInstance,
2848
3127
  });
3128
+ if (pluginInstance.transformComments) {
3129
+ instances = pluginInstance.transformComments(instances);
3130
+ }
2849
3131
  }
2850
3132
  catch (e) {
2851
3133
  console.error("Failed to init plugin");
@@ -2892,9 +3174,10 @@
2892
3174
  pv.push(createCommentInstance(val, this.context));
2893
3175
  return pv;
2894
3176
  }, []);
3177
+ console.log(comments);
2895
3178
  for (const plugin of plugins) {
2896
3179
  try {
2897
- plugin.instance.addComments(comments);
3180
+ plugin.instance.addComments?.(comments);
2898
3181
  }
2899
3182
  catch (e) {
2900
3183
  console.error("Failed to add comments");
@@ -2911,7 +3194,7 @@
2911
3194
  }
2912
3195
  }
2913
3196
  }
2914
- drawCanvas(vpos, forceRendering = false) {
3197
+ drawCanvas(vpos, forceRendering = false, cursor) {
2915
3198
  const drawCanvasStart = performance.now();
2916
3199
  if (this.lastVpos === vpos && !forceRendering)
2917
3200
  return false;
@@ -2922,7 +3205,7 @@
2922
3205
  timelineRange?.filter((item) => item.loc === "naka").length === 0 &&
2923
3206
  this.timeline[this.lastVpos]?.filter((item) => item.loc === "naka")
2924
3207
  ?.length === 0) {
2925
- const current = timelineRange.filter((item) => item.loc !== "naka"), last = this.timeline[this.lastVpos]?.filter((item) => item.loc !== "naka") ||
3208
+ const current = timelineRange.filter((item) => item.loc !== "naka"), last = this.timeline[this.lastVpos]?.filter((item) => item.loc !== "naka") ??
2926
3209
  [];
2927
3210
  if (ArrayEqual(current, last))
2928
3211
  return false;
@@ -2930,17 +3213,17 @@
2930
3213
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
2931
3214
  this.lastVpos = vpos;
2932
3215
  this._drawVideo();
2933
- this._drawCollision(vpos);
2934
- this._drawComments(timelineRange, vpos);
2935
3216
  for (const plugin of plugins) {
2936
3217
  try {
2937
- plugin.instance.draw(vpos);
3218
+ plugin.instance.draw?.(vpos);
2938
3219
  this.context.drawImage(plugin.canvas, 0, 0);
2939
3220
  }
2940
3221
  catch (e) {
2941
3222
  console.error(`Failed to draw comments`);
2942
3223
  }
2943
3224
  }
3225
+ this._drawCollision(vpos);
3226
+ this._drawComments(timelineRange, vpos, cursor);
2944
3227
  this._drawFPS(drawCanvasStart);
2945
3228
  this._drawCommentCount(timelineRange?.length);
2946
3229
  logger(`drawCanvas complete: ${performance.now() - drawCanvasStart}ms`);
@@ -2960,7 +3243,7 @@
2960
3243
  this.context.drawImage(this.video, offsetX, offsetY, this.video.videoWidth * scale, this.video.videoHeight * scale);
2961
3244
  }
2962
3245
  }
2963
- _drawComments(timelineRange, vpos) {
3246
+ _drawComments(timelineRange, vpos, cursor) {
2964
3247
  if (timelineRange) {
2965
3248
  const targetComment = (() => {
2966
3249
  if (config.commentLimit === undefined) {
@@ -2975,7 +3258,7 @@
2975
3258
  if (comment.invisible) {
2976
3259
  continue;
2977
3260
  }
2978
- comment.draw(vpos, this.showCollision, isDebug);
3261
+ comment.draw(vpos, this.showCollision, cursor);
2979
3262
  }
2980
3263
  }
2981
3264
  }
@@ -3016,8 +3299,8 @@
3016
3299
  this.context.font = parseFont("defont", 60);
3017
3300
  this.context.fillStyle = "#00FF00";
3018
3301
  this.context.strokeStyle = `rgba(${hex2rgb(config.contextStrokeColor).join(",")},${config.contextStrokeOpacity})`;
3019
- this.context.strokeText(`Count:${count || 0}`, 100, 200);
3020
- this.context.fillText(`Count:${count || 0}`, 100, 200);
3302
+ this.context.strokeText(`Count:${count ?? 0}`, 100, 200);
3303
+ this.context.fillText(`Count:${count ?? 0}`, 100, 200);
3021
3304
  this.context.restore();
3022
3305
  }
3023
3306
  }
@@ -3030,6 +3313,20 @@
3030
3313
  clear() {
3031
3314
  this.context.clearRect(0, 0, config.canvasWidth, config.canvasHeight);
3032
3315
  }
3316
+ click(vpos, pos) {
3317
+ const _comments = this.timeline[vpos];
3318
+ if (!_comments)
3319
+ return;
3320
+ const comments = [..._comments].reverse();
3321
+ for (const comment of comments) {
3322
+ if (comment.isHovered(pos)) {
3323
+ const newComment = buildAtButtonComment(comment.comment, vpos);
3324
+ if (!newComment)
3325
+ continue;
3326
+ this.addComments(newComment);
3327
+ }
3328
+ }
3329
+ }
3033
3330
  }
3034
3331
  const logger = (msg) => {
3035
3332
  if (isDebug)