The version of vichan running on lainchan.org
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

386 lignes
14KB

  1. /*
  2. * comment-toolbar.js
  3. * - Adds a toolbar above the commenting area containing most of 8Chan's formatting options
  4. * - Press Esc to close quick-reply window when it's in focus
  5. *
  6. * Usage:
  7. * $config['additional_javascript'][] = 'js/jquery.min.js';
  8. * $config['additional_javascript'][] = 'js/comment-toolbar.js';
  9. */
  10. if (active_page == 'thread' || active_page == 'index') {
  11. var formatText = (function($){
  12. "use strict";
  13. var self = {};
  14. self.rules = {
  15. spoiler: {
  16. text: _('Spoiler'),
  17. key: 's',
  18. multiline: false,
  19. exclusiveline: false,
  20. prefix:'[spoiler]',
  21. suffix:'[/spoiler]'
  22. },
  23. italics: {
  24. text: _('Italics'),
  25. key: 'i',
  26. multiline: false,
  27. exclusiveline: false,
  28. prefix: "[i]",
  29. suffix: "[/i]"
  30. },
  31. bold: {
  32. text: _('Bold'),
  33. key: 'b',
  34. multiline: false,
  35. exclusiveline: false,
  36. prefix: "[b]",
  37. suffix: "[/b]"
  38. },
  39. /*underline: {
  40. text: _('Underline'),
  41. key: 'u',
  42. multiline: false,
  43. exclusiveline: false,
  44. prefix:'__',
  45. suffix:'__'
  46. },*/
  47. code: {
  48. text: _('Code'),
  49. key: 'f',
  50. multiline: true,
  51. exclusiveline: false,
  52. prefix: '[code]',
  53. suffix: '[/code]'
  54. },
  55. /*strike: {
  56. text: _('Strike'),
  57. key: 'd',
  58. multiline:false,
  59. exclusiveline:false,
  60. prefix:'~~',
  61. suffix:'~~'
  62. },*/
  63. heading: {
  64. text: _('Heading'),
  65. key: 'r',
  66. multiline:false,
  67. exclusiveline:true,
  68. prefix:'==',
  69. suffix:'=='
  70. }
  71. };
  72. self.toolbar_wrap = function(node) {
  73. var parent = $(node).parents('form[name="post"]');
  74. self.wrap(parent.find('#body')[0],'textarea[name="body"]', parent.find('.format-text > select')[0].value, false);
  75. };
  76. self.wrap = function(ref, target, option, expandedwrap) {
  77. // clean and validate arguments
  78. if (ref == null) return;
  79. var settings = {multiline: false, exclusiveline: false, prefix:'', suffix: null};
  80. $.extend(settings,JSON.parse(localStorage.formatText_rules)[option]);
  81. // resolve targets into array of proper node elements
  82. // yea, this is overly verbose, oh well.
  83. var res = [];
  84. if (target instanceof Array) {
  85. for (var indexa in target) {
  86. if (target.hasOwnProperty(indexa)) {
  87. if (typeof target[indexa] == 'string') {
  88. var nodes = $(target[indexa]);
  89. for (var indexb in nodes) {
  90. if (indexa.hasOwnProperty(indexb)) res.push(nodes[indexb]);
  91. }
  92. } else {
  93. res.push(target[indexa]);
  94. }
  95. }
  96. }
  97. } else {
  98. if (typeof target == 'string') {
  99. var nodes = $(target);
  100. for (var index in nodes) {
  101. if (nodes.hasOwnProperty(index)) res.push(nodes[index]);
  102. }
  103. } else {
  104. res.push(target);
  105. }
  106. }
  107. target = res;
  108. //record scroll top to restore it later.
  109. var scrollTop = ref.scrollTop;
  110. //We will restore the selection later, so record the current selection
  111. var selectionStart = ref.selectionStart;
  112. var selectionEnd = ref.selectionEnd;
  113. var text = ref.value;
  114. var before = text.substring(0, selectionStart);
  115. var selected = text.substring(selectionStart, selectionEnd);
  116. var after = text.substring(selectionEnd);
  117. var whiteSpace = [" ","\t"];
  118. var breakSpace = ["\r","\n"];
  119. var cursor;
  120. // handles multiline selections on formatting that doesn't support spanning over multiple lines
  121. if (!settings.multiline) selected = selected.replace(/(\r|\n|\r\n)/g,settings.suffix +"$1"+ settings.prefix);
  122. // handles formatting that requires it to be on it's own line OR if the user wishes to expand the wrap to the nearest linebreak
  123. if (settings.exclusiveline || expandedwrap) {
  124. // buffer the begining of the selection until a linebreak
  125. cursor = before.length -1;
  126. while (cursor >= 0 && breakSpace.indexOf(before.charAt(cursor)) == -1) {
  127. cursor--;
  128. }
  129. selected = before.substring(cursor +1) + selected;
  130. before = before.substring(0, cursor +1);
  131. // buffer the end of the selection until a linebreak
  132. cursor = 0;
  133. while (cursor < after.length && breakSpace.indexOf(after.charAt(cursor)) == -1) {
  134. cursor++;
  135. }
  136. selected += after.substring(0, cursor);
  137. after = after.substring(cursor);
  138. }
  139. // set values
  140. var res = before + settings.prefix + selected + settings.suffix + after;
  141. $(target).val(res);
  142. // restore the selection area and scroll of the reference
  143. ref.selectionEnd = before.length + settings.prefix.length + selected.length;
  144. if (selectionStart === selectionEnd) {
  145. ref.selectionStart = ref.selectionEnd;
  146. } else {
  147. ref.selectionStart = before.length + settings.prefix.length;
  148. }
  149. ref.scrollTop = scrollTop;
  150. };
  151. self.build_toolbars = function(){
  152. if (localStorage.formatText_toolbar == 'true'){
  153. // remove existing toolbars
  154. if ($('.format-text').length > 0) $('.format-text').remove();
  155. // Place toolbar above each textarea input
  156. var name, options = '', rules = JSON.parse(localStorage.formatText_rules);
  157. for (var index in rules) {
  158. if (!rules.hasOwnProperty(index)) continue;
  159. name = rules[index].text;
  160. //add hint if key exists
  161. if (rules[index].key) {
  162. name += ' (CTRL + '+ rules[index].key.toUpperCase() +')';
  163. }
  164. options += '<option value="'+ index +'">'+ name +'</option>';
  165. }
  166. $('[name="body"]').before('<div class="format-text"><a href="javascript:;" onclick="formatText.toolbar_wrap(this);">Wrap</a><select>'+ options +'</select></div>');
  167. $('body').append('<style>#quick-reply .format-text>a{width:15%;display:inline-block;text-align:center;}#quick-reply .format-text>select{width:85%;};</style>');
  168. }
  169. };
  170. self.add_rule = function(rule, index){
  171. if (rule === undefined) rule = {
  172. text: 'New Rule',
  173. key: '',
  174. multiline:false,
  175. exclusiveline:false,
  176. prefix:'',
  177. suffix:''
  178. }
  179. // generate an id for the rule
  180. if (index === undefined) {
  181. var rules = JSON.parse(localStorage.formatText_rules);
  182. while (rules[index] || index === undefined) {
  183. index = ''
  184. index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
  185. index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
  186. index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
  187. }
  188. }
  189. if (window.Options && Options.get_tab('formatting')){
  190. var html = $('<div class="format_rule" name="'+ index +'"></div>').html('\
  191. <input type="text" name="text" class="format_option" size="10" value=\"'+ rule.text.replace(/"/g, '&quot;') +'\">\
  192. <input type="checkbox" name="multiline" class="format_option" '+ (rule.multiline ? 'checked' : '') +'>\
  193. <input type="checkbox" name="exclusiveline" class="format_option" '+ (rule.exclusiveline ? 'checked' : '') +'>\
  194. <input type="text" name="prefix" class="format_option" size="8" value=\"'+ (rule.prefix ? rule.prefix.replace(/"/g, '&quot;') : '') +'\">\
  195. <input type="text" name="suffix" class="format_option" size="8" value=\"'+ (rule.suffix ? rule.suffix.replace(/"/g, '&quot;') : '') +'\">\
  196. <input type="text" name="key" class="format_option" size="2" maxlength="1" value=\"'+ rule.key +'\">\
  197. <input type="button" value="X" onclick="if(confirm(\'Do you wish to remove the '+ rule.text +' formatting rule?\'))$(this).parent().remove();">\
  198. ');
  199. if ($('.format_rule').length > 0) {
  200. $('.format_rule').last().after(html);
  201. } else {
  202. Options.extend_tab('formatting', html);
  203. }
  204. }
  205. };
  206. self.save_rules = function(){
  207. var rule, newrules = {}, rules = $('.format_rule');
  208. for (var index=0;rules[index];index++) {
  209. rule = $(rules[index]);
  210. newrules[rule.attr('name')] = {
  211. text: rule.find('[name="text"]').val(),
  212. key: rule.find('[name="key"]').val(),
  213. prefix: rule.find('[name="prefix"]').val(),
  214. suffix: rule.find('[name="suffix"]').val(),
  215. multiline: rule.find('[name="multiline"]').is(':checked'),
  216. exclusiveline: rule.find('[name="exclusiveline"]').is(':checked')
  217. };
  218. }
  219. localStorage.formatText_rules = JSON.stringify(newrules);
  220. self.build_toolbars();
  221. };
  222. self.reset_rules = function(to_default) {
  223. $('.format_rule').remove();
  224. var rules;
  225. if (to_default) rules = self.rules;
  226. else rules = JSON.parse(localStorage.formatText_rules);
  227. for (var index in rules){
  228. if (!rules.hasOwnProperty(index)) continue;
  229. self.add_rule(rules[index], index);
  230. }
  231. };
  232. // setup default rules for customizing
  233. if (!localStorage.formatText_rules) localStorage.formatText_rules = JSON.stringify(self.rules);
  234. if (!localStorage.formatText_toolbar) localStorage.formatText_toolbar = true;
  235. if (!localStorage.formatText_keybinds) localStorage.formatText_keybinds = true;
  236. // setup code to be ran when page is ready (work around for main.js compilation).
  237. $(document).ready(function(){
  238. // Add settings to Options panel general tab
  239. if (window.Options && Options.get_tab('general')) {
  240. var s1 = '#formatText_keybinds>input', s2 = '#formatText_toolbar>input', e = 'change';
  241. Options.extend_tab('general', '\
  242. <fieldset>\
  243. <legend>Formatting Options</legend>\
  244. <label id="formatText_keybinds"><input type="checkbox">' + _('Enable formatting keybinds') + '</label>\
  245. <label id="formatText_toolbar"><input type="checkbox">' + _('Show formatting toolbar') + '</label>\
  246. </fieldset>\
  247. ');
  248. } else {
  249. var s1 = '#formatText_keybinds', s2 = '#formatText_toolbar', e = 'click';
  250. $('hr:first').before('<div id="formatText_keybinds" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Enable formatting keybinds') +'</a></div>');
  251. $('hr:first').before('<div id="formatText_toolbar" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Show formatting toolbar') +'</a></div>');
  252. }
  253. // add the tab for customizing the format settings
  254. if (window.Options && !Options.get_tab('formatting')) {
  255. Options.add_tab('formatting', 'angle-right', _('Customize Formatting'));
  256. Options.extend_tab('formatting', '\
  257. <style>\
  258. .format_option{\
  259. margin-right:5px;\
  260. overflow:initial;\
  261. font-size:15px;\
  262. }\
  263. .format_option[type="text"]{\
  264. text-align:center;\
  265. padding-bottom: 2px;\
  266. padding-top: 2px;\
  267. }\
  268. .format_option:last-child{\
  269. margin-right:0;\
  270. }\
  271. fieldset{\
  272. margin-top:5px;\
  273. }\
  274. </style>\
  275. ');
  276. // Data control row
  277. Options.extend_tab('formatting', '\
  278. <button onclick="formatText.add_rule();">'+_('Add Rule')+'</button>\
  279. <button onclick="formatText.save_rules();">'+_('Save Rules')+'</button>\
  280. <button onclick="formatText.reset_rules(false);">'+_('Revert')+'</button>\
  281. <button onclick="formatText.reset_rules(true);">'+_('Reset to Default')+'</button>\
  282. ');
  283. // Descriptor row
  284. Options.extend_tab('formatting', '\
  285. <span class="format_option" style="margin-left:25px;">Name</span>\
  286. <span class="format_option" style="margin-left:45px;" title="Multi-line: Allow formatted area to contain linebreaks.">ML</span>\
  287. <span class="format_option" style="margin-left:0px;" title="Exclusive-line: Require formatted area to start after and end before a linebreak.">EL</span>\
  288. <span class="format_option" style="margin-left:25px;" title="Text injected at the start of a format area.">Prefix</span>\
  289. <span class="format_option" style="margin-left:60px;" title="Text injected at the end of a format area.">Suffix</span>\
  290. <span class="format_option" style="margin-left:40px;" title="Optional keybind value to allow keyboard shortcut access.">Key</span>\
  291. ');
  292. // Rule rows
  293. var rules = JSON.parse(localStorage.formatText_rules);
  294. for (var index in rules){
  295. if (!rules.hasOwnProperty(index)) continue;
  296. self.add_rule(rules[index], index);
  297. }
  298. }
  299. // setting for enabling formatting keybinds
  300. $(s1).on(e, function(e) {
  301. console.log('Keybind');
  302. if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') {
  303. localStorage.formatText_keybinds = 'true';
  304. if (window.Options && Options.get_tab('general')) e.target.checked = true;
  305. } else {
  306. localStorage.formatText_keybinds = 'false';
  307. if (window.Options && Options.get_tab('general')) e.target.checked = false;
  308. }
  309. });
  310. // setting for toolbar injection
  311. $(s2).on(e, function(e) {
  312. console.log('Toolbar');
  313. if (!localStorage.formatText_toolbar || localStorage.formatText_toolbar == 'false') {
  314. localStorage.formatText_toolbar = 'true';
  315. if (window.Options && Options.get_tab('general')) e.target.checked = true;
  316. formatText.build_toolbars();
  317. } else {
  318. localStorage.formatText_toolbar = 'false';
  319. if (window.Options && Options.get_tab('general')) e.target.checked = false;
  320. $('.format-text').remove();
  321. }
  322. });
  323. // make sure the tab settings are switch properly at loadup
  324. if (window.Options && Options.get_tab('general')) {
  325. if (localStorage.formatText_keybinds == 'true') $(s1)[0].checked = true;
  326. else $(s1)[0].checked = false;
  327. if (localStorage.formatText_toolbar == 'true') $(s2)[0].checked = true;
  328. else $(s2)[0].checked = false;
  329. }
  330. // Initial toolbar injection
  331. formatText.build_toolbars();
  332. //attach listener to <body> so it also works on quick-reply box
  333. $('body').on('keydown', '[name="body"]', function(e) {
  334. if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') return;
  335. var key = String.fromCharCode(e.which).toLowerCase();
  336. var rules = JSON.parse(localStorage.formatText_rules);
  337. for (var index in rules) {
  338. if (!rules.hasOwnProperty(index)) continue;
  339. if (key === rules[index].key && e.ctrlKey) {
  340. e.preventDefault();
  341. if (e.shiftKey) {
  342. formatText.wrap(e.target, 'textarea[name="body"]', index, true);
  343. } else {
  344. formatText.wrap(e.target, 'textarea[name="body"]', index, false);
  345. }
  346. }
  347. }
  348. });
  349. // Signal that comment-toolbar loading has completed.
  350. $(document).trigger('formatText');
  351. });
  352. return self;
  353. })(jQuery);
  354. }