// ==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 = `
%s
%s
`; const PREVIEW_SECTION = `
%s
`; const PREVIEW_BUTTONS = `
`; const CONTROL_BUTTONS = ` ` 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 ``; } 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 ? `
${JSPLib.utility.displayCase(name)}
` : ""); 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});