mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2026-02-05 04:45:09 -06:00
192 lines
6.9 KiB
JavaScript
192 lines
6.9 KiB
JavaScript
(async () => {
|
|
let pluginSettings = {};
|
|
const defaultPluginSettings = {
|
|
createIfNotExists: false,
|
|
requireConfirmation: false,
|
|
};
|
|
|
|
var objID = null;
|
|
var objType = null;
|
|
|
|
// helper function to get the innerText of all elements matching a selector
|
|
const getAllInnerText = (selector) => Array.from(document.querySelectorAll(selector))
|
|
.map((el) => el.innerText.trim())
|
|
.filter((text) => text !== "");
|
|
|
|
// On image page, get data about gallery (image's position within gallery, next/prev image IDs),
|
|
// add arrow buttons to page, and register arrow keypress handlers,
|
|
async function setupTagCopyPaste(objTypeTriggered) {
|
|
// Get plugin settings.
|
|
const configSettings = await csLib.getConfiguration("tagCopyPaste", {}); // getConfiguration is from cs-ui-lib.js
|
|
pluginSettings = {
|
|
...defaultPluginSettings,
|
|
...configSettings,
|
|
};
|
|
|
|
objID = window.location.pathname.split("/")[2];
|
|
objType = objTypeTriggered;
|
|
|
|
// Add UI elements.
|
|
if (objID !== "new") {
|
|
insertCopyPasteButtons();
|
|
}
|
|
}
|
|
|
|
function copyEventHandler(event) {
|
|
event.preventDefault();
|
|
handleCopyClick();
|
|
}
|
|
|
|
function pasteEventHandler(event) {
|
|
event.preventDefault();
|
|
handlePasteClick();
|
|
}
|
|
|
|
function insertCopyPasteButtons() {
|
|
// listen for copy and paste events within tag input box
|
|
// find tag input box
|
|
const tagInputBox = document.querySelector("label[for='tag_ids'] + div .react-select__value-container");
|
|
if (tagInputBox) {
|
|
tagInputBox.removeEventListener("copy", copyEventHandler);
|
|
tagInputBox.removeEventListener("paste", pasteEventHandler);
|
|
tagInputBox.addEventListener("copy", copyEventHandler);
|
|
tagInputBox.addEventListener("paste", pasteEventHandler);
|
|
}
|
|
|
|
var copyButton = document.createElement("button");
|
|
copyButton.className = "imageGalleryNav-copyButton btn btn-secondary";
|
|
copyButton.innerText = "Copy";
|
|
copyButton.onclick = (event) => {
|
|
event.preventDefault();
|
|
handleCopyClick();
|
|
}
|
|
|
|
var pasteButton = document.createElement("button");
|
|
pasteButton.className = "imageGalleryNav-pasteButton btn btn-secondary";
|
|
pasteButton.innerText = "Paste";
|
|
pasteButton.onclick = (event) => {
|
|
event.preventDefault();
|
|
handlePasteClick();
|
|
}
|
|
|
|
if (document.querySelector("button.imageGalleryNav-pasteButton") == null) {
|
|
document.querySelector("label[for='tag_ids']").append(pasteButton);
|
|
}
|
|
if (document.querySelector("button.imageGalleryNav-copyButton") == null) {
|
|
document.querySelector("label[for='tag_ids']").append(copyButton);
|
|
}
|
|
}
|
|
|
|
// Handle copy click. Return delimited list of current tags.
|
|
async function handleCopyClick() {
|
|
// Get tags from input box
|
|
// join as comma delimited list
|
|
const tagList = getAllInnerText("label[for='tag_ids'] + div .react-select__multi-value__label").join(",")
|
|
// write to clipboard.
|
|
navigator.clipboard.writeText(tagList);
|
|
}
|
|
|
|
// Handle paste click.
|
|
async function handlePasteClick() {
|
|
// Parse tag list from comma delimited string.
|
|
const tagInput = await navigator.clipboard.readText();
|
|
var inputTagList = tagInput.split(/\r?\n|\r|,/).map(s => s.trim()).filter((text) => text !== "") // do de-duplication later
|
|
|
|
// Get tags from input box and also add to tag list.
|
|
const existingTagList = getAllInnerText("label[for='tag_ids'] + div .react-select__multi-value__label");
|
|
|
|
inputTagList = [...new Set([...inputTagList, ...existingTagList])].sort();
|
|
|
|
var missingTags = [];
|
|
var existingTags = [];
|
|
var tagUpdateList = [];
|
|
|
|
// Search for tag ID for each tag. If exists, add to tag ID list. If not exists, create new tag and add to tag ID list.
|
|
for (const inputTag of inputTagList) {
|
|
const tagID = await getTagByName(inputTag);
|
|
if (tagID && tagID.length) {
|
|
existingTags.push(inputTag);
|
|
tagUpdateList.push(tagID[0]);
|
|
} else {
|
|
missingTags.push(inputTag);
|
|
}
|
|
}
|
|
|
|
if (pluginSettings.requireConfirmation) {
|
|
const missingTagsStr = missingTags.join(", ");
|
|
const existingTagsStr = existingTags.join(", ");
|
|
const msg = pluginSettings.createIfNotExists
|
|
? `Missing Tags that will be created:\n${missingTagsStr}\n\nExisting Tags that will be saved: \n${existingTagsStr}\n\nContinue?`
|
|
: `Missing Tags that will be skipped:\n${missingTagsStr}\n\nExisting Tags that will be saved: \n${existingTagsStr}\n\nContinue?`;
|
|
|
|
if (!confirm(msg)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (pluginSettings.createIfNotExists && missingTags.length) {
|
|
for (const missingTag of missingTags) {
|
|
const newTagID = await createNewTag(missingTag);
|
|
if (newTagID != null) tagUpdateList.push(newTagID);
|
|
}
|
|
}
|
|
|
|
// Update tags on object with new tag ID list.
|
|
await updateObjTags(
|
|
tagUpdateList,
|
|
`${objType.toLowerCase()}Update`,
|
|
`${objType}UpdateInput`
|
|
);
|
|
|
|
window.location.reload();
|
|
}
|
|
|
|
// *** GQL Calls ***
|
|
|
|
// Update Object by ID, new tags list, and GQL mutation name.
|
|
async function updateObjTags(tags, fnName, inputName) {
|
|
const variables = { input: { id: objID, tag_ids: tags } };
|
|
const query = `mutation UpdateObj($input:${inputName}!) { ${fnName}(input: $input) {id} }`;
|
|
return await csLib.callGQL({ query, variables });
|
|
}
|
|
|
|
// Update Object by ID, new tags list, and GQL mutation name.
|
|
async function createNewTag(tagName) {
|
|
const variables = { input: { name: tagName } };
|
|
const query = `mutation CreateTag($input:TagCreateInput!) { tagCreate(input: $input) {id} }`;
|
|
return await csLib
|
|
.callGQL({ query, variables })
|
|
.then((data) => data.tagCreate.id);
|
|
}
|
|
|
|
// Find Tag by name/alias.
|
|
// Return match tag ID.
|
|
async function getTagByName(tagName) {
|
|
const tagFilter = {
|
|
name: { value: tagName, modifier: "EQUALS" },
|
|
OR: { aliases: { value: tagName, modifier: "EQUALS" } },
|
|
};
|
|
const findFilter = { per_page: -1, sort: "name" };
|
|
const variables = { tag_filter: tagFilter, filter: findFilter };
|
|
const query = `query ($tag_filter: TagFilterType!, $filter: FindFilterType!) { findTags(filter: $filter, tag_filter: $tag_filter) { tags { id } } }`;
|
|
return await csLib
|
|
.callGQL({ query, variables })
|
|
.then((data) => data.findTags.tags.map((item) => item.id));
|
|
}
|
|
|
|
// listener arrays
|
|
[
|
|
[ "/scenes/", "[id*='-edit-details']", "Scene" ],
|
|
[ "/studios/", "[id='studio-edit']", "Studio" ],
|
|
[ "/groups/", "[id='group-edit']", "Group" ],
|
|
[ "/performers/", "[id='performer-edit']", "Performer" ],
|
|
[ "/galleries/", "[id*='-edit-details']", "Gallery" ],
|
|
[ "/images/", "[id*='-edit-details']", "Image" ]
|
|
].forEach(([path, selector, objTypeTriggered]) => {
|
|
// Wait for the page to load and the element to be present.
|
|
csLib.PathElementListener(path, selector, () => {
|
|
setupTagCopyPaste(objTypeTriggered);
|
|
}); // PathElementListener is from cs-ui-lib.js
|
|
});
|
|
})();
|