// ==UserScript== // @name PostModeMenu+ // @namespace https://github.com/BrokenEagle // @version 9.10 // @description Provide additional functions on the post mode menu. // @source https://danbooru.donmai.us/users/23799 // @author BrokenEagle // @match https://*.donmai.us/* // @exclude /^(?!https:\/\/\w+\.donmai\.us\/?(posts|settings)?\/?(\?|$)).*/ // @exclude /^https://\w+\.donmai\.us/.*\.(xml|json|atom)(\?|$)/ // @grant none // @run-at document-end // @downloadURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/PostModeMenuPlus.user.js // @updateURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/PostModeMenuPlus.user.js // @require https://cdn.jsdelivr.net/npm/dragselect@2.3.1/dist/ds.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/notice.js // @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/network.js // @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20251218/lib/danbooru.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 $ Danbooru JSPLib DragSelect */ /****Library updates****/ ////NONE /****Global variables****/ //Exterior script variables const DANBOORU_TOPIC_ID = '21812'; //Variables for load.js const PROGRAM_LOAD_REQUIRED_VARIABLES = ['window.jQuery', 'window.Danbooru', 'Danbooru.Utility', 'Danbooru.CurrentUser', 'Danbooru.Autocomplete']; const PROGRAM_LOAD_OPTIONAL_SELECTORS = ['#c-posts #a-index #mode-box', '#c-users #a-edit']; //Program name constants const PROGRAM_NAME = 'PostModeMenu'; const PROGRAM_SHORTCUT = 'pmm'; //Program variable const PMM = {}; //Available setting values const SUPPORTED_MODES = ['edit', 'tag_script', 'commentary', 'copy_ID', 'copy_short', 'copy_link', 'vote_up', 'vote_down', 'unvote', 'favorite', 'unfavorite']; const DRAGGABLE_MODES = ['tag-script', 'commentary', 'copy-id', 'copy-short', 'copy-link', 'vote-up', 'vote-down', 'unvote', 'favorite', 'unfavorite']; const ID_SEPARATORS = ['comma', 'colon', 'semicolon', 'space', 'return']; //Main settings const SETTINGS_CONFIG = { available_modes: { allitems: SUPPORTED_MODES, reset: SUPPORTED_MODES, validate: (data) => JSPLib.menu.validateCheckboxRadio(data, 'checkbox', SUPPORTED_MODES), hint: "Select to enable script support/availability on selected modes." }, mode_order: { allitems: SUPPORTED_MODES, reset: SUPPORTED_MODES, sortvalue: true, validate: (data) => JSPLib.utility.arrayEquals(data, SUPPORTED_MODES), hint: "Set the order for how actions appear in the mode menu. Note: view will still always be first." }, maximum_concurrent_requests: { reset: 5, parse: parseInt, validate: (data) => (Number.isInteger(data) && data > 0), hint: "Determines how many requests will be sent at a time, while the remaining requests wait their turn." }, id_separator: { display: "ID Separator", allitems: ID_SEPARATORS, reset: ['comma'], validate: (data) => JSPLib.menu.validateCheckboxRadio(data, 'radio', ID_SEPARATORS), hint: "Choose how to separate multiple post IDs copied with Copy ID, Copy Short, or Copy Link." }, edit_tag_grouping_enabled: { reset: false, validate: JSPLib.utility.isBoolean, hint: "Groups tags the same way as on the post's main page. (network: 1)" }, autoload_post_commentary_enabled: { reset: false, validate: JSPLib.utility.isBoolean, hint: "Autoloads the commentary when a single post is selected. (network: 1)" }, safe_tag_script_enabled: { reset: false, validate: JSPLib.utility.isBoolean, hint: "Unsets the tag script mode when navigating to a new page." }, long_searchbar_enabled: { reset: false, validate: JSPLib.utility.isBoolean, hint: "Adds additional CSS which repositions the searchbar and has it span the entire screen." }, long_tagscript_enabled: { reset: false, validate: JSPLib.utility.isBoolean, hint: "Adds additional CSS which makes the tagscript bar span the entire screen when selected." }, highlight_errors_enabled: { reset: false, validate: JSPLib.utility.isBoolean, hint: "Adds visualization to the specific posts when network errors occur." }, drag_select_enabled: { reset: true, validate: JSPLib.utility.isBoolean, hint: "Turns on being able to drag select, allowing multiple posts to be processed at once." }, }; const MENU_CONFIG = { topic_id: DANBOORU_TOPIC_ID, settings: [{ name: 'general', }, { name: 'mode', }, { name: 'option', message: "Some options require additional networks calls, which can add latency to the process. These are denoted when present, as well as the sequential amount." }, { name: 'network', }, { name: 'select', }, { name: 'interface', }], controls: [], }; //Default values const DEFAULT_VALUES = { pinned: false, post_votes: {}, post_favorites: {}, }; //CSS constants const PROGRAM_CSS = ` /**GENERAL**/ .pmm-dialog label { display: block; font-weight: bold; } /**POSTS**/ div#posts { margin: -1em; padding: 1em; } article.post-preview { border: solid 1px transparent; padding-top: 10px; } article.pmm-selected { border: solid 1px; } /**MODE BOX**/ section#pmm-mode-box { position: relative; padding: 5px; border: 1px solid; } div#pmm-mode-controls { display: flex; align-items: flex-end; margin-bottom: 2px; justify-content: space-between; } div#pmm-mode-select { width: 44%; } div#pmm-mode-select h2 { text-align: right; } div#pmm-mode-select select { width: 100%; font-size: 12px; } /**SELECT CONTROLS**/ div#pmm-select-controls { width: 54%; } div#pmm-select-only-input label { cursor: pointer; user-select: none; display: block; font-size: 12px; font-weight: bold; border: 1px solid; border-radius: 25px; padding: 3px; margin: 3px; text-align: center; } div#pmm-select-only-input label.pmm-disabled { cursor: default; } div#pmm-select-only-input input { margin-left: 0.25em; vertical-align: middle; } div#pmm-selection-buttons { display: flex; justify-content: space-around; } div#pmm-selection-buttons button.pmm-select { font-size: 10px; width: 30%; padding: 1px; border-radius: 3px; border: 1px solid; } div#pmm-selection-buttons button.pmm-select:disabled { cursor: default; } /**EDIT**/ #pmm-edit-dialog textarea { height: 20em; } /**TAG SCRIPT**/ div#pmm-tag-script-field input { width: 100%; } div#pmm-tag-script-field input.pmm-long-focus:focus { width: 95vw; z-index: 10; position: relative; } /**APPLY**/ div#pmm-apply-all button { width: 100%; margin: 0.2em 0; border: 2px solid; font-weight: bold; border-radius: 10px; } div#pmm-apply-all button:disabled { cursor: default; } /**PIN**/ button#pmm-undock { padding: 0 5px; position: absolute; top: 4px; left: 4px; } button#pmm-undock > span { height: 16px; width: 16px; display: inline-block; } /**COMMENTARY**/ #pmm-commentary-dialog > div { margin-bottom: 0.5em; } #pmm-commentary-dialog textarea { height: 15em; } #pmm-fetch input { width: 25%; } div#pmm-commentary-tags { display: flex; flex-wrap: wrap; justify-content: flex-start; } div.pmm-commentary-tag { width: 36%; margin-left: 1em; } div.pmm-commentary-tag label { cursor: pointer; user-select: none; display: block; font-size: 12px; font-weight: bold; border: 1px solid; border-radius: 25px; padding: 3px 5.5em 3px 3px; margin: 3px; text-align: right; position: relative; } div.pmm-commentary-tag input { position: absolute; right: 3em; top: 25%; }`; const LIGHT_MODE_CSS = ` article.pmm-selected { background-color: var(--grey-1); border-color: var(--grey-2); } article.pmm-selected.pmm-editing { background-color: var(--red-1); border-color: var(--red-2); } section#pmm-mode-box { background-color: var(--white); border-color: var(--grey-2); } div#pmm-select-only-input label { border-color: var(--blue-3); background-color: var(--blue-2); } div#pmm-select-only-input label:hover { border-color: var(--blue-4); background-color: var(--blue-3); } div#pmm-select-only-input label.pmm-disabled { color: var(--grey-5); border-color: var(--blue-2); background-color: var(--blue-1); } button#pmm-undock, div#pmm-selection-buttons button.pmm-select { color: var(--black); background-color: var(--grey-1); border-color: var(--grey-2); } button#pmm-undock:hover, div#pmm-selection-buttons button.pmm-select:hover { background-color: var(--white); } div#pmm-selection-buttons button.pmm-select:disabled { color: var(--grey-5); background-color: var(--white); border-color: var(--grey-1); } div#pmm-apply-all button { border-color: var(--green-5); background-color: var(--green-4); color: var(--white); } div#pmm-apply-all button:hover { border-color: var(--green-3); background-color: var(--green-3); box-shadow: 0 0 0 1px var(--green-4); } div#pmm-apply-all button:disabled { color: var(--grey-3); border-color: var(--green-2); background-color: var(--green-1); box-shadow: none; } button#pmm-undock { border-color: var(--grey-2); background-color: var(--grey-1); } button#pmm-undock:hover { background-color: var(--white); } section#pmm-placeholder { background-color: var(--grey-1); } div.pmm-commentary-tag label { border-color: var(--grey-2); background-color: var(--grey-1); } div.pmm-commentary-tag label:hover { border-color: var(--grey-3); background-color: var(--grey-2); } div.pmm-commentary-tag.pmm-disabled label { color: var(--grey-4); border-color: var(--grey-1); background-color: var(--grey-0); cursor: wait; } div.pmm-commentary-tag.pmm-active label { color: var(--white); border-color: var(--blue-6); background-color: var(--blue-5); } div.pmm-commentary-tag.pmm-active label:hover { border-color: var(--blue-7); background-color: var(--blue-6); } div.pmm-commentary-tag.pmm-active.pmm-disabled label { color: var(--grey-1); border-color: var(--blue-4); background-color: var(--blue-3); }`; const DARK_MODE_CSS = ` article.pmm-selected { background-color: var(--grey-8); border-color: var(--grey-7); } article.pmm-selected.pmm-editing { background-color: var(--red-9); border-color: var(--red-8); } section#pmm-mode-box { background-color: var(--grey-9); border-color: var(--grey-7); } div#pmm-select-only-input label { border-color: var(--blue-7); background-color: var(--blue-8); } div#pmm-select-only-input label:hover { border-color: var(--blue-6); background-color: var(--blue-7); } div#pmm-select-only-input label.pmm-disabled { color: var(--grey-5); border-color: var(--blue-8); background-color: var(--blue-9); } div#pmm-selection-buttons button.pmm-select { color: var(--white); background-color: var(--grey-7); border-color: var(--grey-6); } div#pmm-selection-buttons button.pmm-select:hover { background-color: var(--grey-6); } div#pmm-selection-buttons button.pmm-select:disabled { color: var(--grey-5); background-color: var(--grey-9); border-color: var(--grey-8); } div#pmm-apply-all button { color: var(--white); border-color: var(--green-5); background-color: var(--green-6); } div#pmm-apply-all button:hover { border-color: var(--green-4); background-color: var(--green-5); box-shadow: 0 0 0 1px var(--green-4); } div#pmm-apply-all button:disabled { color: var(--grey-5); border-color: var(--green-8); background-color: var(--green-9); box-shadow: none; } button#pmm-undock { border-color: var(--grey-4); background-color: var(--grey-3); } button#pmm-undock:hover { background-color: var(--grey-2); } section#pmm-placeholder { background-color: var(--grey-8); } div.pmm-commentary-tag label { border-color: var(--grey-5); background-color: var(--grey-6); } div.pmm-commentary-tag label:hover { border-color: var(--grey-4); background-color: var(--grey-5); } div.pmm-commentary-tag.pmm-disabled label { color: var(--grey-4); border-color: var(--grey-6); background-color: var(--grey-7); cursor: wait; } div.pmm-commentary-tag.pmm-active label { color: var(--white); border-color: var(--blue-6); background-color: var(--blue-5); } div.pmm-commentary-tag.pmm-active label:hover { border-color: var(--blue-5); background-color: var(--blue-4); } div.pmm-commentary-tag.pmm-active.pmm-disabled label { color: var(--grey-2); border-color: var(--blue-8); background-color: var(--blue-7); }`; const SEARCHBAR_CSS = ` @media screen and (min-width: 661px){ /* Position the main side bar down and make it relative to allow absolute positioning. */ #c-posts #sidebar { margin-top: 4em; position: relative; } /* Push the content area down so that it doesn't overlap with the search bar. */ #c-posts #content { margin-top: 4em; } /* Move the search box down. */ #c-posts #search-box { position: absolute; top: -4em; } /*Screen-wide search bar*/ #c-posts #search-box-form, #c-posts #tags { width: 95vw; } }`; const MENU_CSS = ` .jsplib-selectors.pmm-selectors[data-setting="available_modes"] label { width: 132px; } .jsplib-selectors.pmm-selectors[data-setting="id_separator"] label { width: 120px; }`; //HTML constants const MODE_CONTROLS_HTML = `

