GreasyFork markdown

在论坛默认使用 Markdown 格式,添加格式帮助链接及 Markdown 工具栏

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

  1. // ==UserScript==
  2. // @name Markdown toolbar for GreasyFork
  3. // @namespace https://greasyfork.org/zh-CN/users/1169082
  4. // @name:ru Markdown-тулбар для GreasyFork
  5. // @name:zh-CN GreasyFork markdown
  6. // @description Select Markdown format by default, add help links, add toolbar formatting buttons for markdown
  7. // @description:ru Включает формат Markdown по умолчанию, добавляет справочные ссылки по форматам, добавляет панель кнопок форматирования markdown
  8. // @description:zh-CN 在论坛默认使用 Markdown 格式,添加格式帮助链接及 Markdown 工具栏
  9. // @author wOxxOm, darkred, 人民的勤务员 <toniaiwanowskiskr47@gmail.com>
  10. // @contributor JixunMoe
  11. // @contributor 人民的勤务员 <toniaiwanowskiskr47@gmail.com>
  12. // @license MIT
  13. // @include https://greasyfork.org/*discussions/*
  14. // @include https://greasyfork.org/*scripts/*/versions/new*
  15. // @include https://greasyfork.org/*scripts/*/feedback*
  16. // @include https://greasyfork.org/*script_versions/new*
  17. // @include https://greasyfork.org/*/conversations/*
  18. // @include https://greasyfork.org/*/users/edit
  19. // @grant GM_addStyle
  20. // @run-at document-start
  21. // @version 2.0.4.10
  22. // @icon https://raw.githubusercontent.com/dcurtis/markdown-mark/master/png/66x40-solid.png
  23. // @supportURL https://github.com/darkred/Userscripts/issues
  24. // ==/UserScript==
  25.  
  26. // Example URLS to test:
  27. // https://greasyfork.org/en/discussions/new
  28. // https://greasyfork.org/en/scripts/422887-markdown-toolbar-for-greasyfork/discussions/78139
  29. // https://greasyfork.org/en/scripts/23493/versions/new
  30. // https://greasyfork.org/en/scripts/422445-github-watcher/feedback
  31. // https://greasyfork.org/en/users/2160-darkred/conversations/new
  32. // https://greasyfork.org/en/users/edit
  33.  
  34. var inForum = location.href.indexOf('/discussions') > 0
  35. var inPostNewScriptVer = location.href.indexOf('/versions/new') > 0
  36.  
  37. function contains(selector, text) {
  38. var elements = document.querySelectorAll(selector)
  39. return Array.from(elements).filter(function (element) {
  40. return RegExp(text).test(element.textContent)
  41. })
  42. }
  43. //DOMContentLoaded有时会和其他脚本冲突导致监听失败
  44. window.addEventListener('load', function (e) {
  45. var refElements = document.querySelectorAll(`
  46. input[name="authenticity_token"] + .label-note,
  47. label[for="script-version-additional-info-0"] + .label-note,
  48. label[for="changelog"] + .label-note,
  49. label[for="conversation_messages_attributes_0_content"] + .label-note,
  50. label[for="user_profile"] + .label-note,
  51. form > .label-note
  52. `)
  53.  
  54.  
  55. if (inForum) {
  56. refElements.forEach(element => {
  57. element.insertAdjacentHTML('beforeend', '<br>')
  58. addFeatures(element)
  59. })
  60. } else { // not in Forum
  61. // This page has 2 non-code textareas: 'Additional info' and 'Changelog'
  62. if (inPostNewScriptVer) {
  63. refElements.forEach(element => {
  64. addFeatures(element.appendChild(document.createElement('br')))
  65. })
  66. } else { // every other page
  67. if (nn = document.querySelectorAll('input[value="markdown"]')) {
  68. for (var n, i = 0; (i < nn.length) && (n = nn[i]); i++) {
  69. if (location.href.indexOf('/script_versions/')) {
  70. n.click()
  71. }
  72. n.click() // posting a new script
  73. addFeatures(n.parentNode.appendChild(document.createElement('br')))
  74. }
  75. }
  76. }
  77. }
  78. })
  79.  
  80. function addFeatures(n) {
  81. if (!n) {
  82. return
  83. }
  84.  
  85. var form = n.closest('form')
  86.  
  87. if (form.action.indexOf('/edit') < 0) {
  88. n.click()
  89. }
  90.  
  91. if (inPostNewScriptVer) {
  92. n.parentNode.textAreaNode = n.parentNode.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea')
  93. } else {
  94. n.parentNode.textAreaNode = form.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea')
  95. }
  96.  
  97. GM_addStyle(`
  98. .mdButton {
  99. display: inline-block;
  100. cursor: pointer;
  101. margin: 0px;
  102. font-size: 12px;
  103. line-height: 1;
  104. font-weight: bold;
  105. padding: 4px 6px;
  106. background: -moz-linear-gradient(center bottom , #CCC 0%, #FAFAFA 100%) repeat scroll 0% 0% #F8F8F8;
  107. border: 1px solid #999;
  108. border-radius: 2px;
  109. white-space: nowrap;
  110. text-shadow: 0px 1px 0px #FFF;
  111. box-shadow: 0px 1px 0px #FFF inset, 0px -1px 2px #BBB inset;
  112. color: #333;
  113. }
  114. `)
  115.  
  116. // Add buttons
  117. btnMake(n, '<b>' + __('B') + '</b>', __('Bold'), '**')
  118. btnMake(n, '#', __('Title'), '#', ' ')
  119. btnMake(n, '-', __('List'), '- ', ' ')
  120.  
  121. btnMake(n, '<i>' + __('I') + '</i>', __('Italic'), '*')
  122. btnMake(n, '<u>' + __('U') + '</u>', __('Underline'), '<u>', '</u>')
  123. btnMake(n, '<s>' + __('S') + '</s>', __('Strikethrough'), '<s>', '</s>')
  124. btnMake(n, '&lt;br&gt;', __('Force line break'), '<br>', '', true)
  125. btnMake(n, '---', __('Horizontal line'), '\n\n---\n\n', '', true)
  126. btnMake(n, __('URL'), __('Add URL to selected text'),
  127. function (e) {
  128. try {
  129. edWrapInTag('[', '](' + prompt(__('URL') + ':') + ')', edInit(e.target))
  130. } catch (ex) { }
  131. }
  132. )
  133. btnMake(n, __('Image (https)'), __('Convert selected https://url to inline image'), '![' + __('image') + '](', ')')
  134. if (inForum) {
  135. btnMake(n, __('Table'), __('Insert table template'), __('\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n'), '', true)
  136. }
  137. btnMake(n, __('Code'), __('Apply CODE markdown to selected text'),
  138. function (e) {
  139. var ed = edInit(e.target)
  140. if (ed.sel.indexOf('\n') < 0) {
  141. edWrapInTag('`', '`', ed)
  142. } else {
  143. edWrapInTag(
  144. ((ed.sel1 == 0) || (ed.text.charAt(ed.sel1 - 1) == '\n') ? '' : '\n') + '```' + (ed.sel.charAt(0) == '\n' ? '' : '\n'),
  145. (ed.sel.substr(-1) == '\n' ? '' : '\n') + '```' + (ed.text.substr(ed.sel2, 1) == '\n' ? '' : '\n'),
  146. ed
  147. )
  148. }
  149. }
  150. )
  151.  
  152. var allPreviewTabs = contains('.preview-tab', 'Preview')
  153. allPreviewTabs.forEach(element => {
  154. element.onclick = function (event) {
  155. let target = event.target // delegation: where was the click?
  156. if (target.tagName !== 'A' && target.tagName !== 'SPAN') { return }
  157. form.querySelectorAll('.Button').forEach(element2 => element2.style.display = 'none')
  158. }
  159. })
  160.  
  161. var allWriteTabs = contains('.write-tab', 'Write')
  162. allWriteTabs.forEach(element => {
  163. element.onclick = function (event) {
  164. let target = event.target // where was the click?
  165. if (target.tagName !== 'A' && target.tagName !== 'SPAN') { return }
  166. form.querySelectorAll('.Button').forEach(element2 => element2.style.display = 'inline-block')
  167. }
  168. })
  169. }
  170.  
  171. function btnMake(afterNode, label, title, tag1_or_cb, tag2, noWrap) {
  172. var a = document.createElement('a')
  173. a.className = 'mdButton'
  174. a.innerHTML = label
  175. a.title = title
  176.  
  177. a.addEventListener('click',
  178. typeof (tag1_or_cb) === 'function' ? tag1_or_cb : // if
  179. noWrap ? function (e) { edInsertText(tag1_or_cb, edInit(e.target)) } : // else if
  180. function (e) { edWrapInTag(tag1_or_cb, tag2, edInit(e.target)) } // else
  181. )
  182.  
  183. var nparent
  184. inForum ? nparent = afterNode : nparent = afterNode.parentNode
  185. a.textAreaNode = nparent.textAreaNode || nparent.parentNode.querySelector('textArea')
  186. nparent.appendChild(a)
  187. }
  188.  
  189. function edInit(btn) {
  190. var ed = { node: btn.textAreaNode || btn.parentNode.textAreaNode }
  191. ed.sel1 = ed.node.selectionStart
  192. ed.sel2 = ed.node.selectionEnd
  193. ed.text = ed.node.value
  194. ed.sel = ed.text.substring(ed.sel1, ed.sel2)
  195. return ed
  196. }
  197.  
  198. function edWrapInTag(tag1, tag2, ed) {
  199. ed.node.value = ed.text.substr(0, ed.sel1) + tag1 + ed.sel + (tag2 ? tag2 : tag1) + ed.text.substr(ed.sel2)
  200. ed.node.setSelectionRange(ed.sel1 + tag1.length, ed.sel1 + tag1.length + ed.sel.length)
  201. ed.node.focus()
  202. }
  203.  
  204. function edInsertText(text, ed) {
  205. ed.node.value = ed.text.substr(0, ed.sel2) + text + ed.text.substr(ed.sel2)
  206. ed.node.setSelectionRange(ed.sel2 + text.length, ed.sel2 + text.length)
  207. ed.node.focus()
  208. }
  209.  
  210. var __ = (function (l, langs) {
  211. var lang = langs[l] || langs[l.replace(/-.+/, '')]
  212. return lang ? function (text) { return lang[text] || text }
  213. : function (text) { return text } // No matching language, fallback to english
  214. })(location.pathname.match(/^\/(.+?)\//)[1], {
  215. 'zh-CN': {
  216. 'Bold': '粗体',
  217. 'Italic': '斜体',
  218. 'Underline': '下划线',
  219. 'Strikethrough': '删除线',
  220. 'Force line break': '强制换行',
  221. 'Horizontal line': '水平分割线',
  222. 'URL': '链接',
  223. 'Title': '标题',
  224. 'List': '无序列表',
  225. 'Add URL to selected text': '为所选文字添加链接',
  226. 'Image (https)': '图片 (https)',
  227. 'Convert selected https://url to inline image': '将所选地址转换为行内图片',
  228. 'image': '图片描述', // Default image alt value
  229. 'Table': '表格',
  230. 'Insert table template': '插入表格模板',
  231. 'Code': '代码',
  232. 'Apply CODE markdown to selected text': '将选中代码围起来',
  233. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  234. '\n| 表头 1 | 表头 2 |\n|-------|-------|\n| 表格 1 | 表格 2 |\n| 表格 3 | 表格 4 |\n'
  235. },
  236. 'ru': {
  237. 'B': 'Ж',
  238. 'I': 'К',
  239. 'U': 'Ч',
  240. 'S': 'П',
  241. 'Bold': 'Жирный',
  242. 'Italic': 'Курсив',
  243. 'Underline': 'Подчеркнутый',
  244. 'Strikethrough': 'Перечеркнутый',
  245. 'Force line break': 'Новая строка',
  246. 'Horizontal line': 'Горизонтальная линия',
  247. 'URL': 'ссылка',
  248. 'Title': 'Заголовок',
  249. 'List': 'Неупорядоченный список',
  250. 'Add URL to selected text': 'Добавить ссылку к выделенному тексту',
  251. 'Image (https)': 'Картинка (https)',
  252. 'Convert selected https://url to inline image': 'Преобразовать выделенный https:// адрес в картинку',
  253. 'image': 'картинка', // Default image alt value
  254. 'Table': 'Таблица',
  255. 'Insert table template': 'Вставить шаблон таблицы',
  256. 'Code': 'Код',
  257. 'Apply CODE markdown to selected text': 'Пометить выделенный фрагмент как программный код',
  258. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  259. '\n| заголовок1 | заголовок2 |\n|-------|-------|\n| ячейка1 | ячейка2 |\n| ячейка3 | ячейка4 |\n'
  260. },
  261. 'fr': {
  262. 'B': 'G',
  263. 'I': 'I',
  264. 'U': 'S',
  265. 'S': 'B',
  266. 'Bold': 'Gras',
  267. 'Italic': 'Italique',
  268. 'Underline': 'Souligné',
  269. 'Strikethrough': 'Barré',
  270. 'Force line break': 'Forcer le saut de ligne',
  271. 'Horizontal line': 'Ligne horizontale',
  272. 'URL': 'URL',
  273. 'Title': 'Titre',
  274. 'List': 'Liste non ordonnée',
  275. 'Add URL to selected text': 'Ajouter URL au texte sélectionné',
  276. 'Image (https)': 'Image (https)',
  277. 'Convert selected https://url to inline image': 'Convertir https://url sélectionnés en images',
  278. 'image': 'image', // Default image alt value
  279. 'Table': 'Tableau',
  280. 'Insert table template': 'Insérer un modèle de table',
  281. 'Code': 'Code',
  282. 'Apply CODE markdown to selected text': 'Appliquer CODE markdown au texte sélectionné',
  283. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  284. '\n| En-tête 1 | En-tête 2 |\n|-------|-------|\n| cellule 1 | cellule 2 |\n| cellule 3 | cellule 4 |\n'
  285. }
  286. })