Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
// LZString - compression library (readable version)
// Copyright (c) 2013 pieroxy <pieroxy@pieroxy.net>
// MIT License
var LZString = (function() {
var f = String.fromCharCode;
var keyStrBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var baseReverseDic = {};
function getBaseValue(alphabet, character) {
if (!baseReverseDic[alphabet]) {
baseReverseDic[alphabet] = {};
for (var i = 0; i < alphabet.length; i++) {
baseReverseDic[alphabet][alphabet.charAt(i)] = i;
}
}
return baseReverseDic[alphabet][character];
}
function _compress(uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed === null) return '';
var i, value,
context_dictionary = {},
context_dictionaryToCreate = {},
context_c = '',
context_wc = '',
context_w = '',
context_enlargeIn = 2,
context_dictSize = 3,
context_numBits = 2,
context_data = [],
context_data_val = 0,
context_data_position = 0,
ii;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
context_w = context_wc;
} else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
if (context_w !== '') {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
value = 2;
for (i = 0; i < context_numBits; i++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
while (true) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar - 1) {
context_data.push(getCharFromInt(context_data_val));
break;
} else {
context_data_position++;
}
}
return context_data.join('');
}
function _decompress(length, resetValue, getNextValue) {
var dictionary = [],
next,
enlargeIn = 4,
dictSize = 4,
numBits = 3,
entry = '',
result = [],
i,
w,
bits, resb, maxpower, power,
c,
data = { val: getNextValue(0), position: resetValue, index: 1 };
for (i = 0; i < 3; i += 1) {
dictionary[i] = i;
}
bits = 0;
maxpower = Math.pow(2, 2);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
switch (next = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2, 8);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 1:
bits = 0;
maxpower = Math.pow(2, 16);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 2:
return '';
}
dictionary[3] = c;
w = c;
result.push(c);
while (true) {
if (data.index > length) {
return '';
}
bits = 0;
maxpower = Math.pow(2, numBits);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
switch (c = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2, 8);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize - 1;
enlargeIn--;
break;
case 1:
bits = 0;
maxpower = Math.pow(2, 16);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize - 1;
enlargeIn--;
break;
case 2:
return result.join('');
}
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
if (dictionary[c]) {
entry = dictionary[c];
} else {
if (c === dictSize) {
entry = w + w.charAt(0);
} else {
return null;
}
}
result.push(entry);
dictionary[dictSize++] = w + entry.charAt(0);
enlargeIn--;
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
w = entry;
}
}
return {
compressToBase64: function(input) {
if (input === null) return '';
var res = _compress(input, 6, function(a) { return keyStrBase64.charAt(a); });
switch (res.length % 4) {
default:
case 0: return res;
case 1: return res + '===';
case 2: return res + '==';
case 3: return res + '=';
}
},
decompressFromBase64: function(input) {
if (input === null) return '';
if (input === '') return null;
return _decompress(input.length, 32, function(index) {
return getBaseValue(keyStrBase64, input.charAt(index));
});
}
};
}());
mw.hook('wikipage.content').add(function() {
var username = mw.config.get('wgUserName');
if (!username) return;
var isDark = document.body.classList.contains('theme-fandomdesktop-dark');
var STORAGE_KEY = 'dynamicstudio_' + username;
var POS_KEY = 'dynamicstudio_pos_' + username;
var editorColors = isDark ? {
bg: '#141414',
gutter: '#232323',
lineNum: '#72777d',
activeLine: 'rgba(221, 240, 255, 0.20)',
text: '#f8f8f8'
} : {
bg: '#f9f9f9',
gutter: '#ebebeb',
lineNum: '#72777d',
activeLine: '#dcdcdc',
text: '#080808'
};
if (!window.DynamicStudioCSSLoaded) {
window.DynamicStudioCSSLoaded = true;
mw.util.addCSS([
'.ds-container {',
' position: fixed;',
' z-index: 666666;',
' width: 500px;',
' background-color: var(--theme-page-background-color);',
' font-family: sans-serif;',
' box-shadow: 0 4px 16px rgba(0,0,0,0.4);',
' display: none;',
' flex-direction: column;',
' border-radius: 0 6px 6px 6px;',
' border: 2px solid var(--theme-page-background-color--secondary);',
' min-width: 300px;',
' min-height: 200px;',
'}',
'.ds-container.ds-open { display: flex; }',
'.ds-tabs {',
' display: flex;',
' flex-direction: row;',
' position: absolute;',
' top: -34px;',
' left: -2px;',
' align-items: flex-end;',
'}',
'.ds-tab {',
' padding: 6px 18px;',
' cursor: pointer;',
' border-radius: 6px 6px 0 0;',
' border: 2px solid var(--theme-page-background-color--secondary);',
' border-bottom: none;',
' font-size: 13px;',
' font-weight: bold;',
' user-select: none;',
' transition: filter 0.15s;',
' height: 26px;',
' box-sizing: border-box;',
' display: flex;',
' align-items: center;',
' position: relative;',
' filter: brightness(0.65);',
' margin-right: 3px;',
' margin-bottom: -2px;',
'}',
'.ds-tab.ds-active {',
' filter: brightness(1);',
' height: 34px;',
' margin-bottom: -2px;',
' z-index: 2;',
'}',
'.ds-tab:hover:not(.ds-active) { filter: brightness(0.8); }',
'.ds-tab-notes { background-color: #2e7d32; color: #ffffff; }',
'.ds-tab-css { background-color: #f9a825; color: #000000; }',
'.ds-tab-js { background-color: #c62828; color: #ffffff; }',
'.ds-header {',
' padding: 8px 12px;',
' font-weight: bold;',
' font-size: 14px;',
' display: flex;',
' justify-content: space-between;',
' align-items: center;',
' cursor: move;',
' transition: background-color 0.15s;',
' flex-shrink: 0;',
' user-select: none;',
' position: relative;',
' background-color: var(--fandom-image-empty-state-color);',
' color: ' + (isDark ? '#ffffff' : '#000000') + ';',
'}',
'.ds-header-buttons { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }',
'.ds-header-tabs {',
' display: none;',
' gap: 12px;',
' align-items: center;',
' font-size: 13px;',
' font-weight: normal;',
' color: ' + (isDark ? '#ffffff' : '#000000') + ';',
' margin-left: auto;',
' margin-right: 8px;',
'}',
'.ds-header-tab {',
' cursor: pointer;',
' opacity: 0.6;',
' transition: opacity 0.15s;',
' user-select: none;',
' color: ' + (isDark ? '#ffffff' : '#000000') + ';',
'}',
'.ds-header-tab:hover { opacity: 1; }',
'.ds-btn {',
' padding: 3px 10px;',
' border: none;',
' border-radius: 4px;',
' cursor: pointer;',
' font-size: 12px;',
' font-weight: bold;',
'}',
'.ds-btn-close, .ds-btn-io, .ds-btn-menu {',
' background-color: rgba(0,0,0,0.25);',
' color: #fff;',
' border: 1px solid rgba(255,255,255,0.3);',
'}',
'.ds-btn-close:hover, .ds-btn-io:hover, .ds-btn-menu:hover {',
' background-color: rgba(0,0,0,0.4);',
'}',
'.ds-btn-close { padding: 3px 8px; }',
'.ds-btn-menu { font-size: 14px; padding: 2px 8px; letter-spacing: 1px; }',
'.ds-status { font-size: 11px; opacity: 0.8; font-style: italic; }',
'.ds-dropdown {',
' position: absolute;',
' top: 100%;',
' right: 0;',
' z-index: 10;',
' background-color: var(--theme-page-background-color);',
' border: 1px solid var(--theme-page-background-color--secondary);',
' border-radius: 4px;',
' box-shadow: 0 4px 12px rgba(0,0,0,0.3);',
' min-width: 160px;',
' display: none;',
' flex-direction: column;',
' overflow: hidden;',
'}',
'.ds-dropdown.ds-dropdown-open { display: flex; }',
'.ds-dropdown-item {',
' padding: 8px 14px;',
' font-size: 12px;',
' cursor: pointer;',
' color: var(--theme-page-text-color);',
' white-space: nowrap;',
' user-select: none;',
' border: none;',
' background: none;',
' text-align: left;',
' width: 100%;',
' box-sizing: border-box;',
'}',
'.ds-dropdown-item:hover { background-color: var(--theme-page-background-color--secondary); }',
'.ds-dropdown-sep {',
' height: 1px;',
' background-color: var(--theme-page-background-color--secondary);',
' margin: 2px 0;',
'}',
'.ds-editor-area {',
' display: flex;',
' flex-direction: column;',
' overflow: hidden;',
' flex-shrink: 0;',
' width: 100%;',
' box-sizing: border-box;',
' min-width: 156px;',
'}',
'.ds-panel {',
' display: none;',
' flex: 1;',
' flex-direction: column;',
' overflow: hidden;',
' width: 100%;',
' box-sizing: border-box;',
' position: relative;',
'}',
'.ds-panel.ds-panel-active { display: flex; }',
'.ds-find-panel {',
' position: absolute;',
' top: 8px;',
' right: 12px;',
' z-index: 10;',
' background-color: var(--theme-page-background-color--secondary);',
' border: 1px solid ' + (isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.15)') + ';',
' border-radius: 6px;',
' padding: 8px 10px;',
' display: none;',
' flex-direction: column;',
' gap: 6px;',
' box-shadow: 0 4px 16px rgba(0,0,0,0.3);',
' min-width: 280px;',
'}',
'.ds-find-panel.ds-find-open { display: flex; }',
'.ds-find-row { display: flex; gap: 5px; align-items: center; }',
'.ds-find-input {',
' flex: 1;',
' padding: 3px 7px;',
' font-size: 11px;',
' font-family: monospace;',
' border: 1px solid ' + (isDark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.2)') + ';',
' border-radius: 3px;',
' background: var(--theme-page-background-color);',
' color: var(--theme-page-text-color);',
' outline: none;',
'}',
'.ds-find-input.ds-find-nomatch { border-color: rgba(198,40,40,0.6); }',
'.ds-find-btn {',
' padding: 3px 7px;',
' font-size: 11px;',
' border: 1px solid ' + (isDark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.2)') + ';',
' border-radius: 3px;',
' background: var(--theme-page-background-color);',
' color: var(--theme-page-text-color);',
' cursor: pointer;',
' white-space: nowrap;',
'}',
'.ds-find-btn:hover { opacity: 0.75; }',
'.ds-find-count { font-size: 10px; opacity: 0.5; white-space: nowrap; font-family: monospace; min-width: 40px; text-align: right; }',
'.ds-hl-match { background: rgba(255,200,0,0.35); border-radius: 2px; }',
'.ds-hl-match-current { background: rgba(255,140,0,0.55); border-radius: 2px; }',
'.ds-textarea {',
' flex: 1;',
' width: 100%;',
' box-sizing: border-box;',
' resize: none;',
' border: none;',
' outline: none;',
' padding: 10px;',
' font-size: 13px;',
' line-height: 21px;',
' background-color: var(--theme-page-background-color);',
' color: var(--theme-page-text-color);',
' font-family: sans-serif;',
' overflow-y: auto;',
'}',
'.ds-wikitext-wrap {',
' display: flex;',
' flex-direction: row;',
' flex: 1;',
' overflow: hidden;',
' position: relative;',
' min-height: 0;',
'}',
'.ds-wikitext-input-pane {',
' display: flex;',
' flex-direction: column;',
' overflow: hidden;',
' min-width: 0;',
' flex-shrink: 0;',
'}',
'.ds-wikitext-slider {',
' width: 6px;',
' flex-shrink: 0;',
' background-color: var(--theme-page-background-color--secondary);',
' cursor: col-resize;',
' transition: background-color 0.15s;',
' position: relative;',
' z-index: 1;',
'}',
'.ds-wikitext-slider:hover, .ds-wikitext-slider.ds-slider-dragging {',
' background-color: var(--theme-link-color, #3366cc);',
'}',
'.ds-wikitext-slider::after {',
' content: "";',
' position: absolute;',
' top: 50%;',
' left: 50%;',
' transform: translate(-50%, -50%);',
' width: 2px;',
' height: 24px;',
' border-left: 2px dotted rgba(128,128,128,0.5);',
' border-right: 2px dotted rgba(128,128,128,0.5);',
' border-left-width: 0;',
' border-right-width: 0;',
' background: repeating-linear-gradient(to bottom, rgba(128,128,128,0.5) 0px, rgba(128,128,128,0.5) 2px, transparent 2px, transparent 4px);',
' border-radius: 1px;',
'}',
'.ds-wikitext-output-pane {',
' flex: 1;',
' overflow-y: auto;',
' overflow-x: hidden;',
' min-width: 0;',
' padding: 24px 36px;',
' font-size: 13px;',
' background-color: var(--theme-page-background-color);',
' color: var(--theme-page-text-color);',
' box-sizing: border-box;',
' border-left: 1px solid var(--theme-page-background-color--secondary);',
' overflow-wrap: break-word;',
' word-break: break-word;',
'}',
'.ds-wikitext-output-pane .mw-parser-output ul:not(.toc *) {',
' list-style: disc;',
' margin: 6px 0 18px 36px;',
'}',
'.ds-wikitext-output-pane .mw-parser-output ol:not(.toc *) {',
' list-style: decimal;',
' margin: 6px 0 18px 36px;',
'}',
'.ds-wikitext-output-pane .mw-parser-output ul ul:not(.toc *),',
'.ds-wikitext-output-pane .mw-parser-output ol ul:not(.toc *),',
'.ds-wikitext-output-pane .mw-parser-output ul ol:not(.toc *),',
'.ds-wikitext-output-pane .mw-parser-output ol ol:not(.toc *) {',
' margin-bottom: 4px;',
'}',
'.ds-wikitext-output-pane .toc ul,',
'.ds-wikitext-output-pane .toc ol {',
' list-style: none;',
' margin: 0;',
' padding: 0;',
'}',
'.ds-wikitext-output-pane.ds-output-loading {',
' opacity: 0.5;',
'}',
'.ds-wikitext-output-empty {',
' color: var(--theme-page-text-color);',
' opacity: 0.4;',
' font-style: italic;',
' font-size: 12px;',
' margin-top: 4px;',
'}',
'.ds-code-wrap {',
' flex: 1;',
' display: flex;',
' overflow-x: hidden;',
' overflow-y: auto;',
' position: relative;',
' background-color: ' + editorColors.bg + ';',
' width: 100%;',
' box-sizing: border-box;',
'}',
'.ds-gutter {',
' width: 44px;',
' min-width: 44px;',
' background-color: ' + editorColors.gutter + ';',
' flex-shrink: 0;',
' z-index: 1;',
' height: fit-content;',
' min-height: 100%;',
'}',
'.ds-line-numbers {',
' font-family: monospace;',
' font-size: 13px;',
' line-height: 21px;',
' color: ' + editorColors.lineNum + ';',
' user-select: none;',
' padding-top: 10px;',
'}',
'.ds-ln-row {',
' display: flex;',
' align-items: flex-start;',
' justify-content: space-between;',
' box-sizing: border-box;',
' width: 44px;',
' position: relative;',
' overflow: hidden;',
'}',
'.ds-ln-row.ds-ln-error { }',
'.ds-ln-row.ds-ln-current { background-color: rgba(255, 255, 255, 0.031); }',
'.ds-ln-icon {',
' flex-shrink: 0;',
' width: 18px;',
' height: 21px;',
' display: none;',
' align-items: center;',
' justify-content: center;',
'}',
'.ds-ln-row.ds-ln-error .ds-ln-icon { display: flex; }',
'.ds-ln-num {',
' flex: 1;',
' text-align: right;',
' padding-right: 2px;',
' line-height: 21px;',
' white-space: nowrap;',
'}',
'.ds-ln-fold {',
' flex-shrink: 0;',
' width: 14px;',
' height: 21px;',
' display: flex;',
' align-items: center;',
' justify-content: center;',
' cursor: pointer;',
' font-size: 9px;',
' opacity: 0.25;',
' user-select: none;',
' color: inherit;',
' line-height: 21px;',
'}',
'.ds-ln-row:hover .ds-ln-fold { opacity: 0.7; }',
'.ds-code-inner {',
' flex: 1;',
' position: relative;',
' min-width: 0;',
' width: 100%;',
' overflow-y: auto;',
' overflow-x: hidden;',
'}',
'.ds-measure {',
' position: absolute;',
' visibility: hidden;',
' pointer-events: none;',
' font-family: monospace;',
' font-size: 13px;',
' line-height: 21px;',
' padding: 0 10px;',
' box-sizing: border-box;',
' white-space: pre-wrap;',
' word-wrap: break-word;',
' top: 0; left: 0; right: 0;',
'}',
'.ds-code-highlight {',
' position: absolute;',
' top: 0; left: 0; right: 0;',
' width: 100%;',
' pointer-events: none;',
' padding: 10px 10px 0 10px;',
' box-sizing: border-box;',
'}',
'.ds-hl-line {',
' display: block;',
' width: 100%;',
' border-radius: 2px;',
'}',
'.ds-hl-line.ds-hl-current { background: ' + editorColors.activeLine + '; }',
'.ds-code-textarea {',
' position: absolute;',
' top: 0; left: 0; right: 0; bottom: 0;',
' width: 100%;',
' height: 100%;',
' border: none;',
' outline: none;',
' padding: 10px;',
' font-family: monospace;',
' font-size: 13px;',
' line-height: 21px;',
' resize: none;',
' tab-size: 4;',
' box-sizing: border-box;',
' background: transparent;',
' color: ' + editorColors.text + ';',
' white-space: pre-wrap;',
' word-wrap: break-word;',
' overflow: hidden;',
'}',
'.ds-corner {',
' position: absolute;',
' width: 14px;',
' height: 14px;',
' z-index: 10;',
'}',
'.ds-corner-nw { top: -2px; left: -2px; cursor: nw-resize; }',
'.ds-corner-ne { top: -2px; right: -2px; cursor: ne-resize; }',
'.ds-corner-sw { bottom: -2px; left: -2px; cursor: sw-resize; }',
'.ds-corner-se { bottom: -2px; right: -2px; cursor: se-resize; }',
'.ds-io-overlay {',
' position: fixed;',
' top: 0; left: 0; right: 0; bottom: 0;',
' background: rgba(0,0,0,0.55);',
' z-index: 777777776;',
' display: none;',
' align-items: center;',
' justify-content: center;',
'}',
'.ds-io-overlay.ds-io-open { display: flex; }',
'.ds-io-modal {',
' background-color: var(--theme-page-background-color--secondary);',
' color: var(--theme-page-text-color);',
' border-radius: 8px;',
' padding: 24px;',
' width: 480px;',
' max-width: 92vw;',
' font-size: 13px;',
' display: flex;',
' flex-direction: column;',
' gap: 14px;',
' box-shadow: 0 8px 32px rgba(0,0,0,0.4);',
'}',
'.ds-io-modal-title {',
' font-size: 15px;',
' font-weight: bold;',
' margin-bottom: 2px;',
'}',
'.ds-io-modal-desc {',
' font-size: 12px;',
' opacity: 0.65;',
' line-height: 18px;',
' margin-top: -6px;',
'}',
'.ds-io-section {',
' display: flex;',
' flex-direction: column;',
' gap: 6px;',
'}',
'.ds-io-section-label {',
' font-size: 11px;',
' font-weight: bold;',
' text-transform: uppercase;',
' letter-spacing: 0.06em;',
' opacity: 0.5;',
'}',
'.ds-io-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }',
'.ds-io-label { font-size: 12px; color: var(--theme-page-text-color); opacity: 0.7; min-width: 60px; }',
'.ds-io-btn {',
' padding: 3px 8px;',
' border: 1px solid var(--theme-page-background-color--secondary);',
' border-radius: 4px;',
' cursor: pointer;',
' font-size: 11px;',
' font-weight: bold;',
' background-color: var(--theme-page-background-color--secondary);',
' color: var(--theme-page-text-color);',
'}',
'.ds-io-btn:hover { opacity: 0.8; }',
'.ds-io-input {',
' flex: 1;',
' padding: 4px 8px;',
' border: 1px solid rgba(128,128,128,0.25);',
' border-radius: 4px;',
' font-size: 11px;',
' background-color: var(--theme-page-background-color);',
' color: var(--theme-page-text-color);',
' font-family: monospace;',
' outline: none;',
' width: 100%;',
' box-sizing: border-box;',
'}',
'.ds-io-close {',
' align-self: flex-start;',
' background: none;',
' border: none;',
' color: var(--theme-page-text-color);',
' font-size: 18px;',
' cursor: pointer;',
' opacity: 0.5;',
' padding: 0;',
' line-height: 1;',
'}',
'.ds-io-close:hover { opacity: 1; }',
'.ds-status-bar {',
' display: flex;',
' align-items: center;',
' height: 22px;',
' font-size: 11px;',
' font-family: monospace;',
' background-color: var(--theme-page-background-color--secondary);',
' color: ' + (isDark ? '#ffffff' : '#000000') + ';',
' border-top: 1px solid ' + (isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.10)') + ';',
' flex-shrink: 0;',
' user-select: none;',
' overflow: hidden;',
'}',
'.ds-status-left {',
' display: flex;',
' align-items: center;',
' gap: 0;',
' flex-shrink: 0;',
' height: 100%;',
' border-right: 1px solid ' + (isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.10)') + ';',
'}',
'.ds-status-counter {',
' display: flex;',
' align-items: center;',
' gap: 3px;',
' padding: 0 8px;',
' height: 100%;',
' cursor: pointer;',
' opacity: 0.45;',
' font-size: 11px;',
' border-right: 1px solid ' + (isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.08)') + ';',
'}',
'.ds-status-counter:last-child { border-right: none; }',
'.ds-status-counter:hover { opacity: 0.85; background: rgba(128,128,128,0.1); }',
'.ds-status-counter.ds-status-has-errors { opacity: 1; }',
'.ds-status-counter-icon { font-size: 12px; line-height: 1; }',
'.ds-status-msg {',
' flex: 1;',
' padding: 0 10px;',
' overflow: hidden;',
' white-space: nowrap;',
' text-overflow: ellipsis;',
' opacity: 0;',
' font-size: 11px;',
' transition: opacity 0.15s;',
'}',
'.ds-status-msg.ds-status-msg-visible { opacity: 1; }',
'.ds-status-pos {',
' flex-shrink: 0;',
' padding: 0 10px;',
' opacity: 0.4;',
' white-space: nowrap;',
' border-left: 1px solid ' + (isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.10)') + ';',
' height: 100%;',
' display: flex;',
' align-items: center;',
'}',
'#ds-toolbar-link { cursor: pointer; color: inherit !important; }',
'.ds-warning-overlay {',
' position: fixed;',
' top: 0; left: 0; right: 0; bottom: 0;',
' background: rgba(0,0,0,0.6);',
' z-index: 777777777;',
' display: flex;',
' align-items: center;',
' justify-content: center;',
'}',
'.ds-warning-box {',
' background-color: var(--theme-page-background-color--secondary);',
' color: var(--theme-page-text-color);',
' border-radius: 8px;',
' padding: 24px;',
' max-width: 420px;',
' width: 90%;',
' font-size: 13px;',
' line-height: 21px;',
' display: flex;',
' flex-direction: column;',
' gap: 12px;',
'}',
'.ds-warning-title {',
' font-size: 22px;',
' font-weight: bold;',
' color: var(--theme-alert-color--secondary);',
' text-align: center;',
' text-transform: uppercase;',
' letter-spacing: 1px;',
'}',
'.ds-warning-body { font-size: 13px; line-height: 21px; }',
'.ds-warning-actions { display: flex; justify-content: space-between; margin-top: 8px; }',
'.ds-warning-confirm {',
' padding: 6px 14px;',
' background: transparent;',
' color: var(--theme-success-color--secondary);',
' border: none;',
' cursor: not-allowed;',
' font-weight: bold;',
' font-size: 13px;',
' opacity: 0.4;',
'}',
'.ds-warning-confirm.ds-ready { cursor: pointer; opacity: 1; }',
'.ds-warning-back {',
' padding: 6px 14px;',
' background: transparent;',
' color: var(--theme-alert-color--secondary);',
' border: none;',
' cursor: pointer;',
' font-weight: bold;',
' font-size: 13px;',
'}'
].join('\n'));
}
var dsConfigEl = document.createElement('div');
dsConfigEl.className = 'dynamic-studio';
dsConfigEl.style.display = 'none';
document.body.appendChild(dsConfigEl);
function getDSProp(prop) {
return getComputedStyle(dsConfigEl).getPropertyValue(prop).trim().replace(/['"]/g, '');
}
var KNOWN_PRESETS = ['Default', 'Folders', 'Notepad', 'Minimal'];
function getPreset() {
var val = getDSProp('--ds-style-preset');
return KNOWN_PRESETS.indexOf(val) !== -1 ? val : 'Default';
}
function getFolderTabColor(tabId) {
var defaults = { notes: '#2e7d32', css: '#f9a825', js: '#c62828' };
var val = getDSProp('--ds-tab-color-' + tabId);
return val || defaults[tabId];
}
function getFolderTabText(tabId) {
var col = getFolderTabColor(tabId);
var val = getDSProp('--ds-tab-text-' + tabId);
if (val) return val;
return tabId === 'css' ? '#000000' : '#ffffff';
}
function getNotepadProp(prop, fallback) {
var val = getDSProp('--ds-notepad-' + prop);
return val || fallback;
}
function savePos() {
try {
localStorage.setItem(POS_KEY, JSON.stringify({
left: container.style.left, top: container.style.top,
width: container.offsetWidth, height: editorArea.offsetHeight
}));
} catch(e) {}
}
function loadPos() {
try { var r = localStorage.getItem(POS_KEY); return r ? JSON.parse(r) : null; }
catch(e) { return null; }
}
var storageWarningShown = false;
var storageWarnEls = [];
function saveLocal() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
wikitext: notesTextarea ? notesTextarea.value : '',
css: cssEditor ? cssEditor.getValue() : '',
js: jsEditor ? jsEditor.getValue() : ''
}));
} catch(e) {
if (e.name === 'QuotaExceededError' || e.code === 22) {
storageWarnEls.forEach(function(el) { el.style.display = ''; });
if (!storageWarningShown) {
storageWarningShown = true;
showWarning('storage', function() {}, null);
}
}
}
}
function loadLocal() {
try { var r = localStorage.getItem(STORAGE_KEY); return r ? JSON.parse(r) : null; }
catch(e) { return null; }
}
function exportData(type) {
var data = {}, prefix = '';
if (type === 'all') { prefix = 'DSALLV1'; data = { wikitext: notesTextarea.value, css: cssEditor.getValue(), js: jsEditor.getValue() }; }
if (type === 'notes') { prefix = 'DSWKTV1'; data = { wikitext: notesTextarea.value }; }
if (type === 'css') { prefix = 'DSCSSV1'; data = { css: cssEditor.getValue() }; }
if (type === 'js') { prefix = 'DSJSV1'; data = { js: jsEditor.getValue() }; }
return prefix + ':' + LZString.compressToBase64(JSON.stringify(data));
}
function importData(str, callback) {
str = str.trim();
var ci = str.indexOf(':');
if (ci === -1) return false;
var prefix = str.substring(0, ci), data;
try {
var decompressed = LZString.decompressFromBase64(str.substring(ci + 1));
if (!decompressed) {
decompressed = decodeURIComponent(escape(atob(str.substring(ci + 1))));
}
data = JSON.parse(decompressed);
} catch(e) { return false; }
var hasJS = data && typeof data.js === 'string' && data.js.trim().length > 0;
if (hasJS) {
showWarning('import', function() { applyImport(prefix, data); callback(true); }, function() { callback(false); });
return 'pending';
}
applyImport(prefix, data);
return true;
}
function applyImport(prefix, data) {
if (prefix === 'DSALLV1') {
if (data.wikitext !== undefined) setNotesValue(data.wikitext);
if (data.css !== undefined) cssEditor.setValue(data.css);
if (data.js !== undefined) jsEditor.setValue(data.js);
} else if (prefix === 'DSWKTV1' || prefix === 'DSNTSV1') {
var wt = data.wikitext !== undefined ? data.wikitext : data.notes;
if (wt !== undefined) setNotesValue(wt);
} else if (prefix === 'DSCSSV1') {
if (data.css !== undefined) cssEditor.setValue(data.css);
} else if (prefix === 'DSJSV1') {
if (data.js !== undefined) jsEditor.setValue(data.js);
}
saveLocal();
}
function showWarning(type, onConfirm, onBack) {
var overlay = document.createElement('div');
overlay.className = 'ds-warning-overlay';
var box = document.createElement('div');
box.className = 'ds-warning-box';
var titleEl = document.createElement('div');
titleEl.className = 'ds-warning-title';
var body = document.createElement('div');
body.className = 'ds-warning-body';
if (type === 'import') {
titleEl.textContent = 'Importing JS';
body.innerHTML = 'This string includes JavaScript that could be potentially harmful.<br><br>Imported JavaScript runs with the same permissions as your account.<br>Malicious code could compromise your account, manipulate site content, or perform other harmful actions without your knowledge.<br><br>Review the code before importing.<br><small>(<b>do not</b> import scripts you do not understand, always attempt to get a trustworthy middleman to read code if you do not understand it, if a user is pressuring you to run javascript in a certain amount of time, do not trust them. There is no need to rush.)</small>';
}
if (type === 'run') {
titleEl.textContent = 'Running JS';
body.innerHTML = 'You are about to execute JavaScript.<br>This will be executed on the page you\'re currently on.<br><br>Every effect caused by JavaScript can be undone by reloading the page.<br><br>Are you sure you want to execute this JavaScript right now?';
}
if (type === 'storage') {
titleEl.textContent = 'Storage Limit Reached';
body.innerHTML = 'DynamicStudio has hit the browser\'s localStorage limit and <b>your changes are no longer being saved automatically.</b><br><br>To avoid losing your work:<br><br><b>Export your data</b> using the ⇅ button in the toolbar before closing this tab.<br><br>You can clear space by exporting and then clearing your browser\'s site data for Fandom, or by removing other stored data.';
confirmBtn.disabled = false;
confirmBtn.textContent = 'I understand';
confirmBtn.classList.add('ds-ready');
clearInterval(timer);
backBtn.style.display = 'none';
}
var actions = document.createElement('div');
actions.className = 'ds-warning-actions';
var confirmBtn = document.createElement('button');
confirmBtn.className = 'ds-warning-confirm';
confirmBtn.disabled = true;
var backBtn = document.createElement('button');
backBtn.className = 'ds-warning-back';
backBtn.textContent = 'Back';
var remaining = 10;
confirmBtn.textContent = 'Confirm (' + remaining + ')';
var timer = setInterval(function() {
remaining--;
if (remaining > 0) { confirmBtn.textContent = 'Confirm (' + remaining + ')'; }
else { clearInterval(timer); confirmBtn.disabled = false; confirmBtn.textContent = 'Confirm'; confirmBtn.classList.add('ds-ready'); }
}, 1000);
backBtn.onclick = function() { clearInterval(timer); document.body.removeChild(overlay); if (onBack) onBack(); };
confirmBtn.onclick = function() { if (confirmBtn.disabled) return; clearInterval(timer); document.body.removeChild(overlay); onConfirm(); };
actions.appendChild(confirmBtn);
actions.appendChild(backBtn);
box.appendChild(titleEl);
box.appendChild(body);
box.appendChild(actions);
overlay.appendChild(box);
document.body.appendChild(overlay);
}
function getCSSErrorLines(code) {
var errorLines = [], lines = code.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line || line.indexOf('{') !== -1 || line.indexOf('}') !== -1 ||
line.indexOf('/*') !== -1 || line.indexOf('*/') !== -1 || line.indexOf('@') === 0) continue;
if (line.indexOf(':') !== -1) {
var ci = line.indexOf(':');
var prop = line.substring(0, ci).trim();
var val = line.substring(ci + 1).trim().replace(/;$/, '').trim();
if (prop && val && !CSS.supports(prop, val)) {
errorLines.push({ line: i, message: 'Unknown property or value: ' + prop + ': ' + val });
}
} else if (line !== '' && line !== ';') {
errorLines.push({ line: i, message: 'Unexpected token: ' + line });
}
}
return errorLines;
}
function getJSErrorLine(code) {
try { new Function(code); } catch(e) { return extractJSErrorLine(e); }
try {
new Function('"use strict";\nconst alert=function(){};\nconst confirm=function(){};\nconst prompt=function(){};\n' + code);
} catch(e) { return extractJSErrorLine(e); }
return { line: -1, message: '' };
}
function extractJSErrorLine(e) {
var line = 0;
if (typeof e.lineNumber === 'number') line = e.lineNumber - 1;
else if (typeof e.line === 'number') line = e.line - 1;
else if (e.stack) {
var m = e.stack.match(/<anonymous>:(\d+):\d+/);
if (m) line = parseInt(m[1], 10) - 2;
}
return { line: line, message: e.message || '' };
}
function cssBeautify(code) {
var result = '', depth = 0, indent = ' ';
var i = 0, len = code.length, ch, buf = '';
function pad() {
var s = '';
for (var d = 0; d < depth; d++) s += indent;
return s;
}
while (i < len) {
ch = code[i];
if (ch === '/' && code[i+1] === '*') {
var end = code.indexOf('*/', i + 2);
if (end === -1) end = len - 2;
result += pad() + code.slice(i, end + 2).trim() + '\n';
i = end + 2;
continue;
}
if (ch === '{') { result += ' {\n'; depth++; buf = ''; i++; continue; }
if (ch === '}') {
if (buf.trim()) result += pad() + buf.trim() + '\n';
buf = '';
depth = Math.max(0, depth - 1);
result += pad() + '}\n\n';
i++;
continue;
}
if (ch === ';') { result += pad() + buf.trim() + ';\n'; buf = ''; i++; continue; }
if (ch === '\n' || ch === '\r') { i++; continue; }
buf += ch;
i++;
}
if (buf.trim()) result += buf.trim() + '\n';
return result.replace(/\n{3,}/g, '\n\n').trim();
}
function cssMinify(code) {
return code
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\s+/g, ' ')
.replace(/\s*{\s*/g, '{')
.replace(/\s*}\s*/g, '}')
.replace(/\s*:\s*/g, ':')
.replace(/\s*;\s*/g, ';')
.replace(/\s*,\s*/g, ',')
.trim();
}
function jsBeautify(code) {
var lines = code.split('\n');
var result = [], depth = 0, indent = ' ';
function pad(d) {
var s = '';
for (var i = 0; i < d; i++) s += indent;
return s;
}
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line) { result.push(''); continue; }
var closes = (line.match(/^[}\])]/) || []).length;
if (closes) depth = Math.max(0, depth - closes);
line = line
.replace(/([^=!<>])=([^=])/g, '$1 = $2')
.replace(/([^<>!])===|==([^>])/g, function(m) { return ' ' + m.trim() + ' '; })
.replace(/,(?!\s)/g, ', ')
.replace(/\s+/g, ' ')
.trim();
result.push(pad(depth) + line);
var open = (line.match(/[{[(]/g) || []).length;
var close = (line.match(/[}\])]/g) || []).length;
depth = Math.max(0, depth + open - close);
}
return result.join('\n');
}
function jsMinify(code) {
code = code.replace(/\/\*[\s\S]*?\*\//g, '');
code = code.replace(/([^:])\/\/.*$/mg, '$1');
code = code.replace(/\s+/g, ' ');
code = code.replace(/\s*([{};,=+\-*/<>!&|?:])\s*/g, '$1');
return code.trim();
}
function addImportant(text) {
return text.replace(/:\s*([^;{}]+?)(\s*!important)?\s*;/g, function(m, val) {
return ': ' + val.trim() + ' !important;';
});
}
function removeImportant(text) {
return text.replace(/\s*!important/gi, '');
}
function buildCodeEditor(type) {
var panel = document.createElement('div');
panel.className = 'ds-panel';
panel.dataset.panel = type;
var wrap = document.createElement('div');
wrap.className = 'ds-code-wrap';
var gutter = document.createElement('div');
gutter.className = 'ds-gutter';
var lineNumbers = document.createElement('div');
lineNumbers.className = 'ds-line-numbers';
gutter.appendChild(lineNumbers);
var inner = document.createElement('div');
inner.className = 'ds-code-inner';
var measure = document.createElement('div');
measure.className = 'ds-measure';
inner.appendChild(measure);
var highlight = document.createElement('div');
highlight.className = 'ds-code-highlight';
var textarea = document.createElement('textarea');
textarea.className = 'ds-code-textarea';
textarea.spellcheck = false;
inner.appendChild(highlight);
inner.appendChild(textarea);
var statusBar = document.createElement('div');
statusBar.className = 'ds-status-bar';
var statusLeft = document.createElement('div');
statusLeft.className = 'ds-status-left';
var errorCounter = document.createElement('span');
errorCounter.className = 'ds-status-counter';
errorCounter.innerHTML = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==" width="14" height="14" style="vertical-align:middle;margin-right:3px;"> <span class="ds-status-counter-num">0</span>';
errorCounter.title = 'Errors - click to cycle';
statusLeft.appendChild(errorCounter);
var statusMsg = document.createElement('span');
statusMsg.className = 'ds-status-msg';
var statusRight = document.createElement('div');
statusRight.style.cssText = 'display:flex; align-items:center; gap:0; margin-left:auto; flex-shrink:0;';
var statusStorageWarn = document.createElement('span');
statusStorageWarn.style.cssText = 'font-size:10px; color:#ff6b6b; font-family:monospace; padding:0 8px; display:none; border-right:1px solid ' + (isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.10)') + ';';
statusStorageWarn.textContent = '! unsaved';
statusStorageWarn.title = 'Storage limit reached - changes are not being saved!';
storageWarnEls.push(statusStorageWarn);
var statusKB = document.createElement('span');
statusKB.style.cssText = 'font-size:10px; font-family:monospace; opacity:0.35; padding:0 8px; border-right:1px solid ' + (isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.10)') + ';';
var statusPos = document.createElement('span');
statusPos.className = 'ds-status-pos';
statusPos.textContent = '1:1';
statusRight.appendChild(statusStorageWarn);
statusRight.appendChild(statusKB);
statusRight.appendChild(statusPos);
statusBar.appendChild(statusLeft);
statusBar.appendChild(statusMsg);
statusBar.appendChild(statusRight);
wrap.appendChild(gutter);
wrap.appendChild(inner);
panel.appendChild(wrap);
panel.appendChild(statusBar);
var currentCSSErrors = [];
var currentJSError = -1;
var foldedLines = {};
var unfoldedValue = '';
function applyFolds() {
var lines = unfoldedValue.split('\n');
var hiddenLines = {};
var openerCloser = {};
for (var openerIdx in foldedLines) {
if (!foldedLines[openerIdx]) continue;
var oi = parseInt(openerIdx, 10);
var range = getFoldRange(lines, oi);
if (range) {
for (var li = range.start; li <= range.end; li++) hiddenLines[li] = true;
openerCloser[oi] = lines[range.end].trim();
}
}
var visibleLines = [];
for (var i = 0; i < lines.length; i++) {
if (hiddenLines[i]) continue;
if (openerCloser[i] !== undefined) {
var openerTrimmed = lines[i].trimRight();
visibleLines.push(openerTrimmed + ' \u2026 ' + openerCloser[i]);
} else {
visibleLines.push(lines[i]);
}
}
textarea.value = visibleLines.join('\n');
refresh();
}
var errorCycleIdx = 0;
function getAllErrors() {
if (type === 'css') return currentCSSErrors.map(function(e) { return e.line; });
return currentJSError >= 0 ? [currentJSError] : [];
}
errorCounter.addEventListener('click', function() {
var errs = getAllErrors();
if (errs.length === 0) return;
errorCycleIdx = (errorCycleIdx + 1) % errs.length;
var spans = highlight.querySelectorAll('.ds-hl-line');
if (spans[errs[errorCycleIdx]]) spans[errs[errorCycleIdx]].scrollIntoView({ block: 'center' });
});
function updateErrorBar(cssErrors, jsError, jsMsg) {
currentCSSErrors = cssErrors;
currentJSError = jsError;
errorCycleIdx = 0;
var errs = type === 'css' ? cssErrors : (jsError >= 0 ? [{ line: jsError }] : []);
var numEl = errorCounter.querySelector('.ds-status-counter-num');
numEl.textContent = errs.length;
if (errs.length > 0) {
errorCounter.classList.add('ds-status-has-errors');
errorCounter.title = errs.length + ' error' + (errs.length > 1 ? 's' : '') + ' - click to cycle';
} else {
errorCounter.classList.remove('ds-status-has-errors');
errorCounter.title = 'No errors';
}
updateStatusMsg(jsMsg);
}
function updateStatusMsg(jsMsg) {
var curLine = textarea.value.substring(0, textarea.selectionStart).split('\n').length - 1;
var msg = '';
if (type === 'css') {
var match = currentCSSErrors.filter(function(e) { return e.line === curLine; })[0];
if (match) msg = match.message;
} else {
if (currentJSError === curLine) msg = jsMsg || ('Syntax error on line ' + (curLine + 1));
}
statusMsg.textContent = msg;
if (msg) statusMsg.classList.add('ds-status-msg-visible');
else statusMsg.classList.remove('ds-status-msg-visible');
}
function updateStatusPos() {
var before = textarea.value.substring(0, textarea.selectionStart);
var lines = before.split('\n');
statusPos.textContent = lines.length + ':' + (lines[lines.length - 1].length + 1);
updateStatusMsg(currentJSMsg);
try {
var val = localStorage.getItem(STORAGE_KEY);
statusKB.textContent = val ? (val.length / 1024).toFixed(1) + ' KB' : '0 KB';
} catch(e) {}
}
var currentJSMsg = '';
textarea.addEventListener('click', updateStatusPos);
textarea.addEventListener('keyup', updateStatusPos);
function getFoldRange(lines, openerIdx) {
var openerLine = lines[openerIdx];
var OPENERS = ['{', '[', '('];
var CLOSERS = ['}', ']', ')'];
var openChar = null, closeChar = null;
for (var k = openerLine.length - 1; k >= 0; k--) {
var ci = OPENERS.indexOf(openerLine[k]);
if (ci !== -1) { openChar = OPENERS[ci]; closeChar = CLOSERS[ci]; break; }
}
if (!openChar) return null;
var depth = 0;
for (var i = openerIdx; i < lines.length; i++) {
for (var c = 0; c < lines[i].length; c++) {
if (lines[i][c] === openChar) depth++;
else if (lines[i][c] === closeChar) {
depth--;
if (depth === 0) {
if (i === openerIdx) return null;
return { start: openerIdx + 1, end: i };
}
}
}
}
return null;
}
var lineHeightCache = {};
var lastContainerWidth = 0;
var cachedLineH = null;
function measureLineHeight(text, lineIndex, isCurrentLine) {
var containerWidth = inner.offsetWidth;
if (containerWidth !== lastContainerWidth) {
lineHeightCache = {};
cachedLineH = null;
lastContainerWidth = containerWidth;
}
if (!cachedLineH) {
var wasHiddenInit = !panel.classList.contains('ds-panel-active');
if (wasHiddenInit) {
inner.style.visibility = 'hidden';
inner.style.position = 'fixed';
inner.style.display = 'block';
}
measure.style.width = containerWidth + 'px';
measure.textContent = '\u200b';
cachedLineH = measure.offsetHeight || 21;
if (wasHiddenInit) {
inner.style.visibility = '';
inner.style.position = '';
inner.style.display = '';
}
}
if (!isCurrentLine && lineHeightCache[lineIndex] !== undefined) {
return lineHeightCache[lineIndex];
}
var wasHidden = !panel.classList.contains('ds-panel-active');
if (wasHidden) {
inner.style.visibility = 'hidden';
inner.style.position = 'fixed';
inner.style.display = 'block';
}
measure.style.width = containerWidth + 'px';
measure.textContent = text + '\u200b';
var h = measure.offsetHeight || cachedLineH || 21;
if (wasHidden) {
inner.style.visibility = '';
inner.style.position = '';
inner.style.display = '';
}
lineHeightCache[lineIndex] = h;
return h;
}
function refresh() {
var code = textarea.value;
var lines = code.split('\n');
var total = lines.length;
var currentLine = code.substring(0, textarea.selectionStart).split('\n').length - 1;
var cssErrors = type === 'css' ? getCSSErrorLines(code) : [];
var jsErrorObj = type === 'js' ? getJSErrorLine(code) : { line: -1, message: '' };
var jsError = jsErrorObj.line;
var jsErrorMsg = jsErrorObj.message;
var heights = new Array(total);
for (var j = 0; j < total; j++) {
heights[j] = measureLineHeight(lines[j], j, j === currentLine);
}
var totalHeight = 0;
for (var j = 0; j < total; j++) totalHeight += heights[j];
totalHeight += 20;
inner.style.minHeight = totalHeight + 'px';
gutter.style.height = totalHeight + 'px';
textarea.style.minHeight = totalHeight + 'px';
var scrollTop = inner.scrollTop || 0;
var viewHeight = inner.clientHeight || 600;
var OVERSCAN = 5;
var cumH = 10;
var rowTops = new Array(total);
var visStart = 0, visEnd = total - 1;
for (var j = 0; j < total; j++) {
rowTops[j] = cumH;
cumH += heights[j];
}
for (var j = 0; j < total; j++) {
if (rowTops[j] + heights[j] >= scrollTop) { visStart = Math.max(0, j - OVERSCAN); break; }
}
for (var j = visStart; j < total; j++) {
if (rowTops[j] > scrollTop + viewHeight) { visEnd = Math.min(total - 1, j + OVERSCAN); break; }
}
var topSpacerH = rowTops[visStart] - 10;
var botSpacerH = totalHeight - 20 - rowTops[visEnd] - heights[visEnd];
highlight.innerHTML = '';
lineNumbers.innerHTML = '';
if (topSpacerH > 0) {
var hlTop = document.createElement('span');
hlTop.className = 'ds-hl-line';
hlTop.style.height = topSpacerH + 'px';
highlight.appendChild(hlTop);
var lnTop = document.createElement('div');
lnTop.className = 'ds-ln-row';
lnTop.style.height = topSpacerH + 'px';
lineNumbers.appendChild(lnTop);
}
var unfoldedLines = unfoldedValue ? unfoldedValue.split('\n') : lines;
var hiddenSet = {};
for (var oi in foldedLines) {
if (!foldedLines[oi]) continue;
var r = getFoldRange(unfoldedLines, parseInt(oi, 10));
if (r) { for (var li = r.start; li <= r.end; li++) hiddenSet[li] = true; }
}
for (var j = visStart; j <= visEnd; j++) {
var h = heights[j];
var cssErrObj = type === 'css' ? cssErrors.filter(function(e) { return e.line === j; })[0] : null;
var isError = cssErrObj !== null && cssErrObj !== undefined ? true : j === jsError;
var span = document.createElement('span');
span.className = 'ds-hl-line';
span.style.height = h + 'px';
if (j === currentLine) {
var lineText = lines[j];
var colInLine = textarea.value.substring(0, textarea.selectionStart).split('\n')[currentLine].length;
measure.style.width = inner.offsetWidth + 'px';
measure.textContent = lineText.substring(0, colInLine) + '\u200b';
var rowOffset = Math.min(measure.offsetHeight - cachedLineH, h - cachedLineH);
rowOffset = Math.max(0, rowOffset);
var color = editorColors.activeLine;
if (h === cachedLineH) {
span.style.background = color;
} else {
var pctTop = (rowOffset / h * 100).toFixed(2);
var pctBot = ((rowOffset + cachedLineH) / h * 100).toFixed(2);
span.style.background = 'linear-gradient(to bottom, transparent ' + pctTop + '%, ' + color + ' ' + pctTop + '%, ' + color + ' ' + pctBot + '%, transparent ' + pctBot + '%)';
}
}
highlight.appendChild(span);
var ln = document.createElement('div');
ln.className = 'ds-ln-row';
ln.style.height = h + 'px';
if (j === currentLine) ln.classList.add('ds-ln-current');
var icon = document.createElement('img');
icon.className = 'ds-ln-icon';
icon.width = 18; icon.height = 21;
icon.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==';
if (isError) {
ln.classList.add('ds-ln-error');
var errMsg = type === 'css'
? (cssErrObj ? cssErrObj.message : 'CSS error on line ' + (j + 1))
: (jsErrorMsg || ('Syntax error on line ' + (j + 1)));
icon.title = errMsg;
ln.title = errMsg;
}
ln.appendChild(icon);
var lineNumSpan = document.createElement('span');
lineNumSpan.className = 'ds-ln-num';
lineNumSpan.textContent = j + 1;
ln.appendChild(lineNumSpan);
var unfoldedIdx = (function(visibleIdx) {
var vi = 0;
for (var ui = 0; ui < unfoldedLines.length; ui++) {
if (hiddenSet[ui]) continue;
if (vi === visibleIdx) return ui;
vi++;
}
return visibleIdx;
})(j);
var foldRange = getFoldRange(unfoldedLines, unfoldedIdx);
if (foldRange) {
var foldBtn = document.createElement('span');
foldBtn.className = 'ds-ln-fold';
foldBtn.textContent = foldedLines[unfoldedIdx] ? '\u25b6' : '\u25bc';
foldBtn.title = foldedLines[unfoldedIdx] ? 'Unfold block' : 'Fold block';
(function(uIdx) {
foldBtn.addEventListener('click', function(e) {
e.stopPropagation();
foldedLines[uIdx] = !foldedLines[uIdx];
applyFolds();
});
})(unfoldedIdx);
ln.appendChild(foldBtn);
}
lineNumbers.appendChild(ln);
}
if (botSpacerH > 0) {
var hlBot = document.createElement('span');
hlBot.className = 'ds-hl-line';
hlBot.style.height = botSpacerH + 'px';
highlight.appendChild(hlBot);
var lnBot = document.createElement('div');
lnBot.className = 'ds-ln-row';
lnBot.style.height = botSpacerH + 'px';
lineNumbers.appendChild(lnBot);
}
currentJSMsg = jsErrorMsg;
updateErrorBar(cssErrors, jsError, jsErrorMsg);
try {
var val = localStorage.getItem(STORAGE_KEY);
statusKB.textContent = val ? (val.length / 1024).toFixed(1) + ' KB' : '0 KB';
} catch(e) {}
saveLocal();
}
var history = [];
var historyIdx = -1;
var historyDebounce = null;
var HISTORY_LIMIT = 200;
function pushHistory(value, selStart, selEnd) {
history = history.slice(0, historyIdx + 1);
if (history.length > 0 && history[historyIdx].value === value) return;
history.push({ value: value, selStart: selStart || 0, selEnd: selEnd || 0 });
if (history.length > HISTORY_LIMIT) history.shift();
historyIdx = history.length - 1;
}
function restoreHistory(entry) {
unfoldedValue = entry.value;
foldedLines = {};
textarea.value = entry.value;
textarea.selectionStart = entry.selStart;
textarea.selectionEnd = entry.selEnd;
refresh();
}
pushHistory('', 0, 0);
textarea.addEventListener('input', function() {
unfoldedValue = textarea.value;
foldedLines = {};
clearTimeout(historyDebounce);
historyDebounce = setTimeout(function() {
pushHistory(textarea.value, textarea.selectionStart, textarea.selectionEnd);
}, 400);
refresh();
});
textarea.addEventListener('keyup', refresh);
textarea.addEventListener('click', refresh);
textarea.addEventListener('blur', refresh);
inner.addEventListener('scroll', refresh);
textarea.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
e.preventDefault();
clearTimeout(historyDebounce);
if (textarea.value !== (history[historyIdx] && history[historyIdx].value)) {
pushHistory(textarea.value, textarea.selectionStart, textarea.selectionEnd);
}
if (historyIdx > 0) {
historyIdx--;
restoreHistory(history[historyIdx]);
}
return;
}
if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
e.preventDefault();
if (historyIdx < history.length - 1) {
historyIdx++;
restoreHistory(history[historyIdx]);
}
return;
}
if (e.key === 'Tab') {
e.preventDefault();
var s = textarea.selectionStart, end = textarea.selectionEnd, val = textarea.value;
if (!e.shiftKey) {
if (s === end) {
textarea.value = val.substring(0, s) + ' ' + val.substring(end);
textarea.selectionStart = textarea.selectionEnd = s + 4;
} else {
var ls = val.lastIndexOf('\n', s - 1) + 1;
var sel = val.substring(ls, end);
var ind = sel.replace(/^/mg, ' ');
textarea.value = val.substring(0, ls) + ind + val.substring(end);
textarea.selectionStart = ls;
textarea.selectionEnd = ls + ind.length;
}
} else {
var ls = val.lastIndexOf('\n', s - 1) + 1;
var sel = val.substring(ls, end);
var ded = sel.replace(/^ {1,4}/mg, '');
textarea.value = val.substring(0, ls) + ded + val.substring(end);
textarea.selectionStart = ls;
textarea.selectionEnd = ls + ded.length;
}
unfoldedValue = textarea.value;
foldedLines = {};
pushHistory(textarea.value, textarea.selectionStart, textarea.selectionEnd);
refresh();
return;
}
var PAIRS = { '{': '}', '(': ')', '[': ']', "'": "'" };
var CLOSERS = new Set(['}', ')', ']', "'"]);
if (CLOSERS.has(e.key)) {
var s = textarea.selectionStart, val = textarea.value;
if (val[s] === e.key) {
e.preventDefault();
textarea.selectionStart = textarea.selectionEnd = s + 1;
refresh();
return;
}
}
if (PAIRS[e.key] && textarea.selectionStart === textarea.selectionEnd) {
e.preventDefault();
var s = textarea.selectionStart, val = textarea.value;
var closer = PAIRS[e.key];
textarea.value = val.substring(0, s) + e.key + closer + val.substring(s);
textarea.selectionStart = textarea.selectionEnd = s + 1;
unfoldedValue = textarea.value;
foldedLines = {};
pushHistory(textarea.value, textarea.selectionStart, textarea.selectionEnd);
refresh();
return;
}
if (e.key === 'Backspace' && textarea.selectionStart === textarea.selectionEnd) {
var s = textarea.selectionStart, val = textarea.value;
var prev = val[s - 1], next = val[s];
if (prev && next && PAIRS[prev] === next) {
e.preventDefault();
textarea.value = val.substring(0, s - 1) + val.substring(s + 1);
textarea.selectionStart = textarea.selectionEnd = s - 1;
unfoldedValue = textarea.value;
foldedLines = {};
pushHistory(textarea.value, textarea.selectionStart, textarea.selectionEnd);
refresh();
return;
}
}
if (e.key === 'Enter' && textarea.selectionStart === textarea.selectionEnd) {
var s = textarea.selectionStart, val = textarea.value;
var lineStart = val.lastIndexOf('\n', s - 1) + 1;
var currentLine = val.substring(lineStart, s);
var indentMatch = currentLine.match(/^(\s*)/);
var indent = indentMatch ? indentMatch[1] : '';
var prevChar = val[s - 1];
var nextChar = val[s];
if (prevChar && nextChar && PAIRS[prevChar] === nextChar) {
e.preventDefault();
var newIndent = indent + ' ';
var insertion = '\n' + newIndent + '\n' + indent;
textarea.value = val.substring(0, s) + insertion + val.substring(s);
textarea.selectionStart = textarea.selectionEnd = s + newIndent.length + 1;
unfoldedValue = textarea.value;
foldedLines = {};
pushHistory(textarea.value, textarea.selectionStart, textarea.selectionEnd);
refresh();
return;
}
if (indent) {
e.preventDefault();
textarea.value = val.substring(0, s) + '\n' + indent + val.substring(s);
textarea.selectionStart = textarea.selectionEnd = s + 1 + indent.length;
unfoldedValue = textarea.value;
foldedLines = {};
pushHistory(textarea.value, textarea.selectionStart, textarea.selectionEnd);
refresh();
return;
}
}
});
var findPanel = document.createElement('div');
findPanel.className = 'ds-find-panel';
var findRow = document.createElement('div');
findRow.className = 'ds-find-row';
var findInput = document.createElement('input');
findInput.className = 'ds-find-input';
findInput.placeholder = 'Find...';
findInput.type = 'text';
var findCount = document.createElement('span');
findCount.className = 'ds-find-count';
var findPrev = document.createElement('button');
findPrev.className = 'ds-find-btn';
findPrev.textContent = '\u2191';
findPrev.title = 'Previous match';
var findNext = document.createElement('button');
findNext.className = 'ds-find-btn';
findNext.textContent = '\u2193';
findNext.title = 'Next match';
var findClose = document.createElement('button');
findClose.className = 'ds-find-btn';
findClose.textContent = '\u2715';
findClose.title = 'Close';
findRow.appendChild(findInput);
findRow.appendChild(findCount);
findRow.appendChild(findPrev);
findRow.appendChild(findNext);
findRow.appendChild(findClose);
var replaceRow = document.createElement('div');
replaceRow.className = 'ds-find-row';
var replaceInput = document.createElement('input');
replaceInput.className = 'ds-find-input';
replaceInput.placeholder = 'Replace...';
replaceInput.type = 'text';
var replaceOne = document.createElement('button');
replaceOne.className = 'ds-find-btn';
replaceOne.textContent = 'Replace';
var replaceAll = document.createElement('button');
replaceAll.className = 'ds-find-btn';
replaceAll.textContent = 'All';
replaceRow.appendChild(replaceInput);
replaceRow.appendChild(replaceOne);
replaceRow.appendChild(replaceAll);
findPanel.appendChild(findRow);
findPanel.appendChild(replaceRow);
panel.appendChild(findPanel);
findPanel.addEventListener('mousedown', function(e) {
if (e.target !== findInput && e.target !== replaceInput) {
e.preventDefault();
}
});
var findMatches = [];
var findMatchIdx = 0;
function findAll() {
var term = findInput.value;
findMatches = [];
findMatchIdx = 0;
if (!term) { findCount.textContent = ''; findInput.classList.remove('ds-find-nomatch'); refresh(); return; }
var val = textarea.value;
var idx = 0;
var lower = val.toLowerCase();
var termLower = term.toLowerCase();
while ((idx = lower.indexOf(termLower, idx)) !== -1) {
findMatches.push(idx);
idx += term.length;
}
findInput.classList.toggle('ds-find-nomatch', findMatches.length === 0);
findCount.textContent = findMatches.length ? (findMatchIdx + 1) + '/' + findMatches.length : '0/0';
highlightMatches();
}
function highlightMatches() {
var term = findInput.value;
var spans = highlight.querySelectorAll('.ds-hl-line');
if (!term || findMatches.length === 0) { spans.forEach(function(s) { s.style.background = s.classList.contains('ds-hl-current') ? editorColors.activeLine : ''; }); return; }
var val = textarea.value;
var lines = val.split('\n');
var lineStart = 0;
for (var li = 0; li < lines.length; li++) {
var lineEnd = lineStart + lines[li].length;
var lineMatches = findMatches.filter(function(m) { return m >= lineStart && m < lineEnd; });
if (lineMatches.length && spans[li]) {
var gradParts = [];
lineMatches.forEach(function(m, mi) {
var isCurrent = findMatches[findMatchIdx] === m;
var color = isCurrent ? 'rgba(255,140,0,0.55)' : 'rgba(255,200,0,0.35)';
gradParts.push(color);
});
spans[li].style.background = gradParts[0];
} else if (spans[li]) {
spans[li].style.background = spans[li].classList.contains('ds-hl-current') ? editorColors.activeLine : '';
}
lineStart = lineEnd + 1;
}
}
function jumpToMatch(idx) {
if (!findMatches.length) return;
findMatchIdx = ((idx % findMatches.length) + findMatches.length) % findMatches.length;
findCount.textContent = (findMatchIdx + 1) + '/' + findMatches.length;
var pos = findMatches[findMatchIdx];
textarea.focus();
textarea.selectionStart = pos;
textarea.selectionEnd = pos + findInput.value.length;
var before = textarea.value.substring(0, pos);
var lineNum = before.split('\n').length - 1;
var spans = highlight.querySelectorAll('.ds-hl-line');
if (spans[lineNum]) spans[lineNum].scrollIntoView({ block: 'center' });
highlightMatches();
}
findInput.addEventListener('input', findAll);
findPrev.addEventListener('click', function() { jumpToMatch(findMatchIdx - 1); });
findNext.addEventListener('click', function() { jumpToMatch(findMatchIdx + 1); });
findInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.shiftKey ? jumpToMatch(findMatchIdx - 1) : jumpToMatch(findMatchIdx + 1); }
if (e.key === 'Escape') { findPanel.classList.remove('ds-find-open'); }
});
replaceInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') { findPanel.classList.remove('ds-find-open'); }
});
replaceOne.addEventListener('click', function() {
if (!findMatches.length) return;
var pos = findMatches[findMatchIdx];
var term = findInput.value;
var rep = replaceInput.value;
var val = textarea.value;
var newVal = val.substring(0, pos) + rep + val.substring(pos + term.length);
textarea.value = newVal;
unfoldedValue = newVal;
pushHistory(newVal, pos + rep.length, pos + rep.length);
findAll();
jumpToMatch(findMatchIdx);
refresh();
});
replaceAll.addEventListener('click', function() {
if (!findMatches.length) return;
var term = findInput.value;
var rep = replaceInput.value;
var newVal = textarea.value.split(new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi')).join(rep);
textarea.value = newVal;
unfoldedValue = newVal;
pushHistory(newVal, 0, 0);
findAll();
refresh();
setStatus('Replaced all!');
});
findClose.addEventListener('click', function() {
findPanel.classList.remove('ds-find-open');
findInput.value = '';
replaceInput.value = '';
findMatches = [];
findCount.textContent = '';
refresh();
});
textarea.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'h') {
e.preventDefault();
findPanel.classList.toggle('ds-find-open');
if (findPanel.classList.contains('ds-find-open')) {
findInput.focus();
findInput.select();
}
}
});
return {
panel: panel, textarea: textarea,
getValue: function() { return unfoldedValue || textarea.value; },
setValue: function(v) {
var cur = unfoldedValue || textarea.value;
if (cur && cur !== v) pushHistory(cur, textarea.selectionStart, textarea.selectionEnd);
unfoldedValue = v; foldedLines = {}; textarea.value = v;
pushHistory(v, 0, 0);
refresh();
},
refresh: refresh
};
}
var container = document.createElement('div');
container.className = 'ds-container';
['nw','ne','sw','se'].forEach(function(dir) {
var corner = document.createElement('div');
corner.className = 'ds-corner ds-corner-' + dir;
corner.dataset.dir = dir;
container.appendChild(corner);
});
var tabsEl = document.createElement('div');
tabsEl.className = 'ds-tabs';
[{id:'notes',label:'Wikitext',cls:'ds-tab-notes'},
{id:'css', label:'CSS', cls:'ds-tab-css' },
{id:'js', label:'JS', cls:'ds-tab-js' }
].forEach(function(t) {
var tab = document.createElement('div');
tab.className = 'ds-tab ' + t.cls;
tab.textContent = t.label;
tab.dataset.tab = t.id;
tabsEl.appendChild(tab);
});
container.appendChild(tabsEl);
var header = document.createElement('div');
header.className = 'ds-header';
var titleEl = document.createElement('span');
titleEl.textContent = 'DynamicStudio';
titleEl.style.minWidth = '0';
titleEl.style.overflow = 'hidden';
titleEl.style.textOverflow = 'ellipsis';
titleEl.style.whiteSpace = 'nowrap';
titleEl.style.flex = '1';
var headerTabsEl = document.createElement('div');
headerTabsEl.className = 'ds-header-tabs';
var headerButtons = document.createElement('div');
headerButtons.className = 'ds-header-buttons';
var statusEl = document.createElement('span');
statusEl.className = 'ds-status';
var ioBtn = document.createElement('button');
ioBtn.className = 'ds-btn ds-btn-io';
ioBtn.textContent = '⇅';
ioBtn.title = 'Import / Export';
var menuWrap = document.createElement('div');
menuWrap.style.position = 'relative';
var menuBtn = document.createElement('button');
menuBtn.className = 'ds-btn ds-btn-menu';
menuBtn.textContent = '≡';
menuBtn.title = 'Code actions';
var dropdown = document.createElement('div');
dropdown.className = 'ds-dropdown';
var dropdownLocked = false;
var dropdownHoverTimeout = null;
function openDropdown() { dropdown.classList.add('ds-dropdown-open'); }
function closeDropdown() { dropdown.classList.remove('ds-dropdown-open'); dropdownLocked = false; }
menuBtn.addEventListener('click', function(e) {
e.stopPropagation();
if (dropdownLocked) { closeDropdown(); }
else { openDropdown(); dropdownLocked = true; }
});
menuWrap.addEventListener('mouseenter', function() { clearTimeout(dropdownHoverTimeout); openDropdown(); });
menuWrap.addEventListener('mouseleave', function() { if (!dropdownLocked) { dropdownHoverTimeout = setTimeout(closeDropdown, 300); } });
document.addEventListener('click', function() { closeDropdown(); });
dropdown.addEventListener('click', function(e) { e.stopPropagation(); });
menuWrap.appendChild(menuBtn);
menuWrap.appendChild(dropdown);
var closeBtn = document.createElement('button');
closeBtn.className = 'ds-btn ds-btn-close';
closeBtn.textContent = '✕';
headerButtons.appendChild(statusEl);
headerButtons.appendChild(ioBtn);
headerButtons.appendChild(menuWrap);
headerButtons.appendChild(closeBtn);
header.appendChild(titleEl);
header.appendChild(headerTabsEl);
header.appendChild(headerButtons);
container.appendChild(header);
var editorArea = document.createElement('div');
editorArea.className = 'ds-editor-area';
var notesPanel = document.createElement('div');
notesPanel.className = 'ds-panel ds-panel-active';
notesPanel.dataset.panel = 'notes';
var wikitextWrap = document.createElement('div');
wikitextWrap.className = 'ds-wikitext-wrap';
var inputPane = document.createElement('div');
inputPane.className = 'ds-wikitext-input-pane';
var notesTextarea = document.createElement('textarea');
notesTextarea.className = 'ds-textarea';
notesTextarea.placeholder = 'Write your wikitext here...';
inputPane.appendChild(notesTextarea);
var notesFindPanel = document.createElement('div');
notesFindPanel.className = 'ds-find-panel';
var notesFindRow = document.createElement('div');
notesFindRow.className = 'ds-find-row';
var notesFindInput = document.createElement('input');
notesFindInput.className = 'ds-find-input'; notesFindInput.placeholder = 'Find...'; notesFindInput.type = 'text';
var notesFindCount = document.createElement('span');
notesFindCount.className = 'ds-find-count';
var notesFindPrev = document.createElement('button');
notesFindPrev.className = 'ds-find-btn'; notesFindPrev.textContent = '\u2191'; notesFindPrev.title = 'Previous';
var notesFindNext = document.createElement('button');
notesFindNext.className = 'ds-find-btn'; notesFindNext.textContent = '\u2193'; notesFindNext.title = 'Next';
var notesFindClose = document.createElement('button');
notesFindClose.className = 'ds-find-btn'; notesFindClose.textContent = '\u2715';
notesFindRow.appendChild(notesFindInput); notesFindRow.appendChild(notesFindCount);
notesFindRow.appendChild(notesFindPrev); notesFindRow.appendChild(notesFindNext); notesFindRow.appendChild(notesFindClose);
var notesReplaceRow = document.createElement('div');
notesReplaceRow.className = 'ds-find-row';
var notesReplaceInput = document.createElement('input');
notesReplaceInput.className = 'ds-find-input'; notesReplaceInput.placeholder = 'Replace...'; notesReplaceInput.type = 'text';
var notesReplaceOne = document.createElement('button');
notesReplaceOne.className = 'ds-find-btn'; notesReplaceOne.textContent = 'Replace';
var notesReplaceAll = document.createElement('button');
notesReplaceAll.className = 'ds-find-btn'; notesReplaceAll.textContent = 'All';
notesReplaceRow.appendChild(notesReplaceInput); notesReplaceRow.appendChild(notesReplaceOne); notesReplaceRow.appendChild(notesReplaceAll);
notesFindPanel.appendChild(notesFindRow); notesFindPanel.appendChild(notesReplaceRow);
notesPanel.appendChild(notesFindPanel);
notesFindPanel.addEventListener('mousedown', function(e) {
if (e.target !== notesFindInput && e.target !== notesReplaceInput) {
e.preventDefault();
}
});
var notesFindMatches = [], notesFindIdx = 0;
function notesFindAll() {
var term = notesFindInput.value;
notesFindMatches = []; notesFindIdx = 0;
if (!term) { notesFindCount.textContent = ''; notesFindInput.classList.remove('ds-find-nomatch'); return; }
var val = notesTextarea.value, lower = val.toLowerCase(), termLower = term.toLowerCase(), idx = 0;
while ((idx = lower.indexOf(termLower, idx)) !== -1) { notesFindMatches.push(idx); idx += term.length; }
notesFindInput.classList.toggle('ds-find-nomatch', notesFindMatches.length === 0);
notesFindCount.textContent = notesFindMatches.length ? (notesFindIdx + 1) + '/' + notesFindMatches.length : '0/0';
}
function notesJumpToMatch(idx) {
if (!notesFindMatches.length) return;
notesFindIdx = ((idx % notesFindMatches.length) + notesFindMatches.length) % notesFindMatches.length;
notesFindCount.textContent = (notesFindIdx + 1) + '/' + notesFindMatches.length;
var pos = notesFindMatches[notesFindIdx];
var termLen = notesFindInput.value.length;
notesTextarea.setSelectionRange(pos, pos + termLen);
notesTextarea.focus();
var cs = getComputedStyle(notesTextarea);
var mirror = document.createElement('div');
mirror.style.cssText = [
'position:absolute', 'visibility:hidden', 'pointer-events:none',
'white-space:pre-wrap', 'word-wrap:break-word',
'width:' + notesTextarea.clientWidth + 'px',
'font-family:' + cs.fontFamily,
'font-size:' + cs.fontSize,
'line-height:' + cs.lineHeight,
'padding:' + cs.padding,
'border:' + cs.border,
'box-sizing:' + cs.boxSizing
].join(';');
document.body.appendChild(mirror);
var before = document.createElement('span');
before.textContent = notesTextarea.value.substring(0, pos);
var marker = document.createElement('span');
marker.textContent = notesTextarea.value.substring(pos, pos + termLen) || '|';
mirror.appendChild(before);
mirror.appendChild(marker);
var matchTop = marker.offsetTop;
var matchHeight = marker.offsetHeight;
document.body.removeChild(mirror);
notesTextarea.scrollTop = Math.max(0, matchTop - (notesTextarea.clientHeight / 2) + (matchHeight / 2));
}
notesFindInput.addEventListener('input', notesFindAll);
notesFindPrev.addEventListener('click', function() { notesJumpToMatch(notesFindIdx - 1); });
notesFindNext.addEventListener('click', function() { notesJumpToMatch(notesFindIdx + 1); });
notesFindInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.shiftKey ? notesJumpToMatch(notesFindIdx - 1) : notesJumpToMatch(notesFindIdx + 1); }
if (e.key === 'Escape') { notesFindPanel.classList.remove('ds-find-open'); }
});
notesReplaceOne.addEventListener('click', function() {
if (!notesFindMatches.length) return;
var pos = notesFindMatches[notesFindIdx], term = notesFindInput.value, rep = notesReplaceInput.value;
var newVal = notesTextarea.value.substring(0, pos) + rep + notesTextarea.value.substring(pos + term.length);
setNotesValue(newVal); notesTextarea.value = newVal;
notesFindAll(); notesJumpToMatch(notesFindIdx);
});
notesReplaceAll.addEventListener('click', function() {
if (!notesFindMatches.length) return;
var term = notesFindInput.value, rep = notesReplaceInput.value;
var newVal = notesTextarea.value.split(new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi')).join(rep);
setNotesValue(newVal); notesTextarea.value = newVal;
notesFindAll(); setStatus('Replaced all!');
});
notesFindClose.addEventListener('click', function() {
notesFindPanel.classList.remove('ds-find-open');
notesFindInput.value = ''; notesReplaceInput.value = ''; notesFindMatches = []; notesFindCount.textContent = '';
});
var sliderHandle = document.createElement('div');
sliderHandle.className = 'ds-wikitext-slider';
sliderHandle.title = 'Drag to adjust panes';
var outputPane = document.createElement('div');
outputPane.className = 'ds-wikitext-output-pane';
outputPane.innerHTML = '<span class="ds-wikitext-output-empty">← Drag the divider to preview wikitext</span>';
wikitextWrap.appendChild(inputPane);
wikitextWrap.appendChild(sliderHandle);
wikitextWrap.appendChild(outputPane);
notesPanel.appendChild(wikitextWrap);
var notesStatusBar = document.createElement('div');
notesStatusBar.className = 'ds-status-bar';
var notesStatusLeft = document.createElement('span');
notesStatusLeft.style.cssText = 'flex:1; font-size:11px; font-family:monospace; opacity:0.5; padding:0 10px;';
var notesStatusPos = document.createElement('span');
notesStatusPos.className = 'ds-status-pos';
notesStatusPos.textContent = '1:1';
notesStatusBar.appendChild(notesStatusLeft);
notesStatusBar.appendChild(notesStatusPos);
notesPanel.appendChild(notesStatusBar);
function updateNotesStatus() {
var val = notesTextarea.value;
var words = val.trim() === '' ? 0 : val.trim().split(/\s+/).length;
var chars = val.length;
notesStatusLeft.textContent = words + ' words, ' + chars + ' chars';
var before = val.substring(0, notesTextarea.selectionStart);
var lines = before.split('\n');
notesStatusPos.textContent = lines.length + ':' + (lines[lines.length - 1].length + 1);
}
notesTextarea.addEventListener('input', updateNotesStatus);
notesTextarea.addEventListener('click', updateNotesStatus);
notesTextarea.addEventListener('keyup', updateNotesStatus);
var sliderDragging = false;
var sliderStartX, sliderStartInputWidth;
var SLIDER_WIDTH = 6;
var wikitextSplitRatio = 1.0;
function applySliderRatio(ratio) {
var totalW = wikitextWrap.offsetWidth - SLIDER_WIDTH;
if (totalW > 0) {
var minRatio = 0;
var maxRatio = 1;
ratio = Math.max(minRatio, Math.min(maxRatio, ratio));
}
wikitextSplitRatio = ratio;
if (totalW <= 0) return;
var inputW = Math.round(totalW * wikitextSplitRatio);
var outputW = totalW - inputW;
inputPane.style.width = inputW + 'px';
outputPane.style.width = outputW + 'px';
if (wikitextSplitRatio >= 1) {
outputPane.style.display = 'none';
} else {
outputPane.style.display = '';
triggerWikitextPreview();
}
}
sliderHandle.style.display = 'block';
function initSlider() {
var totalW = wikitextWrap.offsetWidth - SLIDER_WIDTH;
if (totalW <= 0) { setTimeout(initSlider, 50); return; }
inputPane.style.width = totalW + 'px';
outputPane.style.display = 'none';
}
setTimeout(initSlider, 0);
if (window.ResizeObserver) {
new ResizeObserver(function() {
applySliderRatio(wikitextSplitRatio);
}).observe(wikitextWrap);
}
sliderHandle.addEventListener('mousedown', function(e) {
sliderDragging = true;
sliderStartX = e.clientX;
sliderStartInputWidth = inputPane.offsetWidth;
sliderHandle.classList.add('ds-slider-dragging');
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!sliderDragging) return;
var totalW = wikitextWrap.offsetWidth - SLIDER_WIDTH;
if (totalW <= 0) return;
var dx = e.clientX - sliderStartX;
var newInputW = sliderStartInputWidth + dx;
var ratio = newInputW / totalW;
applySliderRatio(ratio);
});
document.addEventListener('mouseup', function() {
if (sliderDragging) {
sliderDragging = false;
sliderHandle.classList.remove('ds-slider-dragging');
}
});
var wikitextDebounceTimer = null;
var wikitextLastText = null;
function triggerWikitextPreview() {
var text = notesTextarea.value;
if (text === wikitextLastText) return;
wikitextLastText = text;
clearTimeout(wikitextDebounceTimer);
wikitextDebounceTimer = setTimeout(function() {
fetchWikitextPreview(text);
}, 600);
}
function fetchWikitextPreview(text) {
if (wikitextSplitRatio >= 1) return;
outputPane.classList.add('ds-output-loading');
var apiBase = (mw.config.get('wgServer') || '') + '/api.php';
var pageTitle = mw.config.get('wgPageName') || '';
var body = new URLSearchParams({
action: 'parse',
format: 'json',
origin: '*',
text: text,
contentmodel: 'wikitext',
title: pageTitle,
prop: 'text|modulestyles|modules',
disablelimitreport: '1',
disableeditsection: '1'
});
fetch(apiBase, { method: 'POST', body: body })
.then(function(r) { return r.json(); })
.then(function(data) {
outputPane.classList.remove('ds-output-loading');
if (data && data.parse && data.parse.text) {
if (mw.loader) {
if (data.parse.modulestyles && data.parse.modulestyles.length) {
mw.loader.load(data.parse.modulestyles);
}
if (data.parse.modules && data.parse.modules.length) {
mw.loader.load(data.parse.modules);
}
}
outputPane.innerHTML = '<div class="mw-parser-output">' + (data.parse.text['*'] || '') + '</div>';
if (mw.loader) {
mw.loader.using('jquery.makeCollapsible', function() {
outputPane.querySelectorAll('.mw-collapsible').forEach(function(el) {
$(el).makeCollapsible();
});
});
}
} else {
outputPane.innerHTML = '<span class="ds-wikitext-output-empty">No output.</span>';
}
})
.catch(function() {
outputPane.classList.remove('ds-output-loading');
outputPane.innerHTML = '<span class="ds-wikitext-output-empty">Preview unavailable.</span>';
});
}
var notesHistory = [''];
var notesHistoryIdx = 0;
var notesDebounce = null;
function pushNotesHistory(value, selStart, selEnd) {
notesHistory = notesHistory.slice(0, notesHistoryIdx + 1);
if (notesHistory[notesHistoryIdx] && notesHistory[notesHistoryIdx].value === value) return;
notesHistory.push({ value: value, selStart: selStart || 0, selEnd: selEnd || 0 });
if (notesHistory.length > 200) notesHistory.shift();
notesHistoryIdx = notesHistory.length - 1;
}
function setNotesValue(v) {
var cur = notesTextarea.value;
if (cur !== v) pushNotesHistory(cur, notesTextarea.selectionStart, notesTextarea.selectionEnd);
notesTextarea.value = v;
pushNotesHistory(v, 0, 0);
}
pushNotesHistory('', 0, 0);
notesTextarea.addEventListener('input', function() {
saveLocal();
clearTimeout(notesDebounce);
notesDebounce = setTimeout(function() {
pushNotesHistory(notesTextarea.value, notesTextarea.selectionStart, notesTextarea.selectionEnd);
}, 400);
if (wikitextSplitRatio < 1) triggerWikitextPreview();
});
var SELF_CLOSING_TAGS = ['br','img','hr','input','meta','link','area','base','col','embed','param','source','track','wbr'];
function isInsideTemplate(before) {
var depth = 0;
for (var i = 0; i < before.length - 1; i++) {
if (before[i] === '{' && before[i+1] === '{') { depth++; i++; }
else if (before[i] === '}' && before[i+1] === '}') { depth--; i++; }
}
return depth > 0;
}
notesTextarea.addEventListener('keydown', function(e) {
var wpS, wpVal, wpBefore, wpCloser, wpTagMatch, wpTagName, wpInsertion, wpPrev;
if ((e.key === '[' || e.key === '{') && !e.ctrlKey && !e.metaKey) {
wpS = notesTextarea.selectionStart;
wpVal = notesTextarea.value;
wpPrev = wpVal[wpS - 1];
if (wpPrev === e.key) {
e.preventDefault();
wpCloser = e.key === '[' ? ']]' : '}}';
notesTextarea.value = wpVal.substring(0, wpS) + e.key + wpCloser + wpVal.substring(wpS);
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS + 1;
saveLocal();
return;
}
}
if (e.key === "'" && !e.ctrlKey && !e.metaKey) {
wpS = notesTextarea.selectionStart;
wpVal = notesTextarea.value;
wpPrev = wpVal[wpS - 1];
if (wpPrev === "'") {
e.preventDefault();
notesTextarea.value = wpVal.substring(0, wpS) + "'" + "''" + wpVal.substring(wpS);
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS + 1;
saveLocal();
return;
}
}
if (e.key === '>' && !e.ctrlKey && !e.metaKey && notesTextarea.selectionStart === notesTextarea.selectionEnd) {
wpS = notesTextarea.selectionStart;
wpVal = notesTextarea.value;
wpBefore = wpVal.substring(0, wpS);
wpTagMatch = wpBefore.match(/<([a-zA-Z][a-zA-Z0-9]*)([^<]*)$/);
if (wpTagMatch) {
wpTagName = wpTagMatch[1].toLowerCase();
if (SELF_CLOSING_TAGS.indexOf(wpTagName) === -1) {
e.preventDefault();
wpCloser = '></' + wpTagName + '>';
notesTextarea.value = wpVal.substring(0, wpS) + wpCloser + wpVal.substring(wpS);
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS + 1;
saveLocal();
return;
}
}
}
if (e.key === 'Enter' && !e.ctrlKey && !e.metaKey && notesTextarea.selectionStart === notesTextarea.selectionEnd) {
wpS = notesTextarea.selectionStart;
wpVal = notesTextarea.value;
wpBefore = wpVal.substring(0, wpS);
if (isInsideTemplate(wpBefore)) {
e.preventDefault();
var wpAfter = wpVal.substring(wpS);
var wpOnParamLine = wpBefore.match(/\|\s*[^\s]/);
wpInsertion = wpOnParamLine ? '\n | ' : '\n | \n}}';
if (wpInsertion === '\n | \n}}') {
wpAfter = wpAfter.replace(/^\}\}/, '');
}
notesTextarea.value = wpVal.substring(0, wpS) + wpInsertion + wpAfter;
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS + 5;
saveLocal();
return;
}
}
if (e.key === 'Backspace' && notesTextarea.selectionStart === notesTextarea.selectionEnd) {
wpS = notesTextarea.selectionStart;
wpVal = notesTextarea.value;
wpPrev = wpVal[wpS - 1];
wpNext = wpVal[wpS];
var wpTagClose = wpVal.substring(wpS).match(/^<\/([a-zA-Z][a-zA-Z0-9]*)>/);
var wpTagOpen = wpVal.substring(0, wpS).match(/<([a-zA-Z][a-zA-Z0-9]*)>$/);
if (wpTagClose && wpTagOpen && wpTagClose[1].toLowerCase() === wpTagOpen[1].toLowerCase()) {
e.preventDefault();
var wpOpenTag = wpTagOpen[0];
var wpCloseTag = wpTagClose[0];
notesTextarea.value = wpVal.substring(0, wpS - wpOpenTag.length) + wpVal.substring(wpS + wpCloseTag.length);
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS - wpOpenTag.length;
saveLocal();
return;
}
var wpOpenTagEdit = wpVal.substring(0, wpS).match(/<([a-zA-Z][a-zA-Z0-9]*)$/);
var wpCloseTagAhead = wpVal.substring(wpS).match(/^<\/([a-zA-Z][a-zA-Z0-9]*)>/);
if (wpOpenTagEdit && wpCloseTagAhead) {
e.preventDefault();
var wpNewName = wpOpenTagEdit[1].slice(0, -1);
var wpBefore2 = wpVal.substring(0, wpS - 1);
var wpAfter = wpVal.substring(wpS);
if (wpNewName.length === 0) {
wpAfter = wpAfter.replace(/^<\/[a-zA-Z][a-zA-Z0-9]*>/, '');
} else {
wpAfter = wpAfter.replace(/^<\/[a-zA-Z][a-zA-Z0-9]*>/, '</' + wpNewName + '>');
}
notesTextarea.value = wpBefore2 + wpAfter;
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS - 1;
saveLocal();
return;
}
var wpDoublePrev = wpVal.substring(wpS - 2, wpS);
var wpDoubleNext = wpVal.substring(wpS, wpS + 2);
if (wpDoublePrev === '[[' && wpDoubleNext === ']]') {
e.preventDefault();
notesTextarea.value = wpVal.substring(0, wpS - 2) + wpVal.substring(wpS + 2);
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS - 2;
saveLocal();
return;
}
var wpMTOpenIdx = wpVal.lastIndexOf('{{', wpS - 1);
var wpMTCloseIdx = wpVal.indexOf('}}', wpS);
if (wpMTOpenIdx !== -1 && wpMTCloseIdx !== -1 && wpVal.substring(wpMTOpenIdx, wpS).indexOf('}}') === -1) {
if (wpDoublePrev === '{{' || wpDoubleNext === '}}') {
e.preventDefault();
notesTextarea.value = wpVal.substring(0, wpMTOpenIdx) + wpVal.substring(wpMTCloseIdx + 2);
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpMTOpenIdx;
saveLocal();
return;
}
}
var wpNotePairs = { '(': ')', "'": "'" };
if (wpPrev && wpNext && wpNotePairs[wpPrev] === wpNext) {
e.preventDefault();
notesTextarea.value = wpVal.substring(0, wpS - 1) + wpVal.substring(wpS + 1);
notesTextarea.selectionStart = notesTextarea.selectionEnd = wpS - 1;
saveLocal();
return;
}
}
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
e.preventDefault();
clearTimeout(notesDebounce);
if (notesTextarea.value !== (notesHistory[notesHistoryIdx] && notesHistory[notesHistoryIdx].value)) {
pushNotesHistory(notesTextarea.value, notesTextarea.selectionStart, notesTextarea.selectionEnd);
}
if (notesHistoryIdx > 0) {
notesHistoryIdx--;
var entry = notesHistory[notesHistoryIdx];
notesTextarea.value = entry.value;
notesTextarea.selectionStart = entry.selStart;
notesTextarea.selectionEnd = entry.selEnd;
saveLocal();
if (wikitextSplitRatio < 1) triggerWikitextPreview();
}
return;
}
if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
e.preventDefault();
if (notesHistoryIdx < notesHistory.length - 1) {
notesHistoryIdx++;
var entry = notesHistory[notesHistoryIdx];
notesTextarea.value = entry.value;
notesTextarea.selectionStart = entry.selStart;
notesTextarea.selectionEnd = entry.selEnd;
saveLocal();
if (wikitextSplitRatio < 1) triggerWikitextPreview();
}
return;
}
if ((e.ctrlKey || e.metaKey) && e.key === 'h') {
e.preventDefault();
notesFindPanel.classList.toggle('ds-find-open');
if (notesFindPanel.classList.contains('ds-find-open')) { notesFindInput.focus(); notesFindInput.select(); }
return;
}
});
var cssEditor = buildCodeEditor('css');
var jsEditor = buildCodeEditor('js');
editorArea.appendChild(notesPanel);
editorArea.appendChild(cssEditor.panel);
editorArea.appendChild(jsEditor.panel);
container.appendChild(editorArea);
var ioOverlay = document.createElement('div');
ioOverlay.className = 'ds-io-overlay';
var ioModal = document.createElement('div');
ioModal.className = 'ds-io-modal';
var ioHeader = document.createElement('div');
ioHeader.style.cssText = 'display:flex;justify-content:space-between;align-items:flex-start;';
var ioTitleWrap = document.createElement('div');
var ioTitle = document.createElement('div');
ioTitle.className = 'ds-io-modal-title';
ioTitle.textContent = 'Import / Export';
var ioDesc = document.createElement('div');
ioDesc.className = 'ds-io-modal-desc';
ioDesc.textContent = 'Export your editors to a compressed string you can share or back up. Paste a string below to import.';
ioTitleWrap.appendChild(ioTitle);
ioTitleWrap.appendChild(ioDesc);
var ioClose = document.createElement('button');
ioClose.className = 'ds-io-close';
ioClose.textContent = '✕';
ioClose.addEventListener('click', function() { ioOverlay.classList.remove('ds-io-open'); });
ioHeader.appendChild(ioTitleWrap);
ioHeader.appendChild(ioClose);
var exportSection = document.createElement('div');
exportSection.className = 'ds-io-section';
var exportSectionLabel = document.createElement('div');
exportSectionLabel.className = 'ds-io-section-label';
exportSectionLabel.textContent = 'Export';
var exportRow = document.createElement('div');
exportRow.className = 'ds-io-row';
var ioInput = document.createElement('input');
ioInput.className = 'ds-io-input';
ioInput.placeholder = 'Exported string will appear here...';
ioInput.type = 'text';
ioInput.readOnly = true;
['all','notes','css','js'].forEach(function(type) {
var btn = document.createElement('button');
btn.className = 'ds-io-btn';
btn.textContent = type === 'notes' ? 'Wikitext' : type.toUpperCase();
btn.title = 'Export ' + (type === 'all' ? 'everything' : type === 'notes' ? 'wikitext' : type.toUpperCase());
btn.addEventListener('click', function() {
var str = exportData(type);
ioInput.readOnly = false;
ioInput.value = str;
ioInput.readOnly = true;
importInput.value = '';
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(str).then(function() { setStatus('Copied!'); }).catch(function() {
ioInput.select(); document.execCommand('copy'); setStatus('Copied!');
});
} else {
ioInput.select(); document.execCommand('copy'); setStatus('Copied!');
}
});
exportRow.appendChild(btn);
});
exportSection.appendChild(exportSectionLabel);
exportSection.appendChild(exportRow);
exportSection.appendChild(ioInput);
var importSection = document.createElement('div');
importSection.className = 'ds-io-section';
var importSectionLabel = document.createElement('div');
importSectionLabel.className = 'ds-io-section-label';
importSectionLabel.textContent = 'Import';
var importInput = document.createElement('input');
importInput.className = 'ds-io-input';
importInput.placeholder = 'Paste an import string here...';
importInput.type = 'text';
var importBtn = document.createElement('button');
importBtn.className = 'ds-io-btn';
importBtn.textContent = 'Import';
importBtn.style.alignSelf = 'flex-start';
importBtn.addEventListener('click', function() {
var result = importData(importInput.value, function(ok) {
setStatus(ok ? 'Imported!' : 'Cancelled.');
if (ok) { importInput.value = ''; ioOverlay.classList.remove('ds-io-open'); }
});
if (result === true) { setStatus('Imported!'); importInput.value = ''; ioOverlay.classList.remove('ds-io-open'); }
if (result === false) { setStatus('Invalid string!'); }
});
importSection.appendChild(importSectionLabel);
importSection.appendChild(importInput);
importSection.appendChild(importBtn);
ioModal.appendChild(ioHeader);
ioModal.appendChild(exportSection);
ioModal.appendChild(importSection);
ioOverlay.appendChild(ioModal);
ioOverlay.addEventListener('click', function(e) {
if (e.target === ioOverlay) ioOverlay.classList.remove('ds-io-open');
});
document.body.appendChild(ioOverlay);
document.body.appendChild(container);
var liveStyle = document.createElement('style');
liveStyle.id = 'ds-live-css';
document.head.appendChild(liveStyle);
function makeItem(label, onClick) {
var btn = document.createElement('button');
btn.className = 'ds-dropdown-item';
btn.textContent = label;
btn.addEventListener('click', function() { onClick(); closeDropdown(); });
return btn;
}
function makeSep() {
var d = document.createElement('div');
d.className = 'ds-dropdown-sep';
return d;
}
var dropdownItems = {
css: [
makeItem('▶ Run', function() {
var errors = getCSSErrorLines(cssEditor.getValue());
if (errors.length > 0) { setStatus('Fix CSS errors first!'); return; }
liveStyle.textContent = cssEditor.getValue();
setStatus('CSS applied!');
}),
makeSep(),
makeItem('✦ Beautify', function() { cssEditor.setValue(cssBeautify(cssEditor.getValue())); setStatus('Beautified!'); }),
makeItem('⬡ Minify', function() { cssEditor.setValue(cssMinify(cssEditor.getValue())); setStatus('Minified!'); }),
makeSep(),
makeItem('+ Add !important', function() {
var ta = cssEditor.textarea;
var s = ta.selectionStart, e = ta.selectionEnd;
if (s !== e) {
cssEditor.setValue(ta.value.substring(0, s) + addImportant(ta.value.substring(s, e)) + ta.value.substring(e));
} else {
cssEditor.setValue(addImportant(cssEditor.getValue()));
}
setStatus('!important added!');
}),
makeItem('− Remove !important', function() {
var ta = cssEditor.textarea;
var s = ta.selectionStart, e = ta.selectionEnd;
if (s !== e) {
cssEditor.setValue(ta.value.substring(0, s) + removeImportant(ta.value.substring(s, e)) + ta.value.substring(e));
} else {
cssEditor.setValue(removeImportant(cssEditor.getValue()));
}
setStatus('!important removed!');
})
],
js: [
makeItem('▶ Run', function() {
var warned = localStorage.getItem('ds_js_run_warned') === '1';
if (!warned) {
showWarning('run', function() {
localStorage.setItem('ds_js_run_warned', '1');
try { eval(jsEditor.getValue()); setStatus('JS ran!'); }
catch(e) { setStatus('Runtime error: ' + e.message); }
}, function() { setStatus('Cancelled.'); });
return;
}
try { eval(jsEditor.getValue()); setStatus('JS ran!'); }
catch(e) { setStatus('Runtime error: ' + e.message); }
}),
makeSep(),
makeItem('✦ Beautify', function() { jsEditor.setValue(jsBeautify(jsEditor.getValue())); setStatus('Beautified!'); }),
makeItem('⬡ Minify', function() { jsEditor.setValue(jsMinify(jsEditor.getValue())); setStatus('Minified!'); })
]
};
var activeTab = 'notes';
function applyHeaderColor(tabId) {
var preset = getPreset();
if (preset === 'Folders') {
header.style.backgroundColor = getFolderTabColor(tabId);
header.style.color = getFolderTabText(tabId);
} else {
header.style.backgroundColor = '';
header.style.color = '';
}
}
function updateDropdown(tabId) {
dropdown.innerHTML = '';
var items = dropdownItems[tabId];
if (items) {
menuWrap.style.display = '';
items.forEach(function(el) { dropdown.appendChild(el); });
} else {
menuWrap.style.display = 'none';
}
}
function updateDefaultHeaderTabs(tabId) {
headerTabsEl.innerHTML = '';
var allTabs = [{id:'notes',label:'Wikitext'},{id:'css',label:'CSS'},{id:'js',label:'JS'}];
allTabs.forEach(function(t) {
if (t.id === tabId) return;
var el = document.createElement('span');
el.className = 'ds-header-tab';
el.textContent = t.label;
el.addEventListener('click', function() { switchTab(t.id); });
headerTabsEl.appendChild(el);
});
var tabLabels = { notes: 'Wikitext', css: 'CSS', js: 'JS' };
titleEl.textContent = 'DynamicStudio/' + (tabLabels[tabId] || tabId);
}
function applyFolderTabStyles() {
tabsEl.querySelectorAll('.ds-tab').forEach(function(tab) {
var tid = tab.dataset.tab;
tab.style.backgroundColor = getFolderTabColor(tid);
tab.style.color = getFolderTabText(tid);
});
}
function applyNotepadStyle() {
var lineColor = getNotepadProp('line-color', isDark ? 'rgba(100,140,255,0.6)' : 'rgba(60,100,220,0.4)');
var marginColor = getNotepadProp('margin-color', isDark ? 'rgba(220,80,80,0.7)' : 'rgba(180,30,30,0.6)');
var bgColor = getNotepadProp('bg-color', isDark ? '#0d0d0d' : '#ffffff');
var lineH = 21;
var pad = 10;
[cssEditor, jsEditor].forEach(function(ed) {
applyNotepadToEditor(ed, lineColor, marginColor, bgColor, lineH, pad);
});
applyNotepadToNotes(lineColor, marginColor, bgColor, lineH, pad);
header.style.backgroundColor = bgColor;
header.style.padding = '8px 12px 8px 46px';
header.style.borderBottom = '1px solid ' + lineColor;
header.style.boxShadow = bgColor + ' 42px 0px 0px 0px inset, ' +
marginColor + ' 44px 0px 0px 0px inset, ' +
bgColor + ' 46px 0px 0px 0px inset';
}
function applyNotepadToEditor(ed, lineColor, marginColor, bgColor, lineH, pad) {
var pad = 0;
var wrap = ed.panel.querySelector('.ds-code-wrap');
var inner = ed.panel.querySelector('.ds-code-inner');
var gutter = ed.panel.querySelector('.ds-gutter');
if (!wrap || !inner || !gutter) return;
var offset = pad + lineH - 1;
wrap.style.backgroundColor = bgColor;
gutter.style.backgroundColor = bgColor;
inner.style.backgroundColor = bgColor;
inner.style.backgroundImage = 'linear-gradient(' + lineColor + ' 1px, transparent 1px)';
inner.style.backgroundSize = '100% ' + lineH + 'px';
inner.style.backgroundPositionY = offset + 'px';
inner.style.backgroundRepeat = 'repeat';
inner.style.backgroundAttachment = 'local';
gutter.style.backgroundImage = 'linear-gradient(' + lineColor + ' 1px, transparent 1px)';
gutter.style.backgroundSize = '100% ' + lineH + 'px';
gutter.style.backgroundPositionY = offset + 'px';
gutter.style.backgroundRepeat = 'repeat';
gutter.style.backgroundAttachment = 'local';
gutter.style.boxShadow = 'inset -2px 0 0 0 ' + marginColor;
gutter.style.boxSizing = 'border-box';
var ta = ed.panel.querySelector('.ds-code-textarea');
if (ta) ta.style.padding = '0px 10px 10px 10px';
var hl = ed.panel.querySelector('.ds-code-highlight');
if (hl) hl.style.padding = '0 10px 0 10px';
var ln = ed.panel.querySelector('.ds-line-numbers');
if (ln) ln.style.paddingTop = '0';
var font = getNotepadProp('font', '');
if (font && ta) ta.style.fontFamily = font;
}
function applyNotepadToNotes(lineColor, marginColor, bgColor, lineH, pad) {
var pad = 0;
var offset = pad + lineH - 1;
inputPane.style.backgroundColor = bgColor;
notesTextarea.style.backgroundColor = 'transparent';
notesTextarea.style.backgroundImage = 'linear-gradient(' + lineColor + ' 1px, transparent 1px)';
notesTextarea.style.backgroundSize = '100% ' + lineH + 'px';
notesTextarea.style.backgroundPositionY = offset + 'px';
notesTextarea.style.backgroundRepeat = 'repeat';
notesTextarea.style.backgroundAttachment = 'local';
inputPane.style.boxShadow = bgColor + ' 42px 0px 0px 0px inset, ' +
marginColor + ' 44px 0px 0px 0px inset, ' +
bgColor + ' 46px 0px 0px 0px inset';
notesTextarea.style.borderLeft = '';
notesTextarea.style.padding = '0px 10px 10px 46px';
var font = getNotepadProp('font', '');
if (font) notesTextarea.style.fontFamily = font;
}
function clearNotepadStyle() {
[cssEditor, jsEditor].forEach(function(ed) {
var wrap = ed.panel.querySelector('.ds-code-wrap');
var inner = ed.panel.querySelector('.ds-code-inner');
var gutter = ed.panel.querySelector('.ds-gutter');
if (wrap) { wrap.style.backgroundColor = ''; }
var ta = ed.panel.querySelector('.ds-code-textarea');
if (ta) { ta.style.padding = ''; }
var hl = ed.panel.querySelector('.ds-code-highlight');
if (hl) { hl.style.padding = ''; }
var ln = ed.panel.querySelector('.ds-line-numbers');
if (ln) { ln.style.paddingTop = ''; }
if (inner) {
inner.style.backgroundColor = '';
inner.style.backgroundImage = '';
inner.style.backgroundSize = '';
inner.style.backgroundPositionY = '';
inner.style.backgroundRepeat = '';
inner.style.backgroundAttachment = '';
}
if (gutter) {
gutter.style.backgroundColor = '';
gutter.style.backgroundImage = '';
gutter.style.backgroundSize = '';
gutter.style.backgroundPositionY = '';
gutter.style.backgroundRepeat = '';
gutter.style.backgroundAttachment = '';
gutter.style.boxShadow = '';
gutter.style.boxSizing = '';
}
});
notesTextarea.style.backgroundColor = '';
notesTextarea.style.backgroundImage = '';
notesTextarea.style.backgroundSize = '';
notesTextarea.style.backgroundPositionY = '';
notesTextarea.style.backgroundRepeat = '';
notesTextarea.style.backgroundAttachment = '';
notesTextarea.style.borderLeft = '';
notesTextarea.style.padding = '';
notesTextarea.style.fontFamily = '';
header.style.backgroundColor = '';
header.style.borderBottom = '';
header.style.padding = '';
header.style.boxShadow = '';
inputPane.style.backgroundColor = '';
inputPane.style.boxShadow = '';
notesTextarea.style.backgroundColor = '';
notesTextarea.style.borderLeft = '';
notesTextarea.style.paddingLeft = '';
}
function applyMinimalStyle() {
var gutterColor = getDSProp('--ds-minimal-gutter-color');
[cssEditor, jsEditor].forEach(function(ed) {
var gutter = ed.panel.querySelector('.ds-gutter');
var wrap = ed.panel.querySelector('.ds-code-wrap');
if (gutter) gutter.style.backgroundColor = gutterColor || 'transparent';
if (wrap) wrap.style.backgroundColor = 'transparent';
});
}
function clearMinimalStyle() {
[cssEditor, jsEditor].forEach(function(ed) {
var gutter = ed.panel.querySelector('.ds-gutter');
var wrap = ed.panel.querySelector('.ds-code-wrap');
if (gutter) gutter.style.backgroundColor = '';
if (wrap) wrap.style.backgroundColor = '';
});
}
function applyPreset() {
var preset = getPreset();
if (preset === 'Folders') {
tabsEl.style.display = '';
headerTabsEl.style.display = 'none';
titleEl.textContent = 'DynamicStudio';
applyFolderTabStyles();
clearNotepadStyle();
clearMinimalStyle();
} else if (preset === 'Notepad') {
tabsEl.style.display = 'none';
headerTabsEl.style.display = 'flex';
updateDefaultHeaderTabs(activeTab);
clearNotepadStyle();
clearMinimalStyle();
setTimeout(applyNotepadStyle, 0);
} else if (preset === 'Minimal') {
tabsEl.style.display = 'none';
headerTabsEl.style.display = 'flex';
updateDefaultHeaderTabs(activeTab);
clearNotepadStyle();
setTimeout(applyMinimalStyle, 0);
} else {
tabsEl.style.display = 'none';
headerTabsEl.style.display = 'flex';
updateDefaultHeaderTabs(activeTab);
clearNotepadStyle();
clearMinimalStyle();
}
}
function switchTab(tabId) {
activeTab = tabId;
tabsEl.querySelectorAll('.ds-tab').forEach(function(t) {
t.classList.toggle('ds-active', t.dataset.tab === tabId);
});
editorArea.querySelectorAll('.ds-panel').forEach(function(p) {
p.classList.toggle('ds-panel-active', p.dataset.panel === tabId);
});
applyHeaderColor(tabId);
updateDropdown(tabId);
var preset = getPreset();
if (preset === 'Folders') {
applyFolderTabStyles();
} else {
updateDefaultHeaderTabs(tabId);
}
if (tabId === 'css') cssEditor.refresh();
if (tabId === 'js') jsEditor.refresh();
if (tabId === 'notes') setTimeout(initSlider, 0);
if (preset === 'Notepad') setTimeout(applyNotepadStyle, 0);
if (preset === 'Minimal') setTimeout(applyMinimalStyle, 0);
}
applyPreset();
switchTab('notes');
tabsEl.querySelectorAll('.ds-tab').forEach(function(tab) {
tab.addEventListener('click', function() { switchTab(tab.dataset.tab); });
});
function setStatus(msg) {
if (!statusEl) { setTimeout(function() { setStatus(msg); }, 500); return; }
statusEl.textContent = msg;
setTimeout(function() { if (statusEl) statusEl.textContent = ''; }, 3000);
}
ioBtn.addEventListener('click', function() { ioOverlay.classList.toggle('ds-io-open'); });
var saved = loadLocal();
if (saved) {
if (saved.wikitext !== undefined) notesTextarea.value = saved.wikitext;
if (saved.css !== undefined) cssEditor.setValue(saved.css);
if (saved.js !== undefined) jsEditor.setValue(saved.js);
}
var maxW = window.innerWidth - 20;
var maxH = window.innerHeight - 20;
var savedPos = loadPos();
if (savedPos && savedPos.left && savedPos.top) {
container.style.left = savedPos.left;
container.style.top = savedPos.top;
if (savedPos.width) container.style.width = Math.min(savedPos.width, maxW) + 'px';
if (savedPos.height) editorArea.style.height = Math.min(Math.max(156, savedPos.height), maxH) + 'px';
} else {
container.style.left = Math.max(0, (window.innerWidth - 500) / 2) + 'px';
container.style.top = Math.max(0, (window.innerHeight - 350) / 2) + 'px';
}
var dragging = false, dragOffsetX, dragOffsetY;
var resizing = false, resizeDir, resizeStartX, resizeStartY, resizeStartW, resizeStartH, resizeStartLeft, resizeStartTop;
header.addEventListener('mousedown', function(e) {
if (e.target !== header && e.target !== titleEl && e.target !== headerTabsEl) return;
dragging = true;
dragOffsetX = e.clientX - container.getBoundingClientRect().left;
dragOffsetY = e.clientY - container.getBoundingClientRect().top;
e.preventDefault();
});
container.querySelectorAll('.ds-corner').forEach(function(corner) {
corner.addEventListener('mousedown', function(e) {
resizing = true;
resizeDir = corner.dataset.dir;
resizeStartX = e.clientX;
resizeStartY = e.clientY;
resizeStartW = container.offsetWidth;
resizeStartH = editorArea.offsetHeight;
resizeStartLeft = container.getBoundingClientRect().left;
resizeStartTop = container.getBoundingClientRect().top;
e.preventDefault();
e.stopPropagation();
});
});
document.addEventListener('mousemove', function(e) {
if (dragging) {
var HEADER_H = 36;
var newLeft = e.clientX - dragOffsetX;
var newTop = e.clientY - dragOffsetY;
newLeft = Math.max(-(container.offsetWidth - 60), Math.min(window.innerWidth - 60, newLeft));
newTop = Math.max(0, Math.min(window.innerHeight - HEADER_H, newTop));
container.style.left = newLeft + 'px';
container.style.top = newTop + 'px';
savePos();
}
if (resizing) {
var dx = e.clientX - resizeStartX, dy = e.clientY - resizeStartY;
var newW = resizeStartW, newH = resizeStartH, newLeft = resizeStartLeft, newTop = resizeStartTop;
if (resizeDir === 'se') { newW = resizeStartW + dx; newH = resizeStartH + dy; }
if (resizeDir === 'sw') { newW = resizeStartW - dx; newH = resizeStartH + dy; newLeft = resizeStartLeft + dx; }
if (resizeDir === 'ne') { newW = resizeStartW + dx; newH = resizeStartH - dy; newTop = resizeStartTop + dy; }
if (resizeDir === 'nw') { newW = resizeStartW - dx; newH = resizeStartH - dy; newLeft = resizeStartLeft + dx; newTop = resizeStartTop + dy; }
var minH = 156;
var curMaxW = window.innerWidth - 20;
var curMaxH = window.innerHeight - 20;
var HEADER_H = 36;
newLeft = Math.max(-(newW - 60), Math.min(window.innerWidth - 60, newLeft));
newTop = Math.max(0, Math.min(window.innerHeight - HEADER_H, newTop));
if (newW >= 300 && newW <= curMaxW) {
container.style.width = newW + 'px';
container.style.left = newLeft + 'px';
}
if (newH >= minH && newH <= curMaxH) {
editorArea.style.height = newH + 'px';
container.style.top = newTop + 'px';
} else if (resizeDir === 'ne' || resizeDir === 'nw') {
editorArea.style.height = minH + 'px';
container.style.top = Math.max(0, Math.min(window.innerHeight - HEADER_H, resizeStartTop + resizeStartH - minH)) + 'px';
}
savePos();
}
});
document.addEventListener('mouseup', function() { dragging = false; resizing = false; });
function toggle() {
var opening = !container.classList.contains('ds-open');
container.classList.toggle('ds-open');
try { localStorage.setItem('ds_closed_' + username, opening ? '0' : '1'); } catch(e) {}
}
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return;
if (e.key === 'q' && !e.altKey && !e.ctrlKey && !e.metaKey) { e.preventDefault(); toggle(); }
});
closeBtn.addEventListener('click', function() {
container.classList.remove('ds-open');
try { localStorage.setItem('ds_closed_' + username, '1'); } catch(e) {}
});
try {
if (localStorage.getItem('ds_closed_' + username) !== '1') {
container.classList.add('ds-open');
}
} catch(e) {}
var toolbarLink = document.createElement('a');
toolbarLink.id = 'ds-toolbar-link';
toolbarLink.textContent = 'DynamicStudio';
toolbarLink.title = 'DynamicStudio (Q)';
toolbarLink.style.cursor = 'pointer';
toolbarLink.addEventListener('click', toggle);
var toolbarLi = document.createElement('li');
toolbarLi.appendChild(toolbarLink);
var toolbarUl = document.querySelector('#WikiaBar .tools');
if (toolbarUl) {
var overflowMenu = toolbarUl.querySelector('li.menu.overflow-menu.wds-dropdown');
if (overflowMenu) {
toolbarUl.insertBefore(toolbarLi, overflowMenu);
} else {
toolbarUl.appendChild(toolbarLi);
}
}
});