🏷️ 小鱼标签 (UTags) - 为链接添加用户标签

此插件允许用户为网站的链接添加自定义标签。比如,可以给论坛的用户或帖子添加标签。支持 V2EX, Greasy Fork, GitHub, B站, 抖音, 小红书, 知乎, 掘金, 豆瓣, 吾爱破解等网站。

当前为 2025-01-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name 🏷️ UTags - Add usertags to links
  3. // @name:zh-CN 🏷️ 小鱼标签 (UTags) - 为链接添加用户标签
  4. // @namespace https://utags.pipecraft.net/
  5. // @homepageURL https://github.com/utags/utags#readme
  6. // @supportURL https://github.com/utags/utags/issues
  7. // @version 0.9.8
  8. // @description Allow users to add custom tags to links.
  9. // @description:zh-CN 此插件允许用户为网站的链接添加自定义标签。比如,可以给论坛的用户或帖子添加标签。支持 V2EX, Greasy Fork, GitHub, B站, 抖音, 小红书, 知乎, 掘金, 豆瓣, 吾爱破解等网站。
  10. // @icon data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23ff6361' class='bi bi-tags-fill' viewBox='0 0 16 16'%3E %3Cpath d='M2 2a1 1 0 0 1 1-1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 2 6.586V2zm3.5 4a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z'/%3E %3Cpath d='M1.293 7.793A1 1 0 0 1 1 7.086V2a1 1 0 0 0-1 1v4.586a1 1 0 0 0 .293.707l7 7a1 1 0 0 0 1.414 0l.043-.043-7.457-7.457z'/%3E %3C/svg%3E
  11. // @author Pipecraft
  12. // @license MIT
  13. // @match https://x.com/*
  14. // @match https://twitter.com/*
  15. // @match https://github.com/*
  16. // @match https://www.reddit.com/*
  17. // @match https://www.instagram.com/*
  18. // @match https://www.threads.net/*
  19. // @match https://*.facebook.com/*
  20. // @match https://*.youtube.com/*
  21. // @match https://www.tiktok.com/*
  22. // @match https://*.bilibili.com/*
  23. // @match https://*.biligame.com/*
  24. // @match https://greasyfork.org/*
  25. // @match https://lobste.rs/*
  26. // @match https://news.ycombinator.com/*
  27. // @match https://*.v2ex.com/*
  28. // @match https://*.zhihu.com/*
  29. // @match https://*.weibo.com/*
  30. // @match https://*.weibo.cn/*
  31. // @match https://*.douban.com/*
  32. // @match https://www.52pojie.cn/*
  33. // @match https://juejin.cn/*
  34. // @match https://mp.weixin.qq.com/*
  35. // @match https://www.xiaohongshu.com/*
  36. // @match https://sspai.com/*
  37. // @match https://www.douyin.com/*
  38. // @match https://podcasts.google.com/*
  39. // @match https://sleazyfork.org/*
  40. // @match https://tilde.news/*
  41. // @match https://www.journalduhacker.net/*
  42. // @match https://rebang.today/*
  43. // @match https://myanimelist.net/*
  44. // @match https://v2hot.pipecraft.net/*
  45. // @match https://utags.pipecraft.net/*
  46. // @match https://*.pipecraft.net/*
  47. // @run-at document-start
  48. // @grant GM.getValue
  49. // @grant GM.setValue
  50. // @grant GM_addValueChangeListener
  51. // @grant GM_removeValueChangeListener
  52. // @grant GM_addElement
  53. // @grant GM.registerMenuCommand
  54. // ==/UserScript==
  55. //
  56. ;(() => {
  57. "use strict"
  58. var listeners = {}
  59. var getValue = async (key) => {
  60. const value = await GM.getValue(key)
  61. return value && value !== "undefined" ? JSON.parse(value) : void 0
  62. }
  63. var setValue = async (key, value) => {
  64. if (value !== void 0) {
  65. const newValue = JSON.stringify(value)
  66. if (listeners[key]) {
  67. const oldValue = await GM.getValue(key)
  68. await GM.setValue(key, newValue)
  69. if (newValue !== oldValue) {
  70. for (const func of listeners[key]) {
  71. func(key, oldValue, newValue)
  72. }
  73. }
  74. } else {
  75. await GM.setValue(key, newValue)
  76. }
  77. }
  78. }
  79. var _addValueChangeListener = (key, func) => {
  80. listeners[key] = listeners[key] || []
  81. listeners[key].push(func)
  82. return () => {
  83. if (listeners[key] && listeners[key].length > 0) {
  84. for (let i3 = listeners[key].length - 1; i3 >= 0; i3--) {
  85. if (listeners[key][i3] === func) {
  86. listeners[key].splice(i3, 1)
  87. }
  88. }
  89. }
  90. }
  91. }
  92. var addValueChangeListener = (key, func) => {
  93. if (typeof GM_addValueChangeListener !== "function") {
  94. console.warn("Do not support GM_addValueChangeListener!")
  95. return _addValueChangeListener(key, func)
  96. }
  97. const listenerId = GM_addValueChangeListener(key, func)
  98. return () => {
  99. GM_removeValueChangeListener(listenerId)
  100. }
  101. }
  102. var doc = document
  103. var win = window
  104. var uniq = (array) => [...new Set(array)]
  105. if (typeof String.prototype.replaceAll !== "function") {
  106. String.prototype.replaceAll = String.prototype.replace
  107. }
  108. var $ = (selectors, element) => (element || doc).querySelector(selectors)
  109. var $$ = (selectors, element) => [
  110. ...(element || doc).querySelectorAll(selectors),
  111. ]
  112. var getRootElement = (type) =>
  113. type === 1
  114. ? doc.head || doc.body || doc.documentElement
  115. : type === 2
  116. ? doc.body || doc.documentElement
  117. : doc.documentElement
  118. var createElement = (tagName, attributes) =>
  119. setAttributes(doc.createElement(tagName), attributes)
  120. var addElement = (parentNode, tagName, attributes) => {
  121. if (typeof parentNode === "string") {
  122. return addElement(null, parentNode, tagName)
  123. }
  124. if (!tagName) {
  125. return
  126. }
  127. if (!parentNode) {
  128. parentNode = /^(script|link|style|meta)$/.test(tagName)
  129. ? getRootElement(1)
  130. : getRootElement(2)
  131. }
  132. if (typeof tagName === "string") {
  133. const element = createElement(tagName, attributes)
  134. parentNode.append(element)
  135. return element
  136. }
  137. setAttributes(tagName, attributes)
  138. parentNode.append(tagName)
  139. return tagName
  140. }
  141. var addEventListener = (element, type, listener, options) => {
  142. if (!element) {
  143. return
  144. }
  145. if (typeof type === "object") {
  146. for (const type1 in type) {
  147. if (Object.hasOwn(type, type1)) {
  148. element.addEventListener(type1, type[type1])
  149. }
  150. }
  151. } else if (typeof type === "string" && typeof listener === "function") {
  152. element.addEventListener(type, listener, options)
  153. }
  154. }
  155. var removeEventListener = (element, type, listener, options) => {
  156. if (!element) {
  157. return
  158. }
  159. if (typeof type === "object") {
  160. for (const type1 in type) {
  161. if (Object.hasOwn(type, type1)) {
  162. element.removeEventListener(type1, type[type1])
  163. }
  164. }
  165. } else if (typeof type === "string" && typeof listener === "function") {
  166. element.removeEventListener(type, listener, options)
  167. }
  168. }
  169. var getAttribute = (element, name) =>
  170. element && element.getAttribute ? element.getAttribute(name) : null
  171. var setAttribute = (element, name, value) =>
  172. element && element.setAttribute ? element.setAttribute(name, value) : void 0
  173. var setAttributes = (element, attributes) => {
  174. if (element && attributes) {
  175. for (const name in attributes) {
  176. if (Object.hasOwn(attributes, name)) {
  177. const value = attributes[name]
  178. if (value === void 0) {
  179. continue
  180. }
  181. if (/^(value|textContent|innerText)$/.test(name)) {
  182. element[name] = value
  183. } else if (/^(innerHTML)$/.test(name)) {
  184. element[name] = createHTML(value)
  185. } else if (name === "style") {
  186. setStyle(element, value, true)
  187. } else if (/on\w+/.test(name)) {
  188. const type = name.slice(2)
  189. addEventListener(element, type, value)
  190. } else {
  191. setAttribute(element, name, value)
  192. }
  193. }
  194. }
  195. }
  196. return element
  197. }
  198. var addClass = (element, className) => {
  199. if (!element || !element.classList) {
  200. return
  201. }
  202. element.classList.add(className)
  203. }
  204. var removeClass = (element, className) => {
  205. if (!element || !element.classList) {
  206. return
  207. }
  208. element.classList.remove(className)
  209. }
  210. var hasClass = (element, className) => {
  211. if (!element || !element.classList) {
  212. return false
  213. }
  214. return element.classList.contains(className)
  215. }
  216. var setStyle = (element, values, overwrite) => {
  217. if (!element) {
  218. return
  219. }
  220. const style = element.style
  221. if (typeof values === "string") {
  222. style.cssText = overwrite ? values : style.cssText + ";" + values
  223. return
  224. }
  225. if (overwrite) {
  226. style.cssText = ""
  227. }
  228. for (const key in values) {
  229. if (Object.hasOwn(values, key)) {
  230. style[key] = values[key].replace("!important", "")
  231. }
  232. }
  233. }
  234. var isUrl = (text) => /^https?:\/\//.test(text)
  235. var throttle = (func, interval) => {
  236. let timeoutId = null
  237. let next = false
  238. const handler = (...args) => {
  239. if (timeoutId) {
  240. next = true
  241. } else {
  242. func.apply(void 0, args)
  243. timeoutId = setTimeout(() => {
  244. timeoutId = null
  245. if (next) {
  246. next = false
  247. handler()
  248. }
  249. }, interval)
  250. }
  251. }
  252. return handler
  253. }
  254. if (typeof Object.hasOwn !== "function") {
  255. Object.hasOwn = (instance, prop) =>
  256. Object.prototype.hasOwnProperty.call(instance, prop)
  257. }
  258. var extendHistoryApi = () => {
  259. const pushState = history.pushState
  260. const replaceState = history.replaceState
  261. history.pushState = function () {
  262. pushState.apply(history, arguments)
  263. window.dispatchEvent(new Event("pushstate"))
  264. window.dispatchEvent(new Event("locationchange"))
  265. }
  266. history.replaceState = function () {
  267. replaceState.apply(history, arguments)
  268. window.dispatchEvent(new Event("replacestate"))
  269. window.dispatchEvent(new Event("locationchange"))
  270. }
  271. window.addEventListener("popstate", function () {
  272. window.dispatchEvent(new Event("locationchange"))
  273. })
  274. }
  275. var parseInt10 = (number, defaultValue) => {
  276. if (typeof number === "number" && !Number.isNaN(number)) {
  277. return number
  278. }
  279. if (typeof defaultValue !== "number") {
  280. defaultValue = Number.NaN
  281. }
  282. if (!number) {
  283. return defaultValue
  284. }
  285. const result = Number.parseInt(number, 10)
  286. return Number.isNaN(result) ? defaultValue : result
  287. }
  288. var rootFuncArray = []
  289. var headFuncArray = []
  290. var bodyFuncArray = []
  291. var headBodyObserver
  292. var startObserveHeadBodyExists = () => {
  293. if (headBodyObserver) {
  294. return
  295. }
  296. headBodyObserver = new MutationObserver(() => {
  297. if (doc.head && doc.body) {
  298. headBodyObserver.disconnect()
  299. }
  300. if (doc.documentElement && rootFuncArray.length > 0) {
  301. for (const func of rootFuncArray) {
  302. func()
  303. }
  304. rootFuncArray.length = 0
  305. }
  306. if (doc.head && headFuncArray.length > 0) {
  307. for (const func of headFuncArray) {
  308. func()
  309. }
  310. headFuncArray.length = 0
  311. }
  312. if (doc.body && bodyFuncArray.length > 0) {
  313. for (const func of bodyFuncArray) {
  314. func()
  315. }
  316. bodyFuncArray.length = 0
  317. }
  318. })
  319. headBodyObserver.observe(doc, {
  320. childList: true,
  321. subtree: true,
  322. })
  323. }
  324. var runWhenHeadExists = (func) => {
  325. if (!doc.head) {
  326. headFuncArray.push(func)
  327. startObserveHeadBodyExists()
  328. return
  329. }
  330. func()
  331. }
  332. var runWhenDomReady = (func) => {
  333. if (doc.readyState === "interactive" || doc.readyState === "complete") {
  334. return func()
  335. }
  336. const handler = () => {
  337. if (doc.readyState === "interactive" || doc.readyState === "complete") {
  338. func()
  339. removeEventListener(doc, "readystatechange", handler)
  340. }
  341. }
  342. addEventListener(doc, "readystatechange", handler)
  343. }
  344. var isVisible = (element) => {
  345. if (typeof element.checkVisibility === "function") {
  346. return element.checkVisibility()
  347. }
  348. return element.offsetParent !== null
  349. }
  350. var isTouchScreen = () => "ontouchstart" in win
  351. var escapeHTMLPolicy =
  352. typeof trustedTypes !== "undefined" &&
  353. typeof trustedTypes.createPolicy === "function"
  354. ? trustedTypes.createPolicy("beuEscapePolicy", {
  355. createHTML: (string) => string,
  356. })
  357. : void 0
  358. var createHTML = (html) => {
  359. return escapeHTMLPolicy ? escapeHTMLPolicy.createHTML(html) : html
  360. }
  361. var addElement2 =
  362. typeof GM_addElement === "function"
  363. ? (parentNode, tagName, attributes) => {
  364. if (typeof parentNode === "string") {
  365. return addElement2(null, parentNode, tagName)
  366. }
  367. if (!tagName) {
  368. return
  369. }
  370. if (!parentNode) {
  371. parentNode = /^(script|link|style|meta)$/.test(tagName)
  372. ? getRootElement(1)
  373. : getRootElement(2)
  374. }
  375. if (typeof tagName === "string") {
  376. let attributes2
  377. if (attributes) {
  378. const entries1 = []
  379. const entries2 = []
  380. for (const entry of Object.entries(attributes)) {
  381. if (/^(on\w+|innerHTML)$/.test(entry[0])) {
  382. entries2.push(entry)
  383. } else {
  384. entries1.push(entry)
  385. }
  386. }
  387. attributes = Object.fromEntries(entries1)
  388. attributes2 = Object.fromEntries(entries2)
  389. }
  390. const element = GM_addElement(null, tagName, attributes)
  391. setAttributes(element, attributes2)
  392. parentNode.append(element)
  393. return element
  394. }
  395. setAttributes(tagName, attributes)
  396. parentNode.append(tagName)
  397. return tagName
  398. }
  399. : addElement
  400. var addStyle = (styleText) =>
  401. addElement2(null, "style", { textContent: styleText })
  402. var registerMenuCommand = (name, callback, accessKey) => {
  403. if (window !== top) {
  404. return
  405. }
  406. if (typeof GM.registerMenuCommand !== "function") {
  407. console.warn("Do not support GM.registerMenuCommand!")
  408. return
  409. }
  410. GM.registerMenuCommand(name, callback, accessKey)
  411. }
  412. var style_default =
  413. '#browser_extension_settings_container{--browser-extension-settings-background-color: #f2f2f7;--browser-extension-settings-text-color: #444444;--browser-extension-settings-link-color: #217dfc;--sb-track-color: #00000000;--sb-thumb-color: #33334480;--sb-size: 2px;--font-family: "helvetica neue", "microsoft yahei", arial, sans-serif;position:fixed;top:10px;right:30px;max-height:90%;height:600px;overflow:hidden;display:none;z-index:100000;border-radius:5px;-webkit-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);-moz-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);box-shadow:0px 10px 39px 10px rgba(62,66,66,.22) !important}#browser_extension_settings_container .browser_extension_settings_wrapper{display:flex;height:100%;overflow:hidden;background-color:var(--browser-extension-settings-background-color);font-family:var(--font-family)}#browser_extension_settings_container .browser_extension_settings_wrapper h1,#browser_extension_settings_container .browser_extension_settings_wrapper h2{border:none;color:var(--browser-extension-settings-text-color);padding:0;font-family:var(--font-family);line-height:normal;letter-spacing:normal}#browser_extension_settings_container .browser_extension_settings_wrapper h1{font-size:26px;font-weight:800;margin:18px 0}#browser_extension_settings_container .browser_extension_settings_wrapper h2{font-size:18px;font-weight:600;margin:14px 0}#browser_extension_settings_container .browser_extension_settings_wrapper footer{display:flex;justify-content:center;flex-direction:column;font-size:11px;margin:10px auto 0px;background-color:var(--browser-extension-settings-background-color);color:var(--browser-extension-settings-text-color);font-family:var(--font-family)}#browser_extension_settings_container .browser_extension_settings_wrapper footer a{color:var(--browser-extension-settings-link-color) !important;font-family:var(--font-family);text-decoration:none;padding:0}#browser_extension_settings_container .browser_extension_settings_wrapper footer p{text-align:center;padding:0;margin:2px;line-height:13px;font-size:11px;color:var(--browser-extension-settings-text-color);font-family:var(--font-family)}#browser_extension_settings_container .browser_extension_settings_wrapper a.navigation_go_previous{color:var(--browser-extension-settings-link-color);cursor:pointer;display:none}#browser_extension_settings_container .browser_extension_settings_wrapper a.navigation_go_previous::before{content:"< "}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container{overflow-x:auto;box-sizing:border-box;padding:10px 15px;background-color:var(--browser-extension-settings-background-color);color:var(--browser-extension-settings-text-color)}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div{background-color:#fff;font-size:14px;border-top:1px solid #ccc;padding:6px 15px 6px 15px}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:visited,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:visited{display:flex;justify-content:space-between;align-items:center;cursor:pointer;text-decoration:none;color:var(--browser-extension-settings-text-color);font-family:var(--font-family)}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:visited:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:visited:hover{text-decoration:none;color:var(--browser-extension-settings-text-color)}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a span,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div a:visited span,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a span,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div a:visited span{margin-right:10px;line-height:24px;font-family:var(--font-family)}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div.active,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div:hover,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div.active,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div:hover{background-color:#e4e4e6}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div.active a,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div.active a{cursor:default}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div:first-of-type,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div:first-of-type{border-top:none;border-top-right-radius:10px;border-top-left-radius:10px}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .installed_extension_list div:last-of-type,#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container .related_extension_list div:last-of-type{border-bottom-right-radius:10px;border-bottom-left-radius:10px}#browser_extension_settings_container .thin_scrollbar{scrollbar-color:var(--sb-thumb-color) var(--sb-track-color);scrollbar-width:thin}#browser_extension_settings_container .thin_scrollbar::-webkit-scrollbar{width:var(--sb-size)}#browser_extension_settings_container .thin_scrollbar::-webkit-scrollbar-track{background:var(--sb-track-color);border-radius:10px}#browser_extension_settings_container .thin_scrollbar::-webkit-scrollbar-thumb{background:var(--sb-thumb-color);border-radius:10px}#browser_extension_settings_main{min-width:250px;overflow-y:auto;overflow-x:hidden;box-sizing:border-box;padding:10px 15px;background-color:var(--browser-extension-settings-background-color);color:var(--browser-extension-settings-text-color);font-family:var(--font-family)}#browser_extension_settings_main h2{text-align:center;margin:5px 0 0}#browser_extension_settings_main .option_groups{background-color:#fff;padding:6px 15px 6px 15px;border-radius:10px;display:flex;flex-direction:column;margin:10px 0 0}#browser_extension_settings_main .option_groups .action{font-size:14px;padding:6px 0 6px 0;color:var(--browser-extension-settings-link-color);cursor:pointer}#browser_extension_settings_main .bes_external_link{font-size:14px;padding:6px 0 6px 0}#browser_extension_settings_main .bes_external_link a,#browser_extension_settings_main .bes_external_link a:visited,#browser_extension_settings_main .bes_external_link a:hover{color:var(--browser-extension-settings-link-color);font-family:var(--font-family);text-decoration:none;cursor:pointer}#browser_extension_settings_main .option_groups textarea{font-size:12px;margin:10px 0 10px 0;height:100px;width:100%;border:1px solid #a9a9a9;border-radius:4px;box-sizing:border-box}#browser_extension_settings_main .switch_option,#browser_extension_settings_main .select_option{display:flex;justify-content:space-between;align-items:center;padding:6px 0 6px 0;font-size:14px}#browser_extension_settings_main .option_groups>*{border-top:1px solid #ccc}#browser_extension_settings_main .option_groups>*:first-child{border-top:none}#browser_extension_settings_main .bes_option>.bes_icon{width:24px;height:24px;margin-right:10px}#browser_extension_settings_main .bes_option>.bes_title{margin-right:10px;flex-grow:1}#browser_extension_settings_main .bes_option>.bes_select{box-sizing:border-box;background-color:#fff;height:24px;padding:0 2px 0 2px;margin:0;border-radius:6px;border:1px solid #ccc}#browser_extension_settings_main .option_groups .bes_tip{position:relative;margin:0;padding:0 15px 0 0;border:none;max-width:none;font-size:14px}#browser_extension_settings_main .option_groups .bes_tip .bes_tip_anchor{cursor:help;text-decoration:underline}#browser_extension_settings_main .option_groups .bes_tip .bes_tip_content{position:absolute;bottom:15px;left:0;background-color:#fff;color:var(--browser-extension-settings-text-color);text-align:left;padding:10px;display:none;border-radius:5px;-webkit-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);-moz-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);box-shadow:0px 10px 39px 10px rgba(62,66,66,.22) !important}#browser_extension_settings_main .option_groups .bes_tip .bes_tip_anchor:hover+.bes_tip_content,#browser_extension_settings_main .option_groups .bes_tip .bes_tip_content:hover{display:block}#browser_extension_settings_main .option_groups .bes_tip p,#browser_extension_settings_main .option_groups .bes_tip pre{margin:revert;padding:revert}#browser_extension_settings_main .option_groups .bes_tip pre{font-family:Consolas,panic sans,bitstream vera sans mono,Menlo,microsoft yahei,monospace;font-size:13px;letter-spacing:.015em;line-height:120%;white-space:pre;overflow:auto;background-color:#f5f5f5;word-break:normal;overflow-wrap:normal;padding:.5em;border:none}#browser_extension_settings_main .bes_switch_container{--button-width: 51px;--button-height: 24px;--toggle-diameter: 20px;--color-off: #e9e9eb;--color-on: #34c759;width:var(--button-width);height:var(--button-height);position:relative;padding:0;margin:0;flex:none;user-select:none}#browser_extension_settings_main input[type=checkbox]{opacity:0;width:0;height:0;position:absolute}#browser_extension_settings_main .bes_switch{width:100%;height:100%;display:block;background-color:var(--color-off);border-radius:calc(var(--button-height)/2);border:none;cursor:pointer;transition:all .2s ease-out}#browser_extension_settings_main .bes_switch::before{display:none}#browser_extension_settings_main .bes_slider{width:var(--toggle-diameter);height:var(--toggle-diameter);position:absolute;left:2px;top:calc(50% - var(--toggle-diameter)/2);border-radius:50%;background:#fff;box-shadow:0px 3px 8px rgba(0,0,0,.15),0px 3px 1px rgba(0,0,0,.06);transition:all .2s ease-out;cursor:pointer}#browser_extension_settings_main input[type=checkbox]:checked+.bes_switch{background-color:var(--color-on)}#browser_extension_settings_main input[type=checkbox]:checked+.bes_switch .bes_slider{left:calc(var(--button-width) - var(--toggle-diameter) - 2px)}#browser_extension_side_menu{min-height:80px;width:30px;opacity:0;position:fixed;top:80px;right:0;padding-top:20px;z-index:10000}#browser_extension_side_menu:hover{opacity:1}#browser_extension_side_menu button{cursor:pointer;width:24px;height:24px;padding:0;border:none;background-color:rgba(0,0,0,0);background-image:none}#browser_extension_side_menu button svg{width:24px;height:24px}#browser_extension_side_menu button:hover{opacity:70%}#browser_extension_side_menu button:active{opacity:100%}@media(max-width: 500px){#browser_extension_settings_container{right:10px}#browser_extension_settings_container .browser_extension_settings_wrapper a.navigation_go_previous{display:block}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container{display:none}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container.bes_active{display:block}#browser_extension_settings_container .browser_extension_settings_wrapper .extension_list_container.bes_active+div{display:none}}'
  414. function createSwitch(options = {}) {
  415. const container = createElement("label", { class: "bes_switch_container" })
  416. const checkbox = createElement(
  417. "input",
  418. options.checked ? { type: "checkbox", checked: "" } : { type: "checkbox" }
  419. )
  420. addElement2(container, checkbox)
  421. const switchElm = createElement("span", { class: "bes_switch" })
  422. addElement2(switchElm, "span", { class: "bes_slider" })
  423. addElement2(container, switchElm)
  424. if (options.onchange) {
  425. addEventListener(checkbox, "change", options.onchange)
  426. }
  427. return container
  428. }
  429. function createSwitchOption(icon, text, options) {
  430. if (typeof text !== "string") {
  431. return createSwitchOption(void 0, icon, text)
  432. }
  433. const div = createElement("div", { class: "switch_option bes_option" })
  434. if (icon) {
  435. addElement2(div, "img", { src: icon, class: "bes_icon" })
  436. }
  437. addElement2(div, "span", { textContent: text, class: "bes_title" })
  438. div.append(createSwitch(options))
  439. return div
  440. }
  441. var besVersion = 55
  442. var openButton =
  443. '<svg viewBox="0 0 60.2601318359375 84.8134765625" version="1.1" xmlns="http://www.w3.org/2000/svg" class=" glyph-box" style="height: 9.62969px; width: 6.84191px;"><g transform="matrix(1 0 0 1 -6.194965820312518 77.63671875)"><path d="M66.4551-35.2539C66.4551-36.4746 65.9668-37.5977 65.0391-38.4766L26.3672-76.3672C25.4883-77.1973 24.4141-77.6367 23.1445-77.6367C20.6543-77.6367 18.7012-75.7324 18.7012-73.1934C18.7012-71.9727 19.1895-70.8496 19.9707-70.0195L55.5176-35.2539L19.9707-0.488281C19.1895 0.341797 18.7012 1.41602 18.7012 2.68555C18.7012 5.22461 20.6543 7.12891 23.1445 7.12891C24.4141 7.12891 25.4883 6.68945 26.3672 5.81055L65.0391-32.0312C65.9668-32.959 66.4551-34.0332 66.4551-35.2539Z"></path></g></svg>'
  444. var openInNewTabButton =
  445. '<svg viewBox="0 0 72.127685546875 72.2177734375" version="1.1" xmlns="http://www.w3.org/2000/svg" class=" glyph-box" style="height: 8.19958px; width: 8.18935px;"><g transform="matrix(1 0 0 1 -12.451127929687573 71.3388671875)"><path d="M84.5703-17.334L84.5215-66.4551C84.5215-69.2383 82.7148-71.1914 79.7852-71.1914L30.6641-71.1914C27.9297-71.1914 26.0742-69.0918 26.0742-66.748C26.0742-64.4043 28.1738-62.4023 30.4688-62.4023L47.4609-62.4023L71.2891-63.1836L62.207-55.2246L13.8184-6.73828C12.9395-5.85938 12.4512-4.73633 12.4512-3.66211C12.4512-1.31836 14.5508 0.878906 16.9922 0.878906C18.1152 0.878906 19.1895 0.488281 20.0684-0.439453L68.5547-48.877L76.6113-58.0078L75.7324-35.2051L75.7324-17.1387C75.7324-14.8438 77.7344-12.6953 80.127-12.6953C82.4707-12.6953 84.5703-14.6973 84.5703-17.334Z"></path></g></svg>'
  446. var settingButton =
  447. '<svg viewBox="0 0 16 16" version="1.1">\n<path d="M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z"></path>\n</svg>'
  448. function initI18n(messageMaps, language) {
  449. language = (language || navigator.language).toLowerCase()
  450. const language2 = language.slice(0, 2)
  451. let messagesDefault
  452. let messagesLocal
  453. for (const entry of Object.entries(messageMaps)) {
  454. const langs = new Set(
  455. entry[0]
  456. .toLowerCase()
  457. .split(",")
  458. .map((v) => v.trim())
  459. )
  460. const value = entry[1]
  461. if (langs.has(language)) {
  462. messagesLocal = value
  463. }
  464. if (langs.has(language2) && !messagesLocal) {
  465. messagesLocal = value
  466. }
  467. if (langs.has("en")) {
  468. messagesDefault = value
  469. }
  470. if (langs.has("en-us") && !messagesDefault) {
  471. messagesDefault = value
  472. }
  473. }
  474. if (!messagesLocal) {
  475. messagesLocal = {}
  476. }
  477. if (!messagesDefault || messagesDefault === messagesLocal) {
  478. messagesDefault = {}
  479. }
  480. return function (key, ...parameters) {
  481. let text = messagesLocal[key] || messagesDefault[key] || key
  482. if (parameters && parameters.length > 0 && text !== key) {
  483. for (let i3 = 0; i3 < parameters.length; i3++) {
  484. text = text.replaceAll(
  485. new RegExp("\\{".concat(i3 + 1, "\\}"), "g"),
  486. String(parameters[i3])
  487. )
  488. }
  489. }
  490. return text
  491. }
  492. }
  493. var messages = {
  494. "settings.title": "Settings",
  495. "settings.otherExtensions": "Other Extensions",
  496. "settings.displaySettingsButtonInSideMenu":
  497. "Display Settings Button in Side Menu",
  498. "settings.menu.settings": "\u2699\uFE0F Settings",
  499. "settings.extensions.utags.title":
  500. "\u{1F3F7}\uFE0F UTags - Add usertags to links",
  501. "settings.extensions.links-helper.title": "\u{1F517} Links Helper",
  502. "settings.extensions.v2ex.rep.title":
  503. "V2EX.REP - \u4E13\u6CE8\u63D0\u5347 V2EX \u4E3B\u9898\u56DE\u590D\u6D4F\u89C8\u4F53\u9A8C",
  504. "settings.extensions.v2ex.min.title":
  505. "v2ex.min - V2EX Minimalist (\u6781\u7B80\u98CE\u683C)",
  506. "settings.extensions.replace-ugly-avatars.title": "Replace Ugly Avatars",
  507. "settings.extensions.more-by-pipecraft.title":
  508. "Find more useful userscripts",
  509. }
  510. var en_default = messages
  511. var messages2 = {
  512. "settings.title": "\u8BBE\u7F6E",
  513. "settings.otherExtensions": "\u5176\u4ED6\u6269\u5C55",
  514. "settings.displaySettingsButtonInSideMenu":
  515. "\u5728\u4FA7\u8FB9\u680F\u83DC\u5355\u4E2D\u663E\u793A\u8BBE\u7F6E\u6309\u94AE",
  516. "settings.menu.settings": "\u2699\uFE0F \u8BBE\u7F6E",
  517. "settings.extensions.utags.title":
  518. "\u{1F3F7}\uFE0F \u5C0F\u9C7C\u6807\u7B7E (UTags) - \u4E3A\u94FE\u63A5\u6DFB\u52A0\u7528\u6237\u6807\u7B7E",
  519. "settings.extensions.links-helper.title":
  520. "\u{1F517} \u94FE\u63A5\u52A9\u624B",
  521. "settings.extensions.v2ex.rep.title":
  522. "V2EX.REP - \u4E13\u6CE8\u63D0\u5347 V2EX \u4E3B\u9898\u56DE\u590D\u6D4F\u89C8\u4F53\u9A8C",
  523. "settings.extensions.v2ex.min.title":
  524. "v2ex.min - V2EX \u6781\u7B80\u98CE\u683C",
  525. "settings.extensions.replace-ugly-avatars.title":
  526. "\u8D50\u4F60\u4E2A\u5934\u50CF\u5427",
  527. "settings.extensions.more-by-pipecraft.title":
  528. "\u66F4\u591A\u6709\u8DA3\u7684\u811A\u672C",
  529. }
  530. var zh_cn_default = messages2
  531. var i = initI18n({
  532. "en,en-US": en_default,
  533. "zh,zh-CN": zh_cn_default,
  534. })
  535. var lang = navigator.language
  536. var locale
  537. if (lang === "zh-TW" || lang === "zh-HK") {
  538. locale = "zh-TW"
  539. } else if (lang.includes("zh")) {
  540. locale = "zh-CN"
  541. } else {
  542. locale = "en"
  543. }
  544. var relatedExtensions = [
  545. {
  546. id: "utags",
  547. title: i("settings.extensions.utags.title"),
  548. url: "https://greasyfork.org/".concat(
  549. locale,
  550. "/scripts/460718-utags-add-usertags-to-links"
  551. ),
  552. },
  553. {
  554. id: "links-helper",
  555. title: i("settings.extensions.links-helper.title"),
  556. description:
  557. "\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00\u7B2C\u4E09\u65B9\u7F51\u7AD9\u94FE\u63A5\uFF0C\u56FE\u7247\u94FE\u63A5\u8F6C\u56FE\u7247\u6807\u7B7E\u7B49",
  558. url: "https://greasyfork.org/".concat(
  559. locale,
  560. "/scripts/464541-links-helper"
  561. ),
  562. },
  563. {
  564. id: "v2ex.rep",
  565. title: i("settings.extensions.v2ex.rep.title"),
  566. url: "https://greasyfork.org/".concat(
  567. locale,
  568. "/scripts/466589-v2ex-rep-%E4%B8%93%E6%B3%A8%E6%8F%90%E5%8D%87-v2ex-%E4%B8%BB%E9%A2%98%E5%9B%9E%E5%A4%8D%E6%B5%8F%E8%A7%88%E4%BD%93%E9%AA%8C"
  569. ),
  570. },
  571. {
  572. id: "v2ex.min",
  573. title: i("settings.extensions.v2ex.min.title"),
  574. url: "https://greasyfork.org/".concat(
  575. locale,
  576. "/scripts/463552-v2ex-min-v2ex-%E6%9E%81%E7%AE%80%E9%A3%8E%E6%A0%BC"
  577. ),
  578. },
  579. {
  580. id: "replace-ugly-avatars",
  581. title: i("settings.extensions.replace-ugly-avatars.title"),
  582. url: "https://greasyfork.org/".concat(
  583. locale,
  584. "/scripts/472616-replace-ugly-avatars"
  585. ),
  586. },
  587. {
  588. id: "more-by-pipecraft",
  589. title: i("settings.extensions.more-by-pipecraft.title"),
  590. url: "https://greasyfork.org/".concat(locale, "/users/1030884-pipecraft"),
  591. },
  592. ]
  593. var getInstalledExtesionList = () => {
  594. return $(".extension_list_container .installed_extension_list")
  595. }
  596. var getRelatedExtesionList = () => {
  597. return $(".extension_list_container .related_extension_list")
  598. }
  599. var isInstalledExtension = (id) => {
  600. const list = getInstalledExtesionList()
  601. if (!list) {
  602. return false
  603. }
  604. const installed = $('[data-extension-id="'.concat(id, '"]'), list)
  605. return Boolean(installed)
  606. }
  607. var addCurrentExtension = (extension) => {
  608. const list = getInstalledExtesionList()
  609. if (!list) {
  610. return
  611. }
  612. if (isInstalledExtension(extension.id)) {
  613. return
  614. }
  615. const element = createInstalledExtension(extension)
  616. list.append(element)
  617. const list2 = getRelatedExtesionList()
  618. if (list2) {
  619. updateRelatedExtensions(list2)
  620. }
  621. }
  622. var activeExtension = (id) => {
  623. const list = getInstalledExtesionList()
  624. if (!list) {
  625. return false
  626. }
  627. for (const element of $$(".active", list)) {
  628. removeClass(element, "active")
  629. }
  630. const installed = $('[data-extension-id="'.concat(id, '"]'), list)
  631. if (installed) {
  632. addClass(installed, "active")
  633. }
  634. }
  635. var activeExtensionList = () => {
  636. const extensionListContainer = $(".extension_list_container")
  637. if (extensionListContainer) {
  638. addClass(extensionListContainer, "bes_active")
  639. }
  640. }
  641. var deactiveExtensionList = () => {
  642. const extensionListContainer = $(".extension_list_container")
  643. if (extensionListContainer) {
  644. removeClass(extensionListContainer, "bes_active")
  645. }
  646. }
  647. var createInstalledExtension = (installedExtension) => {
  648. const div = createElement("div", {
  649. class: "installed_extension",
  650. "data-extension-id": installedExtension.id,
  651. })
  652. const a = addElement2(div, "a", {
  653. onclick: installedExtension.onclick,
  654. })
  655. addElement2(a, "span", {
  656. textContent: installedExtension.title,
  657. })
  658. const svg = addElement2(a, "svg")
  659. svg.outerHTML = createHTML(openButton)
  660. return div
  661. }
  662. var updateRelatedExtensions = (container) => {
  663. const relatedExtensionElements = $$("[data-extension-id]", container)
  664. if (relatedExtensionElements.length > 0) {
  665. for (const relatedExtensionElement of relatedExtensionElements) {
  666. if (
  667. isInstalledExtension(
  668. relatedExtensionElement.dataset.extensionId || "noid"
  669. )
  670. ) {
  671. relatedExtensionElement.remove()
  672. }
  673. }
  674. } else {
  675. container.innerHTML = createHTML("")
  676. }
  677. for (const relatedExtension of relatedExtensions) {
  678. if (
  679. isInstalledExtension(relatedExtension.id) ||
  680. $('[data-extension-id="'.concat(relatedExtension.id, '"]'), container)
  681. ) {
  682. continue
  683. }
  684. if ($$("[data-extension-id]", container).length >= 4) {
  685. return
  686. }
  687. const div4 = addElement2(container, "div", {
  688. class: "related_extension",
  689. "data-extension-id": relatedExtension.id,
  690. })
  691. const a = addElement2(div4, "a", {
  692. href: relatedExtension.url,
  693. target: "_blank",
  694. })
  695. addElement2(a, "span", {
  696. textContent: relatedExtension.title,
  697. })
  698. const svg = addElement2(a, "svg")
  699. svg.outerHTML = createHTML(openInNewTabButton)
  700. }
  701. }
  702. function createExtensionList(installedExtensions) {
  703. const div = createElement("div", {
  704. class: "extension_list_container thin_scrollbar",
  705. })
  706. addElement2(div, "h1", { textContent: i("settings.title") })
  707. const div2 = addElement2(div, "div", {
  708. class: "installed_extension_list",
  709. })
  710. for (const installedExtension of installedExtensions) {
  711. if (isInstalledExtension(installedExtension.id)) {
  712. continue
  713. }
  714. const element = createInstalledExtension(installedExtension)
  715. div2.append(element)
  716. }
  717. addElement2(div, "h2", { textContent: i("settings.otherExtensions") })
  718. const div3 = addElement2(div, "div", {
  719. class: "related_extension_list",
  720. })
  721. updateRelatedExtensions(div3)
  722. return div
  723. }
  724. var prefix = "browser_extension_settings_"
  725. var randomId = String(Math.round(Math.random() * 1e4))
  726. var settingsContainerId = prefix + "container_" + randomId
  727. var settingsElementId = prefix + "main_" + randomId
  728. var getSettingsElement = () => $("#" + settingsElementId)
  729. var getSettingsStyle = () =>
  730. style_default
  731. .replaceAll(/browser_extension_settings_container/gm, settingsContainerId)
  732. .replaceAll(/browser_extension_settings_main/gm, settingsElementId)
  733. var storageKey = "settings"
  734. var settingsOptions
  735. var settingsTable = {}
  736. var settings = {}
  737. async function getSettings() {
  738. var _a
  739. return (_a = await getValue(storageKey)) != null ? _a : {}
  740. }
  741. async function saveSettingsValue(key, value) {
  742. const settings2 = await getSettings()
  743. settings2[key] =
  744. settingsTable[key] && settingsTable[key].defaultValue === value
  745. ? void 0
  746. : value
  747. await setValue(storageKey, settings2)
  748. }
  749. function getSettingsValue(key) {
  750. var _a
  751. return Object.hasOwn(settings, key)
  752. ? settings[key]
  753. : (_a = settingsTable[key]) == null
  754. ? void 0
  755. : _a.defaultValue
  756. }
  757. var closeModal = () => {
  758. const settingsContainer = getSettingsContainer()
  759. if (settingsContainer) {
  760. settingsContainer.style.display = "none"
  761. }
  762. removeEventListener(document, "click", onDocumentClick, true)
  763. removeEventListener(document, "keydown", onDocumentKeyDown, true)
  764. }
  765. var onDocumentClick = (event) => {
  766. const target = event.target
  767. if (
  768. target == null ? void 0 : target.closest(".".concat(prefix, "container"))
  769. ) {
  770. return
  771. }
  772. closeModal()
  773. }
  774. var onDocumentKeyDown = (event) => {
  775. if (event.defaultPrevented) {
  776. return
  777. }
  778. if (event.key === "Escape") {
  779. closeModal()
  780. event.preventDefault()
  781. }
  782. }
  783. async function updateOptions() {
  784. if (!getSettingsElement()) {
  785. return
  786. }
  787. for (const key in settingsTable) {
  788. if (Object.hasOwn(settingsTable, key)) {
  789. const item = settingsTable[key]
  790. const type = item.type || "switch"
  791. switch (type) {
  792. case "switch": {
  793. const checkbox = $(
  794. "#"
  795. .concat(
  796. settingsElementId,
  797. ' .option_groups .switch_option[data-key="'
  798. )
  799. .concat(key, '"] input')
  800. )
  801. if (checkbox) {
  802. checkbox.checked = getSettingsValue(key)
  803. }
  804. break
  805. }
  806. case "select": {
  807. const options = $$(
  808. "#"
  809. .concat(
  810. settingsElementId,
  811. ' .option_groups .select_option[data-key="'
  812. )
  813. .concat(key, '"] .bes_select option')
  814. )
  815. for (const option of options) {
  816. option.selected = option.value === String(getSettingsValue(key))
  817. }
  818. break
  819. }
  820. case "textarea": {
  821. const textArea = $(
  822. "#"
  823. .concat(
  824. settingsElementId,
  825. ' .option_groups textarea[data-key="'
  826. )
  827. .concat(key, '"]')
  828. )
  829. if (textArea) {
  830. textArea.value = getSettingsValue(key)
  831. }
  832. break
  833. }
  834. default: {
  835. break
  836. }
  837. }
  838. }
  839. }
  840. if (typeof settingsOptions.onViewUpdate === "function") {
  841. const settingsMain = createSettingsElement()
  842. settingsOptions.onViewUpdate(settingsMain)
  843. }
  844. }
  845. function getSettingsContainer() {
  846. const container = $(".".concat(prefix, "container"))
  847. if (container) {
  848. const theVersion = parseInt10(container.dataset.besVersion, 0)
  849. if (theVersion < besVersion) {
  850. container.id = settingsContainerId
  851. container.dataset.besVersion = String(besVersion)
  852. }
  853. return container
  854. }
  855. return addElement2(doc.body, "div", {
  856. id: settingsContainerId,
  857. class: "".concat(prefix, "container"),
  858. "data-bes-version": besVersion,
  859. style: "display: none;",
  860. })
  861. }
  862. function getSettingsWrapper() {
  863. const container = getSettingsContainer()
  864. return (
  865. $(".".concat(prefix, "wrapper"), container) ||
  866. addElement2(container, "div", {
  867. class: "".concat(prefix, "wrapper"),
  868. })
  869. )
  870. }
  871. function initExtensionList() {
  872. const wrapper = getSettingsWrapper()
  873. if (!$(".extension_list_container", wrapper)) {
  874. const list = createExtensionList([])
  875. wrapper.append(list)
  876. }
  877. addCurrentExtension({
  878. id: settingsOptions.id,
  879. title: settingsOptions.title,
  880. onclick: showSettings,
  881. })
  882. }
  883. function createSettingsElement() {
  884. let settingsMain = getSettingsElement()
  885. if (!settingsMain) {
  886. const wrapper = getSettingsWrapper()
  887. for (const element of $$(".".concat(prefix, "main"))) {
  888. element.remove()
  889. }
  890. settingsMain = addElement2(wrapper, "div", {
  891. id: settingsElementId,
  892. class: "".concat(prefix, "main thin_scrollbar"),
  893. })
  894. addElement2(settingsMain, "a", {
  895. textContent: "Settings",
  896. class: "navigation_go_previous",
  897. onclick() {
  898. activeExtensionList()
  899. },
  900. })
  901. if (settingsOptions.title) {
  902. addElement2(settingsMain, "h2", { textContent: settingsOptions.title })
  903. }
  904. const optionGroups = []
  905. const getOptionGroup = (index) => {
  906. if (index > optionGroups.length) {
  907. for (let i3 = optionGroups.length; i3 < index; i3++) {
  908. optionGroups.push(
  909. addElement2(settingsMain, "div", {
  910. class: "option_groups",
  911. })
  912. )
  913. }
  914. }
  915. return optionGroups[index - 1]
  916. }
  917. for (const key in settingsTable) {
  918. if (Object.hasOwn(settingsTable, key)) {
  919. const item = settingsTable[key]
  920. const type = item.type || "switch"
  921. const group = item.group || 1
  922. const optionGroup = getOptionGroup(group)
  923. switch (type) {
  924. case "switch": {
  925. const switchOption = createSwitchOption(item.icon, item.title, {
  926. async onchange(event) {
  927. const checkbox = event.target
  928. if (checkbox) {
  929. let result = true
  930. if (typeof item.onConfirmChange === "function") {
  931. result = item.onConfirmChange(checkbox.checked)
  932. }
  933. if (result) {
  934. await saveSettingsValue(key, checkbox.checked)
  935. } else {
  936. checkbox.checked = !checkbox.checked
  937. }
  938. }
  939. },
  940. })
  941. switchOption.dataset.key = key
  942. addElement2(optionGroup, switchOption)
  943. break
  944. }
  945. case "textarea": {
  946. let timeoutId
  947. const div = addElement2(optionGroup, "div", {
  948. class: "bes_textarea",
  949. })
  950. addElement2(div, "textarea", {
  951. "data-key": key,
  952. placeholder: item.placeholder || "",
  953. onkeyup(event) {
  954. const textArea = event.target
  955. if (timeoutId) {
  956. clearTimeout(timeoutId)
  957. timeoutId = void 0
  958. }
  959. timeoutId = setTimeout(async () => {
  960. if (textArea) {
  961. await saveSettingsValue(key, textArea.value.trim())
  962. }
  963. }, 100)
  964. },
  965. })
  966. break
  967. }
  968. case "action": {
  969. addElement2(optionGroup, "a", {
  970. class: "action",
  971. textContent: item.title,
  972. onclick: item.onclick,
  973. })
  974. break
  975. }
  976. case "externalLink": {
  977. const div4 = addElement2(optionGroup, "div", {
  978. class: "bes_external_link",
  979. })
  980. addElement2(div4, "a", {
  981. textContent: item.title,
  982. href: item.url,
  983. target: "_blank",
  984. })
  985. break
  986. }
  987. case "select": {
  988. const div = addElement2(optionGroup, "div", {
  989. class: "select_option bes_option",
  990. "data-key": key,
  991. })
  992. if (item.icon) {
  993. addElement2(div, "img", { src: item.icon, class: "bes_icon" })
  994. }
  995. addElement2(div, "span", {
  996. textContent: item.title,
  997. class: "bes_title",
  998. })
  999. const select = addElement2(div, "select", {
  1000. class: "bes_select",
  1001. async onchange() {
  1002. await saveSettingsValue(key, select.value)
  1003. },
  1004. })
  1005. for (const option of Object.entries(item.options)) {
  1006. addElement2(select, "option", {
  1007. textContent: option[0],
  1008. value: option[1],
  1009. })
  1010. }
  1011. break
  1012. }
  1013. case "tip": {
  1014. const tip = addElement2(optionGroup, "div", {
  1015. class: "bes_tip",
  1016. })
  1017. addElement2(tip, "a", {
  1018. class: "bes_tip_anchor",
  1019. textContent: item.title,
  1020. })
  1021. const tipContent = addElement2(tip, "div", {
  1022. class: "bes_tip_content",
  1023. innerHTML: createHTML(item.tipContent),
  1024. })
  1025. break
  1026. }
  1027. }
  1028. }
  1029. }
  1030. if (settingsOptions.footer) {
  1031. const footer = addElement2(settingsMain, "footer")
  1032. footer.innerHTML = createHTML(
  1033. typeof settingsOptions.footer === "string"
  1034. ? settingsOptions.footer
  1035. : '<p>Made with \u2764\uFE0F by\n <a href="https://www.pipecraft.net/" target="_blank">\n Pipecraft\n </a></p>'
  1036. )
  1037. }
  1038. }
  1039. return settingsMain
  1040. }
  1041. function addSideMenu() {
  1042. if (!getSettingsValue("displaySettingsButtonInSideMenu")) {
  1043. return
  1044. }
  1045. const menu =
  1046. $("#browser_extension_side_menu") ||
  1047. addElement2(doc.body, "div", {
  1048. id: "browser_extension_side_menu",
  1049. "data-bes-version": besVersion,
  1050. })
  1051. const button = $("button[data-bes-version]", menu)
  1052. if (button) {
  1053. const theVersion = parseInt10(button.dataset.besVersion, 0)
  1054. if (theVersion >= besVersion) {
  1055. return
  1056. }
  1057. button.remove()
  1058. }
  1059. addElement2(menu, "button", {
  1060. type: "button",
  1061. "data-bes-version": besVersion,
  1062. title: i("settings.menu.settings"),
  1063. onclick() {
  1064. setTimeout(showSettings, 1)
  1065. },
  1066. innerHTML: settingButton,
  1067. })
  1068. }
  1069. function addCommonSettings(settingsTable3) {
  1070. let maxGroup = 0
  1071. for (const key in settingsTable3) {
  1072. if (Object.hasOwn(settingsTable3, key)) {
  1073. const item = settingsTable3[key]
  1074. const group = item.group || 1
  1075. if (group > maxGroup) {
  1076. maxGroup = group
  1077. }
  1078. }
  1079. }
  1080. settingsTable3.displaySettingsButtonInSideMenu = {
  1081. title: i("settings.displaySettingsButtonInSideMenu"),
  1082. defaultValue: !(
  1083. typeof GM === "object" && typeof GM.registerMenuCommand === "function"
  1084. ),
  1085. group: maxGroup + 1,
  1086. }
  1087. }
  1088. function handleShowSettingsUrl() {
  1089. if (location.hash === "#bes-show-settings") {
  1090. setTimeout(showSettings, 100)
  1091. }
  1092. }
  1093. async function showSettings() {
  1094. const settingsContainer = getSettingsContainer()
  1095. const settingsMain = createSettingsElement()
  1096. await updateOptions()
  1097. settingsContainer.style.display = "block"
  1098. addEventListener(document, "click", onDocumentClick, true)
  1099. addEventListener(document, "keydown", onDocumentKeyDown, true)
  1100. activeExtension(settingsOptions.id)
  1101. deactiveExtensionList()
  1102. }
  1103. var initSettings = async (options) => {
  1104. settingsOptions = options
  1105. settingsTable = options.settingsTable || {}
  1106. addCommonSettings(settingsTable)
  1107. addValueChangeListener(storageKey, async () => {
  1108. settings = await getSettings()
  1109. await updateOptions()
  1110. addSideMenu()
  1111. if (typeof options.onValueChange === "function") {
  1112. options.onValueChange()
  1113. }
  1114. })
  1115. settings = await getSettings()
  1116. runWhenHeadExists(() => {
  1117. addStyle(getSettingsStyle())
  1118. })
  1119. runWhenDomReady(() => {
  1120. initExtensionList()
  1121. addSideMenu()
  1122. })
  1123. registerMenuCommand(i("settings.menu.settings"), showSettings, "o")
  1124. handleShowSettingsUrl()
  1125. }
  1126. var content_default =
  1127. '\uFEFF:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) #utags_layer{height:200px;width:200px;background-color:red}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_key]{display:none !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul{box-sizing:border-box !important;display:inline-flex !important;flex-wrap:wrap;align-content:flex-start;justify-content:flex-start;overflow:visible;white-space:normal;list-style-type:none !important;margin:0 !important;padding:0 !important;vertical-align:text-bottom !important;line-height:normal !important;background-color:rgba(0,0,0,0);border:none !important;box-shadow:none !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul>li{box-sizing:border-box !important;display:inline-flex !important;align-items:center !important;float:none !important;overflow:visible;width:unset !important;height:unset !important;border:none !important;padding:0 !important;margin:0 !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul>li:first-child .utags_text_tag{margin-left:3px !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul>li:last-child .utags_text_tag{margin-right:3px !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul>li::before,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul>li::after{content:none}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_text_tag{box-sizing:border-box !important;display:block !important;border:var(--utags-text-tag-border-width) solid var(--utags-text-tag-border-color);color:var(--utags-text-tag-color) !important;border-radius:3px !important;padding:1px 3px !important;margin:0 1px !important;font-size:var(--utags-text-tag-font-size) !important;font-family:var(--utags-text-tag-font-family) !important;letter-spacing:0 !important;line-height:1 !important;height:unset !important;width:unset !important;font-weight:normal !important;text-decoration:none !important;text-align:center !important;text-shadow:none !important;min-width:unset !important;min-height:unset !important;max-width:unset !important;max-height:unset !important;background:unset !important;background-color:var(--utags-text-tag-background-color) !important;pointer-events:auto}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_text_tag:link{cursor:pointer}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_text_tag[data-utags_tag]::before{content:attr(data-utags_tag);display:block;font-size:var(--utags-text-tag-font-size);line-height:1;height:unset}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_emoji_tag{--utags-text-tag-background-color: var( --utags-emoji-tag-background-color );--utags-text-tag-font-size: var(--utags-emoji-tag-font-size);--utags-text-tag-font-family: var(--utags-emoji-tag-font-family);--utags-text-tag-border-width: var(--utags-emoji-tag-border-width);--utags-text-tag-border-color: var(--utags-emoji-tag-border-color)}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag2{width:var(--utags-captain-tag-size) !important;height:var(--utags-captain-tag-size) !important;padding:1px 0 0 1px !important;background:none !important;color:var(--utags-captain-tag-color) !important;border:none !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag::before,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag2::before{content:none !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag svg,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag2 svg{fill:currentColor !important;vertical-align:-3px}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag *,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag2 *{color:inherit !important;fill:currentColor !important;width:unset;height:unset}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag{opacity:1%;position:absolute;top:var(--utags-notag-captain-tag-top, 0);left:var(--utags-notag-captain-tag-left, 0);padding:0 !important;margin:0 !important;width:4px !important;height:4px !important;font-size:1px !important;background-color:var(--utags-captain-tag-background-color) !important;transition:all 0s .3s !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag:hover,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag:focus,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag2:hover,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul .utags_captain_tag2:focus{color:var(--utags-captain-tag-hover-color) !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul.utags_ul_0{margin:0 !important;display:var(--utags-notag-ul-disply, inline) !important;float:var(--utags-notag-ul-float, none);height:var(--utags-notag-ul-height, unset);width:var(--utags-notag-ul-width, unset);position:var(--utags-notag-ul-position, unset);top:var(--utags-notag-ul-top, unset)}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul.utags_ul_0>li{position:relative !important;height:var(--utags-captain-tag-size) !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_captain_tag:focus,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) *:hover+.utags_ul .utags_captain_tag,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul:hover .utags_captain_tag,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_show_all .utags_captain_tag,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) :not(a):not([data-utags_node_type=link])+.utags_ul .utags_captain_tag{opacity:100%;width:calc(var(--utags-captain-tag-size) + 8px) !important;height:calc(var(--utags-captain-tag-size) + 8px) !important;padding:5px 4px 4px 5px !important;transition:all 0s .1s !important;z-index:90}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_hide_all .utags_captain_tag,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_show_all .utags_captain_tag{transition:unset !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) :not(a):not([data-utags_node_type=link])+.utags_ul .utags_captain_tag{position:relative}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul_notag_float_left .utags_ul_0,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_ul_0.utags_ul_notag_float_left{float:left}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal{position:fixed;top:0;left:0;height:0;width:0;z-index:100000}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_modal_wrapper{position:fixed;display:flex;align-items:flex-start;justify-content:center;width:100%;inset:0px;padding:14vh 16px 16px;background-color:rgba(255,255,255,.1);z-index:100000}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_modal_content{box-sizing:border-box;display:flex;flex-direction:column;max-width:600px;max-height:100%;overflow:auto;color:#000;background-color:#fff;border-radius:5px;padding:14px;-webkit-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);-moz-box-shadow:0px 10px 39px 10px rgba(62,66,66,.22);box-shadow:0px 10px 39px 10px rgba(62,66,66,.22)}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_title{display:block;color:#000;margin-bottom:10px;font-size:14px}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_buttons_wrapper{display:flex;flex-direction:row;justify-content:end;padding:10px 0 10px 0}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_buttons_wrapper button{font-size:14px;height:32px;min-width:80px;font-weight:600;padding:0 8px;border-radius:2px;color:var(--utags-button-text-color);border:1px solid var(--utags-button-border-color);background-color:var(--utags-button-bg-color);text-shadow:none}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_buttons_wrapper button:hover{background-color:var(--utags-button-hover-bg-color);border-color:var(--utags-button-hover-border-color)}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_buttons_wrapper button:not(:first-child){margin-left:10px}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_buttons_wrapper button.utags_primary{--utags-button-text-color: var(--utags-action-button-text-color);--utags-button-bg-color: var(--utags-action-button-bg-color);--utags-button-border-color: var(--utags-action-button-border-color);--utags-button-hover-bg-color: var( --utags-action-button-hover-bg-color );--utags-button-hover-border-color: var( --utags-action-button-hover-border-color )}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_prompt input{-webkit-appearance:none;background-color:var(--utags-button-hover-bg-color);border:none;border-bottom:2px solid var(--utags-button-hover-bg-color);border-radius:4px;box-sizing:border-box;caret-color:var(--cr-input-focus-color);color:var(--cr-input-color);font-family:var(--utags-text-tag-font-family) !important;font-weight:inherit;line-height:inherit;min-height:var(--cr-input-min-height, auto);outline:0;padding-bottom:var(--cr-input-padding-bottom, 6px);padding-inline-end:var(--cr-input-padding-end, 8px);padding-inline-start:var(--cr-input-padding-start, 8px);padding-top:var(--cr-input-padding-top, 6px);text-align:left;text-overflow:ellipsis;width:100%;margin:0;font-size:12px}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_prompt input:focus,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_modal .utags_prompt input:focus-visible{outline:0;border-bottom:2px solid var(--utags-action-button-hover-border-color)}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_current_tags_wrapper{display:flex;justify-content:space-between}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_current_tags_wrapper .utags_button_copy{cursor:pointer;font-size:10px;line-height:1;height:18px;padding:0 6px;border-radius:2px;color:var(--utags-action-button-text-color);background-color:var(--utags-action-button-bg-color);border:1px solid var(--utags-action-button-border-color);text-shadow:none}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_current_tags{list-style-type:none;margin:0;padding:0 0 10px 0 !important;display:flex !important;flex-direction:row;flex-wrap:wrap}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_current_tags:empty,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_current_tags:empty+button{display:none !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_current_tags li .utags_text_tag:hover{--utags-text-tag-color: #000;--utags-text-tag-border-color: #000;--utags-text-tag-background-color: unset;opacity:.5;text-decoration:line-through !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) .utags_list_wrapper{display:flex;justify-content:space-between;max-height:200px;overflow-y:auto}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list{flex-grow:1;list-style-type:none;margin:0;padding:10px 0 10px 0}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list:empty{display:none !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list:not(:first-child){margin-left:4px}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list::before{content:attr(data-utags_list_name);position:sticky;top:0;display:block;font-size:12px;font-weight:600;text-align:left;padding:0 2px 0 8px;cursor:default;background-color:#f8fafe}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list li{box-sizing:border-box;cursor:pointer;font-size:12px;height:16px;display:flex;align-items:center;padding:0 2px 0 8px;margin:0;max-width:150px;overflow:hidden;text-overflow:ellipsis}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list li.utags_active,:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list li.utags_active2{background-color:#fef2f2}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) ul.utags_select_list li span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--utags-text-tag-font-family) !important;font-size:12px;line-height:1}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u6807\u9898\u515A,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u63A8\u5E7F,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u65E0\u804A,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u5FFD\u7565,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",ignore,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",clickbait,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",promotion,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",sb,"]{opacity:10%}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u5DF2\u9605,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u65B0\u7528\u6237,"]{opacity:50%}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",hide,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u9690\u85CF,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u4E0D\u518D\u663E\u793A,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",block,"]{opacity:5%;display:none}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u70ED\u95E8,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u6536\u85CF,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u5173\u6CE8,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u7A0D\u540E\u9605\u8BFB,"]{background-image:linear-gradient(to right, #ffffff, #fefce8) !important;opacity:100% !important;display:var(--utags-list-node-display) !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u70ED\u95E8,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u6536\u85CF,"],:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node*=",\u5173\u6CE8,"]{background-image:linear-gradient(to right, #ffffff, #fef2f2) !important}:not(#utags_should_has_higher_specificity):not(#utags_should_has_higher_specificity) [data-utags_list_node]:hover{opacity:100% !important}.utags_no_hide [data-utags_list_node*=","]{display:var(--utags-list-node-display) !important}.utags_no_opacity_effect [data-utags_list_node*=","]{opacity:100% !important}textarea[data-key=emojiTags]{font-family:var(--utags-text-tag-font-family)}:root{--utags-list-node-display: block;--utags-captain-tag-background-color: #ffffffb3;--utags-captain-tag-background-color-overlap: #ffffffdd;--utags-captain-tag-color: #ff6361;--utags-captain-tag-hover-color: #256cf1;--utags-captain-tag-size: 14px;--utags-text-tag-color: red;--utags-text-tag-border-color: red;--utags-text-tag-background-color: unset;--utags-text-tag-font-size: 10px;--utags-text-tag-border-width: 1px;--utags-text-tag-font-family: "helvetica neue", "Helvetica", "microsoft yahei", "Arial", "sans-serif", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "noto color emoji", "android emoji", "emojisymbols", "emojione mozilla", "twemoji mozilla", "Segoe UI", "Noto Sans";--utags-emoji-tag-border-color: #fff0;--utags-emoji-tag-background-color: #f8fafe;--utags-emoji-tag-font-size: 12px;--utags-emoji-tag-border-width: 0;--utags-emoji-tag-font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "noto color emoji", "android emoji", "emojisymbols", "emojione mozilla", "twemoji mozilla", "Segoe UI", "Noto Sans";--utags-button-text-color: #1a73e8;--utags-button-bg-color: #ffffff;--utags-button-border-color: #dadce0;--utags-button-hover-bg-color: #4285f40a;--utags-button-hover-border-color: #d2e3fc;--utags-action-button-text-color: #ffffff;--utags-action-button-bg-color: #1a73e8;--utags-action-button-border-color: #1a73e8;--utags-action-button-hover-bg-color: #1a73e8e6;--utags-action-button-hover-border-color: #1a73e8e6;--utags-notag-ul-disply-1: inline;--utags-notag-ul-float-1: none;--utags-notag-ul-height-1: unset;--utags-notag-ul-width-1: unset;--utags-notag-ul-position-1: unset;--utags-notag-ul-top-1: unset;--utags-notag-captain-tag-top-1: 0;--utags-notag-captain-tag-left-1: 0;--utags-notag-ul-disply-2: block;--utags-notag-ul-height-2: 0;--utags-notag-ul-width-2: 0;--utags-notag-ul-position-2: unset;--utags-notag-ul-top-2: unset;--utags-notag-captain-tag-top-2: -22px;--utags-notag-captain-tag-left-2: -4px;--utags-notag-ul-disply-3: block;--utags-notag-ul-height-3: 0;--utags-notag-ul-width-3: 0;--utags-notag-ul-position-3: absolute;--utags-notag-ul-top-3: 0;--utags-notag-captain-tag-top-3: 0;--utags-notag-captain-tag-left-3: -4px;--utags-notag-ul-disply-4: block;--utags-notag-ul-height-4: 0;--utags-notag-ul-width-4: 0;--utags-notag-ul-position-4: absolute;--utags-notag-ul-top-4: unset;--utags-notag-captain-tag-top-4: 0;--utags-notag-captain-tag-left-4: -4px;--utags-notag-ul-disply: var(--utags-notag-ul-disply-1);--utags-notag-ul-float: var(--utags-notag-ul-float-1);--utags-notag-ul-height: var(--utags-notag-ul-height-1);--utags-notag-ul-width: var(--utags-notag-ul-width-1);--utags-notag-ul-position: var(--utags-notag-ul-position-1);--utags-notag-ul-top: var(--utags-notag-ul-top-1);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-1);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-1)}'
  1128. function createTag(tagName, options) {
  1129. const a = createElement("a")
  1130. if (options.enableSelect) {
  1131. a.textContent = tagName
  1132. } else {
  1133. a.dataset.utags_tag = tagName
  1134. }
  1135. if (!options.noLink) {
  1136. a.setAttribute(
  1137. "href",
  1138. "https://utags.pipecraft.net/tags/#" + encodeURIComponent(tagName)
  1139. )
  1140. a.setAttribute("target", "_blank")
  1141. }
  1142. a.setAttribute(
  1143. "class",
  1144. options.isEmoji ? "utags_text_tag utags_emoji_tag" : "utags_text_tag"
  1145. )
  1146. return a
  1147. }
  1148. var messages3 = {
  1149. "settings.enableCurrentSite": "Enable on current site",
  1150. "settings.showHidedItems": "Show hidden items (tags with 'block', 'hide')",
  1151. "settings.noOpacityEffect":
  1152. "No opacity mask effect (tags with 'ignore', 'clickbait', 'promotion')",
  1153. "settings.pinnedTags": "Add the tags you want to pin, separated by commas.",
  1154. "settings.pinnedTagsDefaultValue":
  1155. "block, hide, ignore, clickbait, promotion",
  1156. "settings.pinnedTagsPlaceholder": "foo, bar",
  1157. "settings.emojiTags": "Add the emoji tags, separated by commas",
  1158. "settings.useSimplePrompt": "Use simple prompt method to add tags",
  1159. "settings.openTagsPage": "Open the tag list page",
  1160. "settings.openDataPage": "Open the import data/export data page",
  1161. "settings.title": "\u{1F3F7}\uFE0F UTags - Add usertags to links",
  1162. "settings.information":
  1163. "After changing the settings, reload the page to take effect",
  1164. "settings.report": "Report and Issue...",
  1165. "prompt.addTags":
  1166. "[UTags] Please enter tags, multiple tags are separated by commas",
  1167. "prompt.pinnedTags": "Pinned",
  1168. "prompt.mostUsedTags": "Recently commonly used",
  1169. "prompt.recentAddedTags": "Newly added",
  1170. "prompt.emojiTags": "Emoji",
  1171. "prompt.copy": "Copy",
  1172. "prompt.cancel": "Cancle",
  1173. "prompt.ok": "OK",
  1174. }
  1175. var en_default2 = messages3
  1176. var messages4 = {
  1177. "settings.enableCurrentSite": "\u5728\u5F53\u524D\u7F51\u7AD9\u542F\u7528",
  1178. "settings.showHidedItems":
  1179. "\u663E\u793A\u88AB\u9690\u85CF\u7684\u5185\u5BB9 (\u6DFB\u52A0\u4E86 'block', 'hide', '\u9690\u85CF'\u7B49\u6807\u7B7E\u7684\u5185\u5BB9)",
  1180. "settings.noOpacityEffect":
  1181. "\u53BB\u9664\u534A\u900F\u660E\u6548\u679C (\u6DFB\u52A0\u4E86 'sb', '\u5FFD\u7565', '\u6807\u9898\u515A'\u7B49\u6807\u7B7E\u7684\u5185\u5BB9)",
  1182. "settings.pinnedTags":
  1183. "\u5728\u4E0B\u9762\u6DFB\u52A0\u8981\u7F6E\u9876\u7684\u6807\u7B7E\uFF0C\u4EE5\u9017\u53F7\u5206\u9694",
  1184. "settings.pinnedTagsDefaultValue":
  1185. "\u6536\u85CF, block, sb, \u9690\u85CF, \u5DF2\u9605, \u5FFD\u7565, \u6807\u9898\u515A, \u63A8\u5E7F, \u5173\u6CE8",
  1186. "settings.pinnedTagsPlaceholder": "foo, bar",
  1187. "settings.emojiTags":
  1188. "\u5728\u4E0B\u9762\u6DFB\u52A0\u8868\u60C5\u7B26\u53F7\u6807\u7B7E\uFF0C\u4EE5\u9017\u53F7\u5206\u9694",
  1189. "settings.useSimplePrompt":
  1190. "\u4F7F\u7528\u7B80\u5355\u65B9\u5F0F\u6DFB\u52A0\u6807\u7B7E",
  1191. "settings.openTagsPage": "\u6807\u7B7E\u5217\u8868",
  1192. "settings.openDataPage":
  1193. "\u5BFC\u51FA\u6570\u636E/\u5BFC\u5165\u6570\u636E",
  1194. "settings.title":
  1195. "\u{1F3F7}\uFE0F \u5C0F\u9C7C\u6807\u7B7E (UTags) - \u4E3A\u94FE\u63A5\u6DFB\u52A0\u7528\u6237\u6807\u7B7E",
  1196. "settings.information":
  1197. "\u66F4\u6539\u8BBE\u7F6E\u540E\uFF0C\u91CD\u65B0\u52A0\u8F7D\u9875\u9762\u5373\u53EF\u751F\u6548",
  1198. "settings.report": "\u53CD\u9988\u95EE\u9898",
  1199. "prompt.addTags":
  1200. "[UTags] \u8BF7\u8F93\u5165\u6807\u7B7E\uFF0C\u591A\u4E2A\u6807\u7B7E\u4EE5\u9017\u53F7\u5206\u9694",
  1201. "prompt.pinnedTags": "\u7F6E\u9876",
  1202. "prompt.mostUsedTags": "\u6700\u8FD1\u5E38\u7528",
  1203. "prompt.recentAddedTags": "\u6700\u65B0\u6DFB\u52A0",
  1204. "prompt.emojiTags": "\u7B26\u53F7",
  1205. "prompt.copy": "\u590D\u5236",
  1206. "prompt.cancel": "\u53D6\u6D88",
  1207. "prompt.ok": "\u786E\u8BA4",
  1208. }
  1209. var zh_cn_default2 = messages4
  1210. var i2 = initI18n({
  1211. "en,en-US": en_default2,
  1212. "zh,zh-CN": zh_cn_default2,
  1213. })
  1214. function getFirstHeadElement(tagName = "h1") {
  1215. for (const element of $$(tagName)) {
  1216. if (element.closest(".browser_extension_settings_container")) {
  1217. continue
  1218. }
  1219. return element
  1220. }
  1221. return void 0
  1222. }
  1223. function splitTags(text) {
  1224. if (!text) {
  1225. return []
  1226. }
  1227. return text
  1228. .trim()
  1229. .replaceAll(/[\n\r\t]/gm, " ")
  1230. .split(/\s*[,,]\s*/)
  1231. }
  1232. function sortTags(tags, privilegedTags) {
  1233. return tags.sort((a, b) => {
  1234. const pA = privilegedTags.includes(a)
  1235. const pB = privilegedTags.includes(b)
  1236. if (pA && pB) {
  1237. return 0
  1238. }
  1239. if (pA) {
  1240. return -1
  1241. }
  1242. if (pB) {
  1243. return 1
  1244. }
  1245. return 0
  1246. })
  1247. }
  1248. async function copyText(data) {
  1249. const textArea = createElement("textarea", {
  1250. style: "position: absolute; left: -100%;",
  1251. contentEditable: "true",
  1252. })
  1253. textArea.value = data.replaceAll("\xA0", " ")
  1254. document.body.append(textArea)
  1255. textArea.select()
  1256. await navigator.clipboard.writeText(textArea.value)
  1257. textArea.remove()
  1258. }
  1259. function deleteUrlParameters(urlString, keys, excepts) {
  1260. const url = new URL(urlString)
  1261. if (keys === "*") {
  1262. if (excepts && excepts.length > 0) {
  1263. const parameters2 = new URLSearchParams(url.search)
  1264. keys = []
  1265. for (const key of parameters2.keys()) {
  1266. if (!excepts.includes(key)) {
  1267. keys.push(key)
  1268. }
  1269. }
  1270. } else {
  1271. url.search = ""
  1272. return url.toString()
  1273. }
  1274. }
  1275. if (typeof keys === "string") {
  1276. keys = [keys]
  1277. }
  1278. const parameters = new URLSearchParams(url.search)
  1279. for (const key of keys) {
  1280. parameters.delete(key)
  1281. }
  1282. url.search = parameters.size === 0 ? "" : "?" + parameters.toString()
  1283. return url.toString()
  1284. }
  1285. var extensionVersion = "0.8.0"
  1286. var databaseVersion = 2
  1287. var storageKey2 = "extension.utags.urlmap"
  1288. var storageKeyRecentTags = "extension.utags.recenttags"
  1289. var storageKeyMostUsedTags = "extension.utags.mostusedtags"
  1290. var storageKeyRecentAddedTags = "extension.utags.recentaddedtags"
  1291. var cachedUrlMap
  1292. async function getUrlMap() {
  1293. return (await getValue(storageKey2)) || {}
  1294. }
  1295. async function getUrlMapVesion1() {
  1296. return getValue("plugin.utags.tags.v1")
  1297. }
  1298. async function getCachedUrlMap() {
  1299. if (!cachedUrlMap) {
  1300. cachedUrlMap = await getUrlMap()
  1301. }
  1302. return cachedUrlMap
  1303. }
  1304. function getTags(key) {
  1305. return (cachedUrlMap && cachedUrlMap[key]) || { tags: [] }
  1306. }
  1307. async function saveTags(key, tags, meta) {
  1308. const urlMap = await getUrlMap()
  1309. urlMap.meta = Object.assign({}, urlMap.meta, {
  1310. extensionVersion,
  1311. databaseVersion,
  1312. })
  1313. const newTags = mergeTags(tags, [])
  1314. let oldTags = []
  1315. if (newTags.length === 0) {
  1316. delete urlMap[key]
  1317. } else {
  1318. const now = Date.now()
  1319. const data = urlMap[key] || {}
  1320. oldTags = data.tags
  1321. const newMeta = Object.assign({}, data.meta, meta, {
  1322. updated: now,
  1323. })
  1324. newMeta.created = newMeta.created || now
  1325. urlMap[key] = {
  1326. tags: newTags,
  1327. meta: newMeta,
  1328. }
  1329. }
  1330. await setValue(storageKey2, urlMap)
  1331. await addRecentTags(newTags, oldTags)
  1332. }
  1333. function getScore(weight = 1) {
  1334. return (Math.floor(Date.now() / 1e3) / 1e9) * weight
  1335. }
  1336. async function addRecentTags(newTags, oldTags) {
  1337. if (newTags.length === 0) {
  1338. return
  1339. }
  1340. newTags =
  1341. oldTags && oldTags.length > 0
  1342. ? newTags.filter((v) => !oldTags.includes(v))
  1343. : newTags
  1344. if (newTags.length > 0) {
  1345. const recentTags = (await getValue(storageKeyRecentTags)) || []
  1346. const score = getScore()
  1347. for (const tag of newTags) {
  1348. recentTags.push({
  1349. tag,
  1350. score,
  1351. })
  1352. }
  1353. if (recentTags.length > 1e3) {
  1354. recentTags.splice(0, 100)
  1355. }
  1356. await setValue(storageKeyRecentTags, recentTags)
  1357. await generateMostUsedAndRecentAddedTags(recentTags)
  1358. }
  1359. }
  1360. async function generateMostUsedAndRecentAddedTags(recentTags) {
  1361. const mostUsed = {}
  1362. for (const recentTag of recentTags) {
  1363. if (!recentTag.tag) {
  1364. continue
  1365. }
  1366. if (mostUsed[recentTag.tag]) {
  1367. mostUsed[recentTag.tag].score += recentTag.score
  1368. } else if (recentTag.tag) {
  1369. mostUsed[recentTag.tag] = {
  1370. tag: recentTag.tag,
  1371. score: recentTag.score,
  1372. }
  1373. }
  1374. }
  1375. const mostUsedTags2 = Object.values(mostUsed)
  1376. .filter((v) => v.score > getScore(1.5))
  1377. .sort((a, b) => {
  1378. return b.score - a.score
  1379. })
  1380. .map((v) => v.tag)
  1381. .slice(0, 200)
  1382. const uniqSet = /* @__PURE__ */ new Set()
  1383. const recentAddedTags2 = recentTags
  1384. .map((v) => v.tag)
  1385. .reverse()
  1386. .filter((v) => v && !uniqSet.has(v) && uniqSet.add(v))
  1387. .slice(0, 200)
  1388. await setValue(storageKeyMostUsedTags, mostUsedTags2)
  1389. await setValue(storageKeyRecentAddedTags, recentAddedTags2)
  1390. }
  1391. async function getMostUsedTags() {
  1392. return (await getValue(storageKeyMostUsedTags)) || []
  1393. }
  1394. async function getRecentAddedTags() {
  1395. return (await getValue(storageKeyRecentAddedTags)) || []
  1396. }
  1397. async function getPinnedTags() {
  1398. return splitTags(getSettingsValue("pinnedTags") || "")
  1399. }
  1400. async function getEmojiTags() {
  1401. return splitTags(getSettingsValue("emojiTags") || "")
  1402. }
  1403. function addTagsValueChangeListener(func) {
  1404. addValueChangeListener(storageKey2, func)
  1405. }
  1406. addTagsValueChangeListener(async () => {
  1407. cachedUrlMap = void 0
  1408. await checkVersion()
  1409. })
  1410. async function reload() {
  1411. console.log("Current extionsion is outdated, need reload page")
  1412. const urlMap = await getUrlMap()
  1413. urlMap.meta = urlMap.meta || {}
  1414. await setValue(storageKey2, urlMap)
  1415. location.reload()
  1416. }
  1417. async function checkVersion() {
  1418. cachedUrlMap = await getUrlMap()
  1419. const meta = cachedUrlMap.meta || {}
  1420. if (meta.extensionVersion !== extensionVersion) {
  1421. console.log(
  1422. "Previous extension version:",
  1423. meta.extensionVersion,
  1424. "current extension version:",
  1425. extensionVersion
  1426. )
  1427. if (meta.extensionVersion > extensionVersion) {
  1428. }
  1429. }
  1430. if (meta.databaseVersion !== databaseVersion) {
  1431. console.log(
  1432. "Previous database version:",
  1433. meta.databaseVersion,
  1434. "current database version:",
  1435. databaseVersion
  1436. )
  1437. if (meta.databaseVersion > databaseVersion) {
  1438. await reload()
  1439. return false
  1440. }
  1441. }
  1442. return true
  1443. }
  1444. function isValidKey(key) {
  1445. return isUrl(key)
  1446. }
  1447. function isValidTags(tags) {
  1448. return Array.isArray(tags)
  1449. }
  1450. function mergeTags(tags, tags2) {
  1451. tags = tags || []
  1452. tags2 = tags2 || []
  1453. return uniq(
  1454. tags
  1455. .concat(tags2)
  1456. .map((v) => (v ? String(v).trim() : v))
  1457. .filter(Boolean)
  1458. )
  1459. }
  1460. async function migrationData(urlMap) {
  1461. console.log("Before migration", JSON.stringify(urlMap))
  1462. const meta = urlMap.meta || {}
  1463. const now = Date.now()
  1464. const meta2 = { created: now, updated: now }
  1465. if (!meta.databaseVersion) {
  1466. meta.databaseVersion = 1
  1467. }
  1468. if (meta.databaseVersion === 1) {
  1469. for (const key in urlMap) {
  1470. if (!Object.hasOwn(urlMap, key)) {
  1471. continue
  1472. }
  1473. if (!isValidKey(key)) {
  1474. continue
  1475. }
  1476. const tags = urlMap[key]
  1477. if (!isValidTags(tags)) {
  1478. throw new Error("Invaid data format.")
  1479. }
  1480. const newTags = mergeTags(tags, [])
  1481. if (newTags.length > 0) {
  1482. urlMap[key] = { tags: newTags, meta: meta2 }
  1483. } else {
  1484. delete urlMap[key]
  1485. }
  1486. }
  1487. meta.databaseVersion = 2
  1488. }
  1489. if (meta.databaseVersion === 2) {
  1490. }
  1491. urlMap.meta = meta
  1492. console.log("After migration", JSON.stringify(urlMap))
  1493. return urlMap
  1494. }
  1495. async function mergeData(urlMapNew) {
  1496. if (typeof urlMapNew !== "object") {
  1497. throw new TypeError("Invalid data format")
  1498. }
  1499. let numberOfLinks = 0
  1500. let numberOfTags = 0
  1501. const urlMap = await getUrlMap()
  1502. if (
  1503. !urlMapNew.meta ||
  1504. urlMapNew.meta.databaseVersion !== urlMap.meta.databaseVersion
  1505. ) {
  1506. urlMapNew = await migrationData(urlMapNew)
  1507. }
  1508. if (urlMapNew.meta.databaseVersion !== urlMap.meta.databaseVersion) {
  1509. throw new Error("Invalid database version")
  1510. }
  1511. for (const key in urlMapNew) {
  1512. if (!Object.hasOwn(urlMapNew, key)) {
  1513. continue
  1514. }
  1515. if (!isValidKey(key)) {
  1516. continue
  1517. }
  1518. const tags = urlMapNew[key].tags || []
  1519. const meta = urlMapNew[key].meta || {}
  1520. if (!isValidTags(tags)) {
  1521. throw new Error("Invaid data format.")
  1522. }
  1523. const orgData = urlMap[key] || { tags: [] }
  1524. const orgTags = orgData.tags || []
  1525. const newTags = mergeTags(orgTags, tags)
  1526. const now = Date.now()
  1527. if (newTags.length > 0) {
  1528. const orgMeta = orgData.meta || {}
  1529. const created = Math.min(orgMeta.created || now, meta.created || now)
  1530. const updated = Math.max(
  1531. orgMeta.updated || 0,
  1532. meta.updated || 0,
  1533. created
  1534. )
  1535. const newMata = Object.assign({}, orgMeta, meta, { created, updated })
  1536. urlMap[key] = Object.assign({}, orgData, {
  1537. tags: newTags,
  1538. meta: newMata,
  1539. })
  1540. numberOfTags += Math.max(newTags.length - orgTags.length, 0)
  1541. if (orgTags.length === 0) {
  1542. numberOfLinks++
  1543. }
  1544. } else {
  1545. delete urlMap[key]
  1546. }
  1547. }
  1548. await setValue(storageKey2, urlMap)
  1549. console.log(
  1550. "\u6570\u636E\u5DF2\u6210\u529F\u5BFC\u5165\uFF0C\u65B0\u589E "
  1551. .concat(numberOfLinks, " \u6761\u94FE\u63A5\uFF0C\u65B0\u589E ")
  1552. .concat(numberOfTags, " \u6761\u6807\u7B7E\u3002")
  1553. )
  1554. return { numberOfLinks, numberOfTags }
  1555. }
  1556. async function migration() {
  1557. const result = await checkVersion()
  1558. if (!result) {
  1559. return
  1560. }
  1561. cachedUrlMap = await getUrlMap()
  1562. const meta = cachedUrlMap.meta || {}
  1563. if (meta.databaseVersion !== databaseVersion) {
  1564. meta.databaseVersion = meta.databaseVersion || 1
  1565. if (meta.databaseVersion < databaseVersion) {
  1566. console.log("Migration start")
  1567. await saveTags("any", [])
  1568. console.log("Migration done")
  1569. }
  1570. }
  1571. const urlMapVer1 = await getUrlMapVesion1()
  1572. if (urlMapVer1) {
  1573. console.log(
  1574. "Migration start: database version 1 to database version",
  1575. databaseVersion
  1576. )
  1577. const result2 = await mergeData(urlMapVer1)
  1578. if (result2) {
  1579. await setValue("plugin.utags.tags.v1", null)
  1580. }
  1581. }
  1582. }
  1583. async function outputData() {
  1584. if (
  1585. /^(utags\.pipecraft\.net|localhost|127\.0\.0\.1)$/.test(location.hostname)
  1586. ) {
  1587. const urlMap = await getUrlMap()
  1588. const textarea = createElement("textarea")
  1589. textarea.id = "utags_output"
  1590. textarea.setAttribute("style", "display:none")
  1591. textarea.value = JSON.stringify(urlMap)
  1592. doc.body.append(textarea)
  1593. textarea.addEventListener("click", async () => {
  1594. if (textarea.dataset.utags_type === "export") {
  1595. const urlMap2 = await getUrlMap()
  1596. textarea.value = JSON.stringify(urlMap2)
  1597. textarea.dataset.utags_type = "export_done"
  1598. textarea.click()
  1599. } else if (textarea.dataset.utags_type === "import") {
  1600. const data = textarea.value
  1601. try {
  1602. const result = await mergeData(JSON.parse(data))
  1603. textarea.value = JSON.stringify(result)
  1604. textarea.dataset.utags_type = "import_done"
  1605. textarea.click()
  1606. } catch (error) {
  1607. console.error(error)
  1608. textarea.value = JSON.stringify(error)
  1609. textarea.dataset.utags_type = "import_failed"
  1610. textarea.click()
  1611. }
  1612. }
  1613. })
  1614. }
  1615. }
  1616. function createModal(attributes) {
  1617. const div = addElement2(doc.body, "div", {
  1618. class: "utags_modal",
  1619. })
  1620. const wrapper = addElement2(div, "div", {
  1621. class: "utags_modal_wrapper",
  1622. })
  1623. const content = addElement2(wrapper, "div", attributes)
  1624. addClass(content, "utags_modal_content")
  1625. let removed = false
  1626. return {
  1627. remove() {
  1628. if (!removed) {
  1629. removed = true
  1630. div.remove()
  1631. }
  1632. },
  1633. getContentElement() {
  1634. return content
  1635. },
  1636. }
  1637. }
  1638. var pinnedTags
  1639. var mostUsedTags
  1640. var recentAddedTags
  1641. var emojiTags
  1642. var displayedTags = /* @__PURE__ */ new Set()
  1643. var currentTags = /* @__PURE__ */ new Set()
  1644. function onSelect(selected, input) {
  1645. if (selected) {
  1646. input.value = ""
  1647. const tags = splitTags(selected)
  1648. for (const tag of tags) {
  1649. if (tag.trim()) {
  1650. currentTags.add(tag.trim())
  1651. }
  1652. }
  1653. updateLists()
  1654. }
  1655. }
  1656. function removeTag(tag) {
  1657. if (tag) {
  1658. tag = tag.trim()
  1659. currentTags.delete(tag)
  1660. updateLists()
  1661. }
  1662. }
  1663. function updateLists() {
  1664. displayedTags = /* @__PURE__ */ new Set()
  1665. const ul1 = $(".utags_modal_content ul.utags_current_tags")
  1666. if (ul1) {
  1667. updateCurrentTagList(ul1)
  1668. }
  1669. const ul = $(".utags_modal_content ul.utags_select_list.utags_pined_list")
  1670. if (ul) {
  1671. updateCandidateTagList(ul, pinnedTags)
  1672. }
  1673. const ul4 = $(".utags_modal_content ul.utags_select_list.utags_emoji_list")
  1674. if (ul4) {
  1675. updateCandidateTagList(ul4, emojiTags)
  1676. }
  1677. const ul2 = $(".utags_modal_content ul.utags_select_list.utags_most_used")
  1678. if (ul2) {
  1679. updateCandidateTagList(ul2, mostUsedTags)
  1680. }
  1681. const ul3 = $(
  1682. ".utags_modal_content ul.utags_select_list.utags_recent_added"
  1683. )
  1684. if (ul3) {
  1685. updateCandidateTagList(ul3, recentAddedTags)
  1686. }
  1687. }
  1688. function updateCandidateTagList(ul, candidateTags) {
  1689. ul.textContent = ""
  1690. let index = 0
  1691. for (const text of candidateTags) {
  1692. if (displayedTags.has(text)) {
  1693. continue
  1694. }
  1695. displayedTags.add(text)
  1696. const li = addElement2(ul, "li", {})
  1697. addElement2(li, "span", {
  1698. textContent: text,
  1699. })
  1700. index++
  1701. if (index >= 50) {
  1702. break
  1703. }
  1704. }
  1705. }
  1706. function getNextList(parentElement) {
  1707. let parentNext = parentElement.nextElementSibling
  1708. while (parentNext && parentNext.children.length === 0) {
  1709. parentNext = parentNext.nextElementSibling
  1710. }
  1711. return parentNext
  1712. }
  1713. function getPreviousList(parentElement) {
  1714. let parentPrevious = parentElement.previousElementSibling
  1715. while (parentPrevious && parentPrevious.children.length === 0) {
  1716. parentPrevious = parentPrevious.previousElementSibling
  1717. }
  1718. return parentPrevious
  1719. }
  1720. function updateCurrentTagList(ul) {
  1721. ul.textContent = ""
  1722. const sortedTags = sortTags([...currentTags], emojiTags)
  1723. for (const tag of sortedTags) {
  1724. displayedTags.add(tag)
  1725. const li = addElement2(ul, "li")
  1726. const a = createTag(tag, {
  1727. isEmoji: emojiTags.includes(tag),
  1728. noLink: true,
  1729. })
  1730. li.append(a)
  1731. }
  1732. }
  1733. function removeAllActive(type) {
  1734. if (type !== 2) {
  1735. const selector = ".utags_modal_content ul.utags_select_list .utags_active"
  1736. for (const li of $$(selector)) {
  1737. removeClass(li, "utags_active")
  1738. }
  1739. }
  1740. if (type !== 1) {
  1741. const selector =
  1742. ".utags_modal_content ul.utags_select_list .utags_active2"
  1743. for (const li of $$(selector)) {
  1744. removeClass(li, "utags_active2")
  1745. }
  1746. }
  1747. }
  1748. async function copyCurrentTags(input) {
  1749. const value = sortTags([...currentTags], emojiTags).join(", ")
  1750. await copyText(value)
  1751. input.value = value
  1752. input.focus()
  1753. input.select()
  1754. }
  1755. function createPromptView(message, value, resolve) {
  1756. const modal = createModal({ class: "utags_prompt" })
  1757. const content = modal.getContentElement()
  1758. value = value || ""
  1759. addElement2(content, "span", {
  1760. class: "utags_title",
  1761. textContent: message,
  1762. })
  1763. const currentTagsWrapper = addElement2(content, "div", {
  1764. class: "utags_current_tags_wrapper",
  1765. })
  1766. addElement2(currentTagsWrapper, "span", {
  1767. textContent: "",
  1768. style: "display: none;",
  1769. "data-utags": "",
  1770. })
  1771. addElement2(currentTagsWrapper, "ul", {
  1772. class: "utags_current_tags utags_ul",
  1773. })
  1774. const input = addElement2(content, "input", {
  1775. type: "text",
  1776. placeholder: "foo, bar",
  1777. onblur(event) {
  1778. setTimeout(() => {
  1779. if (doc.activeElement === doc.body) {
  1780. closeModal2()
  1781. }
  1782. }, 1)
  1783. },
  1784. })
  1785. input.focus()
  1786. input.select()
  1787. addElement2(currentTagsWrapper, "button", {
  1788. class: "utags_button_copy",
  1789. textContent: i2("prompt.copy"),
  1790. async onclick() {
  1791. await copyCurrentTags(input)
  1792. },
  1793. })
  1794. const listWrapper = addElement2(content, "div", {
  1795. class: "utags_list_wrapper",
  1796. })
  1797. addElement2(listWrapper, "ul", {
  1798. class: "utags_select_list utags_pined_list",
  1799. "data-utags_list_name": i2("prompt.pinnedTags"),
  1800. })
  1801. addElement2(listWrapper, "ul", {
  1802. class: "utags_select_list utags_most_used",
  1803. "data-utags_list_name": i2("prompt.mostUsedTags"),
  1804. })
  1805. addElement2(listWrapper, "ul", {
  1806. class: "utags_select_list utags_recent_added",
  1807. "data-utags_list_name": i2("prompt.recentAddedTags"),
  1808. })
  1809. addElement2(listWrapper, "ul", {
  1810. class: "utags_select_list utags_emoji_list",
  1811. "data-utags_list_name": i2("prompt.emojiTags"),
  1812. })
  1813. updateLists()
  1814. const buttonWrapper = addElement2(content, "div", {
  1815. class: "utags_buttons_wrapper",
  1816. })
  1817. let closed = false
  1818. const closeModal2 = (value2) => {
  1819. if (closed) {
  1820. return
  1821. }
  1822. closed = true
  1823. removeEventListener(input, "keydown", keydonwHandler, true)
  1824. removeEventListener(doc, "keydown", keydonwHandler, true)
  1825. removeEventListener(doc, "mousedown", mousedownHandler, true)
  1826. removeEventListener(doc, "click", clickHandler, true)
  1827. removeEventListener(doc, "mouseover", mouseoverHandler, true)
  1828. modal.remove()
  1829. resolve(value2 == null ? null : value2)
  1830. }
  1831. const okHandler = () => {
  1832. closeModal2(Array.from(currentTags).join(","))
  1833. }
  1834. addElement2(buttonWrapper, "button", {
  1835. textContent: i2("prompt.cancel"),
  1836. onclick() {
  1837. closeModal2()
  1838. },
  1839. })
  1840. addElement2(buttonWrapper, "button", {
  1841. class: "utags_primary",
  1842. textContent: i2("prompt.ok"),
  1843. onclick() {
  1844. onSelect(input.value.trim(), input)
  1845. okHandler()
  1846. },
  1847. })
  1848. const keydonwHandler = (event) => {
  1849. if (event.defaultPrevented || !$(".utags_modal_content")) {
  1850. return
  1851. }
  1852. let current = $(".utags_modal_content ul.utags_select_list .utags_active")
  1853. switch (event.key) {
  1854. case "Escape": {
  1855. event.preventDefault()
  1856. closeModal2()
  1857. break
  1858. }
  1859. case "Enter": {
  1860. event.preventDefault()
  1861. input.focus()
  1862. if (current) {
  1863. onSelect(current.textContent, input)
  1864. } else if (input.value.trim()) {
  1865. onSelect(input.value.trim(), input)
  1866. } else {
  1867. okHandler()
  1868. }
  1869. break
  1870. }
  1871. case "Tab": {
  1872. event.preventDefault()
  1873. input.focus()
  1874. break
  1875. }
  1876. case "ArrowDown": {
  1877. event.preventDefault()
  1878. input.focus()
  1879. current = $(
  1880. ".utags_modal_content ul.utags_select_list .utags_active,.utags_modal_content ul.utags_select_list .utags_active2"
  1881. )
  1882. if (current) {
  1883. const next = current.nextElementSibling
  1884. if (next) {
  1885. next.scrollIntoView({ block: "end" })
  1886. removeAllActive()
  1887. addClass(next, "utags_active")
  1888. }
  1889. } else {
  1890. const next = $(".utags_modal_content ul.utags_select_list li")
  1891. if (next) {
  1892. next.scrollIntoView({ block: "end" })
  1893. removeAllActive()
  1894. addClass(next, "utags_active")
  1895. }
  1896. }
  1897. break
  1898. }
  1899. case "ArrowUp": {
  1900. event.preventDefault()
  1901. input.focus()
  1902. current = $(
  1903. ".utags_modal_content ul.utags_select_list .utags_active,.utags_modal_content ul.utags_select_list .utags_active2"
  1904. )
  1905. if (current) {
  1906. const previous = current.previousElementSibling
  1907. if (previous) {
  1908. previous.scrollIntoView({ block: "end" })
  1909. removeAllActive()
  1910. addClass(previous, "utags_active")
  1911. }
  1912. }
  1913. break
  1914. }
  1915. case "ArrowLeft": {
  1916. event.preventDefault()
  1917. input.focus()
  1918. current = $(
  1919. ".utags_modal_content ul.utags_select_list .utags_active,.utags_modal_content ul.utags_select_list .utags_active2"
  1920. )
  1921. if (current) {
  1922. const parentElement = current.parentElement
  1923. const index = Array.prototype.indexOf.call(
  1924. parentElement.children,
  1925. current
  1926. )
  1927. const parentPrevious = getPreviousList(parentElement)
  1928. if (parentPrevious) {
  1929. removeAllActive()
  1930. const newIndex = Math.min(
  1931. parentPrevious.children.length - 1,
  1932. index
  1933. )
  1934. const next = parentPrevious.children[newIndex]
  1935. next.scrollIntoView({ block: "end" })
  1936. addClass(next, "utags_active")
  1937. }
  1938. }
  1939. break
  1940. }
  1941. case "ArrowRight": {
  1942. event.preventDefault()
  1943. input.focus()
  1944. current = $(
  1945. ".utags_modal_content ul.utags_select_list .utags_active,.utags_modal_content ul.utags_select_list .utags_active2"
  1946. )
  1947. if (current) {
  1948. const parentElement = current.parentElement
  1949. const index = Array.prototype.indexOf.call(
  1950. parentElement.children,
  1951. current
  1952. )
  1953. const parentNext = getNextList(parentElement)
  1954. if (parentNext) {
  1955. removeAllActive()
  1956. const newIndex = Math.min(parentNext.children.length - 1, index)
  1957. const next = parentNext.children[newIndex]
  1958. next.scrollIntoView({ block: "end" })
  1959. addClass(next, "utags_active")
  1960. }
  1961. }
  1962. break
  1963. }
  1964. default: {
  1965. removeAllActive()
  1966. break
  1967. }
  1968. }
  1969. }
  1970. addEventListener(input, "keydown", keydonwHandler, true)
  1971. addEventListener(doc, "keydown", keydonwHandler, true)
  1972. const mousedownHandler = (event) => {
  1973. if (event.defaultPrevented || !$(".utags_modal_content")) {
  1974. return
  1975. }
  1976. const target = event.target
  1977. if (!target) {
  1978. return
  1979. }
  1980. if (target.closest(".utags_modal_content")) {
  1981. if (target === input) {
  1982. return
  1983. }
  1984. event.preventDefault()
  1985. input.focus()
  1986. } else {
  1987. event.preventDefault()
  1988. input.focus()
  1989. }
  1990. }
  1991. addEventListener(doc, "mousedown", mousedownHandler, true)
  1992. const clickHandler = (event) => {
  1993. if (event.defaultPrevented || !$(".utags_modal_content")) {
  1994. return
  1995. }
  1996. const target = event.target
  1997. if (!target) {
  1998. return
  1999. }
  2000. if (target.closest(".utags_modal_content")) {
  2001. input.focus()
  2002. if (target.closest(".utags_modal_content ul.utags_select_list li")) {
  2003. onSelect(target.textContent, input)
  2004. }
  2005. if (target.closest(".utags_modal_content ul.utags_current_tags li a")) {
  2006. removeTag(target.dataset.utags_tag)
  2007. }
  2008. } else {
  2009. closeModal2()
  2010. }
  2011. }
  2012. addEventListener(doc, "click", clickHandler, true)
  2013. const mouseoverHandler = (event) => {
  2014. const target = event.target
  2015. if (!(target == null ? void 0 : target.closest(".utags_modal_content"))) {
  2016. return
  2017. }
  2018. const li = target.closest("ul.utags_select_list li")
  2019. if (li) {
  2020. removeAllActive()
  2021. addClass(li, "utags_active2")
  2022. } else {
  2023. removeAllActive(2)
  2024. }
  2025. }
  2026. addEventListener(doc, "mousemove", mouseoverHandler, true)
  2027. }
  2028. async function advancedPrompt(message, value) {
  2029. pinnedTags = await getPinnedTags()
  2030. mostUsedTags = await getMostUsedTags()
  2031. recentAddedTags = await getRecentAddedTags()
  2032. emojiTags = await getEmojiTags()
  2033. currentTags = new Set(splitTags(value))
  2034. return new Promise((resolve) => {
  2035. createPromptView(message, value, resolve)
  2036. })
  2037. }
  2038. async function simplePrompt(message, value) {
  2039. return prompt(message, value)
  2040. }
  2041. var numberLimitOfShowAllUtagsInArea = 10
  2042. var lastShownArea
  2043. function hideAllUtagsInArea(target) {
  2044. const element = $(".utags_show_all")
  2045. if (!element) {
  2046. return
  2047. }
  2048. if (element === target || element.contains(target)) {
  2049. return
  2050. }
  2051. if (!target) {
  2052. lastShownArea = void 0
  2053. }
  2054. for (const element2 of $$(".utags_show_all")) {
  2055. addClass(element2, "utags_hide_all")
  2056. removeClass(element2, "utags_show_all")
  2057. setTimeout(() => {
  2058. removeClass(element2, "utags_hide_all")
  2059. })
  2060. }
  2061. }
  2062. function showAllUtagsInArea(element) {
  2063. if (!element) {
  2064. return false
  2065. }
  2066. const utags = $$(".utags_ul", element)
  2067. if (utags.length > 0 && utags.length <= numberLimitOfShowAllUtagsInArea) {
  2068. addClass(element, "utags_show_all")
  2069. return true
  2070. }
  2071. return false
  2072. }
  2073. function bindDocumentEvents() {
  2074. const eventType = isTouchScreen() ? "touchstart" : "click"
  2075. addEventListener(
  2076. doc,
  2077. eventType,
  2078. (event) => {
  2079. let target = event.target
  2080. if (!target) {
  2081. return
  2082. }
  2083. if (target.closest(".utags_prompt")) {
  2084. return
  2085. }
  2086. if (target.closest(".utags_ul")) {
  2087. const captainTag = target.closest(
  2088. ".utags_captain_tag,.utags_captain_tag2"
  2089. )
  2090. const textTag = target.closest(".utags_text_tag")
  2091. if (captainTag) {
  2092. event.preventDefault()
  2093. event.stopPropagation()
  2094. event.stopImmediatePropagation()
  2095. if (!captainTag.dataset.utags_key) {
  2096. return
  2097. }
  2098. setTimeout(async () => {
  2099. const key = captainTag.dataset.utags_key
  2100. const tags = captainTag.dataset.utags_tags
  2101. const meta = captainTag.dataset.utags_meta
  2102. ? JSON.parse(captainTag.dataset.utags_meta)
  2103. : void 0
  2104. const myPrompt = getSettingsValue("useSimplePrompt")
  2105. ? simplePrompt
  2106. : advancedPrompt
  2107. const newTags = await myPrompt(i2("prompt.addTags"), tags)
  2108. captainTag.focus()
  2109. if (key && newTags != void 0) {
  2110. const emojiTags3 = await getEmojiTags()
  2111. const newTagsArray = sortTags(splitTags(newTags), emojiTags3)
  2112. await saveTags(key, newTagsArray, meta)
  2113. }
  2114. })
  2115. } else if (textTag) {
  2116. event.stopPropagation()
  2117. event.stopImmediatePropagation()
  2118. }
  2119. return
  2120. }
  2121. hideAllUtagsInArea(target)
  2122. const targets = []
  2123. do {
  2124. targets.push(target)
  2125. target = target.parentElement
  2126. } while (targets.length <= 8 && target)
  2127. while (targets.length > 0) {
  2128. const area = targets.pop()
  2129. if (showAllUtagsInArea(area)) {
  2130. if (lastShownArea === area) {
  2131. hideAllUtagsInArea()
  2132. return
  2133. }
  2134. lastShownArea = area
  2135. return
  2136. }
  2137. }
  2138. lastShownArea = void 0
  2139. },
  2140. true
  2141. )
  2142. addEventListener(
  2143. doc,
  2144. "keydown",
  2145. (event) => {
  2146. if (event.defaultPrevented) {
  2147. return
  2148. }
  2149. if (event.key === "Escape" && $(".utags_show_all")) {
  2150. hideAllUtagsInArea()
  2151. event.preventDefault()
  2152. }
  2153. },
  2154. true
  2155. )
  2156. }
  2157. function bindWindowEvents() {
  2158. extendHistoryApi()
  2159. addEventListener(window, "locationchange", function () {
  2160. hideAllUtagsInArea()
  2161. })
  2162. }
  2163. var getCanonicalUrl = (url) =>
  2164. deleteUrlParameters(url, ["utm_campaign", "utm_source", "utm_medium"])
  2165. var site = {
  2166. matches: /.*/,
  2167. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  2168. excludeSelectors: [".browser_extension_settings_container"],
  2169. getCanonicalUrl,
  2170. }
  2171. var default_default = site
  2172. function getCanonicalUrl2(url) {
  2173. if (url.startsWith("https://links.pipecraft")) {
  2174. url = url.replace("https://links.pipecraft.net/", "https://")
  2175. }
  2176. if (url.includes("v2ex.com")) {
  2177. return url
  2178. .replace(/[?#].*/, "")
  2179. .replace(/(\w+\.)?v2ex.com/, "www.v2ex.com")
  2180. }
  2181. return url
  2182. }
  2183. function cloneWithoutCitedReplies(element) {
  2184. const newElement = element.cloneNode(true)
  2185. for (const cell of $$(".cell", newElement)) {
  2186. cell.remove()
  2187. }
  2188. return newElement
  2189. }
  2190. var site2 = {
  2191. matches: /v2ex\.com|v2hot\./,
  2192. listNodesSelectors: [".box .cell", ".my-box .comment"],
  2193. conditionNodesSelectors: [
  2194. ".box .cell .topic-link",
  2195. ".item_hot_topic_title a",
  2196. '.box .cell .topic_info strong:first-of-type a[href*="/member/"]',
  2197. ".box .cell .topic_info .node",
  2198. '.box .cell strong a.dark[href*="/member/"]',
  2199. ".box .cell .ago a",
  2200. ".box .cell .fade.small a",
  2201. ".comment .username",
  2202. ".comment .ago",
  2203. ],
  2204. matchedNodesSelectors: [
  2205. 'a[href*="/t/"]',
  2206. 'a[href*="/member/"]',
  2207. 'a[href*="/go/"]',
  2208. 'a[href^="https://"]:not([href*="v2ex.com"])',
  2209. 'a[href^="http://"]:not([href*="v2ex.com"])',
  2210. ],
  2211. excludeSelectors: [
  2212. ...default_default.excludeSelectors,
  2213. ".utags_text_tag",
  2214. ".site-nav a",
  2215. ".cell_tabs a",
  2216. ".tab-alt-container a",
  2217. "#SecondaryTabs a",
  2218. "a.page_normal,a.page_current",
  2219. "a.count_livid",
  2220. ".post-item a.post-content",
  2221. ],
  2222. addExtraMatchedNodes(matchedNodesSet) {
  2223. if (location.pathname.includes("/member/")) {
  2224. const profile = $(".content h1")
  2225. if (profile) {
  2226. const username = profile.textContent
  2227. if (username) {
  2228. const key = "https://www.v2ex.com/member/".concat(username)
  2229. const meta = { title: username, type: "user" }
  2230. profile.utags = { key, meta }
  2231. matchedNodesSet.add(profile)
  2232. }
  2233. }
  2234. }
  2235. if (location.pathname.includes("/t/")) {
  2236. const header = $(".header h1")
  2237. if (header) {
  2238. const key = getCanonicalUrl2(
  2239. "https://www.v2ex.com" + location.pathname
  2240. )
  2241. const title = $("h1").textContent
  2242. const meta = { title, type: "topic" }
  2243. header.utags = { key, meta }
  2244. matchedNodesSet.add(header)
  2245. }
  2246. const main2 = $("#Main") || $(".content")
  2247. const replyElements = $$(
  2248. '.box .cell[id^="r_"],.box .cell[id^="related_r_"]',
  2249. main2
  2250. )
  2251. for (const reply of replyElements) {
  2252. const replyId = reply.id.replace("related_", "")
  2253. const floorNoElement = $(".no", reply)
  2254. const replyContentElement = $(".reply_content", reply)
  2255. const agoElement = $(".ago,.fade.small", reply)
  2256. if (replyId && floorNoElement && replyContentElement && agoElement) {
  2257. let newAgoElement = $("a", agoElement)
  2258. if (!newAgoElement) {
  2259. newAgoElement = createElement("a", {
  2260. textContent: agoElement.textContent,
  2261. href: "#" + replyId,
  2262. })
  2263. agoElement.textContent = ""
  2264. agoElement.append(newAgoElement)
  2265. }
  2266. const floorNo = parseInt10(floorNoElement.textContent, 1)
  2267. const pageNo = Math.floor((floorNo - 1) / 100) + 1
  2268. const key =
  2269. getCanonicalUrl2("https://www.v2ex.com" + location.pathname) +
  2270. "?p=" +
  2271. String(pageNo) +
  2272. "#" +
  2273. replyId
  2274. const title =
  2275. cloneWithoutCitedReplies(replyContentElement).textContent
  2276. const meta = { title, type: "reply" }
  2277. newAgoElement.utags = { key, meta }
  2278. matchedNodesSet.add(newAgoElement)
  2279. }
  2280. }
  2281. }
  2282. if (location.pathname.includes("/go/")) {
  2283. const header = $(".cell_ops.flex-one-row input")
  2284. if (header) {
  2285. const key = getCanonicalUrl2(
  2286. "https://www.v2ex.com" + location.pathname
  2287. )
  2288. const title = document.title.replace(/.*›\s*/, "").trim()
  2289. const meta = { title, type: "node" }
  2290. header.utags = { key, meta }
  2291. matchedNodesSet.add(header)
  2292. }
  2293. }
  2294. },
  2295. getCanonicalUrl: getCanonicalUrl2,
  2296. }
  2297. var v2ex_default = site2
  2298. var greasyfork_org_default =
  2299. ":not(#a):not(#b):not(#c) .discussion-title+.utags_ul_0{display:block !important;height:0}:not(#a):not(#b):not(#c) .discussion-title+.utags_ul_0 .utags_captain_tag{top:-26px;background-color:rgba(255,255,255,.8666666667) !important}:not(#a):not(#b):not(#c) .discussion-title+.utags_ul_1{display:block !important;margin-top:-12px !important;margin-bottom:8px !important}:not(#a):not(#b):not(#c) .discussion-meta .script-link+.utags_ul_0{display:block !important;height:0}:not(#a):not(#b):not(#c) .discussion-meta .script-link+.utags_ul_0 .utags_captain_tag{top:-22px;background-color:rgba(255,255,255,.8666666667) !important}"
  2300. function getScriptUrl(url) {
  2301. return getCanonicalUrl3(url.replace(/(scripts\/\d+)(.*)/, "$1"))
  2302. }
  2303. function getCanonicalUrl3(url) {
  2304. if (/(greasyfork|sleazyfork)\.org/.test(url)) {
  2305. url = url.replace(
  2306. /((greasyfork|sleazyfork)\.org\/)(\w{2}(-\w{2})?)(\/|$)/,
  2307. "$1"
  2308. )
  2309. if (url.includes("/scripts/")) {
  2310. return url.replace(/(scripts\/\d+)([^/]*)/, "$1")
  2311. }
  2312. if (url.includes("/users/")) {
  2313. return url.replace(/(users\/\d+)(.*)/, "$1")
  2314. }
  2315. }
  2316. return url
  2317. }
  2318. var site3 = {
  2319. matches: /(greasyfork|sleazyfork)\.org/,
  2320. listNodesSelectors: [".script-list > li", ".discussion-list-container"],
  2321. conditionNodesSelectors: [
  2322. ".script-list li .script-link",
  2323. ".script-list li .script-list-author a",
  2324. ".discussion-list-container .script-link",
  2325. ".discussion-list-container .discussion-title",
  2326. ".discussion-list-container .discussion-meta-item:nth-child(2) > a",
  2327. ],
  2328. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  2329. excludeSelectors: [
  2330. ...default_default.excludeSelectors,
  2331. ".sidebar",
  2332. ".pagination",
  2333. ".sign-out-link,.sign-in-link",
  2334. ".with-submenu",
  2335. "#script-links.tabs",
  2336. "#install-area",
  2337. ".history_versions .version-number",
  2338. 'a[href*="show_all_versions"]',
  2339. 'a[href*="/reports/new"]',
  2340. 'a[href*="/conversations/new"]',
  2341. 'a[href*="/discussions/mark_all_read"]',
  2342. 'a[href*="/discussions/new"]',
  2343. "div.sidebarred-main-content > p:nth-child(3) > a",
  2344. ],
  2345. addExtraMatchedNodes(matchedNodesSet) {
  2346. if (location.pathname.includes("/scripts/")) {
  2347. const element = $("#script-info header h2")
  2348. if (element) {
  2349. const title = element.textContent
  2350. if (title) {
  2351. const key = getScriptUrl(location.href)
  2352. const meta = { title }
  2353. element.utags = { key, meta }
  2354. matchedNodesSet.add(element)
  2355. }
  2356. }
  2357. } else if (location.pathname.includes("/users/")) {
  2358. const element = $("#about-user h2")
  2359. if (element) {
  2360. const title = element.textContent
  2361. if (title) {
  2362. const key = getCanonicalUrl3(location.href)
  2363. const meta = { title }
  2364. element.utags = { key, meta }
  2365. matchedNodesSet.add(element)
  2366. }
  2367. }
  2368. }
  2369. },
  2370. getCanonicalUrl: getCanonicalUrl3,
  2371. getStyle: () => greasyfork_org_default,
  2372. }
  2373. var greasyfork_org_default2 = site3
  2374. function cloneComment(element) {
  2375. const newElement = element.cloneNode(true)
  2376. for (const node of $$(".reply", newElement)) {
  2377. node.remove()
  2378. }
  2379. return newElement
  2380. }
  2381. var site4 = {
  2382. matches: /news\.ycombinator\.com/,
  2383. listNodesSelectors: [".script-list li", ".discussion-list-container"],
  2384. conditionNodesSelectors: [],
  2385. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  2386. excludeSelectors: [
  2387. ...default_default.excludeSelectors,
  2388. ".pagetop",
  2389. ".morelink",
  2390. ".hnpast",
  2391. ".clicky",
  2392. ".navs > a",
  2393. 'a[href^="login"]',
  2394. 'a[href^="logout"]',
  2395. 'a[href^="forgot"]',
  2396. 'a[href^="vote"]',
  2397. 'a[href^="submit"]',
  2398. 'a[href^="hide"]',
  2399. 'a[href^="fave"]',
  2400. 'a[href^="reply"]',
  2401. 'a[href^="context"]',
  2402. 'a[href^="newcomments"]',
  2403. 'a[href^="#"]',
  2404. '.subline > a[href^="item"]',
  2405. ],
  2406. addExtraMatchedNodes(matchedNodesSet) {
  2407. if (location.pathname === "/item") {
  2408. const comments = $$(".comment-tree .comtr[id]")
  2409. for (const comment of comments) {
  2410. const commentText = $(".commtext", comment)
  2411. const target = $(".age a", comment)
  2412. if (commentText && target) {
  2413. const key = target.href
  2414. const title = cloneComment(commentText).textContent
  2415. if (key && title) {
  2416. const meta = { title, type: "comment" }
  2417. target.utags = { key, meta }
  2418. matchedNodesSet.add(target)
  2419. }
  2420. }
  2421. }
  2422. const fatitem = $(".fatitem")
  2423. if (fatitem) {
  2424. const titleElement = $(".titleline a", fatitem)
  2425. const commentText = titleElement || $(".commtext", fatitem)
  2426. const type = titleElement ? "topic" : "comment"
  2427. const target = $(".age a", fatitem)
  2428. if (commentText && target) {
  2429. const key = target.href
  2430. const title = cloneComment(commentText).textContent
  2431. if (key && title) {
  2432. const meta = { title, type }
  2433. target.utags = { key, meta }
  2434. matchedNodesSet.add(target)
  2435. }
  2436. }
  2437. }
  2438. } else if (location.pathname === "/newcomments") {
  2439. const comments = $$(".athing[id]")
  2440. for (const comment of comments) {
  2441. const commentText = $(".commtext", comment)
  2442. const target = $(".age a", comment)
  2443. if (commentText && target) {
  2444. const key = target.href
  2445. const title = cloneComment(commentText).textContent
  2446. if (key && title) {
  2447. const meta = { title, type: "comment" }
  2448. target.utags = { key, meta }
  2449. matchedNodesSet.add(target)
  2450. }
  2451. }
  2452. }
  2453. } else {
  2454. const topics = $$(".athing[id]")
  2455. for (const topic of topics) {
  2456. const titleElement = $(".titleline a", topic)
  2457. const subtext = topic.nextElementSibling
  2458. if (subtext) {
  2459. const target = $(".age a", subtext)
  2460. if (titleElement && target) {
  2461. const key = target.href
  2462. const title = titleElement.textContent
  2463. if (key && title) {
  2464. const meta = { title, type: "topic" }
  2465. target.utags = { key, meta }
  2466. matchedNodesSet.add(target)
  2467. }
  2468. }
  2469. }
  2470. }
  2471. }
  2472. },
  2473. }
  2474. var news_ycombinator_com_default = site4
  2475. var site5 = {
  2476. matches: /lobste\.rs|dto\.pipecraft\.net|tilde\.news|journalduhacker\.net/,
  2477. listNodesSelectors: [],
  2478. conditionNodesSelectors: [],
  2479. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  2480. excludeSelectors: [
  2481. ...default_default.excludeSelectors,
  2482. "#nav",
  2483. "#header",
  2484. "#subnav",
  2485. ".mobile_comments",
  2486. ".description_present",
  2487. ".morelink",
  2488. ".user_tree",
  2489. ".dropdown_parent",
  2490. 'a[href^="/login"]',
  2491. 'a[href^="/logout"]',
  2492. 'a[href^="/u#"]',
  2493. 'a[href$="/save"]',
  2494. 'a[href$="/hide"]',
  2495. 'a[href$="/suggest"]',
  2496. ],
  2497. }
  2498. var lobste_rs_default = site5
  2499. var github_com_default =
  2500. ':not(#a):not(#b):not(#c) .search-title .utags_ul_0,:not(#a):not(#b):not(#c) .d-flex.flex-justify-between a[href^="/topics/"]+.utags_ul_0,:not(#a):not(#b):not(#c) .d-md-flex.flex-justify-between a[href^="/topics/"].d-flex+.utags_ul_0,:not(#a):not(#b):not(#c) [id=user-starred-repos] a[href^="/topics/"].flex-items-center+.utags_ul_0,:not(#a):not(#b):not(#c) ul.f4 a[href^="/topics/"].d-flex+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-4);--utags-notag-ul-height: var(--utags-notag-ul-height-4);--utags-notag-ul-position: var(--utags-notag-ul-position-4);--utags-notag-ul-top: var(--utags-notag-ul-top-4);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-4);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-4);--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}:not(#a):not(#b):not(#c) .search-title .utags_ul_0{--utags-notag-captain-tag-top: -10px}:not(#a):not(#b):not(#c) .d-flex.flex-justify-between a[href^="/topics/"]+.utags_ul_0{--utags-notag-captain-tag-top: 6px;--utags-notag-captain-tag-left: 76px}:not(#a):not(#b):not(#c) .d-md-flex.flex-justify-between a[href^="/topics/"].d-flex+.utags_ul_0{--utags-notag-captain-tag-top: 20px;--utags-notag-captain-tag-left: 76px}:not(#a):not(#b):not(#c) ul.f4 a[href^="/topics/"].d-flex+.utags_ul_0{--utags-notag-captain-tag-top: -24px;--utags-notag-captain-tag-left: -2px}:not(#a):not(#b):not(#c) div[id=repo-title-component] strong[itemprop=name] a+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-4);--utags-notag-ul-height: var(--utags-notag-ul-height-4);--utags-notag-ul-position: var(--utags-notag-ul-position-4);--utags-notag-ul-top: var(--utags-notag-ul-top-4);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-4);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-4)}'
  2501. var noneUsers = /* @__PURE__ */ new Set([
  2502. "about",
  2503. "pricing",
  2504. "security",
  2505. "login",
  2506. "logout",
  2507. "signup",
  2508. "explore",
  2509. "topics",
  2510. "trending",
  2511. "collections",
  2512. "events",
  2513. "sponsors",
  2514. "features",
  2515. "enterprise",
  2516. "team",
  2517. "customer-stories",
  2518. "readme",
  2519. "premium-support",
  2520. "sitemap",
  2521. "git-guides",
  2522. "open-source",
  2523. "marketplace",
  2524. "codespaces",
  2525. "issues",
  2526. "pulls",
  2527. "discussions",
  2528. "dashboard",
  2529. "account",
  2530. "new",
  2531. "notifications",
  2532. "settings",
  2533. "feedback",
  2534. "organizations",
  2535. "github-copilot",
  2536. "search",
  2537. ])
  2538. var prefix2 = "https://github.com/"
  2539. function getUserProfileUrl(href) {
  2540. if (href.startsWith(prefix2)) {
  2541. const href2 = href.slice(19)
  2542. let username = ""
  2543. if (/^[\w-]+$/.test(href2)) {
  2544. username = /^([\w-]+)$/.exec(href2)[1]
  2545. }
  2546. if (/(author%3A|author=)[\w-]+/.test(href2)) {
  2547. username = /(author%3A|author=)([\w-]+)/.exec(href2)[2]
  2548. }
  2549. if (username && !noneUsers.has(username)) {
  2550. return prefix2 + username
  2551. }
  2552. }
  2553. return void 0
  2554. }
  2555. function getRepoUrl(href) {
  2556. if (href.startsWith(prefix2)) {
  2557. const href2 = href.slice(19)
  2558. if (/^[\w-]+\/[\w-.]+(\?.*)?$/.test(href2)) {
  2559. const username = /^([\w-]+)/.exec(href2)[1]
  2560. if (username && !noneUsers.has(username)) {
  2561. return prefix2 + href2.replace(/(^[\w-]+\/[\w-.]+).*/, "$1")
  2562. }
  2563. }
  2564. }
  2565. return void 0
  2566. }
  2567. function getTopicsUrl(href) {
  2568. if (href.startsWith(prefix2)) {
  2569. const href2 = href.slice(19)
  2570. if (/^topics\/[\w-.]+(\?.*)?$/.test(href2)) {
  2571. return prefix2 + href2.replace(/(^topics\/[\w-.]+).*/, "$1")
  2572. }
  2573. }
  2574. return void 0
  2575. }
  2576. function getIssuesUrl(href) {
  2577. if (href.startsWith(prefix2)) {
  2578. const href2 = href.slice(19)
  2579. if (
  2580. /^[\w-]+\/[\w-.]+\/(issues|pull|discussions)\/\d+(\?.*)?$/.test(href2)
  2581. ) {
  2582. const username = /^([\w-]+)/.exec(href2)[1]
  2583. if (username && !noneUsers.has(username)) {
  2584. return (
  2585. prefix2 +
  2586. href2.replace(
  2587. /(^[\w-]+\/[\w-.]+\/(issues|pull|discussions)\/\d+).*/,
  2588. "$1"
  2589. )
  2590. )
  2591. }
  2592. }
  2593. }
  2594. return void 0
  2595. }
  2596. var site6 = {
  2597. matches: /github\.com/,
  2598. listNodesSelectors: [],
  2599. conditionNodesSelectors: [],
  2600. getMatchedNodes() {
  2601. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  2602. const href = element.href
  2603. if (href.startsWith(prefix2)) {
  2604. if (/since|until/.test(href)) {
  2605. return false
  2606. }
  2607. let key = getUserProfileUrl(href)
  2608. if (key) {
  2609. const username = /^https:\/\/github\.com\/([\w-]+)$/.exec(key)[1]
  2610. const title = username
  2611. const meta = { title, type: "user" }
  2612. element.utags = { key, meta }
  2613. return true
  2614. }
  2615. key = getRepoUrl(href)
  2616. if (key) {
  2617. const title = key.replace(prefix2, "")
  2618. const meta = { title, type: "repo" }
  2619. element.utags = { key, meta }
  2620. return true
  2621. }
  2622. key = getTopicsUrl(href)
  2623. if (key) {
  2624. const text = element.textContent.trim()
  2625. if (text === "#") {
  2626. return false
  2627. }
  2628. const title = "#" + key.replace(prefix2 + "topics/", "")
  2629. const meta = { title, type: "topic" }
  2630. element.utags = { key, meta }
  2631. return true
  2632. }
  2633. key = getIssuesUrl(href)
  2634. if (key) {
  2635. const meta = { type: "issue" }
  2636. element.utags = { key, meta }
  2637. return true
  2638. }
  2639. return false
  2640. }
  2641. return true
  2642. })
  2643. },
  2644. excludeSelectors: [
  2645. ...default_default.excludeSelectors,
  2646. 'section[aria-label~="User"] .Link--secondary',
  2647. ".Popover-message .Link--secondary",
  2648. ".IssueLabel",
  2649. ".subnav-links",
  2650. ".btn",
  2651. ".filter-item",
  2652. ".js-github-dev-shortcut",
  2653. ".js-github-dev-new-tab-shortcut",
  2654. ".js-skip-to-content",
  2655. ],
  2656. validMediaSelectors: ["svg.octicon-repo"],
  2657. getStyle: () => github_com_default,
  2658. }
  2659. var github_com_default2 = site6
  2660. var site7 = {
  2661. matches: /reddit\.com/,
  2662. getMatchedNodes() {
  2663. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  2664. const href = element.href
  2665. const textContent = element.textContent || ""
  2666. if (/^https:\/\/www\.reddit\.com\/user\/\w+\/$/.test(href)) {
  2667. if (/overview/i.test(textContent)) {
  2668. return false
  2669. }
  2670. return true
  2671. }
  2672. if (/^https:\/\/www\.reddit\.com\/r\/\w+\/$/.test(href)) {
  2673. if (/posts/i.test(textContent)) {
  2674. return false
  2675. }
  2676. return true
  2677. }
  2678. return false
  2679. })
  2680. },
  2681. excludeSelectors: [
  2682. ...default_default.excludeSelectors,
  2683. 'a[data-testid="comment_author_icon"]',
  2684. ],
  2685. }
  2686. var reddit_com_default = site7
  2687. var prefix3 = "https://x.com/"
  2688. var prefix22 = "https://twitter.com/"
  2689. var site8 = {
  2690. matches: /x\.com|twitter\.com/,
  2691. getMatchedNodes() {
  2692. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  2693. const href = element.href
  2694. if (href.startsWith(prefix3) || href.startsWith(prefix22)) {
  2695. const href2 = href.startsWith(prefix22)
  2696. ? href.slice(20)
  2697. : href.slice(14)
  2698. if (/^\w+$/.test(href2)) {
  2699. if (
  2700. /^(home|explore|notifications|messages|tos|privacy)$/.test(href2)
  2701. ) {
  2702. return false
  2703. }
  2704. const textContent = element.textContent || ""
  2705. if (!textContent.startsWith("@")) {
  2706. return false
  2707. }
  2708. const parent = element.parentElement
  2709. setStyle(parent, { display: "flex", flexDirection: "row" })
  2710. const parentSibling = parent.nextSibling
  2711. if (
  2712. parentSibling &&
  2713. parentSibling.textContent &&
  2714. parentSibling.textContent.includes("\xB7")
  2715. ) {
  2716. setStyle(parentSibling, {
  2717. paddingLeft: "10px",
  2718. paddingRight: "10px",
  2719. })
  2720. }
  2721. const meta = { type: "user" }
  2722. element.utags = { meta }
  2723. return true
  2724. }
  2725. }
  2726. return false
  2727. })
  2728. },
  2729. excludeSelectors: [...default_default.excludeSelectors],
  2730. addExtraMatchedNodes(matchedNodesSet) {
  2731. const elements = $$('[data-testid="UserName"] span')
  2732. for (const element of elements) {
  2733. const title = element.textContent.trim()
  2734. if (!title || !title.startsWith("@")) {
  2735. continue
  2736. }
  2737. const key = prefix3 + title.slice(1)
  2738. const meta = { title, type: "user" }
  2739. element.utags = { key, meta }
  2740. matchedNodesSet.add(element)
  2741. }
  2742. },
  2743. }
  2744. var twitter_com_default = site8
  2745. function getCanonicalUrl4(url) {
  2746. if (url.startsWith("http://mp.weixin.qq.com")) {
  2747. url = url.replace(/^http:/, "https:")
  2748. }
  2749. if (url.startsWith("https://mp.weixin.qq.com/s/")) {
  2750. url = url.replace(/(\/s\/[\w-]+).*/, "$1")
  2751. }
  2752. if (url.startsWith("https://mp.weixin.qq.com/") && url.includes("#")) {
  2753. url = url.replace(/#.*/, "")
  2754. }
  2755. return url
  2756. }
  2757. var site9 = {
  2758. matches: /mp\.weixin\.qq\.com/,
  2759. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  2760. excludeSelectors: [...default_default.excludeSelectors],
  2761. addExtraMatchedNodes(matchedNodesSet) {
  2762. const element = $("h1.rich_media_title")
  2763. if (element) {
  2764. const title = element.textContent.trim()
  2765. if (title) {
  2766. const key = getCanonicalUrl4(location.href)
  2767. const meta = { title }
  2768. element.utags = { key, meta }
  2769. matchedNodesSet.add(element)
  2770. }
  2771. }
  2772. },
  2773. getCanonicalUrl: getCanonicalUrl4,
  2774. }
  2775. var mp_weixin_qq_com_default = site9
  2776. var instagram_com_default =
  2777. ":not(#a):not(#b):not(#c) [data-utags_node_type=notag_relative]+.utags_ul_0 .utags_captain_tag{position:relative !important;width:14px !important;height:14px !important;padding:1px 0 0 1px !important}"
  2778. var site10 = {
  2779. matches: /instagram\.com/,
  2780. getMatchedNodes() {
  2781. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  2782. const href = element.href
  2783. if (href.startsWith("https://www.instagram.com/")) {
  2784. const href2 = href.slice(26)
  2785. if (/^[\w.]+\/$/.test(href2)) {
  2786. if (/^(explore|reels)\/$/.test(href2)) {
  2787. return false
  2788. }
  2789. if ($("div span", element)) {
  2790. element.dataset.utags_node_type = "notag_relative"
  2791. }
  2792. const meta = { type: "user" }
  2793. element.utags = { meta }
  2794. return true
  2795. }
  2796. }
  2797. return false
  2798. })
  2799. },
  2800. excludeSelectors: [...default_default.excludeSelectors],
  2801. getStyle: () => instagram_com_default,
  2802. }
  2803. var instagram_com_default2 = site10
  2804. var threads_net_default =
  2805. ':not(#a):not(#b):not(#c) a[href^="/@"][data-utags]+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-4);--utags-notag-ul-height: var(--utags-notag-ul-height-4);--utags-notag-ul-position: var(--utags-notag-ul-position-4);--utags-notag-ul-top: var(--utags-notag-ul-top-4);--utags-notag-captain-tag-top: -22px;--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-4)}'
  2806. function getUserProfileUrl2(url) {
  2807. if (url.startsWith("https://www.threads.net/")) {
  2808. const href2 = url.slice(24)
  2809. if (/^@[\w.]+/.test(href2)) {
  2810. return (
  2811. "https://www.threads.net/" +
  2812. href2.replace(/(^@[\w.]+).*/, "$1").toLowerCase()
  2813. )
  2814. }
  2815. }
  2816. return void 0
  2817. }
  2818. var site11 = {
  2819. matches: /threads\.net/,
  2820. getMatchedNodes() {
  2821. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  2822. const href = element.href
  2823. if (href.startsWith("https://www.threads.net/")) {
  2824. const href2 = href.slice(24)
  2825. if (/^@[\w.]+$/.test(href2)) {
  2826. const meta = { type: "user" }
  2827. element.utags = { meta }
  2828. return true
  2829. }
  2830. }
  2831. return false
  2832. })
  2833. },
  2834. excludeSelectors: [...default_default.excludeSelectors, '[role="tablist"]'],
  2835. addExtraMatchedNodes(matchedNodesSet) {
  2836. const element = $("h1+div>div>span,h2+div>div>span")
  2837. if (element) {
  2838. const title = element.textContent.trim()
  2839. const key = getUserProfileUrl2(location.href)
  2840. if (title && key && key === "https://www.threads.net/@" + title) {
  2841. const meta = { title, type: "user" }
  2842. element.utags = { key, meta }
  2843. matchedNodesSet.add(element)
  2844. }
  2845. }
  2846. },
  2847. getStyle: () => threads_net_default,
  2848. }
  2849. var threads_net_default2 = site11
  2850. function getUserProfileUrl3(href) {
  2851. if (
  2852. href.startsWith("https://www.facebook.com/") ||
  2853. href.startsWith("https://m.facebook.com/")
  2854. ) {
  2855. const href2 = href.startsWith("https://m.facebook.com/")
  2856. ? href.slice(23)
  2857. : href.slice(25)
  2858. if (/^[\w.]+/.test(href2)) {
  2859. return "https://www.facebook.com/" + href2.replace(/(^[\w.]+).*/, "$1")
  2860. }
  2861. }
  2862. return void 0
  2863. }
  2864. var site12 = {
  2865. matches: /facebook\.com/,
  2866. getMatchedNodes() {
  2867. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  2868. const href = element.href
  2869. if (
  2870. href.startsWith("https://www.facebook.com/") ||
  2871. href.startsWith("https://m.facebook.com/")
  2872. ) {
  2873. const pathname = element.pathname
  2874. if (/^\/[\w.]+$/.test(pathname)) {
  2875. if (
  2876. /^\/(policies|events|profile\.php|permalink\.php|photo\.php|\w+\.php)$/.test(
  2877. pathname
  2878. )
  2879. ) {
  2880. return false
  2881. }
  2882. const key = "https://www.facebook.com" + pathname
  2883. const meta = { type: "user" }
  2884. element.utags = { key, meta }
  2885. return true
  2886. }
  2887. }
  2888. return false
  2889. })
  2890. },
  2891. excludeSelectors: [...default_default.excludeSelectors],
  2892. addExtraMatchedNodes(matchedNodesSet) {
  2893. const element = getFirstHeadElement("h1")
  2894. if (element) {
  2895. const title = element.textContent.trim()
  2896. const key = getUserProfileUrl3(location.href)
  2897. if (title && key) {
  2898. const meta = { title, type: "user" }
  2899. element.utags = { key, meta }
  2900. matchedNodesSet.add(element)
  2901. }
  2902. }
  2903. },
  2904. }
  2905. var facebook_com_default = site12
  2906. var youtube_com_default =
  2907. ":not(#a):not(#b):not(#c) ytd-rich-item-renderer h3.ytd-rich-grid-media .utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-2);--utags-notag-ul-height: var(--utags-notag-ul-height-2);--utags-notag-ul-position: var(--utags-notag-ul-position-2);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-2);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-2);--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}:not(#a):not(#b):not(#c) ytd-rich-item-renderer yt-formatted-string[ellipsis-truncate-styling] .utags_ul_0 .utags_captain_tag{left:-20px}:not(#a):not(#b):not(#c) ytd-video-renderer.ytd-item-section-renderer h3 .utags_ul_0,:not(#a):not(#b):not(#c) ytd-video-renderer.ytd-vertical-list-renderer h3 .utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-2);--utags-notag-ul-height: var(--utags-notag-ul-height-2);--utags-notag-ul-position: var(--utags-notag-ul-position-2);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-2);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-2);--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}:not(#a):not(#b):not(#c) ytd-video-renderer.ytd-item-section-renderer yt-formatted-string.ytd-channel-name .utags_ul_0 .utags_captain_tag,:not(#a):not(#b):not(#c) ytd-video-renderer.ytd-vertical-list-renderer yt-formatted-string.ytd-channel-name .utags_ul_0 .utags_captain_tag{left:-20px}:not(#a):not(#b):not(#c) .watch-active-metadata ytd-channel-name yt-formatted-string .utags_ul_0,:not(#a):not(#b):not(#c) ytd-comment-thread-renderer h3.ytd-comment-renderer .utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-2);--utags-notag-ul-height: var(--utags-notag-ul-height-2);--utags-notag-ul-position: var(--utags-notag-ul-position-2);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-2);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-2);--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}:not(#a):not(#b):not(#c) .ytd-shorts ytd-reel-video-renderer ytd-channel-name yt-formatted-string .utags_ul_0 .utags_captain_tag{left:-24px}:not(#a):not(#b):not(#c) [hidden]+.utags_ul{display:none !important}"
  2908. var prefix4 = "https://www.youtube.com/"
  2909. var prefix23 = "https://m.youtube.com/"
  2910. function getUserProfileUrl4(href) {
  2911. if (href.startsWith(prefix4) || href.startsWith(prefix23)) {
  2912. const href2 = href.startsWith(prefix23) ? href.slice(22) : href.slice(24)
  2913. if (/^@[\w-]+/.test(href2)) {
  2914. return prefix4 + href2.replace(/(^@[\w-]+).*/, "$1")
  2915. }
  2916. if (/^channel\/[\w-]+/.test(href2)) {
  2917. return prefix4 + href2.replace(/(^channel\/[\w-]+).*/, "$1")
  2918. }
  2919. }
  2920. return void 0
  2921. }
  2922. function getVideoUrl(href) {
  2923. if (href.startsWith(prefix4) || href.startsWith(prefix23)) {
  2924. const href2 = href.startsWith(prefix23) ? href.slice(22) : href.slice(24)
  2925. if (href2.includes("&lc=")) {
  2926. return void 0
  2927. }
  2928. if (/^watch\?v=[\w-]+/.test(href2)) {
  2929. return prefix4 + href2.replace(/(watch\?v=[\w-]+).*/, "$1")
  2930. }
  2931. if (/^shorts\/[\w-]+/.test(href2)) {
  2932. return prefix4 + href2.replace(/(^shorts\/[\w-]+).*/, "$1")
  2933. }
  2934. }
  2935. return void 0
  2936. }
  2937. var site13 = {
  2938. matches: /youtube\.com/,
  2939. getMatchedNodes() {
  2940. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  2941. const hrefAttr = getAttribute(element, "href")
  2942. if (!hrefAttr || hrefAttr === "null" || hrefAttr === "#") {
  2943. return false
  2944. }
  2945. const href = element.href
  2946. if (href.startsWith(prefix4) || href.startsWith(prefix23)) {
  2947. const pathname = element.pathname
  2948. if (/^\/@[\w-]+$/.test(pathname)) {
  2949. const key2 = prefix4 + pathname.slice(1)
  2950. const meta = { type: "user" }
  2951. element.utags = { key: key2, meta }
  2952. return true
  2953. }
  2954. if (/^\/channel\/[\w-]+$/.test(pathname)) {
  2955. const key2 = prefix4 + pathname.slice(1)
  2956. const meta = { type: "channel" }
  2957. element.utags = { key: key2, meta }
  2958. return true
  2959. }
  2960. const key = getVideoUrl(href)
  2961. if (key) {
  2962. let title
  2963. const titleElement = $("#video-title", element)
  2964. if (titleElement) {
  2965. title = titleElement.textContent
  2966. }
  2967. const meta = title ? { title, type: "video" } : { type: "video" }
  2968. element.utags = { key, meta }
  2969. return true
  2970. }
  2971. }
  2972. return false
  2973. })
  2974. },
  2975. excludeSelectors: [...default_default.excludeSelectors],
  2976. addExtraMatchedNodes(matchedNodesSet) {
  2977. let key = getUserProfileUrl4(location.href)
  2978. if (key) {
  2979. const element = $(
  2980. "#inner-header-container #container.ytd-channel-name #text"
  2981. )
  2982. if (element) {
  2983. const title = element.textContent.trim()
  2984. if (title) {
  2985. const meta = { title }
  2986. element.utags = { key, meta }
  2987. matchedNodesSet.add(element)
  2988. }
  2989. }
  2990. }
  2991. key = getVideoUrl(location.href)
  2992. if (key) {
  2993. const element = $(
  2994. "#title h1.ytd-watch-metadata,ytd-reel-video-renderer[is-active] h2.title"
  2995. )
  2996. if (element) {
  2997. const title = element.textContent.trim()
  2998. if (title) {
  2999. const meta = { title, type: "video" }
  3000. element.utags = { key, meta }
  3001. matchedNodesSet.add(element)
  3002. }
  3003. }
  3004. }
  3005. },
  3006. getStyle: () => youtube_com_default,
  3007. }
  3008. var youtube_com_default2 = site13
  3009. var bilibili_com_default =
  3010. ':not(#a):not(#b):not(#c) .bili-video-card__info--right a[href*="/video/"]+.utags_ul_0,:not(#a):not(#b):not(#c) .bili-video-card__info--right h3.bili-video-card__info--tit+.utags_ul_0,:not(#a):not(#b):not(#c) .video-page-card-small a[href*="/video/"]+.utags_ul_0,:not(#a):not(#b):not(#c) .video-page-card-small h3.bili-video-card__info--tit+.utags_ul_0,:not(#a):not(#b):not(#c) .video-page-operator-card-small a[href*="/video/"]+.utags_ul_0,:not(#a):not(#b):not(#c) .video-page-operator-card-small h3.bili-video-card__info--tit+.utags_ul_0{display:block !important;height:0}:not(#a):not(#b):not(#c) .bili-video-card__info--right a[href*="/video/"]+.utags_ul_0 .utags_captain_tag,:not(#a):not(#b):not(#c) .bili-video-card__info--right h3.bili-video-card__info--tit+.utags_ul_0 .utags_captain_tag,:not(#a):not(#b):not(#c) .video-page-card-small a[href*="/video/"]+.utags_ul_0 .utags_captain_tag,:not(#a):not(#b):not(#c) .video-page-card-small h3.bili-video-card__info--tit+.utags_ul_0 .utags_captain_tag,:not(#a):not(#b):not(#c) .video-page-operator-card-small a[href*="/video/"]+.utags_ul_0 .utags_captain_tag,:not(#a):not(#b):not(#c) .video-page-operator-card-small h3.bili-video-card__info--tit+.utags_ul_0 .utags_captain_tag{top:-22px;background-color:rgba(255,255,255,.8666666667) !important}'
  3011. var prefix5 = "https://www.bilibili.com/"
  3012. var prefix24 = "https://space.bilibili.com/"
  3013. var prefix32 = "https://m.bilibili.com/"
  3014. function getUserProfileUrl5(href) {
  3015. if (href.startsWith(prefix24)) {
  3016. const href2 = href.slice(27)
  3017. if (/^\d+/.test(href2)) {
  3018. return prefix24 + href2.replace(/(^\d+).*/, "$1")
  3019. }
  3020. }
  3021. if (href.startsWith(prefix32 + "space/")) {
  3022. const href2 = href.slice(29)
  3023. if (/^\d+/.test(href2)) {
  3024. return prefix24 + href2.replace(/(^\d+).*/, "$1")
  3025. }
  3026. }
  3027. return void 0
  3028. }
  3029. function getVideoUrl2(href) {
  3030. if (
  3031. href.startsWith(prefix5 + "video/") ||
  3032. href.startsWith(prefix32 + "video/")
  3033. ) {
  3034. const href2 = href.startsWith(prefix32) ? href.slice(23) : href.slice(25)
  3035. if (/^video\/\w+/.test(href2)) {
  3036. return prefix5 + href2.replace(/^(video\/\w+).*/, "$1")
  3037. }
  3038. }
  3039. return void 0
  3040. }
  3041. var site14 = {
  3042. matches: /bilibili\.com|biligame\.com/,
  3043. addExtraMatchedNodes(matchedNodesSet) {
  3044. if (location.href.startsWith(prefix5 + "video/")) {
  3045. if ($(".bpx-state-loading")) {
  3046. return
  3047. }
  3048. const img = $(".bpx-player-follow-face")
  3049. const img2 = $("img.video-capture-img")
  3050. if (
  3051. !(img == null ? void 0 : img.src) ||
  3052. !(img2 == null ? void 0 : img2.src)
  3053. ) {
  3054. return
  3055. }
  3056. }
  3057. const elements = $$(
  3058. ".user-name[data-user-id],.sub-user-name[data-user-id],.jump-link.user[data-user-id]"
  3059. )
  3060. for (const element2 of elements) {
  3061. const userId = element2.dataset.userId
  3062. if (!userId) {
  3063. return false
  3064. }
  3065. const title = element2.textContent.trim()
  3066. const key = prefix24 + userId
  3067. const meta = { title, type: "user" }
  3068. element2.utags = { key, meta }
  3069. element2.dataset.utags_node_type = "link"
  3070. matchedNodesSet.add(element2)
  3071. }
  3072. const elements2 = $$(".upname a,a.bili-video-card__info--owner")
  3073. for (const element2 of elements2) {
  3074. const href = element2.href
  3075. if (href.startsWith(prefix24)) {
  3076. const key = getUserProfileUrl5(href)
  3077. if (key) {
  3078. const nameElement = $(
  3079. ".name,.bili-video-card__info--author",
  3080. element2
  3081. )
  3082. if (nameElement) {
  3083. const title = nameElement.textContent
  3084. const meta = { title, type: "user" }
  3085. nameElement.utags = { key, meta }
  3086. nameElement.dataset.utags_node_type = "link"
  3087. matchedNodesSet.add(nameElement)
  3088. }
  3089. }
  3090. }
  3091. }
  3092. const elements3 = $$(
  3093. [
  3094. "a.up-name",
  3095. "a.card-user-name",
  3096. ".usercard-wrap .user .name",
  3097. ".comment-list .user .name",
  3098. ".user-card .user .name",
  3099. "a[data-usercard-mid]",
  3100. "a.user-name",
  3101. ".user-name a",
  3102. 'a[href^="https://space.bilibili.com/"]',
  3103. "a.staff-name",
  3104. ].join(",")
  3105. )
  3106. for (const element2 of elements3) {
  3107. const href = element2.href
  3108. if (href.startsWith(prefix24)) {
  3109. const key = getUserProfileUrl5(href)
  3110. if (key) {
  3111. let title = element2.textContent.trim()
  3112. if (title) {
  3113. title = title.replace(/^@/, "")
  3114. const meta = { title, type: "user" }
  3115. element2.utags = { key, meta }
  3116. matchedNodesSet.add(element2)
  3117. }
  3118. }
  3119. }
  3120. }
  3121. if (
  3122. location.href.startsWith(prefix24) ||
  3123. location.href.startsWith(prefix32 + "space/")
  3124. ) {
  3125. const element2 = $("#h-name,.m-space-info .name")
  3126. if (element2) {
  3127. const title = element2.textContent.trim()
  3128. const key = getUserProfileUrl5(location.href)
  3129. if (title && key) {
  3130. const meta = { title, type: "user" }
  3131. element2.utags = { key, meta }
  3132. matchedNodesSet.add(element2)
  3133. }
  3134. }
  3135. }
  3136. const element = $("h1.video-title,h1.title-text")
  3137. if (element) {
  3138. const title = element.textContent.trim()
  3139. const key = getVideoUrl2(location.href)
  3140. if (title && key) {
  3141. const meta = { title, type: "video" }
  3142. element.utags = { key, meta }
  3143. matchedNodesSet.add(element)
  3144. }
  3145. }
  3146. const elements4 = $$(
  3147. ".bili-video-card__info--right a,.video-page-card-small .info a,.video-page-operator-card-small .info a"
  3148. )
  3149. for (const element2 of elements4) {
  3150. const key = getVideoUrl2(element2.href)
  3151. if (key) {
  3152. const title = element2.textContent.trim()
  3153. const target =
  3154. element2.parentElement.tagName === "H3"
  3155. ? element2.parentElement
  3156. : element2
  3157. if (title) {
  3158. const meta = { title, type: "video" }
  3159. target.utags = { key, meta }
  3160. target.dataset.utags_node_type = "link"
  3161. matchedNodesSet.add(target)
  3162. }
  3163. }
  3164. }
  3165. },
  3166. getStyle: () => bilibili_com_default,
  3167. }
  3168. var bilibili_com_default2 = site14
  3169. var prefix6 = "https://www.tiktok.com/"
  3170. function getUserProfileUrl6(url) {
  3171. if (url.startsWith(prefix6)) {
  3172. const href2 = url.slice(23)
  3173. if (/^@[\w.]+/.test(href2)) {
  3174. return prefix6 + href2.replace(/(^@[\w.]+).*/, "$1")
  3175. }
  3176. }
  3177. return void 0
  3178. }
  3179. var site15 = {
  3180. matches: /tiktok\.com/,
  3181. getMatchedNodes() {
  3182. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  3183. const href = element.href
  3184. if (href.startsWith(prefix6)) {
  3185. const pathname = element.pathname
  3186. if (/^\/@[\w.]+$/.test(pathname)) {
  3187. const titleElement = $("h3", element)
  3188. let title
  3189. if (titleElement) {
  3190. title = titleElement.textContent
  3191. }
  3192. const key = prefix6 + pathname.slice(1)
  3193. const meta = { type: "user" }
  3194. if (title) {
  3195. meta.title = title
  3196. }
  3197. element.utags = { key, meta }
  3198. element.dataset.utags = element.dataset.utags || ""
  3199. return true
  3200. }
  3201. }
  3202. return false
  3203. })
  3204. },
  3205. excludeSelectors: [
  3206. ...default_default.excludeSelectors,
  3207. ".avatar-anchor",
  3208. '[data-e2e*="avatar"]',
  3209. '[data-e2e="user-card-nickname"]',
  3210. ],
  3211. addExtraMatchedNodes(matchedNodesSet) {
  3212. const element = $('h1[data-e2e="user-title"]')
  3213. if (element) {
  3214. const title = element.textContent.trim()
  3215. const key = getUserProfileUrl6(location.href)
  3216. if (title && key) {
  3217. const meta = { title, type: "user" }
  3218. element.utags = { key, meta }
  3219. matchedNodesSet.add(element)
  3220. }
  3221. }
  3222. },
  3223. }
  3224. var tiktok_com_default = site15
  3225. var pojie_cn_default =
  3226. ".fl cite,.tl cite{white-space:break-spaces}.favatar .pi .authi a{line-height:16px}.favatar .pi{height:auto}"
  3227. var site16 = {
  3228. matches: /52pojie\.cn/,
  3229. matchedNodesSelectors: [
  3230. 'a[href*="home.php?mod=space&uid="]',
  3231. 'a[href*="home.php?mod=space&username="]',
  3232. ],
  3233. excludeSelectors: [
  3234. ...default_default.excludeSelectors,
  3235. "#hd",
  3236. "#pt",
  3237. "#pgt",
  3238. "#jz52top",
  3239. ],
  3240. getStyle: () => pojie_cn_default,
  3241. }
  3242. var pojie_cn_default2 = site16
  3243. var prefix7 = "https://juejin.cn/"
  3244. function getUserProfileUrl7(url) {
  3245. if (url.startsWith(prefix7)) {
  3246. const href2 = url.slice(18)
  3247. if (/^user\/\d+/.test(href2)) {
  3248. return prefix7 + href2.replace(/^(user\/\d+).*/, "$1")
  3249. }
  3250. }
  3251. return void 0
  3252. }
  3253. var site17 = {
  3254. matches: /juejin\.cn/,
  3255. getMatchedNodes() {
  3256. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  3257. if ($(".avatar", element)) {
  3258. return false
  3259. }
  3260. const href = element.href
  3261. if (href.startsWith(prefix7)) {
  3262. const key = getUserProfileUrl7(href)
  3263. if (key) {
  3264. const titleElement = $(".name", element)
  3265. let title
  3266. if (titleElement) {
  3267. title = titleElement.textContent
  3268. }
  3269. const meta = { type: "user" }
  3270. if (title) {
  3271. meta.title = title
  3272. }
  3273. element.utags = { key, meta }
  3274. element.dataset.utags = element.dataset.utags || ""
  3275. return true
  3276. }
  3277. }
  3278. return false
  3279. })
  3280. },
  3281. excludeSelectors: [
  3282. ...default_default.excludeSelectors,
  3283. ".list-header",
  3284. ".sub-header",
  3285. ".next-page",
  3286. ".follow-item",
  3287. ".more-item",
  3288. ],
  3289. addExtraMatchedNodes(matchedNodesSet) {
  3290. const key = getUserProfileUrl7(location.href)
  3291. if (key) {
  3292. const element2 = $("h1.username")
  3293. if (element2) {
  3294. const title = element2.textContent.trim()
  3295. if (title) {
  3296. const meta = { title, type: "user" }
  3297. element2.utags = { key, meta }
  3298. matchedNodesSet.add(element2)
  3299. }
  3300. }
  3301. }
  3302. const element = $(".sidebar-block.author-block a .username")
  3303. if (element) {
  3304. const anchor = element.closest("a")
  3305. if (anchor) {
  3306. const key2 = getUserProfileUrl7(anchor.href)
  3307. if (key2) {
  3308. const titleElement = $(".name", element)
  3309. const title = titleElement
  3310. ? titleElement.textContent
  3311. : element.textContent
  3312. if (title) {
  3313. const meta = { title, type: "user" }
  3314. element.utags = { key: key2, meta }
  3315. matchedNodesSet.add(element)
  3316. }
  3317. }
  3318. }
  3319. }
  3320. },
  3321. }
  3322. var juejin_cn_default = site17
  3323. var prefix8 = "https://www.zhihu.com/"
  3324. function getUserProfileUrl8(url, exact = false) {
  3325. if (url.startsWith(prefix8)) {
  3326. const href2 = url.slice(22)
  3327. if (exact) {
  3328. if (/^people\/[\w-]+(\?.*)?$/.test(href2)) {
  3329. return prefix8 + href2.replace(/^(people\/[\w-]+).*/, "$1")
  3330. }
  3331. } else if (/^people\/[\w-]+/.test(href2)) {
  3332. return prefix8 + href2.replace(/^(people\/[\w-]+).*/, "$1")
  3333. }
  3334. }
  3335. return void 0
  3336. }
  3337. var site18 = {
  3338. matches: /zhihu\.com/,
  3339. getMatchedNodes() {
  3340. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  3341. if ($(".avatar", element)) {
  3342. return false
  3343. }
  3344. const href = element.href
  3345. if (!href.includes("zhihu.com")) {
  3346. return true
  3347. }
  3348. if (href.startsWith(prefix8 + "people/")) {
  3349. const key = getUserProfileUrl8(href, true)
  3350. if (key) {
  3351. const titleElement = $(".name", element)
  3352. let title
  3353. if (titleElement) {
  3354. title = titleElement.textContent
  3355. }
  3356. const meta = { type: "user" }
  3357. if (title) {
  3358. meta.title = title
  3359. }
  3360. element.utags = { key, meta }
  3361. return true
  3362. }
  3363. }
  3364. return false
  3365. })
  3366. },
  3367. excludeSelectors: [
  3368. ...default_default.excludeSelectors,
  3369. ".NumberBoard",
  3370. ".ProfileMain-tabs",
  3371. ".Profile-lightList",
  3372. ],
  3373. addExtraMatchedNodes(matchedNodesSet) {
  3374. const key = getUserProfileUrl8(location.href)
  3375. if (key) {
  3376. const element = $("h1.ProfileHeader-title .ProfileHeader-name")
  3377. if (element) {
  3378. const title = element.textContent.trim()
  3379. if (title) {
  3380. const meta = { title, type: "user" }
  3381. element.utags = { key, meta }
  3382. matchedNodesSet.add(element)
  3383. }
  3384. }
  3385. }
  3386. },
  3387. }
  3388. var zhihu_com_default = site18
  3389. var prefix9 = "https://www.xiaohongshu.com/"
  3390. function getUserProfileUrl9(url, exact = false) {
  3391. if (url.startsWith(prefix9)) {
  3392. const href2 = url.slice(28)
  3393. if (exact) {
  3394. if (/^user\/profile\/\w+(\?.*)?$/.test(href2)) {
  3395. return prefix9 + href2.replace(/^(user\/profile\/\w+).*/, "$1")
  3396. }
  3397. } else if (/^user\/profile\/\w+/.test(href2)) {
  3398. return prefix9 + href2.replace(/^(user\/profile\/\w+).*/, "$1")
  3399. }
  3400. }
  3401. return void 0
  3402. }
  3403. function getPostUrl(url) {
  3404. if (url.startsWith(prefix9)) {
  3405. const href2 = url.slice(28)
  3406. if (/^explore\/\w+/.test(href2)) {
  3407. return prefix9 + href2.replace(/^(explore\/\w+).*/, "$1")
  3408. }
  3409. if (/^user\/profile\/\w+\/\w+/.test(href2)) {
  3410. return (
  3411. prefix9 +
  3412. "explore/" +
  3413. href2.replace(/^user\/profile\/\w+\/(\w+).*/, "$1")
  3414. )
  3415. }
  3416. }
  3417. return void 0
  3418. }
  3419. var site19 = {
  3420. matches: /www\.xiaohongshu\.com/,
  3421. getMatchedNodes() {
  3422. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  3423. const href = element.href
  3424. if (!href.includes("www.xiaohongshu.com")) {
  3425. return true
  3426. }
  3427. let key = getUserProfileUrl9(href, true)
  3428. if (key) {
  3429. const titleElement = hasClass(element, "name")
  3430. ? element
  3431. : $(".name", element)
  3432. let title
  3433. if (titleElement) {
  3434. title = titleElement.textContent
  3435. }
  3436. const meta = { type: "user" }
  3437. if (title) {
  3438. meta.title = title
  3439. } else {
  3440. return false
  3441. }
  3442. element.utags = { key, meta }
  3443. element.dataset.utags = element.dataset.utags || ""
  3444. return true
  3445. }
  3446. key = getPostUrl(href)
  3447. if (key) {
  3448. const meta = { type: "post" }
  3449. element.utags = { key, meta }
  3450. return true
  3451. }
  3452. return true
  3453. })
  3454. },
  3455. excludeSelectors: [...default_default.excludeSelectors, ".cover"],
  3456. addExtraMatchedNodes(matchedNodesSet) {
  3457. let key = getUserProfileUrl9(location.href)
  3458. if (key) {
  3459. const element = $(".user-info .user-name")
  3460. if (element) {
  3461. const title = element.textContent.trim()
  3462. if (title) {
  3463. const meta = { title, type: "user" }
  3464. element.utags = { key, meta }
  3465. matchedNodesSet.add(element)
  3466. }
  3467. }
  3468. }
  3469. key = getPostUrl(location.href)
  3470. if (key) {
  3471. const element = $(".note-content .title")
  3472. if (element) {
  3473. const title = element.textContent.trim()
  3474. if (title) {
  3475. const meta = { title, type: "post" }
  3476. element.utags = { key, meta }
  3477. matchedNodesSet.add(element)
  3478. }
  3479. }
  3480. }
  3481. },
  3482. }
  3483. var xiaohongshu_com_default = site19
  3484. var prefix10 = "https://weibo.com/"
  3485. var prefix25 = "https://m.weibo.cn/"
  3486. function getUserProfileUrl10(url, exact = false) {
  3487. if (url.startsWith(prefix10) || url.startsWith(prefix25)) {
  3488. const href2 = url.startsWith(prefix25) ? url.slice(19) : url.slice(18)
  3489. if (exact) {
  3490. if (/^u\/\d+(\?.*)?$/.test(href2)) {
  3491. return prefix10 + href2.replace(/^(u\/\d+).*/, "$1")
  3492. }
  3493. if (/^profile\/\d+(\?.*)?$/.test(href2)) {
  3494. return prefix10 + "u/" + href2.replace(/^profile\/(\d+).*/, "$1")
  3495. }
  3496. if (/^\d+(\?.*)?$/.test(href2)) {
  3497. return prefix10 + "u/" + href2.replace(/^(\d+).*/, "$1")
  3498. }
  3499. } else {
  3500. if (/^u\/\d+/.test(href2)) {
  3501. return prefix10 + href2.replace(/^(u\/\d+).*/, "$1")
  3502. }
  3503. if (/^profile\/\d+/.test(href2)) {
  3504. return prefix10 + "u/" + href2.replace(/^profile\/(\d+).*/, "$1")
  3505. }
  3506. if (/^\d+/.test(href2)) {
  3507. return prefix10 + "u/" + href2.replace(/^(\d+).*/, "$1")
  3508. }
  3509. }
  3510. }
  3511. return void 0
  3512. }
  3513. var site20 = {
  3514. matches: /weibo\.com|weibo\.cn/,
  3515. getMatchedNodes() {
  3516. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  3517. const href = element.href
  3518. if (!href.includes("weibo.com") && !href.includes("weibo.cn")) {
  3519. return true
  3520. }
  3521. const key = getUserProfileUrl10(href, true)
  3522. if (key) {
  3523. const meta = { type: "user" }
  3524. element.utags = { key, meta }
  3525. if ($(".m-icon.vipicon", element)) {
  3526. element.dataset.utags = element.dataset.utags || ""
  3527. }
  3528. return true
  3529. }
  3530. return true
  3531. })
  3532. },
  3533. excludeSelectors: [
  3534. ...default_default.excludeSelectors,
  3535. '[class^="Frame_side_"]',
  3536. 'a[href*="promote.biz.weibo.cn"]',
  3537. ],
  3538. addExtraMatchedNodes(matchedNodesSet) {
  3539. const key = getUserProfileUrl10(location.href)
  3540. if (key) {
  3541. const element = $(
  3542. '[class^="ProfileHeader_name_"],.profile-cover .mod-fil-name .txt-shadow'
  3543. )
  3544. if (element) {
  3545. const title = element.textContent.trim()
  3546. if (title) {
  3547. const meta = { title, type: "user" }
  3548. element.utags = { key, meta }
  3549. matchedNodesSet.add(element)
  3550. }
  3551. }
  3552. }
  3553. },
  3554. }
  3555. var weibo_com_default = site20
  3556. var sspai_com_default =
  3557. ":not(#a):not(#b):not(#c) #article-title+.utags_ul{display:block !important;margin-top:-30px !important;margin-bottom:20px !important}:not(#a):not(#b):not(#c) .user__info__card__center .utags_ul{display:block !important;margin-bottom:5px !important}:not(#a):not(#b):not(#c) .pai_title .utags_ul{float:left}"
  3558. var prefix11 = "https://sspai.com/"
  3559. var excludeLinks = [
  3560. "https://sspai.com/prime",
  3561. "https://sspai.com/matrix",
  3562. "https://sspai.com/page/about-us",
  3563. "https://sspai.com/page/agreement",
  3564. "https://sspai.com/page/bussiness",
  3565. "https://sspai.com/post/37793",
  3566. "https://sspai.com/page/client",
  3567. "https://sspai.com/s/J71e",
  3568. "https://sspai.com/mall",
  3569. ]
  3570. function getCanonicalUrl5(url) {
  3571. if (url.startsWith(prefix11)) {
  3572. const href = url.slice(18)
  3573. if (href.startsWith("u/")) {
  3574. return prefix11 + href.replace(/^(u\/\w+).*/, "$1")
  3575. }
  3576. }
  3577. return url
  3578. }
  3579. function getUserProfileUrl11(url) {
  3580. if (url.startsWith(prefix11)) {
  3581. const href2 = url.slice(18)
  3582. if (/^u\/\w+/.test(href2)) {
  3583. return prefix11 + href2.replace(/^(u\/\w+).*/, "$1")
  3584. }
  3585. }
  3586. return void 0
  3587. }
  3588. function getPostUrl2(url) {
  3589. if (url.startsWith(prefix11)) {
  3590. const href2 = url.slice(18)
  3591. if (/^post\/\d+/.test(href2)) {
  3592. return prefix11 + href2.replace(/^(post\/\d+).*/, "$1")
  3593. }
  3594. }
  3595. return void 0
  3596. }
  3597. var site21 = {
  3598. matches: /sspai\.com/,
  3599. getMatchedNodes() {
  3600. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  3601. const href = element.href
  3602. for (const link of excludeLinks) {
  3603. if (href.includes(link)) {
  3604. return false
  3605. }
  3606. }
  3607. if (
  3608. hasClass(element, "ss__user__nickname__wrapper") ||
  3609. element.closest('.card_bottom > a[href^="/u/"]')
  3610. ) {
  3611. element.dataset.utags = element.dataset.utags || ""
  3612. return true
  3613. }
  3614. return true
  3615. })
  3616. },
  3617. excludeSelectors: [
  3618. ...default_default.excludeSelectors,
  3619. "header",
  3620. "footer",
  3621. ".pai_abstract",
  3622. ".pai_title .link",
  3623. ],
  3624. addExtraMatchedNodes(matchedNodesSet) {
  3625. let key = getPostUrl2(location.href)
  3626. if (key) {
  3627. const element = $(".article-header .title")
  3628. if (element && !element.closest(".pai_title")) {
  3629. const title = element.textContent.trim()
  3630. if (title) {
  3631. const meta = { title, type: "post" }
  3632. element.utags = { key, meta }
  3633. matchedNodesSet.add(element)
  3634. }
  3635. }
  3636. }
  3637. key = getUserProfileUrl11(location.href)
  3638. if (key) {
  3639. const element = $(
  3640. ".user_content .user__info__card .ss__user__card__nickname"
  3641. )
  3642. if (element) {
  3643. const title = element.textContent.trim()
  3644. if (title) {
  3645. const meta = { title, type: "user" }
  3646. element.utags = { key, meta }
  3647. matchedNodesSet.add(element)
  3648. }
  3649. }
  3650. }
  3651. },
  3652. getCanonicalUrl: getCanonicalUrl5,
  3653. getStyle: () => sspai_com_default,
  3654. }
  3655. var sspai_com_default2 = site21
  3656. var douyin_com_default =
  3657. ':not(#a):not(#b):not(#c) [data-e2e=comment-item] .utags_ul_0 .utags_captain_tag{left:-26px}:not(#a):not(#b):not(#c) [data-e2e=detail-video-info] .utags_ul[data-utags_key*="/video/"]{display:block !important;margin-top:0px !important;margin-bottom:2px !important}:not(#a):not(#b):not(#c) [data-e2e=detail-video-info] .utags_ul[data-utags_key*="/video/"].utags_ul_0{height:0}:not(#a):not(#b):not(#c) [data-e2e=detail-video-info] .utags_ul[data-utags_key*="/video/"].utags_ul_0 .utags_captain_tag{top:-26px;background-color:rgba(255,255,255,.8666666667) !important}:not(#a):not(#b):not(#c) [data-e2e=related-video] .utags_ul_0[data-utags_key*="/video/"]{display:block !important;height:0}:not(#a):not(#b):not(#c) [data-e2e=related-video] .utags_ul_0[data-utags_key*="/video/"] .utags_captain_tag{top:-26px;background-color:rgba(255,255,255,.8666666667) !important}:not(#a):not(#b):not(#c) [data-e2e=related-video] .utags_ul_0[data-utags_key*="/user/"]{display:block !important;height:0px;width:0px}:not(#a):not(#b):not(#c) [data-e2e=related-video] .utags_ul_0[data-utags_key*="/user/"] .utags_captain_tag{top:-22px;background-color:rgba(255,255,255,.8666666667) !important}:not(#a):not(#b):not(#c) [data-e2e=user-info] a+.utags_ul_0[data-utags_key*="/user/"]{display:block !important;height:0px;width:0px}:not(#a):not(#b):not(#c) [data-e2e=user-info] a+.utags_ul_0[data-utags_key*="/user/"] .utags_captain_tag{top:-22px;background-color:rgba(255,255,255,.8666666667) !important}'
  3658. var prefix12 = "https://www.douyin.com/"
  3659. function getUserProfileUrl12(url, exact = false) {
  3660. if (url.startsWith(prefix12)) {
  3661. const href2 = url.slice(23)
  3662. if (exact) {
  3663. if (/^user\/[\w-]+(\?.*)?$/.test(href2)) {
  3664. return prefix12 + href2.replace(/^(user\/[\w-]+).*/, "$1")
  3665. }
  3666. } else if (/^user\/[\w-]+/.test(href2)) {
  3667. return prefix12 + href2.replace(/^(user\/[\w-]+).*/, "$1")
  3668. }
  3669. }
  3670. return void 0
  3671. }
  3672. function getVideoUrl3(url) {
  3673. if (url.startsWith(prefix12)) {
  3674. const href2 = url.slice(23)
  3675. if (/^video\/\w+/.test(href2)) {
  3676. return prefix12 + href2.replace(/^(video\/\w+).*/, "$1")
  3677. }
  3678. }
  3679. return void 0
  3680. }
  3681. function getNoteUrl(url) {
  3682. if (url.startsWith(prefix12)) {
  3683. const href2 = url.slice(23)
  3684. if (/^note\/\w+/.test(href2)) {
  3685. return prefix12 + href2.replace(/^(note\/\w+).*/, "$1")
  3686. }
  3687. }
  3688. return void 0
  3689. }
  3690. var site22 = {
  3691. matches: /www\.douyin\.com/,
  3692. getMatchedNodes() {
  3693. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  3694. const href = element.href
  3695. if (!href.includes("www.douyin.com")) {
  3696. return true
  3697. }
  3698. let key = getUserProfileUrl12(href, true)
  3699. if (key) {
  3700. const meta = { type: "user" }
  3701. element.utags = { key, meta }
  3702. return true
  3703. }
  3704. key = getVideoUrl3(href)
  3705. if (key) {
  3706. const meta = { type: "video" }
  3707. element.utags = { key, meta }
  3708. return true
  3709. }
  3710. key = getNoteUrl(href)
  3711. if (key) {
  3712. const meta = { type: "post" }
  3713. element.utags = { key, meta }
  3714. return true
  3715. }
  3716. return true
  3717. })
  3718. },
  3719. excludeSelectors: [
  3720. ...default_default.excludeSelectors,
  3721. '[data-e2e="douyin-navigation"]',
  3722. ],
  3723. validMediaSelectors: ['img[src*="twemoji"]'],
  3724. addExtraMatchedNodes(matchedNodesSet) {
  3725. let key = getUserProfileUrl12(location.href)
  3726. if (key) {
  3727. const element = getFirstHeadElement("h1")
  3728. if (element) {
  3729. const title = element.textContent.trim()
  3730. if (title) {
  3731. const meta = { title, type: "user" }
  3732. element.utags = { key, meta }
  3733. matchedNodesSet.add(element)
  3734. }
  3735. }
  3736. }
  3737. key = getVideoUrl3(location.href)
  3738. if (key) {
  3739. const element = getFirstHeadElement("h1")
  3740. if (element) {
  3741. const title = element.textContent.trim()
  3742. const target = element.parentElement.parentElement
  3743. if (title) {
  3744. const meta = { title, type: "video" }
  3745. target.utags = { key, meta }
  3746. target.dataset.utags_node_type = "link"
  3747. matchedNodesSet.add(target)
  3748. }
  3749. }
  3750. }
  3751. key = getNoteUrl(location.href)
  3752. if (key) {
  3753. const element = getFirstHeadElement("h1")
  3754. if (element) {
  3755. const title = element.textContent.trim()
  3756. if (title) {
  3757. const meta = { title, type: "post" }
  3758. element.utags = { key, meta }
  3759. matchedNodesSet.add(element)
  3760. }
  3761. }
  3762. }
  3763. },
  3764. getStyle: () => douyin_com_default,
  3765. }
  3766. var douyin_com_default2 = site22
  3767. var podcasts_google_com_default = ""
  3768. var prefix13 = "https://podcasts.google.com/"
  3769. function getEpisodeUrl(url, exact = false) {
  3770. if (url.startsWith(prefix13)) {
  3771. const href2 = url.slice(28)
  3772. if (exact) {
  3773. if (/^feed\/\w+\/episode\/\w+(\?.*)?$/.test(href2)) {
  3774. return prefix13 + href2.replace(/^(feed\/\w+\/episode\/\w+).*/, "$1")
  3775. }
  3776. } else if (/^feed\/\w+\/episode\/\w+/.test(href2)) {
  3777. return prefix13 + href2.replace(/^(feed\/\w+\/episode\/\w+).*/, "$1")
  3778. }
  3779. }
  3780. return void 0
  3781. }
  3782. function getFeedUrl(url) {
  3783. if (url.startsWith(prefix13)) {
  3784. const href2 = url.slice(28)
  3785. if (/^feed\/\w+(\?.*)?$/.test(href2)) {
  3786. return prefix13 + href2.replace(/^(feed\/\w+).*/, "$1")
  3787. }
  3788. }
  3789. return void 0
  3790. }
  3791. function getCanonicalUrl6(url) {
  3792. if (url.startsWith(prefix13)) {
  3793. let url2 = getFeedUrl(url)
  3794. if (url2) {
  3795. return url2
  3796. }
  3797. url2 = getEpisodeUrl(url)
  3798. if (url2) {
  3799. return url2
  3800. }
  3801. }
  3802. return url
  3803. }
  3804. var site23 = {
  3805. matches: /podcasts\.google\.com/,
  3806. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  3807. excludeSelectors: [
  3808. ...default_default.excludeSelectors,
  3809. "header",
  3810. "gm-coplanar-drawer",
  3811. ],
  3812. addExtraMatchedNodes(matchedNodesSet) {
  3813. let key = getEpisodeUrl(location.href)
  3814. if (key) {
  3815. const element = $("h5")
  3816. if (element) {
  3817. const title = element.textContent.trim()
  3818. if (title) {
  3819. const meta = { title, type: "episode" }
  3820. element.utags = { key, meta }
  3821. matchedNodesSet.add(element)
  3822. }
  3823. }
  3824. }
  3825. key = getFeedUrl(location.href)
  3826. if (key) {
  3827. for (const container of $$("[data-encoded-feed]")) {
  3828. if (isVisible(container)) {
  3829. const element = $(
  3830. "div:first-child > div:first-child > div:first-child > div:first-child",
  3831. container
  3832. )
  3833. if (element) {
  3834. const title = element.textContent.trim()
  3835. if (title) {
  3836. const meta = { title, type: "feed" }
  3837. element.utags = { key, meta }
  3838. matchedNodesSet.add(element)
  3839. }
  3840. }
  3841. }
  3842. }
  3843. }
  3844. for (const element of $$('a[role="listitem"]')) {
  3845. const key2 = getEpisodeUrl(element.href)
  3846. const titleElement = $(
  3847. 'div[role="navigation"] div div[role="presentation"]',
  3848. element
  3849. )
  3850. if (key2 && titleElement) {
  3851. const title = titleElement.textContent
  3852. const meta = { title, type: "episode" }
  3853. titleElement.utags = { key: key2, meta }
  3854. titleElement.dataset.utags_node_type = "link"
  3855. matchedNodesSet.add(titleElement)
  3856. }
  3857. }
  3858. for (const element of $$(
  3859. 'a[href^="./feed/"]:not(a[href*="/episode/"])'
  3860. )) {
  3861. if (!isVisible(element)) {
  3862. continue
  3863. }
  3864. const key2 = getFeedUrl(element.href)
  3865. const titleElement = $("div > div", element)
  3866. if (titleElement) {
  3867. const title = titleElement.textContent
  3868. const meta = { title, type: "feed" }
  3869. titleElement.utags = { key: key2, meta }
  3870. titleElement.dataset.utags_node_type = "link"
  3871. matchedNodesSet.add(titleElement)
  3872. }
  3873. }
  3874. },
  3875. getCanonicalUrl: getCanonicalUrl6,
  3876. getStyle: () => podcasts_google_com_default,
  3877. }
  3878. var podcasts_google_com_default2 = site23
  3879. var rebang_today_default =
  3880. ":not(#a):not(#b):not(#c) .w-screen ul{--utags-list-node-display: flex}:not(#a):not(#b):not(#c) .w-screen ul .flex-1:not(.relative)>.utags_ul_0,:not(#a):not(#b):not(#c) .w-screen ul .flex.flex-col.relative .flex-1 .utags_ul_0,:not(#a):not(#b):not(#c) .w-screen ul .relative>.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-3);--utags-notag-ul-height: var(--utags-notag-ul-height-3);--utags-notag-ul-position: var(--utags-notag-ul-position-3);--utags-notag-ul-top: var(--utags-notag-ul-top-3);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-3);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-3);--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}:not(#a):not(#b):not(#c) .w-screen ul .flex.flex-col.relative .flex-1 .utags_ul_0,:not(#a):not(#b):not(#c) .w-screen ul .flex-1:not(.relative)>.utags_ul_0{--utags-notag-captain-tag-top: 18px}:not(#a):not(#b):not(#c) .w-screen ul .text-base .block+.utags_ul_0,:not(#a):not(#b):not(#c) .w-screen ul h1 .utags_ul_0,:not(#a):not(#b):not(#c) .w-screen ul li>div.overflow-hidden:nth-of-type(2):not(.relative)>a+.utags_ul_0,:not(#a):not(#b):not(#c) .w-screen ul li>a+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-2);--utags-notag-ul-height: var(--utags-notag-ul-height-2);--utags-notag-ul-position: var(--utags-notag-ul-position-2);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-2);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-2);--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}:not(#a):not(#b):not(#c) .w-screen ul .text-base .block+.utags_ul_0{--utags-notag-captain-tag-top: -24px}:not(#a):not(#b):not(#c) .w-screen ul h1 .utags_ul_0{--utags-notag-captain-tag-top: -28px}:not(#a):not(#b):not(#c) .w-screen ul li>a+.utags_ul_0{--utags-notag-captain-tag-left: 26px}:not(#a):not(#b):not(#c) .w-screen ul .flex.flex-col .text-base+.utags_ul_0,:not(#a):not(#b):not(#c) .w-screen ul li>a.text-base+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-4);--utags-notag-ul-height: var(--utags-notag-ul-height-4);--utags-notag-ul-position: var(--utags-notag-ul-position-4);--utags-notag-ul-top: var(--utags-notag-ul-top-4);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-4);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-4);--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}:not(#a):not(#b):not(#c) .w-screen ul li>a.text-base+.utags_ul_0{--utags-notag-captain-tag-top: -10px;--utags-notag-captain-tag-left: 14px}:not(#a):not(#b):not(#c) .w-screen ul .truncate .utags_ul_0{--utags-notag-captain-tag-left: -22px}:not(#a):not(#b):not(#c) aside{--utags-list-node-display: flex}:not(#a):not(#b):not(#c) aside .select-none .utags_ul_0{--utags-notag-captain-tag-left: -22px}:not(#a):not(#b):not(#c) aside .select-none .arco-tag .utags_ul_0{--utags-notag-captain-tag-left: -6px}:not(#a):not(#b):not(#c) #markdown-body.markdown-body a+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-1);--utags-notag-ul-height: var(--utags-notag-ul-height-1);--utags-notag-ul-width: var(--utags-notag-ul-width-1);--utags-notag-ul-position: var(--utags-notag-ul-position-1);--utags-notag-ul-top: var(--utags-notag-ul-top-1);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-1);--utags-notag-captain-tag-left: var(--utags-notag-captain-tag-left-1)}"
  3881. var nodeNameMap = {
  3882. 知乎: "zhihu",
  3883. 微博: "weibo",
  3884. IT之家: "ithome",
  3885. 虎扑: "hupu",
  3886. 豆瓣社区: "douban-community",
  3887. 虎嗅: "huxiu",
  3888. 少数派: "sspai",
  3889. 网易新闻: "ne-news",
  3890. 澎湃新闻: "thepaper",
  3891. 小红书: "xiaohongshu",
  3892. "36\u6C2A": "36kr",
  3893. 今日头条: "toutiao",
  3894. 爱范儿: "ifanr",
  3895. 豆瓣书影音: "douban-media",
  3896. 什么值得买: "smzdm",
  3897. 百度: "baidu",
  3898. 百度贴吧: "baidu-tieba",
  3899. 吾爱破解: "52pojie",
  3900. 观风闻: "guancha-user",
  3901. 雪球: "xueqiu",
  3902. 东方财富: "eastmoney",
  3903. 新浪财经: "sina-fin",
  3904. 蓝点网: "landian",
  3905. 小众软件: "appinn",
  3906. 反斗限免: "apprcn",
  3907. NGA社区: "nga",
  3908. 游民星空: "gamersky",
  3909. 喷嚏网: "penti",
  3910. 沙雕新闻: "shadiao-news",
  3911. 抖音: "douyin",
  3912. 哔哩哔哩: "bilibili",
  3913. 直播吧: "zhibo8",
  3914. 掘金: "juejin",
  3915. 技术期刊: "journal-tech",
  3916. 开发者头条: "toutiaoio",
  3917. GitHub: "github",
  3918. AcFun: "acfun",
  3919. 宽带山: "kds",
  3920. V2EX: "v2ex",
  3921. 格隆汇: "gelonghui",
  3922. 第一财经: "diyicaijing",
  3923. InfoQ: "infoq",
  3924. CSDN: "csdn",
  3925. }
  3926. var site24 = {
  3927. matches: /rebang\.today/,
  3928. preProcess() {
  3929. const nodes = $$(":not(a) > .arco-tag-content")
  3930. for (const node of nodes) {
  3931. const name = node.textContent
  3932. if (name && !node.closest("a")) {
  3933. const nodeId = nodeNameMap[name]
  3934. if (nodeId) {
  3935. const a = createElement("a", {
  3936. href: "https://rebang.today/home?tab=" + nodeId,
  3937. })
  3938. node.after(a)
  3939. a.append(node)
  3940. }
  3941. }
  3942. }
  3943. },
  3944. listNodesSelectors: [
  3945. ".w-screen ul:not(.utags_ul) > li",
  3946. "aside .w-full .select-none",
  3947. ],
  3948. conditionNodesSelectors: [
  3949. '[data-utags_list_node] [data-utags]:not([href^="https://www.v2ex.com/member/"])',
  3950. '[data-utags_list_node] a[href^="https://www.v2ex.com/member/"][data-utags].hidden',
  3951. ],
  3952. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  3953. excludeSelectors: [
  3954. ...default_default.excludeSelectors,
  3955. "header",
  3956. ".absolute.rounded-xl",
  3957. "ul li h1 + p a",
  3958. ],
  3959. validMediaSelectors: [
  3960. ".text-text-100",
  3961. ".items-center .rounded-full",
  3962. 'a[href^="https://github.com/"] svg',
  3963. 'a[href^="https://space.bilibili.com/"] img',
  3964. 'a[href^="https://toutiao.io/subjects/"] img',
  3965. "svg.arco-icon",
  3966. ],
  3967. getStyle: () => rebang_today_default,
  3968. }
  3969. var rebang_today_default2 = site24
  3970. var myanimelist_net_default =
  3971. ":not(#a):not(#b):not(#c) tbody.list-item td.title{--utags-notag-captain-tag-top: -6px;--utags-notag-captain-tag-left: -24px}"
  3972. var site25 = {
  3973. matches: /myanimelist\.net/,
  3974. listNodesSelectors: [],
  3975. conditionNodesSelectors: [],
  3976. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  3977. excludeSelectors: [
  3978. ...default_default.excludeSelectors,
  3979. "#headerSmall",
  3980. "#menu",
  3981. "#nav",
  3982. ".header",
  3983. "#status-menu",
  3984. 'a[href^="/sns/register/"]',
  3985. 'a[href^="/logout"]',
  3986. 'a[href*="/membership?"]',
  3987. 'a[href*="/login.php"]',
  3988. 'a[href*="/register.php"]',
  3989. 'a[href*="/dbchanges.php"]',
  3990. 'a[href*="/editprofile.php"]',
  3991. 'a[href*="go=write"]',
  3992. 'a[href^="/ownlist/anime/add?"]',
  3993. '[class*="btn-"]',
  3994. '[class*="icon-"]',
  3995. '[rel*="sponsored"]',
  3996. ],
  3997. getStyle: () => myanimelist_net_default,
  3998. }
  3999. var myanimelist_net_default2 = site25
  4000. function getCanonicalUrl7(url) {
  4001. if (url.includes("douban.com")) {
  4002. return deleteUrlParameters(url, [
  4003. "ref",
  4004. "dcs",
  4005. "dcm",
  4006. "from",
  4007. "from_",
  4008. "dt_time_source",
  4009. "target_user_id",
  4010. "_dtcc",
  4011. "_i",
  4012. ])
  4013. }
  4014. return url
  4015. }
  4016. var site26 = {
  4017. matches: /douban\.com/,
  4018. listNodesSelectors: [],
  4019. conditionNodesSelectors: [],
  4020. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  4021. excludeSelectors: [
  4022. ...default_default.excludeSelectors,
  4023. ".tabs",
  4024. 'a[href*="/accounts/login?"]',
  4025. 'a[href*="/passport/login?"]',
  4026. 'a[href*="/register?"]',
  4027. ],
  4028. getCanonicalUrl: getCanonicalUrl7,
  4029. }
  4030. var douban_com_default = site26
  4031. var pornhub_com_default =
  4032. ':not(#a):not(#b):not(#c) .usernameWrap .utags_ul_0 .utags_captain_tag{left:-20px}:not(#a):not(#b):not(#c) .usernameWrap .utags_ul_1::before{content:"";display:block}:not(#a):not(#b):not(#c) .vidTitleWrapper .title .utags_ul_0{display:block !important;height:0;position:absolute;top:0}:not(#a):not(#b):not(#c) .vidTitleWrapper .title .utags_ul_0 .utags_captain_tag{background-color:rgba(255,255,255,.8666666667) !important}:not(#a):not(#b):not(#c) .vidTitleWrapper .title .utags_ul_1{display:block !important;height:0;position:absolute;bottom:0}:not(#a):not(#b):not(#c) ul.videos .thumbnail-info-wrapper{position:relative}:not(#a):not(#b):not(#c) ul.videos .thumbnail-info-wrapper .title .utags_ul_0{display:block !important;height:0;position:absolute;top:0}:not(#a):not(#b):not(#c) ul.videos .thumbnail-info-wrapper .title .utags_ul_0 .utags_captain_tag{background-color:rgba(255,255,255,.8666666667) !important}:not(#a):not(#b):not(#c) ul.videos .thumbnail-info-wrapper .title .utags_ul_1{display:block !important;height:0;position:absolute;bottom:0}'
  4033. var prefix14 = "https://www.pornhub.com/"
  4034. function getUserProfileUrl13(href, exact = false) {
  4035. if (href.includes("pornhub.com")) {
  4036. const index = href.indexOf("pornhub.com") + 12
  4037. const href2 = href.slice(index)
  4038. if (exact) {
  4039. if (/^(model|users)\/[\w-]+(\?.*)?$/.test(href2)) {
  4040. return prefix14 + href2.replace(/(^(model|users)\/[\w-]+).*/, "$1")
  4041. }
  4042. } else if (/^(model|users)\/[\w-]+/.test(href2)) {
  4043. return prefix14 + href2.replace(/(^(model|users)\/[\w-]+).*/, "$1")
  4044. }
  4045. }
  4046. return void 0
  4047. }
  4048. function getChannelUrl(href, exact = false) {
  4049. if (href.includes("pornhub.com")) {
  4050. const index = href.indexOf("pornhub.com") + 12
  4051. const href2 = href.slice(index)
  4052. if (exact) {
  4053. if (/^channels\/[\w-]+(\?.*)?$/.test(href2)) {
  4054. return prefix14 + href2.replace(/(^channels\/[\w-]+).*/, "$1")
  4055. }
  4056. } else if (/^channels\/[\w-]+/.test(href2)) {
  4057. return prefix14 + href2.replace(/(^channels\/[\w-]+).*/, "$1")
  4058. }
  4059. }
  4060. return void 0
  4061. }
  4062. function getVideoUrl4(href) {
  4063. if (href.includes("pornhub.com")) {
  4064. const index = href.indexOf("pornhub.com") + 12
  4065. const href2 = href.slice(index)
  4066. if (/^view_video.php\?viewkey=\w+/.test(href2)) {
  4067. return prefix14 + href2.replace(/(view_video.php\?viewkey=\w+).*/, "$1")
  4068. }
  4069. }
  4070. return void 0
  4071. }
  4072. var site27 = {
  4073. matches: /pornhub\.com/,
  4074. getMatchedNodes() {
  4075. return $$("a[href]:not(.utags_text_tag)").filter((element) => {
  4076. const hrefAttr = getAttribute(element, "href")
  4077. if (!hrefAttr || hrefAttr === "null" || hrefAttr === "#") {
  4078. return false
  4079. }
  4080. const href = element.href
  4081. let key = getChannelUrl(href, true)
  4082. if (key) {
  4083. const meta = { type: "channel" }
  4084. element.utags = { key, meta }
  4085. return true
  4086. }
  4087. key = getUserProfileUrl13(href, true)
  4088. if (key) {
  4089. const meta = { type: "user" }
  4090. element.utags = { key, meta }
  4091. return true
  4092. }
  4093. key = getVideoUrl4(href)
  4094. if (key) {
  4095. let title
  4096. const titleElement = $("#video-title", element)
  4097. if (titleElement) {
  4098. title = titleElement.textContent
  4099. }
  4100. const meta = title ? { title, type: "video" } : { type: "video" }
  4101. element.utags = { key, meta }
  4102. return true
  4103. }
  4104. return true
  4105. })
  4106. },
  4107. excludeSelectors: [
  4108. ...default_default.excludeSelectors,
  4109. ".networkBarWrapper",
  4110. "#headerWrapper",
  4111. "#headerMenuContainer",
  4112. "#mainMenuProfile",
  4113. "#mainMenuAmateurModelProfile",
  4114. "#countryRedirectMessage",
  4115. "aside#leftMenu",
  4116. ".profileSubNav",
  4117. ".subFilterList",
  4118. ".greyButton",
  4119. ".orangeButton",
  4120. ],
  4121. addExtraMatchedNodes(matchedNodesSet) {
  4122. let key = getUserProfileUrl13(location.href)
  4123. if (key) {
  4124. const element = $(".name h1")
  4125. if (element) {
  4126. const title = element.textContent.trim()
  4127. if (title) {
  4128. const meta = { title, type: "user" }
  4129. element.utags = { key, meta }
  4130. matchedNodesSet.add(element)
  4131. }
  4132. }
  4133. }
  4134. key = getChannelUrl(location.href)
  4135. if (key) {
  4136. const element = $(".title h1")
  4137. if (element && !$("a", element)) {
  4138. const title = element.textContent.trim()
  4139. if (title) {
  4140. const meta = { title, type: "channel" }
  4141. element.utags = { key, meta }
  4142. matchedNodesSet.add(element)
  4143. }
  4144. }
  4145. }
  4146. key = getVideoUrl4(location.href)
  4147. if (key) {
  4148. const element = $("h1.title")
  4149. if (element) {
  4150. const title = element.textContent.trim()
  4151. if (title) {
  4152. const meta = { title, type: "video" }
  4153. element.utags = { key, meta }
  4154. matchedNodesSet.add(element)
  4155. }
  4156. }
  4157. }
  4158. },
  4159. getStyle: () => pornhub_com_default,
  4160. }
  4161. var pornhub_com_default2 = site27
  4162. var e_hentai_org_default =
  4163. ":not(#a):not(#b):not(#c) div.gt a+.utags_ul_0,:not(#a):not(#b):not(#c) div.gtl a+.utags_ul_0,:not(#a):not(#b):not(#c) div.gtw a+.utags_ul_0,:not(#a):not(#b):not(#c) div.gl4e.glname .glink+.utags_ul_0,:not(#a):not(#b):not(#c) .gltm .glname a+.utags_ul_0,:not(#a):not(#b):not(#c) .gltc .glname a+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-3);--utags-notag-ul-height: var(--utags-notag-ul-height-3);--utags-notag-ul-position: var(--utags-notag-ul-position-3);--utags-notag-ul-top: var(--utags-notag-ul-top-3);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-3);--utags-notag-captain-tag-left: 24px;--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap );z-index:200}:not(#a):not(#b):not(#c) div.gl1t a+.utags_ul_0{--utags-notag-ul-disply: var(--utags-notag-ul-disply-4);--utags-notag-ul-height: var(--utags-notag-ul-height-4);--utags-notag-ul-position: var(--utags-notag-ul-position-4);--utags-notag-ul-top: var(--utags-notag-ul-top-4);--utags-notag-captain-tag-top: var(--utags-notag-captain-tag-top-4);--utags-notag-captain-tag-left: 24px;--utags-captain-tag-background-color: var( --utags-captain-tag-background-color-overlap )}"
  4164. var prefix15 = "https://e-hentai.org/"
  4165. var prefix26 = "https://exhentai.org/"
  4166. function getPostUrl3(url) {
  4167. if (url.startsWith(prefix15)) {
  4168. const href2 = url.slice(21)
  4169. if (/^g\/\w+/.test(href2)) {
  4170. return prefix15 + href2.replace(/^(g\/\w+\/\w+\/).*/, "$1")
  4171. }
  4172. }
  4173. if (url.startsWith(prefix26)) {
  4174. const href2 = url.slice(21)
  4175. if (/^g\/\w+/.test(href2)) {
  4176. return prefix26 + href2.replace(/^(g\/\w+\/\w+\/).*/, "$1")
  4177. }
  4178. }
  4179. return void 0
  4180. }
  4181. function isImageViewUrl(url) {
  4182. if (url.startsWith(prefix15)) {
  4183. const href2 = url.slice(21)
  4184. return /^s\/\w+/.test(href2)
  4185. }
  4186. if (url.startsWith(prefix26)) {
  4187. const href2 = url.slice(21)
  4188. return /^s\/\w+/.test(href2)
  4189. }
  4190. return false
  4191. }
  4192. var site28 = {
  4193. matches: /e-hentai\.org|exhentai\.org/,
  4194. getMatchedNodes() {
  4195. return $$("a[href]:not(.utags_text_tag)")
  4196. .filter((element) => {
  4197. const href = element.href
  4198. if (href.startsWith(prefix15) || href.startsWith(prefix26)) {
  4199. const key = getPostUrl3(href)
  4200. if (key) {
  4201. const titleElement = $(".glink", element)
  4202. let title
  4203. if (titleElement) {
  4204. title = titleElement.textContent
  4205. }
  4206. const meta = { type: "post" }
  4207. if (title) {
  4208. meta.title = title
  4209. }
  4210. element.utags = { key, meta }
  4211. return true
  4212. }
  4213. if (isImageViewUrl(href)) {
  4214. return false
  4215. }
  4216. }
  4217. return true
  4218. })
  4219. .map((element) => {
  4220. const titleElement = $(".gl4e.glname .glink", element)
  4221. if (titleElement) {
  4222. titleElement.utags = element.utags
  4223. titleElement.dataset.utags = titleElement.dataset.utags || ""
  4224. titleElement.dataset.utags_node_type = "link"
  4225. console.log(titleElement.utags)
  4226. return titleElement
  4227. }
  4228. return element
  4229. })
  4230. },
  4231. excludeSelectors: [
  4232. ...default_default.excludeSelectors,
  4233. "#nb",
  4234. ".searchnav",
  4235. ".gtb",
  4236. 'a[href*="report=select"]',
  4237. 'a[href*="act=expunge"]',
  4238. ],
  4239. addExtraMatchedNodes(matchedNodesSet) {
  4240. const key = getPostUrl3(location.href)
  4241. if (key) {
  4242. const element = getFirstHeadElement()
  4243. if (element) {
  4244. const title = element.textContent.trim()
  4245. if (title) {
  4246. const meta = { title, type: "post" }
  4247. element.utags = { key, meta }
  4248. matchedNodesSet.add(element)
  4249. }
  4250. }
  4251. }
  4252. },
  4253. getStyle: () => e_hentai_org_default,
  4254. }
  4255. var e_hentai_org_default2 = site28
  4256. var panda_chaika_moe_default =
  4257. ":not(#a):not(#b):not(#c) h5+.utags_ul{display:block !important;margin-top:-4px !important;margin-bottom:6px !important}"
  4258. var prefix16 = "https://panda.chaika.moe/"
  4259. function getPostUrl4(url, exact = false) {
  4260. if (url.startsWith(prefix16)) {
  4261. const href2 = url.slice(25)
  4262. if (exact) {
  4263. if (/^archive\/\d+\/(\?.*)?$/.test(href2)) {
  4264. return prefix16 + href2.replace(/^(archive\/\d+\/).*/, "$1")
  4265. }
  4266. } else if (/^archive\/\d+\//.test(href2)) {
  4267. return prefix16 + href2.replace(/^(archive\/\d+\/).*/, "$1")
  4268. }
  4269. }
  4270. return void 0
  4271. }
  4272. var site29 = {
  4273. matches: /panda\.chaika\.moe/,
  4274. matchedNodesSelectors: ["a[href]:not(.utags_text_tag)"],
  4275. excludeSelectors: [
  4276. ...default_default.excludeSelectors,
  4277. ".navbar",
  4278. "th",
  4279. ".pagination",
  4280. ".btn",
  4281. ".caption",
  4282. ],
  4283. addExtraMatchedNodes(matchedNodesSet) {
  4284. const key = getPostUrl4(location.href)
  4285. if (key) {
  4286. const element = $("h5")
  4287. if (element) {
  4288. const title = element.textContent.trim()
  4289. if (title) {
  4290. const meta = { title, type: "post" }
  4291. element.utags = { key, meta }
  4292. matchedNodesSet.add(element)
  4293. }
  4294. }
  4295. }
  4296. for (const element of $$(".gallery a.cover")) {
  4297. const key2 = element.href
  4298. const titleElement = $(".cover-title", element)
  4299. if (titleElement) {
  4300. const title = titleElement.textContent
  4301. const meta = { title, type: "post" }
  4302. titleElement.utags = { key: key2, meta }
  4303. titleElement.dataset.utags_node_type = "link"
  4304. matchedNodesSet.add(titleElement)
  4305. }
  4306. }
  4307. for (const element of $$('.td-extended > a[href^="/archive/"]')) {
  4308. const key2 = element.href
  4309. const titleElement = $("h5", element.parentElement.parentElement)
  4310. if (titleElement) {
  4311. const title = titleElement.textContent
  4312. const meta = { title, type: "post" }
  4313. titleElement.utags = { key: key2, meta }
  4314. titleElement.dataset.utags_node_type = "link"
  4315. matchedNodesSet.add(titleElement)
  4316. }
  4317. }
  4318. },
  4319. getStyle: () => panda_chaika_moe_default,
  4320. }
  4321. var panda_chaika_moe_default2 = site29
  4322. var sites = [
  4323. github_com_default2,
  4324. v2ex_default,
  4325. twitter_com_default,
  4326. reddit_com_default,
  4327. greasyfork_org_default2,
  4328. news_ycombinator_com_default,
  4329. lobste_rs_default,
  4330. mp_weixin_qq_com_default,
  4331. instagram_com_default2,
  4332. threads_net_default2,
  4333. facebook_com_default,
  4334. youtube_com_default2,
  4335. bilibili_com_default2,
  4336. tiktok_com_default,
  4337. pojie_cn_default2,
  4338. juejin_cn_default,
  4339. zhihu_com_default,
  4340. xiaohongshu_com_default,
  4341. weibo_com_default,
  4342. sspai_com_default2,
  4343. douyin_com_default2,
  4344. podcasts_google_com_default2,
  4345. rebang_today_default2,
  4346. myanimelist_net_default2,
  4347. douban_com_default,
  4348. pornhub_com_default2,
  4349. e_hentai_org_default2,
  4350. panda_chaika_moe_default2,
  4351. ]
  4352. var getCanonicalUrlFunctionList = [default_default, ...sites]
  4353. .map((site30) => site30.getCanonicalUrl)
  4354. .filter((v) => typeof v === "function")
  4355. function matchedSite(hostname2) {
  4356. for (const s of sites) {
  4357. if (s.matches.test(hostname2)) {
  4358. return s
  4359. }
  4360. }
  4361. if (false) {
  4362. return siteForExtensions(hostname2)
  4363. }
  4364. return default_default
  4365. }
  4366. var hostname = location.hostname
  4367. var currentSite = matchedSite(hostname)
  4368. function getListNodes() {
  4369. if (typeof currentSite.preProcess === "function") {
  4370. currentSite.preProcess()
  4371. }
  4372. if (typeof currentSite.getStyle === "function" && !$("#utags_site_style")) {
  4373. const styleText = currentSite.getStyle()
  4374. if (styleText) {
  4375. addElement2("style", {
  4376. textContent: styleText,
  4377. id: "utags_site_style",
  4378. })
  4379. }
  4380. }
  4381. if (typeof currentSite.getListNodes === "function") {
  4382. return currentSite.getListNodes()
  4383. }
  4384. if (currentSite.listNodesSelectors) {
  4385. return $$(currentSite.listNodesSelectors.join(",") || "none")
  4386. }
  4387. return []
  4388. }
  4389. function getConditionNodes() {
  4390. if (typeof currentSite.getConditionNodes === "function") {
  4391. return currentSite.getConditionNodes()
  4392. }
  4393. if (currentSite.conditionNodesSelectors) {
  4394. return $$(currentSite.conditionNodesSelectors.join(",") || "none")
  4395. }
  4396. return []
  4397. }
  4398. function getCanonicalUrl8(url) {
  4399. for (const getCanonicalUrlFunc of getCanonicalUrlFunctionList) {
  4400. if (getCanonicalUrlFunc) {
  4401. url = getCanonicalUrlFunc(url)
  4402. }
  4403. }
  4404. return url
  4405. }
  4406. var isValidUtagsElement = (element, validMediaSelector) => {
  4407. if (element.dataset.utags !== void 0) {
  4408. return true
  4409. }
  4410. if (!element.textContent) {
  4411. return false
  4412. }
  4413. const media = $(
  4414. 'img,svg,audio,video,button,.icon,[style*="background-image"]',
  4415. element
  4416. )
  4417. if (media) {
  4418. if (!validMediaSelector) {
  4419. return false
  4420. }
  4421. if (!media.closest(validMediaSelector)) {
  4422. return false
  4423. }
  4424. }
  4425. let href = getAttribute(element, "href")
  4426. if (!href) {
  4427. return false
  4428. }
  4429. href = href.trim()
  4430. if (href.length === 0 || href === "#") {
  4431. return false
  4432. }
  4433. const protocol = element.protocol
  4434. if (protocol !== "http:" && protocol !== "https:") {
  4435. return false
  4436. }
  4437. return true
  4438. }
  4439. var isExcluedUtagsElement = (element, excludeSelector) => {
  4440. return excludeSelector ? Boolean(element.closest(excludeSelector)) : false
  4441. }
  4442. var addMatchedNodes = (matchedNodesSet) => {
  4443. let elements
  4444. if (typeof currentSite.getMatchedNodes === "function") {
  4445. elements = currentSite.getMatchedNodes()
  4446. } else {
  4447. const matchedNodesSelectors = currentSite.matchedNodesSelectors
  4448. if (!matchedNodesSelectors || matchedNodesSelectors.length === 0) {
  4449. return
  4450. }
  4451. elements = $$(matchedNodesSelectors.join(",") || "none")
  4452. }
  4453. if (elements.length === 0) {
  4454. return
  4455. }
  4456. const excludeSelectors = currentSite.excludeSelectors || []
  4457. const excludeSelector = excludeSelectors.join(",")
  4458. const validMediaSelectors = currentSite.validMediaSelectors || []
  4459. const validMediaSelector = validMediaSelectors.join(",")
  4460. for (const element of elements) {
  4461. if (
  4462. !isValidUtagsElement(element, validMediaSelector) ||
  4463. isExcluedUtagsElement(element, excludeSelector)
  4464. ) {
  4465. element.utags = {}
  4466. continue
  4467. }
  4468. const utags = element.utags || {}
  4469. const key = utags.key || getCanonicalUrl8(element.href)
  4470. const title = element.textContent.trim()
  4471. const meta = {}
  4472. if (title && !isUrl(title)) {
  4473. meta.title = title
  4474. }
  4475. element.utags = {
  4476. key,
  4477. meta: utags.meta ? Object.assign(meta, utags.meta) : meta,
  4478. }
  4479. matchedNodesSet.add(element)
  4480. }
  4481. }
  4482. function matchedNodes() {
  4483. const matchedNodesSet = /* @__PURE__ */ new Set()
  4484. addMatchedNodes(matchedNodesSet)
  4485. if (typeof currentSite.addExtraMatchedNodes === "function") {
  4486. currentSite.addExtraMatchedNodes(matchedNodesSet)
  4487. }
  4488. if (typeof currentSite.postProcess === "function") {
  4489. currentSite.postProcess()
  4490. }
  4491. return [...matchedNodesSet]
  4492. }
  4493. var config = {
  4494. run_at: "document_start",
  4495. }
  4496. var emojiTags2
  4497. var host = location.host
  4498. var isEnabledByDefault = () => {
  4499. if (host.includes("www.bilibili.com")) {
  4500. return false
  4501. }
  4502. return true
  4503. }
  4504. var isTagManager = location.href.includes("utags.pipecraft.net/tags/")
  4505. var settingsTable2 = {
  4506. ["enableCurrentSite_".concat(host)]: {
  4507. title: i2("settings.enableCurrentSite"),
  4508. defaultValue: isEnabledByDefault(),
  4509. },
  4510. showHidedItems: {
  4511. title: i2("settings.showHidedItems"),
  4512. defaultValue: false,
  4513. group: 2,
  4514. },
  4515. noOpacityEffect: {
  4516. title: i2("settings.noOpacityEffect"),
  4517. defaultValue: false,
  4518. group: 2,
  4519. },
  4520. pinnedTagsTitle: {
  4521. title: i2("settings.pinnedTags"),
  4522. type: "action",
  4523. async onclick() {
  4524. const input = $('textarea[data-key="pinnedTags"]')
  4525. if (input) {
  4526. input.focus()
  4527. }
  4528. },
  4529. group: 3,
  4530. },
  4531. pinnedTags: {
  4532. title: i2("settings.pinnedTags"),
  4533. defaultValue: i2("settings.pinnedTagsDefaultValue"),
  4534. placeholder: i2("settings.pinnedTagsPlaceholder"),
  4535. type: "textarea",
  4536. group: 3,
  4537. },
  4538. emojiTagsTitle: {
  4539. title: i2("settings.emojiTags"),
  4540. type: "action",
  4541. async onclick() {
  4542. const input = $('textarea[data-key="emojiTags"]')
  4543. if (input) {
  4544. input.focus()
  4545. }
  4546. },
  4547. group: 3,
  4548. },
  4549. emojiTags: {
  4550. title: i2("settings.emojiTags"),
  4551. defaultValue:
  4552. "\u{1F44D}, \u{1F44E}, \u2764\uFE0F, \u2B50, \u{1F31F}, \u{1F525}, \u{1F4A9}, \u26A0\uFE0F, \u{1F4AF}, \u{1F44F}, \u{1F437}, \u{1F4CC}, \u{1F4CD}, \u{1F3C6}, \u{1F48E}, \u{1F4A1}, \u{1F916}, \u{1F4D4}, \u{1F4D6}, \u{1F4DA}, \u{1F4DC}, \u{1F4D5}, \u{1F4D7}, \u{1F9F0}, \u26D4, \u{1F6AB}, \u{1F534}, \u{1F7E0}, \u{1F7E1}, \u{1F7E2}, \u{1F535}, \u{1F7E3}, \u2757, \u2753, \u2705, \u274C",
  4553. placeholder: "\u{1F44D}, \u{1F44E}",
  4554. type: "textarea",
  4555. group: 3,
  4556. },
  4557. useSimplePrompt: {
  4558. title: i2("settings.useSimplePrompt"),
  4559. defaultValue: false,
  4560. group: 4,
  4561. },
  4562. openTagsPage: {
  4563. title: i2("settings.openTagsPage"),
  4564. type: "externalLink",
  4565. url: "https://utags.pipecraft.net/tags/",
  4566. group: 5,
  4567. },
  4568. openDataPage: {
  4569. title: i2("settings.openDataPage"),
  4570. type: "externalLink",
  4571. url: "https://utags.pipecraft.net/data/",
  4572. group: 5,
  4573. },
  4574. }
  4575. var addUtagsStyle = () => {
  4576. const style = addStyle(content_default)
  4577. style.id = "utags_style"
  4578. }
  4579. function onSettingsChange() {
  4580. if (getSettingsValue("showHidedItems")) {
  4581. addClass(doc.documentElement, "utags_no_hide")
  4582. } else {
  4583. removeClass(doc.documentElement, "utags_no_hide")
  4584. }
  4585. if (getSettingsValue("noOpacityEffect")) {
  4586. addClass(doc.documentElement, "utags_no_opacity_effect")
  4587. } else {
  4588. removeClass(doc.documentElement, "utags_no_opacity_effect")
  4589. }
  4590. if (!getSettingsValue("enableCurrentSite_".concat(host))) {
  4591. for (const element of $$(".utags_ul")) {
  4592. element.remove()
  4593. }
  4594. const style = $("#utags_style")
  4595. if (style) {
  4596. style.remove()
  4597. }
  4598. }
  4599. }
  4600. var start = 0
  4601. if (start) {
  4602. start = Date.now()
  4603. }
  4604. function appendTagsToPage(element, key, tags, meta) {
  4605. const utagsUl = element.nextSibling
  4606. if (hasClass(utagsUl, "utags_ul")) {
  4607. if (
  4608. element.dataset.utags === tags.join(",") &&
  4609. key === getAttribute(utagsUl, "data-utags_key")
  4610. ) {
  4611. return
  4612. }
  4613. utagsUl.remove()
  4614. } else {
  4615. if (key === getAttribute(utagsUl, "data-utags_key")) {
  4616. utagsUl.remove()
  4617. }
  4618. }
  4619. const ul = createElement("ul", {
  4620. class: tags.length === 0 ? "utags_ul utags_ul_0" : "utags_ul utags_ul_1",
  4621. "data-utags_key": key,
  4622. })
  4623. let li = createElement("li")
  4624. const a = createElement("button", {
  4625. title: "Add tags",
  4626. "data-utags_tag": "\u{1F3F7}\uFE0F",
  4627. "data-utags_key": key,
  4628. "data-utags_tags": tags.join(", "),
  4629. "data-utags_meta": meta ? JSON.stringify(meta) : "",
  4630. class:
  4631. tags.length === 0
  4632. ? "utags_text_tag utags_captain_tag"
  4633. : "utags_text_tag utags_captain_tag2",
  4634. })
  4635. const svg =
  4636. '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" fill="currentColor" class="bi bi-tags-fill" viewBox="0 0 16 16">\n<path d="M2 2a1 1 0 0 1 1-1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 2 6.586V2zm3.5 4a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>\n<path d="M1.293 7.793A1 1 0 0 1 1 7.086V2a1 1 0 0 0-1 1v4.586a1 1 0 0 0 .293.707l7 7a1 1 0 0 0 1.414 0l.043-.043-7.457-7.457z"/>\n</svg>\n'
  4637. a.innerHTML = createHTML(svg)
  4638. li.append(a)
  4639. ul.append(li)
  4640. for (const tag of tags) {
  4641. li = createElement("li")
  4642. const a2 = createTag(tag, {
  4643. isEmoji: emojiTags2.includes(tag),
  4644. noLink: isTagManager,
  4645. enableSelect: isTagManager,
  4646. })
  4647. li.append(a2)
  4648. ul.append(li)
  4649. }
  4650. element.after(ul)
  4651. element.dataset.utags = tags.join(",")
  4652. setTimeout(() => {
  4653. const style = getComputedStyle(element)
  4654. const zIndex = style.zIndex
  4655. if (zIndex && zIndex !== "auto") {
  4656. setStyle(ul, { zIndex })
  4657. }
  4658. }, 200)
  4659. }
  4660. function cleanUnusedUtags() {
  4661. const utagsUlList = $$(".utags_ul")
  4662. for (const utagsUl of utagsUlList) {
  4663. const element = utagsUl.previousSibling
  4664. if (element && getAttribute(element, "data-utags") !== null) {
  4665. continue
  4666. }
  4667. utagsUl.remove()
  4668. }
  4669. }
  4670. async function displayTags() {
  4671. if (start) {
  4672. console.error("start of displayTags", Date.now() - start)
  4673. }
  4674. emojiTags2 = await getEmojiTags()
  4675. const listNodes = getListNodes()
  4676. for (const node of listNodes) {
  4677. node.dataset.utags_list_node = ""
  4678. }
  4679. if (start) {
  4680. console.error("before matchedNodes", Date.now() - start)
  4681. }
  4682. const nodes = matchedNodes()
  4683. if (start) {
  4684. console.error("after matchedNodes", Date.now() - start, nodes.length)
  4685. }
  4686. await getCachedUrlMap()
  4687. for (const node of nodes) {
  4688. const utags = node.utags
  4689. if (!utags) {
  4690. continue
  4691. }
  4692. const key = utags.key
  4693. if (!key) {
  4694. continue
  4695. }
  4696. const object = getTags(key)
  4697. const tags = object.tags || []
  4698. appendTagsToPage(node, key, tags, utags.meta)
  4699. }
  4700. if (start) {
  4701. console.error("after appendTagsToPage", Date.now() - start)
  4702. }
  4703. const conditionNodes = getConditionNodes()
  4704. for (const node of conditionNodes) {
  4705. if (getAttribute(node, "data-utags") !== null) {
  4706. node.dataset.utags_condition_node = ""
  4707. }
  4708. }
  4709. for (const node of listNodes) {
  4710. const conditionNodes2 = $$("[data-utags_condition_node]", node)
  4711. const tagsArray = []
  4712. for (const node2 of conditionNodes2) {
  4713. if (!node2.dataset.utags) {
  4714. continue
  4715. }
  4716. if (node2.closest("[data-utags_list_node]") !== node) {
  4717. continue
  4718. }
  4719. tagsArray.push(node2.dataset.utags)
  4720. }
  4721. if (tagsArray.length === 1) {
  4722. node.dataset.utags_list_node = "," + tagsArray[0] + ","
  4723. } else if (tagsArray.length > 1) {
  4724. node.dataset.utags_list_node =
  4725. "," + uniq(tagsArray.join(",").split(",")).join(",") + ","
  4726. }
  4727. }
  4728. cleanUnusedUtags()
  4729. if (start) {
  4730. console.error("end of displayTags", Date.now() - start)
  4731. }
  4732. }
  4733. var displayTagsThrottled = throttle(displayTags, 1e3)
  4734. async function initStorage() {
  4735. await migration()
  4736. addTagsValueChangeListener(() => {
  4737. if (!doc.hidden) {
  4738. setTimeout(displayTags)
  4739. }
  4740. })
  4741. }
  4742. var nodeNameCheckPattern = /^(A|H\d|DIV|SPAN|P|UL|LI)$/
  4743. function shouldUpdateUtagsWhenNodeUpdated(nodeList) {
  4744. for (const node of nodeList) {
  4745. if (nodeNameCheckPattern.test(node.nodeName)) {
  4746. return true
  4747. }
  4748. }
  4749. return false
  4750. }
  4751. async function main() {
  4752. addUtagsStyle()
  4753. await initSettings({
  4754. id: "utags",
  4755. title: i2("settings.title"),
  4756. footer: "\n <p>"
  4757. .concat(
  4758. i2("settings.information"),
  4759. '</p>\n <p>\n <a href="https://github.com/utags/utags/issues" target="_blank">\n '
  4760. )
  4761. .concat(
  4762. i2("settings.report"),
  4763. '\n </a></p>\n <p>Made with \u2764\uFE0F by\n <a href="https://www.pipecraft.net/" target="_blank">\n Pipecraft\n </a></p>'
  4764. ),
  4765. settingsTable: settingsTable2,
  4766. async onValueChange() {
  4767. onSettingsChange()
  4768. },
  4769. })
  4770. if (!getSettingsValue("enableCurrentSite_".concat(host))) {
  4771. return
  4772. }
  4773. await initStorage()
  4774. setTimeout(outputData, 1)
  4775. onSettingsChange()
  4776. await displayTags()
  4777. addEventListener(doc, "visibilitychange", async () => {
  4778. if (!doc.hidden) {
  4779. await displayTags()
  4780. }
  4781. })
  4782. bindDocumentEvents()
  4783. bindWindowEvents()
  4784. const observer = new MutationObserver(async (mutationsList) => {
  4785. let shouldUpdate = false
  4786. for (const mutationRecord of mutationsList) {
  4787. if (shouldUpdateUtagsWhenNodeUpdated(mutationRecord.addedNodes)) {
  4788. shouldUpdate = true
  4789. break
  4790. }
  4791. if (shouldUpdateUtagsWhenNodeUpdated(mutationRecord.removedNodes)) {
  4792. shouldUpdate = true
  4793. break
  4794. }
  4795. }
  4796. if (shouldUpdate) {
  4797. displayTagsThrottled()
  4798. }
  4799. if ($("#vimiumHintMarkerContainer")) {
  4800. addClass(doc.body, "utags_show_all")
  4801. addClass(doc.documentElement, "utags_vimium_hint")
  4802. } else if (hasClass(doc.documentElement, "utags_vimium_hint")) {
  4803. removeClass(doc.documentElement, "utags_vimium_hint")
  4804. hideAllUtagsInArea()
  4805. }
  4806. })
  4807. observer.observe(doc, {
  4808. childList: true,
  4809. subtree: true,
  4810. })
  4811. }
  4812. runWhenHeadExists(async () => {
  4813. if (doc.documentElement.dataset.utags === void 0) {
  4814. doc.documentElement.dataset.utags = ""
  4815. await main()
  4816. }
  4817. })
  4818. })()