GreasyFork markdown

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

当前为 2021-03-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Markdown toolbar for GreasyFork
  3. // @name:ru Markdown-тулбар для GreasyFork
  4. // @name:zh-CN GreasyFork markdown
  5. // @namespace darkred
  6. // @version 2.0.2
  7. // @description Select Markdown format by default, add help links, add toolbar formatting buttons for markdown
  8. // @description:ru Включает формат Markdown по умолчанию, добавляет справочные ссылки по форматам, добавляет панель кнопок форматирования markdown
  9. // @description:zh-CN 在论坛默认使用 Markdown 格式,添加格式帮助链接及 Markdown 工具栏
  10. // @author wOxxOm, darkred
  11. // @contributor JixunMoe
  12. // @license MIT
  13. // @icon https://raw.githubusercontent.com/dcurtis/markdown-mark/master/png/66x40-solid.png
  14. // @include https://greasyfork.org/*discussions/*
  15. // @include https://greasyfork.org/*scripts/*/versions/new*
  16. // @include https://greasyfork.org/*scripts/*/feedback*
  17. // @include https://greasyfork.org/*script_versions/new*
  18. // @include https://greasyfork.org/*/conversations/*
  19. // @include https://greasyfork.org/en/users/edit
  20. // @grant GM_addStyle
  21. // @run-at document-start
  22. // @supportURL https://github.com/darkred/Userscripts/issues
  23. // ==/UserScript==
  24.  
  25. /* jshint lastsemic:true, multistr:true, laxbreak:true, -W030, -W041, -W084 */
  26.  
  27.  
  28. // Example URLS to test:
  29. // https://greasyfork.org/en/discussions/new
  30. // https://greasyfork.org/en/scripts/422887-markdown-toolbar-for-greasyfork/discussions/78139
  31. // https://greasyfork.org/en/scripts/23493/versions/new
  32. // https://greasyfork.org/en/scripts/422445-github-watcher/feedback
  33. // https://greasyfork.org/en/users/2160-darkred/conversations/new
  34. // https://greasyfork.org/en/users/edit
  35.  
  36.  
  37.  
  38.  
  39.  
  40. var inForum = location.href.indexOf('/discussions') > 0;
  41.  
  42. window.addEventListener('DOMContentLoaded', function(e) {
  43. if (inForum){
  44. var refElement = document.querySelector('input[value] + .label-note') || document.querySelector('.form-control + .label-note') || document.querySelector('form .label-note') || document.querySelector('div > div > .form-control + .label-note')
  45. addFeatures(refElement.insertAdjacentHTML('beforeend','<br>'));
  46. addFeatures(refElement);
  47. }
  48. else {
  49.  
  50. if (nn = document.querySelectorAll('input[value="markdown"]'))
  51. for (var n, i=0; (i<nn.length) && (n=nn[i]); i++) {
  52. if (location.href.indexOf('/script_versions/')) {
  53. n.click();
  54. }
  55. n.click(); // posting a new script
  56. addFeatures(n.parentNode.appendChild(document.createElement('br')));
  57. }
  58.  
  59. // addFeatures(document.querySelector('.form-control'));
  60. }
  61.  
  62. new MutationObserver(function(mutations) {
  63. for (var i=0, ml=mutations.length, m; (i<ml) && (m=mutations[i]); i++) {
  64. for (var j=0, nodes=m.addedNodes, nl=nodes.length, n; (j<nl) && (n=nodes[j]); j++) {
  65. if (n.nodeType == 1) // Node.ELEMENT_NODE 1 An Element node like <p> or <div>.
  66. if (inForum) {
  67. if ((n.localName == 'label' && n.querySelector('input[value="Markdown"], input[value="Html"], input[value="markdown"], input[value="html"]'))
  68. || (n = n.querySelector('input[value="Markdown"], input[value="markdown"]') || n.querySelector('input[value="Html"], input[value="html"]')))
  69. return addFeatures(n.closest('label'));
  70. } else {
  71. if (((n.localName == 'input') && (n.value.toLowerCase() == 'Markdown'))
  72. || (n = n.querySelector('input[value="Markdown"], input[value="markdown"]'))) {
  73. if (location.href.indexOf('/script_versions/'))
  74. n.click();
  75. return addFeatures(n.parentNode.appendChild(document.createElement('br')));
  76. }
  77. }
  78. }
  79. }
  80. }).observe(document, {subtree:true, childList:true});
  81. });
  82.  
  83.  
  84.  
  85. function insertAfter(referenceNode, newNode) {
  86. referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  87. }
  88.  
  89.  
  90.  
  91. function addFeatures(n) {
  92.  
  93. if (!n){
  94. return;
  95. }
  96.  
  97. /*
  98. if (inForum) {
  99. */
  100. var form = n.closest('form');
  101. // console.log(form)
  102. // span.current > a.write-tab
  103. // var form = n.querySelector('form');
  104. if (form.action.indexOf('/edit') < 0) {
  105. n.click();
  106. }
  107.  
  108. n.parentNode.textAreaNode = form.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea');
  109. // add formatting help tooltips (the '(?)' )
  110.  
  111. // .querySelector('input[value="html').firstElementChild
  112. /*
  113. // n.previousElementSibling.insertAdjacentHTML('beforeend',
  114. // n.querySelector('input[value="html"]').nextSibling.insertAdjacentHTML('beforeend',
  115. const newContent = document.createElement('a');
  116. newContent.innerHTML = '<a href="/help/allowed-markup" target="_blank" title="'+
  117. '* (name, title), a (href), abbr, b, blockquote (cite), br, center, cite, code, dd, del, dfn, div, dl, dt, em, '+
  118. 'h1, h2, h3, h4, h5, h6, hr, i, ins, img (alt, height, src (https), width), kbd, li, mark, ol, p, pre, q (cite), '+
  119. 'rp, rt, ruby, s, samp, small, span, strike, strong, tt, table, tbody, tfoot, thead, td, th, tr, sub, sup, '+
  120. 'time (datetime, pubdate), u, ul, var">?</a>' ;
  121. n.insertAdjacentHTML('beforeend',
  122. ' (<a href="http://www.darkcoding.net/software/markdown-quick-reference/" target="_blank">?</a>)');
  123. // if (location.href.indexOf('/forum/messages/') > -1)
  124. if (location.href.indexOf('/conversations/') > -1)
  125. GM_addStyle('#ConversationForm label { display:inline-block; margin-right:2ex }\
  126. #ConversationForm .TextBox { margin-top:0 }');
  127. */
  128.  
  129. /*
  130.  
  131. } else { // if not in forum
  132. */
  133. for (var wrapper=n; wrapper = wrapper.parentNode; )
  134. // if (t = wrapper.querySelector('textarea[id*="additional-info"]')) {
  135. if (t = wrapper.querySelector('textarea[id*="additional-info"], textarea[id*="conversation_messages_attributes_0_content"], textarea[id*="discussion_comments_attributes_0_text"], textarea[id*="comment_text"], textarea[id*="user_profile"]')) {
  136. n.parentNode.textAreaNode = t;
  137. break;
  138. }
  139. GM_addStyle('\
  140. .Button {\
  141. display: inline-block;\
  142. cursor: pointer;\
  143. margin: 0px;\
  144. font-size: 12px;\
  145. line-height: 1;\
  146. font-weight: bold;\
  147. padding: 4px 6px;\
  148. background: -moz-linear-gradient(center bottom , #CCC 0%, #FAFAFA 100%) repeat scroll 0% 0% #F8F8F8;\
  149. border: 1px solid #999;\
  150. border-radius: 2px;\
  151. white-space: nowrap;\
  152. text-shadow: 0px 1px 0px #FFF;\
  153. box-shadow: 0px 1px 0px #FFF inset, 0px -1px 2px #BBB inset;\
  154. color: #333;}'
  155. );
  156.  
  157. // }
  158.  
  159.  
  160.  
  161.  
  162. // add buttons
  163. // debugger
  164. btnMake(n, '<b>'+__('B')+'</b>', __('Bold'), '**');
  165. btnMake(n, '<i>'+__('I')+'</i>', __('Italic'), '*');
  166. btnMake(n, '<u>'+__('U')+'</u>', __('Underline'), '<u>','</u>');
  167. btnMake(n, '<s>'+__('S')+'</s>', __('Strikethrough'), '<s>','</s>');
  168. btnMake(n, '&lt;br&gt;', __('Force line break'), '<br>','', true);
  169. btnMake(n, '---', __('Horizontal line'), '\n\n---\n\n', '', true);
  170. btnMake(n, __('URL'), __('Add URL to selected text'),
  171. function(e) {
  172. try {edWrapInTag('[', ']('+prompt(__('URL')+':')+')', edInit(e.target))}
  173. catch(ex) {}
  174. });
  175. btnMake(n, __('Image (https)'), __('Convert selected https://url to inline image'), '!['+__('image')+'](', ')');
  176. if (inForum)
  177. btnMake(n, __('Table'), __('Insert table template'), __('\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n'), '', true);
  178. btnMake(n, __('Code'), __('Apply CODE markdown to selected text'),
  179. function(e){
  180. var ed = edInit(e.target);
  181. if (ed.sel.indexOf('\n') < 0)
  182. edWrapInTag('`', '`', ed);
  183. else
  184. edWrapInTag(((ed.sel1==0) || (ed.text.charAt(ed.sel1-1) == '\n') ? '' : '\n') + '```' + (ed.sel.charAt(0) == '\n' ? '' : '\n'),
  185. (ed.sel.substr(-1) == '\n' ? '' : '\n') + '```' + (ed.text.substr(ed.sel2,1) == '\n' ? '' : '\n'),
  186. ed);
  187. });
  188.  
  189.  
  190.  
  191.  
  192. var previewTab = document.querySelector('span > a.preview-tab > span');
  193. if (previewTab){
  194. previewTab.onclick = function(){
  195. document.querySelectorAll('.Button').forEach(element => element.style.display = 'none');
  196. };
  197. }
  198.  
  199. var writeTab = document.querySelector('span > a.write-tab > span');
  200. if (writeTab){
  201. writeTab.onclick = function(){
  202. document.querySelectorAll('.Button').forEach(element => element.style.display = 'inline-block');
  203. };
  204. }
  205.  
  206.  
  207.  
  208.  
  209. }
  210.  
  211.  
  212.  
  213.  
  214. function btnMake(afterNode, label, title, tag1_or_cb, tag2, noWrap) {
  215. var a = document.createElement('a');
  216. a.className = 'Button';
  217. a.innerHTML = label;
  218. a.title = title;
  219. // if (inForum)
  220. // a.style.setProperty('float','right');
  221. a.addEventListener('click',
  222. typeof(tag1_or_cb) == 'function' ? tag1_or_cb : // if
  223. noWrap ? function(e){edInsertText(tag1_or_cb, edInit(e.target))} : // else if
  224. function(e){edWrapInTag(tag1_or_cb, tag2, edInit(e.target))} // else
  225. );
  226.  
  227. /* INITIAL
  228. var nparent = afterNode.parentNode;
  229. a.textAreaNode = nparent.textAreaNode;
  230. inForum ? nparent.insertBefore(a, nparent.firstElementChild) : nparent.appendChild(a);
  231. */
  232.  
  233. var nparent;
  234. inForum ? nparent = afterNode : nparent = afterNode.parentNode;
  235. a.textAreaNode = nparent.textAreaNode || nparent.parentNode.querySelector('textArea');;
  236. // a.textAreaNode = nparent.textAreaNode || nparent.closest('textArea');
  237. nparent.appendChild(a);
  238. }
  239.  
  240.  
  241.  
  242.  
  243. function edInit(btn) {
  244. var ed = {node: btn.textAreaNode || btn.parentNode.textAreaNode};
  245. ed.sel1 = ed.node.selectionStart;
  246. ed.sel2 = ed.node.selectionEnd,
  247. ed.text = ed.node.value;
  248. ed.sel = ed.text.substring(ed.sel1, ed.sel2);
  249. return ed;
  250. }
  251.  
  252. function edWrapInTag(tag1, tag2, ed) {
  253. ed.node.value = ed.text.substr(0, ed.sel1) + tag1 + ed.sel + (tag2?tag2:tag1) + ed.text.substr(ed.sel2);
  254. ed.node.setSelectionRange(ed.sel1 + tag1.length, ed.sel1 + tag1.length + ed.sel.length);
  255. ed.node.focus();
  256. }
  257.  
  258. function edInsertText(text, ed) {
  259. ed.node.value = ed.text.substr(0, ed.sel2) + text + ed.text.substr(ed.sel2);
  260. ed.node.setSelectionRange(ed.sel2 + text.length, ed.sel2 + text.length);
  261. ed.node.focus();
  262. }
  263.  
  264. var __ = (function (l, langs) {
  265. var lang = langs[l] || langs[l.replace(/-.+/, '')];
  266. return lang ? function (text) { return lang[text] || text; }
  267. : function (text) { return text }; // No matching language, fallback to english
  268. })(location.pathname.match(/^\/(.+?)\//)[1], {
  269. // Can be full name, or just the beginning part.
  270. 'zh-CN': {
  271. 'Bold': '粗体',
  272. 'Italic': '斜体',
  273. 'Underline': '下划线',
  274. 'Strikethrough': '删除线',
  275. 'Force line break': '强制换行',
  276. 'Horizontal line': '水平分割线',
  277. 'URL': '链接',
  278. 'Add URL to selected text': '为所选文字添加链接',
  279. 'Image (https)': '图片 (https)',
  280. 'Convert selected https://url to inline image': '将所选地址转换为行内图片',
  281. 'image': '图片描述', // Default image alt value
  282. 'Table': '表格',
  283. 'Insert table template': '插入表格模板',
  284. 'Code': '代码',
  285. 'Apply CODE markdown to selected text': '将选中代码围起来',
  286.  
  287. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  288. '\n| 表头 1 | 表头 2 |\n|-------|-------|\n| 表格 1 | 表格 2 |\n| 表格 3 | 表格 4 |\n'
  289. },
  290. 'ru': {
  291. 'B': 'Ж',
  292. 'I': 'К',
  293. 'U': 'Ч',
  294. 'S': 'П',
  295. 'Bold': 'Жирный',
  296. 'Italic': 'Курсив',
  297. 'Underline': 'Подчеркнутый',
  298. 'Strikethrough': 'Перечеркнутый',
  299. 'Force line break': 'Новая строка',
  300. 'Horizontal line': 'Горизонтальная линия',
  301. 'URL': 'ссылка',
  302. 'Add URL to selected text': 'Добавить ссылку к выделенному тексту',
  303. 'Image (https)': 'Картинка (https)',
  304. 'Convert selected https://url to inline image': 'Преобразовать выделенный https:// адрес в картинку',
  305. 'image': 'картинка', // Default image alt value
  306. 'Table': 'Таблица',
  307. 'Insert table template': 'Вставить шаблон таблицы',
  308. 'Code': 'Код',
  309. 'Apply CODE markdown to selected text': 'Пометить выделенный фрагмент как программный код',
  310.  
  311. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  312. '\n| заголовок1 | заголовок2 |\n|-------|-------|\n| ячейка1 | ячейка2 |\n| ячейка3 | ячейка4 |\n'
  313. },
  314. 'fr': {
  315. 'B': 'G',
  316. 'I': 'I',
  317. 'U': 'S',
  318. 'S': 'B',
  319. 'Bold': 'Gras',
  320. 'Italic': 'Italique',
  321. 'Underline': 'Souligné',
  322. 'Strikethrough': 'Barré',
  323. 'Force line break': 'Forcer le saut de ligne',
  324. 'Horizontal line': 'Ligne horizontale',
  325. 'URL': 'URL',
  326. 'Add URL to selected text': 'Ajouter URL au texte sélectionné',
  327. 'Image (https)': 'Image (https)',
  328. 'Convert selected https://url to inline image': 'Convertir https://url sélectionnés en images',
  329. 'image': 'image', // Default image alt value
  330. 'Table': 'Tableau',
  331. 'Insert table template': 'Insérer un modèle de table',
  332. 'Code': 'Code',
  333. 'Apply CODE markdown to selected text': 'Appliquer CODE markdown au texte sélectionné',
  334.  
  335. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  336. '\n| En-tête 1 | En-tête 2 |\n|-------|-------|\n| cellule 1 | cellule 2 |\n| cellule 3 | cellule 4 |\n'
  337. }
  338. });