|
- /*
- * comment-toolbar.js
- * - Adds a toolbar above the commenting area containing most of 8Chan's formatting options
- * - Press Esc to close quick-reply window when it's in focus
- *
- * Usage:
- * $config['additional_javascript'][] = 'js/jquery.min.js';
- * $config['additional_javascript'][] = 'js/comment-toolbar.js';
- */
- if (active_page == 'thread' || active_page == 'index') {
- var formatText = (function($){
- "use strict";
- var self = {};
- self.rules = {
- spoiler: {
- text: _('Spoiler'),
- key: 's',
- multiline: false,
- exclusiveline: false,
- prefix:'**',
- suffix:'**'
- },
- italics: {
- text: _('Italics'),
- key: 'i',
- multiline: false,
- exclusiveline: false,
- prefix: "''",
- suffix: "''"
- },
- bold: {
- text: _('Bold'),
- key: 'b',
- multiline: false,
- exclusiveline: false,
- prefix: "'''",
- suffix: "'''"
- },
- underline: {
- text: _('Underline'),
- key: 'u',
- multiline: false,
- exclusiveline: false,
- prefix:'__',
- suffix:'__'
- },
- code: {
- text: _('Code'),
- key: 'f',
- multiline: true,
- exclusiveline: false,
- prefix: '[code]',
- suffix: '[/code]'
- },
- strike: {
- text: _('Strike'),
- key: 'd',
- multiline:false,
- exclusiveline:false,
- prefix:'~~',
- suffix:'~~'
- },
- heading: {
- text: _('Heading'),
- key: 'r',
- multiline:false,
- exclusiveline:true,
- prefix:'==',
- suffix:'=='
- }
- };
-
- self.toolbar_wrap = function(node) {
- var parent = $(node).parents('form[name="post"]');
- self.wrap(parent.find('#body')[0],'textarea[name="body"]', parent.find('.format-text > select')[0].value, false);
- };
-
- self.wrap = function(ref, target, option, expandedwrap) {
- // clean and validate arguments
- if (ref == null) return;
- var settings = {multiline: false, exclusiveline: false, prefix:'', suffix: null};
- $.extend(settings,JSON.parse(localStorage.formatText_rules)[option]);
-
- // resolve targets into array of proper node elements
- // yea, this is overly verbose, oh well.
- var res = [];
- if (target instanceof Array) {
- for (var indexa in target) {
- if (target.hasOwnProperty(indexa)) {
- if (typeof target[indexa] == 'string') {
- var nodes = $(target[indexa]);
- for (var indexb in nodes) {
- if (indexa.hasOwnProperty(indexb)) res.push(nodes[indexb]);
- }
- } else {
- res.push(target[indexa]);
- }
- }
- }
- } else {
- if (typeof target == 'string') {
- var nodes = $(target);
- for (var index in nodes) {
- if (nodes.hasOwnProperty(index)) res.push(nodes[index]);
- }
- } else {
- res.push(target);
- }
- }
- target = res;
- //record scroll top to restore it later.
- var scrollTop = ref.scrollTop;
-
- //We will restore the selection later, so record the current selection
- var selectionStart = ref.selectionStart;
- var selectionEnd = ref.selectionEnd;
-
- var text = ref.value;
- var before = text.substring(0, selectionStart);
- var selected = text.substring(selectionStart, selectionEnd);
- var after = text.substring(selectionEnd);
- var whiteSpace = [" ","\t"];
- var breakSpace = ["\r","\n"];
- var cursor;
-
- // handles multiline selections on formatting that doesn't support spanning over multiple lines
- if (!settings.multiline) selected = selected.replace(/(\r|\n|\r\n)/g,settings.suffix +"$1"+ settings.prefix);
-
- // 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
- if (settings.exclusiveline || expandedwrap) {
- // buffer the begining of the selection until a linebreak
- cursor = before.length -1;
- while (cursor >= 0 && breakSpace.indexOf(before.charAt(cursor)) == -1) {
- cursor--;
- }
- selected = before.substring(cursor +1) + selected;
- before = before.substring(0, cursor +1);
-
- // buffer the end of the selection until a linebreak
- cursor = 0;
- while (cursor < after.length && breakSpace.indexOf(after.charAt(cursor)) == -1) {
- cursor++;
- }
- selected += after.substring(0, cursor);
- after = after.substring(cursor);
- }
-
- // set values
- var res = before + settings.prefix + selected + settings.suffix + after;
- $(target).val(res);
-
- // restore the selection area and scroll of the reference
- ref.selectionEnd = before.length + settings.prefix.length + selected.length;
- if (selectionStart === selectionEnd) {
- ref.selectionStart = ref.selectionEnd;
- } else {
- ref.selectionStart = before.length + settings.prefix.length;
- }
- ref.scrollTop = scrollTop;
- };
-
- self.build_toolbars = function(){
- if (localStorage.formatText_toolbar == 'true'){
- // remove existing toolbars
- if ($('.format-text').length > 0) $('.format-text').remove();
-
- // Place toolbar above each textarea input
- var name, options = '', rules = JSON.parse(localStorage.formatText_rules);
- for (var index in rules) {
- if (!rules.hasOwnProperty(index)) continue;
- name = rules[index].text;
-
- //add hint if key exists
- if (rules[index].key) {
- name += ' (CTRL + '+ rules[index].key.toUpperCase() +')';
- }
- options += '<option value="'+ index +'">'+ name +'</option>';
- }
- $('[name="body"]').before('<div class="format-text"><a href="javascript:;" onclick="formatText.toolbar_wrap(this);">Wrap</a><select>'+ options +'</select></div>');
- $('body').append('<style>#quick-reply .format-text>a{width:15%;display:inline-block;text-align:center;}#quick-reply .format-text>select{width:85%;};</style>');
- }
- };
-
- self.add_rule = function(rule, index){
- if (rule === undefined) rule = {
- text: 'New Rule',
- key: '',
- multiline:false,
- exclusiveline:false,
- prefix:'',
- suffix:''
- }
-
- // generate an id for the rule
- if (index === undefined) {
- var rules = JSON.parse(localStorage.formatText_rules);
- while (rules[index] || index === undefined) {
- index = ''
- index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
- index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
- index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
- }
- }
- if (window.Options && Options.get_tab('formatting')){
- var html = $('<div class="format_rule" name="'+ index +'"></div>').html('\
- <input type="text" name="text" class="format_option" size="10" value=\"'+ rule.text.replace(/"/g, '"') +'\">\
- <input type="checkbox" name="multiline" class="format_option" '+ (rule.multiline ? 'checked' : '') +'>\
- <input type="checkbox" name="exclusiveline" class="format_option" '+ (rule.exclusiveline ? 'checked' : '') +'>\
- <input type="text" name="prefix" class="format_option" size="8" value=\"'+ (rule.prefix ? rule.prefix.replace(/"/g, '"') : '') +'\">\
- <input type="text" name="suffix" class="format_option" size="8" value=\"'+ (rule.suffix ? rule.suffix.replace(/"/g, '"') : '') +'\">\
- <input type="text" name="key" class="format_option" size="2" maxlength="1" value=\"'+ rule.key +'\">\
- <input type="button" value="X" onclick="if(confirm(\'Do you wish to remove the '+ rule.text +' formatting rule?\'))$(this).parent().remove();">\
- ');
-
- if ($('.format_rule').length > 0) {
- $('.format_rule').last().after(html);
- } else {
- Options.extend_tab('formatting', html);
- }
- }
- };
-
- self.save_rules = function(){
- var rule, newrules = {}, rules = $('.format_rule');
- for (var index=0;rules[index];index++) {
- rule = $(rules[index]);
- newrules[rule.attr('name')] = {
- text: rule.find('[name="text"]').val(),
- key: rule.find('[name="key"]').val(),
- prefix: rule.find('[name="prefix"]').val(),
- suffix: rule.find('[name="suffix"]').val(),
- multiline: rule.find('[name="multiline"]').is(':checked'),
- exclusiveline: rule.find('[name="exclusiveline"]').is(':checked')
- };
- }
- localStorage.formatText_rules = JSON.stringify(newrules);
- self.build_toolbars();
- };
-
- self.reset_rules = function(to_default) {
- $('.format_rule').remove();
- var rules;
- if (to_default) rules = self.rules;
- else rules = JSON.parse(localStorage.formatText_rules);
- for (var index in rules){
- if (!rules.hasOwnProperty(index)) continue;
- self.add_rule(rules[index], index);
- }
- };
-
- // setup default rules for customizing
- if (!localStorage.formatText_rules) localStorage.formatText_rules = JSON.stringify(self.rules);
-
- // setup code to be ran when page is ready (work around for main.js compilation).
- $(document).ready(function(){
- // Add settings to Options panel general tab
- if (window.Options && Options.get_tab('general')) {
- var s1 = '#formatText_keybinds>input', s2 = '#formatText_toolbar>input', e = 'change';
- Options.extend_tab('general', '\
- <fieldset>\
- <legend>Formatting Options</legend>\
- <label id="formatText_keybinds"><input type="checkbox">' + _('Enable formatting keybinds') + '</label>\
- <label id="formatText_toolbar"><input type="checkbox">' + _('Show formatting toolbar') + '</label>\
- </fieldset>\
- ');
- } else {
- var s1 = '#formatText_keybinds', s2 = '#formatText_toolbar', e = 'click';
- $('hr:first').before('<div id="formatText_keybinds" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Enable formatting keybinds') +'</a></div>');
- $('hr:first').before('<div id="formatText_toolbar" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Show formatting toolbar') +'</a></div>');
- }
-
- // add the tab for customizing the format settings
- if (window.Options && !Options.get_tab('formatting')) {
- Options.add_tab('formatting', 'angle-right', _('Customize Formatting'));
- Options.extend_tab('formatting', '\
- <style>\
- .format_option{\
- margin-right:5px;\
- overflow:initial;\
- font-size:15px;\
- }\
- .format_option[type="text"]{\
- text-align:center;\
- padding-bottom: 2px;\
- padding-top: 2px;\
- }\
- .format_option:last-child{\
- margin-right:0;\
- }\
- fieldset{\
- margin-top:5px;\
- }\
- </style>\
- ');
-
- // Data control row
- Options.extend_tab('formatting', '\
- <button onclick="formatText.add_rule();">'+_('Add Rule')+'</button>\
- <button onclick="formatText.save_rules();">'+_('Save Rules')+'</button>\
- <button onclick="formatText.reset_rules(false);">'+_('Revert')+'</button>\
- <button onclick="formatText.reset_rules(true);">'+_('Reset to Default')+'</button>\
- ');
-
- // Descriptor row
- Options.extend_tab('formatting', '\
- <span class="format_option" style="margin-left:25px;">Name</span>\
- <span class="format_option" style="margin-left:45px;" title="Multi-line: Allow formatted area to contain linebreaks.">ML</span>\
- <span class="format_option" style="margin-left:0px;" title="Exclusive-line: Require formatted area to start after and end before a linebreak.">EL</span>\
- <span class="format_option" style="margin-left:25px;" title="Text injected at the start of a format area.">Prefix</span>\
- <span class="format_option" style="margin-left:60px;" title="Text injected at the end of a format area.">Suffix</span>\
- <span class="format_option" style="margin-left:40px;" title="Optional keybind value to allow keyboard shortcut access.">Key</span>\
- ');
-
- // Rule rows
- var rules = JSON.parse(localStorage.formatText_rules);
- for (var index in rules){
- if (!rules.hasOwnProperty(index)) continue;
- self.add_rule(rules[index], index);
- }
- }
-
- // setting for enabling formatting keybinds
- $(s1).on(e, function(e) {
- console.log('Keybind');
- if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') {
- localStorage.formatText_keybinds = 'true';
- if (window.Options && Options.get_tab('general')) e.target.checked = true;
- } else {
- localStorage.formatText_keybinds = 'false';
- if (window.Options && Options.get_tab('general')) e.target.checked = false;
- }
- });
-
- // setting for toolbar injection
- $(s2).on(e, function(e) {
- console.log('Toolbar');
- if (!localStorage.formatText_toolbar || localStorage.formatText_toolbar == 'false') {
- localStorage.formatText_toolbar = 'true';
- if (window.Options && Options.get_tab('general')) e.target.checked = true;
- formatText.build_toolbars();
- } else {
- localStorage.formatText_toolbar = 'false';
- if (window.Options && Options.get_tab('general')) e.target.checked = false;
- $('.format-text').remove();
- }
- });
-
- // make sure the tab settings are switch properly at loadup
- if (window.Options && Options.get_tab('general')) {
- if (localStorage.formatText_keybinds == 'true') $(s1)[0].checked = true;
- else $(s1)[0].checked = false;
- if (localStorage.formatText_toolbar == 'true') $(s2)[0].checked = true;
- else $(s2)[0].checked = false;
- }
-
- // Initial toolbar injection
- formatText.build_toolbars();
-
- //attach listener to <body> so it also works on quick-reply box
- $('body').on('keydown', '[name="body"]', function(e) {
- if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') return;
- var key = String.fromCharCode(e.which).toLowerCase();
- var rules = JSON.parse(localStorage.formatText_rules);
- for (var index in rules) {
- if (!rules.hasOwnProperty(index)) continue;
- if (key === rules[index].key && e.ctrlKey) {
- e.preventDefault();
- if (e.shiftKey) {
- formatText.wrap(e.target, 'textarea[name="body"]', index, true);
- } else {
- formatText.wrap(e.target, 'textarea[name="body"]', index, false);
- }
- }
- }
- });
-
- // Signal that comment-toolbar loading has completed.
- $(document).trigger('formatText');
- });
-
- return self;
- })(jQuery);
- }
|