summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorForkless <REDACTED@gmail.com>2014-11-01 22:28:16 -0500
committerczaks <marcin@6irc.net>2016-05-05 12:43:57 +0200
commit1663efcf9d0dd85a49526d21e761bf3ee4a675d8 (patch)
tree74368e3759659884776475fe7831e0055b4d7027 /js
parent1b0f5fd24cf363d88460b0483f5b3d26c763b8ed (diff)
Fix for update to comment toolbar (should work now)
Diffstat (limited to 'js')
-rw-r--r--js/comment-toolbar.js549
-rw-r--r--js/options/user-css.js2
-rw-r--r--js/options/user-js.js2
3 files changed, 344 insertions, 209 deletions
diff --git a/js/comment-toolbar.js b/js/comment-toolbar.js
index 7ae11a73..be58c1f6 100644
--- a/js/comment-toolbar.js
+++ b/js/comment-toolbar.js
@@ -8,253 +8,388 @@
* $config['additional_javascript'][] = 'js/comment-toolbar.js';
*/
if (active_page == 'thread' || active_page == 'index') {
- $(document).ready(function () {
- 'use strict';
- var formats = {
- bold: {
- displayText: 'B',
- altText: 'bold',
- styleCSS: 'font-weight: bold;',
- options: {
- prefix: "'''",
- suffix: "'''"
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'b'
+ var formatText = (function($){
+ "use strict";
+ var self = {};
+ self.rules = {
+ spoiler: {
+ text: 'Spoiler',
+ key: 's',
+ multiline: false,
+ exclusiveline: false,
+ prefix:'**',
+ suffix:'**'
},
italics: {
- displayText: 'i',
- altText: 'italics',
- styleCSS: 'font-style: italic;',
- options: {
- prefix: "''",
- suffix: "''"
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'i'
- },
- under: {
- displayText: 'U',
- altText: 'underline',
- styleCSS: 'text-decoration: underline;',
- options: {
- prefix: '__',
- suffix: '__'
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'u'
+ text: 'Italics',
+ key: 'i',
+ multiline: false,
+ exclusiveline: false,
+ prefix: "''",
+ suffix: "''"
+ },
+ bold: {
+ text: 'Bold',
+ key: 'b',
+ multiline: false,
+ exclusiveline: false,
+ prefix: "'''",
+ suffix: "'''"
},
- spoiler: {
- displayText: 'spoiler',
- altText: 'mark as spoiler',
- styleCSS: '',
- options: {
- prefix: '[spoiler]',
- suffix: '[/spoiler]'
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 's'
+ underline: {
+ text: 'Underline',
+ key: 'u',
+ multiline: false,
+ exclusiveline: false,
+ prefix:'__',
+ suffix:'__'
},
code: {
- displayText: 'code',
- altText: "code formatting",
- styleCSS: 'font-family: "Courier New", Courier, monospace;',
- options: {
- prefix: '[code]',
- suffix: '[/code]',
- multiline: true
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'd'
+ text: 'Code',
+ key: 'f',
+ multiline: true,
+ exclusiveline: false,
+ prefix: '[code]',
+ suffix: '[/code]'
},
strike: {
- displayText: 'strike',
- altText: 'strikethrough',
- styleCSS: 'text-decoration: line-through;',
- options: {
- prefix: '~~',
- suffix: '~~'
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- }
+ text: 'Strike',
+ key: 'd',
+ multiline:false,
+ exclusiveline:false,
+ prefix:'~~',
+ suffix:'~~'
},
heading: {
- displayText: 'heading',
- altText: 'redtext',
- styleCSS: 'color: #AF0A0F; font-weight: bold;',
- options: {
- prefix: '==',
- suffix: '==',
- exclusiveLine: true
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- }
+ text: 'Heading',
+ key: 'r',
+ multiline:false,
+ exclusiveline:true,
+ prefix:'==',
+ suffix:'=='
}
};
-
- var key, name, altText, ele;
- var strBuilder = [];
- var subStr = '';
- var styleRules = '';
-
- //not exactly mine
- var wrapSelection = function (box, options) {
- if (box == null) {
- return;
+
+ self.toolbar_wrap = function(node) {
+ if (!localStorage.formatText_enable || localStorage.formatText_enable == 'false') return;
+ 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) {
+ if (!localStorage.formatText_enable || localStorage.formatText_enable == 'false') return;
+ // 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);
+ }
}
- var prefix = options.prefix;
- var suffix = options.suffix;
- var multiline = options.multiline || false;
- var exclusiveLine = options.exclusiveLine || false;
-
+ target = res;
//record scroll top to restore it later.
- var scrollTop = box.scrollTop;
- var selectionStart = box.selectionStart;
- var selectionEnd = box.selectionEnd;
- var text = box.value;
- var beforeSelection = text.substring(0, selectionStart);
- var selectedText = text.substring(selectionStart, selectionEnd);
- var afterSelection = text.substring(selectionEnd);
+ var scrollTop = ref.scrollTop;
- var breakSpace = ["\r","\n"];
- var trailingSpace = "";
- var cursor = selectedText.length - 1;
+ //We will restore the selection later, so record the current selection
+ var selectionStart = ref.selectionStart;
+ var selectionEnd = ref.selectionEnd;
- //remove trailing space
- while (cursor > 0 && selectedText[cursor] === " ") {
- trailingSpace += " ";
- cursor--;
- }
- selectedText = selectedText.substring(0, cursor + 1);
-
- if (!multiline)
- selectedText = selectedText.replace(/(\r|\n|\r\n)/g, suffix +"$1"+ prefix);
-
- if (exclusiveLine) {
+ 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 = beforeSelection.length -1;
- while (cursor >= 0 && breakSpace.indexOf(beforeSelection.charAt(cursor)) == -1) {
+ cursor = before.length -1;
+ while (cursor >= 0 && breakSpace.indexOf(before.charAt(cursor)) == -1) {
cursor--;
}
- selectedText = beforeSelection.substring(cursor +1) + selectedText;
- beforeSelection = beforeSelection.substring(0, cursor +1);
+ 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 < afterSelection.length && breakSpace.indexOf(afterSelection.charAt(cursor)) == -1) {
+ while (cursor < after.length && breakSpace.indexOf(after.charAt(cursor)) == -1) {
cursor++;
}
- selectedText += afterSelection.substring(0, cursor);
- afterSelection = afterSelection.substring(cursor);
+ selected += after.substring(0, cursor);
+ after = after.substring(cursor);
}
-
- box.value = beforeSelection + prefix + selectedText + suffix + trailingSpace + afterSelection;
-
- box.selectionEnd = beforeSelection.length + prefix.length + selectedText.length;
+
+ // 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) {
- box.selectionStart = box.selectionEnd;
+ ref.selectionStart = ref.selectionEnd;
} else {
- box.selectionStart = beforeSelection.length + prefix.length;
+ ref.selectionStart = before.length + settings.prefix.length;
}
- box.scrollTop = scrollTop;
+ ref.scrollTop = scrollTop;
};
+
+ self.build_toolbars = function(){
+ if (!localStorage.formatText_enable || localStorage.formatText_enable == 'false') return;
+ 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;
- /* Generate the HTML for the toolbar
- */
- for (ele in formats) {
- if (formats.hasOwnProperty(ele) && formats[ele].displayText != null) {
- name = formats[ele].displayText;
- altText = formats[ele].altText || '';
- key = formats[ele].shortcutKey;
-
- //add tooltip text
- if (altText) {
- if (key) {
- altText += ' (ctrl+'+ key +')';
+ //add hint if key exists
+ if (rules[index].key) {
+ name += ' (CTRL + '+ rules[index].key.toUpperCase() +')';
}
- altText = 'title="'+ altText +'"';
+ options += '<option value="'+ index +'">'+ name +'</option>';
}
-
- subStr = '<a href="javascript:void(0)" '+ altText +' id="tf-'+ ele +'">'+ name +'</a>';
- strBuilder.push(subStr);
- } else {
- continue;
+ $('[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>');
}
- }
-
- $( 'textarea[name="body"]' ).before( '<div class="tf-toolbar"></div>' );
- $( '.tf-toolbar' ).html( strBuilder.join(' | ') );
-
- /* Sets the CSS style
- */
- styleRules = '\n/* generated by 8chan Formatting Tools */'+
- '\n.tf-toolbar {padding: 0px 5px 1px 5px;}'+
- '\n.tf-toolbar :link {text-decoration: none;}';
- for (ele in formats) {
- if (formats.hasOwnProperty(ele) && formats[ele].styleCSS) {
- styleRules += ' \n#tf-' + ele + ' {' + formats[ele].styleCSS + '}';
- }
+ };
+
+ self.add_rule = function(rule, index){
+ if (rule === undefined) rule = {
+ text: 'New Rule',
+ key: '',
+ multiline:false,
+ exclusiveline:false,
+ prefix:'',
+ suffix:''
}
- //add CSS rule to user's custom CSS if it exist
- if ($( '.user-css' ).length !== 0) {
- $( '.user-css' ).append( styleRules );
- } else {
- $( 'body' ).append( '<style>'+ styleRules +'\n</style>' );
+
+ // 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);
+ }
}
-
- /* Attach event listeners
- */
- $( 'body' ).on( 'keydown', 'textarea[name="body"]', {formats: formats}, function (e) {
- //shortcuts
- if (e.ctrlKey) {
- var ch = String.fromCharCode(e.which).toLowerCase();
- var box = e.target;
- var formats = e.data.formats;
- for (var ele in formats) {
- if (formats.hasOwnProperty(ele) && (ch === formats[ele].shortcutKey)) {
- formats[ele].edit(box, formats[ele].options);
- e.preventDefault();
- }
+ 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, '&quot;') +'\">\
+ <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, '&quot;') : '') +'\">\
+ <input type="text" name="suffix" class="format_option" size="8" value=\"'+ (rule.suffix ? rule.suffix.replace(/"/g, '&quot;') : '') +'\">\
+ <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);
+
+ // Add settings to Options panel general tab
+ if (window.Options && Options.get_tab('general')) {
+ var s1 = '#formatText_enable>input', s2 = '#formatText_keybinds>input', s3 = '#formatText_toolbar>input', e = 'change';
+ Options.extend_tab('general', '\
+ <fieldset>\
+ <legend>Formatting Options</legend>\
+ <label id="formatText_enable"><input type="checkbox" checked="checked" id="formatText_enable">' + _('Enable post formatting') + '</label>\
+ <label id="formatText_keybinds"><input type="checkbox" checked="checked" id="formatText_keybinds">' + _('Enable formatting keybinds') + '</label>\
+ <label id="formatText_toolbar"><input type="checkbox" checked="checked" id="formatText_toolbar">' + _('Show formatting toolbar') + '</label>\
+ </fieldset>\
+ ');
+ } else {
+ var s1 = '#formatText_enable', s2 = '#formatText_keybinds', s3 = '#formatText_toolbar', e = 'click';
+ $('hr:first').before('<div id="formatText_enable" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Enable post formatting') +'</a></div>');
+ $('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>');
+ }
+
+ // setting for enableing text formatting
+ $(s1).on(e, function(e) {
+ if (!localStorage.formatText_enable || localStorage.formatText_enable == 'false') {
+ localStorage.formatText_enable = 'true';
+ if (window.Options && Options.get_tab('general')) e.target.checked = true;
+ } else {
+ localStorage.formatText_enable = 'false';
+ if (window.Options && Options.get_tab('general')) e.target.checked = false;
+ }
});
- $( 'body' ).on( 'keydown', '#quick-reply textarea[name="body"]', {formats: formats}, function (e) {
- //close quick reply when esc is prssed
- if (e.which === 27) {
- $( '.close-btn' ).trigger( 'click' );
+
+ // setting for enableing formatting keybinds
+ $(s2).on(e, function(e) {
+ 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;
}
});
- $( 'body' ).on( 'click', '.tf-toolbar a[id]', {formats: formats}, function (e) {
- //toolbar buttons
- var formats = e.data.formats;
- var box = $(e.target).parent().next()[0];
-
- for (var ele in formats) {
- if (formats.hasOwnProperty(ele) && (e.target.id === 'tf-' + ele)) {
- formats[ele].edit(box, formats[ele].options);
- }
+
+ // setting for toolbar injection
+ $(s3).on(e, function(e) {
+ 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();
}
});
- // $( 'body' ).on( 'keydown', function (e) {
- // if (e.which === 67 &&
- // e.target.nodeName !== 'INPUT' && //The C, the whole C, and nothing but the C
- // e.target.nodeName !== 'TEXTAREA' &&
- // !(e.ctrlKey || e.altKey || e.shiftKey)) {
- // document.location.href = '//'+ document.location.host +'/'+ board_name +'/catalog.html';
- // }
- // });
+
+ // make sure the tab settings are switch properly at loadup
+ if (window.Options && Options.get_tab('general')) {
+ if (localStorage.formatText_enable == 'true') $(s1)[0].checked = true;
+ else $(s1)[0].checked = false;
+ if (localStorage.formatText_keybinds == 'true') $(s2)[0].checked = true;
+ else $(s2)[0].checked = false;
+ if (localStorage.formatText_toolbar == 'true') $(s2)[0].checked = true;
+ else $(s3)[0].checked = false;
+ }
+
+ // 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);
+ }
+ }
+
+ return self;
+ })(jQuery);
+
+ // run initial toolbar injection
+ formatText.build_toolbars();
+ //attach listeners to <body> so it also works on quick-reply box
+ $('body').on('keydown', '#body, #quick-reply #body', function(e) {
+ if (!localStorage.formatText_enable || localStorage.formatText_enable == 'false') return;
+ 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);
+ }
+ }
+ }
});
-}
+ $(document).trigger('formatText');
+} \ No newline at end of file
diff --git a/js/options/user-css.js b/js/options/user-css.js
index 840bcd1f..fed91ab3 100644
--- a/js/options/user-css.js
+++ b/js/options/user-css.js
@@ -17,7 +17,7 @@ var textarea = $("<textarea></textarea>").css({
"font-size": 12,
position: "absolute",
top: 35, bottom: 35,
- width: "calc(100% - 12px)", margin: 0, padding: 0, border: "1px solid black",
+ width: "calc(100% - 20px)", margin: 0, padding: "4px", border: "1px solid black",
left: 5, right: 5
}).appendTo(tab.content);
var submit = $("<input type='button' value='"+_("Update custom CSS")+"'>").css({
diff --git a/js/options/user-js.js b/js/options/user-js.js
index 0b7d140f..0ba9b1d0 100644
--- a/js/options/user-js.js
+++ b/js/options/user-js.js
@@ -17,7 +17,7 @@ var textarea = $("<textarea></textarea>").css({
"font-size": 12,
position: "absolute",
top: 35, bottom: 35,
- width: "calc(100% - 12px)", margin: 0, padding: 0, border: "1px solid black",
+ width: "calc(100% - 20px)", margin: 0, padding: "4px", border: "1px solid black",
left: 5, right: 5
}).appendTo(tab.content);
var submit = $("<input type='button' value='"+_("Update custom Javascript")+"'>").css({