🔗 链接助手

支持所有网站在新标签页中打开第三方网站链接(外链),在新标签页中打开符合指定规则的本站链接,解析文本链接为超链接,微信公众号文本转可点击的超链接,图片链接转图片标签,解析 Markdown 格式链接与图片标签,解析 BBCode 格式链接与图片标签

  1. // ==UserScript==
  2. // @name 🔗 Links Helper
  3. // @name:zh-CN 🔗 链接助手
  4. // @namespace https://github.com/utags/links-helper
  5. // @homepageURL https://github.com/utags/links-helper#readme
  6. // @supportURL https://github.com/utags/links-helper/issues
  7. // @version 0.6.1
  8. // @description Open external links in a new tab, open internal links matching the specified rules in a new tab, convert text to hyperlinks, convert image links to image tags(<img>), parse Markdown style links and image tags, parse BBCode style links and image tags
  9. // @description:zh-CN 支持所有网站在新标签页中打开第三方网站链接(外链),在新标签页中打开符合指定规则的本站链接,解析文本链接为超链接,微信公众号文本转可点击的超链接,图片链接转图片标签,解析 Markdown 格式链接与图片标签,解析 BBCode 格式链接与图片标签
  10. // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMTUnIGhlaWdodD0nMTUnIHZpZXdCb3g9JzAgMCAxNSAxNScgZmlsbD0nbm9uZScgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz48cGF0aCBkPSdNMyAyQzIuNDQ3NzIgMiAyIDIuNDQ3NzIgMiAzVjEyQzIgMTIuNTUyMyAyLjQ0NzcyIDEzIDMgMTNIMTJDMTIuNTUyMyAxMyAxMyAxMi41NTIzIDEzIDEyVjguNUMxMyA4LjIyMzg2IDEyLjc3NjEgOCAxMi41IDhDMTIuMjIzOSA4IDEyIDguMjIzODYgMTIgOC41VjEySDNWM0w2LjUgM0M2Ljc3NjE0IDMgNyAyLjc3NjE0IDcgMi41QzcgMi4yMjM4NiA2Ljc3NjE0IDIgNi41IDJIM1pNMTIuODUzNiAyLjE0NjQ1QzEyLjkwMTUgMi4xOTQzOSAxMi45Mzc3IDIuMjQ5NjQgMTIuOTYyMSAyLjMwODYxQzEyLjk4NjEgMi4zNjY2OSAxMi45OTk2IDIuNDMwMyAxMyAyLjQ5N0wxMyAyLjVWMi41MDA0OVY1LjVDMTMgNS43NzYxNCAxMi43NzYxIDYgMTIuNSA2QzEyLjIyMzkgNiAxMiA1Ljc3NjE0IDEyIDUuNVYzLjcwNzExTDYuODUzNTUgOC44NTM1NUM2LjY1ODI5IDkuMDQ4ODIgNi4zNDE3MSA5LjA0ODgyIDYuMTQ2NDUgOC44NTM1NUM1Ljk1MTE4IDguNjU4MjkgNS45NTExOCA4LjM0MTcxIDYuMTQ2NDUgOC4xNDY0NUwxMS4yOTI5IDNIOS41QzkuMjIzODYgMyA5IDIuNzc2MTQgOSAyLjVDOSAyLjIyMzg2IDkuMjIzODYgMiA5LjUgMkgxMi40OTk5SDEyLjVDMTIuNTY3OCAyIDEyLjYzMjQgMi4wMTM0OSAxMi42OTE0IDIuMDM3OTRDMTIuNzUwNCAyLjA2MjM0IDEyLjgwNTYgMi4wOTg1MSAxMi44NTM2IDIuMTQ2NDVaJyBmaWxsPSdjdXJyZW50Q29sb3InIGZpbGwtcnVsZT0nZXZlbm9kZCcgY2xpcC1ydWxlPSdldmVub2RkJz48L3BhdGg+PC9zdmc+
  11. // @author Pipecraft
  12. // @license MIT
  13. // @match https://*/*
  14. // @match http://*/*
  15. // @run-at document-start
  16. // @grant GM.getValue
  17. // @grant GM.setValue
  18. // @grant GM_addValueChangeListener
  19. // @grant GM_removeValueChangeListener
  20. // @grant GM_addElement
  21. // @grant GM.registerMenuCommand
  22. // ==/UserScript==
  23. //
  24. ;(() => {
  25. "use strict"
  26. var listeners = {}
  27. var getValue = async (key) => {
  28. const value = await GM.getValue(key)
  29. return value && value !== "undefined" ? JSON.parse(value) : void 0
  30. }
  31. var setValue = async (key, value) => {
  32. if (value !== void 0) {
  33. const newValue = JSON.stringify(value)
  34. if (listeners[key]) {
  35. const oldValue = await GM.getValue(key)
  36. await GM.setValue(key, newValue)
  37. if (newValue !== oldValue) {
  38. for (const func of listeners[key]) {
  39. func(key, oldValue, newValue)
  40. }
  41. }
  42. } else {
  43. await GM.setValue(key, newValue)
  44. }
  45. }
  46. }
  47. var _addValueChangeListener = (key, func) => {
  48. listeners[key] = listeners[key] || []
  49. listeners[key].push(func)
  50. return () => {
  51. if (listeners[key] && listeners[key].length > 0) {
  52. for (let i3 = listeners[key].length - 1; i3 >= 0; i3--) {
  53. if (listeners[key][i3] === func) {
  54. listeners[key].splice(i3, 1)
  55. }
  56. }
  57. }
  58. }
  59. }
  60. var addValueChangeListener = (key, func) => {
  61. if (typeof GM_addValueChangeListener !== "function") {
  62. console.warn("Do not support GM_addValueChangeListener!")
  63. return _addValueChangeListener(key, func)
  64. }
  65. const listenerId = GM_addValueChangeListener(key, func)
  66. return () => {
  67. GM_removeValueChangeListener(listenerId)
  68. }
  69. }
  70. var doc = document
  71. if (typeof String.prototype.replaceAll !== "function") {
  72. String.prototype.replaceAll = String.prototype.replace
  73. }
  74. var $ = (selectors, element) => (element || doc).querySelector(selectors)
  75. var $$ = (selectors, element) => [
  76. ...(element || doc).querySelectorAll(selectors),
  77. ]
  78. var getRootElement = (type) =>
  79. type === 1
  80. ? doc.head || doc.body || doc.documentElement
  81. : type === 2
  82. ? doc.body || doc.documentElement
  83. : doc.documentElement
  84. var createElement = (tagName, attributes) =>
  85. setAttributes(doc.createElement(tagName), attributes)
  86. var addElement = (parentNode, tagName, attributes) => {
  87. if (typeof parentNode === "string") {
  88. return addElement(null, parentNode, tagName)
  89. }
  90. if (!tagName) {
  91. return
  92. }
  93. if (!parentNode) {
  94. parentNode = /^(script|link|style|meta)$/.test(tagName)
  95. ? getRootElement(1)
  96. : getRootElement(2)
  97. }
  98. if (typeof tagName === "string") {
  99. const element = createElement(tagName, attributes)
  100. parentNode.append(element)
  101. return element
  102. }
  103. setAttributes(tagName, attributes)
  104. parentNode.append(tagName)
  105. return tagName
  106. }
  107. var addEventListener = (element, type, listener, options) => {
  108. if (!element) {
  109. return
  110. }
  111. if (typeof type === "object") {
  112. for (const type1 in type) {
  113. if (Object.hasOwn(type, type1)) {
  114. element.addEventListener(type1, type[type1])
  115. }
  116. }
  117. } else if (typeof type === "string" && typeof listener === "function") {
  118. element.addEventListener(type, listener, options)
  119. }
  120. }
  121. var removeEventListener = (element, type, listener, options) => {
  122. if (!element) {
  123. return
  124. }
  125. if (typeof type === "object") {
  126. for (const type1 in type) {
  127. if (Object.hasOwn(type, type1)) {
  128. element.removeEventListener(type1, type[type1])
  129. }
  130. }
  131. } else if (typeof type === "string" && typeof listener === "function") {
  132. element.removeEventListener(type, listener, options)
  133. }
  134. }
  135. var getAttribute = (element, name) =>
  136. element ? element.getAttribute(name) : null
  137. var setAttribute = (element, name, value) =>
  138. element ? element.setAttribute(name, value) : void 0
  139. var setAttributes = (element, attributes) => {
  140. if (element && attributes) {
  141. for (const name in attributes) {
  142. if (Object.hasOwn(attributes, name)) {
  143. const value = attributes[name]
  144. if (value === void 0) {
  145. continue
  146. }
  147. if (/^(value|textContent|innerText)$/.test(name)) {
  148. element[name] = value
  149. } else if (/^(innerHTML)$/.test(name)) {
  150. element[name] = createHTML(value)
  151. } else if (name === "style") {
  152. setStyle(element, value, true)
  153. } else if (/on\w+/.test(name)) {
  154. const type = name.slice(2)
  155. addEventListener(element, type, value)
  156. } else {
  157. setAttribute(element, name, value)
  158. }
  159. }
  160. }
  161. }
  162. return element
  163. }
  164. var addAttribute = (element, name, value) => {
  165. const orgValue = getAttribute(element, name)
  166. if (!orgValue) {
  167. setAttribute(element, name, value)
  168. } else if (!orgValue.includes(value)) {
  169. setAttribute(element, name, orgValue + " " + value)
  170. }
  171. }
  172. var addClass = (element, className) => {
  173. if (!element || !element.classList) {
  174. return
  175. }
  176. element.classList.add(className)
  177. }
  178. var removeClass = (element, className) => {
  179. if (!element || !element.classList) {
  180. return
  181. }
  182. element.classList.remove(className)
  183. }
  184. var hasClass = (element, className) => {
  185. if (!element || !element.classList) {
  186. return false
  187. }
  188. return element.classList.contains(className)
  189. }
  190. var setStyle = (element, values, overwrite) => {
  191. if (!element) {
  192. return
  193. }
  194. const style = element.style
  195. if (typeof values === "string") {
  196. style.cssText = overwrite ? values : style.cssText + ";" + values
  197. return
  198. }
  199. if (overwrite) {
  200. style.cssText = ""
  201. }
  202. for (const key in values) {
  203. if (Object.hasOwn(values, key)) {
  204. style[key] = values[key].replace("!important", "")
  205. }
  206. }
  207. }
  208. var throttle = (func, interval) => {
  209. let timeoutId = null
  210. let next = false
  211. const handler = (...args) => {
  212. if (timeoutId) {
  213. next = true
  214. } else {
  215. func.apply(void 0, args)
  216. timeoutId = setTimeout(() => {
  217. timeoutId = null
  218. if (next) {
  219. next = false
  220. handler()
  221. }
  222. }, interval)
  223. }
  224. }
  225. return handler
  226. }
  227. if (typeof Object.hasOwn !== "function") {
  228. Object.hasOwn = (instance, prop) =>
  229. Object.prototype.hasOwnProperty.call(instance, prop)
  230. }
  231. var parseInt10 = (number, defaultValue) => {
  232. if (typeof number === "number" && !Number.isNaN(number)) {
  233. return number
  234. }
  235. if (typeof defaultValue !== "number") {
  236. defaultValue = Number.NaN
  237. }
  238. if (!number) {
  239. return defaultValue
  240. }
  241. const result = Number.parseInt(number, 10)
  242. return Number.isNaN(result) ? defaultValue : result
  243. }
  244. var rootFuncArray = []
  245. var headFuncArray = []
  246. var bodyFuncArray = []
  247. var headBodyObserver
  248. var startObserveHeadBodyExists = () => {
  249. if (headBodyObserver) {
  250. return
  251. }
  252. headBodyObserver = new MutationObserver(() => {
  253. if (doc.head && doc.body) {
  254. headBodyObserver.disconnect()
  255. }
  256. if (doc.documentElement && rootFuncArray.length > 0) {
  257. for (const func of rootFuncArray) {
  258. func()
  259. }
  260. rootFuncArray.length = 0
  261. }
  262. if (doc.head && headFuncArray.length > 0) {
  263. for (const func of headFuncArray) {
  264. func()
  265. }
  266. headFuncArray.length = 0
  267. }
  268. if (doc.body && bodyFuncArray.length > 0) {
  269. for (const func of bodyFuncArray) {
  270. func()
  271. }
  272. bodyFuncArray.length = 0
  273. }
  274. })
  275. headBodyObserver.observe(doc, {
  276. childList: true,
  277. subtree: true,
  278. })
  279. }
  280. var runWhenHeadExists = (func) => {
  281. if (!doc.head) {
  282. headFuncArray.push(func)
  283. startObserveHeadBodyExists()
  284. return
  285. }
  286. func()
  287. }
  288. var runWhenBodyExists = (func) => {
  289. if (!doc.body) {
  290. bodyFuncArray.push(func)
  291. startObserveHeadBodyExists()
  292. return
  293. }
  294. func()
  295. }
  296. var runWhenDomReady = (func) => {
  297. if (doc.readyState === "interactive" || doc.readyState === "complete") {
  298. return func()
  299. }
  300. const handler = () => {
  301. if (doc.readyState === "interactive" || doc.readyState === "complete") {
  302. func()
  303. removeEventListener(doc, "readystatechange", handler)
  304. }
  305. }
  306. addEventListener(doc, "readystatechange", handler)
  307. }
  308. var escapeHTMLPolicy =
  309. typeof trustedTypes !== "undefined" &&
  310. typeof trustedTypes.createPolicy === "function"
  311. ? trustedTypes.createPolicy("beuEscapePolicy", {
  312. createHTML: (string) => string,
  313. })
  314. : void 0
  315. var createHTML = (html) => {
  316. return escapeHTMLPolicy ? escapeHTMLPolicy.createHTML(html) : html
  317. }
  318. var addElement2 =
  319. typeof GM_addElement === "function"
  320. ? (parentNode, tagName, attributes) => {
  321. if (typeof parentNode === "string") {
  322. return addElement2(null, parentNode, tagName)
  323. }
  324. if (!tagName) {
  325. return
  326. }
  327. if (!parentNode) {
  328. parentNode = /^(script|link|style|meta)$/.test(tagName)
  329. ? getRootElement(1)
  330. : getRootElement(2)
  331. }
  332. if (typeof tagName === "string") {
  333. let attributes2
  334. if (attributes) {
  335. const entries1 = []
  336. const entries2 = []
  337. for (const entry of Object.entries(attributes)) {
  338. if (/^(on\w+|innerHTML)$/.test(entry[0])) {
  339. entries2.push(entry)
  340. } else {
  341. entries1.push(entry)
  342. }
  343. }
  344. attributes = Object.fromEntries(entries1)
  345. attributes2 = Object.fromEntries(entries2)
  346. }
  347. const element = GM_addElement(null, tagName, attributes)
  348. setAttributes(element, attributes2)
  349. parentNode.append(element)
  350. return element
  351. }
  352. setAttributes(tagName, attributes)
  353. parentNode.append(tagName)
  354. return tagName
  355. }
  356. : addElement
  357. var addStyle = (styleText) =>
  358. addElement2(null, "style", { textContent: styleText })
  359. var registerMenuCommand = (name, callback, accessKey) => {
  360. if (window !== top) {
  361. return
  362. }
  363. if (typeof GM.registerMenuCommand !== "function") {
  364. console.warn("Do not support GM.registerMenuCommand!")
  365. return
  366. }
  367. GM.registerMenuCommand(name, callback, accessKey)
  368. }
  369. var style_default =
  370. '#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}}'
  371. function createSwitch(options = {}) {
  372. const container = createElement("label", { class: "bes_switch_container" })
  373. const checkbox = createElement(
  374. "input",
  375. options.checked ? { type: "checkbox", checked: "" } : { type: "checkbox" }
  376. )
  377. addElement2(container, checkbox)
  378. const switchElm = createElement("span", { class: "bes_switch" })
  379. addElement2(switchElm, "span", { class: "bes_slider" })
  380. addElement2(container, switchElm)
  381. if (options.onchange) {
  382. addEventListener(checkbox, "change", options.onchange)
  383. }
  384. return container
  385. }
  386. function createSwitchOption(icon, text, options) {
  387. if (typeof text !== "string") {
  388. return createSwitchOption(void 0, icon, text)
  389. }
  390. const div = createElement("div", { class: "switch_option bes_option" })
  391. if (icon) {
  392. addElement2(div, "img", { src: icon, class: "bes_icon" })
  393. }
  394. addElement2(div, "span", { textContent: text, class: "bes_title" })
  395. div.append(createSwitch(options))
  396. return div
  397. }
  398. var besVersion = 55
  399. var openButton =
  400. '<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>'
  401. var openInNewTabButton =
  402. '<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>'
  403. var settingButton =
  404. '<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>'
  405. function initI18n(messageMaps, language) {
  406. language = (language || navigator.language).toLowerCase()
  407. const language2 = language.slice(0, 2)
  408. let messagesDefault
  409. let messagesLocal
  410. for (const entry of Object.entries(messageMaps)) {
  411. const langs = new Set(
  412. entry[0]
  413. .toLowerCase()
  414. .split(",")
  415. .map((v) => v.trim())
  416. )
  417. const value = entry[1]
  418. if (langs.has(language)) {
  419. messagesLocal = value
  420. }
  421. if (langs.has(language2) && !messagesLocal) {
  422. messagesLocal = value
  423. }
  424. if (langs.has("en")) {
  425. messagesDefault = value
  426. }
  427. if (langs.has("en-us") && !messagesDefault) {
  428. messagesDefault = value
  429. }
  430. }
  431. if (!messagesLocal) {
  432. messagesLocal = {}
  433. }
  434. if (!messagesDefault || messagesDefault === messagesLocal) {
  435. messagesDefault = {}
  436. }
  437. return function (key, ...parameters) {
  438. let text = messagesLocal[key] || messagesDefault[key] || key
  439. if (parameters && parameters.length > 0 && text !== key) {
  440. for (let i3 = 0; i3 < parameters.length; i3++) {
  441. text = text.replaceAll(
  442. new RegExp("\\{".concat(i3 + 1, "\\}"), "g"),
  443. String(parameters[i3])
  444. )
  445. }
  446. }
  447. return text
  448. }
  449. }
  450. var messages = {
  451. "settings.title": "Settings",
  452. "settings.otherExtensions": "Other Extensions",
  453. "settings.displaySettingsButtonInSideMenu":
  454. "Display Settings Button in Side Menu",
  455. "settings.menu.settings": "\u2699\uFE0F Settings",
  456. "settings.extensions.utags.title":
  457. "\u{1F3F7}\uFE0F UTags - Add usertags to links",
  458. "settings.extensions.links-helper.title": "\u{1F517} Links Helper",
  459. "settings.extensions.v2ex.rep.title":
  460. "V2EX.REP - \u4E13\u6CE8\u63D0\u5347 V2EX \u4E3B\u9898\u56DE\u590D\u6D4F\u89C8\u4F53\u9A8C",
  461. "settings.extensions.v2ex.min.title":
  462. "v2ex.min - V2EX Minimalist (\u6781\u7B80\u98CE\u683C)",
  463. "settings.extensions.replace-ugly-avatars.title": "Replace Ugly Avatars",
  464. "settings.extensions.more-by-pipecraft.title":
  465. "Find more useful userscripts",
  466. }
  467. var en_default = messages
  468. var messages2 = {
  469. "settings.title": "\u8BBE\u7F6E",
  470. "settings.otherExtensions": "\u5176\u4ED6\u6269\u5C55",
  471. "settings.displaySettingsButtonInSideMenu":
  472. "\u5728\u4FA7\u8FB9\u680F\u83DC\u5355\u4E2D\u663E\u793A\u8BBE\u7F6E\u6309\u94AE",
  473. "settings.menu.settings": "\u2699\uFE0F \u8BBE\u7F6E",
  474. "settings.extensions.utags.title":
  475. "\u{1F3F7}\uFE0F \u5C0F\u9C7C\u6807\u7B7E (UTags) - \u4E3A\u94FE\u63A5\u6DFB\u52A0\u7528\u6237\u6807\u7B7E",
  476. "settings.extensions.links-helper.title":
  477. "\u{1F517} \u94FE\u63A5\u52A9\u624B",
  478. "settings.extensions.v2ex.rep.title":
  479. "V2EX.REP - \u4E13\u6CE8\u63D0\u5347 V2EX \u4E3B\u9898\u56DE\u590D\u6D4F\u89C8\u4F53\u9A8C",
  480. "settings.extensions.v2ex.min.title":
  481. "v2ex.min - V2EX \u6781\u7B80\u98CE\u683C",
  482. "settings.extensions.replace-ugly-avatars.title":
  483. "\u8D50\u4F60\u4E2A\u5934\u50CF\u5427",
  484. "settings.extensions.more-by-pipecraft.title":
  485. "\u66F4\u591A\u6709\u8DA3\u7684\u811A\u672C",
  486. }
  487. var zh_cn_default = messages2
  488. var i = initI18n({
  489. "en,en-US": en_default,
  490. "zh,zh-CN": zh_cn_default,
  491. })
  492. var lang = navigator.language
  493. var locale
  494. if (lang === "zh-TW" || lang === "zh-HK") {
  495. locale = "zh-TW"
  496. } else if (lang.includes("zh")) {
  497. locale = "zh-CN"
  498. } else {
  499. locale = "en"
  500. }
  501. var relatedExtensions = [
  502. {
  503. id: "utags",
  504. title: i("settings.extensions.utags.title"),
  505. url: "https://greasyfork.org/".concat(
  506. locale,
  507. "/scripts/460718-utags-add-usertags-to-links"
  508. ),
  509. },
  510. {
  511. id: "links-helper",
  512. title: i("settings.extensions.links-helper.title"),
  513. description:
  514. "\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",
  515. url: "https://greasyfork.org/".concat(
  516. locale,
  517. "/scripts/464541-links-helper"
  518. ),
  519. },
  520. {
  521. id: "v2ex.rep",
  522. title: i("settings.extensions.v2ex.rep.title"),
  523. url: "https://greasyfork.org/".concat(
  524. locale,
  525. "/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"
  526. ),
  527. },
  528. {
  529. id: "v2ex.min",
  530. title: i("settings.extensions.v2ex.min.title"),
  531. url: "https://greasyfork.org/".concat(
  532. locale,
  533. "/scripts/463552-v2ex-min-v2ex-%E6%9E%81%E7%AE%80%E9%A3%8E%E6%A0%BC"
  534. ),
  535. },
  536. {
  537. id: "replace-ugly-avatars",
  538. title: i("settings.extensions.replace-ugly-avatars.title"),
  539. url: "https://greasyfork.org/".concat(
  540. locale,
  541. "/scripts/472616-replace-ugly-avatars"
  542. ),
  543. },
  544. {
  545. id: "more-by-pipecraft",
  546. title: i("settings.extensions.more-by-pipecraft.title"),
  547. url: "https://greasyfork.org/".concat(locale, "/users/1030884-pipecraft"),
  548. },
  549. ]
  550. var getInstalledExtesionList = () => {
  551. return $(".extension_list_container .installed_extension_list")
  552. }
  553. var getRelatedExtesionList = () => {
  554. return $(".extension_list_container .related_extension_list")
  555. }
  556. var isInstalledExtension = (id) => {
  557. const list = getInstalledExtesionList()
  558. if (!list) {
  559. return false
  560. }
  561. const installed = $('[data-extension-id="'.concat(id, '"]'), list)
  562. return Boolean(installed)
  563. }
  564. var addCurrentExtension = (extension) => {
  565. const list = getInstalledExtesionList()
  566. if (!list) {
  567. return
  568. }
  569. if (isInstalledExtension(extension.id)) {
  570. return
  571. }
  572. const element = createInstalledExtension(extension)
  573. list.append(element)
  574. const list2 = getRelatedExtesionList()
  575. if (list2) {
  576. updateRelatedExtensions(list2)
  577. }
  578. }
  579. var activeExtension = (id) => {
  580. const list = getInstalledExtesionList()
  581. if (!list) {
  582. return false
  583. }
  584. for (const element of $$(".active", list)) {
  585. removeClass(element, "active")
  586. }
  587. const installed = $('[data-extension-id="'.concat(id, '"]'), list)
  588. if (installed) {
  589. addClass(installed, "active")
  590. }
  591. }
  592. var activeExtensionList = () => {
  593. const extensionListContainer = $(".extension_list_container")
  594. if (extensionListContainer) {
  595. addClass(extensionListContainer, "bes_active")
  596. }
  597. }
  598. var deactiveExtensionList = () => {
  599. const extensionListContainer = $(".extension_list_container")
  600. if (extensionListContainer) {
  601. removeClass(extensionListContainer, "bes_active")
  602. }
  603. }
  604. var createInstalledExtension = (installedExtension) => {
  605. const div = createElement("div", {
  606. class: "installed_extension",
  607. "data-extension-id": installedExtension.id,
  608. })
  609. const a = addElement2(div, "a", {
  610. onclick: installedExtension.onclick,
  611. })
  612. addElement2(a, "span", {
  613. textContent: installedExtension.title,
  614. })
  615. const svg = addElement2(a, "svg")
  616. svg.outerHTML = createHTML(openButton)
  617. return div
  618. }
  619. var updateRelatedExtensions = (container) => {
  620. const relatedExtensionElements = $$("[data-extension-id]", container)
  621. if (relatedExtensionElements.length > 0) {
  622. for (const relatedExtensionElement of relatedExtensionElements) {
  623. if (
  624. isInstalledExtension(
  625. relatedExtensionElement.dataset.extensionId || "noid"
  626. )
  627. ) {
  628. relatedExtensionElement.remove()
  629. }
  630. }
  631. } else {
  632. container.innerHTML = createHTML("")
  633. }
  634. for (const relatedExtension of relatedExtensions) {
  635. if (
  636. isInstalledExtension(relatedExtension.id) ||
  637. $('[data-extension-id="'.concat(relatedExtension.id, '"]'), container)
  638. ) {
  639. continue
  640. }
  641. if ($$("[data-extension-id]", container).length >= 4) {
  642. return
  643. }
  644. const div4 = addElement2(container, "div", {
  645. class: "related_extension",
  646. "data-extension-id": relatedExtension.id,
  647. })
  648. const a = addElement2(div4, "a", {
  649. href: relatedExtension.url,
  650. target: "_blank",
  651. })
  652. addElement2(a, "span", {
  653. textContent: relatedExtension.title,
  654. })
  655. const svg = addElement2(a, "svg")
  656. svg.outerHTML = createHTML(openInNewTabButton)
  657. }
  658. }
  659. function createExtensionList(installedExtensions) {
  660. const div = createElement("div", {
  661. class: "extension_list_container thin_scrollbar",
  662. })
  663. addElement2(div, "h1", { textContent: i("settings.title") })
  664. const div2 = addElement2(div, "div", {
  665. class: "installed_extension_list",
  666. })
  667. for (const installedExtension of installedExtensions) {
  668. if (isInstalledExtension(installedExtension.id)) {
  669. continue
  670. }
  671. const element = createInstalledExtension(installedExtension)
  672. div2.append(element)
  673. }
  674. addElement2(div, "h2", { textContent: i("settings.otherExtensions") })
  675. const div3 = addElement2(div, "div", {
  676. class: "related_extension_list",
  677. })
  678. updateRelatedExtensions(div3)
  679. return div
  680. }
  681. var prefix = "browser_extension_settings_"
  682. var randomId = String(Math.round(Math.random() * 1e4))
  683. var settingsContainerId = prefix + "container_" + randomId
  684. var settingsElementId = prefix + "main_" + randomId
  685. var getSettingsElement = () => $("#" + settingsElementId)
  686. var getSettingsStyle = () =>
  687. style_default
  688. .replaceAll(/browser_extension_settings_container/gm, settingsContainerId)
  689. .replaceAll(/browser_extension_settings_main/gm, settingsElementId)
  690. var storageKey = "settings"
  691. var settingsOptions
  692. var settingsTable = {}
  693. var settings = {}
  694. async function getSettings() {
  695. var _a
  696. return (_a = await getValue(storageKey)) != null ? _a : {}
  697. }
  698. async function saveSettingsValue(key, value) {
  699. const settings2 = await getSettings()
  700. settings2[key] =
  701. settingsTable[key] && settingsTable[key].defaultValue === value
  702. ? void 0
  703. : value
  704. await setValue(storageKey, settings2)
  705. }
  706. function getSettingsValue(key) {
  707. var _a
  708. return Object.hasOwn(settings, key)
  709. ? settings[key]
  710. : (_a = settingsTable[key]) == null
  711. ? void 0
  712. : _a.defaultValue
  713. }
  714. var closeModal = () => {
  715. const settingsContainer = getSettingsContainer()
  716. if (settingsContainer) {
  717. settingsContainer.style.display = "none"
  718. }
  719. removeEventListener(document, "click", onDocumentClick, true)
  720. removeEventListener(document, "keydown", onDocumentKeyDown, true)
  721. }
  722. function hideSettings() {
  723. closeModal()
  724. }
  725. var onDocumentClick = (event) => {
  726. const target = event.target
  727. if (
  728. target == null ? void 0 : target.closest(".".concat(prefix, "container"))
  729. ) {
  730. return
  731. }
  732. closeModal()
  733. }
  734. var onDocumentKeyDown = (event) => {
  735. if (event.defaultPrevented) {
  736. return
  737. }
  738. if (event.key === "Escape") {
  739. closeModal()
  740. event.preventDefault()
  741. }
  742. }
  743. async function updateOptions() {
  744. if (!getSettingsElement()) {
  745. return
  746. }
  747. for (const key in settingsTable) {
  748. if (Object.hasOwn(settingsTable, key)) {
  749. const item = settingsTable[key]
  750. const type = item.type || "switch"
  751. switch (type) {
  752. case "switch": {
  753. const checkbox = $(
  754. "#"
  755. .concat(
  756. settingsElementId,
  757. ' .option_groups .switch_option[data-key="'
  758. )
  759. .concat(key, '"] input')
  760. )
  761. if (checkbox) {
  762. checkbox.checked = getSettingsValue(key)
  763. }
  764. break
  765. }
  766. case "select": {
  767. const options = $$(
  768. "#"
  769. .concat(
  770. settingsElementId,
  771. ' .option_groups .select_option[data-key="'
  772. )
  773. .concat(key, '"] .bes_select option')
  774. )
  775. for (const option of options) {
  776. option.selected = option.value === String(getSettingsValue(key))
  777. }
  778. break
  779. }
  780. case "textarea": {
  781. const textArea = $(
  782. "#"
  783. .concat(
  784. settingsElementId,
  785. ' .option_groups textarea[data-key="'
  786. )
  787. .concat(key, '"]')
  788. )
  789. if (textArea) {
  790. textArea.value = getSettingsValue(key)
  791. }
  792. break
  793. }
  794. default: {
  795. break
  796. }
  797. }
  798. }
  799. }
  800. if (typeof settingsOptions.onViewUpdate === "function") {
  801. const settingsMain = createSettingsElement()
  802. settingsOptions.onViewUpdate(settingsMain)
  803. }
  804. }
  805. function getSettingsContainer() {
  806. const container = $(".".concat(prefix, "container"))
  807. if (container) {
  808. const theVersion = parseInt10(container.dataset.besVersion, 0)
  809. if (theVersion < besVersion) {
  810. container.id = settingsContainerId
  811. container.dataset.besVersion = String(besVersion)
  812. }
  813. return container
  814. }
  815. return addElement2(doc.body, "div", {
  816. id: settingsContainerId,
  817. class: "".concat(prefix, "container"),
  818. "data-bes-version": besVersion,
  819. style: "display: none;",
  820. })
  821. }
  822. function getSettingsWrapper() {
  823. const container = getSettingsContainer()
  824. return (
  825. $(".".concat(prefix, "wrapper"), container) ||
  826. addElement2(container, "div", {
  827. class: "".concat(prefix, "wrapper"),
  828. })
  829. )
  830. }
  831. function initExtensionList() {
  832. const wrapper = getSettingsWrapper()
  833. if (!$(".extension_list_container", wrapper)) {
  834. const list = createExtensionList([])
  835. wrapper.append(list)
  836. }
  837. addCurrentExtension({
  838. id: settingsOptions.id,
  839. title: settingsOptions.title,
  840. onclick: showSettings,
  841. })
  842. }
  843. function createSettingsElement() {
  844. let settingsMain = getSettingsElement()
  845. if (!settingsMain) {
  846. const wrapper = getSettingsWrapper()
  847. for (const element of $$(".".concat(prefix, "main"))) {
  848. element.remove()
  849. }
  850. settingsMain = addElement2(wrapper, "div", {
  851. id: settingsElementId,
  852. class: "".concat(prefix, "main thin_scrollbar"),
  853. })
  854. addElement2(settingsMain, "a", {
  855. textContent: "Settings",
  856. class: "navigation_go_previous",
  857. onclick() {
  858. activeExtensionList()
  859. },
  860. })
  861. if (settingsOptions.title) {
  862. addElement2(settingsMain, "h2", { textContent: settingsOptions.title })
  863. }
  864. const optionGroups = []
  865. const getOptionGroup = (index) => {
  866. if (index > optionGroups.length) {
  867. for (let i3 = optionGroups.length; i3 < index; i3++) {
  868. optionGroups.push(
  869. addElement2(settingsMain, "div", {
  870. class: "option_groups",
  871. })
  872. )
  873. }
  874. }
  875. return optionGroups[index - 1]
  876. }
  877. for (const key in settingsTable) {
  878. if (Object.hasOwn(settingsTable, key)) {
  879. const item = settingsTable[key]
  880. const type = item.type || "switch"
  881. const group = item.group || 1
  882. const optionGroup = getOptionGroup(group)
  883. switch (type) {
  884. case "switch": {
  885. const switchOption = createSwitchOption(item.icon, item.title, {
  886. async onchange(event) {
  887. const checkbox = event.target
  888. if (checkbox) {
  889. let result = true
  890. if (typeof item.onConfirmChange === "function") {
  891. result = item.onConfirmChange(checkbox.checked)
  892. }
  893. if (result) {
  894. await saveSettingsValue(key, checkbox.checked)
  895. } else {
  896. checkbox.checked = !checkbox.checked
  897. }
  898. }
  899. },
  900. })
  901. switchOption.dataset.key = key
  902. addElement2(optionGroup, switchOption)
  903. break
  904. }
  905. case "textarea": {
  906. let timeoutId
  907. const div = addElement2(optionGroup, "div", {
  908. class: "bes_textarea",
  909. })
  910. addElement2(div, "textarea", {
  911. "data-key": key,
  912. placeholder: item.placeholder || "",
  913. onkeyup(event) {
  914. const textArea = event.target
  915. if (timeoutId) {
  916. clearTimeout(timeoutId)
  917. timeoutId = void 0
  918. }
  919. timeoutId = setTimeout(async () => {
  920. if (textArea) {
  921. await saveSettingsValue(key, textArea.value.trim())
  922. }
  923. }, 100)
  924. },
  925. })
  926. break
  927. }
  928. case "action": {
  929. addElement2(optionGroup, "a", {
  930. class: "action",
  931. textContent: item.title,
  932. onclick: item.onclick,
  933. })
  934. break
  935. }
  936. case "externalLink": {
  937. const div4 = addElement2(optionGroup, "div", {
  938. class: "bes_external_link",
  939. })
  940. addElement2(div4, "a", {
  941. textContent: item.title,
  942. href: item.url,
  943. target: "_blank",
  944. })
  945. break
  946. }
  947. case "select": {
  948. const div = addElement2(optionGroup, "div", {
  949. class: "select_option bes_option",
  950. "data-key": key,
  951. })
  952. if (item.icon) {
  953. addElement2(div, "img", { src: item.icon, class: "bes_icon" })
  954. }
  955. addElement2(div, "span", {
  956. textContent: item.title,
  957. class: "bes_title",
  958. })
  959. const select = addElement2(div, "select", {
  960. class: "bes_select",
  961. async onchange() {
  962. await saveSettingsValue(key, select.value)
  963. },
  964. })
  965. for (const option of Object.entries(item.options)) {
  966. addElement2(select, "option", {
  967. textContent: option[0],
  968. value: option[1],
  969. })
  970. }
  971. break
  972. }
  973. case "tip": {
  974. const tip = addElement2(optionGroup, "div", {
  975. class: "bes_tip",
  976. })
  977. addElement2(tip, "a", {
  978. class: "bes_tip_anchor",
  979. textContent: item.title,
  980. })
  981. const tipContent = addElement2(tip, "div", {
  982. class: "bes_tip_content",
  983. innerHTML: createHTML(item.tipContent),
  984. })
  985. break
  986. }
  987. }
  988. }
  989. }
  990. if (settingsOptions.footer) {
  991. const footer = addElement2(settingsMain, "footer")
  992. footer.innerHTML = createHTML(
  993. typeof settingsOptions.footer === "string"
  994. ? settingsOptions.footer
  995. : '<p>Made with \u2764\uFE0F by\n <a href="https://www.pipecraft.net/" target="_blank">\n Pipecraft\n </a></p>'
  996. )
  997. }
  998. }
  999. return settingsMain
  1000. }
  1001. function addSideMenu() {
  1002. if (!getSettingsValue("displaySettingsButtonInSideMenu")) {
  1003. return
  1004. }
  1005. const menu =
  1006. $("#browser_extension_side_menu") ||
  1007. addElement2(doc.body, "div", {
  1008. id: "browser_extension_side_menu",
  1009. "data-bes-version": besVersion,
  1010. })
  1011. const button = $("button[data-bes-version]", menu)
  1012. if (button) {
  1013. const theVersion = parseInt10(button.dataset.besVersion, 0)
  1014. if (theVersion >= besVersion) {
  1015. return
  1016. }
  1017. button.remove()
  1018. }
  1019. addElement2(menu, "button", {
  1020. type: "button",
  1021. "data-bes-version": besVersion,
  1022. title: i("settings.menu.settings"),
  1023. onclick() {
  1024. setTimeout(showSettings, 1)
  1025. },
  1026. innerHTML: settingButton,
  1027. })
  1028. }
  1029. function addCommonSettings(settingsTable3) {
  1030. let maxGroup = 0
  1031. for (const key in settingsTable3) {
  1032. if (Object.hasOwn(settingsTable3, key)) {
  1033. const item = settingsTable3[key]
  1034. const group = item.group || 1
  1035. if (group > maxGroup) {
  1036. maxGroup = group
  1037. }
  1038. }
  1039. }
  1040. settingsTable3.displaySettingsButtonInSideMenu = {
  1041. title: i("settings.displaySettingsButtonInSideMenu"),
  1042. defaultValue: !(
  1043. typeof GM === "object" && typeof GM.registerMenuCommand === "function"
  1044. ),
  1045. group: maxGroup + 1,
  1046. }
  1047. }
  1048. function handleShowSettingsUrl() {
  1049. if (location.hash === "#bes-show-settings") {
  1050. setTimeout(showSettings, 100)
  1051. }
  1052. }
  1053. async function showSettings() {
  1054. const settingsContainer = getSettingsContainer()
  1055. const settingsMain = createSettingsElement()
  1056. await updateOptions()
  1057. settingsContainer.style.display = "block"
  1058. addEventListener(document, "click", onDocumentClick, true)
  1059. addEventListener(document, "keydown", onDocumentKeyDown, true)
  1060. activeExtension(settingsOptions.id)
  1061. deactiveExtensionList()
  1062. }
  1063. var initSettings = async (options) => {
  1064. settingsOptions = options
  1065. settingsTable = options.settingsTable || {}
  1066. addCommonSettings(settingsTable)
  1067. addValueChangeListener(storageKey, async () => {
  1068. settings = await getSettings()
  1069. await updateOptions()
  1070. addSideMenu()
  1071. if (typeof options.onValueChange === "function") {
  1072. options.onValueChange()
  1073. }
  1074. })
  1075. settings = await getSettings()
  1076. runWhenHeadExists(() => {
  1077. addStyle(getSettingsStyle())
  1078. })
  1079. runWhenDomReady(() => {
  1080. initExtensionList()
  1081. addSideMenu()
  1082. })
  1083. registerMenuCommand(i("settings.menu.settings"), showSettings, "o")
  1084. handleShowSettingsUrl()
  1085. }
  1086. var content_default =
  1087. ".lh_selected_element{border:solid 1px red;cursor:not-allowed}a[data-lh-erased-href],a[data-lh-erased-href]:hover{cursor:default;pointer-events:none;text-decoration:none}"
  1088. var messages3 = {
  1089. "settings.enable": "Enable",
  1090. "settings.enableCurrentSite": "Enable on current site",
  1091. "settings.enableCustomRulesForTheCurrentSite":
  1092. "Enable custom rules for the current site",
  1093. "settings.customRulesPlaceholder":
  1094. "/* Custom rules for internal URLs, matching URLs will be opened in new tabs */",
  1095. "settings.customRulesTipTitle": "Examples",
  1096. "settings.customRulesTipContent":
  1097. "<p>Custom rules for internal URLs, matching URLs will be opened in new tabs</p>\n <p>\n - One line per url pattern<br>\n - All URLs contains '/posts' or '/users/'<br>\n <pre>/posts/\n/users/</pre>\n\n - Regex is supported<br>\n <pre>^/(posts|members)/d+</pre>\n\n - '*' for all URLs\n </p>",
  1098. "settings.enableLinkToImgForCurrentSite":
  1099. "Enable converting image links to image tags for the current site",
  1100. "settings.eraseLinks": "Erase Links",
  1101. "settings.restoreLinks": "Restore Links",
  1102. "settings.title": "\u{1F517} Links Helper",
  1103. "settings.information":
  1104. "After changing the settings, reload the page to take effect",
  1105. "settings.report": "Report and Issue...",
  1106. }
  1107. var en_default2 = messages3
  1108. var messages4 = {
  1109. "settings.enable": "\u542F\u7528\u811A\u672C",
  1110. "settings.enableCurrentSite":
  1111. "\u5728\u5F53\u524D\u7F51\u7AD9\u542F\u7528\u811A\u672C",
  1112. "settings.enableCustomRulesForTheCurrentSite":
  1113. "\u5728\u5F53\u524D\u7F51\u7AD9\u542F\u7528\u81EA\u5B9A\u4E49\u89C4\u5219",
  1114. "settings.customRulesPlaceholder":
  1115. "/* \u5185\u90E8\u94FE\u63A5\u7684\u81EA\u5B9A\u4E49\u89C4\u5219\uFF0C\u5339\u914D\u7684\u94FE\u63A5\u4F1A\u5728\u65B0\u7A97\u53E3\u6253\u5F00 */",
  1116. "settings.customRulesTipTitle": "\u793A\u4F8B",
  1117. "settings.customRulesTipContent":
  1118. "<p>\u5185\u90E8\u94FE\u63A5\u7684\u81EA\u5B9A\u4E49\u89C4\u5219\uFF0C\u5339\u914D\u7684\u94FE\u63A5\u4F1A\u5728\u65B0\u7A97\u53E3\u6253\u5F00</p>\n <p>\n - \u6BCF\u884C\u4E00\u6761\u89C4\u5219<br>\n - \u6240\u6709\u5305\u542B '/posts' \u6216 '/users/' \u7684\u94FE\u63A5<br>\n <pre>/posts/\n/users/</pre>\n\n - \u652F\u6301\u6B63\u5219\u8868\u8FBE\u5F0F<br>\n <pre>^/(posts|members)/d+</pre>\n\n - '*' \u4EE3\u8868\u5339\u914D\u6240\u6709\u94FE\u63A5\n </p>",
  1119. "settings.enableLinkToImgForCurrentSite":
  1120. "\u5728\u5F53\u524D\u7F51\u7AD9\u542F\u7528\u56FE\u7247\u94FE\u63A5\u81EA\u52A8\u8F6C\u6362\u4E3A\u56FE\u7247\u6807\u7B7E",
  1121. "settings.eraseLinks":
  1122. "\u53BB\u9664\u6307\u5B9A\u533A\u57DF\u7684\u94FE\u63A5",
  1123. "settings.restoreLinks": "\u6062\u590D\u53BB\u9664\u7684\u94FE\u63A5",
  1124. "settings.title": "\u{1F517} \u94FE\u63A5\u52A9\u624B",
  1125. "settings.information":
  1126. "\u66F4\u6539\u8BBE\u7F6E\u540E\uFF0C\u91CD\u65B0\u52A0\u8F7D\u9875\u9762\u5373\u53EF\u751F\u6548",
  1127. "settings.report": "\u53CD\u9988\u95EE\u9898",
  1128. }
  1129. var zh_cn_default2 = messages4
  1130. var i2 = initI18n({
  1131. "en,en-US": en_default2,
  1132. "zh,zh-CN": zh_cn_default2,
  1133. })
  1134. var lastTarget
  1135. var handleMouseOver = (event) => {
  1136. const target = event.target
  1137. if (!target || target === lastTarget) {
  1138. return
  1139. }
  1140. if (lastTarget) {
  1141. removeClass(lastTarget, "lh_selected_element")
  1142. }
  1143. lastTarget = target
  1144. while (lastTarget && !$("a", lastTarget)) {
  1145. lastTarget = lastTarget.parentElement
  1146. }
  1147. if (lastTarget) {
  1148. addClass(lastTarget, "lh_selected_element")
  1149. }
  1150. }
  1151. var handleMouseClick = (event) => {
  1152. event.preventDefault()
  1153. event.stopPropagation()
  1154. event.stopImmediatePropagation()
  1155. if (lastTarget) {
  1156. for (const element of $$("a[href]", lastTarget)) {
  1157. const href = getAttribute(element, "href")
  1158. if (href) {
  1159. setAttribute(element, "data-lh-erased-href", href)
  1160. element.removeAttribute("href")
  1161. }
  1162. }
  1163. removeClass(lastTarget, "lh_selected_element")
  1164. }
  1165. removeEventListener(doc, "mouseover", handleMouseOver, true)
  1166. removeEventListener(doc, "click", handleMouseClick, true)
  1167. return false
  1168. }
  1169. function eraseLinks() {
  1170. addEventListener(doc, "mouseover", handleMouseOver, true)
  1171. addEventListener(doc, "click", handleMouseClick, true)
  1172. }
  1173. function restoreLinks() {
  1174. for (const element of $$("a[data-lh-erased-href]")) {
  1175. const href = getAttribute(element, "data-lh-erased-href")
  1176. setAttribute(element, "href", href)
  1177. delete element.dataset.lhErasedHref
  1178. }
  1179. }
  1180. var image_url_default =
  1181. '{\n "imgur.com": [\n "https?://imgur.com/(\\\\w+)($|\\\\?) -> https://i.imgur.com/$1.png # ex: https://imgur.com/gi2b1rj",\n "https?://imgur.com/(\\\\w+)\\\\.(\\\\w+) -> https://i.imgur.com/$1.$2 # ex: https://imgur.com/gi2b1rj.png"\n ],\n "imgur.io": [\n "https?://imgur.io/(\\\\w+)($|\\\\?) -> https://i.imgur.com/$1.png # ex: https://imgur.io/gi2b1rj",\n "https?://imgur.io/(\\\\w+)\\\\.(\\\\w+) -> https://i.imgur.com/$1.$2 # ex: https://imgur.io/gi2b1rj.png"\n ],\n "i.imgur.com": [\n "https?://i.imgur.com/(\\\\w+)($|\\\\?) -> https://i.imgur.com/$1.png"\n ],\n "camo.githubusercontent.com": [\n "https://camo.githubusercontent.com/.* # This is a img url, no need to replace value"\n ]\n}\n'
  1182. var rules = JSON.parse(image_url_default)
  1183. var cachedRules = {}
  1184. var getHostname = (url) => (/https?:\/\/([^/]+)/.exec(url) || [])[1]
  1185. var processRule = (rule, href) => {
  1186. var _a
  1187. let pattern
  1188. let replacement
  1189. const cachedRule = cachedRules[rule]
  1190. try {
  1191. if (cachedRule) {
  1192. pattern = cachedRule.pattern
  1193. replacement = cachedRule.replacement
  1194. } else {
  1195. const result = rule.replace(/ #.*/, "").split("->")
  1196. const patternString = result[0].trim()
  1197. pattern = new RegExp(
  1198. patternString.startsWith("http")
  1199. ? "^" + patternString
  1200. : patternString,
  1201. "i"
  1202. )
  1203. replacement = (_a = result[1]) == null ? void 0 : _a.trim()
  1204. cachedRules[rule] = { pattern, replacement }
  1205. }
  1206. if (pattern.test(href)) {
  1207. return replacement ? href.replace(pattern, replacement) : href
  1208. }
  1209. } catch (error) {
  1210. console.error(error)
  1211. }
  1212. }
  1213. var convertImgUrl = (href) => {
  1214. if (!href) {
  1215. return
  1216. }
  1217. const hostname = getHostname(href)
  1218. if (Object.hasOwn(rules, hostname)) {
  1219. for (const rule of rules[hostname]) {
  1220. const newHref = processRule(rule, href)
  1221. if (newHref) {
  1222. return newHref
  1223. }
  1224. }
  1225. }
  1226. }
  1227. var createImgTagString = (src, text) =>
  1228. '<img src="'
  1229. .concat(src, '" title="')
  1230. .concat(text || "image", '" alt="')
  1231. .concat(
  1232. text || "image",
  1233. '" role="img" style="max-width: 100% !important; vertical-align: bottom;" loading="lazy" referrerpolicy="no-referrer" rel="noreferrer" data-lh-status="1"/>'
  1234. )
  1235. var bindOnError = () => {
  1236. for (const element of $$('img[data-lh-status="1"]')) {
  1237. setAttribute(element, "data-lh-status", "2")
  1238. addEventListener(element, "error", (event) => {
  1239. const img = event.target
  1240. const anchor = img.parentElement
  1241. img.outerHTML = createHTML(getAttribute(img, "src"))
  1242. if ((anchor == null ? void 0 : anchor.tagName) === "A") {
  1243. setStyle(anchor, "opacity: 50%;")
  1244. setAttribute(anchor, "data-message", "failed to load image")
  1245. }
  1246. })
  1247. }
  1248. }
  1249. var anchorElementToImgElement = (anchor, href, text) => {
  1250. anchor.innerHTML = createHTML(createImgTagString(href, text))
  1251. setAttribute(anchor, "target", "_blank")
  1252. addAttribute(anchor, "rel", "noopener")
  1253. addAttribute(anchor, "rel", "noreferrer")
  1254. }
  1255. var linkToImg = (anchor) => {
  1256. if (
  1257. !anchor ||
  1258. anchor.childElementCount !== 0 ||
  1259. (anchor.childNodes[0] && anchor.childNodes[0].nodeType !== 3) ||
  1260. anchor.closest("td h1,td h2,td h3,td h4,td h5")
  1261. ) {
  1262. return
  1263. }
  1264. const href = anchor.href
  1265. const text = anchor.textContent || href
  1266. const newHref = convertImgUrl(href)
  1267. if (newHref) {
  1268. anchorElementToImgElement(anchor, newHref, text)
  1269. } else if (
  1270. /^https:[^?]+\.(?:jpg|jpeg|jpe|bmp|png|gif|webp|ico|svg)/i.test(href)
  1271. ) {
  1272. anchorElementToImgElement(anchor, href, text)
  1273. }
  1274. }
  1275. var ignoredTags = /* @__PURE__ */ new Set([
  1276. "A",
  1277. "BUTTON",
  1278. "SVG",
  1279. "PATH",
  1280. "G",
  1281. "SCRIPT",
  1282. "STYLE",
  1283. "TEXTAREA",
  1284. "CODE",
  1285. "PRE",
  1286. "TEMPLATE",
  1287. "FILE-ATTACHMENT",
  1288. "NOSCRIPT",
  1289. "TITLE",
  1290. ])
  1291. var urlPattern =
  1292. "\\b((?:https?:\\/\\/(?:[\\w-.]+\\.[a-z]{2,15}|localhost|(?:\\d{1,3}\\.){3}\\d{1,3}))(?::\\d+)?(?:\\/[\\w-/%.~+:;!@=&?#]*)?)"
  1293. var linkPattern1 = new RegExp(
  1294. "!\\[([^\\[\\]]*)\\]\\((?:\\s|<br/?>)*".concat(
  1295. urlPattern,
  1296. "(?:\\s|<br/?>)*\\)"
  1297. ),
  1298. "gim"
  1299. )
  1300. var linkPattern2 = new RegExp(
  1301. "\\[([^\\[\\]]*)\\]\\((?:\\s|<br/?>)*".concat(
  1302. urlPattern,
  1303. "(?:\\s|<br/?>)*\\)"
  1304. ),
  1305. "gim"
  1306. )
  1307. var linkPattern3 = new RegExp(urlPattern, "gim")
  1308. var linkPattern4 = new RegExp(
  1309. "\\[img\\](?:\\s|<br/?>)*".concat(urlPattern, "(?:\\s|<br/?>)*\\[/img\\]"),
  1310. "gim"
  1311. )
  1312. var linkPattern5 = new RegExp(
  1313. "\\[url\\](?:\\s|<br/?>)*".concat(urlPattern, "(?:\\s|<br/?>)*\\[/url\\]"),
  1314. "gim"
  1315. )
  1316. var linkPattern6 = new RegExp(
  1317. "\\[url=".concat(urlPattern, "\\]([^\\[\\]]+)\\[/url\\]"),
  1318. "gim"
  1319. )
  1320. var replaceMarkdownImgLinks = (text) => {
  1321. if (text.search(linkPattern1) >= 0) {
  1322. text = text.replaceAll(linkPattern1, (m, p1, p2) => {
  1323. return createImgTagString(convertImgUrl(p2) || p2, p1)
  1324. })
  1325. }
  1326. return text
  1327. }
  1328. var replaceMarkdownLinks = (text) => {
  1329. if (text.search(linkPattern2) >= 0) {
  1330. text = text.replaceAll(linkPattern2, (m, p1, p2) => {
  1331. return '<a href="'
  1332. .concat(p2, '">')
  1333. .concat(p1.replaceAll(/<br>$/gi, ""), "</a>")
  1334. })
  1335. }
  1336. return text
  1337. }
  1338. var replaceTextLinks = (text) => {
  1339. if (text.search(linkPattern3) >= 0) {
  1340. text = text.replaceAll(linkPattern3, (m, p1) => {
  1341. return '<a href="'.concat(p1, '">').concat(p1, "</a>")
  1342. })
  1343. }
  1344. return text
  1345. }
  1346. var replaceBBCodeImgLinks = (text) => {
  1347. if (text.search(linkPattern4) >= 0) {
  1348. text = text.replaceAll(linkPattern4, (m, p1) => {
  1349. return createImgTagString(convertImgUrl(p1) || p1, p1)
  1350. })
  1351. }
  1352. return text
  1353. }
  1354. var replaceBBCodeLinks = (text) => {
  1355. if (text.search(linkPattern5) >= 0) {
  1356. text = text.replaceAll(linkPattern5, (m, p1) => {
  1357. return '<a href="'.concat(p1, '">').concat(p1, "</a>")
  1358. })
  1359. }
  1360. if (text.search(linkPattern6) >= 0) {
  1361. text = text.replaceAll(linkPattern6, (m, p1, p2) => {
  1362. return '<a href="'.concat(p1, '">').concat(p2, "</a>")
  1363. })
  1364. }
  1365. return text
  1366. }
  1367. var textToLink = (textNode, previousText) => {
  1368. var _a, _b
  1369. const textContent = (_a = textNode.textContent) != null ? _a : ""
  1370. const parentNode = textNode.parentNode
  1371. const mergedText = previousText + textContent
  1372. if (
  1373. !parentNode ||
  1374. textNode.nodeName !== "#text" ||
  1375. textContent.trim().length === 0 ||
  1376. mergedText.trim().length < 3
  1377. ) {
  1378. return
  1379. }
  1380. if (textContent.includes("://")) {
  1381. const original = textContent
  1382. let newContent = original
  1383. if (new RegExp("\\[.*]\\(", "ms").test(original)) {
  1384. newContent = replaceMarkdownImgLinks(newContent)
  1385. newContent = replaceMarkdownLinks(newContent)
  1386. }
  1387. if (/\[(img|url)]|\[url=/.test(textContent)) {
  1388. newContent = replaceBBCodeImgLinks(newContent)
  1389. newContent = replaceBBCodeLinks(newContent)
  1390. }
  1391. if (newContent === original) {
  1392. newContent = replaceTextLinks(original)
  1393. } else {
  1394. newContent = newContent.replaceAll(
  1395. new RegExp(
  1396. "(<a(?:\\s[^<>]*)?>.*?<\\/a>)|(<img(?:\\s[^<>]*)?\\/?>)|(.+?(?=(?:<a|<img))|.+$)",
  1397. "gims"
  1398. ),
  1399. (m, p1, p2) => (p1 || p2 ? m : replaceTextLinks(m))
  1400. )
  1401. }
  1402. if (newContent !== original) {
  1403. const span = createElement("span")
  1404. span.innerHTML = createHTML(newContent)
  1405. textNode.after(span)
  1406. textNode.remove()
  1407. return true
  1408. }
  1409. }
  1410. const parentTextContent = (_b = parentNode.textContent) != null ? _b : ""
  1411. if (
  1412. new RegExp("\\[.*]\\(", "ms").test(mergedText) &&
  1413. (parentTextContent.search(linkPattern2) >= 0 ||
  1414. $$("img", parentNode).length > 0)
  1415. ) {
  1416. const original = parentNode.innerHTML
  1417. const newContent = original
  1418. .replaceAll(new RegExp("\\[.*]\\([^[\\]()]+?\\)", "gims"), (m) =>
  1419. m
  1420. .replaceAll(
  1421. /<img[^<>]*\ssrc=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>/gim,
  1422. "$1"
  1423. )
  1424. .replaceAll(
  1425. /\((?:\s|<br\/?>)*<a[^<>]*\shref=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>\1<\/a>(?:\s|<br\/?>)*\)/gim,
  1426. "($1)"
  1427. )
  1428. )
  1429. .replaceAll(
  1430. new RegExp("\\[!\\[.*]\\([^()]+\\)]\\([^[\\]()]+?\\)", "gims"),
  1431. (m) =>
  1432. m
  1433. .replaceAll(
  1434. /<img[^<>]*\ssrc=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>/gim,
  1435. "$1"
  1436. )
  1437. .replaceAll(
  1438. /\((?:\s|<br\/?>)*<a[^<>]*\shref=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>\1<\/a>(?:\s|<br\/?>)*\)/gim,
  1439. "($1)"
  1440. )
  1441. )
  1442. if (newContent !== original) {
  1443. let newContent2 = replaceMarkdownImgLinks(newContent)
  1444. newContent2 = replaceMarkdownLinks(newContent2)
  1445. if (newContent2 !== newContent) {
  1446. parentNode.innerHTML = createHTML(newContent2)
  1447. return true
  1448. }
  1449. }
  1450. }
  1451. if (
  1452. /\[(img|url)]|\[url=/.test(textContent) &&
  1453. parentTextContent.search(/\[(img|url)[^\]]*]([^[\]]*?)\[\/\1]/) >= 0
  1454. ) {
  1455. const original = parentNode.innerHTML
  1456. let before = ""
  1457. let after = original
  1458. let count = 0
  1459. while (before !== after && count < 5) {
  1460. count++
  1461. before = after
  1462. after = before.replaceAll(
  1463. /\[(img|url)[^\]]*]([^[\]]+?)\[\/\1]/gim,
  1464. (m, p1) => {
  1465. let tagsRemoved
  1466. let converted
  1467. if (p1 === "img") {
  1468. tagsRemoved = m
  1469. .replaceAll(
  1470. /<img[^<>]*\ssrc=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>/gim,
  1471. "$1"
  1472. )
  1473. .replaceAll(
  1474. /\[img](?:\s|<br\/?>)*<a[^<>]*\shref=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>\1<\/a>(?:\s|<br\/?>)*\[\/img]/gim,
  1475. "[img]$1[/img]"
  1476. )
  1477. converted = replaceBBCodeImgLinks(tagsRemoved)
  1478. } else {
  1479. tagsRemoved = m
  1480. .replaceAll(
  1481. /\[url](?:\s|<br\/?>)*<a[^<>]*\shref=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>\1<\/a>(?:\s|<br\/?>)*\[\/url]/gim,
  1482. "[url]$1[/url]"
  1483. )
  1484. .replaceAll(
  1485. /\[url=<a[^<>]*\shref=['"]?(http[^'"]+)['"]?(\s[^<>]*)?>\1<\/a>]/gim,
  1486. "[url=$1]"
  1487. )
  1488. converted = replaceBBCodeLinks(tagsRemoved)
  1489. }
  1490. return converted === tagsRemoved ? m : converted
  1491. }
  1492. )
  1493. }
  1494. const newContent = after
  1495. if (newContent !== original) {
  1496. parentNode.innerHTML = createHTML(newContent)
  1497. return true
  1498. }
  1499. }
  1500. }
  1501. var fixAnchorTag = (anchorElement) => {
  1502. var _a
  1503. const href = anchorElement.href
  1504. const textContent = (_a = anchorElement.textContent) != null ? _a : ""
  1505. const nextSibling = anchorElement.nextSibling
  1506. if (
  1507. anchorElement.childElementCount === 0 &&
  1508. href.includes(")") &&
  1509. textContent.includes(")")
  1510. ) {
  1511. const index = textContent.indexOf(")")
  1512. const removed = textContent.slice(Math.max(0, index))
  1513. anchorElement.textContent = textContent.slice(0, Math.max(0, index))
  1514. anchorElement.href = anchorElement.href.slice(
  1515. 0,
  1516. Math.max(0, href.indexOf(")"))
  1517. )
  1518. if (nextSibling && nextSibling.nodeType === 3) {
  1519. nextSibling.textContent = removed + nextSibling.textContent
  1520. } else {
  1521. anchorElement.after(doc.createTextNode(removed))
  1522. }
  1523. }
  1524. }
  1525. var isCodeViewer = (element) => {
  1526. return hasClass(element, "diff-view") || hasClass(element, "diff")
  1527. }
  1528. var scanAndConvertChildNodes = (parentNode) => {
  1529. if (
  1530. !parentNode ||
  1531. parentNode.nodeType === 8 ||
  1532. !parentNode.tagName ||
  1533. ignoredTags.has(parentNode.tagName.toUpperCase()) ||
  1534. isCodeViewer(parentNode)
  1535. ) {
  1536. if (parentNode.tagName === "A") {
  1537. fixAnchorTag(parentNode)
  1538. }
  1539. return
  1540. }
  1541. let previousText = ""
  1542. for (const child of parentNode.childNodes) {
  1543. try {
  1544. if (child.nodeName === "#text") {
  1545. if (textToLink(child, previousText)) {
  1546. scanAndConvertChildNodes(parentNode)
  1547. break
  1548. }
  1549. previousText += child.textContent
  1550. } else if (child.nodeName === "BR") {
  1551. previousText += "\n"
  1552. } else {
  1553. previousText = ""
  1554. scanAndConvertChildNodes(child)
  1555. }
  1556. } catch (error) {
  1557. console.error(error)
  1558. }
  1559. }
  1560. }
  1561. var origin = location.origin
  1562. var host = location.host
  1563. var config = {
  1564. run_at: "document_start",
  1565. }
  1566. var settingsTable2 = {
  1567. enable: {
  1568. title: i2("settings.enable"),
  1569. defaultValue: true,
  1570. },
  1571. ["enableCurrentSite_".concat(host)]: {
  1572. title: i2("settings.enableCurrentSite"),
  1573. defaultValue: true,
  1574. },
  1575. ["enableCustomRulesForCurrentSite_".concat(host)]: {
  1576. title: i2("settings.enableCustomRulesForTheCurrentSite"),
  1577. defaultValue: false,
  1578. },
  1579. ["customRulesForCurrentSite_".concat(host)]: {
  1580. title: i2("settings.enableCustomRulesForTheCurrentSite"),
  1581. defaultValue: "",
  1582. placeholder: i2("settings.customRulesPlaceholder"),
  1583. type: "textarea",
  1584. group: 2,
  1585. },
  1586. customRulesTip: {
  1587. title: i2("settings.customRulesTipTitle"),
  1588. type: "tip",
  1589. tipContent: i2("settings.customRulesTipContent"),
  1590. group: 2,
  1591. },
  1592. ["enableLinkToImgForCurrentSite_".concat(host)]: {
  1593. title: i2("settings.enableLinkToImgForCurrentSite"),
  1594. defaultValue: Boolean(/v2ex\.com|localhost/.test(host)),
  1595. group: 3,
  1596. },
  1597. eraseLinks: {
  1598. title: i2("settings.eraseLinks"),
  1599. type: "action",
  1600. async onclick() {
  1601. hideSettings()
  1602. eraseLinks()
  1603. },
  1604. group: 4,
  1605. },
  1606. restoreLinks: {
  1607. title: i2("settings.restoreLinks"),
  1608. type: "action",
  1609. async onclick() {
  1610. hideSettings()
  1611. restoreLinks()
  1612. },
  1613. group: 4,
  1614. },
  1615. }
  1616. var getWithoutOrigin = (url) => url.replace(/(^https?:\/\/[^/]+)/, "")
  1617. var shouldOpenInNewTab = (element) => {
  1618. var _a
  1619. const url = element.href
  1620. if (
  1621. !url ||
  1622. !/^https?:\/\//.test(url) ||
  1623. ((_a = element.getAttribute("href")) == null
  1624. ? void 0
  1625. : _a.startsWith("#"))
  1626. ) {
  1627. return false
  1628. }
  1629. if (element.origin !== origin) {
  1630. return true
  1631. }
  1632. if (getSettingsValue("enableCustomRulesForCurrentSite_".concat(host))) {
  1633. const rules2 = (
  1634. getSettingsValue("customRulesForCurrentSite_".concat(host)) || ""
  1635. ).split("\n")
  1636. if (rules2.includes("*")) {
  1637. return true
  1638. }
  1639. const hrefWithoutOrigin = getWithoutOrigin(url)
  1640. for (let rule of rules2) {
  1641. rule = rule.trim()
  1642. if (rule.length === 0) {
  1643. continue
  1644. }
  1645. try {
  1646. const regexp = new RegExp(rule)
  1647. if (regexp.test(hrefWithoutOrigin)) {
  1648. return true
  1649. }
  1650. } catch (error) {
  1651. console.log(error.message)
  1652. if (hrefWithoutOrigin.includes(rule)) {
  1653. return true
  1654. }
  1655. }
  1656. }
  1657. }
  1658. }
  1659. var setAttributeAsOpenInNewTab = (element) => {
  1660. if (shouldOpenInNewTab(element)) {
  1661. setAttribute(element, "target", "_blank")
  1662. addAttribute(element, "rel", "noopener")
  1663. }
  1664. }
  1665. async function main() {
  1666. await initSettings({
  1667. id: "links-helper",
  1668. title: i2("settings.title"),
  1669. footer: "\n <p>"
  1670. .concat(
  1671. i2("settings.information"),
  1672. '</p>\n <p>\n <a href="https://github.com/utags/links-helper/issues" target="_blank">\n '
  1673. )
  1674. .concat(
  1675. i2("settings.report"),
  1676. '\n </a></p>\n <p>Made with \u2764\uFE0F by\n <a href="https://www.pipecraft.net/" target="_blank">\n Pipecraft\n </a></p>'
  1677. ),
  1678. settingsTable: settingsTable2,
  1679. onViewUpdate(settingsMainView) {
  1680. const group2 = $(".option_groups:nth-of-type(2)", settingsMainView)
  1681. if (group2) {
  1682. group2.style.display = getSettingsValue(
  1683. "enableCustomRulesForCurrentSite_".concat(host)
  1684. )
  1685. ? "block"
  1686. : "none"
  1687. }
  1688. },
  1689. })
  1690. if (
  1691. !getSettingsValue("enable") ||
  1692. !getSettingsValue("enableCurrentSite_".concat(host))
  1693. ) {
  1694. return
  1695. }
  1696. runWhenHeadExists(() => {
  1697. addStyle(content_default)
  1698. })
  1699. addEventListener(
  1700. doc,
  1701. "click",
  1702. (event) => {
  1703. let anchorElement = event.target
  1704. if (!anchorElement) {
  1705. return
  1706. }
  1707. if (anchorElement.closest(".utags_ul")) {
  1708. if (
  1709. hasClass(anchorElement, "utags_captain_tag") ||
  1710. hasClass(anchorElement, "utags_captain_tag2")
  1711. ) {
  1712. event.preventDefault()
  1713. }
  1714. return
  1715. }
  1716. while (anchorElement && anchorElement.tagName !== "A") {
  1717. anchorElement = anchorElement.parentNode
  1718. }
  1719. if (anchorElement) {
  1720. setAttributeAsOpenInNewTab(anchorElement)
  1721. if (getAttribute(anchorElement, "target") === "_blank") {
  1722. event.stopImmediatePropagation()
  1723. event.stopPropagation()
  1724. }
  1725. }
  1726. },
  1727. true
  1728. )
  1729. const scanAnchors = () => {
  1730. for (const element of $$("a")) {
  1731. if (element.__links_helper_scaned) {
  1732. continue
  1733. }
  1734. element.__links_helper_scaned = 1
  1735. try {
  1736. setAttributeAsOpenInNewTab(element)
  1737. } catch (error) {
  1738. console.error(error)
  1739. }
  1740. if (getSettingsValue("enableLinkToImgForCurrentSite_".concat(host))) {
  1741. try {
  1742. linkToImg(element)
  1743. } catch (error) {
  1744. console.error(error)
  1745. }
  1746. }
  1747. }
  1748. }
  1749. const scanNodes = throttle(() => {
  1750. scanAndConvertChildNodes(doc.body)
  1751. scanAnchors()
  1752. bindOnError()
  1753. }, 500)
  1754. const observer = new MutationObserver((mutationsList) => {
  1755. scanNodes()
  1756. })
  1757. const startObserver = () => {
  1758. observer.observe(doc.body, {
  1759. childList: true,
  1760. subtree: true,
  1761. characterData: true,
  1762. })
  1763. }
  1764. runWhenBodyExists(() => {
  1765. startObserver()
  1766. scanAndConvertChildNodes(doc.body)
  1767. })
  1768. scanAnchors()
  1769. }
  1770. runWhenHeadExists(async () => {
  1771. if (doc.documentElement.dataset.linksHelper === void 0) {
  1772. doc.documentElement.dataset.linksHelper = ""
  1773. await main()
  1774. }
  1775. })
  1776. })()