// ==UserScript==
// @name Rule34 Favorites Search Gallery
// @namespace bruh3396
// @version 1.19.1
// @description Search, View, and Play Rule34 Favorites (Desktop/Android/iOS)
// @author bruh3396
// @compatible Chrome
// @compatible Edge
// @compatible Firefox
// @compatible Safari
// @compatible Opera
// @match https://rule34.xxx/index.php?page=favorites&s=view&id=*
// @match https://rule34.xxx/index.php?page=post&s=list*
// ==/UserScript==
"use strict";
(() => {
function getCookie(key, defaultValue) {
const nameEquation = `${key}=`;
const cookies = document.cookie.split(";").map((cookie) => cookie.trimStart());
for (const cookie of cookies) {
if (cookie.startsWith(nameEquation)) {
return cookie.substring(nameEquation.length, cookie.length);
}
}
return defaultValue;
}
function setCookie(key, value) {
let cookieString = `${key}=${value ?? ""}`;
const expirationDate = /* @__PURE__ */ new Date();
expirationDate.setFullYear(expirationDate.getFullYear() + 1);
cookieString += `; expires=${expirationDate.toUTCString()}`;
cookieString += "; path=/";
document.cookie = cookieString;
}
function getUserId() {
return getCookie("user_id", "");
}
function getFavoritesPageId() {
const match = /(?:&|\?)id=(\d+)/.exec(window.location.href);
return match ? match[1] : null;
}
function isUserIsOnTheirOwnFavoritesPage() {
return getUserId() === getFavoritesPageId();
}
function getTagBlacklist() {
let tags = getCookie("tag_blacklist", "") ?? "";
for (let i = 0; i < 3; i += 1) {
tags = decodeURIComponent(tags).replace(/(?:^| )-/, "");
}
return tags;
}
var ON_SEARCH_PAGE = location.href.includes("page=post&s=list");
var ON_FAVORITES_PAGE = location.href.includes("page=favorites");
var ON_POST_PAGE = location.href.includes("page=post&s=view");
var USING_FIREFOX = navigator.userAgent.toLowerCase().includes("firefox");
var ON_MOBILE_DEVICE = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
var ON_DESKTOP_DEVICE = !ON_MOBILE_DEVICE;
var USER_IS_ON_THEIR_OWN_FAVORITES_PAGE = isUserIsOnTheirOwnFavoritesPage();
var STORAGE_KEY = "preferences";
function readAll() {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
}
function get(key) {
return readAll()[key];
}
function set(key, value) {
const preferences = readAll();
preferences[key] = value;
localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
}
var Preference = class {
defaultValue;
key;
constructor(key, defaultValue) {
this.key = key;
this.defaultValue = defaultValue;
}
get value() {
return get(this.key) ?? this.defaultValue;
}
set(value) {
set(this.key, value);
}
};
var Preferences = {
savedSearchesVisible: new Preference("savedSearchVisibility", false),
savedSearchSuggestionsEnabled: new Preference("savedSearchSuggestions", false),
savedSearchTutorialEnabled: new Preference("savedSearchTutorial", false),
captionsVisible: new Preference("showCaptions", false),
tooltipsVisible: new Preference("showTooltip", false),
showOnHoverEnabled: new Preference("showOnHover", false),
tagAliasingEnabled: new Preference("tagAliasing", false),
allowedRatings: new Preference("allowedRatings", 7),
favoriteFinderId: new Preference("findFavorite", ""),
searchPagesEnabled: new Preference("enableOnSearchPages", false),
performanceProfile: new Preference("performanceProfile", 0 /* HIGH */),
favoritesLayout: new Preference("layout", "column"),
excludeBlacklistEnabled: new Preference("excludeBlacklist", false),
resultsPerPage: new Preference("resultsPerPage", 200),
sortAscendingEnabled: new Preference("sortAscending", false),
sortingMethod: new Preference("sortingMethod", "default"),
optionsVisible: new Preference("showOptions", false),
columnCount: new Preference("columnCount", ON_MOBILE_DEVICE ? 3 : 6),
rowSize: new Preference("rowSize", 7),
dockGalleryMenuLeft: new Preference("dockGalleryMenuLeft", ON_DESKTOP_DEVICE),
uiVisible: new Preference("showUI", true),
infiniteScrollEnabled: new Preference("infiniteScroll", false),
galleryMenuPinned: new Preference("galleryMenuPinned", ON_MOBILE_DEVICE),
galleryMenuEnabled: new Preference("galleryMenuEnabled", ON_MOBILE_DEVICE),
removeButtonsVisible: new Preference("showRemoveFavoriteButtons", false),
addButtonsVisible: new Preference("showAddFavoriteButtons", false),
fancyThumbHoveringEnabled: new Preference("fancyThumbHovering", true),
hintsEnabled: new Preference("showHints", false),
headerEnabled: new Preference("showHeader", true),
colorScheme: new Preference("colorScheme", "black"),
backgroundOpacity: new Preference("backgroundOpacity", "1"),
videoVolume: new Preference("videoVolume", 1),
videoMuted: new Preference("videoMuted", false),
autoplayActive: new Preference("autoplayActive", false),
autoplayPaused: new Preference("autoplayPaused", false),
autoplayImageDuration: new Preference("autoplayImageDuration", 3e3),
autoplayMinimumVideoDuration: new Preference("autoplayMinimumVideoDuration", 5e3),
autoplayForward: new Preference("autoplayForward", true),
downloadBatchSize: new Preference("downloadBatchSize", 250),
downloadButtonsVisible: new Preference("showDownloadButtons", false),
mobileGalleryEnabled: new Preference("mobileGalleryEnabled", true)
};
var GALLERY_DISABLED = ON_POST_PAGE || Preferences.performanceProfile.value !== 0 /* HIGH */;
var TOOLTIP_DISABLED = ON_MOBILE_DEVICE || Preferences.performanceProfile.value === 2 /* LOW */ || ON_POST_PAGE;
var FAVORITES_SEARCH_GALLERY_ENABLED = ON_FAVORITES_PAGE || ON_SEARCH_PAGE && Preferences.searchPagesEnabled.value;
var TAG_MODIFIER_DISABLED = ON_MOBILE_DEVICE || !ON_FAVORITES_PAGE;
var TAG_MODIFIER_ENABLED = !TAG_MODIFIER_DISABLED;
var SAVED_SEARCHES_DISABLED = !ON_FAVORITES_PAGE || ON_MOBILE_DEVICE;
var CAPTIONS_DISABLED = !ON_FAVORITES_PAGE || ON_MOBILE_DEVICE || Preferences.performanceProfile.value === 2 /* LOW */;
var AWESOMPLETE_ENABLED = ON_FAVORITES_PAGE;
var GALLERY_ENABLED = !GALLERY_DISABLED;
var TOOLTIP_ENABLED = !TOOLTIP_DISABLED;
var CAPTIONS_ENABLED = !CAPTIONS_DISABLED;
var DEFAULT_DIMENSIONS_2D = { width: 100, height: 100 };
var DIMENSIONS_2D_REGEX = /^(\d+)x(\d+)$/;
var OR_GROUP_REGEX = /(?:^|\s+)\(\s+((?:\S+)(?:(?:\s+~\s+)\S+)*)\s+\)/g;
function removeExtraWhiteSpace(text) {
return text.trim().replace(/\s\s+/g, " ");
}
function getDimensions2D(dimensionString) {
const match = dimensionString.match(DIMENSIONS_2D_REGEX);
if (match) {
return {
width: parseInt(match[1]),
height: parseInt(match[2])
};
}
return DEFAULT_DIMENSIONS_2D;
}
function isEmptyString(text) {
return text.trim().length === 0;
}
function escapeParenthesis(text) {
return text.replace(/([()])/g, "\\$&");
}
function prepareSearchQuery(searchQuery2) {
return removeExtraWhiteSpace(searchQuery2).toLowerCase();
}
function extractOrGroups(searchQuery2) {
return Array.from(searchQuery2.matchAll(OR_GROUP_REGEX)).map((orGroup) => orGroup[1].split(" ~ "));
}
function extractRemainingTags(searchQuery2) {
return removeExtraWhiteSpace(searchQuery2.replace(OR_GROUP_REGEX, "")).split(" ").filter((tag) => tag !== "");
}
function extractTagGroups(searchQuery2) {
searchQuery2 = prepareSearchQuery(searchQuery2);
return {
orGroups: extractOrGroups(searchQuery2),
remainingTags: extractRemainingTags(searchQuery2)
};
}
function getContentType(tags) {
if (typeof tags === "string") {
tags = convertToTagSet(tags);
}
if (tags.has("video") || tags.has("mp4")) {
return "video";
}
if (tags.has("gif") || tags.has("animated")) {
return "gif";
}
return "image";
}
function removeNonNumericCharacters(text) {
return text.replace(/\D/g, "");
}
function negateTags(tags) {
return tags.replace(/(\S+)/g, "-$1");
}
function isOnlyDigits(text) {
return /^\d+$/.test(text);
}
function convertToTagSet(tagString) {
tagString = removeExtraWhiteSpace(tagString);
if (tagString === "") {
return /* @__PURE__ */ new Set();
}
return new Set(tagString.split(" ").sort());
}
function convertToTagString(tagSet) {
if (tagSet.size === 0) {
return "";
}
return Array.from(tagSet).sort().join(" ");
}
function capitalize(word) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
function removeFirstAndLastLines(text) {
const lines = text.split("\n").filter((line) => line.trim() !== "");
if (lines.length <= 2) {
return "";
}
return lines.slice(1, -1).join("\n").trim();
}
function removeLeadingHyphens(tag) {
return tag.replace(/^[-*]*/, "");
}
var PromiseTimeoutError = class extends Error {
};
var DownloadAbortedError = class extends Error {
};
var EventEmitter = class {
listeners;
oneTimeListeners;
enabled;
constructor(enabled = true) {
this.listeners = /* @__PURE__ */ new Set();
this.oneTimeListeners = /* @__PURE__ */ new Set();
this.enabled = enabled;
}
get disabled() {
return !this.enabled;
}
on(callback, options = void 0) {
if (this.disabled) {
return;
}
this.listeners.add(callback);
if (options === void 0) {
return;
}
if (options.once) {
this.oneTimeListeners.add(callback);
}
if (options.signal) {
options.signal.addEventListener("abort", () => {
this.off(callback);
});
}
}
off(callback) {
this.listeners.delete(callback);
}
emit(argument) {
if (this.disabled) {
return;
}
for (const callback of this.listeners.keys()) {
callback(argument);
}
this.removeOneTimeListeners();
}
timeout(milliseconds) {
return new Promise((resolve, reject) => {
const timer2 = setTimeout(() => {
this.off(listener);
reject(new PromiseTimeoutError());
}, milliseconds);
const listener = (args) => {
this.off(listener);
clearTimeout(timer2);
resolve(args);
};
this.on(listener, {
once: true
});
});
}
toggle(value = void 0) {
this.enabled = value === void 0 ? !this.enabled : value;
}
removeOneTimeListeners() {
this.listeners = this.listeners.difference(this.oneTimeListeners);
this.oneTimeListeners.clear();
}
};
var FAVORITES_SEARCH_GALLERY_CONTAINER = document.createElement("div");
FAVORITES_SEARCH_GALLERY_CONTAINER.id = "favorites-search-gallery";
function insertFavoritesSearchGalleryContainer() {
if (document.body !== null) {
document.body.appendChild(FAVORITES_SEARCH_GALLERY_CONTAINER);
}
Events.document.domLoaded.on(() => {
document.body.appendChild(FAVORITES_SEARCH_GALLERY_CONTAINER);
}, { once: true });
}
var ORIGIN = "https://rule34.xxx";
var API_ORIGIN = "https://api.rule34.xxx";
var POST_API_URL = `${API_ORIGIN}/index.php?page=dapi&s=post&q=index&id=`;
var TAG_API_URL = `${API_ORIGIN}/index.php?page=dapi&s=tag&q=index&name=`;
var POST_PAGE_URL = `${ORIGIN}/index.php?page=post&s=view&id=`;
var SEARCH_PAGE_URL = `${ORIGIN}/index.php?page=post&s=list&tags=`;
var FAVORITES_PAGE_URL = document.location.href;
var PROFILE_PAGE_URL = `${ORIGIN}/index.php?page=account&s=profile&id=${getFavoritesPageId()}`;
var POST_VOTE_URL = `${ORIGIN}/index.php?page=post&s=vote&type=up&id=`;
var REMOVE_FAVORITE_URL = `${ORIGIN}/index.php?page=favorites&s=delete&id=`;
var ADD_FAVORITE_URL = `${ORIGIN}/public/addfav.php?id=`;
var CSS_URL = `${ORIGIN}//css/`;
function createPostPageURL(id) {
return `${POST_PAGE_URL}${id}`;
}
function createSearchPageURL(searchQuery2) {
return `${SEARCH_PAGE_URL}${encodeURIComponent(searchQuery2)}`;
}
function createFavoritesPageURL(pageNumber) {
return `${FAVORITES_PAGE_URL}&pid=${pageNumber}`;
}
function createProfilePageURL() {
return PROFILE_PAGE_URL;
}
function createAPIURL(id) {
return `${POST_API_URL}${id}`;
}
function createTagAPIURL(tagName) {
return `${TAG_API_URL}${encodeURIComponent(tagName)}`;
}
function createPostVoteURL(id) {
return `${POST_VOTE_URL}${id}`;
}
function createRemoveFavoriteURL(id) {
return `${REMOVE_FAVORITE_URL}${id}`;
}
function createAddFavoriteURL(id) {
return `${ADD_FAVORITE_URL}${id}`;
}
function getStyleSheetURL(useDark) {
return `${CSS_URL}${ON_MOBILE_DEVICE ? "mobile" : "desktop"}${useDark ? "-dark" : ""}.css?44`;
}
var AUTOPLAY_HTML = `
<div id="autoplay-container">
<style>
#autoplay-container {
visibility: hidden;
}
#autoplay-menu {
position: fixed;
left: 50%;
transform: translate(-50%);
bottom: 5%;
padding: 0;
margin: 0;
/* background: rgba(40, 40, 40, 1); */
background: var(--gallery-menu-background);
border-radius: 4px;
white-space: nowrap;
z-index: 10000;
opacity: 0;
transition: opacity .25s ease-in-out;
&.visible {
opacity: 1;
}
&.persistent {
opacity: 1 !important;
visibility: visible !important;
}
>div>img {
color: red;
position: relative;
height: 75px;
cursor: pointer;
background-color: rgba(128, 128, 128, 0);
margin: 5px;
background-size: 10%;
z-index: 3;
border-radius: 4px;
&:hover {
background-color: rgba(200, 200, 200, .5);
}
}
}
.autoplay-progress-bar {
position: absolute;
top: 0;
left: 0;
width: 0%;
height: 100%;
background-color: steelblue;
z-index: 1;
/* position: fixed !important;
top: 0;
left: 0;
width: 0;
height: 4px;
background: #ff5733;
z-index: 1; */
}
body.autoplay::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 0;
height: 4px;
background: #ff5733;
animation: progress 5s linear forwards;
z-index: 9999;
}
@keyframes progress {
from {
width: 0;
}
to {
width: 100%;
}
}
#autoplay-video-progress-bar {
background-color: royalblue;
}
#autoplay-settings-menu {
visibility: hidden;
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -105%);
border-radius: 4px;
font-size: 10px !important;
background: var(--gallery-menu-background);
&.visible {
visibility: visible;
}
>div {
font-size: 30px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
color: white;
>label {
padding-right: 20px;
}
>.number {
background: none;
outline: 2px solid white;
>hold-button,
>button {
&::after {
width: 200%;
height: 130%;
}
}
>input[type="number"] {
color: white;
width: 7ch;
}
}
}
select {
/* height: 25px; */
font-size: larger;
width: 10ch;
}
}
#autoplay-settings-button.settings-menu-opened {
filter: drop-shadow(6px 6px 3px #0075FF);
}
#autoplay-change-direction-mask {
filter: drop-shadow(2px 2px 3px #0075FF);
}
#autoplay-play-button:active {
filter: drop-shadow(2px 2px 10px #0075FF);
}
#autoplay-change-direction-mask-container {
pointer-events: none;
opacity: 0.75;
height: 75px;
width: 75px;
margin: 5px;
border-radius: 4px;
right: 0;
bottom: 0;
z-index: 4;
position: absolute;
clip-path: polygon(0% 0%, 0% 100%, 100% 100%);
&.upper-right {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%);
}
}
.autoplay-settings-menu-label {
pointer-events: none;
}
</style>
<div id="autoplay-menu" class="not-highlightable gallery-sub-menu">
<div id="autoplay-buttons">
<img id="autoplay-settings-button" title="Autoplay settings">
<img id="autoplay-play-button" title="Pause autoplay">
<img id="autoplay-change-direction-button" title="Change autoplay direction">
<div id="autoplay-change-direction-mask-container">
<img id="autoplay-change-direction-mask" title="Change autoplay direction">
</div>
</div>
<div id="autoplay-image-progress-bar" class="autoplay-progress-bar"></div>
<div id="autoplay-video-progress-bar" class="autoplay-progress-bar"></div>
<div id="autoplay-settings-menu">
<div>
<label for="autoplay-image-duration-input">Image/GIF Duration</label>
<span class="number">
<hold-button class="number-arrow-down" pollingtime="50"><span><</span></hold-button>
<input type="number" id="autoplay-image-duration-input" min="1" max="60" step="1">
<hold-button class="number-arrow-up" pollingtime="50"><span>></span></hold-button>
</span>
</div>
<div>
<label for="autoplay-minimum-video-duration-input">Minimum Video Duration</label>
<span class="number">
<hold-button class="number-arrow-down" pollingtime="50"><span><</span></hold-button>
<input type="number" id="autoplay-minimum-animated-duration-input" min="0" max="60" step="1">
<hold-button class="number-arrow-up" pollingtime="50"><span>></span></hold-button>
</span>
</div>
</div>
</div>
</div>
`;
var CAPTION_HTML = `
<style>
.caption {
overflow: hidden;
pointer-events: none;
background: rgba(0, 0, 0, .75);
z-index: 15;
position: absolute;
width: 100%;
height: 100%;
top: -100%;
left: 0px;
top: 0px;
text-align: left;
transform: translateX(-100%);
/* transition: transform .3s cubic-bezier(.26,.28,.2,.82); */
transition: transform .35s ease;
padding-top: 0.5ch;
padding-left: 7px;
h6 {
display: block;
color: white;
padding-top: 0px;
}
li {
width: fit-content;
list-style-type: none;
display: inline-block;
}
&.active {
transform: translateX(0%);
}
&.transition-completed {
.caption-tag {
pointer-events: all;
}
}
}
.caption.hide {
display: none;
}
.caption.inactive {
display: none;
}
.caption-tag {
pointer-events: none;
color: #6cb0ff;
word-wrap: break-word;
&:hover {
text-decoration-line: underline;
cursor: pointer;
}
}
.artist-tag {
color: #f0a0a0;
}
.character-tag {
color: #f0f0a0;
}
.copyright-tag {
color: #EFA1CF;
}
.metadata-tag {
color: #8FD9ED;
}
.caption-wrapper {
pointer-events: none;
position: absolute !important;
overflow: hidden;
top: -1px;
left: -1px;
width: 102%;
height: 102%;
display: block !important;
}
</style>
`;
var COMMON_HTML = `
<style>
body {
overflow-x: hidden;
&:fullscreen,
&::backdrop {
background-color: var(--c-bg);
}
}
.light-green-gradient {
background: linear-gradient(to bottom, #aae5a4, #89e180);
color: black;
}
.dark-green-gradient {
background: linear-gradient(to bottom, #5e715e, #293129);
color: white;
}
img {
border: none !important;
}
.not-highlightable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
input[type=number] {
border: 1px solid #767676;
border-radius: 2px;
}
.size-calculation-div {
position: absolute !important;
top: 0;
left: 0;
width: 100%;
height: 100%;
visibility: hidden;
transition: none !important;
transform: scale(1.05, 1.05);
}
.number {
white-space: nowrap;
position: relative;
margin-top: 5px;
border: 1px solid;
padding: 0;
border-radius: 20px;
background-color: white;
>hold-button,
button {
position: relative;
top: 0;
left: 0;
font-size: inherit;
outline: none;
background: none;
cursor: pointer;
border: none;
margin: 0px 8px;
padding: 0;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200%;
height: 100%;
/* outline: 1px solid greenyellow; */
/* background-color: hotpink; */
}
&:hover {
>span {
color: #0075FF;
}
}
>span {
font-weight: bold;
font-family: Verdana, Geneva, Tahoma, sans-serif;
position: relative;
pointer-events: none;
border: none;
outline: none;
top: 0;
z-index: 5;
font-size: 1.2em !important;
}
&.number-arrow-up {
>span {
transition: left .1s ease;
left: 0;
}
&:hover>span {
left: 3px;
}
}
&.number-arrow-down {
>span {
transition: right .1s ease;
right: 0;
}
&:hover>span {
right: 3px;
}
}
}
>input[type="number"] {
font-size: inherit;
text-align: center;
width: 2ch;
padding: 0;
margin: 0;
font-weight: bold;
padding: 3px;
background: none;
border: none;
&:focus {
outline: none;
}
}
>input[type="number"]::-webkit-outer-spin-button,
>input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
input[type=number] {
appearance: textfield;
-moz-appearance: textfield;
}
}
.fullscreen-icon {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10010;
pointer-events: none;
width: 30vw;
}
input[type="checkbox"] {
accent-color: #0075FF;
}
.thumb {
>a {
pointer-events: none;
>img {
pointer-events: all;
}
}
}
.blink {
animation: blink 0.35s step-start infinite;
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* html::before {
content: "";
position: fixed;
z-index: 10000;
opacity: 0;
background: black;
transition: opacity 0.2s linear;
pointer-events: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
} */
html.fullscreen-effect::before {
opacity: 1;
}
html.transition-disabled::before {
transition: none;
}
body.dialog-opened {
overflow: hidden;
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.7);
}
.indented {
ul {
padding-left: 20px;
}
}
.mobile-tap-control {
position: fixed;
top: 50% !important;
height: 80vh;
width: 30vw;
background: red;
z-index: 9999;
color: red;
transform: translateY(-50%);
pointer-events: all !important;
opacity: 0;
}
#left-mobile-tap-control {
left: 0 !important;
right: unset !important;
}
#right-mobile-tap-control {
right: 0 !important;
left: unset !important;
}
#mobile-symbol-container {
display: flex;
gap: 10px;
text-align: center;
height: 0;
overflow: hidden;
width: 100%;
/* transition: height .2s ease; */
margin-bottom: 5px;
>button {
font-size: 20px;
padding: 0;
margin: 0;
font-weight: bold;
text-align: center;
flex: 1;
height: 100% !important;
border: none;
border-radius: 4px;
}
&.active {
height: 30px;
}
}
</style>
`;
var CONTROLS_HTML = `
<style>
#controls-guide {
display: none;
z-index: 99999;
--tap-control: blue;
--swipe-down: red;
--swipe-up: green;
top: 0;
left: 0;
background: lightblue;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
flex-direction: column;
position: fixed;
&.active {
display: flex;
}
}
#controls-guide-image-container {
background: black;
width: 100%;
height: 100%;
}
#controls-guide-sample-image {
background: lightblue;
position: relative;
top: 50%;
left: 0;
width: 100%;
transform: translateY(-50%);
}
#controls-guide-top {
position: relative;
flex: 3;
}
#controls-guide-bottom {
flex: 1;
min-height: 25%;
padding: 10px;
font-size: 20px;
align-content: center;
}
#controls-guide-tap-container {
width: 100%;
height: 100%;
position: absolute;
}
.controls-guide-tap {
color: white;
font-size: 50px;
position: absolute;
top: 50%;
height: 65%;
width: 15%;
background: var(--tap-control);
z-index: 9999;
transform: translateY(-50%);
writing-mode: vertical-lr;
text-align: center;
opacity: 0.8;
}
#controls-guide-tap-right {
right: 0;
}
#controls-guide-tap-left {
left: 0;
}
#controls-guide-swipe-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
svg {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 25%;
}
}
#controls-guide-swipe-down {
top: 0;
color: var(--swipe-down);
fill: var(--swipe-down);
}
#controls-guide-swipe-up {
bottom: 0;
color: var(--swipe-up);
fill: var(--swipe-up);
}
</style>
<div id="controls-guide">
<div id="controls-guide-top">
<div id="controls-guide-tap-container">
<div id="controls-guide-tap-left" class="controls-guide-tap">
Previous
</div>
<div id="controls-guide-tap-right" class="controls-guide-tap">
Next
</div>
</div>
<div id="controls-guide-image-container">
<img id="controls-guide-sample-image" src="https://rule34.xxx/images/header2.png">
</div>
<div id="controls-guide-swipe-container">
<svg id="controls-guide-swipe-down" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M180-360 40-500l42-42 70 70q-6-27-9-54t-3-54q0-82 27-159t78-141l43 43q-43 56-65.5 121.5T200-580q0 26 3 51.5t10 50.5l65-64 42 42-140 140Zm478 233q-23 8-46.5 7.5T566-131L304-253l18-40q10-20 28-32.5t40-14.5l68-5-112-307q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l148 407-100 7 131 61q7 3 15 3.5t15-1.5l157-57q31-11 45-41.5t3-61.5l-55-150q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l55 150q23 63-4.5 122.5T815-184l-157 57Zm-90-265-54-151q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l55 150-76 28Zm113-41-41-113q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l41 112-75 28Zm8 78Z" />
</svg>
<svg id="controls-guide-swipe-up" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M245-400q-51-64-78-141t-27-159q0-27 3-54t9-54l-70 70-42-42 140-140 140 140-42 42-65-64q-7 25-10 50.5t-3 51.5q0 70 22.5 135.5T288-443l-43 43Zm413 273q-23 8-46.5 7.5T566-131L304-253l18-40q10-20 28-32.5t40-14.5l68-5-112-307q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l148 407-100 7 131 61q7 3 15 3.5t15-1.5l157-57q31-11 45-41.5t3-61.5l-55-150q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l55 150q23 63-4.5 122.5T815-184l-157 57Zm-90-265-54-151q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l55 150-76 28Zm113-41-41-113q-6-16 1-30.5t23-20.5q16-6 30.5 1t20.5 23l41 112-75 28Zm8 78Z" />
</svg>
</div>
</div>
<div id="controls-guide-bottom">
<ul style="text-align: center; list-style: none;">
<li style="color: var(--tap-control);">Tap edges to traverse gallery</li>
<li style="color: var(--swipe-down);">Swipe down to exit gallery</li>
<li style="color: var(--swipe-up);">Swipe up to open autoplay menu</li>
</ul>
</div>
</div>
`;
var DARK_THEME_HTML = `
<style>
input[type=number] {
background-color: #303030;
color: white;
}
.number {
background-color: #303030;
>hold-button,
button {
color: white;
}
}
#favorites-pagination-container {
>button {
border: 1px solid white !important;
color: white !important;
}
}
</style>
`;
var DESKTOP_HTML = `
<style>
.checkbox {
cursor: pointer;
&:hover {
color: #000;
background: #93b393;
text-shadow: none;
}
input[type="checkbox"] {
width: 20px;
height: 20px;
}
}
#sort-ascending-checkbox {
width: 20px;
height: 20px;
}
#favorites-pagination-container>button {
height: 32px;
}
.gallery-menu-button {
&:hover {
opacity: 1;
transform: scale(1.25);
}
}
.gallery-menu-button::after {
content: attr(data-hint);
position: absolute;
top: 50%;
left: 100%;
transform: translateY(-50%);
background: black;
padding: 2px 6px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
font-size: larger;
transition: opacity 0.35s ease-in-out;
border-radius: 3px;
pointer-events: none;
}
.gallery-menu-button:hover::after {
opacity: 1;
visibility: visible;
}
</style>
`;
var DOWNLOADER_HTML = `
<style>
#download-menu {
background: transparent;
border: none;
gap: 10px;
padding: 0;
overflow: hidden;
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}
#download-menu-container {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
gap: 10px;
border-radius: 10px;
}
#download-menu-container-wrapper {
display: flex;
width: 450px;
height: 150px;
flex-direction: column;
gap: 2px;
}
#download-menu-container-wrapper-inner {
display: flex;
padding: 10px;
border-radius: 8px;
height: 100%;
}
#download-menus button {
width: 100%;
height: 4rem;
border-radius: 8px;
font-size: large;
}
#download-menu.downloading #download-menu-buttons-start-download {
display: none;
}
#download-menu.downloading #download-menu-options {
display: none;
}
#download-menu.downloading #download-menu-status {
flex: 1 1 100%;
}
#download-menu-options {
flex: 1 0 25%;
text-align: center;
}
#download-menu-options select {
width: 150px !important;
font-size: 40px;
height: 60px;
cursor: pointer;
}
#download-menu-buttons {
display: flex;
gap: 10px;
flex-direction: column;
flex: 1 0 15%;
}
#download-menu-buttons button {
flex: 1 1 100%;
}
#download-menu-status {
flex: 0 0 0%;
display: flex;
flex-direction: column;
gap: 2px;
/* transition: flex 0.15s linear; */
}
#download-menu-status-header {
text-align: center;
margin: 0;
background: transparent;
}
#download-menu-status-header.dark-green-gradient {
color: white;
}
#download-menu-status span {
font-size: medium;
}
#download-menu p {
color: black;
}
#download-menu-warning-container button {
margin: auto;
border-radius: 8px;
}
#download-menu-warning-container {
text-align: center;
}
#download-menu-help {
display: flex;
flex: 0 0 5%;
align-items: center;
justify-content: center;
}
#download-menu-help button {
background: transparent;
width: 100%;
aspect-ratio: 1;
border: none;
position: relative;
}
#download-menu-help button:hover {
background: rgba(0, 0, 0, 0.1);
}
#download-menu-help svg {
background: transparent !important;
position: absolute;
left: 50%;
top: 50%;
width: 100%;
height: 100%;
transform: translate(-50%, -50%);
}
#download-menu-help svg.dark-green-gradient {
fill: white;
}
#download-menu-help-text {
position: absolute;
bottom: 0;
left: 0;
width: 500px;
transform: translateY(100%);
background: gray;
color: black;
}
#download-menu-options-batch-size-container>span {
font-size: x-large;
}
</style>
<div id="download-menus">
<dialog id="download-menu">
<div id="download-menu-container-wrapper">
<h1 id="download-menu-status-header" class="light-green-gradient">Download</h1>
<div id="download-menu-container-wrapper-inner" class="light-green-gradient">
<div id="download-menu-container">
<div id="download-menu-buttons">
<button id="download-menu-buttons-start-download">Download</button>
<button id="download-menu-buttons-cancel-download">Cancel</button>
</div>
<div id="download-menu-options" class="download-menu-setup">
<div id="download-menu-options-batch-size-container">
<span>Batch Size</span>
<select id="download-menu-options-batch-size">
<option value="50">50</option>
<option value="100">100</option>
<option value="250">250</option>
<option value="500">500</option>
<option value="1000">1000</option>
<option value="2500">2500</option>
<option value="5000">5000</option>
</select>
</div>
</div>
<div id="download-menu-status">
</div>
<!-- <div id="download-menu-help">
<button>
<svg class="light-green-gradient" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
</svg>
</button>
</div> -->
</div>
</div>
</div>
<!-- <div id="download-menu-help-text">
<p>
This is some text that will be displayed in the download menu. It can be used to provide information or instructions to the user.
</p>
</div> -->
</dialog>
<dialog id="download-menu-warning" class="light-green-gradient">
<div id="download-menu-warning-container">
<h1>Wait for all favorites to load before downloading</h1>
<form method="dialog"><button>Close</button></form>
</div>
</dialog>
</div>
`;
var FAVORITES_HTML = `
<div id="favorites-search-gallery-menu" class="light-green-gradient not-highlightable">
<style>
#favorites-search-gallery-menu {
position: sticky;
top: 0;
/* padding: 10px; */
padding: 5px;
z-index: 30;
margin-bottom: 10px;
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
select {
cursor: pointer;
min-height: 25px;
width: 150px;
margin-top: 2px;
}
}
#favorites-search-gallery-menu-panels {
>div {
flex: 1;
}
}
#left-favorites-panel {
flex: 10 !important;
}
#left-favorites-panel-top-row {
margin-bottom: 5px;
>label {
align-content: center;
margin-right: 5px;
margin-top: 4px;
}
>button {
height: 35px;
border: none;
border-radius: 4px;
&:hover {
filter: brightness(140%);
}
&:not(:last-of-type) {
margin-right: 5px;
}
}
>button[disabled] {
filter: none !important;
cursor: wait !important;
}
}
#right-favorites-panel {
flex: 9 !important;
margin-left: 30px;
display: none;
}
textarea {
max-width: 100%;
height: 50px;
width: 98%;
padding: 10px;
border-radius: 6px;
resize: vertical;
}
button,
input[type="checkbox"] {
cursor: pointer;
}
.checkbox {
display: block;
padding: 2px 6px 2px 0px;
border-radius: 4px;
margin-left: -3px;
height: 27px;
>input {
vertical-align: -5px;
}
}
#column-count-container {
>div {
align-content: center;
}
}
#favorite-finder {
margin-top: 7px;
>button {
white-space: nowrap;
/* border-radius: 4px; */
/* height: 30px; */
}
>button:last-of-type {
margin-left: 2px;
margin-bottom: 5px;
}
>input {
width: 75px;
/* border-radius: 6px;
height: 35px;
border: 1px solid; */
}
}
#favorites-pagination-container {
/* padding: 0px 10px 0px 10px; */
>button {
background: transparent;
margin: 0px 2px;
padding: 2px 6px;
border: 1px solid black;
font-size: 14px;
color: black;
font-weight: normal;
width: 6ch;
&:hover {
background-color: #93b393;
}
&.selected {
border: none !important;
font-weight: bold;
pointer-events: none;
}
&[disabled] {
background-color: transparent !important;
}
}
}
#help-links-container {
>a:not(:last-child)::after {
content: " |";
}
/* display: flex;
flex-direction: column;
>a {
font-size: x-large;
}
margin-top: 17px; */
}
#whats-new-link {
cursor: pointer;
padding: 0;
position: relative;
font-weight: bolder;
font-style: italic;
background: none;
text-decoration: none !important;
&.hidden:not(.persistent)>div {
display: none;
}
&.persistent,
&:hover {
&.light-green-gradient {
color: black;
}
&:not(.light-green-gradient) {
color: white;
}
}
}
#whats-new-container {
z-index: 10;
top: 20px;
right: 0;
transform: translateX(25%);
font-style: normal;
font-weight: normal;
white-space: nowrap;
max-width: 100vw;
padding: 5px 20px;
position: absolute;
pointer-events: none;
text-shadow: none;
border-radius: 2px;
&.light-green-gradient {
outline: 2px solid black;
}
&:not(.light-green-gradient) {
outline: 1.5px solid white;
}
}
.hotkey {
font-weight: bolder;
color: orange;
}
#left-favorites-panel-bottom-row {
display: flex;
/* margin-top: 10px; */
flex-wrap: nowrap;
>div {
flex: 1;
}
.number {
font-size: 18px;
>input {
width: 5ch;
}
}
}
#additional-favorite-options {
>div:not(:last-child) {
margin-bottom: 10px;
}
}
.number-label-container {
display: inline-block;
min-width: 130px;
}
#show-ui-container.ui-hidden {
label {
text-align: center;
}
max-width: 100vw;
text-align: center;
align-content: center;
}
#rating-container {
white-space: nowrap;
}
#allowed-ratings {
margin-top: 5px;
font-size: 12px;
>label {
outline: 1px solid;
padding: 3px;
cursor: pointer;
opacity: 0.5;
position: relative;
}
>label[for="explicit-rating"] {
border-radius: 7px 0px 0px 7px;
margin-right: 2px;
}
>label[for="questionable-rating"] {
margin-right: 2px;
}
>label[for="safe-rating"] {
border-radius: 0px 7px 7px 0px;
}
>input[type="checkbox"] {
display: none;
&:checked+label {
background-color: #0075FF;
color: white;
opacity: 1;
}
}
}
#favorites-load-status {
>label {
display: inline-block;
min-width: 100px;
margin-right: 20px;
}
}
#main-favorite-options-container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
>div {
flex-basis: 45%;
}
}
#sort-ascending {
position: absolute;
top: 18px;
left: 150px;
width: 20px;
height: 20px;
}
#favorite-finder-input {
border: none !important;
}
div#header {
margin-bottom: 0 !important;
}
body {
overflow-x: hidden;
}
#goto-page-input {
width: 5ch;
}
#sort-container {
position: relative;
}
.toggle-switch {
position: relative;
display: block;
width: 60px;
height: 34px;
transform: scale(.75);
align-content: center;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked+.slider {
background-color: #0075FF;
}
input:focus+.slider {
box-shadow: 0 0 1px #0075FF;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.toggle-switch-label {
margin-left: 60px;
margin-top: 20px;
font-size: 16px;
}
.inline-option-container {
>div {
display: inline-block;
}
}
#sorting-method-id {
display: none;
}
textarea#favorites-search-box {
margin-top: 5px;
}
#favorites-load-status-label.hidden {
display: none;
}
</style>
<div id="favorites-search-gallery-menu-panels" style="display: flex;">
<div id="left-favorites-panel">
<h2 style="display: inline;" id="search-header">Search Favorites</h2>
<span id="favorites-load-status" style="margin-left: 5px;">
<label id="match-count-label"></label>
<label id="favorites-load-status-label"></label>
</span>
<div id="left-favorites-panel-top-row">
<span id="favorites-pagination-placeholder"></span>
</div>
<div id="left-favorites-panel-bottom-row">
<div id="bottom-panel-1">
<div class="options-container">
<div id="main-favorite-options-container">
<div id="favorite-options-left">
</div>
<div id="favorite-options-right">
</div>
</div>
</div>
</div>
<div id="bottom-panel-2">
<div id="additional-favorite-options-container" class="options-container">
<div id="additional-favorite-options">
<div id="layout-sort-container" class="inline-option-container">
<div id="layout-container">
<label>Layout</label>
<br>
</div>
<div id="sort-container" title="Change sorting order of search results">
<span id="sort-labels">
<label style="margin-right: 22px;" for="sorting-method">Sort By</label>
<label style="margin-left: 22px;" for="sort-ascending">Ascending</label>
</span>
<div id="sort-inputs">
</div>
</div>
</div>
<div id="results-columns-container" class="inline-option-container">
<div id="results-per-page-container"
title="Set the maximum number of search results to display on each page
Lower numbers improve responsiveness">
<span class="number-label-container">
<label id="results-per-page-label" for="results-per-page-input">Results per Page</label>
</span>
<br>
</div>
<div id="column-count-container" title="Set the number of favorites per row">
<div>
<span class="number-label-container">
<label id="column-count-label">Columns</label>
</span>
<br>
</div>
</div>
<div id="row-size-container" title="Set the height of each row">
<div>
<span class="number-label-container">
<label id="row-size-label">Row height</label>
</span>
<br>
</div>
</div>
</div>
<div id="rating-container" title="Filter search results by rating">
</div>
<div id="performance-profile-container" title="Improve performance by disabling features">
<label for="performance-profile">Performance Profile</label>
<br>
</div>
</div>
</div>
</div>
<div id="bottom-panel-3">
<div id="show-ui-wrapper">
</div>
<div class="options-container">
</div>
</div>
<div id="bottom-panel-4">
</div>
</div>
</div>
<div id="right-favorites-panel"></div>
</div>
</div>
`;
var FAVORITES_CONTENT_HTML = `
<style>
html {
width: 100vw;
}
#favorites-search-gallery-content {
padding: 0px 20px 30px 20px;
margin-right: 15px;
&.grid,
&.square {
display: grid !important;
grid-template-columns: repeat(10, 1fr);
grid-gap: 0.5cqw;
.utility-button {
width: 30%;
}
}
&.square {
.favorite {
border-radius: 10px !important;
overflow: hidden;
aspect-ratio: 1 / 1;
>a,
>div {
width: 100%;
height: 100%;
>img:first-child,
>canvas {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
}
&.row {
display: flex;
flex-wrap: wrap;
.favorite {
&.last-row {
flex: 0 1 auto;
/* opacity: 0.1; */
}
height: 300px;
flex: 1 1 auto;
border-radius: 10px;
overflow: hidden;
}
.favorite {
>a,
>div {
width: 100%;
height: 100%;
>img:first-child {
object-fit: cover;
width: 100%;
height: 100%;
vertical-align: middle;
}
>canvas {
height: 100%;
object-fit: cover;
}
}
}
.utility-button {
height: 30%;
}
}
&.column {
display: grid;
grid-template-columns: repeat(10, 1fr);
.favorites-column {
display: flex;
flex-direction: column;
flex: 0 0 25%;
.favorite {
border-radius: 10px;
overflow: hidden;
}
}
.utility-button {
width: 30%;
}
}
}
.favorite {
position: relative;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
>a,
>div {
display: block;
overflow: hidden;
position: relative;
cursor: default;
>img:first-child {
width: 100%;
z-index: 1;
}
>a>div {
height: 100%;
}
>canvas {
width: 100%;
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 1;
}
}
&.hidden {
display: none;
}
}
.utility-button {
cursor: pointer;
position: absolute;
left: 0;
top: 0;
font-weight: bold;
background: none;
border: none;
z-index: 2;
filter: grayscale(70%);
&:active,
&:hover {
filter: none !important;
}
}
.download-button {
top: 0 !important;
right: 0 !important;
left: unset !important;
top: unset !important;
}
img {
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
}
</style>
`;
var GALLERY_HTML = `
<style>
html {
width: 100vw;
}
body {
overflow-x: hidden;
}
video::-webkit-media-controls-panel {
background: transparent !important;
}
video::-webkit-media-controls-enclosure {
background: transparent !important;
}
#gallery-container {
pointer-events: none;
z-index: 9000;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
* {
top: 0;
left: 0;
position: fixed;
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
}
}
.fullscreen-image-container {
width: 100%;
height: 100%;
pointer-events: none;
position: relative;
&.zoomed-in {
overflow: scroll;
pointer-events: all;
&.zooming {
cursor: zoom-out;
}
.fullscreen-image {
height: 250%;
left: 0 !important;
top: 0 !important;
transform: none !important;
}
}
}
.fullscreen-image {
position: relative !important;
pointer-events: none;
height: 100%;
margin: 0;
left: 50% !important;
top: 50% !important;
transform: translate(-50%, -50%);
}
a.hide {
cursor: default;
}
option {
font-size: 15px;
}
#gallery-background {
background: black;
z-index: -1;
pointer-events: none;
cursor: none;
width: 100vw;
height: 100vh;
&.show-on-hover {
display: block;
}
&.in-gallery {
display: block;
pointer-events: all;
}
&.zooming {
cursor: zoom-in !important;
&.zoomed-in {
pointer-events: none;
}
}
}
:root {
/* --gallery-menu-background: rgba(0, 0, 0, 0.75); */
--gallery-menu-background: rgba(0, 0, 0, 1);
--gallery-menu-size: 80px;
}
#gallery-menu {
pointer-events: none;
position: fixed;
display: flex;
justify-content: flex-end;
top: 0;
left: 0;
width: 100vw;
height: var(--gallery-menu-size);
z-index: 20;
background: transparent;
transform: translateY(-100%);
opacity: 0;
transition: transform 0.4s cubic-bezier(0, 0, 0.25, 1), opacity 0.25s cubic-bezier(0, 0, 0.25, 1);
#dock-gallery {
>img,
>svg {
transform: rotateZ(-90deg) !important;
}
}
&.active,
&.persistent,
&.pinned {
opacity: 1;
transform: translateY(0%);
}
&.dock-left {
width: var(--gallery-menu-size);
height: 100vh;
top: 0;
left: 0;
transform: translateX(-100%);
&.active,
&.persistent,
&.pinned {
opacity: 1;
transform: translateX(0%);
}
#dock-gallery {
>img,
>svg {
transform: none !important;
}
}
#gallery-menu-button-container {
max-width: 100%;
flex-direction: column;
justify-content: flex-start;
margin: 0;
}
.gallery-menu-button {
max-width: 100%;
margin: 0;
>img,
svg {
width: 75% !important;
height: auto;
}
}
}
&.pinned {
#pin-gallery {
>img,
>svg {
/* fill: #0075FF; */
transform: rotateZ(90deg) !important;
}
}
}
* {
position: static;
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}
#gallery-menu-button-container {
display: flex;
justify-content: center;
height: 100%;
/* margin: 0px 20px 0px 0px; */
width: fit-content;
background: var(--gallery-menu-background);
}
.gallery-menu-button {
pointer-events: all;
display: inline-block;
align-content: center;
text-align: center;
aspect-ratio: 1;
cursor: pointer;
filter: grayscale(50%);
opacity: 0.75;
transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1);
>img,
svg {
pointer-events: none;
height: 75% !important;
transform: none !important;
transition: transform 0.25s ease;
}
}
:root {
--rainbow: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet);
}
#add-favorite-gallery {
>svg {
fill: white;
}
}
@keyframes glowSwipe {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
#gallery-menu-background-color-picker {
position: absolute;
visibility: hidden;
left: 100%;
top: 50%;
transform: translateY(-50%);
}
#gallery-mobile-menu {
position: fixed;
pointer-events: all;
top: 0;
left: 0;
width: 100vw !important;
height: 100vh !important;
z-index: 10000;
background: rgba(0, 0, 0, 0.75);
}
</style>
`;
var HELP_HTML = `
<span id="help-links-container">
<a href="https://github.com/bruh3396/favorites-search-gallery/#controls" target="_blank">Help</a>
<a href="https://sleazyfork.org/en/scripts/504184-rule34-favorites-search-gallery/feedback"
target="_blank">Feedback</a>
<a href="https://github.com/bruh3396/favorites-search-gallery/issues" target="_blank">Report
Issue</a>
<a id="whats-new-link" href="" class="hidden light-green-gradient">What's new?
<div id="whats-new-container" class="light-green-gradient indented">
<h2>v1.19.1</h2>
<ul>
<li>Fixed mass downloads slowing down over time</li>
<li>Fixed Firefox gallery not opening</li>
</ul>
<h2>v1.19</h2>
<!-- <h5>Features:</h5> -->
<h3>Favorites</h3>
<ul>
<li>Download (experimental)</li>
<ul>
<li>Download all search results</li>
<li>Download individual results</li>
</ul>
<li>Layout</li>
<ul>
<li>Waterfall (column/masonry)</li>
<li>River (row)</li>
<li>Square</li>
<li>Legacy (grid)</li>
</ul>
<li>Infinite scroll</li>
</ul>
<h3>Gallery</h3>
<ul>
<li>Mobile gallery on search pages</li>
<li>Infinite gallery on search pages</li>
<ul>
<li>Automatically move to next search page without ever exiting gallery</li>
<li>Mimics manually going to the next page while staying in gallery</li>
</ul>
<li>Desktop side menu (experimental)</li>
<li>Hotkeys</li>
<ul>
<li>G: Open post</li>
<li>Q: Open original</li>
<li>E: Add favorite</li>
<li>S: Download</li>
</ul>
<li>Zoom (desktop) Shift+Click</li>
</ul>
<h3>General</h3>
<ul>
<li>Hotkeys</li>
<ul>
<li>F: Fullscreen</li>
</ul>
</ul>
</div>
</a>
</span>
`;
var MOBILE_HTML = `
<style>
#performance-profile-container,
#show-hints-container,
#whats-new-link,
#show-ui-div,
#search-header,
#fullscreen-gallery,
#exit-gallery,
#background-color-gallery,
#left-favorites-panel-top-row,
#layout-select-row,
#favorite-finder {
display: none !important;
}
#favorites-pagination-container>button {
&:active,
&:focus {
background-color: slategray;
}
&:hover {
background-color: transparent;
}
}
.thumb,
.favorite {
>div>canvas {
display: none;
}
}
#more-options-label {
margin-left: 6px;
}
.checkbox {
margin-bottom: 8px;
input[type="checkbox"] {
margin-right: 10px;
}
}
#mobile-container {
position: fixed !important;
z-index: 30;
width: 100vw;
top: 0px;
left: 0px;
}
#favorites-search-gallery-menu-panels {
display: block !important;
}
#right-favorites-panel {
margin-left: 0px !important;
}
#left-favorites-panel-bottom-row {
margin: 4px 0px 0px 0px !important;
}
#additional-favorite-options-container {
margin-right: 5px;
}
#favorites-search-gallery-content {
grid-gap: 1.2cqw;
padding: 0px 5px 20px 5px !important;
margin-right: 0px !important;
}
#favorites-search-gallery-menu {
padding: 7px 5px 5px 5px;
top: 0;
left: 0;
width: 100vw;
&.fixed {
position: fixed;
margin-top: 0;
}
}
#favorites-load-status-label {
display: inline;
}
textarea {
border-radius: 0px;
height: 50px;
padding: 8px 0px 8px 10px !important;
}
select {
width: 120px !important;
min-height: 30px !important;
margin-bottom: 2px;
}
body {
width: 100% !important;
}
#favorites-pagination-container>button {
text-align: center;
font-size: 16px;
height: 30px;
}
#goto-page-input {
top: -1px;
position: relative;
height: 25px;
width: 1em !important;
text-align: center;
font-size: 16px;
}
#goto-page-button {
display: none;
height: 36px;
position: absolute;
margin-left: 5px;
}
#additional-favorite-options {
.number {
display: none;
}
}
#results-per-page-container {
margin-bottom: 10px;
}
#bottom-panel-3,
#bottom-panel-4 {
flex: none !important;
}
#bottom-panel-2 {
padding-top: 8px;
}
#rating-container {
position: relative;
left: -5px;
top: -2px;
display: none;
}
#favorites-pagination-container>button {
&[disabled] {
opacity: 0.25;
pointer-events: none;
}
}
html {
-webkit-tap-highlight-color: transparent;
-webkit-text-size-adjust: 100%;
}
#additional-favorite-options {
select {
width: 120px;
}
}
.utility-button {
filter: none;
width: 60%;
}
#left-favorites-panel-bottom-row {
overflow: hidden;
/* -webkit-transition: height 0.2s ease;
-moz-transition: height 0.2s ease;
-ms-transition: height 0.2s ease;
-o-transition: height 0.2s ease;
transition: height 0.2s ease; */
height: 270px;
&.hidden {
height: 0px;
}
}
#favorites-search-gallery-content.sticky-menu-shadow {
/* transition: margin 0.2s ease; */
}
#favorites-search-gallery-content.sticky-menu {
margin-top: 340px !important;
}
#autoplay-settings-menu {
>div {
font-size: 14px !important;
}
}
#results-columns-container {
margin-top: -6px;
}
#mobile-toolbar-row {
display: flex;
align-items: center;
background: none;
svg {
fill: black;
-webkit-transition: none;
transition: none;
transform: scale(0.85);
}
input[type="checkbox"]:checked+label {
svg {
fill: #0075FF;
}
color: #0075FF;
}
.dark-green-gradient {
svg {
fill: white;
}
}
}
.search-bar-container {
align-content: center;
width: 100%;
height: 40px;
border-radius: 50px;
padding-left: 10px;
padding-right: 10px;
flex: 1;
&.light-green-gradient {
background: white !important;
}
&.dark-green-gradient {
background: #303030;
}
}
.search-bar-items {
display: flex;
align-items: center;
height: 100%;
width: 100%;
>div {
flex: 0;
min-width: 40px;
width: 100%;
height: 100%;
display: block;
align-content: center;
}
}
.search-icon-container {
flex: 0;
min-width: 40px;
}
.search-bar-input-container {
flex: 1 !important;
display: flex;
width: 100%;
height: 100%;
}
.search-bar-input {
flex: 1;
border: none;
box-sizing: content-box;
height: 100%;
padding: 0;
margin: 0;
outline: none !important;
border: none !important;
font-size: 14px !important;
width: 100%;
&:focus,
&:focus-visible {
background: none !important;
border: none !important;
outline: none !important;
}
}
.search-clear-container {
visibility: hidden;
svg {
transition: none !important;
transform: scale(0.6) !important;
}
}
.circle-icon-container {
padding: 0;
margin: 0;
align-content: center;
border-radius: 50%;
&:active {
background-color: #0075FF;
}
}
#options-checkbox {
display: none;
}
.mobile-toolbar-checkbox-label {
width: 100%;
height: 100%;
display: block;
}
#reset-button {
transition: none !important;
height: 100%;
>svg {
transition: none !important;
transform: scale(0.65);
}
&:active {
svg {
fill: #0075FF;
}
}
}
#help-button {
height: 100%;
>svg {
transform: scale(0.75);
}
}
#sort-ascending-toggle-switch {
transform: scale(0.6) !important;
}
#sort-inputs>.toggle-switch {
display: inline-block;
}
#sort-inputs {
margin-top: -5px;
}
#layout-sort-container {
margin-bottom: 4px !important;
}
#mobile-footer {
padding-top: 4px;
z-index: 10;
position: fixed;
width: 100%;
bottom: -1px;
left: 0;
/* padding: 4px 0px; */
>div {
text-align: center;
}
&.light-green-gradient {
background: linear-gradient(to top, #aae5a4, #89e180);
}
&.dark-green-gradient {
background: linear-gradient(to top, #5e715e, #293129);
}
}
#mobile-footer-top {
margin-bottom: 4px;
}
#mobile-footer-bottom {
margin-bottom: 5px;
}
#favorites-load-status {
font-size: 12px !important;
>span {
margin-right: 10px;
}
>span:nth-child(odd) {
font-weight: bold;
}
>label {
/* width: 300px; */
min-width: unset !important;
}
}
#favorites-load-status-label {
padding-left: 0 !important;
}
#pagination-number:active {
opacity: 0.5;
}
#favorites-pagination-container>button {
min-width: 30px !important;
width: unset !important;
}
#results-per-page-container {
margin-bottom: unset !important;
}
#gallery-menu {
justify-content: center !important;
}
#download-button {
font-size: large;
border-radius: 4px;
border: none;
}
#download-menu-container-wrapper {
width: unset !important;
}
#download-menu-options select {
font-size: 30px !important;
}
input[type="text"] {
font-size: 16px !important;
}
</style>
`;
var SAVED_SEARCHES_HTML = `
<div id="saved-searches">
<style>
#saved-searches-container {
margin: 0;
display: flex;
flex-direction: column;
padding: 0;
}
#saved-searches-input-container {
margin-bottom: 10px;
}
#saved-searches-input {
flex: 15 1 auto;
margin-right: 10px;
}
#savedSearches {
max-width: 100%;
button {
flex: 1 1 auto;
cursor: pointer;
}
}
#saved-searches-buttons button {
margin-right: 1px;
margin-bottom: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
height: 35px;
&:hover {
filter: brightness(140%);
}
}
#saved-search-list-container {
direction: rtl;
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
margin: 0;
padding: 0;
}
#saved-search-list {
direction: ltr;
>li {
display: flex;
flex-direction: row;
cursor: pointer;
background: rgba(0, 0, 0, .1);
&:nth-child(odd) {
background: rgba(0, 0, 0, 0.2);
}
>div {
padding: 4px;
align-content: center;
svg {
height: 20px;
width: 20px;
}
}
}
}
.save-search-label {
flex: 1000 30px;
text-align: left;
&:hover {
color: white;
background: #0075FF;
}
}
.edit-saved-search-button {
text-align: center;
flex: 1 20px;
&:hover {
color: white;
background: slategray;
}
}
.remove-saved-search-button {
text-align: center;
flex: 1 20px;
&:hover {
color: white;
background: #f44336;
}
}
.move-saved-search-to-top-button {
text-align: center;
&:hover {
color: white;
background: steelblue;
}
}
/* .tag-type-saved>a,
.tag-type-saved {
color: lightblue;
} */
</style>
<h2>Saved Searches</h2>
<div id="saved-searches-buttons">
<button title="Save custom search" id="save-custom-search-button">Save</button>
<button id="stop-editing-saved-search-button" style="display: none;">Cancel</button>
<span>
<button title="Export all saved searches" id="export-saved-search-button">Export</button>
<button title="Import saved searches" id="import-saved-search-button">Import</button>
</span>
<button title="Save result ids as search" id="save-results-button">Save Results</button>
</div>
<div id="saved-searches-container">
<div id="saved-searches-input-container">
<textarea id="saved-searches-input" spellcheck="false" style="width: 97%;"
placeholder="Save Custom Search"></textarea>
</div>
<div id="saved-search-list-container">
<ul id="saved-search-list"></ul>
</div>
</div>
</div>
<script>
<\/script>
`;
var SKELETON_HTML = `
<style>
.skeleton-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
padding: 0px 30px;
}
.skeleton-column {
display: flex;
flex-direction: column;
gap: 10px;
}
.skeleton-item {
/* width: 100%; */
/* aspect-ratio: 1/3; */
background: #555;
overflow: hidden;
}
.skeleton-item.pulse {
animation: pulse var(--skeleton-animation-duration, 1s) infinite ease-in-out;
animation-delay: var(--skeleton-animation-delay, 0s);
}
.skeleton-item.shine::after {
background-image: linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(255, 255, 255, 0.08), rgba(0, 0, 0, 0));
content: "";
position: absolute;
transform: translateX(-100%);
bottom: 0;
left: 0;
right: 0;
top: 0;
animation: shine var(--skeleton-animation-duration, 1s) linear infinite;
animation-delay: var(--skeleton-animation-delay, 0s);
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
@keyframes shine {
0% {
transform: translateX(-100%);
}
/* 100% {
transform: translateX(100%);
} */
50% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
</style>
`;
var TAG_MODIFIER_HTML = `
<div id="tag-modifier-container">
<style>
#tag-modifier-ui-container {
display: none;
>* {
margin-top: 10px;
}
}
#tag-modifier-ui-textarea {
width: 80%;
}
.favorite.tag-modifier-selected {
outline: 2px dashed white !important;
>div, >a {
opacity: 1;
filter: grayscale(0%);
}
}
#tag-modifier-ui-status-label {
visibility: hidden;
}
.tag-type-custom>a,
.tag-type-custom {
color: hotpink;
}
</style>
<div id="tag-modifier-option-container">
<label class="checkbox" title="Add or remove custom or official tags to favorites">
<input type="checkbox" id="tag-modifier-option-checkbox"> Modify Tags<span class="option-hint"></span>
</label>
</div>
<div id="tag-modifier-ui-container">
<label id="tag-modifier-ui-status-label">No Status</label>
<textarea id="tag-modifier-ui-textarea" placeholder="tags" spellcheck="false"></textarea>
<div id="tag-modifier-buttons">
<span id="tag-modifier-ui-modification-buttons">
<button id="tag-modifier-ui-add" title="Add tags to selected favorites">Add</button>
<button id="tag-modifier-remove" title="Remove tags from selected favorites">Remove</button>
</span>
<span id="tag-modifier-ui-selection-buttons">
<button id="tag-modifier-ui-select-all" title="Select all favorites for tag modification">Select all</button>
<button id="tag-modifier-ui-un-select-all" title="Unselect all favorites for tag modification">Unselect
all</button>
</span>
</div>
<div id="tag-modifier-ui-reset-button-container">
<button id="tag-modifier-reset" title="Reset tag modifications">Reset</button>
</div>
<div id="tag-modifier-ui-configuration" style="display: none;">
<button id="tag-modifier-import" title="Import modified tags">Import</button>
<button id="tag-modifier-export" title="Export modified tags">Export</button>
</div>
</div>
</div>
`;
var TOOLTIP_HTML = `
<div id="tooltip-container">
<style>
#tooltip {
max-width: 750px;
border: 1px solid black;
padding: 0.25em;
position: absolute;
box-sizing: border-box;
z-index: 25;
pointer-events: none;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.25s linear;
font-size: 1.05em;
}
#tooltip.visible {
visibility: visible;
opacity: 1;
}
/* .favorite {
overflow: unset !important;
} */
.favorite::after {
opacity: 0;
transition: visibility 0s, opacity 0.25s linear;
content: attr(data-tooltip);
position: absolute;
font-size: 12px;
z-index: 1000;
background: gray;
padding: 5px;
color: white;
left: 0;
top: 100%;
min-width: 500px;
pointer-events: none;
}
.favorite.tooltip::after {
opacity: 1;
}
</style>
</div>
`;
function sleep(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
function yield1() {
return sleep(0);
}
function debounceAfterFirstCall(fn, delay) {
let timeoutId;
let firstCall = true;
let calledDuringDebounce = false;
return (...args) => {
if (firstCall) {
Reflect.apply(fn, this, args);
firstCall = false;
} else {
calledDuringDebounce = true;
}
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
if (calledDuringDebounce) {
Reflect.apply(fn, this, args);
calledDuringDebounce = false;
}
firstCall = true;
}, delay);
};
}
function debounceAlways(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
Reflect.apply(fn, this, args);
}, delay);
};
}
function throttle(fn, delay) {
let throttling = false;
return (...args) => {
if (!throttling) {
fn(...args);
throttling = true;
setTimeout(() => {
throttling = false;
}, delay);
}
};
}
async function runWithPools(items, poolSize, task) {
let index = 0;
async function worker() {
while (index < items.length) {
const i = index;
index += 1;
await task(items[i], i);
}
}
await Promise.all(Array.from({ length: poolSize }, () => worker()));
}
function withTimeout(promise, milliseconds) {
const timeout = new Promise((_, reject) => setTimeout(() => reject(new PromiseTimeoutError()), milliseconds));
return Promise.race([promise, timeout]);
}
function getMainStyleSheetElement() {
return Array.from(document.querySelectorAll("link")).filter((link) => link.rel === "stylesheet")[0];
}
function setStyleSheet(url) {
getMainStyleSheetElement()?.setAttribute("href", url);
}
function toggleDarkStyleSheet(useDark) {
setStyleSheet(getStyleSheetURL(useDark));
}
function toggleLocalDarkStyles(useDark) {
const currentTheme = useDark ? "light-green-gradient" : "dark-green-gradient";
const targetTheme = useDark ? "dark-green-gradient" : "light-green-gradient";
for (const element of Array.from(document.querySelectorAll(`.${currentTheme}`))) {
element.classList.remove(currentTheme);
element.classList.add(targetTheme);
}
}
function setupVideoAndGifOutlines() {
const size = ON_MOBILE_DEVICE ? 1 : 2;
const videoSelector = ON_FAVORITES_PAGE ? "&:has(img.video)" : ">img.video";
const gifSelector = ON_FAVORITES_PAGE ? "&:has(img.gif)" : ">img.gif";
const videoRule = `${videoSelector} {outline: ${size}px solid blue}`;
const gifRule = `${gifSelector} {outline: ${size}px solid hotpink}`;
insertStyleHTML(`
#favorites-search-gallery-content {
&.row,
&.square,
&.column
{
.favorite {
${videoRule}
${gifRule}
}
}
}
#favorites-search-gallery-content.grid .favorite, .thumb {
>a,
>div {
${videoRule}
${gifRule}
}
}
`, "video-gif-borders");
}
async function toggleDarkTheme(useDark) {
await yield1();
insertStyleHTML(useDark ? DARK_THEME_HTML : "", "dark-theme");
toggleDarkStyleSheet(useDark);
toggleLocalDarkStyles(useDark);
setCookie("theme", useDark ? "dark" : "light");
}
function insertStyleHTML(html, id = void 0) {
const style = document.createElement("style");
style.textContent = html.replace("<style>", "").replace("</style>", "");
if (id !== void 0) {
id += "-fsg-style";
const oldStyle = document.getElementById(id);
if (oldStyle !== null) {
oldStyle.remove();
}
style.id = id;
}
document.head.appendChild(style);
}
function usingDarkTheme() {
return getCookie("theme", "") === "dark";
}
function getCurrentThemeClass() {
return usingDarkTheme() ? "dark-green-gradient" : "light-green-gradient";
}
function insertHTMLAndExtractStyle(element, position, html) {
const dom = new DOMParser().parseFromString(html, "text/html");
const styles = Array.from(dom.querySelectorAll("style"));
for (const style of styles) {
insertStyleHTML(style.innerHTML);
style.remove();
}
element.insertAdjacentHTML(position, dom.body.innerHTML);
}
function setupCommonStyles() {
insertStyleHTML(SKELETON_HTML, "skeleton-style");
insertStyleHTML(COMMON_HTML, "common-style");
toggleDarkTheme(usingDarkTheme());
setupVideoAndGifOutlines();
}
function setGalleryBackgroundColor(color) {
insertStyleHTML(`
#gallery-background,
#gallery-menu,
#gallery-menu-button-container,
#autoplay-menu,
#autoplay-settings-menu {
background: ${color} !important;
}
.gallery-menu-button:not(:hover) {
>svg {
fill: ${color} !important;
filter: invert(100%);
}
}
`, "gallery-background-color");
}
function setColorScheme(color) {
setGalleryBackgroundColor(color);
Preferences.colorScheme.set(color);
}
var TYPEABLE_INPUTS = /* @__PURE__ */ new Set(["color", "email", "number", "password", "search", "tel", "text", "url", "datetime"]);
var FAVORITE_ITEM_CLASS_NAME = "favorite";
var DEFAULT_ITEM_CLASS_NAME = "thumb";
var ITEM_CLASS_NAME = ON_SEARCH_PAGE ? DEFAULT_ITEM_CLASS_NAME : FAVORITE_ITEM_CLASS_NAME;
var ITEM_SELECTOR = `.${ITEM_CLASS_NAME}`;
var IMAGE_SELECTOR = `.${ITEM_CLASS_NAME} ${ON_SEARCH_PAGE ? "img" : "img:first-child"}`;
function getClosestItem(element) {
return element.closest(ITEM_SELECTOR);
}
function getImageFromThumb(thumb) {
return thumb.querySelector("img");
}
function getThumbFromImage(image) {
return getClosestItem(image);
}
function getPreviewURL(item) {
if (item instanceof HTMLElement) {
const image = getImageFromThumb(item);
return image ? image.src : null;
}
return item.thumbURL;
}
function getThumbUnderCursor(event) {
if (!(event.target instanceof HTMLElement) || event.target.matches(".caption-tag")) {
return null;
}
const image = event.target.matches(IMAGE_SELECTOR) ? event.target : null;
return image === null ? null : getThumbFromImage(image);
}
function isHotkeyEvent(event) {
return !event.repeat && event.target instanceof HTMLElement && !isTypeableInput(event.target);
}
function isTypeableInput(element) {
const tagName = element.tagName.toLowerCase();
return tagName === "textarea" || tagName === "input" && TYPEABLE_INPUTS.has(element.getAttribute("type") ?? "");
}
function insideOfThumb(element) {
return element instanceof HTMLElement && getClosestItem(element) !== null;
}
function waitForDOMToLoad() {
if (document.readyState !== "loading") {
return Promise.resolve();
}
return new Promise((resolve) => {
Events.document.domLoaded.on(() => {
resolve();
}, {
once: true
});
});
}
function imageIsLoaded(image) {
return image.complete || image.naturalWidth !== 0;
}
function imageIsLoading(image) {
return !imageIsLoaded(image);
}
function originalGetAllThumbs() {
return Array.from(document.querySelectorAll(ITEM_SELECTOR)).filter((thumb) => thumb instanceof HTMLElement);
}
var getAllThumbs = originalGetAllThumbs;
function changeGetAllTHumbsImplementation(newGetAllThumbs) {
getAllThumbs = newGetAllThumbs;
}
function resetGetAllThumbsImplementation() {
getAllThumbs = originalGetAllThumbs;
}
function waitForAllThumbnailsToLoad() {
const unloadedImages = getAllThumbs().map((thumb) => getImageFromThumb(thumb)).filter((image) => image instanceof HTMLImageElement).filter((image) => image.dataset.preload !== "true" && imageIsLoading(image));
return Promise.all(unloadedImages.map((image) => new Promise((resolve) => {
image.addEventListener("load", resolve, {
once: true
});
image.addEventListener("error", resolve, {
once: true
});
})));
}
function getIdFromThumb(thumb) {
const id = thumb.getAttribute("id");
if (id !== null) {
return removeNonNumericCharacters(id);
}
const anchor = thumb.querySelector("a");
if (anchor !== null && anchor.hasAttribute("id")) {
return removeNonNumericCharacters(anchor.id);
}
if (anchor !== null && anchor.hasAttribute("href")) {
const match2 = /id=(\d+)$/.exec(anchor.href);
if (match2 !== null) {
return match2[1];
}
}
const image = thumb.querySelector("img");
if (image === null) {
return "NA";
}
const match = /\?(\d+)$/.exec(image.src);
return match === null ? "NA" : match[1];
}
function scrollToTop() {
window.scrollTo(0, ON_MOBILE_DEVICE ? 10 : 0);
}
function hasTagName(element, tagName) {
return element instanceof HTMLElement && element.tagName !== void 0 && element.tagName.toLowerCase() === tagName;
}
function getRectDistance(rect1, rect2) {
const x1 = rect1.left + rect1.width / 2;
const y1 = rect1.top + rect1.height / 2;
const x2 = rect2.left + rect2.width / 2;
const y2 = rect2.top + rect2.height / 2;
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function toggleFullscreen() {
const html = document.documentElement;
if (document.fullscreenElement === null) {
html.requestFullscreen();
} else {
document.exitFullscreen();
}
}
function overGalleryMenu(event) {
if (!(event.target instanceof HTMLElement)) {
return false;
}
return event.target.classList.contains(".gallery-sub-menu") || event.target.closest(".gallery-sub-menu") !== null;
}
function toggleGalleryMenuEnabled(value) {
insertStyleHTML(`
#gallery-menu {
visibility: ${value ? "visible" : "hidden"} !important;
}`, "enable-gallery-menu");
}
function showFullscreenIcon(svg, duration = 500) {
const svgDocument = new DOMParser().parseFromString(svg, "image/svg+xml");
const svgElement = svgDocument.documentElement;
const svgOverlay = document.createElement("div");
svgOverlay.classList.add("fullscreen-icon");
svgOverlay.innerHTML = new XMLSerializer().serializeToString(svgElement);
document.body.appendChild(svgOverlay);
setTimeout(() => {
svgOverlay.remove();
}, duration);
}
function blurCurrentlyFocusedElement() {
const activeElement = document.activeElement;
if (activeElement instanceof HTMLElement) {
activeElement.blur();
}
}
var FavoritesKeyboardEvent = class {
key;
originalEvent;
isHotkey;
constructor(event) {
this.originalEvent = event;
this.key = event.key.toLowerCase();
this.isHotkey = isHotkeyEvent(event);
}
};
function convertTouchEventToMouseEvent(touchEvent, type) {
const touch = touchEvent.changedTouches[0];
return new MouseEvent(type, {
clientX: touch.clientX,
clientY: touch.clientY,
screenX: touch.screenX,
screenY: touch.screenY,
button: 0 /* LEFT */
});
}
var FavoritesMouseEvent = class {
originalEvent;
leftClick;
rightClick;
middleClick;
ctrlKey;
shiftKey;
thumb;
insideOfThumb;
constructor(event) {
if (!(event instanceof MouseEvent)) {
event = convertTouchEventToMouseEvent(event, "mousedown");
}
this.originalEvent = event;
this.leftClick = event.button === 0 /* LEFT */;
this.rightClick = event.button === 2 /* RIGHT */;
this.middleClick = event.button === 1 /* MIDDLE */;
this.ctrlKey = event.ctrlKey;
this.shiftKey = event.shiftKey;
this.thumb = getThumbUnderCursor(event);
this.insideOfThumb = this.thumb !== null || insideOfThumb(this.originalEvent.target);
}
};
var FORWARD_NAVIGATION_KEYS = /* @__PURE__ */ new Set(["d", "D", "ArrowRight"]);
var NAVIGATION_KEYS = /* @__PURE__ */ new Set(["a", "A", "ArrowLeft", "d", "D", "ArrowRight"]);
var EXIT_KEYS = /* @__PURE__ */ new Set(["Escape", "Delete", "Backspace"]);
function isExitKey(value) {
return EXIT_KEYS.has(value);
}
function isNavigationKey(value) {
return NAVIGATION_KEYS.has(value);
}
function isForwardNavigationKey(key) {
return FORWARD_NAVIGATION_KEYS.has(key);
}
function isSearchableMetadataMetric(value) {
return value === "score" || value === "width" || value === "height" || value === "id";
}
function isMetadataComparator(value) {
return value === ":" || value === ":<" || value === ":>";
}
function isTagCategory(value) {
return value === "general" || value === "artist" || value === "unknown" || value === "copyright" || value === "character" || value === "metadata";
}
var FavoritesWheelEvent = class {
originalEvent;
direction;
constructor(event) {
this.originalEvent = event;
this.direction = event.deltaY > 0 ? "ArrowRight" : "ArrowLeft";
}
get isForward() {
return isForwardNavigationKey(this.direction);
}
};
var THRESHOLD = 90;
var TOUCH_START = { x: 0, y: 0 };
var TOUCH_END = { x: 0, y: 0 };
function getXDelta() {
return TOUCH_END.x - TOUCH_START.x;
}
function getYDelta() {
return TOUCH_END.y - TOUCH_START.y;
}
function swipedDown() {
return getYDelta() > THRESHOLD;
}
function swipedUp() {
return getYDelta() < -THRESHOLD;
}
function swipedRight() {
return getXDelta() > THRESHOLD;
}
function swipedLeft() {
return getXDelta() < -THRESHOLD;
}
function onlySwipedDown() {
return swipedDown() && !swipedUp() && !swipedLeft() && !swipedRight();
}
function onlySwipedUp() {
return swipedUp() && !swipedDown() && !swipedLeft() && !swipedRight();
}
function onlySwipedRight() {
return swipedRight() && !swipedLeft() && !swipedUp() && !swipedDown();
}
function onlySwipedLeft() {
return swipedLeft() && !swipedRight() && !swipedUp() && !swipedDown();
}
function setTouchStart(event) {
TOUCH_START.x = event.changedTouches[0].screenX;
TOUCH_START.y = event.changedTouches[0].screenY;
}
function setTouchEnd(event) {
TOUCH_END.x = event.changedTouches[0].screenX;
TOUCH_END.y = event.changedTouches[0].screenY;
}
function onTouchEnd(event) {
setTouchEnd(event);
if (onlySwipedUp()) {
Events.mobile.swipedUp.emit();
return;
}
if (onlySwipedDown()) {
Events.mobile.swipedDown.emit();
return;
}
if (onlySwipedLeft()) {
Events.mobile.swipedLeft.emit();
return;
}
if (onlySwipedRight()) {
Events.mobile.swipedRight.emit();
}
}
function didSwipe() {
return swipedDown() || swipedUp() || swipedLeft() || swipedRight();
}
function setupSwipeEvents() {
Events.document.touchStart.on(setTouchStart);
Events.document.touchEnd.on(onTouchEnd);
}
var timer;
var target = null;
var THRESHOLD2 = 300;
function stopHoldTimer() {
if (timer !== void 0) {
clearTimeout(timer);
timer = void 0;
}
target = null;
}
function startHoldTimer(event) {
target = event.target;
if (timer === void 0) {
timer = setTimeout(() => {
if (target instanceof EventTarget) {
Events.mobile.touchHold.emit(target);
}
}, THRESHOLD2);
}
}
function setupTouchHoldEvents() {
Events.document.touchStart.on(startHoldTimer);
Events.document.touchEnd.on(stopHoldTimer);
}
var container = ON_FAVORITES_PAGE ? FAVORITES_SEARCH_GALLERY_CONTAINER : document.documentElement;
var favorites = {
searchStarted: new EventEmitter(true),
searchBoxUpdated: new EventEmitter(true),
pageChanged: new EventEmitter(true),
pageSelected: new EventEmitter(true),
relativePageSelected: new EventEmitter(true),
findFavoriteStarted: new EventEmitter(true),
findFavoriteInAllStarted: new EventEmitter(true),
favoritesLoadedFromDatabase: new EventEmitter(true),
favoritesLoaded: new EventEmitter(true),
startedFetchingFavorites: new EventEmitter(true),
searchResultsUpdated: new EventEmitter(true),
favoriteRemoved: new EventEmitter(true),
inGalleryRequest: new EventEmitter(true),
pageChangeResponse: new EventEmitter(true),
newFavoritesFoundOnReload: new EventEmitter(true),
resultsAddedToCurrentPage: new EventEmitter(true),
missingMetadataFound: new EventEmitter(true),
favoritesResized: new EventEmitter(true),
captionsReEnabled: new EventEmitter(true),
resultsPerPageChanged: new EventEmitter(true),
allowedRatingsChanged: new EventEmitter(true),
columnCountChanged: new EventEmitter(true),
rowSizeChanged: new EventEmitter(true),
layoutChanged: new EventEmitter(true),
sortingMethodChanged: new EventEmitter(true),
performanceProfileChanged: new EventEmitter(true),
showOnHoverToggled: new EventEmitter(true),
tooltipsToggled: new EventEmitter(true),
autoplayToggled: new EventEmitter(true),
hintsToggled: new EventEmitter(true),
optionsToggled: new EventEmitter(true),
removeButtonsToggled: new EventEmitter(true),
addButtonsToggled: new EventEmitter(true),
downloadButtonsToggled: new EventEmitter(true),
uiToggled: new EventEmitter(true),
darkThemeToggled: new EventEmitter(true),
headerToggled: new EventEmitter(true),
captionsToggled: new EventEmitter(true),
sortAscendingToggled: new EventEmitter(true),
galleryMenuToggled: new EventEmitter(true),
blacklistToggled: new EventEmitter(true),
infiniteScrollToggled: new EventEmitter(true),
fancyHoveringToggled: new EventEmitter(true),
savedSearchesToggled: new EventEmitter(true),
downloadButtonClicked: new EventEmitter(true),
searchSubsetClicked: new EventEmitter(true),
stopSearchSubsetClicked: new EventEmitter(true),
invertButtonClicked: new EventEmitter(true),
shuffleButtonClicked: new EventEmitter(true),
searchButtonClicked: new EventEmitter(true),
clearButtonClicked: new EventEmitter(true),
resetButtonClicked: new EventEmitter(true),
resetConfirmed: new EventEmitter(true)
};
var gallery = {
inGalleryResponse: new EventEmitter(true),
pageChangeRequested: new EventEmitter(true),
favoriteToggled: new EventEmitter(true),
showOnHoverToggled: new EventEmitter(true),
enteredGallery: new EventEmitter(true),
exitedGallery: new EventEmitter(true),
visibleThumbsChanged: new EventEmitter(true),
galleryMenuButtonClicked: new EventEmitter(true),
videoEnded: new EventEmitter(true),
videoDoubleClicked: new EventEmitter(true),
rightTap: new EventEmitter(true),
leftTap: new EventEmitter(true)
};
var caption = {
idClicked: new EventEmitter(true),
searchForTag: new EventEmitter(true)
};
var searchBox = {
appendSearchBox: new EventEmitter(true)
};
var mobile = {
swipedUp: new EventEmitter(true),
swipedDown: new EventEmitter(true),
swipedLeft: new EventEmitter(true),
swipedRight: new EventEmitter(true),
touchHold: new EventEmitter(true)
};
var tagModifier = {
resetConfirmed: new EventEmitter(true)
};
var document1 = {
domLoaded: new EventEmitter(true),
postProcess: new EventEmitter(true),
mouseover: new EventEmitter(true),
click: new EventEmitter(true),
mousedown: new EventEmitter(true),
touchStart: new EventEmitter(true),
touchEnd: new EventEmitter(true),
keydown: new EventEmitter(true),
keyup: new EventEmitter(true),
wheel: new EventEmitter(true),
contextmenu: new EventEmitter(true),
mousemove: new EventEmitter(true)
};
var window1 = {
focus: new EventEmitter(true),
blur: new EventEmitter(true),
orientationChange: new EventEmitter(true)
};
function setupDocumentEvents() {
broadcastDOMLoad();
setupCommonEvents();
}
function setupCommonEvents() {
container.addEventListener("click", (event) => {
Events.document.click.emit(event);
});
container.addEventListener("mousedown", (event) => {
Events.document.mousedown.emit(event);
});
document.addEventListener("keydown", (event) => {
Events.document.keydown.emit(new FavoritesKeyboardEvent(event));
});
document.addEventListener("keyup", (event) => {
Events.document.keyup.emit(new FavoritesKeyboardEvent(event));
});
container.addEventListener("mouseover", (event) => {
Events.document.mouseover.emit(new FavoritesMouseEvent(event));
}, {
passive: true
});
container.addEventListener("mousemove", (event) => {
Events.document.mousemove.emit(event);
}, {
passive: true
});
document.addEventListener("wheel", (event) => {
Events.document.wheel.emit(new FavoritesWheelEvent(event));
}, {
passive: true
});
container.addEventListener("contextmenu", (event) => {
Events.document.contextmenu.emit(event);
});
container.addEventListener("touchstart", (event) => {
Events.document.touchStart.emit(event);
}, { passive: false });
container.addEventListener("touchend", (event) => {
Events.document.touchEnd.emit(event);
});
}
function setupWindowEvents() {
window.addEventListener("focus", (event) => {
window1.focus.emit(event);
});
window.addEventListener("blur", (event) => {
window1.focus.emit(event);
});
window.addEventListener("orientationchange", (event) => {
Events.window.orientationChange.emit(event);
});
}
function setupMobileEvents() {
if (ON_DESKTOP_DEVICE) {
return;
}
setupTouchHoldEvents();
setupSwipeEvents();
}
function toggleGlobalInputEvents(value) {
for (const event of Object.values(Events.document)) {
event.toggle(value);
}
}
function broadcastDOMLoad() {
document.addEventListener("DOMContentLoaded", () => {
Events.document.domLoaded.emit();
});
}
var Events = {
favorites,
gallery,
caption,
searchBox,
document: document1,
window: window1,
mobile,
tagModifier,
toggleGlobalInputEvents
};
function setupEvents() {
setupDocumentEvents();
setupWindowEvents();
setupMobileEvents();
}
function addAwesompleteToGlobalScope() {
!function() {
function t(t2) {
const e2 = Array.isArray(t2) ? {
label: t2[0],
value: t2[1]
} : typeof t2 === "object" && t2 != null && "label" in t2 && "value" in t2 ? t2 : {
label: t2,
value: t2
};
this.label = e2.label || e2.value, this.value = e2.value, this.type = e2.type;
}
function e(t2, e2, i2) {
for (const n2 in e2) {
const s2 = e2[n2], r2 = t2.input.getAttribute(`data-${n2.toLowerCase()}`);
typeof s2 === "number" ? t2[n2] = parseInt(r2) : false === s2 ? t2[n2] = r2 !== null : s2 instanceof Function ? t2[n2] = null : t2[n2] = r2, t2[n2] || t2[n2] === 0 || (t2[n2] = n2 in i2 ? i2[n2] : s2);
}
}
function i(t2, e2) {
return typeof t2 === "string" ? (e2 || document).querySelector(t2) : t2 || null;
}
function n(t2, e2) {
return o.call((e2 || document).querySelectorAll(t2));
}
function s() {
n("input.awesomplete").forEach((t2) => {
new r(t2);
});
}
var r = function(t2, n2) {
const s2 = this;
this.isOpened = false, this.input = i(t2), this.input.setAttribute("autocomplete", "off"), this.input.setAttribute("aria-autocomplete", "list"), n2 = n2 || {}, e(this, {
minChars: 2,
maxItems: 20,
autoFirst: false,
data: r.DATA,
filter: r.FILTER_CONTAINS,
sort: false !== n2.sort && r.SORT_BYLENGTH,
item: r.ITEM,
replace: r.REPLACE
}, n2), this.index = -1, this.container = i.create("div", {
className: "awesomplete",
around: t2
}), this.ul = i.create("ul", {
hidden: "hidden",
inside: this.container
}), this.status = i.create("span", {
className: "visually-hidden",
role: "status",
"aria-live": "assertive",
"aria-relevant": "additions",
inside: this.container
}), this._events = {
input: {
input: this.evaluate.bind(this),
blur: this.close.bind(this, {
reason: "blur"
}),
keypress(t3) {
const e2 = t3.keyCode;
if (s2.opened) {
switch (e2) {
case 13:
if (s2.selected == true) {
t3.preventDefault();
s2.select();
break;
}
case 66:
break;
case 27:
s2.close({
reason: "esc"
});
break;
}
}
},
keydown(t3) {
const e2 = t3.keyCode;
if (s2.opened) {
switch (e2) {
case 9:
if (s2.selected == true) {
t3.preventDefault();
s2.select();
break;
}
case 38:
t3.preventDefault();
s2.previous();
break;
case 40:
t3.preventDefault();
s2.next();
break;
}
}
}
},
form: {
submit: this.close.bind(this, {
reason: "submit"
})
},
ul: {
mousedown(t3) {
let e2 = t3.target;
if (e2 !== this) {
for (; e2 && !/li/i.test(e2.nodeName); ) e2 = e2.parentNode;
e2 && t3.button === 0 && (t3.preventDefault(), s2.select(e2, t3.target));
}
}
}
}, i.bind(this.input, this._events.input), i.bind(this.input.form, this._events.form), i.bind(this.ul, this._events.ul), this.input.hasAttribute("list") ? (this.list = `#${this.input.getAttribute("list")}`, this.input.removeAttribute("list")) : this.list = this.input.getAttribute("data-list") || n2.list || [], r.all.push(this);
};
r.prototype = {
set list(t2) {
if (Array.isArray(t2)) this._list = t2;
else if (typeof t2 === "string" && t2.indexOf(",") > -1) this._list = t2.split(/\s*,\s*/);
else if ((t2 = i(t2)) && t2.children) {
const e2 = [];
o.apply(t2.children).forEach((t3) => {
if (!t3.disabled) {
const i2 = t3.textContent.trim(), n2 = t3.value || i2, s2 = t3.label || i2;
n2 !== "" && e2.push({
label: s2,
value: n2
});
}
}), this._list = e2;
}
document.activeElement === this.input && this.evaluate();
},
get selected() {
return this.index > -1;
},
get opened() {
return this.isOpened;
},
close(t2) {
this.opened && (this.ul.setAttribute("hidden", ""), this.isOpened = false, this.index = -1, i.fire(this.input, "awesomplete-close", t2 || {}));
},
open() {
this.ul.removeAttribute("hidden"), this.isOpened = true, this.autoFirst && this.index === -1 && this.goto(0), i.fire(this.input, "awesomplete-open");
},
destroy() {
i.unbind(this.input, this._events.input), i.unbind(this.input.form, this._events.form);
const t2 = this.container.parentNode;
t2.insertBefore(this.input, this.container), t2.removeChild(this.container), this.input.removeAttribute("autocomplete"), this.input.removeAttribute("aria-autocomplete");
const e2 = r.all.indexOf(this);
e2 !== -1 && r.all.splice(e2, 1);
},
next() {
const t2 = this.ul.children.length;
this.goto(this.index < t2 - 1 ? this.index + 1 : t2 ? 0 : -1);
},
previous() {
const t2 = this.ul.children.length, e2 = this.index - 1;
this.goto(this.selected && e2 !== -1 ? e2 : t2 - 1);
},
goto(t2) {
const e2 = this.ul.children;
this.selected && e2[this.index].setAttribute("aria-selected", "false"), this.index = t2, t2 > -1 && e2.length > 0 && (e2[t2].setAttribute("aria-selected", "true"), this.status.textContent = e2[t2].textContent, this.ul.scrollTop = e2[t2].offsetTop - this.ul.clientHeight + e2[t2].clientHeight, i.fire(this.input, "awesomplete-highlight", {
text: this.suggestions[this.index]
}));
},
select(t2, e2) {
if (t2 ? this.index = i.siblingIndex(t2) : t2 = this.ul.children[this.index], t2) {
const n2 = this.suggestions[this.index];
i.fire(this.input, "awesomplete-select", {
text: n2,
origin: e2 || t2
}) && (this.replace(n2), this.close({
reason: "select"
}), i.fire(this.input, "awesomplete-selectcomplete", {
text: n2
}));
}
},
evaluate() {
const e2 = this, i2 = this.input.value;
i2.length >= this.minChars && this._list.length > 0 ? (this.index = -1, this.ul.innerHTML = "", this.suggestions = this._list.map((n2) => {
return new t(e2.data(n2, i2));
}).filter((t2) => {
return e2.filter(t2, i2);
}), false !== this.sort && (this.suggestions = this.suggestions.sort(this.sort)), this.suggestions = this.suggestions.slice(0, this.maxItems), this.suggestions.forEach((t2) => {
e2.ul.appendChild(e2.item(t2, i2));
}), this.ul.children.length === 0 ? this.close({
reason: "nomatches"
}) : this.open()) : this.close({
reason: "nomatches"
});
}
}, r.all = [], r.FILTER_CONTAINS = function(t2, e2) {
return RegExp(i.regExpEscape(e2.trim()), "i").test(t2);
}, r.FILTER_STARTSWITH = function(t2, e2) {
return RegExp(`^${i.regExpEscape(e2.trim())}`, "i").test(t2);
}, r.SORT_BYLENGTH = function(t2, e2) {
return t2.length !== e2.length ? t2.length - e2.length : t2 < e2 ? -1 : 1;
}, r.ITEM = function(t2, e2) {
return i.create("li", {
innerHTML: e2.trim() === "" ? t2 : t2.replace(RegExp(i.regExpEscape(e2.trim()), "gi"), "<mark>$&</mark>"),
"aria-selected": "false"
});
}, r.REPLACE = function(t2) {
this.input.value = t2.value;
}, r.DATA = function(t2) {
return t2;
}, Object.defineProperty(t.prototype = Object.create(String.prototype), "length", {
get() {
return this.label.length;
}
}), t.prototype.toString = t.prototype.valueOf = function() {
return `${this.label}`;
};
var o = Array.prototype.slice;
i.create = function(t2, e2) {
const n2 = document.createElement(t2);
for (const s2 in e2) {
const r2 = e2[s2];
if (s2 === "inside") i(r2).appendChild(n2);
else if (s2 === "around") {
const o2 = i(r2);
o2.parentNode.insertBefore(n2, o2), n2.appendChild(o2);
} else s2 in n2 ? n2[s2] = r2 : n2.setAttribute(s2, r2);
}
return n2;
}, i.bind = function(t2, e2) {
if (t2) for (const i2 in e2) {
var n2 = e2[i2];
i2.split(/\s+/).forEach((e3) => {
t2.addEventListener(e3, n2);
});
}
}, i.unbind = function(t2, e2) {
if (t2) for (const i2 in e2) {
var n2 = e2[i2];
i2.split(/\s+/).forEach((e3) => {
t2.removeEventListener(e3, n2);
});
}
}, i.fire = function(t2, e2, i2) {
const n2 = document.createEvent("HTMLEvents");
n2.initEvent(e2, true, true);
for (const s2 in i2) n2[s2] = i2[s2];
return t2.dispatchEvent(n2);
}, i.regExpEscape = function(t2) {
return t2.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
}, i.siblingIndex = function(t2) {
for (var e2 = 0; t2 = t2.previousElementSibling; e2++) ;
return e2;
}, typeof Document !== "undefined" && (document.readyState !== "loading" ? s() : document.addEventListener("DOMContentLoaded", s)), r.$ = i, r.$$ = n, typeof self !== "undefined" && (self.Awesomplete_ = r), typeof module === "object" && module.exports;
}();
}
var PARSER = new DOMParser();
function extractFavoritesCount(html) {
const favoritesURL = Array.from(PARSER.parseFromString(html, "text/html").querySelectorAll("a")).find((a) => a.href.includes("page=favorites&s=view"));
if (favoritesURL === void 0 || favoritesURL.textContent === null) {
return 0;
}
return parseInt(favoritesURL.textContent);
}
var ApiParseError = class extends Error {
};
var PARSER2 = new DOMParser();
function parseNumber(attribute, post) {
return Number(post.getAttribute(attribute) || 0);
}
function parseString(attribute, post) {
return String(post.getAttribute(attribute) || "");
}
function parseBoolean(attribute, post) {
return post.getAttribute(attribute) === "true";
}
function createPostFromAPIElement(element) {
return {
id: parseString("id", element),
height: parseNumber("height", element),
score: parseNumber("score", element),
fileURL: parseString("file_url", element),
parentId: parseString("parent_id", element),
sampleURL: parseString("sample_url", element),
sampleWidth: parseNumber("sample_width", element),
sampleHeight: parseNumber("sample_height", element),
previewURL: parseString("preview_url", element),
rating: parseString("rating", element),
tags: parseString("tags", element),
width: parseNumber("width", element),
change: parseNumber("change", element),
md5: parseString("md5", element),
creatorId: parseString("creator_id", element),
hasChildren: parseBoolean("has_children", element),
createdAt: parseString("created_at", element),
status: parseString("status", element),
source: parseString("source", element),
hasNotes: parseBoolean("has_notes", element),
hasComments: parseBoolean("has_comments", element),
previewWidth: parseNumber("preview_width", element),
previewHeight: parseNumber("preview_height", element)
};
}
function extractPostFromAPI(html) {
const post = PARSER2.parseFromString(html, "text/html").querySelector("post");
if (post === null) {
throw new ApiParseError();
}
return createPostFromAPIElement(post);
}
var IMAGE_SOURCE_CLEANUP_REGEX = /^([^.]*\/\/)?(?:[^.]+\.)*rule34/;
var THUMB_SOURCE_COMPRESSION_REGEX = /thumbnails\/+([0-9]+)\/+thumbnail_([0-9a-f]+)/;
var SAMPLE_REGEX = /\/([^/]+)$/;
var EXTENSION_REGEX = /\.(png|jpg|jpeg|gif|mp4)/;
function cleanImageSource(source) {
return source.replace(IMAGE_SOURCE_CLEANUP_REGEX, "$1rule34");
}
function decompressPreviewSource(compressedSource) {
const splitSource = compressedSource.split("_");
return `https://us.rule34.xxx/thumbnails//${splitSource[0]}/thumbnail_${splitSource[1]}.jpg`;
}
function compressPreviewSource(source) {
const match = source.match(THUMB_SOURCE_COMPRESSION_REGEX);
return match === null ? "" : match.splice(1).join("_");
}
function convertPreviewURLToImageURL(thumbURL) {
return cleanImageSource(thumbURL).replace("thumbnails", "images").replace("thumbnail_", "").replace("us.rule34", "rule34");
}
function convertImageURLToSampleURL(imageURL) {
return imageURL.replace("images", "samples").replace(SAMPLE_REGEX, "/sample_$1").replace(EXTENSION_REGEX, ".jpg");
}
var PARSER3 = new DOMParser();
var STATISTICS_REGEX = /(\S+):\s+(\S+)/g;
function getStatistics(dom) {
const stats = dom.querySelector("#stats");
if (stats === null) {
return {};
}
const textContent = removeExtraWhiteSpace(stats.textContent || "");
const matches = Array.from(textContent.matchAll(STATISTICS_REGEX));
const entries = matches.map((match) => [match[1].toLowerCase(), match[2]]);
return Object.fromEntries(entries);
}
function getFileURL(dom) {
const image = dom.querySelector("#image");
return image instanceof HTMLImageElement ? cleanImageSource(image.src) : "";
}
function getTags(dom) {
return removeExtraWhiteSpace(Array.from(dom.querySelectorAll(".tag>a")).filter((anchor) => anchor instanceof HTMLAnchorElement && anchor.textContent !== "?").map((anchor) => (anchor.textContent || "").replaceAll(" ", "_")).join(" ") || "");
}
function getRating(statistics) {
if (statistics.rating === void 0 || statistics.rating === "") {
return "e";
}
return statistics.rating.charAt(0).toLowerCase();
}
function hasComments(dom) {
return Array.from(dom.querySelectorAll("#comments>div")).length > 0;
}
function parsePostFromPostPage(html) {
const dom = PARSER3.parseFromString(html, "text/html");
const statistics = getStatistics(dom);
const fileURL = getFileURL(dom);
const tags = getTags(dom);
const rating = getRating(statistics);
const dimensions = getDimensions2D(statistics.size);
const hasNotes = statistics.notes !== void 0 && statistics.notes !== "0";
const hasCommentsValue = hasComments(dom);
return {
id: statistics.id,
height: dimensions.height,
score: Number(statistics.score),
fileURL,
parentId: "",
sampleURL: "",
sampleWidth: 0,
sampleHeight: 0,
previewURL: "",
rating,
tags,
width: dimensions.width,
change: 0,
md5: "",
creatorId: "",
hasChildren: false,
createdAt: statistics.posted,
status: "active",
source: statistics.source,
hasNotes,
hasComments: hasCommentsValue,
previewWidth: 0,
previewHeight: 0
};
}
async function getHTML(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
}
return response.text();
}
async function fetchPostFromAPI(id) {
return extractPostFromAPI(await getHTML(createAPIURL(id)));
}
function fetchPostPage(id) {
return getHTML(createPostPageURL(id));
}
async function fetchPostFromPostPage(id) {
return parsePostFromPostPage(await fetchPostPage(id));
}
function fetchPostFromAPISafe(id) {
return fetchPostFromAPI(id).catch(() => {
return fetchPostFromPostPage(id);
});
}
function fetchFavoritesPage(pageNumber) {
return getHTML(createFavoritesPageURL(pageNumber));
}
function fetchTagFromAPI(tagName) {
return getHTML(createTagAPIURL(tagName));
}
async function addFavorite(id) {
fetch(createPostVoteURL(id));
const status = await getHTML(createAddFavoriteURL(id));
return parseInt(status);
}
function removeFavorite(id) {
return fetch(createRemoveFavoriteURL(id), { method: "GET", redirect: "manual" });
}
function getFavoritesCount() {
return getHTML(createProfilePageURL()).then(extractFavoritesCount).catch(null);
}
var CUSTOM_TAGS = loadCustomTags();
var PARSER4 = new DOMParser();
function loadCustomTags() {
return new Set(JSON.parse(localStorage.getItem("customTags") || "[]"));
}
async function setCustomTags(tags) {
for (const tag of removeExtraWhiteSpace(tags).split(" ")) {
if (tag === "" || CUSTOM_TAGS.has(tag)) {
continue;
}
const isAnOfficialTag = await isOfficialTag(tag);
if (!isAnOfficialTag) {
CUSTOM_TAGS.add(tag);
}
}
localStorage.setItem("customTags", JSON.stringify(Array.from(CUSTOM_TAGS)));
}
function clearCustomTags() {
CUSTOM_TAGS.clear();
localStorage.removeItem("customTags");
}
async function isOfficialTag(tagName) {
try {
const html = await fetchTagFromAPI(tagName);
const dom = PARSER4.parseFromString(html, "text/html");
const columnOfFirstRow = dom.getElementsByClassName("highlightable")[0].getElementsByTagName("td");
return columnOfFirstRow.length === 3;
} catch (error) {
console.error(error);
return false;
}
}
function addCustomTagsToAutocomplete(officialTags, searchQuery2) {
const customTags = Array.from(CUSTOM_TAGS);
const officialTagValues = new Set(officialTags.map((officialTag) => officialTag.value));
const mergedTags = officialTags;
for (const customTag of customTags) {
if (!officialTagValues.has(customTag) && customTag.startsWith(searchQuery2)) {
mergedTags.unshift({
label: `${customTag} (custom)`,
value: customTag,
type: "custom"
});
}
}
return mergedTags;
}
var DEFAULT_BOUNDARIES = { start: 0, end: 0 };
function isNegatedLeftTagBoundary(text, index) {
return text[index] === "-" && (text[index - 1] === " " || text[index - 1] === void 0);
}
function isLeftTagBoundary(text, index) {
return index < 0 || text[index] === " " || isNegatedLeftTagBoundary(text, index);
}
function isRightTagBoundary(text, index) {
return index >= text.length || text[index] === " ";
}
function getLeftTagBoundary(selectionStart, text) {
let boundary = selectionStart - 1;
while (!isLeftTagBoundary(text, boundary)) {
boundary -= 1;
}
return boundary + 1;
}
function getRightTagBoundary(selectionStart, text) {
let boundary = selectionStart;
while (!isRightTagBoundary(text, boundary)) {
boundary += 1;
}
return boundary;
}
function getTagBoundary(text, selectionStart) {
if (selectionStart < 0 || selectionStart > text.length || text.length === 0) {
return DEFAULT_BOUNDARIES;
}
return {
start: getLeftTagBoundary(selectionStart, text),
end: getRightTagBoundary(selectionStart, text)
};
}
function getQueryWithTagReplaced(text, selectionStart, replacement) {
if (selectionStart < 0 || selectionStart > text.length) {
return { result: text, selectionStart };
}
const { start, end } = getTagBoundary(text, selectionStart);
const firstHalf = text.slice(0, start);
const secondHalf = text.slice(end, text.length);
const result = `${firstHalf}${replacement}${secondHalf}`;
return {
result,
selectionStart: firstHalf.length + replacement.length
};
}
function getSavedSearches() {
return Array.from(document.getElementsByClassName("save-search-label")).filter((element) => element instanceof HTMLElement).map((element) => element.innerText);
}
var SUGGESTION_LIMIT = 5;
var MIN_TAG_LENGTH = 3;
function getSavedSearchTagList(savedSearch) {
return removeExtraWhiteSpace(savedSearch.replace(/[~())]/g, "")).split(" ");
}
function createAwesompleteSuggestion(searchTag, savedSearch) {
return {
label: savedSearch,
value: `${searchTag}_saved_search ${savedSearch}`,
type: "saved"
};
}
function savedSearchMatchesSearchTag(searchTag, savedSearch) {
return getSavedSearchTagList(savedSearch).some((tag) => tag.startsWith(searchTag));
}
function getSavedSearchesSuggestions(searchTag) {
if (searchTag.length < MIN_TAG_LENGTH) {
return [];
}
return getSavedSearches().filter((savedSearch) => savedSearchMatchesSearchTag(searchTag, savedSearch)).slice(0, SUGGESTION_LIMIT).map((savedSearch) => createAwesompleteSuggestion(searchTag, savedSearch));
}
function getAwesompleteFromInput(input2) {
const awesomplete = input2.parentElement;
if (awesomplete === null || awesomplete.className !== "awesomplete") {
return null;
}
return awesomplete;
}
function hideAwesomplete(input2) {
const awesomplete = getAwesompleteFromInput(input2);
if (awesomplete !== null) {
awesomplete.querySelector("ul")?.setAttribute("hidden", "");
}
}
function awesompleteIsVisible(input2) {
const awesomplete = getAwesompleteFromInput(input2);
if (awesomplete === null) {
return false;
}
const awesompleteSuggestions = awesomplete.querySelector("ul");
return awesompleteSuggestions !== null && !awesompleteSuggestions.hasAttribute("hidden");
}
function awesompleteIsUnselected(input2) {
const awesomplete = getAwesompleteFromInput(input2);
if (awesomplete === null) {
return true;
}
if (!awesompleteIsVisible(input2)) {
return true;
}
const searchSuggestions = Array.from(awesomplete.querySelectorAll("li"));
if (searchSuggestions.length === 0) {
return true;
}
const somethingIsSelected = searchSuggestions.map((li) => li.getAttribute("aria-selected")).some((element) => element === "true");
return !somethingIsSelected;
}
var DUMMY_ELEMENT = document.createElement("div");
var AUTOCOMPLETE_API_URL = "https://ac.rule34.xxx/autocomplete.php?q=";
function decodeEntities(encodedString) {
encodedString = encodedString.replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, "");
encodedString = encodedString.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, "");
encodedString = encodedString.replace(/\w+_saved_search\s*/, "");
DUMMY_ELEMENT.innerHTML = encodedString;
encodedString = DUMMY_ELEMENT.textContent ?? "";
DUMMY_ELEMENT.textContent = "";
return encodedString;
}
function getAutocompleteSuggestions(prefix) {
return getHTML(`${AUTOCOMPLETE_API_URL}${prefix}`);
}
function getFinalAutocompleteSuggestions(html, prefix) {
const suggestions = addCustomTagsToAutocomplete(JSON.parse(html), prefix);
return Preferences.savedSearchSuggestionsEnabled.value ? suggestions.concat(getSavedSearchesSuggestions(prefix)) : suggestions;
}
async function populateAwesompleteList(inputId, prefix, awesomplete) {
if (isEmptyString(prefix)) {
return;
}
prefix = removeLeadingHyphens(prefix);
const html = await getAutocompleteSuggestions(prefix);
awesomplete.list = getFinalAutocompleteSuggestions(html, prefix);
}
function getCurrentTag(input2) {
return getLastTag(input2.value.slice(0, input2.selectionStart || 0));
}
function getLastTag(searchQuery2) {
const lastTag = searchQuery2.match(/[^ -]\S*$/);
return lastTag === null ? "" : lastTag[0];
}
function getLastTagWithHyphen(searchQuery2) {
const lastTag = searchQuery2.match(/[^ ]*$/);
return lastTag === null ? "" : lastTag[0];
}
function getCurrentTagWithHyphen(input2) {
const selectionStart = input2.selectionStart ?? void 0;
return getLastTagWithHyphen(input2.value.slice(0, selectionStart));
}
function insertSuggestion(input2, suggestion) {
const result = getQueryWithTagReplaced(input2.value, input2.selectionStart ?? -1, suggestion);
input2.value = result.result;
input2.selectionStart = result.selectionStart;
input2.selectionEnd = result.selectionStart;
}
function createAwesompleteInstance(input2) {
const awesomplete = new Awesomplete_(input2, {
minChars: 1,
list: [],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filter: (suggestion, _) => {
return Awesomplete_.FILTER_STARTSWITH(suggestion.value, getCurrentTag(awesomplete.input).replaceAll("*", ""));
},
sort: false,
item: (suggestion, tags) => {
const html = isEmptyString(tags) ? suggestion.label : suggestion.label.replace(RegExp(Awesomplete_.$.regExpEscape(tags.trim()), "gi"), "<mark>$&</mark>");
return Awesomplete_.$.create("li", {
innerHTML: html,
"aria-selected": "false",
className: `tag-type-${suggestion.type}`
});
},
replace: (suggestion) => {
insertSuggestion(awesomplete.input, decodeEntities(suggestion.value));
Events.favorites.searchBoxUpdated.emit();
}
});
return awesomplete;
}
function addAwesompleteToInput(input2) {
addEventListenersToInput(input2, createAwesompleteInstance(input2));
}
function addEventListenersToInput(input2, awesomplete) {
input2.addEventListener("keydown", (event) => {
switch (event.key) {
case "Tab":
if (!awesomplete.isOpened || awesomplete.suggestions.length === 0) {
return;
}
awesomplete.next();
awesomplete.select();
event.preventDefault();
break;
case "Escape":
hideAwesomplete(input2);
break;
default:
break;
}
});
input2.oninput = () => {
populateAwesompleteList(input2.id, getCurrentTagWithHyphen(input2), awesomplete);
};
}
function addAwesompleteToAllInputs() {
for (const input2 of document.querySelectorAll("textarea, input[needs-autocomplete]")) {
addAwesompleteToInput(input2);
}
}
function setupAutocomplete() {
if (AWESOMPLETE_ENABLED) {
addAwesompleteToGlobalScope();
addAwesompleteToAllInputs();
}
}
var BatchExecutor = class {
limit;
timeout;
executor;
pollingInterval;
lastAddTime = 0;
poller = void 0;
batch = [];
constructor(limit, timeout, executor) {
this.limit = limit;
this.timeout = timeout;
this.executor = executor;
this.pollingInterval = this.getPollingInterval();
}
get overLimit() {
return this.batch.length >= this.limit;
}
get timeSinceLastAdd() {
return performance.now() - this.lastAddTime;
}
get overTimeout() {
return this.timeSinceLastAdd >= this.timeout;
}
add(item) {
this.batch.push(item);
this.lastAddTime = performance.now();
if (this.overLimit) {
this.execute();
return;
}
if (this.poller !== void 0) {
return;
}
this.poller = setInterval(() => {
if (this.overTimeout) {
this.execute();
}
}, this.pollingInterval);
}
reset() {
clearInterval(this.poller);
this.poller = void 0;
this.batch = [];
}
execute() {
this.executor(this.batch);
this.reset();
}
getPollingInterval() {
return Math.round(Math.max(10, this.timeout / 5));
}
};
var DO_NOTHING = () => {
};
var LockedDatabaseError = class extends Error {
};
var Database = class {
name;
defaultObjectStoreName;
version;
locked;
constructor(name, defaultObjectStoreName, version = 1) {
this.name = name;
this.defaultObjectStoreName = defaultObjectStoreName;
this.version = version;
this.locked = false;
}
async load(objectStoreName = void 0) {
const database = await this.open(objectStoreName ?? this.defaultObjectStoreName);
return this.getAllRecords(database, objectStoreName ?? this.defaultObjectStoreName);
}
async store(records, objectStoreName = void 0) {
if (this.locked) {
return Promise.reject(new LockedDatabaseError());
}
objectStoreName = objectStoreName ?? this.defaultObjectStoreName;
const database = await this.open(objectStoreName);
const transaction = database.transaction(objectStoreName, "readwrite");
const objectStore = transaction.objectStore(objectStoreName);
return new Promise((resolve, reject) => {
transaction.onerror = reject;
records.forEach((record) => this.putRecord(objectStore, record));
transaction.oncomplete = () => {
database.close();
resolve();
};
});
}
async update(records, objectStoreName = void 0) {
if (this.locked) {
return Promise.reject(new LockedDatabaseError());
}
objectStoreName = objectStoreName ?? this.defaultObjectStoreName;
const database = await this.open(objectStoreName);
const transaction = database.transaction(objectStoreName, "readwrite");
const objectStore = transaction.objectStore(objectStoreName);
const index = objectStore.index("id");
return new Promise((resolve, reject) => {
transaction.onerror = reject;
records.forEach((record) => {
this.updateRecord(index, record, objectStore);
transaction.oncomplete = () => {
database.close();
resolve();
};
});
});
}
async deleteRecords(ids, objectStoreName = void 0) {
objectStoreName = objectStoreName || this.defaultObjectStoreName;
const database = await this.open(objectStoreName);
const transaction = database.transaction(objectStoreName, "readwrite");
const objectStore = transaction.objectStore(objectStoreName);
const index = objectStore.index("id");
for (const id of ids) {
await this.deleteRecord(index, id, objectStore);
}
}
async delete() {
this.lock();
await sleep(0);
indexedDB.deleteDatabase(this.name);
}
updateRecord(index, record, objectStore) {
index.getKey(record.id).onsuccess = (indexEvent) => {
const target2 = indexEvent.target;
this.putRecord(objectStore, record, target2.result);
};
}
putRecord(objectStore, record, key = void 0) {
if (this.locked) {
throw new LockedDatabaseError();
}
objectStore.put(record, key);
}
deleteRecord(index, id, objectStore) {
return new Promise((resolve) => {
const request = index.getKey(id);
request.onsuccess = (event) => {
const target2 = event.target;
const primaryKey = target2.result;
if (primaryKey !== void 0) {
objectStore.delete(primaryKey);
}
resolve();
};
request.onerror = () => {
console.error(request.error);
resolve();
};
});
}
open(objectStoreName) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.name, this.version);
request.onsuccess = () => {
const database = request.result;
if (!database.objectStoreNames.contains(objectStoreName)) {
database.close();
this.version += 1;
this.open(objectStoreName).then(resolve, reject);
return;
}
resolve(database);
};
request.onupgradeneeded = () => this.createObjectStore(request.result, objectStoreName);
request.onerror = () => {
if (request.error instanceof DOMException && request.error.name === "VersionError") {
this.version += 1;
this.open(objectStoreName).then(resolve, reject);
return;
}
reject(request.error);
};
});
}
getAllRecords(database, objectStoreName) {
const transaction = database.transaction(objectStoreName, "readwrite");
const objectStore = transaction.objectStore(objectStoreName);
return new Promise((resolve, reject) => {
transaction.onerror = (event) => {
reject(event);
};
const getAllRequest = objectStore.getAll();
getAllRequest.onsuccess = () => {
database.close();
resolve(getAllRequest.result.reverse());
};
getAllRequest.onerror = (event) => {
database.close();
reject(event);
};
});
}
createObjectStore(database, objectStoreName) {
if (database.objectStoreNames.contains(objectStoreName)) {
return;
}
const objectStore = database.createObjectStore(objectStoreName, {
autoIncrement: true
});
objectStore.createIndex("id", "id", {
unique: true
});
}
lock() {
this.locked = true;
}
unlock() {
this.locked = false;
}
};
function createEmptyPost() {
return {
id: "",
height: 0,
score: 0,
fileURL: "",
parentId: "",
sampleURL: "",
sampleWidth: 0,
sampleHeight: 0,
previewURL: "",
rating: "",
tags: "",
width: 0,
change: 0,
md5: "",
creatorId: "",
hasChildren: false,
createdAt: "",
status: "",
source: "",
hasNotes: false,
hasComments: false,
previewWidth: 0,
previewHeight: 0
};
}
function createPostFromRawFavorite(object) {
if (object instanceof HTMLElement) {
return createPostFromFavoritesPageThumb(object);
}
return createPostFromDatabaseRecord(object);
}
function clearPost(post) {
post.id = "";
post.height = 0;
post.score = 0;
post.fileURL = "";
post.parentId = "";
post.sampleURL = "";
post.sampleWidth = 0;
post.sampleHeight = 0;
post.previewURL = "";
post.rating = "";
post.tags = "";
post.width = 0;
post.change = 0;
post.md5 = "";
post.creatorId = "";
post.hasChildren = false;
post.createdAt = "";
post.status = "";
post.source = "";
post.hasNotes = false;
post.hasComments = false;
post.previewWidth = 0;
post.previewHeight = 0;
}
function createPostFromDatabaseRecord(record) {
const post = createEmptyPost();
post.id = record.id;
post.height = record.metadata.height;
post.width = record.metadata.width;
post.previewURL = decompressPreviewSource(record.src);
return post;
}
function createPostFromFavoritesPageThumb(element) {
const post = createEmptyPost();
post.id = getIdFromThumb(element);
const image = getImageFromThumb(element);
if (image === null) {
return post;
}
const source = image.src || image.getAttribute("data-cfsrc") || "";
post.previewURL = source;
post.tags = preprocessTags(image, post.id);
return post;
}
function preprocessTags(image, id) {
if (image === null) {
return "";
}
const tags = image.title || image.getAttribute("tags") || "";
const tagsWithIdAdded = `${tags} ${id}`;
return removeExtraWhiteSpace(tagsWithIdAdded).split(" ").sort().join(" ");
}
var FavoritesSettings = {
columnCountBounds: {
min: 2,
max: 25
},
rowSizeBounds: {
min: 1,
max: 10
},
resultsPerPageBounds: {
min: 1,
max: 1e4
},
favoritesPageFetchDelay: 200,
resultsPerPageStep: 25,
maxPageNumberButtons: ON_MOBILE_DEVICE ? 5 : 5,
defaultMediaExtension: "jpg",
thumbnailSpacing: 5,
rightContentMargin: 15,
infiniteScrollBatchSize: 50,
infiniteScrollMargin: "75%",
randomSkeletonAnimationTiming: true,
preloadThumbnails: true,
useSearchIndex: true,
buildIndexAsynchronously: true,
skeletonAnimationClasses: "pulse",
infiniteScrollBatchCount: 25,
infiniteScrollPreloadCount: 100,
favoriteFinderEnabled: false
};
var FavoriteMetadataSearchExpression = class _FavoriteMetadataSearchExpression {
static regex = /^-?(score|width|height|id)(:[<>]?)(\d+|score|width|height|id)$/;
metric;
operator;
hasRelativeValue;
relativeMetric;
relativeValue;
constructor(searchTag) {
const extractedExpression = this.extractExpression(searchTag);
const value = extractedExpression.value;
this.metric = extractedExpression.metric;
this.operator = extractedExpression.operator;
this.hasRelativeValue = isSearchableMetadataMetric(value);
this.relativeValue = isSearchableMetadataMetric(value) ? 0 : value;
this.relativeMetric = isSearchableMetadataMetric(value) ? value : "id";
}
extractExpression(searchTag) {
const extractedExpression = _FavoriteMetadataSearchExpression.regex.exec(searchTag);
if (extractedExpression === null || extractedExpression.length !== 4) {
return {
metric: "width",
operator: ":",
value: 0
};
}
const metric = isSearchableMetadataMetric(extractedExpression[1]) ? extractedExpression[1] : "id";
const operator = isMetadataComparator(extractedExpression[2]) ? extractedExpression[2] : ":";
const value = isSearchableMetadataMetric(extractedExpression[3]) ? extractedExpression[3] : Number(extractedExpression[3]);
return {
metric,
operator,
value
};
}
};
var SearchTag = class {
value;
negated;
constructor(searchTag) {
this.negated = searchTag.startsWith("-") && searchTag.length > 1;
this.value = this.negated ? searchTag.substring(1) : searchTag;
this.matches = this.negated ? this.matchesNegated : this.matches;
}
get finalCost() {
return this.negated ? this.cost + 1 : this.cost;
}
get cost() {
return 0;
}
matches(item) {
return item.tags.has(this.value);
}
matchesNegated(item) {
return !item.tags.has(this.value);
}
};
var MetadataSearchTag = class extends SearchTag {
expression;
constructor(searchTag) {
super(searchTag);
this.expression = new FavoriteMetadataSearchExpression(this.value);
}
get cost() {
return 0;
}
matches(item) {
const { metric, operator, value } = this.getExpressionValues(item);
switch (operator) {
case ":":
return metric === value;
case ":<":
return metric < value;
case ":>":
return metric > value;
default:
return false;
}
}
getExpressionValues(item) {
const metricItem = item;
const metric = metricItem.metrics[this.expression.metric];
const operator = this.expression.operator;
const value = this.expression.hasRelativeValue ? metricItem.metrics[this.expression.relativeMetric] : this.expression.relativeValue;
return { metric, operator, value };
}
};
var UNMATCHABLE_REGEX = /^\b$/;
var STARTS_WITH_REGEX = /^[^*]*\*$/;
var CONTAINS_REGEX = /^\*[^*]*\*$/;
var WildcardSearchTag = class extends SearchTag {
getMatchingTags;
matchRegex;
matchType;
startsWithPrefix;
containsSubstring;
constructor(searchTag) {
super(searchTag);
this.value = this.removeDuplicateAsterisks(this.value);
this.matchRegex = this.createWildcardRegex();
this.startsWithPrefix = this.value.slice(0, -1);
this.containsSubstring = this.value.slice(1, -1);
this.matchType = this.getMatchType();
this.matches = this.getMatchFunction();
this.getMatchingTags = this.getMatchingTagsFunction();
}
get cost() {
return this.matchType.valueOf();
}
matchesSingleTag(tag) {
return this.matchRegex.test(tag);
}
getMatchingTagsFunction() {
return {
[10 /* STARTS_WITH */]: this.getMatchingTagsStartsWith,
[15 /* CONTAINS */]: this.getMatchingTagsContains,
[20 /* DEFAULT */]: this.getMatchingTagsRegex
}[this.matchType] ?? this.getMatchingTagsRegex;
}
getMatchType() {
if (STARTS_WITH_REGEX.test(this.value)) {
return 10 /* STARTS_WITH */;
}
if (CONTAINS_REGEX.test(this.value)) {
return 15 /* CONTAINS */;
}
return 20 /* DEFAULT */;
}
getMatchFunction() {
return {
[10 /* STARTS_WITH */]: this.matchesStartsWith,
[15 /* CONTAINS */]: this.matchesContains,
[20 /* DEFAULT */]: this.matchesRegex
}[this.matchType] ?? this.matchesRegex;
}
getMatchingTagsStartsWith(tags) {
const matchingTags = [];
for (const tag of tags) {
if (tag.startsWith(this.startsWithPrefix)) {
matchingTags.push(tag);
continue;
}
if (matchingTags.length > 0) {
break;
}
}
return matchingTags;
}
getMatchingTagsContains(tags) {
return tags.filter((tag) => tag.includes(this.containsSubstring));
}
getMatchingTagsRegex(tags) {
return tags.filter((tag) => this.matchRegex.test(tag));
}
matchesStartsWith(item) {
for (const tag of item.tags.values()) {
if (tag.startsWith(this.startsWithPrefix)) {
return !this.negated;
}
if (this.startsWithPrefix < tag) {
break;
}
}
return this.negated;
}
matchesContains(item) {
for (const tag of item.tags.values()) {
if (tag.includes(this.containsSubstring)) {
return !this.negated;
}
}
return this.negated;
}
matchesRegex(item) {
for (const tag of item.tags.values()) {
if (this.matchRegex.test(tag)) {
return !this.negated;
}
}
return this.negated;
}
removeDuplicateAsterisks(value) {
return value.replace(/\*+/g, "*");
}
createWildcardRegex() {
try {
const regex = escapeParenthesis(this.value.replace(/\*/g, ".*"));
return new RegExp(`^${regex}$`);
} catch {
return UNMATCHABLE_REGEX;
}
}
};
function isWildcardSearchTag(tag) {
return tag.includes("*");
}
function isMetadataSearchTag(tag) {
return FavoriteMetadataSearchExpression.regex.test(tag);
}
function createSearchTag(tag) {
if (isWildcardSearchTag(tag)) {
return new WildcardSearchTag(tag);
}
if (isMetadataSearchTag(tag)) {
return new MetadataSearchTag(tag);
}
return new SearchTag(tag);
}
function createSearchTagGroup(tags) {
const searchTags = Array.from(new Set(tags)).map((tag) => createSearchTag(tag));
return sortSearchTagGroup(searchTags);
}
function sortSearchTagGroup(searchTags) {
return searchTags.sort((a, b) => {
return a.finalCost - b.finalCost;
});
}
var SearchCommand = class {
orGroups = [];
remainingTags = [];
isEmpty;
details;
query;
constructor(searchQuery2) {
this.query = searchQuery2;
this.isEmpty = isEmptyString(searchQuery2);
this.details = this.getSearchCommandMetadata();
if (this.isEmpty) {
return;
}
const { orGroups, remainingTags } = extractTagGroups(searchQuery2);
this.orGroups = orGroups.map((orGroup) => createSearchTagGroup(orGroup));
this.remainingTags = createSearchTagGroup(remainingTags);
this.simplifyOrGroupsWithOnlyOneTag();
this.sortOrGroupsByLength();
this.details = this.getSearchCommandMetadata();
}
get negatedTags() {
return new Set(this.remainingTags.filter((tag) => tag.negated).map((tag) => tag.value));
}
get nonNegatedTags() {
return this.remainingTags.filter((tag) => !tag.negated).map((tag) => tag.value);
}
get tagGroups() {
return {
orGroups: this.orGroups,
remainingTags: this.remainingTags
};
}
getSearchResults(items) {
return this.isEmpty ? items : items.filter((item) => this.matches(item));
}
getSearchCommandMetadata() {
const normalTags = [];
const wildcardTags = [];
const metadataTags = [];
for (const tag of this.remainingTags) {
if (tag instanceof WildcardSearchTag) {
wildcardTags.push(tag);
} else if (tag instanceof MetadataSearchTag) {
metadataTags.push(tag);
} else if (!tag.negated) {
normalTags.push(tag);
}
}
for (const orGroup of this.orGroups) {
for (const tag of orGroup) {
if (tag instanceof WildcardSearchTag) {
wildcardTags.push(tag);
} else if (tag instanceof MetadataSearchTag) {
metadataTags.push(tag);
}
}
}
return {
normalTags,
wildcardTags,
metadataTags,
hasNormalTag: normalTags.length > 0,
hasWildcardTag: wildcardTags.length > 0,
hasMetadataTag: metadataTags.length > 0,
hasOrGroup: this.orGroups.length > 0
};
}
matches(item) {
return this.matchesAllRemainingTags(item) && this.matchesAllOrGroups(item);
}
matchesAllRemainingTags(item) {
return this.remainingTags.every((tag) => tag.matches(item));
}
matchesAllOrGroups(item) {
return this.orGroups.every((orGroup) => orGroup.some((tag) => tag.matches(item)));
}
simplifyOrGroupsWithOnlyOneTag() {
this.orGroups = this.orGroups.filter((orGroup) => {
if (orGroup.length === 1) {
this.remainingTags.push(orGroup[0]);
return false;
}
return true;
});
sortSearchTagGroup(this.remainingTags);
}
sortOrGroupsByLength() {
this.orGroups.sort((a, b) => {
return a.length - b.length;
});
}
};
var ExpandedSearchCommand = class extends SearchCommand {
hasNoMatches;
indexedTags;
constructor(searchQuery2, indexedTags) {
super(searchQuery2);
this.indexedTags = indexedTags;
this.hasNoMatches = false;
this.expandRemainingWildcardTags();
this.expandAllOrGroupWildcardTags();
}
expandRemainingWildcardTags() {
const newRemainingTags = [];
for (const tag of this.remainingTags) {
if (!(tag instanceof WildcardSearchTag)) {
newRemainingTags.push(tag);
continue;
}
const expandedWildcardTags = this.expandWildcardTag(tag);
if (tag.negated) {
for (const expandedNegatedTag of expandedWildcardTags) {
this.remainingTags.push(expandedNegatedTag);
}
continue;
}
if (expandedWildcardTags.length > 1) {
this.orGroups.push(expandedWildcardTags);
continue;
}
if (expandedWildcardTags.length === 1) {
newRemainingTags.push(expandedWildcardTags[0]);
continue;
}
this.setAsUnmatchable();
return;
}
this.remainingTags = newRemainingTags;
}
expandAllOrGroupWildcardTags() {
const newOrGroups = [];
for (const orGroup of this.orGroups) {
const newOrGroup = [];
for (const tag of orGroup) {
if (!(tag instanceof WildcardSearchTag)) {
newOrGroup.push(tag);
continue;
}
for (const expandedTag of this.expandWildcardTag(tag)) {
newOrGroup.push(expandedTag);
}
}
if (newOrGroup.length === 1) {
this.remainingTags.push(newOrGroup[0]);
continue;
}
if (newOrGroup.length === 0) {
this.setAsUnmatchable();
return;
}
newOrGroups.push(newOrGroup);
}
this.orGroups = newOrGroups;
}
setAsUnmatchable() {
this.hasNoMatches = true;
this.remainingTags = [];
this.orGroups = [];
}
expandWildcardTag(wildcardTag) {
const result = [];
for (const matchingIndexedTag of wildcardTag.getMatchingTags(this.indexedTags)) {
const indexedTagWithNegation = wildcardTag.negated ? `-${matchingIndexedTag}` : matchingIndexedTag;
result.push(new SearchTag(indexedTagWithNegation));
}
return result;
}
};
var SortedArray = class {
array = [];
isSorted = true;
get length() {
return this.array.length;
}
toArray() {
if (this.isSorted) {
return this.array;
}
this.isSorted = true;
return this.sort();
}
insert(value) {
this.array.splice(this.getSortedIndex(value), 0, value);
}
push(value) {
this.array.push(value);
this.isSorted = false;
}
sort() {
return this.array.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
}
getSortedIndex(value) {
let low = 0;
let high = this.array.length;
while (low < high) {
const mid = low + high >>> 1;
if (this.array[mid] < value) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
};
function intersection(A, B) {
const result = /* @__PURE__ */ new Set();
if (A.size === 0 || B.size === 0) {
return result;
}
if (A.size < B.size) {
for (const a of A) {
if (B.has(a)) {
result.add(a);
}
}
return result;
}
for (const b of B) {
if (A.has(b)) {
result.add(b);
}
}
return result;
}
var InvertedSearchIndex = class {
allSortedTags = new SortedArray();
allItems = /* @__PURE__ */ new Set();
tagItemMap = /* @__PURE__ */ new Map();
sortTagsOnAdd = false;
get allTags() {
return this.allSortedTags.toArray();
}
add(item) {
this.allItems.add(item);
for (const tag of item.tags) {
let indexedItems = this.tagItemMap.get(tag);
if (indexedItems === void 0) {
indexedItems = /* @__PURE__ */ new Set();
this.tagItemMap.set(tag, indexedItems);
this.addTag(tag);
}
indexedItems.add(item);
}
}
remove(item) {
for (const tag of item.tags) {
const indexedItems = this.tagItemMap.get(tag);
if (indexedItems !== void 0) {
indexedItems.delete(item);
}
}
}
getSearchResults(searchCommand2, items) {
return this.getSearchResultsUsingIndex(searchCommand2.query, items);
}
keepIndexedTagsSorted(value) {
this.sortTagsOnAdd = value;
}
sortTags() {
this.allSortedTags.toArray();
}
getSearchResultsUsingIndex(searchQuery2, itemsToSearch) {
const expandedCommand = new ExpandedSearchCommand(searchQuery2, this.allSortedTags.toArray());
if (expandedCommand.isEmpty) {
return itemsToSearch;
}
if (expandedCommand.hasNoMatches) {
return [];
}
const negatedItems = this.getNegatedItems(expandedCommand);
let resultItems = this.filterByNonNegatedTags(this.allItems, expandedCommand);
if (resultItems.size === 0) {
return [];
}
resultItems = this.filterByOrGroups(resultItems, expandedCommand);
if (resultItems.size === 0) {
return [];
}
return itemsToSearch.filter((item) => resultItems.has(item) && !negatedItems.has(item));
}
addTag(tag) {
if (this.sortTagsOnAdd) {
this.allSortedTags.insert(tag);
} else {
this.allSortedTags.push(tag);
}
}
getNegatedItems(command) {
const negatedItems = /* @__PURE__ */ new Set();
for (const negatedTag of command.negatedTags) {
const itemsWithNegatedTag = this.tagItemMap.get(negatedTag);
if (itemsWithNegatedTag === void 0) {
continue;
}
for (const item of itemsWithNegatedTag) {
negatedItems.add(item);
}
}
return negatedItems;
}
filterByNonNegatedTags(currentResult, command) {
let result = currentResult;
const itemSets = command.nonNegatedTags.map((tag) => this.tagItemMap.get(tag));
if (itemSets.some((set3) => set3 === void 0)) {
return /* @__PURE__ */ new Set();
}
for (const itemSet of itemSets.sort((a, b) => a.size - b.size)) {
result = intersection(itemSet, result);
if (result.size === 0) {
return /* @__PURE__ */ new Set();
}
}
return result;
}
filterByOrGroups(currentResult, command) {
let result = currentResult;
for (const orGroup of command.orGroups) {
result = intersection(this.getAllItemsInOrGroup(orGroup), result);
if (result.size === 0) {
return /* @__PURE__ */ new Set();
}
}
return result;
}
getAllItemsInOrGroup(orGroup) {
const allItemsInOrGroup = /* @__PURE__ */ new Set();
for (const tag of orGroup) {
const itemsWithTag = this.tagItemMap.get(tag.value);
if (itemsWithTag === void 0) {
continue;
}
for (const item of itemsWithTag) {
allItemsInOrGroup.add(item);
}
}
return allItemsInOrGroup;
}
};
var ThrottledQueue = class {
queue;
delay;
draining;
paused;
constructor(delay) {
this.queue = [];
this.delay = delay;
this.draining = false;
this.paused = false;
}
wait() {
return new Promise((resolve) => {
this.queue.push(resolve);
this.startDraining();
});
}
setDelay(newDelay) {
this.delay = newDelay;
}
pause() {
this.paused = true;
}
resume() {
this.paused = false;
this.startDraining();
}
reset() {
this.queue = [];
this.draining = false;
this.paused = false;
}
async startDraining() {
if (this.draining) {
return;
}
this.draining = true;
await this.drain();
this.draining = false;
}
async drain() {
while (this.queue.length > 0) {
const resolve = this.queue.shift();
if (resolve === void 0) {
continue;
}
resolve();
await sleep(this.delay);
if (this.paused) {
break;
}
}
}
};
var internalSeed = 100;
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function getRandomPositiveInteger(maximum) {
return Math.floor(Math.random() * maximum);
}
function seededRandom(seed) {
const x = Math.sin(seed) * 4051.2948;
return x - Math.floor(x);
}
function getSeededRandomPositiveInteger(maximum) {
internalSeed += 1;
return Math.floor(seededRandom(internalSeed) * maximum);
}
function getSeededRandomPositiveIntegerInRange(min, max) {
return getSeededRandomPositiveInteger(max - min) + min;
}
function mapRange(value, fromMin, fromMax, toMin, toMax) {
return Math.round(toMin + (value - fromMin) / (fromMax - fromMin) * (toMax - toMin));
}
function roundToTwoDecimalPlaces(value) {
return Math.round((value + Number.EPSILON) * 100) / 100;
}
function millisecondsToSeconds(milliseconds) {
return roundToTwoDecimalPlaces(milliseconds / 1e3);
}
function randomBetween(min, max) {
return min + Math.random() * (max - min);
}
function indexInBounds(array, index) {
return index >= 0 && index < array.length;
}
function shuffleArray(array) {
let maxIndex = array.length;
let randomIndex;
while (maxIndex > 0) {
randomIndex = getRandomPositiveInteger(maxIndex);
maxIndex -= 1;
[
array[maxIndex],
array[randomIndex]
] = [
array[randomIndex],
array[maxIndex]
];
}
return array;
}
function getNumbersAround(number, count, min, max) {
if (count <= 0) {
return [];
}
if (min > max) {
return [];
}
const numbers = [number];
let i = 1;
while (numbers.length < count) {
const left = number - i;
const right = number + i;
const leftInBounds = left >= min && left <= max;
const rightInBounds = right >= min && right <= max;
const bothOutOfBounds = !leftInBounds && !rightInBounds;
if (bothOutOfBounds) {
break;
}
if (leftInBounds) {
numbers.push(left);
}
if (rightInBounds && numbers.length < count) {
numbers.push(right);
}
i += 1;
}
return numbers.sort((a, b) => a - b);
}
function getElementsAroundIndex(array, startIndex, limit) {
if (!indexInBounds(array, startIndex) || limit === 0) {
return [];
}
const result = [array[startIndex]];
let i = 1;
while (result.length < limit) {
const leftIndex = startIndex - i;
const rightIndex = startIndex + i;
const leftIndexInBounds = indexInBounds(array, leftIndex);
const rightIndexInBounds = indexInBounds(array, rightIndex);
const bothIndexesOutOfBounds = !leftIndexInBounds && !rightIndexInBounds;
if (bothIndexesOutOfBounds) {
break;
}
if (leftIndexInBounds) {
result.push(array[leftIndex]);
}
if (rightIndexInBounds && result.length < limit) {
result.push(array[rightIndex]);
}
i += 1;
}
return result;
}
function getWrappedElementsAroundIndex(array, startIndex, limit) {
if (!indexInBounds(array, startIndex) || limit === 0) {
return [];
}
const result = [array[startIndex]];
let i = 1;
while (result.length < limit && result.length < array.length) {
const leftIndex = (startIndex - i + array.length) % array.length;
const rightIndex = (startIndex + i) % array.length;
result.push(array[leftIndex]);
if (result.length < limit && result.length < array.length) {
result.push(array[rightIndex]);
}
i += 1;
}
return result;
}
function splitIntoChunks(array, chunkSize) {
const result = [];
if (chunkSize <= 0) {
return [array];
}
for (let i = 0; i < array.length; i += chunkSize) {
result.push(array.slice(i, i + chunkSize));
}
return result;
}
var BATCH_SIZE = 750;
var BATCH_SLEEP_TIME = 0;
var FavoritesSearchIndex = class extends InvertedSearchIndex {
ready = false;
asyncBuildStarted = false;
batchExecutor = new BatchExecutor(BATCH_SIZE, 100, this.addBatch.bind(this));
addQueue = new ThrottledQueue(BATCH_SLEEP_TIME);
asyncItemsToAdd = /* @__PURE__ */ new Set();
cachedAsyncItemsToAdd = [];
constructor() {
super();
if (!FavoritesSettings.useSearchIndex) {
this.add = DO_NOTHING;
return;
}
if (FavoritesSettings.buildIndexAsynchronously) {
this.add = this.cacheAdditionsWhileFavoritesAreLoading;
return;
}
this.ready = true;
}
async buildIndexAsynchronously() {
if (!FavoritesSettings.useSearchIndex || this.asyncBuildStarted) {
return;
}
this.asyncBuildStarted = true;
await sleep(50);
this.keepIndexedTagsSorted(false);
this.add = this.addAsynchronously;
this.emptyAdditionsCache();
}
buildIndexSynchronously() {
this.add = super.add;
this.ready = true;
}
emptyAdditionsCache() {
const chunks = splitIntoChunks(this.cachedAsyncItemsToAdd, BATCH_SIZE);
for (const chunk of chunks) {
for (const item of chunk) {
this.asyncItemsToAdd.add(item);
}
}
for (const chunk of chunks) {
this.addBatch(chunk);
}
}
cacheAdditionsWhileFavoritesAreLoading(item) {
this.cachedAsyncItemsToAdd.push(item);
}
addAsynchronously(item) {
this.ready = false;
this.asyncItemsToAdd.add(item);
this.batchExecutor.add(item);
}
async addBatch(batch) {
await this.addQueue.wait();
for (const item of batch) {
super.add(item);
this.asyncItemsToAdd.delete(item);
}
this.ready = this.asyncItemsToAdd.size === 0;
if (this.ready) {
this.keepIndexedTagsSorted(true);
this.sortTags();
}
}
};
var FAVORITES_SEARCH_INDEX = new FavoritesSearchIndex();
var DELETE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>';
var EDIT = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
var UP_ARROW = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-up"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>';
var HEART_PLUS = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FF69B4"><path d="M440-501Zm0 381L313-234q-72-65-123.5-116t-85-96q-33.5-45-49-87T40-621q0-94 63-156.5T260-840q52 0 99 22t81 62q34-40 81-62t99-22q81 0 136 45.5T831-680h-85q-18-40-53-60t-73-20q-51 0-88 27.5T463-660h-46q-31-45-70.5-72.5T260-760q-57 0-98.5 39.5T120-621q0 33 14 67t50 78.5q36 44.5 98 104T440-228q26-23 61-53t56-50l9 9 19.5 19.5L605-283l9 9q-22 20-56 49.5T498-172l-58 52Zm280-160v-120H600v-80h120v-120h80v120h120v80H800v120h-80Z"/></svg>';
var HEART_MINUS = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FF0000"><path d="M440-501Zm0 381L313-234q-72-65-123.5-116t-85-96q-33.5-45-49-87T40-621q0-94 63-156.5T260-840q52 0 99 22t81 62q34-40 81-62t99-22q84 0 153 59t69 160q0 14-2 29.5t-6 31.5h-85q5-18 8-34t3-30q0-75-50-105.5T620-760q-51 0-88 27.5T463-660h-46q-31-45-70.5-72.5T260-760q-57 0-98.5 39.5T120-621q0 33 14 67t50 78.5q36 44.5 98 104T440-228q26-23 61-53t56-50l9 9 19.5 19.5L605-283l9 9q-22 20-56 49.5T498-172l-58 52Zm160-280v-80h320v80H600Z"/></svg>';
var HEART_CHECK = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#51b330"><path d="M718-313 604-426l57-56 57 56 141-141 57 56-198 198ZM440-501Zm0 381L313-234q-72-65-123.5-116t-85-96q-33.5-45-49-87T40-621q0-94 63-156.5T260-840q52 0 99 22t81 62q34-40 81-62t99-22q81 0 136 45.5T831-680h-85q-18-40-53-60t-73-20q-51 0-88 27.5T463-660h-46q-31-45-70.5-72.5T260-760q-57 0-98.5 39.5T120-621q0 33 14 67t50 78.5q36 44.5 98 104T440-228q26-23 61-53t56-50l9 9 19.5 19.5L605-283l9 9q-22 20-56 49.5T498-172l-58 52Z"/></svg>';
var ERROR = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FF0000"><path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240Zm40 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>';
var WARNING = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#DAB600"><path d="m40-120 440-760 440 760H40Zm138-80h604L480-720 178-200Zm302-40q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Zm40-100Z"/></svg>';
var BULB = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="M480-80q-26 0-47-12.5T400-126q-33 0-56.5-23.5T320-206v-142q-59-39-94.5-103T190-590q0-121 84.5-205.5T480-880q121 0 205.5 84.5T770-590q0 77-35.5 140T640-348v142q0 33-23.5 56.5T560-126q-12 21-33 33.5T480-80Zm-80-126h160v-36H400v36Zm0-76h160v-38H400v38Zm-8-118h58v-108l-88-88 42-42 76 76 76-76 42 42-88 88v108h58q54-26 88-76.5T690-590q0-88-61-149t-149-61q-88 0-149 61t-61 149q0 63 34 113.5t88 76.5Zm88-162Zm0-38Z"/></svg>';
var PLAY = '<svg id="autoplay-play-button" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="white"><path d="M320-200v-560l440 280-440 280Zm80-280Zm0 134 210-134-210-134v268Z" /></svg>';
var PAUSE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="white"><path d="M520-200v-560h240v560H520Zm-320 0v-560h240v560H200Zm400-80h80v-400h-80v400Zm-320 0h80v-400h-80v400Zm0-400v400-400Zm320 0v400-400Z"/></svg>';
var CHANGE_DIRECTION = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="white"><path d="M280-160 80-360l200-200 56 57-103 103h287v80H233l103 103-56 57Zm400-240-56-57 103-103H440v-80h287L624-743l56-57 200 200-200 200Z"/></svg>';
var CHANGE_DIRECTION_2 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#0075FF"><path d="M280-160 80-360l200-200 56 57-103 103h287v80H233l103 103-56 57Zm400-240-56-57 103-103H440v-80h287L624-743l56-57 200 200-200 200Z"/></svg>';
var TUNE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="white"><path d="M440-120v-240h80v80h320v80H520v80h-80Zm-320-80v-80h240v80H120Zm160-160v-80H120v-80h160v-80h80v240h-80Zm160-80v-80h400v80H440Zm160-160v-240h80v80h160v80H680v80h-80Zm-480-80v-80h400v80H120Z"/></svg>';
var EXIT = '<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" viewBox="0 -960 960 960"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>';
var FULLSCREEN_ENTER = '<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" viewBox="0 -960 960 960"><path d="M120-120v-200h80v120h120v80H120Zm520 0v-80h120v-120h80v200H640ZM120-640v-200h200v80H200v120h-80Zm640 0v-120H640v-80h200v200h-80Z"/></svg>';
var OPEN_IN_NEW = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"/></svg>';
var DOWNLOAD = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>';
var IMAGE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z"/></svg>';
var PIN = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Zm-286 80h252l-46-46v-314H400v314l-46 46Zm126 0Z"/></svg>';
var DOCK = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm440-80h120v-560H640v560Zm-80 0v-560H200v560h360Zm80 0h120-120Z"/></svg>';
var SEARCH = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>';
var PALETTE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#FFFFFF"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 32.5-156t88-127Q256-817 330-848.5T488-880q80 0 151 27.5t124.5 76q53.5 48.5 85 115T880-518q0 115-70 176.5T640-280h-74q-9 0-12.5 5t-3.5 11q0 12 15 34.5t15 51.5q0 50-27.5 74T480-80Zm0-400Zm-220 40q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120-160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm200 0q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120 160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17ZM480-160q9 0 14.5-5t5.5-13q0-14-15-33t-15-57q0-42 29-67t71-25h70q66 0 113-38.5T800-518q0-121-92.5-201.5T488-800q-136 0-232 93t-96 227q0 133 93.5 226.5T480-160Z"/></svg>';
function getTagAttributeFromImage(image) {
return image.hasAttribute("tags") ? "tags" : "title";
}
function getTagsFromThumbOnSearchPage(thumb) {
if (!(thumb instanceof HTMLElement)) {
return "";
}
const image = getImageFromThumb(thumb);
if (image === null) {
return "";
}
const tagAttribute = getTagAttributeFromImage(image);
return image.getAttribute(tagAttribute) || "";
}
function getTagSetFromThumbOnSearchPage(thumb) {
return convertToTagSet(getTagsFromThumbOnSearchPage(thumb));
}
function getTagSetFromThumbOnFavoritesPage(thumb) {
const favorite = getFavorite(thumb.id);
return favorite === void 0 ? /* @__PURE__ */ new Set() : new Set(favorite.tags);
}
var getTagSetFromThumb = ON_FAVORITES_PAGE ? getTagSetFromThumbOnFavoritesPage : getTagSetFromThumbOnSearchPage;
function getContentTypeFromThumb(thumb) {
return getContentType(getTagSetFromThumb(thumb));
}
function moveTagsFromTitleToTagsAttribute(thumb) {
const image = getImageFromThumb(thumb);
if (image === null || !image.hasAttribute("title")) {
return;
}
image.setAttribute("tags", image.title);
image.removeAttribute("title");
}
function isFavoriteContentType(favorite, contentType) {
return getContentType(favorite.tags) === contentType;
}
function isThumbContentType(thumb, contentType) {
const image = getImageFromThumb(thumb);
return image !== null && getContentType(getTagSetFromThumb(thumb)) === contentType;
}
function isContentType(object, contentType) {
if (object instanceof HTMLElement) {
return isThumbContentType(object, contentType);
}
return isFavoriteContentType(object, contentType);
}
var isVideo = (object) => isContentType(object, "video");
var isGif = (object) => isContentType(object, "gif");
var isImage = (object) => isContentType(object, "image");
var DATABASE_NAME = "ImageExtensions";
var OBJECT_STORE_NAME = "extensionMappings";
var EXTENSION_MAP = /* @__PURE__ */ new Map();
var DATABASE = new Database(DATABASE_NAME, OBJECT_STORE_NAME);
var DATABASE_WRITE_SCHEDULER = new BatchExecutor(100, 2e3, DATABASE.update.bind(DATABASE));
var EXTENSIONS = ["jpg", "png", "jpeg"];
var BRUTE_FORCE_EXTENSION_QUEUE = new ThrottledQueue(40);
async function loadExtensions() {
for (const mapping of await DATABASE.load()) {
EXTENSION_MAP.set(mapping.id, mapping.extension);
}
}
function transferExtensionsFromLocalStorageToIndexedDB() {
const extensionMappingsString = localStorage.getItem("imageExtensions");
if (extensionMappingsString === null) {
return;
}
const extensionMappings = JSON.parse(extensionMappingsString);
const extensionDecodings = {
0: "jpg",
1: "png",
2: "jpeg",
3: "gif",
4: "mp4"
};
for (const [id, extensionEncoding] of Object.entries(extensionMappings)) {
const extension = extensionDecodings[extensionEncoding];
if (extension !== void 0) {
set2(id, extensionDecodings[extensionEncoding]);
}
}
localStorage.removeItem("imageExtensions");
}
function getExtensionFromPost(post) {
return getExtensionFromURL(post.fileURL);
}
function getExtensionFromURL(url) {
const match = EXTENSION_REGEX.exec(url);
return match === null ? null : match[1];
}
function has(id) {
return EXTENSION_MAP.has(id);
}
function get2(id) {
return EXTENSION_MAP.get(id);
}
function set2(id, extension) {
if (has(id) || extension === "mp4" || extension === "gif") {
return;
}
EXTENSION_MAP.set(id, extension);
if (ON_FAVORITES_PAGE) {
DATABASE_WRITE_SCHEDULER.add({ id, extension });
}
}
function getExtension(item) {
if (isVideo(item)) {
return Promise.resolve("mp4");
}
if (isGif(item)) {
return Promise.resolve("gif");
}
return withTimeout(getExtensionFromId(item.id), 3e3).catch((error) => {
if (error instanceof PromiseTimeoutError) {
return tryAllPossibleExtensions(item);
}
throw error;
});
}
async function tryAllPossibleExtensions(item) {
const baseURL = getOriginalImageURLWithJPGExtension(item);
for (const extension of EXTENSIONS) {
const testURL = baseURL.replace(".jpg", `.${extension}`);
await BRUTE_FORCE_EXTENSION_QUEUE.wait();
const response = await fetch(testURL);
if (response.ok) {
set2(item.id, extension);
return extension;
}
}
return "jpg";
}
async function getExtensionFromId(id) {
if (has(id)) {
return get2(id);
}
const post = await fetchPostFromAPISafe(id);
const extension = getExtensionFromPost(post);
if (extension !== null) {
set2(id, extension);
return extension;
}
return "jpg";
}
function setExtensionFromPost(post) {
const extension = getExtensionFromPost(post);
if (extension !== null) {
set2(post.id, extension);
}
}
function deleteExtensionsDatabase() {
DATABASE.delete();
}
function setupExtensions() {
transferExtensionsFromLocalStorageToIndexedDB();
loadExtensions();
}
async function fetchImageBitmap(url, abortController2) {
const response = await fetch(url, { signal: abortController2?.signal });
const blob = await response.blob();
return createImageBitmap(blob);
}
async function fetchImageBitmapFromThumb(thumb, abortController2) {
return fetchImageBitmap(await getOriginalImageURL(thumb), abortController2);
}
async function getOriginalImageURL(item) {
return (await getOriginalContentURL(item)).replace(".mp4", ".jpg");
}
async function getOriginalContentURL(item) {
return getOriginalImageURLWithJPGExtension(item).replace(".jpg", `.${await getExtension(item)}`);
}
function getOriginalImageURLWithJPGExtension(item) {
return convertPreviewURLToImageURL(getPreviewURL(item) ?? "");
}
function openPostPage(id) {
window.open(createPostPageURL(id), "_blank");
}
function openSearchPage(searchQuery2) {
window.open(createSearchPageURL(searchQuery2));
}
async function openOriginal(thumb) {
window.open(await getOriginalContentURL(thumb), "_blank");
}
function createObjectURLFromSvg(svg) {
const blob = new Blob([svg], {
type: "image/svg+xml"
});
return URL.createObjectURL(blob);
}
function downloadBlob(blob, filename) {
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
}
async function download(url, filename) {
const response = await fetch(url);
const blob = await response.blob();
downloadBlob(blob, filename);
}
async function downloadFromThumb(thumb) {
const originalContentURL = await getOriginalContentURL(thumb);
const extension = getExtensionFromURL(originalContentURL) ?? "jpg";
const filename = `${thumb.id}.${extension}`;
download(originalContentURL, filename);
}
var htmlTemplate;
var removeFavoriteButtonHTML;
var addFavoriteButtonHTML;
function createFavoriteItemHTMLTemplates() {
removeFavoriteButtonHTML = createRemoveFavoriteButtonHTMLTemplate();
addFavoriteButtonHTML = createAddFavoriteButtonHTMLTemplate();
createPostHTMLTemplate();
}
function createRemoveFavoriteButtonHTMLTemplate() {
return `<img class="remove-favorite-button utility-button" src=${createObjectURLFromSvg(HEART_MINUS)}>`;
}
function createAddFavoriteButtonHTMLTemplate() {
return `<img class="add-favorite-button utility-button" src=${createObjectURLFromSvg(HEART_PLUS)}>`;
}
function createDownloadButtonHTMLTemplate() {
const downloadHTML = `<img class="download-button utility-button" src=${createObjectURLFromSvg(DOWNLOAD.replace("FFFFFF", "0075FF"))}>`;
return downloadHTML;
}
function createPostHTMLTemplate() {
htmlTemplate = new DOMParser().parseFromString("", "text/html").createElement("div");
htmlTemplate.className = ITEM_CLASS_NAME;
htmlTemplate.innerHTML = `
<a>
<img>
${USER_IS_ON_THEIR_OWN_FAVORITES_PAGE ? removeFavoriteButtonHTML : addFavoriteButtonHTML}
${createDownloadButtonHTMLTemplate()}
${GALLERY_DISABLED ? "" : "<canvas></canvas>"}
</a>
`;
}
var FavoriteHTMLElement = class {
root;
container;
image;
favoriteButton;
downloadButton;
constructor(post) {
this.root = htmlTemplate.cloneNode(true);
this.container = this.root.children[0];
this.image = this.root.children[0].children[0];
this.favoriteButton = this.root.children[0].children[1];
this.downloadButton = this.root.children[0].children[2];
this.downloadButton.onclick = this.download.bind(this);
this.populateAttributes(post);
this.setupFavoriteButton(USER_IS_ON_THEIR_OWN_FAVORITES_PAGE);
this.openPostInNewTabOnClick();
this.presetCanvasDimensions(post);
}
get thumbURL() {
return this.image.src;
}
swapFavoriteButton() {
const isRemoveButton = this.favoriteButton.classList.contains("remove-favorite-button");
this.favoriteButton.outerHTML = isRemoveButton ? addFavoriteButtonHTML : removeFavoriteButtonHTML;
this.favoriteButton = this.root.children[0].children[1];
this.setupFavoriteButton(!isRemoveButton);
}
populateAttributes(post) {
this.image.src = post.previewURL;
this.image.classList.add(getContentType(post.tags));
this.root.id = post.id;
}
setupFavoriteButton(isRemoveButton) {
this.favoriteButton.onmousedown = (event) => {
event.stopPropagation();
if (event.button !== 0 /* LEFT */) {
return;
}
if (isRemoveButton) {
this.removeFavorite();
} else {
this.addFavorite();
}
};
}
removeFavorite() {
Events.favorites.favoriteRemoved.emit(this.root.id);
removeFavorite(this.root.id);
this.swapFavoriteButton();
}
addFavorite() {
addFavorite(this.root.id);
this.swapFavoriteButton();
}
openPostInNewTabOnClick() {
if (ON_DESKTOP_DEVICE) {
this.openPostInNewTabOnClickDesktop();
} else {
this.openPostInNewTabOnClickMobile();
}
}
openPostInNewTabOnClickDesktop() {
this.container.onclick = (event) => {
if (event.ctrlKey) {
openOriginal(this.root);
}
};
this.container.addEventListener("mousedown", (event) => {
if (event.ctrlKey) {
return;
}
const middleClick = event.button === 1 /* MIDDLE */;
const leftClick = event.button === 0 /* LEFT */;
const shiftClick = leftClick && event.shiftKey;
if (middleClick || shiftClick || leftClick && GALLERY_DISABLED) {
event.preventDefault();
openPostPage(this.root.id);
}
});
}
openPostInNewTabOnClickMobile() {
this.container.href = createPostPageURL(this.root.id);
}
presetCanvasDimensions(post) {
const canvas = this.root.querySelector("canvas");
if (canvas === null || post.height === 0 || post.width === 0) {
return;
}
canvas.dataset.size = `${post.width}x${post.height}`;
}
download() {
downloadFromThumb(this.root);
}
};
var GeneralSettings = {
apiTimeout: 3500,
throttledMetadataAPIRequestDelay: 5,
throttledExtensionAPIRequestDelay: 2,
bruteForceImageExtensionRequestDelay: 75,
imageRequestDelay: 35,
postPageRequestDelayWhileFetchingFavorites: 3e4,
postPageRequestDelayAfterFavoritesLoaded: 400,
searchPagePostPageRequestDelay: 400,
preloadThumbnails: true,
galleryMenuOptionEnabled: true
};
var PENDING_REQUESTS = /* @__PURE__ */ new Set();
var UPDATE_QUEUE = [];
var MAX_PENDING_REQUESTS = 250;
var THROTTLED_FETCH_QUEUE = new ThrottledQueue(GeneralSettings.throttledMetadataAPIRequestDelay);
THROTTLED_FETCH_QUEUE.pause();
function decodeRating(rating) {
return {
"Explicit": 4 /* EXPLICIT */,
"E": 4 /* EXPLICIT */,
"e": 4 /* EXPLICIT */,
"Questionable": 2 /* QUESTIONABLE */,
"Q": 2 /* QUESTIONABLE */,
"q": 2 /* QUESTIONABLE */,
"Safe": 1 /* SAFE */,
"S": 1 /* SAFE */,
"s": 1 /* SAFE */
}[rating] ?? 4 /* EXPLICIT */;
}
function tooManyPendingRequests() {
return PENDING_REQUESTS.size > MAX_PENDING_REQUESTS;
}
async function fetchMissingMetadata() {
THROTTLED_FETCH_QUEUE.resume();
for (const metadata of UPDATE_QUEUE) {
await THROTTLED_FETCH_QUEUE.wait();
metadata.populateFromAPI();
}
}
var FavoriteMetadata = class {
metrics;
id;
rating;
isDeleted;
constructor(id, record) {
this.metrics = {
id: parseInt(id),
width: 0,
height: 0,
score: 0,
creationTimestamp: 0,
lastChangedTimestamp: 0,
default: 0,
random: 0
};
this.id = id;
this.rating = 4 /* EXPLICIT */;
this.isDeleted = false;
this.populate(record);
}
get isEmpty() {
return this.metrics.width === 0 && this.metrics.height === 0;
}
get json() {
return JSON.stringify(this.databaseRecord);
}
get databaseRecord() {
return {
width: this.metrics.width,
height: this.metrics.height,
score: this.metrics.score,
rating: this.rating,
create: this.metrics.creationTimestamp,
change: this.metrics.lastChangedTimestamp,
deleted: this.isDeleted
};
}
get pixelCount() {
return this.metrics.width * this.metrics.height;
}
async populate(object) {
if (object instanceof HTMLElement) {
this.populateFromAPI();
return;
}
if (object.metadata === void 0) {
await THROTTLED_FETCH_QUEUE.wait();
await this.populateFromAPI();
Events.favorites.missingMetadataFound.emit(this.id);
return;
}
this.populateFromDatabase(object);
if (this.isEmpty) {
await THROTTLED_FETCH_QUEUE.wait();
await this.populateFromAPI();
Events.favorites.missingMetadataFound.emit(this.id);
}
}
async populateFromAPI() {
if (tooManyPendingRequests()) {
UPDATE_QUEUE.push(this);
return;
}
PENDING_REQUESTS.add(this.id);
const post = await fetchPostFromAPISafe(this.id);
PENDING_REQUESTS.delete(this.id);
setExtensionFromPost(post);
validateTags(post);
this.populateFromPost(post);
}
populateFromPost(post) {
this.metrics.width = post.width;
this.metrics.height = post.height;
this.metrics.score = post.score;
this.metrics.creationTimestamp = Date.parse(post.createdAt);
this.metrics.lastChangedTimestamp = post.change;
this.rating = decodeRating(post.rating);
}
populateFromDatabase(record) {
this.metrics.width = record.metadata.width;
this.metrics.height = record.metadata.height;
this.metrics.score = record.metadata.score;
this.rating = record.metadata.rating;
this.metrics.creationTimestamp = record.metadata.create;
this.metrics.lastChangedTimestamp = record.metadata.change;
this.isDeleted = record.metadata.deleted;
}
};
var TAG_MODIFICATIONS = /* @__PURE__ */ new Map();
var DATABASE2 = new Database("AdditionalTags", "additionalTags", 12);
async function loadTagModifications() {
const records = await DATABASE2.load();
for (const record of records) {
TAG_MODIFICATIONS.set(record.id, record.tags);
}
}
function getAdditionalTags(id) {
return TAG_MODIFICATIONS.get(id);
}
function storeTagModifications() {
DATABASE2.update(getDatabaseRecords());
}
function getDatabaseRecords() {
return Array.from(TAG_MODIFICATIONS.entries()).map((entry) => ({
id: entry[0],
tags: entry[1]
}));
}
function resetTagModifications() {
if (!confirm("Are you sure you want to delete all tag modifications?")) {
return;
}
indexedDB.deleteDatabase("AdditionalTags");
Events.tagModifier.resetConfirmed.emit();
clearCustomTags();
}
function getCorrectTags(post) {
const correctTags = convertToTagSet(post.tags);
correctTags.add(post.id);
if (post.fileURL.endsWith("mp4")) {
correctTags.add("video");
} else if (post.fileURL.endsWith("gif")) {
correctTags.add("gif");
} else if (!correctTags.has("animated_png")) {
correctTags.delete("video");
correctTags.delete("animated");
}
return correctTags;
}
var FavoriteTags = class {
// @ts-expect-error not directly defined in constructor
tags;
id;
additionalTags = /* @__PURE__ */ new Set();
constructor(post, record) {
this.id = post.id;
this.set(record instanceof HTMLElement ? post.tags : record.tags);
post.tags = "";
}
get tagString() {
return convertToTagString(this.tags);
}
get originalTagSet() {
return this.tags.difference(this.additionalTags);
}
get additionalTagString() {
return convertToTagString(this.additionalTags);
}
set(tags) {
this.tags = tags instanceof Set ? tags : convertToTagSet(tags);
const additionalTags = getAdditionalTags(this.id);
if (additionalTags !== void 0) {
this.additionalTags = convertToTagSet(additionalTags);
this.combineOriginalAndAdditionalTagSets();
}
}
update(tags) {
this.set(tags);
}
tagsAreEqual(post) {
const correctTags = getCorrectTags(post);
const difference = this.tags.symmetricDifference(correctTags);
const equal = difference.size === 0 || difference.size === 1 && difference.has(post.id);
if (equal) {
return true;
}
post.tags = convertToTagString(correctTags);
return false;
}
addAdditionalTags(newTagString) {
const newTags = convertToTagSet(newTagString).difference(this.tags);
if (newTags.size > 0) {
this.additionalTags = this.additionalTags.union(newTags);
this.combineOriginalAndAdditionalTagSets();
}
return this.additionalTagString;
}
removeAdditionalTags(tagsToRemove) {
const tagsToRemoveSet = convertToTagSet(tagsToRemove).intersection(this.additionalTags);
if (tagsToRemoveSet.size > 0) {
this.tags = this.tags.difference(tagsToRemoveSet);
this.additionalTags = this.additionalTags.difference(tagsToRemoveSet);
}
return this.additionalTagString;
}
resetAdditionalTags() {
if (this.additionalTags.size === 0) {
return;
}
this.additionalTags = /* @__PURE__ */ new Set();
this.combineOriginalAndAdditionalTagSets();
}
combineOriginalAndAdditionalTagSets() {
const union = this.originalTagSet.union(this.additionalTags);
this.tags = new Set(Array.from(union).sort());
}
};
var ALL_FAVORITES = /* @__PURE__ */ new Map();
function getFavorite(id) {
return ALL_FAVORITES.get(id);
}
function validateTags(post) {
const favorite = getFavorite(post.id);
if (favorite !== void 0) {
favorite.validateTags(post);
}
}
function registerFavorite(favorite) {
if (!ALL_FAVORITES.has(favorite.id)) {
ALL_FAVORITES.set(favorite.id, favorite);
FAVORITES_SEARCH_INDEX.add(favorite);
}
}
var FavoriteItem2 = class {
id;
post;
element;
favoriteTags;
metadata;
constructor(object) {
this.id = object instanceof HTMLElement ? getIdFromThumb(object) : object.id;
this.post = createPostFromRawFavorite(object);
this.element = null;
this.favoriteTags = new FavoriteTags(this.post, object);
this.metadata = new FavoriteMetadata(this.id, object);
registerFavorite(this);
}
get tags() {
return this.favoriteTags.tags;
}
get root() {
if (this.element === null) {
this.post.tags = this.favoriteTags.tagString;
this.element = new FavoriteHTMLElement(this.post);
}
clearPost(this.post);
return this.element.root;
}
get thumbURL() {
return this.element === null ? this.post.previewURL : this.element.thumbURL;
}
get metrics() {
return this.metadata.metrics;
}
get databaseRecord() {
return {
id: this.id,
tags: this.tags,
src: compressPreviewSource(this.thumbURL),
metadata: this.metadata.databaseRecord
};
}
withinRating(rating) {
return (this.metadata.rating & rating) > 0;
}
validateTags(post) {
if (!this.favoriteTags.tagsAreEqual(post)) {
this.updateTags(post.tags);
}
}
swapFavoriteButton() {
if (this.element !== null) {
this.element.swapFavoriteButton();
}
}
addAdditionalTags(newTags) {
FAVORITES_SEARCH_INDEX.remove(this);
const result = this.favoriteTags.addAdditionalTags(newTags);
FAVORITES_SEARCH_INDEX.add(this);
return result;
}
removeAdditionalTags(tagsToRemove) {
FAVORITES_SEARCH_INDEX.remove(this);
const result = this.favoriteTags.removeAdditionalTags(tagsToRemove);
FAVORITES_SEARCH_INDEX.add(this);
return result;
}
resetAdditionalTags() {
FAVORITES_SEARCH_INDEX.remove(this);
this.favoriteTags.resetAdditionalTags();
FAVORITES_SEARCH_INDEX.add(this);
}
updateTags(tags) {
FAVORITES_SEARCH_INDEX.remove(this);
this.favoriteTags.update(tags);
FAVORITES_SEARCH_INDEX.add(this);
}
};
var importantTagCategories = /* @__PURE__ */ new Set([
"copyright",
"character",
"artist",
"metadata"
]);
var template = `
<ul id="caption-list">
<li id="caption-id" style="display: block;"><h6>ID</h6></li>
${getCategoryHeaderHTML()}
</ul>
`;
var TAG_CATEGORY_MAPPINGS = {};
var PENDING_REQUESTS2 = /* @__PURE__ */ new Set();
var SETTINGS = {
tagFetchDelayAfterFinishedLoading: 35,
tagFetchDelayBeforeFinishedLoading: 100,
maxPendingRequestsAllowed: 100
};
var FLAGS = {
finishedLoading: false
};
var TAG_CATEGORY_DECODINGS = {
0: "general",
1: "artist",
2: "unknown",
3: "copyright",
4: "character",
5: "metadata"
};
var DATABASE3 = new Database("TagCategories", "tagMappings");
var DATABASE_WRITE_SCHEDULER2 = new BatchExecutor(500, 2e3, saveTagCategories);
var captionWrapper;
var caption2;
var currentThumb = null;
var problematicTags;
var currentThumbId = null;
var abortController;
function getCategoryHeaderHTML() {
let html = "";
for (const category of importantTagCategories) {
const capitalizedCategory = capitalize(category);
const header = capitalizedCategory === "Metadata" ? "Meta" : capitalizedCategory;
html += `<li id="caption${capitalizedCategory}" style="display: none;"><h6>${header}</h6></li>`;
}
return html;
}
function saveTagCategories(mappings) {
DATABASE3.store(mappings);
}
function isHidden() {
return caption2.classList.contains("hide") || caption2.classList.contains("disabled") || caption2.classList.contains("remove");
}
function getTagFetchDelay() {
if (FLAGS.finishedLoading) {
return SETTINGS.tagFetchDelayAfterFinishedLoading;
}
return SETTINGS.tagFetchDelayBeforeFinishedLoading;
}
function initializeFields() {
loadTagCategoryMappings();
problematicTags = /* @__PURE__ */ new Set();
abortController = new AbortController();
}
function createHTMLElement() {
captionWrapper = document.createElement("div");
captionWrapper.className = "caption-wrapper";
caption2 = document.createElement("div");
caption2.className = "caption inactive not-highlightable";
captionWrapper.appendChild(caption2);
document.head.appendChild(captionWrapper);
caption2.innerHTML = template;
}
function insertHTML() {
insertStyleHTML(CAPTION_HTML, "caption");
}
function toggleVisibility(value) {
if (value === void 0) {
value = caption2.classList.contains("disabled");
}
if (value) {
caption2.classList.remove("disabled");
} else if (!caption2.classList.contains("disabled")) {
caption2.classList.add("disabled");
}
Preferences.captionsVisible.set(value);
}
function addEventListeners() {
addCommonEventListeners();
addFavoritesPageEventListeners();
}
function addCommonEventListeners() {
caption2.addEventListener("transitionend", () => {
if (caption2.classList.contains("active")) {
caption2.classList.add("transition-completed");
}
caption2.classList.remove("transitioning");
});
caption2.addEventListener("transitionstart", () => {
caption2.classList.add("transitioning");
});
Events.favorites.captionsToggled.on((value) => {
toggleVisibility(value);
if (currentThumb !== null && !caption2.classList.contains("remove")) {
if (value) {
attachToThumbHelper(currentThumb);
} else {
removeFromThumbHelper(currentThumb);
}
}
});
Events.document.mouseover.on((mouseOverEvent) => {
if (mouseOverEvent.insideOfThumb) {
const insideOfDifferentThumb = currentThumb !== null && mouseOverEvent.thumb !== null && currentThumb.id !== mouseOverEvent.thumb.id;
if (insideOfDifferentThumb) {
removeFromThumb(currentThumb);
}
attachToThumb(mouseOverEvent.thumb);
currentThumb = mouseOverEvent.thumb;
} else {
if (currentThumb !== null) {
removeFromThumb(currentThumb);
}
currentThumb = null;
}
});
}
function addFavoritesPageEventListeners() {
Events.favorites.favoritesLoaded.on(() => {
FLAGS.finishedLoading = true;
}, {
once: true
});
Events.favorites.favoritesLoadedFromDatabase.on(() => {
FLAGS.finishedLoading = true;
}, {
once: true
});
Events.favorites.pageChanged.on(debounceAfterFirstCall(() => {
abortAllRequests("Changed Page");
abortController = new AbortController();
setTimeout(() => {
findTagCategoriesOnPageChange();
}, 600);
}, 2e3));
Events.favorites.captionsReEnabled.on(() => {
if (currentThumb !== null) {
attachToThumb(currentThumb);
}
});
Events.favorites.resetConfirmed.on(() => {
DATABASE3.delete();
});
}
function attachToThumb(thumb) {
if (isHidden() || thumb === null) {
return;
}
attachToThumbHelper(thumb);
}
function attachToThumbHelper(thumb) {
thumb.querySelectorAll(".caption-wrapper-clone").forEach((element) => element.remove());
caption2.classList.remove("inactive");
caption2.innerHTML = template;
captionWrapper.removeAttribute("style");
const captionIdHeader = caption2.querySelector("#caption-id");
const captionIdTag = document.createElement("li");
captionIdTag.className = "caption-tag";
captionIdTag.textContent = thumb.id;
captionIdTag.onclick = (event) => {
event.preventDefault();
event.stopPropagation();
};
captionIdTag.addEventListener("contextmenu", (event) => {
event.preventDefault();
event.stopPropagation();
});
captionIdTag.onmousedown = (event) => {
event.preventDefault();
event.stopPropagation();
tagOnClick(thumb.id, event);
Events.caption.idClicked.emit(thumb.id);
};
captionIdHeader?.insertAdjacentElement("afterend", captionIdTag);
thumb.children[0].appendChild(captionWrapper);
populateTags(thumb);
}
function removeFromThumb(thumb) {
if (isHidden() || thumb === null) {
return;
}
removeFromThumbHelper(thumb);
}
function removeFromThumbHelper(thumb) {
animateRemoval(thumb);
caption2.classList.add("inactive");
animate(false);
caption2.classList.remove("transition-completed");
}
function animateRemoval(thumb) {
const captionWrapperClone = captionWrapper.cloneNode(true);
if (!(captionWrapperClone instanceof HTMLElement)) {
return;
}
const captionClone = captionWrapperClone.children[0];
thumb.querySelectorAll(".caption-wrapper-clone").forEach((element) => element.remove());
captionWrapperClone.classList.add("caption-wrapper-clone");
captionWrapperClone.querySelectorAll("*").forEach((element) => element.removeAttribute("id"));
if (!(captionClone instanceof HTMLElement)) {
return;
}
captionClone.ontransitionend = () => {
captionWrapperClone.remove();
};
thumb.children[0].appendChild(captionWrapperClone);
setTimeout(() => {
captionClone.classList.remove("active");
}, 4);
}
function resizeFont(thumb) {
const columnInput = document.getElementById("column-count");
const heightCanBeDerivedWithoutRect = thumbMetadataExists(thumb) && columnInput !== null;
const image = getImageFromThumb(thumb);
let height = 200;
if (heightCanBeDerivedWithoutRect && columnInput instanceof HTMLInputElement) {
height = estimateThumbHeightFromMetadata(thumb, columnInput);
} else if (image !== null) {
height = image.getBoundingClientRect().height;
}
const fancyImageHoveringStyle = document.getElementById("fancy-image-hovering-fsg-style");
const fancyHoveringEnabled = fancyImageHoveringStyle !== null && fancyImageHoveringStyle.innerHTML.length > 10;
const captionListRect = caption2.children[0].getBoundingClientRect();
const ratio = height / captionListRect.height;
const scale = ratio > 1 ? Math.sqrt(ratio) : ratio * 0.85;
const finalScale = fancyHoveringEnabled ? scale * 0.8 : scale;
if (caption2 !== null && caption2.parentElement !== null) {
caption2.parentElement.style.fontSize = `${roundToTwoDecimalPlaces(finalScale)}em`;
}
}
function thumbMetadataExists(thumb) {
if (ON_SEARCH_PAGE) {
return false;
}
const favorite = getFavorite(thumb.id);
if (favorite === void 0) {
return false;
}
if (favorite.metrics.width <= 0 || favorite.metrics.width <= 0) {
return false;
}
return true;
}
function estimateThumbHeightFromMetadata(thumb, columnInput) {
const favorite = getFavorite(thumb.id);
if (favorite === void 0) {
return 200;
}
const gridGap = 16;
const columnCount = Math.max(1, parseInt(columnInput.value));
const thumbWidthEstimate = (window.innerWidth - columnCount * gridGap) / columnCount;
const thumbWidthScale = favorite.metrics.width / thumbWidthEstimate;
return favorite.metrics.height / thumbWidthScale;
}
function addTag(tagCategory, tagName) {
if (!importantTagCategories.has(tagCategory)) {
return;
}
const header = document.getElementById(getCategoryHeaderId(tagCategory));
const tag = document.createElement("li");
if (header === null) {
return;
}
tag.className = `${tagCategory}-tag caption-tag`;
tag.textContent = replaceUnderscoresWithSpaces(tagName);
header.insertAdjacentElement("afterend", tag);
header.style.display = "block";
tag.onmouseover = (event) => {
event.stopPropagation();
};
tag.onclick = (event) => {
event.stopPropagation();
event.preventDefault();
};
tag.addEventListener("contextmenu", (event) => {
event.preventDefault();
event.stopPropagation();
});
tag.onmousedown = (event) => {
event.preventDefault();
event.stopPropagation();
tagOnClick(tagName, event);
};
}
async function loadTagCategoryMappings() {
const mappings = await DATABASE3.load();
for (const mapping of mappings) {
TAG_CATEGORY_MAPPINGS[mapping.id] = mapping.category;
}
}
function tagOnClick(tagName, event) {
switch (event.button) {
case 0 /* LEFT */:
if (event.shiftKey && isOnlyDigits(tagName)) {
Events.favorites.findFavoriteInAllStarted.emit(tagName);
} else {
tagOnClickHelper(tagName, event);
}
break;
case 1 /* MIDDLE */:
Events.caption.searchForTag.emit(tagName);
break;
case 2 /* RIGHT */:
tagOnClickHelper(`-${tagName}`, event);
break;
default:
break;
}
}
function tagOnClickHelper(value, mouseEvent) {
if (ON_SEARCH_PAGE) {
return;
}
if (mouseEvent.ctrlKey) {
openSearchPage(value);
return;
}
Events.searchBox.appendSearchBox.emit(value);
}
function replaceUnderscoresWithSpaces(tagName) {
return tagName.replaceAll(/_/gm, " ");
}
function replaceSpacesWithUnderscores(tagName) {
return tagName.replaceAll(/\s/gm, "_");
}
function animate(value) {
caption2.classList.toggle("active", value);
}
function getCategoryHeaderId(tagCategory) {
return `caption${capitalize(tagCategory)}`;
}
function populateTags(thumb) {
const tagNames = getTagSetFromThumb(thumb);
tagNames.delete(thumb.id);
const unknownThumbTags = Array.from(tagNames).filter((tagName) => tagCategoryIsUnknown(thumb, tagName));
currentThumbId = thumb.id;
if (allTagsAreProblematic(unknownThumbTags)) {
correctAllProblematicTagsFromThumb(thumb, () => {
addTags(tagNames, thumb);
});
return;
}
if (unknownThumbTags.length > 0) {
findTagCategories(unknownThumbTags, () => {
addTags(tagNames, thumb);
}, 3);
return;
}
addTags(tagNames, thumb);
}
function addTags(tags, thumb) {
if (currentThumbId !== thumb.id) {
return;
}
if (thumb.getElementsByClassName("caption-tag").length > 1) {
return;
}
for (const tagName of Array.from(tags).reverse()) {
const category = getTagCategory(tagName);
addTag(category, tagName);
}
resizeFont(thumb);
setTimeout(() => {
animate(true);
}, 0);
}
function getTagCategory(tagName) {
return TAG_CATEGORY_MAPPINGS[tagName] ?? "general";
}
function allTagsAreProblematic(tags) {
for (const tag of tags) {
if (!problematicTags.has(tag)) {
return false;
}
}
return tags.length > 0;
}
function correctAllProblematicTagsFromThumb(thumb, onProblematicTagsCorrected) {
fetchPostPage(thumb.id).then((html) => {
const tagCategoryMap = getTagCategoryMapFromPostPage(html);
for (const [tagName, tagCategory] of tagCategoryMap.entries()) {
addTagCategoryMapping(tagName, isTagCategory(tagCategory) ? tagCategory : "general");
problematicTags.delete(tagName);
}
onProblematicTagsCorrected();
}).catch((error) => {
console.error(error);
});
}
function getTagCategoryMapFromPostPage(html) {
const dom = new DOMParser().parseFromString(html, "text/html");
return Array.from(dom.querySelectorAll(".tag")).reduce((map, element) => {
const tagCategory = element.classList[0].replace("tag-type-", "");
const tagName = replaceSpacesWithUnderscores(element.children[1].textContent || "");
map.set(tagName, tagCategory);
return map;
}, /* @__PURE__ */ new Map());
}
function setAsProblematic(tag) {
if (TAG_CATEGORY_MAPPINGS[tag] === void 0) {
problematicTags.add(tag);
}
}
function findTagCategoriesOnPageChange() {
if (!FLAGS.finishedLoading) {
return;
}
const tagNames = getTagNamesWithUnknownCategories(getAllThumbs().slice(0, 200));
findTagCategories(tagNames, DO_NOTHING, getTagFetchDelay());
}
function abortAllRequests(reason) {
abortController.abort(reason);
abortController = new AbortController();
PENDING_REQUESTS2.clear();
}
async function findTagCategories(tagNames, onAllCategoriesFound, fetchDelay) {
const parser = new DOMParser();
const lastTagName = tagNames[tagNames.length - 1];
const uniqueTagNames = new Set(tagNames);
for (const tagName of uniqueTagNames) {
if (isOnlyDigits(tagName) && tagName.length > 5) {
addTagCategoryMapping(tagName, "general");
continue;
}
if (PENDING_REQUESTS2.size > SETTINGS.maxPendingRequestsAllowed) {
abortAllRequests(`Too many pending requests: ${PENDING_REQUESTS2.size}`);
return;
}
if (tagName.includes("'")) {
setAsProblematic(tagName);
}
if (problematicTags.has(tagName)) {
if (tagName === lastTagName) {
onAllCategoriesFound();
}
continue;
}
try {
PENDING_REQUESTS2.add(tagName);
fetch(createTagAPIURL(tagName), {
signal: abortController.signal
}).then((response) => {
if (response.ok) {
return response.text();
}
throw new Error(response.statusText);
}).then((html) => {
PENDING_REQUESTS2.delete(tagName);
const dom = parser.parseFromString(html, "text/html");
const encoding = dom.getElementsByTagName("tag")[0].getAttribute("type");
if (encoding === "array") {
setAsProblematic(tagName);
return;
}
addTagCategoryMapping(tagName, decodeTagCategory(parseInt(encoding ?? "0")));
if (tagName === lastTagName) {
onAllCategoriesFound();
}
}).catch(() => {
onAllCategoriesFound();
});
} catch (error) {
PENDING_REQUESTS2.delete(tagName);
console.error(error);
}
await sleep(fetchDelay ?? getTagFetchDelay());
}
}
function getTagNamesWithUnknownCategories(thumbs) {
const tagNamesWithUnknownCategories = /* @__PURE__ */ new Set();
for (const thumb of thumbs) {
const tagNames = Array.from(getTagSetFromThumb(thumb));
for (const tagName of tagNames) {
if (tagCategoryIsUnknown(thumb, tagName)) {
tagNamesWithUnknownCategories.add(tagName);
}
}
}
return Array.from(tagNamesWithUnknownCategories);
}
function tagCategoryIsUnknown(thumb, tagName) {
return tagName !== thumb.id && TAG_CATEGORY_MAPPINGS[tagName] === void 0;
}
function decodeTagCategory(encoding) {
return TAG_CATEGORY_DECODINGS[encoding] ?? "general";
}
function addTagCategoryMapping(id, category) {
if (TAG_CATEGORY_MAPPINGS[id] !== void 0) {
return;
}
TAG_CATEGORY_MAPPINGS[id] = category;
DATABASE_WRITE_SCHEDULER2.add({
id,
category
});
}
function setupCaptions() {
if (CAPTIONS_DISABLED) {
return;
}
initializeFields();
createHTMLElement();
insertHTML();
toggleVisibility(Preferences.captionsVisible.value);
addEventListeners();
}
var DownloadRequest = class {
id;
url;
extension;
constructor(id, url, extension) {
this.id = id;
this.url = url;
this.extension = extension;
}
get filename() {
return `${this.id}.${this.extension}`;
}
async blob() {
const response = await fetch(this.url);
return response.blob();
}
};
async function createDownloadRequest(favorite) {
let extension;
if (isVideo(favorite)) {
extension = "mp4";
} else if (isGif(favorite)) {
extension = "gif";
} else {
extension = await getExtension(favorite);
}
const url = await getOriginalContentURL(favorite);
return new DownloadRequest(favorite.id, url, extension);
}
var aborted = false;
var currentlyDownloading = false;
function setupFavoritesDownloader() {
loadZipJS();
}
async function loadZipJS() {
await new Promise((resolve, reject) => {
if (typeof zip !== "undefined") {
resolve(zip);
return;
}
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/gh/gildas-lormeau/zip.js/dist/zip-full.min.js";
script.onload = () => resolve(zip);
script.onerror = reject;
document.head.appendChild(script);
});
}
async function startDownloading(favorites3, progressCallback) {
if (currentlyDownloading) {
return;
}
currentlyDownloading = true;
aborted = false;
await downloadFavorites(favorites3, progressCallback);
}
async function downloadFavorites(favorites3, progressCallback) {
downloadBlob(await createTotalFavoriteBlob(favorites3, progressCallback), "download.zip");
currentlyDownloading = false;
}
async function createTotalFavoriteBlob(favorites3, progressCallback, poolSize = 15) {
const blobWriter = new zip.BlobWriter("application/zip");
const zipWriter = new zip.ZipWriter(blobWriter);
await runWithPools(favorites3, poolSize, async (favorite) => {
await createFavoriteBlob(favorite, zipWriter, progressCallback);
});
stopIfAborted();
return zipWriter.close();
}
async function createFavoriteBlob(favorite, zipWriter, progressCallback) {
try {
stopIfAborted();
const request = await createDownloadRequest(favorite);
stopIfAborted();
const blob = await request.blob();
stopIfAborted();
await zipFile(zipWriter, request, blob);
stopIfAborted();
progressCallback?.(request);
stopIfAborted();
} catch (error) {
stopIfAborted();
console.error(error);
}
}
async function zipFile(zipWriter, request, blob) {
const reader = new zip.BlobReader(blob);
await zipWriter.add(request.filename, reader, {
compression: "STORE"
});
}
function stopIfAborted() {
if (aborted) {
throw new DownloadAbortedError();
}
}
function abort() {
aborted = true;
}
function reset() {
currentlyDownloading = false;
}
var dialog;
var warningDialog;
var downloadButton;
var cancelButton;
var statusContainer;
var statusHeader;
var favoritesLoaded;
var latestSearchResults = [];
function setupDownloadMenu() {
setupFavoritesDownloader();
FAVORITES_SEARCH_GALLERY_CONTAINER.insertAdjacentHTML("beforeend", DOWNLOADER_HTML);
dialog = getDialog("download-menu");
warningDialog = getDialog("download-menu-warning");
downloadButton = getDownloadButton();
cancelButton = getCancelButton();
statusContainer = getStatusContainer();
statusHeader = getStatusHeader();
favoritesLoaded = false;
addEventListeners2();
}
function getDialog(id) {
const newDialog = document.getElementById(id);
return newDialog instanceof HTMLDialogElement ? newDialog : document.createElement("dialog");
}
function getDownloadButton() {
const button = document.getElementById("download-menu-buttons-start-download");
if (!(button instanceof HTMLButtonElement)) {
return document.createElement("button");
}
button.addEventListener("click", () => {
button.disabled = true;
downloadFavorites2(latestSearchResults);
});
return button;
}
function getCancelButton() {
const button = document.getElementById("download-menu-buttons-cancel-download");
if (!(button instanceof HTMLButtonElement)) {
return document.createElement("button");
}
button.addEventListener("click", () => {
dialog.close();
});
return button;
}
function getStatusContainer() {
const container4 = document.getElementById("download-menu-status");
if (!(container4 instanceof HTMLElement)) {
return document.createElement("div");
}
return container4;
}
function getStatusHeader() {
const header = document.getElementById("download-menu-status-header");
if (!(header instanceof HTMLElement)) {
return document.createElement("div");
}
return header;
}
function createStatusTextRow() {
const row = document.createElement("span");
row.classList.add("download-menu-status-row");
statusContainer.appendChild(row);
return row;
}
function addEventListeners2() {
enableAfterFavoritesLoad();
openWhenDownloadButtonClicked();
setupMenuCancelHandler();
setupMenuCloseHandler();
setupMenuOptions();
keepTrackOfSearchResults();
}
function enableAfterFavoritesLoad() {
Events.favorites.favoritesLoaded.on(() => {
favoritesLoaded = true;
}, {
once: true
});
}
function openWhenDownloadButtonClicked() {
Events.favorites.downloadButtonClicked.on(() => {
if (favoritesLoaded) {
downloadButton.disabled = false;
dialog.showModal();
statusHeader.textContent = `Download ${latestSearchResults.length} Results`;
} else {
warningDialog.showModal();
}
Events.toggleGlobalInputEvents(false);
document.body.classList.add("dialog-opened");
});
}
function setupMenuCancelHandler() {
dialog.addEventListener("cancel", (event) => {
event.preventDefault();
});
}
function setupMenuCloseHandler() {
dialog.addEventListener("close", async () => {
cancelButton.textContent = "Cancel";
Events.toggleGlobalInputEvents(true);
await yield1();
document.body.classList.remove("dialog-opened");
dialog.classList.remove("downloading");
abort();
clearStatusTextRows();
downloadButton.disabled = true;
await sleep(2e3);
downloadButton.disabled = false;
reset();
});
warningDialog.addEventListener("close", () => {
document.body.classList.remove("dialog-opened");
Events.toggleGlobalInputEvents(true);
});
}
function setupMenuOptions() {
setupMenuBatchSizeSelect();
}
function keepTrackOfSearchResults() {
Events.favorites.searchResultsUpdated.on((results) => {
latestSearchResults = results;
});
}
function setupMenuBatchSizeSelect() {
const batchSizeSelect = document.getElementById("download-menu-options-batch-size");
if (!(batchSizeSelect instanceof HTMLSelectElement)) {
return;
}
batchSizeSelect.value = String(Preferences.downloadBatchSize.value);
batchSizeSelect.addEventListener("change", () => {
Preferences.downloadBatchSize.set(parseInt(batchSizeSelect.value));
});
}
function clearStatusTextRows() {
const rows = Array.from(statusContainer.querySelectorAll(".download-menu-status-row"));
for (const row of rows) {
row.remove();
}
}
async function downloadFavorites2(favorites3) {
const favoriteCount = favorites3.length;
if (favoriteCount === 0) {
finishDownload();
return;
}
dialog.classList.add("downloading");
statusHeader.textContent = `Downloading ${favoriteCount} Results`;
const batches = splitIntoChunks(favorites3, Preferences.downloadBatchSize.value);
const totalProgressRow = createStatusTextRow();
const batchProgressRow = createStatusTextRow();
const fileNameRow = createStatusTextRow();
let totalCount = 0;
const progressCallback = (request) => {
totalCount += 1;
totalProgressRow.textContent = `Downloading: ${totalCount} / ${favoriteCount}`;
fileNameRow.textContent = `${request.filename}`;
};
for (let i = 0; i < batches.length; i += 1) {
const batch = batches[i];
batchProgressRow.textContent = `Batch: ${i + 1} / ${batches.length}`;
totalProgressRow.textContent = `Total ${batch.length} posts`;
await startDownloading(batch, progressCallback);
}
statusHeader.textContent = `Downloaded ${favoriteCount} Results`;
finishDownload();
}
function finishDownload() {
clearStatusTextRows();
createStatusTextRow().textContent = "Finished";
cancelButton.textContent = "Exit";
}
var SCHEMA_VERSION = 1;
var SCHEMA_VERSION_LOCAL_STORAGE_KEY = "favoritesSearchGallerySchemaVersion";
var DATABASE4 = new Database("Favorites", `user${getFavoritesPageId()}`);
var METADATA_UPDATER = new BatchExecutor(100, 1e3, updateFavorites);
function updateFavorites(favorites3) {
DATABASE4.update(favorites3.map((favorite) => favorite.databaseRecord));
}
function convertToFavorites(records) {
return records.map((record) => new FavoriteItem2(record));
}
function getSchemaVersion() {
const version = localStorage.getItem(SCHEMA_VERSION_LOCAL_STORAGE_KEY);
return version === null ? null : parseInt(version);
}
function setSchemaVersion(version) {
localStorage.setItem(SCHEMA_VERSION_LOCAL_STORAGE_KEY, version.toString());
}
function usingCorrectSchema(records) {
return getSchemaVersion() === SCHEMA_VERSION && records.length > 0 && records[0].tags instanceof Set;
}
function updateDatabaseRecord(record) {
return {
...record,
tags: convertToTagSet(record.tags),
metadata: JSON.parse(record.metadata)
};
}
function updateDatabaseRecords(records) {
return records.map((record) => updateDatabaseRecord(record));
}
async function updateDatabaseRecordsIfNeeded(records) {
if (records.length === 0) {
setSchemaVersion(SCHEMA_VERSION);
return Promise.resolve(records);
}
if (usingCorrectSchema(records)) {
return Promise.resolve(records);
}
const updatedRecords = updateDatabaseRecords(records);
await DATABASE4.update(updatedRecords);
setSchemaVersion(SCHEMA_VERSION);
return updatedRecords;
}
async function loadAllFavorites() {
const records = await DATABASE4.load();
const updatedRecords = await updateDatabaseRecordsIfNeeded(records);
return convertToFavorites(updatedRecords);
}
async function storeFavorites(favorites3) {
const records = favorites3.slice().reverse().map((favorite) => favorite.databaseRecord);
await sleep(500);
DATABASE4.store(records);
}
function storeAllFavorites(favorites3) {
return storeFavorites(favorites3);
}
function updateMetadataInDatabase(id) {
const favorite = getFavorite(id);
if (favorite !== void 0) {
METADATA_UPDATER.add(favorite);
}
}
function deleteFavorite(id) {
return DATABASE4.deleteRecords([id]);
}
function deleteDatabase() {
return DATABASE4.delete();
}
var queue = [];
var mostRecentlyDequeuedPageNumber = -1;
var dequeuing = false;
var onDequeue = DO_NOTHING;
function getSmallestEnqueuedPageNumber() {
return queue[0].pageNumber;
}
function getNextPageNumberToDequeue() {
return mostRecentlyDequeuedPageNumber + 1;
}
function allPreviousPagesWereDequeued() {
return getNextPageNumberToDequeue() === getSmallestEnqueuedPageNumber();
}
function isEmpty() {
return queue.length === 0;
}
function canDequeue() {
return !isEmpty() && allPreviousPagesWereDequeued();
}
function sortByLowestPageNumber() {
queue.sort((request1, request2) => request1.pageNumber - request2.pageNumber);
}
function drain() {
if (dequeuing) {
return;
}
dequeuing = true;
while (canDequeue()) {
dequeue();
}
dequeuing = false;
}
function dequeue() {
mostRecentlyDequeuedPageNumber += 1;
const request = queue.shift();
const favorites3 = request?.favorites ?? [];
onDequeue(favorites3);
}
function setDequeueCallback(callback) {
onDequeue = callback;
}
function enqueue(request) {
queue.push(request);
sortByLowestPageNumber();
drain();
}
var FavoritesPageRequest = class {
pageNumber;
favorites = [];
retryCount;
constructor(pageNumber) {
this.pageNumber = pageNumber;
this.retryCount = 0;
}
get url() {
return createFavoritesPageURL(this.pageNumber * 50);
}
get fetchDelay() {
return 7 ** this.retryCount + FavoritesSettings.favoritesPageFetchDelay;
}
get realPageNumber() {
return this.pageNumber * 50;
}
retry() {
this.retryCount += 1;
}
};
var PARSER5 = new DOMParser();
function extractFavoriteElements(favoritesPageHTML) {
const dom = PARSER5.parseFromString(favoritesPageHTML, "text/html");
const thumbs = Array.from(dom.querySelectorAll(".thumb"));
if (thumbs.length > 0) {
return thumbs.filter((thumb) => thumb instanceof HTMLElement);
}
return Array.from(dom.querySelectorAll("img")).filter((image) => image.src.includes("thumbnail_")).map((image) => image.parentElement).filter((thumb) => thumb !== null);
}
function extractFavorites(favoritesPageHTML) {
return extractFavoriteElements(favoritesPageHTML).map((element) => new FavoriteItem2(element));
}
var PENDING_PAGE_NUMBERS = /* @__PURE__ */ new Set();
var FAILED_REQUESTS = [];
var storedFavoriteIds = /* @__PURE__ */ new Set();
var currentPageNumber = 0;
var fetchedAnEmptyPage = false;
function hasFailedRequests() {
return FAILED_REQUESTS.length > 0;
}
function allRequestsHaveStarted() {
return fetchedAnEmptyPage;
}
function someRequestsArePending() {
return PENDING_PAGE_NUMBERS.size > 0 || hasFailedRequests();
}
function noRequestsArePending() {
return !someRequestsArePending();
}
function allRequestsHaveCompleted() {
return allRequestsHaveStarted() && noRequestsArePending();
}
function someRequestsAreIncomplete() {
return !allRequestsHaveCompleted();
}
function oldestFailedFetchRequest() {
return FAILED_REQUESTS.shift() ?? null;
}
function getNewFetchRequest() {
const request = new FavoritesPageRequest(currentPageNumber);
PENDING_PAGE_NUMBERS.add(request.realPageNumber);
currentPageNumber += 1;
return request;
}
function nextFetchRequest() {
if (hasFailedRequests()) {
return oldestFailedFetchRequest();
}
if (!allRequestsHaveStarted()) {
return getNewFetchRequest();
}
return null;
}
async function fetchNextFavoritesPage() {
const request = nextFetchRequest();
if (request === null) {
await sleep(200);
return;
}
await fetchFavoritesPageHelper(request);
}
async function fetchFavoritesPageHelper(request) {
fetchFavoritesPage(request.realPageNumber).then((html) => {
onFavoritesPageRequestSuccess(request, html);
}).catch((error) => {
onFavoritesPageRequestError(request, error);
});
await sleep(request.fetchDelay);
}
function onFavoritesPageRequestSuccess(request, html) {
request.favorites = extractFavorites(html);
PENDING_PAGE_NUMBERS.delete(request.realPageNumber);
const favoritesPageIsEmpty = request.favorites.length === 0;
fetchedAnEmptyPage = fetchedAnEmptyPage || favoritesPageIsEmpty;
if (!favoritesPageIsEmpty) {
enqueue(request);
}
}
function onFavoritesPageRequestError(request, error) {
console.error(error);
request.retry();
FAILED_REQUESTS.push(request);
}
function fetchNewFavoritesOnReloadHelper() {
return fetchFavoritesPage(getNewFetchRequest().realPageNumber).then((html) => {
return extractNewFavorites(html);
});
}
function extractNewFavorites(html) {
const newFavorites = [];
const fetchedFavorites = extractFavorites(html);
let allNewFavoritesFound = fetchedFavorites.length === 0;
for (const favorite of fetchedFavorites) {
if (storedFavoriteIds.has(favorite.id)) {
allNewFavoritesFound = true;
break;
}
newFavorites.push(favorite);
}
return {
allNewFavoritesFound,
newFavorites
};
}
async function fetchAllFavorites(onFavoritesFound) {
setDequeueCallback(onFavoritesFound);
while (someRequestsAreIncomplete()) {
await fetchNextFavoritesPage();
}
}
async function fetchNewFavoritesOnReload(ids) {
await sleep(100);
storedFavoriteIds = ids;
let favorites3 = [];
while (true) {
const { allNewFavoritesFound, newFavorites } = await fetchNewFavoritesOnReloadHelper();
favorites3 = favorites3.concat(newFavorites);
if (allNewFavoritesFound) {
storedFavoriteIds.clear();
return favorites3;
}
}
}
var allFavorites = [];
var useSearchSubset = false;
var subsetFavorites = [];
function getAllFavoriteIds() {
return new Set(Array.from(allFavorites.values()).map((favorite) => favorite.id));
}
async function loadAllFavoritesFromDatabase() {
allFavorites = await loadAllFavorites();
return allFavorites;
}
function fetchAllFavorites2(onFavoritesFound) {
const onFavoritesFoundHelper = (favorites3) => {
allFavorites = allFavorites.concat(favorites3);
return onFavoritesFound(favorites3);
};
return fetchAllFavorites(onFavoritesFoundHelper);
}
async function fetchNewFavoritesOnReload2() {
const newFavorites = await fetchNewFavoritesOnReload(getAllFavoriteIds());
allFavorites = newFavorites.concat(allFavorites);
return newFavorites;
}
function getAllFavorites() {
return useSearchSubset ? subsetFavorites : allFavorites;
}
function storeAllFavorites2() {
return storeAllFavorites(allFavorites);
}
function storeNewFavorites(newFavorites) {
return storeFavorites(newFavorites);
}
function updateMetadataInDatabase2(id) {
updateMetadataInDatabase(id);
}
function deleteFavorite2(id) {
return deleteFavorite(id);
}
function deleteDatabase2() {
deleteDatabase();
}
var currentPageNumber2 = 1;
var resultsPerPage = Preferences.resultsPerPage.value;
var favorites2 = [];
function getPageCount() {
const favoriteCount = favorites2.length;
if (favoriteCount === 0) {
return 1;
}
const pageCount = favoriteCount / resultsPerPage;
if (favoriteCount % resultsPerPage === 0) {
return pageCount;
}
return Math.floor(pageCount) + 1;
}
function onFirstPage() {
return currentPageNumber2 === 1;
}
function onFinalPage() {
return currentPageNumber2 === getPageCount();
}
function onlyOnePage() {
return onFirstPage() && onFinalPage();
}
function getPaginationParameters() {
const { start, end } = getCurrentPageRange();
return { currentPageNumber: currentPageNumber2, finalPageNumber: getPageCount(), favoritesCount: favorites2.length, startIndex: start, endIndex: end };
}
function paginate(newFavorites) {
favorites2 = newFavorites;
}
function changePage(pageNumber) {
currentPageNumber2 = clamp(pageNumber, 1, getPageCount());
}
function gotoFirstPage() {
changePage(1);
}
function gotoLastPage() {
changePage(getPageCount());
}
function getFavoritesOnCurrentPage() {
return getFavoritesOnPage(currentPageNumber2);
}
function getFavoritesOnNextPage() {
return getFavoritesOnPage(currentPageNumber2 + 1);
}
function getFavoritesOnPreviousPage() {
return getFavoritesOnPage(currentPageNumber2 - 1);
}
function getFavoritesOnPage(pageNumber) {
const { start, end } = getPageRange(pageNumber);
return favorites2.slice(start, end);
}
function getCurrentPageRange() {
return getPageRange(currentPageNumber2);
}
function getPageRange(pageNumber) {
return {
start: resultsPerPage * (pageNumber - 1),
end: resultsPerPage * pageNumber
};
}
function changeResultsPerPage(newResultsPerPage) {
resultsPerPage = newResultsPerPage;
}
function gotoAdjacentPage(direction) {
const forward = isForwardNavigationKey(direction);
if (onlyOnePage()) {
return false;
}
if (onFinalPage() && forward) {
gotoFirstPage();
} else if (onFirstPage() && !forward) {
gotoLastPage();
return true;
} else {
changePage(forward ? currentPageNumber2 + 1 : currentPageNumber2 - 1);
}
return true;
}
function gotoRelativePage(relation) {
if (onlyOnePage()) {
return false;
}
switch (relation) {
case "previous":
if (onFirstPage()) {
return false;
}
gotoAdjacentPage("ArrowLeft");
return true;
case "first":
if (onFirstPage()) {
return false;
}
gotoFirstPage();
return true;
case "next":
if (onFinalPage()) {
return false;
}
return gotoAdjacentPage("ArrowRight");
case "final":
if (onFinalPage()) {
return false;
}
gotoLastPage();
return true;
default:
return false;
}
}
function gotoPageWithFavorite(id) {
const favoriteIds = favorites2.map((favorite) => favorite.id);
const index = favoriteIds.indexOf(id);
const favoriteNotFound = index === -1;
if (favoriteNotFound) {
return false;
}
const pageNumber = Math.floor(index / resultsPerPage) + 1;
const favoriteOnDifferentPage = currentPageNumber2 !== pageNumber;
if (favoriteOnDifferentPage) {
changePage(pageNumber);
return true;
}
return false;
}
var NEGATED_TAG_BLACKLIST = negateTags(getTagBlacklist());
var searchQuery = "";
var useTagBlacklist = !USER_IS_ON_THEIR_OWN_FAVORITES_PAGE || Preferences.excludeBlacklistEnabled.value;
var allowedRatings = Preferences.allowedRatings.value;
var searchCommand = updateSearchCommand();
function allRatingsAreAllowed() {
return allowedRatings === 7;
}
function getFinalSearchQuery() {
return useTagBlacklist ? `${searchQuery} ${NEGATED_TAG_BLACKLIST}` : searchQuery;
}
function updateSearchCommand() {
searchCommand = new SearchCommand(getFinalSearchQuery());
return searchCommand;
}
function shouldUseIndex(favorites3) {
return FavoritesSettings.useSearchIndex && FAVORITES_SEARCH_INDEX.ready && !searchCommand.details.hasMetadataTag && favorites3.length > 50;
}
function filter(favorites3) {
const results = shouldUseIndex(favorites3) ? FAVORITES_SEARCH_INDEX.getSearchResults(searchCommand, favorites3) : searchCommand.getSearchResults(favorites3);
return filterByRating(results);
}
function filterByRating(favorites3) {
return allRatingsAreAllowed() ? favorites3 : favorites3.filter((result) => result.withinRating(allowedRatings));
}
function filterOutBlacklisted(favorites3) {
return USER_IS_ON_THEIR_OWN_FAVORITES_PAGE ? favorites3 : new SearchCommand(NEGATED_TAG_BLACKLIST).getSearchResults(favorites3);
}
function setSearchQuery(newSearchQuery) {
searchQuery = newSearchQuery;
updateSearchCommand();
}
function toggleBlacklistFiltering(value) {
useTagBlacklist = value;
updateSearchCommand();
}
function setAllowedRatings(newAllowedRating) {
allowedRatings = newAllowedRating;
}
var useAscendingOrder = Preferences.sortAscendingEnabled.value;
var sortingMethod = Preferences.sortingMethod.value;
function setAscendingOrder(value) {
useAscendingOrder = value;
}
function setSortingMethod(value) {
sortingMethod = value;
}
function sortFavorites(favorites3) {
const toSort = favorites3.slice();
if (sortingMethod === "random") {
return shuffleArray(toSort);
}
toSort.sort((a, b) => b.metrics[sortingMethod] - a.metrics[sortingMethod]);
return useAscendingOrder ? toSort.reverse() : toSort;
}
function getMoreResults(favorites3) {
const result = [];
for (const favorite of favorites3) {
if (document.getElementById(favorite.id) === null) {
result.push(favorite.root);
}
if (result.length >= FavoritesSettings.infiniteScrollBatchCount) {
break;
}
}
return result;
}
function getFirstResults(favorites3) {
return favorites3.slice(0, FavoritesSettings.infiniteScrollBatchCount);
}
function getThumbURLsToPreload(favorites3) {
const result = [];
for (const favorite of favorites3) {
if (document.getElementById(favorite.id) === null) {
result.push(favorite.thumbURL);
}
if (result.length >= FavoritesSettings.infiniteScrollPreloadCount) {
break;
}
}
return result;
}
var FAVORITES_CONTENT_CONTAINER = document.createElement("div");
FAVORITES_CONTENT_CONTAINER.id = "favorites-search-gallery-content";
function insertFavoritesContentContainer() {
insertStyleHTML(FAVORITES_CONTENT_HTML);
FAVORITES_SEARCH_GALLERY_CONTAINER.insertAdjacentElement("beforeend", FAVORITES_CONTENT_CONTAINER);
}
var latestSearchResults2 = [];
var infiniteScroll = Preferences.infiniteScrollEnabled.value;
async function loadAllFavoritesFromDatabase2() {
await loadAllFavoritesFromDatabase();
return getAllFavorites2().length === 0 ? [] : getSearchResults("");
}
function fetchAllFavorites3(onSearchResultsFound) {
const onFavoritesFound = (favorites3) => {
latestSearchResults2 = latestSearchResults2.concat(filter(favorites3));
return onSearchResultsFound();
};
return fetchAllFavorites2(onFavoritesFound);
}
async function fetchNewFavoritesOnReload3() {
const newFavorites = await fetchNewFavoritesOnReload2();
const newSearchResults = filter(newFavorites);
latestSearchResults2 = newSearchResults.concat(latestSearchResults2);
return {
newFavorites,
newSearchResults,
allSearchResults: latestSearchResults2
};
}
function storeNewFavorites2(newFavorites) {
return storeNewFavorites(newFavorites);
}
function getAllFavorites2() {
return getAllFavorites();
}
function storeAllFavorites3() {
return storeAllFavorites2();
}
function getLatestSearchResults() {
return latestSearchResults2;
}
function getSearchResults(searchQuery2) {
setSearchQuery(searchQuery2);
return getSearchResultsFromLatestQuery();
}
function getSearchResultsFromLatestQuery() {
const favorites3 = filter(getAllFavorites2());
latestSearchResults2 = sortFavorites(favorites3);
return latestSearchResults2;
}
function getShuffledSearchResults() {
return shuffleArray(latestSearchResults2);
}
function invertSearchResults() {
const searchResultIds = new Set(latestSearchResults2.map((favorite) => favorite.id));
const invertedSearchResults = getAllFavorites2().filter((favorite) => !searchResultIds.has(favorite.id));
const ratingFilteredInvertedSearchResults = filterByRating(invertedSearchResults);
latestSearchResults2 = filterOutBlacklisted(ratingFilteredInvertedSearchResults);
}
function paginate2(searchResults) {
paginate(searchResults);
}
function changePage2(pageNumber) {
changePage(pageNumber);
}
function getFavoritesOnCurrentPage2() {
return getFavoritesOnCurrentPage();
}
function getFavoritesOnNextPage2() {
return getFavoritesOnNextPage();
}
function getFavoritesOnPreviousPage2() {
return getFavoritesOnPreviousPage();
}
function gotoAdjacentPage2(direction) {
return gotoAdjacentPage(direction);
}
function gotoRelativePage2(relation) {
return gotoRelativePage(relation);
}
function gotoPageWithFavoriteId(id) {
return gotoPageWithFavorite(id);
}
function getPaginationParameters2() {
return getPaginationParameters();
}
function onFinalPage2() {
return onFinalPage();
}
function toggleBlacklist(value) {
toggleBlacklistFiltering(value);
}
function changeAllowedRatings(allowedRatings2) {
setAllowedRatings(allowedRatings2);
}
function setSortingMethod2(sortingMethod2) {
setSortingMethod(sortingMethod2);
}
function toggleSortAscending(value) {
setAscendingOrder(value);
}
function updateMetadata(id) {
updateMetadataInDatabase2(id);
}
function changeResultsPerPage2(resultsPerPage2) {
changeResultsPerPage(resultsPerPage2);
}
function getMoreResults2() {
return getMoreResults(latestSearchResults2);
}
function getThumbURLsToPreload2() {
return getThumbURLsToPreload(latestSearchResults2);
}
function getFirstResults2() {
return getFirstResults(latestSearchResults2);
}
function deleteFavorite3(id) {
return deleteFavorite2(id);
}
function deleteDatabase3() {
deleteDatabase2();
}
function usingInfiniteScroll() {
return infiniteScroll;
}
function toggleInfiniteScroll(value) {
infiniteScroll = value;
}
function keepIndexedTagsSorted() {
FAVORITES_SEARCH_INDEX.keepIndexedTagsSorted(true);
}
function buildSearchIndexAsynchronously() {
FAVORITES_SEARCH_INDEX.buildIndexAsynchronously();
}
function buildSearchIndexSynchronously() {
FAVORITES_SEARCH_INDEX.buildIndexSynchronously();
}
function noFavoritesAreVisible() {
return FAVORITES_CONTENT_CONTAINER.querySelector(ITEM_SELECTOR) === null;
}
function fetchMissingMetadata2() {
fetchMissingMetadata();
}
function swapFavoriteButton(id) {
getFavorite(id)?.swapFavoriteButton();
}
function resetTagModifications2() {
getAllFavorites2().forEach((favorite) => {
favorite.resetAdditionalTags();
});
}
var LOCAL_STORAGE_KEY = "aspectRatios";
var ASPECT_RATIOS = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || "[]");
function getAspectRatio(width, height) {
const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
const divisor = gcd(width, height);
return `${width / divisor}/${height / divisor}`;
}
async function collectAspectRatios() {
await waitForAllThumbnailsToLoad();
const thumbs = getAllThumbs();
const images = thumbs.map((thumb) => getImageFromThumb(thumb)).filter((image) => image !== null).slice(0, 50);
const sizes = images.map((image) => getAspectRatio(image.naturalWidth, image.naturalHeight));
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(sizes.reverse()));
}
function getNextAspectRatio() {
return ASPECT_RATIOS.pop();
}
var EMPTY_FAVORITES_PAGINATION_PARAMETERS = {
currentPageNumber: 1,
finalPageNumber: 1,
favoritesCount: 0,
startIndex: 0,
endIndex: 0
};
var CONTAINER = createContainer();
var RANGE_INDICATOR = document.createElement("label");
RANGE_INDICATOR.id = "pagination-range-label";
function createContainer() {
const menu = document.createElement("span");
menu.id = "favorites-pagination-container";
return menu;
}
function insertMenu() {
if (ON_DESKTOP_DEVICE) {
const placeToInsert = document.getElementById("favorites-pagination-placeholder");
if (placeToInsert !== null) {
placeToInsert.insertAdjacentElement("afterend", CONTAINER);
placeToInsert.remove();
}
return;
}
const footerBottom = document.getElementById("mobile-footer-bottom");
if (footerBottom !== null) {
footerBottom.insertAdjacentElement("afterbegin", CONTAINER);
}
}
function insert() {
const matchCountLabel = document.getElementById("match-count-label");
if (matchCountLabel !== null) {
matchCountLabel.insertAdjacentElement("afterend", RANGE_INDICATOR);
}
insertMenu();
}
function create(parameters) {
CONTAINER.innerHTML = "";
updateRangeIndicator(parameters.startIndex, parameters.endIndex, parameters.favoritesCount);
createNumberTraversalButtons(parameters.currentPageNumber, parameters.finalPageNumber);
createArrowTraversalButtons(parameters);
createGotoSpecificPageInputs(parameters.finalPageNumber);
}
function update(parameters) {
const atMaxPageNumberButtons = document.getElementsByClassName("pagination-number").length >= FavoritesSettings.maxPageNumberButtons;
if (atMaxPageNumberButtons) {
return;
}
create(parameters);
}
function updateRangeIndicator(start, end, count) {
end = Math.min(count, end);
RANGE_INDICATOR.textContent = end === 0 ? "" : `${start + 1} - ${end}`;
}
function createNumberTraversalButtons(currentPageNumber4, finalPageNumber) {
const pageNumbers = getNumbersAround(currentPageNumber4, FavoritesSettings.maxPageNumberButtons, 1, finalPageNumber);
for (const pageNumber of pageNumbers) {
createNumberTraversalButton(currentPageNumber4, pageNumber);
}
}
function createNumberTraversalButton(currentPageNumber4, pageNumber) {
const button = document.createElement("button");
const selected = currentPageNumber4 === pageNumber;
button.id = `favorites-page-${pageNumber}`;
button.title = `Goto page ${pageNumber}`;
button.className = "pagination-number";
button.classList.toggle("selected", selected);
button.onclick = () => {
Events.favorites.pageSelected.emit(pageNumber);
};
CONTAINER.appendChild(button);
button.textContent = String(pageNumber);
}
function createArrowTraversalButtons(parameters) {
const previous = createArrowTraversalButton("previous", "<", "afterbegin");
const first = createArrowTraversalButton("first", "<<", "afterbegin");
const next = createArrowTraversalButton("next", ">", "beforeend");
const final = createArrowTraversalButton("final", ">>", "beforeend");
updateArrowTraversalButtonInteractability(previous, first, next, final, parameters);
}
function createArrowTraversalButton(name, textContent, position) {
const button = document.createElement("button");
button.id = `${name}-page`;
button.title = `Goto ${name} page`;
button.textContent = textContent;
button.onclick = () => {
Events.favorites.relativePageSelected.emit(name);
};
CONTAINER.insertAdjacentElement(position, button);
return button;
}
function createGotoSpecificPageInputs(finalPageNumber) {
if (finalPageNumber === 1) {
return;
}
const container4 = document.createElement("span");
const input2 = document.createElement("input");
const button = document.createElement("button");
container4.title = "Goto specific page";
input2.type = "number";
input2.placeholder = "#";
input2.id = "goto-page-input";
button.textContent = "Go";
button.id = "goto-page-button";
button.onclick = () => {
if (isOnlyDigits(input2.value)) {
Events.favorites.pageSelected.emit(Number(input2.value));
}
};
input2.onkeydown = (event) => {
if (event.key === "Enter") {
button.click();
}
};
container4.appendChild(input2);
container4.appendChild(button);
CONTAINER.appendChild(container4);
}
function updateArrowTraversalButtonInteractability(previousPage, firstPage, nextPage, finalPage, parameters) {
if (parameters.currentPageNumber === 1) {
previousPage.disabled = true;
firstPage.disabled = true;
}
if (parameters.currentPageNumber === parameters.finalPageNumber) {
nextPage.disabled = true;
finalPage.disabled = true;
}
}
function toggle(value) {
const html = `
#favorites-pagination-container,
#results-per-page-container,
#favorite-finder,
#pagination-range-label
{
display: none !important;
}
`;
insertStyleHTML(value ? "" : html, "pagination-menu-enable");
}
function setupFavoritesPaginationMenu() {
insert();
create(EMPTY_FAVORITES_PAGINATION_PARAMETERS);
toggle(!Preferences.infiniteScrollEnabled.value);
}
function preloadThumbnails(favorites3) {
preloadImages(favorites3.map((favorite) => favorite.thumbURL));
}
async function preloadImages(urls) {
await waitForAllThumbnailsToLoad();
for (const url of urls) {
await sleep(3);
preloadImage(url);
}
}
function preloadImage(url) {
const img = new Image();
img.src = url;
}
var matchCountIndicator;
var statusIndicator;
var expectedTotalFavoritesCount = null;
var statusTimeout;
var TEMPORARY_STATUS_TIMEOUT = 1e3;
function setStatus(text) {
statusIndicator.classList.remove("hidden");
statusIndicator.textContent = text;
}
function clearStatus() {
statusIndicator.textContent = "";
statusIndicator.classList.add("hidden");
}
function setTemporaryStatus(text) {
setStatus(text);
clearTimeout(statusTimeout);
statusTimeout = setTimeout(clearStatus, TEMPORARY_STATUS_TIMEOUT);
}
function setMatchCount(value) {
matchCountIndicator.textContent = `${value} ${value === 1 ? "Result" : "Results"}`;
}
function updateStatusWhileFetching(searchResultsCount, favoritesFoundCount) {
const prefix = ON_MOBILE_DEVICE ? "" : "Favorites ";
let statusText = `Fetching ${prefix}${favoritesFoundCount}`;
if (expectedTotalFavoritesCount !== null) {
statusText = `${statusText} / ${expectedTotalFavoritesCount}`;
}
setStatus(statusText);
setMatchCount(searchResultsCount);
}
function notifyNewFavoritesFound(newFavoritesCount) {
if (newFavoritesCount > 0) {
const pluralSuffix = newFavoritesCount > 1 ? "s" : "";
setStatus(`Found ${newFavoritesCount} new favorite${pluralSuffix}`);
}
}
async function setExpectedTotalFavoritesCount() {
expectedTotalFavoritesCount = await getFavoritesCount();
}
function setupFavoritesStatus() {
setExpectedTotalFavoritesCount();
matchCountIndicator = FAVORITES_SEARCH_GALLERY_CONTAINER.querySelector("#match-count-label") ?? document.createElement("label");
statusIndicator = FAVORITES_SEARCH_GALLERY_CONTAINER.querySelector("#favorites-load-status-label") ?? document.createElement("label");
}
function getRandomAnimationDelay() {
return roundToTwoDecimalPlaces(randomBetween(0, 0.3));
}
function getRandomAnimationDuration() {
return roundToTwoDecimalPlaces(randomBetween(0.75, 1.5));
}
function getPredictedAspectRatio() {
return getNextAspectRatio() ?? `10/${getSeededRandomPositiveIntegerInRange(5, 20)}`;
}
var SkeletonItem = class {
element;
constructor(style) {
this.element = document.createElement("div");
this.setStyle(style);
}
setStyle(style) {
this.setAspectRatio();
this.setAnimation();
this.setCustomStyle(style);
this.setClassName();
}
setAnimation() {
if (FavoritesSettings.randomSkeletonAnimationTiming) {
this.element.style.setProperty("--skeleton-animation-delay", `${getRandomAnimationDelay()}s`);
this.element.style.setProperty("--skeleton-animation-duration", `${getRandomAnimationDuration()}s`);
}
}
setAspectRatio() {
this.element.style.setProperty("aspect-ratio", getPredictedAspectRatio());
}
setCustomStyle(style) {
for (const [key, value] of Object.entries(style)) {
this.element.style.setProperty(key, value);
}
}
setClassName() {
this.element.className = `skeleton-item ${ITEM_CLASS_NAME} ${FavoritesSettings.skeletonAnimationClasses}`;
}
};
var Skeleton = class {
items;
constructor(style) {
this.items = this.createItems(style);
}
get elements() {
return this.items.map((item) => item.element);
}
get itemCount() {
return Math.min(Preferences.resultsPerPage.value, 200);
}
createItems(style) {
return Array.from({ length: this.itemCount }, () => new SkeletonItem(style));
}
};
var FavoritesBaseTiler = class {
container;
constructor() {
this.container = FAVORITES_CONTENT_CONTAINER;
}
showSkeleton() {
this.tile(new Skeleton(this.skeletonStyle).elements);
}
tile(items) {
const fragment = document.createDocumentFragment();
for (const item of items) {
fragment.appendChild(item);
}
this.container.innerHTML = "";
this.container.appendChild(fragment);
}
setColumnCount(columnCount) {
insertStyleHTML(`
#favorites-search-gallery-content.${this.className} {
grid-template-columns: repeat(${columnCount}, 1fr) !important;
}
`, `${this.className}-column-count`);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setRowSize(rowSize) {
}
addItemsToTop(items) {
for (const item of items.reverse()) {
this.container.insertAdjacentElement("afterbegin", item);
}
}
addItemsToBottom(items) {
for (const item of items) {
this.container.appendChild(item);
}
}
};
var ColumnTiler = class extends FavoritesBaseTiler {
className = "column";
skeletonStyle = {
"width": "100%"
};
columns;
columnCount;
constructor() {
super();
this.columns = [];
this.columnCount = Preferences.columnCount.value;
}
get active() {
return FAVORITES_CONTENT_CONTAINER.classList.contains(this.className);
}
get inactive() {
return !this.active;
}
tile(items) {
this.clearContainer();
this.deleteColumns();
this.createColumns();
this.addItemsToColumns(items);
this.addColumnsToContainer();
this.updateGetAllThumbsImplementation();
}
addItemsToTop(items) {
if (this.active) {
this.onDeactivate();
}
this.tile(items.concat(getAllThumbs()));
}
addItemsToBottom(items) {
if (this.inactive) {
this.tile(items);
return;
}
this.addNewItemsToColumns(items);
}
setColumnCount(columnCount) {
super.setColumnCount(columnCount);
if (columnCount === this.columnCount) {
return;
}
if (this.inactive) {
this.columnCount = columnCount;
return;
}
const items = this.getAllItems();
this.columnCount = columnCount;
this.tile(items);
}
onActivate() {
this.tile(getAllThumbs());
}
onDeactivate() {
const items = this.getAllItems();
this.container.innerHTML = "";
super.tile(items);
resetGetAllThumbsImplementation();
}
createColumns() {
for (let i = 0; i < this.columnCount; i += 1) {
const column = document.createElement("div");
column.classList.add("favorites-column");
this.columns.push(column);
}
}
deleteColumns() {
for (const column of this.columns) {
column.remove();
}
this.columns = [];
}
addItemsToColumns(items) {
for (let i = 0; i < items.length; i += 1) {
this.addItemToColumn(i, items[i]);
}
}
addItemToColumn(itemIndex, item) {
this.columns[itemIndex % this.columnCount].appendChild(item);
}
clearContainer() {
this.container.innerHTML = "";
}
addColumnsToContainer() {
for (const column of this.columns) {
this.container.appendChild(column);
}
}
getAllItems() {
const itemCount = Array.from(document.querySelectorAll(ITEM_SELECTOR)).length;
const result = [];
const matrix = this.columns.map((column) => Array.from(column.querySelectorAll(ITEM_SELECTOR)));
for (let i = 0; i < itemCount; i += 1) {
const column = i % this.columnCount;
const row = Math.floor(i / this.columnCount);
const item = matrix[column][row];
if (item instanceof HTMLElement) {
result.push(item);
}
}
return result;
}
updateGetAllThumbsImplementation() {
changeGetAllTHumbsImplementation(this.getAllItems.bind(this));
}
addNewItemsToColumns(items) {
const columnIndexOffset = this.getIndexOfNextAvailableColumn();
for (let i = 0; i < items.length; i += 1) {
this.addItemToColumn(i + columnIndexOffset, items[i]);
}
}
getIndexOfNextAvailableColumn() {
const columnLengths = this.columns.map((column) => column.children.length);
const minColumnLength = Math.min(...columnLengths);
const firstIndexWithMinimumLength = columnLengths.findIndex((length) => length === minColumnLength);
return firstIndexWithMinimumLength === -1 ? 0 : firstIndexWithMinimumLength;
}
};
var FavoritesColumnTiler = new ColumnTiler();
var GridTiler = class extends FavoritesBaseTiler {
className = "grid";
skeletonStyle = {
"width": "100%"
};
onActivate() {
}
onDeactivate() {
}
};
var FavoritesGridTiler = new GridTiler();
var RowTiler = class extends FavoritesBaseTiler {
className = "row";
skeletonStyle = {};
tile(items) {
super.tile(items);
this.markItemsOnLastRow();
}
addItemsToBottom(items) {
super.addItemsToBottom(items);
this.markItemsOnLastRow();
}
setColumnCount() {
}
setRowSize(rowSize) {
const minWidth = Math.floor(window.innerWidth / 20);
const maxWidth = Math.floor(window.innerWidth / 4);
const pixelSize = Math.round(mapRange(rowSize, FavoritesSettings.rowSizeBounds.min, FavoritesSettings.rowSizeBounds.max, minWidth, maxWidth));
insertStyleHTML(`
#favorites-search-gallery-content.row {
.favorite {
height: ${pixelSize}px;
}
}
`, "row-size");
}
onActivate() {
this.markItemsOnLastRow();
}
onDeactivate() {
}
async markItemsOnLastRow() {
await waitForAllThumbnailsToLoad();
const items = getAllThumbs();
if (items.length === 0) {
return;
}
this.unMarkAllItemsAsLastRow(items);
this.markItemsAsLastRow(this.getItemsOnLastRow(items));
}
unMarkAllItemsAsLastRow(items) {
for (const item of items) {
item.classList.remove("last-row");
}
}
markItemsAsLastRow(items) {
for (const item of items) {
item.classList.add("last-row");
}
}
getItemsOnLastRow(items) {
items = items.slice().reverse();
const itemsOnLastRow = [];
const lastRowY = items[0].offsetTop;
for (const item of items) {
if (item.offsetTop !== lastRowY) {
break;
}
itemsOnLastRow.push(item);
}
return itemsOnLastRow;
}
};
var FavoritesRowTiler = new RowTiler();
var SquareTiler = class extends FavoritesBaseTiler {
className = "square";
skeletonStyle = {
"width": "100%",
"height": "100%",
"aspect-ratio": "1/1"
};
onActivate() {
}
onDeactivate() {
}
};
var FavoritesSquareTiler = new SquareTiler();
var TILERS = [FavoritesGridTiler, FavoritesRowTiler, FavoritesSquareTiler, FavoritesColumnTiler];
var currentLayout = Preferences.favoritesLayout.value;
function getCurrentTiler() {
return TILERS.find((tiler) => tiler.className === currentLayout) || FavoritesRowTiler;
}
function getCurrentLayout() {
return currentLayout;
}
function tile(favorites3) {
getCurrentTiler().tile(favorites3);
}
function addItemsToBottom(favorites3) {
getCurrentTiler().addItemsToBottom(favorites3);
}
function addItemsToTop(favorites3) {
getCurrentTiler().addItemsToTop(favorites3);
}
function changeLayout(layout) {
if (currentLayout === layout) {
return;
}
getCurrentTiler().onDeactivate();
FAVORITES_CONTENT_CONTAINER.className = layout;
currentLayout = layout;
getCurrentTiler().onActivate();
}
function updateColumnCount(columnCount) {
for (const tiler of TILERS) {
tiler.setColumnCount(columnCount);
}
}
function updateRowSize(rowSize) {
for (const tiler of TILERS) {
tiler.setRowSize(rowSize);
}
}
function addStyles() {
const style = `
#favorites-search-gallery-content {
&.row, &.column, &.column .favorites-column, &.square, &.grid {
gap: ${FavoritesSettings.thumbnailSpacing}px;
}
&.column {
margin-right: ${ON_DESKTOP_DEVICE ? FavoritesSettings.rightContentMargin : 0}px;
}
}`;
insertStyleHTML(style, "tiler-style");
}
function showSkeleton() {
getCurrentTiler().showSkeleton();
}
function setupFavoritesTiler() {
showSkeleton();
addStyles();
FAVORITES_CONTENT_CONTAINER.className = currentLayout;
getCurrentTiler().setColumnCount(Preferences.columnCount.value);
getCurrentTiler().setRowSize(Preferences.rowSize.value);
getCurrentTiler().onActivate();
}
function isInGallery() {
if (GALLERY_DISABLED) {
return Promise.resolve(false);
}
return new Promise((resolve) => {
Events.gallery.inGalleryResponse.timeout(10).then((inGallery2) => {
resolve(inGallery2);
}).catch(() => {
resolve(false);
});
Events.favorites.inGalleryRequest.emit();
});
}
function updateShowOnHoverOptionTriggeredFromGallery(value) {
const showOnHoverCheckbox = document.getElementById("show-on-hover");
if (showOnHoverCheckbox !== null && showOnHoverCheckbox instanceof HTMLInputElement) {
showOnHoverCheckbox.checked = value;
Preferences.showOnHoverEnabled.set(value);
}
}
function toggleOptionHotkeyHints(value) {
insertStyleHTML(value ? "" : ".option-hint {display:none;}", "option-hint-visibility");
}
function toggleUI(value) {
const menu = document.getElementById("favorites-search-gallery-menu");
const panels = document.getElementById("favorites-search-gallery-menu-panels");
const header = document.getElementById("header");
const container4 = document.getElementById("show-ui-container");
const bottomPanel3 = document.getElementById("bottom-panel-3");
if (menu === null || panels === null || container4 === null || bottomPanel3 === null) {
return;
}
if (value) {
if (header !== null) {
header.style.display = "";
}
bottomPanel3.insertAdjacentElement("afterbegin", container4);
panels.style.display = "flex";
menu.removeAttribute("style");
} else {
menu.appendChild(container4);
if (header !== null) {
header.style.display = "none";
}
panels.style.display = "none";
menu.style.background = getComputedStyle(document.body).background;
}
container4.classList.toggle("ui-hidden", !value);
}
function toggleFavoritesOptions(value) {
if (ON_MOBILE_DEVICE) {
document.getElementById("left-favorites-panel-bottom-row")?.classList.toggle("hidden", !value);
insertStyleHTML(`
#mobile-button-row {
display: ${value ? "block" : "none"};
}
`, "options");
return;
}
insertStyleHTML(`
.options-container {
display: ${value ? "block" : "none"};
}
`, "options");
}
function toggleAddOrRemoveButtons(value) {
insertStyleHTML(`
.remove-favorite-button, .add-favorite-button {
visibility: ${value ? "visible" : "hidden"} !important;
}
`, "add-or-remove-button-visibility");
}
function toggleDownloadButtons(value) {
insertStyleHTML(`
.download-button {
visibility: ${value ? "visible" : "hidden"} !important;
}
`, "download-button-visibility");
}
function hideUnusedLayoutSizer(layout) {
const rowSizeContainer = document.getElementById("row-size-container");
const columnCountContainer = document.getElementById("column-count-container");
const usingRowLayout = layout === "row";
if (columnCountContainer !== null && rowSizeContainer !== null) {
columnCountContainer.style.display = usingRowLayout ? "none" : "";
rowSizeContainer.style.display = usingRowLayout ? "" : "none";
}
}
function toggleHeader(value) {
insertStyleHTML(`#header {display: ${value ? "block" : "none"}}`, "header");
}
function reloadWindow() {
window.location.reload();
}
async function changeFavoritesSizeOnShiftScroll(wheelEvent) {
if (!wheelEvent.originalEvent.shiftKey) {
return;
}
const usingRowLayout = getCurrentLayout2() === "row";
const id = usingRowLayout ? "row-size" : "column-count";
const input2 = document.getElementById(id);
if (input2 === null || !(input2 instanceof HTMLInputElement)) {
return;
}
const inGallery2 = await isInGallery();
if (inGallery2) {
return;
}
let delta = wheelEvent.isForward ? 1 : -1;
if (usingRowLayout) {
delta = -delta;
}
input2.value = String(parseInt(input2.value) + delta);
input2.dispatchEvent(new KeyboardEvent("keydown", {
key: "Enter",
bubbles: true
}));
input2.dispatchEvent(new Event("change", {
bubbles: true
}));
}
function setStatus2(message) {
setStatus(message);
}
function showTemporaryNotification(message) {
setTemporaryStatus(message);
}
function updateStatusWhileFetching2(searchResultCount, totalFavoritesCount) {
updateStatusWhileFetching(searchResultCount, totalFavoritesCount);
}
function insertNewSearchResults(thumbs) {
addItemsToBottom(thumbs);
}
function insertNewSearchResultsOnReload(results) {
addItemsToTop(results.newSearchResults.map((favorite) => favorite.root));
notifyNewFavoritesFound(results.newFavorites.length);
}
function changeLayout2(layout) {
changeLayout(layout);
}
function updateColumnCount2(columnCount) {
updateColumnCount(columnCount);
}
function updateRowSize2(rowSize) {
updateRowSize(rowSize);
}
function showSearchResults(searchResults) {
tile(searchResults.map((result) => result.root));
scrollToTop();
}
function setMatchCount2(matchCount) {
setMatchCount(matchCount);
}
function createPageSelectionMenu(parameters) {
create(parameters);
}
function createPageSelectionMenuWhileFetching(parameters) {
update(parameters);
}
async function revealFavorite(id) {
await waitForAllThumbnailsToLoad();
const thumb = document.getElementById(id);
if (thumb === null || thumb.classList.contains("blink")) {
return;
}
thumb.scrollIntoView({
behavior: "smooth",
block: "center"
});
thumb.classList.add("blink");
await sleep(1500);
thumb.classList.remove("blink");
}
function togglePaginationMenu(value) {
toggle(value);
}
function setupFavoritesView() {
createFavoriteItemHTMLTemplates();
collectAspectRatios2();
setupFavoritesStatus();
setupFavoritesTiler();
setupFavoritesPaginationMenu();
updateColumnCount2(Preferences.columnCount.value);
toggleAddOrRemoveButtons(USER_IS_ON_THEIR_OWN_FAVORITES_PAGE ? Preferences.removeButtonsVisible.value : Preferences.addButtonsVisible.value);
toggleDownloadButtons(Preferences.downloadButtonsVisible.value);
}
function preloadThumbnails2(favorites3) {
if (GeneralSettings.preloadThumbnails) {
preloadThumbnails(favorites3);
}
}
function preloadURLs(urls) {
if (GeneralSettings.preloadThumbnails) {
preloadImages(urls);
}
}
function getCurrentLayout2() {
return getCurrentLayout();
}
function changeFavoritesSizeUsingWheel(wheelEvent) {
changeFavoritesSizeOnShiftScroll(wheelEvent);
}
function collectAspectRatios2() {
collectAspectRatios();
}
var FavoritesPageBottomObserver = class {
intersectionObserver;
onBottomReached;
constructor(onBottomReached) {
this.onBottomReached = onBottomReached;
this.intersectionObserver = this.createIntersectionObserver();
}
disconnect() {
this.intersectionObserver.disconnect();
}
refresh() {
this.disconnect();
this.observeBottomElements();
}
createIntersectionObserver() {
return new IntersectionObserver(this.onIntersectionChanged.bind(this), {
threshold: [0.1],
rootMargin: `0% 0% ${FavoritesSettings.infiniteScrollMargin} 0%`
});
}
observeBottomElements() {
const bottomElements = Array.from(FAVORITES_CONTENT_CONTAINER.querySelectorAll(`.${ITEM_CLASS_NAME}:last-child`));
for (const element of bottomElements) {
this.intersectionObserver.observe(element);
}
}
onIntersectionChanged(entries) {
for (const entry of entries) {
if (entry.isIntersecting) {
this.onBottomReached();
this.disconnect();
return;
}
}
}
};
var InfiniteScrollFlow = class {
pageBottomObserver;
constructor() {
this.pageBottomObserver = new FavoritesPageBottomObserver(this.showMoreResults.bind(this));
}
present() {
this.showFirstResults();
Events.favorites.pageChanged.emit();
}
onLayoutChanged() {
this.pageBottomObserver.refresh();
}
reset() {
this.pageBottomObserver.disconnect();
}
handleNewSearchResults() {
if (noFavoritesAreVisible()) {
this.showMoreResults();
}
}
revealFavorite() {
}
handlePageChangeRequest() {
}
async showMoreResults() {
const moreResults = getMoreResults2();
if (moreResults.length === 0) {
return;
}
insertNewSearchResults(moreResults);
Events.favorites.resultsAddedToCurrentPage.emit(moreResults);
await waitForAllThumbnailsToLoad();
const urlsToPreload = getThumbURLsToPreload2();
preloadURLs(urlsToPreload);
this.pageBottomObserver.refresh();
}
async showFirstResults() {
showSearchResults(getFirstResults2());
await waitForAllThumbnailsToLoad();
this.pageBottomObserver.refresh();
await sleep(50);
}
};
var FavoritesInfiniteScrollFlow = new InfiniteScrollFlow();
var PaginationFlow = class {
present(results) {
paginate2(results);
changePage2(1);
this.showCurrentPage();
}
gotoPage(pageNumber) {
changePage2(pageNumber);
this.showCurrentPage();
}
gotoRelativePage(relativePage) {
if (gotoRelativePage2(relativePage)) {
this.showCurrentPage();
}
}
showCurrentPage() {
showSearchResults(getFavoritesOnCurrentPage2());
createPageSelectionMenu(getPaginationParameters2());
preloadThumbnails2(getFavoritesOnNextPage2());
preloadThumbnails2(getFavoritesOnPreviousPage2());
Events.favorites.pageChanged.emit();
}
onLayoutChanged() {
}
revealFavorite(id) {
if (gotoPageWithFavoriteId(id)) {
this.showCurrentPage();
}
revealFavorite(id);
}
handlePageChangeRequest(direction) {
this.gotoAdjacentPage(direction);
Events.favorites.pageChangeResponse.emit();
}
reset() {
}
handleNewSearchResults() {
paginate2(getLatestSearchResults());
createPageSelectionMenuWhileFetching(getPaginationParameters2());
this.addNewlyFetchedSearchResultsToCurrentPage();
Events.favorites.searchResultsUpdated.emit(getLatestSearchResults());
}
addNewlyFetchedSearchResultsToCurrentPage() {
if (!onFinalPage2()) {
return;
}
const newFavorites = getFavoritesOnCurrentPage2().filter((favorite) => document.getElementById(favorite.id) === null);
const thumbs = newFavorites.map((favorite) => favorite.root);
insertNewSearchResults(thumbs);
Events.favorites.resultsAddedToCurrentPage.emit(thumbs);
}
gotoAdjacentPage(direction) {
if (gotoAdjacentPage2(direction)) {
this.showCurrentPage();
}
}
};
var FavoritesPaginationFlow = new PaginationFlow();
function getPresentationFlow() {
return usingInfiniteScroll() ? FavoritesInfiniteScrollFlow : FavoritesPaginationFlow;
}
function present(favorites3) {
getPresentationFlow().present(favorites3);
}
function clear() {
getPresentationFlow().present([]);
}
function revealFavorite2(id) {
getPresentationFlow().revealFavorite(id);
}
function handleNewSearchResults() {
getPresentationFlow().handleNewSearchResults();
}
function showSearchResults2(searchResults) {
Events.favorites.searchResultsUpdated.emit(searchResults);
setMatchCount2(searchResults.length);
present(searchResults);
}
function searchFavorites(searchQuery2) {
showSearchResults2(getSearchResults(searchQuery2));
}
function searchFavoritesUsingLatestQuery() {
showSearchResults2(getSearchResultsFromLatestQuery());
}
function showLatestSearchResults() {
showSearchResults2(getLatestSearchResults());
}
function showAllFavorites() {
searchFavorites("");
}
function shuffleSearchResults() {
showSearchResults2(getShuffledSearchResults());
}
function invertSearchResults2() {
invertSearchResults();
showLatestSearchResults();
}
function findFavoriteInAll(id) {
showAllFavorites();
revealFavorite2(id);
}
function changeLayout3(layout) {
changeLayout2(layout);
}
function toggleInfiniteScroll2(value) {
FavoritesInfiniteScrollFlow.reset();
togglePaginationMenu(!value);
toggleInfiniteScroll(value);
showLatestSearchResults();
}
function toggleBlacklist2(value) {
toggleBlacklist(value);
searchFavoritesUsingLatestQuery();
}
function changeSortingMethod(sortingMethod2) {
setSortingMethod2(sortingMethod2);
searchFavoritesUsingLatestQuery();
}
function toggleSortAscending2(value) {
toggleSortAscending(value);
searchFavoritesUsingLatestQuery();
}
function changeAllowedRatings2(ratings) {
changeAllowedRatings(ratings);
searchFavoritesUsingLatestQuery();
}
function changeResultsPerPage3(resultsPerPage2) {
changeResultsPerPage2(resultsPerPage2);
showLatestSearchResults();
}
function onFavoritesLoaded() {
fetchMissingMetadata2();
collectAspectRatios2();
buildSearchIndexAsynchronously();
}
function onStartedFetchingFavorites() {
keepIndexedTagsSorted();
buildSearchIndexSynchronously();
}
function resetFavorites() {
deleteDatabase3();
deleteExtensionsDatabase();
}
function addFavoritesEventsListeners() {
Events.favorites.favoritesLoadedFromDatabase.on(keepIndexedTagsSorted, { once: true });
Events.favorites.startedFetchingFavorites.on(onStartedFetchingFavorites, { once: true });
Events.favorites.favoritesLoaded.on(onFavoritesLoaded, { once: true });
Events.favorites.searchStarted.on(searchFavorites);
Events.favorites.shuffleButtonClicked.on(shuffleSearchResults);
Events.favorites.invertButtonClicked.on(invertSearchResults2);
Events.favorites.pageSelected.on(FavoritesPaginationFlow.gotoPage.bind(FavoritesPaginationFlow));
Events.favorites.relativePageSelected.on(FavoritesPaginationFlow.gotoRelativePage.bind(FavoritesPaginationFlow));
Events.favorites.resetConfirmed.on(resetFavorites);
Events.favorites.favoriteRemoved.on(deleteFavorite3);
Events.favorites.missingMetadataFound.on(updateMetadata);
Events.favorites.findFavoriteStarted.on(revealFavorite2);
Events.favorites.findFavoriteInAllStarted.on(findFavoriteInAll);
Events.gallery.pageChangeRequested.on(FavoritesPaginationFlow.handlePageChangeRequest.bind(FavoritesPaginationFlow));
Events.gallery.showOnHoverToggled.on(updateShowOnHoverOptionTriggeredFromGallery);
Events.gallery.favoriteToggled.on(swapFavoriteButton);
Events.tagModifier.resetConfirmed.on(resetTagModifications2);
Events.favorites.infiniteScrollToggled.on(toggleInfiniteScroll2);
Events.favorites.blacklistToggled.on(toggleBlacklist2);
Events.favorites.layoutChanged.on(changeLayout3);
Events.favorites.columnCountChanged.on(updateColumnCount2);
Events.favorites.rowSizeChanged.on(updateRowSize2);
Events.favorites.sortAscendingToggled.on(toggleSortAscending2);
Events.favorites.sortingMethodChanged.on(changeSortingMethod);
Events.favorites.allowedRatingsChanged.on(changeAllowedRatings2);
Events.favorites.resultsPerPageChanged.on(changeResultsPerPage3);
Events.document.wheel.on(changeFavoritesSizeUsingWheel);
}
function createFooter() {
const footer = document.createElement("div");
const footerHeader = document.createElement("div");
const footerTop = document.createElement("div");
const footerBottom = document.createElement("div");
footer.id = "mobile-footer";
footerHeader.id = "mobile-footer-header";
footerTop.id = "mobile-footer-top";
footerBottom.id = "mobile-footer-bottom";
footer.className = "dark-green-gradient";
footer.appendChild(footerHeader);
footer.appendChild(footerTop);
footer.appendChild(footerBottom);
FAVORITES_SEARCH_GALLERY_CONTAINER.appendChild(footer);
}
function moveStatusToFooter() {
const status = document.getElementById("favorites-load-status");
const footerTop = document.getElementById("mobile-footer-top");
if (status === null || footerTop === null) {
return;
}
footerTop.appendChild(status);
}
async function createControlsGuide() {
insertHTMLAndExtractStyle(FAVORITES_SEARCH_GALLERY_CONTAINER, "beforeend", CONTROLS_HTML);
const controlGuide = document.getElementById("controls-guide");
if (controlGuide === null) {
return;
}
const anchor = document.createElement("a");
anchor.textContent = "Controls";
anchor.href = "#";
anchor.onmousedown = (event) => {
event.preventDefault();
event.stopPropagation();
controlGuide.classList.toggle("active", true);
};
controlGuide.ontouchstart = (event) => {
event.preventDefault();
event.stopPropagation();
controlGuide.classList.toggle("active", false);
};
await sleep(0);
const helpLinksContainer = document.getElementById("help-links-container");
if (helpLinksContainer === null) {
return;
}
helpLinksContainer.insertAdjacentElement("afterbegin", anchor);
controlGuide.onmousedown = () => {
controlGuide.classList.toggle("active", false);
};
}
var DEFAULT_MENU_ELEMENT = {
parentId: "",
id: "",
enabled: true,
title: "bruh",
position: "afterbegin",
textContent: ""
};
function createCheckboxTemplate(partial) {
return {
...DEFAULT_MENU_ELEMENT,
savePreference: true,
event: null,
defaultValue: false,
hotkey: "",
function: DO_NOTHING,
preference: null,
triggerOnCreation: false,
...partial
};
}
function createCheckboxElement(partial) {
const template2 = createCheckboxTemplate(partial);
const parent = document.getElementById(template2.parentId);
if (parent === null) {
return;
}
const checkbox = document.createElement("input");
checkbox.id = template2.id;
checkbox.type = "checkbox";
checkbox.checked = template2.preference === null ? template2.defaultValue : template2.preference.value;
const onChange = () => {
if (template2.event !== null) {
template2.event.emit(checkbox.checked);
}
if (template2.savePreference && template2.preference !== null) {
template2.preference.set(checkbox.checked);
}
template2.function(checkbox.checked);
};
if (template2.triggerOnCreation) {
onChange();
}
checkbox.addEventListener("change", onChange);
parent.insertAdjacentElement(template2.position, checkbox);
if (template2.hotkey === "") {
return;
}
Events.document.keydown.on(async (event) => {
if (!event.isHotkey || event.key.toLowerCase() !== template2.hotkey.toLowerCase()) {
return;
}
const inGallery2 = await isInGallery();
if (inGallery2) {
return;
}
checkbox.checked = !checkbox.checked;
onChange();
});
}
function createToggleSwitch(partial) {
const template2 = createCheckboxTemplate(partial);
const parent = document.getElementById(template2.parentId);
if (parent === null) {
return;
}
const toggleSwitchId = `${template2.id}-toggle-switch`;
const switchHTML = `
<label id="${toggleSwitchId}" class="toggle-switch" title="${template2.title}">
<span class="slider round"></span>
<span class="toggle-switch-label"> ${template2.textContent}</span>
</label>`;
parent.insertAdjacentHTML(template2.position, switchHTML);
template2.position = "afterbegin";
template2.parentId = toggleSwitchId;
createCheckboxElement(template2);
const checkbox = document.getElementById(template2.id);
if (checkbox !== null) {
checkbox.style.width = "0";
checkbox.style.height = "0";
checkbox.style.opacity = "0";
}
}
function createCheckboxOption(partial) {
const parent = document.getElementById(partial.parentId || "not-an-id");
if (parent === null) {
return;
}
const container4 = document.createElement("div");
const label = document.createElement("label");
const span = document.createElement("span");
const hint = document.createElement("span");
const labelId = `${partial.id}-label`;
container4.id = `${partial.id}-container`;
label.id = labelId;
label.className = "checkbox";
label.title = partial.title ?? "";
span.textContent = ` ${partial.textContent ?? "Missing text"}`;
hint.className = "option-hint";
hint.textContent = ` (${partial.hotkey ?? "Missing hotkey"})`;
container4.appendChild(label);
label.appendChild(span);
if (partial.hotkey !== "" && partial.hotkey !== void 0) {
label.appendChild(hint);
}
parent.insertAdjacentElement(partial.position ?? "afterbegin", container4);
partial.parentId = labelId;
createCheckboxElement(partial);
}
function createButtonTemplate(partial) {
return {
...DEFAULT_MENU_ELEMENT,
event: null,
hotkey: "",
function: DO_NOTHING,
triggerOnCreation: false,
rightClickEnabled: false,
...partial
};
}
function createButtonElement(partial) {
const template2 = createButtonTemplate(partial);
const parent = document.getElementById(template2.parentId);
if (!template2.enabled || parent === null) {
return;
}
const button = document.createElement("button");
parent.insertAdjacentElement(template2.position, button);
button.id = template2.id;
button.title = template2.title;
button.textContent = template2.textContent;
if (template2.event === null) {
return;
}
const eventEmitter = template2.event;
button.onclick = (event) => {
template2.function(event);
eventEmitter.emit(event);
};
if (template2.rightClickEnabled) {
button.oncontextmenu = (event) => {
eventEmitter.emit(event);
};
}
}
var HoldButton = class _HoldButton extends HTMLElement {
static defaultPollingTime = 100;
static minPollingTime = 40;
static maxPollingTime = 500;
intervalId;
timeoutId;
pollingTime = _HoldButton.defaultPollingTime;
holdingDown = false;
static {
customElements.define("hold-button", _HoldButton);
}
connectedCallback() {
if (ON_MOBILE_DEVICE) {
return;
}
this.addEventListeners();
this.initializePollingTime();
}
attributeChangedCallback(name, _, newValue) {
switch (name) {
case "pollingtime":
this.setPollingTime(newValue);
break;
default:
break;
}
}
onmousehold() {
}
onMouseLeaveWhileHoldingDown() {
}
initializePollingTime() {
const pollingTime = this.getAttribute("pollingtime");
if (pollingTime !== null) {
this.setPollingTime(pollingTime);
}
}
setPollingTime(newValue) {
this.stopPolling();
const pollingTime = parseFloat(newValue) ?? _HoldButton.defaultPollingTime;
this.pollingTime = clamp(Math.round(pollingTime), _HoldButton.minPollingTime, _HoldButton.maxPollingTime);
}
addEventListeners() {
this.addEventListener("mousedown", (event) => {
if (event.button === 0) {
this.holdingDown = true;
this.startPolling();
}
});
this.addEventListener("mouseup", (event) => {
if (event.button === 0) {
this.holdingDown = false;
this.stopPolling();
}
});
this.addEventListener("mouseleave", () => {
if (this.holdingDown) {
this.onMouseLeaveWhileHoldingDown();
this.holdingDown = false;
}
this.stopPolling();
});
}
startPolling() {
this.timeoutId = setTimeout(() => {
this.intervalId = setInterval(() => {
this.onmousehold();
}, this.pollingTime);
}, this.pollingTime);
}
stopPolling() {
clearTimeout(this.timeoutId);
clearInterval(this.intervalId);
}
};
var NumberComponent = class {
input;
upArrow;
downArrow;
stepSize;
range;
defaultValue;
constructor(element) {
this.input = element.querySelector("input") ?? document.createElement("input");
this.upArrow = element.querySelector(".number-arrow-up") ?? new HoldButton();
this.downArrow = element.querySelector(".number-arrow-down") ?? new HoldButton();
this.stepSize = 1;
this.range = { min: 0, max: 100 };
this.defaultValue = 1;
this.initializeFields();
this.addEventListeners();
}
initializeFields() {
this.stepSize = Math.round(parseFloat(this.input.getAttribute("step") ?? "1"));
if (this.input.onchange === null) {
this.input.onchange = () => {
};
}
this.range = {
min: parseFloat(this.input.getAttribute("min") ?? "0"),
max: parseFloat(this.input.getAttribute("max") ?? "100")
};
this.defaultValue = parseFloat(this.input.getAttribute("defaultValue") ?? "1");
this.setValue(this.defaultValue);
}
addEventListeners() {
this.upArrow.onmousehold = () => {
this.increment();
};
this.downArrow.onmousehold = () => {
this.decrement();
};
this.upArrow.addEventListener("mousedown", (event) => {
if (event.button === 0) {
this.increment();
}
});
this.downArrow.addEventListener("mousedown", (event) => {
if (event.button === 0) {
this.decrement();
}
});
this.upArrow.addEventListener("mouseup", () => {
this.onChange();
});
this.downArrow.addEventListener("mouseup", () => {
this.onChange();
});
this.upArrow.onMouseLeaveWhileHoldingDown = () => {
this.onChange();
};
this.downArrow.onMouseLeaveWhileHoldingDown = () => {
this.onChange();
};
this.input.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
this.setValue(Number(this.input.value));
}
});
}
onChange() {
this.input.dispatchEvent(new Event("change"));
}
increment() {
this.setValue(this.getSnapMax(this.getSanitizedValue()));
}
decrement() {
const value = this.getSanitizedValue();
if (value % this.stepSize === 0) {
this.setValue(value - this.stepSize);
return;
}
this.setValue(this.getSnapMin(value));
}
setValue(value) {
this.input.value = String(clamp(value, this.range.min, this.range.max));
}
getSnapMin(value) {
return Math.floor(value / this.stepSize) * this.stepSize;
}
getSnapMax(value) {
return this.getSnapMin(value) + this.stepSize;
}
getSanitizedValue() {
const value = parseFloat(this.input.value);
return isNaN(value) ? this.range.min : value;
}
};
function createNumberTemplate(partial) {
return {
...DEFAULT_MENU_ELEMENT,
savePreference: false,
event: null,
function: DO_NOTHING,
triggerOnCreation: false,
preference: null,
min: 0,
max: 100,
step: 1,
defaultValue: 10,
pollingTime: 50,
...partial
};
}
function createNumberComponent(partial) {
const template2 = createNumberTemplate(partial);
const parent = document.getElementById(template2.parentId);
if (parent === null) {
return;
}
const numberComponentId = `${template2.id}-number`;
const defaultValue = template2.preference === null ? 1 : template2.preference.value;
const html = `
<span class="number" id="${numberComponentId}">
<hold-button class="number-arrow-down" pollingtime="${template2.pollingTime}">
<span><</span>
</hold-button>
<input id="${template2.id}" type="number" min="${template2.min}" max="${template2.max}" step="${template2.step}" defaultValue="${defaultValue}">
<hold-button class="number-arrow-up" pollingtime="${template2.pollingTime}">
<span>></span>
</hold-button>
</span>
`;
parent.insertAdjacentHTML(template2.position, html);
const element = document.getElementById(numberComponentId);
if (element === null) {
return;
}
const numberComponent = new NumberComponent(element);
const numberInput = numberComponent.input;
const emitEvent = () => {
const value = parseFloat(numberInput.value);
if (template2.event !== null) {
template2.event.emit(value);
}
if (template2.preference !== null) {
template2.preference.set(value);
}
};
if (numberInput === null) {
return;
}
numberInput.value = String(defaultValue);
numberInput.dispatchEvent(new KeyboardEvent("keydown", {
key: "Enter",
bubbles: true
}));
if (template2.triggerOnCreation) {
Events.document.postProcess.on(() => {
emitEvent();
});
}
numberInput.onchange = () => {
emitEvent();
template2.preference?.set(parseFloat(numberInput.value));
};
}
function createSelectTemplate(partial) {
return {
...DEFAULT_MENU_ELEMENT,
options: {},
savePreference: false,
defaultValue: "",
event: null,
hotkey: "",
function: DO_NOTHING,
triggerOnCreation: false,
preference: null,
isNumeric: false,
...partial
};
}
function createSelectElement(partial) {
const template2 = createSelectTemplate(partial);
const parent = document.getElementById(template2.parentId);
if (parent === null) {
return;
}
const select2 = document.createElement("select");
select2.id = template2.id;
select2.title = template2.title;
for (const [value, text] of Object.entries(template2.options)) {
const option = document.createElement("option");
option.id = `${template2.id}-${value}`;
option.value = value;
option.textContent = text;
select2.appendChild(option);
}
parent.insertAdjacentElement(template2.position, select2);
const onChange = () => {
const value = template2.isNumeric ? Number(select2.value) : select2.value;
if (template2.event !== null) {
template2.event.emit(value);
}
if (template2.preference !== null) {
template2.preference.set(value);
}
template2.function(value);
};
if (template2.preference === null) {
select2.value = Object.keys(template2.options)[0];
} else {
select2.value = String(template2.preference.value);
}
select2.onchange = onChange;
}
function prepareDynamicElements(elements) {
return elements.reverse().filter((element) => element.enabled !== false);
}
var PERSISTENT_LOCAL_STORAGE_KEYS = /* @__PURE__ */ new Set(["customTags", "savedSearches"]);
var DESKTOP_RESET_PROMPT_SUFFIX = "\nTag modifications and saved searches will be preserved.";
var RESET_PROMPT = `Are you sure you want to reset? This will delete all cached favorites, and preferences.${ON_MOBILE_DEVICE ? "" : DESKTOP_RESET_PROMPT_SUFFIX}`;
function clearLocalStorage() {
Object.keys(localStorage).forEach((key) => {
if (!PERSISTENT_LOCAL_STORAGE_KEYS.has(key)) {
localStorage.removeItem(key);
}
});
}
function tryResetting() {
if (confirm(RESET_PROMPT)) {
clearLocalStorage();
Events.favorites.resetConfirmed.emit();
}
}
var BUTTONS = [
{
id: "search-button",
parentId: "left-favorites-panel-top-row",
title: "Search favorites\nctrl+click/right-click: Search all of rule34 in a new tab",
position: "afterbegin",
textContent: "Search",
rightClickEnabled: true,
event: Events.favorites.searchButtonClicked
},
{
id: "shuffle-button",
parentId: "left-favorites-panel-top-row",
textContent: "Shuffle",
title: "Randomize order of search results",
event: Events.favorites.shuffleButtonClicked
},
{
id: "invert-button",
parentId: "left-favorites-panel-top-row",
textContent: "Invert",
title: "Show results not matched by latest search",
event: Events.favorites.invertButtonClicked
},
{
id: "clear-button",
parentId: "left-favorites-panel-top-row",
textContent: "Clear",
title: "Empty the search box",
event: Events.favorites.clearButtonClicked
},
{
id: "download-button",
parentId: "left-favorites-panel-top-row",
textContent: "Download",
title: "Download search results (experimental)",
event: Events.favorites.downloadButtonClicked
},
{
id: "subset-button",
parentId: "left-favorites-panel-top-row",
textContent: "Set Subset",
title: "Make the current search results the entire set of results to search from",
enabled: false,
event: Events.favorites.searchSubsetClicked
},
{
id: "stop-subset-button",
parentId: "left-favorites-panel-top-row",
textContent: "Stop Subset",
title: "Stop subset and return entire set of results to all favorites",
enabled: false,
event: Events.favorites.stopSearchSubsetClicked
},
{
id: "reset-button",
parentId: "left-favorites-panel-top-row",
textContent: "Reset",
title: "Delete cached favorites and reset preferences",
function: tryResetting,
event: Events.favorites.resetButtonClicked
}
];
var CHECKBOXES = [
{
id: "options",
parentId: "bottom-panel-1",
textContent: "More Options",
title: "Show more options",
preference: Preferences.optionsVisible,
hotkey: "O",
function: toggleFavoritesOptions,
triggerOnCreation: true,
event: Events.favorites.optionsToggled
},
{
id: "show-ui",
parentId: "show-ui-wrapper",
textContent: "UI",
title: "Toggle UI",
preference: Preferences.uiVisible,
hotkey: "U",
function: toggleUI,
triggerOnCreation: true,
event: Events.favorites.uiToggled
},
{
id: "enhance-search-pages",
parentId: "favorite-options-left",
textContent: "Enhance Search Pages",
title: "Enable gallery and other features on search pages",
preference: Preferences.searchPagesEnabled,
hotkey: "",
savePreference: true
},
{
id: "infinite-scroll",
parentId: "favorite-options-left",
textContent: "Infinite Scroll",
title: "Use infinite scroll (waterfall) instead of pages",
preference: Preferences.infiniteScrollEnabled,
hotkey: "",
event: Events.favorites.infiniteScrollToggled
},
{
id: "show-remove-favorite-buttons",
parentId: "favorite-options-left",
textContent: "Remove Buttons",
title: "Toggle remove favorite buttons",
enabled: USER_IS_ON_THEIR_OWN_FAVORITES_PAGE,
preference: Preferences.removeButtonsVisible,
hotkey: "R",
function: toggleAddOrRemoveButtons,
event: Events.favorites.removeButtonsToggled
},
{
id: "show-add-favorite-buttons",
parentId: "favorite-options-left",
textContent: "Add Favorite Buttons",
title: "Toggle add favorite buttons",
enabled: !USER_IS_ON_THEIR_OWN_FAVORITES_PAGE,
preference: Preferences.addButtonsVisible,
function: toggleAddOrRemoveButtons,
hotkey: "R",
event: Events.favorites.addButtonsToggled
},
{
id: "show-download-buttons",
parentId: "favorite-options-left",
textContent: "Download Buttons",
title: "Toggle download buttons",
enabled: true,
preference: Preferences.downloadButtonsVisible,
hotkey: "",
function: toggleDownloadButtons,
event: Events.favorites.downloadButtonsToggled
},
{
id: "exclude-blacklist",
parentId: "favorite-options-left",
textContent: "Exclude Blacklist",
title: "Exclude favorites with blacklisted tags from search",
enabled: USER_IS_ON_THEIR_OWN_FAVORITES_PAGE,
preference: Preferences.excludeBlacklistEnabled,
hotkey: "",
event: Events.favorites.blacklistToggled
},
{
id: "fancy-thumb-hovering",
parentId: "favorite-options-left",
textContent: "Fancy Hovering",
title: "Enable fancy thumbnail hovering",
preference: Preferences.fancyThumbHoveringEnabled,
enabled: false,
hotkey: "",
event: Events.favorites.fancyHoveringToggled
},
{
id: "show-hints",
parentId: "favorite-options-left",
textContent: "Hotkey Hints",
title: "Show hotkeys",
preference: Preferences.hintsEnabled,
hotkey: "H",
event: Events.favorites.hintsToggled,
triggerOnCreation: true,
function: toggleOptionHotkeyHints
},
{
id: "enable-autoplay",
parentId: "favorite-options-right",
textContent: "Autoplay",
title: "Enable autoplay in gallery",
enabled: GALLERY_ENABLED,
preference: Preferences.autoplayActive,
hotkey: "",
event: Events.favorites.autoplayToggled
},
{
id: "show-on-hover",
parentId: "favorite-options-right",
textContent: "Fullscreen on Hover",
title: "View full resolution images or play videos and GIFs when hovering over a thumbnail",
enabled: GALLERY_ENABLED,
preference: Preferences.showOnHoverEnabled,
hotkey: "",
event: Events.favorites.showOnHoverToggled
},
{
id: "show-tooltips",
parentId: "favorite-options-right",
textContent: "Tooltips",
title: "Show tags when hovering over a thumbnail and see which ones were matched by a search",
enabled: TOOLTIP_ENABLED,
preference: Preferences.tooltipsVisible,
hotkey: "T",
event: Events.favorites.tooltipsToggled
},
{
id: "show-captions",
parentId: "favorite-options-right",
textContent: "Details",
title: "Show details when hovering over thumbnail",
enabled: CAPTIONS_ENABLED,
preference: Preferences.captionsVisible,
hotkey: "D",
event: Events.favorites.captionsToggled
},
{
id: "toggle-header",
parentId: "favorite-options-right",
textContent: "Header",
title: "Toggle site header",
preference: Preferences.headerEnabled,
hotkey: "",
event: Events.favorites.headerToggled,
triggerOnCreation: true,
function: toggleHeader
},
{
id: "dark-theme",
parentId: "favorite-options-right",
textContent: "Dark Theme",
title: "Toggle dark theme",
defaultValue: usingDarkTheme(),
hotkey: "",
event: Events.favorites.darkThemeToggled,
function: toggleDarkTheme
},
{
id: "use-aliases",
parentId: "favorite-options-right",
textContent: "Aliases",
title: "Alias similar tags",
enabled: false,
preference: Preferences.tagAliasingEnabled,
hotkey: "A"
},
{
id: "show-saved-search-suggestions",
parentId: "favorite-options-right",
textContent: "Saved Suggestions",
title: "Show saved search suggestions in autocomplete dropdown",
enabled: false,
preference: Preferences.savedSearchSuggestionsEnabled,
hotkey: "",
savePreference: true
},
{
id: "show-saved-searches",
parentId: "bottom-panel-2",
textContent: "Saved Searches",
title: "Show saved searches",
enabled: true,
preference: Preferences.savedSearchesVisible,
event: Events.favorites.savedSearchesToggled
},
{
id: "enable-gallery-menu",
parentId: "favorite-options-left",
textContent: "Gallery Menu",
title: "Show menu in gallery",
enabled: GALLERY_ENABLED && GeneralSettings.galleryMenuOptionEnabled,
function: toggleGalleryMenuEnabled,
preference: Preferences.galleryMenuEnabled,
event: Events.favorites.galleryMenuToggled
}
];
var SIMPLE_CHECKBOXES = [
{
id: "sort-ascending",
parentId: "sort-inputs",
position: "beforeend",
preference: Preferences.sortAscendingEnabled,
event: Events.favorites.sortAscendingToggled
}
];
var SELECTS = [
{
id: "sorting-method",
parentId: "sort-inputs",
title: "Change sorting order of search results",
position: "beforeend",
preference: Preferences.sortingMethod,
event: Events.favorites.sortingMethodChanged,
options: {
default: "Default",
score: "Score",
width: "Width",
height: "Height",
creationTimestamp: "Date Uploaded",
lastChangedTimestamp: "Date Changed",
id: "ID",
random: "Random"
}
},
{
id: "layout-select",
parentId: "layout-container",
title: "Change layout",
position: "beforeend",
preference: Preferences.favoritesLayout,
event: Events.favorites.layoutChanged,
function: hideUnusedLayoutSizer,
options: {
column: "Waterfall",
row: "River",
square: "Square",
grid: "Legacy"
}
},
{
id: "performance-profile",
parentId: "performance-profile-container",
title: "Improve performance by disabling features",
position: "beforeend",
preference: Preferences.performanceProfile,
event: Events.favorites.performanceProfileChanged,
function: reloadWindow,
isNumeric: true,
options: {
0: "Normal",
1: "Low (no gallery)",
2: "Potato (only search)"
}
}
];
var NUMBERS = [
{
id: "column-count",
parentId: "column-count-container",
position: "beforeend",
preference: Preferences.columnCount,
min: FavoritesSettings.columnCountBounds.min,
max: FavoritesSettings.columnCountBounds.max,
step: 1,
pollingTime: 50,
triggerOnCreation: true,
event: Events.favorites.columnCountChanged
},
{
id: "row-size",
parentId: "row-size-container",
position: "beforeend",
preference: Preferences.rowSize,
min: FavoritesSettings.rowSizeBounds.min,
max: FavoritesSettings.rowSizeBounds.max,
step: 1,
pollingTime: 50,
triggerOnCreation: true,
event: Events.favorites.rowSizeChanged
},
{
id: "results-per-page",
parentId: "results-per-page-container",
position: "beforeend",
preference: Preferences.resultsPerPage,
min: FavoritesSettings.resultsPerPageBounds.min,
max: FavoritesSettings.resultsPerPageBounds.max,
step: FavoritesSettings.resultsPerPageStep,
pollingTime: 50,
triggerOnCreation: false,
event: Events.favorites.resultsPerPageChanged
}
];
function createButtons() {
for (const button of prepareDynamicElements(BUTTONS)) {
createButtonElement(button);
}
}
function createCheckboxes() {
for (const checkbox of prepareDynamicElements(CHECKBOXES)) {
createCheckboxOption(checkbox);
}
}
function createSelects() {
for (const select2 of prepareDynamicElements(SELECTS)) {
createSelectElement(select2);
}
}
function createNumbers() {
for (const number of prepareDynamicElements(NUMBERS)) {
createNumberComponent(number);
}
}
function createSimpleCheckboxes() {
for (const checkbox of prepareDynamicElements(SIMPLE_CHECKBOXES)) {
createCheckboxElement(checkbox);
}
}
function createDynamicFavoritesDesktopMenuElements() {
createButtons();
createCheckboxes();
createSimpleCheckboxes();
createSelects();
createNumbers();
hideUnusedLayoutSizer(Preferences.favoritesLayout.value);
}
var BUTTONS2 = [
{
id: "download-button",
parentId: "additional-favorite-options",
textContent: "Download",
title: "Download search results",
event: Events.favorites.downloadButtonClicked,
position: "beforeend"
}
];
var TOGGLE_SWITCHES = [
{
id: "infinite-scroll",
parentId: "favorite-options-left",
textContent: "Infinite Scroll",
title: "Use infinite scroll (waterfall) instead of pages",
preference: Preferences.infiniteScrollEnabled,
hotkey: "",
event: Events.favorites.infiniteScrollToggled
},
{
id: "show-remove-favorite-buttons",
parentId: "favorite-options-left",
textContent: "Remove Buttons",
title: "Toggle remove favorite buttons",
enabled: USER_IS_ON_THEIR_OWN_FAVORITES_PAGE,
preference: Preferences.removeButtonsVisible,
hotkey: "R",
function: toggleAddOrRemoveButtons,
event: Events.favorites.removeButtonsToggled
},
{
id: "show-add-favorite-buttons",
parentId: "favorite-options-left",
textContent: "Add Favorite Buttons",
title: "Toggle add favorite buttons",
enabled: !USER_IS_ON_THEIR_OWN_FAVORITES_PAGE,
preference: Preferences.addButtonsVisible,
function: toggleAddOrRemoveButtons,
hotkey: "R",
event: Events.favorites.addButtonsToggled
},
{
id: "show-download-buttons",
parentId: "favorite-options-left",
textContent: "Download Buttons",
title: "Toggle download buttons",
enabled: true,
preference: Preferences.downloadButtonsVisible,
hotkey: "",
function: toggleDownloadButtons,
event: Events.favorites.downloadButtonsToggled
},
{
id: "exclude-blacklist",
parentId: "favorite-options-left",
textContent: "Exclude Blacklist",
title: "Exclude favorites with blacklisted tags from search",
enabled: USER_IS_ON_THEIR_OWN_FAVORITES_PAGE,
preference: Preferences.excludeBlacklistEnabled,
hotkey: "",
event: Events.favorites.blacklistToggled
},
{
id: "enable-autoplay",
parentId: "favorite-options-left",
textContent: "Autoplay",
title: "Enable autoplay in gallery",
enabled: GALLERY_ENABLED,
preference: Preferences.autoplayActive,
hotkey: "",
event: Events.favorites.autoplayToggled
},
{
id: "toggle-header",
parentId: "favorite-options-left",
textContent: "Header",
title: "Toggle site header",
preference: Preferences.headerEnabled,
hotkey: "",
enabled: false,
event: Events.favorites.headerToggled,
triggerOnCreation: true,
function: toggleHeader
},
{
id: "dark-theme",
parentId: "favorite-options-left",
textContent: "Dark Theme",
title: "Toggle dark theme",
defaultValue: usingDarkTheme(),
hotkey: "",
event: Events.favorites.darkThemeToggled,
function: toggleDarkTheme
},
{
id: "enhance-search-pages",
parentId: "favorite-options-left",
textContent: "Search Page Gallery",
title: "Enable gallery and other features on search pages",
preference: Preferences.searchPagesEnabled,
hotkey: "",
savePreference: true
},
{
id: "sort-ascending",
parentId: "sort-inputs",
position: "beforeend",
enabled: true,
preference: Preferences.sortAscendingEnabled,
event: Events.favorites.sortAscendingToggled
},
{
id: "mobile-gallery",
parentId: "favorite-options-left",
textContent: "Gallery",
title: "Enable gallery",
position: "beforeend",
enabled: true,
preference: Preferences.mobileGalleryEnabled
}
];
var SELECTS2 = [
{
id: "sorting-method",
parentId: "sort-inputs",
title: "Change sorting order of search results",
position: "beforeend",
preference: Preferences.sortingMethod,
event: Events.favorites.sortingMethodChanged,
options: {
default: "Default",
score: "Score",
width: "Width",
height: "Height",
creationTimestamp: "Date Uploaded",
lastChangedTimestamp: "Date Changed",
id: "ID",
random: "Random"
}
},
{
id: "layout-select",
parentId: "layout-container",
title: "Change layout",
position: "beforeend",
preference: Preferences.favoritesLayout,
event: Events.favorites.layoutChanged,
function: hideUnusedLayoutSizer,
options: {
column: "Waterfall",
row: "River",
square: "Square",
grid: "Legacy"
}
},
{
id: "results-per-page",
parentId: "results-per-page-container",
title: "Change results per page",
position: "beforeend",
triggerOnCreation: true,
preference: Preferences.resultsPerPage,
event: Events.favorites.resultsPerPageChanged,
options: {
5: "5",
10: "10",
20: "20",
50: "50",
100: "100",
200: "200",
500: "500",
1e3: "1000"
}
},
{
id: "column-count",
parentId: "column-count-container",
position: "beforeend",
preference: Preferences.columnCount,
triggerOnCreation: true,
event: Events.favorites.columnCountChanged,
options: {
1: "1",
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
9: "9",
10: "10"
}
},
{
id: "row-size",
parentId: "row-size-container",
position: "beforeend",
preference: Preferences.rowSize,
triggerOnCreation: true,
event: Events.favorites.rowSizeChanged,
options: {
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7"
}
}
];
function createButtons2() {
for (const button of prepareDynamicElements(BUTTONS2)) {
createButtonElement(button);
}
}
function createToggleSwitches() {
for (const checkbox of prepareDynamicElements(TOGGLE_SWITCHES)) {
createToggleSwitch(checkbox);
}
}
function createSelects2() {
for (const select2 of prepareDynamicElements(SELECTS2)) {
createSelectElement(select2);
}
}
function createDynamicFavoritesMobileMenuElements() {
createSelects2();
createToggleSwitches();
hideUnusedLayoutSizer(Preferences.favoritesLayout.value);
createButtons2();
}
var dialog2;
function insertHelpHTML() {
const parent = document.getElementById(ON_MOBILE_DEVICE ? "mobile-footer-header" : "left-favorites-panel-top-row");
if (parent !== null) {
parent.insertAdjacentHTML("beforeend", HELP_HTML);
}
}
function createWhatsNewMenu() {
const whatsNew = document.getElementById("whats-new-link");
if (whatsNew === null) {
return;
}
if (ON_MOBILE_DEVICE) {
whatsNew.remove();
return;
}
createDialogWhatsNewMenu(whatsNew);
}
function createDialogWhatsNewMenu(menu) {
dialog2 = document.createElement("dialog");
dialog2.style.padding = "5px 10px";
dialog2.style.fontSize = "large";
dialog2.classList.add(getCurrentThemeClass());
FAVORITES_SEARCH_GALLERY_CONTAINER.appendChild(dialog2);
const whatsNewContainer = menu.querySelector("#whats-new-container");
if (whatsNewContainer === null) {
return;
}
whatsNewContainer.removeAttribute("id");
dialog2.appendChild(whatsNewContainer);
const closeButton = document.createElement("button");
closeButton.textContent = "X";
closeButton.style.position = "absolute";
closeButton.style.top = "1em";
closeButton.style.right = "1em";
closeButton.addEventListener("click", () => dialog2.close());
dialog2.appendChild(closeButton);
menu.onmousedown = () => {
dialog2.showModal();
return false;
};
dialog2.onclick = () => {
dialog2.close();
};
}
function createFavoritesHelpMenu() {
insertHelpHTML();
createWhatsNewMenu();
}
var parent1;
var container2;
var findButton;
var findInAllButton;
var input;
function insertFavoritesFinder() {
if (ON_MOBILE_DEVICE || !FavoritesSettings.favoriteFinderEnabled) {
return;
}
const foundParent = document.querySelector("#left-favorites-panel-top-row");
if (!(foundParent instanceof HTMLElement)) {
return;
}
parent1 = foundParent;
createElements();
addEventListeners3();
appendElements();
}
function createElements() {
container2 = document.createElement("span");
container2.id = "favorite-finder";
findButton = document.createElement("button");
findButton.id = "favorite-finder-button";
findButton.title = "Find favorite favorite using its ID";
findButton.textContent = "Find";
findInAllButton = document.createElement("button");
findInAllButton.id = "favorite-finder-in-all-button";
findInAllButton.title = "Find favorite favorite using its ID in all Favorites";
findInAllButton.textContent = "Find in All";
input = document.createElement("input");
input.id = "favorite-finder-input";
input.type = "number";
input.value = Preferences.favoriteFinderId.value;
input.placeholder = "ID";
}
function find() {
Events.favorites.findFavoriteStarted.emit(input.value);
}
function findInAll() {
Events.favorites.findFavoriteInAllStarted.emit(input.value);
}
function setFinderValue(value) {
input.value = value;
Preferences.favoriteFinderId.set(input.value);
}
function addEventListeners3() {
const setValue = debounceAfterFirstCall((value) => {
setFinderValue(value);
}, 1e3);
findButton.onclick = find;
findInAllButton.onclick = findInAll;
input.onkeydown = (event) => {
if (event.key === "Enter") {
find();
}
};
input.oninput = (event) => {
setValue(event.target.value);
};
Events.caption.idClicked.on(setValue);
}
function appendElements() {
container2.appendChild(input);
container2.appendChild(findButton);
parent1.appendChild(container2);
}
var parentContainer = document.createElement("div");
var CONTAINER2 = createContainer2();
var EXPLICIT = createRatingElement("explicit");
var QUESTIONABLE = createRatingElement("questionable");
var SAFE = createRatingElement("safe");
function insertFavoritesRatingFilter() {
if (ON_MOBILE_DEVICE) {
return;
}
parentContainer = document.getElementById("rating-container") ?? parentContainer;
parentContainer.appendChild(createLabel());
parentContainer.appendChild(document.createElement("br"));
parentContainer.appendChild(CONTAINER2);
changeWhichRatingsAreSelected(Preferences.allowedRatings.value);
addEventListeners4();
}
function createContainer2() {
const container4 = document.createElement("div");
container4.id = "allowed-ratings";
container4.className = "not-highlightable";
return container4;
}
function createLabel() {
const label = document.createElement("label");
label.htmlFor = "allowed-ratings";
label.textContent = "Rating";
return label;
}
function createRatingElement(ratingName) {
const input2 = document.createElement("input");
const label = document.createElement("label");
input2.type = "checkbox";
input2.id = `${ratingName}-rating`;
label.htmlFor = input2.id;
label.textContent = capitalize(ratingName);
CONTAINER2.appendChild(input2);
CONTAINER2.appendChild(label);
return {
input: input2,
label
};
}
function addEventListeners4() {
CONTAINER2.onclick = (event) => {
if (event.target === null || hasTagName(event.target, "label")) {
return;
}
const rating = getCurrentRating();
Events.favorites.allowedRatingsChanged.emit(rating);
preventAllRatingsFromBeingUnselected();
Preferences.allowedRatings.set(rating);
};
}
function getCurrentRating() {
const rating = 4 * Number(EXPLICIT.input.checked) + 2 * Number(QUESTIONABLE.input.checked) + Number(SAFE.input.checked);
return rating;
}
function changeWhichRatingsAreSelected(rating) {
EXPLICIT.input.checked = (rating & 4) === 4;
QUESTIONABLE.input.checked = (rating & 2) === 2;
SAFE.input.checked = (rating & 1) === 1;
preventAllRatingsFromBeingUnselected();
}
function preventAllRatingsFromBeingUnselected() {
switch (getCurrentRating()) {
case 4:
EXPLICIT.label.style.pointerEvents = "none";
break;
case 2:
QUESTIONABLE.label.style.pointerEvents = "none";
break;
case 1:
SAFE.label.style.pointerEvents = "none";
break;
default:
for (const element of [EXPLICIT, QUESTIONABLE, SAFE]) {
element.label.removeAttribute("style");
}
break;
}
}
var SearchHistory = class {
lastEditedQuery;
history;
index;
depth;
constructor(depth) {
this.index = -1;
this.history = this.loadSearchHistory();
this.lastEditedQuery = this.loadLastEditedQuery();
this.depth = depth;
}
get selectedQuery() {
if (indexInBounds(this.history, this.index)) {
return this.history[this.index];
}
return this.lastEditedQuery;
}
add(searchQuery2) {
if (isEmptyString(searchQuery2)) {
return;
}
const searchHistory = this.history.slice();
const cleanedSearchQuery = removeExtraWhiteSpace(searchQuery2);
const searchHistoryWithoutQuery = searchHistory.filter((search) => search !== cleanedSearchQuery);
const searchHistoryWithQueryAtFront = [searchQuery2].concat(searchHistoryWithoutQuery);
const truncatedSearchHistory = searchHistoryWithQueryAtFront.slice(0, this.depth);
this.history = truncatedSearchHistory;
localStorage.setItem("searchHistory", JSON.stringify(this.history));
}
updateLastEditedSearchQuery(searchQuery2) {
this.lastEditedQuery = searchQuery2;
this.resetIndex();
localStorage.setItem("lastEditedSearchQuery", this.lastEditedQuery);
}
navigate(direction) {
if (direction === "ArrowUp") {
const selectedQuery = this.selectedQuery;
this.incrementIndex();
const queryHasNotChanged = this.selectedQuery === selectedQuery;
if (queryHasNotChanged) {
this.incrementIndex();
}
return;
}
this.decrementIndex();
}
loadSearchHistory() {
return JSON.parse(localStorage.getItem("searchHistory") || "[]");
}
loadLastEditedQuery() {
return localStorage.getItem("lastEditedSearchQuery") || "";
}
resetIndex() {
this.index = -1;
}
incrementIndex() {
this.index = Math.min(this.index + 1, this.history.length - 1);
}
decrementIndex() {
this.index = Math.max(this.index - 1, -1);
}
};
function createDesktopSearchBar(id, parentId, initialValue) {
const searchBox3 = document.createElement("textarea");
searchBox3.id = id;
searchBox3.placeholder = "Search Favorites";
searchBox3.spellcheck = false;
searchBox3.value = initialValue ?? "";
const parent = document.getElementById(parentId);
if (parent !== null) {
parent.insertAdjacentElement("beforeend", searchBox3);
}
return searchBox3;
}
function createMobileSearchBar(id, parentId, onClick2) {
insertMobileSearchBar(id, parentId);
const searchButton = document.getElementById("search-button");
const searchBar = document.getElementById(id);
const clearButton = document.querySelector(".search-clear-container");
const resetButton = document.getElementById("reset-button");
if (!(searchBar instanceof HTMLInputElement) || searchButton === null || !(clearButton instanceof HTMLElement) || !(resetButton instanceof HTMLElement)) {
return document.createElement("input");
}
searchButton.onclick = onClick2;
searchBar.addEventListener("input", () => {
const clearButtonContainer = document.querySelector(".search-clear-container");
if (clearButtonContainer === null) {
return;
}
const clearButtonIsHidden = getComputedStyle(clearButtonContainer).visibility === "hidden";
const searchBarIsEmpty = searchBar.value === "";
const styleId = "search-clear-button-visibility";
if (searchBarIsEmpty && !clearButtonIsHidden) {
insertStyleHTML(".search-clear-container {visibility: hidden}", styleId);
} else if (!searchBarIsEmpty && clearButtonIsHidden) {
insertStyleHTML(".search-clear-container {visibility: visible}", styleId);
}
});
clearButton.onclick = () => {
searchBar.value = "";
searchBar.dispatchEvent(new Event("input"));
};
resetButton.onclick = tryResetting;
const options = document.getElementById("options-checkbox");
if (!(options instanceof HTMLInputElement)) {
return searchBar;
}
let headerIsVisible = true;
toggleFavoritesOptions(Preferences.optionsVisible.value);
options.checked = Preferences.optionsVisible.value;
options.addEventListener("change", () => {
Preferences.optionsVisible.set(options.checked);
toggleFavoritesOptions(options.checked);
if (!headerIsVisible) {
FAVORITES_CONTENT_CONTAINER.classList.toggle("sticky-menu", options.checked);
}
});
const stickyMenuHTML = `
#favorites-search-gallery-content {
margin-top: 65px;
margin-bottom: 65px;
}
#favorites-search-gallery-menu {
position: fixed;
margin-top: 0;
}`;
const header = document.getElementById("header");
const onHeaderVisibilityChange = async (headerVisible) => {
headerIsVisible = headerVisible;
insertStyleHTML(headerVisible ? "" : stickyMenuHTML, "sticky-menu");
const optionsMenu = document.getElementById("left-favorites-panel-bottom-row");
FAVORITES_CONTENT_CONTAINER.classList.remove("sticky-menu");
FAVORITES_CONTENT_CONTAINER.classList.remove("sticky-menu-shadow");
if (optionsMenu === null) {
return;
}
const menuIsOpen = !optionsMenu.classList.contains("hidden");
if (!headerVisible) {
if (menuIsOpen) {
FAVORITES_CONTENT_CONTAINER.classList.add("sticky-menu");
}
await sleep(30);
FAVORITES_CONTENT_CONTAINER.classList.add("sticky-menu-shadow");
}
};
if (header !== null) {
const observer = new IntersectionObserver((entries) => {
onHeaderVisibilityChange(entries[0].isIntersecting);
}, { threshold: 0 });
observer.observe(header);
}
createMobileSymbolRow(searchBar);
return searchBar;
}
function insertMobileSearchBar(id, parentId) {
const html = `
<div id="mobile-toolbar-row" class="light-green-gradient">
<div class="search-bar-container light-green-gradient">
<div class="search-bar-items">
<div>
<div class="circle-icon-container">
<svg class="search-icon" id="search-button" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/>
</svg>
</div>
</div>
<div class="search-bar-input-container">
<input type="text" id="${id}" class="search-bar-input" needs-autocomplete placeholder="Search Favorites">
</div>
<div class="toolbar-button search-clear-container">
<div class="circle-icon-container">
<svg id="clear-button" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/>
</svg>
</div>
</div>
<div>
<input type="checkbox" id="options-checkbox" data-action="toggleOptions">
<label for="options-checkbox" class="mobile-toolbar-checkbox-label">
<svg id="options-menu-icon" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368">
<path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/>
</svg>
</label>
</div>
<div>
<div id="reset-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-84 31.5-156.5T197-763l56 56q-44 44-68.5 102T160-480q0 134 93 227t227 93q134 0 227-93t93-227q0-67-24.5-125T707-707l56-56q54 54 85.5 126.5T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-40-360v-440h80v440h-80Z"/>
</svg>
</div>
</div>
<div style="display: none;">
<div id="">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M424-320q0-81 14.5-116.5T500-514q41-36 62.5-62.5T584-637q0-41-27.5-68T480-732q-51 0-77.5 31T365-638l-103-44q21-64 77-111t141-47q105 0 161.5 58.5T698-641q0 50-21.5 85.5T609-475q-49 47-59.5 71.5T539-320H424Zm56 240q-33 0-56.5-23.5T400-160q0-33 23.5-56.5T480-240q33 0 56.5 23.5T560-160q0 33-23.5 56.5T480-80Z"/>
</svg>
</div>
</div>
</div>
</div>
</div>
`;
const parent = document.getElementById(parentId);
if (parent !== null) {
parent.insertAdjacentHTML("afterend", html);
}
}
function createMobileSymbolRow(searchBox3) {
if (ON_DESKTOP_DEVICE) {
return;
}
const placeToInsert = document.getElementById("left-favorites-panel");
if (placeToInsert === null) {
return;
}
const symbolContainer = document.createElement("div");
symbolContainer.id = "mobile-symbol-container";
symbolContainer.innerHTML = `
<button>-</button>
<button>*</button>
<button>_</button>
<button>(</button>
<button>)</button>
<button>~</button>
`;
placeToInsert.insertAdjacentElement("afterbegin", symbolContainer);
for (const button of Array.from(symbolContainer.querySelectorAll("button"))) {
button.addEventListener("blur", async () => {
await sleep(0);
if (document.activeElement === null || document.activeElement.id !== "favorites-search-box" && !symbolContainer.contains(document.activeElement)) {
symbolContainer.classList.toggle("active", false);
}
});
button.addEventListener("click", () => {
const value = searchBox3.value;
const selectionStart = searchBox3.selectionStart ?? 0;
searchBox3.value = value.slice(0, selectionStart) + button.textContent + value.slice(selectionStart);
searchBox3.selectionStart = selectionStart + 1;
searchBox3.selectionEnd = selectionStart + 1;
searchBox3.focus();
}, {
passive: true
});
}
searchBox3.addEventListener("focus", () => {
symbolContainer.classList.toggle("active", true);
}, {
passive: true
});
searchBox3.addEventListener("blur", async () => {
await sleep(10);
if (document.activeElement === null || document.activeElement.id !== "favorites-search-box" && !symbolContainer.contains(document.activeElement)) {
symbolContainer.classList.toggle("active", false);
}
});
}
var SEARCH_BOX;
var PARENT_ID = "left-favorites-panel-top-row";
var ID = "favorites-search-box";
var SEARCH_HISTORY = new SearchHistory(30);
function addEventListenersToSearchBox() {
Events.caption.searchForTag.on((tag) => {
SEARCH_BOX.value = tag;
startSearch();
});
Events.searchBox.appendSearchBox.on((text) => {
const initialSearchBoxValue = SEARCH_BOX.value;
const optionalSpace = initialSearchBoxValue === "" ? "" : " ";
const newSearchBoxValue = `${initialSearchBoxValue}${optionalSpace}${text}`;
SEARCH_BOX.value = newSearchBoxValue;
SEARCH_HISTORY.add(newSearchBoxValue);
updateLastEditedSearchQuery();
});
Events.favorites.searchButtonClicked.on(onSearchButtonClicked);
Events.favorites.clearButtonClicked.on(() => {
SEARCH_BOX.value = "";
});
Events.favorites.searchBoxUpdated.on(() => {
updateLastEditedSearchQuery();
});
SEARCH_BOX.addEventListener("keydown", (event) => {
if (!(event instanceof KeyboardEvent)) {
return;
}
if (event.key === "Enter") {
if (!event.repeat && awesompleteIsUnselected(SEARCH_BOX)) {
event.preventDefault();
startSearch();
}
return;
}
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
if (!awesompleteIsVisible(SEARCH_BOX)) {
event.preventDefault();
SEARCH_HISTORY.navigate(event.key);
SEARCH_BOX.value = SEARCH_HISTORY.selectedQuery;
}
}
});
updateLastEditedSearchQueryOnInput();
}
function updateLastEditedSearchQueryOnInput() {
SEARCH_BOX.addEventListener("keyup", debounceAfterFirstCall((event) => {
if (!(event instanceof KeyboardEvent)) {
return;
}
if (event.key.length === 1 || event.key === "Backspace" || event.key === "Delete") {
updateLastEditedSearchQuery();
}
}, 500));
}
function updateLastEditedSearchQuery() {
SEARCH_HISTORY.updateLastEditedSearchQuery(SEARCH_BOX.value);
}
function onSearchButtonClicked(event) {
const mouseEvent = new FavoritesMouseEvent(event);
if (mouseEvent.rightClick || mouseEvent.ctrlKey) {
openSearchPage(SEARCH_BOX.value);
return;
}
startSearch();
}
function startSearch() {
SEARCH_HISTORY.add(SEARCH_BOX.value);
updateLastEditedSearchQuery();
hideAwesomplete(SEARCH_BOX);
Events.favorites.searchStarted.emit(SEARCH_BOX.value);
}
function setupFavoritesSearchBox() {
SEARCH_BOX = ON_MOBILE_DEVICE ? createMobileSearchBar(ID, PARENT_ID, startSearch) : createDesktopSearchBar(ID, PARENT_ID, SEARCH_HISTORY.lastEditedQuery);
addEventListenersToSearchBox();
}
function createStaticFavoritesMenuElements() {
insertFavoritesFinder();
createFavoritesHelpMenu();
insertFavoritesRatingFilter();
setupFavoritesSearchBox();
}
function buildFavoritesMenu() {
insertFavoritesMenuHTML();
if (ON_DESKTOP_DEVICE) {
buildDesktopFavoritesMenu();
} else {
buildMobileFavoritesMenu();
}
createStaticFavoritesMenuElements();
}
function insertFavoritesMenuHTML() {
insertStyleHTML(ON_MOBILE_DEVICE ? MOBILE_HTML : DESKTOP_HTML, "favorites-menu-style");
insertHTMLAndExtractStyle(FAVORITES_SEARCH_GALLERY_CONTAINER, "afterbegin", FAVORITES_HTML);
}
function buildDesktopFavoritesMenu() {
createDynamicFavoritesDesktopMenuElements();
}
function buildMobileFavoritesMenu() {
createFooter();
moveStatusToFooter();
createControlsGuide();
createDynamicFavoritesMobileMenuElements();
}
function getOriginalFavoritesContent() {
return document.querySelector("#content, div:has(.thumb)");
}
function clearOriginalFavoritesContent() {
getOriginalFavoritesContent()?.remove();
}
function removeUnusedFavoritesPageScripts() {
for (const script of document.querySelectorAll("script")) {
if (/(?:fluidplayer|awesomplete)/.test(script.src || "")) {
script.remove();
}
}
}
async function cleanOriginalFavoritesPage() {
await waitForDOMToLoad();
await sleep(20);
clearOriginalFavoritesContent();
removeUnusedFavoritesPageScripts();
}
function buildFavoritesPage() {
cleanOriginalFavoritesPage();
buildFavoritesMenu();
insertFavoritesContentContainer();
}
async function loadAllFavorites2() {
await loadAllFavoritesFromDatabase3();
if (hasFavorites()) {
Events.favorites.favoritesLoadedFromDatabase.emit();
showLoadedFavorites();
await loadNewFavorites();
} else {
await fetchAllFavorites4();
await saveAllFavorites();
}
Events.favorites.favoritesLoaded.emit();
}
async function loadAllFavoritesFromDatabase3() {
setStatus2("Loading favorites");
await loadAllFavoritesFromDatabase2();
}
function hasFavorites() {
return getAllFavorites2().length > 0;
}
async function fetchAllFavorites4() {
clear();
Events.favorites.startedFetchingFavorites.emit();
await fetchAllFavorites3(processFetchedFavorites);
}
async function saveAllFavorites() {
setStatus2("Saving favorites");
await storeAllFavorites3();
showTemporaryNotification("All favorites saved");
}
function showLoadedFavorites() {
showTemporaryNotification("Favorites loaded");
showLatestSearchResults();
}
function processFetchedFavorites() {
updateStatusWhileFetching2(getLatestSearchResults().length, getAllFavorites2().length);
Events.favorites.searchResultsUpdated.emit(getLatestSearchResults());
handleNewSearchResults();
}
async function loadNewFavorites() {
const results = await fetchNewFavoritesOnReload3();
if (results.newSearchResults.length === 0) {
return;
}
insertNewSearchResultsOnReload(results);
saveNewFavorites(results.newFavorites);
paginate2(getLatestSearchResults());
Events.favorites.newFavoritesFoundOnReload.emit(results.newSearchResults);
Events.favorites.searchResultsUpdated.emit(results.allSearchResults);
}
async function saveNewFavorites(newFavorites) {
await storeNewFavorites2(newFavorites);
showTemporaryNotification("New favorites saved");
}
function setupFavorites() {
if (ON_FAVORITES_PAGE) {
buildFavoritesPage();
setupFavoritesView();
addFavoritesEventsListeners();
loadAllFavorites2();
}
}
var Timer = class {
waitTime;
onTimerEnd;
timeout;
constructor(waitTime) {
this.waitTime = waitTime;
this.onTimerEnd = DO_NOTHING;
this.timeout = void 0;
}
get isRunning() {
return this.timeout !== void 0;
}
get isStopped() {
return !this.isRunning;
}
restart() {
this.stop();
this.start();
}
stop() {
clearTimeout(this.timeout);
this.timeout = void 0;
}
start() {
this.timeout = setTimeout(() => {
this.timeout = void 0;
this.onTimerEnd();
}, this.waitTime);
}
};
var MENU_ICONS = {
play: createObjectURLFromSvg(PLAY),
pause: createObjectURLFromSvg(PAUSE),
changeDirection: createObjectURLFromSvg(CHANGE_DIRECTION),
changeDirectionAlt: createObjectURLFromSvg(CHANGE_DIRECTION_2),
tune: createObjectURLFromSvg(TUNE)
};
var CONFIG = {
imageViewDuration: Preferences.autoplayImageDuration.value,
minimumVideoDuration: Preferences.autoplayMinimumVideoDuration.value,
menuVisibilityDuration: ON_MOBILE_DEVICE ? 1500 : 1e3,
get imageViewDurationInSeconds() {
return millisecondsToSeconds(this.imageViewDuration);
},
get minimumVideoDurationInSeconds() {
return millisecondsToSeconds(this.minimumVideoDuration);
}
};
var ui;
var events;
var eventListenersAbortController;
var currentThumb2;
var imageViewTimer;
var menuVisibilityTimer;
var videoViewTimer;
var active;
var paused;
var menuIsPersistent;
var menuIsVisible;
function setupAutoplay(inEvents) {
initializeFields2();
initializeEvents(inEvents);
initializeTimers();
insertHTML2();
configureMobileUi();
extractUiElements();
setMenuIconImageSources();
setupNumberComponents();
addEventListeners5();
loadAutoplaySettingsIntoUI();
}
function isPaused() {
return paused;
}
function isActive() {
return active;
}
function initializeFields2() {
ui = {
settingsMenu: {},
changeDirectionMask: {}
};
eventListenersAbortController = new AbortController();
currentThumb2 = null;
active = Preferences.autoplayActive.value;
paused = Preferences.autoplayPaused.value;
menuIsPersistent = false;
menuIsVisible = false;
}
function getDirection() {
return Preferences.autoplayForward.value ? "ArrowRight" : "ArrowLeft";
}
function initializeEvents(inEvents) {
events = inEvents;
const onComplete = events.onComplete;
events.onComplete = () => {
if (active && !paused) {
onComplete(getDirection());
}
};
}
function initializeTimers() {
imageViewTimer = new Timer(CONFIG.imageViewDuration);
menuVisibilityTimer = new Timer(CONFIG.menuVisibilityDuration);
videoViewTimer = new Timer(CONFIG.minimumVideoDuration);
imageViewTimer.onTimerEnd = () => {
};
menuVisibilityTimer.onTimerEnd = () => {
hideMenu();
setTimeout(() => {
if (!menuIsPersistent && !menuIsVisible) {
toggleSettingMenu(false);
}
}, 100);
};
}
function insertHTML2() {
insertMenuHTML();
insertImageProgressHTML();
insertVideoProgressHTML();
}
function insertMenuHTML() {
FAVORITES_SEARCH_GALLERY_CONTAINER.insertAdjacentHTML("afterbegin", AUTOPLAY_HTML);
}
function insertImageProgressHTML() {
insertStyleHTML(`
#autoplay-image-progress-bar.animated {
transition: width ${CONFIG.imageViewDurationInSeconds}s linear;
width: 100%;
}
body.autoplay::before {
animation: progress ${CONFIG.imageViewDurationInSeconds}s linear forwards
}
`, "autoplay-image-progress-bar-animation");
}
function insertVideoProgressHTML() {
insertStyleHTML(`
#autoplay-video-progress-bar.animated {
transition: width ${CONFIG.minimumVideoDurationInSeconds}s linear;
width: 100%;
}
`, "autoplay-video-progress-bar-animation");
}
function extractUiElements() {
ui.container = document.getElementById("autoplay-container");
ui.menu = document.getElementById("autoplay-menu");
ui.settingsButton = document.getElementById("autoplay-settings-button");
ui.settingsMenu.container = document.getElementById("autoplay-settings-menu");
ui.settingsMenu.imageDurationInput = document.getElementById("autoplay-image-duration-input");
ui.settingsMenu.minimumVideoDurationInput = document.getElementById("autoplay-minimum-animated-duration-input");
ui.playButton = document.getElementById("autoplay-play-button");
ui.changeDirectionButton = document.getElementById("autoplay-change-direction-button");
ui.changeDirectionMask.container = document.getElementById("autoplay-change-direction-mask-container");
ui.changeDirectionMask.image = document.getElementById("autoplay-change-direction-mask");
ui.imageProgressBar = document.getElementById("autoplay-image-progress-bar");
ui.videoProgressBar = document.getElementById("autoplay-video-progress-bar");
}
function configureMobileUi() {
if (ON_DESKTOP_DEVICE) {
return;
}
createViewDurationSelects();
}
function createViewDurationSelects() {
const imageViewDurationSelect = createDurationSelect(1, 60);
const videoViewDurationSelect = createDurationSelect(0, 60);
const imageViewDurationInput = document.getElementById("autoplay-image-duration-input").parentElement;
const videoViewDurationInput = document.getElementById("autoplay-minimum-animated-duration-input").parentElement;
imageViewDurationSelect.value = String(CONFIG.imageViewDurationInSeconds);
videoViewDurationSelect.value = String(CONFIG.minimumVideoDurationInSeconds);
imageViewDurationInput.insertAdjacentElement("afterend", imageViewDurationSelect);
videoViewDurationInput.insertAdjacentElement("afterend", videoViewDurationSelect);
imageViewDurationInput.remove();
videoViewDurationInput.remove();
imageViewDurationSelect.id = "autoplay-image-duration-input";
videoViewDurationSelect.id = "autoplay-minimum-animated-duration-input";
}
function createDurationSelect(minimum, maximum) {
const select2 = document.createElement("select");
for (let i = minimum; i <= maximum; i += 1) {
const option = document.createElement("option");
switch (true) {
case i <= 5:
break;
case i <= 20:
i += 4;
break;
case i <= 30:
i += 9;
break;
default:
i += 29;
break;
}
option.value = String(i);
option.innerText = String(i);
select2.append(option);
}
select2.ontouchstart = () => {
select2.dispatchEvent(new Event("mousedown"));
};
return select2;
}
function setMenuIconImageSources() {
ui.playButton.src = paused ? MENU_ICONS.play : MENU_ICONS.pause;
ui.settingsButton.src = MENU_ICONS.tune;
ui.changeDirectionButton.src = MENU_ICONS.changeDirection;
ui.changeDirectionMask.image.src = MENU_ICONS.changeDirectionAlt;
ui.changeDirectionMask.container.classList.toggle("upper-right", Preferences.autoplayForward.value);
}
function loadAutoplaySettingsIntoUI() {
ui.settingsMenu.imageDurationInput.value = String(CONFIG.imageViewDurationInSeconds);
ui.settingsMenu.minimumVideoDurationInput.value = String(CONFIG.minimumVideoDurationInSeconds);
}
function setupNumberComponents() {
for (const input2 of [ui.settingsMenu.imageDurationInput, ui.settingsMenu.minimumVideoDurationInput]) {
const element = input2.closest(".number");
if (element instanceof HTMLElement) {
new NumberComponent(element);
}
}
}
function addEventListeners5() {
addMenuEventListeners();
addSettingsMenuEventListeners();
addFavoritesPageEventListeners2();
}
function addMenuEventListeners() {
addDesktopMenuEventListeners();
addMobileMenuEventListeners();
}
function addDesktopMenuEventListeners() {
if (ON_MOBILE_DEVICE) {
return;
}
ui.settingsButton.onclick = () => {
toggleSettingMenu();
};
ui.playButton.onclick = () => {
pause();
};
ui.changeDirectionButton.onclick = () => {
toggleDirection();
};
ui.menu.onmouseenter = () => {
toggleMenuPersistence(true);
};
ui.menu.onmouseleave = () => {
toggleMenuPersistence(false);
};
}
function addMobileMenuEventListeners() {
if (ON_DESKTOP_DEVICE) {
return;
}
ui.settingsButton.ontouchstart = () => {
toggleSettingMenu();
const settingsMenuIsVisible = ui.settingsMenu.container.classList.contains("visible");
toggleMenuPersistence(settingsMenuIsVisible);
menuVisibilityTimer.restart();
};
ui.playButton.ontouchstart = () => {
pause();
menuVisibilityTimer.restart();
};
ui.changeDirectionButton.ontouchstart = () => {
toggleDirection();
menuVisibilityTimer.restart();
};
}
function addSettingsMenuEventListeners() {
ui.settingsMenu.imageDurationInput.onchange = () => {
setImageViewDuration();
if (currentThumb2 !== null && isImage(currentThumb2)) {
startViewTimer(currentThumb2);
}
};
ui.settingsMenu.minimumVideoDurationInput.onchange = () => {
setMinimumVideoViewDuration();
if (currentThumb2 !== null && !isImage(currentThumb2)) {
startViewTimer(currentThumb2);
}
};
}
function addFavoritesPageEventListeners2() {
Events.favorites.autoplayToggled.on((value) => {
toggle2(value);
});
}
function toggleDirection() {
Preferences.autoplayForward.set(!Preferences.autoplayForward.value);
ui.changeDirectionMask.container.classList.toggle("upper-right", Preferences.autoplayForward.value);
}
function toggleMenuPersistence(value) {
menuIsPersistent = value;
ui.menu.classList.toggle("persistent", value);
}
function toggleMenuVisibility(value) {
menuIsVisible = value;
ui.menu.classList.toggle("visible", value);
}
function toggleSettingMenu(value) {
if (value === void 0) {
ui.settingsMenu.container.classList.toggle("visible");
ui.settingsButton.classList.toggle("settings-menu-opened");
} else {
ui.settingsMenu.container.classList.toggle("visible", value);
ui.settingsButton.classList.toggle("settings-menu-opened", value);
}
}
function toggle2(value) {
Preferences.autoplayActive.set(value);
active = value;
if (value) {
events.onEnable();
} else {
events.onDisable();
}
}
function setImageViewDuration() {
let durationInSeconds = parseFloat(ui.settingsMenu.imageDurationInput.value);
if (isNaN(durationInSeconds)) {
durationInSeconds = CONFIG.imageViewDurationInSeconds;
}
const duration = Math.round(clamp(durationInSeconds * 1e3, 1e3, 6e4));
Preferences.autoplayImageDuration.set(duration);
CONFIG.imageViewDuration = duration;
imageViewTimer.waitTime = duration;
ui.settingsMenu.imageDurationInput.value = String(CONFIG.imageViewDurationInSeconds);
insertImageProgressHTML();
}
function setMinimumVideoViewDuration() {
let durationInSeconds = parseFloat(ui.settingsMenu.minimumVideoDurationInput.value);
if (isNaN(durationInSeconds)) {
durationInSeconds = CONFIG.minimumVideoDurationInSeconds;
}
const duration = Math.round(clamp(durationInSeconds * 1e3, 0, 6e4));
Preferences.autoplayMinimumVideoDuration.set(duration);
CONFIG.minimumVideoDuration = duration;
videoViewTimer.waitTime = duration;
ui.settingsMenu.minimumVideoDurationInput.value = String(CONFIG.minimumVideoDurationInSeconds);
insertVideoProgressHTML();
}
function startViewTimer(thumb) {
if (thumb === null) {
return;
}
currentThumb2 = thumb;
if (!active || paused) {
return;
}
if (isVideo(thumb)) {
startVideoViewTimer();
} else {
startImageViewTimer();
}
}
function startImageViewTimer() {
stopVideoProgressBar();
stopVideoViewTimer();
startImageProgressBar();
imageViewTimer.restart();
}
function stopImageViewTimer() {
imageViewTimer.stop();
stopImageProgressBar();
}
function startVideoViewTimer() {
stopImageViewTimer();
stopImageProgressBar();
startVideoProgressBar();
videoViewTimer.restart();
}
function stopVideoViewTimer() {
videoViewTimer.stop();
stopVideoProgressBar();
}
function startAutoplay(thumb) {
if (!active) {
return;
}
addAutoplayEventListeners();
ui.container.style.visibility = "visible";
showMenu();
startViewTimer(thumb);
}
function stopAutoplay() {
ui.container.style.visibility = "hidden";
removeAutoplayEventListeners();
stopImageViewTimer();
stopVideoViewTimer();
forceHideMenu();
}
function pause() {
paused = !paused;
Preferences.autoplayPaused.set(paused);
if (paused) {
ui.playButton.src = MENU_ICONS.play;
ui.playButton.title = "Resume Autoplay";
stopImageViewTimer();
stopVideoViewTimer();
events.onPause();
} else {
ui.playButton.src = MENU_ICONS.pause;
ui.playButton.title = "Pause Autoplay";
startViewTimer(currentThumb2);
events.onResume();
}
}
function onVideoEnded() {
if (!active || paused) {
return;
}
if (videoViewTimer.isRunning) {
events.onVideoEndedBeforeMinimumViewTime();
} else {
events.onComplete();
}
}
function addAutoplayEventListeners() {
imageViewTimer.onTimerEnd = () => {
events.onComplete();
};
Events.document.mousemove.on(throttle(() => {
showMenu();
}, 250), {
signal: eventListenersAbortController.signal
});
Events.document.keydown.on((event) => {
if (!event.isHotkey) {
return;
}
switch (event.key) {
case "p":
showMenu();
pause();
break;
case " ":
if (currentThumb2 !== null && !isVideo(currentThumb2)) {
showMenu();
pause();
}
break;
default:
break;
}
}, {
signal: eventListenersAbortController.signal
});
}
function removeAutoplayEventListeners() {
imageViewTimer.onTimerEnd = () => {
};
eventListenersAbortController.abort();
eventListenersAbortController = new AbortController();
}
function showMenu() {
toggleMenuVisibility(true);
menuVisibilityTimer.restart();
}
function hideMenu() {
toggleMenuVisibility(false);
}
function forceHideMenu() {
toggleMenuPersistence(false);
toggleMenuVisibility(false);
toggleSettingMenu(false);
}
function startImageProgressBar() {
stopImageProgressBar();
setTimeout(() => {
ui.imageProgressBar.classList.add("animated");
}, 20);
}
function stopImageProgressBar() {
ui.imageProgressBar.classList.remove("animated");
document.body.classList.remove("autoplay");
}
function startVideoProgressBar() {
stopVideoProgressBar();
setTimeout(() => {
ui.videoProgressBar.classList.add("animated");
}, 20);
}
function stopVideoProgressBar() {
ui.videoProgressBar.classList.remove("animated");
}
async function addFavoriteInGallery(thumb) {
if (thumb === void 0) {
return Promise.resolve(0 /* ERROR */);
}
const status = await addFavorite(thumb.id);
if (status === 3 /* SUCCESSFULLY_ADDED */) {
Events.gallery.favoriteToggled.emit(thumb.id);
}
return status;
}
function removeFavoriteInGallery(thumb) {
if (thumb === void 0) {
return Promise.resolve(0 /* ERROR */);
}
const removeFavoriteButton = thumb.querySelector(".remove-favorite-button");
const showRemoveFavoriteCheckbox = document.getElementById("show-remove-favorite-buttons");
if (removeFavoriteButton === null || showRemoveFavoriteCheckbox === null) {
return Promise.resolve(0 /* ERROR */);
}
const allowedToRemoveFavorites = showRemoveFavoriteCheckbox instanceof HTMLInputElement && showRemoveFavoriteCheckbox.checked;
if (!allowedToRemoveFavorites) {
return Promise.resolve(1 /* FORBIDDEN */);
}
removeFavorite(thumb.id);
Events.gallery.favoriteToggled.emit(thumb.id);
Events.favorites.favoriteRemoved.emit(thumb.id);
return Promise.resolve(2 /* SUCCESSFULLY_REMOVED */);
}
var currentState = getStartState();
function getCurrentState() {
return currentState;
}
function changeState(state) {
currentState = state;
onStateChange();
}
function getStartState() {
if (Preferences.showOnHoverEnabled.value) {
return 1 /* SHOWING_CONTENT_ON_HOVER */;
}
return 0 /* IDLE */;
}
function onStateChange() {
switch (currentState) {
case 0 /* IDLE */:
break;
case 1 /* SHOWING_CONTENT_ON_HOVER */:
break;
case 2 /* IN_GALLERY */:
break;
default:
break;
}
}
var GallerySettings = {
mainCanvasResolutions: {
search: "3840x2160",
favorites: "7680x4320",
mobile: "1920x1080"
},
get mainCanvasResolution() {
if (ON_MOBILE_DEVICE) {
return GallerySettings.mainCanvasResolutions.mobile;
}
return ON_SEARCH_PAGE ? GallerySettings.mainCanvasResolutions.search : GallerySettings.mainCanvasResolutions.favorites;
},
imageMegabyteLimit: ON_MOBILE_DEVICE ? 0 : 850,
searchPagePreloadedImageCount: ON_MOBILE_DEVICE ? 4 : 42,
minimumPreloadedImageCount: ON_MOBILE_DEVICE ? 3 : 5,
preloadedVideoCount: ON_MOBILE_DEVICE ? 2 : 2,
preloadedGifCount: ON_MOBILE_DEVICE ? 2 : 2,
preloadContentDebounceTime: 150,
preloadingEnabled: true,
visibleThumbsDownwardScrollPixelGenerosity: 50,
visibleThumbsDownwardScrollPercentageGenerosity: 100,
navigationThrottleTime: 250,
maxImagesToRenderAroundInGallery: ON_MOBILE_DEVICE ? 3 : 50,
idleInteractionDuration: 1e3,
menuVisibilityTime: ON_MOBILE_DEVICE ? 2e3 : 1e3,
maxVisibleThumbsBeforeStoppingPreload: 175,
useOffscreenThumbUpscaler: false,
fetchImageBitmapsInWorker: true,
get sendImageBitmapsToWorker() {
return !this.fetchImageBitmapsInWorker;
},
createImageAccentColors: false,
galleryMenuMonoColor: true
};
var latestSearchResults3 = [];
var thumbsOnCurrentPage = [];
var enumeratedThumbs = /* @__PURE__ */ new Map();
function getThumbsOnCurrentPage() {
return thumbsOnCurrentPage;
}
function indexCurrentPageThumbs(thumbs) {
thumbsOnCurrentPage = thumbs;
enumerateCurrentPageThumbs();
}
function setLatestSearchResults(searchResults) {
latestSearchResults3 = searchResults;
}
function enumerateCurrentPageThumbs() {
for (let i = 0; i < thumbsOnCurrentPage.length; i += 1) {
enumeratedThumbs.set(thumbsOnCurrentPage[i].id, i);
}
}
function getIndexFromThumb(thumb) {
return enumeratedThumbs.get(thumb.id) ?? 0;
}
function getSearchResultsAround(thumb, limit = 50) {
const startIndex = latestSearchResults3.findIndex((post) => post.id === thumb.id);
const adjacentSearchResults = getWrappedElementsAroundIndex(latestSearchResults3, startIndex, limit);
return adjacentSearchResults.map((favorite) => favorite.root);
}
var PARSER6 = new DOMParser();
function prepareThumbsOnSearchPage(thumbs) {
for (const thumb of thumbs) {
prepareThumb(thumb);
}
return thumbs;
}
function prepareAllThumbsOnSearchPage() {
const thumbs = getAllThumbs();
prepareThumbsOnSearchPage(thumbs);
findImageExtensions(thumbs);
}
function prepareThumb(thumb) {
moveTagsFromTitleToTagsAttribute(thumb);
assignContentType(thumb);
thumb.id = removeNonNumericCharacters(getIdFromThumb(thumb));
if (ON_MOBILE_DEVICE) {
prepareMobileThumb(thumb);
}
}
function extractSearchPageThumbs(dom) {
const thumbs = Array.from(dom.querySelectorAll(".thumb")).filter((thumb) => thumb instanceof HTMLElement);
return prepareThumbsOnSearchPage(thumbs);
}
async function findImageExtensions(thumbs) {
for (const thumb of thumbs) {
await sleep(5);
findImageExtension(thumb);
}
}
async function findImageExtension(thumb) {
const post = await fetchPostFromAPI(thumb.id);
setExtensionFromPost(post);
correctMediaTags(thumb, post);
}
function correctMediaTags(thumb, post) {
if (!ON_SEARCH_PAGE) {
return;
}
const tagSet = convertToTagSet(post.tags);
const isVideo2 = post.fileURL.endsWith("mp4");
const isGif2 = post.fileURL.endsWith("gif");
const isImage2 = !isVideo2 && !isGif2;
const documentThumb = document.getElementById(thumb.id);
if (isImage2) {
removeAnimatedTags(tagSet);
removeAnimatedAttributes(thumb);
removeAnimatedAttributes(documentThumb);
} else if (isVideo2) {
tagSet.add("video");
} else if (isGif2) {
tagSet.add("gif");
}
setThumbTagsOnSearchPage(thumb, convertToTagString(tagSet));
setThumbTagsOnSearchPage(documentThumb, convertToTagString(tagSet));
}
function setThumbTagsOnSearchPage(thumb, tags) {
if (thumb === null) {
return;
}
const image = getImageFromThumb(thumb);
if (image === null) {
return;
}
image.setAttribute("tags", tags);
}
function removeAnimatedTags(tagSet) {
tagSet.delete("animated");
tagSet.delete("video");
tagSet.delete("mp4");
tagSet.delete("gif");
}
function assignContentType(thumb) {
thumb.classList.remove("image");
thumb.classList.remove("video");
thumb.classList.remove("gif");
const image = getImageFromThumb(thumb);
if (image === null) {
return;
}
const tags = image.getAttribute("tags") ?? "";
image.classList.add(getContentType(tags));
}
function removeAnimatedAttributes(thumb) {
if (thumb === null) {
return;
}
thumb.classList.remove("video");
thumb.classList.remove("gif");
const image = getImageFromThumb(thumb);
if (image === null) {
return;
}
image.classList.remove("video");
image.classList.remove("gif");
}
function prepareMobileThumb(thumb) {
for (const script of thumb.querySelectorAll("script")) {
script.remove();
}
const image = getImageFromThumb(thumb);
if (image === null) {
return;
}
image.removeAttribute("style");
const altSource = image.getAttribute("data-cfsrc");
if (altSource !== null) {
image.setAttribute("src", altSource);
image.removeAttribute("data-cfsrc");
}
}
var SearchPage = class {
html;
thumbs;
paginator;
ids;
pageNumber;
constructor(pageNumber, html) {
const dom = PARSER6.parseFromString(html, "text/html");
this.html = html;
this.thumbs = extractSearchPageThumbs(dom);
this.pageNumber = pageNumber;
this.paginator = dom.getElementById("paginator");
this.ids = new Set(this.thumbs.map((thumb) => thumb.id));
}
get isEmpty() {
return this.thumbs.length === 0;
}
get isLastPage() {
return this.thumbs.length < 42;
}
get isFirstPage() {
return this.pageNumber === 0;
}
};
var searchPages;
var fetchedPageNumbers;
var initialPageNumber;
var currentPageNumber3;
var initialURL = getInitialURL();
var allThumbs = [];
function setupSearchPageLoader() {
searchPages = /* @__PURE__ */ new Map();
fetchedPageNumbers = /* @__PURE__ */ new Set();
initialPageNumber = getInitialPageNumber();
currentPageNumber3 = initialPageNumber;
initialURL = getInitialURL();
allThumbs = Array.from(getAllThumbs());
searchPages.set(initialPageNumber, new SearchPage(initialPageNumber, document.documentElement.outerHTML));
preloadSearchPages();
}
function navigateSearchPages(direction) {
const nextSearchPageNumber = getAdjacentSearchPageNumber(direction);
const searchPage = searchPages.get(nextSearchPageNumber);
if (searchPage === void 0 || searchPage.isEmpty) {
return null;
}
currentPageNumber3 = nextSearchPageNumber;
return searchPage;
}
function getAdjacentSearchPageNumber(direction) {
const forward = isForwardNavigationKey(direction);
return forward ? currentPageNumber3 + 1 : currentPageNumber3 - 1;
}
function getThumbsAround(thumb) {
const index = allThumbs.findIndex((t) => t.id === thumb.id);
if (index === -1) {
return [];
}
return getElementsAroundIndex(allThumbs, index, 100);
}
async function preloadSearchPages() {
const previousPageNumber = Math.max(0, currentPageNumber3 - 1);
const nextPageNumber = currentPageNumber3 + 1;
await loadSearchPage(currentPageNumber3);
await loadSearchPage(previousPageNumber);
await loadSearchPage(nextPageNumber);
}
function loadSearchPage(pageNumber) {
if (pageHasAlreadyBeenFetched(pageNumber)) {
return Promise.resolve();
}
fetchedPageNumbers.add(pageNumber);
return fetchSearchPage(pageNumber).then((html) => {
registerNewPage(pageNumber, html);
}).catch(() => {
fetchedPageNumbers.delete(pageNumber);
searchPages.delete(pageNumber);
});
}
function pageHasAlreadyBeenFetched(pageNumber) {
return searchPages.has(pageNumber) || fetchedPageNumbers.has(pageNumber);
}
function registerNewPage(pageNumber, html) {
searchPages.set(pageNumber, new SearchPage(pageNumber, html));
updateAllThumbs();
}
function fetchSearchPage(pageNumber) {
return fetch(getSearchPageURL(pageNumber)).then((response) => {
if (response.ok) {
return response.text();
}
throw new Error(String(response.status));
});
}
function getSearchPageURL(pageNumber) {
return `${initialURL}&pid=${42 * pageNumber}`;
}
function updateAllThumbs() {
const sortedPageNumbers = Array.from(searchPages.keys()).sort();
let thumbs = [];
for (const pageNumber of sortedPageNumbers) {
const page = searchPages.get(pageNumber);
if (page !== void 0) {
thumbs = thumbs.concat(page.thumbs);
}
}
allThumbs = thumbs;
}
function getInitialPageNumber() {
const match = /&pid=(\d+)/.exec(location.href);
return match === null ? 0 : Math.round(parseInt(match[1]) / 42);
}
function getInitialURL() {
return location.href.replace(/&pid=(\d+)/, "");
}
var currentIndex = 0;
var recentlyExitedGallery = false;
function hasRecentlyExitedGallery() {
return recentlyExitedGallery;
}
function getCurrentThumb() {
return getThumbsOnCurrentPage()[currentIndex];
}
function getCurrentState2() {
return getCurrentState();
}
function inGallery() {
return getCurrentState() === 2 /* IN_GALLERY */;
}
function showOnHoverEnabled() {
return getCurrentState() === 1 /* SHOWING_CONTENT_ON_HOVER */;
}
function isViewingVideo() {
if (getCurrentState() !== 2 /* IN_GALLERY */) {
return false;
}
const currentThumb3 = getCurrentThumb();
return currentThumb3 !== void 0 && isVideo(currentThumb3);
}
function enterGallery(thumb) {
currentIndex = getIndexFromThumb(thumb);
changeState(2 /* IN_GALLERY */);
}
function exitGallery() {
changeState(0 /* IDLE */);
recentlyExitedGallery = true;
setTimeout(() => {
recentlyExitedGallery = false;
}, 500);
}
function toggleShowContentOnHover() {
if (getCurrentState() === 1 /* SHOWING_CONTENT_ON_HOVER */) {
changeState(0 /* IDLE */);
return;
}
changeState(1 /* SHOWING_CONTENT_ON_HOVER */);
}
function navigate(direction) {
currentIndex += isForwardNavigationKey(direction) ? 1 : -1;
return getCurrentThumb();
}
function navigateAfterPageChange(direction) {
currentIndex = isForwardNavigationKey(direction) ? 0 : getThumbsOnCurrentPage().length - 1;
return getCurrentThumb();
}
function getThumbsAround2(thumb) {
if (ON_FAVORITES_PAGE) {
return getSearchResultsAround(thumb);
}
return getThumbsAround(thumb);
}
function setSearchResults(searchResults) {
setLatestSearchResults(searchResults);
}
function indexCurrentPageThumbs2() {
indexCurrentPageThumbs(getAllThumbs());
}
function preloadSearchPages2() {
preloadSearchPages();
}
function clampCurrentIndex() {
currentIndex = clamp(currentIndex, 0, getThumbsOnCurrentPage().length - 1);
}
function navigateSearchPages2(direction) {
return navigateSearchPages(direction);
}
function openPostInNewTab() {
const currentThumb3 = getCurrentThumb();
if (currentThumb3 !== void 0) {
openPostPage(currentThumb3.id);
}
}
function openOriginalInNewTab() {
const currentThumb3 = getCurrentThumb();
if (currentThumb3 !== void 0) {
openOriginal(currentThumb3);
}
}
function downloadInGallery() {
const currentThumb3 = getCurrentThumb();
if (currentThumb3 !== void 0) {
downloadFromThumb(currentThumb3);
}
}
function addFavoriteInGallery2() {
return addFavoriteInGallery(getCurrentThumb());
}
function removeFavoriteInGallery2() {
return removeFavoriteInGallery(getCurrentThumb());
}
async function getLinksFromCurrentThumb() {
const thumb = getCurrentThumb();
if (thumb === void 0) {
return { post: "", image: "" };
}
return { post: createPostPageURL(thumb.id), image: await getOriginalContentURL(thumb) };
}
var visibleThumbs = /* @__PURE__ */ new Map();
var centerThumb = null;
var intersectionObserver = createIntersectionObserver(getInitialFavoritesMenuHeight());
var bypassDebounce = true;
var broadcastDebounceAlways = debounceAlways((entries) => {
Events.gallery.visibleThumbsChanged.emit(entries);
}, GallerySettings.preloadContentDebounceTime);
function broadcastVisibleThumbsChanged(entries) {
if (bypassDebounce) {
bypassDebounce = false;
Events.gallery.visibleThumbsChanged.emit(entries);
} else {
broadcastDebounceAlways(entries);
}
}
function onVisibleThumbsChanged(entries) {
updateVisibleThumbs(entries);
broadcastVisibleThumbsChanged(entries);
}
function updateVisibleThumbs(entries) {
for (const entry of entries) {
if (entry.isIntersecting) {
visibleThumbs.set(entry.target.id, entry);
} else {
visibleThumbs.delete(entry.target.id);
}
}
}
function getInitialFavoritesMenuHeight() {
return -200;
}
function createIntersectionObserver(topMargin = 0) {
return new IntersectionObserver(onVisibleThumbsChanged, {
root: null,
rootMargin: getFinalRootMargin(topMargin),
threshold: [0.1]
});
}
function getFinalRootMargin(topMargin) {
return `${topMargin}px 0px ${GallerySettings.visibleThumbsDownwardScrollPercentageGenerosity}% 0px`;
}
function sortByDistanceFromCenterThumb(entries) {
if (centerThumb === null) {
return entries;
}
const centerEntry = visibleThumbs.get(centerThumb.id);
return centerEntry === void 0 ? entries : sortByDistance(centerEntry, entries);
}
function sortByDistance(centerEntry, entries) {
return entries.sort((a, b) => {
const distanceA = getRectDistance(centerEntry.boundingClientRect, a.boundingClientRect);
const distanceB = getRectDistance(centerEntry.boundingClientRect, b.boundingClientRect);
return distanceA - distanceB;
});
}
function bypassDebounceAlwaysOnPageChange() {
Events.favorites.pageChanged.on(() => {
bypassDebounce = true;
});
}
function observe(thumbs) {
for (const thumb of thumbs) {
intersectionObserver.observe(thumb);
}
}
async function observeAllThumbsOnPage() {
intersectionObserver.disconnect();
visibleThumbs.clear();
await waitForAllThumbnailsToLoad();
observe(getAllThumbs());
}
function setCenterThumb(thumb) {
centerThumb = thumb;
}
function resetCenterThumb() {
centerThumb = null;
}
function getVisibleThumbs() {
const entries = Array.from(visibleThumbs.values());
return sortByDistanceFromCenterThumb(entries).map((entry) => entry.target).filter((target2) => target2 instanceof HTMLElement);
}
function setupVisibleThumbObserver() {
if (ON_MOBILE_DEVICE || !ON_FAVORITES_PAGE) {
return;
}
bypassDebounceAlwaysOnPageChange();
}
var GALLERY_CONTAINER = document.createElement("div");
GALLERY_CONTAINER.id = "gallery-container";
GALLERY_CONTAINER.style.display = "none";
function insertGalleryContainer() {
insertStyleHTML(GALLERY_HTML);
FAVORITES_SEARCH_GALLERY_CONTAINER.insertAdjacentElement("beforeend", GALLERY_CONTAINER);
}
var GalleryBaseRenderer = class {
container;
constructor() {
this.container = document.createElement("div");
GALLERY_CONTAINER.appendChild(this.container);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
display(element) {
this.container.style.display = "block";
}
hide() {
this.container.style.display = "none";
}
};
function getGIFSource(thumb) {
const tags = getTagSetFromThumb(thumb);
const extension = tags.has("animated_png") ? "png" : "gif";
return getOriginalImageURLWithJPGExtension(thumb).replace("jpg", extension);
}
var GifRenderer = class extends GalleryBaseRenderer {
gif;
preloadedGIFs;
constructor() {
super();
this.container.id = "gif-container";
this.gif = document.createElement("img");
this.container.className = "fullscreen-image-container";
this.gif.className = "fullscreen-image";
this.preloadedGIFs = [];
this.container.appendChild(this.gif);
}
display(element) {
super.display(element);
this.gif.src = "";
this.gif.src = getGIFSource(element);
}
hide() {
super.hide();
this.gif.src = "";
}
preload(elements) {
const gifSources = elements.filter((element) => isGif(element)).slice(0, GallerySettings.preloadedGifCount).map((element) => getGIFSource(element));
for (const source of gifSources) {
const gif = new Image();
gif.src = source;
this.preloadedGIFs.push(gif);
}
}
handlePageChange() {
}
handlePageChangeInGallery() {
}
};
var GalleryGifRenderer = new GifRenderer();
function drawScaledCanvas(context, imageBitmap) {
if (context === null) {
return;
}
const canvas = context.canvas;
const ratio = Math.min(canvas.width / imageBitmap.width, canvas.height / imageBitmap.height);
const centerShiftX = (canvas.width - imageBitmap.width * ratio) / 2;
const centerShiftY = (canvas.height - imageBitmap.height * ratio) / 2;
context.drawImage(
imageBitmap,
0,
0,
imageBitmap.width,
imageBitmap.height,
centerShiftX,
centerShiftY,
imageBitmap.width * ratio,
imageBitmap.height * ratio
);
}
function drawScaledCanvasAfterClearing(context, imageBitmap) {
if (context !== null) {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
drawScaledCanvas(context, imageBitmap);
}
}
var CANVAS = document.createElement("canvas");
var CONTEXT = CANVAS.getContext("2d") ?? new CanvasRenderingContext2D();
var LANDSCAPE_STYLE = `
.fullscreen-image {
height: 100vh !important;
width: auto !important;
}
`;
var PORTRAIT_STYLE = `
.fullscreen-image {
width: 100vw !important;
height: auto !important;
}
`;
var container3;
function insertGalleryCanvas(newContainer) {
newContainer.id = "canvas-container";
newContainer.className = "fullscreen-image-container";
newContainer.appendChild(CANVAS);
container3 = newContainer;
}
function correctOrientation() {
if (ON_DESKTOP_DEVICE) {
return;
}
const usingLandscape = window.screen.orientation.angle === 90;
const usingCorrectOrientation = usingLandscape && CANVAS.width > CANVAS.height || !usingLandscape && CANVAS.width < CANVAS.height;
if (usingCorrectOrientation) {
return;
}
insertStyleHTML(usingLandscape ? LANDSCAPE_STYLE : PORTRAIT_STYLE, "gallery-canvas-orientation");
const tempWidth = CANVAS.width;
CANVAS.width = CANVAS.height;
CANVAS.height = tempWidth;
}
function setupGalleryCanvas(newContainer) {
CANVAS.className = "fullscreen-image";
const dimensions = getDimensions2D(GallerySettings.mainCanvasResolution);
CANVAS.width = dimensions.width;
CANVAS.height = dimensions.height;
correctOrientation();
insertGalleryCanvas(newContainer);
}
function draw(bitmap) {
if (bitmap !== null) {
drawScaledCanvasAfterClearing(CONTEXT, bitmap);
}
}
function clear2() {
CONTEXT.clearRect(0, 0, CANVAS.width, CANVAS.height);
}
function zoomToPoint(x, y) {
const xPercentage = clamp(roundToTwoDecimalPlaces(x / window.innerWidth), 0, 1);
const yPercentage = clamp(roundToTwoDecimalPlaces(y / window.innerHeight), 0, 1);
container3.scrollLeft = (container3.scrollWidth - container3.clientWidth) * xPercentage;
container3.scrollTop = (container3.scrollHeight - container3.clientHeight) * yPercentage;
}
var IMAGE_BITMAP_CLOSE_QUEUE = new ThrottledQueue(100);
function getFavoritePixelCount(id) {
const favorite = getFavorite(id);
if (favorite === void 0) {
return 0;
}
return favorite.metrics.width * favorite.metrics.height;
}
var ImageRequest = class {
id;
thumbURL;
thumb;
bitmap;
abortController;
cancelled;
contentType;
isLowResolution;
accentColor;
constructor(thumb, isLowResolution = false) {
this.id = thumb.id;
this.thumbURL = getPreviewURL(thumb) ?? "";
this.thumb = thumb;
this.bitmap = null;
this.abortController = new AbortController();
this.cancelled = false;
this.contentType = getContentTypeFromThumb(thumb);
this.isLowResolution = isLowResolution;
this.accentColor = null;
}
get megabytes() {
return getFavoritePixelCount(this.id) / 22e4;
}
get isImage() {
return this.contentType === "image";
}
get isAnimated() {
return !this.isImage;
}
get isIncomplete() {
return this.bitmap === null;
}
get hasCompleted() {
return !this.isIncomplete;
}
get isOriginalResolution() {
return !this.isLowResolution;
}
complete(bitmap) {
this.bitmap = bitmap;
}
stop() {
this.cancelled = true;
this.abortController.abort();
}
async close() {
if (this.bitmap instanceof ImageBitmap) {
await IMAGE_BITMAP_CLOSE_QUEUE.wait();
this.bitmap.close();
}
}
};
var ANIMATED_REQUEST_IDS = /* @__PURE__ */ new Set();
var IMAGE_REQUESTS = /* @__PURE__ */ new Map();
var FETCH_QUEUE = new ThrottledQueue(10);
var onImageCreated = DO_NOTHING;
function completeImageRequest(request) {
if (IMAGE_REQUESTS.has(request.id) && request.isImage) {
IMAGE_REQUESTS.set(request.id, request);
}
onImageCreated(request);
}
function getTruncatedImageRequests(thumbs) {
return truncateImageRequests(thumbs.map((thumb) => new ImageRequest(thumb)));
}
function truncateImageRequests(requests) {
if (ON_FAVORITES_PAGE) {
return truncateImagesOnFavoritesPage(requests);
}
return truncateImagesOnSearchPage(requests);
}
function truncateImagesOnFavoritesPage(requests) {
return truncateImagesExceedingMemoryLimit(requests);
}
function truncateImagesExceedingMemoryLimit(requests) {
const truncatedRequests = [];
let accumulatedMegabytes = 0;
let i = 0;
while (i < requests.length && (accumulatedMegabytes < GallerySettings.imageMegabyteLimit || truncatedRequests.length < GallerySettings.minimumPreloadedImageCount)) {
accumulatedMegabytes += requests[i].isImage ? requests[i].megabytes : 0;
truncatedRequests.push(requests[i]);
i += 1;
}
return truncatedRequests;
}
function truncateImagesOnSearchPage(requests) {
return requests.slice(0, GallerySettings.searchPagePreloadedImageCount).filter((request) => !request.isAnimated);
}
function removeOutdatedRequestsFromCache(newRequests) {
const idsToCache = new Set(newRequests.map((thumb) => thumb.id));
for (const [id, request] of IMAGE_REQUESTS.entries()) {
if (!idsToCache.has(id)) {
removeRequest(request);
}
}
}
function removeRequest(request) {
request.close();
request.stop();
IMAGE_REQUESTS.delete(request.id);
}
function filterAlreadyStartedRequests(requests) {
return requests.filter((request) => !ANIMATED_REQUEST_IDS.has(request.id) && !IMAGE_REQUESTS.has(request.id));
}
async function createImages(requests) {
for (const request of requests) {
await FETCH_QUEUE.wait();
createImage(request);
}
}
async function createImage(request) {
if (request.cancelled) {
return;
}
const bitmap = await fetchImageBitmapFromThumb(request.thumb, request.abortController).catch((error) => {
if (!(error instanceof DOMException) || error.name !== "AbortError") {
throw error;
}
});
if (bitmap instanceof ImageBitmap) {
request.complete(bitmap);
completeImageRequest(request);
}
}
function saveRequests(requests) {
for (const request of requests) {
saveRequest(request);
}
}
function saveRequest(request) {
if (request.isImage) {
IMAGE_REQUESTS.set(request.id, request);
} else {
ANIMATED_REQUEST_IDS.add(request.id);
}
}
function clearAnimatedImages() {
ANIMATED_REQUEST_IDS.clear();
}
function setupGalleryImageCache(creationCallback) {
onImageCreated = creationCallback;
}
function cacheImages(thumbs) {
const newRequests = getTruncatedImageRequests(thumbs);
removeOutdatedRequestsFromCache(newRequests);
const finalRequests = filterAlreadyStartedRequests(newRequests);
saveRequests(finalRequests);
createImages(finalRequests);
}
function clear3() {
for (const request of IMAGE_REQUESTS.values()) {
removeRequest(request);
}
IMAGE_REQUESTS.clear();
clearAnimatedImages();
}
function getImageRequests() {
return Array.from(IMAGE_REQUESTS.values());
}
function getImageRequest(thumb) {
return IMAGE_REQUESTS.get(thumb.id);
}
function createLowResolutionImage(thumb) {
const image = getImageFromThumb(thumb);
if (image === null || image.naturalWidth === 0 || image.naturalHeight === 0) {
return;
}
const lowResolutionRequest = new ImageRequest(thumb, true);
createImageBitmap(image).then((bitmap) => {
const originalResolutionRequest = IMAGE_REQUESTS.get(thumb.id);
if (originalResolutionRequest === void 0 || originalResolutionRequest.isIncomplete) {
lowResolutionRequest.complete(bitmap);
completeImageRequest(lowResolutionRequest);
}
});
}
function createImageFromThumb(thumb) {
const request = new ImageRequest(thumb);
createImage(request);
saveRequest(request);
}
var SharedGallerySettings = {
upscaledThumbCanvasWidth: 1e3,
maxUpscaledThumbCanvasHeight: 16e3
};
var TRANSFERRED_CANVAS_IDS = /* @__PURE__ */ new Set();
function getImageBitmapClone(imageRequest) {
if (GallerySettings.fetchImageBitmapsInWorker) {
return Promise.resolve(null);
}
if (!(imageRequest.bitmap instanceof ImageBitmap)) {
throw new Error("Tried to create upscale request without image bitmap");
}
return createImageBitmap(imageRequest.bitmap);
}
async function getUpscaleRequest(imageRequest) {
const bitmapClone = await getImageBitmapClone(imageRequest);
const imageURL = await getOriginalImageURL(imageRequest.thumb);
return new UpscaleRequest(imageRequest.thumb, bitmapClone, imageURL);
}
var UpscaleRequest = class {
id;
action;
hasDimensions;
offscreenCanvas;
bitmap;
imageURL;
sampleURL;
constructor(thumb, bitmap, imageURL) {
this.id = thumb.id;
this.action = "upscale";
this.hasDimensions = false;
this.offscreenCanvas = this.getOffscreenCanvas(thumb);
this.bitmap = bitmap;
this.imageURL = imageURL;
this.sampleURL = convertImageURLToSampleURL(imageURL);
}
get transferable() {
return this.offscreenCanvas === null ? [] : [this.offscreenCanvas];
}
getOffscreenCanvas(thumb) {
if (TRANSFERRED_CANVAS_IDS.has(this.id)) {
return null;
}
TRANSFERRED_CANVAS_IDS.add(this.id);
const canvas = thumb.querySelector("canvas");
if (canvas === null) {
throw new Error("Tried to create upscale request with null canvas");
}
this.hasDimensions = canvas.dataset.size !== void 0;
return canvas.transferControlToOffscreen();
}
};
var GalleryBaseThumbUpscaler = class {
upscaledIds;
constructor() {
this.upscaledIds = /* @__PURE__ */ new Set();
}
upscale(request) {
if (ON_DESKTOP_DEVICE && this.requestIsValid(request)) {
this.finishUpscale(request);
this.upscaledIds.add(request.id);
}
}
handlePageChange() {
this.clear();
this.presetCanvasDimensions(getAllThumbs());
}
clear() {
this.upscaledIds.clear();
}
presetCanvasDimensions(thumbs) {
if (!ON_FAVORITES_PAGE) {
return;
}
for (const item of this.getCanvasDimensions(thumbs)) {
if (TRANSFERRED_CANVAS_IDS.has(item.id)) {
continue;
}
this.setThumbCanvasDimensions(item.canvas, item.width, item.height);
}
}
getCanvasDimensions(thumbs) {
return thumbs.map((thumb) => ({
id: thumb.id,
canvas: thumb.querySelector("canvas") || new HTMLCanvasElement()
})).filter((item) => item.canvas.dataset.size !== void 0).map((item) => {
const dimensions = getDimensions2D(item.canvas.dataset.size);
return {
id: item.id,
canvas: item.canvas,
width: dimensions.width,
height: dimensions.height
};
});
}
setThumbCanvasDimensions(canvas, width, height) {
const maxHeight = SharedGallerySettings.maxUpscaledThumbCanvasHeight;
let targetWidth = SharedGallerySettings.upscaledThumbCanvasWidth;
let targetHeight = targetWidth / width * height;
if (targetWidth > width) {
targetWidth = width;
targetHeight = height;
}
if (height > maxHeight) {
targetWidth *= maxHeight / height;
targetHeight = maxHeight;
}
canvas.width = targetWidth;
canvas.height = targetHeight;
}
requestIsValid(request) {
const thumbIsOnPage = document.getElementById(request.id) !== null;
return thumbIsOnPage && ON_FAVORITES_PAGE && request.isOriginalResolution && request.hasCompleted && !this.upscaledIds.has(request.id);
}
};
var GalleryNormalThumbUpscaler = class extends GalleryBaseThumbUpscaler {
canvases = /* @__PURE__ */ new Map();
scheduler = new BatchExecutor(1, 500, this.upscaleBatch.bind(this));
drawQueue = new ThrottledQueue(75);
finishUpscale(request) {
this.finishUpscaleHelper(request);
}
finishUpscaleHelper(request) {
const canvas = request.thumb.querySelector("canvas");
if (!(canvas instanceof HTMLCanvasElement) || !(request.bitmap instanceof ImageBitmap)) {
return;
}
this.canvases.set(request.id, canvas);
this.setCanvasDimensionsFromImageBitmap(canvas, request.bitmap);
drawScaledCanvas(canvas.getContext("2d"), request.bitmap);
if (request.isAnimated) {
request.close();
}
}
clear() {
super.clear();
for (const canvas of this.canvases.values()) {
this.clearCanvas(canvas);
}
this.canvases.clear();
this.scheduler.reset();
}
setCanvasDimensionsFromImageBitmap(canvas, bitmap) {
if (canvas.dataset.size === void 0) {
this.setThumbCanvasDimensions(canvas, bitmap.width, bitmap.height);
}
}
clearCanvas(canvas) {
const context = canvas.getContext("2d");
if (context !== null) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
canvas.width = 0;
canvas.height = 0;
}
upscaleBatch(requests) {
for (const request of requests) {
this.finishUpscaleHelper(request);
}
}
};
// ts-raw:C:\Users\Allen2\Documents\Research\Rule34\Rule34FavoritesSearchGallery\developer\src\features\gallery\view\renderers\image\upscalers\gallery_offscreen_thumbnail_upscaler.ts
var gallery_offscreen_thumbnail_upscaler_default = '(() => {\n const OFFSCREEN_CANVASES = /* @__PURE__ */ new Map();\n async function createImageBitmapFromRequest(request) {\n const response = await fetch(request.imageURL);\n const blob = await response.blob();\n return createImageBitmap(blob);\n }\n function getImageBitmapFromRequest(request) {\n return request.bitmap instanceof ImageBitmap ? Promise.resolve(request.bitmap) : createImageBitmapFromRequest(request);\n }\n function drawOffscreenCanvas(context, bitmap) {\n if (context === null) {\n return;\n }\n const offscreenCanvas = context.canvas;\n const ratio = Math.min(offscreenCanvas.width / bitmap.width, offscreenCanvas.height / bitmap.height);\n const centerShiftX = (offscreenCanvas.width - bitmap.width * ratio) / 2;\n const centerShiftY = (offscreenCanvas.height - bitmap.height * ratio) / 2;\n context.drawImage(\n bitmap,\n 0,\n 0,\n bitmap.width,\n bitmap.height,\n centerShiftX,\n centerShiftY,\n bitmap.width * ratio,\n bitmap.height * ratio\n );\n bitmap.close();\n }\n function clearOffscreenCanvas(offscreenCanvas) {\n const width = offscreenCanvas.width;\n const height = offscreenCanvas.height;\n const context = offscreenCanvas.getContext("2d");\n if (context instanceof OffscreenCanvasRenderingContext2D) {\n context.clearRect(0, 0, width, height);\n }\n offscreenCanvas.width = 0;\n offscreenCanvas.height = 0;\n setTimeout(() => {\n offscreenCanvas.width = width;\n offscreenCanvas.height = height;\n }, 20);\n }\n function setOffscreenCanvasDimensions(request, bitmap) {\n if (request.hasDimensions || request.offscreenCanvas === null) {\n return;\n }\n const maxHeight = SharedGallerySettings.maxUpscaledThumbCanvasHeight;\n const width = bitmap.width;\n const height = bitmap.height;\n let targetWidth = SharedGallerySettings.upscaledThumbCanvasWidth;\n let targetHeight = targetWidth / width * height;\n if (targetWidth > width) {\n targetWidth = width;\n targetHeight = height;\n }\n if (height > maxHeight) {\n targetWidth *= maxHeight / height;\n targetHeight = maxHeight;\n }\n request.offscreenCanvas.width = targetWidth;\n request.offscreenCanvas.height = targetHeight;\n }\n function handleMessage(message) {\n const request = message.data;\n switch (request.action) {\n case "upscale":\n upscale(request.request);\n break;\n case "clear":\n clear();\n break;\n default:\n break;\n }\n }\n async function upscale(request) {\n const bitmap = await getImageBitmapFromRequest(request);\n collectOffscreenCanvas(request, bitmap);\n drawOffscreenCanvasFromRequest(request, bitmap);\n }\n function collectOffscreenCanvas(request, bitmap) {\n if (!OFFSCREEN_CANVASES.has(request.id) && request.offscreenCanvas !== null) {\n OFFSCREEN_CANVASES.set(request.id, request.offscreenCanvas);\n setOffscreenCanvasDimensions(request, bitmap);\n }\n }\n function drawOffscreenCanvasFromRequest(request, bitmap) {\n const offscreenCanvas = OFFSCREEN_CANVASES.get(request.id);\n if (offscreenCanvas === void 0) {\n return;\n }\n drawOffscreenCanvas(offscreenCanvas.getContext("2d"), bitmap);\n }\n function clear() {\n for (const offscreenCanvas of OFFSCREEN_CANVASES.values()) {\n clearOffscreenCanvas(offscreenCanvas);\n }\n }\n onmessage = handleMessage;\n})();\n';
// ts-raw:C:\Users\Allen2\Documents\Research\Rule34\Rule34FavoritesSearchGallery\developer\src\config\shared_gallery_settings.ts
var shared_gallery_settings_default = "(() => {\n const SharedGallerySettings = {\n upscaledThumbCanvasWidth: 1e3,\n maxUpscaledThumbCanvasHeight: 16e3\n };\n})();\n";
function createWebWorker(script) {
const blob = new Blob([script], { type: "application/javascript" });
return new Worker(URL.createObjectURL(blob));
}
var GalleryOffscreenThumbnailUpscalerWrapper = class extends GalleryBaseThumbUpscaler {
worker;
upscaleQueue;
constructor() {
super();
this.worker = createWebWorker(`${removeFirstAndLastLines(shared_gallery_settings_default)}
${gallery_offscreen_thumbnail_upscaler_default}`);
this.upscaleQueue = new ThrottledQueue(100);
}
async finishUpscale(request) {
const upscaleRequest = await getUpscaleRequest(request);
this.sendRequestToWorker(upscaleRequest);
}
clear() {
super.clear();
this.upscaleQueue.reset();
this.worker.postMessage({
action: "clear"
});
}
sendRequestToWorker(request) {
this.worker.postMessage({
action: "upscale",
request
}, request.transferable);
}
};
var UPSCALER = GallerySettings.useOffscreenThumbUpscaler ? new GalleryOffscreenThumbnailUpscalerWrapper() : new GalleryNormalThumbUpscaler();
var ImageRenderer = class extends GalleryBaseRenderer {
lastShownId;
constructor() {
super();
setupGalleryImageCache(this.onImageCreated.bind(this));
setupGalleryCanvas(this.container);
this.lastShownId = "";
}
display(thumb) {
super.display(thumb);
this.draw(thumb);
}
preload(thumbs) {
cacheImages(thumbs);
}
handlePageChange() {
clear3();
UPSCALER.handlePageChange();
}
handlePageChangeInGallery() {
clearAnimatedImages();
setTimeout(() => {
UPSCALER.handlePageChange();
this.upscaleThumbsWithAvailableImages();
}, 10);
}
handleResultsAddedToCurrentPage(thumbs) {
UPSCALER.presetCanvasDimensions(thumbs);
}
upscaleThumbsWithAvailableImages() {
for (const request of getImageRequests()) {
UPSCALER.upscale(request);
}
}
clear() {
clear2();
}
onImageCreated(request) {
UPSCALER.upscale(request);
if (request.id === this.lastShownId) {
this.draw(request.thumb);
}
}
toggleZoom(value) {
return this.container.classList.toggle("zoomed-in", value);
}
toggleZoomCursor(value) {
this.container.classList.toggle("zooming", value);
}
zoomToPoint(x, y) {
zoomToPoint(x, y);
}
correctOrientation() {
correctOrientation();
this.redrawOnOrientationChange();
}
redrawOnOrientationChange() {
const thumb = document.getElementById(this.lastShownId);
if (thumb === null) {
return;
}
const imageRequest = getImageRequest(thumb);
if (imageRequest === void 0 || imageRequest.isIncomplete) {
return;
}
this.draw(thumb);
}
draw(thumb) {
this.lastShownId = thumb.id;
const imageRequest = getImageRequest(thumb);
if (imageRequest === void 0) {
createImageFromThumb(thumb);
createLowResolutionImage(thumb);
return;
}
if (imageRequest.isIncomplete) {
createLowResolutionImage(thumb);
return;
}
draw(imageRequest.bitmap);
}
};
var GalleryImageRenderer = new ImageRenderer();
var VIDEO_PLAYERS = [];
var VIDEO_CLIPS = /* @__PURE__ */ new Map();
var VIDEO_CONTAINER = document.createElement("div");
VIDEO_CONTAINER.id = "video-container-inner";
function createVideoPlayer(volume, muted) {
const video = document.createElement("video");
video.setAttribute("width", "100%");
video.setAttribute("height", "100%");
video.autoplay = true;
video.volume = volume;
video.muted = muted;
video.loop = true;
video.playsInline = true;
video.setAttribute("controlsList", "nofullscreen");
video.setAttribute("webkit-playsinline", "");
VIDEO_PLAYERS.push(video);
VIDEO_CONTAINER.appendChild(video);
}
function createVideoPlayers() {
const volume = Preferences.videoVolume.value;
const muted = Preferences.videoMuted.value;
createVideoPlayer(volume, muted);
for (let i = 0; i < GallerySettings.preloadedVideoCount; i += 1) {
createVideoPlayer(volume, muted);
}
}
function preventVideoPlayersFromFlashingWhenLoaded() {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (context !== null) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
canvas.toBlob((blob) => {
if (blob === null) {
return;
}
const videoBackgroundURL = URL.createObjectURL(blob);
for (const video of VIDEO_PLAYERS) {
video.setAttribute("poster", videoBackgroundURL);
}
});
}
function preventDefaultBehaviorWhenControlKeyIsPressed() {
VIDEO_CONTAINER.onclick = (event) => {
if (!event.ctrlKey) {
event.preventDefault();
}
};
}
function addEventListenersToVideoContainer() {
preventDefaultBehaviorWhenControlKeyIsPressed();
}
function insertVideoContainer(container4) {
container4.appendChild(VIDEO_CONTAINER);
}
function setupVideoController(container4) {
insertVideoContainer(container4);
createVideoPlayers();
preventVideoPlayersFromFlashingWhenLoaded();
addEventListenersToVideoContainer();
addEventListenersToVideoPlayers();
loadVideoClips();
}
function addEventListenersToVideoPlayers() {
for (const video of VIDEO_PLAYERS) {
addEventListenerToVideoPlayer(video);
}
}
function addEventListenerToVideoPlayer(video) {
revealControlsWhenMouseMoves(video);
pauseWhenClicked(video);
updateVolumeOfOtherVideoPlayersWhenVolumeChanges(video);
broadcastEnding(video);
broadcastDoubleClick(video);
revealControlsWhenTouched(video);
}
function revealControlsWhenMouseMoves(video) {
if (ON_MOBILE_DEVICE) {
return;
}
video.addEventListener("mousemove", () => {
if (!video.hasAttribute("controls")) {
video.setAttribute("controls", "");
}
}, {
passive: true
});
}
function pauseWhenClicked(video) {
video.addEventListener("click", (event) => {
if (event.ctrlKey) {
return;
}
toggleVideoPause(video);
}, {
passive: true
});
}
function toggleVideoPause(video) {
if (video.paused) {
video.play().catch(() => {
});
} else {
video.pause();
}
}
function toggleVideoMute() {
getActiveVideoPlayer().muted = !getActiveVideoPlayer().muted;
Preferences.videoMuted.set(getActiveVideoPlayer().muted);
}
function updateVolumeOfOtherVideoPlayersWhenVolumeChanges(video) {
video.addEventListener("volumechange", (event) => {
if (!(event.target instanceof HTMLVideoElement)) {
return;
}
if (event.target === null || !event.target.hasAttribute("active")) {
return;
}
Preferences.videoVolume.set(video.volume);
Preferences.videoMuted.set(video.muted);
for (const v of getInactiveVideoPlayers()) {
v.volume = video.volume;
v.muted = video.muted;
}
}, {
passive: true
});
}
function broadcastEnding(video) {
video.addEventListener("ended", () => {
Events.gallery.videoEnded.emit();
}, {
passive: true
});
}
function broadcastDoubleClick(video) {
video.addEventListener("dblclick", (event) => {
Events.gallery.videoDoubleClicked.emit(event);
});
}
function revealControlsWhenTouched(video) {
if (ON_DESKTOP_DEVICE) {
return;
}
video.addEventListener("touchend", () => {
toggleVideoControls(true);
}, {
passive: true
});
}
function loadVideoClips() {
setTimeout(() => {
let storedVideoClips;
try {
storedVideoClips = JSON.parse(localStorage.getItem("storedVideoClips") || "{}");
for (const [id, videoClip] of Object.entries(storedVideoClips)) {
VIDEO_CLIPS.set(id, videoClip);
}
} catch (error) {
console.error(error);
}
}, 50);
}
function getActiveVideoPlayer() {
return VIDEO_PLAYERS.find((video) => video.hasAttribute("active")) || VIDEO_PLAYERS[0];
}
function getInactiveVideoPlayers() {
return VIDEO_PLAYERS.filter((video) => !video.hasAttribute("active"));
}
function playVideo(thumb) {
setActiveVideoPlayer(thumb);
toggleVideoContainer(true);
stopAllVideos();
const video = getActiveVideoPlayer();
setVideoSource(video, thumb);
video.style.display = "block";
video.play().catch(() => {
});
toggleVideoControls(true);
}
function stopAllVideos() {
for (const video of VIDEO_PLAYERS) {
stopVideo(video);
}
}
function stopVideo(video) {
video.style.display = "none";
pauseVideo(video);
}
function pauseVideo(video) {
video.pause();
video.removeAttribute("controls");
}
function preloadVideoPlayers(thumbs) {
const activeVideoPlayer = getActiveVideoPlayer();
const inactiveVideoPlayers = getInactiveVideoPlayers();
const videoThumbsAroundInitialThumb = thumbs.filter((thumb) => isVideo(thumb) && !videoPlayerHasSource(activeVideoPlayer, thumb)).slice(0, inactiveVideoPlayers.length);
const loadedVideoSources = new Set(inactiveVideoPlayers.map((video) => video.src).filter((src) => src !== ""));
const videoSourcesAroundInitialThumb = new Set(videoThumbsAroundInitialThumb.map((thumb) => getVideoSource(thumb)));
const videoThumbsNotLoaded = videoThumbsAroundInitialThumb.filter((thumb) => !loadedVideoSources.has(getVideoSource(thumb)));
const freeInactiveVideoPlayers = inactiveVideoPlayers.filter((video) => !videoSourcesAroundInitialThumb.has(video.src));
for (let i = 0; i < freeInactiveVideoPlayers.length && i < videoThumbsNotLoaded.length; i += 1) {
setVideoSource(freeInactiveVideoPlayers[i], videoThumbsNotLoaded[i]);
pauseVideo(freeInactiveVideoPlayers[i]);
}
}
function videoPlayerHasSource(video, thumb) {
return video.src === getVideoSource(thumb);
}
function getVideoSource(thumb) {
return convertPreviewURLToImageURL(getPreviewURL(thumb) ?? "").replace("jpg", "mp4");
}
function setVideoSource(video, thumb) {
if (videoPlayerHasSource(video, thumb)) {
return;
}
createVideoClip(video, thumb);
video.src = getVideoSource(thumb);
}
function createVideoClip(video, thumb) {
const videoClip = VIDEO_CLIPS.get(thumb.id);
if (videoClip === void 0) {
video.ontimeupdate = null;
return;
}
video.ontimeupdate = () => {
if (video.currentTime < videoClip.start || video.currentTime > videoClip.end) {
video.removeAttribute("controls");
video.currentTime = videoClip.start;
}
};
}
function setActiveVideoPlayer(thumb) {
for (const video of VIDEO_PLAYERS) {
video.removeAttribute("active");
}
for (const video of VIDEO_PLAYERS) {
if (videoPlayerHasSource(video, thumb)) {
video.setAttribute("active", "");
return;
}
}
VIDEO_PLAYERS[0].setAttribute("active", "");
}
function toggleVideoControls(value) {
const video = getActiveVideoPlayer();
if (ON_MOBILE_DEVICE) {
if (value) {
video.setAttribute("controls", "");
}
} else {
}
if (!value) {
video.removeAttribute("controls");
}
}
function clearVideoSources() {
for (const video of VIDEO_PLAYERS) {
video.src = "";
}
}
function toggleVideoLooping(value) {
for (const video of VIDEO_PLAYERS) {
video.toggleAttribute("loop", value);
}
}
function toggleVideoContainer(value) {
VIDEO_CONTAINER.style.display = value ? "block" : "none";
}
function toggleActiveVideoPause() {
if (document.activeElement !== getActiveVideoPlayer()) {
toggleVideoPause(getActiveVideoPlayer());
}
}
function restartActiveVideo() {
getActiveVideoPlayer().play().catch();
}
var VideoRenderer = class extends GalleryBaseRenderer {
constructor() {
super();
this.container.id = "video-container";
setupVideoController(this.container);
}
display(thumb) {
super.display(thumb);
playVideo(thumb);
}
hide() {
super.hide();
stopAllVideos();
}
handlePageChange() {
clearVideoSources();
}
handlePageChangeInGallery() {
}
preload(thumbs) {
preloadVideoPlayers(thumbs);
}
toggleVideoLooping(value) {
toggleVideoLooping(value);
}
restartVideo() {
restartActiveVideo();
}
toggleVideoPause() {
toggleActiveVideoPause();
}
toggleVideoMute() {
toggleVideoMute();
}
};
var GalleryVideoRenderer = new VideoRenderer();
function getRenderers() {
return [GalleryImageRenderer, GalleryGifRenderer, GalleryVideoRenderer];
}
function render(thumb) {
switch (true) {
case isVideo(thumb):
return startRenderer(GalleryVideoRenderer, thumb);
case isGif(thumb):
return startRenderer(GalleryGifRenderer, thumb);
default:
return startRenderer(GalleryImageRenderer, thumb);
}
}
function startRenderer(targetRenderer, thumb) {
for (const renderer of getRenderers()) {
if (renderer === targetRenderer) {
renderer.display(thumb);
} else {
renderer.hide();
}
}
}
function clear4() {
for (const renderer of getRenderers()) {
renderer.hide();
}
}
function preloadContentOutOfGallery(thumbs) {
GalleryImageRenderer.preload(thumbs);
}
function preloadContentInGallery(thumbs) {
for (const renderer of getRenderers()) {
renderer.preload(thumbs);
}
}
function handlePageChange() {
for (const renderer of getRenderers()) {
renderer.handlePageChange();
}
}
function handlePageChangeInGallery() {
for (const renderer of getRenderers()) {
renderer.handlePageChangeInGallery();
}
}
function handleResultsAddedToCurrentPage(thumbs) {
GalleryImageRenderer.handleResultsAddedToCurrentPage(thumbs);
}
function toggleVideoLooping2(value) {
GalleryVideoRenderer.toggleVideoLooping(value);
}
function restartVideo() {
GalleryVideoRenderer.restartVideo();
}
function toggleVideoPause2() {
GalleryVideoRenderer.toggleVideoPause();
}
function toggleVideoMute2() {
GalleryVideoRenderer.toggleVideoMute();
}
function toggleZoom(value) {
return GalleryImageRenderer.toggleZoom(value);
}
function toggleZoomCursor(value) {
GalleryImageRenderer.toggleZoomCursor(value);
}
function zoomToPoint2(x, y) {
GalleryImageRenderer.zoomToPoint(x, y);
}
function correctOrientation2() {
GalleryImageRenderer.correctOrientation();
}
var background = document.createElement("div");
background.id = "gallery-background";
background.style.opacity = Preferences.backgroundOpacity.value;
var lastVisitedThumb = null;
function isUsingColumnLayout() {
return ON_FAVORITES_PAGE && document.querySelector("#favorites-search-gallery-content.column") !== null;
}
function setupGalleryUI() {
GALLERY_CONTAINER.appendChild(background);
toggleVideoPointerEvents(false);
toggleGalleryMenuVisibility(false);
}
function enterGallery2(thumb) {
setLastVisitedThumb(thumb);
blurCurrentlyFocusedElement();
toggleBackgroundInteractability(true);
toggleScrollbar(false);
toggleVideoPointerEvents(true);
toggleGalleryMenuVisibility(true);
}
function exitGallery2() {
toggleBackgroundInteractability(false);
toggleScrollbar(true);
scrollToLastVisitedThumb();
toggleVideoPointerEvents(false);
toggleCursor(true);
toggleGalleryMenuVisibility(false);
}
function scrollToLastVisitedThumb() {
waitForAllThumbnailsToLoad().then(() => {
if (lastVisitedThumb !== null && isUsingColumnLayout()) {
scrollToThumb(lastVisitedThumb);
}
});
}
function toggleVideoPointerEvents(value) {
insertStyleHTML(`
video {
pointer-events: ${value ? "auto" : "none"}
}
`, "video-pointer-events");
}
function toggleBackgroundInteractability(value) {
background.classList.toggle("in-gallery", value);
}
function toggleBackgroundOpacity() {
const opacity = parseFloat(background.style.opacity);
if (opacity < 1) {
updateBackgroundOpacity(1);
} else {
updateBackgroundOpacity(0);
}
}
function show() {
toggleScrollbar(false);
}
function hide() {
toggleScrollbar(true);
}
function toggleScrollbar(value) {
document.body.style.overflowY = value ? "auto" : "hidden";
}
function updateUIInGallery(thumb) {
setLastVisitedThumb(thumb);
if (isUsingColumnLayout() || USING_FIREFOX) {
return;
}
scrollToThumb(thumb);
}
function updateBackgroundOpacityFromEvent(event) {
let opacity = parseFloat(Preferences.backgroundOpacity.value);
opacity -= event.deltaY * 5e-4;
opacity = clamp(opacity, 0, 1);
updateBackgroundOpacity(roundToTwoDecimalPlaces(opacity));
}
function updateBackgroundOpacity(opacity) {
const opacityString = String(opacity);
background.style.opacity = opacityString;
Preferences.backgroundOpacity.set(opacityString);
}
function showAddedFavoriteStatus(status) {
const icon = {
[1 /* ALREADY_ADDED */]: HEART_CHECK,
[3 /* SUCCESSFULLY_ADDED */]: HEART_PLUS,
[0 /* ERROR */]: ERROR,
[2 /* NOT_LOGGED_IN */]: ERROR
}[status] ?? ERROR;
showFullscreenIcon(icon);
}
function showRemovedFavoriteStatus(status) {
switch (status) {
case 2 /* SUCCESSFULLY_REMOVED */:
showFullscreenIcon(HEART_MINUS);
break;
case 1 /* FORBIDDEN */:
showFullscreenIcon(WARNING, 1e3);
setTimeout(() => {
alert('The "Remove Buttons" option must be checked to use this hotkey');
}, 20);
break;
default:
break;
}
}
function setLastVisitedThumb(thumb) {
lastVisitedThumb = thumb;
}
function scrollToThumb(thumb) {
thumb.scrollIntoView({
behavior: "smooth",
block: "center"
});
}
function toggleCursor(value) {
background.style.cursor = value ? "default" : "none";
}
function toggleGalleryMenuVisibility(value) {
insertStyleHTML(`
#gallery-menu {
display: ${value ? "flex" : "none"} !important;
}
`, "gallery-menu-visibility");
}
function toggleZoomCursor2(value) {
background.classList.toggle("zooming", value);
}
var thumbContainer = null;
function getMainThumbnailContainer() {
const thumb = document.querySelector(".thumb");
if (thumb !== null) {
return thumb.parentElement;
}
return document.querySelector(".image-list");
}
function insertNewThumbs(searchPage) {
if (thumbContainer === null) {
return;
}
thumbContainer.innerHTML = "";
for (const thumb of searchPage.thumbs) {
thumbContainer.appendChild(thumb);
}
}
function updatePaginator(searchPage) {
if (searchPage.paginator === null) {
return;
}
const currentPaginator = document.getElementById("paginator");
const placeToInsert = currentPaginator || thumbContainer;
if (placeToInsert === null) {
return;
}
placeToInsert.insertAdjacentElement("afterend", searchPage.paginator);
if (currentPaginator !== null) {
currentPaginator.remove();
}
}
function updateAddressBar(searchPage) {
const baseURL = location.origin + location.pathname;
const searchFragment = `${location.search.replace(/&pid=\d+/g, "")}&pid=${searchPage.pageNumber * 42}`;
window.history.replaceState(null, "", baseURL + searchFragment);
}
async function setupSearchPageCreator() {
await waitForDOMToLoad();
thumbContainer = getMainThumbnailContainer();
}
function createSearchPage(searchPage) {
insertNewThumbs(searchPage);
updatePaginator(searchPage);
updateAddressBar(searchPage);
}
function showContentInGallery(thumb) {
display(thumb);
updateUIInGallery(thumb);
}
function display(thumb) {
toggleVisibility2(true);
render(thumb);
show();
toggleZoom(false);
}
function hide2() {
toggleVisibility2(false);
clear4();
hide();
}
function enterGallery3(thumb) {
render(thumb);
enterGallery2(thumb);
toggleVisibility2(true);
}
function exitGallery3() {
clear4();
exitGallery2();
toggleVisibility2(false);
toggleZoomCursor3(false);
}
function toggleVisibility2(value) {
GALLERY_CONTAINER.style.display = value ? "" : "none";
}
function preloadContentOutOfGallery2(thumbs) {
if (GallerySettings.preloadingEnabled && ON_DESKTOP_DEVICE) {
preloadContentOutOfGallery(thumbs);
}
}
function preloadContentInGallery2(thumbs) {
if (GallerySettings.preloadingEnabled) {
preloadContentInGallery(thumbs);
}
}
function handlePageChange2() {
handlePageChange();
}
function handlePageChangeInGallery2() {
handlePageChangeInGallery();
scrollToLastVisitedThumb();
}
function handleMouseMoveInGallery() {
toggleCursor2(true);
}
function toggleBackgroundOpacity2() {
toggleBackgroundOpacity();
}
function updateBackgroundOpacity2(event) {
updateBackgroundOpacityFromEvent(event);
}
function showAddedFavoriteStatus2(status) {
showAddedFavoriteStatus(status);
}
function showRemovedFavoriteStatus2(status) {
showRemovedFavoriteStatus(status);
}
function toggleCursor2(value) {
toggleCursor(value);
}
function toggleVideoLooping3(value) {
toggleVideoLooping2(value);
}
function restartVideo2() {
restartVideo();
}
function createSearchPage2(searchPage) {
createSearchPage(searchPage);
}
function toggleVideoPause3() {
toggleVideoPause2();
}
function toggleVideoMute3() {
toggleVideoMute2();
}
function handleResultsAddedToCurrentPage2(thumbs) {
handleResultsAddedToCurrentPage(thumbs);
}
function toggleZoomCursor3(value) {
toggleZoomCursor2(value);
toggleZoomCursor(value);
}
function toggleZoom2(value = void 0) {
const zoomedIn = toggleZoom(value);
return zoomedIn;
}
function zoomToPoint3(x, y) {
zoomToPoint2(x, y);
}
function correctOrientation3() {
correctOrientation2();
}
function setupGalleryView() {
setupGalleryUI();
setupSearchPageCreator();
}
function setLinks(links) {
}
function executeFunctionBasedOnGalleryState(executors, args) {
const executor = {
[0 /* IDLE */]: executors.idle,
[1 /* SHOWING_CONTENT_ON_HOVER */]: executors.hover,
[2 /* IN_GALLERY */]: executors.gallery
}[getCurrentState2()];
if (executor) {
executor(args);
}
}
function handlePageChange3() {
resetCenterThumb();
observeAllThumbsOnPage();
indexCurrentPageThumbs2();
executeFunctionBasedOnGalleryState({
idle: handlePageChange2,
hover: handlePageChange2,
gallery: handlePageChangeInGallery2
});
}
function handleResultsAddedToCurrentPage3(results) {
observe(results);
indexCurrentPageThumbs2();
handleResultsAddedToCurrentPage2(results);
}
function handleNewFavoritesFoundOnReload() {
observeAllThumbsOnPage();
indexCurrentPageThumbs2();
}
function inGalleryResponse() {
Events.gallery.inGalleryResponse.emit(inGallery());
}
async function addFavoriteInGallery3() {
showAddedFavoriteStatus2(await addFavoriteInGallery2());
}
async function removeFavoriteInGallery3() {
showRemovedFavoriteStatus2(await removeFavoriteInGallery2());
}
function preloadContentInGalleryAround(thumb) {
if (thumb !== null) {
preloadContentInGallery2(getThumbsAround2(thumb));
}
}
function preloadVisibleContentAround(thumb) {
if (ON_FAVORITES_PAGE && !hasRecentlyExitedGallery() && thumb !== null) {
setCenterThumb(thumb);
preloadVisibleContent();
}
}
function preloadVisibleContent() {
if (getCurrentState2() === 2 /* IN_GALLERY */) {
return;
}
const thumbs = getVisibleThumbs();
if (thumbs.length < GallerySettings.maxVisibleThumbsBeforeStoppingPreload && thumbs.length > 0) {
preloadContentOutOfGallery2(thumbs);
}
}
function changeFavoritesPageInGallery(direction) {
return new Promise((resolve, reject) => {
const onPageChangeInGallery = () => {
const thumb = navigateAfterPageChange(direction);
if (thumb === void 0) {
reject(new Error("Could not find favorite after changing page"));
} else {
resolve(thumb);
}
};
Events.favorites.pageChangeResponse.timeout(50).then(onPageChangeInGallery).catch(onPageChangeInGallery);
Events.gallery.pageChangeRequested.emit(direction);
});
}
function changeSearchPageInGallery(direction) {
const searchPage = navigateSearchPages2(direction);
if (searchPage === null) {
clampCurrentIndex();
return;
}
preloadSearchPages2();
createSearchPage2(searchPage);
handlePageChange3();
const thumb = navigateAfterPageChange(direction);
if (thumb === void 0) {
console.error("Could not navigate in gallery");
} else {
completeNavigation(thumb);
}
}
async function completeNavigation(thumb) {
showContentInGallery(thumb);
startViewTimer(thumb);
preloadContentInGalleryAround(thumb);
if (ON_MOBILE_DEVICE) {
setLinks(await getLinksFromCurrentThumb());
}
}
function navigateRight() {
navigate2("ArrowRight");
}
function navigateLeft() {
navigate2("ArrowLeft");
}
function navigate2(direction) {
const thumb = navigate(direction);
const thumbIsOnPage = thumb !== void 0;
if (thumbIsOnPage) {
completeNavigation(thumb);
return;
}
if (ON_FAVORITES_PAGE) {
changeFavoritesPageThenNavigate(direction);
return;
}
changeSearchPageInGallery(direction);
}
async function changeFavoritesPageThenNavigate(direction) {
completeNavigation(await changeFavoritesPageInGallery(direction));
}
function toggleGalleryImageZoom(value = void 0) {
const zoomedIn = toggleZoom2(value);
Events.document.wheel.toggle(!zoomedIn);
return zoomedIn;
}
var InteractionTracker = class {
onInteractionStopped;
onMouseMoveStopped;
onScrollingStopped;
onNoInteractionOnStart;
idleDuration;
mouseTimeout;
scrollTimeout;
interactionOnStartTimeout;
mouseIsMoving;
scrolling;
abortController;
constructor(idleDuration, onInteractionStopped, onMouseMoveStopped, onScrollingStopped, onNoInteractionOnStart) {
this.idleDuration = idleDuration;
this.onInteractionStopped = onInteractionStopped;
this.onMouseMoveStopped = onMouseMoveStopped;
this.onScrollingStopped = onScrollingStopped;
this.onNoInteractionOnStart = onNoInteractionOnStart;
this.mouseIsMoving = false;
this.scrolling = false;
this.abortController = new AbortController();
}
start() {
this.toggle(true);
}
stop() {
this.toggle(false);
}
toggle(value) {
if (value) {
this.abortController = new AbortController();
this.startInteractionOnStartTimer();
this.trackMouseMove();
this.trackScroll();
return;
}
this.abortController.abort();
}
startInteractionOnStartTimer() {
this.interactionOnStartTimeout = setTimeout(() => {
this.onNoInteractionOnStart();
}, this.idleDuration);
}
trackMouseMove() {
Events.document.mousemove.on(this.onMouseMove.bind(this), {
passive: true,
signal: this.abortController.signal
});
}
trackScroll() {
window.addEventListener("scroll", this.onScroll.bind(this), {
passive: true,
signal: this.abortController.signal
});
}
onMouseMove() {
this.mouseIsMoving = true;
clearTimeout(this.interactionOnStartTimeout);
clearTimeout(this.mouseTimeout);
this.mouseTimeout = setTimeout(() => {
this.mouseIsMoving = false;
this.onMouseMoveStopped();
if (!this.scrolling) {
this.onInteractionStopped();
}
}, this.idleDuration);
}
onScroll() {
this.scrolling = true;
clearTimeout(this.interactionOnStartTimeout);
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(() => {
this.scrolling = false;
this.onScrollingStopped();
if (!this.mouseIsMoving) {
this.onInteractionStopped();
}
}, this.idleDuration);
}
};
var GalleryInteractionTracker = null;
function createGalleryInteractionTracker() {
if (ON_MOBILE_DEVICE) {
return null;
}
const hideCursor = () => {
executeFunctionBasedOnGalleryState({
gallery: () => {
toggleCursor2(false);
}
});
};
return new InteractionTracker(
GallerySettings.idleInteractionDuration,
DO_NOTHING,
hideCursor,
DO_NOTHING,
hideCursor
);
}
function setupGalleryInteractionTracker() {
if (ON_DESKTOP_DEVICE) {
GalleryInteractionTracker = createGalleryInteractionTracker();
}
}
function enterGallery4(thumb) {
enterGallery(thumb);
enterGallery3(thumb);
GalleryInteractionTracker?.start();
startAutoplay(thumb);
preloadContentInGalleryAround(thumb);
Events.gallery.showOnHoverToggled.emit(false);
Events.gallery.enteredGallery.emit();
}
function exitGallery4() {
exitGallery();
exitGallery3();
GalleryInteractionTracker?.stop();
stopAutoplay();
toggleGalleryImageZoom(false);
Events.gallery.exitedGallery.emit();
}
function toggleShowContentOnHover2() {
toggleShowContentOnHover();
Events.gallery.showOnHoverToggled.emit(showOnHoverEnabled());
}
function onKeyDownInGallery(keyboardEvent) {
const event = keyboardEvent.originalEvent;
if (isNavigationKey(event.key)) {
event.stopImmediatePropagation();
navigate2(event.key);
return;
}
if (isExitKey(event.key)) {
exitGallery4();
return;
}
if (event.shiftKey) {
toggleZoomCursor3(true);
return;
}
const currentThumb3 = getCurrentThumb();
switch (event.key.toLowerCase()) {
case "b":
toggleBackgroundOpacity2();
break;
case "e":
addFavoriteInGallery3();
break;
case "x":
removeFavoriteInGallery3();
break;
case "f":
toggleFullscreen();
break;
case "g":
openPostInNewTab();
break;
case "q":
openOriginalInNewTab();
break;
case "s":
downloadInGallery();
break;
case "m":
toggleVideoMute3();
break;
case " ":
if (currentThumb3 !== void 0 && isVideo(currentThumb3)) {
toggleVideoPause3();
}
break;
default:
break;
}
}
function onKeyDownOutsideGallery(event) {
if (!event.isHotkey) {
return;
}
switch (event.key.toLowerCase()) {
case "f":
toggleFullscreen();
break;
default:
break;
}
}
var onKeyDownNoThrottle = (event) => {
executeFunctionBasedOnGalleryState({
idle: onKeyDownOutsideGallery,
hover: onKeyDownOutsideGallery,
gallery: onKeyDownInGallery
}, new FavoritesKeyboardEvent(event));
};
var onKeyDownThrottled = throttle(onKeyDownNoThrottle, 100);
function onKeyUpInGallery(event) {
if (event.key === "shift") {
toggleZoomCursor3(false);
}
}
function onKeyDown(keyboardEvent) {
const event = keyboardEvent.originalEvent;
if (event.repeat) {
onKeyDownThrottled(event);
} else {
onKeyDownNoThrottle(event);
}
}
function onKeyUp(event) {
executeFunctionBasedOnGalleryState({
gallery: onKeyUpInGallery
}, event);
}
function onGalleryMenuAction(action) {
switch (action) {
case "exit":
exitGallery4();
break;
case "openPost":
openPostInNewTab();
break;
case "openOriginal":
openOriginalInNewTab();
break;
case "download":
downloadInGallery();
break;
case "addFavorite":
addFavoriteInGallery3();
break;
case "removeFavorite":
removeFavoriteInGallery3();
break;
case "toggleBackground":
toggleBackgroundOpacity2();
break;
case "none":
break;
default:
break;
}
}
function onMouseOverWhileHoverEnabled(thumb) {
if (thumb === null) {
hide2();
return;
}
display(thumb);
preloadVisibleContentAround(thumb);
}
function onMouseDownInGallery(mouseEvent) {
if (mouseEvent.ctrlKey || overGalleryMenu(mouseEvent.originalEvent)) {
return;
}
if (mouseEvent.shiftKey) {
if (toggleGalleryImageZoom()) {
zoomToPoint3(mouseEvent.originalEvent.x, mouseEvent.originalEvent.y);
}
return;
}
const zoomedIn = mouseEvent.originalEvent.target instanceof HTMLElement && mouseEvent.originalEvent.target.closest(".zoomed-in") !== null;
if (mouseEvent.leftClick && !zoomedIn && !isViewingVideo()) {
exitGallery4();
return;
}
if (mouseEvent.rightClick) {
return;
}
if (mouseEvent.middleClick) {
openPostInNewTab();
}
}
function onMouseDownOutsideGallery(mouseEvent) {
if (mouseEvent.leftClick && mouseEvent.thumb !== null && !mouseEvent.ctrlKey) {
mouseEvent.originalEvent.preventDefault();
enterGallery4(mouseEvent.thumb);
return;
}
if (mouseEvent.middleClick && mouseEvent.thumb === null) {
mouseEvent.originalEvent.preventDefault();
toggleShowContentOnHover2();
}
}
function onClickInGallery(mouseEvent) {
if (mouseEvent.ctrlKey) {
openOriginalInNewTab();
}
}
function onContextMenuInGallery(mouseEvent) {
mouseEvent.preventDefault();
exitGallery4();
}
function onWheelWhileHoverEnabled(wheelEvent) {
updateBackgroundOpacity2(wheelEvent.originalEvent);
}
function onWheelInGallery(wheelEvent) {
if (!wheelEvent.originalEvent.shiftKey) {
navigate2(wheelEvent.direction);
}
}
var onMouseMove = throttle(() => {
executeFunctionBasedOnGalleryState({
gallery: handleMouseMoveInGallery
});
}, 250);
function onMouseOver(mouseEvent) {
executeFunctionBasedOnGalleryState({
hover: onMouseOverWhileHoverEnabled,
idle: preloadVisibleContentAround
}, mouseEvent.thumb);
}
function onClick(mouseEvent) {
executeFunctionBasedOnGalleryState({
gallery: onClickInGallery
}, mouseEvent);
}
function onMouseDown(event) {
executeFunctionBasedOnGalleryState({
hover: onMouseDownOutsideGallery,
idle: onMouseDownOutsideGallery,
gallery: onMouseDownInGallery
}, new FavoritesMouseEvent(event));
}
function onContextMenu(mouseEvent) {
executeFunctionBasedOnGalleryState({
gallery: onContextMenuInGallery
}, mouseEvent);
}
function onWheel(wheelEvent) {
executeFunctionBasedOnGalleryState({
hover: onWheelWhileHoverEnabled,
gallery: onWheelInGallery
}, wheelEvent);
}
function onSwipeDown() {
executeFunctionBasedOnGalleryState({
gallery: exitGallery4
});
}
function galleryEnabled() {
return ON_FAVORITES_PAGE && Preferences.mobileGalleryEnabled.value || ON_SEARCH_PAGE && Preferences.searchPagesEnabled.value;
}
function onMouseDownOutsideGallery2(mouseEvent) {
if (mouseEvent.thumb !== null && galleryEnabled()) {
mouseEvent.originalEvent.preventDefault();
mouseEvent.originalEvent.stopPropagation();
mouseEvent.originalEvent.stopImmediatePropagation();
enterGallery4(mouseEvent.thumb);
}
}
function onTouchStartInGallery(event) {
if (event.target instanceof HTMLElement && event.target.closest("#gallery-menu") !== null) {
return;
}
event.preventDefault();
}
function onMouseDown2(event) {
executeFunctionBasedOnGalleryState({
hover: onMouseDownOutsideGallery2,
idle: onMouseDownOutsideGallery2
}, new FavoritesMouseEvent(event));
}
function onTouchStart(event) {
executeFunctionBasedOnGalleryState({
gallery: onTouchStartInGallery
}, event);
}
function onLeftTap() {
if (didSwipe()) {
return;
}
executeFunctionBasedOnGalleryState({
gallery: navigateLeft
});
}
function onRightTap() {
if (didSwipe()) {
return;
}
executeFunctionBasedOnGalleryState({
gallery: navigateRight
});
}
function addGalleryEventListeners() {
Events.favorites.newFavoritesFoundOnReload.on(handleNewFavoritesFoundOnReload, { once: true });
Events.favorites.pageChanged.on(handlePageChange3);
Events.favorites.resultsAddedToCurrentPage.on(handleResultsAddedToCurrentPage3);
Events.favorites.searchResultsUpdated.on(setSearchResults);
Events.favorites.showOnHoverToggled.on(toggleShowContentOnHover);
Events.favorites.inGalleryRequest.on(inGalleryResponse);
Events.gallery.visibleThumbsChanged.on(preloadVisibleContent);
Events.gallery.videoEnded.on(onVideoEnded);
Events.gallery.videoDoubleClicked.on(exitGallery4);
addPlatformDependentEventListeners();
Events.gallery.galleryMenuButtonClicked.on(onGalleryMenuAction);
}
function addPlatformDependentEventListeners() {
if (ON_DESKTOP_DEVICE) {
addDesktopEventListeners();
} else {
addMobileEventListeners();
}
}
function addDesktopEventListeners() {
Events.document.mouseover.on(onMouseOver);
Events.document.click.on(onClick);
Events.document.mousedown.on(onMouseDown);
Events.document.contextmenu.on(onContextMenu);
Events.document.mousemove.on(onMouseMove);
Events.document.wheel.on(onWheel);
Events.document.keydown.on(onKeyDown);
Events.document.keyup.on(onKeyUp);
}
function addMobileEventListeners() {
Events.gallery.leftTap.on(onLeftTap);
Events.gallery.rightTap.on(onRightTap);
Events.document.mousedown.on(onMouseDown2);
Events.document.touchStart.on(onTouchStart);
Events.mobile.swipedDown.on(onSwipeDown);
Events.mobile.swipedUp.on(showMenu);
Events.window.orientationChange.on(correctOrientation3);
}
function setupAutoplay2() {
const events2 = {
onEnable: () => {
toggleVideoLooping3(false);
},
onDisable: () => {
toggleVideoLooping3(true);
},
onPause: () => {
toggleVideoLooping3(true);
},
onResume: () => {
toggleVideoLooping3(false);
},
onComplete: (direction) => {
executeFunctionBasedOnGalleryState({
gallery: navigate2
}, direction);
},
onVideoEndedBeforeMinimumViewTime: () => {
restartVideo2();
}
};
setupAutoplay(events2);
toggleVideoLooping3(isPaused() || !isActive());
}
var BUTTONS3 = [
{ id: "exit-gallery", icon: EXIT, action: "exit", enabled: true, hint: "Exit (Escape, Right-Click)", color: "red" },
{ id: "fullscreen-gallery", icon: FULLSCREEN_ENTER, action: "fullscreen", enabled: true, hint: "Toggle Fullscreen (F)", color: "#0075FF" },
{ id: "open-in-new-gallery", icon: OPEN_IN_NEW, action: "openPost", enabled: true, hint: "Open Post (Middle-Click, G)", color: "lightgreen" },
{ id: "open-image-gallery", icon: IMAGE, action: "openOriginal", enabled: true, hint: "Open Original (Ctrl + Left-Click, Q)", color: "magenta" },
{ id: "download-gallery", icon: DOWNLOAD, action: "download", enabled: true, hint: "Download (S)", color: "lightskyblue" },
{ id: "add-favorite-gallery", icon: HEART_PLUS, action: "addFavorite", enabled: true, hint: "Add Favorite (E)", color: "hotpink" },
{ id: "remove-favorite-gallery", icon: HEART_MINUS, action: "removeFavorite", enabled: false, hint: "Remove Favorite (X)", color: "red" },
{ id: "dock-gallery", icon: DOCK, action: "toggleDockPosition", enabled: false, hint: "Change Position", color: "" },
{ id: "toggle-background-gallery", icon: BULB, action: "toggleBackground", enabled: true, hint: "Toggle Background (B)", color: "gold" },
{ id: "search-gallery", icon: SEARCH, action: "search", enabled: false, hint: "Search", color: "cyan" },
{ id: "background-color-gallery", icon: PALETTE, action: "changeBackgroundColor", enabled: true, hint: "Background Color", color: "orange" },
{ id: "pin-gallery", icon: PIN, action: "pin", enabled: true, hint: "Pin Menu", color: "#0075FF" }
];
var MENU = document.createElement("div");
var throttledReveal = throttle(() => {
reveal();
}, 250);
var menuVisibilityTimeout;
MENU.id = "gallery-menu";
MENU.className = "gallery-sub-menu";
function loadPreferences() {
if (Preferences.dockGalleryMenuLeft.value) {
toggleDockPosition();
}
if (Preferences.galleryMenuPinned.value) {
togglePin();
}
toggleGalleryMenuEnabled(Preferences.galleryMenuEnabled.value);
}
function addEventListeners6() {
Events.document.mousemove.on(throttledReveal);
Events.document.mouseover.on((mouseOverEvent) => {
togglePersistence(mouseOverEvent.originalEvent);
});
}
function handleGalleryMenuAction(action) {
switch (action) {
case "fullscreen":
toggleFullscreen();
break;
case "pin":
togglePin();
break;
case "toggleDockPosition":
toggleDockPosition();
break;
default:
break;
}
}
function createButtons3() {
const buttonContainer = document.createElement("div");
buttonContainer.id = "gallery-menu-button-container";
for (const template2 of BUTTONS3) {
if (template2.enabled) {
buttonContainer.appendChild(createButton(template2));
}
}
MENU.appendChild(buttonContainer);
}
function createButton(template2) {
const button = document.createElement("span");
button.innerHTML = template2.icon;
button.id = template2.id;
button.className = "gallery-menu-button";
button.dataset.hint = template2.hint;
button.onclick = () => {
handleGalleryMenuAction(template2.action);
Events.gallery.galleryMenuButtonClicked.emit(template2.action);
};
if (GallerySettings.galleryMenuMonoColor) {
template2.color = "#0075FF";
}
if (template2.color !== "") {
insertStyleHTML(`
#${template2.id}:hover {
&::after {
outline: 2px solid ${template2.color};
}
color: ${template2.color};
>svg {
fill: ${template2.color};
}
}
`, template2.id);
}
return button;
}
function createColorPicker() {
const button = document.getElementById("background-color-gallery");
if (!(button instanceof HTMLElement)) {
return;
}
const colorPicker = document.createElement("input");
colorPicker.type = "color";
colorPicker.id = "gallery-menu-background-color-picker";
button.onclick = () => {
colorPicker.click();
};
colorPicker.oninput = () => {
setColorScheme(colorPicker.value);
};
if (Preferences.colorScheme.defaultValue !== Preferences.colorScheme.value) {
setColorScheme(Preferences.colorScheme.value);
}
button.insertAdjacentElement("afterbegin", colorPicker);
}
function reveal() {
MENU.classList.add("active");
clearTimeout(menuVisibilityTimeout);
menuVisibilityTimeout = setTimeout(() => {
hide3();
}, GallerySettings.menuVisibilityTime);
}
function hide3() {
MENU.classList.remove("active");
}
function togglePersistence(event) {
MENU.classList.toggle("persistent", event.target instanceof HTMLElement && MENU.contains(event.target));
}
function togglePin() {
if (ON_MOBILE_DEVICE) {
MENU.classList.add("pinned");
Preferences.galleryMenuPinned.set(true);
return;
}
Preferences.galleryMenuPinned.set(MENU.classList.toggle("pinned"));
}
function toggleDockPosition() {
if (ON_MOBILE_DEVICE) {
MENU.classList.remove("dock-left");
Preferences.dockGalleryMenuLeft.set(false);
return;
}
Preferences.dockGalleryMenuLeft.set(MENU.classList.toggle("dock-left"));
}
function setupDesktopGalleryMenu() {
if (!GeneralSettings.galleryMenuOptionEnabled) {
return;
}
GALLERY_CONTAINER.appendChild(MENU);
loadPreferences();
createButtons3();
createColorPicker();
addEventListeners6();
}
function setupGalleryMobileTapControls() {
if (ON_DESKTOP_DEVICE) {
return;
}
const tapControlContainer = document.createElement("div");
const leftTap = document.createElement("div");
const rightTap = document.createElement("div");
tapControlContainer.id = "tap-control-container";
leftTap.className = "mobile-tap-control";
rightTap.className = "mobile-tap-control";
leftTap.id = "left-mobile-tap-control";
rightTap.id = "right-mobile-tap-control";
tapControlContainer.appendChild(leftTap);
tapControlContainer.appendChild(rightTap);
GALLERY_CONTAINER.appendChild(tapControlContainer);
leftTap.ontouchend = async () => {
await yield1();
Events.gallery.leftTap.emit();
};
rightTap.ontouchend = async () => {
await yield1();
Events.gallery.rightTap.emit();
};
}
async function setupGallery() {
if (GALLERY_DISABLED) {
return;
}
await setupSearchPageGallery();
insertGalleryContainer();
setupVisibleThumbObserver();
setupGalleryMobileTapControls();
setupGalleryInteractionTracker();
setupGalleryView();
setupGalleryMenu();
addGalleryEventListeners();
setupAutoplay2();
}
async function setupSearchPageGallery() {
if (ON_SEARCH_PAGE) {
await waitForDOMToLoad();
prepareAllThumbsOnSearchPage();
indexCurrentPageThumbs2();
setupSearchPageLoader();
}
}
function setupGalleryMenu() {
if (ON_MOBILE_DEVICE) {
} else {
setupDesktopGalleryMenu();
}
}
function setupGlobals() {
setupEvents();
setupExtensions();
setupCommonStyles();
loadTagModifications();
insertFavoritesSearchGalleryContainer();
}
var textarea;
var savedSearchesList;
var stopEditingButton;
var saveButton;
var importButton;
var exportButton;
var saveSearchResultsButton;
function setupSavedSearches() {
if (SAVED_SEARCHES_DISABLED) {
return;
}
insertHTML3();
extractHTMLElements();
addEventListeners7();
loadSavedSearches();
toggleSavedSearchesVisibility(Preferences.savedSearchesVisible.value);
}
function insertHTML3() {
insertHTMLAndExtractStyle(document.getElementById("right-favorites-panel") || document.createElement("div"), "beforeend", SAVED_SEARCHES_HTML);
}
function extractHTMLElements() {
saveButton = document.getElementById("save-custom-search-button");
textarea = document.getElementById("saved-searches-input");
savedSearchesList = document.getElementById("saved-search-list");
stopEditingButton = document.getElementById("stop-editing-saved-search-button");
importButton = document.getElementById("import-saved-search-button");
exportButton = document.getElementById("export-saved-search-button");
saveSearchResultsButton = document.getElementById("save-results-button");
}
function addEventListeners7() {
saveButton.onclick = () => {
saveSearch(textarea.value.trim());
storeSavedSearches();
};
textarea.addEventListener("keydown", (event) => {
switch (event.key) {
case "Enter":
if (awesompleteIsUnselected(textarea)) {
event.preventDefault();
saveButton.click();
textarea.blur();
setTimeout(() => {
textarea.focus();
}, 100);
}
break;
case "Escape":
if (awesompleteIsUnselected(textarea) && stopEditingButton.style.display === "block") {
stopEditingButton.click();
}
break;
default:
break;
}
}, {
passive: true
});
exportButton.onclick = () => {
exportSavedSearches();
};
importButton.onclick = () => {
importSavedSearches();
};
saveSearchResultsButton.onclick = () => {
saveSearchResultsAsCustomSearch();
};
Events.favorites.savedSearchesToggled.on(toggleSavedSearchesVisibility);
}
function toggleSavedSearchesVisibility(value) {
insertStyleHTML(`
#right-favorites-panel {
display: ${value ? "block" : "none"};
}
`, "saved-searches-visibility");
Preferences.savedSearchesVisible.set(value);
}
function saveSearch(newSavedSearch) {
if (newSavedSearch === "" || newSavedSearch === void 0) {
return;
}
const newListItem = document.createElement("li");
const savedSearchLabel = document.createElement("div");
const editButton = document.createElement("div");
const removeButton = document.createElement("div");
const moveToTopButton = document.createElement("div");
savedSearchLabel.innerText = newSavedSearch;
editButton.innerHTML = EDIT;
removeButton.innerHTML = DELETE;
moveToTopButton.innerHTML = UP_ARROW;
editButton.title = "Edit";
removeButton.title = "Delete";
moveToTopButton.title = "Move to top";
savedSearchLabel.className = "save-search-label";
editButton.className = "edit-saved-search-button";
removeButton.className = "remove-saved-search-button";
moveToTopButton.className = "move-saved-search-to-top-button";
newListItem.appendChild(removeButton);
newListItem.appendChild(editButton);
newListItem.appendChild(moveToTopButton);
newListItem.appendChild(savedSearchLabel);
savedSearchesList.insertBefore(newListItem, savedSearchesList.firstChild);
savedSearchLabel.onclick = () => {
navigator.clipboard.writeText(savedSearchLabel.innerText);
Events.searchBox.appendSearchBox.emit(savedSearchLabel.innerText);
};
removeButton.onclick = () => {
if (inEditMode()) {
alert("Cancel current edit before removing another search");
return;
}
if (confirm(`Remove saved search: ${savedSearchLabel.innerText} ?`)) {
savedSearchesList.removeChild(newListItem);
storeSavedSearches();
}
};
editButton.onclick = () => {
if (inEditMode()) {
alert("Cancel current edit before editing another search");
} else {
editSavedSearches(savedSearchLabel);
}
};
moveToTopButton.onclick = () => {
if (inEditMode() || newListItem.parentElement === null) {
alert("Cancel current edit before moving this search to the top");
return;
}
newListItem.parentElement.insertAdjacentElement("afterbegin", newListItem);
storeSavedSearches();
};
stopEditingButton.onclick = () => {
stopEditingSavedSearches(newListItem);
};
textarea.value = "";
}
function editSavedSearches(savedSearchLabel) {
textarea.value = savedSearchLabel.innerText;
saveButton.textContent = "Save Changes";
textarea.focus();
exportButton.style.display = "none";
importButton.style.display = "none";
stopEditingButton.style.display = "";
saveButton.onclick = () => {
savedSearchLabel.innerText = textarea.value.trim();
storeSavedSearches();
stopEditingButton.click();
};
}
function stopEditingSavedSearches(newListItem) {
saveButton.textContent = "Save";
saveButton.onclick = () => {
saveSearch(textarea.value.trim());
storeSavedSearches();
};
textarea.value = "";
exportButton.style.display = "";
importButton.style.display = "";
stopEditingButton.style.display = "none";
newListItem.style.border = "";
}
function storeSavedSearches() {
localStorage.setItem("savedSearches", JSON.stringify(getSavedSearches()));
}
function loadSavedSearches() {
const savedSearches = JSON.parse(localStorage.getItem("savedSearches") || "[]");
const firstUse = Boolean(Preferences.savedSearchTutorialEnabled.value);
Preferences.savedSearchTutorialEnabled.set(false);
if (firstUse && savedSearches.length === 0) {
createTutorialSearches();
return;
}
for (let i = savedSearches.length - 1; i >= 0; i -= 1) {
saveSearch(savedSearches[i]);
}
}
function createTutorialSearches() {
const searches = [];
Events.favorites.startedFetchingFavorites.on(async () => {
await sleep(1e3);
const postIds = getAllThumbs().map((thumb) => thumb.id);
shuffleArray(postIds);
const exampleSearch = `( EXAMPLE: ~ ${postIds.slice(0, 9).join(" ~ ")} ) ( male* ~ female* ~ 1boy ~ 1girls )`;
searches.push(exampleSearch);
for (let i = searches.length - 1; i >= 0; i -= 1) {
saveSearch(searches[i]);
}
storeSavedSearches();
}, {
once: true
});
}
function inEditMode() {
return stopEditingButton.style.display !== "none";
}
function exportSavedSearches() {
const savedSearchString = Array.from(document.getElementsByClassName("save-search-label")).map((search) => search.innerText).join("\n");
navigator.clipboard.writeText(savedSearchString);
alert("Copied saved searches to clipboard");
}
function importSavedSearches() {
const doesNotHaveSavedSearches = savedSearchesList.querySelectorAll("li").length === 0;
if (doesNotHaveSavedSearches || confirm("Are you sure you want to import saved searches? This will overwrite current saved searches.")) {
const savedSearches = textarea.value.split("\n");
savedSearchesList.innerHTML = "";
for (let i = savedSearches.length - 1; i >= 0; i -= 1) {
saveSearch(savedSearches[i]);
}
storeSavedSearches();
}
}
function saveSearchResultsAsCustomSearch() {
}
var SELECTED = /* @__PURE__ */ new Set();
var UI = {};
var FAVORITES_OPTION = {};
var tagEditModeAbortController = new AbortController();
var latestSearchResults4 = [];
var atLeastOneFavoriteIsSelected = false;
function setupTagModifier() {
if (TAG_MODIFIER_ENABLED) {
insertHTML4();
addEventListeners8();
}
}
function insertHTML4() {
if (!ON_FAVORITES_PAGE) {
return;
}
insertHTMLAndExtractStyle(document.getElementById("bottom-panel-4"), "beforeend", TAG_MODIFIER_HTML);
FAVORITES_OPTION.container = document.getElementById("tag-modifier-container");
FAVORITES_OPTION.checkbox = document.getElementById("tag-modifier-option-checkbox");
UI.container = document.getElementById("tag-modifier-ui-container");
UI.statusLabel = document.getElementById("tag-modifier-ui-status-label");
UI.textarea = document.getElementById("tag-modifier-ui-textarea");
UI.add = document.getElementById("tag-modifier-ui-add");
UI.remove = document.getElementById("tag-modifier-remove");
UI.reset = document.getElementById("tag-modifier-reset");
UI.selectAll = document.getElementById("tag-modifier-ui-select-all");
UI.unSelectAll = document.getElementById("tag-modifier-ui-un-select-all");
UI.import = document.getElementById("tag-modifier-import");
UI.export = document.getElementById("tag-modifier-export");
}
function addEventListeners8() {
if (!ON_FAVORITES_PAGE) {
return;
}
FAVORITES_OPTION.checkbox.onchange = (event) => {
if (event.target instanceof HTMLInputElement) {
toggleTagEditMode(event.target.checked);
}
};
UI.selectAll.onclick = selectAll;
UI.unSelectAll.onclick = unSelectAll;
UI.add.onclick = addTagsToSelected;
UI.remove.onclick = removeTagsFromSelected;
UI.reset.onclick = resetTagModifications;
UI.import.onclick = DO_NOTHING;
UI.export.onclick = DO_NOTHING;
Events.favorites.searchResultsUpdated.on(() => {
unSelectAll();
});
Events.favorites.pageChanged.on(() => {
highlightSelectedThumbsOnPageChange();
});
Events.favorites.searchResultsUpdated.on((searchResults) => {
latestSearchResults4 = searchResults;
});
}
function getSelectedFavoritesOnPage() {
return latestSearchResults4.filter((favorite) => document.getElementById(favorite.id) !== null && isSelected(favorite));
}
function highlightSelectedThumbsOnPageChange() {
if (atLeastOneFavoriteIsSelected) {
for (const favorite of getSelectedFavoritesOnPage()) {
toggleOutline(favorite, true);
}
}
}
function toggleTagEditMode(value) {
toggleThumbInteraction(value);
toggleUI2(value);
toggleTagEditModeEventListeners(value);
UI.unSelectAll.click();
}
function toggleThumbInteraction(value) {
let html = "";
if (value) {
html = `
.favorite {
cursor: pointer;
outline: 1px solid black;
> div,
>a
{
outline: none !important;
> img {
outline: none !important;
}
pointer-events:none;
opacity: 0.6;
filter: grayscale(40%);
transition: none !important;
}
}
`;
}
insertStyleHTML(html, "tag-edit-mode");
}
function toggleUI2(value) {
UI.container.style.display = value ? "block" : "none";
}
function getFavorite2(id) {
return latestSearchResults4.find((favorite) => favorite.id === id);
}
function toggleTagEditModeEventListeners(value) {
if (!value) {
tagEditModeAbortController.abort();
tagEditModeAbortController = new AbortController();
return;
}
Events.document.click.on((event) => {
if (!(event.target instanceof HTMLElement) || !event.target.classList.contains(ITEM_CLASS_NAME)) {
return;
}
const favorite = getFavorite2(event.target.id);
if (favorite !== void 0) {
select(favorite);
}
}, {
signal: tagEditModeAbortController.signal
});
}
function showStatus(text) {
UI.statusLabel.style.visibility = "visible";
UI.statusLabel.textContent = text;
setTimeout(() => {
const statusHasNotChanged = UI.statusLabel.textContent === text;
if (statusHasNotChanged) {
UI.statusLabel.style.visibility = "hidden";
}
}, 1e3);
}
function unSelectAll() {
if (!atLeastOneFavoriteIsSelected) {
return;
}
for (const favorite of SELECTED) {
select(favorite, false);
}
atLeastOneFavoriteIsSelected = false;
}
function selectAll() {
for (const favorite of latestSearchResults4) {
select(favorite, true);
}
}
function select(favorite, value) {
atLeastOneFavoriteIsSelected = true;
if (value === void 0) {
value = !SELECTED.has(favorite);
}
if (value) {
SELECTED.add(favorite);
} else {
SELECTED.delete(favorite);
}
toggleOutline(favorite, value);
}
function toggleOutline(favorite, value) {
if (document.getElementById(favorite.id) !== null || !value) {
favorite.root.classList.toggle("tag-modifier-selected", value);
}
}
function isSelected(favorite) {
return SELECTED.has(favorite);
}
function removeContentTypeTags(tags) {
return tags.replace(/(?:^|\s*)(?:video|animated|mp4)(?:$|\s*)/g, "");
}
function addTagsToSelected() {
modifyTagsOfSelected(false);
}
function removeTagsFromSelected() {
modifyTagsOfSelected(true);
}
function modifyTagsOfSelected(remove) {
const tags = UI.textarea.value.toLowerCase();
const tagsWithoutContentTypes = removeContentTypeTags(tags);
const tagsToModify = removeExtraWhiteSpace(tagsWithoutContentTypes);
const statusPrefix = remove ? "Removed tag(s) from" : "Added tag(s) to";
let modifiedTagsCount = 0;
if (tagsToModify === "") {
return;
}
for (const favorite of SELECTED) {
const additionalTags = remove ? favorite.removeAdditionalTags(tagsToModify) : favorite.addAdditionalTags(tagsToModify);
TAG_MODIFICATIONS.set(favorite.id, additionalTags);
modifiedTagsCount += 1;
}
if (modifiedTagsCount === 0) {
return;
}
if (tags !== tagsWithoutContentTypes) {
alert("Warning: video, animated, and mp4 tags are unchanged.\nThey cannot be modified.");
}
showStatus(`${statusPrefix} ${modifiedTagsCount} favorite(s)`);
dispatchEvent(new Event("modifiedTags"));
setCustomTags(tagsToModify);
storeTagModifications();
}
var tooltip;
var defaultTransition;
var visible;
var searchTagColorCodes;
var searchBox2;
var previousSearch;
var currentImage;
function setupTooltip() {
if (TOOLTIP_DISABLED) {
return;
}
visible = Preferences.tooltipsVisible.value;
FAVORITES_SEARCH_GALLERY_CONTAINER.insertAdjacentHTML("afterbegin", TOOLTIP_HTML);
tooltip = createTooltip();
defaultTransition = tooltip.style.transition;
searchTagColorCodes = {};
currentImage = null;
addEventListeners9();
assignColorsToMatchedTags();
}
function createTooltip() {
const t = document.createElement("span");
const container4 = document.getElementById("tooltip-container");
t.className = "light-green-gradient";
t.id = "tooltip";
if (container4 !== null) {
container4.appendChild(t);
}
return t;
}
function addEventListeners9() {
addCommonEventListeners2();
addFavoritesPageEventListeners3();
}
function addCommonEventListeners2() {
addMouseOverEventListener();
}
function addFavoritesPageEventListeners3() {
if (!ON_FAVORITES_PAGE) {
return;
}
Events.favorites.tooltipsToggled.on((value) => {
toggleVisibility3(value);
if (ON_FAVORITES_PAGE) {
if (currentImage === null) {
return;
}
if (visible) {
show2(currentImage);
} else {
hide4();
}
return;
}
if (ON_SEARCH_PAGE) {
toggleVisibility3();
if (currentImage !== null) {
hide4();
}
}
});
}
function addMouseOverEventListener() {
Events.document.mouseover.on((mouseOverEvent) => {
if (mouseOverEvent.thumb === null) {
hide4();
currentImage = null;
return;
}
const image = getImageFromThumb(mouseOverEvent.thumb);
if (image === null) {
return;
}
currentImage = image;
if (visible) {
show2(image);
}
});
}
function assignColorsToMatchedTags() {
if (ON_SEARCH_PAGE) {
assignColorsToMatchedTagsOnSearchPage();
return;
}
const newSearchBox = document.getElementById("favorites-search-box");
if (!(newSearchBox instanceof HTMLTextAreaElement)) {
return;
}
searchBox2 = newSearchBox;
assignColorsToMatchedTagsOnFavoritesPage();
searchBox2.addEventListener("input", () => {
assignColorsToMatchedTagsOnFavoritesPage();
});
Events.favorites.searchResultsUpdated.on(() => {
assignColorsToMatchedTagsOnFavoritesPage();
});
}
function setPosition(image) {
const fancyHoveringStyle = document.getElementById("fancy-image-hovering-fsg-style");
const imageChangesSizeOnHover = fancyHoveringStyle !== null && fancyHoveringStyle.textContent !== "";
let rect;
if (imageChangesSizeOnHover) {
const imageContainer = image.parentElement;
const sizeCalculationDiv = document.createElement("div");
sizeCalculationDiv.className = "size-calculation-div";
imageContainer.appendChild(sizeCalculationDiv);
rect = sizeCalculationDiv.getBoundingClientRect();
sizeCalculationDiv.remove();
} else {
rect = image.getBoundingClientRect();
}
const offset = 7;
let tooltipRect;
tooltip.style.top = `${rect.bottom + offset + window.scrollY}px`;
tooltip.style.left = `${rect.x - 3}px`;
tooltip.classList.toggle("visible", true);
tooltipRect = tooltip.getBoundingClientRect();
const toolTipIsClippedAtBottom = tooltipRect.bottom > window.innerHeight;
if (!toolTipIsClippedAtBottom) {
return;
}
tooltip.style.top = `${rect.top - tooltipRect.height + window.scrollY - offset}px`;
tooltipRect = tooltip.getBoundingClientRect();
const menu = document.getElementById("favorites-search-gallery-menu");
const elementAboveTooltip = menu === null ? document.getElementById("header") : menu;
if (elementAboveTooltip === null) {
return;
}
const elementAboveTooltipRect = elementAboveTooltip.getBoundingClientRect();
const toolTipIsClippedAtTop = tooltipRect.top < elementAboveTooltipRect.bottom;
if (!toolTipIsClippedAtTop) {
return;
}
const tooltipIsLeftOfCenter = tooltipRect.left < window.innerWidth / 2;
tooltip.style.top = `${rect.top + window.scrollY + rect.height / 2 - offset}px`;
if (tooltipIsLeftOfCenter) {
tooltip.style.left = `${rect.right + offset}px`;
} else {
tooltip.style.left = `${rect.left - 750 - offset}px`;
}
}
function show2(image) {
tooltip.innerHTML = formatHTML(getTags2(image));
setPosition(image);
}
function hide4() {
tooltip.style.transition = "none";
tooltip.classList.toggle("visible", false);
setTimeout(() => {
tooltip.style.transition = defaultTransition;
}, 5);
}
function getTags2(image) {
const thumb = getThumbFromImage(image);
if (thumb === null) {
return "";
}
const tags = getTagSetFromThumb(thumb);
if (searchTagColorCodes[thumb.id] === void 0) {
tags.delete(thumb.id);
}
return convertToTagString(tags);
}
function getRandomColor() {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i += 1) {
if (i === 2 || i === 3) {
color += "0";
} else {
color += letters[Math.floor(Math.random() * letters.length)];
}
}
return color;
}
function formatHTML(tags) {
let unmatchedTagsHTML = "";
let matchedTagsHTML = "";
const tagList = removeExtraWhiteSpace(tags).split(" ");
for (let i = 0; i < tagList.length; i += 1) {
const tag = tagList[i];
const tagColor = getColorCode(tag);
const tagWithSpace = `${tag} `;
if (tagColor === void 0) {
unmatchedTagsHTML += tagWithSpace;
} else {
matchedTagsHTML += `<span style="color:${tagColor}"><b>${tagWithSpace}</b></span>`;
}
}
const html = matchedTagsHTML + unmatchedTagsHTML;
if (html === "") {
return tags;
}
return html;
}
function assignTagColors(searchQuery2) {
searchQuery2 = removeNotTags(searchQuery2);
const { orGroups, remainingTags } = extractTagGroups(searchQuery2);
searchTagColorCodes = {};
assignColorsToOrGroupTags(orGroups);
assignColorsToRemainingTags(remainingTags);
}
function assignColorsToOrGroupTags(orGroups) {
for (const orGroup of orGroups) {
const color = getRandomColor();
for (const tag of orGroup) {
addColorCodedTag(tag, color);
}
}
}
function assignColorsToRemainingTags(remainingTags) {
for (const tag of remainingTags) {
addColorCodedTag(tag, getRandomColor());
}
}
function removeNotTags(tags) {
return tags.replace(/(?:^| )-\S+/gm, "");
}
function sanitizeTags(tags) {
return tags.toLowerCase().trim();
}
function addColorCodedTag(tag, color) {
tag = sanitizeTags(tag);
if (searchTagColorCodes[tag] === void 0) {
searchTagColorCodes[tag] = color;
}
}
function getColorCode(tag) {
if (searchTagColorCodes[tag] !== void 0) {
return searchTagColorCodes[tag];
}
for (const searchTag of Object.keys(searchTagColorCodes)) {
if (tagsMatchWildcardSearchTag(searchTag, [tag])) {
return searchTagColorCodes[searchTag];
}
}
return void 0;
}
function tagsMatchWildcardSearchTag(searchTag, tags) {
try {
const wildcardRegex = new RegExp(`^${searchTag.replace(/\*/g, ".*")}$`);
return tags.some((tag) => wildcardRegex.test(tag));
} catch {
return false;
}
}
function toggleVisibility3(value) {
if (value === void 0) {
value = !visible;
}
Preferences.tooltipsVisible.set(value);
visible = value;
}
function assignColorsToMatchedTagsOnSearchPage() {
const searchQuery2 = document.getElementsByName("tags")[0].getAttribute("value");
assignTagColors(searchQuery2 ?? "");
}
function assignColorsToMatchedTagsOnFavoritesPage() {
if (searchBox2.value === previousSearch) {
return;
}
previousSearch = searchBox2.value;
assignTagColors(searchBox2.value);
}
function runFavoritesSearchGallery() {
setupGlobals();
setupFavorites();
setupGallery();
setupSavedSearches();
setupTagModifier();
setupAutocomplete();
setupTooltip();
setupCaptions();
setupDownloadMenu();
}
if (FAVORITES_SEARCH_GALLERY_ENABLED) {
runFavoritesSearchGallery();
}
})();