Mode

`; const EDIT_DIALOG_HTML = `
`; const COMMENTARY_DIALOG_HTML = `
`; const MODE_SETTINGS_DETAILS = ` `; //Other constants const EDIT_DIALOG_SETTINGS = { title: "Post Edit", classes: { 'ui-dialog': 'pmm-dialog', }, autoOpen: false, modal: false, width: 1000, }; const COMMENTARY_DIALOG_SETTINGS = { title: 'Edit Commentaries', classes: { 'ui-dialog': 'pmm-dialog', }, autoOpen: false, modal: true, width: 700, }; const SEPARATOR_DICT = { comma: ',', colon: ':', semicolon: ';', space: ' ', return: '\n', }; const ACTION_DICT = { 'vote-up': 'already been voted up', 'vote-down': 'already been voted down', 'unvote': 'not been voted on', 'favorite': 'already been favorited', 'unfavorite': 'not been favorited', }; const POST_PARENT_FIELDS = 'parent_id'; const POST_CATEGORY_FIELDS = 'tag_string_artist,tag_string_copyright,tag_string_character,tag_string_meta,tag_string_general'; const POST_VOTE_FIELDS = 'id,post_id,score'; const POOL_FIELDS = 'post_ids'; const ARTIST_COMMENTARY_FIELDS = 'original_title,original_description,translated_title,translated_description,post[tag_string_meta]'; const GOLD_LEVEL = 30; /****Functions****/ //Helper functions function GetAction() { return ACTION_DICT[PMM.mode]; } function AlreadyActionNotice(post_id, singular) { if (singular) { JSPLib.notice.error(`post #${post_id} has ${GetAction()}.`); } } function EnableEditInterface() { PMM.edit_dialog.find('input, button, textarea').attr('disabled', null); $('#pmm-edit-submit, #pmm-edit-validate').attr('disabled', null); } function DisableEditInterface() { PMM.edit_dialog.find('input, button, textarea').attr('disabled', 'disabled'); $('#pmm-edit-submit, #pmm-edit-validate').attr('disabled', 'disabled'); } function EnableCommentaryInterface() { PMM.commentary_dialog.find('input, button, textarea').attr('disabled', null); PMM.commentary_dialog.find('.pmm-commentary-tag').removeClass('pmm-disabled'); $('#pmm-commentary-submit').attr('disabled', null); } function DisableCommentaryInterface() { PMM.commentary_dialog.find('input, button, textarea').attr('disabled', 'disabled'); PMM.commentary_dialog.find('.pmm-commentary-tag').addClass('pmm-disabled'); $('#pmm-commentary-submit').attr('disabled', 'disabled'); } function GetCurrentScriptID() { return JSPLib.storage.getLocalData('current_tag_script_id', {default_val: 1}); } function GetCurrentTagScript() { return localStorage.getItem("tag-script-" + GetCurrentScriptID()); } function CopyToClipboard(post_ids, prefix, suffix, separator, afterspace) { if (afterspace && !['\n', ' '].includes(separator)) { separator += " "; } let post_string = JSPLib.utility.joinList(post_ids, prefix, suffix, separator); Danbooru.Utility.copyToClipboard(post_string); } function CoordinateInBox(coord, box) { return coord.x > box.left && coord.x < box.right && coord.y > box.top && coord.y < box.bottom; } function AreTagsEdited() { let tag_string = $('#pmm-tag-string textarea').val(); let old_tag_string = $(`#post_${PMM.edit_post_id}`).data('tags'); let normalized_tag_string = tag_string.trim().split(/\s+/).toSorted().join(' '); return normalized_tag_string !== old_tag_string; } async function ValidateTags() { if (!PMM.use_VTI) return true; let statuses = await Promise.all([PMM.VTI.ValidateTagAdds(), PMM.VTI.ValidateTagRemoves(), PMM.VTI.ValidateTagDeprecations()]); return statuses.every((item) => item); } //Relationship functions async function ParentPostCheck(post_ids) { const printer = JSPLib.debug.getFunctionPrint('ParentPostCheck'); let parent_id = null; let child_ids = []; for (let i = 0; i < post_ids.length; i++) { let $post = $(`#post_${post_ids[i]}`); if ($post.hasClass('post-status-has-children')) { //Can include at most one parent into the selection if (parent_id !== null) { printer.debuglogLevel("Multiple parents found.", JSPLib.debug.INFO); return null; } parent_id = post_ids[i]; } else if ($post.hasClass('post-status-has-parent')) { child_ids.push(post_ids[i]); } else { //Early bail when post has no parent or children printer.debuglogLevel("Post found without parent/child:", post_ids[i], JSPLib.debug.INFO); return null; } } if (child_ids.length === 0) { printer.debuglogLevel("No children found.", JSPLib.debug.INFO); return null; } let posts = await JSPLib.danbooru.submitRequest('posts', {tags: `id:${child_ids.join(',')} status:any`, limit: child_ids.length, only: POST_PARENT_FIELDS}); printer.debuglogLevel("Parents found:", posts, JSPLib.debug.DEBUG); let parent_ids = JSPLib.utility.getObjectAttributes(posts, 'parent_id'); //Must have only a single parent if (JSPLib.utility.arrayUnique(parent_ids).length !== 1) { printer.debuglogLevel("Multiple parents found.", JSPLib.debug.INFO); return null; } //If the parent was included, it must match the children if (parent_id !== null && parent_id !== parent_ids[0]) { printer.debuglogLevel("Parent does not match children:", parent_id, parent_ids[0], JSPLib.debug.INFO); return null; } return parent_ids[0]; } async function PoolPostCheck(post_ids) { const printer = JSPLib.debug.getFunctionPrint('PoolPostCheck'); let pools = await JSPLib.danbooru.submitRequest('pools', {search: {post_ids_include_all: post_ids.join(' '), category: 'series'}, only: POOL_FIELDS}); printer.debuglogLevel("Pools found:", pools, JSPLib.debug.DEBUG); if (pools.length !== 1) { return null; } return pools[0].post_ids[0]; } function DestroyTooltip(post_id) { $(`#post_${post_id} .post-preview-image`).get(0)._tippy?.destroy(); } //Update functions function UpdateModeMenu(primary = true) { PMM.mode = $("#pmm-mode-box select").val(); UpdateSelectControls(); $('.pmm-selected').removeClass('pmm-selected'); if (PMM.drag_select_enabled) { UpdateDraggerStatus(); } if (PMM.edit_dialog?.dialog('isOpen')) { PMM.edit_dialog.dialog('close'); } if (['favorite', 'unfavorite'].includes(PMM.mode)) { PreloadPostFavorites(); } if (PMM.mode === 'unvote') { PreloadPostVotes(); } if (primary) { JSPLib.storage.setLocalData('pmm-mode', PMM.mode); PMM.channel.postMessage({type: 'change_mode', mode: PMM.mode}); } } function UpdateSelectControls() { if (PMM.mode === 'tag-script') { $('#pmm-tag-script-field').show(); } else { $('#pmm-tag-script-field').hide(); } if (['edit', 'view'].includes(PMM.mode)) { $('#pmm-select-only-input input, #pmm-selection-buttons button, #pmm-apply-all button').attr('disabled', 'disabled'); $('#pmm-select-only-input label').addClass('pmm-disabled'); } else { $('#pmm-select-only-input input, #pmm-selection-buttons button, #pmm-apply-all button').attr('disabled', null); $('#pmm-select-only-input label').removeClass('pmm-disabled'); if (!PMM.select_only) { $('#pmm-selection-buttons button, #pmm-apply-all button').attr('disabled', 'disabled'); } } } function UpdateSelectOnly(primary = true) { PMM.select_only = $('#pmm-select-only-input input').prop('checked'); UpdateSelectControls(); $('.pmm-selected').removeClass('pmm-selected'); if (primary) { JSPLib.storage.setLocalData('pmm-select-only', PMM.select_only); PMM.channel.postMessage({type: 'change_select_only', select_only: PMM.select_only}); } } function UpdatePostPreview(post_id, score, {score_change = null, post_score = null, vote_id = null} = {}) { let $post_article = $(`#post_${post_id}`); let $score_link = $post_article.find('.post-score a'); if ($score_link.length) { if (!post_score) { let current_score = Number($score_link.text()); post_score = current_score + score_change; } $score_link.text(post_score); $post_article.find('.post-votes .active-link').toggleClass('active-link inactive-link'); UpdatePostVoteLink($post_article.find('.post-upvote-link'), 'upvote', post_id, score, vote_id); UpdatePostVoteLink($post_article.find('.post-downvote-link'), 'downvote', post_id, score, vote_id); } DestroyTooltip(post_id); } function UpdatePostVoteLink($vote, type, post_id, score, vote_id) { if ((type === 'upvote' && score === 1) || (type === 'downvote' && score === -1)) { $vote.toggleClass('active-link inactive-link'); $vote.addClass('post-unvote-link'); JSPLib.utility.setDataAttribute($vote, 'method', 'delete'); if (vote_id === null) { JSPLib.danbooru.submitRequest('post_votes', {search: {post_id, user_id: PMM.user_id}, limit: 1, only: POST_VOTE_FIELDS}).then((data) => { $vote.attr('href', `/post_votes/${data.id}`); }); } else { $vote.attr('href', `/post_votes/${vote_id}`); } } else { let link_score = (type === 'upvote' ? 1 : -1); $vote.removeClass('post-unvote-link'); JSPLib.utility.setDataAttribute($vote, 'method', 'post'); $vote.attr('href', `/posts/${post_id}/votes?score=${link_score}`); } } function UpdateCommentaryTags(tag_string) { let tags = tag_string.split(' '); $('.pmm-commentary-tag.pmm-active').removeClass('pmm-active'); $('.pmm-commentary-tag input').prop('checked', false); ['commentary', 'commentary_request', 'commentary_check', 'partial_commentary'].forEach((tag_name) => { if (tags.includes(tag_name)) { $(`.pmm-commentary-tag[data-tag="${tag_name}"]`).addClass('pmm-active'); $(`.pmm-commentary-tag input[name="${tag_name}"]`).prop('checked', true); } }); } function UpdateDraggerStatus() { const printer = JSPLib.debug.getFunctionPrint('UpdateDraggerStatus'); if (DRAGGABLE_MODES.includes(PMM.mode) && PMM.dragger.stopped) { printer.debuglogLevel("Dragger started.", JSPLib.debug.DEBUG); PMM.dragger.start(); } else if (!DRAGGABLE_MODES.includes(PMM.mode) && !PMM.dragger.stopped) { PMM.dragger.stop(); printer.debuglogLevel("Dragger stopped.", JSPLib.debug.DEBUG); } } //Render functions function RenderPostModeMenu() { let selection_options = RenderPostModeMenuAddons(); return JSPLib.utility.sprintf(MODE_CONTROLS_HTML, selection_options); } function RenderPostModeMenuAddons() { let html = ''; PMM.mode_order.forEach((mode) => { let key = JSPLib.utility.kebabCase(mode); if (!PMM.available_mode_keys.has(key)) return; let name = JSPLib.utility.displayCase(mode); html += ``; }); return html; } //Initialize functions function InitializeModeMenu() { $('#mode-box').replaceWith(RenderPostModeMenu()); $('#pmm-mode-box select').on(JSPLib.program_change, () => UpdateModeMenu()); $('#pmm-select-only').on(JSPLib.program_change, () => UpdateSelectOnly()); $('.pmm-select').on(JSPLib.program_click, BatchSelection); $('#pmm-apply-all button').on(JSPLib.program_click, BatchApply); $('#pmm-undock').on(JSPLib.program_click, UndockModeMenu); $('.post-preview a.post-preview-link').on(JSPLib.program_click, PostModeMenu); $('.post-preview a.post-upvote-link').on(JSPLib.program_click, PostUpvote); $('.post-preview a.post-downvote-link').on(JSPLib.program_click, PostDownvote); $('#pmm-tag-script-field input').on('blur.pmm', SaveTagScript); $(document).on('keydown.pmm.change_tag_script', null, "0 1 2 3 4 5 6 7 8 9", ChangeTagScript); $("#pmm-mode-controls select").val(PMM.mode); $("#pmm-select-only").prop('checked', PMM.select_only); $('#pmm-tag-script-field input').val(GetCurrentTagScript()); SetupAutocomplete('#pmm-tag-script-field input'); if (PMM.long_tagscript_enabled) { $('#pmm-tag-script-field input').addClass('pmm-long-focus'); } UpdateModeMenu(false); } function EditDialog(post_ids) { if (post_ids.length !== 1) return; PMM.edit_post_id = post_ids[0]; if (!PMM.edit_dialog) { PMM.edit_dialog = $(EDIT_DIALOG_HTML); let buttons = {Submit: SubmitEdit}; if (PMM.use_VTI) { buttons.Validate = ValidateEdit; } buttons.Cancel = CloseDialog; PMM.edit_dialog.dialog(Object.assign({ buttons, open: EditDialogOpen, close: EditDialogClose }, EDIT_DIALOG_SETTINGS)); SetupAutocomplete('#pmm-tag-string textarea'); PMM.edit_dialog.find('#pmm-tag-string textarea').on('keydown.pmm', null, 'ctrl+return', SubmitEdit); PMM.edit_dialog.closest('.pmm-dialog').find('.ui-button').each((_, entry) => { let button_id = 'pmm-edit-' + entry.innerText.toLowerCase(); $(entry).attr('id', button_id); }); } if (PMM.edit_dialog.dialog('isOpen')) { //Trigger a close that the the close/open handlers get called PMM.edit_dialog.dialog('close'); } PMM.edit_dialog.dialog('open'); } function CommentaryDialog(post_ids) { PMM.commentary_post_ids = post_ids; if (!PMM.commentary_dialog) { PMM.commentary_dialog = $(COMMENTARY_DIALOG_HTML); PMM.commentary_dialog.dialog(Object.assign({ buttons: { Submit: SubmitCommentary, Cancel: CloseDialog, }, open: CommentaryDialogOpen, close: CommentaryDialogClose, }, COMMENTARY_DIALOG_SETTINGS)); PMM.commentary_dialog.find('#pmm-fetch button[name=post]').on(JSPLib.program_click, FetchPostCommentary); PMM.commentary_dialog.find('#pmm-fetch button[name=parent]').on(JSPLib.program_click, FetchParentCommentary); PMM.commentary_dialog.find('#pmm-fetch button[name=pool]').on(JSPLib.program_click, FetchPoolCommentary); PMM.commentary_dialog.find('.pmm-commentary-tag input').on(JSPLib.program_change, ChangeCommentaryTag); PMM.commentary_dialog.closest('.pmm-dialog').find('.ui-button').each((_, entry) => { let button_id = 'pmm-commentary-' + entry.innerText.toLowerCase(); $(entry).attr('id', button_id); }); } PMM.commentary_dialog.dialog('open'); } function SetupAutocomplete(selector) { const printer = JSPLib.debug.getFunctionPrint('SetupAutocomplete'); JSPLib.load.scriptWaitExecute(PMM, 'IAC', { available: () => { $(selector).data('autocomplete', 'tag-edit'); PMM.IAC.InitializeTagQueryAutocompleteIndexed(selector, null); printer.debuglogLevel(`Initialized IAC autocomplete on ${selector}.`, JSPLib.debug.DEBUG); }, fallback: () => { JSPLib.utility.setDataAttribute($(selector), 'autocomplete', 'tag-edit'); $(selector).autocomplete({ select (_event, ui) { Danbooru.Autocomplete.insert_completion(this, ui.item.value); return false; }, async source(_req, resp) { let term = Danbooru.Autocomplete.current_term(this.element); let results = await Danbooru.Autocomplete.autocomplete_source(term, "tag_query"); resp(results); }, }); printer.debuglogLevel(`Initialized Danbooru autocomplete on ${selector}.`, JSPLib.debug.DEBUG); }, }); } function UnbindEventHandlers() { const printer = JSPLib.debug.getFunctionPrint('UnbindEventHandlers'); JSPLib.utility.namespaceWaitExecute({ root: document, type: 'keydown', namespace: 'danbooru.change_tag_script', presence: true, interval: 100, duration: JSPLib.utility.one_second * 5, found () { $(document).off('keydown.danbooru.change_tag_script'); $(document).off('click.danbooru', '.post-preview-container a'); printer.debuglogLevel("Unbound Danbooru event handlers.", JSPLib.debug.VERBOSE); }, }); JSPLib.utility.namespaceWaitExecute({ root: '.post-preview a', type: 'click', namespace: 'vti', presence: true, interval: 100, duration: JSPLib.utility.one_second * 5, found () { $('.post-preview a').off('click.vti'); printer.debuglogLevel("Unbound VTI event handlers.", JSPLib.debug.VERBOSE); }, }); } //Network functions async function VotePost(post_id, score, singular) { let selector = (score > 0 ? '.post-upvote-link.active-link' : '.post-downvote-link.active-link'); if ($(`#post_${post_id} ${selector}`).length) { AlreadyActionNotice(post_id, singular); return false; } const printer = JSPLib.debug.getFunctionPrint('VotePost'); printer.debuglogLevel(post_id, JSPLib.debug.DEBUG); await JSPLib.danbooru.updateSetup(); JSPLib.network.post(`/posts/${post_id}/votes.json?score=${score}`) .always(JSPLib.danbooru.alwaysCallback()) .then( JSPLib.danbooru.successCallback(post_id, 'VotePost', (data) => { let score_change = score - (PMM.post_votes[post_id]?.score ?? 0); UpdatePostPreview(post_id, score, {score_change, vote_id: data.id}); PMM.post_votes[post_id] = {id: data.id, score}; }), JSPLib.danbooru.errorCallback(post_id, 'VotePost', {score}) ); return true; } async function UnvotePost(post_id, singular) { if ($(`#post_${post_id} .post-votes .active-link`).length === 0) { AlreadyActionNotice(post_id, singular); return false; } const printer = JSPLib.debug.getFunctionPrint('UnvotePost'); printer.debuglogLevel(post_id, JSPLib.debug.DEBUG); await PMM.post_vote_promise; let vote_id = PMM.post_votes[post_id].id; await JSPLib.danbooru.updateSetup(); //eslint-disable-next-line dot-notation JSPLib.network.delete(`/post_votes/${vote_id}.json`) .always(JSPLib.danbooru.alwaysCallback()) .then( JSPLib.danbooru.successCallback(post_id, 'UnvotePost', () => { let score_change = -PMM.post_votes[post_id].score; UpdatePostPreview(post_id, 0, {score_change}); delete PMM.post_votes[post_id]; }), JSPLib.danbooru.errorCallback(post_id, 'UnvotePost') ); return true; } async function FavoritePost(post_id, singular) { if (PMM.post_favorites[post_id]) { AlreadyActionNotice(post_id, singular); return false; } const printer = JSPLib.debug.getFunctionPrint('FavoritePost'); printer.debuglogLevel(post_id, JSPLib.debug.DEBUG); await JSPLib.danbooru.updateSetup(); JSPLib.network.post(`/favorites.json?post_id=${post_id}`) .always(JSPLib.danbooru.alwaysCallback()) .then( JSPLib.danbooru.successCallback(post_id, 'FavoritePost', (data) => { UpdatePostPreview(post_id, 1, {post_score: data.score}); PMM.post_favorites[post_id] = true; JSPLib.utility.setDataAttribute($(`#post_${post_id}`), 'is-favorited', true); }), JSPLib.danbooru.errorCallback(post_id, 'FavoritePost') ); return true; } async function UnfavoritePost(post_id, singular) { await PMM.post_favorite_promise; if (!PMM.post_favorites[post_id]) { AlreadyActionNotice(post_id, singular); return false; } const printer = JSPLib.debug.getFunctionPrint('UnfavoritePost'); printer.debuglogLevel(post_id, JSPLib.debug.DEBUG); await JSPLib.danbooru.updateSetup(); //eslint-disable-next-line dot-notation JSPLib.network.delete(`/favorites/${post_id}.json`) .always(JSPLib.danbooru.alwaysCallback()) .then( JSPLib.danbooru.successCallback(post_id, 'UnfavoritePost', () => { UpdatePostPreview(post_id, 0, {score_change: -1}); PMM.post_favorites[post_id] = false; JSPLib.utility.setDataAttribute($(`#post_${post_id}`), 'is-favorited', false); }), JSPLib.danbooru.errorCallback(post_id, 'UnfavoritePost') ); return true; } async function UpdatePostCommentary(post_id, artist_commentary, tag_changes) { const printer = JSPLib.debug.getFunctionPrint('UpdatePostCommentary'); printer.debuglogLevel(post_id, artist_commentary, tag_changes, JSPLib.debug.DEBUG); await JSPLib.danbooru.updateSetup(); return JSPLib.network.put(`/posts/${post_id}/artist_commentary/create_or_update.json`, {data: {artist_commentary}}) .always(JSPLib.danbooru.alwaysCallback()) .then( JSPLib.danbooru.successCallback(post_id, 'UpdatePostCommentary', () => { let $post = $(`#post_${post_id}`); let tags = $post.data('tags').split(' '); let updated_tags = JSPLib.utility.arrayUnion(tags, tag_changes.adds); updated_tags = JSPLib.utility.arrayDifference(updated_tags, tag_changes.removes); JSPLib.utility.setDataAttribute($post, 'tags', updated_tags.toSorted().join(' ')); DestroyTooltip(post_id); }), JSPLib.danbooru.errorCallback(post_id, 'UpdatePostCommentary', artist_commentary) ); } function TagscriptPost(post_id) { const printer = JSPLib.debug.getFunctionPrint('TagscriptPost'); let tag_script = $('#pmm-tag-script-field input').val().trim(); if (tag_script) { printer.debuglogLevel(post_id, {tag_script}, JSPLib.debug.DEBUG); JSPLib.danbooru.updatePost(post_id, {post: {old_tag_string: "", tag_string: tag_script}}).then(() => { DestroyTooltip(post_id); }); } else { JSPLib.notice.error('No tag script set!'); } } function GetCommentary(post_id) { const printer = JSPLib.debug.getFunctionPrint('GetCommentary'); printer.debuglogLevel(post_id, JSPLib.debug.DEBUG); return JSPLib.danbooru.submitRequest(`posts/${post_id}/artist_commentary`, {only: ARTIST_COMMENTARY_FIELDS}).then((artist_commentary) => { if (artist_commentary !== null) { ['original_title', 'original_description', 'translated_title', 'translated_description'].forEach((field) => { PMM.commentary_dialog.find(`[name="${field}"]`).val(artist_commentary[field]); }); UpdateCommentaryTags(artist_commentary.post.tag_string_meta); } else { JSPLib.notice.error("No commentary found."); } }); } async function PreloadPostVotes() { if (Object.keys(PMM.post_votes).length) return; const printer = JSPLib.debug.getFunctionPrint('PreloadPostVotes'); let p = JSPLib.utility.createPromise(); PMM.post_vote_promise = p.promise; let $post_votes = $('.post-votes'); if ($post_votes.length) { printer.debuglog("Loading votes from DOM."); $post_votes.each((_, entry) => { let $entry = $(entry); let post_id = $entry.closest('.post-preview').data('id'); let $active_link = $entry.find('.active-link'); if ($active_link.length) { let score = ($active_link.hasClass('post-upvote-link') ? 1 : -1); let vote_id = Number($active_link.attr('href').match(/\d+/)); PMM.post_votes[post_id] = {id: vote_id, score}; } }); } else { printer.debuglog("Loading votes from network."); let post_ids = JSPLib.utility.getDOMAttributes($('.post-votes .active-link').closest('.post-preview'), 'id', Number); if (post_ids.length) { let post_votes = await JSPLib.danbooru.submitRequest('post_votes', {search: {post_id: post_ids.join(','), user_id: PMM.user_id}, limit: post_ids.length, only: POST_VOTE_FIELDS}); post_votes.forEach((vote) => { PMM.post_votes[vote.post_id] = {id: vote.id, score: vote.score}; }); } } p.resolve(null); } function PreloadPostFavorites() { if (Object.keys(PMM.post_favorites).length) return; const printer = JSPLib.debug.getFunctionPrint('PreloadPostFavorites'); let p = JSPLib.utility.createPromise(); PMM.post_favorite_promise = p.promise; JSPLib.load.scriptWaitExecute(PMM, 'DPI', { version: false, available: () => { printer.debuglog("Loading favorites from DOM."); let post_ids = []; $('.post-preview').each((_, entry) => { let $entry = $(entry); let favorited = $entry.data('is-favorited'); if (typeof favorited === 'boolean') { let post_id = $entry.data('id'); post_ids.push(post_id); PMM.post_favorites[post_id] = favorited; } }); p.resolve(null); }, fallback: () => { printer.debuglog("Loading favorites from network."); let post_ids = JSPLib.utility.getDOMAttributes($('.post-preview'), 'id', Number); if (post_ids.length) { JSPLib.danbooru.submitRequest('favorites', {search: {post_id: post_ids.join(','), user_id: PMM.user_id}, limit: post_ids.length, only: POST_VOTE_FIELDS}).then((post_favorites) => { let favorite_post_ids = JSPLib.utility.getObjectAttributes(post_favorites, 'post_id'); post_ids.forEach((post_id) => { PMM.post_favorites[post_id] = favorite_post_ids.includes(post_id); }); p.resolve(null); }); } else { p.resolve(null); } }, }); } //Event handlers function PostModeMenu(event) { let $article = $(event.currentTarget).closest("article"); if (PMM.select_only && DRAGGABLE_MODES.includes(PMM.mode)) { $article.toggleClass('pmm-selected'); } else if (PMM.mode !== 'view') { let post_id = $article.data("id"); $article.addClass('pmm-selected'); MenuFunctions([post_id]); } else { return; } event.preventDefault(); } function BatchSelection(event) { let type = $(event.currentTarget).data('type'); switch (type) { case 'all': $('.post-preview').addClass('pmm-selected'); break; case 'none': $('.post-preview').removeClass('pmm-selected'); break; case 'invert': $('.post-preview').toggleClass('pmm-selected'); //falls through default: //do nothing } } function BatchApply() { let $selected_posts = $('.pmm-selected'); let post_ids = JSPLib.utility.getDOMAttributes($selected_posts, 'id', Number); MenuFunctions(post_ids); } function SaveTagScript(event) { let tag_script = $(event.target).val(); let current_script_id = GetCurrentScriptID(); if (tag_script) { localStorage.setItem("tag-script-" + current_script_id, tag_script); } else { localStorage.removeItem("tag-script-" + current_script_id); } } function ChangeTagScript(event) { if (PMM.mode === 'tag-script') { let current_script_id = GetCurrentScriptID(); let change_script_id = Number(event.key); if (current_script_id !== change_script_id) { JSPLib.storage.setLocalData('current_tag_script_id', change_script_id); JSPLib.notice.notice(`Switched to tag script #${event.key}. To switch tag scripts, use the number keys.`); } $("#pmm-tag-script-field input").val(GetCurrentTagScript()); event.preventDefault(); } } function UndockModeMenu() { let $mode_box = $('#pmm-mode-box'); let $placeholder = $('#pmm-placeholder'); let $pin_svg = $('#pmm-undock > svg'); let {height, width} = getComputedStyle($mode_box.get(0)); if (PMM.pinned) { $mode_box.css({top: "", left: "", width: "", position: 'relative'}); $pin_svg.css('transform', 'rotate(63deg)'); $placeholder.hide(); PMM.pinned = false; } else { let {top, left} = $mode_box.get(0).getBoundingClientRect(); $mode_box.css({top, left, width, position: 'fixed'}); $pin_svg.css('transform', 'rotate(-27deg)'); $placeholder.show(); PMM.pinned = true; } $('#pmm-placeholder').css('height', height); } function PostUpvote(event) { let $link = $(event.currentTarget); let $post = $link.closest('.post-preview'); let is_voted = $link.hasClass('active-link'); let post_id = $post.data('id'); if (is_voted) { let vote_id = Number($link.attr('href').match(/\d+/)); PMM.post_votes[post_id] = {id: vote_id, score: 1}; UnvotePost(post_id, true); } else { VotePost(post_id, 1, true); } event.preventDefault(); event.stopImmediatePropagation(); } function PostDownvote(event) { let $link = $(event.currentTarget); let $post = $link.closest('.post-preview'); let is_voted = $link.hasClass('active-link'); let post_id = $post.data('id'); if (is_voted) { let vote_id = Number($link.attr('href').match(/\d+/)); PMM.post_votes[post_id] = {id: vote_id, score: -1}; UnvotePost(post_id, true); } else { VotePost(post_id, -1, true); } event.preventDefault(); event.stopImmediatePropagation(); } function SubmitEdit(event) { const printer = JSPLib.debug.getFunctionPrint('SubmitEdit'); if (AreTagsEdited()) { $('#pmm-edit-submit, #pmm-edit-validate').attr('disabled', 'disabled'); DisableEditInterface(); ValidateTags().then((status) => { if (status) { let tag_string = $('#pmm-tag-string textarea').val(); let old_tag_string = $(`#post_${PMM.edit_post_id}`).data('tags'); printer.debuglogLevel({old_tag_string, tag_string}, JSPLib.debug.DEBUG); JSPLib.danbooru.updatePost(PMM.edit_post_id, {post: {old_tag_string, tag_string}}).then(() => { DestroyTooltip(PMM.edit_post_id); }); CloseDialog(event); } else { JSPLib.notice.error("Tag validation failed!"); } $('#pmm-edit-submit, #pmm-edit-validate').attr('disabled', null); EnableEditInterface('#pmm-edit-dialog'); }); } else { printer.debuglogLevel("Tags not edited.", JSPLib.debug.VERBOSE); CloseDialog(event); } } function ValidateEdit() { if (AreTagsEdited()) { $('#pmm-edit-submit, #pmm-edit-validate').attr('disabled', 'disabled'); DisableEditInterface(); ValidateTags().then((status) => { if (status) { JSPLib.notice.notice("Tags good to submit!"); } else { JSPLib.notice.error("Tag validation failed!"); } $('#pmm-edit-submit, #pmm-edit-validate').attr('disabled', null); EnableEditInterface(); }); } else { $("#warning-new-tags, #warning-bad-removes, #warning-deprecated-tags").hide(); JSPLib.notice.notice("Tags good to submit!"); } } function EditDialogOpen() { let tag_string = $(`#post_${PMM.edit_post_id}`).data('tags'); let $text_area = PMM.edit_dialog.find('#pmm-tag-string textarea'); if (PMM.edit_tag_grouping_enabled) { $text_area.val('loading...'); $text_area.attr('disabled', 'disabled'); JSPLib.danbooru.submitRequest('posts/' + PMM.edit_post_id, {only: POST_CATEGORY_FIELDS}).then((data) => { let grouped_tag_string = ""; ['artist', 'copyright', 'character', 'meta', 'general'].forEach((type) => { let type_tag_string = data['tag_string_' + type]; if (type_tag_string.length) { grouped_tag_string += type_tag_string + '\n'; } }); $text_area.val(grouped_tag_string.trim() + " "); $text_area.attr('disabled', null); $text_area.get(0).focus(); }); } else { $text_area.val(tag_string + ' '); $text_area.get(0).focus(); } if (PMM.use_VTI) { PMM.VTI.preedit_tags = tag_string.split(' '); PMM.VTI.PreloadImplications(); } $('.pmm-editing').removeClass('pmm-editing'); $(`#post_${PMM.edit_post_id}`).addClass('pmm-editing'); } function EditDialogClose() { $(`#post_${PMM.edit_post_id}`).removeClass('pmm-editing'); $("#validation-input, #warning-new-tags, #warning-bad-removes, #warning-deprecated-tags").hide(); } function FetchPostCommentary() { let post_id = Number($('#pmm-fetch input').val()); if (JSPLib.utility.validateID(post_id)) { JSPLib.notice.notice("Loading commentary data."); DisableCommentaryInterface(); GetCommentary(post_id).then(() => { JSPLib.notice.notice("Commentary loaded."); EnableCommentaryInterface(); }); } else { JSPLib.notice.error("Must enter a valid post ID."); } } function FetchParentCommentary() { JSPLib.notice.notice("Checking parent/child relationship."); DisableCommentaryInterface(); ParentPostCheck(PMM.commentary_post_ids).then((post_id) => { if (post_id !== null) { JSPLib.notice.notice("Loading commentary data."); $('#pmm-fetch input').val(post_id); GetCommentary(post_id).then(() => { JSPLib.notice.notice("Commentary loaded."); EnableCommentaryInterface(); }); } else { JSPLib.notice.error("Parent/child relationship not found."); EnableCommentaryInterface(); } }); } function FetchPoolCommentary() { JSPLib.notice.notice("Checking pool relationship."); DisableCommentaryInterface(); PoolPostCheck(PMM.commentary_post_ids).then((post_id) => { if (post_id !== null) { JSPLib.notice.notice("Loading commentary data."); $('#pmm-fetch input').val(post_id); GetCommentary(post_id).then(() => { JSPLib.notice.notice("Commentary loaded."); EnableCommentaryInterface(); }); } else { JSPLib.notice.error("Pool relationship not found."); EnableCommentaryInterface(); } }); } function ChangeCommentaryTag(event) { $(event.currentTarget).closest('.pmm-commentary-tag').toggleClass('pmm-active'); } function SubmitCommentary(event) { let post_ids = PMM.commentary_post_ids; let artist_commentary = {}; $('.pmm-commentary-input input, .pmm-commentary-input textarea').each((_, input) => { artist_commentary[input.name] = input.value; }); let tag_changes = {adds: [], removes: []}; $('.pmm-commentary-tag input').each((_, input) => { let field_name = (input.checked ? 'add_' : 'remove_') + input.name + '_tag'; artist_commentary[field_name] = 1; if (input.checked) { tag_changes.adds.push(input.name); } else { tag_changes.removes.push(input.name); } }); let promise_array = post_ids.map((post_id) => UpdatePostCommentary(post_id, artist_commentary, tag_changes)); Promise.all(promise_array).then((responses) => { if (responses.every(Boolean)) { JSPLib.notice.notice("All posts updated."); } else { let successes = responses.reduce((acc, val) => acc + Number(val), 0); let failures = responses.length - successes; JSPLib.notice.error(`Error updating posts:
`); } }); CloseDialog(event); } function CommentaryDialogOpen() { let post_ids = PMM.commentary_post_ids; if (PMM.commentary_post_ids.length === 1) { $('#pmm-fetch input').val(PMM.commentary_post_ids[0]); UpdateCommentaryTags($(`#post_${post_ids[0]}`).data('tags')); if (PMM.autoload_post_commentary_enabled) { JSPLib.notice.notice("Loading commentary data."); DisableCommentaryInterface(); GetCommentary(PMM.commentary_post_ids[0]).then(() => { JSPLib.notice.notice("Commentary loaded."); EnableCommentaryInterface(); }); } } } function CommentaryDialogClose() { $('#pmm-commentary-dialog').find('input, textarea').val(""); $('#pmm-commentary-dialog').find('.pmm-commentary-tag.pmm-active').removeClass('pmm-active'); $('#pmm-commentary-dialog').find('.pmm-commentary-tag input').prop('checked', false); } function CloseDialog(event) { $('#close-notice-link').get(0).click(); let $dialog = $(event.target).closest('.ui-dialog').find('.ui-dialog-content'); $dialog.dialog('close'); } function DragSelectCallback({items, event}) { const printer = JSPLib.debug.getFunctionPrint('DragSelectCallback'); //Only process drag select events when the primary (left) and only the primary mouse button is used. if (!event.button !== 0 && event.buttons !== 0) return; printer.debuglogLevel("Parameters:", {items, event}, JSPLib.debug.DEBUG); let click_coords = PMM.dragger.getInitialCursorPositionArea(); let mouseup_coords = PMM.dragger.getCurrentCursorPositionArea(); if (mouseup_coords.x === click_coords.x && mouseup_coords.y === click_coords.y) { printer.debuglog("Click event."); return; } if (items.length === 1) { let area_coords = JSPLib.utility.getElemPosition(PMM.$drag_area); let page_click_coords = {x: click_coords.x + area_coords.left, y: click_coords.y + area_coords.top}; let page_mouseup_coords = {x: mouseup_coords.x + area_coords.left, y: mouseup_coords.y + area_coords.top}; let box = JSPLib.utility.getElemPosition(items[0]); box.bottom = box.top + items[0].offsetHeight; box.right = box.left + items[0].offsetWidth; printer.debuglogLevel('Coordinates:', {page_click_coords, page_mouseup_coords, box}, JSPLib.debug.DEBUG); if (CoordinateInBox(page_click_coords, box) && CoordinateInBox(page_mouseup_coords, box)) { printer.debuglog("Click-drag within element."); return; } } let articles = items.map((entry) => $(entry).closest('article').get(0)); let post_ids = articles.map((entry) => $(entry).data('id')); printer.debuglog('Drag Select IDs:', post_ids); if (PMM.select_only) { $(articles).toggleClass('pmm-selected'); } else { $(articles).addClass('pmm-selected'); MenuFunctions(post_ids); } document.getSelection().removeAllRanges(); } //Menu function function MenuFunctions(post_ids) { if (['edit', 'copy-id', 'copy-short', 'copy-link', 'commentary'].includes(PMM.mode)) { MenuFunctionsMulti(post_ids); } else { let singular = post_ids.length === 1; let promise_array = []; for (let i = 0; i < post_ids.length; i++) { promise_array.push(MenuFunctionsSingle(post_ids[i], singular)); } if (['vote-up', 'vote-down', 'unvote', 'favorite', 'unfavorite'].includes(PMM.mode) && post_ids.length > 1) { Promise.all(promise_array).then((statuses) => { if (!statuses.some((status) => status)) { JSPLib.notice.error(`All selected posts have ${GetAction()}.`); } }); } } } function MenuFunctionsSingle(post_id, singular) { switch (PMM.mode) { case 'vote-up': return VotePost(post_id, 1, singular); case 'vote-down': return VotePost(post_id, -1, singular); case 'unvote': return UnvotePost(post_id, singular); case 'favorite': return FavoritePost(post_id, singular); case 'unfavorite': return UnfavoritePost(post_id, singular); case 'tag-script': TagscriptPost(post_id); //falls through default: //do nothing } } function MenuFunctionsMulti(post_ids) { switch (PMM.mode) { case 'edit': EditDialog(post_ids); break; case 'copy-id': CopyToClipboard(post_ids, "", "", PMM.id_separator_char, false); break; case 'copy-short': CopyToClipboard(post_ids, "post #", "", PMM.id_separator_char, true); break; case 'copy-link': CopyToClipboard(post_ids, "https://danbooru.donmai.us/posts/", " ", PMM.id_separator_char, true); break; case 'commentary': CommentaryDialog(post_ids); //falls through default: //do nothing } } //Settings functions function BroadcastPMM(ev) { const printer = JSPLib.debug.getFunctionPrint('BroadcastPMM'); printer.debuglog(`(${ev.data.type}):`, ev.data); switch (ev.data.type) { case 'change_mode': $("#pmm-mode-controls select").val(ev.data.mode); UpdateModeMenu(false); break; case 'change_select_only': $('#pmm-select-only-input input').prop('checked', ev.data.select_only); UpdateSelectOnly(false); //falls through default: //do nothing } } function InitializeProgramValues() { PMM.user_id = Danbooru.CurrentUser.data('id'); if (!JSPLib.utility.validateID(PMM.user_id) || Danbooru.CurrentUser.data('level') < GOLD_LEVEL || Danbooru.CurrentUser.data('is-banned')) return false; Object.assign(PMM, { mode: JSPLib.storage.getLocalData('pmm-mode'), available_mode_keys: new Set(PMM.available_modes.map((mode) => JSPLib.utility.kebabCase(mode.toLocaleLowerCase()))), id_separator_char: SEPARATOR_DICT[PMM.id_separator[0]], select_only: JSPLib.storage.getLocalData('pmm-select-only', {default_val: false}), $drag_area: document.querySelector('#posts'), }); if ((PMM.safe_tag_script_enabled && PMM.mode === 'tag-script') || !PMM.available_mode_keys.has(PMM.mode)) { PMM.mode = 'view'; } if (PMM.drag_select_enabled) { PMM.dragger = new DragSelect({ selectables: document.querySelectorAll('.post-preview img'), area: PMM.$drag_area, draggability: false, immediateDrag: false, }); } JSPLib.danbooru.max_network_requests = PMM.maximum_concurrent_requests; JSPLib.danbooru.highlight_post_enabled = PMM.highlight_errors_enabled; JSPLib.load.setProgramGetter(PMM, 'VTI', 'ValidateTagInput', 29.13); JSPLib.load.setProgramGetter(PMM, 'IAC', 'IndexedAutocomplete', 29.25); JSPLib.load.setProgramGetter(PMM, 'DPI', 'DisplayPostInfo'); return true; } function RenderSettingsMenu() { $('#post-mode-menu').append(JSPLib.menu.renderMenuFramework(MENU_CONFIG)); $('#pmm-general-settings').append(JSPLib.menu.renderDomainSelectors()); $('#pmm-mode-settings-message').append(JSPLib.menu.renderExpandable("Additional setting details", MODE_SETTINGS_DETAILS)); $('#pmm-mode-settings').append(JSPLib.menu.renderInputSelectors('available_modes', 'checkbox')); $('#pmm-mode-settings').append(JSPLib.menu.renderSortlist('mode_order')); $('#pmm-option-settings').append(JSPLib.menu.renderCheckbox('edit_tag_grouping_enabled')); $('#pmm-option-settings').append(JSPLib.menu.renderCheckbox('safe_tag_script_enabled')); $('#pmm-option-settings').append(JSPLib.menu.renderCheckbox('autoload_post_commentary_enabled')); $('#pmm-option-settings').append(JSPLib.menu.renderInputSelectors('id_separator', 'radio')); $("#pmm-network-settings").append(JSPLib.menu.renderTextinput('maximum_concurrent_requests', 10)); $('#pmm-network-settings').append(JSPLib.menu.renderCheckbox('highlight_errors_enabled')); $('#pmm-select-settings').append(JSPLib.menu.renderCheckbox('drag_select_enabled')); $('#pmm-interface-settings').append(JSPLib.menu.renderCheckbox('long_searchbar_enabled')); $('#pmm-interface-settings').append(JSPLib.menu.renderCheckbox('long_tagscript_enabled')); JSPLib.menu.engageUI(true, true); JSPLib.menu.saveUserSettingsClick(); JSPLib.menu.resetUserSettingsClick(); JSPLib.menu.expandableClick(); } //Main function function Main() { const preload = { run_on_settings: false, default_data: DEFAULT_VALUES, initialize_func: InitializeProgramValues, broadcast_func: BroadcastPMM, render_menu_func: RenderSettingsMenu, program_css: PROGRAM_CSS, menu_css: MENU_CSS, light_css: LIGHT_MODE_CSS, dark_css: DARK_MODE_CSS, }; if (!JSPLib.menu.preloadScript(PMM, preload)) return; InitializeModeMenu(); if (PMM.long_searchbar_enabled) { JSPLib.utility.setCSSStyle(SEARCHBAR_CSS, 'searchbar'); } if (PMM.highlight_errors_enabled) { JSPLib.utility.setCSSStyle(JSPLib.danbooru.highlight_css, 'highlight'); } if (PMM.drag_select_enabled) { PMM.dragger.subscribe('callback', DragSelectCallback); } if (PMM.available_mode_keys.has('edit')) { $('#quick-edit-div').remove(); } UnbindEventHandlers(); } /****Initialization****/ //Variables for JSPLib JSPLib.program_name = PROGRAM_NAME; JSPLib.program_shortcut = PROGRAM_SHORTCUT; JSPLib.program_data = PMM; //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});