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

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

目前为 2024-01-12 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name ▲V2EX Polish - 体验更现代化的 V2EX 🟢
  3. // @namespace LeoKu(https://leoku.top)
  4. // @version 1.8.11
  5. // @description 一款专为 V2EX 用户设计的浏览器插件,提供了丰富的扩展功能,让原生页面焕然一新!✨
  6. // @author LeoKu
  7. // @match https://*.v2ex.com/*
  8. // @match https://v2ex.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
  10. // @run-at document-start
  11. // @grant GM_addStyle
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. "use strict";
  16. var __getOwnPropNames = Object.getOwnPropertyNames;
  17. var __esm = (fn, res) => function __init() {
  18. return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
  19. };
  20.  
  21. // src/constants.ts
  22. var EXTENSION_NAME, biliEmojiLink, emoticons, READABLE_CONTENT_HEIGHT, MAX_CONTENT_HEIGHT, READING_CONTENT_LIMIT, dataExpiryTime, imgurClientIdPool, defaultOptions;
  23. var init_constants = __esm({
  24. "src/constants.ts"() {
  25. "use strict";
  26. EXTENSION_NAME = "V2EX_Polish";
  27. biliEmojiLink = {
  28. ["[\u8131\u5355doge]" /* 脱单doge */]: "https://i.imgur.com/L62ZP7V.png",
  29. ["[doge]" /* doge */]: "https://i.imgur.com/agAJ0Rd.png",
  30. ["[\u8FA3\u773C\u775B]" /* 辣眼睛 */]: "https://i.imgur.com/n119Wvk.png",
  31. ["[\u7591\u60D1]" /* 疑惑 */]: "https://i.imgur.com/U3hKhrT.png",
  32. ["[\u6342\u8138]" /* 捂脸 */]: "https://i.imgur.com/14cwgsI.png",
  33. ["[\u54E6\u547C]" /* 哦呼 */]: "https://i.imgur.com/km62MY2.png",
  34. ["[\u50B2\u5A07]" /* 傲娇 */]: "https://i.imgur.com/TkdeN49.png",
  35. ["[\u601D\u8003]" /* 思考 */]: "https://i.imgur.com/MAyk5GN.png",
  36. ["[\u5403\u74DC]" /* 吃瓜 */]: "https://i.imgur.com/Ug1iMq4.png",
  37. ["[\u65E0\u8BED]" /* 无语 */]: "https://i.imgur.com/e1q9ScT.png",
  38. ["[\u5927\u54ED]" /* 大哭 */]: "https://i.imgur.com/YGIx7lh.png",
  39. ["[\u9178\u4E86]" /* 酸了 */]: "https://i.imgur.com/5FDsp6L.png",
  40. ["[\u6253call]" /* 打call */]: "https://i.imgur.com/pmNOo2w.png",
  41. ["[\u6B6A\u5634]" /* 歪嘴 */]: "https://i.imgur.com/XzEYBoY.png",
  42. ["[\u661F\u661F\u773C]" /* 星星眼 */]: "https://i.imgur.com/2spsghH.png",
  43. ["[OK]" /* OK */]: "https://i.imgur.com/6DMydmQ.png",
  44. ["[\u8DEA\u4E86]" /* 跪了 */]: "https://i.imgur.com/TYtySHv.png",
  45. ["[\u54CD\u6307]" /* 响指 */]: "https://i.imgur.com/Ac88cMm.png",
  46. ["[\u54ED\u60F9R]" /* 哭惹 */]: "https://i.imgur.com/HgxsUD2.png",
  47. ["[\u54C7R]" /* 哇 */]: "https://i.imgur.com/OZySWIG.png",
  48. ["[\u6C57\u989CR]" /* 汗颜 */]: "https://i.imgur.com/jrVZoLi.png",
  49. ["[\u5BB3\u7F9ER]" /* 害羞 */]: "https://i.imgur.com/OVQjxIr.png",
  50. ["[\u840C\u840C\u54D2R]" /* 萌萌哒 */]: "https://i.imgur.com/Ue1kikn.png",
  51. ["[\u5077\u7B11R]" /* 偷笑 */]: "https://i.imgur.com/aF7QiE5.png"
  52. };
  53. emoticons = [
  54. {
  55. title: "\u6D41\u884C",
  56. list: [
  57. "[\u8131\u5355doge]" /* 脱单doge */,
  58. "[doge]" /* doge */,
  59. "[\u6253call]" /* 打call */,
  60. "[\u661F\u661F\u773C]" /* 星星眼 */,
  61. "[\u5403\u74DC]" /* 吃瓜 */,
  62. "[OK]" /* OK */,
  63. "[\u54E6\u547C]" /* 哦呼 */,
  64. "[\u601D\u8003]" /* 思考 */,
  65. "[\u7591\u60D1]" /* 疑惑 */,
  66. "[\u8FA3\u773C\u775B]" /* 辣眼睛 */,
  67. "[\u50B2\u5A07]" /* 傲娇 */,
  68. "[\u6342\u8138]" /* 捂脸 */,
  69. "[\u65E0\u8BED]" /* 无语 */,
  70. "[\u5927\u54ED]" /* 大哭 */,
  71. "[\u9178\u4E86]" /* 酸了 */,
  72. "[\u6B6A\u5634]" /* 歪嘴 */,
  73. "[\u8DEA\u4E86]" /* 跪了 */,
  74. "[\u54CD\u6307]" /* 响指 */,
  75. "[\u54C7R]" /* 哇 */,
  76. "[\u840C\u840C\u54D2R]" /* 萌萌哒 */,
  77. "[\u5BB3\u7F9ER]" /* 害羞 */,
  78. "[\u5077\u7B11R]" /* 偷笑 */,
  79. "[\u54ED\u60F9R]" /* 哭惹 */,
  80. "[\u6C57\u989CR]" /* 汗颜 */
  81. ]
  82. },
  83. {
  84. title: "\u5C0F\u9EC4\u8138",
  85. list: [
  86. "\u{1F600}",
  87. "\u{1F601}",
  88. "\u{1F602}",
  89. "\u{1F923}",
  90. "\u{1F605}",
  91. "\u{1F60A}",
  92. "\u{1F60B}",
  93. "\u{1F618}",
  94. "\u{1F970}",
  95. "\u{1F617}",
  96. "\u{1F929}",
  97. "\u{1F914}",
  98. "\u{1F928}",
  99. "\u{1F610}",
  100. "\u{1F611}",
  101. "\u{1F644}",
  102. "\u{1F60F}",
  103. "\u{1F62A}",
  104. "\u{1F62B}",
  105. "\u{1F971}",
  106. "\u{1F61C}",
  107. "\u{1F612}",
  108. "\u{1F614}",
  109. "\u{1F628}",
  110. "\u{1F630}",
  111. "\u{1F631}",
  112. "\u{1F975}",
  113. "\u{1F621}",
  114. "\u{1F973}",
  115. "\u{1F97A}",
  116. "\u{1F92D}",
  117. "\u{1F9D0}",
  118. "\u{1F60E}",
  119. "\u{1F913}",
  120. "\u{1F62D}",
  121. "\u{1F911}",
  122. "\u{1F92E}"
  123. ]
  124. },
  125. {
  126. title: "\u624B\u52BF",
  127. list: [
  128. "\u{1F64B}",
  129. "\u{1F64E}",
  130. "\u{1F645}",
  131. "\u{1F647}",
  132. "\u{1F937}",
  133. "\u{1F90F}",
  134. "\u{1F449}",
  135. "\u270C\uFE0F",
  136. "\u{1F918}",
  137. "\u{1F919}",
  138. "\u{1F44C}",
  139. "\u{1F90C}",
  140. "\u{1F44D}",
  141. "\u{1F44E}",
  142. "\u{1F44B}",
  143. "\u{1F91D}",
  144. "\u{1F64F}",
  145. "\u{1F44F}"
  146. ]
  147. },
  148. {
  149. title: "\u5E86\u795D",
  150. list: ["\u2728", "\u{1F389}", "\u{1F38A}"]
  151. },
  152. {
  153. title: "\u5176\u4ED6",
  154. 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}"]
  155. }
  156. ];
  157. READABLE_CONTENT_HEIGHT = 250;
  158. MAX_CONTENT_HEIGHT = 550;
  159. READING_CONTENT_LIMIT = 150;
  160. dataExpiryTime = 60 * 60 * 1e3;
  161. imgurClientIdPool = [
  162. "3107b9ef8b316f3",
  163. // 以下 Client ID 来自「V2EX Plus」
  164. "442b04f26eefc8a",
  165. "59cfebe717c09e4",
  166. "60605aad4a62882",
  167. "6c65ab1d3f5452a",
  168. "83e123737849aa9",
  169. "9311f6be1c10160",
  170. "c4a4a563f698595",
  171. "81be04b9e4a08ce"
  172. ];
  173. defaultOptions = {
  174. openInNewTab: false,
  175. autoCheckIn: {
  176. enabled: true
  177. },
  178. theme: {
  179. autoSwitch: false
  180. },
  181. reply: {
  182. preload: "off"
  183. },
  184. replyContent: {
  185. autoFold: true,
  186. hideReplyTime: true,
  187. hideRefName: true
  188. },
  189. nestedReply: {
  190. display: "indent",
  191. multipleInsideOne: "nested"
  192. },
  193. userTag: {
  194. display: "inline"
  195. }
  196. };
  197. }
  198. });
  199.  
  200. // src/icons.ts
  201. var iconLoading, iconLogo, iconGitHub;
  202. var init_icons = __esm({
  203. "src/icons.ts"() {
  204. "use strict";
  205. iconLoading = `
  206. <svg version="1.1" id="L4" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
  207. viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
  208. <circle fill="currentcolor" stroke="none" cx="6" cy="50" r="6">
  209. <animate
  210. attributeName="opacity"
  211. dur="1s"
  212. values="0;1;0"
  213. repeatCount="indefinite"
  214. begin="0.1"/>
  215. </circle>
  216. <circle fill="currentcolor" stroke="none" cx="26" cy="50" r="6">
  217. <animate
  218. attributeName="opacity"
  219. dur="1s"
  220. values="0;1;0"
  221. repeatCount="indefinite"
  222. begin="0.2"/>
  223. </circle>
  224. <circle fill="currentcolor" stroke="none" cx="46" cy="50" r="6">
  225. <animate
  226. attributeName="opacity"
  227. dur="1s"
  228. values="0;1;0"
  229. repeatCount="indefinite"
  230. begin="0.3"/>
  231. </circle>
  232. </svg>
  233. `;
  234. iconLogo = `
  235. <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>
  236. `;
  237. iconGitHub = `
  238. <svg viewBox="0 0 24 24" aria-hidden="true">
  239. <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>
  240. </svg>
  241. `;
  242. }
  243. });
  244.  
  245. // src/components/toast.ts
  246. function createToast(props) {
  247. const { message, duration = 3e3 } = props;
  248. const $existTosat = $(".v2p-toast");
  249. if ($existTosat.length > 0) {
  250. $existTosat.remove();
  251. }
  252. const $toast = $(`<div class="v2p-toast">${message}</div>`).hide();
  253. $(document.body).append($toast);
  254. $toast.fadeIn("fast");
  255. if (duration !== 0) {
  256. setTimeout(() => {
  257. $toast.fadeOut("fast", () => {
  258. $toast.remove();
  259. });
  260. }, duration);
  261. }
  262. return {
  263. clear() {
  264. $toast.remove();
  265. }
  266. };
  267. }
  268. var init_toast = __esm({
  269. "src/components/toast.ts"() {
  270. "use strict";
  271. }
  272. });
  273.  
  274. // src/utils.ts
  275. function getOS() {
  276. const userAgent = window.navigator.userAgent.toLowerCase();
  277. const macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos)/i;
  278. const windowsPlatforms = /(win32|win64|windows|wince)/i;
  279. const iosPlatforms = /(iphone|ipad|ipod)/i;
  280. let os = null;
  281. if (macosPlatforms.test(userAgent)) {
  282. os = "macos";
  283. } else if (iosPlatforms.test(userAgent)) {
  284. os = "ios";
  285. } else if (windowsPlatforms.test(userAgent)) {
  286. os = "windows";
  287. } else if (userAgent.includes("android")) {
  288. os = "android";
  289. } else if (userAgent.includes("linux")) {
  290. os = "linux";
  291. }
  292. return os;
  293. }
  294. function formatTimestamp(timestamp, { format = "YMD" } = {}) {
  295. const date = new Date(timestamp.toString().length === 10 ? timestamp * 1e3 : timestamp);
  296. const year = date.getFullYear().toString();
  297. const month = (date.getMonth() + 1).toString().padStart(2, "0");
  298. const day = date.getDate().toString().padStart(2, "0");
  299. const YMD = `${year}-${month}-${day}`;
  300. if (format === "YMDHMS") {
  301. const hour = date.getHours().toString().padStart(2, "0");
  302. const minute = date.getMinutes().toString().padStart(2, "0");
  303. const second = date.getSeconds().toString().padStart(2, "0");
  304. return `${YMD} ${hour}:${minute}:${second}`;
  305. }
  306. return YMD;
  307. }
  308. function isSameDay(timestamp1, timestamp2) {
  309. const date1 = new Date(timestamp1);
  310. const date2 = new Date(timestamp2);
  311. return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
  312. }
  313. function isObject(value) {
  314. return typeof value === "object" && value !== null && !Array.isArray(value);
  315. }
  316. function deepMerge(target, source) {
  317. const result = {};
  318. for (const key in target) {
  319. const targetProp = target[key];
  320. const sourceProp = source[key];
  321. if (isObject(targetProp) && isObject(sourceProp)) {
  322. result[key] = deepMerge(targetProp, sourceProp);
  323. } else if (Reflect.has(source, key)) {
  324. result[key] = sourceProp;
  325. } else {
  326. result[key] = targetProp;
  327. }
  328. }
  329. for (const key in source) {
  330. if (!Reflect.has(target, key)) {
  331. result[key] = source[key];
  332. }
  333. }
  334. return result;
  335. }
  336. function getRunEnv() {
  337. if (typeof chrome === "object" && typeof chrome.extension !== "undefined") {
  338. return "chrome";
  339. }
  340. if (typeof browser === "object" && typeof browser.extension !== "undefined") {
  341. return "web-ext";
  342. }
  343. return null;
  344. }
  345. function escapeHTML(htmlString) {
  346. return htmlString.replace(/[<>&"'']/g, (match) => {
  347. switch (match) {
  348. case "<":
  349. return "&lt;";
  350. case ">":
  351. return "&gt;";
  352. case "&":
  353. return "&amp;";
  354. case '"':
  355. return "&quot;";
  356. case "'":
  357. return "&#39;";
  358. default:
  359. return match;
  360. }
  361. });
  362. }
  363. function injectScript(scriptSrc) {
  364. const script = document.createElement("script");
  365. script.setAttribute("type", "text/javascript");
  366. script.setAttribute("src", scriptSrc);
  367. document.body.appendChild(script);
  368. }
  369. function isValidSettings(settings) {
  370. return !!settings && typeof settings === "object" && "options" /* Options */ in settings;
  371. }
  372. function sleep(ms) {
  373. return new Promise((resolve) => setTimeout(resolve, ms));
  374. }
  375. async function getV2P_Settings() {
  376. let noteId;
  377. {
  378. const res = await fetch(`${"https://www.v2ex.com" /* Origin */}/notes`);
  379. const htmlText = await res.text();
  380. const $page = $(htmlText);
  381. const $note = $page.find('.note_item > .note_item_title > a[href^="/notes"]');
  382. $note.each((_, dom) => {
  383. const $dom = $(dom);
  384. if ($dom.text().startsWith(mark)) {
  385. const href = $dom.attr("href");
  386. if (typeof href === "string") {
  387. const id = href.split("/").at(2);
  388. noteId = id;
  389. }
  390. return false;
  391. }
  392. });
  393. }
  394. if (noteId) {
  395. const res = await fetch(`${"https://www.v2ex.com" /* Origin */}/notes/edit/${noteId}`);
  396. const htmlText = await res.text();
  397. const $editor = $(htmlText).find("#note_content.note_editor");
  398. const value = $editor.val();
  399. if (typeof value === "string") {
  400. const syncSettings = JSON.parse(value.replace(mark, ""));
  401. if (isValidSettings(syncSettings)) {
  402. return { noteId, config: syncSettings };
  403. }
  404. }
  405. }
  406. }
  407. async function setV2P_Settings(storageSettings, signal) {
  408. const data = await getV2P_Settings();
  409. const updating = !!data;
  410. const formData = new FormData();
  411. const syncVersion = updating ? data.config["settings-sync" /* SyncInfo */].version + 1 : 1;
  412. const syncInfo = {
  413. version: syncVersion,
  414. lastSyncTime: Date.now()
  415. };
  416. formData.append(
  417. "content",
  418. mark + JSON.stringify({ ...storageSettings, ["settings-sync" /* SyncInfo */]: syncInfo })
  419. );
  420. formData.append("syntax", "0");
  421. if (updating) {
  422. const { noteId } = data;
  423. await fetch(`${"https://www.v2ex.com" /* Origin */}/notes/edit/${noteId}`, {
  424. method: "POST",
  425. body: formData,
  426. signal
  427. });
  428. } else {
  429. formData.append("parent_id", "0");
  430. await fetch(`${"https://www.v2ex.com" /* Origin */}/notes/new`, {
  431. method: "POST",
  432. body: formData,
  433. signal
  434. });
  435. }
  436. await setStorage("settings-sync" /* SyncInfo */, syncInfo);
  437. return syncInfo;
  438. }
  439. function getStorage(useCache = true) {
  440. return new Promise((resolve, reject) => {
  441. if (useCache) {
  442. if (window.__V2P_StorageCache) {
  443. resolve(window.__V2P_StorageCache);
  444. }
  445. }
  446. const runEnv = getRunEnv();
  447. if (!(runEnv === "chrome" || runEnv === "web-ext")) {
  448. const data = { ["options" /* Options */]: defaultOptions };
  449. if (typeof window !== "undefined") {
  450. window.__V2P_StorageCache = data;
  451. }
  452. resolve(data);
  453. } else {
  454. chrome.storage.sync.get().then((items) => {
  455. let data;
  456. const options = items["options" /* Options */];
  457. if (options) {
  458. data = { ...items, ["options" /* Options */]: deepMerge(defaultOptions, options) };
  459. } else {
  460. data = { ...items, ["options" /* Options */]: defaultOptions };
  461. }
  462. if (typeof window !== "undefined") {
  463. window.__V2P_StorageCache = data;
  464. }
  465. resolve(data);
  466. }).catch((err) => {
  467. reject(err);
  468. });
  469. }
  470. });
  471. }
  472. function getStorageSync() {
  473. const storage = window.__V2P_StorageCache;
  474. if (!storage) {
  475. throw new Error(`${EXTENSION_NAME}: \u65E0\u53EF\u7528\u7684 Storage \u7F13\u5B58\u6570\u636E`);
  476. }
  477. return storage;
  478. }
  479. async function setStorage(storageKey, storageItem) {
  480. switch (storageKey) {
  481. case "options" /* Options */:
  482. case "api" /* API */:
  483. case "daily" /* Daily */:
  484. case "member-tag" /* MemberTag */:
  485. case "settings-sync" /* SyncInfo */:
  486. case "reading-list" /* ReadingList */:
  487. try {
  488. await chrome.storage.sync.set({ [storageKey]: storageItem });
  489. if (storageKey !== "api" /* API */ && storageKey !== "settings-sync" /* SyncInfo */) {
  490. const settings = await getStorage(false);
  491. if (controller) {
  492. controller.abort();
  493. }
  494. controller = new AbortController();
  495. setV2P_Settings(settings, controller.signal);
  496. }
  497. } catch (err) {
  498. if (String(err).includes("QUOTA_BYTES_PER_ITEM quota exceeded")) {
  499. console.error(
  500. `${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`
  501. );
  502. createToast({ message: "\u274C \u7528\u6237\u6570\u636E\u8D85\u51FA\u5B58\u50A8\u7A7A\u95F4\u9650\u5236" });
  503. }
  504. throw new Error(`\u274C \u65E0\u6CD5\u8BBE\u7F6E\uFF1A${storageKey}`);
  505. }
  506. break;
  507. default:
  508. throw new Error(`\u672A\u77E5\u7684 storageKey\uFF1A ${storageKey}`);
  509. }
  510. }
  511. var mark, controller;
  512. var init_utils = __esm({
  513. "src/utils.ts"() {
  514. "use strict";
  515. init_toast();
  516. init_constants();
  517. mark = `${EXTENSION_NAME}_settings`;
  518. controller = null;
  519. }
  520. });
  521.  
  522. // src/contents/globals.ts
  523. function updateCommentCells() {
  524. $commentCells = $commentBox.find('.cell[id^="r_"]');
  525. $commentTableRows = $commentCells.find("> table > tbody > tr");
  526. }
  527. var $wrapper, $wrapperContent, $main, $topicList, $topicContentBox, $topicHeader, $commentBox, $commentCells, $commentTableRows, $replyBox, $replyForm, $replyTextArea, replyTextArea, loginName, topicOwnerName;
  528. var init_globals = __esm({
  529. "src/contents/globals.ts"() {
  530. "use strict";
  531. $wrapper = $("#Wrapper");
  532. $wrapperContent = $wrapper.find("> .content");
  533. $main = $("#Main");
  534. $topicList = $(
  535. "#Main #Tabs ~ .cell.item, #Main #TopicsNode > .cell, #Main .cell.item:has(.item_title > .topic-link)"
  536. );
  537. $topicContentBox = $("#Main .box:has(.topic_buttons)");
  538. $topicHeader = $topicContentBox.find(".header");
  539. $commentBox = $('#Main .box:has(.cell[id^="r_"])');
  540. $commentCells = $commentBox.find('.cell[id^="r_"]');
  541. $commentTableRows = $commentCells.find("> table > tbody > tr");
  542. $replyBox = $("#reply-box");
  543. $replyForm = $replyBox.find('form[action^="/t"]');
  544. $replyTextArea = $("#reply_content");
  545. replyTextArea = document.querySelector("#reply_content");
  546. loginName = $('#Top .tools > a[href^="/member"]').text();
  547. topicOwnerName = $topicHeader.find('> small > a[href^="/member"]').text();
  548. }
  549. });
  550.  
  551. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/createElement.js
  552. var createElement, createElement$1;
  553. var init_createElement = __esm({
  554. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/createElement.js"() {
  555. "use strict";
  556. createElement = (tag, attrs, children = []) => {
  557. const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
  558. Object.keys(attrs).forEach((name) => {
  559. element.setAttribute(name, String(attrs[name]));
  560. });
  561. if (children.length) {
  562. children.forEach((child) => {
  563. const childElement = createElement(...child);
  564. element.appendChild(childElement);
  565. });
  566. }
  567. return element;
  568. };
  569. createElement$1 = ([tag, attrs, children]) => createElement(tag, attrs, children);
  570. }
  571. });
  572.  
  573. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/replaceElement.js
  574. var getAttrs, getClassNames, combineClassNames, toPascalCase, replaceElement;
  575. var init_replaceElement = __esm({
  576. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/replaceElement.js"() {
  577. "use strict";
  578. init_createElement();
  579. getAttrs = (element) => Array.from(element.attributes).reduce((attrs, attr) => {
  580. attrs[attr.name] = attr.value;
  581. return attrs;
  582. }, {});
  583. getClassNames = (attrs) => {
  584. if (typeof attrs === "string")
  585. return attrs;
  586. if (!attrs || !attrs.class)
  587. return "";
  588. if (attrs.class && typeof attrs.class === "string") {
  589. return attrs.class.split(" ");
  590. }
  591. if (attrs.class && Array.isArray(attrs.class)) {
  592. return attrs.class;
  593. }
  594. return "";
  595. };
  596. combineClassNames = (arrayOfClassnames) => {
  597. const classNameArray = arrayOfClassnames.flatMap(getClassNames);
  598. return classNameArray.map((classItem) => classItem.trim()).filter(Boolean).filter((value, index, self) => self.indexOf(value) === index).join(" ");
  599. };
  600. toPascalCase = (string) => string.replace(/(\w)(\w*)(_|-|\s*)/g, (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase());
  601. replaceElement = (element, { nameAttr, icons, attrs }) => {
  602. const iconName = element.getAttribute(nameAttr);
  603. if (iconName == null)
  604. return;
  605. const ComponentName = toPascalCase(iconName);
  606. const iconNode = icons[ComponentName];
  607. if (!iconNode) {
  608. return console.warn(
  609. `${element.outerHTML} icon name was not found in the provided icons object.`
  610. );
  611. }
  612. const elementAttrs = getAttrs(element);
  613. const [tag, iconAttributes, children] = iconNode;
  614. const iconAttrs = {
  615. ...iconAttributes,
  616. "data-lucide": iconName,
  617. ...attrs,
  618. ...elementAttrs
  619. };
  620. const classNames = combineClassNames(["lucide", `lucide-${iconName}`, elementAttrs, attrs]);
  621. if (classNames) {
  622. Object.assign(iconAttrs, {
  623. class: classNames
  624. });
  625. }
  626. const svgElement = createElement$1([tag, iconAttrs, children]);
  627. return element.parentNode?.replaceChild(svgElement, element);
  628. };
  629. }
  630. });
  631.  
  632. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/defaultAttributes.js
  633. var defaultAttributes;
  634. var init_defaultAttributes = __esm({
  635. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/defaultAttributes.js"() {
  636. "use strict";
  637. defaultAttributes = {
  638. xmlns: "http://www.w3.org/2000/svg",
  639. width: 24,
  640. height: 24,
  641. viewBox: "0 0 24 24",
  642. fill: "none",
  643. stroke: "currentColor",
  644. "stroke-width": 2,
  645. "stroke-linecap": "round",
  646. "stroke-linejoin": "round"
  647. };
  648. }
  649. });
  650.  
  651. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/book-open-check.js
  652. var BookOpenCheck;
  653. var init_book_open_check = __esm({
  654. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/book-open-check.js"() {
  655. "use strict";
  656. init_defaultAttributes();
  657. BookOpenCheck = [
  658. "svg",
  659. defaultAttributes,
  660. [
  661. ["path", { d: "M8 3H2v15h7c1.7 0 3 1.3 3 3V7c0-2.2-1.8-4-4-4Z" }],
  662. ["path", { d: "m16 12 2 2 4-4" }],
  663. ["path", { d: "M22 6V3h-6c-2.2 0-4 1.8-4 4v14c0-1.7 1.3-3 3-3h7v-2.3" }]
  664. ]
  665. ];
  666. }
  667. });
  668.  
  669. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/chevrons-up.js
  670. var ChevronsUp;
  671. var init_chevrons_up = __esm({
  672. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/chevrons-up.js"() {
  673. "use strict";
  674. init_defaultAttributes();
  675. ChevronsUp = [
  676. "svg",
  677. defaultAttributes,
  678. [
  679. ["path", { d: "m17 11-5-5-5 5" }],
  680. ["path", { d: "m17 18-5-5-5 5" }]
  681. ]
  682. ];
  683. }
  684. });
  685.  
  686. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/eye-off.js
  687. var EyeOff;
  688. var init_eye_off = __esm({
  689. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/eye-off.js"() {
  690. "use strict";
  691. init_defaultAttributes();
  692. EyeOff = [
  693. "svg",
  694. defaultAttributes,
  695. [
  696. ["path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }],
  697. ["path", { 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" }],
  698. ["path", { 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" }],
  699. ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
  700. ]
  701. ];
  702. }
  703. });
  704.  
  705. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/heart.js
  706. var Heart;
  707. var init_heart = __esm({
  708. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/heart.js"() {
  709. "use strict";
  710. init_defaultAttributes();
  711. Heart = [
  712. "svg",
  713. defaultAttributes,
  714. [
  715. [
  716. "path",
  717. {
  718. 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"
  719. }
  720. ]
  721. ]
  722. ];
  723. }
  724. });
  725.  
  726. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/message-square-plus.js
  727. var MessageSquarePlus;
  728. var init_message_square_plus = __esm({
  729. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/message-square-plus.js"() {
  730. "use strict";
  731. init_defaultAttributes();
  732. MessageSquarePlus = [
  733. "svg",
  734. defaultAttributes,
  735. [
  736. ["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }],
  737. ["path", { d: "M12 7v6" }],
  738. ["path", { d: "M9 10h6" }]
  739. ]
  740. ];
  741. }
  742. });
  743.  
  744. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/message-square.js
  745. var MessageSquare;
  746. var init_message_square = __esm({
  747. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/message-square.js"() {
  748. "use strict";
  749. init_defaultAttributes();
  750. MessageSquare = [
  751. "svg",
  752. defaultAttributes,
  753. [["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }]]
  754. ];
  755. }
  756. });
  757.  
  758. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/moon.js
  759. var Moon;
  760. var init_moon = __esm({
  761. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/moon.js"() {
  762. "use strict";
  763. init_defaultAttributes();
  764. Moon = [
  765. "svg",
  766. defaultAttributes,
  767. [["path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }]]
  768. ];
  769. }
  770. });
  771.  
  772. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/package-plus.js
  773. var PackagePlus;
  774. var init_package_plus = __esm({
  775. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/package-plus.js"() {
  776. "use strict";
  777. init_defaultAttributes();
  778. PackagePlus = [
  779. "svg",
  780. defaultAttributes,
  781. [
  782. ["path", { d: "M16 16h6" }],
  783. ["path", { d: "M19 13v6" }],
  784. [
  785. "path",
  786. {
  787. 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"
  788. }
  789. ],
  790. ["path", { d: "m7.5 4.27 9 5.15" }],
  791. ["polyline", { points: "3.29 7 12 12 20.71 7" }],
  792. ["line", { x1: "12", x2: "12", y1: "22", y2: "12" }]
  793. ]
  794. ];
  795. }
  796. });
  797.  
  798. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/panel-right.js
  799. var PanelRight;
  800. var init_panel_right = __esm({
  801. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/panel-right.js"() {
  802. "use strict";
  803. init_defaultAttributes();
  804. PanelRight = [
  805. "svg",
  806. defaultAttributes,
  807. [
  808. ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
  809. ["path", { d: "M15 3v18" }]
  810. ]
  811. ];
  812. }
  813. });
  814.  
  815. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/panel-top.js
  816. var PanelTop;
  817. var init_panel_top = __esm({
  818. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/panel-top.js"() {
  819. "use strict";
  820. init_defaultAttributes();
  821. PanelTop = [
  822. "svg",
  823. defaultAttributes,
  824. [
  825. ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
  826. ["path", { d: "M3 9h18" }]
  827. ]
  828. ];
  829. }
  830. });
  831.  
  832. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/smile.js
  833. var Smile;
  834. var init_smile = __esm({
  835. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/smile.js"() {
  836. "use strict";
  837. init_defaultAttributes();
  838. Smile = [
  839. "svg",
  840. defaultAttributes,
  841. [
  842. ["circle", { cx: "12", cy: "12", r: "10" }],
  843. ["path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }],
  844. ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }],
  845. ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9" }]
  846. ]
  847. ];
  848. }
  849. });
  850.  
  851. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/star.js
  852. var Star;
  853. var init_star = __esm({
  854. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/star.js"() {
  855. "use strict";
  856. init_defaultAttributes();
  857. Star = [
  858. "svg",
  859. defaultAttributes,
  860. [
  861. [
  862. "polygon",
  863. {
  864. 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"
  865. }
  866. ]
  867. ]
  868. ];
  869. }
  870. });
  871.  
  872. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/sun.js
  873. var Sun;
  874. var init_sun = __esm({
  875. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/sun.js"() {
  876. "use strict";
  877. init_defaultAttributes();
  878. Sun = [
  879. "svg",
  880. defaultAttributes,
  881. [
  882. ["circle", { cx: "12", cy: "12", r: "4" }],
  883. ["path", { d: "M12 2v2" }],
  884. ["path", { d: "M12 20v2" }],
  885. ["path", { d: "m4.93 4.93 1.41 1.41" }],
  886. ["path", { d: "m17.66 17.66 1.41 1.41" }],
  887. ["path", { d: "M2 12h2" }],
  888. ["path", { d: "M20 12h2" }],
  889. ["path", { d: "m6.34 17.66-1.41 1.41" }],
  890. ["path", { d: "m19.07 4.93-1.41 1.41" }]
  891. ]
  892. ];
  893. }
  894. });
  895.  
  896. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/twitter.js
  897. var Twitter;
  898. var init_twitter = __esm({
  899. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/icons/twitter.js"() {
  900. "use strict";
  901. init_defaultAttributes();
  902. Twitter = [
  903. "svg",
  904. defaultAttributes,
  905. [
  906. [
  907. "path",
  908. {
  909. 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"
  910. }
  911. ]
  912. ]
  913. ];
  914. }
  915. });
  916.  
  917. // node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/lucide.js
  918. var createIcons;
  919. var init_lucide = __esm({
  920. "node_modules/.pnpm/lucide@0.309.0/node_modules/lucide/dist/esm/lucide.js"() {
  921. "use strict";
  922. init_replaceElement();
  923. init_createElement();
  924. init_book_open_check();
  925. init_chevrons_up();
  926. init_eye_off();
  927. init_heart();
  928. init_message_square_plus();
  929. init_message_square();
  930. init_moon();
  931. init_package_plus();
  932. init_panel_right();
  933. init_panel_top();
  934. init_smile();
  935. init_star();
  936. init_sun();
  937. init_twitter();
  938. createIcons = ({ icons = {}, nameAttr = "data-lucide", attrs = {} } = {}) => {
  939. if (!Object.values(icons).length) {
  940. throw new Error(
  941. "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});`"
  942. );
  943. }
  944. if (typeof document === "undefined") {
  945. throw new Error("`createIcons()` only works in a browser environment.");
  946. }
  947. const elementsToReplace = document.querySelectorAll(`[${nameAttr}]`);
  948. Array.from(elementsToReplace).forEach(
  949. (element) => replaceElement(element, { nameAttr, icons, attrs })
  950. );
  951. if (nameAttr === "data-lucide") {
  952. const deprecatedElements = document.querySelectorAll("[icon-name]");
  953. if (deprecatedElements.length > 0) {
  954. console.warn(
  955. "[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"
  956. );
  957. Array.from(deprecatedElements).forEach(
  958. (element) => replaceElement(element, { nameAttr: "icon-name", icons, attrs })
  959. );
  960. }
  961. }
  962. };
  963. }
  964. });
  965.  
  966. // src/contents/helpers.ts
  967. function isV2EX_RequestError(error) {
  968. if ("cause" in error) {
  969. const cause = error["cause"];
  970. if ("success" in cause && "message" in cause) {
  971. return (
  972. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  973. typeof cause["success"] === "boolean" && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  974. !cause["success"] && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  975. typeof cause["message"] === "string"
  976. );
  977. }
  978. }
  979. return false;
  980. }
  981. function focusReplyInput() {
  982. if (replyTextArea instanceof HTMLTextAreaElement) {
  983. replyTextArea.focus();
  984. }
  985. }
  986. function insertTextToReplyInput(text) {
  987. if (replyTextArea instanceof HTMLTextAreaElement) {
  988. const startPos = replyTextArea.selectionStart;
  989. const endPos = replyTextArea.selectionEnd;
  990. const valueToStart = replyTextArea.value.substring(0, startPos);
  991. const valueFromEnd = replyTextArea.value.substring(endPos, replyTextArea.value.length);
  992. replyTextArea.value = `${valueToStart}${text}${valueFromEnd}`;
  993. focusReplyInput();
  994. replyTextArea.selectionStart = replyTextArea.selectionEnd = startPos + text.length;
  995. }
  996. }
  997. async function setMemberTags(memberName, tags) {
  998. const storage = await getStorage(false);
  999. const tagData = storage["member-tag" /* MemberTag */];
  1000. const runEnv = getRunEnv();
  1001. if (!(runEnv === "chrome" || runEnv === "web-ext")) {
  1002. return;
  1003. }
  1004. if (tags && tags.length > 0) {
  1005. const newTagData = { ...tagData, [memberName]: { tags } };
  1006. await setStorage("member-tag" /* MemberTag */, newTagData);
  1007. } else {
  1008. if (tagData && Reflect.has(tagData, memberName)) {
  1009. delete tagData[memberName];
  1010. await setStorage("member-tag" /* MemberTag */, tagData);
  1011. }
  1012. }
  1013. }
  1014. async function addToReadingList(params) {
  1015. const { url, title, content } = params;
  1016. if (!(typeof url === "string" || typeof title === "string" || typeof content === "string")) {
  1017. const message = "\u65E0\u6CD5\u8BC6\u522B\u5C06\u8BE5\u4E3B\u9898\u7684\u5143\u6570\u636E";
  1018. createToast({ message });
  1019. throw new Error(message);
  1020. }
  1021. const storage = await getStorage();
  1022. const currentData = storage["reading-list" /* ReadingList */]?.data || [];
  1023. const exist = currentData.findIndex((it) => it.url === url) !== -1;
  1024. if (exist) {
  1025. createToast({ message: "\u8BE5\u4E3B\u9898\u5DF2\u5B58\u5728\u4E8E\u7A0D\u540E\u9605\u8BFB" });
  1026. } else {
  1027. if (window.__V2P_AddingReading !== true) {
  1028. window.__V2P_AddingReading = true;
  1029. try {
  1030. await setStorage("reading-list" /* ReadingList */, {
  1031. data: [
  1032. {
  1033. url,
  1034. title: title.replace(" - V2EX", ""),
  1035. content: content.length > READING_CONTENT_LIMIT ? content.substring(0, READING_CONTENT_LIMIT) + "..." : content,
  1036. addedTime: Date.now()
  1037. },
  1038. ...currentData
  1039. ]
  1040. });
  1041. createToast({ message: "\u2705 \u5DF2\u6DFB\u52A0\u8FDB\u7A0D\u540E\u9605\u8BFB" });
  1042. await sleep(500);
  1043. } finally {
  1044. window.__V2P_AddingReading = false;
  1045. }
  1046. }
  1047. }
  1048. }
  1049. function decodeBase64TopicPage() {
  1050. const dataTitle = "\u70B9\u51FB\u590D\u5236";
  1051. if (window.__V2P_DecodeStatus === "decodeed") {
  1052. createToast({ message: "\u5DF2\u89E3\u6790\u5B8C\u672C\u9875\u6240\u6709\u7684 Base64 \u5B57\u7B26\u4E32" });
  1053. } else {
  1054. const $topicContentBox2 = $("#Main .box:has(.topic_content)");
  1055. const $commentBox2 = $('#Main .box:has(.cell[id^="r_"])');
  1056. const $commentCells2 = $commentBox2.find('.cell[id^="r_"]');
  1057. let count = 0;
  1058. const excludeList = [
  1059. "boss",
  1060. "bilibili",
  1061. "Bilibili",
  1062. "Encrypto",
  1063. "encrypto",
  1064. "Window10",
  1065. "airpords",
  1066. "Windows7"
  1067. ];
  1068. const convertHTMLText = (text, excludeTextList) => {
  1069. if (text.length % 4 !== 0 || text.length <= 8) {
  1070. return text;
  1071. }
  1072. if (excludeList.includes(text)) {
  1073. return text;
  1074. }
  1075. if (text.includes("=")) {
  1076. const paddingIndex = text.indexOf("=");
  1077. if (paddingIndex !== text.length - 1 && paddingIndex !== text.length - 2) {
  1078. return text;
  1079. }
  1080. }
  1081. if (excludeTextList?.some((excludeText) => excludeText.includes(text))) {
  1082. return text;
  1083. }
  1084. try {
  1085. const decodedStr = decodeURIComponent(window.atob(text));
  1086. count += 1;
  1087. return `${text}<span class="v2p-decode-block">(<ins class="v2p-decode" data-title="${dataTitle}">${decodedStr}</ins>)</span>`;
  1088. } catch (err) {
  1089. if (err instanceof Error) {
  1090. console.error(`\u89E3\u6790 Base64 \u51FA\u9519\uFF1A${err.message}`);
  1091. }
  1092. return text;
  1093. }
  1094. };
  1095. const base64regex = /[A-z0-9+/=]+/g;
  1096. const contentHandler = (_, content) => {
  1097. const excludeTextList = [
  1098. ...content.getElementsByTagName("a"),
  1099. ...content.getElementsByTagName("img")
  1100. ].map((ele) => ele.outerHTML);
  1101. content.innerHTML = content.innerHTML.replace(
  1102. base64regex,
  1103. (htmlText) => convertHTMLText(htmlText, excludeTextList)
  1104. );
  1105. };
  1106. $commentCells2.find(".reply_content").each(contentHandler);
  1107. $topicContentBox2.find(".topic_content").each(contentHandler);
  1108. if (count === 0) {
  1109. createToast({ message: "\u672C\u9875\u672A\u53D1\u73B0 Base64 \u5B57\u7B26\u4E32" });
  1110. } else {
  1111. window.__V2P_DecodeStatus = "decodeed";
  1112. createToast({ message: `\u2705 \u5DF2\u89E3\u6790\u672C\u9875\u6240\u6709\u7684 Base64 \u5B57\u7B26\u4E32\uFF0C\u5171 ${count} \u6761` });
  1113. }
  1114. $(".v2p-decode").on("click", (ev) => {
  1115. const text = ev.target.innerText;
  1116. void navigator.clipboard.writeText(text).then(() => {
  1117. ev.target.dataset.title = "\u2705 \u5DF2\u590D\u5236";
  1118. setTimeout(() => {
  1119. ev.target.dataset.title = dataTitle;
  1120. }, 1e3);
  1121. });
  1122. });
  1123. }
  1124. }
  1125. function postTask(expression, callback) {
  1126. const runEnv = getRunEnv();
  1127. if (!runEnv) {
  1128. const result = Function(`"use strict"; ${expression}`)();
  1129. callback?.(result);
  1130. } else {
  1131. if (callback) {
  1132. if (window.__V2P_Tasks) {
  1133. window.__V2P_Tasks.set(Date.now(), callback);
  1134. } else {
  1135. window.__V2P_Tasks = /* @__PURE__ */ new Map([[Date.now(), callback]]);
  1136. }
  1137. }
  1138. const messageData = {
  1139. from: 0 /* Content */,
  1140. payload: { task: { id: Date.now(), expression } }
  1141. };
  1142. window.postMessage(messageData);
  1143. }
  1144. }
  1145. function loadIcons() {
  1146. setTimeout(() => {
  1147. createIcons({
  1148. attrs: {
  1149. width: "100%",
  1150. height: "100%"
  1151. },
  1152. icons: {
  1153. MessageSquarePlus,
  1154. MessageSquare,
  1155. BookOpenCheck,
  1156. ChevronsUp,
  1157. Heart,
  1158. EyeOff,
  1159. Sun,
  1160. Moon,
  1161. Smile,
  1162. PackagePlus,
  1163. Star,
  1164. Twitter
  1165. }
  1166. });
  1167. }, 0);
  1168. }
  1169. function transformEmoji(textValue) {
  1170. return textValue.replace(/\[[^\]]+\]/g, (x) => {
  1171. const emojiLink = biliEmojiLink[x];
  1172. if (typeof emojiLink === "string") {
  1173. return `${emojiLink} `;
  1174. }
  1175. return x;
  1176. });
  1177. }
  1178. var init_helpers = __esm({
  1179. "src/contents/helpers.ts"() {
  1180. "use strict";
  1181. init_lucide();
  1182. init_toast();
  1183. init_constants();
  1184. init_utils();
  1185. init_globals();
  1186. }
  1187. });
  1188.  
  1189. // src/contents/common.ts
  1190. var common_exports = {};
  1191. var init_common = __esm({
  1192. "src/contents/common.ts"() {
  1193. "use strict";
  1194. init_constants();
  1195. init_icons();
  1196. init_utils();
  1197. init_globals();
  1198. init_helpers();
  1199. void (async () => {
  1200. const storage = await getStorage();
  1201. const options = storage["options" /* Options */];
  1202. const $toggle = $("#Rightbar .light-toggle").addClass("v2p-color-mode-toggle");
  1203. if (options.theme.autoSwitch) {
  1204. const perfersDark = window.matchMedia("(prefers-color-scheme: dark)");
  1205. const toggleTheme = (preferDark) => {
  1206. const shouldSync = preferDark && !$wrapper.hasClass("Night") || !preferDark && $wrapper.hasClass("Night");
  1207. if (shouldSync) {
  1208. const href = $toggle.attr("href");
  1209. if (typeof href === "string") {
  1210. fetch(href);
  1211. }
  1212. if (preferDark) {
  1213. $toggle.prop("title", "\u4F7F\u7528\u6D45\u8272\u4E3B\u9898");
  1214. $toggle.html('<i data-lucide="sun"></i>');
  1215. } else {
  1216. $toggle.prop("title", "\u4F7F\u7528\u6DF1\u8272\u4E3B\u9898");
  1217. $toggle.html('<i data-lucide="moon"></i>');
  1218. }
  1219. loadIcons();
  1220. }
  1221. if (preferDark) {
  1222. $(document.body).addClass("v2p-theme-dark");
  1223. $wrapper.addClass("Night");
  1224. } else {
  1225. $(document.body).removeClass("v2p-theme-dark");
  1226. $wrapper.removeClass("Night");
  1227. }
  1228. };
  1229. toggleTheme(perfersDark.matches);
  1230. perfersDark.addEventListener("change", ({ matches }) => {
  1231. toggleTheme(matches);
  1232. });
  1233. $toggle.on("click", () => {
  1234. void setStorage("options" /* Options */, deepMerge(options, { theme: { autoSwitch: false } }));
  1235. });
  1236. }
  1237. {
  1238. const syncInfo = storage["settings-sync" /* SyncInfo */];
  1239. if (syncInfo) {
  1240. const lastCheckTime = syncInfo.lastCheckTime;
  1241. const twoHours = 2 * 60 * 1e3 * 60;
  1242. const neverChecked = !lastCheckTime;
  1243. if (lastCheckTime && Date.now() - lastCheckTime >= twoHours || neverChecked) {
  1244. void getV2P_Settings().then(async (res) => {
  1245. const settings = res?.config;
  1246. const remoteSyncInfo = settings?.["settings-sync" /* SyncInfo */];
  1247. if (settings && remoteSyncInfo) {
  1248. if (syncInfo.version < remoteSyncInfo.version || neverChecked) {
  1249. await chrome.storage.sync.set(
  1250. deepMerge(storage, {
  1251. ...settings,
  1252. ["settings-sync" /* SyncInfo */]: {
  1253. ...settings["settings-sync" /* SyncInfo */],
  1254. lastCheckTime: Date.now()
  1255. }
  1256. })
  1257. );
  1258. }
  1259. }
  1260. });
  1261. }
  1262. }
  1263. }
  1264. {
  1265. const $toggleImg = $toggle.find("> img");
  1266. const alt = $toggleImg.prop("alt");
  1267. if (alt === "Light") {
  1268. $toggle.prop("title", "\u4F7F\u7528\u6DF1\u8272\u4E3B\u9898");
  1269. $toggleImg.replaceWith('<i data-lucide="moon"></i>');
  1270. } else if (alt === "Dark") {
  1271. $toggle.prop("title", "\u4F7F\u7528\u6D45\u8272\u4E3B\u9898");
  1272. $toggleImg.replaceWith('<i data-lucide="sun"></i>');
  1273. }
  1274. }
  1275. {
  1276. $("#Top .site-nav .tools > .top").addClass("v2p-hover-btn");
  1277. }
  1278. {
  1279. const runEnv = getRunEnv();
  1280. if (runEnv === "chrome" || runEnv === "web-ext") {
  1281. injectScript(chrome.runtime.getURL("scripts/web_accessible_resources.min.js"));
  1282. window.addEventListener("message", (ev) => {
  1283. if (ev.data.from === 1 /* Web */) {
  1284. const payload = ev.data.payload;
  1285. const task = payload?.task;
  1286. if (payload?.status === "ready") {
  1287. postTask('if (typeof window.once === "string") { return window.once; }', (result) => {
  1288. if (typeof result === "string") {
  1289. window.once = result;
  1290. }
  1291. });
  1292. }
  1293. if (task) {
  1294. window.__V2P_Tasks?.get(task.id)?.(task.result);
  1295. }
  1296. }
  1297. });
  1298. }
  1299. }
  1300. {
  1301. const $extraFooter = $(`
  1302. <div class="v2p-footer">
  1303. <div class="v2p-footer-text">\u6269\u5C55\u81EA V2EX Polish </div>
  1304. <div class="v2p-footer-links">
  1305. <a class="v2p-footer-link v2p-hover-btn" href="${"https://v2p.app" /* Home */}" target="_blank">\u63D2\u4EF6\u5B98\u7F51</a>
  1306. <a class="v2p-footer-link v2p-hover-btn" href="${"https://github.com/coolpace/V2EX_Polish/discussions/1?sort=new" /* Feedback */}" target="_blank">\u95EE\u9898\u53CD\u9988</a>
  1307. </div>
  1308. <div class="v2p-footer-brand">
  1309. <span>
  1310. <a
  1311. href="https://github.com/coolpace/V2EX_Polish"
  1312. target="_blank"
  1313. title="GitHub \u4ED3\u5E93"
  1314. >
  1315. ${iconGitHub}
  1316. </a>
  1317. </span>
  1318. </div>
  1319. </div>
  1320. `);
  1321. $(`<div class="v2p-footer-logo">${iconLogo}</div>`).prependTo($extraFooter);
  1322. $("#Bottom .content").append($extraFooter);
  1323. }
  1324. })();
  1325. }
  1326. });
  1327.  
  1328. // src/components/button.ts
  1329. function createButton(props) {
  1330. const { children, className = "", type = "button", tag = "button" } = props;
  1331. const $button = $(`<${tag} class="normal button ${className}">${children}</${tag}>`);
  1332. if (tag === "button") {
  1333. $button.prop("type", type);
  1334. }
  1335. return $button;
  1336. }
  1337. var init_button = __esm({
  1338. "src/components/button.ts"() {
  1339. "use strict";
  1340. }
  1341. });
  1342.  
  1343. // src/components/model.ts
  1344. function createModel(props) {
  1345. const { root, title, onOpen, onClose, onMount } = props;
  1346. const $mask = $('<div class="v2p-modal-mask">');
  1347. const $content = $('<div class="v2p-modal-content">');
  1348. const $closeBtn = createButton({
  1349. children: "\u5173\u95ED<kbd>Esc</kbd>",
  1350. className: "v2p-modal-close-btn"
  1351. });
  1352. const $title = $(`<div class="v2p-modal-title">${title ?? ""}</div>`);
  1353. const $actions = $('<div class="v2p-modal-actions">').append($closeBtn);
  1354. const $header = $('<div class="v2p-modal-header">').append($title, $actions);
  1355. const $main2 = $('<div class="v2p-modal-main">').append($header, $content).on("click", (ev) => {
  1356. ev.stopPropagation();
  1357. });
  1358. const $container = $mask.append($main2).hide();
  1359. const modelElements = {
  1360. $mask,
  1361. $main: $main2,
  1362. $container,
  1363. $title,
  1364. $actions,
  1365. $content
  1366. };
  1367. let boundEvent = false;
  1368. const maskClickHandler = () => {
  1369. handleModalClose();
  1370. };
  1371. const keyupHandler = (ev) => {
  1372. if (ev.key === "Escape") {
  1373. handleModalClose();
  1374. }
  1375. };
  1376. const handleModalClose = () => {
  1377. $mask.off("click", maskClickHandler);
  1378. $(document).off("keydown", keyupHandler);
  1379. boundEvent = false;
  1380. $container.fadeOut("fast");
  1381. document.body.classList.remove("v2p-modal-open");
  1382. onClose?.(modelElements);
  1383. };
  1384. const handleModalOpen = () => {
  1385. setTimeout(() => {
  1386. if (!boundEvent) {
  1387. $mask.on("click", maskClickHandler);
  1388. $(document).on("keydown", keyupHandler);
  1389. boundEvent = true;
  1390. }
  1391. });
  1392. $container.fadeIn("fast");
  1393. document.body.classList.add("v2p-modal-open");
  1394. onOpen?.(modelElements);
  1395. };
  1396. $closeBtn.on("click", handleModalClose);
  1397. onMount?.(modelElements);
  1398. if (root) {
  1399. root.append($container);
  1400. }
  1401. return { ...modelElements, open: handleModalOpen, close: handleModalClose };
  1402. }
  1403. var init_model = __esm({
  1404. "src/components/model.ts"() {
  1405. "use strict";
  1406. init_button();
  1407. }
  1408. });
  1409.  
  1410. // src/services.ts
  1411. async function legacyRequest(url, options) {
  1412. const res = await fetch(url, options);
  1413. return res.json();
  1414. }
  1415. function fetchUserInfo(memberName, options) {
  1416. return legacyRequest(
  1417. `${V2EX_LEGACY_API}/members/show.json?username=${memberName}`,
  1418. options
  1419. );
  1420. }
  1421. async function request(url, options) {
  1422. const storage = await getStorage();
  1423. const PAT = storage["api" /* API */]?.pat;
  1424. const res = await fetch(url, {
  1425. ...options,
  1426. headers: { Authorization: PAT ? `Bearer ${PAT}` : "", ...options?.headers }
  1427. });
  1428. {
  1429. const limit = res.headers.get("X-Rate-Limit-Limit");
  1430. const reset = res.headers.get("X-Rate-Limit-Reset");
  1431. const remaining = res.headers.get("X-Rate-Limit-Remaining");
  1432. const api = {
  1433. pat: PAT,
  1434. limit: limit ? Number(limit) : void 0,
  1435. reset: reset ? Number(reset) : void 0,
  1436. remaining: remaining ? Number(remaining) : void 0
  1437. };
  1438. void setStorage("api" /* API */, api);
  1439. }
  1440. const resultData = await res.json();
  1441. if (typeof resultData.success === "boolean" && !resultData.success) {
  1442. throw new Error(resultData.message, { cause: resultData });
  1443. }
  1444. return resultData;
  1445. }
  1446. function fetchTopic(topicId, options) {
  1447. return request(`${V2EX_API}/topics/${topicId}`, { method: "GET", ...options });
  1448. }
  1449. function fetchTopicReplies(topicId, options) {
  1450. return request(`${V2EX_API}/topics/${topicId}/replies`, {
  1451. method: "GET",
  1452. ...options
  1453. });
  1454. }
  1455. async function uploadImage(file) {
  1456. const formData = new FormData();
  1457. formData.append("image", file);
  1458. const randomIndex = Math.floor(Math.random() * imgurClientIdPool.length);
  1459. const clidenId = imgurClientIdPool[randomIndex];
  1460. const res = await fetch("https://api.imgur.com/3/upload", {
  1461. method: "POST",
  1462. headers: { Authorization: `Client-ID ${clidenId}` },
  1463. body: formData
  1464. });
  1465. if (res.ok) {
  1466. const resData = await res.json();
  1467. if (resData.success) {
  1468. return resData.data.link;
  1469. }
  1470. }
  1471. throw new Error("\u4E0A\u4F20\u5931\u8D25");
  1472. }
  1473. async function refreshMoney() {
  1474. const res = await fetch("/ajax/money", { method: "POST" });
  1475. const data = await res.text();
  1476. $("#money").html(data);
  1477. }
  1478. async function thankReply(params) {
  1479. try {
  1480. const res = await fetch(`/thank/reply/${params.replyId}?once=${window.once}`, {
  1481. method: "POST"
  1482. });
  1483. const data = await res.json();
  1484. postTask(`window.once = ${data.once}`);
  1485. window.once = data.once;
  1486. if (data.success) {
  1487. $("#thank_area_" + params.replyId).addClass("thanked").html("\u611F\u8C22\u5DF2\u53D1\u9001");
  1488. params.onSuccess?.();
  1489. await refreshMoney();
  1490. } else {
  1491. alert(data.message);
  1492. }
  1493. } catch {
  1494. params.onFail?.();
  1495. }
  1496. }
  1497. async function crawalTopicPage(path, page) {
  1498. const res = await fetch(`${"https://www.v2ex.com" /* Origin */}${path}?p=${page}`);
  1499. const htmlText = await res.text();
  1500. return htmlText;
  1501. }
  1502. async function getPreviewContent(params) {
  1503. const formData = new FormData();
  1504. formData.append("text", params.text);
  1505. const res = await fetch(`${"https://www.v2ex.com" /* Origin */}/preview/${params.syntax}`, {
  1506. method: "POST",
  1507. body: formData
  1508. });
  1509. if (res.ok) {
  1510. const renderedContent = await res.text();
  1511. return renderedContent;
  1512. } else {
  1513. throw new Error("\u9884\u89C8\u5931\u8D25");
  1514. }
  1515. }
  1516. var V2EX_ORIGIN, V2EX_LEGACY_API, V2EX_API;
  1517. var init_services = __esm({
  1518. "src/services.ts"() {
  1519. "use strict";
  1520. init_constants();
  1521. init_helpers();
  1522. init_utils();
  1523. V2EX_ORIGIN = window.location.origin.includes("v2ex.com") ? window.location.origin : "https://www.v2ex.com" /* Origin */;
  1524. V2EX_LEGACY_API = `${V2EX_ORIGIN}/api`;
  1525. V2EX_API = `${V2EX_ORIGIN}/api/v2`;
  1526. }
  1527. });
  1528.  
  1529. // src/contents/home/topic-list.ts
  1530. function handlingTopicList() {
  1531. const runEnv = getRunEnv();
  1532. if (!runEnv) {
  1533. return;
  1534. }
  1535. const storage = getStorageSync();
  1536. const options = storage["options" /* Options */];
  1537. const PAT = storage["api" /* API */]?.pat;
  1538. let abortController = null;
  1539. const $detailBtn = createButton({
  1540. children: "\u8FDB\u5165\u4E3B\u9898",
  1541. className: "special",
  1542. tag: "a"
  1543. });
  1544. if (options.openInNewTab) {
  1545. $detailBtn.prop("target", "_blank");
  1546. }
  1547. const model = createModel({
  1548. root: $(document.body),
  1549. onMount: ({ $actions }) => {
  1550. $actions.prepend($detailBtn);
  1551. },
  1552. onClose: ({ $title, $content }) => {
  1553. $title.empty();
  1554. $content.empty();
  1555. abortController?.abort();
  1556. }
  1557. });
  1558. const topicDataCache = /* @__PURE__ */ new Map();
  1559. const handlePreview = (params) => {
  1560. const { topicId, topicTitle, linkHref } = params;
  1561. if (topicId) {
  1562. model.open();
  1563. $detailBtn.prop("href", linkHref);
  1564. const $titleLink = $(
  1565. `<a class="v2p-topic-preview-title-link" title="${topicTitle}">${topicTitle}</a>`
  1566. );
  1567. model.$title.empty().append($titleLink);
  1568. if (PAT) {
  1569. void (async () => {
  1570. let cacheData = topicDataCache.get(topicId);
  1571. if (!cacheData || Date.now() - cacheData.cacheTime > 1e3 * 60 * 10) {
  1572. try {
  1573. abortController = new AbortController();
  1574. model.$content.empty().append(`
  1575. <div class="v2p-modal-loading">
  1576. <div class="v2p-icon-loading">${iconLoading}</div>
  1577. </div>
  1578. `);
  1579. const promises = [
  1580. fetchTopic(topicId, { signal: abortController.signal }),
  1581. fetchTopicReplies(topicId, { signal: abortController.signal })
  1582. ];
  1583. const [{ result: topic }, { result: topicReplies }] = await Promise.all(promises);
  1584. const data = {
  1585. topic,
  1586. topicReplies,
  1587. cacheTime: Date.now()
  1588. };
  1589. topicDataCache.set(topicId, data);
  1590. cacheData = data;
  1591. } catch (err) {
  1592. if (isV2EX_RequestError(err)) {
  1593. const message = err.cause.message;
  1594. if (
  1595. /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
  1596. message === "Token expired" /* TokenExpired */ || message === "Invalid token" /* InvalidToken */
  1597. ) {
  1598. model.$content.empty().append(invalidTemplate("\u60A8\u7684 PAT \u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u8BBE\u7F6E\u3002"));
  1599. }
  1600. }
  1601. }
  1602. }
  1603. if (cacheData) {
  1604. const { topic, topicReplies } = cacheData;
  1605. const $topicPreview = $('<div class="v2p-topic-preview">');
  1606. $titleLink.prop("href", topic.url);
  1607. if (options.openInNewTab) {
  1608. $titleLink.prop("target", "_blank");
  1609. }
  1610. const $infoBar = $(`
  1611. <div class="v2p-tp-info-bar">
  1612. <div class="v2p-tp-info">
  1613. <a class="v2p-tp-member" href="${topic.member.url}">
  1614. <img class="v2p-tp-avatar" src="${topic.member.avatar}">
  1615. <span>${topic.member.username}</span>
  1616. </a>
  1617.  
  1618. <span>
  1619. ${formatTimestamp(topic.created, { format: "YMDHMS" })}
  1620. </span>
  1621.  
  1622. <span>${topic.replies} \u6761\u56DE\u590D</span>
  1623. </div>
  1624. </div>
  1625. `);
  1626. const iconBook = createElement$1(BookOpenCheck);
  1627. iconBook.setAttribute("width", "100%");
  1628. iconBook.setAttribute("height", "100%");
  1629. const $readingBtn = $(`
  1630. <div class="v2p-tp-read"><span class="v2p-tp-read-icon"></span>\u7A0D\u540E\u9605\u8BFB</div>
  1631. `);
  1632. $readingBtn.find(".v2p-tp-read-icon").append(iconBook);
  1633. $readingBtn.on("click", () => {
  1634. void addToReadingList({
  1635. url: topic.url,
  1636. title: topic.title,
  1637. content: topic.content
  1638. });
  1639. }).appendTo($infoBar);
  1640. $topicPreview.append($infoBar);
  1641. if (topic.content_rendered) {
  1642. $topicPreview.append(
  1643. `<div class="v2p-topic-preview-content markdown_body">${topic.content_rendered}</div>`
  1644. );
  1645. } else {
  1646. $topicPreview.append(`
  1647. <div class="v2p-empty-content">
  1648. <div class="v2p-text-emoji">\xAF\\_(\u30C4)_/\xAF</div>
  1649. <p>\u8BE5\u4E3B\u9898\u6CA1\u6709\u6B63\u6587\u5185\u5BB9</p>
  1650. </div>
  1651. `);
  1652. }
  1653. if (topic.supplements && topic.supplements.length > 0) {
  1654. $topicPreview.append(`
  1655. <div class="v2p-topic-preview-addons">
  1656. ${topic.supplements.map((addon, idx) => {
  1657. return `
  1658. <div class="v2p-topic-preview-addon subtle">
  1659. <div class="fade" style="margin-bottom:10px;">\u9644\u8A00 ${idx + 1}\uFF1A</div>
  1660. <div class="topic_content markdown_body">${addon.content_rendered}</div>
  1661. </div>
  1662. `;
  1663. }).join("")}
  1664. </div>
  1665. `);
  1666. }
  1667. if (topicReplies.length > 0) {
  1668. const $template = $("<div>");
  1669. const op = topic.member.username;
  1670. topicReplies.forEach((r) => {
  1671. $template.append(`
  1672. <div class="v2p-topic-reply">
  1673. <div class="v2p-topic-reply-member">
  1674. <a href="${r.member.url}" target="_blank">
  1675. <img class="v2p-topic-reply-avatar" src="${r.member.avatar}">
  1676. <span>${r.member.username}</span>
  1677. <span style="
  1678. display: ${op === r.member.username ? "unset" : "none"};
  1679. margin-left:${op === r.member.username ? "3px" : "0"};
  1680. ">
  1681. <span class="badge op mini">OP</span>
  1682. </span>
  1683. </a>\uFF1A
  1684. </div>
  1685. <div class="v2p-topic-reply-content">${escapeHTML(r.content)}</div>
  1686. </div>
  1687. `);
  1688. });
  1689. $('<div class="v2p-topic-reply-box">').append($template.html()).append(
  1690. `
  1691. <div class="v2p-more-reply-tip">
  1692. <a
  1693. href="${linkHref || ""}"
  1694. style="color: currentColor;"
  1695. target="${options.openInNewTab ? "_blank" : "_self"}"
  1696. >
  1697. \u5728\u4E3B\u9898\u5185\u67E5\u770B\u5B8C\u6574\u8BC4\u8BBA...
  1698. </a>
  1699. </div>
  1700. `
  1701. ).appendTo($topicPreview);
  1702. }
  1703. model.$content.empty().append($topicPreview);
  1704. }
  1705. })();
  1706. } else {
  1707. model.$content.empty().append(invalidTemplate("\u60A8\u9700\u8981\u5148\u8BBE\u7F6E PAT \u624D\u80FD\u83B7\u53D6\u9884\u89C8\u5185\u5BB9\u3002"));
  1708. }
  1709. }
  1710. };
  1711. const $previewBtn = $('<button class="v2p-topic-preview-btn">\u9884\u89C8</button>');
  1712. $topicList.each((_, topicItem) => {
  1713. const $topicItem = $(topicItem);
  1714. const $itemTitle = $topicItem.find(".item_title");
  1715. const topicTitle = $itemTitle.find(".topic-link").text();
  1716. $previewBtn.clone().on("click", () => {
  1717. const linkHref = $topicItem.find(".topic-link").attr("href");
  1718. const match = linkHref?.match(/\/t\/(\d+)/);
  1719. const topicId = match?.at(1);
  1720. handlePreview({ topicId, topicTitle, linkHref });
  1721. }).appendTo($itemTitle);
  1722. });
  1723. if (PAT) {
  1724. $("#TopicsHot,#my-recent-topics").find(".cell .item_hot_topic_title").each((_, topicTitle) => {
  1725. const $topicItem = $(topicTitle);
  1726. $previewBtn.clone().on("click", () => {
  1727. const $link = $topicItem.find("> a");
  1728. const linkHref = $link.attr("href");
  1729. const match = linkHref?.match(/\/t\/(\d+)/);
  1730. const topicId = match?.at(1);
  1731. const topicTitle2 = $link.text();
  1732. handlePreview({ topicId, topicTitle: topicTitle2, linkHref });
  1733. }).appendTo($topicItem);
  1734. });
  1735. }
  1736. }
  1737. var invalidTemplate;
  1738. var init_topic_list = __esm({
  1739. "src/contents/home/topic-list.ts"() {
  1740. "use strict";
  1741. init_lucide();
  1742. init_button();
  1743. init_model();
  1744. init_constants();
  1745. init_icons();
  1746. init_services();
  1747. init_utils();
  1748. init_globals();
  1749. init_helpers();
  1750. invalidTemplate = (tip) => `
  1751. <div class="v2p-no-pat">
  1752. <div class="v2p-no-pat-title">${tip}</div>
  1753. <div class="v2p-no-pat-desc">
  1754. \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
  1755. </div>
  1756.  
  1757. <div class="v2p-no-pat-steps">
  1758. <div class="v2p-no-pat-step">
  1759. <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>
  1760. <img class="v2p-no-pat-img" src="https://i.imgur.com/UfNkuTF.png">
  1761. </div>
  1762. <div class="v2p-no-pat-step">
  1763. <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>
  1764. <img class="v2p-no-pat-img" src="https://i.imgur.com/O6hP86A.png">
  1765. </div>
  1766. </div>
  1767. </div>
  1768. `;
  1769. }
  1770. });
  1771.  
  1772. // src/contents/home/index.ts
  1773. var home_exports = {};
  1774. var init_home = __esm({
  1775. "src/contents/home/index.ts"() {
  1776. "use strict";
  1777. init_constants();
  1778. init_utils();
  1779. init_helpers();
  1780. init_topic_list();
  1781. void (async () => {
  1782. const storage = await getStorage();
  1783. const options = storage["options" /* Options */];
  1784. {
  1785. $("#Main .tab").addClass("v2p-hover-btn");
  1786. if (options.openInNewTab) {
  1787. $('#Main .topic-link, .item_hot_topic_title > a, .item_node, a[href="/write"]').prop(
  1788. "target",
  1789. "_blank"
  1790. );
  1791. }
  1792. }
  1793. handlingTopicList();
  1794. {
  1795. const dailyInfo = storage["daily" /* Daily */];
  1796. if (dailyInfo?.lastCheckInTime) {
  1797. if (isSameDay(dailyInfo.lastCheckInTime, Date.now())) {
  1798. const $info = $(`
  1799. <a class="cell v2p-info-row" href="/mission/daily">
  1800. \u4ECA\u65E5\u5DF2\u81EA\u52A8\u7B7E\u5230
  1801. </a>
  1802. `);
  1803. $('#Rightbar > .box:has("#member-activity")').append($info);
  1804. }
  1805. }
  1806. }
  1807. loadIcons();
  1808. })();
  1809. }
  1810. });
  1811.  
  1812. // node_modules/.pnpm/@floating-ui+utils@0.1.6/node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs
  1813. function clamp(start, value, end) {
  1814. return max(start, min(value, end));
  1815. }
  1816. function evaluate(value, param) {
  1817. return typeof value === "function" ? value(param) : value;
  1818. }
  1819. function getSide(placement) {
  1820. return placement.split("-")[0];
  1821. }
  1822. function getAlignment(placement) {
  1823. return placement.split("-")[1];
  1824. }
  1825. function getOppositeAxis(axis) {
  1826. return axis === "x" ? "y" : "x";
  1827. }
  1828. function getAxisLength(axis) {
  1829. return axis === "y" ? "height" : "width";
  1830. }
  1831. function getSideAxis(placement) {
  1832. return ["top", "bottom"].includes(getSide(placement)) ? "y" : "x";
  1833. }
  1834. function getAlignmentAxis(placement) {
  1835. return getOppositeAxis(getSideAxis(placement));
  1836. }
  1837. function getAlignmentSides(placement, rects, rtl) {
  1838. if (rtl === void 0) {
  1839. rtl = false;
  1840. }
  1841. const alignment = getAlignment(placement);
  1842. const alignmentAxis = getAlignmentAxis(placement);
  1843. const length = getAxisLength(alignmentAxis);
  1844. let mainAlignmentSide = alignmentAxis === "x" ? alignment === (rtl ? "end" : "start") ? "right" : "left" : alignment === "start" ? "bottom" : "top";
  1845. if (rects.reference[length] > rects.floating[length]) {
  1846. mainAlignmentSide = getOppositePlacement(mainAlignmentSide);
  1847. }
  1848. return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)];
  1849. }
  1850. function getExpandedPlacements(placement) {
  1851. const oppositePlacement = getOppositePlacement(placement);
  1852. return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)];
  1853. }
  1854. function getOppositeAlignmentPlacement(placement) {
  1855. return placement.replace(/start|end/g, (alignment) => oppositeAlignmentMap[alignment]);
  1856. }
  1857. function getSideList(side, isStart, rtl) {
  1858. const lr = ["left", "right"];
  1859. const rl = ["right", "left"];
  1860. const tb = ["top", "bottom"];
  1861. const bt = ["bottom", "top"];
  1862. switch (side) {
  1863. case "top":
  1864. case "bottom":
  1865. if (rtl)
  1866. return isStart ? rl : lr;
  1867. return isStart ? lr : rl;
  1868. case "left":
  1869. case "right":
  1870. return isStart ? tb : bt;
  1871. default:
  1872. return [];
  1873. }
  1874. }
  1875. function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) {
  1876. const alignment = getAlignment(placement);
  1877. let list = getSideList(getSide(placement), direction === "start", rtl);
  1878. if (alignment) {
  1879. list = list.map((side) => side + "-" + alignment);
  1880. if (flipAlignment) {
  1881. list = list.concat(list.map(getOppositeAlignmentPlacement));
  1882. }
  1883. }
  1884. return list;
  1885. }
  1886. function getOppositePlacement(placement) {
  1887. return placement.replace(/left|right|bottom|top/g, (side) => oppositeSideMap[side]);
  1888. }
  1889. function expandPaddingObject(padding) {
  1890. return {
  1891. top: 0,
  1892. right: 0,
  1893. bottom: 0,
  1894. left: 0,
  1895. ...padding
  1896. };
  1897. }
  1898. function getPaddingObject(padding) {
  1899. return typeof padding !== "number" ? expandPaddingObject(padding) : {
  1900. top: padding,
  1901. right: padding,
  1902. bottom: padding,
  1903. left: padding
  1904. };
  1905. }
  1906. function rectToClientRect(rect) {
  1907. return {
  1908. ...rect,
  1909. top: rect.y,
  1910. left: rect.x,
  1911. right: rect.x + rect.width,
  1912. bottom: rect.y + rect.height
  1913. };
  1914. }
  1915. var min, max, oppositeSideMap, oppositeAlignmentMap;
  1916. var init_floating_ui_utils = __esm({
  1917. "node_modules/.pnpm/@floating-ui+utils@0.1.6/node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs"() {
  1918. "use strict";
  1919. min = Math.min;
  1920. max = Math.max;
  1921. oppositeSideMap = {
  1922. left: "right",
  1923. right: "left",
  1924. bottom: "top",
  1925. top: "bottom"
  1926. };
  1927. oppositeAlignmentMap = {
  1928. start: "end",
  1929. end: "start"
  1930. };
  1931. }
  1932. });
  1933.  
  1934. // node_modules/.pnpm/@floating-ui+core@1.5.2/node_modules/@floating-ui/core/dist/floating-ui.core.mjs
  1935. function computeCoordsFromPlacement(_ref, placement, rtl) {
  1936. let {
  1937. reference,
  1938. floating
  1939. } = _ref;
  1940. const sideAxis = getSideAxis(placement);
  1941. const alignmentAxis = getAlignmentAxis(placement);
  1942. const alignLength = getAxisLength(alignmentAxis);
  1943. const side = getSide(placement);
  1944. const isVertical = sideAxis === "y";
  1945. const commonX = reference.x + reference.width / 2 - floating.width / 2;
  1946. const commonY = reference.y + reference.height / 2 - floating.height / 2;
  1947. const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2;
  1948. let coords;
  1949. switch (side) {
  1950. case "top":
  1951. coords = {
  1952. x: commonX,
  1953. y: reference.y - floating.height
  1954. };
  1955. break;
  1956. case "bottom":
  1957. coords = {
  1958. x: commonX,
  1959. y: reference.y + reference.height
  1960. };
  1961. break;
  1962. case "right":
  1963. coords = {
  1964. x: reference.x + reference.width,
  1965. y: commonY
  1966. };
  1967. break;
  1968. case "left":
  1969. coords = {
  1970. x: reference.x - floating.width,
  1971. y: commonY
  1972. };
  1973. break;
  1974. default:
  1975. coords = {
  1976. x: reference.x,
  1977. y: reference.y
  1978. };
  1979. }
  1980. switch (getAlignment(placement)) {
  1981. case "start":
  1982. coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1);
  1983. break;
  1984. case "end":
  1985. coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1);
  1986. break;
  1987. }
  1988. return coords;
  1989. }
  1990. async function detectOverflow(state, options) {
  1991. var _await$platform$isEle;
  1992. if (options === void 0) {
  1993. options = {};
  1994. }
  1995. const {
  1996. x,
  1997. y,
  1998. platform: platform2,
  1999. rects,
  2000. elements,
  2001. strategy
  2002. } = state;
  2003. const {
  2004. boundary = "clippingAncestors",
  2005. rootBoundary = "viewport",
  2006. elementContext = "floating",
  2007. altBoundary = false,
  2008. padding = 0
  2009. } = evaluate(options, state);
  2010. const paddingObject = getPaddingObject(padding);
  2011. const altContext = elementContext === "floating" ? "reference" : "floating";
  2012. const element = elements[altBoundary ? altContext : elementContext];
  2013. const clippingClientRect = rectToClientRect(await platform2.getClippingRect({
  2014. 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)),
  2015. boundary,
  2016. rootBoundary,
  2017. strategy
  2018. }));
  2019. const rect = elementContext === "floating" ? {
  2020. ...rects.floating,
  2021. x,
  2022. y
  2023. } : rects.reference;
  2024. const offsetParent = await (platform2.getOffsetParent == null ? void 0 : platform2.getOffsetParent(elements.floating));
  2025. const offsetScale = await (platform2.isElement == null ? void 0 : platform2.isElement(offsetParent)) ? await (platform2.getScale == null ? void 0 : platform2.getScale(offsetParent)) || {
  2026. x: 1,
  2027. y: 1
  2028. } : {
  2029. x: 1,
  2030. y: 1
  2031. };
  2032. const elementClientRect = rectToClientRect(platform2.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform2.convertOffsetParentRelativeRectToViewportRelativeRect({
  2033. rect,
  2034. offsetParent,
  2035. strategy
  2036. }) : rect);
  2037. return {
  2038. top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
  2039. bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
  2040. left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
  2041. right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
  2042. };
  2043. }
  2044. async function convertValueToCoords(state, options) {
  2045. const {
  2046. placement,
  2047. platform: platform2,
  2048. elements
  2049. } = state;
  2050. const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating));
  2051. const side = getSide(placement);
  2052. const alignment = getAlignment(placement);
  2053. const isVertical = getSideAxis(placement) === "y";
  2054. const mainAxisMulti = ["left", "top"].includes(side) ? -1 : 1;
  2055. const crossAxisMulti = rtl && isVertical ? -1 : 1;
  2056. const rawValue = evaluate(options, state);
  2057. let {
  2058. mainAxis,
  2059. crossAxis,
  2060. alignmentAxis
  2061. } = typeof rawValue === "number" ? {
  2062. mainAxis: rawValue,
  2063. crossAxis: 0,
  2064. alignmentAxis: null
  2065. } : {
  2066. mainAxis: 0,
  2067. crossAxis: 0,
  2068. alignmentAxis: null,
  2069. ...rawValue
  2070. };
  2071. if (alignment && typeof alignmentAxis === "number") {
  2072. crossAxis = alignment === "end" ? alignmentAxis * -1 : alignmentAxis;
  2073. }
  2074. return isVertical ? {
  2075. x: crossAxis * crossAxisMulti,
  2076. y: mainAxis * mainAxisMulti
  2077. } : {
  2078. x: mainAxis * mainAxisMulti,
  2079. y: crossAxis * crossAxisMulti
  2080. };
  2081. }
  2082. var computePosition, flip, offset, shift;
  2083. var init_floating_ui_core = __esm({
  2084. "node_modules/.pnpm/@floating-ui+core@1.5.2/node_modules/@floating-ui/core/dist/floating-ui.core.mjs"() {
  2085. "use strict";
  2086. init_floating_ui_utils();
  2087. init_floating_ui_utils();
  2088. computePosition = async (reference, floating, config) => {
  2089. const {
  2090. placement = "bottom",
  2091. strategy = "absolute",
  2092. middleware = [],
  2093. platform: platform2
  2094. } = config;
  2095. const validMiddleware = middleware.filter(Boolean);
  2096. const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(floating));
  2097. let rects = await platform2.getElementRects({
  2098. reference,
  2099. floating,
  2100. strategy
  2101. });
  2102. let {
  2103. x,
  2104. y
  2105. } = computeCoordsFromPlacement(rects, placement, rtl);
  2106. let statefulPlacement = placement;
  2107. let middlewareData = {};
  2108. let resetCount = 0;
  2109. for (let i = 0; i < validMiddleware.length; i++) {
  2110. const {
  2111. name,
  2112. fn
  2113. } = validMiddleware[i];
  2114. const {
  2115. x: nextX,
  2116. y: nextY,
  2117. data,
  2118. reset
  2119. } = await fn({
  2120. x,
  2121. y,
  2122. initialPlacement: placement,
  2123. placement: statefulPlacement,
  2124. strategy,
  2125. middlewareData,
  2126. rects,
  2127. platform: platform2,
  2128. elements: {
  2129. reference,
  2130. floating
  2131. }
  2132. });
  2133. x = nextX != null ? nextX : x;
  2134. y = nextY != null ? nextY : y;
  2135. middlewareData = {
  2136. ...middlewareData,
  2137. [name]: {
  2138. ...middlewareData[name],
  2139. ...data
  2140. }
  2141. };
  2142. if (reset && resetCount <= 50) {
  2143. resetCount++;
  2144. if (typeof reset === "object") {
  2145. if (reset.placement) {
  2146. statefulPlacement = reset.placement;
  2147. }
  2148. if (reset.rects) {
  2149. rects = reset.rects === true ? await platform2.getElementRects({
  2150. reference,
  2151. floating,
  2152. strategy
  2153. }) : reset.rects;
  2154. }
  2155. ({
  2156. x,
  2157. y
  2158. } = computeCoordsFromPlacement(rects, statefulPlacement, rtl));
  2159. }
  2160. i = -1;
  2161. continue;
  2162. }
  2163. }
  2164. return {
  2165. x,
  2166. y,
  2167. placement: statefulPlacement,
  2168. strategy,
  2169. middlewareData
  2170. };
  2171. };
  2172. flip = function(options) {
  2173. if (options === void 0) {
  2174. options = {};
  2175. }
  2176. return {
  2177. name: "flip",
  2178. options,
  2179. async fn(state) {
  2180. var _middlewareData$arrow, _middlewareData$flip;
  2181. const {
  2182. placement,
  2183. middlewareData,
  2184. rects,
  2185. initialPlacement,
  2186. platform: platform2,
  2187. elements
  2188. } = state;
  2189. const {
  2190. mainAxis: checkMainAxis = true,
  2191. crossAxis: checkCrossAxis = true,
  2192. fallbackPlacements: specifiedFallbackPlacements,
  2193. fallbackStrategy = "bestFit",
  2194. fallbackAxisSideDirection = "none",
  2195. flipAlignment = true,
  2196. ...detectOverflowOptions
  2197. } = evaluate(options, state);
  2198. if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
  2199. return {};
  2200. }
  2201. const side = getSide(placement);
  2202. const isBasePlacement = getSide(initialPlacement) === initialPlacement;
  2203. const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating));
  2204. const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement));
  2205. if (!specifiedFallbackPlacements && fallbackAxisSideDirection !== "none") {
  2206. fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));
  2207. }
  2208. const placements2 = [initialPlacement, ...fallbackPlacements];
  2209. const overflow = await detectOverflow(state, detectOverflowOptions);
  2210. const overflows = [];
  2211. let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];
  2212. if (checkMainAxis) {
  2213. overflows.push(overflow[side]);
  2214. }
  2215. if (checkCrossAxis) {
  2216. const sides2 = getAlignmentSides(placement, rects, rtl);
  2217. overflows.push(overflow[sides2[0]], overflow[sides2[1]]);
  2218. }
  2219. overflowsData = [...overflowsData, {
  2220. placement,
  2221. overflows
  2222. }];
  2223. if (!overflows.every((side2) => side2 <= 0)) {
  2224. var _middlewareData$flip2, _overflowsData$filter;
  2225. const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;
  2226. const nextPlacement = placements2[nextIndex];
  2227. if (nextPlacement) {
  2228. return {
  2229. data: {
  2230. index: nextIndex,
  2231. overflows: overflowsData
  2232. },
  2233. reset: {
  2234. placement: nextPlacement
  2235. }
  2236. };
  2237. }
  2238. 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;
  2239. if (!resetPlacement) {
  2240. switch (fallbackStrategy) {
  2241. case "bestFit": {
  2242. var _overflowsData$map$so;
  2243. 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];
  2244. if (placement2) {
  2245. resetPlacement = placement2;
  2246. }
  2247. break;
  2248. }
  2249. case "initialPlacement":
  2250. resetPlacement = initialPlacement;
  2251. break;
  2252. }
  2253. }
  2254. if (placement !== resetPlacement) {
  2255. return {
  2256. reset: {
  2257. placement: resetPlacement
  2258. }
  2259. };
  2260. }
  2261. }
  2262. return {};
  2263. }
  2264. };
  2265. };
  2266. offset = function(options) {
  2267. if (options === void 0) {
  2268. options = 0;
  2269. }
  2270. return {
  2271. name: "offset",
  2272. options,
  2273. async fn(state) {
  2274. var _middlewareData$offse, _middlewareData$arrow;
  2275. const {
  2276. x,
  2277. y,
  2278. placement,
  2279. middlewareData
  2280. } = state;
  2281. const diffCoords = await convertValueToCoords(state, options);
  2282. if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
  2283. return {};
  2284. }
  2285. return {
  2286. x: x + diffCoords.x,
  2287. y: y + diffCoords.y,
  2288. data: {
  2289. ...diffCoords,
  2290. placement
  2291. }
  2292. };
  2293. }
  2294. };
  2295. };
  2296. shift = function(options) {
  2297. if (options === void 0) {
  2298. options = {};
  2299. }
  2300. return {
  2301. name: "shift",
  2302. options,
  2303. async fn(state) {
  2304. const {
  2305. x,
  2306. y,
  2307. placement
  2308. } = state;
  2309. const {
  2310. mainAxis: checkMainAxis = true,
  2311. crossAxis: checkCrossAxis = false,
  2312. limiter = {
  2313. fn: (_ref) => {
  2314. let {
  2315. x: x2,
  2316. y: y2
  2317. } = _ref;
  2318. return {
  2319. x: x2,
  2320. y: y2
  2321. };
  2322. }
  2323. },
  2324. ...detectOverflowOptions
  2325. } = evaluate(options, state);
  2326. const coords = {
  2327. x,
  2328. y
  2329. };
  2330. const overflow = await detectOverflow(state, detectOverflowOptions);
  2331. const crossAxis = getSideAxis(getSide(placement));
  2332. const mainAxis = getOppositeAxis(crossAxis);
  2333. let mainAxisCoord = coords[mainAxis];
  2334. let crossAxisCoord = coords[crossAxis];
  2335. if (checkMainAxis) {
  2336. const minSide = mainAxis === "y" ? "top" : "left";
  2337. const maxSide = mainAxis === "y" ? "bottom" : "right";
  2338. const min3 = mainAxisCoord + overflow[minSide];
  2339. const max3 = mainAxisCoord - overflow[maxSide];
  2340. mainAxisCoord = clamp(min3, mainAxisCoord, max3);
  2341. }
  2342. if (checkCrossAxis) {
  2343. const minSide = crossAxis === "y" ? "top" : "left";
  2344. const maxSide = crossAxis === "y" ? "bottom" : "right";
  2345. const min3 = crossAxisCoord + overflow[minSide];
  2346. const max3 = crossAxisCoord - overflow[maxSide];
  2347. crossAxisCoord = clamp(min3, crossAxisCoord, max3);
  2348. }
  2349. const limitedCoords = limiter.fn({
  2350. ...state,
  2351. [mainAxis]: mainAxisCoord,
  2352. [crossAxis]: crossAxisCoord
  2353. });
  2354. return {
  2355. ...limitedCoords,
  2356. data: {
  2357. x: limitedCoords.x - x,
  2358. y: limitedCoords.y - y
  2359. }
  2360. };
  2361. }
  2362. };
  2363. };
  2364. }
  2365. });
  2366.  
  2367. // node_modules/.pnpm/@floating-ui+dom@1.4.5/node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs
  2368. function getWindow(node) {
  2369. var _node$ownerDocument;
  2370. return (node == null ? void 0 : (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
  2371. }
  2372. function getComputedStyle$1(element) {
  2373. return getWindow(element).getComputedStyle(element);
  2374. }
  2375. function isNode(value) {
  2376. return value instanceof getWindow(value).Node;
  2377. }
  2378. function getNodeName(node) {
  2379. if (isNode(node)) {
  2380. return (node.nodeName || "").toLowerCase();
  2381. }
  2382. return "#document";
  2383. }
  2384. function isHTMLElement(value) {
  2385. return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement;
  2386. }
  2387. function isShadowRoot(node) {
  2388. if (typeof ShadowRoot === "undefined") {
  2389. return false;
  2390. }
  2391. return node instanceof getWindow(node).ShadowRoot || node instanceof ShadowRoot;
  2392. }
  2393. function isOverflowElement(element) {
  2394. const {
  2395. overflow,
  2396. overflowX,
  2397. overflowY,
  2398. display
  2399. } = getComputedStyle$1(element);
  2400. return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display);
  2401. }
  2402. function isTableElement(element) {
  2403. return ["table", "td", "th"].includes(getNodeName(element));
  2404. }
  2405. function isContainingBlock(element) {
  2406. const safari = isSafari();
  2407. const css = getComputedStyle$1(element);
  2408. return css.transform !== "none" || css.perspective !== "none" || (css.containerType ? css.containerType !== "normal" : false) || !safari && (css.backdropFilter ? css.backdropFilter !== "none" : false) || !safari && (css.filter ? css.filter !== "none" : false) || ["transform", "perspective", "filter"].some((value) => (css.willChange || "").includes(value)) || ["paint", "layout", "strict", "content"].some((value) => (css.contain || "").includes(value));
  2409. }
  2410. function isSafari() {
  2411. if (typeof CSS === "undefined" || !CSS.supports)
  2412. return false;
  2413. return CSS.supports("-webkit-backdrop-filter", "none");
  2414. }
  2415. function isLastTraversableNode(node) {
  2416. return ["html", "body", "#document"].includes(getNodeName(node));
  2417. }
  2418. function getCssDimensions(element) {
  2419. const css = getComputedStyle$1(element);
  2420. let width = parseFloat(css.width) || 0;
  2421. let height = parseFloat(css.height) || 0;
  2422. const hasOffset = isHTMLElement(element);
  2423. const offsetWidth = hasOffset ? element.offsetWidth : width;
  2424. const offsetHeight = hasOffset ? element.offsetHeight : height;
  2425. const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;
  2426. if (shouldFallback) {
  2427. width = offsetWidth;
  2428. height = offsetHeight;
  2429. }
  2430. return {
  2431. width,
  2432. height,
  2433. $: shouldFallback
  2434. };
  2435. }
  2436. function isElement(value) {
  2437. return value instanceof Element || value instanceof getWindow(value).Element;
  2438. }
  2439. function unwrapElement(element) {
  2440. return !isElement(element) ? element.contextElement : element;
  2441. }
  2442. function getScale(element) {
  2443. const domElement = unwrapElement(element);
  2444. if (!isHTMLElement(domElement)) {
  2445. return createCoords(1);
  2446. }
  2447. const rect = domElement.getBoundingClientRect();
  2448. const {
  2449. width,
  2450. height,
  2451. $: $2
  2452. } = getCssDimensions(domElement);
  2453. let x = ($2 ? round(rect.width) : rect.width) / width;
  2454. let y = ($2 ? round(rect.height) : rect.height) / height;
  2455. if (!x || !Number.isFinite(x)) {
  2456. x = 1;
  2457. }
  2458. if (!y || !Number.isFinite(y)) {
  2459. y = 1;
  2460. }
  2461. return {
  2462. x,
  2463. y
  2464. };
  2465. }
  2466. function getVisualOffsets(element) {
  2467. const win = getWindow(element);
  2468. if (!isSafari() || !win.visualViewport) {
  2469. return noOffsets;
  2470. }
  2471. return {
  2472. x: win.visualViewport.offsetLeft,
  2473. y: win.visualViewport.offsetTop
  2474. };
  2475. }
  2476. function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) {
  2477. if (isFixed === void 0) {
  2478. isFixed = false;
  2479. }
  2480. if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow(element)) {
  2481. return false;
  2482. }
  2483. return isFixed;
  2484. }
  2485. function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {
  2486. if (includeScale === void 0) {
  2487. includeScale = false;
  2488. }
  2489. if (isFixedStrategy === void 0) {
  2490. isFixedStrategy = false;
  2491. }
  2492. const clientRect = element.getBoundingClientRect();
  2493. const domElement = unwrapElement(element);
  2494. let scale = createCoords(1);
  2495. if (includeScale) {
  2496. if (offsetParent) {
  2497. if (isElement(offsetParent)) {
  2498. scale = getScale(offsetParent);
  2499. }
  2500. } else {
  2501. scale = getScale(element);
  2502. }
  2503. }
  2504. const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0);
  2505. let x = (clientRect.left + visualOffsets.x) / scale.x;
  2506. let y = (clientRect.top + visualOffsets.y) / scale.y;
  2507. let width = clientRect.width / scale.x;
  2508. let height = clientRect.height / scale.y;
  2509. if (domElement) {
  2510. const win = getWindow(domElement);
  2511. const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent;
  2512. let currentIFrame = win.frameElement;
  2513. while (currentIFrame && offsetParent && offsetWin !== win) {
  2514. const iframeScale = getScale(currentIFrame);
  2515. const iframeRect = currentIFrame.getBoundingClientRect();
  2516. const css = getComputedStyle(currentIFrame);
  2517. const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
  2518. const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
  2519. x *= iframeScale.x;
  2520. y *= iframeScale.y;
  2521. width *= iframeScale.x;
  2522. height *= iframeScale.y;
  2523. x += left;
  2524. y += top;
  2525. currentIFrame = getWindow(currentIFrame).frameElement;
  2526. }
  2527. }
  2528. return rectToClientRect({
  2529. width,
  2530. height,
  2531. x,
  2532. y
  2533. });
  2534. }
  2535. function getNodeScroll(element) {
  2536. if (isElement(element)) {
  2537. return {
  2538. scrollLeft: element.scrollLeft,
  2539. scrollTop: element.scrollTop
  2540. };
  2541. }
  2542. return {
  2543. scrollLeft: element.pageXOffset,
  2544. scrollTop: element.pageYOffset
  2545. };
  2546. }
  2547. function getDocumentElement(node) {
  2548. var _ref;
  2549. return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement;
  2550. }
  2551. function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
  2552. let {
  2553. rect,
  2554. offsetParent,
  2555. strategy
  2556. } = _ref;
  2557. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  2558. const documentElement = getDocumentElement(offsetParent);
  2559. if (offsetParent === documentElement) {
  2560. return rect;
  2561. }
  2562. let scroll = {
  2563. scrollLeft: 0,
  2564. scrollTop: 0
  2565. };
  2566. let scale = createCoords(1);
  2567. const offsets = createCoords(0);
  2568. if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== "fixed") {
  2569. if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) {
  2570. scroll = getNodeScroll(offsetParent);
  2571. }
  2572. if (isHTMLElement(offsetParent)) {
  2573. const offsetRect = getBoundingClientRect(offsetParent);
  2574. scale = getScale(offsetParent);
  2575. offsets.x = offsetRect.x + offsetParent.clientLeft;
  2576. offsets.y = offsetRect.y + offsetParent.clientTop;
  2577. }
  2578. }
  2579. return {
  2580. width: rect.width * scale.x,
  2581. height: rect.height * scale.y,
  2582. x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x,
  2583. y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y
  2584. };
  2585. }
  2586. function getClientRects(element) {
  2587. return Array.from(element.getClientRects());
  2588. }
  2589. function getWindowScrollBarX(element) {
  2590. return getBoundingClientRect(getDocumentElement(element)).left + getNodeScroll(element).scrollLeft;
  2591. }
  2592. function getDocumentRect(element) {
  2593. const html = getDocumentElement(element);
  2594. const scroll = getNodeScroll(element);
  2595. const body = element.ownerDocument.body;
  2596. const width = max2(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth);
  2597. const height = max2(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
  2598. let x = -scroll.scrollLeft + getWindowScrollBarX(element);
  2599. const y = -scroll.scrollTop;
  2600. if (getComputedStyle$1(body).direction === "rtl") {
  2601. x += max2(html.clientWidth, body.clientWidth) - width;
  2602. }
  2603. return {
  2604. width,
  2605. height,
  2606. x,
  2607. y
  2608. };
  2609. }
  2610. function getParentNode(node) {
  2611. if (getNodeName(node) === "html") {
  2612. return node;
  2613. }
  2614. const result = (
  2615. // Step into the shadow DOM of the parent of a slotted node.
  2616. node.assignedSlot || // DOM Element detected.
  2617. node.parentNode || // ShadowRoot detected.
  2618. isShadowRoot(node) && node.host || // Fallback.
  2619. getDocumentElement(node)
  2620. );
  2621. return isShadowRoot(result) ? result.host : result;
  2622. }
  2623. function getNearestOverflowAncestor(node) {
  2624. const parentNode = getParentNode(node);
  2625. if (isLastTraversableNode(parentNode)) {
  2626. return node.ownerDocument ? node.ownerDocument.body : node.body;
  2627. }
  2628. if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
  2629. return parentNode;
  2630. }
  2631. return getNearestOverflowAncestor(parentNode);
  2632. }
  2633. function getOverflowAncestors(node, list) {
  2634. var _node$ownerDocument;
  2635. if (list === void 0) {
  2636. list = [];
  2637. }
  2638. const scrollableAncestor = getNearestOverflowAncestor(node);
  2639. const isBody = scrollableAncestor === ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.body);
  2640. const win = getWindow(scrollableAncestor);
  2641. if (isBody) {
  2642. return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []);
  2643. }
  2644. return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor));
  2645. }
  2646. function getViewportRect(element, strategy) {
  2647. const win = getWindow(element);
  2648. const html = getDocumentElement(element);
  2649. const visualViewport = win.visualViewport;
  2650. let width = html.clientWidth;
  2651. let height = html.clientHeight;
  2652. let x = 0;
  2653. let y = 0;
  2654. if (visualViewport) {
  2655. width = visualViewport.width;
  2656. height = visualViewport.height;
  2657. const visualViewportBased = isSafari();
  2658. if (!visualViewportBased || visualViewportBased && strategy === "fixed") {
  2659. x = visualViewport.offsetLeft;
  2660. y = visualViewport.offsetTop;
  2661. }
  2662. }
  2663. return {
  2664. width,
  2665. height,
  2666. x,
  2667. y
  2668. };
  2669. }
  2670. function getInnerBoundingClientRect(element, strategy) {
  2671. const clientRect = getBoundingClientRect(element, true, strategy === "fixed");
  2672. const top = clientRect.top + element.clientTop;
  2673. const left = clientRect.left + element.clientLeft;
  2674. const scale = isHTMLElement(element) ? getScale(element) : createCoords(1);
  2675. const width = element.clientWidth * scale.x;
  2676. const height = element.clientHeight * scale.y;
  2677. const x = left * scale.x;
  2678. const y = top * scale.y;
  2679. return {
  2680. width,
  2681. height,
  2682. x,
  2683. y
  2684. };
  2685. }
  2686. function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) {
  2687. let rect;
  2688. if (clippingAncestor === "viewport") {
  2689. rect = getViewportRect(element, strategy);
  2690. } else if (clippingAncestor === "document") {
  2691. rect = getDocumentRect(getDocumentElement(element));
  2692. } else if (isElement(clippingAncestor)) {
  2693. rect = getInnerBoundingClientRect(clippingAncestor, strategy);
  2694. } else {
  2695. const visualOffsets = getVisualOffsets(element);
  2696. rect = {
  2697. ...clippingAncestor,
  2698. x: clippingAncestor.x - visualOffsets.x,
  2699. y: clippingAncestor.y - visualOffsets.y
  2700. };
  2701. }
  2702. return rectToClientRect(rect);
  2703. }
  2704. function hasFixedPositionAncestor(element, stopNode) {
  2705. const parentNode = getParentNode(element);
  2706. if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
  2707. return false;
  2708. }
  2709. return getComputedStyle$1(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode);
  2710. }
  2711. function getClippingElementAncestors(element, cache) {
  2712. const cachedResult = cache.get(element);
  2713. if (cachedResult) {
  2714. return cachedResult;
  2715. }
  2716. let result = getOverflowAncestors(element).filter((el) => isElement(el) && getNodeName(el) !== "body");
  2717. let currentContainingBlockComputedStyle = null;
  2718. const elementIsFixed = getComputedStyle$1(element).position === "fixed";
  2719. let currentNode = elementIsFixed ? getParentNode(element) : element;
  2720. while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
  2721. const computedStyle = getComputedStyle$1(currentNode);
  2722. const currentNodeIsContaining = isContainingBlock(currentNode);
  2723. if (!currentNodeIsContaining && computedStyle.position === "fixed") {
  2724. currentContainingBlockComputedStyle = null;
  2725. }
  2726. const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === "static" && !!currentContainingBlockComputedStyle && ["absolute", "fixed"].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
  2727. if (shouldDropCurrentNode) {
  2728. result = result.filter((ancestor) => ancestor !== currentNode);
  2729. } else {
  2730. currentContainingBlockComputedStyle = computedStyle;
  2731. }
  2732. currentNode = getParentNode(currentNode);
  2733. }
  2734. cache.set(element, result);
  2735. return result;
  2736. }
  2737. function getClippingRect(_ref) {
  2738. let {
  2739. element,
  2740. boundary,
  2741. rootBoundary,
  2742. strategy
  2743. } = _ref;
  2744. const elementClippingAncestors = boundary === "clippingAncestors" ? getClippingElementAncestors(element, this._c) : [].concat(boundary);
  2745. const clippingAncestors = [...elementClippingAncestors, rootBoundary];
  2746. const firstClippingAncestor = clippingAncestors[0];
  2747. const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
  2748. const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);
  2749. accRect.top = max2(rect.top, accRect.top);
  2750. accRect.right = min2(rect.right, accRect.right);
  2751. accRect.bottom = min2(rect.bottom, accRect.bottom);
  2752. accRect.left = max2(rect.left, accRect.left);
  2753. return accRect;
  2754. }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
  2755. return {
  2756. width: clippingRect.right - clippingRect.left,
  2757. height: clippingRect.bottom - clippingRect.top,
  2758. x: clippingRect.left,
  2759. y: clippingRect.top
  2760. };
  2761. }
  2762. function getDimensions(element) {
  2763. return getCssDimensions(element);
  2764. }
  2765. function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
  2766. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  2767. const documentElement = getDocumentElement(offsetParent);
  2768. const isFixed = strategy === "fixed";
  2769. const rect = getBoundingClientRect(element, true, isFixed, offsetParent);
  2770. let scroll = {
  2771. scrollLeft: 0,
  2772. scrollTop: 0
  2773. };
  2774. const offsets = createCoords(0);
  2775. if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
  2776. if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) {
  2777. scroll = getNodeScroll(offsetParent);
  2778. }
  2779. if (isHTMLElement(offsetParent)) {
  2780. const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent);
  2781. offsets.x = offsetRect.x + offsetParent.clientLeft;
  2782. offsets.y = offsetRect.y + offsetParent.clientTop;
  2783. } else if (documentElement) {
  2784. offsets.x = getWindowScrollBarX(documentElement);
  2785. }
  2786. }
  2787. return {
  2788. x: rect.left + scroll.scrollLeft - offsets.x,
  2789. y: rect.top + scroll.scrollTop - offsets.y,
  2790. width: rect.width,
  2791. height: rect.height
  2792. };
  2793. }
  2794. function getTrueOffsetParent(element, polyfill) {
  2795. if (!isHTMLElement(element) || getComputedStyle$1(element).position === "fixed") {
  2796. return null;
  2797. }
  2798. if (polyfill) {
  2799. return polyfill(element);
  2800. }
  2801. return element.offsetParent;
  2802. }
  2803. function getContainingBlock(element) {
  2804. let currentNode = getParentNode(element);
  2805. while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
  2806. if (isContainingBlock(currentNode)) {
  2807. return currentNode;
  2808. } else {
  2809. currentNode = getParentNode(currentNode);
  2810. }
  2811. }
  2812. return null;
  2813. }
  2814. function getOffsetParent(element, polyfill) {
  2815. const window2 = getWindow(element);
  2816. if (!isHTMLElement(element)) {
  2817. return window2;
  2818. }
  2819. let offsetParent = getTrueOffsetParent(element, polyfill);
  2820. while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === "static") {
  2821. offsetParent = getTrueOffsetParent(offsetParent, polyfill);
  2822. }
  2823. if (offsetParent && (getNodeName(offsetParent) === "html" || getNodeName(offsetParent) === "body" && getComputedStyle$1(offsetParent).position === "static" && !isContainingBlock(offsetParent))) {
  2824. return window2;
  2825. }
  2826. return offsetParent || getContainingBlock(element) || window2;
  2827. }
  2828. function isRTL(element) {
  2829. return getComputedStyle(element).direction === "rtl";
  2830. }
  2831. var min2, max2, round, createCoords, noOffsets, getElementRects, platform, computePosition2;
  2832. var init_floating_ui_dom = __esm({
  2833. "node_modules/.pnpm/@floating-ui+dom@1.4.5/node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs"() {
  2834. "use strict";
  2835. init_floating_ui_core();
  2836. init_floating_ui_core();
  2837. min2 = Math.min;
  2838. max2 = Math.max;
  2839. round = Math.round;
  2840. createCoords = (v) => ({
  2841. x: v,
  2842. y: v
  2843. });
  2844. noOffsets = /* @__PURE__ */ createCoords(0);
  2845. getElementRects = async function(_ref) {
  2846. let {
  2847. reference,
  2848. floating,
  2849. strategy
  2850. } = _ref;
  2851. const getOffsetParentFn = this.getOffsetParent || getOffsetParent;
  2852. const getDimensionsFn = this.getDimensions;
  2853. return {
  2854. reference: getRectRelativeToOffsetParent(reference, await getOffsetParentFn(floating), strategy),
  2855. floating: {
  2856. x: 0,
  2857. y: 0,
  2858. ...await getDimensionsFn(floating)
  2859. }
  2860. };
  2861. };
  2862. platform = {
  2863. convertOffsetParentRelativeRectToViewportRelativeRect,
  2864. getDocumentElement,
  2865. getClippingRect,
  2866. getOffsetParent,
  2867. getElementRects,
  2868. getClientRects,
  2869. getDimensions,
  2870. getScale,
  2871. isElement,
  2872. isRTL
  2873. };
  2874. computePosition2 = (reference, floating, options) => {
  2875. const cache = /* @__PURE__ */ new Map();
  2876. const mergedOptions = {
  2877. platform,
  2878. ...options
  2879. };
  2880. const platformWithCache = {
  2881. ...mergedOptions.platform,
  2882. _c: cache
  2883. };
  2884. return computePosition(reference, floating, {
  2885. ...mergedOptions,
  2886. platform: platformWithCache
  2887. });
  2888. };
  2889. }
  2890. });
  2891.  
  2892. // src/components/popup.ts
  2893. function createPopup(props) {
  2894. const {
  2895. root,
  2896. trigger,
  2897. triggerType = "click",
  2898. content,
  2899. options,
  2900. onOpen,
  2901. onClose,
  2902. placement = "bottom-start",
  2903. offsetOptions = { mainAxis: 5, crossAxis: 5 }
  2904. } = props;
  2905. const $popupContent = $('<div class="v2p-popup-content">');
  2906. const $popup = $('<div class="v2p-popup" tabindex="0">').css("visibility", "hidden").append($popupContent);
  2907. root.append($popup);
  2908. if (content) {
  2909. $popup.append(content);
  2910. }
  2911. const popup = $popup.get(0);
  2912. const handleClickOutside = (ev) => {
  2913. if ($(ev.target).closest(popup).length === 0) {
  2914. handlePopupClose();
  2915. }
  2916. };
  2917. const handlePopupClose = () => {
  2918. $popup.css("visibility", "hidden");
  2919. $(document).off("click", handleClickOutside);
  2920. onClose?.();
  2921. popupControl2.onClose?.();
  2922. };
  2923. const handlePopupOpen = ($reference) => {
  2924. if (!$reference) {
  2925. return;
  2926. }
  2927. setTimeout(() => {
  2928. $(document).on("click", handleClickOutside);
  2929. });
  2930. const referenceElement = $reference.get(0);
  2931. computePosition2(referenceElement, popup, {
  2932. placement,
  2933. middleware: [offset(offsetOptions), flip(), shift({ padding: 8 })],
  2934. ...options
  2935. }).then(({ x, y }) => {
  2936. Object.assign(popup.style, {
  2937. left: `${x}px`,
  2938. top: `${y}px`
  2939. });
  2940. $popup.css("visibility", "visible");
  2941. }).catch(() => {
  2942. handlePopupClose();
  2943. createToast({ message: "\u274C Popup \u6E32\u67D3\u5931\u8D25" });
  2944. });
  2945. onOpen?.();
  2946. };
  2947. const popupControl2 = {
  2948. $content: $popupContent,
  2949. isOver: false,
  2950. open: (reference) => {
  2951. handlePopupOpen(reference);
  2952. },
  2953. close: handlePopupClose
  2954. };
  2955. if (triggerType === "hover") {
  2956. $popup.on("mouseover", () => {
  2957. if (!popupControl2.isOver) {
  2958. popupControl2.isOver = true;
  2959. $popup.off("mouseleave").on("mouseleave", () => {
  2960. popupControl2.isOver = false;
  2961. setTimeout(() => {
  2962. if (!popupControl2.isOver) {
  2963. popupControl2.close();
  2964. }
  2965. }, hoverDelay);
  2966. });
  2967. }
  2968. });
  2969. }
  2970. trigger?.on("click", () => {
  2971. if (popup.style.visibility !== "hidden") {
  2972. handlePopupClose();
  2973. } else {
  2974. handlePopupOpen(trigger);
  2975. }
  2976. });
  2977. return popupControl2;
  2978. }
  2979. var hoverDelay;
  2980. var init_popup = __esm({
  2981. "src/components/popup.ts"() {
  2982. "use strict";
  2983. init_floating_ui_dom();
  2984. init_toast();
  2985. hoverDelay = 350;
  2986. }
  2987. });
  2988.  
  2989. // src/contents/topic/content.ts
  2990. function handlingContent() {
  2991. const storage = getStorageSync();
  2992. const options = storage["options" /* Options */];
  2993. if (options.openInNewTab) {
  2994. $topicContentBox.find(".topic_content a[href]").prop("target", "_blank").prop("rel", "noopener noreferrer");
  2995. }
  2996. {
  2997. const $topicContents = $topicContentBox.find(".subtle > .topic_content");
  2998. const textLength = $topicContents.text().length;
  2999. if (textLength >= 200) {
  3000. $topicContents.each((_, topicContent) => {
  3001. if (textLength >= 400) {
  3002. topicContent.style.fontSize = "14px";
  3003. }
  3004. topicContent.style.fontSize = "14.5px";
  3005. });
  3006. }
  3007. }
  3008. {
  3009. const topicBtn = $(".topic_buttons .tb").addClass("v2p-tb v2p-hover-btn");
  3010. const $favoriteBtn = topicBtn.eq(0);
  3011. $favoriteBtn.append('<span class="v2p-tb-icon"><i data-lucide="star"></i></span>');
  3012. topicBtn.eq(1).append('<span class="v2p-tb-icon"><i data-lucide="twitter"></i></span>');
  3013. topicBtn.eq(2).append('<span class="v2p-tb-icon"><i data-lucide="eye-off"></i></span>');
  3014. topicBtn.eq(3).append('<span class="v2p-tb-icon"><i data-lucide="heart"></i></span>');
  3015. loadIcons();
  3016. }
  3017. }
  3018. function processReplyContent($cellDom) {
  3019. if ($cellDom.find(".v2p-reply-content").length > 0) {
  3020. return;
  3021. }
  3022. const $replyContent = $cellDom.find(".reply_content");
  3023. const contentHeight = $replyContent.height() ?? 0;
  3024. const shouldCollapsed = contentHeight + READABLE_CONTENT_HEIGHT >= MAX_CONTENT_HEIGHT;
  3025. if (shouldCollapsed) {
  3026. const collapsedCSS = {
  3027. maxHeight: `${READABLE_CONTENT_HEIGHT}px`,
  3028. overflow: "hidden",
  3029. paddingBottom: "0"
  3030. };
  3031. const $contentBox = $('<div class="v2p-reply-content v2p-collapsed">').css(collapsedCSS);
  3032. const $expandBtn = createButton({ children: "\u5C55\u5F00\u56DE\u590D", className: "v2p-expand-btn" });
  3033. const toggleContent = () => {
  3034. const collapsed = $contentBox.hasClass("v2p-collapsed");
  3035. $contentBox.toggleClass("v2p-collapsed").css(
  3036. collapsed ? { maxHeight: "none", overflow: "auto", paddingBottom: "40px" } : collapsedCSS
  3037. );
  3038. $expandBtn.html(collapsed ? "\u6536\u8D77\u56DE\u590D" : "\u5C55\u5F00\u56DE\u590D");
  3039. };
  3040. $expandBtn.on("click", () => {
  3041. toggleContent();
  3042. });
  3043. $contentBox.append($replyContent.clone()).replaceAll($replyContent).append($expandBtn);
  3044. }
  3045. }
  3046. function updateMemberTag(memberName, tags, options) {
  3047. const $v2pTags = $(`.v2p-tags-${memberName}`);
  3048. const tagsValue = tags?.map((it) => it.name).join("\uFF0C");
  3049. if ($v2pTags.length > 0) {
  3050. if (tagsValue) {
  3051. $v2pTags.html(`<b>#</b>&nbsp;${tagsValue}`);
  3052. } else {
  3053. $v2pTags.remove();
  3054. }
  3055. } else {
  3056. if (tagsValue) {
  3057. const $tags = $(
  3058. `<div class="v2p-reply-tags v2p-tags-${memberName}" title="${tagsValue}"><b>#</b>&nbsp;${tagsValue}</div>`
  3059. );
  3060. $tags.on("click", () => {
  3061. openTagsSetter(memberName);
  3062. });
  3063. if (memberName === topicOwnerName) {
  3064. $topicHeader.append($tags.clone(true));
  3065. }
  3066. if (options.userTag.display === "inline") {
  3067. $tags.addClass("v2p-reply-tags-inline").insertBefore(
  3068. $commentCells.filter(`:has(> table strong > a[href="/member/${memberName}"])`).find("> table .badges")
  3069. );
  3070. } else {
  3071. $tags.insertBefore(
  3072. $commentCells.filter(`:has(> table strong > a[href="/member/${memberName}"])`).find("> table .reply_content")
  3073. );
  3074. }
  3075. }
  3076. }
  3077. }
  3078. function openTagsSetter(memberName) {
  3079. void (async () => {
  3080. const storage = await getStorage(false);
  3081. const latestTagsData = storage["member-tag" /* MemberTag */];
  3082. const options = storage["options" /* Options */];
  3083. const tagsValue = latestTagsData ? Reflect.has(latestTagsData, memberName) ? latestTagsData[memberName].tags?.map((it) => it.name).join("\uFF0C") : void 0 : void 0;
  3084. const newTagsValue = window.prompt(
  3085. `\u5BF9 @${memberName} \u8BBE\u7F6E\u6807\u7B7E\uFF0C\u591A\u4E2A\u6807\u7B7E\u4EE5\u9017\u53F7\uFF08\uFF0C\uFF09\u5206\u9694\u3002`,
  3086. tagsValue
  3087. );
  3088. if (newTagsValue !== null) {
  3089. const tags = newTagsValue.trim().length > 0 ? newTagsValue.split(/,|,/g).filter((it) => it.trim().length > 0).map((it) => ({ name: it })) : void 0;
  3090. await setMemberTags(memberName, tags);
  3091. updateMemberTag(memberName, tags, options);
  3092. }
  3093. })();
  3094. }
  3095. var init_content = __esm({
  3096. "src/contents/topic/content.ts"() {
  3097. "use strict";
  3098. init_button();
  3099. init_constants();
  3100. init_utils();
  3101. init_globals();
  3102. init_helpers();
  3103. }
  3104. });
  3105.  
  3106. // src/contents/topic/avatar.ts
  3107. function processAvatar(params) {
  3108. const { $trigger, popupControl: popupControl2, commentData, shouldWrap = true, onSetTagsClick } = params;
  3109. const { memberName, memberAvatar, memberLink } = commentData;
  3110. let abortController = null;
  3111. const handleOver = () => {
  3112. popupControl2.close();
  3113. popupControl2.open($trigger);
  3114. const $content = $(`
  3115. <div class="v2p-member-card">
  3116. <div class="v2p-info">
  3117. <div class="v2p-info-left">
  3118. <a class="v2p-avatar-box" href="${memberLink}">
  3119. <img class="v2p-avatar" src="${memberAvatar}">
  3120. </a>
  3121. </div>
  3122.  
  3123. <div class="v2p-info-right">
  3124. <div class="v2p-username">
  3125. <a href="${memberLink}">${memberName}</a>
  3126. </div>
  3127. <div class="v2p-no v2p-loading"></div>
  3128. <div class="v2p-created-date v2p-loading"></div>
  3129. </div>
  3130.  
  3131. </div>
  3132.  
  3133. <div class="v2p-bio" style="disply:none;"></div>
  3134.  
  3135. <div class="v2p-member-card-actions"></div>
  3136. </div>
  3137. `);
  3138. popupControl2.$content.empty().append($content);
  3139. createButton({ children: "\u6DFB\u52A0\u7528\u6237\u6807\u7B7E" }).on("click", () => {
  3140. popupControl2.close();
  3141. openTagsSetter(memberName);
  3142. onSetTagsClick?.();
  3143. }).appendTo($(".v2p-member-card-actions"));
  3144. void (async () => {
  3145. if (!memberDataCache.has(memberName)) {
  3146. abortController = new AbortController();
  3147. popupControl2.onClose = () => {
  3148. abortController?.abort();
  3149. };
  3150. try {
  3151. const memberData = await fetchUserInfo(memberName, {
  3152. signal: abortController.signal
  3153. });
  3154. memberDataCache.set(memberName, memberData);
  3155. } catch (err) {
  3156. if (err && typeof err === "object" && "name" in err && err.name !== "AbortError") {
  3157. $content.html(`<span>\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25</span>`);
  3158. }
  3159. return null;
  3160. }
  3161. }
  3162. const data = memberDataCache.get(memberName);
  3163. if (data) {
  3164. $content.find(".v2p-no").removeClass("v2p-loading").text(`V2EX \u7B2C ${data.id} \u53F7\u4F1A\u5458`);
  3165. $content.find(".v2p-created-date").removeClass("v2p-loading").text(`\u52A0\u5165\u4E8E ${formatTimestamp(data.created)}`);
  3166. if (data.bio && data.bio.trim().length > 0) {
  3167. $content.find(".v2p-bio").css("disply", "block").text(data.bio);
  3168. }
  3169. }
  3170. })();
  3171. };
  3172. let isOver = false;
  3173. $trigger.on("mouseover", () => {
  3174. isOver = true;
  3175. setTimeout(() => {
  3176. if (isOver) {
  3177. handleOver();
  3178. }
  3179. }, hoverDelay);
  3180. }).on("mouseleave", () => {
  3181. isOver = false;
  3182. setTimeout(() => {
  3183. if (!popupControl2.isOver && !isOver) {
  3184. popupControl2.close();
  3185. }
  3186. }, hoverDelay);
  3187. });
  3188. if (shouldWrap) {
  3189. $trigger.wrap(
  3190. `<a href="/member/${commentData.memberName}" target="_blank" style="cursor: pointer;">`
  3191. );
  3192. }
  3193. }
  3194. var memberDataCache;
  3195. var init_avatar = __esm({
  3196. "src/contents/topic/avatar.ts"() {
  3197. "use strict";
  3198. init_button();
  3199. init_popup();
  3200. init_services();
  3201. init_utils();
  3202. init_content();
  3203. memberDataCache = /* @__PURE__ */ new Map();
  3204. }
  3205. });
  3206.  
  3207. // src/contents/topic/comment.ts
  3208. function handlingFilteredComments() {
  3209. const iconHeart = createElement$1(Heart);
  3210. iconHeart.setAttribute("width", "100%");
  3211. iconHeart.setAttribute("height", "100%");
  3212. const $commentsBtn = $(
  3213. `<span class="v2p-tool v2p-hover-btn"><span class="v2p-tool-icon"></span>\u70ED\u95E8\u56DE\u590D</span>`
  3214. );
  3215. $commentsBtn.find(".v2p-tool-icon").append(iconHeart);
  3216. $(".v2p-tools").prepend($commentsBtn);
  3217. const popularCommentData = commentDataList.filter(({ likes }) => likes > 0).sort((a, b) => b.likes - a.likes);
  3218. const popularCount = popularCommentData.length;
  3219. const model = createModel({
  3220. root: $main,
  3221. onMount: ({ $title, $content }) => {
  3222. const $template = $('<div class="v2p-modal-comments">');
  3223. const $t1 = $template.clone().attr("data-tab-key", "hot").addClass("v2p-tab-content-active");
  3224. const $t2 = $template.clone().attr("data-tab-key", "recent");
  3225. const $t3 = $template.clone().attr("data-tab-key", "op");
  3226. {
  3227. if (popularCount > 0) {
  3228. popularCommentData.forEach(({ index, refMemberNames }) => {
  3229. const $clonedCell = $commentCells.eq(index).clone();
  3230. $clonedCell.find(".v2p-controls > a:has(.v2p-control-reply)").remove();
  3231. $clonedCell.find(".no").css("pointer-events", "none");
  3232. const firstRefMember = refMemberNames?.at(0);
  3233. if (firstRefMember) {
  3234. const replyMember = commentDataList.findLast(
  3235. (it, idx) => idx < index && it.memberName === firstRefMember
  3236. );
  3237. if (replyMember) {
  3238. $clonedCell.prepend(
  3239. $(`
  3240. <div class="v2p-topic-reply-ref">
  3241. <div class="v2p-topic-reply">
  3242. <div class="v2p-topic-reply-member">
  3243. <a href="${replyMember.memberLink}" target="_blank">
  3244. <img class="v2p-topic-reply-avatar" src="${replyMember.memberAvatar}">
  3245. <span>${replyMember.memberName}</span>
  3246. </a>\uFF1A
  3247. </div>
  3248. <div class="v2p-topic-reply-content">${escapeHTML(
  3249. replyMember.content
  3250. )}</div>
  3251. </div>
  3252. </div>
  3253. `)
  3254. );
  3255. }
  3256. }
  3257. $t1.append($clonedCell);
  3258. });
  3259. } else {
  3260. $t1.append($("<div>\u6682\u65E0\u70ED\u95E8\u56DE\u590D</div>").css({ padding: "20px", textAlign: "center" }));
  3261. }
  3262. $content.append($t1);
  3263. }
  3264. {
  3265. const len = commentDataList.length;
  3266. const displayNum = len < 10 ? len : len <= 10 ? 5 : len <= 30 ? 10 : len <= 60 ? 20 : len <= 100 ? 40 : len <= 200 ? 60 : 90;
  3267. if (displayNum > 0) {
  3268. const recentCommentData = commentDataList.slice(-1 * displayNum).reverse();
  3269. recentCommentData.forEach(({ index, refMemberNames }) => {
  3270. const $clonedCell = $commentCells.eq(index).clone();
  3271. $clonedCell.find(".v2p-controls > a:has(.v2p-control-reply)").remove();
  3272. $clonedCell.find(".no").css("pointer-events", "none");
  3273. const firstRefMember = refMemberNames?.at(0);
  3274. if (firstRefMember) {
  3275. const replyMember = commentDataList.findLast(
  3276. (it, idx) => idx < index && it.memberName === firstRefMember
  3277. );
  3278. if (replyMember) {
  3279. $clonedCell.prepend(
  3280. $(`
  3281. <div class="v2p-topic-reply-ref">
  3282. <div class="v2p-topic-reply">
  3283. <div class="v2p-topic-reply-member">
  3284. <a href="${replyMember.memberLink}" target="_blank">
  3285. <img class="v2p-topic-reply-avatar" src="${replyMember.memberAvatar}">
  3286. <span>${replyMember.memberName}</span>
  3287. </a>\uFF1A
  3288. </div>
  3289. <div class="v2p-topic-reply-content">${escapeHTML(
  3290. replyMember.content
  3291. )}</div>
  3292. </div>
  3293. </div>
  3294. `)
  3295. );
  3296. }
  3297. }
  3298. $t2.append($clonedCell);
  3299. });
  3300. } else {
  3301. $t2.append($("<div>\u6682\u65E0\u6700\u8FD1\u56DE\u590D</div>").css({ padding: "20px", textAlign: "center" }));
  3302. }
  3303. $content.append($t2);
  3304. }
  3305. {
  3306. const opCommentData = commentDataList.filter(
  3307. ({ memberName }) => memberName === topicOwnerName
  3308. );
  3309. if (opCommentData.length > 0) {
  3310. opCommentData.forEach(({ index, refMemberNames }) => {
  3311. const $clonedCell = $commentCells.eq(index).clone();
  3312. $clonedCell.find(".v2p-controls > a:has(.v2p-control-reply)").remove();
  3313. $clonedCell.find(".no").css("pointer-events", "none");
  3314. const firstRefMember = refMemberNames?.at(0);
  3315. if (firstRefMember) {
  3316. const replyMember = commentDataList.findLast(
  3317. (it, idx) => idx < index && it.memberName === firstRefMember
  3318. );
  3319. if (replyMember) {
  3320. $clonedCell.prepend(
  3321. $(`
  3322. <div class="v2p-topic-reply-ref">
  3323. <div class="v2p-topic-reply">
  3324. <div class="v2p-topic-reply-member">
  3325. <a href="${replyMember.memberLink}" target="_blank">
  3326. <img class="v2p-topic-reply-avatar" src="${replyMember.memberAvatar}">
  3327. <span>${replyMember.memberName}</span>
  3328. </a>\uFF1A
  3329. </div>
  3330. <div class="v2p-topic-reply-content">${escapeHTML(
  3331. replyMember.content
  3332. )}</div>
  3333. </div>
  3334. </div>
  3335. `)
  3336. );
  3337. }
  3338. }
  3339. $t3.append($clonedCell);
  3340. });
  3341. } else {
  3342. $t3.append($("<div>\u6682\u65E0\u9898\u4E3B\u56DE\u590D</div>").css({ padding: "20px", textAlign: "center" }));
  3343. }
  3344. $content.append($t3);
  3345. }
  3346. const $tabs = $(`
  3347. <div class="v2p-modal-comment-tabs">
  3348. <div data-tab-key="hot" class="v2p-tab-active">\u70ED\u95E8\u56DE\u590D</div>
  3349. <div data-tab-key="recent">\u6700\u8FD1\u56DE\u590D</div>
  3350. <div data-tab-key="op">\u9898\u4E3B\u56DE\u590D</div>
  3351. </div>
  3352. `);
  3353. $title.append($tabs);
  3354. $tabs.find("[data-tab-key]").on("click", (ev) => {
  3355. const $target = $(ev.currentTarget);
  3356. const { tabKey } = $target.data();
  3357. $target.addClass("v2p-tab-active").siblings().removeClass("v2p-tab-active");
  3358. $(`.v2p-modal-comments[data-tab-key="${tabKey}"]`).addClass("v2p-tab-content-active").siblings().removeClass("v2p-tab-content-active");
  3359. });
  3360. },
  3361. onOpen: ({ $content }) => {
  3362. $content.find('.cell[id^="r_"]').each((_, cellDom) => {
  3363. const storage = getStorageSync();
  3364. const options = storage["options" /* Options */];
  3365. if (options.replyContent.autoFold) {
  3366. processReplyContent($(cellDom));
  3367. }
  3368. });
  3369. }
  3370. });
  3371. $commentsBtn.on("click", () => {
  3372. model.open();
  3373. });
  3374. {
  3375. const $commentBoxCount = $commentBox.find(".cell:first-of-type > span.gray");
  3376. const countText = $commentBoxCount.text();
  3377. const newCountText = countText.substring(0, countText.indexOf("\u56DE\u590D") + 2);
  3378. const countTextSpan = `<span class="count-text">${newCountText}</span><span class="v2p-dot">\xB7</span>${popularCount} \u6761\u70ED\u95E8\u56DE\u590D`;
  3379. $commentBoxCount.empty().append(countTextSpan);
  3380. }
  3381. }
  3382. function processActions($cellDom, data) {
  3383. const $actions = $cellDom.find("> table > tbody > tr > td:last-of-type > .fr");
  3384. const $controls = $('<span class="v2p-controls">');
  3385. const $thankIcon = $(
  3386. `<span
  3387. class="v2p-control v2p-control-thank"
  3388. data-id="${data.id}"
  3389. data-member-name="${data.memberName}"
  3390. >
  3391. <i data-lucide="heart"></i>
  3392. </span>`
  3393. );
  3394. const thankArea = $actions.find("> .thank_area");
  3395. const thanked = thankArea.hasClass("thanked");
  3396. if (thanked) {
  3397. $thankIcon.addClass("v2p-thanked");
  3398. $controls.append($("<a>").append($thankIcon));
  3399. } else {
  3400. const thankEle = thankArea.find("> .thank");
  3401. const $hide = thankEle.eq(0).removeClass("thank");
  3402. const $thank = thankEle.eq(1).removeClass("thank");
  3403. $hide.html(
  3404. `<span class="v2p-control v2p-hover-btn v2p-control-hide"><i data-lucide="eye-off"></i></span>`
  3405. );
  3406. $thankIcon.addClass("v2p-hover-btn").replaceAll($thank);
  3407. $controls.append($hide).append($thankIcon);
  3408. }
  3409. const $reply = $actions.find("a:last-of-type");
  3410. $reply.find('> img[alt="Reply"]').replaceWith(
  3411. `<span class="v2p-control v2p-hover-btn v2p-control-reply"><i data-lucide="message-square"></i></span>`
  3412. );
  3413. $controls.append($reply);
  3414. thankArea.remove();
  3415. const floorNum = $actions.find(".no").clone();
  3416. $reply.on("click", () => {
  3417. const replyVal = $replyTextArea.val();
  3418. if (typeof replyVal === "string" && replyVal.length > 0) {
  3419. const floor = floorNum.text();
  3420. const replyComment = commentDataList.find((it) => it.floor === floor);
  3421. if (replyComment) {
  3422. const replyMemberName = replyComment.memberName;
  3423. const moreThanOneReply = commentDataList.findIndex(
  3424. (it) => it.memberName === replyMemberName && it.floor !== floor
  3425. ) !== -1;
  3426. if (moreThanOneReply) {
  3427. insertTextToReplyInput(`#${floor} `);
  3428. } else {
  3429. const $page = $(".v2p-paging").eq(0).find(".page_normal, .page_current");
  3430. if ($page.length > 1) {
  3431. const onLastPage = $page.last().hasClass("page_current");
  3432. if (!onLastPage) {
  3433. insertTextToReplyInput(`#${floor} `);
  3434. }
  3435. }
  3436. }
  3437. }
  3438. }
  3439. });
  3440. $actions.empty().append($controls, floorNum);
  3441. }
  3442. async function handlingComments() {
  3443. const storage = getStorageSync();
  3444. const tagData = storage["member-tag" /* MemberTag */];
  3445. const options = storage["options" /* Options */];
  3446. if (options.replyContent.hideReplyTime) {
  3447. $(".cell .ago").addClass("v2p-auto-hide");
  3448. }
  3449. if (options.reply.preload !== "off") {
  3450. const $paging = $(".v2p-paging");
  3451. if ($paging.length > 0) {
  3452. const $pagingTop = $paging.eq(0);
  3453. const $pagingBottom = $paging.eq(1);
  3454. const $pageNormal = $paging.find(".page_normal");
  3455. const $pagingTopNormal = $pagingTop.find(".page_normal");
  3456. const toastControl = createToast({ message: "\u6B63\u5728\u9884\u52A0\u8F7D\u56DE\u590D\uFF0C\u8BF7\u7A0D\u5019...", duration: 0 });
  3457. try {
  3458. const $pagingBottomNormal = $pagingBottom.find(".page_normal");
  3459. const $pageCurrent = $pagingTop.find(".page_current");
  3460. const currentPage = $pageCurrent.text();
  3461. if (currentPage === "1") {
  3462. const pages = [];
  3463. $pagingTopNormal.each((i, ele) => {
  3464. if (i <= 1) {
  3465. if (ele.textContent) {
  3466. ele.classList.add("page_current");
  3467. ele.classList.remove("page_normal");
  3468. $pagingBottomNormal.eq(i).addClass("page_current").removeClass("page_normal");
  3469. pages.push(ele.textContent);
  3470. }
  3471. }
  3472. });
  3473. if (pages.length > 0) {
  3474. const pagesText = await Promise.all(
  3475. pages.map((p) => crawalTopicPage(window.location.pathname, p))
  3476. );
  3477. pagesText.map((pageText) => {
  3478. $pagingBottom.before($(pageText).find('.cell[id^="r_"]'));
  3479. });
  3480. }
  3481. updateCommentCells();
  3482. }
  3483. toastControl.clear();
  3484. } catch (err) {
  3485. if (err instanceof Error) {
  3486. console.error(`\u52A0\u8F7D\u591A\u9875\u56DE\u590D\u51FA\u9519\uFF1A${err.message}`);
  3487. }
  3488. createToast({ message: "\u274C \u52A0\u8F7D\u591A\u9875\u56DE\u590D\u5931\u8D25" });
  3489. $pageNormal.removeClass("page_current").addClass("page_normal");
  3490. }
  3491. }
  3492. }
  3493. const canHideRefName = options.nestedReply.display === "indent" && !!options.replyContent.hideRefName;
  3494. commentDataList = $commentTableRows.map((idx, tr) => {
  3495. const id = $commentCells[idx].id;
  3496. const $tr = $(tr);
  3497. const $td = $tr.find("> td:nth-child(3)");
  3498. const thanked = $tr.find("> td:last-of-type > .fr").find("> .thank_area").hasClass("thanked");
  3499. const $member = $td.find("> strong > a");
  3500. const memberName = $member.text();
  3501. const memberLink = $member.prop("href");
  3502. const memberAvatar = $tr.find(".avatar").prop("src");
  3503. const $content = $td.find("> .reply_content");
  3504. const content = $content.text();
  3505. const likes = Number($td.find("span.small").text());
  3506. const floor = $td.find("span.no").text();
  3507. const memberNameMatches = Array.from(content.matchAll(/@([a-zA-Z0-9]+)/g));
  3508. const refMemberNames = memberNameMatches.length > 0 ? memberNameMatches.map(([, name]) => {
  3509. return name;
  3510. }) : void 0;
  3511. const floorNumberMatches = Array.from(content.matchAll(/#(\d+)/g));
  3512. const refFloors = floorNumberMatches.length > 0 ? floorNumberMatches.map(([, floor2]) => {
  3513. return floor2;
  3514. }) : void 0;
  3515. let contentHtml = void 0;
  3516. if (refMemberNames) {
  3517. if (canHideRefName) {
  3518. if (refMemberNames.length === 1) {
  3519. contentHtml = $content.html();
  3520. const pattern = /(@<a href="\/member\/\w+">[\w\s]+<\/a>)\s+/g;
  3521. const replacement = '<span class="v2p-member-ref">$1</span> ';
  3522. contentHtml = contentHtml.replace(pattern, replacement);
  3523. }
  3524. }
  3525. }
  3526. return {
  3527. id,
  3528. memberName,
  3529. memberLink,
  3530. memberAvatar,
  3531. content,
  3532. contentHtml,
  3533. likes,
  3534. floor,
  3535. index: idx,
  3536. refMemberNames,
  3537. refFloors,
  3538. thanked
  3539. };
  3540. }).get();
  3541. {
  3542. const membersHasSetTags = /* @__PURE__ */ new Set();
  3543. $commentCells.each((i, cellDom) => {
  3544. const currentComment = commentDataList.at(i);
  3545. if (currentComment?.id !== cellDom.id) {
  3546. return;
  3547. }
  3548. const $cellDom = $(cellDom);
  3549. const { memberName, thanked } = currentComment;
  3550. processAvatar({
  3551. $trigger: $cellDom.find(".avatar"),
  3552. popupControl,
  3553. commentData: currentComment
  3554. });
  3555. if (memberName === loginName) {
  3556. $cellDom.find(".badges").append(`<div class="badge ${memberName === topicOwnerName ? "mod" : "you"}">YOU</div>`);
  3557. }
  3558. const $likesBox = $cellDom.find(".small.fade").addClass("v2p-likes-box");
  3559. $likesBox.find('img[alt="\u2764\uFE0F"]').replaceWith('<span class="v2p-icon-heart"><i data-lucide="heart"></i></span>');
  3560. if (thanked) {
  3561. $likesBox.addClass("v2p-thanked");
  3562. }
  3563. if (tagData && Reflect.has(tagData, memberName) && !membersHasSetTags.has(memberName)) {
  3564. updateMemberTag(memberName, tagData[memberName].tags, options);
  3565. membersHasSetTags.add(memberName);
  3566. }
  3567. processActions($cellDom, currentComment);
  3568. if (canHideRefName) {
  3569. if (currentComment.contentHtml) {
  3570. $cellDom.find(".reply_content").html(currentComment.contentHtml);
  3571. }
  3572. }
  3573. });
  3574. updateCommentCells();
  3575. handlingFilteredComments();
  3576. $(".v2p-control-thank").on("click", (ev) => {
  3577. ev.stopPropagation();
  3578. const id = ev.currentTarget.dataset.id;
  3579. const memberName = ev.currentTarget.dataset.memberName;
  3580. if (typeof id === "string" && typeof memberName === "string") {
  3581. if (confirm(`\u786E\u8BA4\u82B1\u8D39 10 \u4E2A\u94DC\u5E01\u5411 @${memberName} \u7684\u8FD9\u6761\u56DE\u590D\u53D1\u9001\u611F\u8C22\uFF1F`)) {
  3582. const replyId = id.split("_").at(1);
  3583. if (replyId) {
  3584. void thankReply({
  3585. replyId,
  3586. onSuccess: () => {
  3587. const $cell = $(`.cell[id="r_${replyId}"]`);
  3588. const $tableInCell = $cell.find("> table");
  3589. const $likesBox = $tableInCell.find(".v2p-likes-box");
  3590. const $firstLikesBox = $likesBox.eq(0);
  3591. const likes = Number($firstLikesBox.text());
  3592. const $clonedIconHeart = $firstLikesBox.find(".v2p-icon-heart").clone();
  3593. if (likes > 0) {
  3594. $likesBox.addClass("v2p-thanked").empty().append($clonedIconHeart, ` ${likes + 1}`);
  3595. } else {
  3596. $(`
  3597. <span class="small v2p-likes-box v2p-thanked" style="position:relative;top:-1px;">
  3598. &nbsp;&nbsp;<span class="v2p-icon-heart"><i data-lucide="heart"></i></span>1
  3599. </span>
  3600. `).insertAfter($tableInCell.find(".ago"));
  3601. loadIcons();
  3602. }
  3603. const $thankAction = $tableInCell.find(".v2p-control-thank");
  3604. $thankAction.addClass("v2p-thanked").off("click");
  3605. $thankAction.siblings().has(".v2p-control-hide").hide();
  3606. },
  3607. onFail: () => {
  3608. createToast({ message: "\u274C \u611F\u8C22\u56DE\u590D\u5931\u8D25" });
  3609. }
  3610. });
  3611. }
  3612. }
  3613. }
  3614. });
  3615. }
  3616. {
  3617. const display = options.nestedReply.display;
  3618. if (display !== "off") {
  3619. $commentCells.each((i, cellDom) => {
  3620. const $cellDom = $(cellDom);
  3621. const dataFromIndex = commentDataList.at(i);
  3622. if (options.replyContent.autoFold) {
  3623. processReplyContent($cellDom);
  3624. }
  3625. const currentComment = dataFromIndex?.id === cellDom.id ? dataFromIndex : commentDataList.find((data) => data.id === cellDom.id);
  3626. if (currentComment) {
  3627. const { refMemberNames, refFloors } = currentComment;
  3628. if (!refMemberNames || refMemberNames.length === 0) {
  3629. return;
  3630. }
  3631. if (options.nestedReply.multipleInsideOne === "off" && refMemberNames.length > 1) {
  3632. return;
  3633. }
  3634. for (const refName of refMemberNames) {
  3635. for (let j = i - 1; j >= 0; j--) {
  3636. const { memberName: compareName, floor: eachFloor } = commentDataList.at(j) || {};
  3637. if (compareName === refName) {
  3638. let refCommentIdx = j;
  3639. const firstRefFloor = refFloors?.at(0);
  3640. if (firstRefFloor && firstRefFloor !== eachFloor) {
  3641. const targetIdx = commentDataList.slice(0, j).findIndex(
  3642. (data) => data.floor === firstRefFloor && data.memberName === refName
  3643. );
  3644. if (targetIdx >= 0) {
  3645. refCommentIdx = targetIdx;
  3646. }
  3647. }
  3648. if (display === "indent") {
  3649. cellDom.classList.add("v2p-indent");
  3650. }
  3651. $commentCells.eq(refCommentIdx).append(cellDom);
  3652. return;
  3653. }
  3654. }
  3655. }
  3656. }
  3657. });
  3658. }
  3659. }
  3660. {
  3661. const $opAvatar = $topicHeader.find(".avatar");
  3662. const $opName = $topicHeader.find('.gray a[href^="/member"]');
  3663. const memberName = $opAvatar.prop("alt");
  3664. const memberAvatar = $opAvatar.prop("src");
  3665. const memberLink = $topicHeader.find(".fr > a").prop("href");
  3666. if (typeof memberName === "string" && typeof memberAvatar === "string" && typeof memberLink === "string") {
  3667. processAvatar({
  3668. $trigger: $opAvatar,
  3669. popupControl,
  3670. commentData: { memberName, memberAvatar, memberLink }
  3671. });
  3672. processAvatar({
  3673. $trigger: $opName,
  3674. popupControl,
  3675. commentData: { memberName, memberAvatar, memberLink },
  3676. shouldWrap: false
  3677. });
  3678. fetchUserInfo(memberName).then((memberData) => {
  3679. memberDataCache.set(memberName, memberData);
  3680. const diffInDays = (Date.now() / 1e3 - memberData.created) / (60 * 60 * 24);
  3681. if (diffInDays <= 30) {
  3682. $opName.append(
  3683. `<span class="v2p-register-days v2p-register-days-15">${diffInDays <= 15 ? "15" : "30"} \u5929\u5185\u6CE8\u518C</span>`
  3684. );
  3685. }
  3686. });
  3687. }
  3688. }
  3689. }
  3690. var commentDataList, popupControl;
  3691. var init_comment = __esm({
  3692. "src/contents/topic/comment.ts"() {
  3693. "use strict";
  3694. init_lucide();
  3695. init_model();
  3696. init_popup();
  3697. init_toast();
  3698. init_constants();
  3699. init_services();
  3700. init_utils();
  3701. init_globals();
  3702. init_helpers();
  3703. init_avatar();
  3704. init_content();
  3705. commentDataList = [];
  3706. popupControl = createPopup({
  3707. root: $wrapper,
  3708. triggerType: "hover",
  3709. placement: "right-start",
  3710. offsetOptions: { mainAxis: 8, crossAxis: -4 }
  3711. });
  3712. }
  3713. });
  3714.  
  3715. // src/contents/topic/layout.ts
  3716. function toggleTopicLayout() {
  3717. if ($wrapperContent.hasClass("v2p-content-layout")) {
  3718. switchToVerticalLayout();
  3719. } else {
  3720. switchToHorizontalLayout();
  3721. }
  3722. }
  3723. function handlingLayout() {
  3724. const storage = getStorageSync();
  3725. const options = storage["options" /* Options */];
  3726. if (options.reply.layout === "horizontal") {
  3727. switchToHorizontalLayout();
  3728. } else {
  3729. switchToVerticalLayout();
  3730. }
  3731. $layoutToggle.on("click", () => {
  3732. toggleTopicLayout();
  3733. });
  3734. $(".tools").prepend($layoutToggle);
  3735. }
  3736. var $layoutToggle, iconLayoutV, iconLayoutH, switchToHorizontalLayout, switchToVerticalLayout;
  3737. var init_layout = __esm({
  3738. "src/contents/topic/layout.ts"() {
  3739. "use strict";
  3740. init_lucide();
  3741. init_constants();
  3742. init_utils();
  3743. init_globals();
  3744. $layoutToggle = $('<span class="v2p-layout-toggle v2p-hover-btn">');
  3745. iconLayoutV = createElement$1(PanelTop);
  3746. iconLayoutV.setAttribute("width", "100%");
  3747. iconLayoutV.setAttribute("height", "100%");
  3748. iconLayoutH = createElement$1(PanelRight);
  3749. iconLayoutH.setAttribute("width", "100%");
  3750. iconLayoutH.setAttribute("height", "100%");
  3751. switchToHorizontalLayout = () => {
  3752. if (!$wrapperContent.hasClass("v2p-content-layout")) {
  3753. const $divider1 = $main.find("> .sep20:first-of-type");
  3754. const $leftGroup = $divider1.add($divider1.next(".box"));
  3755. const $leftSide = $('<div class="v2p-left-side">');
  3756. $leftGroup.wrapAll($leftSide);
  3757. const $content = $leftGroup.find("> .cell");
  3758. $content.add($content.nextAll(".subtle")).wrapAll('<div class="v2p-left-side-content">');
  3759. const $divider2 = $main.find(".sep20:nth-of-type(2)");
  3760. const $rightGroup = $divider2.add($divider2.nextAll());
  3761. $rightGroup.wrapAll('<div class="v2p-right-side">');
  3762. $wrapperContent.addClass("v2p-content-layout");
  3763. $main.addClass("v2p-horizontal-layout");
  3764. }
  3765. $layoutToggle.html(iconLayoutV);
  3766. $layoutToggle.attr("title", "\u5207\u6362\u4E3A\u5782\u76F4\u5E03\u5C40");
  3767. $(".v2p-reply-tool-layout").text("\u5207\u6362\u4E3A\u5782\u76F4\u5E03\u5C40");
  3768. };
  3769. switchToVerticalLayout = () => {
  3770. if ($wrapperContent.hasClass("v2p-content-layout")) {
  3771. $wrapperContent.removeClass("v2p-content-layout");
  3772. $main.removeClass("v2p-horizontal-layout");
  3773. $(".v2p-left-side-content").children().unwrap();
  3774. $(".v2p-left-side").children().unwrap();
  3775. $(".v2p-right-side").children().unwrap();
  3776. }
  3777. $layoutToggle.html(iconLayoutH);
  3778. $layoutToggle.attr("title", "\u5207\u6362\u4E3A\u6C34\u5E73\u5E03\u5C40");
  3779. $(".v2p-reply-tool-layout").text("\u5207\u6362\u4E3A\u6C34\u5E73\u5E03\u5C40");
  3780. };
  3781. }
  3782. });
  3783.  
  3784. // src/contents/topic/paging.ts
  3785. function handlingPaging() {
  3786. const $notCommentCells = $commentBox.find('> .cell:not([id^="r_"])');
  3787. if ($notCommentCells.length <= 1) {
  3788. return;
  3789. }
  3790. const pagingCells = $notCommentCells.slice(1).addClass("v2p-paging");
  3791. const pageBtns = pagingCells.find(".super.button");
  3792. pageBtns.eq(0).addClass("v2p-prev-btn");
  3793. pageBtns.eq(1).addClass("v2p-next-btn");
  3794. }
  3795. var init_paging = __esm({
  3796. "src/contents/topic/paging.ts"() {
  3797. "use strict";
  3798. init_globals();
  3799. }
  3800. });
  3801.  
  3802. // src/components/image-upload.ts
  3803. function bindImageUpload(props) {
  3804. const { $wrapper: $wrapper2, $input, insertText, replaceText } = props;
  3805. const $uploadBar = $(`<div class="v2p-reply-upload-bar">${uploadTip}</div>`);
  3806. const handleUploadImage = (file) => {
  3807. const placeholder = "[\u4E0A\u4F20\u56FE\u7247\u4E2D...]";
  3808. insertText(` ${placeholder} `);
  3809. $uploadBar.addClass("v2p-reply-upload-bar-disabled").text("\u6B63\u5728\u4E0A\u4F20\u56FE\u7247...");
  3810. uploadImage(file).then((imgLink) => {
  3811. replaceText(placeholder, imgLink);
  3812. }).catch(() => {
  3813. replaceText(placeholder, "");
  3814. window.alert("\u274C \u4E0A\u4F20\u56FE\u7247\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u53F0\u67E5\u770B\u539F\u56E0");
  3815. }).finally(() => {
  3816. $uploadBar.removeClass("v2p-reply-upload-bar-disabled").text(uploadTip);
  3817. });
  3818. };
  3819. const handleClickUploadImage = () => {
  3820. const imgInput = document.createElement("input");
  3821. imgInput.style.display = "none";
  3822. imgInput.type = "file";
  3823. imgInput.accept = "image/*";
  3824. imgInput.addEventListener("change", () => {
  3825. const selectedFile = imgInput.files?.[0];
  3826. if (selectedFile) {
  3827. handleUploadImage(selectedFile);
  3828. }
  3829. });
  3830. imgInput.click();
  3831. };
  3832. document.addEventListener("paste", (ev) => {
  3833. if (!(ev instanceof ClipboardEvent)) {
  3834. return;
  3835. }
  3836. if ($input && !$input.get(0)?.matches(":focus")) {
  3837. return;
  3838. }
  3839. const items = ev.clipboardData?.items;
  3840. if (!items) {
  3841. return;
  3842. }
  3843. const imageItem = Array.from(items).find((item) => item.type.includes("image"));
  3844. if (imageItem) {
  3845. const file = imageItem.getAsFile();
  3846. if (file) {
  3847. handleUploadImage(file);
  3848. }
  3849. }
  3850. });
  3851. $wrapper2.get(0)?.addEventListener("drop", (ev) => {
  3852. ev.preventDefault();
  3853. if (!(ev instanceof DragEvent)) {
  3854. return;
  3855. }
  3856. const file = ev.dataTransfer?.files[0];
  3857. if (file) {
  3858. handleUploadImage(file);
  3859. }
  3860. });
  3861. $(".flex-one-row:last-of-type > .gray").text("");
  3862. $uploadBar.on("click", () => {
  3863. if (!$uploadBar.hasClass("v2p-reply-upload-bar-disabled")) {
  3864. handleClickUploadImage();
  3865. }
  3866. });
  3867. $wrapper2.append($uploadBar);
  3868. return {
  3869. uploadBar: $uploadBar
  3870. };
  3871. }
  3872. var uploadTip;
  3873. var init_image_upload = __esm({
  3874. "src/components/image-upload.ts"() {
  3875. "use strict";
  3876. init_services();
  3877. uploadTip = "\u9009\u62E9\u3001\u7C98\u8D34\u3001\u62D6\u653E\u4E0A\u4F20\u56FE\u7247\u3002";
  3878. }
  3879. });
  3880.  
  3881. // src/contents/topic/reply.ts
  3882. function handlingReplyActions() {
  3883. const os = getOS();
  3884. const replyBtnText = `\u56DE\u590D<kbd>${os === "macos" ? "Cmd" : "Ctrl"}+Enter</kbd>`;
  3885. const $replyBtn = createButton({
  3886. children: replyBtnText,
  3887. type: "submit"
  3888. }).replaceAll($replyBox.find('input[type="submit"]'));
  3889. $replyForm.on("submit", () => {
  3890. const replyVal = $replyTextArea.val();
  3891. if (typeof replyVal === "string") {
  3892. $replyTextArea.val(transformEmoji(replyVal));
  3893. }
  3894. $replyBtn.text("\u63D0\u4EA4\u56DE\u590D\u4E2D...").prop("disabled", true);
  3895. setTimeout(() => {
  3896. $replyBtn.html(replyBtnText).prop("disabled", false);
  3897. }, 5e3);
  3898. });
  3899. document.addEventListener("keydown", (ev) => {
  3900. if (ev.key === "Enter" && (ev.ctrlKey || ev.metaKey)) {
  3901. ev.preventDefault();
  3902. $replyForm.trigger("submit");
  3903. }
  3904. });
  3905. {
  3906. const emoticonGroup = $('<div class="v2p-emoji-group">');
  3907. const emoticonList = $('<div class="v2p-emoji-list">');
  3908. const emoticonSpan = $('<span class="v2p-emoji">');
  3909. const groups = emoticons.map((emojiGroup) => {
  3910. const group = emoticonGroup.clone();
  3911. const list = emoticonList.clone();
  3912. group.append(`<div class="v2p-emoji-title">${emojiGroup.title}</div>`);
  3913. list.append(
  3914. emojiGroup.list.map((emoji) => {
  3915. const emoticon = emoticonSpan.clone();
  3916. if (emojiGroup.title === "\u6D41\u884C") {
  3917. const emojiLink = biliEmojiLink[emoji];
  3918. emoticon.html(`<img src="${emojiLink}" />`).prop("title", emoji);
  3919. } else {
  3920. emoticon.text(emoji);
  3921. }
  3922. emoticon.on("click", () => {
  3923. insertTextToReplyInput(emoji);
  3924. });
  3925. return emoticon;
  3926. })
  3927. );
  3928. group.append(list);
  3929. return group;
  3930. });
  3931. const emoticonsBox = $('<div class="v2p-emoticons-box">').append(groups);
  3932. const $emojiBtn = createButton({
  3933. children: '<span style="width:18px; height:18px;"><i data-lucide="smile"></i></span>'
  3934. }).insertAfter($replyBtn);
  3935. const $emojiContent = $('<div class="v2p-emoji-container">').append(emoticonsBox).appendTo($replyBox).on("click", () => {
  3936. focusReplyInput();
  3937. });
  3938. const keyupHandler = (ev) => {
  3939. if (ev.key === "Escape") {
  3940. ev.preventDefault();
  3941. emojiPopup.close();
  3942. }
  3943. };
  3944. $emojiBtn.on("click", () => {
  3945. focusReplyInput();
  3946. });
  3947. const emojiPopup = createPopup({
  3948. root: $replyBox,
  3949. trigger: $emojiBtn,
  3950. content: $emojiContent,
  3951. options: { placement: "right-end" },
  3952. onOpen: () => {
  3953. $(document.body).on("keydown", keyupHandler);
  3954. },
  3955. onClose: () => {
  3956. $(document.body).off("keydown", keyupHandler);
  3957. }
  3958. });
  3959. }
  3960. {
  3961. $replyBox.find("#undock-button, #undock-button + a").addClass("v2p-hover-btn").css("padding", "5px 4px");
  3962. }
  3963. }
  3964. function handleReply() {
  3965. $replyTextArea.attr("placeholder", "\u7559\u4E0B\u5BF9\u4ED6\u4EBA\u6709\u5E2E\u52A9\u7684\u56DE\u590D").wrap('<div class="v2p-reply-wrap">');
  3966. const $replyWrap = $(".v2p-reply-wrap");
  3967. const $replyPreview = $('<div class="v2p-reply-preview">');
  3968. $replyPreview.hide().insertAfter($replyWrap);
  3969. bindImageUpload({
  3970. $wrapper: $replyWrap,
  3971. $input: $replyTextArea,
  3972. insertText: (text) => {
  3973. insertTextToReplyInput(text);
  3974. },
  3975. replaceText: (find, replace) => {
  3976. const val = $replyTextArea.val();
  3977. if (typeof val === "string") {
  3978. const newVal = val.replace(find, replace);
  3979. $replyTextArea.val(newVal).trigger("focus");
  3980. }
  3981. }
  3982. });
  3983. {
  3984. const $replyTabs = $('<div class="v2p-reply-tabs">');
  3985. const $replyTabEdit = $('<div class="v2p-reply-tab active">\u7F16\u8F91</div>');
  3986. const $replyTabPreview = $('<div class="v2p-reply-tab">\u9884\u89C8</div>');
  3987. $replyTabEdit.on("click", () => {
  3988. $replyTabEdit.addClass("active");
  3989. $replyTabPreview.removeClass("active");
  3990. $replyWrap.show();
  3991. $replyPreview.hide();
  3992. }).appendTo($replyTabs);
  3993. let lastPreviewText = null;
  3994. $replyTabPreview.on("click", () => {
  3995. if (!$replyTabPreview.hasClass("active")) {
  3996. $replyTabPreview.addClass("active");
  3997. $replyTabEdit.removeClass("active");
  3998. const replyText = $replyTextArea.val();
  3999. if (typeof replyText === "string") {
  4000. $replyWrap.hide();
  4001. $replyPreview.show();
  4002. if (replyText.trim() === "") {
  4003. $replyPreview.html("\u6CA1\u6709\u53EF\u9884\u89C8\u7684\u5185\u5BB9");
  4004. } else {
  4005. const textToPreview = transformEmoji(replyText);
  4006. const handlePreview = async () => {
  4007. $replyPreview.html("\u6B63\u5728\u52A0\u8F7D\u9884\u89C8...");
  4008. try {
  4009. const renderedContent = await getPreviewContent({
  4010. text: textToPreview,
  4011. syntax: "default"
  4012. });
  4013. $replyPreview.html(renderedContent);
  4014. lastPreviewText = textToPreview;
  4015. } catch {
  4016. $replyPreview.html('\u9884\u89C8\u5931\u8D25\uFF0C<a class="v2p-preview-retry">\u70B9\u51FB\u91CD\u8BD5</a>\u3002');
  4017. $replyPreview.find(".v2p-preview-retry").on("click", () => {
  4018. void handlePreview();
  4019. });
  4020. }
  4021. };
  4022. if (replyText !== lastPreviewText) {
  4023. void handlePreview();
  4024. }
  4025. }
  4026. }
  4027. }
  4028. }).appendTo($replyTabs);
  4029. $replyBox.find("> .cell:first-of-type > div:first-of-type").replaceWith($replyTabs);
  4030. }
  4031. $(".flex-one-row:last-of-type > .gray").text("");
  4032. handlingReplyActions();
  4033. }
  4034. var init_reply = __esm({
  4035. "src/contents/topic/reply.ts"() {
  4036. "use strict";
  4037. init_button();
  4038. init_image_upload();
  4039. init_popup();
  4040. init_constants();
  4041. init_services();
  4042. init_utils();
  4043. init_globals();
  4044. init_helpers();
  4045. }
  4046. });
  4047.  
  4048. // src/contents/topic/tool.ts
  4049. function handlingTools() {
  4050. const storage = getStorageSync();
  4051. const options = storage["options" /* Options */];
  4052. const $tools = $(`
  4053. <div class="cell v2p-tools">
  4054. <span class="v2p-tool v2p-hover-btn v2p-tool-reply">
  4055. <span class="v2p-tool-icon"><i data-lucide="message-square-plus"></i></span>\u56DE\u590D\u4E3B\u9898
  4056. </span>
  4057. <span class="v2p-tool v2p-hover-btn v2p-tool-reading">
  4058. <span class="v2p-tool-icon"><i data-lucide="book-open-check"></i></span>\u7A0D\u540E\u9605\u8BFB
  4059. </span>
  4060. <span class="v2p-tool v2p-hover-btn v2p-tool-scroll-top">
  4061. <span class="v2p-tool-icon"><i data-lucide="chevrons-up"></i></span>\u56DE\u5230\u9876\u90E8
  4062. </span>
  4063. <span class="v2p-tool v2p-hover-btn v2p-tool-more">
  4064. <span class="v2p-tool-icon"><i data-lucide="package-plus"></i></span>\u66F4\u591A\u529F\u80FD
  4065. </span>
  4066. </div>
  4067. `);
  4068. $tools.find(".v2p-tool-reply").on("click", () => {
  4069. $replyTextArea.trigger("focus");
  4070. });
  4071. $tools.find(".v2p-tool-reading").on("click", () => {
  4072. void addToReadingList({
  4073. url: window.location.href,
  4074. title: document.title.replace(" - V2EX", ""),
  4075. content: String($('head meta[property="og:description"]').prop("content"))
  4076. });
  4077. });
  4078. $tools.find(".v2p-tool-scroll-top").on("click", () => {
  4079. window.scrollTo({ top: 0, behavior: "smooth" });
  4080. });
  4081. {
  4082. const $moreTool = $tools.find(".v2p-tool-more");
  4083. const $toolContent = $(`
  4084. <div class="v2p-reply-tool-content">
  4085. <div class="v2p-reply-tool v2p-reply-tool-decode">\u89E3\u6790\u672C\u9875 Base64</div>
  4086. <div class="v2p-reply-tool v2p-reply-tool-encode">\u6587\u672C\u8F6C Base64</div>
  4087. <div class="v2p-reply-tool v2p-reply-tool-share">\u751F\u6210\u5206\u4EAB\u56FE\u7247</div>
  4088. </div>
  4089. `);
  4090. const toolsPopup = createPopup({
  4091. root: $replyBox,
  4092. trigger: $moreTool,
  4093. content: $toolContent,
  4094. offsetOptions: { mainAxis: 5, crossAxis: -5 }
  4095. });
  4096. $toolContent.find(".v2p-reply-tool-decode").on("click", () => {
  4097. decodeBase64TopicPage();
  4098. });
  4099. $toolContent.find(".v2p-reply-tool-encode").on("click", () => {
  4100. focusReplyInput();
  4101. toolsPopup.close();
  4102. setTimeout(() => {
  4103. 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");
  4104. if (inputText) {
  4105. let encodedText;
  4106. try {
  4107. encodedText = window.btoa(encodeURIComponent(inputText));
  4108. } catch (err) {
  4109. const errorTip = "\u8BE5\u6587\u672C\u65E0\u6CD5\u7F16\u7801\u4E3A Base64";
  4110. console.error(err, `${errorTip}\uFF0C\u53EF\u80FD\u7684\u9519\u8BEF\u539F\u56E0\uFF1A\u6587\u672C\u5305\u542B\u4E2D\u6587\u3002`);
  4111. createToast({ message: errorTip });
  4112. }
  4113. if (encodedText) {
  4114. insertTextToReplyInput(encodedText);
  4115. }
  4116. }
  4117. });
  4118. });
  4119. $toolContent.find(".v2p-reply-tool-share").on("click", () => {
  4120. const splits = window.location.pathname.split("/");
  4121. const topicId = splits.at(-1);
  4122. if (topicId && splits.at(-2) === "t") {
  4123. window.open(`${"https://v2p.app" /* Home */}/share/${topicId}`, "_blank");
  4124. }
  4125. });
  4126. const canHideRefName = options.nestedReply.display === "indent" && !!options.replyContent.hideRefName;
  4127. if (canHideRefName) {
  4128. let isHidden = options.replyContent.hideRefName;
  4129. const $toolToggleDisplay = $('<div class="v2p-reply-tool">\u663E\u793A @ \u7528\u6237\u540D</div>');
  4130. $toolToggleDisplay.on("click", () => {
  4131. if (isHidden) {
  4132. isHidden = false;
  4133. $toolToggleDisplay.text("\u9690\u85CF @ \u7528\u6237\u540D");
  4134. $(".v2p-member-ref").addClass("v2p-member-ref-show");
  4135. } else {
  4136. isHidden = true;
  4137. $toolToggleDisplay.text("\u663E\u793A @ \u7528\u6237\u540D");
  4138. $(".v2p-member-ref").removeClass("v2p-member-ref-show");
  4139. }
  4140. });
  4141. $toolContent.prepend($toolToggleDisplay);
  4142. }
  4143. const $toolToggleLayout = $(
  4144. `
  4145. <div class="v2p-reply-tool v2p-reply-tool-layout">
  4146. ${options.reply.layout === "horizontal" ? "\u5207\u6362\u4E3A\u5782\u76F4\u5E03\u5C40" : "\u5207\u6362\u4E3A\u6C34\u5E73\u5E03\u5C40"}
  4147. </div>
  4148. `
  4149. );
  4150. $toolToggleLayout.on("click", () => {
  4151. toggleTopicLayout();
  4152. });
  4153. $toolContent.prepend($toolToggleLayout);
  4154. }
  4155. $('#Rightbar > .box:has("#member-activity")').addClass("v2p-tool-box").append($tools);
  4156. loadIcons();
  4157. }
  4158. var init_tool = __esm({
  4159. "src/contents/topic/tool.ts"() {
  4160. "use strict";
  4161. init_popup();
  4162. init_toast();
  4163. init_constants();
  4164. init_utils();
  4165. init_globals();
  4166. init_helpers();
  4167. init_layout();
  4168. }
  4169. });
  4170.  
  4171. // src/contents/topic/index.ts
  4172. var topic_exports = {};
  4173. var init_topic = __esm({
  4174. "src/contents/topic/index.ts"() {
  4175. "use strict";
  4176. init_utils();
  4177. init_globals();
  4178. init_helpers();
  4179. init_comment();
  4180. init_content();
  4181. init_layout();
  4182. init_paging();
  4183. init_reply();
  4184. init_tool();
  4185. void (async () => {
  4186. await getStorage();
  4187. handlingLayout();
  4188. $commentTableRows.find("> td:nth-child(3) > strong > a").prop("target", "_blank");
  4189. handlingTools();
  4190. {
  4191. $(document).on("keydown", (ev) => {
  4192. if (!ev.isDefaultPrevented()) {
  4193. if (ev.key === "Escape") {
  4194. const $replyContent = $("#reply_content");
  4195. if ($replyBox.hasClass("reply-box-sticky")) {
  4196. $replyBox.removeClass("reply-box-sticky");
  4197. $("#undock-button").css("display", "none");
  4198. }
  4199. $replyContent.trigger("blur");
  4200. }
  4201. }
  4202. });
  4203. }
  4204. handlingContent();
  4205. if (document.referrer !== "") {
  4206. if (document.referrer.includes(document.location.pathname)) {
  4207. const url = new URL(document.location.href);
  4208. const page = url.searchParams.get("p");
  4209. if (page && page !== "1") {
  4210. document.querySelector(".topic_buttons")?.scrollIntoView({ behavior: "smooth" });
  4211. }
  4212. }
  4213. }
  4214. handlingPaging();
  4215. await handlingComments();
  4216. handleReply();
  4217. loadIcons();
  4218. })();
  4219. }
  4220. });
  4221.  
  4222. // src/contents/write/write.ts
  4223. function handlingWrite() {
  4224. bindImageUpload({
  4225. $wrapper: $("#workspace"),
  4226. insertText: (text) => {
  4227. postTask(`editor.getDoc().replaceRange("${text}", editor.getCursor())`);
  4228. },
  4229. replaceText: (find, replace) => {
  4230. if (replace) {
  4231. const mode = $("input[name=syntax]:checked").val();
  4232. if (mode === "markdown") {
  4233. replace = `![](${replace})`;
  4234. }
  4235. }
  4236. postTask(`
  4237. editor.setValue(editor.getValue().replace("${find}", "${replace}"));
  4238. const doc = editor.getDoc();
  4239. const lastLine = doc.lastLine();
  4240. const lastChar = doc.getLine(lastLine).length;
  4241. doc.setCursor({ line: doc.lastLine(), ch: lastChar });
  4242. `);
  4243. }
  4244. });
  4245. }
  4246. var init_write = __esm({
  4247. "src/contents/write/write.ts"() {
  4248. "use strict";
  4249. init_image_upload();
  4250. init_helpers();
  4251. }
  4252. });
  4253.  
  4254. // src/contents/write/index.ts
  4255. var write_exports = {};
  4256. var init_write2 = __esm({
  4257. "src/contents/write/index.ts"() {
  4258. "use strict";
  4259. init_helpers();
  4260. init_write();
  4261. handlingWrite();
  4262. loadIcons();
  4263. }
  4264. });
  4265.  
  4266. // node_modules/.pnpm/webext-patterns@1.3.0/node_modules/webext-patterns/index.js
  4267. var patternValidationRegex = /^(https?|wss?|file|ftp|\*):\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^file:\/\/\/.*$|^resource:\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^about:/;
  4268. var isFirefox = typeof navigator === "object" && navigator.userAgent.includes("Firefox/");
  4269. var allStarsRegex = isFirefox ? /^(https?|wss?):[/][/][^/]+([/].*)?$/ : /^https?:[/][/][^/]+([/].*)?$/;
  4270. var allUrlsRegex = /^(https?|file|ftp):[/]+/;
  4271. function getRawPatternRegex(matchPattern) {
  4272. if (!patternValidationRegex.test(matchPattern)) {
  4273. throw new Error(matchPattern + " is an invalid pattern, it must match " + String(patternValidationRegex));
  4274. }
  4275. let [, protocol, host, pathname] = matchPattern.split(/(^[^:]+:[/][/])([^/]+)?/);
  4276. protocol = protocol.replace("*", isFirefox ? "(https?|wss?)" : "https?").replace(/[/]/g, "[/]");
  4277. host = (host !== null && host !== void 0 ? host : "").replace(/^[*][.]/, "([^/]+.)*").replace(/^[*]$/, "[^/]+").replace(/[.]/g, "[.]").replace(/[*]$/g, "[^.]+");
  4278. pathname = pathname.replace(/[/]/g, "[/]").replace(/[.]/g, "[.]").replace(/[*]/g, ".*");
  4279. return "^" + protocol + host + "(" + pathname + ")?$";
  4280. }
  4281. function patternToRegex(...matchPatterns) {
  4282. if (matchPatterns.length === 0) {
  4283. return /$./;
  4284. }
  4285. if (matchPatterns.includes("<all_urls>")) {
  4286. return allUrlsRegex;
  4287. }
  4288. if (matchPatterns.includes("*://*/*")) {
  4289. return allStarsRegex;
  4290. }
  4291. return new RegExp(matchPatterns.map((x) => getRawPatternRegex(x)).join("|"));
  4292. }
  4293.  
  4294. // src/user-scripts/style.ts
  4295. 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-column-gap: 25px;--v2p-layout-row-gap: 20px}: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%);--v2p-color-error: #ef4444;--v2p-color-bg-error: #fee2e2;--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, #f1f5f9);background-color:var(--v2p-color-main-700, #334155)}:root body img::selection{background-color:var(--v2p-color-main-500, #64748b)}:root body.v2p-theme-dark,:root[data-darkreader-scheme=dark] body{--v2p-color-main-50: unset;--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-100);--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, #1c2128);background-color:var(--v2p-color-foreground, #adbac7)}:root body.v2p-theme-dark img::selection,:root[data-darkreader-scheme=dark] body img::selection{background-color:var(--v2p-color-foreground, #adbac7)}@supports selector(:has(*)){:root body:has(#Wrapper.Night){--v2p-color-main-50: unset;--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-100);--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, #1c2128);background-color:var(--v2p-color-foreground, #adbac7)}:root body:has(#Wrapper.Night) img::selection{background-color:var(--v2p-color-foreground, #adbac7)}}@supports not selector(:has(*)){:root #Wrapper.Night{--v2p-color-main-50: unset;--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-100);--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, #1c2128);background-color:var(--v2p-color-foreground, #adbac7)}:root #Wrapper.Night img::selection{background-color:var(--v2p-color-foreground, #adbac7)}}
  4296. :root{color-scheme:light}:root:has(#Wrapper.Night){color-scheme:dark}:root:has(#Wrapper.Night) #Top{background-color:rgba(0,0,0,0)}:root:has(#Wrapper.Night) #search-container::before{background-image:url("/static/img/search_icon_light.png")}: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-column-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-column-gap)}#Navcol .nav_item:hover{background-color:var(--box-background-hover-color)}#Navcol .nav_item_current:hover{color:var(--box-foreground-color);background-color:var(--box-background-color)}#Rightcol #page-outline-title{background-color:var(--v2p-color-main-200)}#Rightcol .page-outline-item:hover{background-color:var(--box-background-hover-color)}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-100)}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{padding:6px 8px;color:var(--v2p-color-bg-content);font-size:13px;background:var(--v2p-color-accent-400);border-radius:5px;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;position:relative;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"] .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,.v2p-reply-preview) a[href^=http]{text-decoration:underline 1.5px;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,.v2p-reply-preview) 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,.v2p-reply-preview) a[href*="v2ex.com/t"],body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons,.v2p-reply-preview) a[href^="/t"],body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons,.v2p-reply-preview) a[href^="/go"]{text-decoration:underline 1.5px;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,.v2p-reply-preview) a[href*="v2ex.com/t"]:hover,body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons,.v2p-reply-preview) a[href^="/t"]:hover,body :is(.topic_content,.reply_content,.v2p-topic-preview-content,.v2p-topic-preview-addons,.v2p-reply-preview) a[href^="/go"]:hover{color:var(--v2p-color-accent-500);background-color:var(--v2p-color-accent-50)}body .CodeMirror .CodeMirror-focused.cm-s-one-dark{background-color:rgba(0,0,0,0)}body .CodeMirror .CodeMirror-gutters{padding-right:5px;background-color:var(--v2p-color-bg-input);border-right:1px solid var(--v2p-color-main-300)}body .CodeMirror .CodeMirror-linenumber{color:var(--v2p-color-main-400)}body .cm-s-one-dark{background-color:var(--v2p-color-bg-input)}body #workspace{overflow:hidden;border:1px solid var(--button-border-color);border-radius:8px}body .select2-container{width:200px !important}body .select2-container .select2-selection{background-color:var(--v2p-color-background);border:1px solid var(--v2p-color-border)}body .select2-container .select2-selection .select2-selection__rendered,body .select2-container .select2-selection .select2-selection__placeholder{color:var(--v2p-color-foreground)}body .select2-container .select2-dropdown{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);transform:translateY(5px)}body .select2-container .select2-dropdown .select2-search{padding:5px}body .select2-container .select2-dropdown .select2-search .select2-search__field{padding:6px 4px;background-color:rgba(0,0,0,0);border:1px solid var(--v2p-color-main-200);border-radius:4px}body .select2-container .select2-dropdown .select2-search .select2-search__field:focus-visible{border-color:var(--v2p-color-main-400);outline:none}body .select2-container .select2-dropdown .select2-results>.select2-results__options{padding:5px}body .select2-container .select2-dropdown .select2-container--default .select2-results__option--selected{color:currentColor;background-color:var(--v2p-color-accent-100)}body .select2-container .select2-results__option{border-radius:4px}body .select2-container .select2-results__option--highlighted.select2-results__option--selectable{color:currentColor;background-color:var(--v2p-color-main-200)}body .select2-container .select2-results__option--selected{color:currentColor !important;background-color:var(--v2p-color-accent-100) !important}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}body .onoffswitch label .frame::before{color:#fff;background-color:var(--v2p-color-accent-400)}body .onoffswitch label .frame::after{color:var(--v2p-color-main-100);background-color:var(--v2p-color-main-400)}body select{color:var(--v2p-color-foreground);background-color:var(--v2p-color-background);border:1px solid var(--v2p-color-border);border-radius:4px;padding:4px 6px}.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;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)}#Top .tools *{margin-left:0}#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 .box:has(form[action="/write"]) .cell:nth-child(1),#Main .box:has(form[action="/write"]) .cell:nth-child(2){border:none}#Main .box:has(form[action="/write"]) .cell:has(#syntax-selector){padding:8px 0 !important}#Main .box:has(form[action="/write"]) .cell:has(#syntax-selector) .tab-alt-container{gap:0 8px}#Main .box:has(form[action="/write"]) .cell:has(#syntax-selector) .tab-alt{padding:4px 2px;border-bottom-width:2px;transition:none}#Main .box:has(form[action="/write"]) .cell:has(#syntax-selector) .tab-alt:not(.active):hover{border-color:rgba(0,0,0,0)}#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-main-400)}#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,#Main .v2p-modal-content>.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] .ago{color:var(--v2p-color-main-400);font-size:12px;white-space:nowrap}#Main .cell[id^=r] .reply_content{padding-bottom:10px;line-height:1.5}#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}#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{font-size:15px;line-height:1.6;color:currentColor}#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-main-400)}#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)}
  4297. \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[id^=r] .v2p-auto-hide{display:inline-flex;width:0;overflow:hidden}#Main #reply-box .v2p-reply-preview{font-size:15px;line-height:1.6}#Main .cell:hover .v2p-topic-preview-btn,#Rightbar .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)}#Rightbar .v2p-topic-preview-btn{position:absolute;right:0;bottom:0;height:20px;font-size:12px;box-shadow:0 0 0 3px var(--v2p-color-bg-content)}.v2p-tool-box{position:sticky;top:var(--v2p-layout-row-gap);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{font-size:15px;line-height:1.6;padding:25px}.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 1.5px;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-modal-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-modal-main{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:800px;height:100%;margin:0 auto;overflow:hidden;background-color:var(--v2p-color-bg-content);border-radius:var(--box-border-radius)}.v2p-modal-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-modal-title{padding:2px 0;overflow:hidden;font-weight:bold;font-size:16px;white-space:nowrap;text-overflow:ellipsis}.v2p-modal-actions{display:flex;gap:0 10px;align-items:center;margin-left:auto}.v2p-modal-content{position:relative;flex:1;overflow-y:auto}.v2p-modal-loading{display:flex;align-items:center;justify-content:center;padding:50px 0;color:currentColor}.v2p-modal-loading .v2p-icon-loading{position:relative;right:-13px;width:50px}.v2p-modal-comments{position:absolute;inset:0;padding:0 20px;overflow-y:auto;visibility:hidden}.v2p-modal-comments.v2p-tab-content-active{z-index:20;visibility:visible}.v2p-modal-comment-tabs{display:flex;gap:4px;align-items:center;padding:4px 5px;font-weight:normal;font-size:14px;background-color:var(--button-background-color);border-radius:4px}.v2p-modal-comment-tabs>[data-tab-key]{padding:4px 5px;border-radius:4px;cursor:pointer}.v2p-modal-comment-tabs>[data-tab-key]:hover{background-color:var(--v2p-color-main-200)}.v2p-modal-comment-tabs>[data-tab-key].v2p-tab-active{color:var(--v2p-color-foreground);background-color:var(--v2p-color-accent-100)}.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-auto-hide{width:auto}#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-flex;margin:0 0 2px;padding:0 3px;font-size:12px;background-color:var(--v2p-color-main-200);border-radius:3px;cursor:pointer}.v2p-reply-tags-inline{max-width:300px;margin:0 5px 0 0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.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:5px;font-size:20px}.v2p-emoji{height:20px;padding:3px;line-height:20px;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 .reply_content a,.v2p-reply-content.v2p-collapsed .reply_content .embedded_video{pointer-events:none}.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 20px;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-tabs{display:flex;gap:0 6px;align-items:center;font-size:14px}.v2p-reply-tabs .v2p-reply-tab{padding:2px 3px;cursor:pointer}.v2p-reply-tabs .v2p-reply-tab.active{text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:4px;text-decoration-color:var(--v2p-color-main-500)}.v2p-reply-tool-content{padding:5px;font-size:12px;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)}.v2p-preview-retry{text-decoration:underline 1px;text-underline-offset:var(--v2p-underline-offset);cursor:pointer}.v2p-member-ref{display:none}.v2p-member-ref.v2p-member-ref-show{display:inline}.v2p-layout-toggle{display:inline-block;width:18px;height:18px;padding:4px 2px;color:var(--v2p-color-main-500)}.v2p-content-layout.v2p-content-layout{max-width:2000px}.v2p-content-layout.v2p-content-layout .v2p-horizontal-layout{display:flex;flex-wrap:wrap;gap:var(--v2p-layout-column-gap)}.v2p-left-side{flex:1}.v2p-left-side>.box{position:sticky;top:var(--v2p-layout-row-gap);display:flex;flex-direction:column;max-height:calc(100vh - 2*var(--v2p-layout-row-gap))}.v2p-left-side>.box>.header{flex-shrink:0}.v2p-left-side .v2p-left-side-content{flex:1;overflow:auto}.v2p-right-side{flex:1}.v2p-register-days{display:inline-flex;align-items:center;margin-left:2px;padding:0 2px;color:var(--v2p-color-orange-400);background-color:var(--v2p-color-orange-100);border-radius:2px}
  4298. `;
  4299.  
  4300. // src/user-scripts/index.ts
  4301. if (typeof window.GM_addStyle !== "undefined") {
  4302. window.GM_addStyle(style);
  4303. } else {
  4304. document.addEventListener("DOMContentLoaded", () => {
  4305. $(`<style type='text/css'>${style}</style>`).appendTo("head");
  4306. });
  4307. }
  4308. document.addEventListener("DOMContentLoaded", () => {
  4309. const commonRegex = patternToRegex(
  4310. "https://v2ex.com/*",
  4311. "https://www.v2ex.com/*",
  4312. "https://cn.v2ex.com/*",
  4313. "https://global.v2ex.com/*",
  4314. "https://fast.v2ex.com/*",
  4315. "https://hk.v2ex.com/*"
  4316. );
  4317. const topicRegex = patternToRegex(
  4318. "https://v2ex.com/t/*",
  4319. "https://www.v2ex.com/t/*",
  4320. "https://cn.v2ex.com/t/*",
  4321. "https://global.v2ex.com/t/*",
  4322. "https://fast.v2ex.com/t/*",
  4323. "https://hk.v2ex.com/t/*"
  4324. );
  4325. const writeRegex = patternToRegex(
  4326. "https://v2ex.com/write*",
  4327. "https://www.v2ex.com/write*",
  4328. "https://cn.v2ex.com/write*"
  4329. );
  4330. const url = window.location.href;
  4331. void (async () => {
  4332. if (commonRegex.test(url)) {
  4333. await Promise.resolve().then(() => (init_common(), common_exports));
  4334. await Promise.resolve().then(() => (init_home(), home_exports));
  4335. }
  4336. if (topicRegex.test(url)) {
  4337. await Promise.resolve().then(() => (init_topic(), topic_exports));
  4338. }
  4339. if (writeRegex.test(url)) {
  4340. await Promise.resolve().then(() => (init_write2(), write_exports));
  4341. }
  4342. })();
  4343. });
  4344. /*! Bundled license information:
  4345.  
  4346. lucide/dist/esm/createElement.js:
  4347. (**
  4348. * @license lucide v0.309.0 - ISC
  4349. *
  4350. * This source code is licensed under the ISC license.
  4351. * See the LICENSE file in the root directory of this source tree.
  4352. *)
  4353.  
  4354. lucide/dist/esm/replaceElement.js:
  4355. (**
  4356. * @license lucide v0.309.0 - ISC
  4357. *
  4358. * This source code is licensed under the ISC license.
  4359. * See the LICENSE file in the root directory of this source tree.
  4360. *)
  4361.  
  4362. lucide/dist/esm/defaultAttributes.js:
  4363. (**
  4364. * @license lucide v0.309.0 - ISC
  4365. *
  4366. * This source code is licensed under the ISC license.
  4367. * See the LICENSE file in the root directory of this source tree.
  4368. *)
  4369.  
  4370. lucide/dist/esm/icons/book-open-check.js:
  4371. (**
  4372. * @license lucide v0.309.0 - ISC
  4373. *
  4374. * This source code is licensed under the ISC license.
  4375. * See the LICENSE file in the root directory of this source tree.
  4376. *)
  4377.  
  4378. lucide/dist/esm/icons/chevrons-up.js:
  4379. (**
  4380. * @license lucide v0.309.0 - ISC
  4381. *
  4382. * This source code is licensed under the ISC license.
  4383. * See the LICENSE file in the root directory of this source tree.
  4384. *)
  4385.  
  4386. lucide/dist/esm/icons/eye-off.js:
  4387. (**
  4388. * @license lucide v0.309.0 - ISC
  4389. *
  4390. * This source code is licensed under the ISC license.
  4391. * See the LICENSE file in the root directory of this source tree.
  4392. *)
  4393.  
  4394. lucide/dist/esm/icons/heart.js:
  4395. (**
  4396. * @license lucide v0.309.0 - ISC
  4397. *
  4398. * This source code is licensed under the ISC license.
  4399. * See the LICENSE file in the root directory of this source tree.
  4400. *)
  4401.  
  4402. lucide/dist/esm/icons/message-square-plus.js:
  4403. (**
  4404. * @license lucide v0.309.0 - ISC
  4405. *
  4406. * This source code is licensed under the ISC license.
  4407. * See the LICENSE file in the root directory of this source tree.
  4408. *)
  4409.  
  4410. lucide/dist/esm/icons/message-square.js:
  4411. (**
  4412. * @license lucide v0.309.0 - ISC
  4413. *
  4414. * This source code is licensed under the ISC license.
  4415. * See the LICENSE file in the root directory of this source tree.
  4416. *)
  4417.  
  4418. lucide/dist/esm/icons/moon.js:
  4419. (**
  4420. * @license lucide v0.309.0 - ISC
  4421. *
  4422. * This source code is licensed under the ISC license.
  4423. * See the LICENSE file in the root directory of this source tree.
  4424. *)
  4425.  
  4426. lucide/dist/esm/icons/package-plus.js:
  4427. (**
  4428. * @license lucide v0.309.0 - ISC
  4429. *
  4430. * This source code is licensed under the ISC license.
  4431. * See the LICENSE file in the root directory of this source tree.
  4432. *)
  4433.  
  4434. lucide/dist/esm/icons/panel-right.js:
  4435. (**
  4436. * @license lucide v0.309.0 - ISC
  4437. *
  4438. * This source code is licensed under the ISC license.
  4439. * See the LICENSE file in the root directory of this source tree.
  4440. *)
  4441.  
  4442. lucide/dist/esm/icons/panel-top.js:
  4443. (**
  4444. * @license lucide v0.309.0 - ISC
  4445. *
  4446. * This source code is licensed under the ISC license.
  4447. * See the LICENSE file in the root directory of this source tree.
  4448. *)
  4449.  
  4450. lucide/dist/esm/icons/smile.js:
  4451. (**
  4452. * @license lucide v0.309.0 - ISC
  4453. *
  4454. * This source code is licensed under the ISC license.
  4455. * See the LICENSE file in the root directory of this source tree.
  4456. *)
  4457.  
  4458. lucide/dist/esm/icons/star.js:
  4459. (**
  4460. * @license lucide v0.309.0 - ISC
  4461. *
  4462. * This source code is licensed under the ISC license.
  4463. * See the LICENSE file in the root directory of this source tree.
  4464. *)
  4465.  
  4466. lucide/dist/esm/icons/sun.js:
  4467. (**
  4468. * @license lucide v0.309.0 - ISC
  4469. *
  4470. * This source code is licensed under the ISC license.
  4471. * See the LICENSE file in the root directory of this source tree.
  4472. *)
  4473.  
  4474. lucide/dist/esm/icons/twitter.js:
  4475. (**
  4476. * @license lucide v0.309.0 - ISC
  4477. *
  4478. * This source code is licensed under the ISC license.
  4479. * See the LICENSE file in the root directory of this source tree.
  4480. *)
  4481.  
  4482. lucide/dist/esm/lucide.js:
  4483. (**
  4484. * @license lucide v0.309.0 - ISC
  4485. *
  4486. * This source code is licensed under the ISC license.
  4487. * See the LICENSE file in the root directory of this source tree.
  4488. *)
  4489. */