▲V2EX Polish - 体验更现代化的 V2EX 🟢

一款专为 V2EX 用户设计的浏览器插件,提供了丰富的扩展功能,让原生页面焕然一新!✨

目前為 2023-08-14 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name ▲V2EX Polish - 体验更现代化的 V2EX 🟢
  3. // @namespace LeoKu(https://leoku.top)
  4. // @version 1.6.3
  5. // @description 一款专为 V2EX 用户设计的浏览器插件,提供了丰富的扩展功能,让原生页面焕然一新!✨
  6. // @author LeoKu
  7. // @match https://*.v2ex.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
  9. // @run-at document-start
  10. // @grant GM_addStyle
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. "use strict";
  15. var __getOwnPropNames = Object.getOwnPropertyNames;
  16. var __esm = (fn, res) => function __init() {
  17. return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
  18. };
  19.  
  20. // src/constants.ts
  21. var EXTENSION_NAME, emoticons, READABLE_CONTENT_HEIGHT, MAX_CONTENT_HEIGHT, READING_CONTENT_LIMIT, dataExpiryTime, imgurClientIdPool, defaultOptions;
  22. var init_constants = __esm({
  23. "src/constants.ts"() {
  24. "use strict";
  25. EXTENSION_NAME = "V2EX_Polish";
  26. emoticons = [
  27. {
  28. title: "\u5C0F\u9EC4\u8138",
  29. list: [
  30. "\u{1F600}",
  31. "\u{1F601}",
  32. "\u{1F602}",
  33. "\u{1F923}",
  34. "\u{1F605}",
  35. "\u{1F60A}",
  36. "\u{1F60B}",
  37. "\u{1F618}",
  38. "\u{1F970}",
  39. "\u{1F617}",
  40. "\u{1F929}",
  41. "\u{1F914}",
  42. "\u{1F928}",
  43. "\u{1F610}",
  44. "\u{1F611}",
  45. "\u{1F644}",
  46. "\u{1F60F}",
  47. "\u{1F62A}",
  48. "\u{1F62B}",
  49. "\u{1F971}",
  50. "\u{1F61C}",
  51. "\u{1F612}",
  52. "\u{1F614}",
  53. "\u{1F628}",
  54. "\u{1F630}",
  55. "\u{1F631}",
  56. "\u{1F975}",
  57. "\u{1F621}",
  58. "\u{1F973}",
  59. "\u{1F97A}",
  60. "\u{1F92D}",
  61. "\u{1F9D0}",
  62. "\u{1F60E}",
  63. "\u{1F913}",
  64. "\u{1F62D}",
  65. "\u{1F911}",
  66. "\u{1F92E}"
  67. ]
  68. },
  69. {
  70. title: "\u624B\u52BF",
  71. list: [
  72. "\u{1F64B}",
  73. "\u{1F64E}",
  74. "\u{1F645}",
  75. "\u{1F647}",
  76. "\u{1F937}",
  77. "\u{1F90F}",
  78. "\u{1F449}",
  79. "\u270C\uFE0F",
  80. "\u{1F918}",
  81. "\u{1F919}",
  82. "\u{1F44C}",
  83. "\u{1F90C}",
  84. "\u{1F44D}",
  85. "\u{1F44E}",
  86. "\u{1F44B}",
  87. "\u{1F91D}",
  88. "\u{1F64F}",
  89. "\u{1F44F}"
  90. ]
  91. },
  92. {
  93. title: "\u5E86\u795D",
  94. list: ["\u2728", "\u{1F389}", "\u{1F38A}"]
  95. },
  96. {
  97. title: "\u5176\u4ED6",
  98. list: ["\u{1F47B}", "\u{1F921}", "\u{1F414}", "\u{1F440}", "\u{1F4A9}", "\u{1F434}", "\u{1F984}", "\u{1F427}", "\u{1F436}", "\u{1F412}", "\u{1F648}", "\u{1F649}", "\u{1F64A}", "\u{1F435}"]
  99. }
  100. ];
  101. READABLE_CONTENT_HEIGHT = 250;
  102. MAX_CONTENT_HEIGHT = 550;
  103. READING_CONTENT_LIMIT = 150;
  104. dataExpiryTime = 60 * 60 * 1e3;
  105. imgurClientIdPool = [
  106. "3107b9ef8b316f3",
  107. // 以下 Client ID 来自「V2EX Plus」
  108. "442b04f26eefc8a",
  109. "59cfebe717c09e4",
  110. "60605aad4a62882",
  111. "6c65ab1d3f5452a",
  112. "83e123737849aa9",
  113. "9311f6be1c10160",
  114. "c4a4a563f698595",
  115. "81be04b9e4a08ce"
  116. ];
  117. defaultOptions = {
  118. openInNewTab: false,
  119. autoCheckIn: {
  120. enabled: true
  121. },
  122. theme: {
  123. autoSwitch: false
  124. },
  125. reply: {
  126. preload: "off"
  127. },
  128. replyContent: {
  129. autoFold: true
  130. },
  131. nestedReply: {
  132. display: "indent",
  133. multipleInsideOne: "nested"
  134. }
  135. };
  136. }
  137. });
  138.  
  139. // src/icons.ts
  140. var iconLoading, iconLogo, iconChromeWebStore, iconGitHub;
  141. var init_icons = __esm({
  142. "src/icons.ts"() {
  143. "use strict";
  144. iconLoading = `
  145. <svg version="1.1" id="L4" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
  146. viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
  147. <circle fill="currentcolor" stroke="none" cx="6" cy="50" r="6">
  148. <animate
  149. attributeName="opacity"
  150. dur="1s"
  151. values="0;1;0"
  152. repeatCount="indefinite"
  153. begin="0.1"/>
  154. </circle>
  155. <circle fill="currentcolor" stroke="none" cx="26" cy="50" r="6">
  156. <animate
  157. attributeName="opacity"
  158. dur="1s"
  159. values="0;1;0"
  160. repeatCount="indefinite"
  161. begin="0.2"/>
  162. </circle>
  163. <circle fill="currentcolor" stroke="none" cx="46" cy="50" r="6">
  164. <animate
  165. attributeName="opacity"
  166. dur="1s"
  167. values="0;1;0"
  168. repeatCount="indefinite"
  169. begin="0.3"/>
  170. </circle>
  171. </svg>
  172. `;
  173. iconLogo = `
  174. <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 88 88"><g style="mix-blend-mode:passthrough"><path d="M87.92 86.098v-.052a.592.592 0 0 0 0-.07L44.978.72l-.059-.105c-.16-.3-.415-.511-.705-.586a.961.961 0 0 0-.841.19 1.315 1.315 0 0 0-.336.378l-.058.115a2571.004 2571.004 0 0 1-8.695 17.172c-.59 1.024-.59 2.382 0 3.406 3.856 7.57 7.7 15.142 11.532 22.718.641 1.108.641 2.58 0 3.688C39.5 60.23 32.826 73.406 26.45 85.993c-.291.661-.086 1.482.46 1.84.16.104.341.158.525.158h18.52c.415.003.797-.272.992-.713l.635-1.285 8.585-17.023c.142-.317.383-.552.67-.653a.949.949 0 0 1 .855.116c.156.1.289.245.386.423l8.506 16.723.787 1.558c.199.433.575.702.985.704h.518c.087.009.175.009.263 0h17.74c.617 0 1.119-.601 1.123-1.347a1.615 1.615 0 0 0-.08-.396Z" fill="currentColor" style="mix-blend-mode:passthrough"/><path d="m38.551 48.541.62-1.232a3.095 3.095 0 0 0 0-3.02l-3.807-7.446-4.377-8.511c-.155-.308-.406-.527-.697-.61a.957.957 0 0 0-.85.17 1.252 1.252 0 0 0-.4.502L.132 86.002c-.29.658-.085 1.477.46 1.83.161.113.345.17.532.168h16.981c.41 0 .788-.27.985-.705l.65-1.302c.029-.048.055-.098.08-.15l.729-1.408c6.047-12.103 11.839-23.66 17.9-35.7.038-.062.072-.127.102-.194Z" fill="currentColor" style="mix-blend-mode:passthrough"/></g></svg>
  175. `;
  176. iconChromeWebStore = `
  177. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 152.01 132"><defs><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="39.16" y1="775.015" x2="152.84" y2="775.015" gradientTransform="translate(0 -650)"><stop offset="0" style="stop-color:#d93025"/><stop offset="1" style="stop-color:#ea4335"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="-1169.827" y1="59.741" x2="-1056.123" y2="59.741" gradientTransform="rotate(-120 -489.637 -232.003)"><stop offset="0" style="stop-color:#1e8e3e"/><stop offset="1" style="stop-color:#34a853"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="56.707" y1="-664.775" x2="170.407" y2="-664.775" gradientTransform="rotate(120 -125.508 -249.005)"><stop offset="0" style="stop-color:#fbbc04"/><stop offset="1" style="stop-color:#fcc934"/></linearGradient><linearGradient xlink:href="#a" id="d" gradientUnits="userSpaceOnUse" gradientTransform="translate(-20 -678)" x1="39.16" y1="775.015" x2="152.84" y2="775.015"/><linearGradient xlink:href="#b" id="e" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-120 -507.72 -240.23)" x1="-1169.827" y1="59.741" x2="-1056.123" y2="59.741"/><linearGradient xlink:href="#c" id="f" gradientUnits="userSpaceOnUse" gradientTransform="rotate(120 -127.425 -268.779)" x1="56.707" y1="-664.775" x2="170.407" y2="-664.775"/></defs><path d="M0 0v121.63C0 127.35 4.64 132 10.37 132h131.27c5.72 0 10.37-4.64 10.37-10.37L152 0Zm58.73 14.52h34.54c3.82 0 6.91 3.09 6.91 6.91 0 3.81-3.09 6.91-6.91 6.91H58.73c-3.82 0-6.91-3.09-6.91-6.91 0-3.82 3.09-6.91 6.91-6.91z" fill="#f1f3f4"/><path d="M0 0v121.63C0 127.35 4.64 132 10.37 132h131.27c5.72 0 10.37-4.64 10.37-10.37L152 0Zm58.73 14.52h34.54c3.82 0 6.91 3.09 6.91 6.91 0 3.81-3.09 6.91-6.91 6.91H58.73c-3.82 0-6.91-3.09-6.91-6.91 0-3.82 3.09-6.91 6.91-6.91z" fill="#f1f3f4"/><path d="M0 0v66.35h152V0Zm58.73 14.52h34.54c3.82 0 6.91 3.09 6.91 6.91 0 3.81-3.09 6.91-6.91 6.91H58.73c-3.82 0-6.91-3.09-6.91-6.91 0-3.82 3.09-6.91 6.91-6.91z" fill="#e8eaed"/><path style="fill:url(#d)" d="M76 55.99c-24.29 0-45.49 13.19-56.84 32.81l9.05 27.61L42.18 132h8.894L76 88.81l56.84-.01C121.49 69.18 100.29 55.99 76 55.99Z"/><path style="fill:url(#e)" d="M19.16 88.8C11.694 101.767 8.8 117.054 11.186 132h32.908z"/><path style="fill:url(#f)" d="M76 88.81 100.934 132h39.88c2.387-14.943-.508-30.225-7.974-43.19z"/><path style="fill:#f1f3f4" d="M76 88.81c-18.12 0-32.81 14.695-32.81 32.82 0 3.626.598 7.11 1.683 10.37h62.254a32.771 32.771 0 0 0 1.684-10.37c0-18.125-14.69-32.82-32.811-32.82Z"/><path d="M76 94.96a26.66 26.67 0 0 0-26.66 26.67A26.66 26.67 0 0 0 51.486 132h49.028a26.66 26.67 0 0 0 2.146-10.37A26.66 26.67 0 0 0 76 94.96Z" fill="#1a73e8"/><path opacity=".1" fill="#bdc1c6" d="M0 66.35h152v.86H0zM0 65.48h152v.86H0z"/></svg>
  178. `;
  179. iconGitHub = `
  180. <svg viewBox="0 0 24 24" aria-hidden="true">
  181. <path fill="currentColor" clip-rule="evenodd" d="M12 2C6.477 2 2 6.463 2 11.97c0 4.404 2.865 8.14 6.839 9.458.5.092.682-.216.682-.48 0-.236-.008-.864-.013-1.695-2.782.602-3.369-1.337-3.369-1.337-.454-1.151-1.11-1.458-1.11-1.458-.908-.618.069-.606.069-.606 1.003.07 1.531 1.027 1.531 1.027.892 1.524 2.341 1.084 2.91.828.092-.643.35-1.083.636-1.332-2.22-.251-4.555-1.107-4.555-4.927 0-1.088.39-1.979 1.029-2.675-.103-.252-.446-1.266.098-2.638 0 0 .84-.268 2.75 1.022A9.607 9.607 0 0 1 12 6.82c.85.004 1.705.114 2.504.336 1.909-1.29 2.747-1.022 2.747-1.022.546 1.372.202 2.386.1 2.638.64.696 1.028 1.587 1.028 2.675 0 3.83-2.339 4.673-4.566 4.92.359.307.678.915.678 1.846 0 1.332-.012 2.407-.012 2.734 0 .267.18.577.688.48 3.97-1.32 6.833-5.054 6.833-9.458C22 6.463 17.522 2 12 2Z"></path>
  182. </svg>
  183. `;
  184. }
  185. });
  186.  
  187. // src/components/toast.ts
  188. function createToast(props) {
  189. const { message, duration = 3e3 } = props;
  190. const $existTosat = $(".v2p-toast");
  191. if ($existTosat.length > 0) {
  192. $existTosat.remove();
  193. }
  194. const $toast = $(`<div class="v2p-toast">${message}</div>`).hide();
  195. $(document.body).append($toast);
  196. $toast.fadeIn("fast");
  197. if (duration !== 0) {
  198. setTimeout(() => {
  199. $toast.fadeOut("fast", () => {
  200. $toast.remove();
  201. });
  202. }, duration);
  203. }
  204. return {
  205. clear() {
  206. $toast.remove();
  207. }
  208. };
  209. }
  210. var init_toast = __esm({
  211. "src/components/toast.ts"() {
  212. "use strict";
  213. }
  214. });
  215.  
  216. // src/utils.ts
  217. function getOS() {
  218. const userAgent = window.navigator.userAgent.toLowerCase();
  219. const macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos)/i;
  220. const windowsPlatforms = /(win32|win64|windows|wince)/i;
  221. const iosPlatforms = /(iphone|ipad|ipod)/i;
  222. let os = null;
  223. if (macosPlatforms.test(userAgent)) {
  224. os = "macos";
  225. } else if (iosPlatforms.test(userAgent)) {
  226. os = "ios";
  227. } else if (windowsPlatforms.test(userAgent)) {
  228. os = "windows";
  229. } else if (userAgent.includes("android")) {
  230. os = "android";
  231. } else if (userAgent.includes("linux")) {
  232. os = "linux";
  233. }
  234. return os;
  235. }
  236. function formatTimestamp(timestamp, { format = "YMD" } = {}) {
  237. const date = new Date(timestamp.toString().length === 10 ? timestamp * 1e3 : timestamp);
  238. const year = date.getFullYear().toString();
  239. const month = (date.getMonth() + 1).toString().padStart(2, "0");
  240. const day = date.getDate().toString().padStart(2, "0");
  241. const YMD = `${year}-${month}-${day}`;
  242. if (format === "YMDHMS") {
  243. const hour = date.getHours().toString().padStart(2, "0");
  244. const minute = date.getMinutes().toString().padStart(2, "0");
  245. const second = date.getSeconds().toString().padStart(2, "0");
  246. return `${YMD} ${hour}:${minute}:${second}`;
  247. }
  248. return YMD;
  249. }
  250. function isSameDay(timestamp1, timestamp2) {
  251. const date1 = new Date(timestamp1);
  252. const date2 = new Date(timestamp2);
  253. return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
  254. }
  255. function isObject(value) {
  256. return typeof value === "object" && value !== null && !Array.isArray(value);
  257. }
  258. function deepMerge(target, source) {
  259. const result = {};
  260. for (const key in target) {
  261. const targetProp = target[key];
  262. const sourceProp = source[key];
  263. if (isObject(targetProp) && isObject(sourceProp)) {
  264. result[key] = deepMerge(targetProp, sourceProp);
  265. } else if (Reflect.has(source, key)) {
  266. result[key] = sourceProp;
  267. } else {
  268. result[key] = targetProp;
  269. }
  270. }
  271. for (const key in source) {
  272. if (!Reflect.has(target, key)) {
  273. result[key] = source[key];
  274. }
  275. }
  276. return result;
  277. }
  278. function getRunEnv() {
  279. if (typeof chrome === "object" && typeof chrome.extension !== "undefined") {
  280. return "chrome";
  281. }
  282. if (typeof browser === "object" && typeof browser.extension !== "undefined") {
  283. return "web-ext";
  284. }
  285. return null;
  286. }
  287. function getStorage(useCache = true) {
  288. return new Promise((resolve, reject) => {
  289. if (useCache) {
  290. if (typeof window !== "undefined" && window.__V2P_StorageCache) {
  291. resolve(window.__V2P_StorageCache);
  292. }
  293. }
  294. const runEnv = getRunEnv();
  295. if (!(runEnv === "chrome" || runEnv === "web-ext")) {
  296. const data = { ["options" /* Options */]: defaultOptions };
  297. if (typeof window !== "undefined") {
  298. window.__V2P_StorageCache = data;
  299. }
  300. resolve(data);
  301. } else {
  302. chrome.storage.sync.get().then((items) => {
  303. let data;
  304. const options = items["options" /* Options */];
  305. if (options) {
  306. data = { ...items, ["options" /* Options */]: deepMerge(defaultOptions, options) };
  307. } else {
  308. data = { ...items, ["options" /* Options */]: defaultOptions };
  309. }
  310. if (typeof window !== "undefined") {
  311. window.__V2P_StorageCache = data;
  312. }
  313. resolve(data);
  314. }).catch((err) => {
  315. reject(err);
  316. });
  317. }
  318. });
  319. }
  320. function getStorageSync() {
  321. const storage = window.__V2P_StorageCache;
  322. if (!storage) {
  323. throw new Error(`${EXTENSION_NAME}: \u65E0\u53EF\u7528\u7684 Storage \u7F13\u5B58\u6570\u636E`);
  324. }
  325. return storage;
  326. }
  327. async function setStorage(storageKey, storageItem) {
  328. switch (storageKey) {
  329. case "options" /* Options */:
  330. case "api" /* API */:
  331. case "daily" /* Daily */:
  332. case "member-tag" /* MemberTag */:
  333. case "settings-sync" /* SyncInfo */:
  334. case "reading-list" /* ReadingList */:
  335. try {
  336. await chrome.storage.sync.set({ [storageKey]: storageItem });
  337. } catch (err) {
  338. if (String(err).includes("QUOTA_BYTES_PER_ITEM quota exceeded")) {
  339. console.error(
  340. `${EXTENSION_NAME}: \u65E0\u6CD5\u8BBE\u7F6E ${storageKey}\uFF0C \u5355\u4E2A item \u4E0D\u80FD\u8D85\u51FA 8 KB\uFF0C\u8BE6\u60C5\u67E5\u770B\uFF1Ahttps://developer.chrome.com/docs/extensions/reference/storage/#storage-areas`
  341. );
  342. createToast({ message: "\u274C \u7528\u6237\u6570\u636E\u8D85\u51FA\u5B58\u50A8\u7A7A\u95F4\u9650\u5236" });
  343. }
  344. throw new Error(`\u274C \u65E0\u6CD5\u8BBE\u7F6E\uFF1A${storageKey}`);
  345. }
  346. break;
  347. default:
  348. throw new Error(`\u672A\u77E5\u7684 storageKey\uFF1A ${storageKey}`);
  349. }
  350. }
  351. function escapeHTML(htmlString) {
  352. return htmlString.replace(/[<>&"'']/g, (match) => {
  353. switch (match) {
  354. case "<":
  355. return "&lt;";
  356. case ">":
  357. return "&gt;";
  358. case "&":
  359. return "&amp;";
  360. case '"':
  361. return "&quot;";
  362. case "'":
  363. return "&#39;";
  364. default:
  365. return match;
  366. }
  367. });
  368. }
  369. function injectScript(scriptSrc) {
  370. const script = document.createElement("script");
  371. script.setAttribute("type", "text/javascript");
  372. script.setAttribute("src", scriptSrc);
  373. document.body.appendChild(script);
  374. }
  375. function sleep(ms) {
  376. return new Promise((resolve) => setTimeout(resolve, ms));
  377. }
  378. var init_utils = __esm({
  379. "src/utils.ts"() {
  380. "use strict";
  381. init_toast();
  382. init_constants();
  383. }
  384. });
  385.  
  386. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/createElement.js
  387. var createElement, createElement$1;
  388. var init_createElement = __esm({
  389. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/createElement.js"() {
  390. createElement = (tag, attrs, children = []) => {
  391. const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
  392. Object.keys(attrs).forEach((name) => {
  393. element.setAttribute(name, String(attrs[name]));
  394. });
  395. if (children.length) {
  396. children.forEach((child) => {
  397. const childElement = createElement(...child);
  398. element.appendChild(childElement);
  399. });
  400. }
  401. return element;
  402. };
  403. createElement$1 = ([tag, attrs, children]) => createElement(tag, attrs, children);
  404. }
  405. });
  406.  
  407. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/replaceElement.js
  408. var getAttrs, getClassNames, combineClassNames, toPascalCase, replaceElement;
  409. var init_replaceElement = __esm({
  410. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/replaceElement.js"() {
  411. init_createElement();
  412. getAttrs = (element) => Array.from(element.attributes).reduce((attrs, attr) => {
  413. attrs[attr.name] = attr.value;
  414. return attrs;
  415. }, {});
  416. getClassNames = (attrs) => {
  417. if (typeof attrs === "string")
  418. return attrs;
  419. if (!attrs || !attrs.class)
  420. return "";
  421. if (attrs.class && typeof attrs.class === "string") {
  422. return attrs.class.split(" ");
  423. }
  424. if (attrs.class && Array.isArray(attrs.class)) {
  425. return attrs.class;
  426. }
  427. return "";
  428. };
  429. combineClassNames = (arrayOfClassnames) => {
  430. const classNameArray = arrayOfClassnames.flatMap(getClassNames);
  431. return classNameArray.map((classItem) => classItem.trim()).filter(Boolean).filter((value, index, self) => self.indexOf(value) === index).join(" ");
  432. };
  433. toPascalCase = (string) => string.replace(/(\w)(\w*)(_|-|\s*)/g, (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase());
  434. replaceElement = (element, { nameAttr, icons, attrs }) => {
  435. const iconName = element.getAttribute(nameAttr);
  436. if (iconName == null)
  437. return;
  438. const ComponentName = toPascalCase(iconName);
  439. const iconNode = icons[ComponentName];
  440. if (!iconNode) {
  441. return console.warn(
  442. `${element.outerHTML} icon name was not found in the provided icons object.`
  443. );
  444. }
  445. const elementAttrs = getAttrs(element);
  446. const [tag, iconAttributes, children] = iconNode;
  447. const iconAttrs = {
  448. ...iconAttributes,
  449. "data-lucide": iconName,
  450. ...attrs,
  451. ...elementAttrs
  452. };
  453. const classNames = combineClassNames(["lucide", `lucide-${iconName}`, elementAttrs, attrs]);
  454. if (classNames) {
  455. Object.assign(iconAttrs, {
  456. class: classNames
  457. });
  458. }
  459. const svgElement = createElement$1([tag, iconAttrs, children]);
  460. return element.parentNode?.replaceChild(svgElement, element);
  461. };
  462. }
  463. });
  464.  
  465. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/defaultAttributes.js
  466. var defaultAttributes;
  467. var init_defaultAttributes = __esm({
  468. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/defaultAttributes.js"() {
  469. defaultAttributes = {
  470. xmlns: "http://www.w3.org/2000/svg",
  471. width: 24,
  472. height: 24,
  473. viewBox: "0 0 24 24",
  474. fill: "none",
  475. stroke: "currentColor",
  476. "stroke-width": 2,
  477. "stroke-linecap": "round",
  478. "stroke-linejoin": "round"
  479. };
  480. }
  481. });
  482.  
  483. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/book-open-check.js
  484. var BookOpenCheck;
  485. var init_book_open_check = __esm({
  486. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/book-open-check.js"() {
  487. init_defaultAttributes();
  488. BookOpenCheck = [
  489. "svg",
  490. defaultAttributes,
  491. [
  492. ["path", { d: "M8 3H2v15h7c1.7 0 3 1.3 3 3V7c0-2.2-1.8-4-4-4Z" }],
  493. ["path", { d: "m16 12 2 2 4-4" }],
  494. ["path", { d: "M22 6V3h-6c-2.2 0-4 1.8-4 4v14c0-1.7 1.3-3 3-3h7v-2.3" }]
  495. ]
  496. ];
  497. }
  498. });
  499.  
  500. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/chevrons-up.js
  501. var ChevronsUp;
  502. var init_chevrons_up = __esm({
  503. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/chevrons-up.js"() {
  504. init_defaultAttributes();
  505. ChevronsUp = [
  506. "svg",
  507. defaultAttributes,
  508. [
  509. ["polyline", { points: "17 11 12 6 7 11" }],
  510. ["polyline", { points: "17 18 12 13 7 18" }]
  511. ]
  512. ];
  513. }
  514. });
  515.  
  516. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/clock-4.js
  517. var Clock4;
  518. var init_clock_4 = __esm({
  519. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/clock-4.js"() {
  520. init_defaultAttributes();
  521. Clock4 = [
  522. "svg",
  523. defaultAttributes,
  524. [
  525. ["circle", { cx: "12", cy: "12", r: "10" }],
  526. ["polyline", { points: "12 6 12 12 16 14" }]
  527. ]
  528. ];
  529. }
  530. });
  531.  
  532. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/eye-off.js
  533. var EyeOff;
  534. var init_eye_off = __esm({
  535. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/eye-off.js"() {
  536. init_defaultAttributes();
  537. EyeOff = [
  538. "svg",
  539. defaultAttributes,
  540. [
  541. ["path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }],
  542. [
  543. "path",
  544. {
  545. d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"
  546. }
  547. ],
  548. [
  549. "path",
  550. {
  551. d: "M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"
  552. }
  553. ],
  554. ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
  555. ]
  556. ];
  557. }
  558. });
  559.  
  560. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/heart.js
  561. var Heart;
  562. var init_heart = __esm({
  563. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/heart.js"() {
  564. init_defaultAttributes();
  565. Heart = [
  566. "svg",
  567. defaultAttributes,
  568. [
  569. [
  570. "path",
  571. {
  572. d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"
  573. }
  574. ]
  575. ]
  576. ];
  577. }
  578. });
  579.  
  580. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/message-square-plus.js
  581. var MessageSquarePlus;
  582. var init_message_square_plus = __esm({
  583. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/message-square-plus.js"() {
  584. init_defaultAttributes();
  585. MessageSquarePlus = [
  586. "svg",
  587. defaultAttributes,
  588. [
  589. [
  590. "path",
  591. { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }
  592. ],
  593. ["line", { x1: "9", x2: "15", y1: "10", y2: "10" }],
  594. ["line", { x1: "12", x2: "12", y1: "7", y2: "13" }]
  595. ]
  596. ];
  597. }
  598. });
  599.  
  600. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/message-square.js
  601. var MessageSquare;
  602. var init_message_square = __esm({
  603. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/message-square.js"() {
  604. init_defaultAttributes();
  605. MessageSquare = [
  606. "svg",
  607. defaultAttributes,
  608. [
  609. [
  610. "path",
  611. { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }
  612. ]
  613. ]
  614. ];
  615. }
  616. });
  617.  
  618. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/moon.js
  619. var Moon;
  620. var init_moon = __esm({
  621. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/moon.js"() {
  622. init_defaultAttributes();
  623. Moon = [
  624. "svg",
  625. defaultAttributes,
  626. [["path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }]]
  627. ];
  628. }
  629. });
  630.  
  631. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/package-plus.js
  632. var PackagePlus;
  633. var init_package_plus = __esm({
  634. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/package-plus.js"() {
  635. init_defaultAttributes();
  636. PackagePlus = [
  637. "svg",
  638. defaultAttributes,
  639. [
  640. ["path", { d: "M16 16h6" }],
  641. ["path", { d: "M19 13v6" }],
  642. [
  643. "path",
  644. {
  645. d: "M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14"
  646. }
  647. ],
  648. ["path", { d: "M16.5 9.4 7.55 4.24" }],
  649. ["polyline", { points: "3.29 7 12 12 20.71 7" }],
  650. ["line", { x1: "12", x2: "12", y1: "22", y2: "12" }]
  651. ]
  652. ];
  653. }
  654. });
  655.  
  656. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/smile.js
  657. var Smile;
  658. var init_smile = __esm({
  659. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/smile.js"() {
  660. init_defaultAttributes();
  661. Smile = [
  662. "svg",
  663. defaultAttributes,
  664. [
  665. ["circle", { cx: "12", cy: "12", r: "10" }],
  666. ["path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }],
  667. ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }],
  668. ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9" }]
  669. ]
  670. ];
  671. }
  672. });
  673.  
  674. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/star.js
  675. var Star;
  676. var init_star = __esm({
  677. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/star.js"() {
  678. init_defaultAttributes();
  679. Star = [
  680. "svg",
  681. defaultAttributes,
  682. [
  683. [
  684. "polygon",
  685. {
  686. points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
  687. }
  688. ]
  689. ]
  690. ];
  691. }
  692. });
  693.  
  694. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/sun.js
  695. var Sun;
  696. var init_sun = __esm({
  697. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/sun.js"() {
  698. init_defaultAttributes();
  699. Sun = [
  700. "svg",
  701. defaultAttributes,
  702. [
  703. ["circle", { cx: "12", cy: "12", r: "4" }],
  704. ["path", { d: "M12 2v2" }],
  705. ["path", { d: "M12 20v2" }],
  706. ["path", { d: "m4.93 4.93 1.41 1.41" }],
  707. ["path", { d: "m17.66 17.66 1.41 1.41" }],
  708. ["path", { d: "M2 12h2" }],
  709. ["path", { d: "M20 12h2" }],
  710. ["path", { d: "m6.34 17.66-1.41 1.41" }],
  711. ["path", { d: "m19.07 4.93-1.41 1.41" }]
  712. ]
  713. ];
  714. }
  715. });
  716.  
  717. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/twitter.js
  718. var Twitter;
  719. var init_twitter = __esm({
  720. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/icons/twitter.js"() {
  721. init_defaultAttributes();
  722. Twitter = [
  723. "svg",
  724. defaultAttributes,
  725. [
  726. [
  727. "path",
  728. {
  729. d: "M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"
  730. }
  731. ]
  732. ]
  733. ];
  734. }
  735. });
  736.  
  737. // node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/lucide.js
  738. var createIcons;
  739. var init_lucide = __esm({
  740. "node_modules/.pnpm/lucide@0.258.0/node_modules/lucide/dist/esm/lucide.js"() {
  741. init_replaceElement();
  742. init_createElement();
  743. init_book_open_check();
  744. init_chevrons_up();
  745. init_clock_4();
  746. init_eye_off();
  747. init_heart();
  748. init_message_square_plus();
  749. init_message_square();
  750. init_moon();
  751. init_package_plus();
  752. init_smile();
  753. init_star();
  754. init_sun();
  755. init_twitter();
  756. createIcons = ({ icons = {}, nameAttr = "data-lucide", attrs = {} } = {}) => {
  757. if (!Object.values(icons).length) {
  758. throw new Error(
  759. "Please provide an icons object.\nIf you want to use all the icons you can import it like:\n `import { createIcons, icons } from 'lucide';\nlucide.createIcons({icons});`"
  760. );
  761. }
  762. if (typeof document === "undefined") {
  763. throw new Error("`createIcons()` only works in a browser environment.");
  764. }
  765. const elementsToReplace = document.querySelectorAll(`[${nameAttr}]`);
  766. Array.from(elementsToReplace).forEach(
  767. (element) => replaceElement(element, { nameAttr, icons, attrs })
  768. );
  769. if (nameAttr === "data-lucide") {
  770. const deprecatedElements = document.querySelectorAll("[icon-name]");
  771. if (deprecatedElements.length > 0) {
  772. console.warn("[Lucide] Some icons were found with the now deprecated icon-name attribute. These will still be replaced for backwards compatibility, but will no longer be supported in v1.0 and you should switch to data-lucide");
  773. Array.from(deprecatedElements).forEach(
  774. (element) => replaceElement(element, { nameAttr: "icon-name", icons, attrs })
  775. );
  776. }
  777. }
  778. };
  779. }
  780. });
  781.  
  782. // src/contents/globals.ts
  783. function updateCommentCells() {
  784. $commentCells = $commentBox.find('.cell[id^="r_"]');
  785. $commentTableRows = $commentCells.find("> table > tbody > tr");
  786. }
  787. var loginName, topicOwnerName, $topicList, $topicContentBox, $commentBox, $commentCells, $commentTableRows, $replyBox, $replyForm, colorTheme, $replyTextArea, replyTextArea;
  788. var init_globals = __esm({
  789. "src/contents/globals.ts"() {
  790. "use strict";
  791. loginName = $('#Top .tools > a[href^="/member"]').text();
  792. topicOwnerName = $('#Main > .box > .header > small > a[href^="/member"]').text();
  793. $topicList = $(
  794. "#Main #Tabs ~ .cell.item, #Main #TopicsNode > .cell, #Main .cell.item:has(.item_title > .topic-link)"
  795. );
  796. $topicContentBox = $("#Main .box:has(.topic_content)");
  797. $commentBox = $('#Main .box:has(.cell[id^="r_"])');
  798. $commentCells = $commentBox.find('.cell[id^="r_"]');
  799. $commentTableRows = $commentCells.find("> table > tbody > tr");
  800. $replyBox = $("#reply-box");
  801. $replyForm = $replyBox.find('form[action^="/t"]');
  802. colorTheme = $("#Wrapper").hasClass("Night") ? "dark" : "light";
  803. $replyTextArea = $("#reply_content");
  804. replyTextArea = document.querySelector("#reply_content");
  805. }
  806. });
  807.  
  808. // src/contents/helpers.ts
  809. function isV2EX_RequestError(error) {
  810. if ("cause" in error) {
  811. const cause = error["cause"];
  812. if ("success" in cause && "message" in cause) {
  813. return (
  814. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  815. typeof cause["success"] === "boolean" && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  816. !cause["success"] && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  817. typeof cause["message"] === "string"
  818. );
  819. }
  820. }
  821. return false;
  822. }
  823. function focusReplyInput() {
  824. if (replyTextArea instanceof HTMLTextAreaElement) {
  825. replyTextArea.focus();
  826. }
  827. }
  828. function insertTextToReplyInput(text) {
  829. if (replyTextArea instanceof HTMLTextAreaElement) {
  830. const startPos = replyTextArea.selectionStart;
  831. const endPos = replyTextArea.selectionEnd;
  832. const valueToStart = replyTextArea.value.substring(0, startPos);
  833. const valueFromEnd = replyTextArea.value.substring(endPos, replyTextArea.value.length);
  834. replyTextArea.value = `${valueToStart}${text}${valueFromEnd}`;
  835. focusReplyInput();
  836. replyTextArea.selectionStart = replyTextArea.selectionEnd = startPos + text.length;
  837. }
  838. }
  839. async function setMemberTags(memberName, tags) {
  840. const storage = await getStorage(false);
  841. const tagData = storage["member-tag" /* MemberTag */];
  842. const runEnv = getRunEnv();
  843. if (!(runEnv === "chrome" || runEnv === "web-ext")) {
  844. return;
  845. }
  846. if (tags && tags.length > 0) {
  847. const newTagData = { ...tagData, [memberName]: { tags } };
  848. await setStorage("member-tag" /* MemberTag */, newTagData);
  849. } else {
  850. if (tagData && Reflect.has(tagData, memberName)) {
  851. delete tagData[memberName];
  852. await setStorage("member-tag" /* MemberTag */, tagData);
  853. }
  854. }
  855. }
  856. async function addToReadingList(params) {
  857. const { url, title, content } = params;
  858. if (!(typeof url === "string" || typeof title === "string" || typeof content === "string")) {
  859. const message = "\u65E0\u6CD5\u8BC6\u522B\u5C06\u8BE5\u4E3B\u9898\u7684\u5143\u6570\u636E";
  860. createToast({ message });
  861. throw new Error(message);
  862. }
  863. const storage = await getStorage();
  864. const currentData = storage["reading-list" /* ReadingList */]?.data || [];
  865. const exist = currentData.findIndex((it) => it.url === url) !== -1;
  866. if (exist) {
  867. createToast({ message: "\u8BE5\u4E3B\u9898\u5DF2\u5B58\u5728\u4E8E\u7A0D\u540E\u9605\u8BFB" });
  868. } else {
  869. if (window.__V2P_AddingReading !== true) {
  870. window.__V2P_AddingReading = true;
  871. try {
  872. await setStorage("reading-list" /* ReadingList */, {
  873. data: [
  874. {
  875. url,
  876. title: title.replace(" - V2EX", ""),
  877. content: content.length > READING_CONTENT_LIMIT ? content.substring(0, READING_CONTENT_LIMIT) + "..." : content,
  878. addedTime: Date.now()
  879. },
  880. ...currentData
  881. ]
  882. });
  883. createToast({ message: "\u2705 \u5DF2\u6DFB\u52A0\u8FDB\u7A0D\u540E\u9605\u8BFB" });
  884. await sleep(500);
  885. } finally {
  886. window.__V2P_AddingReading = false;
  887. }
  888. }
  889. }
  890. }
  891. function postTask(expression, callback) {
  892. const runEnv = getRunEnv();
  893. if (!runEnv) {
  894. const result = Function(`"use strict"; ${expression}`)();
  895. callback?.(result);
  896. } else {
  897. if (callback) {
  898. if (window.__V2P_Tasks) {
  899. window.__V2P_Tasks.set(Date.now(), callback);
  900. } else {
  901. window.__V2P_Tasks = /* @__PURE__ */ new Map([[Date.now(), callback]]);
  902. }
  903. }
  904. const messageData = {
  905. from: 0 /* Content */,
  906. payload: { task: { id: Date.now(), expression } }
  907. };
  908. window.postMessage(messageData);
  909. }
  910. }
  911. function loadIcons() {
  912. setTimeout(() => {
  913. createIcons({
  914. attrs: {
  915. width: "100%",
  916. height: "100%"
  917. },
  918. icons: {
  919. MessageSquarePlus,
  920. MessageSquare,
  921. BookOpenCheck,
  922. ChevronsUp,
  923. Heart,
  924. EyeOff,
  925. Sun,
  926. Moon,
  927. Smile,
  928. PackagePlus,
  929. Star,
  930. Twitter
  931. }
  932. });
  933. }, 0);
  934. }
  935. var init_helpers = __esm({
  936. "src/contents/helpers.ts"() {
  937. "use strict";
  938. init_lucide();
  939. init_toast();
  940. init_constants();
  941. init_utils();
  942. init_globals();
  943. }
  944. });
  945.  
  946. // src/contents/common.ts
  947. var common_exports = {};
  948. var init_common = __esm({
  949. "src/contents/common.ts"() {
  950. "use strict";
  951. init_constants();
  952. init_icons();
  953. init_utils();
  954. init_helpers();
  955. void (async () => {
  956. const storage = await getStorage();
  957. const options = storage["options" /* Options */];
  958. {
  959. const $toggle = $("#Rightbar .light-toggle").addClass("v2p-color-mode-toggle");
  960. const $toggleImg = $toggle.find("> img");
  961. const alt = $toggleImg.prop("alt");
  962. if (alt === "Light") {
  963. $toggle.prop("title", "\u4F7F\u7528\u6DF1\u8272\u4E3B\u9898");
  964. $toggleImg.replaceWith('<i data-lucide="moon"></i>');
  965. } else if (alt === "Dark") {
  966. $toggle.prop("title", "\u4F7F\u7528\u6D45\u8272\u4E3B\u9898");
  967. $toggleImg.replaceWith(`<i data-lucide="sun"></i>`);
  968. }
  969. if (options.theme.autoSwitch) {
  970. const perfersDark = window.matchMedia("(prefers-color-scheme: dark)");
  971. if (perfersDark.matches) {
  972. $("#Wrapper").addClass("Night");
  973. }
  974. perfersDark.addEventListener("change", ({ matches }) => {
  975. if (matches) {
  976. $("#Wrapper").addClass("Night");
  977. } else {
  978. $("#Wrapper").removeClass("Night");
  979. }
  980. });
  981. $toggle.on("click", () => {
  982. void setStorage("options" /* Options */, deepMerge(options, { theme: { autoSwitch: false } }));
  983. });
  984. }
  985. }
  986. {
  987. $("#Top .site-nav .tools > .top").addClass("v2p-hover-btn");
  988. }
  989. {
  990. const $searchItem = $('<a class="search-item cell" target="_blank">');
  991. $searchItem.on("mouseover", () => {
  992. $("#search-result .search-item.active").addClass("v2p-no-active");
  993. $searchItem.addClass("active");
  994. }).on("mouseleave", () => {
  995. $("#search-result .search-item.active").removeClass("v2p-no-active");
  996. $searchItem.removeClass("active");
  997. });
  998. const $search = $("#search");
  999. $search.on("input", (ev) => {
  1000. const value = ev.target.value;
  1001. const $searchGroup = $("#search-result .search-item-group:last-of-type");
  1002. $searchItem.text(`SOV2EX ${value}`).prop("href", `https://www.sov2ex.com/?q=${value}`);
  1003. $searchGroup.append($searchItem);
  1004. });
  1005. }
  1006. {
  1007. const runEnv = getRunEnv();
  1008. if (runEnv === "chrome" || runEnv === "web-ext") {
  1009. injectScript(chrome.runtime.getURL("scripts/web_accessible_resources.min.js"));
  1010. window.addEventListener("message", (ev) => {
  1011. if (ev.data.from === 1 /* Web */) {
  1012. const payload = ev.data.payload;
  1013. const task = payload?.task;
  1014. if (payload?.status === "ready") {
  1015. postTask('if (typeof window.once === "string") { return window.once; }', (result) => {
  1016. if (typeof result === "string") {
  1017. window.once = result;
  1018. }
  1019. });
  1020. }
  1021. if (task) {
  1022. window.__V2P_Tasks?.get(task.id)?.(task.result);
  1023. }
  1024. }
  1025. });
  1026. }
  1027. }
  1028. {
  1029. const $extraFooter = $(`
  1030. <div class="v2p-footer">
  1031. <div class="v2p-footer-text">\u6269\u5C55\u81EA V2EX Polish </div>
  1032. <div class="v2p-footer-links">
  1033. <a class="v2p-footer-link v2p-hover-btn" href="${"https://v2p.app" /* Home */}" target="_blank">\u63D2\u4EF6\u4E3B\u9875</a>
  1034. <a class="v2p-footer-link v2p-hover-btn" href="${"https://github.com/coolpace/V2EX_Polish/discussions/1" /* Feedback */}" target="_blank">\u95EE\u9898\u53CD\u9988</a>
  1035. </div>
  1036. <div class="v2p-footer-brand">
  1037. <span>
  1038. <a
  1039. href="https://chrome.google.com/webstore/detail/v2ex-polish/onnepejgdiojhiflfoemillegpgpabdm"
  1040. target="_blank"
  1041. title="Chrome \u5E94\u7528\u5546\u5E97"
  1042. >
  1043. ${iconChromeWebStore}
  1044. </a>
  1045. </span>
  1046. <span>
  1047. <a
  1048. href="https://github.com/coolpace/V2EX_Polish"
  1049. target="_blank"
  1050. title="GitHub \u4ED3\u5E93"
  1051. >
  1052. ${iconGitHub}
  1053. </a>
  1054. </span>
  1055. </div>
  1056. </div>
  1057. `);
  1058. $(`<div class="v2p-footer-logo">${iconLogo}</div>`).prependTo($extraFooter);
  1059. $("#Bottom .content").append($extraFooter);
  1060. }
  1061. })();
  1062. }
  1063. });
  1064.  
  1065. // src/components/button.ts
  1066. function createButton(props) {
  1067. const { children, className = "", type = "button", tag = "button" } = props;
  1068. const $button = $(`<${tag} class="normal button ${className}">${children}</${tag}>`);
  1069. if (tag === "button") {
  1070. $button.prop("type", type);
  1071. }
  1072. return $button;
  1073. }
  1074. var init_button = __esm({
  1075. "src/components/button.ts"() {
  1076. "use strict";
  1077. }
  1078. });
  1079.  
  1080. // src/components/model.ts
  1081. function createModel(props) {
  1082. const { root, title, onOpen, onClose, onMount } = props;
  1083. const $mask = $('<div class="v2p-model-mask">');
  1084. const $content = $('<div class="v2p-model-content">');
  1085. const $closeBtn = createButton({
  1086. children: "\u5173\u95ED<kbd>Esc</kbd>",
  1087. className: "v2p-model-close-btn"
  1088. });
  1089. const $title = $(`<div class="v2p-model-title">${title ?? ""}</div>`);
  1090. const $actions = $('<div class="v2p-model-actions">').append($closeBtn);
  1091. const $header = $('<div class="v2p-model-header">').append($title, $actions);
  1092. const $main = $('<div class="v2p-model-main">').append($header, $content).on("click", (ev) => ev.stopPropagation());
  1093. const $container = $mask.append($main).hide();
  1094. const modelElements = {
  1095. $mask,
  1096. $main,
  1097. $container,
  1098. $title,
  1099. $actions,
  1100. $content
  1101. };
  1102. let boundEvent = false;
  1103. const maskClickHandler = () => {
  1104. handleModalClose();
  1105. };
  1106. const keyupHandler = (ev) => {
  1107. if (ev.key === "Escape") {
  1108. handleModalClose();
  1109. }
  1110. };
  1111. const handleModalClose = () => {
  1112. $mask.off("click", maskClickHandler);
  1113. $(document).off("keydown", keyupHandler);
  1114. boundEvent = false;
  1115. $container.fadeOut("fast");
  1116. document.body.classList.remove("v2p-modal-open");
  1117. onClose?.(modelElements);
  1118. };
  1119. const handleModalOpen = () => {
  1120. setTimeout(() => {
  1121. if (!boundEvent) {
  1122. $mask.on("click", maskClickHandler);
  1123. $(document).on("keydown", keyupHandler);
  1124. boundEvent = true;
  1125. }
  1126. });
  1127. $container.fadeIn("fast");
  1128. document.body.classList.add("v2p-modal-open");
  1129. onOpen?.(modelElements);
  1130. };
  1131. $closeBtn.on("click", handleModalClose);
  1132. onMount?.(modelElements);
  1133. if (root) {
  1134. root.append($container);
  1135. }
  1136. return { ...modelElements, open: handleModalOpen, close: handleModalClose };
  1137. }
  1138. var init_model = __esm({
  1139. "src/components/model.ts"() {
  1140. "use strict";
  1141. init_button();
  1142. }
  1143. });
  1144.  
  1145. // src/services.ts
  1146. async function legacyRequest(url, options) {
  1147. const res = await fetch(url, options);
  1148. return res.json();
  1149. }
  1150. function fetchUserInfo(memberName, options) {
  1151. return legacyRequest(
  1152. `${V2EX_LEGACY_API}/members/show.json?username=${memberName}`,
  1153. options
  1154. );
  1155. }
  1156. async function request(url, options) {
  1157. const storage = await getStorage();
  1158. const PAT = storage["api" /* API */]?.pat;
  1159. const res = await fetch(url, {
  1160. ...options,
  1161. headers: { Authorization: PAT ? `Bearer ${PAT}` : "", ...options?.headers }
  1162. });
  1163. {
  1164. const limit = res.headers.get("X-Rate-Limit-Limit");
  1165. const reset = res.headers.get("X-Rate-Limit-Reset");
  1166. const remaining = res.headers.get("X-Rate-Limit-Remaining");
  1167. const api = {
  1168. pat: PAT,
  1169. limit: limit ? Number(limit) : void 0,
  1170. reset: reset ? Number(reset) : void 0,
  1171. remaining: remaining ? Number(remaining) : void 0
  1172. };
  1173. void setStorage("api" /* API */, api);
  1174. }
  1175. const resultData = await res.json();
  1176. if (typeof resultData.success === "boolean" && !resultData.success) {
  1177. throw new Error(resultData.message, { cause: resultData });
  1178. }
  1179. return resultData;
  1180. }
  1181. function fetchTopic(topicId, options) {
  1182. return request(`${V2EX_API}/topics/${topicId}`, { method: "GET", ...options });
  1183. }
  1184. function fetchTopicReplies(topicId, options) {
  1185. return request(`${V2EX_API}/topics/${topicId}/replies`, {
  1186. method: "GET",
  1187. ...options
  1188. });
  1189. }
  1190. async function uploadImage(file) {
  1191. const formData = new FormData();
  1192. formData.append("image", file);
  1193. const randomIndex = Math.floor(Math.random() * imgurClientIdPool.length);
  1194. const clidenId = imgurClientIdPool[randomIndex];
  1195. const res = await fetch("https://api.imgur.com/3/upload", {
  1196. method: "POST",
  1197. headers: { Authorization: `Client-ID ${clidenId}` },
  1198. body: formData
  1199. });
  1200. if (res.ok) {
  1201. const resData = await res.json();
  1202. if (resData.success) {
  1203. return resData.data.link;
  1204. }
  1205. }
  1206. throw new Error("\u4E0A\u4F20\u5931\u8D25");
  1207. }
  1208. async function refreshMoney() {
  1209. const res = await fetch("/ajax/money", { method: "POST" });
  1210. const data = await res.text();
  1211. $("#money").html(data);
  1212. }
  1213. async function thankReply(params) {
  1214. try {
  1215. const res = await fetch(`/thank/reply/${params.replyId}?once=${window.once}`, {
  1216. method: "POST"
  1217. });
  1218. const data = await res.json();
  1219. postTask(`window.once = ${data.once}`);
  1220. window.once = data.once;
  1221. if (data.success) {
  1222. $("#thank_area_" + params.replyId).addClass("thanked").html("\u611F\u8C22\u5DF2\u53D1\u9001");
  1223. params.onSuccess?.();
  1224. await refreshMoney();
  1225. } else {
  1226. alert(data.message);
  1227. }
  1228. } catch {
  1229. params.onFail?.();
  1230. }
  1231. }
  1232. async function crawalTopicPage(path, page) {
  1233. const res = await fetch(`${"https://www.v2ex.com" /* Origin */}${path}?p=${page}`);
  1234. const htmlText = await res.text();
  1235. return htmlText;
  1236. }
  1237. var V2EX_ORIGIN, V2EX_LEGACY_API, V2EX_API, mark;
  1238. var init_services = __esm({
  1239. "src/services.ts"() {
  1240. "use strict";
  1241. init_constants();
  1242. init_helpers();
  1243. init_utils();
  1244. V2EX_ORIGIN = window.location.origin.includes("v2ex.com") ? window.location.origin : "https://www.v2ex.com" /* Origin */;
  1245. V2EX_LEGACY_API = `${V2EX_ORIGIN}/api`;
  1246. V2EX_API = `${V2EX_ORIGIN}/api/v2`;
  1247. mark = `${EXTENSION_NAME}_settings`;
  1248. }
  1249. });
  1250.  
  1251. // src/contents/home/topic-list.ts
  1252. function handlingTopicList() {
  1253. const runEnv = getRunEnv();
  1254. if (!runEnv) {
  1255. return;
  1256. }
  1257. const storage = getStorageSync();
  1258. const options = storage["options" /* Options */];
  1259. const PAT = storage["api" /* API */]?.pat;
  1260. let abortController = null;
  1261. const $detailBtn = createButton({
  1262. children: "\u8FDB\u5165\u4E3B\u9898",
  1263. className: "special",
  1264. tag: "a"
  1265. });
  1266. if (options.openInNewTab) {
  1267. $detailBtn.prop("target", "_blank");
  1268. }
  1269. const model = createModel({
  1270. root: $(document.body),
  1271. onMount: ({ $actions }) => {
  1272. $actions.prepend($detailBtn);
  1273. },
  1274. onClose: ({ $title, $content }) => {
  1275. $title.empty();
  1276. $content.empty();
  1277. abortController?.abort();
  1278. }
  1279. });
  1280. const topicDataCache = /* @__PURE__ */ new Map();
  1281. $topicList.each((_, topicItem) => {
  1282. const $topicItem = $(topicItem);
  1283. const $itemTitle = $topicItem.find(".item_title");
  1284. $('<button class="v2p-topic-preview-btn">\u9884\u89C8</button>').on("click", () => {
  1285. const linkHref = $topicItem.find(".topic-link").attr("href");
  1286. const match = linkHref?.match(/\/t\/(\d+)/);
  1287. const topicId = match?.at(1);
  1288. if (topicId) {
  1289. model.open();
  1290. $detailBtn.prop("href", linkHref);
  1291. const topicTitle = $itemTitle.find(".topic-link").text();
  1292. const $titleLink = $(
  1293. `<a class="v2p-topic-preview-title-link" title="${topicTitle}">${topicTitle}</a>`
  1294. );
  1295. model.$title.empty().append($titleLink);
  1296. if (PAT) {
  1297. void (async () => {
  1298. let cacheData = topicDataCache.get(topicId);
  1299. if (!cacheData || Date.now() - cacheData.cacheTime > 1e3 * 60 * 10) {
  1300. try {
  1301. abortController = new AbortController();
  1302. model.$content.empty().append(`
  1303. <div class="v2p-model-loading">
  1304. <div class="v2p-icon-loading">${iconLoading}</div>
  1305. </div>
  1306. `);
  1307. const promises = [
  1308. fetchTopic(topicId, { signal: abortController.signal }),
  1309. fetchTopicReplies(topicId, { signal: abortController.signal })
  1310. ];
  1311. const [{ result: topic }, { result: topicReplies }] = await Promise.all(promises);
  1312. const data = {
  1313. topic,
  1314. topicReplies,
  1315. cacheTime: Date.now()
  1316. };
  1317. topicDataCache.set(topicId, data);
  1318. cacheData = data;
  1319. } catch (err) {
  1320. if (isV2EX_RequestError(err)) {
  1321. const message = err.cause.message;
  1322. if (
  1323. /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
  1324. message === "Token expired" /* TokenExpired */ || message === "Invalid token" /* InvalidToken */
  1325. ) {
  1326. model.$content.empty().append(invalidTemplate("\u60A8\u7684 PAT \u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u8BBE\u7F6E\u3002"));
  1327. }
  1328. }
  1329. }
  1330. }
  1331. if (cacheData) {
  1332. const { topic, topicReplies } = cacheData;
  1333. const $topicPreview = $('<div class="v2p-topic-preview">');
  1334. $titleLink.prop("href", topic.url);
  1335. if (options.openInNewTab) {
  1336. $titleLink.prop("target", "_blank");
  1337. }
  1338. const $infoBar = $(`
  1339. <div class="v2p-tp-info-bar">
  1340. <div class="v2p-tp-info">
  1341. <a class="v2p-tp-member" href="${topic.member.url}">
  1342. <img class="v2p-tp-avatar" src="${topic.member.avatar}">
  1343. <span>${topic.member.username}</span>
  1344. </a>
  1345.  
  1346. <span>
  1347. ${formatTimestamp(topic.created, { format: "YMDHMS" })}
  1348. </span>
  1349.  
  1350. <span>${topic.replies} \u6761\u56DE\u590D</span>
  1351. </div>
  1352. </div>
  1353. `);
  1354. const iconBook = createElement$1(BookOpenCheck);
  1355. iconBook.setAttribute("width", "100%");
  1356. iconBook.setAttribute("height", "100%");
  1357. const $readingBtn = $(`
  1358. <div class="v2p-tp-read"><span class="v2p-tp-read-icon"></span>\u7A0D\u540E\u9605\u8BFB</div>
  1359. `);
  1360. $readingBtn.find(".v2p-tp-read-icon").append(iconBook);
  1361. $readingBtn.on("click", () => {
  1362. void addToReadingList({
  1363. url: topic.url,
  1364. title: topic.title,
  1365. content: topic.content
  1366. });
  1367. }).appendTo($infoBar);
  1368. $topicPreview.append($infoBar);
  1369. if (topic.content_rendered) {
  1370. $topicPreview.append(
  1371. `<div class="v2p-topic-preview-content markdown_body">${topic.content_rendered}</div>`
  1372. );
  1373. } else {
  1374. $topicPreview.append(`
  1375. <div class="v2p-empty-content">
  1376. <div class="v2p-text-emoji">\xAF\\_(\u30C4)_/\xAF</div>
  1377. <p>\u8BE5\u4E3B\u9898\u6CA1\u6709\u6B63\u6587\u5185\u5BB9</p>
  1378. </div>
  1379. `);
  1380. }
  1381. if (topic.supplements && topic.supplements.length > 0) {
  1382. $topicPreview.append(
  1383. `
  1384. <div class="v2p-topic-preview-addons">
  1385. ${topic.supplements.map((addon, idx) => {
  1386. return `
  1387. <div class="v2p-topic-preview-addon subtle">
  1388. <div class="fade" style="margin-bottom:10px;">\u9644\u8A00 ${idx + 1}\uFF1A</div>
  1389. <div class="topic_content markdown_body">${addon.content_rendered}</div>
  1390. </div>
  1391. `;
  1392. }).join("")}
  1393. </div>
  1394. `
  1395. );
  1396. }
  1397. if (topicReplies.length > 0) {
  1398. const $template = $("<div>");
  1399. const op = topic.member.username;
  1400. topicReplies.forEach((r) => {
  1401. $template.append(`
  1402. <div class="v2p-topic-reply">
  1403. <div class="v2p-topic-reply-member">
  1404. <a href="${r.member.url}">
  1405. <img class="v2p-topic-reply-avatar" src="${r.member.avatar}">
  1406. <span>${r.member.username}</span>
  1407. <span style="
  1408. display: ${op === r.member.username ? "unset" : "none"};
  1409. margin-left:${op === r.member.username ? "3px" : "0"};
  1410. ">
  1411. <span class="badge op mini">OP</span>
  1412. </span>
  1413. </a>\uFF1A
  1414. </div>
  1415. <div class="v2p-topic-reply-content">${escapeHTML(r.content)}</div>
  1416. </div>
  1417. `);
  1418. });
  1419. $('<div class="v2p-topic-reply-box">').append($template.html()).append(
  1420. `
  1421. <div class="v2p-more-reply-tip">
  1422. <a
  1423. href="${linkHref || ""}"
  1424. style="color: currentColor;"
  1425. target="${options.openInNewTab ? "_blank" : "_self"}"
  1426. >
  1427. \u5728\u4E3B\u9898\u5185\u67E5\u770B\u5B8C\u6574\u8BC4\u8BBA...
  1428. </a>
  1429. </div>
  1430. `
  1431. ).appendTo($topicPreview);
  1432. }
  1433. model.$content.empty().append($topicPreview);
  1434. }
  1435. })();
  1436. } else {
  1437. model.$content.empty().append(invalidTemplate("\u60A8\u9700\u8981\u5148\u8BBE\u7F6E PAT \u624D\u80FD\u83B7\u53D6\u9884\u89C8\u5185\u5BB9\u3002"));
  1438. }
  1439. }
  1440. }).appendTo($itemTitle);
  1441. });
  1442. }
  1443. var invalidTemplate;
  1444. var init_topic_list = __esm({
  1445. "src/contents/home/topic-list.ts"() {
  1446. "use strict";
  1447. init_lucide();
  1448. init_button();
  1449. init_model();
  1450. init_constants();
  1451. init_icons();
  1452. init_services();
  1453. init_utils();
  1454. init_globals();
  1455. init_helpers();
  1456. invalidTemplate = (tip) => `
  1457. <div class="v2p-no-pat">
  1458. <div class="v2p-no-pat-title">${tip}</div>
  1459. <div class="v2p-no-pat-desc">
  1460. \u8BF7\u524D\u5F80<span class="v2p-no-pat-block"><span class="v2p-icon-logo">${iconLogo}</span> <span style="margin: 0 5px;">></span> \u8BBE\u7F6E</span> \u8FDB\u884C\u8BBE\u7F6E\u3002
  1461. </div>
  1462.  
  1463. <div class="v2p-no-pat-steps">
  1464. <div class="v2p-no-pat-step">
  1465. <div style="font-weight:bold;margin-bottom:10px;font-size:15px;">1. \u5728\u6269\u5C55\u7A0B\u5E8F\u5217\u8868\u4E2D\u627E\u5230\u5E76\u70B9\u51FB\u300CV2EX Polish\u300D\u3002</div>
  1466. <img class="v2p-no-pat-img" src="https://i.imgur.com/UfNkuTF.png">
  1467. </div>
  1468. <div class="v2p-no-pat-step">
  1469. <div style="font-weight:bold;margin-bottom:10px;font-size:15px;">2. \u5728\u5F39\u51FA\u7684\u5C0F\u7A97\u53E3\u4E2D\u627E\u5230\u300C\u2699\uFE0F \u6309\u94AE\u300D\uFF0C\u8F93\u5165 PAT\u3002</div>
  1470. <img class="v2p-no-pat-img" src="https://i.imgur.com/O6hP86A.png">
  1471. </div>
  1472. </div>
  1473. </div>
  1474. `;
  1475. }
  1476. });
  1477.  
  1478. // src/contents/home/index.ts
  1479. var home_exports = {};
  1480. var init_home = __esm({
  1481. "src/contents/home/index.ts"() {
  1482. "use strict";
  1483. init_constants();
  1484. init_utils();
  1485. init_helpers();
  1486. init_topic_list();
  1487. void (async () => {
  1488. const storage = await getStorage();
  1489. const options = storage["options" /* Options */];
  1490. {
  1491. $("#Main .tab").addClass("v2p-hover-btn");
  1492. if (options.openInNewTab) {
  1493. $('#Main .topic-link, .item_hot_topic_title > a, .item_node, a[href="/write"]').prop(
  1494. "target",
  1495. "_blank"
  1496. );
  1497. }
  1498. }
  1499. handlingTopicList();
  1500. {
  1501. const dailyInfo = storage["daily" /* Daily */];
  1502. if (dailyInfo?.lastCheckInTime) {
  1503. if (isSameDay(dailyInfo.lastCheckInTime, Date.now())) {
  1504. const $info = $(`
  1505. <a class="cell v2p-info-row" href="/mission/daily">
  1506. \u4ECA\u65E5\u5DF2\u81EA\u52A8\u7B7E\u5230
  1507. </a>
  1508. `);
  1509. $('#Rightbar > .box:has("#member-activity")').append($info);
  1510. }
  1511. }
  1512. }
  1513. loadIcons();
  1514. })();
  1515. }
  1516. });
  1517.  
  1518. // node_modules/.pnpm/@floating-ui+core@1.2.6/node_modules/@floating-ui/core/dist/floating-ui.core.mjs
  1519. function getAlignment(placement) {
  1520. return placement.split("-")[1];
  1521. }
  1522. function getLengthFromAxis(axis) {
  1523. return axis === "y" ? "height" : "width";
  1524. }
  1525. function getSide(placement) {
  1526. return placement.split("-")[0];
  1527. }
  1528. function getMainAxisFromPlacement(placement) {
  1529. return ["top", "bottom"].includes(getSide(placement)) ? "x" : "y";
  1530. }
  1531. function computeCoordsFromPlacement(_ref, placement, rtl) {
  1532. let {
  1533. reference,
  1534. floating
  1535. } = _ref;
  1536. const commonX = reference.x + reference.width / 2 - floating.width / 2;
  1537. const commonY = reference.y + reference.height / 2 - floating.height / 2;
  1538. const mainAxis = getMainAxisFromPlacement(placement);
  1539. const length = getLengthFromAxis(mainAxis);
  1540. const commonAlign = reference[length] / 2 - floating[length] / 2;
  1541. const side = getSide(placement);
  1542. const isVertical = mainAxis === "x";
  1543. let coords;
  1544. switch (side) {
  1545. case "top":
  1546. coords = {
  1547. x: commonX,
  1548. y: reference.y - floating.height
  1549. };
  1550. break;
  1551. case "bottom":
  1552. coords = {
  1553. x: commonX,
  1554. y: reference.y + reference.height
  1555. };
  1556. break;
  1557. case "right":
  1558. coords = {
  1559. x: reference.x + reference.width,
  1560. y: commonY
  1561. };
  1562. break;
  1563. case "left":
  1564. coords = {
  1565. x: reference.x - floating.width,
  1566. y: commonY
  1567. };
  1568. break;
  1569. default:
  1570. coords = {
  1571. x: reference.x,
  1572. y: reference.y
  1573. };
  1574. }
  1575. switch (getAlignment(placement)) {
  1576. case "start":
  1577. coords[mainAxis] -= commonAlign * (rtl && isVertical ? -1 : 1);
  1578. break;
  1579. case "end":
  1580. coords[mainAxis] += commonAlign * (rtl && isVertical ? -1 : 1);
  1581. break;
  1582. }
  1583. return coords;
  1584. }
  1585. function expandPaddingObject(padding) {
  1586. return {
  1587. top: 0,
  1588. right: 0,
  1589. bottom: 0,
  1590. left: 0,
  1591. ...padding
  1592. };
  1593. }
  1594. function getSideObjectFromPadding(padding) {
  1595. return typeof padding !== "number" ? expandPaddingObject(padding) : {
  1596. top: padding,
  1597. right: padding,
  1598. bottom: padding,
  1599. left: padding
  1600. };
  1601. }
  1602. function rectToClientRect(rect) {
  1603. return {
  1604. ...rect,
  1605. top: rect.y,
  1606. left: rect.x,
  1607. right: rect.x + rect.width,
  1608. bottom: rect.y + rect.height
  1609. };
  1610. }
  1611. async function detectOverflow(state, options) {
  1612. var _await$platform$isEle;
  1613. if (options === void 0) {
  1614. options = {};
  1615. }
  1616. const {
  1617. x,
  1618. y,
  1619. platform: platform2,
  1620. rects,
  1621. elements,
  1622. strategy
  1623. } = state;
  1624. const {
  1625. boundary = "clippingAncestors",
  1626. rootBoundary = "viewport",
  1627. elementContext = "floating",
  1628. altBoundary = false,
  1629. padding = 0
  1630. } = options;
  1631. const paddingObject = getSideObjectFromPadding(padding);
  1632. const altContext = elementContext === "floating" ? "reference" : "floating";
  1633. const element = elements[altBoundary ? altContext : elementContext];
  1634. const clippingClientRect = rectToClientRect(await platform2.getClippingRect({
  1635. element: ((_await$platform$isEle = await (platform2.isElement == null ? void 0 : platform2.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || await (platform2.getDocumentElement == null ? void 0 : platform2.getDocumentElement(elements.floating)),
  1636. boundary,
  1637. rootBoundary,
  1638. strategy
  1639. }));
  1640. const rect = elementContext === "floating" ? {
  1641. ...rects.floating,
  1642. x,
  1643. y
  1644. } : rects.reference;
  1645. const offsetParent = await (platform2.getOffsetParent == null ? void 0 : platform2.getOffsetParent(elements.floating));
  1646. const offsetScale = await (platform2.isElement == null ? void 0 : platform2.isElement(offsetParent)) ? await (platform2.getScale == null ? void 0 : platform2.getScale(offsetParent)) || {
  1647. x: 1,
  1648. y: 1
  1649. } : {
  1650. x: 1,
  1651. y: 1
  1652. };
  1653. const elementClientRect = rectToClientRect(platform2.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform2.convertOffsetParentRelativeRectToViewportRelativeRect({
  1654. rect,
  1655. offsetParent,
  1656. strategy
  1657. }) : rect);
  1658. return {
  1659. top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
  1660. bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
  1661. left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
  1662. right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
  1663. };
  1664. }
  1665. function within(min$1, value, max$1) {
  1666. return max(min$1, min(value, max$1));
  1667. }
  1668. function getOppositePlacement(placement) {
  1669. return placement.replace(/left|right|bottom|top/g, (side) => oppositeSideMap[side]);
  1670. }
  1671. function getAlignmentSides(placement, rects, rtl) {
  1672. if (rtl === void 0) {
  1673. rtl = false;
  1674. }
  1675. const alignment = getAlignment(placement);
  1676. const mainAxis = getMainAxisFromPlacement(placement);
  1677. const length = getLengthFromAxis(mainAxis);
  1678. let mainAlignmentSide = mainAxis === "x" ? alignment === (rtl ? "end" : "start") ? "right" : "left" : alignment === "start" ? "bottom" : "top";
  1679. if (rects.reference[length] > rects.floating[length]) {
  1680. mainAlignmentSide = getOppositePlacement(mainAlignmentSide);
  1681. }
  1682. return {
  1683. main: mainAlignmentSide,
  1684. cross: getOppositePlacement(mainAlignmentSide)
  1685. };
  1686. }
  1687. function getOppositeAlignmentPlacement(placement) {
  1688. return placement.replace(/start|end/g, (alignment) => oppositeAlignmentMap[alignment]);
  1689. }
  1690. function getExpandedPlacements(placement) {
  1691. const oppositePlacement = getOppositePlacement(placement);
  1692. return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)];
  1693. }
  1694. function getSideList(side, isStart, rtl) {
  1695. const lr = ["left", "right"];
  1696. const rl = ["right", "left"];
  1697. const tb = ["top", "bottom"];
  1698. const bt = ["bottom", "top"];
  1699. switch (side) {
  1700. case "top":
  1701. case "bottom":
  1702. if (rtl)
  1703. return isStart ? rl : lr;
  1704. return isStart ? lr : rl;
  1705. case "left":
  1706. case "right":
  1707. return isStart ? tb : bt;
  1708. default:
  1709. return [];
  1710. }
  1711. }
  1712. function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) {
  1713. const alignment = getAlignment(placement);
  1714. let list = getSideList(getSide(placement), direction === "start", rtl);
  1715. if (alignment) {
  1716. list = list.map((side) => side + "-" + alignment);
  1717. if (flipAlignment) {
  1718. list = list.concat(list.map(getOppositeAlignmentPlacement));
  1719. }
  1720. }
  1721. return list;
  1722. }
  1723. async function convertValueToCoords(state, value) {
  1724. const {
  1725. placement,
  1726. platform: platform2,
  1727. elements
  1728. } = state;
  1729. const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating));
  1730. const side = getSide(placement);
  1731. const alignment = getAlignment(placement);
  1732. const isVertical = getMainAxisFromPlacement(placement) === "x";
  1733. const mainAxisMulti = ["left", "top"].includes(side) ? -1 : 1;
  1734. const crossAxisMulti = rtl && isVertical ? -1 : 1;
  1735. const rawValue = typeof value === "function" ? value(state) : value;
  1736. let {
  1737. mainAxis,
  1738. crossAxis,
  1739. alignmentAxis
  1740. } = typeof rawValue === "number" ? {
  1741. mainAxis: rawValue,
  1742. crossAxis: 0,
  1743. alignmentAxis: null
  1744. } : {
  1745. mainAxis: 0,
  1746. crossAxis: 0,
  1747. alignmentAxis: null,
  1748. ...rawValue
  1749. };
  1750. if (alignment && typeof alignmentAxis === "number") {
  1751. crossAxis = alignment === "end" ? alignmentAxis * -1 : alignmentAxis;
  1752. }
  1753. return isVertical ? {
  1754. x: crossAxis * crossAxisMulti,
  1755. y: mainAxis * mainAxisMulti
  1756. } : {
  1757. x: mainAxis * mainAxisMulti,
  1758. y: crossAxis * crossAxisMulti
  1759. };
  1760. }
  1761. function getCrossAxis(axis) {
  1762. return axis === "x" ? "y" : "x";
  1763. }
  1764. var computePosition, min, max, oppositeSideMap, oppositeAlignmentMap, flip, offset, shift;
  1765. var init_floating_ui_core = __esm({
  1766. "node_modules/.pnpm/@floating-ui+core@1.2.6/node_modules/@floating-ui/core/dist/floating-ui.core.mjs"() {
  1767. computePosition = async (reference, floating, config) => {
  1768. const {
  1769. placement = "bottom",
  1770. strategy = "absolute",
  1771. middleware = [],
  1772. platform: platform2
  1773. } = config;
  1774. const validMiddleware = middleware.filter(Boolean);
  1775. const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(floating));
  1776. let rects = await platform2.getElementRects({
  1777. reference,
  1778. floating,
  1779. strategy
  1780. });
  1781. let {
  1782. x,
  1783. y
  1784. } = computeCoordsFromPlacement(rects, placement, rtl);
  1785. let statefulPlacement = placement;
  1786. let middlewareData = {};
  1787. let resetCount = 0;
  1788. for (let i = 0; i < validMiddleware.length; i++) {
  1789. const {
  1790. name,
  1791. fn
  1792. } = validMiddleware[i];
  1793. const {
  1794. x: nextX,
  1795. y: nextY,
  1796. data,
  1797. reset
  1798. } = await fn({
  1799. x,
  1800. y,
  1801. initialPlacement: placement,
  1802. placement: statefulPlacement,
  1803. strategy,
  1804. middlewareData,
  1805. rects,
  1806. platform: platform2,
  1807. elements: {
  1808. reference,
  1809. floating
  1810. }
  1811. });
  1812. x = nextX != null ? nextX : x;
  1813. y = nextY != null ? nextY : y;
  1814. middlewareData = {
  1815. ...middlewareData,
  1816. [name]: {
  1817. ...middlewareData[name],
  1818. ...data
  1819. }
  1820. };
  1821. if (reset && resetCount <= 50) {
  1822. resetCount++;
  1823. if (typeof reset === "object") {
  1824. if (reset.placement) {
  1825. statefulPlacement = reset.placement;
  1826. }
  1827. if (reset.rects) {
  1828. rects = reset.rects === true ? await platform2.getElementRects({
  1829. reference,
  1830. floating,
  1831. strategy
  1832. }) : reset.rects;
  1833. }
  1834. ({
  1835. x,
  1836. y
  1837. } = computeCoordsFromPlacement(rects, statefulPlacement, rtl));
  1838. }
  1839. i = -1;
  1840. continue;
  1841. }
  1842. }
  1843. return {
  1844. x,
  1845. y,
  1846. placement: statefulPlacement,
  1847. strategy,
  1848. middlewareData
  1849. };
  1850. };
  1851. min = Math.min;
  1852. max = Math.max;
  1853. oppositeSideMap = {
  1854. left: "right",
  1855. right: "left",
  1856. bottom: "top",
  1857. top: "bottom"
  1858. };
  1859. oppositeAlignmentMap = {
  1860. start: "end",
  1861. end: "start"
  1862. };
  1863. flip = function(options) {
  1864. if (options === void 0) {
  1865. options = {};
  1866. }
  1867. return {
  1868. name: "flip",
  1869. options,
  1870. async fn(state) {
  1871. var _middlewareData$flip;
  1872. const {
  1873. placement,
  1874. middlewareData,
  1875. rects,
  1876. initialPlacement,
  1877. platform: platform2,
  1878. elements
  1879. } = state;
  1880. const {
  1881. mainAxis: checkMainAxis = true,
  1882. crossAxis: checkCrossAxis = true,
  1883. fallbackPlacements: specifiedFallbackPlacements,
  1884. fallbackStrategy = "bestFit",
  1885. fallbackAxisSideDirection = "none",
  1886. flipAlignment = true,
  1887. ...detectOverflowOptions
  1888. } = options;
  1889. const side = getSide(placement);
  1890. const isBasePlacement = getSide(initialPlacement) === initialPlacement;
  1891. const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating));
  1892. const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement));
  1893. if (!specifiedFallbackPlacements && fallbackAxisSideDirection !== "none") {
  1894. fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));
  1895. }
  1896. const placements = [initialPlacement, ...fallbackPlacements];
  1897. const overflow = await detectOverflow(state, detectOverflowOptions);
  1898. const overflows = [];
  1899. let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];
  1900. if (checkMainAxis) {
  1901. overflows.push(overflow[side]);
  1902. }
  1903. if (checkCrossAxis) {
  1904. const {
  1905. main,
  1906. cross
  1907. } = getAlignmentSides(placement, rects, rtl);
  1908. overflows.push(overflow[main], overflow[cross]);
  1909. }
  1910. overflowsData = [...overflowsData, {
  1911. placement,
  1912. overflows
  1913. }];
  1914. if (!overflows.every((side2) => side2 <= 0)) {
  1915. var _middlewareData$flip2, _overflowsData$filter;
  1916. const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;
  1917. const nextPlacement = placements[nextIndex];
  1918. if (nextPlacement) {
  1919. return {
  1920. data: {
  1921. index: nextIndex,
  1922. overflows: overflowsData
  1923. },
  1924. reset: {
  1925. placement: nextPlacement
  1926. }
  1927. };
  1928. }
  1929. let resetPlacement = (_overflowsData$filter = overflowsData.filter((d) => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement;
  1930. if (!resetPlacement) {
  1931. switch (fallbackStrategy) {
  1932. case "bestFit": {
  1933. var _overflowsData$map$so;
  1934. const placement2 = (_overflowsData$map$so = overflowsData.map((d) => [d.placement, d.overflows.filter((overflow2) => overflow2 > 0).reduce((acc, overflow2) => acc + overflow2, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$map$so[0];
  1935. if (placement2) {
  1936. resetPlacement = placement2;
  1937. }
  1938. break;
  1939. }
  1940. case "initialPlacement":
  1941. resetPlacement = initialPlacement;
  1942. break;
  1943. }
  1944. }
  1945. if (placement !== resetPlacement) {
  1946. return {
  1947. reset: {
  1948. placement: resetPlacement
  1949. }
  1950. };
  1951. }
  1952. }
  1953. return {};
  1954. }
  1955. };
  1956. };
  1957. offset = function(value) {
  1958. if (value === void 0) {
  1959. value = 0;
  1960. }
  1961. return {
  1962. name: "offset",
  1963. options: value,
  1964. async fn(state) {
  1965. const {
  1966. x,
  1967. y
  1968. } = state;
  1969. const diffCoords = await convertValueToCoords(state, value);
  1970. return {
  1971. x: x + diffCoords.x,
  1972. y: y + diffCoords.y,
  1973. data: diffCoords
  1974. };
  1975. }
  1976. };
  1977. };
  1978. shift = function(options) {
  1979. if (options === void 0) {
  1980. options = {};
  1981. }
  1982. return {
  1983. name: "shift",
  1984. options,
  1985. async fn(state) {
  1986. const {
  1987. x,
  1988. y,
  1989. placement
  1990. } = state;
  1991. const {
  1992. mainAxis: checkMainAxis = true,
  1993. crossAxis: checkCrossAxis = false,
  1994. limiter = {
  1995. fn: (_ref) => {
  1996. let {
  1997. x: x2,
  1998. y: y2
  1999. } = _ref;
  2000. return {
  2001. x: x2,
  2002. y: y2
  2003. };
  2004. }
  2005. },
  2006. ...detectOverflowOptions
  2007. } = options;
  2008. const coords = {
  2009. x,
  2010. y
  2011. };
  2012. const overflow = await detectOverflow(state, detectOverflowOptions);
  2013. const mainAxis = getMainAxisFromPlacement(getSide(placement));
  2014. const crossAxis = getCrossAxis(mainAxis);
  2015. let mainAxisCoord = coords[mainAxis];
  2016. let crossAxisCoord = coords[crossAxis];
  2017. if (checkMainAxis) {
  2018. const minSide = mainAxis === "y" ? "top" : "left";
  2019. const maxSide = mainAxis === "y" ? "bottom" : "right";
  2020. const min3 = mainAxisCoord + overflow[minSide];
  2021. const max3 = mainAxisCoord - overflow[maxSide];
  2022. mainAxisCoord = within(min3, mainAxisCoord, max3);
  2023. }
  2024. if (checkCrossAxis) {
  2025. const minSide = crossAxis === "y" ? "top" : "left";
  2026. const maxSide = crossAxis === "y" ? "bottom" : "right";
  2027. const min3 = crossAxisCoord + overflow[minSide];
  2028. const max3 = crossAxisCoord - overflow[maxSide];
  2029. crossAxisCoord = within(min3, crossAxisCoord, max3);
  2030. }
  2031. const limitedCoords = limiter.fn({
  2032. ...state,
  2033. [mainAxis]: mainAxisCoord,
  2034. [crossAxis]: crossAxisCoord
  2035. });
  2036. return {
  2037. ...limitedCoords,
  2038. data: {
  2039. x: limitedCoords.x - x,
  2040. y: limitedCoords.y - y
  2041. }
  2042. };
  2043. }
  2044. };
  2045. };
  2046. }
  2047. });
  2048.  
  2049. // node_modules/.pnpm/@floating-ui+dom@1.2.1/node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs
  2050. function getWindow(node) {
  2051. var _node$ownerDocument;
  2052. return ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
  2053. }
  2054. function getComputedStyle$1(element) {
  2055. return getWindow(element).getComputedStyle(element);
  2056. }
  2057. function getCssDimensions(element) {
  2058. const css = getComputedStyle$1(element);
  2059. let width = parseFloat(css.width);
  2060. let height = parseFloat(css.height);
  2061. const offsetWidth = element.offsetWidth;
  2062. const offsetHeight = element.offsetHeight;
  2063. const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;
  2064. if (shouldFallback) {
  2065. width = offsetWidth;
  2066. height = offsetHeight;
  2067. }
  2068. return {
  2069. width,
  2070. height,
  2071. fallback: shouldFallback
  2072. };
  2073. }
  2074. function getNodeName(node) {
  2075. return isNode(node) ? (node.nodeName || "").toLowerCase() : "";
  2076. }
  2077. function getUAString() {
  2078. if (uaString) {
  2079. return uaString;
  2080. }
  2081. const uaData = navigator.userAgentData;
  2082. if (uaData && Array.isArray(uaData.brands)) {
  2083. uaString = uaData.brands.map((item) => item.brand + "/" + item.version).join(" ");
  2084. return uaString;
  2085. }
  2086. return navigator.userAgent;
  2087. }
  2088. function isHTMLElement(value) {
  2089. return value instanceof getWindow(value).HTMLElement;
  2090. }
  2091. function isElement(value) {
  2092. return value instanceof getWindow(value).Element;
  2093. }
  2094. function isNode(value) {
  2095. return value instanceof getWindow(value).Node;
  2096. }
  2097. function isShadowRoot(node) {
  2098. if (typeof ShadowRoot === "undefined") {
  2099. return false;
  2100. }
  2101. const OwnElement = getWindow(node).ShadowRoot;
  2102. return node instanceof OwnElement || node instanceof ShadowRoot;
  2103. }
  2104. function isOverflowElement(element) {
  2105. const {
  2106. overflow,
  2107. overflowX,
  2108. overflowY,
  2109. display
  2110. } = getComputedStyle$1(element);
  2111. return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display);
  2112. }
  2113. function isTableElement(element) {
  2114. return ["table", "td", "th"].includes(getNodeName(element));
  2115. }
  2116. function isContainingBlock(element) {
  2117. const isFirefox2 = /firefox/i.test(getUAString());
  2118. const css = getComputedStyle$1(element);
  2119. const backdropFilter = css.backdropFilter || css.WebkitBackdropFilter;
  2120. return css.transform !== "none" || css.perspective !== "none" || (backdropFilter ? backdropFilter !== "none" : false) || isFirefox2 && css.willChange === "filter" || isFirefox2 && (css.filter ? css.filter !== "none" : false) || ["transform", "perspective"].some((value) => css.willChange.includes(value)) || ["paint", "layout", "strict", "content"].some((value) => {
  2121. const contain = css.contain;
  2122. return contain != null ? contain.includes(value) : false;
  2123. });
  2124. }
  2125. function isClientRectVisualViewportBased() {
  2126. return /^((?!chrome|android).)*safari/i.test(getUAString());
  2127. }
  2128. function isLastTraversableNode(node) {
  2129. return ["html", "body", "#document"].includes(getNodeName(node));
  2130. }
  2131. function unwrapElement(element) {
  2132. return !isElement(element) ? element.contextElement : element;
  2133. }
  2134. function getScale(element) {
  2135. const domElement = unwrapElement(element);
  2136. if (!isHTMLElement(domElement)) {
  2137. return FALLBACK_SCALE;
  2138. }
  2139. const rect = domElement.getBoundingClientRect();
  2140. const {
  2141. width,
  2142. height,
  2143. fallback
  2144. } = getCssDimensions(domElement);
  2145. let x = (fallback ? round(rect.width) : rect.width) / width;
  2146. let y = (fallback ? round(rect.height) : rect.height) / height;
  2147. if (!x || !Number.isFinite(x)) {
  2148. x = 1;
  2149. }
  2150. if (!y || !Number.isFinite(y)) {
  2151. y = 1;
  2152. }
  2153. return {
  2154. x,
  2155. y
  2156. };
  2157. }
  2158. function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {
  2159. var _win$visualViewport, _win$visualViewport2;
  2160. if (includeScale === void 0) {
  2161. includeScale = false;
  2162. }
  2163. if (isFixedStrategy === void 0) {
  2164. isFixedStrategy = false;
  2165. }
  2166. const clientRect = element.getBoundingClientRect();
  2167. const domElement = unwrapElement(element);
  2168. let scale = FALLBACK_SCALE;
  2169. if (includeScale) {
  2170. if (offsetParent) {
  2171. if (isElement(offsetParent)) {
  2172. scale = getScale(offsetParent);
  2173. }
  2174. } else {
  2175. scale = getScale(element);
  2176. }
  2177. }
  2178. const win = domElement ? getWindow(domElement) : window;
  2179. const addVisualOffsets = isClientRectVisualViewportBased() && isFixedStrategy;
  2180. let x = (clientRect.left + (addVisualOffsets ? ((_win$visualViewport = win.visualViewport) == null ? void 0 : _win$visualViewport.offsetLeft) || 0 : 0)) / scale.x;
  2181. let y = (clientRect.top + (addVisualOffsets ? ((_win$visualViewport2 = win.visualViewport) == null ? void 0 : _win$visualViewport2.offsetTop) || 0 : 0)) / scale.y;
  2182. let width = clientRect.width / scale.x;
  2183. let height = clientRect.height / scale.y;
  2184. if (domElement) {
  2185. const win2 = getWindow(domElement);
  2186. const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent;
  2187. let currentIFrame = win2.frameElement;
  2188. while (currentIFrame && offsetParent && offsetWin !== win2) {
  2189. const iframeScale = getScale(currentIFrame);
  2190. const iframeRect = currentIFrame.getBoundingClientRect();
  2191. const css = getComputedStyle(currentIFrame);
  2192. iframeRect.x += (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
  2193. iframeRect.y += (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
  2194. x *= iframeScale.x;
  2195. y *= iframeScale.y;
  2196. width *= iframeScale.x;
  2197. height *= iframeScale.y;
  2198. x += iframeRect.x;
  2199. y += iframeRect.y;
  2200. currentIFrame = getWindow(currentIFrame).frameElement;
  2201. }
  2202. }
  2203. return {
  2204. width,
  2205. height,
  2206. top: y,
  2207. right: x + width,
  2208. bottom: y + height,
  2209. left: x,
  2210. x,
  2211. y
  2212. };
  2213. }
  2214. function getDocumentElement(node) {
  2215. return ((isNode(node) ? node.ownerDocument : node.document) || window.document).documentElement;
  2216. }
  2217. function getNodeScroll(element) {
  2218. if (isElement(element)) {
  2219. return {
  2220. scrollLeft: element.scrollLeft,
  2221. scrollTop: element.scrollTop
  2222. };
  2223. }
  2224. return {
  2225. scrollLeft: element.pageXOffset,
  2226. scrollTop: element.pageYOffset
  2227. };
  2228. }
  2229. function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
  2230. let {
  2231. rect,
  2232. offsetParent,
  2233. strategy
  2234. } = _ref;
  2235. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  2236. const documentElement = getDocumentElement(offsetParent);
  2237. if (offsetParent === documentElement) {
  2238. return rect;
  2239. }
  2240. let scroll = {
  2241. scrollLeft: 0,
  2242. scrollTop: 0
  2243. };
  2244. let scale = {
  2245. x: 1,
  2246. y: 1
  2247. };
  2248. const offsets = {
  2249. x: 0,
  2250. y: 0
  2251. };
  2252. if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== "fixed") {
  2253. if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) {
  2254. scroll = getNodeScroll(offsetParent);
  2255. }
  2256. if (isHTMLElement(offsetParent)) {
  2257. const offsetRect = getBoundingClientRect(offsetParent);
  2258. scale = getScale(offsetParent);
  2259. offsets.x = offsetRect.x + offsetParent.clientLeft;
  2260. offsets.y = offsetRect.y + offsetParent.clientTop;
  2261. }
  2262. }
  2263. return {
  2264. width: rect.width * scale.x,
  2265. height: rect.height * scale.y,
  2266. x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x,
  2267. y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y
  2268. };
  2269. }
  2270. function getWindowScrollBarX(element) {
  2271. return getBoundingClientRect(getDocumentElement(element)).left + getNodeScroll(element).scrollLeft;
  2272. }
  2273. function getDocumentRect(element) {
  2274. const html = getDocumentElement(element);
  2275. const scroll = getNodeScroll(element);
  2276. const body = element.ownerDocument.body;
  2277. const width = max2(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth);
  2278. const height = max2(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
  2279. let x = -scroll.scrollLeft + getWindowScrollBarX(element);
  2280. const y = -scroll.scrollTop;
  2281. if (getComputedStyle$1(body).direction === "rtl") {
  2282. x += max2(html.clientWidth, body.clientWidth) - width;
  2283. }
  2284. return {
  2285. width,
  2286. height,
  2287. x,
  2288. y
  2289. };
  2290. }
  2291. function getParentNode(node) {
  2292. if (getNodeName(node) === "html") {
  2293. return node;
  2294. }
  2295. const result = (
  2296. // Step into the shadow DOM of the parent of a slotted node.
  2297. node.assignedSlot || // DOM Element detected.
  2298. node.parentNode || // ShadowRoot detected.
  2299. isShadowRoot(node) && node.host || // Fallback.
  2300. getDocumentElement(node)
  2301. );
  2302. return isShadowRoot(result) ? result.host : result;
  2303. }
  2304. function getNearestOverflowAncestor(node) {
  2305. const parentNode = getParentNode(node);
  2306. if (isLastTraversableNode(parentNode)) {
  2307. return parentNode.ownerDocument.body;
  2308. }
  2309. if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
  2310. return parentNode;
  2311. }
  2312. return getNearestOverflowAncestor(parentNode);
  2313. }
  2314. function getOverflowAncestors(node, list) {
  2315. var _node$ownerDocument;
  2316. if (list === void 0) {
  2317. list = [];
  2318. }
  2319. const scrollableAncestor = getNearestOverflowAncestor(node);
  2320. const isBody = scrollableAncestor === ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.body);
  2321. const win = getWindow(scrollableAncestor);
  2322. if (isBody) {
  2323. return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []);
  2324. }
  2325. return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor));
  2326. }
  2327. function getViewportRect(element, strategy) {
  2328. const win = getWindow(element);
  2329. const html = getDocumentElement(element);
  2330. const visualViewport = win.visualViewport;
  2331. let width = html.clientWidth;
  2332. let height = html.clientHeight;
  2333. let x = 0;
  2334. let y = 0;
  2335. if (visualViewport) {
  2336. width = visualViewport.width;
  2337. height = visualViewport.height;
  2338. const visualViewportBased = isClientRectVisualViewportBased();
  2339. if (!visualViewportBased || visualViewportBased && strategy === "fixed") {
  2340. x = visualViewport.offsetLeft;
  2341. y = visualViewport.offsetTop;
  2342. }
  2343. }
  2344. return {
  2345. width,
  2346. height,
  2347. x,
  2348. y
  2349. };
  2350. }
  2351. function getInnerBoundingClientRect(element, strategy) {
  2352. const clientRect = getBoundingClientRect(element, true, strategy === "fixed");
  2353. const top = clientRect.top + element.clientTop;
  2354. const left = clientRect.left + element.clientLeft;
  2355. const scale = isHTMLElement(element) ? getScale(element) : {
  2356. x: 1,
  2357. y: 1
  2358. };
  2359. const width = element.clientWidth * scale.x;
  2360. const height = element.clientHeight * scale.y;
  2361. const x = left * scale.x;
  2362. const y = top * scale.y;
  2363. return {
  2364. width,
  2365. height,
  2366. x,
  2367. y
  2368. };
  2369. }
  2370. function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) {
  2371. let rect;
  2372. if (clippingAncestor === "viewport") {
  2373. rect = getViewportRect(element, strategy);
  2374. } else if (clippingAncestor === "document") {
  2375. rect = getDocumentRect(getDocumentElement(element));
  2376. } else if (isElement(clippingAncestor)) {
  2377. rect = getInnerBoundingClientRect(clippingAncestor, strategy);
  2378. } else {
  2379. const mutableRect = {
  2380. ...clippingAncestor
  2381. };
  2382. if (isClientRectVisualViewportBased()) {
  2383. var _win$visualViewport, _win$visualViewport2;
  2384. const win = getWindow(element);
  2385. mutableRect.x -= ((_win$visualViewport = win.visualViewport) == null ? void 0 : _win$visualViewport.offsetLeft) || 0;
  2386. mutableRect.y -= ((_win$visualViewport2 = win.visualViewport) == null ? void 0 : _win$visualViewport2.offsetTop) || 0;
  2387. }
  2388. rect = mutableRect;
  2389. }
  2390. return rectToClientRect(rect);
  2391. }
  2392. function getClippingElementAncestors(element, cache) {
  2393. const cachedResult = cache.get(element);
  2394. if (cachedResult) {
  2395. return cachedResult;
  2396. }
  2397. let result = getOverflowAncestors(element).filter((el) => isElement(el) && getNodeName(el) !== "body");
  2398. let currentContainingBlockComputedStyle = null;
  2399. const elementIsFixed = getComputedStyle$1(element).position === "fixed";
  2400. let currentNode = elementIsFixed ? getParentNode(element) : element;
  2401. while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
  2402. const computedStyle = getComputedStyle$1(currentNode);
  2403. const containingBlock = isContainingBlock(currentNode);
  2404. const shouldIgnoreCurrentNode = computedStyle.position === "fixed";
  2405. if (shouldIgnoreCurrentNode) {
  2406. currentContainingBlockComputedStyle = null;
  2407. } else {
  2408. const shouldDropCurrentNode = elementIsFixed ? !containingBlock && !currentContainingBlockComputedStyle : !containingBlock && computedStyle.position === "static" && !!currentContainingBlockComputedStyle && ["absolute", "fixed"].includes(currentContainingBlockComputedStyle.position);
  2409. if (shouldDropCurrentNode) {
  2410. result = result.filter((ancestor) => ancestor !== currentNode);
  2411. } else {
  2412. currentContainingBlockComputedStyle = computedStyle;
  2413. }
  2414. }
  2415. currentNode = getParentNode(currentNode);
  2416. }
  2417. cache.set(element, result);
  2418. return result;
  2419. }
  2420. function getClippingRect(_ref) {
  2421. let {
  2422. element,
  2423. boundary,
  2424. rootBoundary,
  2425. strategy
  2426. } = _ref;
  2427. const elementClippingAncestors = boundary === "clippingAncestors" ? getClippingElementAncestors(element, this._c) : [].concat(boundary);
  2428. const clippingAncestors = [...elementClippingAncestors, rootBoundary];
  2429. const firstClippingAncestor = clippingAncestors[0];
  2430. const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
  2431. const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);
  2432. accRect.top = max2(rect.top, accRect.top);
  2433. accRect.right = min2(rect.right, accRect.right);
  2434. accRect.bottom = min2(rect.bottom, accRect.bottom);
  2435. accRect.left = max2(rect.left, accRect.left);
  2436. return accRect;
  2437. }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
  2438. return {
  2439. width: clippingRect.right - clippingRect.left,
  2440. height: clippingRect.bottom - clippingRect.top,
  2441. x: clippingRect.left,
  2442. y: clippingRect.top
  2443. };
  2444. }
  2445. function getDimensions(element) {
  2446. if (isHTMLElement(element)) {
  2447. return getCssDimensions(element);
  2448. }
  2449. return element.getBoundingClientRect();
  2450. }
  2451. function getTrueOffsetParent(element, polyfill) {
  2452. if (!isHTMLElement(element) || getComputedStyle$1(element).position === "fixed") {
  2453. return null;
  2454. }
  2455. if (polyfill) {
  2456. return polyfill(element);
  2457. }
  2458. return element.offsetParent;
  2459. }
  2460. function getContainingBlock(element) {
  2461. let currentNode = getParentNode(element);
  2462. while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
  2463. if (isContainingBlock(currentNode)) {
  2464. return currentNode;
  2465. } else {
  2466. currentNode = getParentNode(currentNode);
  2467. }
  2468. }
  2469. return null;
  2470. }
  2471. function getOffsetParent(element, polyfill) {
  2472. const window2 = getWindow(element);
  2473. let offsetParent = getTrueOffsetParent(element, polyfill);
  2474. while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === "static") {
  2475. offsetParent = getTrueOffsetParent(offsetParent, polyfill);
  2476. }
  2477. if (offsetParent && (getNodeName(offsetParent) === "html" || getNodeName(offsetParent) === "body" && getComputedStyle$1(offsetParent).position === "static" && !isContainingBlock(offsetParent))) {
  2478. return window2;
  2479. }
  2480. return offsetParent || getContainingBlock(element) || window2;
  2481. }
  2482. function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
  2483. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  2484. const documentElement = getDocumentElement(offsetParent);
  2485. const rect = getBoundingClientRect(element, true, strategy === "fixed", offsetParent);
  2486. let scroll = {
  2487. scrollLeft: 0,
  2488. scrollTop: 0
  2489. };
  2490. const offsets = {
  2491. x: 0,
  2492. y: 0
  2493. };
  2494. if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== "fixed") {
  2495. if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) {
  2496. scroll = getNodeScroll(offsetParent);
  2497. }
  2498. if (isHTMLElement(offsetParent)) {
  2499. const offsetRect = getBoundingClientRect(offsetParent, true);
  2500. offsets.x = offsetRect.x + offsetParent.clientLeft;
  2501. offsets.y = offsetRect.y + offsetParent.clientTop;
  2502. } else if (documentElement) {
  2503. offsets.x = getWindowScrollBarX(documentElement);
  2504. }
  2505. }
  2506. return {
  2507. x: rect.left + scroll.scrollLeft - offsets.x,
  2508. y: rect.top + scroll.scrollTop - offsets.y,
  2509. width: rect.width,
  2510. height: rect.height
  2511. };
  2512. }
  2513. var min2, max2, round, uaString, FALLBACK_SCALE, platform, computePosition2;
  2514. var init_floating_ui_dom = __esm({
  2515. "node_modules/.pnpm/@floating-ui+dom@1.2.1/node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs"() {
  2516. init_floating_ui_core();
  2517. init_floating_ui_core();
  2518. min2 = Math.min;
  2519. max2 = Math.max;
  2520. round = Math.round;
  2521. FALLBACK_SCALE = {
  2522. x: 1,
  2523. y: 1
  2524. };
  2525. platform = {
  2526. getClippingRect,
  2527. convertOffsetParentRelativeRectToViewportRelativeRect,
  2528. isElement,
  2529. getDimensions,
  2530. getOffsetParent,
  2531. getDocumentElement,
  2532. getScale,
  2533. async getElementRects(_ref) {
  2534. let {
  2535. reference,
  2536. floating,
  2537. strategy
  2538. } = _ref;
  2539. const getOffsetParentFn = this.getOffsetParent || getOffsetParent;
  2540. const getDimensionsFn = this.getDimensions;
  2541. return {
  2542. reference: getRectRelativeToOffsetParent(reference, await getOffsetParentFn(floating), strategy),
  2543. floating: {
  2544. x: 0,
  2545. y: 0,
  2546. ...await getDimensionsFn(floating)
  2547. }
  2548. };
  2549. },
  2550. getClientRects: (element) => Array.from(element.getClientRects()),
  2551. isRTL: (element) => getComputedStyle$1(element).direction === "rtl"
  2552. };
  2553. computePosition2 = (reference, floating, options) => {
  2554. const cache = /* @__PURE__ */ new Map();
  2555. const mergedOptions = {
  2556. platform,
  2557. ...options
  2558. };
  2559. const platformWithCache = {
  2560. ...mergedOptions.platform,
  2561. _c: cache
  2562. };
  2563. return computePosition(reference, floating, {
  2564. ...mergedOptions,
  2565. platform: platformWithCache
  2566. });
  2567. };
  2568. }
  2569. });
  2570.  
  2571. // src/components/popup.ts
  2572. function createPopup(props) {
  2573. const {
  2574. root,
  2575. trigger,
  2576. triggerType = "click",
  2577. content,
  2578. options,
  2579. onOpen,
  2580. onClose,
  2581. placement = "bottom-start",
  2582. offsetOptions = { mainAxis: 5, crossAxis: 5 }
  2583. } = props;
  2584. const $popupContent = $('<div class="v2p-popup-content">');
  2585. const $popup = $('<div class="v2p-popup" tabindex="0">').css("visibility", "hidden").append($popupContent);
  2586. root.append($popup);
  2587. if (content) {
  2588. $popup.append(content);
  2589. }
  2590. const popup = $popup.get(0);
  2591. const handleClickOutside = (ev) => {
  2592. if ($(ev.target).closest(popup).length === 0) {
  2593. handlePopupClose();
  2594. }
  2595. };
  2596. const handlePopupClose = () => {
  2597. $popup.css("visibility", "hidden");
  2598. $(document).off("click", handleClickOutside);
  2599. onClose?.();
  2600. popupControl.onClose?.();
  2601. };
  2602. const handlePopupOpen = ($reference) => {
  2603. if (!$reference) {
  2604. return;
  2605. }
  2606. setTimeout(() => {
  2607. $(document).on("click", handleClickOutside);
  2608. });
  2609. const referenceElement = $reference.get(0);
  2610. computePosition2(referenceElement, popup, {
  2611. placement,
  2612. middleware: [offset(offsetOptions), flip(), shift({ padding: 8 })],
  2613. ...options
  2614. }).then(({ x, y }) => {
  2615. Object.assign(popup.style, {
  2616. left: `${x}px`,
  2617. top: `${y}px`
  2618. });
  2619. $popup.css("visibility", "visible");
  2620. }).catch(() => {
  2621. handlePopupClose();
  2622. createToast({ message: "\u274C Popup \u6E32\u67D3\u5931\u8D25" });
  2623. });
  2624. onOpen?.();
  2625. };
  2626. const popupControl = {
  2627. $content: $popupContent,
  2628. isOver: false,
  2629. open: (reference) => {
  2630. handlePopupOpen(reference);
  2631. },
  2632. close: handlePopupClose
  2633. };
  2634. if (triggerType === "hover") {
  2635. $popup.on("mouseover", () => {
  2636. if (!popupControl.isOver) {
  2637. popupControl.isOver = true;
  2638. $popup.off("mouseleave").on("mouseleave", () => {
  2639. popupControl.isOver = false;
  2640. setTimeout(() => {
  2641. if (!popupControl.isOver) {
  2642. popupControl.close();
  2643. }
  2644. }, hoverDelay);
  2645. });
  2646. }
  2647. });
  2648. }
  2649. trigger?.on("click", () => {
  2650. if (popup.style.visibility !== "hidden") {
  2651. handlePopupClose();
  2652. } else {
  2653. handlePopupOpen(trigger);
  2654. }
  2655. });
  2656. return popupControl;
  2657. }
  2658. var hoverDelay;
  2659. var init_popup = __esm({
  2660. "src/components/popup.ts"() {
  2661. "use strict";
  2662. init_floating_ui_dom();
  2663. init_toast();
  2664. hoverDelay = 350;
  2665. }
  2666. });
  2667.  
  2668. // src/contents/topic/avatar.ts
  2669. function processAvatar(params) {
  2670. const { $cellDom, popupControl, commentData, onSetTagsClick: onSetTags } = params;
  2671. const { memberName, memberAvatar, memberLink } = commentData;
  2672. let abortController = null;
  2673. const $avatar = $cellDom.find(".avatar");
  2674. const handleOver = () => {
  2675. popupControl.close();
  2676. popupControl.open($avatar);
  2677. const $content = $(`
  2678. <div class="v2p-member-card">
  2679. <div class="v2p-info">
  2680. <div class="v2p-info-left">
  2681. <a class="v2p-avatar-box" href="${memberLink}">
  2682. <img class="v2p-avatar" src="${memberAvatar}">
  2683. </a>
  2684. </div>
  2685.  
  2686. <div class="v2p-info-right">
  2687. <div class="v2p-username">
  2688. <a href="${memberLink}">${memberName}</a>
  2689. </div>
  2690. <div class="v2p-no v2p-loading"></div>
  2691. <div class="v2p-created-date v2p-loading"></div>
  2692. </div>
  2693.  
  2694. </div>
  2695.  
  2696. <div class="v2p-bio" style="disply:none;"></div>
  2697.  
  2698. <div class="v2p-member-card-actions"></div>
  2699. </div>
  2700. `);
  2701. popupControl.$content.empty().append($content);
  2702. createButton({ children: "\u6DFB\u52A0\u7528\u6237\u6807\u7B7E" }).on("click", () => {
  2703. popupControl.close();
  2704. onSetTags?.();
  2705. }).appendTo($(".v2p-member-card-actions"));
  2706. void (async () => {
  2707. if (!memberDataCache.has(memberName)) {
  2708. abortController = new AbortController();
  2709. popupControl.onClose = () => {
  2710. abortController?.abort();
  2711. };
  2712. try {
  2713. const memberData = await fetchUserInfo(memberName, {
  2714. signal: abortController.signal
  2715. });
  2716. memberDataCache.set(memberName, memberData);
  2717. } catch (err) {
  2718. if (err && typeof err === "object" && "name" in err && err.name !== "AbortError") {
  2719. $content.html(`<span>\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25</span>`);
  2720. }
  2721. return null;
  2722. }
  2723. }
  2724. const data = memberDataCache.get(memberName);
  2725. if (data) {
  2726. $content.find(".v2p-no").removeClass("v2p-loading").text(`V2EX \u7B2C ${data.id} \u53F7\u4F1A\u5458`);
  2727. $content.find(".v2p-created-date").removeClass("v2p-loading").text(`\u52A0\u5165\u4E8E ${formatTimestamp(data.created)}`);
  2728. if (data.bio && data.bio.trim().length > 0) {
  2729. $content.find(".v2p-bio").css("disply", "block").text(data.bio);
  2730. }
  2731. }
  2732. })();
  2733. };
  2734. let isOver = false;
  2735. $avatar.on("mouseover", () => {
  2736. isOver = true;
  2737. setTimeout(() => {
  2738. if (isOver) {
  2739. handleOver();
  2740. }
  2741. }, hoverDelay);
  2742. }).on("mouseleave", () => {
  2743. isOver = false;
  2744. setTimeout(() => {
  2745. if (!popupControl.isOver && !isOver) {
  2746. popupControl.close();
  2747. }
  2748. }, hoverDelay);
  2749. }).wrap(`<a href="/member/${commentData.memberName}" style="cursor: pointer;">`);
  2750. }
  2751. var memberDataCache;
  2752. var init_avatar = __esm({
  2753. "src/contents/topic/avatar.ts"() {
  2754. "use strict";
  2755. init_button();
  2756. init_popup();
  2757. init_services();
  2758. init_utils();
  2759. memberDataCache = /* @__PURE__ */ new Map();
  2760. }
  2761. });
  2762.  
  2763. // src/contents/topic/content.ts
  2764. function handlingContent() {
  2765. const storage = getStorageSync();
  2766. const options = storage["options" /* Options */];
  2767. if (options.openInNewTab) {
  2768. $topicContentBox.find(".topic_content a[href]").prop("target", "_blank").prop("rel", "noopener noreferrer");
  2769. }
  2770. {
  2771. const $topicContents = $topicContentBox.find(".subtle > .topic_content");
  2772. const textLength = $topicContents.text().length;
  2773. if (textLength >= 200) {
  2774. $topicContents.each((_, topicContent) => {
  2775. if (textLength >= 400) {
  2776. topicContent.style.fontSize = "14px";
  2777. }
  2778. topicContent.style.fontSize = "14.5px";
  2779. });
  2780. }
  2781. }
  2782. {
  2783. const topicBtn = $(".topic_buttons .tb").addClass("v2p-tb v2p-hover-btn");
  2784. const $favoriteBtn = topicBtn.eq(0);
  2785. $favoriteBtn.append(`<span class="v2p-tb-icon"><i data-lucide="star"></i></span>`);
  2786. topicBtn.eq(1).append(`<span class="v2p-tb-icon"><i data-lucide="twitter"></i></span>`);
  2787. topicBtn.eq(2).append(`<span class="v2p-tb-icon"><i data-lucide="eye-off"></i></span>`);
  2788. topicBtn.eq(3).append(`<span class="v2p-tb-icon"><i data-lucide="heart"></i></span>`);
  2789. loadIcons();
  2790. const url = $favoriteBtn.attr("href");
  2791. if (typeof url === "string") {
  2792. let hasFavorited = !url.startsWith("/favorite");
  2793. $favoriteBtn.attr("href", "javascript:").on("click", () => {
  2794. void (async () => {
  2795. createToast({ message: hasFavorited ? "\u6B63\u5728\u53D6\u6D88\u6536\u85CF..." : "\u6B63\u5728\u52A0\u5165\u6536\u85CF..." });
  2796. $favoriteBtn.css("pointer-events", "none");
  2797. try {
  2798. const res = await fetch(hasFavorited ? url.replace("/favorite", "/unfavorite") : url);
  2799. if (res.redirected) {
  2800. const htmlText = await res.text();
  2801. if (!hasFavorited && htmlText.includes("\u53D6\u6D88\u6536\u85CF")) {
  2802. $favoriteBtn.html($favoriteBtn.html().replace("\u52A0\u5165\u6536\u85CF", "\u53D6\u6D88\u6536\u85CF"));
  2803. hasFavorited = true;
  2804. } else if (hasFavorited && htmlText.includes("\u52A0\u5165\u6536\u85CF")) {
  2805. $favoriteBtn.html($favoriteBtn.html().replace("\u53D6\u6D88\u6536\u85CF", "\u52A0\u5165\u6536\u85CF"));
  2806. hasFavorited = false;
  2807. }
  2808. }
  2809. } catch {
  2810. createToast({ message: "\u274C \u52A0\u5165\u6536\u85CF\u5931\u8D25" });
  2811. } finally {
  2812. $favoriteBtn.css("pointer-events", "auto");
  2813. }
  2814. })();
  2815. });
  2816. }
  2817. }
  2818. }
  2819. function processReplyContent($cellDom, replyContentOptions) {
  2820. if (!replyContentOptions.autoFold || $cellDom.find(".v2p-reply-content").length > 0) {
  2821. return;
  2822. }
  2823. const $replyContent = $cellDom.find(".reply_content");
  2824. const contentHeight = $replyContent.height() ?? 0;
  2825. const shouldCollapsed = contentHeight + READABLE_CONTENT_HEIGHT >= MAX_CONTENT_HEIGHT;
  2826. if (shouldCollapsed) {
  2827. const collapsedCSS = {
  2828. maxHeight: `${READABLE_CONTENT_HEIGHT}px`,
  2829. overflow: "hidden",
  2830. paddingBottom: "0"
  2831. };
  2832. const $contentBox = $('<div class="v2p-reply-content v2p-collapsed">').css(collapsedCSS);
  2833. const $expandBtn = createButton({ children: "\u5C55\u5F00\u56DE\u590D", className: "v2p-expand-btn" });
  2834. const toggleContent = () => {
  2835. const collapsed = $contentBox.hasClass("v2p-collapsed");
  2836. $contentBox.toggleClass("v2p-collapsed").css(
  2837. collapsed ? { maxHeight: "none", overflow: "auto", paddingBottom: "40px" } : collapsedCSS
  2838. );
  2839. $expandBtn.html(collapsed ? "\u6536\u8D77\u56DE\u590D" : "\u5C55\u5F00\u56DE\u590D");
  2840. };
  2841. $expandBtn.on("click", () => {
  2842. toggleContent();
  2843. });
  2844. $contentBox.append($replyContent.clone()).replaceAll($replyContent).append($expandBtn);
  2845. }
  2846. }
  2847. function updateMemberTag(memberName, tags) {
  2848. const $v2pTags = $(`.v2p-tags-${memberName}`);
  2849. const tagsValue = tags?.map((it) => it.name).join("\uFF0C");
  2850. if ($v2pTags.length > 0) {
  2851. if (tagsValue) {
  2852. $v2pTags.html(`<b>#</b>&nbsp;${tagsValue}`);
  2853. } else {
  2854. $v2pTags.remove();
  2855. }
  2856. } else {
  2857. if (tagsValue) {
  2858. $(`<div class="v2p-reply-tags v2p-tags-${memberName}"><b>#</b>&nbsp;${tagsValue}</div>`).on("click", () => {
  2859. openTagsSetter(memberName);
  2860. }).insertBefore(
  2861. $commentCells.filter(`:has(> table strong > a[href="/member/${memberName}"])`).find("> table .reply_content")
  2862. );
  2863. }
  2864. }
  2865. }
  2866. function openTagsSetter(memberName) {
  2867. void (async () => {
  2868. const storage = await getStorage(false);
  2869. const latestTagsData = storage["member-tag" /* MemberTag */];
  2870. const tagsValue = latestTagsData ? Reflect.has(latestTagsData, memberName) ? latestTagsData[memberName].tags?.map((it) => it.name).join("\uFF0C") : void 0 : void 0;
  2871. const newTagsValue = window.prompt(
  2872. `\u5BF9 @${memberName} \u8BBE\u7F6E\u6807\u7B7E\uFF0C\u591A\u4E2A\u6807\u7B7E\u4EE5\u9017\u53F7\uFF08\uFF0C\uFF09\u5206\u9694\u3002`,
  2873. tagsValue
  2874. );
  2875. if (newTagsValue !== null) {
  2876. const tags = newTagsValue.trim().length > 0 ? newTagsValue.split(/,|,/g).map((it) => ({ name: it })) : void 0;
  2877. await setMemberTags(memberName, tags);
  2878. updateMemberTag(memberName, tags);
  2879. }
  2880. })();
  2881. }
  2882. var init_content = __esm({
  2883. "src/contents/topic/content.ts"() {
  2884. "use strict";
  2885. init_button();
  2886. init_toast();
  2887. init_constants();
  2888. init_utils();
  2889. init_globals();
  2890. init_helpers();
  2891. }
  2892. });
  2893.  
  2894. // src/contents/topic/comment.ts
  2895. function handlingPopularComments() {
  2896. const popularCommentData = commentDataList.filter(({ likes }) => likes > 0).sort((a, b) => b.likes - a.likes);
  2897. const popularCount = popularCommentData.length;
  2898. const iconHeart = createElement$1(Heart);
  2899. iconHeart.setAttribute("width", "100%");
  2900. iconHeart.setAttribute("height", "100%");
  2901. const $popularBtn = $(
  2902. `<span class="v2p-tool v2p-hover-btn"><span class="v2p-tool-icon"></span>\u70ED\u95E8\u56DE\u590D</span>`
  2903. );
  2904. $popularBtn.find(".v2p-tool-icon").append(iconHeart);
  2905. $(".v2p-tools").prepend($popularBtn);
  2906. if (popularCount <= 0) {
  2907. $popularBtn.addClass("v2p-hover-btn-disabled").contents().last().replaceWith("\u6682\u65E0\u70ED\u95E8");
  2908. return;
  2909. }
  2910. const model = createModel({
  2911. root: $commentBox,
  2912. title: `\u672C\u9875\u5171\u6709 ${popularCommentData.length} \u6761\u70ED\u95E8\u56DE\u590D`,
  2913. onMount: ({ $content }) => {
  2914. const $template = $("<div>");
  2915. popularCommentData.forEach(({ index, refMemberNames }) => {
  2916. const $clonedCells = $commentCells.eq(index).clone();
  2917. $clonedCells.find(".v2p-controls > a:has(.v2p-control-reply)").remove();
  2918. $clonedCells.find(".no").css("pointer-events", "none");
  2919. const firstRefMember = refMemberNames?.at(0);
  2920. if (firstRefMember) {
  2921. const replyMember = commentDataList.findLast(
  2922. (it, idx) => idx < index && it.memberName === firstRefMember
  2923. );
  2924. if (replyMember) {
  2925. const $refCell = $(`
  2926. <div class="v2p-topic-reply-ref">
  2927. <div class="v2p-topic-reply">
  2928. <div class="v2p-topic-reply-member">
  2929. <a href="${replyMember.memberAvatar}">
  2930. <img class="v2p-topic-reply-avatar" src="${replyMember.memberAvatar}">
  2931. <span>${replyMember.memberName}</span>
  2932. </a>\uFF1A
  2933. </div>
  2934. <div class="v2p-topic-reply-content">${escapeHTML(replyMember.content)}</div>
  2935. </div>
  2936. </div>
  2937. `);
  2938. $clonedCells.prepend($refCell);
  2939. }
  2940. }
  2941. $template.append($clonedCells);
  2942. });
  2943. $content.css({ padding: "0 20px" }).append($template.html());
  2944. },
  2945. onOpen: ({ $container }) => {
  2946. $container.find('.cell[id^="r_"]').each((_, cellDom) => {
  2947. const storage = getStorageSync();
  2948. const options = storage["options" /* Options */];
  2949. processReplyContent($(cellDom), options.replyContent);
  2950. });
  2951. }
  2952. });
  2953. $popularBtn.on("click", () => {
  2954. model.open();
  2955. });
  2956. {
  2957. const commentBoxCount = $commentBox.find(".cell:first-of-type > span.gray");
  2958. const countText = commentBoxCount.text();
  2959. const newCountText = countText.substring(0, countText.indexOf("\u56DE\u590D") + 2);
  2960. const countTextSpan = `<span class="count-text">${newCountText}</span><span class="v2p-dot">\xB7</span>${popularCount} \u6761\u70ED\u95E8\u56DE\u590D`;
  2961. commentBoxCount.empty().append(countTextSpan);
  2962. }
  2963. }
  2964. function handlingRecentComments() {
  2965. if (commentDataList.length <= 5) {
  2966. return;
  2967. }
  2968. const len = commentDataList.length;
  2969. const displayNum = len <= 10 ? 5 : len <= 30 ? 10 : len <= 60 ? 20 : len <= 100 ? 40 : len <= 200 ? 60 : 90;
  2970. const recentCommentData = commentDataList.slice(-1 * displayNum).reverse();
  2971. const iconClock = createElement$1(Clock4);
  2972. iconClock.setAttribute("width", "100%");
  2973. iconClock.setAttribute("height", "100%");
  2974. const $recentBtn = $(
  2975. `<span class="v2p-tool v2p-hover-btn"><span class="v2p-tool-icon"></span>\u6700\u8FD1\u56DE\u590D</span>`
  2976. );
  2977. $recentBtn.find(".v2p-tool-icon").append(iconClock);
  2978. $(".v2p-tools").prepend($recentBtn);
  2979. const model = createModel({
  2980. root: $commentBox,
  2981. title: `\u6700\u8FD1 ${displayNum} \u6761\u56DE\u590D`,
  2982. onMount: ({ $content }) => {
  2983. const $template = $("<div>");
  2984. recentCommentData.forEach(({ index, refMemberNames }) => {
  2985. const $clonedCells = $commentCells.eq(index).clone();
  2986. $clonedCells.find(".v2p-controls > a:has(.v2p-control-reply)").remove();
  2987. $clonedCells.find(".no").css("pointer-events", "none");
  2988. const firstRefMember = refMemberNames?.at(0);
  2989. if (firstRefMember) {
  2990. const replyMember = commentDataList.findLast(
  2991. (it, idx) => idx < index && it.memberName === firstRefMember
  2992. );
  2993. if (replyMember) {
  2994. const $refCell = $(`
  2995. <div class="v2p-topic-reply-ref">
  2996. <div class="v2p-topic-reply">
  2997. <div class="v2p-topic-reply-member">
  2998. <a href="${replyMember.memberAvatar}">
  2999. <img class="v2p-topic-reply-avatar" src="${replyMember.memberAvatar}">
  3000. <span>${replyMember.memberName}</span>
  3001. </a>\uFF1A
  3002. </div>
  3003. <div class="v2p-topic-reply-content">${escapeHTML(replyMember.content)}</div>
  3004. </div>
  3005. </div>
  3006. `);
  3007. $clonedCells.prepend($refCell);
  3008. }
  3009. }
  3010. $template.append($clonedCells);
  3011. });
  3012. $content.css({ padding: "0 20px" }).append($template.html());
  3013. },
  3014. onOpen: ({ $container }) => {
  3015. $container.find('.cell[id^="r_"]').each((_, cellDom) => {
  3016. const storage = getStorageSync();
  3017. const options = storage["options" /* Options */];
  3018. processReplyContent($(cellDom), options.replyContent);
  3019. });
  3020. }
  3021. });
  3022. $recentBtn.on("click", () => {
  3023. model.open();
  3024. });
  3025. }
  3026. function processActions($cellDom, data) {
  3027. const $actions = $cellDom.find("> table > tbody > tr > td:last-of-type > .fr");
  3028. const $controls = $('<span class="v2p-controls">');
  3029. const $thankIcon = $(
  3030. `<span
  3031. class="v2p-control v2p-control-thank"
  3032. data-id="${data.id}"
  3033. data-member-name="${data.memberName}"
  3034. >
  3035. <i data-lucide="heart"></i>
  3036. </span>`
  3037. );
  3038. const thankArea = $actions.find("> .thank_area");
  3039. const thanked = thankArea.hasClass("thanked");
  3040. if (thanked) {
  3041. $thankIcon.addClass("v2p-thanked");
  3042. $controls.append($("<a>").append($thankIcon));
  3043. } else {
  3044. const thankEle = thankArea.find("> .thank");
  3045. const $hide = thankEle.eq(0).removeClass("thank");
  3046. const $thank = thankEle.eq(1).removeClass("thank");
  3047. $hide.html(
  3048. `<span class="v2p-control v2p-hover-btn v2p-control-hide"><i data-lucide="eye-off"></i></span>`
  3049. );
  3050. $thankIcon.addClass("v2p-hover-btn").replaceAll($thank);
  3051. $controls.append($hide).append($thankIcon);
  3052. }
  3053. const $reply = $actions.find("a:last-of-type");
  3054. $reply.find('> img[alt="Reply"]').replaceWith(
  3055. `<span class="v2p-control v2p-hover-btn v2p-control-reply"><i data-lucide="message-square"></i></span>`
  3056. );
  3057. $controls.append($reply);
  3058. thankArea.remove();
  3059. const floorNum = $actions.find(".no").clone();
  3060. $reply.on("click", () => {
  3061. const replyVal = $replyTextArea.val();
  3062. if (typeof replyVal === "string" && replyVal.length > 0) {
  3063. const floor = floorNum.text();
  3064. const replyComment = commentDataList.find((it) => it.floor === floor);
  3065. if (replyComment) {
  3066. const replyMemberName = replyComment.memberName;
  3067. const moreThanOneReply = commentDataList.findIndex(
  3068. (it) => it.memberName === replyMemberName && it.floor !== floor
  3069. ) !== -1;
  3070. if (moreThanOneReply) {
  3071. insertTextToReplyInput(`#${floor} `);
  3072. } else {
  3073. const $page = $(".v2p-paging").eq(0).find(".page_normal, .page_current");
  3074. if ($page.length > 1) {
  3075. const onLastPage = $page.last().hasClass("page_current");
  3076. if (!onLastPage) {
  3077. insertTextToReplyInput(`#${floor} `);
  3078. }
  3079. }
  3080. }
  3081. }
  3082. }
  3083. });
  3084. $actions.empty().append($controls, floorNum);
  3085. }
  3086. async function handlingComments() {
  3087. const storage = getStorageSync();
  3088. const tagData = storage["member-tag" /* MemberTag */];
  3089. const options = storage["options" /* Options */];
  3090. if (options.reply.preload !== "off") {
  3091. const $paging = $(".v2p-paging");
  3092. if ($paging.length > 0) {
  3093. const $pagingTop = $paging.eq(0);
  3094. const $pagingBottom = $paging.eq(1);
  3095. const $pageNormal = $paging.find(".page_normal");
  3096. const $pagingTopNormal = $pagingTop.find(".page_normal");
  3097. const toastControl = createToast({ message: "\u6B63\u5728\u9884\u52A0\u8F7D\u56DE\u590D\uFF0C\u8BF7\u7A0D\u5019...", duration: 0 });
  3098. try {
  3099. const $pagingBottomNormal = $pagingBottom.find(".page_normal");
  3100. const $pageCurrent = $pagingTop.find(".page_current");
  3101. const currentPage = $pageCurrent.text();
  3102. if (currentPage === "1") {
  3103. const pages = [];
  3104. $pagingTopNormal.each((i, ele) => {
  3105. if (i <= 1) {
  3106. if (ele.textContent) {
  3107. ele.classList.add("page_current");
  3108. ele.classList.remove("page_normal");
  3109. $pagingBottomNormal.eq(i).addClass("page_current").removeClass("page_normal");
  3110. pages.push(ele.textContent);
  3111. }
  3112. }
  3113. });
  3114. if (pages.length > 0) {
  3115. const pagesText = await Promise.all(
  3116. pages.map((p) => crawalTopicPage(window.location.pathname, p))
  3117. );
  3118. pagesText.map((pageText) => {
  3119. $pagingBottom.before($(pageText).find('.cell[id^="r_"]'));
  3120. });
  3121. }
  3122. updateCommentCells();
  3123. }
  3124. toastControl.clear();
  3125. } catch {
  3126. createToast({ message: "\u274C \u52A0\u8F7D\u591A\u9875\u56DE\u590D\u5931\u8D25" });
  3127. $pageNormal.removeClass("page_current").addClass("page_normal");
  3128. }
  3129. }
  3130. }
  3131. commentDataList = $commentTableRows.map((idx, tr) => {
  3132. const id = $commentCells[idx].id;
  3133. const $tr = $(tr);
  3134. const $td = $tr.find("> td:nth-child(3)");
  3135. const thanked = $tr.find("> td:last-of-type > .fr").find("> .thank_area").hasClass("thanked");
  3136. const $member = $td.find("> strong > a");
  3137. const memberName = $member.text();
  3138. const memberLink = $member.prop("href");
  3139. const memberAvatar = $tr.find(".avatar").prop("src");
  3140. const content = $td.find("> .reply_content").text();
  3141. const likes = Number($td.find("span.small").text());
  3142. const floor = $td.find("span.no").text();
  3143. const memberNameMatches = Array.from(content.matchAll(/@([a-zA-Z0-9]+)/g));
  3144. const refMemberNames = memberNameMatches.length > 0 ? memberNameMatches.map(([, name]) => {
  3145. return name;
  3146. }) : void 0;
  3147. const floorNumberMatches = Array.from(content.matchAll(/#(\d+)/g));
  3148. const refFloors = floorNumberMatches.length > 0 ? floorNumberMatches.map(([, floor2]) => {
  3149. return floor2;
  3150. }) : void 0;
  3151. return {
  3152. id,
  3153. memberName,
  3154. memberLink,
  3155. memberAvatar,
  3156. content,
  3157. likes,
  3158. floor,
  3159. index: idx,
  3160. refMemberNames,
  3161. refFloors,
  3162. thanked
  3163. };
  3164. }).get();
  3165. {
  3166. const popupControl = createPopup({
  3167. root: $commentBox,
  3168. triggerType: "hover",
  3169. placement: "right-start",
  3170. offsetOptions: { mainAxis: 8, crossAxis: -4 }
  3171. });
  3172. const membersHasSetTags = /* @__PURE__ */ new Set();
  3173. $commentCells.each((i, cellDom) => {
  3174. const currentComment = commentDataList.at(i);
  3175. if (currentComment?.id !== cellDom.id) {
  3176. return;
  3177. }
  3178. const $cellDom = $(cellDom);
  3179. const { memberName, thanked } = currentComment;
  3180. processAvatar({
  3181. $cellDom,
  3182. popupControl,
  3183. commentData: currentComment,
  3184. onSetTagsClick: () => {
  3185. openTagsSetter(memberName);
  3186. }
  3187. });
  3188. if (memberName === loginName) {
  3189. $cellDom.find(".badges").append(`<div class="badge ${memberName === topicOwnerName ? "mod" : "you"}">YOU</div>`);
  3190. }
  3191. const $likesBox = $cellDom.find(".small.fade").addClass("v2p-likes-box");
  3192. $likesBox.find('img[alt="\u2764\uFE0F"]').replaceWith(`<span class="v2p-icon-heart"><i data-lucide="heart"></i></span>`);
  3193. if (thanked) {
  3194. $likesBox.addClass("v2p-thanked");
  3195. }
  3196. if (tagData && Reflect.has(tagData, memberName) && !membersHasSetTags.has(memberName)) {
  3197. updateMemberTag(memberName, tagData[memberName].tags);
  3198. membersHasSetTags.add(memberName);
  3199. }
  3200. processActions($cellDom, currentComment);
  3201. });
  3202. updateCommentCells();
  3203. handlingRecentComments();
  3204. handlingPopularComments();
  3205. $(".v2p-control-thank").on("click", (ev) => {
  3206. ev.stopPropagation();
  3207. const id = ev.currentTarget.dataset.id;
  3208. const memberName = ev.currentTarget.dataset.memberName;
  3209. if (typeof id === "string" && typeof memberName === "string") {
  3210. if (confirm(`\u786E\u8BA4\u82B1\u8D39 10 \u4E2A\u94DC\u5E01\u5411 @${memberName} \u7684\u8FD9\u6761\u56DE\u590D\u53D1\u9001\u611F\u8C22\uFF1F`)) {
  3211. const replyId = id.split("_").at(1);
  3212. if (replyId) {
  3213. void thankReply({
  3214. replyId,
  3215. onSuccess: () => {
  3216. const $cell = $(`.cell[id="r_${replyId}"]`);
  3217. const $tableInCell = $cell.find("> table");
  3218. const $likesBox = $tableInCell.find(".v2p-likes-box");
  3219. const $firstLikesBox = $likesBox.eq(0);
  3220. const likes = Number($firstLikesBox.text());
  3221. const $clonedIconHeart = $firstLikesBox.find(".v2p-icon-heart").clone();
  3222. if (likes > 0) {
  3223. $likesBox.addClass("v2p-thanked").empty().append($clonedIconHeart, ` ${likes + 1}`);
  3224. } else {
  3225. $(`
  3226. <span class="small v2p-likes-box v2p-thanked" style="position:relative;top:-1px;">
  3227. &nbsp;&nbsp;<span class="v2p-icon-heart"><i data-lucide="heart"></i></span>1
  3228. </span>
  3229. `).insertAfter($tableInCell.find(".ago"));
  3230. loadIcons();
  3231. }
  3232. const $thankAction = $tableInCell.find(".v2p-control-thank");
  3233. $thankAction.addClass("v2p-thanked").off("click");
  3234. $thankAction.siblings().find(".v2p-control-hide").hide();
  3235. createToast({ message: `\u2764\uFE0F \u5DF2\u611F\u8C22 @${memberName} \u7684\u56DE\u590D` });
  3236. },
  3237. onFail: () => {
  3238. createToast({ message: "\u274C \u611F\u8C22\u56DE\u590D\u5931\u8D25" });
  3239. }
  3240. });
  3241. }
  3242. }
  3243. }
  3244. });
  3245. }
  3246. {
  3247. const display = options.nestedReply.display;
  3248. $commentCells.each((i, cellDom) => {
  3249. const $cellDom = $(cellDom);
  3250. const dataFromIndex = commentDataList.at(i);
  3251. processReplyContent($cellDom, options.replyContent);
  3252. const currentComment = dataFromIndex?.id === cellDom.id ? dataFromIndex : commentDataList.find((data) => data.id === cellDom.id);
  3253. if (currentComment) {
  3254. const { refMemberNames, refFloors } = currentComment;
  3255. if (!refMemberNames || refMemberNames.length === 0) {
  3256. return;
  3257. }
  3258. if (options.nestedReply.multipleInsideOne === "off" && refMemberNames.length > 1) {
  3259. return;
  3260. }
  3261. for (const refName of refMemberNames) {
  3262. for (let j = i - 1; j >= 0; j--) {
  3263. const { memberName: compareName, floor: eachFloor } = commentDataList.at(j) || {};
  3264. if (compareName === refName) {
  3265. let refCommentIdx = j;
  3266. const firstRefFloor = refFloors?.at(0);
  3267. if (firstRefFloor && firstRefFloor !== eachFloor) {
  3268. const targetIdx = commentDataList.slice(0, j).findIndex((data) => data.floor === firstRefFloor && data.memberName === refName);
  3269. if (targetIdx >= 0) {
  3270. refCommentIdx = targetIdx;
  3271. }
  3272. }
  3273. if (display === "indent") {
  3274. cellDom.classList.add("v2p-indent");
  3275. }
  3276. $commentCells.eq(refCommentIdx).append(cellDom);
  3277. return;
  3278. }
  3279. }
  3280. }
  3281. }
  3282. });
  3283. }
  3284. }
  3285. var commentDataList;
  3286. var init_comment = __esm({
  3287. "src/contents/topic/comment.ts"() {
  3288. "use strict";
  3289. init_lucide();
  3290. init_model();
  3291. init_popup();
  3292. init_toast();
  3293. init_constants();
  3294. init_services();
  3295. init_utils();
  3296. init_globals();
  3297. init_helpers();
  3298. init_avatar();
  3299. init_content();
  3300. commentDataList = [];
  3301. }
  3302. });
  3303.  
  3304. // src/contents/topic/paging.ts
  3305. function handlingPaging() {
  3306. const $notCommentCells = $commentBox.find('> .cell:not([id^="r_"])');
  3307. if ($notCommentCells.length <= 1) {
  3308. return;
  3309. }
  3310. const pagingCells = $notCommentCells.slice(1).addClass("v2p-paging");
  3311. const pageBtns = pagingCells.find(".super.button");
  3312. pageBtns.eq(0).addClass("v2p-prev-btn");
  3313. pageBtns.eq(1).addClass("v2p-next-btn");
  3314. }
  3315. var init_paging = __esm({
  3316. "src/contents/topic/paging.ts"() {
  3317. "use strict";
  3318. init_globals();
  3319. }
  3320. });
  3321.  
  3322. // src/components/image-upload.ts
  3323. function bindImageUpload(props) {
  3324. const { $wrapper, $input, insertText, replaceText } = props;
  3325. const $uploadBar = $(`<div class="v2p-reply-upload-bar">${uploadTip}</div>`);
  3326. const handleUploadImage = (file) => {
  3327. const placeholder = "[\u4E0A\u4F20\u56FE\u7247\u4E2D...]";
  3328. insertText(` ${placeholder} `);
  3329. $uploadBar.addClass("v2p-reply-upload-bar-disabled").text("\u6B63\u5728\u4E0A\u4F20\u56FE\u7247...");
  3330. uploadImage(file).then((imgLink) => {
  3331. replaceText(placeholder, imgLink);
  3332. }).catch(() => {
  3333. replaceText(placeholder, "");
  3334. window.alert("\u274C \u4E0A\u4F20\u56FE\u7247\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u53F0\u67E5\u770B\u539F\u56E0");
  3335. }).finally(() => {
  3336. $uploadBar.removeClass("v2p-reply-upload-bar-disabled").text(uploadTip);
  3337. });
  3338. };
  3339. const handleClickUploadImage = () => {
  3340. const imgInput = document.createElement("input");
  3341. imgInput.style.display = "none";
  3342. imgInput.type = "file";
  3343. imgInput.accept = "image/*";
  3344. imgInput.addEventListener("change", () => {
  3345. const selectedFile = imgInput.files?.[0];
  3346. if (selectedFile) {
  3347. handleUploadImage(selectedFile);
  3348. }
  3349. });
  3350. imgInput.click();
  3351. };
  3352. document.addEventListener("paste", (ev) => {
  3353. if (!(ev instanceof ClipboardEvent)) {
  3354. return;
  3355. }
  3356. if ($input && !$input.get(0)?.matches(":focus")) {
  3357. return;
  3358. }
  3359. const items = ev.clipboardData?.items;
  3360. if (!items) {
  3361. return;
  3362. }
  3363. const imageItem = Array.from(items).find((item) => item.type.includes("image"));
  3364. if (imageItem) {
  3365. const file = imageItem.getAsFile();
  3366. if (file) {
  3367. handleUploadImage(file);
  3368. }
  3369. }
  3370. });
  3371. $wrapper.get(0)?.addEventListener("drop", (ev) => {
  3372. ev.preventDefault();
  3373. if (!(ev instanceof DragEvent)) {
  3374. return;
  3375. }
  3376. const file = ev.dataTransfer?.files[0];
  3377. if (file) {
  3378. handleUploadImage(file);
  3379. }
  3380. });
  3381. $(".flex-one-row:last-of-type > .gray").text("");
  3382. $uploadBar.on("click", () => {
  3383. if (!$uploadBar.hasClass("v2p-reply-upload-bar-disabled")) {
  3384. handleClickUploadImage();
  3385. }
  3386. });
  3387. $wrapper.append($uploadBar);
  3388. return {
  3389. uploadBar: $uploadBar
  3390. };
  3391. }
  3392. var uploadTip;
  3393. var init_image_upload = __esm({
  3394. "src/components/image-upload.ts"() {
  3395. "use strict";
  3396. init_services();
  3397. uploadTip = "\u9009\u62E9\u3001\u7C98\u8D34\u3001\u62D6\u653E\u4E0A\u4F20\u56FE\u7247\u3002";
  3398. }
  3399. });
  3400.  
  3401. // src/contents/topic/reply.ts
  3402. function handlingReplyActions() {
  3403. const os = getOS();
  3404. const replyBtnText = `\u56DE\u590D<kbd>${os === "macos" ? "Cmd" : "Ctrl"}+Enter</kbd>`;
  3405. const $replyBtn = createButton({
  3406. children: replyBtnText,
  3407. type: "submit"
  3408. }).replaceAll($replyBox.find('input[type="submit"]'));
  3409. $replyForm.on("submit", () => {
  3410. $replyBtn.text("\u63D0\u4EA4\u56DE\u590D\u4E2D...").prop("disabled", true);
  3411. setTimeout(() => {
  3412. $replyBtn.html(replyBtnText).prop("disabled", false);
  3413. }, 5e3);
  3414. });
  3415. document.addEventListener("keydown", (ev) => {
  3416. if (ev.key === "Enter" && (ev.ctrlKey || ev.metaKey)) {
  3417. ev.preventDefault();
  3418. $replyForm.trigger("submit");
  3419. }
  3420. });
  3421. {
  3422. const emoticonGroup = $('<div class="v2p-emoji-group">');
  3423. const emoticonList = $('<div class="v2p-emoji-list">');
  3424. const emoticonSpan = $('<span class="v2p-emoji">');
  3425. const groups = emoticons.map((emojiGroup) => {
  3426. const group = emoticonGroup.clone();
  3427. group.append(`<div class="v2p-emoji-title">${emojiGroup.title}</div>`);
  3428. const list = emoticonList.clone().append(
  3429. emojiGroup.list.map((emoji) => {
  3430. const emoticon = emoticonSpan.clone().text(emoji).on("click", () => {
  3431. insertTextToReplyInput(emoji);
  3432. });
  3433. return emoticon;
  3434. })
  3435. );
  3436. group.append(list);
  3437. return group;
  3438. });
  3439. const emoticonsBox = $('<div class="v2p-emoticons-box">').append(groups);
  3440. const $emojiBtn = createButton({
  3441. children: '<span style="width:18px; height:18px;"><i data-lucide="smile"></i></span>'
  3442. }).insertAfter($replyBtn);
  3443. const $emojiContent = $('<div class="v2p-emoji-container">').append(emoticonsBox).appendTo($replyBox).on("click", () => {
  3444. focusReplyInput();
  3445. });
  3446. const keyupHandler = (ev) => {
  3447. if (ev.key === "Escape") {
  3448. ev.preventDefault();
  3449. emojiPopup.close();
  3450. }
  3451. };
  3452. $emojiBtn.on("click", () => {
  3453. focusReplyInput();
  3454. });
  3455. const emojiPopup = createPopup({
  3456. root: $replyBox,
  3457. trigger: $emojiBtn,
  3458. content: $emojiContent,
  3459. options: { placement: "right-end" },
  3460. onOpen: () => {
  3461. $(document.body).on("keydown", keyupHandler);
  3462. },
  3463. onClose: () => {
  3464. $(document.body).off("keydown", keyupHandler);
  3465. }
  3466. });
  3467. }
  3468. {
  3469. $replyBox.find("#undock-button, #undock-button + a").addClass("v2p-hover-btn").css("padding", "5px 4px");
  3470. }
  3471. }
  3472. function handleReply() {
  3473. $replyTextArea.attr("placeholder", "\u7559\u4E0B\u5BF9\u4ED6\u4EBA\u6709\u5E2E\u52A9\u7684\u56DE\u590D").wrap('<div class="v2p-reply-wrap">');
  3474. const { uploadBar } = bindImageUpload({
  3475. $wrapper: $(".v2p-reply-wrap"),
  3476. $input: $replyTextArea,
  3477. insertText: (text) => {
  3478. insertTextToReplyInput(text);
  3479. },
  3480. replaceText: (find, replace) => {
  3481. const val = $replyTextArea.val();
  3482. if (typeof val === "string") {
  3483. const newVal = val.replace(find, replace);
  3484. $replyTextArea.val(newVal).trigger("focus");
  3485. }
  3486. }
  3487. });
  3488. const $tools = $(`
  3489. <div class="v2p-reply-tools-box v2p-hover-btn">
  3490. <span class="v2p-reply-tools-icon"><i data-lucide="package-plus"></i></span>
  3491. \u5DE5\u5177\u7BB1
  3492. </div>
  3493. `);
  3494. const $toolContent = $(`
  3495. <div class="v2p-reply-tool-content">
  3496. <div class="v2p-reply-tool v2p-reply-tool-encode">\u6587\u5B57\u8F6C Base64</div>
  3497. <div class="v2p-reply-tool v2p-reply-tool-img">\u4E0A\u4F20\u56FE\u7247</div>
  3498. </div>
  3499. `);
  3500. const toolsPopup = createPopup({
  3501. root: $replyBox,
  3502. trigger: $tools,
  3503. content: $toolContent,
  3504. offsetOptions: { mainAxis: 5, crossAxis: -5 }
  3505. });
  3506. $toolContent.find(".v2p-reply-tool-encode").on("click", () => {
  3507. focusReplyInput();
  3508. toolsPopup.close();
  3509. setTimeout(() => {
  3510. const inputText = window.prompt("\u8F93\u5165\u8981\u52A0\u5BC6\u7684\u5B57\u7B26\u4E32\uFF0C\u5B8C\u6210\u540E\u5C06\u586B\u5199\u5230\u56DE\u590D\u6846\u4E2D\uFF1A");
  3511. if (inputText) {
  3512. let encodedText;
  3513. try {
  3514. if (typeof window.encodeURIComponent === "undefined") {
  3515. encodedText = window.btoa(inputText);
  3516. } else {
  3517. encodedText = window.btoa(window.encodeURIComponent(inputText));
  3518. }
  3519. } catch (err) {
  3520. console.error(err, "\u53EF\u80FD\u7684\u9519\u8BEF\u539F\u56E0\uFF1A\u6587\u672C\u5305\u542B\u4E2D\u6587\u3002");
  3521. createToast({ message: "\u8BE5\u6587\u672C\u65E0\u6CD5\u7F16\u7801\u4E3A Base64" });
  3522. }
  3523. if (encodedText) {
  3524. insertTextToReplyInput(encodedText);
  3525. }
  3526. }
  3527. });
  3528. });
  3529. $toolContent.find(".v2p-reply-tool-img").on("click", () => {
  3530. uploadBar.trigger("click");
  3531. });
  3532. $replyBox.find("> .flex-row-end").prepend($tools);
  3533. $(".flex-one-row:last-of-type > .gray").text("");
  3534. handlingReplyActions();
  3535. }
  3536. var init_reply = __esm({
  3537. "src/contents/topic/reply.ts"() {
  3538. "use strict";
  3539. init_button();
  3540. init_image_upload();
  3541. init_popup();
  3542. init_toast();
  3543. init_constants();
  3544. init_utils();
  3545. init_globals();
  3546. init_helpers();
  3547. }
  3548. });
  3549.  
  3550. // src/contents/topic/index.ts
  3551. var topic_exports = {};
  3552. var init_topic = __esm({
  3553. "src/contents/topic/index.ts"() {
  3554. "use strict";
  3555. init_constants();
  3556. init_utils();
  3557. init_globals();
  3558. init_helpers();
  3559. init_comment();
  3560. init_content();
  3561. init_paging();
  3562. init_reply();
  3563. void (async () => {
  3564. const storage = await getStorage();
  3565. const options = storage["options" /* Options */];
  3566. if (options.openInNewTab) {
  3567. $commentTableRows.find("> td:nth-child(3) > strong > a").prop("target", "_blank");
  3568. }
  3569. {
  3570. const $tools = $(`
  3571. <div class="cell v2p-tools">
  3572. <span class="v2p-tool v2p-hover-btn v2p-tool-reply">
  3573. <span class="v2p-tool-icon"><i data-lucide="message-square-plus"></i></span>\u56DE\u590D\u4E3B\u9898
  3574. </span>
  3575. <span class="v2p-tool v2p-hover-btn v2p-tool-reading">
  3576. <span class="v2p-tool-icon"><i data-lucide="book-open-check"></i></span>\u7A0D\u540E\u9605\u8BFB
  3577. </span>
  3578. <span class="v2p-tool v2p-hover-btn v2p-tool-scroll-top">
  3579. <span class="v2p-tool-icon"><i data-lucide="chevrons-up"></i></span>\u56DE\u5230\u9876\u90E8
  3580. </span>
  3581. </div>
  3582. `);
  3583. $tools.find(".v2p-tool-reply").on("click", () => {
  3584. $replyTextArea.trigger("focus");
  3585. });
  3586. $tools.find(".v2p-tool-reading").on("click", () => {
  3587. void addToReadingList({
  3588. url: window.location.href,
  3589. title: document.title.replace(" - V2EX", ""),
  3590. content: String($('head meta[property="og:description"]').prop("content"))
  3591. });
  3592. });
  3593. $tools.find(".v2p-tool-scroll-top").on("click", () => {
  3594. window.scrollTo({ top: 0, behavior: "smooth" });
  3595. });
  3596. $('#Rightbar > .box:has("#member-activity")').addClass("v2p-tool-box").append($tools);
  3597. loadIcons();
  3598. }
  3599. {
  3600. $(document).on("keydown", (ev) => {
  3601. if (!ev.isDefaultPrevented()) {
  3602. if (ev.key === "Escape") {
  3603. const $replyContent = $("#reply_content");
  3604. if ($replyBox.hasClass("reply-box-sticky")) {
  3605. $replyBox.removeClass("reply-box-sticky");
  3606. $("#undock-button").css("display", "none");
  3607. }
  3608. $replyContent.trigger("blur");
  3609. }
  3610. }
  3611. });
  3612. }
  3613. handlingContent();
  3614. if (document.referrer !== "") {
  3615. if (document.referrer.includes(document.location.pathname)) {
  3616. const url = new URL(document.location.href);
  3617. const page = url.searchParams.get("p");
  3618. if (page && page !== "1") {
  3619. document.querySelector(".topic_buttons")?.scrollIntoView({ behavior: "smooth" });
  3620. }
  3621. }
  3622. }
  3623. handlingPaging();
  3624. await handlingComments();
  3625. handleReply();
  3626. loadIcons();
  3627. })();
  3628. }
  3629. });
  3630.  
  3631. // src/contents/write/write.ts
  3632. function handlingWrite() {
  3633. bindImageUpload({
  3634. $wrapper: $("#workspace"),
  3635. insertText: (text) => {
  3636. postTask(`editor.getDoc().replaceRange("${text}", editor.getCursor())`);
  3637. },
  3638. replaceText: (find, replace) => {
  3639. if (replace) {
  3640. const mode = $("input[name=syntax]:checked").val();
  3641. if (mode === "markdown") {
  3642. replace = `![](${replace})`;
  3643. }
  3644. }
  3645. postTask(`
  3646. editor.setValue(editor.getValue().replace("${find}", "${replace}"));
  3647. const doc = editor.getDoc();
  3648. const lastLine = doc.lastLine();
  3649. const lastChar = doc.getLine(lastLine).length;
  3650. doc.setCursor({ line: doc.lastLine(), ch: lastChar });
  3651. `);
  3652. }
  3653. });
  3654. }
  3655. var init_write = __esm({
  3656. "src/contents/write/write.ts"() {
  3657. "use strict";
  3658. init_image_upload();
  3659. init_helpers();
  3660. }
  3661. });
  3662.  
  3663. // src/contents/write/index.ts
  3664. var write_exports = {};
  3665. var init_write2 = __esm({
  3666. "src/contents/write/index.ts"() {
  3667. "use strict";
  3668. init_helpers();
  3669. init_write();
  3670. handlingWrite();
  3671. loadIcons();
  3672. }
  3673. });
  3674.  
  3675. // node_modules/.pnpm/webext-patterns@1.3.0/node_modules/webext-patterns/index.js
  3676. var patternValidationRegex = /^(https?|wss?|file|ftp|\*):\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^file:\/\/\/.*$|^resource:\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^about:/;
  3677. var isFirefox = typeof navigator === "object" && navigator.userAgent.includes("Firefox/");
  3678. var allStarsRegex = isFirefox ? /^(https?|wss?):[/][/][^/]+([/].*)?$/ : /^https?:[/][/][^/]+([/].*)?$/;
  3679. var allUrlsRegex = /^(https?|file|ftp):[/]+/;
  3680. function getRawPatternRegex(matchPattern) {
  3681. if (!patternValidationRegex.test(matchPattern)) {
  3682. throw new Error(matchPattern + " is an invalid pattern, it must match " + String(patternValidationRegex));
  3683. }
  3684. let [, protocol, host, pathname] = matchPattern.split(/(^[^:]+:[/][/])([^/]+)?/);
  3685. protocol = protocol.replace("*", isFirefox ? "(https?|wss?)" : "https?").replace(/[/]/g, "[/]");
  3686. host = (host !== null && host !== void 0 ? host : "").replace(/^[*][.]/, "([^/]+.)*").replace(/^[*]$/, "[^/]+").replace(/[.]/g, "[.]").replace(/[*]$/g, "[^.]+");
  3687. pathname = pathname.replace(/[/]/g, "[/]").replace(/[.]/g, "[.]").replace(/[*]/g, ".*");
  3688. return "^" + protocol + host + "(" + pathname + ")?$";
  3689. }
  3690. function patternToRegex(...matchPatterns) {
  3691. if (matchPatterns.length === 0) {
  3692. return /$./;
  3693. }
  3694. if (matchPatterns.includes("<all_urls>")) {
  3695. return allUrlsRegex;
  3696. }
  3697. if (matchPatterns.includes("*://*/*")) {
  3698. return allStarsRegex;
  3699. }
  3700. return new RegExp(matchPatterns.map((x) => getRawPatternRegex(x)).join("|"));
  3701. }
  3702.  
  3703. // src/user-scripts/style.ts
  3704. var style = `:root{--zidx-serach: 100;--zidx-tabs: 10;--zidx-tools-card: 10;--zidx-reply-box: 99;--zidx-model-header: 50;--zidx-model-mask: 888;--zidx-toast: 999;--zidx-tip: 10;--zidx-popup: 99;--zidx-expand-mask: 10;--zidx-expand-btn: 20;--v2p-underline-offset: 0.5ex;--v2p-layout-gap: 25px}:root body{--v2p-color-main-50: #f7f9fb;--v2p-color-main-100: #f1f5f9;--v2p-color-main-200: #e2e8f0;--v2p-color-main-300: #cbd5e1;--v2p-color-main-350: #94a3b8cc;--v2p-color-main-400: #94a3b8;--v2p-color-main-500: #64748b;--v2p-color-main-600: #475569;--v2p-color-main-700: #334155;--v2p-color-main-800: #1e293b;--v2p-color-accent-50: #ecfdf5;--v2p-color-accent-100: #d1fae5;--v2p-color-accent-200: #a7f3d0;--v2p-color-accent-300: #6ee7b7;--v2p-color-accent-400: #34d399;--v2p-color-accent-500: #10b981;--v2p-color-accent-600: #059669;--v2p-color-orange-50: #fff7ed;--v2p-color-orange-100: #ffedd5;--v2p-color-orange-400: #fb923c;--v2p-color-background: #f2f3f5;--v2p-color-foreground: var(--v2p-color-main-800);--v2p-color-font-secondary: var(--v2p-color-main-500);--v2p-color-bg-content: #fff;--v2p-color-bg-footer: var(--v2p-color-bg-content);--v2p-color-bg-hover-btn: var(--v2p-color-main-200);--v2p-color-bg-subtle: rgb(236 253 245 / 90%);--v2p-color-bg-input: var(--v2p-color-main-50);--v2p-color-bg-search: var(--v2p-color-main-100);--v2p-color-bg-search-active: var(--v2p-color-main-200);--v2p-color-bg-widget: rgb(255 255 255 / 70%);--v2p-color-bg-reply: var(--v2p-color-main-100);--v2p-color-bg-tooltip: var(--v2p-color-bg-content);--v2p-color-heart: #ef4444;--v2p-color-heart-fill: #fee2e2;--v2p-color-mask: rgb(0 0 0 / 25%);--v2p-color-divider: var(--v2p-color-main-200);--v2p-color-border: var(--v2p-color-main-200);--v2p-color-border-darker: var(--v2p-color-main-300);--v2p-box-shadow: 0 3px 5px 0 rgb(0 0 0 / 4%);--v2p-widget-shadow: 0 9px 24px -3px rgb(0 0 0 / 6%), 0 4px 8px -1px rgb(0 0 0 /12%);--v2p-toast-shadow: 0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), 0 9px 28px 8px rgb(0 0 0 / 5%);--color-fade: var(--v2p-color-font-secondary);--color-gray: var(--v2p-color-font-secondary);--link-color: var(--v2p-color-foreground);--link-darker-color: var(--v2p-color-main-600);--link-hover-color: var(--v2p-color-foreground);--link-caution-color: var(--v2p-color-orange-400);--box-border-color: var(--v2p-color-border);--box-foreground-color: var(--v2p-color-foreground);--box-background-color: var(--v2p-color-bg-content);--box-background-alt-color: var(--v2p-color-main-100);--box-background-hover-color: var(--v2p-color-main-200);--box-border-focus-color: var(--v2p-color-main-200);--box-border-radius: 10px;--button-background-color: var(--v2p-color-main-100);--button-background-hover-color: var(--v2p-color-main-200);--button-hover-color: var(--button-background-hover-color);--button-foreground-color: var(--v2p-color-main-500);--button-foreground-hover-color: var(--v2p-color-main-600);--button-border-color: var(--v2p-color-main-300);--button-border-hover-color: var(--v2p-color-main-400);color:var(--v2p-color-foreground);font-family:system-ui,sans-serif;background-color:var(--v2p-color-background)}:root body #Logo{background-image:url("https://www.v2ex.com/static/img/v2ex@2x.png")}:root body ::selection{color:var(--v2p-color-main-100);background-color:var(--v2p-color-main-700)}:root body img::selection{background-color:var(--v2p-color-main-500)}:root body.v2p-theme-dark,:root[data-darkreader-scheme=dark] body{--v2p-color-main-100: #2d333b;--v2p-color-main-200: #374151;--v2p-color-main-300: #374151;--v2p-color-main-350: #6b7280cc;--v2p-color-main-400: #6b7280;--v2p-color-main-500: #9ca3af;--v2p-color-main-600: #9ca3af;--v2p-color-main-700: #d1d5db;--v2p-color-main-800: #e5e7eb;--v2p-color-main-900: #111827;--v2p-color-main-950: #030712;--v2p-color-accent-50: #064e3b;--v2p-color-accent-100: #065f46;--v2p-color-accent-200: #047857;--v2p-color-accent-300: #059669;--v2p-color-accent-400: #10b981;--v2p-color-accent-500: #34d399;--v2p-color-accent-600: #6ee7b7;--v2p-color-orange-50: #593600;--v2p-color-orange-100: #9a3412;--v2p-color-orange-400: #fbe090;--v2p-color-background: #1c2128;--v2p-color-foreground: #adbac7;--v2p-color-font-secondary: var(--v2p-color-main-600);--v2p-color-bg-content: #22272e;--v2p-color-bg-subtle: rgb(6 78 59 / 30%);--v2p-color-bg-input: var(--v2p-color-background);--v2p-color-bg-search: var(--v2p-color-main-200);--v2p-color-bg-search-active: var(--v2p-color-main-200);--v2p-color-bg-widget: var(--v2p-color-bg-content);--v2p-color-bg-reply: var(--v2p-color-main-100);--v2p-color-bg-tooltip: var(--v2p-color-main-100);--v2p-color-heart: #ef4444;--v2p-color-heart-fill: #fca5a5;--v2p-color-mask: rgb(99 110 123 / 40%);--v2p-color-border: #444c56;--v2p-color-border-darker: #444c56;--v2p-box-shadow: 0 0 0 1px var(--v2p-color-border);--v2p-toast-shadow: none;--link-color: var(--v2p-color-foreground);--box-background-color: var(--v2p-color-bg-content);--box-background-alt-color: var(--v2p-color-main-100);--box-background-hover-color: var(--v2p-color-main-300);--button-background-color: #373e47;--button-background-hover-color: #444c56;--button-hover-color: var(--button-background-hover-color);--button-foreground-color: var(--v2p-color-foreground);--button-foreground-hover-color: var(--v2p-color-foreground);--button-border-color: var(--v2p-color-border);--button-border-hover-color: #768390}:root body.v2p-theme-dark #Logo,:root[data-darkreader-scheme=dark] body #Logo{background-image:url("https://www.v2ex.com/static/img/v2ex-alt@2x.png")}:root body.v2p-theme-dark ::selection,:root[data-darkreader-scheme=dark] body ::selection{color:var(--v2p-color-background);background-color:var(--v2p-color-foreground)}:root body.v2p-theme-dark img::selection,:root[data-darkreader-scheme=dark] body img::selection{background-color:var(--v2p-color-foreground)}@supports selector(:has(*)){:root body:has(#Wrapper.Night){--v2p-color-main-100: #2d333b;--v2p-color-main-200: #374151;--v2p-color-main-300: #374151;--v2p-color-main-350: #6b7280cc;--v2p-color-main-400: #6b7280;--v2p-color-main-500: #9ca3af;--v2p-color-main-600: #9ca3af;--v2p-color-main-700: #d1d5db;--v2p-color-main-800: #e5e7eb;--v2p-color-main-900: #111827;--v2p-color-main-950: #030712;--v2p-color-accent-50: #064e3b;--v2p-color-accent-100: #065f46;--v2p-color-accent-200: #047857;--v2p-color-accent-300: #059669;--v2p-color-accent-400: #10b981;--v2p-color-accent-500: #34d399;--v2p-color-accent-600: #6ee7b7;--v2p-color-orange-50: #593600;--v2p-color-orange-100: #9a3412;--v2p-color-orange-400: #fbe090;--v2p-color-background: #1c2128;--v2p-color-foreground: #adbac7;--v2p-color-font-secondary: var(--v2p-color-main-600);--v2p-color-bg-content: #22272e;--v2p-color-bg-subtle: rgb(6 78 59 / 30%);--v2p-color-bg-input: var(--v2p-color-background);--v2p-color-bg-search: var(--v2p-color-main-200);--v2p-color-bg-search-active: var(--v2p-color-main-200);--v2p-color-bg-widget: var(--v2p-color-bg-content);--v2p-color-bg-reply: var(--v2p-color-main-100);--v2p-color-bg-tooltip: var(--v2p-color-main-100);--v2p-color-heart: #ef4444;--v2p-color-heart-fill: #fca5a5;--v2p-color-mask: rgb(99 110 123 / 40%);--v2p-color-border: #444c56;--v2p-color-border-darker: #444c56;--v2p-box-shadow: 0 0 0 1px var(--v2p-color-border);--v2p-toast-shadow: none;--link-color: var(--v2p-color-foreground);--box-background-color: var(--v2p-color-bg-content);--box-background-alt-color: var(--v2p-color-main-100);--box-background-hover-color: var(--v2p-color-main-300);--button-background-color: #373e47;--button-background-hover-color: #444c56;--button-hover-color: var(--button-background-hover-color);--button-foreground-color: var(--v2p-color-foreground);--button-foreground-hover-color: var(--v2p-color-foreground);--button-border-color: var(--v2p-color-border);--button-border-hover-color: #768390}:root body:has(#Wrapper.Night) #Logo{background-image:url("https://www.v2ex.com/static/img/v2ex-alt@2x.png")}:root body:has(#Wrapper.Night) ::selection{color:var(--v2p-color-background);background-color:var(--v2p-color-foreground)}:root body:has(#Wrapper.Night) img::selection{background-color:var(--v2p-color-foreground)}}@supports not selector(:has(*)){:root #Wrapper.Night{--v2p-color-main-100: #2d333b;--v2p-color-main-200: #374151;--v2p-color-main-300: #374151;--v2p-color-main-350: #6b7280cc;--v2p-color-main-400: #6b7280;--v2p-color-main-500: #9ca3af;--v2p-color-main-600: #9ca3af;--v2p-color-main-700: #d1d5db;--v2p-color-main-800: #e5e7eb;--v2p-color-main-900: #111827;--v2p-color-main-950: #030712;--v2p-color-accent-50: #064e3b;--v2p-color-accent-100: #065f46;--v2p-color-accent-200: #047857;--v2p-color-accent-300: #059669;--v2p-color-accent-400: #10b981;--v2p-color-accent-500: #34d399;--v2p-color-accent-600: #6ee7b7;--v2p-color-orange-50: #593600;--v2p-color-orange-100: #9a3412;--v2p-color-orange-400: #fbe090;--v2p-color-background: #1c2128;--v2p-color-foreground: #adbac7;--v2p-color-font-secondary: var(--v2p-color-main-600);--v2p-color-bg-content: #22272e;--v2p-color-bg-subtle: rgb(6 78 59 / 30%);--v2p-color-bg-input: var(--v2p-color-background);--v2p-color-bg-search: var(--v2p-color-main-200);--v2p-color-bg-search-active: var(--v2p-color-main-200);--v2p-color-bg-widget: var(--v2p-color-bg-content);--v2p-color-bg-reply: var(--v2p-color-main-100);--v2p-color-bg-tooltip: var(--v2p-color-main-100);--v2p-color-heart: #ef4444;--v2p-color-heart-fill: #fca5a5;--v2p-color-mask: rgb(99 110 123 / 40%);--v2p-color-border: #444c56;--v2p-color-border-darker: #444c56;--v2p-box-shadow: 0 0 0 1px var(--v2p-color-border);--v2p-toast-shadow: none;--link-color: var(--v2p-color-foreground);--box-background-color: var(--v2p-color-bg-content);--box-background-alt-color: var(--v2p-color-main-100);--box-background-hover-color: var(--v2p-color-main-300);--button-background-color: #373e47;--button-background-hover-color: #444c56;--button-hover-color: var(--button-background-hover-color);--button-foreground-color: var(--v2p-color-foreground);--button-foreground-hover-color: var(--v2p-color-foreground);--button-border-color: var(--v2p-color-border);--button-border-hover-color: #768390}:root #Wrapper.Night #Logo{background-image:url("https://www.v2ex.com/static/img/v2ex-alt@2x.png")}:root #Wrapper.Night ::selection{color:var(--v2p-color-background);background-color:var(--v2p-color-foreground)}:root #Wrapper.Night img::selection{background-color:var(--v2p-color-foreground)}}
  3705. :root{color-scheme:light}:root:has(#Wrapper.Night){color-scheme:dark}:root html,:root body{min-height:100vh}body{overflow:overlay;scrollbar-gutter:stable}body h1{font-weight:bold}body a{text-decoration:none;cursor:default}body a[href]{cursor:pointer}body a:hover{text-decoration:underline 1px;text-underline-offset:var(--v2p-underline-offset)}body pre{max-width:calc(830px - 2*var(--v2p-layout-gap) - 24px)}body #Top{height:55px;background-color:var(--v2p-color-bg-content);border:none}body #Bottom{color:var(--v2p-color-font-secondary);background-color:var(--v2p-color-bg-footer);border:none}body #Wrapper{background-color:inherit;background-image:none}body #Wrapper.Night{background-color:inherit;background-image:none}body #Wrapper .content{display:flex;gap:var(--v2p-layout-gap)}body #Leftbar{order:1;float:none}body #Main{flex:1;order:2;max-width:85vw;margin:0}body #Rightbar{order:3;float:none}body #search-container{height:30px;margin:0 30px;background-color:var(--v2p-color-bg-search);border:none;border-radius:6px}body #search-container::before{top:0;left:4px;background-size:14px 14px;opacity:.6;filter:none}body #search-container.active{background-color:var(--v2p-color-bg-search-active)}body #search-container #search-result{top:42px;z-index:var(--zidx-serach);color:var(--v2p-color-main-600);font-size:14px;background:var(--v2p-color-bg-widget);border:1px solid var(--box-border-color);box-shadow:var(--v2p-widget-shadow);backdrop-filter:blur(16px)}body #search-container #search-result .fade{color:var(--v2p-color-main-600)}body #search-container #search-result .search-item{color:var(--v2p-color-foreground);font-weight:bold;border-radius:5px}body #search-container #search-result .search-item.active{color:var(--v2p-color-foreground)}body #search-container #search-result .search-item.active.v2p-no-active{background-color:rgba(0,0,0,0)}body .box{color:var(--box-foreground-color);background-color:var(--v2p-color-bg-content);border:none;border-radius:var(--box-border-radius);box-shadow:var(--v2p-box-shadow)}body .box .header>h1{font-weight:bold;font-size:22px}body .box .header .gray{color:var(--color-gray)}body .button{--button-hover-shadow: 0 1.8px 0 var(--button-border-color), 0 1.8px 0 var(--button-background-color)}body .button.normal,body .button.super{position:relative;display:inline-flex;gap:5px;align-items:center;height:28px;padding:0 12px;color:var(--button-foreground-color);font-weight:500;font-size:14px;font-family:inherit;line-height:28px;white-space:nowrap;text-shadow:none;background:var(--button-background-color);border:none;border-radius:6px;outline:none;box-shadow:0 1.8px 0 var(--box-background-hover-color),0 1.8px 0 var(--button-background-color);cursor:pointer;transition:color .25s,background-color .25s,box-shadow .25s;user-select:none}body .button.normal:is(:hover:enabled,:active:enabled),body .button.super:is(:hover:enabled,:active:enabled){color:var(--button-foreground-hover-color);font-weight:500;text-shadow:none;background:var(--button-hover-color);border:none;box-shadow:var(--button-hover-shadow)}body .button.normal:is(.hover_now,.disable_now),body .button.super:is(.hover_now,.disable_now){color:var(--button-foreground-color) !important;text-shadow:none !important;background:var(--button-background-color) !important;border:none !important;box-shadow:0 1.8px 0 var(--box-background-hover-color) !important,0 1.8px 0 var(--button-background-color) !important}body .button.normal:is(.disable_now,:disabled),body .button.super:is(.disable_now,:disabled){color:var(--button-foreground-color);font-weight:500;text-shadow:none;background:var(--button-background-color);box-shadow:0 1.8px 0 var(--box-background-hover-color),0 1.8px 0 var(--button-background-color);cursor:default;opacity:.8;pointer-events:none}body .button.normal kbd,body .button.super kbd{position:relative;right:-4px;padding:0 3px;font-size:90%;font-family:inherit;line-height:initial;border:1px solid var(--button-border-color);border-radius:4px}body .button.special{--button-hover-shadow: 0 1.8px 0 var(--v2p-color-accent-200), 0 1.8px 0 var(--v2p-color-accent-100);color:var(--v2p-color-accent-500);background:var(--v2p-color-accent-100);box-shadow:var(--button-hover-shadow)}body .button.special:hover,body .button.special:hover:enabled{color:var(--v2p-color-accent-600);background:var(--v2p-color-accent-100);border:none;box-shadow:var(--button-hover-shadow)}body .button a{color:inherit;text-decoration:none}body .badge{padding:2px 5px;font-weight:bold;border:1px solid var(--v2p-color-accent-400);user-select:none}body .badge:first-child{border:1px solid var(--v2p-color-accent-400);border-top-left-radius:4px;border-bottom-left-radius:4px}body .badge:last-child{border:1px solid var(--v2p-color-accent-400);border-top-right-radius:4px;border-bottom-right-radius:4px}body .badge.op{color:var(--v2p-color-accent-500);background-color:var(--v2p-color-accent-50)}body .badge.mod{color:var(--v2p-color-bg-content);background-color:var(--v2p-color-accent-400)}body .badge.you{color:var(--v2p-color-orange-400);background-color:var(--v2p-color-orange-50);border:1px solid var(--v2p-color-orange-400)}body .badge.mini{height:1.2em;padding:0 3px;font-weight:normal;font-size:12px;line-height:1}body a.node:is(:active,:link,:visited){padding:5px 6px;color:var(--v2p-color-font-secondary);font-size:13px;background-color:var(--v2p-color-main-100);border-radius:4px}body a.node:is(:active,:link,:visited):hover{color:var(--v2p-color-main-500);background-color:var(--v2p-color-main-200)}body .outdated{font-size:12px;border-color:var(--v2p-color-main-200);border-bottom:none}body :is(.page_normal,.page_current):is(:link,:visited){padding:6px 9px;font-size:14px;border:none;border-radius:4px;user-select:none}body .page_normal:is(:link,:visited){font-weight:500;background-color:var(--v2p-color-bg-content);box-shadow:0 2px 2px var(--box-background-hover-color);transition:transform .25s}body .page_normal:is(:link,:visited):hover{transform:scale(1.1) translateY(-2px)}body .page_current:is(:link,:visited){font-weight:bold;background-color:var(--box-background-hover-color);box-shadow:none;pointer-events:none}body .page_input{display:none}body .dock_area{background:var(--v2p-color-main-200)}body .member-activity-bar{background-color:var(--v2p-color-main-200)}body .member-activity-bar .member-activity-start{background-color:var(--v2p-color-accent-200)}body .member-activity-bar .member-activity-fourth{background-color:var(--v2p-color-accent-400)}body .member-activity-bar .member-activity-half{background-color:var(--v2p-color-accent-500)}body .member-activity-bar .member-activity-almost{background-color:var(--v2p-color-accent-600)}body .member-activity-bar .member-activity-done{background-color:var(--v2p-color-orange-400)}body .online{user-select:none}body #topic_supplement{height:unset;min-height:550px !important;max-height:800px !important;overflow:hidden;color:currentColor;font-size:15px;background-color:var(--v2p-color-bg-input);border:1px solid var(--button-border-color);border-radius:8px;transition:opacity .25s;resize:none;overflow-y:auto}body #topic_supplement::placeholder{color:var(--v2p-color-main-500);font-size:15px}body #topic_supplement:is(:focus,:focus-within){background-color:rgba(0,0,0,0);outline:none;box-shadow:0 0 0 1px var(--button-border-color)}body .item_hot_topic_title{--offset: 2.4px;display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-height:1.4;padding:var(--offset) 0;text-shadow:none}body .item_hot_topic_title>a:hover{text-underline-offset:var(--offset)}body form textarea#topic_title{height:unset;min-height:75px !important;max-height:800px !important;overflow:hidden;color:currentColor;font-size:15px;background-color:var(--v2p-color-bg-input);border:1px solid var(--button-border-color);border-radius:8px;transition:opacity .25s;resize:none}body form textarea#topic_title::placeholder{color:var(--v2p-color-main-500);font-size:15px}body form textarea#topic_title:is(:focus,:focus-within){background-color:rgba(0,0,0,0);outline:none;box-shadow:0 0 0 1px var(--button-border-color)}body form #topic_title{height:unset;min-height:30px !important;max-height:800px !important;overflow:hidden;color:currentColor;font-size:15px;background-color:var(--v2p-color-bg-input);border:1px solid var(--button-border-color);border-radius:8px;transition:opacity .25s;resize:none}body form #topic_title::placeholder{color:var(--v2p-color-main-500);font-size:15px}body form #topic_title:is(:focus,:focus-within){background-color:rgba(0,0,0,0);outline:none;box-shadow:0 0 0 1px var(--button-border-color)}body form #topic_content{height:unset;min-height:120px !important;max-height:800px !important;overflow:hidden;color:currentColor;font-size:15px;background-color:var(--v2p-color-bg-input);border:1px solid var(--button-border-color);border-radius:8px;transition:opacity .25s;resize:none}body form #topic_content::placeholder{color:var(--v2p-color-main-500);font-size:15px}body form #topic_content:is(:focus,:focus-within){background-color:rgba(0,0,0,0);outline:none;box-shadow:0 0 0 1px var(--button-border-color)}body #syntax-selector .radio-group{padding:3px;background-color:var(--v2p-color-background)}body #syntax-selector .radio-group>input[type=radio]:checked+label{background-color:var(--v2p-color-accent-100)}body #syntax-selector .radio-group>input[type=radio]+label{font-size:13px;cursor:pointer}body #syntax-selector label{color:var(--v2p-color-foreground)}body .snow{color:var(--v2p-color-main-400)}body .orange-dot{background:var(--v2p-color-orange-400)}body form[action="/notes/new"] .cell{background-color:rgba(0,0,0,0) !important}body .alt{background-color:var(--v2p-color-bg-input);border:1px solid var(--button-border-color)}body a.btn_hero{border-color:var(--v2p-color-foreground)}body a.btn_hero:hover{background-color:var(--v2p-color-foreground)}body .cell_ops{background-color:rgba(0,0,0,0)}body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href^=http]{text-decoration:underline 2px;text-underline-offset:.46ex;color:currentColor;background-color:var(--v2p-color-main-100)}body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href^=http]:hover{background-color:var(--v2p-color-main-200)}body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href*="v2ex.com/t"],body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href^="/t"],body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href^="/go"]{text-decoration:underline 2px;text-underline-offset:.46ex;color:var(--v2p-color-accent-500);background-color:var(--v2p-color-accent-50)}body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href*="v2ex.com/t"]:hover,body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href^="/t"]:hover,body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons) a[href^="/go"]:hover{color:var(--v2p-color-accent-500);background-color:var(--v2p-color-accent-50)}body .select2-container--default .select2-selection--single{background-color:var(--v2p-color-background);border:1px solid var(--v2p-color-border)}body .select2-container--default .select2-selection--single .select2-selection__placeholder{color:var(--v2p-color-foreground)}body .problem{color:currentColor;color:var(--v2p-color-orange-400);background-color:var(--v2p-color-orange-50);border-color:var(--v2p-color-orange-400);border-bottom:none}body .markdown_body table{border-top:1px solid var(--v2p-color-border-darker);box-shadow:none}body .markdown_body table tr th,body .markdown_body table tr td{border:1px solid var(--v2p-color-border-darker)}body .markdown_body table tr:nth-child(2n){background-color:var(--box-background-alt-color)}body .social_label:is(:link,:visited,:active){background-color:var(--v2p-color-main-100);box-shadow:none}body .social_label:is(:link,:visited,:active):hover{background-color:var(--v2p-color-main-200)}body .green{color:var(--v2p-color-accent-500)}body .message{color:var(--v2p-color-orange-400);background-color:var(--v2p-color-orange-50);border:none}body .balance_area,body a.balance_area:is(:link,:visited){display:inline-flex;gap:3px;align-items:center;color:var(--v2p-color-foreground);font-weight:600;text-shadow:none;background:var(--v2p-color-main-100)}body .balance_area:hover,body a.balance_area:is(:link,:visited):hover{background:var(--v2p-color-main-200)}body :is(.subtle){background-color:var(--v2p-color-bg-subtle);border-left:3px solid var(--v2p-color-accent-200)}body :is(.subtle) .topic_content{font-size:15px}.box .tag:link,.box .tag:visited{color:var(--v2p-color-font-secondary);font-size:12px;background-color:var(--v2p-color-main-100);border-radius:5px}.box .tag::before{color:var(--v2p-color-main-500)}.box .tag>li{opacity:.6}#Top .content{height:100%}#Top .site-nav{height:100%;padding:0}#Top .tools{display:flex;gap:8px 14px;align-items:center;justify-content:flex-end;font-weight:400;font-size:14px}#Top .tools .top{height:26px;margin-left:0;padding:0 6px;color:var(--v2p-color-main-500);line-height:26px;white-space:nowrap;border-radius:4px}#Top .tools .top:hover{color:var(--v2p-color-foreground)}#Top .tools .top:not(.v2p-hover-btn):hover{background-color:var(--v2p-color-main-100)}#Main>.box{padding:0 12px}#Main>.box.node-header>.cell{margin:0 -12px}#Main>.box .cell{padding:20px 10px;background-image:none !important}#Main>.box .cell_ops{padding:15px 5px}#Main .topic_buttons{display:flex;flex-wrap:wrap;align-items:center;padding:8px 0;column-gap:5px;background:none}#Main .topic_buttons .topic_stats{flex:1;order:99;float:none;margin-left:10px;padding:0 !important;font-size:12px;text-shadow:none}#Main .topic_buttons .topic_thanked{font-size:12px}#Main .topic_buttons a.tb:link{display:flex;flex-direction:row-reverse;align-items:center;padding:5px;white-space:nowrap;text-shadow:none;column-gap:5px;background:none;border-radius:4px}#Main .topic_buttons a.tb:link:not(.v2p-hover-btn){color:var(--v2p-color-font-secondary)}#Main .topic_buttons a.tb:link:hover:not(.v2p-hover-btn){color:currentColor;background:var(--v2p-color-main-100)}#Main .vote:link{color:var(--v2p-color-main-500);border-color:var(--v2p-color-main-300);border-radius:5px}#Main .vote:link:hover{box-shadow:0 2px 2px var(--v2p-color-main-200)}#Main .cell .topic-link{color:var(--v2p-color-foreground);text-decoration:none}#Main .cell .topic-link:visited{color:var(--v2p-color-font-secondary)}#Main .cell .topic_info{position:relative;display:flex;align-items:center;user-select:none;pointer-events:none}#Main .cell .topic_info::after{position:absolute;inset:0 0 -6px;z-index:1;background-color:var(--v2p-color-bg-content);content:""}#Main .cell .topic_info .votes,#Main .cell .topic_info .node,#Main .cell .topic_info strong:first-of-type,#Main .cell .topic_info span:first-of-type{position:relative;z-index:2;pointer-events:auto}#Main .cell .topic_info a[href^="/member"]{color:var(--v2p-color-main-500);font-weight:500}#Main .cell .count_livid{display:inline-block;padding:5px 10px;font-weight:400;font-size:12px;white-space:nowrap;border-radius:5px;user-select:none;color:var(--v2p-color-main-500);background-color:var(--v2p-color-main-200)}#Main .cell .count_orange{display:inline-block;padding:5px 10px;font-weight:400;font-size:12px;white-space:nowrap;border-radius:5px;user-select:none;color:var(--v2p-color-main-100);font-weight:bold;background-color:var(--v2p-color-orange-400)}#Main .cell .item_title .topic-link{font-weight:bold}#Main .cell.item tr>td:nth-child(2){width:30px}#Main .box>.cell[id^=r]:not(:has(.cell[id^=r])) .reply_content{padding-bottom:0}#Main .cell[id^=r]{--bg-reply: var(--v2p-color-bg-content);background-color:var(--bg-reply)}#Main .cell[id^=r]:not(:has(+.cell[id^=r])){border-bottom:none}#Main .cell[id^=r]:hover>table td:last-of-type .fr a{opacity:1}#Main .cell[id^=r] .reply_content{padding-bottom:10px}#Main .cell[id^=r]>table:first-of-type td:first-of-type{width:40px}#Main .cell[id^=r]>table:first-of-type td:first-of-type .avatar{width:40px !important;height:40px !important;border-radius:5px;aspect-ratio:1}#Main .cell[id^=r]>table~.cell[id^=r]{--bg-reply: var(--v2p-color-bg-reply);position:relative;z-index:var(--zidx-expand-btn);padding:15px 0 0 15px;border:none;border-radius:0;box-shadow:-2.4px 0 var(--v2p-color-border-darker)}#Main .cell[id^=r]>table~.cell[id^=r] .cell[id^=r]{padding:0;box-shadow:none}#Main .cell[id^=r]>table~.cell[id^=r] .cell[id^=r].v2p-indent{padding-left:15px;border-left:1px solid var(--v2p-color-border-darker)}#Main .cell[id^=r]>table~.cell[id^=r] tr td:first-of-type{width:25px}#Main .cell[id^=r]>table~.cell[id^=r] tr td:first-of-type .avatar{width:25px !important;height:25px !important;border-radius:4px}#Main .cell[id^=r]>table~.cell[id^=r] tr td:nth-child(3) strong a{font-size:13px}#Main .cell[id^=r]>table~.cell[id^=r] .reply_content{padding-right:5px;font-size:15px}#Main .cell[id^=r]>table td:nth-of-type(2){width:15px}#Main .cell[id^=r]>table td:last-of-type a.dark{color:var(--v2p-color-main-600);text-decoration:none}#Main .cell[id^=r]>table td:last-of-type a.dark:hover{text-decoration:none}#Main .cell[id^=r]>table td:last-of-type .fr{position:relative;top:-3px;user-select:none}#Main .cell[id^=r]>table td:last-of-type .fr a{opacity:0}#Main .cell[id^=r]>table td:last-of-type .fr+.sep3{height:0}#Main .cell[id^=r]:last-of-type{border:none}#Main .cell[id^=r] .no{position:relative;top:-4px;padding:5px 10px;color:var(--v2p-color-main-350);font-size:12px;background-color:rgba(0,0,0,0);border-radius:5px;user-select:none}#Main #Tabs{position:sticky;top:0;z-index:var(--zidx-tabs);display:flex;flex-wrap:wrap;gap:6px 8px;align-items:center;padding:10px;background-color:var(--v2p-color-bg-content);border-bottom:1px solid var(--box-border-color);user-select:none}#Main #Tabs .tab{margin:0}#Main #SecondaryTabs{padding:10px;background-color:var(--v2p-color-main-100);border-radius:5px}#Main .topic_content,#Main .reply_content{color:currentColor;font-size:15.4px}#Main .topic_content a[href^="/member"],#Main .reply_content a[href^="/member"]{position:relative;bottom:1px;color:var(--v2p-color-main-500);font-size:13px;text-decoration:underline;text-underline-offset:.4ex}#Main .thank_area{font-size:12px}#Main .tab{color:var(--v2p-color-foreground);background-color:rgba(0,0,0,0);user-select:none}#Main .tab:not(.v2p-hover-btn):hover{background-color:var(--v2p-color-main-100)}#Main .tab_current{color:var(--box-background-color);background-color:var(--box-foreground-color);user-select:none}#Main #reply-box.reply-box-sticky{bottom:20px;z-index:var(--zidx-reply-box);margin:0 -10px;padding:0 22px;overflow:visible;border:none;border-radius:var(--box-border-radius);outline:2px solid var(--v2p-color-main-200)}#Main #reply-box .v2p-reply-wrap #reply_content{background-color:rgba(0,0,0,0);border:none}#Main #reply-box .v2p-reply-wrap #reply_content:focus{background-color:var(--v2p-color-bg-content);outline:none}#Main #reply-box .v2p-reply-wrap #reply_content::placeholder{color:var(--v2p-color-main-500);font-size:14px}#Main #reply-box .flex-one-row:last-of-type{flex-direction:row-reverse;gap:10px;justify-content:flex-start}#Main #reply-box .flex-one-row:last-of-type .gray{margin-right:auto}#Main #reply-box>.cell{font-size:12px}#Main #reply-box>.cell.flex-one-row{min-height:45px;padding:0 10px;border:none}#Main #reply-box>.cell.flex-row-end{padding:12px 10px;border:none}#Main #reply-box>.cell:has(form){padding-top:0}#Main #no-comments-yet{color:var(--color-gray);border-color:var(--color-gray)}#Main #notifications .cell[id^=n]:hover .node{opacity:1}#Main #notifications .cell[id^=n] .node{opacity:0}#Main #notifications .cell[id^=n] .payload{color:var(--v2p-color-foreground);background-color:var(--v2p-color-main-100)}#Main #notifications .cell[id^=n] .topic-link:visited{color:var(--v2p-color-foreground)}#Main .cell_tabs .cell_tab_current{font-weight:bold;border-color:var(--v2p-color-foreground)}#Main .cell_tabs .cell_tab{color:var(--v2p-color-foreground)}#Main .cell_tabs .cell_tab:hover{border-color:var(--v2p-color-main-300)}#Rightbar .cell:has(.light-toggle){font-size:13px}#Rightbar a.dark:is(:link,:active,:visited,:hover){color:var(--v2p-color-main-500)}#Rightbar a.dark:is(:link,:active,:visited,:hover):hover{color:var(--v2p-color-main-600)}#Bottom{position:sticky;top:100%}#Bottom a.dark{font-weight:400;font-size:13px}#Bottom a.dark:is(:link,:active,:visited,:hover){color:var(--v2p-color-main-500)}
  3706. \uFEFFbody{position:relative}body.v2p-modal-open{overflow:hidden}body .button.v2p-prev-btn,body .button.v2p-next-btn{padding:0 15px}.v2p-hover-btn{position:relative;z-index:1;margin:0;white-space:nowrap;text-decoration:none;background:none;background-color:rgba(0,0,0,0);cursor:pointer;transition:color .2s;user-select:none}.v2p-hover-btn::before{position:absolute;inset:0 -5px;z-index:-1;background-color:var(--v2p-color-bg-hover-btn);border-radius:5px;transform:scale(0.65);opacity:0;transition:background-color .2s,color .2s,transform .2s,opacity .2s;content:""}.v2p-hover-btn:hover{text-decoration:none}.v2p-hover-btn:hover::before{transform:scale(1);opacity:1}.v2p-hover-btn-disabled{opacity:.8;pointer-events:none}.v2p-icon-heart{display:inline-flex;width:16px;height:16px;color:var(--v2p-color-heart)}.v2p-icon-heart svg{fill:var(--v2p-color-heart-fill)}#Main .cell:hover .v2p-topic-preview-btn{visibility:visible}#Rightbar .v2p-info-row{display:block;color:var(--v2p-color-accent-500);font-size:12px;text-align:center}#Rightbar .v2p-info-row:hover{text-decoration:none;background-color:var(--v2p-color-accent-50)}.v2p-tool-box{position:sticky;top:20px;z-index:var(--zidx-tools-card)}.v2p-tool-box .v2p-tools{display:grid;grid-auto-rows:auto;grid-template-columns:repeat(3, 1fr);gap:8px 15px;align-items:center;justify-content:center;color:var(--v2p-color-main-600);font-size:12px}.v2p-tool{display:inline-flex;gap:0 5px;align-items:center;padding:3px 0}.v2p-tool .v2p-tool-icon{width:16px;height:16px}.v2p-topic-preview-btn{position:relative;top:-1px;margin-left:10px;color:var(--button-foreground-color);font-size:14px;background-color:var(--button-hover-color);border:none;border-radius:3px;outline:none;visibility:hidden;cursor:pointer}.v2p-topic-preview{padding:25px;line-height:1.4}.v2p-tp-info-bar{display:flex;gap:10px;align-items:center;margin-bottom:10px}.v2p-tp-info,.v2p-tp-read{display:inline-flex;gap:20px;align-items:center;padding:5px 10px;overflow:hidden;font-size:13px;background-color:var(--v2p-color-main-200);border-radius:5px}.v2p-tp-read{gap:4px;cursor:pointer;user-select:none}.v2p-tp-read-icon{width:16px;height:16px}.v2p-tp-member{display:inline-flex;gap:5px;align-items:center;font-weight:bold}.v2p-tp-avatar{width:20px;height:20px;border-radius:3px}.v2p-topic-preview-addons{margin-top:30px}a.v2p-topic-preview-title-link:hover{text-decoration:underline 2px;text-underline-offset:.46ex}.v2p-dot{margin:0 8px;font-weight:800;font-size:15px}.v2p-paging{background:none !important}.v2p-paging.cell{border-bottom:none}.v2p-model-mask{position:fixed;z-index:var(--zidx-model-mask);padding:60px;overflow:hidden;overflow-y:auto;background-color:var(--v2p-color-mask);inset:0}.v2p-popup{position:absolute;top:0;left:0;z-index:var(--zidx-popup);font-size:14px;background:var(--v2p-color-bg-widget);border:1px solid var(--box-border-color);border-radius:8px;box-shadow:var(--v2p-widget-shadow);backdrop-filter:blur(16px)}.v2p-popup-content{width:max-content;overflow-y:auto}.v2p-toast{position:fixed;top:50px;left:50%;z-index:var(--zidx-toast);padding:10px 15px;color:var(--v2p-color-background);font-size:14px;background:var(--v2p-color-foreground);border-radius:8px;box-shadow:var(--v2p-toast-shadow);transform:translateX(-50%)}.v2p-model-main{position:relative;box-sizing:border-box;width:800px;height:100%;margin:0 auto;overflow-x:hidden;overflow-y:auto;background-color:var(--v2p-color-bg-content);border-radius:var(--box-border-radius)}.v2p-model-header{position:sticky;top:0;right:0;left:0;z-index:var(--zidx-model-header);display:flex;gap:0 20px;align-items:center;padding:15px 20px 20px;background-color:var(--v2p-color-bg-content);border-bottom:1px solid var(--box-border-color)}.v2p-model-title{padding:2px 0;overflow:hidden;font-weight:bold;font-size:16px;white-space:nowrap;text-overflow:ellipsis}.v2p-model-actions{display:flex;gap:0 10px;align-items:center;margin-left:auto}.v2p-model-loading{display:flex;align-items:center;justify-content:center;padding:50px 0;color:currentColor}.v2p-model-loading .v2p-icon-loading{position:relative;right:-13px;width:50px}.v2p-no-pat{padding:30px 10px;font-size:15px;text-align:center}.v2p-no-pat .v2p-no-pat-title{font-weight:bold;font-size:16px}.v2p-no-pat .v2p-no-pat-desc{display:flex;align-items:center;justify-content:center;margin-top:15px}.v2p-no-pat .v2p-no-pat-block{display:inline-flex;align-items:center;margin:0 5px;padding:2px 10px;background-color:var(--v2p-color-main-100);border-radius:2px}.v2p-no-pat .v2p-no-pat-steps{display:flex;flex-wrap:wrap;gap:20px;max-width:800px;margin-top:20px;padding:20px;background-color:var(--v2p-color-main-100);border-radius:10px}.v2p-no-pat .v2p-no-pat-step{flex:1}.v2p-no-pat .v2p-no-pat-img{width:100%;border-radius:8px;box-shadow:var(--v2p-widget-shadow)}.v2p-no-pat .v2p-icon-logo{width:15px;height:15px}.v2p-likes-box{position:relative;top:3px;display:inline-flex;align-items:center;column-gap:5px;user-select:none}.v2p-likes-box.v2p-thanked{color:var(--v2p-color-heart);font-weight:bold;opacity:.8}.v2p-likes-box.v2p-thanked .v2p-icon-heart svg{fill:var(--v2p-color-heart)}@supports not selector(:has(*)){#Main .cell[id^=r]>table:hover .v2p-controls{opacity:1}}@supports selector(:has(*)){#Main .cell[id^=r]:not(:has(.cell:hover))>table:hover .v2p-controls{opacity:1}}.v2p-controls{display:inline-flex;align-items:center;margin-right:15px;font-size:12px;column-gap:15px;opacity:0}.v2p-controls>a{text-decoration:none}.v2p-control{position:relative;display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:4px 0;color:var(--v2p-color-main-500)}.v2p-control:hover{color:var(--v2p-color-main-600)}.v2p-control.v2p-thanked{color:var(--v2p-color-heart);cursor:default}.v2p-control::after{z-index:var(--zidx-tip);width:max-content;min-width:30px;padding:2px 5px;overflow:hidden;color:var(--v2p-color-foreground);font-size:12px;white-space:nowrap;text-align:center;background-color:var(--v2p-color-bg-tooltip);border-radius:4px;box-shadow:var(--v2p-widget-shadow);pointer-events:none;position:absolute;top:-8px;transform:translateY(-100%);opacity:0}.v2p-control:hover::after{opacity:1}.v2p-control.v2p-control-hide::after{content:"\u9690\u85CF\u56DE\u590D"}.v2p-control.v2p-control-thank::after{content:"\u611F\u8C22\u56DE\u590D"}.v2p-control.v2p-control-thank.v2p-thanked::after{content:"\u5DF2\u611F\u8C22"}.v2p-control.v2p-control-reply::after{content:"\u56DE\u590D"}.topic_buttons .v2p-tb.v2p-hover-btn{color:var(--v2p-color-font-secondary)}.topic_buttons .v2p-tb.v2p-hover-btn:hover{color:currentColor}.topic_buttons .v2p-tb.v2p-hover-btn::after{display:none}.v2p-tb-icon{width:15px;height:15px}.v2p-emoji-container{max-height:285px;padding:15px 18px;overflow-y:auto;color:var(--v2p-color-main-600)}.v2p-member-card{max-width:300px;max-height:285px;padding:12px;font-size:13px;text-align:left}.v2p-member-card .v2p-info{display:flex;gap:15px}.v2p-member-card .v2p-info-right{padding:2px 0}.v2p-member-card .v2p-avatar-box{display:inline-block;width:73px;height:73px;overflow:hidden;background-color:var(--button-background-hover-color);border-radius:5px}.v2p-member-card .v2p-avatar{width:100%;height:100%}.v2p-member-card .v2p-username{font-weight:bold;font-size:16px}.v2p-member-card .v2p-no{margin:5px 0}.v2p-member-card .v2p-no,.v2p-member-card .v2p-created-date{width:160px;height:16px}.v2p-member-card .v2p-loading{background-color:var(--button-background-hover-color);border-radius:4px}.v2p-member-card .v2p-bio{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-height:1.4;margin-top:10px}.v2p-member-card-actions{padding:10px 0 0}.v2p-reply-tags{display:inline-block;margin-bottom:2px;padding:0 3px;font-size:12px;background-color:var(--v2p-color-main-200);border-radius:3px;cursor:pointer}.v2p-emoticons-box{font-size:15px}.v2p-emoji-group~.v2p-emoji-group{margin-top:10px}.v2p-emoji-title{margin:0 0 10px;font-size:14px;text-align:left}.v2p-emoji-list{display:grid;grid-template-columns:repeat(8, 1fr);gap:6px;font-size:20px}.v2p-emoji{padding:2px;border-radius:4px;cursor:pointer}.v2p-emoji:hover{background-color:var(--box-background-hover-color)}.v2p-decode{position:relative;padding:2px 4px;color:var(--v2p-color-orange-400);font-size:13px;text-decoration:none;background-color:var(--v2p-color-orange-50);cursor:copy}.v2p-decode:hover{color:var(--v2p-color-orange-400)}.v2p-decode:hover::after{opacity:1}.v2p-decode::after{z-index:var(--zidx-tip);width:max-content;min-width:30px;padding:2px 5px;overflow:hidden;color:var(--v2p-color-foreground);font-size:12px;white-space:nowrap;text-align:center;background-color:var(--v2p-color-bg-tooltip);border-radius:4px;box-shadow:var(--v2p-widget-shadow);pointer-events:none;position:absolute;top:-8px;left:50%;transform:translate(-50%, -100%);opacity:0;content:attr(data-title)}.v2p-reply-content{position:relative}.v2p-reply-content .v2p-expand-btn.normal.button{position:absolute;bottom:5px;left:50%;z-index:var(--zidx-expand-btn);font-weight:400;font-size:12px;transform:translateX(-50%)}.v2p-reply-content.v2p-collapsed::before{position:absolute;right:0;bottom:0;left:0;z-index:var(--zidx-expand-mask);height:130px;background:linear-gradient(to top, var(--bg-reply) 10px, transparent);content:"";pointer-events:none}.v2p-reply-content.v2p-collapsed .v2p-expand-btn.normal.button{bottom:10px;transform:translateX(-50%)}.cell[id^=r] .cell[id^=r] .v2p-reply-content .v2p-expand-btn.normal.button{color:var(--button-foreground-color);background:var(--button-hover-color);box-shadow:var(--button-hover-shadow)}.v2p-empty-content{display:flex;flex-direction:column;align-items:center;padding-top:20px;color:var(--v2p-color-font-secondary);font-size:14px}.v2p-empty-content .v2p-text-emoji{font-size:20px}.v2p-topic-reply-ref{margin:0 -10px 15px;padding:5px 10px;color:var(--v2p-color-main-500);font-size:13px;background-color:var(--v2p-color-main-100);border-radius:5px}.v2p-topic-reply-box{margin-top:50px;padding:30px 0;color:var(--v2p-color-main-500);font-size:14px;line-height:1.55;border-top:1px solid var(--v2p-color-divider)}.v2p-topic-reply~.v2p-topic-reply{margin-top:15px}.v2p-topic-reply-member{display:inline;color:var(--v2p-color-main-700);font-weight:bold}.v2p-topic-reply-avatar{position:relative;top:2px;width:15px;height:15px;margin-right:5px;object-fit:cover;overflow:hidden;background-color:var(--v2p-color-main-200);border-radius:2px}.v2p-topic-reply-content{display:inline}.v2p-more-reply-tip{margin-top:20px;color:var(--v2p-color-main-400);font-size:13px;text-align:center}.v2p-reply-wrap{height:unset;min-height:140px !important;max-height:800px !important;overflow:hidden;color:currentColor;font-size:15px;background-color:var(--v2p-color-bg-input);border:1px solid var(--button-border-color);border-radius:8px;transition:opacity .25s;resize:none}.v2p-reply-wrap::placeholder{color:var(--v2p-color-main-500);font-size:15px}.v2p-reply-wrap:is(:focus,:focus-within){background-color:rgba(0,0,0,0);outline:none;box-shadow:0 0 0 1px var(--button-border-color)}.v2p-reply-upload-bar{padding:6px 10px;color:var(--v2p-color-main-500);font-size:12px;background-color:var(--v2p-color-bg-input);border-top:1px dashed var(--v2p-color-main-300);cursor:pointer}.v2p-reply-upload-bar-disabled{pointer-events:none}.v2p-footer{position:relative;display:flex;align-items:center;justify-content:space-between;padding:35px 10px;color:var(--v2p-color-main-500);font-size:12px;border-top:1px solid var(--v2p-color-divider)}.v2p-footer a:hover{text-decoration:none}.v2p-footer-logo{--logo-size: 16px;position:absolute;top:calc(-1*(var(--logo-size) + 5px)/2);left:50%;display:inline-flex;box-sizing:border-box;padding:3px 25px;background-color:var(--v2p-color-bg-footer);transform:translateX(-50%)}.v2p-footer-logo svg{width:var(--logo-size)}.v2p-footer-text{display:inline-flex;align-items:center;justify-content:flex-start;width:240px;color:var(--v2p-color-font-secondary)}.v2p-footer-links{display:inline-flex;gap:0 8px;align-items:center}.v2p-footer-link{padding:4px 5px;color:currentColor}.v2p-footer-brand{display:inline-flex;gap:0 15px;align-items:center;justify-content:flex-end;width:240px}.v2p-footer-brand>span{width:20px}.v2p-color-mode-toggle{width:22px;height:22px;opacity:.8}.v2p-color-mode-toggle:hover{opacity:1}.v2p-reply-tools-box{position:relative;display:inline-flex;gap:0 5px;align-items:center;margin-right:auto;padding:2px 0;font-size:13px}.v2p-reply-tools-icon{display:inline-block;width:16px;height:16px}.v2p-reply-tool-content{padding:5px;border-radius:5px}.v2p-reply-tool{padding:5px 10px;white-space:nowrap;border-radius:4px;cursor:pointer}.v2p-reply-tool:hover{background-color:var(--v2p-color-main-200)}
  3707. `;
  3708.  
  3709. // src/user-scripts/index.ts
  3710. if (typeof window.GM_addStyle !== "undefined") {
  3711. window.GM_addStyle(style);
  3712. } else {
  3713. document.addEventListener("DOMContentLoaded", () => {
  3714. $(`<style type='text/css'>${style}</style>`).appendTo("head");
  3715. });
  3716. }
  3717. document.addEventListener("DOMContentLoaded", () => {
  3718. const commonRegex = patternToRegex(
  3719. "https://v2ex.com/*",
  3720. "https://www.v2ex.com/*",
  3721. "https://cn.v2ex.com/*"
  3722. );
  3723. const topicRegex = patternToRegex(
  3724. "https://v2ex.com/t/*",
  3725. "https://www.v2ex.com/t/*",
  3726. "https://cn.v2ex.com/t/*"
  3727. );
  3728. const writeRegex = patternToRegex(
  3729. "https://v2ex.com/write*",
  3730. "https://www.v2ex.com/write*",
  3731. "https://cn.v2ex.com/write*"
  3732. );
  3733. const url = window.location.href;
  3734. void (async () => {
  3735. if (commonRegex.test(url)) {
  3736. await Promise.resolve().then(() => (init_common(), common_exports));
  3737. await Promise.resolve().then(() => (init_home(), home_exports));
  3738. }
  3739. if (topicRegex.test(url)) {
  3740. await Promise.resolve().then(() => (init_topic(), topic_exports));
  3741. }
  3742. if (writeRegex.test(url)) {
  3743. await Promise.resolve().then(() => (init_write2(), write_exports));
  3744. }
  3745. })();
  3746. });
  3747.