UNPKG

@ovv/artalk

Version:

A self-hosted comment system

1,711 lines (1,704 loc) 242 kB
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 = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }; var unescapes = { "&amp;": "&", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#39;": "'" }; var rescaped = /(&amp;|&lt;|&gt;|&quot;|&#39;)/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 "&lt;"; } 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(/&amp;/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 `