@ovv/artalk
Version:
A self-hosted comment system
1,711 lines (1,704 loc) • 242 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
import { marked as marked$1, Marked } from "marked";
function createElement(htmlStr = "") {
const div = document.createElement("div");
div.innerHTML = htmlStr.trim();
return div.firstElementChild || div;
}
function getHeight(el) {
const num = parseFloat(getComputedStyle(el, null).height.replace("px", ""));
return num || 0;
}
function htmlEncode(str) {
const temp = document.createElement("div");
temp.innerText = str;
const output = temp.innerHTML;
return output;
}
function getQueryParam(name) {
const match = RegExp(`[?&]${name}=([^&]*)`).exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, " "));
}
function getOffset(el, relativeTo) {
const getOffsetRecursive = (element) => {
const rect = element.getBoundingClientRect();
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return {
top: rect.top + scrollTop,
left: rect.left + scrollLeft
};
};
const elOffset = getOffsetRecursive(el);
if (!relativeTo) return elOffset;
const relativeToOffset = getOffsetRecursive(relativeTo);
return {
top: elOffset.top - relativeToOffset.top,
left: elOffset.left - relativeToOffset.left
};
}
function padWithZeros(vNumber, width) {
let numAsString = vNumber.toString();
while (numAsString.length < width) {
numAsString = `0${numAsString}`;
}
return numAsString;
}
function dateFormat(date) {
const vDay = padWithZeros(date.getDate(), 2);
const vMonth = padWithZeros(date.getMonth() + 1, 2);
const vYear = padWithZeros(date.getFullYear(), 2);
return `${vYear}-${vMonth}-${vDay}`;
}
function timeAgo(date, $t = (n) => n) {
try {
const oldTime = date.getTime();
const currTime = (/* @__PURE__ */ new Date()).getTime();
const diffValue = currTime - oldTime;
const days = Math.floor(diffValue / (24 * 3600 * 1e3));
if (days === 0) {
const leave1 = diffValue % (24 * 3600 * 1e3);
const hours = Math.floor(leave1 / (3600 * 1e3));
if (hours === 0) {
const leave2 = leave1 % (3600 * 1e3);
const minutes = Math.floor(leave2 / (60 * 1e3));
if (minutes === 0) {
const leave3 = leave2 % (60 * 1e3);
const seconds = Math.round(leave3 / 1e3);
if (seconds < 10) return $t("now");
return `${seconds} ${$t("seconds")}`;
}
return `${minutes} ${$t("minutes")}`;
}
return `${hours} ${$t("hours")}`;
}
if (days < 0) return $t("now");
if (days < 8) {
return `${days} ${$t("days")}`;
}
return dateFormat(date);
} catch (error) {
console.error(error);
return " - ";
}
}
function getGravatarURL(opts) {
return `${opts.mirror.replace(/\/$/, "")}/${opts.emailHash}?${opts.params.replace(/^\?/, "")}`;
}
function versionCompare(a, b) {
const pa = a.split(".");
const pb = b.split(".");
for (let i = 0; i < 3; i++) {
const na = Number(pa[i]);
const nb = Number(pb[i]);
if (na > nb) return 1;
if (nb > na) return -1;
if (!Number.isNaN(na) && Number.isNaN(nb)) return 1;
if (Number.isNaN(na) && !Number.isNaN(nb)) return -1;
}
return 0;
}
function getCorrectUserAgent() {
return __async(this, null, function* () {
const uaRaw = navigator.userAgent;
if (!navigator.userAgentData || !navigator.userAgentData.getHighEntropyValues) {
return uaRaw;
}
const uaData = navigator.userAgentData;
let uaGot = null;
try {
uaGot = yield uaData.getHighEntropyValues(["platformVersion"]);
} catch (err) {
console.error(err);
return uaRaw;
}
const majorPlatformVersion = Number(uaGot.platformVersion.split(".")[0]);
if (uaData.platform === "Windows") {
if (majorPlatformVersion >= 13) {
return uaRaw.replace(/Windows NT 10.0/, "Windows NT 11.0");
}
}
if (uaData.platform === "macOS") {
if (majorPlatformVersion >= 11) {
return uaRaw.replace(
/(Mac OS X \d+_\d+_\d+|Mac OS X)/,
`Mac OS X ${uaGot.platformVersion.replace(/\./g, "_")}`
);
}
}
return uaRaw;
});
}
function isValidURL(urlRaw) {
let url;
try {
url = new URL(urlRaw);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
function getURLBasedOnApi(opts) {
return getURLBasedOn(opts.base, opts.path);
}
function getURLBasedOn(baseURL, path) {
return `${baseURL.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
}
const en = {
/* Editor */
placeholder: "Leave a comment",
noComment: "No Comment",
send: "Send",
signIn: "Sign in",
signUp: "Sign up",
save: "Save",
nick: "Nickname",
email: "Email",
link: "Website",
emoticon: "Emoji",
preview: "Preview",
uploadImage: "Upload Image",
uploadFail: "Upload Failed",
commentFail: "Failed to comment",
restoredMsg: "Content has been restored",
onlyAdminCanReply: "Only admin can reply",
uploadLoginMsg: "Please fill in your name and email to upload",
/* List */
counter: "{count} Comments",
sortLatest: "Latest",
sortOldest: "Oldest",
sortBest: "Best",
sortAuthor: "Author",
openComment: "Open Comment",
closeComment: "Close Comment",
listLoadFailMsg: "Failed to load comments",
listRetry: "Retry",
loadMore: "Load More",
/* Comment */
admin: "Admin",
reply: "Reply",
voteUp: "Up",
voteDown: "Down",
voteFail: "Vote Failed",
readMore: "Read More",
actionConfirm: "Confirm",
collapse: "Collapse",
collapsed: "Collapsed",
collapsedMsg: "This comment has been collapsed",
expand: "Expand",
approved: "Approved",
pending: "Pending",
pendingMsg: "Pending, visible only to commenter.",
edit: "Edit",
editCancel: "Cancel Edit",
delete: "Delete",
deleteConfirm: "Confirm",
pin: "Pin",
unpin: "Unpin",
/* Time */
seconds: "seconds ago",
minutes: "minutes ago",
hours: "hours ago",
days: "days ago",
now: "just now",
/* Checker */
adminCheck: "Enter admin password:",
captchaCheck: "Enter the CAPTCHA to continue:",
confirm: "Confirm",
cancel: "Cancel",
/* Sidebar */
msgCenter: "Messages",
ctrlCenter: "Dashboard",
/* Auth */
userProfile: "Profile",
noAccountPrompt: "Don't have an account?",
haveAccountPrompt: "Already have an account?",
forgetPassword: "Forget Password",
resetPassword: "Reset Password",
changePassword: "Change Password",
confirmPassword: "Confirm Password",
passwordMismatch: "Passwords do not match",
verificationCode: "Verification Code",
verifySend: "Send Code",
verifyResend: "Resend",
waitSeconds: "Wait {seconds}s",
emailVerified: "Email has been verified",
password: "Password",
username: "Username",
nextStep: "Next Step",
skipVerify: "Skip verification",
logoutConfirm: "Are you sure to logout?",
accountMergeNotice: "Your email has multiple accounts with different id.",
accountMergeSelectOne: "Please select one you want to merge all the data into it.",
accountMergeConfirm: "All data will be merged into one account, the id is {id}.",
dismiss: "Dismiss",
merge: "Merge",
/* General */
client: "Client",
server: "Server",
loading: "Loading",
loadFail: "Load Failed",
editing: "Editing",
editFail: "Edit Failed",
deleting: "Deleting",
deleteFail: "Delete Failed",
reqGot: "Request got",
reqAborted: "Request timed out or terminated unexpectedly",
updateMsg: "Please update Artalk {name} to get the best experience!",
currentVersion: "Current Version",
ignore: "Ignore",
open: "Open",
openName: "Open {name}"
};
const zhCN = {
/* Editor */
placeholder: "键入内容...",
noComment: "「此时无声胜有声」",
send: "发送",
signIn: "登录",
signUp: "注册",
save: "保存",
nick: "昵称",
email: "邮箱",
link: "网址",
emoticon: "表情",
preview: "预览",
uploadImage: "上传图片",
uploadFail: "上传失败",
commentFail: "评论失败",
restoredMsg: "内容已自动恢复",
onlyAdminCanReply: "仅管理员可评论",
uploadLoginMsg: "填入你的名字邮箱才能上传哦",
/* List */
counter: "{count} 条评论",
sortLatest: "最新",
sortOldest: "最早",
sortBest: "最热",
sortAuthor: "作者",
openComment: "打开评论",
closeComment: "关闭评论",
listLoadFailMsg: "无法获取评论列表数据",
listRetry: "点击重新获取",
loadMore: "加载更多",
/* Comment */
admin: "管理员",
reply: "回复",
voteUp: "赞同",
voteDown: "反对",
voteFail: "投票失败",
readMore: "阅读更多",
actionConfirm: "确认操作",
collapse: "折叠",
collapsed: "已折叠",
collapsedMsg: "该评论已被系统或管理员折叠",
expand: "展开",
approved: "已审",
pending: "待审",
pendingMsg: "审核中,仅本人可见。",
edit: "编辑",
editCancel: "取消编辑",
delete: "删除",
deleteConfirm: "确认删除",
pin: "置顶",
unpin: "取消置顶",
/* Time */
seconds: "秒前",
minutes: "分钟前",
hours: "小时前",
days: "天前",
now: "刚刚",
/* Checker */
adminCheck: "键入密码来验证管理员身份:",
captchaCheck: "键入验证码继续:",
confirm: "确认",
cancel: "取消",
/* Sidebar */
msgCenter: "通知中心",
ctrlCenter: "控制中心",
/* Auth */
userProfile: "个人资料",
noAccountPrompt: "没有账号?",
haveAccountPrompt: "已有账号?",
forgetPassword: "忘记密码",
resetPassword: "重置密码",
changePassword: "修改密码",
confirmPassword: "确认密码",
passwordMismatch: "两次输入的密码不一致",
verificationCode: "验证码",
verifySend: "发送验证码",
verifyResend: "重新发送",
waitSeconds: "等待 {seconds}秒",
emailVerified: "邮箱已验证",
password: "密码",
username: "用户名",
nextStep: "下一步",
skipVerify: "跳过验证",
logoutConfirm: "确定要退出登录吗?",
accountMergeNotice: "您的电子邮件下有多个不同 ID 的账户。",
accountMergeSelectOne: "请选择将所有数据合并到其中的一个。",
accountMergeConfirm: "所有数据将合并到 ID 为 {id} 的账户中。",
dismiss: "忽略",
merge: "合并",
/* General */
client: "客户端",
server: "服务器",
loading: "加载中",
loadFail: "加载失败",
editing: "修改中",
editFail: "修改失败",
deleting: "删除中",
deleteFail: "删除失败",
reqGot: "请求响应",
reqAborted: "请求超时或意外终止",
updateMsg: "请更新 Artalk {name} 以获得更好的体验!",
currentVersion: "当前版本",
ignore: "忽略",
open: "打开",
openName: "打开{name}"
};
const GLOBAL_LOCALES_KEY = "ArtalkI18n";
const internal = {
en,
"en-US": en,
"zh-CN": zhCN
};
function findLocaleSet(lang) {
lang = lang.replace(
/^([a-zA-Z]+)(-[a-zA-Z]+)?$/,
(_, p1, p2) => p1.toLowerCase() + (p2 || "").toUpperCase()
);
if (internal[lang]) {
return internal[lang];
}
if (window[GLOBAL_LOCALES_KEY] && window[GLOBAL_LOCALES_KEY][lang]) {
return window[GLOBAL_LOCALES_KEY][lang];
}
return internal.en;
}
let LocaleConf = "en";
let LocaleDict = findLocaleSet(LocaleConf);
function setLocale(locale) {
if (locale === LocaleConf) return;
LocaleConf = locale;
LocaleDict = typeof locale === "string" ? findLocaleSet(locale) : locale;
}
function t(key, args = {}) {
let str = (LocaleDict == null ? void 0 : LocaleDict[key]) || key;
str = str.replace(/\{\s*(\w+?)\s*\}/g, (_, token) => args[token] || "");
return htmlEncode(str);
}
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
function getDefaultExportFromCjs(x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
}
var escapes = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
};
var unescapes = {
"&": "&",
"<": "<",
">": ">",
""": '"',
"'": "'"
};
var rescaped = /(&|<|>|"|')/g;
var runescaped = /[&<>"']/g;
function escapeHtmlChar(match) {
return escapes[match];
}
function unescapeHtmlChar(match) {
return unescapes[match];
}
function escapeHtml(text) {
return text == null ? "" : String(text).replace(runescaped, escapeHtmlChar);
}
function unescapeHtml(html) {
return html == null ? "" : String(html).replace(rescaped, unescapeHtmlChar);
}
escapeHtml.options = unescapeHtml.options = {};
var she = {
encode: escapeHtml,
escape: escapeHtml,
decode: unescapeHtml,
unescape: unescapeHtml,
version: "1.0.0-browser"
};
function assignment(result) {
var stack = Array.prototype.slice.call(arguments, 1);
var item;
var key;
while (stack.length) {
item = stack.shift();
for (key in item) {
if (item.hasOwnProperty(key)) {
if (Object.prototype.toString.call(result[key]) === "[object Object]") {
result[key] = assignment(result[key], item[key]);
} else {
result[key] = item[key];
}
}
}
}
return result;
}
var assignment_1 = assignment;
var lowercase$2 = function lowercase(string) {
return typeof string === "string" ? string.toLowerCase() : string;
};
function toMap$2(list) {
return list.reduce(asKey, {});
}
function asKey(accumulator, item) {
accumulator[item] = true;
return accumulator;
}
var toMap_1 = toMap$2;
var toMap$1 = toMap_1;
var uris = ["background", "base", "cite", "href", "longdesc", "src", "usemap"];
var attributes$1 = {
uris: toMap$1(uris)
// attributes that have an href and hence need to be sanitized
};
var toMap = toMap_1;
var voids = ["area", "br", "col", "hr", "img", "wbr", "input", "base", "basefont", "link", "meta"];
var elements$2 = {
voids: toMap(voids)
};
var he$1 = she;
var lowercase$1 = lowercase$2;
var elements$1 = elements$2;
var rstart = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/;
var rend = /^<\s*\/\s*([\w:-]+)[^>]*>/;
var rattrs = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g;
var rtag = /^</;
var rtagend = /^<\s*\//;
function createStack() {
var stack = [];
stack.lastItem = function lastItem() {
return stack[stack.length - 1];
};
return stack;
}
function parser$1(html, handler) {
var stack = createStack();
var last = html;
var chars;
while (html) {
parsePart();
}
parseEndTag();
function parsePart() {
chars = true;
parseTag();
var same = html === last;
last = html;
if (same) {
html = "";
}
}
function parseTag() {
if (html.substr(0, 4) === "<!--") {
parseComment();
} else if (rtagend.test(html)) {
parseEdge(rend, parseEndTag);
} else if (rtag.test(html)) {
parseEdge(rstart, parseStartTag);
}
parseTagDecode();
}
function parseEdge(regex, parser2) {
var match = html.match(regex);
if (match) {
html = html.substring(match[0].length);
match[0].replace(regex, parser2);
chars = false;
}
}
function parseComment() {
var index = html.indexOf("-->");
if (index >= 0) {
if (handler.comment) {
handler.comment(html.substring(4, index));
}
html = html.substring(index + 3);
chars = false;
}
}
function parseTagDecode() {
if (!chars) {
return;
}
var text;
var index = html.indexOf("<");
if (index >= 0) {
text = html.substring(0, index);
html = html.substring(index);
} else {
text = html;
html = "";
}
if (handler.chars) {
handler.chars(text);
}
}
function parseStartTag(tag, tagName, rest, unary) {
var attrs = {};
var low = lowercase$1(tagName);
var u = elements$1.voids[low] || !!unary;
rest.replace(rattrs, attrReplacer);
if (!u) {
stack.push(low);
}
if (handler.start) {
handler.start(low, attrs, u);
}
function attrReplacer(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
if (doubleQuotedValue === void 0 && singleQuotedValue === void 0 && unquotedValue === void 0) {
attrs[name] = void 0;
} else {
attrs[name] = he$1.decode(doubleQuotedValue || singleQuotedValue || unquotedValue || "");
}
}
}
function parseEndTag(tag, tagName) {
var i;
var pos = 0;
var low = lowercase$1(tagName);
if (low) {
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] === low) {
break;
}
}
}
if (pos >= 0) {
for (i = stack.length - 1; i >= pos; i--) {
if (handler.end) {
handler.end(stack[i]);
}
}
stack.length = pos;
}
}
}
var parser_1 = parser$1;
var he = she;
var lowercase2 = lowercase$2;
var attributes = attributes$1;
var elements = elements$2;
function sanitizer$1(buffer, options) {
var context;
var o = options || {};
reset();
return {
start,
end,
chars
};
function out(value) {
buffer.push(value);
}
function start(tag, attrs, unary) {
var low = lowercase2(tag);
if (context.ignoring) {
ignore(low);
return;
}
if ((o.allowedTags || []).indexOf(low) === -1) {
ignore(low);
return;
}
if (o.filter && !o.filter({ tag: low, attrs })) {
ignore(low);
return;
}
out("<");
out(low);
Object.keys(attrs).forEach(parse);
out(unary ? "/>" : ">");
function parse(key) {
var value = attrs[key];
var classesOk = (o.allowedClasses || {})[low] || [];
var attrsOk = (o.allowedAttributes || {})[low] || [];
var valid;
var lkey = lowercase2(key);
if (lkey === "class" && attrsOk.indexOf(lkey) === -1) {
value = value.split(" ").filter(isValidClass).join(" ").trim();
valid = value.length;
} else {
valid = attrsOk.indexOf(lkey) !== -1 && (attributes.uris[lkey] !== true || testUrl(value));
}
if (valid) {
out(" ");
out(key);
if (typeof value === "string") {
out('="');
out(he.encode(value));
out('"');
}
}
function isValidClass(className) {
return classesOk && classesOk.indexOf(className) !== -1;
}
}
}
function end(tag) {
var low = lowercase2(tag);
var allowed = (o.allowedTags || []).indexOf(low) !== -1;
if (allowed) {
if (context.ignoring === false) {
out("</");
out(low);
out(">");
} else {
unignore(low);
}
} else {
unignore(low);
}
}
function testUrl(text) {
var start2 = text[0];
if (start2 === "#" || start2 === "/") {
return true;
}
var colon = text.indexOf(":");
if (colon === -1) {
return true;
}
var questionmark = text.indexOf("?");
if (questionmark !== -1 && colon > questionmark) {
return true;
}
var hash = text.indexOf("#");
if (hash !== -1 && colon > hash) {
return true;
}
return o.allowedSchemes.some(matches);
function matches(scheme) {
return text.indexOf(scheme + ":") === 0;
}
}
function chars(text) {
if (context.ignoring === false) {
out(o.transformText ? o.transformText(text) : text);
}
}
function ignore(tag) {
if (elements.voids[tag]) {
return;
}
if (context.ignoring === false) {
context = { ignoring: tag, depth: 1 };
} else if (context.ignoring === tag) {
context.depth++;
}
}
function unignore(tag) {
if (context.ignoring === tag) {
if (--context.depth <= 0) {
reset();
}
}
}
function reset() {
context = { ignoring: false, depth: 0 };
}
}
var sanitizer_1 = sanitizer$1;
var defaults$2 = {
allowedAttributes: {
a: ["href", "name", "target", "title", "aria-label"],
iframe: ["allowfullscreen", "frameborder", "src"],
img: ["src", "alt", "title", "aria-label"]
},
allowedClasses: {},
allowedSchemes: ["http", "https", "mailto"],
allowedTags: [
"a",
"abbr",
"article",
"b",
"blockquote",
"br",
"caption",
"code",
"del",
"details",
"div",
"em",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hr",
"i",
"img",
"ins",
"kbd",
"li",
"main",
"mark",
"ol",
"p",
"pre",
"section",
"span",
"strike",
"strong",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"th",
"thead",
"tr",
"u",
"ul"
],
filter: null
};
var defaults_1 = defaults$2;
var assign = assignment_1;
var parser = parser_1;
var sanitizer = sanitizer_1;
var defaults$1 = defaults_1;
function insane(html, options, strict) {
var buffer = [];
var configuration = strict === true ? options : assign({}, defaults$1, options);
var handler = sanitizer(buffer, configuration);
parser(html, handler);
return buffer.join("");
}
insane.defaults = defaults$1;
var insane_1 = insane;
const insane$1 = /* @__PURE__ */ getDefaultExportFromCjs(insane_1);
const insaneOptions = {
allowedClasses: {},
// @refer CVE-2018-8495
// @link https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8495
// @link https://leucosite.com/Microsoft-Edge-RCE/
// @link https://medium.com/@knownsec404team/analysis-of-the-security-issues-of-url-scheme-in-pc-from-cve-2018-8495-934478a36756
allowedSchemes: [
"http",
"https",
"mailto",
"data"
// for support base64 encoded image (安全性有待考虑)
],
allowedTags: [
"a",
"abbr",
"article",
"b",
"blockquote",
"br",
"caption",
"code",
"del",
"details",
"div",
"em",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hr",
"i",
"img",
"ins",
"kbd",
"li",
"main",
"mark",
"ol",
"p",
"pre",
"section",
"span",
"strike",
"strong",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"th",
"thead",
"tr",
"u",
"ul"
],
allowedAttributes: {
"*": ["title", "accesskey"],
a: ["href", "name", "target", "aria-label", "rel"],
img: ["src", "alt", "title", "atk-emoticon", "aria-label", "data-src", "class", "loading"],
// for code highlight
code: ["class"],
span: ["class", "style"]
},
filter: (node) => {
const allowed = [
["code", /^hljs\W+language-(.*)$/],
["span", /^(hljs-.*)$/],
["img", /^lazyload$/]
];
allowed.forEach(([tag, reg]) => {
if (node.tag === tag && !!node.attrs.class && !reg.test(node.attrs.class)) {
delete node.attrs.class;
}
});
if (node.tag === "span" && !!node.attrs.style && !/^color:(\W+)?#[0-9a-f]{3,6};?$/i.test(node.attrs.style)) {
delete node.attrs.style;
}
return true;
}
};
function sanitize(content) {
return insane$1(content, insaneOptions);
}
var hanabi$1 = { exports: {} };
(function(module, exports) {
(function(global2, factory) {
module.exports = factory();
})(commonjsGlobal, function() {
function createCommonjsModule(fn, module2) {
return module2 = { exports: {} }, fn(module2, module2.exports), module2.exports;
}
var index$1 = createCommonjsModule(function(module2) {
var comment = module2.exports = function() {
return new RegExp("(?:" + comment.line().source + ")|(?:" + comment.block().source + ")", "gm");
};
comment.line = function() {
return /(?:^|\s)\/\/(.+?)$/gm;
};
comment.block = function() {
return /\/\*([\S\s]*?)\*\//gm;
};
});
var defaultColors = ["23AC69", "91C132", "F19726", "E8552D", "1AAB8E", "E1147F", "2980C1", "1BA1E6", "9FA0A0", "F19726", "E30B20", "E30B20", "A3338B"];
var index = function(input, ref) {
if (ref === void 0) ref = {};
var colors = ref.colors;
if (colors === void 0) colors = defaultColors;
var index2 = 0;
var cache = {};
var wordRe = /[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af\u0400-\u04FF]+|\w+/;
var leftAngleRe = /</;
var re = new RegExp("(" + wordRe.source + "|" + leftAngleRe.source + ")|(" + index$1().source + ")", "gmi");
return input.replace(re, function(m, word, cm) {
if (cm) {
return toComment(cm);
}
if (word === "<") {
return "<";
}
var color;
if (cache[word]) {
color = cache[word];
} else {
color = colors[index2];
cache[word] = color;
}
var out = '<span style="color: #' + color + '">' + word + "</span>";
index2 = ++index2 % colors.length;
return out;
});
};
function toComment(cm) {
return '<span style="color: slategray">' + cm + "</span>";
}
return index;
});
})(hanabi$1);
var hanabiExports = hanabi$1.exports;
const hanabi = /* @__PURE__ */ getDefaultExportFromCjs(hanabiExports);
function renderCode(code) {
return hanabi(code);
}
function getRenderer(options) {
const renderer = new marked$1.Renderer();
renderer.link = markedLinkRenderer(renderer, renderer.link);
renderer.code = markedCodeRenderer();
renderer.image = markedImageRenderer(renderer, renderer.image, options);
return renderer;
}
const markedLinkRenderer = (renderer, orgLinkRenderer) => (args) => {
const getLinkOrigin = (link) => {
try {
return new URL(link).origin;
} catch (e) {
return "";
}
};
const isSameOriginLink = getLinkOrigin(args.href) === window.location.origin;
const html = orgLinkRenderer.call(renderer, args);
return html.replace(
/^<a /,
`<a target="_blank" ${!isSameOriginLink ? `rel="noreferrer noopener nofollow"` : ""} `
);
};
const markedCodeRenderer = () => ({ lang, text }) => {
const realLang = !lang ? "plaintext" : lang;
let colorized = text;
if (window.hljs) {
if (realLang && window.hljs.getLanguage(realLang)) {
colorized = window.hljs.highlight(realLang, text).value;
}
} else {
colorized = renderCode(text);
}
return `<pre rel="${realLang}">
<code class="hljs language-${realLang}">${colorized.replace(/&/g, "&")}</code>
</pre>`;
};
const markedImageRenderer = (renderer, orgImageRenderer, { imgLazyLoad }) => (args) => {
const html = orgImageRenderer.call(renderer, args);
if (!imgLazyLoad) return html;
if (imgLazyLoad === "native" || imgLazyLoad === true)
return html.replace(/^<img /, '<img class="lazyload" loading="lazy" ');
if (imgLazyLoad === "data-src")
return html.replace(/^<img /, '<img class="lazyload" ').replace("src=", "data-src=");
return html;
};
let instance;
let replacers = [];
const markedOptions = {
gfm: true,
breaks: true,
async: false
};
function getInstance() {
return instance;
}
function setReplacers(arr) {
replacers = arr;
}
function initMarked(options) {
try {
if (!Marked.name) return;
} catch (e) {
return;
}
const marked2 = new Marked();
marked2.setOptions(__spreadValues(__spreadValues({
renderer: getRenderer({
imgLazyLoad: options.imgLazyLoad
})
}, markedOptions), options.markedOptions));
instance = marked2;
}
function marked(src) {
var _a;
let markedContent = (_a = getInstance()) == null ? void 0 : _a.parse(src);
if (!markedContent) {
markedContent = simpleMarked(src);
}
let dest = sanitize(markedContent);
replacers.forEach((replacer) => {
if (typeof replacer === "function") dest = replacer(dest);
});
return dest;
}
function simpleMarked(src) {
return src.replace(
/```\s*([^]+?.*?[^]+?[^]+?)```/g,
(_, code) => `<pre><code>${renderCode(code)}</code></pre>`
).replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`).replace(
/\[(.*?)\]\((.*?)\)/g,
(_, text, link) => `<a href="${link}" target="_blank">${text}</a>`
).replace(/\n/g, "<br>");
}
function createInjectionContainer() {
const providers = /* @__PURE__ */ new Map();
const initializedDeps = /* @__PURE__ */ new Map();
const provide = (key, impl, deps, opts = {}) => {
providers.set(key, { impl, deps: deps || [], lifecycle: opts.lifecycle || "singleton" });
};
const inject = (key) => {
const provider = providers.get(key);
if (!provider) {
throw new Error(`No provide for ${String(key)}`);
}
if (provider.lifecycle === "singleton" && initializedDeps.has(key)) {
return initializedDeps.get(key);
}
const { impl, deps } = provider;
const params = deps.map((d) => inject(d));
const resolved = impl(...params);
initializedDeps.set(key, resolved);
return resolved;
};
return { provide, inject };
}
class Context {
constructor(_$root) {
__publicField(this, "_deps", createInjectionContainer());
// -------------------------------------------------------------------
// Dependency Injection
// -------------------------------------------------------------------
__publicField(this, "provide", (key, impl, deps, opts) => {
this._deps.provide(key, impl, deps, opts);
});
__publicField(this, "inject", (key) => {
return this._deps.inject(key);
});
__publicField(this, "get", this.inject);
// -------------------------------------------------------------------
// Event Manager
// -------------------------------------------------------------------
__publicField(this, "on", (name, handler) => {
this.inject("events").on(name, handler);
});
__publicField(this, "off", (name, handler) => {
this.inject("events").off(name, handler);
});
__publicField(this, "trigger", (name, payload) => {
this.inject("events").trigger(name, payload);
});
__publicField(this, "getCommentList", this.getCommentNodes);
__publicField(this, "getCommentDataList", this.getComments);
this._$root = _$root;
}
getEl() {
return this._$root;
}
destroy() {
this.trigger("unmounted");
while (this._$root.firstChild) {
this._$root.removeChild(this._$root.firstChild);
}
}
// -------------------------------------------------------------------
// Configurations
// -------------------------------------------------------------------
getConf() {
return this.inject("config").get();
}
updateConf(conf) {
this.inject("config").update(conf);
}
watchConf(keys, effect) {
this.inject("config").watchConf(keys, effect);
}
getMarked() {
return getInstance();
}
setDarkMode(darkMode) {
this.updateConf({ darkMode });
}
get conf() {
return this.getConf();
}
set conf(val) {
console.error("Cannot set config directly, please call updateConf()");
}
get $root() {
return this.getEl();
}
set $root(val) {
console.error("set $root is prohibited");
}
// -------------------------------------------------------------------
// I18n: Internationalization
// -------------------------------------------------------------------
$t(key, args = {}) {
return t(key, args);
}
// -------------------------------------------------------------------
// HTTP API Client
// -------------------------------------------------------------------
getApi() {
return this.inject("api");
}
getApiHandlers() {
return this.inject("apiHandlers");
}
// -------------------------------------------------------------------
// User Manager
// -------------------------------------------------------------------
getUser() {
return this.inject("user");
}
// -------------------------------------------------------------------
// Data Manager
// -------------------------------------------------------------------
getData() {
return this.inject("data");
}
fetch(params) {
this.getData().fetchComments(params);
}
reload() {
this.getData().fetchComments({ offset: 0 });
}
// -------------------------------------------------------------------
// List
// -------------------------------------------------------------------
listGotoFirst() {
this.trigger("list-goto-first");
}
getCommentNodes() {
return this.inject("list").getCommentNodes();
}
getComments() {
return this.getData().getComments();
}
// -------------------------------------------------------------------
// Editor
// -------------------------------------------------------------------
replyComment(commentData, $comment) {
this.inject("editor").setReplyComment(commentData, $comment);
}
editComment(commentData, $comment) {
this.inject("editor").setEditComment(commentData, $comment);
}
editorShowLoading() {
this.inject("editor").showLoading();
}
editorHideLoading() {
this.inject("editor").hideLoading();
}
editorShowNotify(msg, type) {
this.inject("editor").showNotify(msg, type);
}
editorResetState() {
this.inject("editor").resetState();
}
// -------------------------------------------------------------------
// Sidebar
// -------------------------------------------------------------------
showSidebar(payload) {
this.inject("sidebar").show(payload);
}
hideSidebar() {
this.inject("sidebar").hide();
}
// -------------------------------------------------------------------
// Checker
// -------------------------------------------------------------------
checkAdmin(payload) {
return this.inject("checkers").checkAdmin(payload);
}
checkCaptcha(payload) {
return this.inject("checkers").checkCaptcha(payload);
}
}
function mergeDeep(...objects) {
const isObject = (obj) => obj && typeof obj === "object" && obj.constructor === Object;
return objects.reduce((prev, obj) => {
Object.keys(obj != null ? obj : {}).forEach((key) => {
if (key === "__proto__" || key === "constructor" || key === "prototype") {
return;
}
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
} else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
} else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
const Defaults = {
el: "",
pageKey: "",
pageTitle: "",
server: "",
site: "",
placeholder: "",
noComment: "",
sendBtn: "",
darkMode: false,
editorTravel: true,
flatMode: "auto",
nestMax: 2,
nestSort: "DATE_ASC",
emoticons: "https://cdn.jsdelivr.net/gh/ArtalkJS/Emoticons/grps/default.json",
pageVote: true,
vote: true,
voteDown: false,
uaBadge: true,
listSort: true,
preview: true,
countEl: ".artalk-comment-count",
pvEl: ".artalk-pv-count",
statPageKeyAttr: "data-page-key",
gravatar: {
mirror: "https://www.gravatar.com/avatar/",
params: "sha256=1&d=mp&s=240"
},
pagination: {
pageSize: 20,
readMore: true,
autoLoad: true
},
heightLimit: {
content: 300,
children: 400,
scrollable: false
},
imgUpload: true,
imgLazyLoad: false,
reqTimeout: 15e3,
versionCheck: true,
useBackendConf: true,
preferRemoteConf: false,
listUnreadHighlight: false,
pvAdd: true,
fetchCommentsOnInit: true,
locale: "en",
apiVersion: "",
pluginURLs: [],
markedReplacers: [],
markedOptions: {}
};
function handelCustomConf(customConf, full = false) {
const conf = full ? mergeDeep(Defaults, customConf) : customConf;
if (conf.pageKey === "") conf.pageKey = `${window.location.pathname}`;
if (conf.pageTitle === "") conf.pageTitle = `${document.title}`;
if (conf.server) conf.server = conf.server.replace(/\/$/, "").replace(/\/api\/?$/, "");
if (conf.locale === "auto") conf.locale = navigator.language;
if (conf.flatMode === "auto") conf.flatMode = window.matchMedia("(max-width: 768px)").matches;
if (typeof conf.nestMax === "number" && Number(conf.nestMax) <= 1) conf.flatMode = true;
return conf;
}
function handleConfFormServer(conf) {
const ExcludedKeys = [
"el",
"pageKey",
"pageTitle",
"server",
"site",
"pvEl",
"countEl",
"statPageKeyAttr",
"pageVote"
];
Object.keys(conf).forEach((k) => {
if (ExcludedKeys.includes(k)) delete conf[k];
if (k === "darkMode" && conf[k] !== "auto") delete conf[k];
});
if (conf.emoticons && typeof conf.emoticons === "string") {
conf.emoticons = conf.emoticons.trim();
if (conf.emoticons.startsWith("[") || conf.emoticons.startsWith("{")) {
conf.emoticons = JSON.parse(conf.emoticons);
} else if (conf.emoticons === "false") {
conf.emoticons = false;
}
}
return conf;
}
function getRootEl(conf) {
let $root;
if (typeof conf.el === "string") {
const el = document.querySelector(conf.el);
if (!el) throw new Error(`Element "${conf.el}" not found.`);
$root = el;
} else if (conf.el instanceof HTMLElement) {
$root = conf.el;
} else {
throw new Error("Please provide a valid `el` config for Artalk.");
}
return $root;
}
function convertApiOptions(conf, user, handlers) {
return {
baseURL: `${conf.server}/api/v2`,
siteName: conf.site || "",
pageKey: conf.pageKey || "",
pageTitle: conf.pageTitle || "",
timeout: conf.reqTimeout,
getApiToken: () => user == null ? void 0 : user.getData().token,
userInfo: (user == null ? void 0 : user.checkHasBasicUserInfo()) ? {
name: user == null ? void 0 : user.getData().name,
email: user == null ? void 0 : user.getData().email
} : void 0,
handlers
};
}
const PvCountWidget = (ctx) => {
ctx.watchConf(
["site", "pageKey", "pageTitle", "countEl", "pvEl", "statPageKeyAttr", "pvAdd"],
(conf) => {
initCountWidget({
getApi: () => ctx.getApi(),
siteName: conf.site,
pageKey: conf.pageKey,
pageTitle: conf.pageTitle,
countEl: conf.countEl,
pvEl: conf.pvEl,
pageKeyAttr: conf.statPageKeyAttr,
pvAdd: conf.pvAdd
});
}
);
};
function initCountWidget(opt) {
return __async(this, null, function* () {
yield loadCommentCount(opt);
const cacheData = yield incrementPvCount(opt);
yield loadPvCount(opt, cacheData);
});
}
function incrementPvCount(opt) {
return __async(this, null, function* () {
if (!opt.pvAdd || !opt.pageKey) return void 0;
const pvCount = (yield opt.getApi().pages.logPv({
page_key: opt.pageKey,
page_title: opt.pageTitle,
site_name: opt.siteName
})).data.pv;
return {
[opt.pageKey]: pvCount
};
});
}
function loadCommentCount(opt) {
return __async(this, null, function* () {
yield loadStatCount({ opt, query: "page_comment", containers: [opt.countEl, "#ArtalkCount"] });
});
}
function loadPvCount(opt, cache) {
return __async(this, null, function* () {
yield loadStatCount({ opt, query: "page_pv", containers: [opt.pvEl, "#ArtalkPV"], cache });
});
}
function loadStatCount(args) {
return __async(this, null, function* () {
const { opt } = args;
let cache = args.cache || {};
const els = retrieveElements(args.containers);
const pageKeys = getPageKeys(els, opt.pageKeyAttr, opt.pageKey, cache);
if (pageKeys.length > 0) {
const res = (yield opt.getApi().stats.getStats(args.query, {
page_keys: pageKeys.join(","),
site_name: opt.siteName
})).data.data;
cache = __spreadValues(__spreadValues({}, cache), res);
}
updateElementsText(els, cache, opt.pageKey);
});
}
function retrieveElements(containers) {
const els = /* @__PURE__ */ new Set();
new Set(containers).forEach((selector) => {
document.querySelectorAll(selector).forEach((el) => els.add(el));
});
return els;
}
function getPageKeys(els, pageKeyAttr, pageKey, cache) {
const pageKeys = Array.from(els).map((el) => el.getAttribute(pageKeyAttr) || pageKey).filter((key) => key && typeof cache[key] !== "number");
return [...new Set(pageKeys)];
}
function updateElementsText(els, data, defaultPageKey) {
els.forEach((el) => {
const pageKey = el.getAttribute("data-page-key");
const count = pageKey && data[pageKey] || defaultPageKey && data[defaultPageKey] || 0;
el.innerText = `${Number(count)}`;
});
}
class HttpClient {
constructor(apiConfig = {}) {
__publicField(this, "baseUrl", "/api/v2");
__publicField(this, "securityData", null);
__publicField(this, "securityWorker");
__publicField(this, "abortControllers", /* @__PURE__ */ new Map());
__publicField(this, "customFetch", (...fetchParams) => fetch(...fetchParams));
__publicField(this, "baseApiParams", {
credentials: "same-origin",
headers: {},
redirect: "follow",
referrerPolicy: "no-referrer"
});
__publicField(this, "setSecurityData", (data) => {
this.securityData = data;
});
__publicField(this, "contentFormatters", {
[
"application/json"
/* Json */
]: (input) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[
"text/plain"
/* Text */
]: (input) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input,
[
"multipart/form-data"
/* FormData */
]: (input) => Object.keys(input || {}).reduce((formData, key) => {
const property = input[key];
formData.append(
key,
property instanceof Blob ? property : typeof property === "object" && property !== null ? JSON.stringify(property) : `${property}`
);
return formData;
}, new FormData()),
[
"application/x-www-form-urlencoded"
/* UrlEncoded */
]: (input) => this.toQueryString(input)
});
__publicField(this, "createAbortSignal", (cancelToken) => {
if (this.abortControllers.has(cancelToken)) {
const abortController2 = this.abortControllers.get(cancelToken);
if (abortController2) {
return abortController2.signal;
}
return void 0;
}
const abortController = new AbortController();
this.abortControllers.set(cancelToken, abortController);
return abortController.signal;
});
__publicField(this, "abortRequest", (cancelToken) => {
const abortController = this.abortControllers.get(cancelToken);
if (abortController) {
abortController.abort();
this.abortControllers.delete(cancelToken);
}
});
__publicField(this, "request", (_a) => __async(this, null, function* () {
var _b = _a, {
body,
secure,
path,
type,
query,
format,
baseUrl,
cancelToken
} = _b, params = __objRest(_b, [
"body",
"secure",
"path",
"type",
"query",
"format",
"baseUrl",
"cancelToken"
]);
const secureParams = (typeof secure === "boolean" ? secure : this.baseApiParams.secure) && this.securityWorker && (yield this.securityWorker(this.securityData)) || {};
const requestParams = this.mergeRequestParams(params, secureParams);
const queryString = query && this.toQueryString(query);
const payloadFormatter = this.contentFormatters[
type || "application/json"
/* Json */
];
const responseFormat = format || requestParams.format;
return this.customFetch(
`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`,
__spreadProps(__spreadValues({}, requestParams), {
headers: __spreadValues(__spreadValues({}, requestParams.headers || {}), type && type !== "multipart/form-data" ? { "Content-Type": type } : {}),
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body)
})
).then((response) => __async(this, null, function* () {
const r = response.clone();
r.data = null;
r.error = null;
const data = !responseFormat ? r : yield response[responseFormat]().then((data2) => {
if (r.ok) {
r.data = data2;
} else {
r.error = data2;
}
return r;
}).catch((e) => {
r.error = e;
return r;
});
if (cancelToken) {
this.abortControllers.delete(cancelToken);
}
if (!response.ok) throw data;
return data;
}));
}));
Object.assign(this, apiConfig);
}
encodeQueryParam(key, value) {
const encodedKey = encodeURIComponent(key);
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
}
addQueryParam(query, key) {
return this.encodeQueryParam(key, query[key]);
}
addArrayQueryParam(query, key) {
const value = query[key];
return value.map((v) => this.encodeQueryParam(key, v)).join("&");
}
toQueryString(rawQuery) {
const query = rawQuery || {};
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
return keys.map(
(key) => Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)
).join("&");
}
addQueryParams(rawQuery) {
const queryString = this.toQueryString(rawQuery);
return queryString ? `?${queryString}` : "";
}
mergeRequestParams(params1, params2) {
return __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, this.baseApiParams), params1), params2 || {}), {
headers: __spreadValues(__spreadValues(__spreadValues({}, this.baseApiParams.headers || {}), params1.headers || {}), params2 && params2.headers || {})
});
}
}
/**
* @title Artalk API
* @version 2.0
* @license MIT (https://github.com/ArtalkJS/Artalk/blob/master/LICENSE)
* @baseUrl /api/v2
* @contact API Support <artalkjs@gmail.com> (https://artalk.js.org)
*
* Artalk is a modern comment system based on Golang.
*/
let Api$1 = class Api extends HttpClient {
constructor() {
super(...arguments);
__publicField(this, "auth", {
/**
* @description Login by email with verify code (Need send email verify code first) or password
*
* @tags Auth
* @name LoginByEmail
* @summary Login by email
* @request POST:/auth/email/login
* @response `200` `HandlerResponseUserLogin` OK
* @response `400` `(HandlerMap & {
msg?: string,
})` Bad Request
* @response `500` `(HandlerMap & {
msg?: string,
})` Internal Server Error
*/
loginByEmail: (data, params = {}) => this.request(__spreadValues({
path: `/auth/email/login`,
method: "POST",
body: data,
type: "application/json",
format: "json"
}, params)),
/**
* @description Register by email and verify code (if user exists, will update user, like forget or change password. Need send email verify code first)
*
* @tags Auth
* @name RegisterByEmail
* @summary Register by email
* @request POST:/auth/email/register
* @response `200` `HandlerResponseUserLogin` OK
* @response `400` `(HandlerMap & {
msg?: string,
})` Bad Request
* @response `500` `(HandlerMap & {
msg?: string,
})` Internal Server Error
*/
registerByEmail: (data, params = {}) => this.request(__spreadValues({
path: `/auth/email/register`,
method: "POST",
body: data,
type: "application/json",
format: "json"
}, params)),
/**
* @description Send email including verify code to user
*
* @tags Auth
* @name SendVerifyEmail
* @summary Send verify email
* @request POST:/auth/email/send
* @response `200` `(HandlerMap & {
msg?: string,
})` OK
* @response `400` `(HandlerMap & {
msg?: string,
})` Bad Request
* @response `500` `(HandlerMap & {
msg?: string,
})` Internal Server Error
*/
sendVerifyEmail: (data, params = {}) => this.request(__spreadValues({
path: `/auth/email/send`,
method: "POST",
body: data,
type: "application/json",
format: "json"
}, params)),
/**
* @description Get all users with same email, if there are more than one user with same email, need merge
*
* @tags Auth
* @name CheckDataMerge
* @summary Check data merge
* @request GET:/auth/merge
* @secure
* @response `200` `HandlerResponseAuthDataMergeCheck` OK
* @response `