lc-to-markdown-txt-html

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

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

  1. // ==UserScript==
  2. // @name lc-to-markdown-txt-html
  3. // @author wuxin0011
  4. // @version 0.0.1
  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.  
  19.  
  20.  
  21.  
  22. (function () {
  23. 'use strict';
  24. const url = window.location.href
  25. const HTML_CONVERT = '__HTML_CONVERT__'
  26. const TXT_CONVERT = '__TXT_CONVERT__'
  27. const MARKDOWN_CONVERT = '__MARKDOWN_CONVERT__'
  28. const markdownURL = "https://stonehank.github.io/html-to-md/"
  29.  
  30. const isDiscuss = () => url.indexOf('https://leetcode.cn/circle/discuss') != -1
  31. const isProblem = () => url.indexOf('https://leetcode.cn/problems') != -1
  32. //
  33. const use = (key) => typeof GM_getValue(key) == 'undefined' ? true : GM_getValue(key)
  34. const isUseMarkDown = () => use(MARKDOWN_CONVERT)
  35. const isUseTxt = () => use(TXT_CONVERT)
  36. const isUseHTML = () => use(HTML_CONVERT)
  37. console.log('markdown', isUseMarkDown(), 'txt', isUseTxt(), 'html', isUseHTML())
  38.  
  39.  
  40. const SUPPORT_TYPE = {
  41. 'md': 'md',
  42. 'txt': 'txt',
  43. 'html': 'html'
  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.textContent = type
  55. buttons.push(temp)
  56. }
  57.  
  58. const updateDisplay = (element, u) => element && element instanceof HTMLElement ? (element.style.display = u ? 'inline-block' : 'none') : ''
  59. // markdown button
  60. const markdownButton = buttons[0]
  61. markdownButton.id = targetClass
  62. updateDisplay(markdownButton,isUseMarkDown())
  63.  
  64. // txt button
  65. const txtButton = buttons[1]
  66. updateDisplay(txtButton,isUseTxt())
  67.  
  68. // html button
  69. const htmlButton = buttons[2]
  70. updateDisplay(htmlButton,isUseHTML())
  71.  
  72. function getHtmlContent(className) {
  73. const htmlContent = document.querySelector(className)
  74. return htmlContent ? htmlContent.innerHTML : ''
  75. }
  76.  
  77. function updateElementShow(element) {
  78. if (!element instanceof HTMLElement) {
  79. return
  80. }
  81. element.style.display = element.style.display == 'none' ? 'inline-block' : 'none'
  82. }
  83.  
  84.  
  85. function runQuestionActionsContainer() {
  86. const className = '[class$=MarkdownContent]';
  87. const questionActionsContainer = document.querySelector('[class*=QuestionActionsContainer]')
  88. markdownButton.className = 'e11vgnte0 css-yf7o-BaseButtonComponent-ThemedButton ery7n2v0'
  89. htmlButton.className = 'e11vgnte0 css-yf7o-BaseButtonComponent-ThemedButton ery7n2v0'
  90. txtButton.className = 'e11vgnte0 css-yf7o-BaseButtonComponent-ThemedButton ery7n2v0'
  91. const htmlContent = getHtmlContent(className)
  92. runCopy(questionActionsContainer, markdownButton, htmlContent, SUPPORT_TYPE['md'])
  93. runCopy(questionActionsContainer, htmlButton, htmlContent, SUPPORT_TYPE['html'])
  94. }
  95.  
  96.  
  97.  
  98. const toMarkdown = (htmlContent) => {
  99. try {
  100. var turndownService = new TurndownService()
  101. var markdown = turndownService.turndown(htmlContent)
  102. return markdown
  103. } catch (e) {
  104. if (confirm('markdown转换失败,跳转到网站转换?')) {
  105. if (window?.navigator?.clipboard?.writeText) {
  106. window.navigator.clipboard.writeText(htmlContent).then(() => {
  107. window.open(markdownURL, '_blank')
  108. }, () => {
  109.  
  110. })
  111. }
  112. } else {
  113. console.error('convert markdown error default convert txt !', e)
  114. const d = document.createElement('div')
  115. d.innerHTML = content
  116. const txt = handlerText(d.textContent)
  117. return txt
  118. }
  119. }
  120. }
  121.  
  122.  
  123.  
  124. function runProblems() {
  125. 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'
  126. const className = "[data-track-load=description_content]"
  127. let title = document.querySelector('#qd-content [class*=text-title]')
  128. const titleTxt = title?.textContent
  129. title = title ? '<h2>' + (title?.textContent) + '</h2>' : ''
  130. let htmlContent = title + getHtmlContent(className)
  131. let container = document.querySelector(className)
  132. if (!container) {
  133. console.warn('找不到 容器!', url)
  134. return;
  135. }
  136. container = container.previousElementSibling
  137. markdownButton.className = buttonClassName
  138. txtButton.className = buttonClassName
  139. htmlButton.className = buttonClassName
  140. runCopy(container, markdownButton, htmlContent, SUPPORT_TYPE['md'])
  141. runCopy(container, txtButton, htmlContent, SUPPORT_TYPE['txt'], titleTxt)
  142. runCopy(container, htmlButton, htmlContent, SUPPORT_TYPE['html'])
  143. }
  144.  
  145.  
  146. function copy(w, element) {
  147. if (!element || !(element instanceof HTMLElement)) {
  148. return
  149. }
  150. try {
  151. const clipboard = new ClipboardJS(element, {
  152. text: function () {
  153. return w;
  154. }
  155. })
  156. clipboard.on('success', function (e) {
  157. updateButtonStatus(element)
  158. })
  159. clipboard.on('error', function (e) {
  160. updateButtonStatus(element, 'copy error!')
  161. })
  162. } catch (error) {
  163. // 如果 clipboardjs 引入失败 使用原生的
  164. // use navigator writeText
  165. element.onclick = () => {
  166. navigator.clipboard.writeText(w).then(() => {
  167. updateButtonStatus(element)
  168. }, () => {
  169. updateButtonStatus(element, 'copy error!')
  170. })
  171. }
  172.  
  173. }
  174.  
  175. }
  176.  
  177. function runCopy(container, ele, htmlContent, type = SUPPORT_TYPE['md'], title = '') {
  178.  
  179. if (!ele || !container || !htmlContent || !type) {
  180. return
  181. }
  182. if (!(container instanceof HTMLElement && ele instanceof HTMLElement)) {
  183. return;
  184. }
  185. // append
  186. container.appendChild(ele)
  187. if (type == SUPPORT_TYPE['md']) {
  188. const markdown = toMarkdown(htmlContent)
  189. copy(markdown, ele)
  190. } else if (type == SUPPORT_TYPE['txt']) {
  191. const d = document.createElement('div')
  192. d.innerHTML = htmlContent
  193. const txt = handlerText(d.textContent, title)
  194. copy(txt, ele)
  195. } else if (type == SUPPORT_TYPE['html']) {
  196. // html
  197. copy(htmlContent, ele)
  198. } else {
  199. console.warn('no support format ' + type)
  200. }
  201.  
  202. }
  203.  
  204. const handlerText = (str, title = '') => {
  205. if (!str) return str
  206. // 移出空白字符
  207. str = str.replaceAll(' ','')
  208. str = str.replaceAll('&nbsp;','')
  209. function isIgnore(c) {
  210. return c == '\t' || c == '\b' || c == '\n' || c == '\f'
  211. }
  212. let newstr = ''
  213. let find = str.indexOf('提示')
  214. let tipPos = find == -1 ? str.length : find
  215. for (let i = 0; i < tipPos; i++) {
  216. if (isIgnore(str.charAt(i))) {
  217. continue;
  218. }
  219. newstr = newstr + str.charAt(i)
  220. }
  221. newstr = newstr.replaceAll('示例', '\n\n示例')
  222.  
  223. if (tipPos != str.length) {
  224. newstr = newstr + str.substring(find)
  225. newstr = newstr.replace('提示', '\n\n提示')
  226. }
  227.  
  228. if (title) {
  229. newstr = newstr.replace(title, `${title}\n\n`)
  230. }
  231.  
  232. let i = newstr.length
  233. for (; i >= 0; i--) {
  234. let c = newstr.charAt(i)
  235. if (!(isIgnore(c) || c == ' ')) {
  236. break
  237. }
  238. }
  239. return newstr.substring(0, i)
  240. }
  241.  
  242.  
  243. const updateButtonStatus = (element, newText = 'copy success !', newClass = '', timeout = 1500) => {
  244. if (!element) {
  245. return;
  246. }
  247. const origin = element.textContent
  248. const originClass = element.className
  249. element.textContent = newText
  250. if (newClass) {
  251. element.className = newClass
  252. }
  253. setTimeout(() => {
  254. element.textContent = origin
  255. element.className = originClass
  256. }, timeout)
  257. }
  258.  
  259.  
  260.  
  261.  
  262. const initConmand = () => {
  263. try {
  264.  
  265. // const message = (u, type) => u ? '关闭' : '启用' + (type == 'md' ? ' markdown ' : ` ${type} `)
  266.  
  267. const html_to_markdown = GM_registerMenuCommand(`${isUseMarkDown() ? '关闭' : '启用'} markdown `, () => {
  268. GM_setValue(MARKDOWN_CONVERT, !isUseMarkDown())
  269. updateElementShow(markdownButton)
  270. }, { title: `点击 ${isUseMarkDown() ? '关闭' : '启用'} markdown ` })
  271.  
  272.  
  273. const html_to_txt = GM_registerMenuCommand(`${isUseTxt() ? '关闭' : '启用'} txt `, () => {
  274. GM_setValue(TXT_CONVERT, !isUseTxt())
  275. updateElementShow(txtButton)
  276. }, { title: `点击 ${isUseTxt() ? '关闭' : '启用'} txt ` })
  277.  
  278. const html_to_html = GM_registerMenuCommand(`${isUseHTML() ? '关闭' : '启用'} html `, () => {
  279. GM_setValue(HTML_CONVERT, !isUseHTML())
  280. updateElementShow(htmlButton)
  281. }, { title: `点击 ${isUseHTML() ? '关闭' : '启用'} html ` })
  282.  
  283.  
  284.  
  285.  
  286. const html_to_markdown_web = GM_registerMenuCommand('html转换markdown网站', () => {
  287. window.open(markdownURL, '_blank')
  288. }, { title: '如果格式转换有问题,请复制为 html 然后用这个网站转换' })
  289.  
  290.  
  291.  
  292. } catch (e) {
  293. console.log('init command error', e)
  294. }
  295.  
  296. }
  297.  
  298.  
  299.  
  300. window.onload = () => {
  301. let support = true
  302. let timer = setInterval(() => {
  303.  
  304. if (isDiscuss()) {
  305. runQuestionActionsContainer()
  306. } else if (isProblem()) {
  307. runProblems()
  308. } else {
  309. support = false
  310. }
  311.  
  312. if (support) {
  313. initConmand()
  314. const my_button = document.querySelector(BUTTON_ID)
  315. if (my_button) {
  316. window.clearInterval(timer)
  317. }
  318. } else {
  319. console.warn('No support address ! ', url)
  320. window.clearInterval(timer)
  321. }
  322. }, 3000);
  323. }
  324. })();