lc-to-markdown-txt-html

力扣题目描述,讨论发布内容复制 复制为 markdown、txt、html 等格式

当前为 2024-04-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name lc-to-markdown-txt-html
  3. // @author wuxin0011
  4. // @version 0.0.2
  5. // @namespace https://github.com/wuxin0011/tampermonkey-script/tree/main/lc-to-markdown-txt-html
  6. // @description 力扣题目描述,讨论发布内容复制 复制为 markdown、txt、html 等格式
  7. // @icon data:image/png;base64,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
  8. // @match https://leetcode.cn/circle/discuss/*
  9. // @match https://leetcode.cn/problems/*
  10. // @require https://cdn.bootcdn.net/ajax/libs/clipboard.js/2.0.11/clipboard.min.js
  11. // @require https://cdn.bootcdn.net/ajax/libs/turndown/7.1.2/turndown.min.js
  12. // @grant GM_registerMenuCommand
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @license MIT
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. 'use strict';
  20. const url = window.location.href
  21. const HTML_CONVERT = '__HTML_CONVERT__'
  22. const TXT_CONVERT = '__TXT_CONVERT__'
  23. const MARKDOWN_CONVERT = '__MARKDOWN_CONVERT__'
  24. const markdownURL = "https://stonehank.github.io/html-to-md/"
  25.  
  26. const isDiscuss = () => url.indexOf('https://leetcode.cn/circle/discuss') != -1
  27. const isProblem = () => url.indexOf('https://leetcode.cn/problems') != -1
  28. //
  29. const use = (key) => typeof GM_getValue(key) == 'undefined' ? true : GM_getValue(key)
  30. const isUseMarkDown = () => use(MARKDOWN_CONVERT)
  31. const isUseTxt = () => use(TXT_CONVERT)
  32. const isUseHTML = () => use(HTML_CONVERT)
  33. let timerId = null
  34. let loadOk = false
  35. console.log('markdown', isUseMarkDown(), 'txt', isUseTxt(), 'html', isUseHTML())
  36.  
  37.  
  38. const SUPPORT_TYPE = {
  39. 'md': 'md',
  40. 'txt': 'txt',
  41. 'html': 'html'
  42. }
  43.  
  44.  
  45.  
  46. const buttons = []
  47. const targetClass = 'my-button-target'
  48. const BUTTON_ID = `#${targetClass}`
  49. for (let i = 0; i < 3; i++) {
  50. const temp = document.createElement('button')
  51. temp.style.marginLeft = '10px'
  52. const type = i == 0 ? SUPPORT_TYPE['md'] : i == 1 ? SUPPORT_TYPE['txt'] : SUPPORT_TYPE['html']
  53. temp.title = `复制为 ${type == 'md' ? 'markdown' : type} 格式`
  54. temp.id = `${BUTTON_ID}-${type}`
  55. temp.textContent = type
  56. temp.copytype = type
  57. buttons.push(temp)
  58. }
  59.  
  60.  
  61. const updateDisplay = (element, u) => element && element instanceof HTMLElement ? (element.style.display = u ? 'inline-block' : 'none') : ''
  62. // markdown button
  63. const markdownButton = buttons[0]
  64. updateDisplay(markdownButton, isUseMarkDown())
  65.  
  66. // txt button
  67. const txtButton = buttons[1]
  68. updateDisplay(txtButton, isUseTxt())
  69.  
  70. // html button
  71. const htmlButton = buttons[2]
  72. updateDisplay(htmlButton, isUseHTML())
  73.  
  74. function getHtmlContent(className) {
  75. const htmlContent = document.querySelector(className)
  76. return htmlContent ? htmlContent.innerHTML : ''
  77. }
  78.  
  79. function updateElementShow(element) {
  80. if (!element instanceof HTMLElement) {
  81. return
  82. }
  83. element.style.display = element.style.display == 'none' ? 'inline-block' : 'none'
  84. }
  85.  
  86.  
  87. function runQuestionActionsContainer() {
  88. const className = '[class$=MarkdownContent]';
  89. const questionActionsContainer = document.querySelector('[class*=QuestionActionsContainer]')
  90. markdownButton.className = 'e11vgnte0 css-yf7o-BaseButtonComponent-ThemedButton ery7n2v0'
  91. htmlButton.className = 'e11vgnte0 css-yf7o-BaseButtonComponent-ThemedButton ery7n2v0'
  92. txtButton.className = 'e11vgnte0 css-yf7o-BaseButtonComponent-ThemedButton ery7n2v0'
  93. const htmlContent = getHtmlContent(className)
  94. runCopy(questionActionsContainer, markdownButton, htmlContent, SUPPORT_TYPE['md'])
  95. runCopy(questionActionsContainer, htmlButton, htmlContent, SUPPORT_TYPE['html'])
  96. }
  97.  
  98.  
  99.  
  100. const toMarkdown = (htmlContent) => {
  101. try {
  102. var turndownService = new TurndownService()
  103. var markdown = turndownService.turndown(htmlContent)
  104. return markdown
  105. } catch (e) {
  106. if (confirm('markdown转换失败,跳转到网站转换?')) {
  107. if (window?.navigator?.clipboard?.writeText) {
  108. window.navigator.clipboard.writeText(htmlContent).then(() => {
  109. window.open(markdownURL, '_blank')
  110. }, () => {
  111.  
  112. })
  113. }
  114. } else {
  115. console.error('convert markdown error default convert txt !', e)
  116. const d = document.createElement('div')
  117. d.innerHTML = content
  118. const txt = handlerText(d.textContent)
  119. return txt
  120. }
  121. }
  122. }
  123.  
  124.  
  125.  
  126. function runProblems() {
  127. // console.log('run problem', url)
  128. const buttonClassName = 'relative inline-flex items-center justify-center text-caption px-2 py-1 gap-1 rounded-full bg-fill-secondary text-difficulty-easy dark:text-difficulty-easy'
  129. const className = "[data-track-load=description_content]"
  130. let title = document.querySelector('#qd-content [class*=text-title]')
  131. const titleTxt = title?.textContent
  132. title = title ? '<h2>' + (title?.textContent) + '</h2>' : ''
  133. let u = window.location.href
  134. let orginUrl = title ? `<a href="${u}">` + (u) + '</a>' : ''
  135. let htmlContent = title + getHtmlContent(className) + orginUrl
  136. let container = document.querySelector(className)
  137. if (!container) {
  138. console.warn('找不到 容器!', url)
  139. return;
  140. }
  141. container = container.previousElementSibling
  142. markdownButton.className = buttonClassName
  143. txtButton.className = buttonClassName
  144. htmlButton.className = buttonClassName
  145. runCopy(container, txtButton, htmlContent, SUPPORT_TYPE['txt'], titleTxt)
  146. runCopy(container, htmlButton, htmlContent, SUPPORT_TYPE['html'])
  147. runCopy(container, markdownButton, htmlContent, SUPPORT_TYPE['md'])
  148. }
  149.  
  150.  
  151. function copy(w, element) {
  152. if (!element || !(element instanceof HTMLElement)) {
  153. return
  154. }
  155.  
  156. try {
  157. let clipboard = element?.clipboardObject
  158. if (clipboard) {
  159. //console.log('clipboard destroy')
  160. clipboard.destroy();
  161. }
  162. clipboard = new ClipboardJS(element, {
  163. text: function () {
  164. return w;
  165. }
  166. })
  167. // console.log('update txt >>>>>>>>>')
  168. element.clipboardObject = clipboard
  169. clipboard.on('success', function (e) {
  170. updateButtonStatus(element)
  171. })
  172. clipboard.on('error', function (e) {
  173. updateButtonStatus(element, 'copy error!')
  174. })
  175.  
  176.  
  177. } catch (error) {
  178. // 如果 clipboardjs 引入失败 使用原生的
  179. // use navigator writeText
  180. element.onclick = () => {
  181. navigator.clipboard.writeText(w).then(() => {
  182. //updateButtonStatus(element)
  183. }, () => {
  184. updateButtonStatus(element, 'copy error!')
  185. })
  186. }
  187.  
  188. }
  189.  
  190. }
  191.  
  192.  
  193.  
  194.  
  195. function runCopy(container, ele, htmlContent, type = SUPPORT_TYPE['md'], title = '') {
  196.  
  197. if (!ele || !container || !htmlContent || !type) {
  198. return
  199. }
  200. if (!(container instanceof HTMLElement && ele instanceof HTMLElement)) {
  201. return;
  202. }
  203. // append
  204. if (!document.getElementById(ele.id)) {
  205. ele.originClass = ele.className
  206. container.appendChild(ele)
  207. } else {
  208. if (timerId != null) {
  209. window.clearInterval(timerId)
  210. timerId = null
  211. }
  212. // 加载完成 初始化
  213. loadOk = true
  214. initConmand()
  215. updateButtonStatus(ele, ele.copytype, '', 100)
  216. }
  217.  
  218. if (type == SUPPORT_TYPE['md']) {
  219. const markdown = toMarkdown(htmlContent)
  220. copy(markdown, ele)
  221. } else if (type == SUPPORT_TYPE['txt']) {
  222. const d = document.createElement('div')
  223. d.innerHTML = htmlContent
  224. const txt = handlerText(d.textContent, title)
  225. copy(txt, ele)
  226. } else if (type == SUPPORT_TYPE['html']) {
  227. // html
  228. copy(htmlContent, ele)
  229. } else {
  230. console.warn('no support format ' + type)
  231. }
  232.  
  233. }
  234.  
  235.  
  236. const MAX_LEN = 80
  237.  
  238.  
  239. const handlerText = (str, title = '') => {
  240. if (!str) return str
  241. // 移出空白字符
  242. str = str.replaceAll(' ', '')
  243. str = str.replaceAll('&nbsp;', '')
  244. function isIgnore(c) {
  245. return c == '\t' || c == '\b' || c == '\n' || c == '\f'
  246. }
  247. try {
  248. let newstr = ''
  249. let find = str.indexOf('提示')
  250. let findExample = str.indexOf('示例')
  251. let desc = findExample == -1 ? str : str.substring(0, findExample)
  252. let tipPos = find == -1 ? str.length : find
  253.  
  254.  
  255. for (let i = 0; i < desc.length; i++) {
  256. let chr = desc.charAt(i)
  257. if (isIgnore(chr)) {
  258. continue;
  259. }
  260. newstr = newstr + chr
  261. }
  262.  
  263. // 示例部分
  264. if (findExample != -1 && tipPos != str.length) {
  265. let exampleStr = str.substring(findExample, tipPos)
  266. exampleStr = exampleStr.replace(/\n{2,}/g, "\n")
  267. exampleStr = exampleStr.replaceAll("示例", "\n示例")
  268. newstr += exampleStr
  269. }
  270.  
  271. // 处理提示文本内容
  272. if (tipPos != str.length) {
  273. let tipsStr = str.substring(find);
  274.  
  275. // 多个换行处理
  276. tipsStr = tipsStr.replace(/\n{2,}/g, "\n")
  277.  
  278. // 修改常见的数据范围异常问题
  279. tipsStr = tipsStr.replace('231', '2^31')
  280. tipsStr = tipsStr.replace(/10(\d?)/g, '10^$1')
  281. newstr = newstr + tipsStr
  282. newstr = newstr.replace('提示', '\n\n提示')
  283. }
  284.  
  285. if (title) {
  286. newstr = newstr.replace(title, `${title}\n\n`)
  287. }
  288.  
  289. let i = newstr.length
  290. for (; i >= 0; i--) {
  291. let c = newstr.charAt(i)
  292. if (!(isIgnore(c) || c == ' ')) {
  293. break
  294. }
  295. }
  296. newstr = newstr.replaceAll("。", "。\n")
  297. newstr = newstr.substring(0, i);
  298. newstr = newstr.replace('https','\n\nhttps')
  299. return newstr
  300. } catch (e) {
  301. console.error('handler error', e)
  302. if (title) {
  303. title = title + "\n\n"
  304. }
  305. str = str.replace(/\n{2,}/g, "\n\n")
  306. str = str.replace('231', '2^31')
  307. str = str.replace(/10(\d?)/g, '10^$1')
  308. str = str.replace('https','\n\nhttps')
  309. return title + str
  310. }
  311.  
  312. }
  313.  
  314.  
  315. const updateButtonStatus = (element, newText = 'copied!', newClass = '', timeout = 1500) => {
  316. if (!element) {
  317. return;
  318. }
  319. // console.log('update button status', element, newText)
  320. element.textContent = newText
  321. if (newClass) {
  322. element.className = newClass
  323. }
  324. setTimeout(() => {
  325. element.textContent = element.copytype
  326. element.className = element.originClass
  327. }, timeout)
  328. }
  329.  
  330.  
  331.  
  332.  
  333. const initConmand = () => {
  334. try {
  335.  
  336. // const message = (u, type) => u ? '关闭' : '启用' + (type == 'md' ? ' markdown ' : ` ${type} `)
  337.  
  338. const html_to_markdown = GM_registerMenuCommand(`${isUseMarkDown() ? '关闭' : '启用'} markdown `, () => {
  339. GM_setValue(MARKDOWN_CONVERT, !isUseMarkDown())
  340. updateElementShow(markdownButton)
  341. }, { title: `点击 ${isUseMarkDown() ? '关闭' : '启用'} markdown ` })
  342.  
  343.  
  344. const html_to_txt = GM_registerMenuCommand(`${isUseTxt() ? '关闭' : '启用'} txt `, () => {
  345. GM_setValue(TXT_CONVERT, !isUseTxt())
  346. updateElementShow(txtButton)
  347. }, { title: `点击 ${isUseTxt() ? '关闭' : '启用'} txt ` })
  348.  
  349. const html_to_html = GM_registerMenuCommand(`${isUseHTML() ? '关闭' : '启用'} html `, () => {
  350. GM_setValue(HTML_CONVERT, !isUseHTML())
  351. updateElementShow(htmlButton)
  352. }, { title: `点击 ${isUseHTML() ? '关闭' : '启用'} html ` })
  353.  
  354.  
  355.  
  356.  
  357. const html_to_markdown_web = GM_registerMenuCommand('html转换markdown网站', () => {
  358. window.open(markdownURL, '_blank')
  359. }, { title: '如果格式转换有问题,请复制为 html 然后用这个网站转换' })
  360.  
  361.  
  362.  
  363. } catch (e) {
  364. console.log('init command error', e)
  365. }
  366.  
  367. }
  368.  
  369.  
  370. let times = 0
  371.  
  372.  
  373. const start = () => {
  374. timerId = setInterval(() => {
  375. let support = true
  376. if (isDiscuss()) {
  377. runQuestionActionsContainer()
  378. } else if (isProblem()) {
  379. runProblems()
  380. } else {
  381. support = false
  382. }
  383. times += 1
  384. if (times > 10 && timerId != null) {
  385. window.clearInterval(timerId)
  386. timerId = null
  387. }
  388. if (!support) {
  389. console.warn('No support address ! ', url)
  390. if (timerId != null) {
  391. window.clearInterval(timerId)
  392. }
  393. return
  394. }
  395. }, 3000);
  396.  
  397.  
  398. }
  399.  
  400.  
  401.  
  402. window.onload = () => {
  403.  
  404. times = 0
  405. start()
  406. }
  407.  
  408.  
  409.  
  410. // 监听地址改变
  411. // 重新修改描述
  412. window.addEventListener("urlchange", () => {
  413.  
  414. if (!loadOk) {
  415. return
  416. }
  417. // console.log('url is change ...')
  418. // console.log('ok ok')
  419. let pretitle = document.querySelector('title').textContent
  420. let titleId = null
  421. let updateTimes = 0
  422. titleId = setInterval(() => {
  423. let curTitle = document.querySelector('title').textContent
  424. if (isDiscuss()) {
  425. runQuestionActionsContainer()
  426. } else if (isProblem()) {
  427. runProblems()
  428. }
  429. if (pretitle == curTitle && updateTimes >= 3) {
  430. window.clearInterval(titleId)
  431. return
  432. }
  433. updateTimes += 1
  434. }, 1500);
  435.  
  436. })
  437.  
  438. })();