Greasy Fork is available in English.

Markdown toolbar for GreasyFork

Select Markdown format by default, add help links, add toolbar formatting buttons for markdown

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
  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.4
  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/*/users/edit
  20. // @grant GM_addStyle
  21. // @run-at document-start
  22. // @supportURL https://github.com/darkred/Userscripts/issues
  23. // ==/UserScript==
  24.  
  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.  
  35.  
  36.  
  37.  
  38. var inForum = location.href.indexOf('/discussions') > 0;
  39. var inPostNewScriptVer = location.href.indexOf('/versions/new') > 0;
  40.  
  41.  
  42. // Native $("selector:contains('text')"); https://github.com/nefe/You-Dont-Need-jQuery#1.12
  43. function contains(selector, text) {
  44. var elements = document.querySelectorAll(selector);
  45. return Array.from(elements).filter(function(element) {
  46. return RegExp(text).test(element.textContent);
  47. });
  48. }
  49.  
  50.  
  51. window.addEventListener('DOMContentLoaded', function(e) {
  52.  
  53. // var refElements = contains('.label-note', '(See allowed code)' );
  54. var refElements = document.querySelectorAll(`
  55. input[name="authenticity_token"] + .label-note,
  56.  
  57. label[for="script-version-additional-info-0"] + .label-note,
  58. label[for="changelog"] + .label-note,
  59.  
  60. label[for="conversation_messages_attributes_0_content"] + .label-note,
  61.  
  62. label[for="user_profile"] + .label-note,
  63.  
  64. form > .label-note
  65. `);
  66.  
  67. if (inForum){
  68.  
  69. refElements.forEach(element => {element.insertAdjacentHTML('beforeend','<br>'); addFeatures(element); });
  70.  
  71. }
  72. else { // not in Forum
  73.  
  74. // This page has 2 non-code textareas: 'Additional info' and 'Changelog'
  75. if (inPostNewScriptVer) {
  76.  
  77. refElements.forEach(element => { addFeatures(element.appendChild(document.createElement('br'))); });
  78.  
  79. } else { // every other page
  80.  
  81.  
  82. if (nn = document.querySelectorAll('input[value="markdown"]')) {
  83. for (var n, i=0; (i<nn.length) && (n=nn[i]); i++) {
  84. if (location.href.indexOf('/script_versions/')) {
  85. n.click();
  86. }
  87. n.click(); // posting a new script
  88. addFeatures(n.parentNode.appendChild(document.createElement('br')));
  89. }
  90. }
  91.  
  92.  
  93. }
  94.  
  95. }
  96.  
  97.  
  98. });
  99.  
  100.  
  101.  
  102.  
  103.  
  104. function addFeatures(n) {
  105.  
  106. if (!n){
  107. return;
  108. }
  109.  
  110. var form = n.closest('form');
  111. if (form.action.indexOf('/edit') < 0) {
  112. n.click();
  113. }
  114.  
  115.  
  116.  
  117.  
  118.  
  119.  
  120. if (inPostNewScriptVer) {
  121. n.parentNode.textAreaNode = n.parentNode.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea');
  122. } else {
  123. n.parentNode.textAreaNode = form.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea');
  124.  
  125. }
  126.  
  127. // ------------------------------------------------------------------
  128.  
  129.  
  130. // console.log(form.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea'));
  131. // console.log(form)
  132. // console.log(form.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea'));
  133. //THIS // n.parentNode.textAreaNode = form.querySelector('textarea.TextBox, textarea.previewable, div.previewable textarea');
  134. // add formatting help tooltips (the '(?)' )
  135.  
  136. // .querySelector('input[value="html').firstElementChild
  137. /*
  138. // n.previousElementSibling.insertAdjacentHTML('beforeend',
  139. // n.querySelector('input[value="html"]').nextSibling.insertAdjacentHTML('beforeend',
  140. const newContent = document.createElement('a');
  141. newContent.innerHTML = '<a href="/help/allowed-markup" target="_blank" title="'+
  142. '* (name, title), a (href), abbr, b, blockquote (cite), br, center, cite, code, dd, del, dfn, div, dl, dt, em, '+
  143. 'h1, h2, h3, h4, h5, h6, hr, i, ins, img (alt, height, src (https), width), kbd, li, mark, ol, p, pre, q (cite), '+
  144. 'rp, rt, ruby, s, samp, small, span, strike, strong, tt, table, tbody, tfoot, thead, td, th, tr, sub, sup, '+
  145. 'time (datetime, pubdate), u, ul, var">?</a>' ;
  146. n.insertAdjacentHTML('beforeend',
  147. ' (<a href="http://www.darkcoding.net/software/markdown-quick-reference/" target="_blank">?</a>)');
  148. // if (location.href.indexOf('/forum/messages/') > -1)
  149. if (location.href.indexOf('/conversations/') > -1)
  150. GM_addStyle('#ConversationForm label { display:inline-block; margin-right:2ex }\
  151. #ConversationForm .TextBox { margin-top:0 }');
  152. */
  153. // ------------------------------------------------------------------
  154.  
  155.  
  156. GM_addStyle('\
  157. .Button {\
  158. display: inline-block;\
  159. cursor: pointer;\
  160. margin: 0px;\
  161. font-size: 12px;\
  162. line-height: 1;\
  163. font-weight: bold;\
  164. padding: 4px 6px;\
  165. background: -moz-linear-gradient(center bottom , #CCC 0%, #FAFAFA 100%) repeat scroll 0% 0% #F8F8F8;\
  166. border: 1px solid #999;\
  167. border-radius: 2px;\
  168. white-space: nowrap;\
  169. text-shadow: 0px 1px 0px #FFF;\
  170. box-shadow: 0px 1px 0px #FFF inset, 0px -1px 2px #BBB inset;\
  171. color: #333;}'
  172. );
  173.  
  174.  
  175.  
  176.  
  177.  
  178.  
  179. // add buttons
  180. // debugger
  181. btnMake(n, '<b>'+__('B')+'</b>', __('Bold'), '**');
  182. btnMake(n, '<i>'+__('I')+'</i>', __('Italic'), '*');
  183. btnMake(n, '<u>'+__('U')+'</u>', __('Underline'), '<u>','</u>');
  184. btnMake(n, '<s>'+__('S')+'</s>', __('Strikethrough'), '<s>','</s>');
  185. btnMake(n, '&lt;br&gt;', __('Force line break'), '<br>','', true);
  186. btnMake(n, '---', __('Horizontal line'), '\n\n---\n\n', '', true);
  187. btnMake(n, __('URL'), __('Add URL to selected text'),
  188. function(e) {
  189. try {edWrapInTag('[', ']('+prompt(__('URL')+':')+')', edInit(e.target))}
  190. catch(ex) {}
  191. });
  192. btnMake(n, __('Image (https)'), __('Convert selected https://url to inline image'), '!['+__('image')+'](', ')');
  193. if (inForum)
  194. btnMake(n, __('Table'), __('Insert table template'), __('\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n'), '', true);
  195. btnMake(n, __('Code'), __('Apply CODE markdown to selected text'),
  196. function(e){
  197. var ed = edInit(e.target);
  198. if (ed.sel.indexOf('\n') < 0)
  199. edWrapInTag('`', '`', ed);
  200. else
  201. edWrapInTag(((ed.sel1==0) || (ed.text.charAt(ed.sel1-1) == '\n') ? '' : '\n') + '```' + (ed.sel.charAt(0) == '\n' ? '' : '\n'),
  202. (ed.sel.substr(-1) == '\n' ? '' : '\n') + '```' + (ed.text.substr(ed.sel2,1) == '\n' ? '' : '\n'),
  203. ed);
  204. });
  205.  
  206.  
  207.  
  208.  
  209.  
  210. var allPreviewTabs = contains('.preview-tab', 'Preview' );
  211. allPreviewTabs.forEach(element => { element.onclick = function(event) {
  212. let target = event.target; // delegation: where was the click?
  213. if (target.tagName !== 'A' && target.tagName !== 'SPAN') {return}
  214. // console.log(element.closest('.label-note') )
  215. form.querySelectorAll('.Button').forEach(element2 => element2.style.display = 'none');
  216. } } );
  217.  
  218. var allWriteTabs = contains('.write-tab', 'Write' );
  219. allWriteTabs.forEach(element => { element.onclick = function(event) {
  220. let target = event.target; // where was the click?
  221. if (target.tagName !== 'A' && target.tagName !== 'SPAN') {return}
  222. form.querySelectorAll('.Button').forEach(element2 => element2.style.display = 'inline-block');
  223. } });
  224.  
  225.  
  226.  
  227.  
  228.  
  229. }
  230.  
  231.  
  232.  
  233.  
  234. function btnMake(afterNode, label, title, tag1_or_cb, tag2, noWrap) {
  235. var a = document.createElement('a');
  236. a.className = 'Button';
  237. a.innerHTML = label;
  238. a.title = title;
  239. // if (inForum)
  240. // a.style.setProperty('float','right');
  241. a.addEventListener('click',
  242. typeof(tag1_or_cb) == 'function' ? tag1_or_cb : // if
  243. noWrap ? function(e){edInsertText(tag1_or_cb, edInit(e.target))} : // else if
  244. function(e){edWrapInTag(tag1_or_cb, tag2, edInit(e.target))} // else
  245. );
  246.  
  247.  
  248. var nparent;
  249. inForum ? nparent = afterNode : nparent = afterNode.parentNode;
  250. a.textAreaNode = nparent.textAreaNode || nparent.parentNode.querySelector('textArea');;
  251. nparent.appendChild(a);
  252. }
  253.  
  254.  
  255.  
  256.  
  257. function edInit(btn) {
  258. var ed = {node: btn.textAreaNode || btn.parentNode.textAreaNode};
  259. ed.sel1 = ed.node.selectionStart;
  260. ed.sel2 = ed.node.selectionEnd,
  261. ed.text = ed.node.value;
  262. ed.sel = ed.text.substring(ed.sel1, ed.sel2);
  263. return ed;
  264. }
  265.  
  266. function edWrapInTag(tag1, tag2, ed) {
  267. ed.node.value = ed.text.substr(0, ed.sel1) + tag1 + ed.sel + (tag2?tag2:tag1) + ed.text.substr(ed.sel2);
  268. ed.node.setSelectionRange(ed.sel1 + tag1.length, ed.sel1 + tag1.length + ed.sel.length);
  269. ed.node.focus();
  270. }
  271.  
  272. function edInsertText(text, ed) {
  273. ed.node.value = ed.text.substr(0, ed.sel2) + text + ed.text.substr(ed.sel2);
  274. ed.node.setSelectionRange(ed.sel2 + text.length, ed.sel2 + text.length);
  275. ed.node.focus();
  276. }
  277.  
  278. var __ = (function (l, langs) {
  279. var lang = langs[l] || langs[l.replace(/-.+/, '')];
  280. return lang ? function (text) { return lang[text] || text; }
  281. : function (text) { return text }; // No matching language, fallback to english
  282. })(location.pathname.match(/^\/(.+?)\//)[1], {
  283. // Can be full name, or just the beginning part.
  284. 'zh-CN': {
  285. 'Bold': '粗体',
  286. 'Italic': '斜体',
  287. 'Underline': '下划线',
  288. 'Strikethrough': '删除线',
  289. 'Force line break': '强制换行',
  290. 'Horizontal line': '水平分割线',
  291. 'URL': '链接',
  292. 'Add URL to selected text': '为所选文字添加链接',
  293. 'Image (https)': '图片 (https)',
  294. 'Convert selected https://url to inline image': '将所选地址转换为行内图片',
  295. 'image': '图片描述', // Default image alt value
  296. 'Table': '表格',
  297. 'Insert table template': '插入表格模板',
  298. 'Code': '代码',
  299. 'Apply CODE markdown to selected text': '将选中代码围起来',
  300.  
  301. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  302. '\n| 表头 1 | 表头 2 |\n|-------|-------|\n| 表格 1 | 表格 2 |\n| 表格 3 | 表格 4 |\n'
  303. },
  304. 'ru': {
  305. 'B': 'Ж',
  306. 'I': 'К',
  307. 'U': 'Ч',
  308. 'S': 'П',
  309. 'Bold': 'Жирный',
  310. 'Italic': 'Курсив',
  311. 'Underline': 'Подчеркнутый',
  312. 'Strikethrough': 'Перечеркнутый',
  313. 'Force line break': 'Новая строка',
  314. 'Horizontal line': 'Горизонтальная линия',
  315. 'URL': 'ссылка',
  316. 'Add URL to selected text': 'Добавить ссылку к выделенному тексту',
  317. 'Image (https)': 'Картинка (https)',
  318. 'Convert selected https://url to inline image': 'Преобразовать выделенный https:// адрес в картинку',
  319. 'image': 'картинка', // Default image alt value
  320. 'Table': 'Таблица',
  321. 'Insert table template': 'Вставить шаблон таблицы',
  322. 'Code': 'Код',
  323. 'Apply CODE markdown to selected text': 'Пометить выделенный фрагмент как программный код',
  324.  
  325. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  326. '\n| заголовок1 | заголовок2 |\n|-------|-------|\n| ячейка1 | ячейка2 |\n| ячейка3 | ячейка4 |\n'
  327. },
  328. 'fr': {
  329. 'B': 'G',
  330. 'I': 'I',
  331. 'U': 'S',
  332. 'S': 'B',
  333. 'Bold': 'Gras',
  334. 'Italic': 'Italique',
  335. 'Underline': 'Souligné',
  336. 'Strikethrough': 'Barré',
  337. 'Force line break': 'Forcer le saut de ligne',
  338. 'Horizontal line': 'Ligne horizontale',
  339. 'URL': 'URL',
  340. 'Add URL to selected text': 'Ajouter URL au texte sélectionné',
  341. 'Image (https)': 'Image (https)',
  342. 'Convert selected https://url to inline image': 'Convertir https://url sélectionnés en images',
  343. 'image': 'image', // Default image alt value
  344. 'Table': 'Tableau',
  345. 'Insert table template': 'Insérer un modèle de table',
  346. 'Code': 'Code',
  347. 'Apply CODE markdown to selected text': 'Appliquer CODE markdown au texte sélectionné',
  348.  
  349. '\n| head1 | head2 |\n|-------|-------|\n| cell1 | cell2 |\n| cell3 | cell4 |\n':
  350. '\n| En-tête 1 | En-tête 2 |\n|-------|-------|\n| cellule 1 | cellule 2 |\n| cellule 3 | cellule 4 |\n'
  351. }
  352. });