diff --git a/plugins/sceneCoverCropper/sceneCoverCropper.js b/plugins/sceneCoverCropper/sceneCoverCropper.js index 7d53c4e..3808681 100644 --- a/plugins/sceneCoverCropper/sceneCoverCropper.js +++ b/plugins/sceneCoverCropper/sceneCoverCropper.js @@ -139,7 +139,7 @@ cropBtnContainer.appendChild(cropInfo); } - stash.addEventListener('page:scene', function () { + stash.addEventListener('stash:page:scene', function () { waitForElementId('scene-edit-details', setupCropper); }); })(); diff --git a/plugins/stashUserscriptLibrary/custom.js b/plugins/stashUserscriptLibrary/custom.js new file mode 100644 index 0000000..f13164c --- /dev/null +++ b/plugins/stashUserscriptLibrary/custom.js @@ -0,0 +1,1434 @@ +const stashListener = new EventTarget(); + +const { + fetch: originalFetch +} = window; + +window.fetch = async (...args) => { + let [resource, config] = args; + // request interceptor here + const response = await originalFetch(resource, config); + // response interceptor here + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1 && resource.endsWith('/graphql')) { + try { + const data = await response.clone().json(); + stashListener.dispatchEvent(new CustomEvent('response', { + 'detail': data + })); + } catch (e) { + + } + } + return response; +}; + +class Logger { + constructor(enabled) { + this.enabled = enabled; + } + debug() { + if (this.enabled) return; + console.debug(...arguments); + } +} + + +class Stash extends EventTarget { + constructor({ + pageUrlCheckInterval = 100, + detectReRenders = false, // detects if .main element is re-rendered. eg: When you are in scenes page and clicking the scenes nav tab the url wont change but the elements are re-rendered, So with this you can listen and alter the elements inside the .main node + logging = false + } = {}) { + super(); + this.log = new Logger(logging); + this._pageUrlCheckInterval = pageUrlCheckInterval; + this._detectReRenders = detectReRenders; + this.fireOnHashChangesToo = true; + this._lastPathStr = ""; + this._lastQueryStr = ""; + this._lastHashStr = ""; + this._lastHref = ""; + this._lastStashPageEvent = ""; + this.waitForElement(this._detectReRenders ? ".main > div" : "html").then(() => { + this._pageURLCheckTimerId = setInterval(() => { + // Loop every 100 ms + if ( + this._lastPathStr !== location.pathname || + this._lastQueryStr !== location.search || + (this.fireOnHashChangesToo && this._lastHashStr !== location.hash) || + this._lastHref !== location.href || + (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders) + ) { + this._dispatchPageEvent("stash:page", false) + this.gmMain({ + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); + this._lastPathStr = location.pathname + this._lastQueryStr = location.search + this._lastHashStr = location.hash + this._lastHref = location.href + if (this._detectReRenders) { + this.waitForElement(".main > div", 10000).then((element) => { + element.setAttribute("stashUserscriptLibrary", ""); + }) + } + } + }, this._pageUrlCheckInterval); + }) + stashListener.addEventListener('response', (evt) => { + if (evt.detail.data?.plugins) { + this.getPluginVersion(evt.detail); + } + this.processRemoteScenes(evt.detail); + this.processScene(evt.detail); + this.processScenes(evt.detail); + this.processStudios(evt.detail); + this.processPerformers(evt.detail); + this.processApiKey(evt.detail); + this.dispatchEvent(new CustomEvent('stash:response', { + 'detail': evt.detail + })); + }); + stashListener.addEventListener('pluginVersion', (evt) => { + if (this.pluginVersion !== evt.detail) { + this.pluginVersion = evt.detail; + this.dispatchEvent(new CustomEvent('stash:pluginVersion', { + 'detail': evt.detail + })); + } + }); + this.version = [0, 0, 0]; + this.getVersion(); + this.pluginVersion = null; + this.getPlugins().then(plugins => this.getPluginVersion(plugins)); + this.visiblePluginTasks = ['Userscript Functions']; + this.settingsCallbacks = []; + this.settingsId = 'userscript-settings'; + this.remoteScenes = {}; + this.scenes = {}; + this.studios = {}; + this.performers = {}; + this.userscripts = []; + this._pageListeners = {}; + this.assignPageListeners() + } + async getVersion() { + const reqData = { + "operationName": "", + "variables": {}, + "query": `query version { + version { + version + } + }` + }; + const data = await this.callGQL(reqData); + const versionString = data.data.version.version; + this.version = versionString.substring(1).split('.').map(o => parseInt(o)); + } + compareVersion(minVersion) { + let [currMajor, currMinor, currPatch = 0] = this.version; + let [minMajor, minMinor, minPatch = 0] = minVersion.split('.').map(i => parseInt(i)); + if (currMajor > minMajor) return 1; + if (currMajor < minMajor) return -1; + if (currMinor > minMinor) return 1; + if (currMinor < minMinor) return -1; + return 0; + + } + comparePluginVersion(minPluginVersion) { + if (!this.pluginVersion) return -1; + let [currMajor, currMinor, currPatch = 0] = this.pluginVersion.split('.').map(i => parseInt(i)); + let [minMajor, minMinor, minPatch = 0] = minPluginVersion.split('.').map(i => parseInt(i)); + if (currMajor > minMajor) return 1; + if (currMajor < minMajor) return -1; + if (currMinor > minMinor) return 1; + if (currMinor < minMinor) return -1; + return 0; + + } + async runPluginTask(pluginId, taskName, args = []) { + const reqData = { + "operationName": "RunPluginTask", + "variables": { + "plugin_id": pluginId, + "task_name": taskName, + "args": args + }, + "query": "mutation RunPluginTask($plugin_id: ID!, $task_name: String!, $args: [PluginArgInput!]) {\n runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)\n}\n" + }; + return this.callGQL(reqData); + } + async callGQL(reqData) { + const options = { + method: 'POST', + body: JSON.stringify(reqData), + headers: { + 'Content-Type': 'application/json' + } + } + + try { + const res = await window.fetch('/graphql', options); + // this.log.debug(res); + return res.json(); + } catch (err) { + console.error(err); + } + } + async getFreeOnesStats(link) { + try { + const doc = await fetch(link) + .then(function(response) { + // When the page is loaded convert it to text + return response.text() + }) + .then(function(html) { + // Initialize the DOM parser + var parser = new DOMParser(); + + // Parse the text + var doc = parser.parseFromString(html, "text/html"); + + // You can now even select part of that html as you would in the regular DOM + // Example: + // var docArticle = doc.querySelector('article').innerHTML; + + console.log(doc); + return doc + }) + .catch(function(err) { + console.log('Failed to fetch page: ', err); + }); + + var data = new Object(); + data.rank = doc.querySelector('rank-chart-button'); + console.log(data.rank); + data.views = doc.querySelector('.d-none.d-m-flex.flex-column.align-items-center.global-header > div.font-weight-bold').textContent; + data.votes = '0' + return JSON.stringify(data); + } catch (err) { + console.error(err); + } + } + async getPlugins() { + const reqData = { + "operationName": "Plugins", + "variables": {}, + "query": `query Plugins { + plugins { + id + name + description + url + version + tasks { + name + description + __typename + } + hooks { + name + description + hooks + } + } + } + ` + }; + return this.callGQL(reqData); + } + async getPluginVersion(plugins) { + let version = null; + for (const plugin of plugins?.data?.plugins || []) { + if (plugin.id === 'userscript_functions') { + version = plugin.version; + } + } + stashListener.dispatchEvent(new CustomEvent('pluginVersion', { + 'detail': version + })); + } + async getStashBoxes() { + const reqData = { + "operationName": "Configuration", + "variables": {}, + "query": `query Configuration { + configuration { + general { + stashBoxes { + endpoint + api_key + name + } + } + } + }` + }; + return this.callGQL(reqData); + } + async getApiKey() { + const reqData = { + "operationName": "Configuration", + "variables": {}, + "query": `query Configuration { + configuration { + general { + apiKey + } + } + }` + }; + return this.callGQL(reqData); + } + matchUrl(href, fragment) { + const regexp = concatRegexp(new RegExp(window.location.origin), fragment); + // this.log.debug(regexp, location.href.match(regexp)); + return href.match(regexp) != null; + } + createSettings() { + waitForElementId('configuration-tabs-tabpane-system', async (elementId, el) => { + let section; + if (!document.getElementById(this.settingsId)) { + section = document.createElement("div"); + section.setAttribute('id', this.settingsId); + section.classList.add('setting-section'); + section.innerHTML = `

Userscript Settings

`; + el.appendChild(section); + + const expectedApiKey = (await this.getApiKey())?.data?.configuration?.general?.apiKey || ''; + const expectedUrl = window.location.origin; + + const serverUrlInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-url', 'userscript-server-url', 'Stash Server URL', '', 'Server URL…', true); + serverUrlInput.addEventListener('change', () => { + const value = serverUrlInput.value || ''; + if (value) { + this.updateConfigValueTask('STASH', 'url', value); + alert(`Userscripts plugin server URL set to ${value}`); + } else { + this.getConfigValueTask('STASH', 'url').then(value => { + serverUrlInput.value = value; + }); + } + }); + serverUrlInput.disabled = true; + serverUrlInput.value = expectedUrl; + this.getConfigValueTask('STASH', 'url').then(value => { + if (value !== expectedUrl) { + return this.updateConfigValueTask('STASH', 'url', expectedUrl); + } + }); + + const apiKeyInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-apikey', 'userscript-server-apikey', 'Stash API Key', '', 'API Key…', true); + apiKeyInput.addEventListener('change', () => { + const value = apiKeyInput.value || ''; + this.updateConfigValueTask('STASH', 'api_key', value); + if (value) { + alert(`Userscripts plugin server api key set to ${value}`); + } else { + alert(`Userscripts plugin server api key value cleared`); + } + }); + apiKeyInput.disabled = true; + apiKeyInput.value = expectedApiKey; + this.getConfigValueTask('STASH', 'api_key').then(value => { + if (value !== expectedApiKey) { + return this.updateConfigValueTask('STASH', 'api_key', expectedApiKey); + } + }); + } else { + section = document.getElementById(this.settingsId); + } + + for (const callback of this.settingsCallbacks) { + callback(this.settingsId, section); + } + + if (this.pluginVersion) { + this.dispatchEvent(new CustomEvent('stash:pluginVersion', { + 'detail': this.pluginVersion + })); + } + + }); + } + addSystemSetting(callback) { + const section = document.getElementById(this.settingsId); + if (section) { + callback(this.settingsId, section); + } + this.settingsCallbacks.push(callback); + } + async createSystemSettingCheckbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader) { + const section = document.createElement("div"); + section.setAttribute('id', settingsId); + section.classList.add('card'); + section.style.display = 'none'; + section.innerHTML = `
+
+

${settingsHeader}

+
${settingsSubheader}
+
+
+
+ + +
+
+
`; + containerEl.appendChild(section); + return document.getElementById(inputId); + } + async createSystemSettingTextbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader, placeholder, visible) { + const section = document.createElement("div"); + section.setAttribute('id', settingsId); + section.classList.add('card'); + section.style.display = visible ? 'flex' : 'none'; + section.innerHTML = `
+
+

${settingsHeader}

+
${settingsSubheader}
+
+
+
+ +
+
+
`; + containerEl.appendChild(section); + return document.getElementById(inputId); + } + get serverUrl() { + return window.location.origin; + } + async waitForElement(selector, timeout = null, location = document.body, disconnectOnPageChange = false) { + return new Promise((resolve) => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)) + } + + const observer = new MutationObserver(async () => { + if (document.querySelector(selector)) { + resolve(document.querySelector(selector)) + observer.disconnect() + } else { + if (timeout) { + async function timeOver() { + return new Promise((resolve) => { + setTimeout(() => { + observer.disconnect() + resolve(false) + }, timeout) + }) + } + resolve(await timeOver()) + } + } + }) + + observer.observe(location, { + childList: true, + subtree: true, + }) + + const stash = this + if (disconnectOnPageChange) { + function disconnect() { + observer.disconnect() + stash.removeEventListener("stash:page", disconnect) + } + stash.addEventListener("stash:page", disconnect) + } + }) + } + async waitForElementDeath(selector, location = document.body, disconnectOnPageChange = false) { + return new Promise((resolve) => { + const observer = new MutationObserver(async () => { + if (!document.querySelector(selector)) { + resolve(true) + observer.disconnect() + } + }) + + observer.observe(location, { + childList: true, + subtree: true, + }) + + const stash = this + if (disconnectOnPageChange) { + function disconnect() { + observer.disconnect() + stash.removeEventListener("stash:page", disconnect) + } + stash.addEventListener("stash:page", disconnect) + } + }) + } + async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ + if (recursive) return + if (listenType === "tabs") { + const locationElement = await this.waitForElement(location, 10000, document.body, true) + const stash = this + let previousEvent = "" + function listenForTabClicks(domEvent) { + const clickedChild = domEvent.target ? domEvent.target : domEvent; + if(!clickedChild.classList?.contains("nav-link")) return + const tagName = clickedChild.getAttribute("data-rb-event-key") + const parentEvent = tagName.split("-")[0] + const childEvent = tagName.split("-").slice(1, -1).join("-") + event = `stash:page:${parentEvent}:${childEvent}` + if (previousEvent === event || !condition()) return + previousEvent = event + stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false) + stash._dispatchPageEvent(event) + } + if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active")) + locationElement.addEventListener("click", listenForTabClicks); + function removeEventListenerOnPageChange() { + locationElement.removeEventListener("click", listenForTabClicks) + stash.removeEventListener("stash:page", removeEventListenerOnPageChange) + } + stash.addEventListener("stash:page", removeEventListenerOnPageChange) + } else if (await this.waitForElement(selector, null, location, true)) { + this._dispatchPageEvent(event) + if (await this.waitForElementDeath(selector, location, true)) { + if (this._lastPathStr === window.location.pathname && !reRunGmMain) { + await this._listenForNonPageChanges({selector: selector, event: event}) + } else if (this._lastPathStr === window.location.pathname && reRunGmMain) { + this.gmMain({ + recursive: true, + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); + } + } + } + callback() + } + _dispatchPageEvent(event, addToHistory = true) { + this.dispatchEvent(new CustomEvent(event, { + detail: { + event: event, + lastEventState: { + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + } + } + })) + if (addToHistory) { + this.log.debug(`[Navigation] ${event}`); + if (event.startsWith("stash:")) { + this._lastStashPageEvent = event; + } + } + } + addPageListener(eventData) { + const {event, regex, callBack = () => {}, manuallyHandleDispatchEvent = false} = eventData + if (event && !event?.startsWith("stash:") && regex && this._pageListeners[event] === undefined){ + this._pageListeners[event] = { + regex: regex, + callBack: callBack, + manuallyHandleDispatchEvent: manuallyHandleDispatchEvent + } + return event + } else { + if (this._pageListeners[event] !== undefined) { + console.error(`Can't add page listener: Event ${event} already exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't add page listener: Event name can't start with "stash:"`) + } else { + console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`) + } + return false + } + } + removePageListener(event) { + if(event && !event?.startsWith("stash:") && this._pageListeners[event]){ + delete this._pageListeners[event] + return event + } else { + if (this._pageListeners[event] === undefined && event) { + console.error(`Can't remove page listener: Event ${event} doesn't exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't remove page listener: Event ${event} is a built in event`) + } else { + console.error(`Can't remove page listener: Missing "event" argument`) + } + return false + } + } + stopPageListener() { + clearInterval(this._pageURLCheckTimerId) + } + assignPageListeners() { + this._pageListeners = { + // scenes tab + "stash:page:scenes": { + regex: /\/scenes\?/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, + "stash:page:scene:new": { + regex: /\/scenes\/new/ + }, + "stash:page:scene": { + regex: /\/scenes\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".scene-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, + + // images tab + "stash:page:images": { + regex: /\/images\?/, + handleDisplayView: true, + }, + "stash:page:image": { + regex: /\/images\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".image-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, + + // movies tab + "stash:page:movies": { + regex: /\/movies\?/, + }, + "stash:page:movie": { + regex: /\/movies\/\d+/, + }, + "stash:page:movie:scenes": { + regex: /\/movies\/\d+\?/, + callBack: () => this.processTagger() + }, + + // markers tab + "stash:page:markers": { + regex: /\/scenes\/markers/ + }, + + // galleries tab + "stash:page:galleries": { + regex: /\/galleries\?/, + handleDisplayView: true, + }, + "stash:page:gallery:new": { + regex: /\/galleries\/new/, + }, + "stash:page:gallery:images": { + regex: /\/galleries\/\d+\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) + } + }, + "stash:page:gallery:add": { + regex: /\/galleries\/\d+\/add/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) + } + }, + + // performers tab + "stash:page:performers": { + regex: /\/performers\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/performers\?/) || this._detectReRenders ? this._dispatchPageEvent(event) : null + }, + "stash:page:performer:new": { + regex: /\/performers\/new/ + }, + "stash:page:performer": { + regex: /\/performers\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/performers\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#performer-edit", + event: "stash:page:performer:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:performer:scenes": { + regex: /\/performers\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:performer:galleries": { + regex: /\/performers\/\d+\/galleries/, + handleDisplayView: true + }, + "stash:page:performer:images": { + regex: /\/performers\/\d+\/images/, + handleDisplayView: true + }, + "stash:page:performer:movies": { + regex: /\/performers\/\d+\/movies/ + }, + "stash:page:performer:appearswith": { + regex: /\/performers\/\d+\/appearswith/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, + + // studios tab + "stash:page:studios": { + regex: /\/studios\?/, + handleDisplayView: true, + }, + "stash:page:studio:new": { + regex: /\/studios\/new/ + }, + "stash:page:studio": { + regex: /\/studios\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/studios\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#studio-edit", + event: "stash:page:studio:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:studio:scenes": { + regex: /\/studios\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:studio:galleries": { + regex: /\/studios\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:studio:images": { + regex: /\/studios\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:studio:performers": { + regex: /\/studios\/\d+\/performers/, + handleDisplayView: true, + }, + "stash:page:studio:movies": { + regex: /\/studios\/\d+\/movies/ + }, + "stash:page:studio:childstudios": { + regex: /\/studios\/\d+\/childstudios/, + handleDisplayView: true, + }, + + // tags tab + "stash:page:tags": { + regex: /\/tags\?/, + handleDisplayView: true, + }, + "stash:page:tag:new": { + regex: /\/tags\/new/ + }, + "stash:page:tag": { + regex: /\/tags\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/tags\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#tag-edit", + event: "stash:page:tag:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:tag:scenes": { + regex: /\/tags\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:tag:galleries": { + regex: /\/tags\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:tag:images": { + regex: /\/tags\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:tag:markers": { + regex: /\/tags\/\d+\/markers/ + }, + "stash:page:tag:performers": { + regex: /\/tags\/\d+\/performers/, + handleDisplayView: true, + }, + + // settings page + "stash:page:settings": { + regex: /\/settings/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/settings/) ? this._dispatchPageEvent(event) : null + }, + "stash:page:settings:tasks": { + regex: /\/settings\?tab=tasks/, + callback: () => this.hidePluginTasks() + }, + "stash:page:settings:library": { + regex: /\/settings\?tab=library/ + }, + "stash:page:settings:interface": { + regex: /\/settings\?tab=interface/ + }, + "stash:page:settings:security": { + regex: /\/settings\?tab=security/ + }, + "stash:page:settings:metadata-providers": { + regex: /\/settings\?tab=metadata-providers/ + }, + "stash:page:settings:services": { + regex: /\/settings\?tab=services/ + }, + "stash:page:settings:system": { + regex: /\/settings\?tab=system/, + callBack: () => this.createSettings() + }, + "stash:page:settings:plugins": { + regex: /\/settings\?tab=plugins/ + }, + "stash:page:settings:logs": { + regex: /\/settings\?tab=logs/ + }, + "stash:page:settings:tools": { + regex: /\/settings\?tab=tools/ + }, + "stash:page:settings:changelog": { + regex: /\/settings\?tab=changelog/ + }, + "stash:page:settings:about": { + regex: /\/settings\?tab=about/ + }, + + // stats page + "stash:page:stats": { + regex: /\/stats/ + }, + + // home page + "stash:page:home": { + regex: /\/$/, + callBack: () => this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "stash:page:home:edit", reRunGmMain: true}) + }, + } + } + gmMain(args) { + const events = Object.keys(this._pageListeners) + + for (const event of events) { + const {regex, callBack = async () => {}, manuallyHandleDispatchEvent = false, handleDisplayView = false} = this._pageListeners[event] + + let isDisplayViewPage = false + let isListPage, isWallPage, isTaggerPage + + if (handleDisplayView) { + isListPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=1/)) + isWallPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=2/)) + isTaggerPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=3/)) + + if (isListPage || isWallPage || isTaggerPage) isDisplayViewPage = true + } + + const handleDisplayViewCondition = handleDisplayView !== true || (handleDisplayView && (!isDisplayViewPage || args.lastHref === "")) + + if (this.matchUrl(window.location.href, regex) && handleDisplayViewCondition) { + if (!manuallyHandleDispatchEvent) this._dispatchPageEvent(event) + callBack({...args, location: window.location}, event) + } + + if (handleDisplayView) { + if (isListPage) { + this._dispatchPageEvent("stash:page:any:list", false); + this._dispatchPageEvent(event + ":list"); + } else if (isWallPage) { + this._dispatchPageEvent("stash:page:any:wall", false); + this._dispatchPageEvent(event + ":wall"); + } else if (isTaggerPage) { + this._dispatchPageEvent("stash:page:any:tagger", false); + this._dispatchPageEvent(event + ":tagger"); + } + } + } + } + addEventListeners(events, callback, ...options) { + events.forEach((event) => { + this.addEventListener(event, callback, ...options); + }); + } + hidePluginTasks() { + // hide userscript functions plugin tasks + waitForElementByXpath("//div[@id='tasks-panel']//h3[text()='Userscript Functions']/ancestor::div[contains(@class, 'setting-group')]", (elementId, el) => { + const tasks = el.querySelectorAll('.setting'); + for (const task of tasks) { + const taskName = task.querySelector('h3').innerText; + task.classList.add(this.visiblePluginTasks.indexOf(taskName) === -1 ? 'd-none' : 'd-flex'); + this.dispatchEvent(new CustomEvent('stash:plugin:task', { + 'detail': { + taskName, + task + } + })); + } + }); + } + async updateConfigValueTask(sectionKey, propName, value) { + return this.runPluginTask("userscript_functions", "Update Config Value", [{ + "key": "section_key", + "value": { + "str": sectionKey + } + }, { + "key": "prop_name", + "value": { + "str": propName + } + }, { + "key": "value", + "value": { + "str": value + } + }]); + } + async getConfigValueTask(sectionKey, propName) { + await this.runPluginTask("userscript_functions", "Get Config Value", [{ + "key": "section_key", + "value": { + "str": sectionKey + } + }, { + "key": "prop_name", + "value": { + "str": propName + } + }]); + + // poll logs until plugin task output appears + const prefix = `[Plugin / Userscript Functions] get_config_value: [${sectionKey}][${propName}] =`; + return this.pollLogsForMessage(prefix); + } + async pollLogsForMessage(prefix) { + const reqTime = Date.now(); + const reqData = { + "variables": {}, + "query": `query Logs { + logs { + time + level + message + } + }` + }; + await new Promise(r => setTimeout(r, 500)); + let retries = 0; + while (true) { + const delay = 2 ** retries * 100; + await new Promise(r => setTimeout(r, delay)); + retries++; + + const logs = await this.callGQL(reqData); + for (const log of logs.data.logs) { + const logTime = Date.parse(log.time); + if (logTime > reqTime && log.message.startsWith(prefix)) { + return log.message.replace(prefix, '').trim(); + } + } + + if (retries >= 5) { + throw `Poll logs failed for message: ${prefix}`; + } + } + } + processTagger() { + waitForElementByXpath("//button[text()='Scrape All']", (xpath, el) => { + this.dispatchEvent(new CustomEvent('tagger', { + 'detail': el + })); + + const searchItemContainer = document.querySelector('.tagger-container').lastChild; + + const observer = new MutationObserver(mutations => { + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Performer:')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:remoteperformer', { + 'detail': { + node, + mutation + } + })); + } else if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Studio:')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:remotestudio', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'SPAN' && node.innerText.startsWith('Matched:')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:local', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'UL') { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:container', { + 'detail': { + node, + mutation + } + })); + } else if (node?.classList?.contains('col-lg-6')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:subcontainer', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'H5') { // scene date + this.dispatchEvent(new CustomEvent('tagger:mutation:add:date', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'DIV' && node?.classList?.contains('d-flex') && node?.classList?.contains('flex-column')) { // scene stashid, url, details + this.dispatchEvent(new CustomEvent('tagger:mutation:add:detailscontainer', { + 'detail': { + node, + mutation + } + })); + } else { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:other', { + 'detail': { + node, + mutation + } + })); + } + }); + }); + this.dispatchEvent(new CustomEvent('tagger:mutations:searchitems', { + 'detail': mutations + })); + }); + observer.observe(searchItemContainer, { + childList: true, + subtree: true + }); + + const taggerContainerHeader = document.querySelector('.tagger-container-header'); + const taggerContainerHeaderObserver = new MutationObserver(mutations => { + this.dispatchEvent(new CustomEvent('tagger:mutations:header', { + 'detail': mutations + })); + }); + taggerContainerHeaderObserver.observe(taggerContainerHeader, { + childList: true, + subtree: true + }); + + for (const searchItem of document.querySelectorAll('.search-item')) { + this.dispatchEvent(new CustomEvent('tagger:searchitem', { + 'detail': searchItem + })); + } + + if (!document.getElementById('progress-bar')) { + const progressBar = createElementFromHTML(`
`); + progressBar.classList.add('progress'); + progressBar.style.display = 'none'; + taggerContainerHeader.appendChild(progressBar); + } + }); + waitForElementByXpath("//div[@class='tagger-container-header']/div/div[@class='row']/h4[text()='Configuration']", (xpath, el) => { + this.dispatchEvent(new CustomEvent('tagger:configuration', { + 'detail': el + })); + }); + } + setProgress(value) { + const progressBar = document.getElementById('progress-bar'); + if (progressBar) { + progressBar.firstChild.style.width = value + '%'; + progressBar.style.display = (value <= 0 || value > 100) ? 'none' : 'flex'; + } + } + processRemoteScenes(data) { + if (data.data?.scrapeMultiScenes) { + for (const matchResults of data.data.scrapeMultiScenes) { + for (const scene of matchResults) { + this.remoteScenes[scene.remote_site_id] = scene; + } + } + } else if (data.data?.scrapeSingleScene) { + for (const scene of data.data.scrapeSingleScene) { + this.remoteScenes[scene.remote_site_id] = scene; + } + } + } + processScene(data) { + if (data.data.findScene) { + this.scenes[data.data.findScene.id] = data.data.findScene; + } + } + processScenes(data) { + if (data.data.findScenes?.scenes) { + for (const scene of data.data.findScenes.scenes) { + this.scenes[scene.id] = scene; + } + } + } + processStudios(data) { + if (data.data.findStudios?.studios) { + for (const studio of data.data.findStudios.studios) { + this.studios[studio.id] = studio; + } + } + } + processPerformers(data) { + if (data.data.findPerformers?.performers) { + for (const performer of data.data.findPerformers.performers) { + this.performers[performer.id] = performer; + } + } + } + processApiKey(data) { + if (data.data.generateAPIKey != null && this.pluginVersion) { + this.updateConfigValueTask('STASH', 'api_key', data.data.generateAPIKey); + } + } + parseSearchItem(searchItem) { + const urlNode = searchItem.querySelector('a.scene-link'); + const url = new URL(urlNode.href); + const id = url.pathname.replace('/scenes/', ''); + const data = this.scenes[id]; + const nameNode = searchItem.querySelector('a.scene-link > div.TruncatedText'); + const name = nameNode.innerText; + const queryInput = searchItem.querySelector('input.text-input'); + const performerNodes = searchItem.querySelectorAll('.performer-tag-container'); + + return { + urlNode, + url, + id, + data, + nameNode, + name, + queryInput, + performerNodes + } + } + parseSearchResultItem(searchResultItem) { + const remoteUrlNode = searchResultItem.querySelector('.scene-details .optional-field .optional-field-content a'); + const remoteId = remoteUrlNode?.href.split('/').pop(); + const remoteUrl = remoteUrlNode?.href ? new URL(remoteUrlNode.href) : null; + const remoteData = this.remoteScenes[remoteId]; + + const sceneDetailNodes = searchResultItem.querySelectorAll('.scene-details .optional-field .optional-field-content'); + let urlNode = null; + let detailsNode = null; + for (const sceneDetailNode of sceneDetailNodes) { + if (sceneDetailNode.innerText.startsWith('http') && (remoteUrlNode?.href !== sceneDetailNode.innerText)) { + urlNode = sceneDetailNode; + } else if (!sceneDetailNode.innerText.startsWith('http')) { + detailsNode = sceneDetailNode; + } + } + + const imageNode = searchResultItem.querySelector('.scene-image-container .optional-field .optional-field-content'); + + const metadataNode = searchResultItem.querySelector('.scene-metadata'); + const titleNode = metadataNode.querySelector('h4 .optional-field .optional-field-content'); + const codeAndDateNodes = metadataNode.querySelectorAll('h5 .optional-field .optional-field-content'); + let codeNode = null; + let dateNode = null; + for (const node of codeAndDateNodes) { + if (node.textContent.includes('-')) { + dateNode = node; + } else { + codeNode = node; + } + } + + const entityNodes = searchResultItem.querySelectorAll('.entity-name'); + let studioNode = null; + const performerNodes = []; + for (const entityNode of entityNodes) { + if (entityNode.innerText.startsWith('Studio:')) { + studioNode = entityNode; + } else if (entityNode.innerText.startsWith('Performer:')) { + performerNodes.push(entityNode); + } + } + + const matchNodes = searchResultItem.querySelectorAll('div.col-lg-6 div.mt-2 div.row.no-gutters.my-2 span.ml-auto'); + const matches = [] + for (const matchNode of matchNodes) { + let matchType = null; + const entityNode = matchNode.parentElement.querySelector('.entity-name'); + + const matchName = matchNode.querySelector('.optional-field-content b').innerText; + const remoteName = entityNode.querySelector('b').innerText; + + let data; + if (entityNode.innerText.startsWith('Performer:')) { + matchType = 'performer'; + if (remoteData) { + data = remoteData.performers.find(performer => performer.name === remoteName); + } + } else if (entityNode.innerText.startsWith('Studio:')) { + matchType = 'studio'; + if (remoteData) { + data = remoteData.studio + } + } + + matches.push({ + matchType, + matchNode, + entityNode, + matchName, + remoteName, + data + }); + } + + return { + remoteUrlNode, + remoteId, + remoteUrl, + remoteData, + urlNode, + detailsNode, + imageNode, + titleNode, + codeNode, + dateNode, + studioNode, + performerNodes, + matches + } + } +} + +window.stash = new Stash(); + +function waitForElementQuerySelector(query, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = document.querySelector(query); + if (element) { + callBack(query, element); + } else { + waitForElementQuerySelector(query, callBack, time); + } + }, time); +} + +function waitForElementClass(elementId, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = document.getElementsByClassName(elementId); + if (element.length > 0) { + callBack(elementId, element); + } else { + waitForElementClass(elementId, callBack, time); + } + }, time); +} + +function waitForElementId(elementId, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = document.getElementById(elementId); + if (element != null) { + callBack(elementId, element); + } else { + waitForElementId(elementId, callBack, time); + } + }, time); +} + +function waitForElementByXpath(xpath, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = getElementByXpath(xpath); + if (element) { + callBack(xpath, element); + } else { + waitForElementByXpath(xpath, callBack, time); + } + }, time); +} + +function getElementByXpath(xpath, contextNode) { + return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +function createElementFromHTML(htmlString) { + const div = document.createElement('div'); + div.innerHTML = htmlString.trim(); + + // Change this to div.childNodes to support multiple top-level nodes. + return div.firstChild; +} + +function getElementByXpath(xpath, contextNode) { + return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +function getElementsByXpath(xpath, contextNode) { + return document.evaluate(xpath, contextNode || document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); +} + +function getClosestAncestor(el, selector, stopSelector) { + let retval = null; + while (el) { + if (el.matches(selector)) { + retval = el; + break + } else if (stopSelector && el.matches(stopSelector)) { + break + } + el = el.parentElement; + } + return retval; +} + +function setNativeValue(element, value) { + const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set; + const prototype = Object.getPrototypeOf(element); + const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set; + + if (valueSetter && valueSetter !== prototypeValueSetter) { + prototypeValueSetter.call(element, value); + } else { + valueSetter.call(element, value); + } +} + +function updateTextInput(element, value) { + setNativeValue(element, value); + element.dispatchEvent(new Event('input', { + bubbles: true + })); +} + +function concatRegexp(reg, exp) { + let flags = reg.flags + exp.flags; + flags = Array.from(new Set(flags.split(''))).join(); + return new RegExp(reg.source + exp.source, flags); +} + +function sortElementChildren(node) { + const items = node.childNodes; + const itemsArr = []; + for (const i in items) { + if (items[i].nodeType == Node.ELEMENT_NODE) { // get rid of the whitespace text nodes + itemsArr.push(items[i]); + } + } + + itemsArr.sort((a, b) => { + return a.innerHTML == b.innerHTML ? + 0 : + (a.innerHTML > b.innerHTML ? 1 : -1); + }); + + for (let i = 0; i < itemsArr.length; i++) { + node.appendChild(itemsArr[i]); + } +} + +function xPathResultToArray(result) { + let node = null; + const nodes = []; + while (node = result.iterateNext()) { + nodes.push(node); + } + return nodes; +} + +function createStatElement(container, title, heading) { + const statEl = document.createElement('div'); + statEl.classList.add('stats-element'); + container.appendChild(statEl); + + const statTitle = document.createElement('p'); + statTitle.classList.add('title'); + statTitle.innerText = title; + statEl.appendChild(statTitle); + + const statHeading = document.createElement('p'); + statHeading.classList.add('heading'); + statHeading.innerText = heading; + statEl.appendChild(statHeading); +} + +const reloadImg = url => + fetch(url, { + cache: 'reload', + mode: 'no-cors' + }) + .then(() => document.body.querySelectorAll(`img[src='${url}']`) + .forEach(img => img.src = url)); diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index 6e2ee88..7915210 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -45,23 +45,41 @@ class Stash extends EventTarget { this._pageUrlCheckInterval = pageUrlCheckInterval; this._detectReRenders = detectReRenders; this.fireOnHashChangesToo = true; - waitForElementQuerySelector(this._detectReRenders ? ".main > div" : "html", () => { - this.pageURLCheckTimer = setInterval(() => { + this._lastPathStr = ""; + this._lastQueryStr = ""; + this._lastHashStr = ""; + this._lastHref = ""; + this._lastStashPageEvent = ""; + this.waitForElement(this._detectReRenders ? ".main > div" : "html").then(() => { + this._pageURLCheckTimerId = setInterval(() => { // Loop every 100 ms - if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || this.lastHref !== location.href || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { - this.log.debug('[Navigation] Page Changed'); - this.dispatchEvent(new Event('page')); - this.gmMain(false, this.lastHref); - this.lastPathStr = location.pathname; - this.lastQueryStr = location.search; - this.lastHashStr = location.hash; - this.lastHref = location.href; - if (this._detectReRenders) waitForElementQuerySelector(".main > div", (query, element) => { - element.setAttribute("stashUserscriptLibrary", ""); - }, 10) + if ( + this._lastPathStr !== location.pathname || + this._lastQueryStr !== location.search || + (this.fireOnHashChangesToo && this._lastHashStr !== location.hash) || + this._lastHref !== location.href || + (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders) + ) { + this._dispatchPageEvent("stash:page", false) + this.gmMain({ + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); + this._lastPathStr = location.pathname + this._lastQueryStr = location.search + this._lastHashStr = location.hash + this._lastHref = location.href + if (this._detectReRenders) { + this.waitForElement(".main > div", 10000).then((element) => { + element.setAttribute("stashUserscriptLibrary", ""); + }) + } } }, this._pageUrlCheckInterval); - }, 1) + }) stashListener.addEventListener('response', (evt) => { if (evt.detail.data?.plugins) { this.getPluginVersion(evt.detail); @@ -96,17 +114,18 @@ class Stash extends EventTarget { this.studios = {}; this.performers = {}; this.userscripts = []; + this._pageListeners = {}; + this.assignPageListeners() } async getVersion() { const reqData = { "operationName": "", "variables": {}, "query": `query version { - version { - version - } -} -` + version { + version + } + }` }; const data = await this.callGQL(reqData); const versionString = data.data.version.version; @@ -267,10 +286,10 @@ class Stash extends EventTarget { }; return this.callGQL(reqData); } - matchUrl(location, fragment) { - const regexp = concatRegexp(new RegExp(location.origin), fragment); + matchUrl(href, fragment) { + const regexp = concatRegexp(new RegExp(window.location.origin), fragment); this.log.debug(regexp, location.href.match(regexp)); - return location.href.match(regexp) != null; + return href.match(regexp) != null; } createSettings() { waitForElementId('configuration-tabs-tabpane-system', async (elementId, el) => { @@ -421,9 +440,9 @@ class Stash extends EventTarget { if (disconnectOnPageChange) { function disconnect() { observer.disconnect() - stash.removeEventListener("page", disconnect) + stash.removeEventListener("stash:page", disconnect) } - this.addEventListener("page", disconnect) + stash.addEventListener("stash:page", disconnect) } }) } @@ -445,14 +464,14 @@ class Stash extends EventTarget { if (disconnectOnPageChange) { function disconnect() { observer.disconnect() - stash.removeEventListener("page", disconnect) + stash.removeEventListener("stash:page", disconnect) } - this.addEventListener("page", disconnect) + stash.addEventListener("stash:page", disconnect) } }) } - async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", eventMessage = "", isRecursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ - if (isRecursive) return + async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ + if (recursive) return if (listenType === "tabs") { const locationElement = await this.waitForElement(location, 10000, document.body, true) const stash = this @@ -463,386 +482,431 @@ class Stash extends EventTarget { const tagName = clickedChild.getAttribute("data-rb-event-key") const parentEvent = tagName.split("-")[0] const childEvent = tagName.split("-").slice(1, -1).join("-") - event = `page:${parentEvent}:${childEvent}` + event = `stash:page:${parentEvent}:${childEvent}` if (previousEvent === event || !condition()) return previousEvent = event - stash.log.debug("[Navigation] " + `${parentEvent[0].toUpperCase() + parentEvent.slice(1)} Page - ${childEvent[0].toUpperCase() + childEvent.slice(1)}`); - stash.dispatchEvent(new Event(event)); + stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false) + stash._dispatchPageEvent(event) } if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active")) locationElement.addEventListener("click", listenForTabClicks); function removeEventListenerOnPageChange() { locationElement.removeEventListener("click", listenForTabClicks) - stash.removeEventListener("page", removeEventListenerOnPageChange) + stash.removeEventListener("stash:page", removeEventListenerOnPageChange) } - stash.addEventListener("page", removeEventListenerOnPageChange) + stash.addEventListener("stash:page", removeEventListenerOnPageChange) } else if (await this.waitForElement(selector, null, location, true)) { - this.log.debug("[Navigation] " + eventMessage); - this.dispatchEvent(new Event(event)); + this._dispatchPageEvent(event) if (await this.waitForElementDeath(selector, location, true)) { - if (this.lastPathStr === window.location.pathname && !reRunGmMain) { - this._listenForNonPageChanges({selector: selector, event: event, eventMessage: eventMessage}) - } else if (this.lastPathStr === window.location.pathname && reRunGmMain) { - this.gmMain(true, this.lastHref) + if (this._lastPathStr === window.location.pathname && !reRunGmMain) { + await this._listenForNonPageChanges({selector: selector, event: event}) + } else if (this._lastPathStr === window.location.pathname && reRunGmMain) { + this.gmMain({ + recursive: true, + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); } } } callback() } - gmMain(isRecursive = false, lastHref) { - const location = window.location; - this.log.debug(URL, window.location); - - // Run parent page specific functions - // detect performer edit page - if (this.matchUrl(location, /\/performers\/\d+/)) { - if(!new RegExp(/\/performers\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Performer Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:performer')); - } - - this._listenForNonPageChanges({selector: "#performer-edit", event: "page:performer:edit", eventMessage: "Performer Page - Edit", reRunGmMain: true, callback: () => { - if (this._detectReRenders) { - this.log.debug('[Navigation] Performer Page'); - this.dispatchEvent(new Event('page:performer')); + _dispatchPageEvent(event, addToHistory = true) { + this.dispatchEvent(new CustomEvent(event, { + detail: { +event: event, + lastEventState: { + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, } - }}) - } - // detect studio edit page - else if (this.matchUrl(location, /\/studios\/\d+/)) { - if(!new RegExp(/\/studios\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Studio Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:studio')); } - - this._listenForNonPageChanges({selector: "#studio-edit", event: "page:studio:edit", eventMessage: "Studio Page - Edit", reRunGmMain: true, callback: () => { - if (this._detectReRenders) { - this.log.debug('[Navigation] Studio Page'); - this.dispatchEvent(new Event('page:studio')); - } - }}) - } - // detect tag edit page - else if (this.matchUrl(location, /\/tags\/\d+/)) { - if(!new RegExp(/\/tags\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Tag Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:tag')); + })) + if (addToHistory) { + this.log.debug(`[Navigation] ${event}`); + if (event.startsWith("stash:")) { + this._lastStashPageEvent = event; } + } + } + addPageListener(eventData) { + const {event, regex, callBack = () => {}, manuallyHandleDispatchEvent = false} = eventData + if (event && !event?.startsWith("stash:") && regex && this._pageListeners[event] === undefined){ + this._pageListeners[event] = { + regex: regex, + callBack: callBack, + manuallyHandleDispatchEvent: manuallyHandleDispatchEvent + } + return event + } else { + if (this._pageListeners[event] !== undefined) { + console.error(`Can't add page listener: Event ${event} already exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't add page listener: Event name can't start with "stash:"`) + } else { + console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`) + } + return false + } + } + removePageListener(event) { + if(event && !event?.startsWith("stash:") && this._pageListeners[event]){ + delete this._pageListeners[event] + return event + } else { + if (this._pageListeners[event] === undefined && event) { + console.error(`Can't remove page listener: Event ${event} doesn't exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't remove page listener: Event ${event} is a built in event`) + } else { + console.error(`Can't remove page listener: Missing "event" argument`) + } + return false + } + } + stopPageListener() { + clearInterval(this._pageURLCheckTimerId) + } + assignPageListeners() { + this._pageListeners = { + // scenes tab + "stash:page:scenes": { + regex: /\/scenes\?/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, + "stash:page:scene:new": { + regex: /\/scenes\/new/ + }, + "stash:page:scene": { + regex: /\/scenes\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".scene-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, - this._listenForNonPageChanges({selector: "#tag-edit", event: "page:tag:edit", eventMessage: "Tag Page - Edit", reRunGmMain: true, callback: () => { - if (this._detectReRenders) { - this.log.debug('[Navigation] Tag Page'); - this.dispatchEvent(new Event('page:tag')); + // images tab + "stash:page:images": { + regex: /\/images\?/, + handleDisplayView: true, + }, + "stash:page:image": { + regex: /\/images\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".image-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, + + // movies tab + "stash:page:movies": { + regex: /\/movies\?/, + }, + "stash:page:movie": { + regex: /\/movies\/\d+/, + }, + "stash:page:movie:scenes": { + regex: /\/movies\/\d+\?/, + callBack: () => this.processTagger() + }, + + // markers tab + "stash:page:markers": { + regex: /\/scenes\/markers/ + }, + + // galleries tab + "stash:page:galleries": { + regex: /\/galleries\?/, + handleDisplayView: true, + }, + "stash:page:gallery:new": { + regex: /\/galleries\/new/, + }, + "stash:page:gallery:images": { + regex: /\/galleries\/\d+\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) } - }}) - } + }, + "stash:page:gallery:add": { + regex: /\/galleries\/\d+\/add/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) + } + }, - // markers page - if (this.matchUrl(location, /\/scenes\/markers/)) { - this.log.debug('[Navigation] Markers Page'); - this.dispatchEvent(new Event('page:markers')); + // performers tab + "stash:page:performers": { + regex: /\/performers\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/performers\?/) || this._detectReRenders ? this._dispatchPageEvent(event) : null + }, + "stash:page:performer:new": { + regex: /\/performers\/new/ + }, + "stash:page:performer": { + regex: /\/performers\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/performers\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#performer-edit", + event: "stash:page:performer:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:performer:scenes": { + regex: /\/performers\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:performer:galleries": { + regex: /\/performers\/\d+\/galleries/, + handleDisplayView: true + }, + "stash:page:performer:images": { + regex: /\/performers\/\d+\/images/, + handleDisplayView: true + }, + "stash:page:performer:movies": { + regex: /\/performers\/\d+\/movies/ + }, + "stash:page:performer:appearswith": { + regex: /\/performers\/\d+\/appearswith/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, + + // studios tab + "stash:page:studios": { + regex: /\/studios\?/, + handleDisplayView: true, + }, + "stash:page:studio:new": { + regex: /\/studios\/new/ + }, + "stash:page:studio": { + regex: /\/studios\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/studios\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#studio-edit", + event: "stash:page:studio:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:studio:scenes": { + regex: /\/studios\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:studio:galleries": { + regex: /\/studios\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:studio:images": { + regex: /\/studios\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:studio:performers": { + regex: /\/studios\/\d+\/performers/, + handleDisplayView: true, + }, + "stash:page:studio:movies": { + regex: /\/studios\/\d+\/movies/ + }, + "stash:page:studio:childstudios": { + regex: /\/studios\/\d+\/childstudios/, + handleDisplayView: true, + }, + + // tags tab + "stash:page:tags": { + regex: /\/tags\?/, + handleDisplayView: true, + }, + "stash:page:tag:new": { + regex: /\/tags\/new/ + }, + "stash:page:tag": { + regex: /\/tags\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/tags\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#tag-edit", + event: "stash:page:tag:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:tag:scenes": { + regex: /\/tags\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:tag:galleries": { + regex: /\/tags\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:tag:images": { + regex: /\/tags\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:tag:markers": { + regex: /\/tags\/\d+\/markers/ + }, + "stash:page:tag:performers": { + regex: /\/tags\/\d+\/performers/, + handleDisplayView: true, + }, + + // settings page + "stash:page:settings": { + regex: /\/settings/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/settings/) ? this._dispatchPageEvent(event) : null + }, + "stash:page:settings:tasks": { + regex: /\/settings\?tab=tasks/, + callback: () => this.hidePluginTasks() + }, + "stash:page:settings:library": { + regex: /\/settings\?tab=library/ + }, + "stash:page:settings:interface": { + regex: /\/settings\?tab=interface/ + }, + "stash:page:settings:security": { + regex: /\/settings\?tab=security/ + }, + "stash:page:settings:metadata-providers": { + regex: /\/settings\?tab=metadata-providers/ + }, + "stash:page:settings:services": { + regex: /\/settings\?tab=services/ + }, + "stash:page:settings:system": { + regex: /\/settings\?tab=system/, + callBack: () => this.createSettings() + }, + "stash:page:settings:plugins": { + regex: /\/settings\?tab=plugins/ + }, + "stash:page:settings:logs": { + regex: /\/settings\?tab=logs/ + }, + "stash:page:settings:tools": { + regex: /\/settings\?tab=tools/ + }, + "stash:page:settings:changelog": { + regex: /\/settings\?tab=changelog/ + }, + "stash:page:settings:about": { + regex: /\/settings\?tab=about/ + }, + + // stats page + "stash:page:stats": { + regex: /\/stats/ + }, + + // home page + "stash:page:home": { + regex: /\/$/, + callBack: () => this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "stash:page:home:edit", reRunGmMain: true}) + }, } - // create scene page - else if (this.matchUrl(location, /\/scenes\/new/)) { - this.log.debug('[Navigation] Create Scene Page'); - this.dispatchEvent(new Event('page:scene:new')); - } - // scene page - else if (this.matchUrl(location, /\/scenes\/\d+/)) { - this.log.debug('[Navigation] Scene Page'); - this.dispatchEvent(new Event('page:scene')); + } + gmMain(args) { + const events = Object.keys(this._pageListeners) + + for (const event of events) { + const {regex, callBack = async () => {}, manuallyHandleDispatchEvent = false, handleDisplayView = false} = this._pageListeners[event] - this._listenForNonPageChanges({ - location: ".scene-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive - }) - } - // scenes page - else if (this.matchUrl(location, /\/scenes\?/)) { - this.log.debug('[Navigation] Scenes Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:scenes')); - } - - // image page - else if (this.matchUrl(location, /\/images\/\d+/)) { - this.log.debug('[Navigation] Image Page'); - this.dispatchEvent(new Event('page:image')); - - this._listenForNonPageChanges({ - location: ".image-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive - }) - } - // images page - else if (this.matchUrl(location, /\/images\?/)) { - this.log.debug('[Navigation] Images Page'); - this.dispatchEvent(new Event('page:images')); - } - - // movie scenes page - else if (this.matchUrl(location, /\/movies\/\d+\?/)) { - this.log.debug('[Navigation] Movie Page - Scenes'); - this.processTagger(); - this.dispatchEvent(new Event('page:movie:scenes')); - } - // movie page - else if (this.matchUrl(location, /\/movies\/\d+/)) { - this.log.debug('[Navigation] Movie Page'); - this.dispatchEvent(new Event('page:movie')); - } - // movies page - else if (this.matchUrl(location, /\/movies\?/)) { - this.log.debug('[Navigation] Movies Page'); - this.dispatchEvent(new Event('page:movies')); - } - - // create gallery page - else if (this.matchUrl(location, /\/galleries\/new/)) { - this.log.debug('[Navigation] Create Gallery Page'); - this.dispatchEvent(new Event('page:gallery:new')); - } - // gallery add page - else if (this.matchUrl(location, /\/galleries\/\d+\/add/)) { - if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Gallery Page'); - this.dispatchEvent(new Event('page:gallery')); - this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"}) - } - - this.log.debug('[Navigation] Gallery Page - Add'); - this.dispatchEvent(new Event('page:gallery:add')); + let isDisplayViewPage = false + let isListPage, isWallPage, isTaggerPage - this._listenForNonPageChanges({ - location: ".gallery-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive, - listenDefaultTab: false - }) - } - // gallery page - else if (this.matchUrl(location, /\/galleries\/\d+/)) { - if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Gallery Page'); - this.dispatchEvent(new Event('page:gallery')); - this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"}) + if (handleDisplayView) { + isListPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=1/)) + isWallPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=2/)) + isTaggerPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=3/)) + + if (isListPage || isWallPage || isTaggerPage) isDisplayViewPage = true } - this.log.debug('[Navigation] Gallery Page - Images'); - this.dispatchEvent(new Event('page:gallery:images')); + const handleDisplayViewCondition = handleDisplayView !== true || (handleDisplayView && (!isDisplayViewPage || args.lastHref === "")) - this._listenForNonPageChanges({ - location: ".gallery-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive, - listenDefaultTab: false - }) - } - // galleries page - else if (this.matchUrl(location, /\/galleries\?/)) { - this.log.debug('[Navigation] Galleries Page'); - this.dispatchEvent(new Event('page:galleries')); - } - - // performer galleries page - else if (this.matchUrl(location, /\/performers\/\d+\/galleries/)) { - this.log.debug('[Navigation] Performer Page - Galleries'); - this.dispatchEvent(new Event('page:performer:galleries')); - } - // performer images page - else if (this.matchUrl(location, /\/performers\/\d+\/images/)) { - this.log.debug('[Navigation] Performer Page - Images'); - this.dispatchEvent(new Event('page:performer:images')); - } - // performer movies page - else if (this.matchUrl(location, /\/performers\/\d+\/movies/)) { - this.log.debug('[Navigation] Performer Page - Movies'); - this.dispatchEvent(new Event('page:performer:movies')); - } - // performer appearswith page - else if (this.matchUrl(location, /\/performers\/\d+\/appearswith/)) { - this.log.debug('[Navigation] Performer Page - Appears With'); - this.processTagger(); - this.dispatchEvent(new Event('page:performer:appearswith')); - } - // create performer page - else if (this.matchUrl(location, /\/performers\/new/)) { - this.log.debug('[Navigation] Create Performer Page'); - this.dispatchEvent(new Event('page:performer:new')); - } - // performer scenes page - else if (this.matchUrl(location, /\/performers\/\d+\?/)) { - this.log.debug('[Navigation] Performer Page - Scenes'); - this.dispatchEvent(new Event('page:performer:scenes')); - } - // performers page - else if (this.matchUrl(location, /\/performers\?/)) { - this.log.debug('[Navigation] Performers Page'); - this.dispatchEvent(new Event('page:performers')); - } - - // studio galleries page - else if (this.matchUrl(location, /\/studios\/\d+\/galleries/)) { - this.log.debug('[Navigation] Studio Page - Galleries'); - this.dispatchEvent(new Event('page:studio:galleries')); - } - // studio images page - else if (this.matchUrl(location, /\/studios\/\d+\/images/)) { - this.log.debug('[Navigation] Studio Page - Images'); - this.dispatchEvent(new Event('page:studio:images')); - } - // studio performers page - else if (this.matchUrl(location, /\/studios\/\d+\/performers/)) { - this.log.debug('[Navigation] Studio Page - Performers'); - this.dispatchEvent(new Event('page:studio:performers')); - } - // studio movies page - else if (this.matchUrl(location, /\/studios\/\d+\/movies/)) { - this.log.debug('[Navigation] Studio Page - Movies'); - this.dispatchEvent(new Event('page:studio:movies')); - } - // studio childstudios page - else if (this.matchUrl(location, /\/studios\/\d+\/childstudios/)) { - this.log.debug('[Navigation] Studio Page - Child Studios'); - this.dispatchEvent(new Event('page:studio:childstudios')); - } - // create studio page - else if (this.matchUrl(location, /\/studios\/new/)) { - this.log.debug('[Navigation] Create Studio Page'); - this.dispatchEvent(new Event('page:studio:new')); - } - // studio scenes page - else if (this.matchUrl(location, /\/studios\/\d+\?/)) { - this.log.debug('[Navigation] Studio Page - Scenes'); - this.dispatchEvent(new Event('page:studio:scenes')); - } - // studios page - else if (this.matchUrl(location, /\/studios\?/)) { - this.log.debug('[Navigation] Studios Page'); - this.dispatchEvent(new Event('page:studios')); - } - - // tag galleries page - else if (this.matchUrl(location, /\/tags\/\d+\/galleries/)) { - this.log.debug('[Navigation] Tag Page - Galleries'); - this.dispatchEvent(new Event('page:tag:galleries')); - } - // tag images page - else if (this.matchUrl(location, /\/tags\/\d+\/images/)) { - this.log.debug('[Navigation] Tag Page - Images'); - this.dispatchEvent(new Event('page:tag:images')); - } - // tag markers page - else if (this.matchUrl(location, /\/tags\/\d+\/markers/)) { - this.log.debug('[Navigation] Tag Page - Markers'); - this.dispatchEvent(new Event('page:tag:markers')); - } - // tag performers page - else if (this.matchUrl(location, /\/tags\/\d+\/performers/)) { - this.log.debug('[Navigation] Tag Page - Performers'); - this.dispatchEvent(new Event('page:tag:performers')); - } - // create tag page - else if (this.matchUrl(location, /\/tags\/new/)) { - this.log.debug('[Navigation] Create Tag Page'); - this.dispatchEvent(new Event('page:tag:new')); - } - // tag scenes page - else if (this.matchUrl(location, /\/tags\/\d+\?/)) { - this.log.debug('[Navigation] Tag Page - Scenes'); - this.dispatchEvent(new Event('page:tag:scenes')); - } - // tags page - else if (this.matchUrl(location, /\/tags\?/)) { - this.log.debug('[Navigation] Tags Page'); - this.dispatchEvent(new Event('page:tags')); - } - - // settings page tasks tab - else if (this.matchUrl(location, /\/settings\?tab=tasks/)) { - if(!new RegExp(/\/settings\?/).test(lastHref)){ - this.log.debug('[Navigation] Settings Page'); - this.dispatchEvent(new Event('page:settings')); - this.hidePluginTasks(); + if (this.matchUrl(window.location.href, regex) && handleDisplayViewCondition) { + if (!manuallyHandleDispatchEvent) this._dispatchPageEvent(event) + callBack({...args, location: window.location}, event) } - this.log.debug('[Navigation] Settings Page Tasks Tab'); - this.dispatchEvent(new Event('page:settings:tasks')); - } - // settings page library tab - else if (this.matchUrl(location, /\/settings\?tab=library/)) { - this.log.debug('[Navigation] Settings Page Library Tab'); - this.dispatchEvent(new Event('page:settings:library')); - } - // settings page interface tab - else if (this.matchUrl(location, /\/settings\?tab=interface/)) { - this.log.debug('[Navigation] Settings Page Interface Tab'); - this.dispatchEvent(new Event('page:settings:interface')); - } - // settings page security tab - else if (this.matchUrl(location, /\/settings\?tab=security/)) { - this.log.debug('[Navigation] Settings Page Security Tab'); - this.dispatchEvent(new Event('page:settings:security')); - } - // settings page metadata providers tab - else if (this.matchUrl(location, /\/settings\?tab=metadata-providers/)) { - this.log.debug('[Navigation] Settings Page Metadata Providers Tab'); - this.dispatchEvent(new Event('page:settings:metadata-providers')); - } - // settings page services tab - else if (this.matchUrl(location, /\/settings\?tab=services/)) { - this.log.debug('[Navigation] Settings Page Services Tab'); - this.dispatchEvent(new Event('page:settings:services')); - } - // settings page system tab - else if (this.matchUrl(location, /\/settings\?tab=system/)) { - this.log.debug('[Navigation] Settings Page System Tab'); - this.createSettings(); - this.dispatchEvent(new Event('page:settings:system')); - } - // settings page plugins tab - else if (this.matchUrl(location, /\/settings\?tab=plugins/)) { - this.log.debug('[Navigation] Settings Page Plugins Tab'); - this.dispatchEvent(new Event('page:settings:plugins')); - } - // settings page logs tab - else if (this.matchUrl(location, /\/settings\?tab=logs/)) { - this.log.debug('[Navigation] Settings Page Logs Tab'); - this.dispatchEvent(new Event('page:settings:logs')); - } - // settings page tools tab - else if (this.matchUrl(location, /\/settings\?tab=tools/)) { - this.log.debug('[Navigation] Settings Page Tools Tab'); - this.dispatchEvent(new Event('page:settings:tools')); - } - // settings page changelog tab - else if (this.matchUrl(location, /\/settings\?tab=changelog/)) { - this.log.debug('[Navigation] Settings Page Changelog Tab'); - this.dispatchEvent(new Event('page:settings:changelog')); - } - // settings page about tab - else if (this.matchUrl(location, /\/settings\?tab=about/)) { - this.log.debug('[Navigation] Settings Page About Tab'); - this.dispatchEvent(new Event('page:settings:about')); - } - - // stats page - else if (this.matchUrl(location, /\/stats/)) { - this.log.debug('[Navigation] Stats Page'); - this.dispatchEvent(new Event('page:stats')); - } - - // home page - else if (this.matchUrl(location, /\/$/)) { - this.log.debug('[Navigation] Home Page'); - this.dispatchEvent(new Event('page:home')); - - this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "page:home:edit", eventMessage: "Home Page - Edit", reRunGmMain: true}) + if (handleDisplayView) { + if (isListPage) { + this._dispatchPageEvent("stash:page:any:list", false); + this._dispatchPageEvent(event + ":list"); + } else if (isWallPage) { + this._dispatchPageEvent("stash:page:any:wall", false); + this._dispatchPageEvent(event + ":wall"); + } else if (isTaggerPage) { + this._dispatchPageEvent("stash:page:any:tagger", false); + this._dispatchPageEvent(event + ":tagger"); + } + } } } addEventListeners(events, callback, ...options) { diff --git a/plugins/stats/stats.js b/plugins/stats/stats.js index 65f30d5..3444a6f 100644 --- a/plugins/stats/stats.js +++ b/plugins/stats/stats.js @@ -113,7 +113,7 @@ createStatElement(row, totalCount, 'Markers'); } - stash.addEventListener('page:stats', function() { + stash.addEventListener('stash:page:stats', function() { waitForElementByXpath("//div[contains(@class, 'container-fluid')]/div[@class='mt-5']", function(xpath, el) { if (!document.getElementById('custom-stats-row')) { const changelog = el.querySelector('div.changelog');