// ==UserScript==
// @name DTextStyler
// @namespace https://github.com/BrokenEagle/JavaScripts
// @version 5.13
// @description Danbooru DText UI addon.
// @source https://danbooru.donmai.us/users/23799
// @author BrokenEagle
// @match https://*.donmai.us/*
// @exclude /^https://\w+\.donmai\.us/.*\.(xml|json|atom)(\?|$)/
// @grant none
// @run-at document-end
// @downloadURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/DTextStyler.user.js
// @updateURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/DTextStyler.user.js
// @require https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/module.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/debug.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/utility.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/validate.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/storage.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/network.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/load.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/menu.js
// ==/UserScript==
/* global JSPLib $ Danbooru Papa */
/****Library updates****/
JSPLib.program = new Proxy(JSPLib, {
get(target, prop, _receiver) {
return prop + (target.program_shortcut.length ? '.' + target.program_shortcut : "");
},
});
JSPLib.utility.getAttr = function (domobj, key) {
if (typeof key === 'string') {
return domobj.attributes[key].value;
}
let data = {};
for (let attr of domobj.attributes) {
if (Array.isArray(key) && !key.includes(attr.name)) continue;
data[attr.name] = attr.value;
}
return data;
};
JSPLib.utility.isNamespaceBound2 = function ({root = null, eventtype = null, namespace = null, selector = null, presence = true} = {}) {
let event_namespaces = this.getBoundEventNames(root, eventtype, selector);
let name_parts = namespace.split('.');
return this._not(event_namespaces.some((name) => this.isSubArray(name.split('.'), name_parts)), !presence);
};
JSPLib.utility.DOMWaitExecute = function ({namespace_check = null, data_check = null, extra_check = null, found = null, interval = null, duration = null, name = null} = {}) {
const printer = (name ? JSPLib.debug.getFunctionPrint('utility.DOMWaitExecute') : (()=>{}));
extra_check ??= (() => true);
this.recheckInterval({
check: () => {
let checks = [];
if (namespace_check !== null) {
checks.push(this.isNamespaceBound2(namespace_check));
}
if (data_check !== null) {
checks.push(this.hasDOMDataKey(data_check.selector, data_check.key));
}
if (extra_check !== null) {
checks.push(extra_check());
}
return checks.every((c) => c);
},
debug: () => printer.debuglogLevel(`Waiting on DOM: ${name}.`, JSPLib.debug.VERBOSE),
fail: () => printer.debuglogLevel(`Failed to execute: ${name}.`, JSPLib.debug.WARNING),
exec: () => {
printer.debuglogLevel(`Event triggered: ${name}.`, JSPLib.debug.INFO);
found();
},
interval,
duration,
});
};
JSPLib.utility.subscribeDOMProperty = function (obj, prop, func) {
const property = Object.getOwnPropertyDescriptor(obj.constructor.prototype, prop);
Object.defineProperty(obj, prop, {
set: function(value) {
property.set.call(obj, value);
func?.(value);
},
get: function() {
return property.get.call(obj);
},
configurable: false,
enumerable: true,
writeable: false,
});
};
JSPLib.utility.setPropertyTrap = function (obj, property, {value = {}, getter = null, setter = null} = {}) {
// For subproperties that are accessed/written after the DOM object is initialized
const private_property = '_' + property;
obj[property] = new Proxy(obj, {
get(target, prop, receiver) {
getter?.(prop);
return target[private_property][prop];
},
set(target, prop, value, receiver) {
target[private_property][prop] = value;
setter?.(prop, value);
},
});
Object.defineProperty(obj, property, {
configurable: false,
enumerable: true,
writeable: false,
});
Object.defineProperty(obj, private_property, {
value,
configurable: false,
enumerable: false,
writeable: false,
});
};
//For module.js, since this script does not import notice.js. The notice.js module needs its own version
JSPLib.notice.close = (() => JSPLib._document.getElementById('close-notice-link')?.click());
/****Global variables****/
//Exterior script variables
const DANBOORU_TOPIC_ID = '14229';
const GITHUB_WIKI_PAGE = 'https://github.com/BrokenEagle/JavaScripts/wiki/DtextStyler';
//Variables for load.js
const PROGRAM_LOAD_REQUIRED_VARIABLES = ['window.jQuery', 'window.Danbooru', 'Danbooru.Upload'];
const PROGRAM_LOAD_OPTIONAL_SELECTORS = ['.dtext-editor textarea', '#add-commentary-dialog', '.upload-edit-container', '#c-users #a-edit'];
//Program name constants
const PROGRAM_SHORTCUT = 'ds';
const PROGRAM_NAME = 'DTextStyler';
//Main program variable
const DS = {};
const DEFAULT_VALUES = {
mode: 'edit',
};
//Available setting values
const ALL_TYPES = ['comment', 'forum', 'wiki', 'pool', 'dmail'];
const ALL_MARKUP = ['bold', 'italic', 'underline', 'strikethrough', 'translation', 'spoiler', 'code', 'nodtext', 'quote', 'expand', 'textile_link', 'wiki_link', 'named_link', 'search_link', 'full_table', 'headless_table'];
const ALL_ACTIONS = ['undo', 'redo'];
//Main settings
const SETTINGS_CONFIG = {
post_commentary_enabled: {
reset: true,
validate: JSPLib.utility.isBoolean,
hint: "Show dtext controls on the post commentary dialog."
},
upload_commentary_enabled: {
reset: true,
validate: JSPLib.utility.isBoolean,
hint: "Show dtext controls above the upload commentary inputs."
},
dtext_types_handled: {
allitems: ALL_TYPES,
reset: ALL_TYPES,
validate: (data) => JSPLib.menu.validateCheckboxRadio(data, 'checkbox', ALL_TYPES),
hint: "Show dtext controls above the preview area for the available types.",
},
available_dtext_markup: {
allitems: ALL_MARKUP,
reset: ALL_MARKUP,
validate: (data) => (JSPLib.menu.validateCheckboxRadio(data, 'checkbox', ALL_MARKUP) && (data.length > 0)),
hint: "Select the list of available DText tags to be shown. Must have at least one.",
},
available_dtext_actions: {
allitems: ALL_ACTIONS,
reset: ALL_ACTIONS,
validate: (data) => JSPLib.menu.validateCheckboxRadio(data, 'checkbox', ALL_ACTIONS),
hint: "Select the list of available DText actions to be shown.",
},
}
const MENU_CONFIG = {
topic_id: DANBOORU_TOPIC_ID,
wiki_page: GITHUB_WIKI_PAGE,
settings: [{
name: 'general',
},{
name: 'main',
},{
name: 'commentary',
},{
name: 'controls',
}],
controls: [],
};
//CSS constants
const PROGRAM_CSS = `
/** General **/
/**** Preview ****/
.ds-preview-display {
max-height: 300px;
overflow-y: auto;
}
.ds-preview-display .ds-section {
border: 1px solid;
padding: 5px;
min-height: 10em;
}
.ds-preview-display .ds-section-header {
font-size: var(--text-lg);
font-weight: bold;
text-decoration: underline;
margin: 0.5rem 0;
}
.ds-button {
width: 7em;
}
/**** Markup buttons ****/
.ds-markup-headers > div,
.ds-markup-headers > div > div,
.ds-buttons > div,
.ds-buttons > div > div {
display: inline-block;
}
.ds-markup-headers > div > div {
text-align: center;
font-weight: bold;
color: var(--black);
}
.dtext-button {
width: 40px;
height: 40px;
position: relative;
}
.dtext-button > * {
position: absolute;
}
.ds-translate-content {
font-size: 16px;
font-weight: bold;
white-space: nowrap;
}
/** Posts page **/
form#fetch-commentary input#commentary_source {
max-width: 75%;
}
form#edit-commentary input#artist_commentary_original_title,
form#edit-commentary input#artist_commentary_translated_title {
max-width: 100%;
}
button.ds-dialog-button[name=Cancel] {
color: white;
background: red;
}
button.ds-dialog-button[name=Submit] {
color: white;
background: green;
}`;
const LIGHT_MODE_CSS = `
.ds-preview-display .ds-section {
border-color: var(--grey-2);
}`;
const DARK_MODE_CSS = `
.ds-preview-display .ds-section {
border-color: var(--grey-7);
}`;
const MENU_CSS = `
.jsplib-selectors.ds-selectors[data-setting="dtext_types_handled"] label {
width: 120px;
}
.jsplib-selectors.ds-selectors[data-setting="available_dtext_markup"] label {
width: 150px;
}`;
//HTML constants
const BOLD_CONTENT = ' ';
const ITALIC_CONTENT = ' ';
const UNDERLINE_CONTENT = ' ';
const STRIKETHROUGH_CONTENT = ' ';
const TRANSLATION_CONTENT = '
Aあ
';
const QUOTE_CONTENT = ' ';
const EXPAND_CONTENT = ' ';
const SPOILER_CONTENT = ' ';
const CODE_CONTENT = ' ';
const NODTEXT_CONTENT = ' ';
const TEXTILELINK_CONTENT = ' ';
const NAMEDLINK_CONTENT = ' ';
const WIKILINK_CONTENT = ' ';
const SEARCHLINK_CONTENT = ' ';
const FULL_TABLE_CONTENT = ' ';
const HEADLESS_TABLE_CONTENT = ' ';
const UNDO_CONTENT = ' ';
const REDO_CONTENT = ' ';
const MARKUP_CONTROLS = `
`;
const PREVIEW_SECTION = `
%s
`;
const PREVIEW_BUTTONS = `
`;
const CONTROL_BUTTONS = `
Preview
Edit
`
const UPLOAD_COMMENTARY_DESCRIPTION = `
`;
const DTEXT_TEXTAREA = `
`;
const LOADING_MESSAGE = 'loading...
';
//Config constants
const MARKUP_BUTTON_CONFIG = {
bold: {
stripped: false,
inline: true,
block: false,
prefix: '[b]',
suffix: '[/b]',
top: '12px',
left: '15px',
content: BOLD_CONTENT,
},
italic: {
stripped: false,
inline: true,
block: false,
prefix: '[i]',
suffix: '[/i]',
top: '12px',
left: '16px',
content: ITALIC_CONTENT,
},
underline: {
stripped: false,
inline: true,
block: false,
prefix: '[u]',
suffix: '[/u]',
top: '12px',
left: '11px',
content: UNDERLINE_CONTENT,
},
strikethrough: {
stripped: false,
inline: true,
block: false,
prefix: '[s]',
suffix: '[/s]',
top: '12px',
left: '11px',
content: STRIKETHROUGH_CONTENT,
},
translation: {
stripped: true,
inline: true,
block: true,
prefix: '[tn]',
suffix: '[/tn]',
top: '10px',
left: '6px',
content: TRANSLATION_CONTENT,
},
spoiler: {
stripped: true,
inline: true,
block: true,
prefix: '[spoiler]',
suffix: '[/spoiler]',
top: '12px',
left: '10px',
content: SPOILER_CONTENT,
},
code: {
stripped: false,
inline: true,
block: true,
prefix: '[code]',
suffix: '[/code]',
top: '12px',
left: '14px',
content: CODE_CONTENT,
},
nodtext: {
stripped: false,
inline: true,
block: true,
prefix: '[nodtext]',
suffix: '[/nodtext]',
top: '4px',
left: '5px',
content: NODTEXT_CONTENT,
},
quote: {
stripped: true,
inline: false,
block: true,
prefix: '[quote]',
suffix: '[/quote]',
top: '12px',
left: '14px',
content: QUOTE_CONTENT,
},
expand: {
stripped: true,
inline: false,
block: true,
prefix: '[expand]',
suffix: '[/expand]',
top: '12px',
left: '10px',
content: EXPAND_CONTENT,
},
textile_link: {
stripped: false,
inline: true,
block: false,
prefix: '"',
suffix: '":[url]',
select_func: (text_area, _cursor_start, cursor_end)=>{
text_area.setSelectionRange(cursor_end - 4, cursor_end - 1);
},
top: '12px',
left: '12px',
content: TEXTILELINK_CONTENT,
},
wiki_link: {
stripped: false,
inline: true,
block: false,
prefix: '[[',
suffix: ']]',
top: '13px',
left: '11px',
content: WIKILINK_CONTENT,
},
named_link: {
stripped: false,
inline: true,
block: false,
prefix: '[[',
suffix: '|wiki_name]]',
select_func: (text_area, _cursor_start, cursor_end)=>{
text_area.setSelectionRange(cursor_end - 11, cursor_end - 2);
},
top: '11px',
left: '12px',
content: NAMEDLINK_CONTENT,
},
search_link: {
stripped: false,
inline: true,
block: false,
prefix: '{{',
suffix: '}}',
top: '13px',
left: '10px',
content: SEARCHLINK_CONTENT,
},
full_table: {
markup: (text_area)=>{TableMarkup(text_area, true);},
top: '12px',
left: '10px',
content: FULL_TABLE_CONTENT,
},
headless_table: {
markup: (text_area)=>{TableMarkup(text_area, false);},
top: '10px',
left: '10px',
content: HEADLESS_TABLE_CONTENT,
},
};
const ACTION_BUTTON_CONFIG = {
undo: {
action: (text_area)=>{UndoAction(text_area);},
top: '9px',
left: '9px',
content: UNDO_CONTENT,
},
redo: {
action: (text_area)=>{RedoAction(text_area);},
top: '9px',
left: '9px',
content: REDO_CONTENT,
},
};
const MARKUP_SECTION_CONFIG = {
font: {
color: 'beige',
buttons: ['bold', 'italic', 'underline', 'strikethrough'],
},
special: {
color: 'lightgreen',
buttons: ['translation', 'spoiler', 'code', 'nodtext'],
},
blocks: {
color: 'orange',
buttons: ['quote', 'expand'],
},
links: {
color: 'lightblue',
buttons: ['textile_link', 'wiki_link', 'named_link', 'search_link'],
},
tables: {
color: 'pink',
buttons: ['full_table', 'headless_table'],
},
};
const ACTION_SECTION_CONFIG = {
actions: {
color: 'lightgrey',
buttons: ['undo', 'redo'],
},
};
const DIALOG_CONFIG = {
Preview: CommentaryDtextPreview,
Edit: CommentaryDtextEdit,
};
//Other
const DTEXT_SELECTORS = {
comment: ['comment_body'],
forum: ['forum_topic_original_post_body', 'forum_post_body'],
wiki: ['wiki_page_body'],
pool: ['pool_description'],
dmail: ['dmail_body'],
};
/****Functions****/
//Auxiliary functions
function GetTextArea($obj) {
let $text_areas = $obj.closest('.ds-container').find('.ds-input');
if ($text_areas.length <= 1) {
var text_area = $text_areas.get(0);
} else {
text_area = (['TEXTAREA', 'INPUT'].includes(document.activeElement.tagName) && [...document.activeElement.classList].includes('ds-commentary-input') ? document.activeElement : null);
}
if (!text_area) {
JSPLib.notice.error("No text input selected.");
} else {
JSPLib.notice.close();
}
return text_area;
}
function MarkupSelectionText(text_area, config) {
if ([...text_area.classList].includes('string') && config.stripped) {
JSPLib.notice.notice("Block elements not available for inline DText.");
return false;
}
SaveMarkup(text_area);
let {prefix, suffix, block, inline, select_func} = config;
let start_text = text_area.value.slice(0, text_area.selectionStart);
let selection_text = text_area.value.slice(text_area.selectionStart, text_area.selectionEnd);
let end_text = text_area.value.slice(text_area.selectionEnd);
if ((block && !inline) || ((block && inline) && (selection_text.search('\n') >= 0))) {
let starting_CRs = start_text.replace(/ /g, "").match(/\n*$/)[0].length;
let ending_CRs = end_text.replace(/ /g, "").match(/^\n*/)[0].length;
start_text = start_text + '\n'.repeat(Math.max(2 - starting_CRs));
end_text = '\n'.repeat(Math.max(2 - ending_CRs)) + end_text;
var markup_text = prefix + '\n\n' + selection_text.trim() + '\n\n' + suffix;
} else {
markup_text = prefix + selection_text + suffix;
}
let cursor_start = start_text.length;
let cursor_end = cursor_start + markup_text.length;
let final_text = start_text + markup_text + end_text;
final_text = final_text.replace(/^\s+/, "");
text_area.value = final_text;
text_area.focus();
if (select_func) {
select_func(text_area, cursor_start, cursor_end);
} else {
text_area.setSelectionRange(cursor_start, cursor_end);
}
return true;
}
function TableMarkup(text_area, has_header) {
if ([...text_area.classList].includes('string')) {
JSPLib.notice.notice("Block elements not available for inline DText.");
return false;
}
SaveMarkup(text_area);
let final_text = text_area.value.slice(0, text_area.selectionStart);
let selection_text = text_area.value.slice(text_area.selectionStart, text_area.selectionEnd);
final_text += CSVtoDtextTable(selection_text, has_header);
final_text = final_text.replace(/^\s+/, "");
let cursor = final_text.length;
final_text += text_area.value.slice(text_area.selectionEnd);
text_area.value = final_text;
text_area.focus();
text_area.setSelectionRange(cursor, cursor);
return true;
}
function SaveMarkup(text_area) {
const printer = JSPLib.debug.getFunctionPrint('SaveMarkup');
let $text_area = $(text_area);
let undo_actions = $text_area.data('undo_actions') || [];
let undo_index = $text_area.data('undo_index') || 0;
undo_actions = undo_actions.slice(0, undo_index);
undo_actions.push(text_area.value);
$text_area.data('undo_actions', undo_actions);
$text_area.data('undo_index', undo_actions.length);
$text_area.data('undo_saved', true);
printer.debuglog('SaveMarkup', {undo_actions, undo_index});
}
function UndoAction(text_area) {
const printer = JSPLib.debug.getFunctionPrint('UndoAction');
let $text_area = $(text_area);
let {undo_actions = [], undo_index = 0, undo_saved} = $text_area.data();
if (undo_saved) {
undo_actions.push(text_area.value);
$text_area.data('undo_actions', undo_actions);
}
let undo_html = undo_actions.slice(undo_index - 1, undo_index)[0];
if (undo_html) {
text_area.value = undo_html;
} else {
JSPLib.notice.notice("Beginning of actions buffer reached.");
}
let new_index = Math.max(0, undo_index - 1);
$text_area.data('undo_index', new_index);
$text_area.data('undo_saved', false);
printer.debuglog('UndoAction', {undo_actions, undo_index, new_index});
return Boolean(undo_html);
}
function RedoAction(text_area) {
const printer = JSPLib.debug.getFunctionPrint('RedoAction');
let $text_area = $(text_area);
let {undo_actions = [], undo_index = 0} = $text_area.data();
let undo_html = undo_actions.slice(undo_index + 1, undo_index + 2)[0];
if (undo_html) {
text_area.value = undo_html;
} else {
JSPLib.notice.notice("End of actions buffer reached.");
}
let new_index = Math.min(undo_actions.length - 1, undo_index + 1);
$text_area.data('undo_index', new_index);
$text_area.data('undo_saved', false);
printer.debuglog('RedoAction', {undo_actions, undo_index, new_index});
return Boolean(undo_html);
}
function ClearActions(event) {
const printer = JSPLib.debug.getFunctionPrint('ClearActions');
let $text_area = $(event.currentTarget);
$text_area.data('undo_actions', []);
$text_area.data('undo_index', 0);
$text_area.data('undo_saved', false);
printer.debuglogLevel('Cleared actions.', JSPLib.debug.DEBUG);
}
function DisplayUploadCommentary(open) {
if (open) {
DS.$commentary_buttons.show();
DS.$markup_controls.show();
DS.$remove_button.show();
DS.$preview_button.show();
DS.$edit_button.hide();
} else {
DS.$commentary_buttons.hide();
DS.$markup_controls.hide();
DS.$preview_display.hide();
}
}
//Render functions
function RenderMarkupButton(type, name, config) {
let title = JSPLib.utility.displayCase(name);
return `${config.content} `;
}
function RenderSectionControls(type, section_config, button_config, available_controls) {
let header_html = "";
let button_html = "";
for (let section in section_config) {
let html = "";
let button_length = 0;
for (let i = 0; i < section_config[section].buttons.length; i++) {
let button = section_config[section].buttons[i];
if (!available_controls.includes(button)) continue;
html += RenderMarkupButton(type, button, button_config[button]);
button_length++;
}
if (button_length === 0) continue;
button_html += `${html}
`;
let width = (button_length * 40);
let color = section_config[section].color;
let name = (button_length > 1 ? JSPLib.utility.displayCase(section) : " ");
header_html += `${name}
`;
}
return [header_html, button_html];
}
function RenderMarkupControls() {
if (DS.available_dtext_markup.length === 0 && DS.available_dtext_actions.length === 0) return;
let header_html = "";
let button_html = "";
if (DS.available_dtext_markup.length) {
let [markup_header, markup_buttons] = RenderSectionControls('markup', MARKUP_SECTION_CONFIG, MARKUP_BUTTON_CONFIG, DS.available_dtext_markup);
header_html += `${markup_header}
`;
button_html += `${markup_buttons}
`;
}
if (DS.available_dtext_actions.length > 0) {
let [action_header, action_buttons] = RenderSectionControls('action', ACTION_SECTION_CONFIG, ACTION_BUTTON_CONFIG, DS.available_dtext_actions);
header_html += `${action_header}
`;
button_html += `${action_buttons}
`;
}
let width = ((DS.available_dtext_markup.length + DS.available_dtext_actions.length) * 40) + 20;
return JSPLib.utility.sprintf(MARKUP_CONTROLS, String(width), header_html, button_html);
}
function RenderUploadCommentary(identifier) {
let display_name = JSPLib.utility.displayCase(identifier);
return JSPLib.utility.regexReplace(UPLOAD_COMMENTARY_DESCRIPTION, {
DISPLAY: display_name,
IDENTIFIER: identifier,
});
}
function RenderDtextPreview(id, name, value, classes) {
return JSPLib.utility.regexReplace(DTEXT_TEXTAREA, {
CLASSES: classes,
IDENTIFIER: id,
NAME: name,
VALUE: value,
});
}
function RenderPreviewSection(name, has_header=false) {
let section_header = (has_header ? `` : "");
return JSPLib.utility.sprintf(PREVIEW_SECTION, name, section_header);
}
//Dtext functions
function CSVtoDtextTable(csvtext, has_header) {
const printer = JSPLib.debug.getFunctionPrint('CSVtoDtextTable');
let tabletext = "";
let sectiontext = "";
let csvdata = Papa.parse(csvtext);
printer.debuglog('CSVtoDtextTable', {csvdata, has_header});
csvdata.data.forEach((row, i)=>{
let rowtext = "";
row.forEach((col)=>{
if (i === 0 && has_header) {
rowtext += AddTableHeader(col);
} else {
rowtext += AddTableData(col);
}
});
sectiontext += AddTableRow(rowtext);
if (i === 0 && has_header) {
tabletext += AddTableHead(sectiontext);
sectiontext = "";
}
});
tabletext += AddTableBody(sectiontext);
return AddTable(tabletext);
}
function AddTable(input) {
return '[table]\n' + input + '[/table]\n';
}
function AddTableHead(input) {
return '[thead]\n' + input + '[/thead]\n';
}
function AddTableBody(input) {
return '[tbody]\n' + input + '[/tbody]\n';
}
function AddTableRow(input) {
return '[tr]\n' + input + '[/tr]\n';
}
function AddTableHeader(input) {
return '[th]' + input + '[/th]\n';
}
function AddTableData(input) {
return '[td]' + input + '[/td]\n';
}
//Network functions
function GetDtextPreview(body, inline) {
GetDtextPreview.memoized ??= {};
if (!(body in GetDtextPreview.memoized)) {
GetDtextPreview.memoized[body] = JSPLib.network.post('/dtext_preview', {data: {body, inline, disable_mentions: true, media_embeds: false}});
}
return GetDtextPreview.memoized[body];
}
//Event handlers
function DtextMarkup(event) {
let $button = $(event.currentTarget);
let text_area = GetTextArea($button);
if (!text_area) return;
let name = $button.attr('name');
let markup_func = (MARKUP_BUTTON_CONFIG[name].markup ? MARKUP_BUTTON_CONFIG[name].markup : MarkupSelectionText);
markup_func(text_area, MARKUP_BUTTON_CONFIG[name]);
}
function DtextAction(event) {
let $button = $(event.currentTarget);
let text_area = GetTextArea($button);
if (!text_area) return;
let name = $button.attr('name');
let action_func = ACTION_BUTTON_CONFIG[name].action;
action_func(text_area, ACTION_BUTTON_CONFIG[name]);
}
function OpenDialog() {
if (!DS.$add_commentary_dialog.data('initialized')) {
(DS.$dialog_buttons = DS.$add_commentary_dialog.closest('.ui-dialog').find('.ui-dialog-buttonset button'))
.each((_i, button)=>{
$(button).addClass('ds-button ds-dialog-button').attr('name', button.innerText);
});
DS.$preview_button = DS.$dialog_buttons.filter('[name=Preview]');
DS.$edit_button = DS.$dialog_buttons.filter('[name=Edit]');
DS.$add_commentary_dialog.data('initialized', true);
}
CommentaryDtextEdit();
}
function CommentaryDtextPreview() {
const printer = JSPLib.debug.getFunctionPrint('CommentaryDtextPreview');
if (DS.controller === 'posts') {
let {height} = getComputedStyle(DS.$add_commentary_dialog.get(0));
DS.$add_commentary_dialog.css({height});
}
DS.$preview_button.hide();
DS.$edit_button.show();
DS.$edit_commentary.hide();
DS.$preview_display.show();
DS.$preview_display.find('.ds-section').html(LOADING_MESSAGE);
let promise_array = [];
let preview_array = [];
(DS.$commentary_input ||= $('.ds-commentary-input')).each((_i, input)=>{
let {section, part} = $(input).data();
let preview = {section, part};
let inline = preview.inline = [...input.classList].includes('string');
let body = preview.original_body = input.value;
var promise;
if (body.trim(/\s+/).length > 0) {
promise = GetDtextPreview(body, inline);
} else {
promise = Promise.resolve(null);
}
promise_array.push(promise);
preview_array.push(preview);
});
Promise.all(promise_array).then((data)=>{
data.forEach((body, i)=>{
preview_array[i].rendered_body = body;
});
['original', 'translated'].forEach((section)=>{
let $display = DS.$preview_display.find(`[data-section=${section}]`);
let $section = $display.find('.ds-section').html("");
['title', 'description'].forEach((part)=>{
let preview = preview_array.find((item) => (item.section === section && item.part === part));
if (!preview?.rendered_body) return;
if (part === 'title') {
$section.append(`${preview.rendered_body} `);
} else if (part === 'description') {
$section.append(`${preview.rendered_body}
`);
}
});
});
printer.debuglog(preview_array);
});
DS.mode = 'preview';
}
function CommentaryDtextEdit() {
if (DS.controller === 'posts') {
DS.$add_commentary_dialog.css('height', 'auto');
}
DS.$preview_button.show();
DS.$edit_button.hide();
DS.$preview_display.hide();
DS.$edit_commentary.show();
DS.mode = 'edit';
}
function GeneralDtextPreview(event) {
let $container = $(event.target).closest('form').find('.ds-container');
DS.size_observer.disconnect($container.find('.ds-edit-dtext').get(0));
let $preview = $container.find('.ds-preview-dtext').show();
$container.find('.ds-edit-dtext').hide();
let body = $container.find('.ds-edit-dtext textarea').val() ?? "";
if (body.trim().length > 0) {
$preview.html(LOADING_MESSAGE);
GetDtextPreview(body, false).then((preview_html) => {
$preview.html(preview_html);
});
}
$(event.target).hide();
$(event.target).parent().find('button.ds-edit-preview').show();
event.preventDefault();
}
function GeneralDtextEdit(event) {
let $container = $(event.target).closest('form').find('.ds-container');
DS.size_observer.observe($container.find('.ds-edit-dtext').get(0));
$container.find('.ds-preview-dtext').hide();
$container.find('.ds-edit-dtext').show();
$(event.target).hide();
$(event.target).parent().find('button.ds-show-preview').show();
event.preventDefault();
}
function ToggleCommentary() {
if (DS.commentary_open) {
DS.$toggle_artist_commentary.text('show »');
DS.$artist_commentary.slideUp();
DS.$upload_commentary_translation_container.slideUp();
DisplayUploadCommentary(false);
} else {
DS.$toggle_artist_commentary.text('« hide');
DS.$artist_commentary.slideDown();
DS.$upload_commentary_translation_container.slideDown();
DisplayUploadCommentary(true);
}
DS.commentary_open = !DS.commentary_open;
DS.mode = 'edit';
}
function ToggleTranslation() {
DS.$translation_edit_commentary ||= DS.$upload_commentary_translation_container.find('.ds-edit-commentary');
DS.$translation_preview_display ||= DS.$upload_commentary_translation_container.find('.ds-preview-display');
if (DS.translation_open) {
DS.$toggle_commentary_translation.text('show »');
DS.$translation_edit_commentary.slideUp();
DS.$translation_preview_display.slideUp();
} else {
DS.$toggle_commentary_translation.text('« hide');
if (DS.mode === 'preview') {
DS.$translation_preview_display.slideDown();
} else if (DS.mode === 'edit') {
DS.$translation_edit_commentary.slideDown();
}
}
DS.translation_open = !DS.translation_open;
}
function ResizeDtextPreview(resizes, observer) {
for (let i = 0; i < resizes.length; i++) {
if (resizes[i].contentRect.height > 0) {
$(resizes[i].target).closest('.ds-container').find('.ds-preview-dtext').css({height: resizes[i].contentRect.height, width: resizes[i].contentRect.width});
}
}
}
//Initialize
function InitializeButtons($button_container) {
let all_configs = Object.assign({}, MARKUP_BUTTON_CONFIG, ACTION_BUTTON_CONFIG);
for (let key in all_configs) {
let {top, left} = all_configs[key];
$button_container.find(`button[name=${key}] > *:first-of-type`)
.css({top, left});
}
$button_container.find('.dtext-markup').on(JSPLib.program.click, DtextMarkup);
$button_container.find('.dtext-action').on(JSPLib.program.click, DtextAction);
JSPLib.utility.blockActiveElementSwitch('.dtext-markup, .dtext-action');
}
function InitializeAutocomplete(selector) {
const printer = JSPLib.debug.getFunctionPrint('InitializeAutocomplete');
JSPLib.load.scriptWaitExecute(DS, 'IAC', {
available: () => {
DS.IAC.InitializeProgramValues(true);
DS.IAC.InitializeTextAreaAutocomplete(selector);
printer.debuglogLevel(`Initialized IAC textarea autocomplete: ${selector}`, JSPLib.debug.DEBUG);
},
fallback: () => {
printer.debuglogLevel(`Unable to initialize textarea autocomplete: ${selector}`, JSPLib.debug.DEBUG);
},
});
}
function InitializeDtextPreviews() {
const printer = JSPLib.debug.getFunctionPrint('InitializeDtextPreviews');
let containers = JSPLib.utility.multiConcat(...DS.dtext_types_handled.map((type) => DTEXT_SELECTORS[type]));
let final_selector = JSPLib.utility.joinList(containers, '.', ' .dtext-editor textarea', ', ');
let textarea_selectors = [];
for (let type in DTEXT_SELECTORS) {
DTEXT_SELECTORS[type].forEach((classname) => {
let selector = `.${classname} .dtext-editor textarea`;
let $textareas = $(selector);
if ($textareas.length === 0) return;
textarea_selectors.push(selector);
$textareas.each((_i, textarea)=>{
let $textarea = $(textarea);
let $container = $textarea.closest('.input.dtext');
let $form = $container.closest('form');
$container.addClass('ds-container');
let {id, name} = JSPLib.utility.getAttr(textarea, ['id', 'name']);
let value = $textarea.val() ?? "";
let classes = ($container.find('.dtext-editor-large').length ? 'dtext-editor-large' : "");
$container.html(RenderDtextPreview(id, name, value, classes));
$container.prepend(RenderMarkupControls());
InitializeButtons($container.find('.ds-buttons'));
DS.size_observer.observe($container.find('.ds-edit-dtext').get(0));
$container.find('.ds-input').on(JSPLib.program.keyup, ClearActions);
let $controls = $form.children().eq(-1);
var $submit_control;
if ($controls.get(0).tagName === 'INPUT') {
$submit_control = $controls;
$controls = $('
')
$controls.append($submit_control.detach());
$form.append($controls);
} else {
$submit_control = $controls.find('input[type=submit]');
}
$submit_control.after(CONTROL_BUTTONS);
$controls.find('.ds-show-preview').on(JSPLib.program.click, GeneralDtextPreview);
$controls.find('.ds-edit-preview').on(JSPLib.program.click, GeneralDtextEdit);
});
});
}
if(textarea_selectors.length) {
InitializeAutocomplete(textarea_selectors.join(', '));
}
}
function InitializeCommentaryDialog() {
DS.$add_commentary_dialog = $('#add-commentary-dialog');
if (DS.$add_commentary_dialog.length === 0) return;
DS.$add_commentary_dialog.addClass('ds-container');
DS.$add_commentary_dialog.find('#fetch-commentary')
.after(RenderMarkupControls());
InitializeButtons(DS.$add_commentary_dialog.find('.ds-buttons'));
DS.$preview_display = $('
');
DS.$preview_display.append(RenderPreviewSection('original', true));
DS.$preview_display.append(RenderPreviewSection('translated', true));
DS.$edit_commentary = $('#edit-commentary').after(DS.$preview_display);
DS.$add_commentary_dialog.data('initialized', false);
['original', 'translated'].forEach((section)=>{
['title', 'description'].forEach((part)=>{
$(`#artist_commentary_${section}_${part}`).data({section, part}).addClass('ds-input ds-commentary-input');
});
});
//Wait for the dialog to be initialized before performing the final step
JSPLib.utility.DOMWaitExecute({
name: "commentary dialog initialization",
data_check: {
selector: '#add-commentary-dialog',
key: 'uiDialog',
},
interval: 500,
duration: JSPLib.utility.one_second * 15,
found: () => {
let buttons = DS.$add_commentary_dialog.dialog('option', 'buttons');
buttons = Object.assign(DIALOG_CONFIG, buttons);
let dialog_width = Math.max((DS.available_dtext_markup.length + DS.available_dtext_actions.length) * 40 + 50, DS.$add_commentary_dialog.dialog('option', 'width'));
DS.$add_commentary_dialog.dialog('option', 'buttons', buttons);
DS.$add_commentary_dialog.dialog('option', 'width', dialog_width);
DS.$add_commentary_dialog.on('dialogopen.ds', OpenDialog);
},
});
DS.$add_commentary_dialog.find('.ds-input').on(JSPLib.program.keyup, ClearActions);
InitializeAutocomplete('#edit-commentary .ds-input');
}
function InitializeUploadCommentary() {
const setOverallContainerHeight = function () {
let {height} = getComputedStyle(DS.$overall_container.get(0));
DS.$overall_container.css({height});
};
let description = $(".post_artist_commentary_original_description .dtext-editor").get(0).editor?.dtext ?? "";
$('.post_artist_commentary_original_description, .post_artist_commentary_translated_description').remove();
DS.$overall_container = $('
');
DS.$edit_commentary = $('
');
DS.$preview_display = $('
').hide();
DS.$markup_controls = $(RenderMarkupControls());
DS.$commentary_buttons = $(PREVIEW_BUTTONS);
DS.$edit_commentary.append($('.post_artist_commentary_original_title').detach());
DS.$edit_commentary.append(RenderUploadCommentary('original_description'));
DS.$edit_commentary.append($('.post_artist_commentary_translated_title').detach());
DS.$edit_commentary.append(RenderUploadCommentary('translated_description'));
DS.$preview_display.append(RenderPreviewSection('original', true));
DS.$preview_display.append(RenderPreviewSection('translated', true));
DS.$overall_container.append(DS.$edit_commentary);
DS.$overall_container.append(DS.$preview_display);
$('div.source-tab').addClass('ds-container');
$('div.source-tab').append(DS.$markup_controls);
$('div.source-tab').append(DS.$overall_container);
$('div.source-tab').append(DS.$commentary_buttons);
InitializeButtons(DS.$markup_controls.find('.ds-buttons'));
$("#post_artist_commentary_original_description").val(description);
['original', 'translated'].forEach((section)=>{
['title', 'description'].forEach((part)=>{
$(`#post_artist_commentary_${section}_${part}`).data({section, part}).addClass('ds-input ds-commentary-input');
});
});
DS.$preview_button = $('#ds-preview-button').on(JSPLib.program.click, CommentaryDtextPreview);
DS.$edit_button = $('#ds-edit-button').on(JSPLib.program.click, CommentaryDtextEdit).hide();
let $source_tab_link = $('a.source-tab');
if ($source_tab_link.hasClass('active-tab')) {
setOverallContainerHeight();
} else {
$source_tab_link.one(JSPLib.program.click, setOverallContainerHeight);
}
DS.$edit_commentary.find('.ds-input').on(JSPLib.program.keyup, ClearActions);
InitializeAutocomplete('.source-tab .ds-input');
JSPLib.utility.setPropertyTrap($(".post_artist_commentary_original_description .dtext-editor").get(0), 'editor', {
setter: (prop, value) => {
if (prop === 'dtext') {
$("#post_artist_commentary_original_description").val(value);
}
},
value: {dtext: description},
});
}
// Settings functions
function InitializeProgramValues() {
Object.assign(DS, {
size_observer: new ResizeObserver(ResizeDtextPreview),
});
JSPLib.load.setProgramGetter(DS, 'IAC', 'IndexedAutocomplete', 29.32);
return true;
}
function RenderSettingsMenu() {
$('#dtext-styler').append(JSPLib.menu.renderMenuFramework(MENU_CONFIG));
$("#ds-general-settings").append(JSPLib.menu.renderDomainSelectors());
$("#ds-main-settings").append(JSPLib.menu.renderInputSelectors('dtext_types_handled', 'checkbox'));
$('#ds-commentary-settings').append(JSPLib.menu.renderCheckbox('post_commentary_enabled'));
$('#ds-commentary-settings').append(JSPLib.menu.renderCheckbox('upload_commentary_enabled'));
$("#ds-controls-settings").append(JSPLib.menu.renderInputSelectors('available_dtext_markup', 'checkbox'));
$("#ds-controls-settings").append(JSPLib.menu.renderInputSelectors('available_dtext_actions', 'checkbox'));
JSPLib.menu.engageUI(true);
JSPLib.menu.saveUserSettingsClick();
JSPLib.menu.resetUserSettingsClick();
}
//Main program
function Main() {
const preload = {
run_on_settings: false,
default_data: DEFAULT_VALUES,
initialize_func: InitializeProgramValues,
render_menu_func: RenderSettingsMenu,
program_css: PROGRAM_CSS,
light_css: LIGHT_MODE_CSS,
dark_css: DARK_MODE_CSS,
menu_css: MENU_CSS,
};
if (!JSPLib.menu.preloadScript(DS, preload)) return;
if (DS.dtext_types_handled.length) {
InitializeDtextPreviews();
}
if (DS.post_commentary_enabled && DS.controller === 'posts' && DS.action === 'show') {
InitializeCommentaryDialog();
} else if (DS.upload_commentary_enabled && (DS.action === 'show' && ['uploads', 'upload-media-assets'].includes(DS.controller))) {
InitializeUploadCommentary();
}
}
/****Initialization****/
//Variables for JSPLib
JSPLib.program_name = PROGRAM_NAME;
JSPLib.program_shortcut = PROGRAM_SHORTCUT;
JSPLib.program_data = DS;
//Variables for debug.js
JSPLib.debug.mode = false;
JSPLib.debug.level = JSPLib.debug.INFO;
//Variables for menu.js
JSPLib.menu.settings_config = SETTINGS_CONFIG;
//Export JSPLib
JSPLib.load.exportData();
/****Execution start****/
JSPLib.load.programInitialize(Main, {required_variables: PROGRAM_LOAD_REQUIRED_VARIABLES, optional_selectors: PROGRAM_LOAD_OPTIONAL_SELECTORS});