|
// ==UserScript== |
|
// @name Add useful text snippets for StashDB |
|
// @icon https://cdn.discordapp.com/attachments/559159668912553989/841890253707149352/stash2.png |
|
// @namespace https://github.com/SpedNSFW |
|
// @version 0.4 |
|
// @description Adds a button below the notes fields for various edit pages to add common snippets easily |
|
// @author Norse |
|
// @match https://stashdb.org/* |
|
// @grant GM.setValue |
|
// @grant GM.getValue |
|
// @grant GM.deleteValue |
|
// @homepageURL https://gist.githubusercontent.com/SpedNSFW/15a88e59a927261029bc574837183800 |
|
// @downloadURL https://gist.githubusercontent.com/SpedNSFW/15a88e59a927261029bc574837183800/raw/stashdb_edit_snippets.js |
|
// @updateURL https://gist.githubusercontent.com/SpedNSFW/15a88e59a927261029bc574837183800/raw/stashdb_edit_snippets.js |
|
// ==/UserScript== |
|
|
|
addEventListener('DOMContentLoaded', _ => { |
|
const pattern = /^https:\/\/stashdb\.org\/((performers|scenes|tags|studios)\/(add|([a-f0-9-]+\/edit))|(?:(edits)\/[a-f0-9-]+\/update)|drafts\/[a-f0-9-]+)$/i |
|
|
|
let previousUrl = '' |
|
const observer = new MutationObserver(mutations => { |
|
if (location.href !== previousUrl) { |
|
previousUrl = location.href |
|
let m |
|
if (m = pattern.exec(location.href)) { |
|
init(m) |
|
} |
|
} |
|
}) |
|
observer.observe(document, { subtree: true, childList: true }) |
|
}) |
|
|
|
function waitForElement(selector) { |
|
return new Promise(resolve => { |
|
let element |
|
if (element = document.querySelector(selector)) { |
|
return resolve(element) |
|
} |
|
|
|
const observer = new MutationObserver(mutations => { |
|
if (element = document.querySelector(selector)) { |
|
resolve(element) |
|
observer.disconnect() |
|
} |
|
}) |
|
|
|
observer.observe(document.body, { subtree: true, childList: true }) |
|
}) |
|
} |
|
|
|
async function init(match) { |
|
const noteWrapper = await waitForElement('.NoteInput-tab') |
|
if (!noteWrapper) { |
|
console.warn('Note wrapper is not present. Aborting...') |
|
return |
|
} |
|
|
|
const updateDraft = match[5] === 'edits' |
|
const newDraft = match[6] === 'drafts' |
|
const addNew = !(updateDraft || newDraft) && match[3] === 'add' |
|
const editExisting = !(updateDraft || newDraft) && !addNew |
|
const type = !(updateDraft || newDraft) && match[2] |
|
|
|
const key = valueKey(updateDraft, newDraft, addNew, editExisting, type) |
|
const value = await GM.getValue(key, "[]") |
|
const snippets = JSON.parse(value) |
|
|
|
await buildPrompt(key, noteWrapper, snippets) |
|
} |
|
|
|
async function buildPrompt(key, noteWrapper, snippets) { |
|
noteWrapper.classList.add('position-relative') |
|
|
|
const parent = document.createElement('div') |
|
parent.classList.add('position-absolute', 'd-flex', 'flex-column') |
|
parent.style.setProperty('top', 0) |
|
parent.style.setProperty('left', 'calc(100% + 2rem)') |
|
noteWrapper.appendChild(parent) |
|
|
|
const addNew = document.createElement('button') |
|
addNew.setAttribute('type', 'button') |
|
addNew.classList.add('btn', 'btn-primary', 'order-1', 'mt-3') |
|
addNew.style.setProperty('width', 'max-content') |
|
addNew.appendChild(document.createTextNode('Create New Snippet')) |
|
addNew.addEventListener('click', _ => { |
|
const textarea = noteWrapper.querySelector('textarea') |
|
if (textarea.value) { |
|
void newSnippet(key, noteWrapper, parent, snippets, textarea.value) |
|
} |
|
}) |
|
parent.appendChild(addNew) |
|
|
|
for (let i = 0; i < snippets.length; i++) { |
|
await newSnippet(key, noteWrapper, parent, snippets, snippets[i], false) |
|
} |
|
} |
|
|
|
async function newSnippet(key, noteWrapper, parent, snippets, snippet, isNew = true) { |
|
if (isNew) { |
|
console.log(arguments) |
|
snippets.push(snippet) |
|
await GM.setValue(key, JSON.stringify(snippets)) |
|
} |
|
const wrapper = document.createElement('div') |
|
wrapper.classList.add('position-relative', 'd-flex') |
|
parent.appendChild(wrapper) |
|
|
|
const deleteSnippet = document.createElement('a') |
|
deleteSnippet.setAttribute('role', 'button') |
|
deleteSnippet.classList.add('text-danger', 'text-decoration-none') |
|
deleteSnippet.style.setProperty('margin-right', '1ch') |
|
deleteSnippet.appendChild(document.createTextNode('\u2717')) |
|
deleteSnippet.addEventListener('click', _ => { |
|
const index = snippets.findIndex(s => s === snippet) |
|
if (index !== -1) { |
|
snippets.splice(index, 1) |
|
void GM.setValue(key, JSON.stringify(snippets)) |
|
wrapper.remove() |
|
} |
|
}) |
|
wrapper.appendChild(deleteSnippet) |
|
|
|
const snippetEl = document.createElement('span') |
|
snippetEl.appendChild(document.createTextNode(snippet)) |
|
snippetEl.addEventListener('click', _ => { |
|
const textarea = noteWrapper.querySelector('textarea') |
|
textarea.value = snippet |
|
setTimeout(_ => fireChangeEvent(textarea)) |
|
}) |
|
wrapper.appendChild(snippetEl) |
|
} |
|
|
|
function fireChangeEvent(el) { |
|
// May need browser specific code here |
|
const eventOptions = { bubbles: true } |
|
;['change', 'input', 'invalid'] |
|
.map(ev => new Event(ev, eventOptions)) |
|
.forEach(ev => el.dispatchEvent(ev)) |
|
} |
|
|
|
function valueKey(updateEdit, newDraft, addNew, editExisting, type) { |
|
if (updateEdit) { |
|
return 'edits' |
|
} |
|
if (newDraft) { |
|
return 'drafts' |
|
} |
|
if (addNew) { |
|
return `new${type}` |
|
} else if (editExisting) { |
|
return `edit${type}` |
|
} |
|
return null |
|
} |