mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-24 04:27:59 +01:00
port a bunch of stuff to typescript (untested)
This commit is contained in:
parent
b6973eb437
commit
8ca3902005
27 changed files with 431 additions and 370 deletions
|
@ -14,6 +14,7 @@ import './when-ready';
|
||||||
// Would typically be either the theme file, or any additional file
|
// Would typically be either the theme file, or any additional file
|
||||||
// you later intend to put in the <link> tag.
|
// you later intend to put in the <link> tag.
|
||||||
|
|
||||||
// import '../css/themes/default.scss';
|
import '../css/application.css';
|
||||||
|
import '../css/themes/dark-blue.css';
|
||||||
// import '../css/themes/dark.scss';
|
// import '../css/themes/dark.scss';
|
||||||
// import '../css/themes/red.scss';
|
// import '../css/themes/red.scss';
|
|
@ -1,18 +1,20 @@
|
||||||
import { delegate, leftClick } from './utils/events';
|
import { delegate, leftClick } from './utils/events';
|
||||||
import { clearEl, makeEl } from './utils/dom';
|
import { clearEl, makeEl } from './utils/dom';
|
||||||
|
|
||||||
function insertCaptcha(_event, target) {
|
function insertCaptcha(_event: Event, target: HTMLInputElement) {
|
||||||
const { parentNode, dataset: { sitekey } } = target;
|
const { parentElement, dataset: { sitekey } } = target;
|
||||||
|
|
||||||
|
if (!parentElement) { return; }
|
||||||
|
|
||||||
const script = makeEl('script', {src: 'https://hcaptcha.com/1/api.js', async: true, defer: true});
|
const script = makeEl('script', {src: 'https://hcaptcha.com/1/api.js', async: true, defer: true});
|
||||||
const frame = makeEl('div', {className: 'h-captcha'});
|
const frame = makeEl('div', {className: 'h-captcha'});
|
||||||
|
|
||||||
frame.dataset.sitekey = sitekey;
|
frame.dataset.sitekey = sitekey;
|
||||||
|
|
||||||
clearEl(parentNode);
|
clearEl(parentElement);
|
||||||
|
|
||||||
parentNode.insertAdjacentElement('beforeend', frame);
|
parentElement.insertAdjacentElement('beforeend', frame);
|
||||||
parentNode.insertAdjacentElement('beforeend', script);
|
parentElement.insertAdjacentElement('beforeend', script);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindCaptchaLinks() {
|
export function bindCaptchaLinks() {
|
|
@ -3,24 +3,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $ } from './utils/dom';
|
import { $ } from './utils/dom';
|
||||||
import { showOwnedComments } from './communications/comment';
|
|
||||||
import { filterNode } from './imagesclientside';
|
import { filterNode } from './imagesclientside';
|
||||||
import { fetchHtml } from './utils/requests';
|
import { fetchHtml } from './utils/requests';
|
||||||
import { timeAgo } from './timeago';
|
import { timeAgo } from './timeago';
|
||||||
|
|
||||||
function handleError(response) {
|
function handleError(response) {
|
||||||
|
|
||||||
const errorMessage = '<div>Comment failed to load!</div>';
|
const errorMessage = '<div>Comment failed to load!</div>';
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
}
|
}
|
||||||
return response.text();
|
|
||||||
|
|
||||||
|
return response.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentPosted(response) {
|
function commentPosted(response) {
|
||||||
|
|
||||||
const commentEditTab = $('#js-comment-form a[data-click-tab="write"]'),
|
const commentEditTab = $('#js-comment-form a[data-click-tab="write"]'),
|
||||||
commentEditForm = $('#js-comment-form'),
|
commentEditForm = $('#js-comment-form'),
|
||||||
container = document.getElementById('comments'),
|
container = document.getElementById('comments'),
|
||||||
|
@ -43,11 +40,9 @@ function commentPosted(response) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
window.scrollTo(0, 0); // Error message is displayed at the top of the page (flash)
|
window.scrollTo(0, 0); // Error message is displayed at the top of the page (flash)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadParentPost(event) {
|
function loadParentPost(event) {
|
||||||
|
|
||||||
const clickedLink = event.target,
|
const clickedLink = event.target,
|
||||||
// Find the comment containing the link that was clicked
|
// Find the comment containing the link that was clicked
|
||||||
fullComment = clickedLink.closest('article.block'),
|
fullComment = clickedLink.closest('article.block'),
|
||||||
|
@ -62,7 +57,6 @@ function loadParentPost(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commentMatches) {
|
if (commentMatches) {
|
||||||
|
|
||||||
// If the regex matched, get the image and comment ID
|
// If the regex matched, get the image and comment ID
|
||||||
const [ , imageId, commentId ] = commentMatches;
|
const [ , imageId, commentId ] = commentMatches;
|
||||||
|
|
||||||
|
@ -74,13 +68,10 @@ function loadParentPost(event) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertParentPost(data, clickedLink, fullComment) {
|
function insertParentPost(data, clickedLink, fullComment) {
|
||||||
|
|
||||||
// Add the 'subthread' class to the comment with the clicked link
|
// Add the 'subthread' class to the comment with the clicked link
|
||||||
fullComment.classList.add('subthread');
|
fullComment.classList.add('subthread');
|
||||||
|
|
||||||
|
@ -99,11 +90,9 @@ function insertParentPost(data, clickedLink, fullComment) {
|
||||||
|
|
||||||
// Filter images (if any) in the loaded comment
|
// Filter images (if any) in the loaded comment
|
||||||
filterNode(fullComment.previousSibling);
|
filterNode(fullComment.previousSibling);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearParentPost(clickedLink, fullComment) {
|
function clearParentPost(clickedLink, fullComment) {
|
||||||
|
|
||||||
// Remove any previous siblings with the class fetched-comment
|
// Remove any previous siblings with the class fetched-comment
|
||||||
while (fullComment.previousSibling && fullComment.previousSibling.classList.contains('fetched-comment')) {
|
while (fullComment.previousSibling && fullComment.previousSibling.classList.contains('fetched-comment')) {
|
||||||
fullComment.previousSibling.parentNode.removeChild(fullComment.previousSibling);
|
fullComment.previousSibling.parentNode.removeChild(fullComment.previousSibling);
|
||||||
|
@ -118,11 +107,9 @@ function clearParentPost(clickedLink, fullComment) {
|
||||||
if (!fullComment.classList.contains('fetched-comment')) {
|
if (!fullComment.classList.contains('fetched-comment')) {
|
||||||
fullComment.classList.remove('subthread');
|
fullComment.classList.remove('subthread');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayComments(container, commentsHtml) {
|
function displayComments(container, commentsHtml) {
|
||||||
|
|
||||||
container.innerHTML = commentsHtml;
|
container.innerHTML = commentsHtml;
|
||||||
|
|
||||||
// Execute timeago on comments
|
// Execute timeago on comments
|
||||||
|
@ -130,14 +117,9 @@ function displayComments(container, commentsHtml) {
|
||||||
|
|
||||||
// Filter images in the comments
|
// Filter images in the comments
|
||||||
filterNode(container);
|
filterNode(container);
|
||||||
|
|
||||||
// Show options on own comments
|
|
||||||
showOwnedComments();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadComments(event) {
|
function loadComments(event) {
|
||||||
|
|
||||||
const container = document.getElementById('comments'),
|
const container = document.getElementById('comments'),
|
||||||
hasHref = event.target && event.target.getAttribute('href'),
|
hasHref = event.target && event.target.getAttribute('href'),
|
||||||
hasHash = window.location.hash && window.location.hash.match(/#comment_([a-f0-9]+)/),
|
hasHash = window.location.hash && window.location.hash.match(/#comment_([a-f0-9]+)/),
|
||||||
|
@ -147,7 +129,6 @@ function loadComments(event) {
|
||||||
fetchHtml(getURL)
|
fetchHtml(getURL)
|
||||||
.then(handleError)
|
.then(handleError)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|
||||||
displayComments(container, data);
|
displayComments(container, data);
|
||||||
|
|
||||||
// Make sure the :target CSS selector applies to the inserted content
|
// Make sure the :target CSS selector applies to the inserted content
|
||||||
|
@ -159,7 +140,6 @@ function loadComments(event) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupComments() {
|
function setupComments() {
|
||||||
|
@ -175,7 +155,6 @@ function setupComments() {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
filterNode(comments);
|
filterNode(comments);
|
||||||
showOwnedComments();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { $ } from '../utils/dom';
|
|
||||||
|
|
||||||
function showOwnedComments() {
|
|
||||||
const editableComments = $('.js-editable-comments');
|
|
||||||
const editableCommentIds = editableComments && JSON.parse(editableComments.dataset.editable);
|
|
||||||
|
|
||||||
if (editableCommentIds) editableCommentIds.forEach(id => $(`#comment_${id} .owner-options`).classList.remove('hidden'));
|
|
||||||
}
|
|
||||||
|
|
||||||
export { showOwnedComments };
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { $ } from '../utils/dom';
|
|
||||||
|
|
||||||
function showOwnedPosts() {
|
|
||||||
const editablePost = $('.js-editable-posts');
|
|
||||||
const editablePostIds = editablePost && JSON.parse(editablePost.dataset.editable);
|
|
||||||
|
|
||||||
if (editablePostIds) editablePostIds.forEach(id => $(`#post_${id} .owner-options`).classList.remove('hidden'));
|
|
||||||
}
|
|
||||||
|
|
||||||
export { showOwnedPosts };
|
|
|
@ -1,42 +0,0 @@
|
||||||
/**
|
|
||||||
* Interactive behavior for duplicate reports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { $, $$ } from './utils/dom';
|
|
||||||
|
|
||||||
function setupDupeReports() {
|
|
||||||
const [ onion, slider ] = $$('.onion-skin__image, .onion-skin__slider');
|
|
||||||
const swipe = $('.swipe__image');
|
|
||||||
|
|
||||||
if (swipe) setupSwipe(swipe);
|
|
||||||
if (onion) setupOnionSkin(onion, slider);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupSwipe(swipe) {
|
|
||||||
const [ clip, divider ] = $$('#clip rect, #divider', swipe);
|
|
||||||
const { width } = swipe.viewBox.baseVal;
|
|
||||||
|
|
||||||
function moveDivider({ clientX }) {
|
|
||||||
// Move center to cursor
|
|
||||||
const rect = swipe.getBoundingClientRect();
|
|
||||||
const newX = (clientX - rect.left) * (width / rect.width);
|
|
||||||
|
|
||||||
divider.setAttribute('x', newX);
|
|
||||||
clip.setAttribute('width', newX);
|
|
||||||
}
|
|
||||||
|
|
||||||
swipe.addEventListener('mousemove', moveDivider);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupOnionSkin(onion, slider) {
|
|
||||||
const target = $('#target', onion);
|
|
||||||
|
|
||||||
function setOpacity() {
|
|
||||||
target.setAttribute('opacity', slider.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpacity();
|
|
||||||
slider.addEventListener('input', setOpacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { setupDupeReports };
|
|
45
assets/js/duplicate_reports.ts
Normal file
45
assets/js/duplicate_reports.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* Interactive behavior for duplicate reports.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $, $$ } from './utils/dom';
|
||||||
|
|
||||||
|
function setupDupeReports() {
|
||||||
|
const onion = $<SVGSVGElement>('.onion-skin__image');
|
||||||
|
const slider = $<HTMLInputElement>('.onion-skin__slider');
|
||||||
|
const swipe = $<SVGSVGElement>('.swipe__image');
|
||||||
|
|
||||||
|
if (swipe) setupSwipe(swipe);
|
||||||
|
if (onion && slider) setupOnionSkin(onion, slider);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSwipe(swipe: SVGSVGElement) {
|
||||||
|
const [ clip, divider ] = $$<SVGRectElement>('#clip rect, #divider', swipe);
|
||||||
|
const { width } = swipe.viewBox.baseVal;
|
||||||
|
|
||||||
|
function moveDivider({ clientX }: MouseEvent) {
|
||||||
|
// Move center to cursor
|
||||||
|
const rect = swipe.getBoundingClientRect();
|
||||||
|
const newX = (clientX - rect.left) * (width / rect.width);
|
||||||
|
|
||||||
|
divider.setAttribute('x', newX.toString());
|
||||||
|
clip.setAttribute('width', newX.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
swipe.addEventListener('mousemove', moveDivider);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupOnionSkin(onion: SVGSVGElement, slider: HTMLInputElement) {
|
||||||
|
const target = $<HTMLImageElement>('#target', onion);
|
||||||
|
|
||||||
|
function setOpacity() {
|
||||||
|
if (target) {
|
||||||
|
target.setAttribute('opacity', slider.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpacity();
|
||||||
|
slider.addEventListener('input', setOpacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { setupDupeReports };
|
|
@ -19,9 +19,9 @@ interface RealUserAgentData {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RealNavigator extends Navigator {
|
interface RealNavigator extends Navigator {
|
||||||
deviceMemory: number,
|
deviceMemory: number | null,
|
||||||
keyboard: RealKeyboard,
|
keyboard: RealKeyboard | null,
|
||||||
userAgentData: RealUserAgentData,
|
userAgentData: RealUserAgentData | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,7 +81,7 @@ async function createFp(): Promise<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let width: string | null = store.get('cached_rem_size');
|
let width: string | null = store.get('cached_rem_size');
|
||||||
const body = $('body');
|
const body = $<HTMLBodyElement>('body');
|
||||||
|
|
||||||
if (!width && body) {
|
if (!width && body) {
|
||||||
const testElement = document.createElement('span');
|
const testElement = document.createElement('span');
|
||||||
|
@ -127,7 +127,7 @@ async function createFp(): Promise<string> {
|
||||||
/**
|
/**
|
||||||
* Sets the `_ses` cookie.
|
* Sets the `_ses` cookie.
|
||||||
*
|
*
|
||||||
* If `cached_ses_value` is present in local storage, uses that instead.
|
* If `cached_ses_value` is present in local storage, uses it to set the `_ses` cookie.
|
||||||
* Otherwise if the `_ses` cookie already exits, uses its value instead.
|
* Otherwise if the `_ses` cookie already exits, uses its value instead.
|
||||||
* Otherwise attempts to generate a new value for the `_ses` cookie
|
* Otherwise attempts to generate a new value for the `_ses` cookie
|
||||||
* based on various browser attributes.
|
* based on various browser attributes.
|
||||||
|
|
|
@ -8,11 +8,13 @@ import { initDraggables } from './utils/draggable';
|
||||||
import { fetchJson } from './utils/requests';
|
import { fetchJson } from './utils/requests';
|
||||||
|
|
||||||
export function setupGalleryEditing() {
|
export function setupGalleryEditing() {
|
||||||
if (!$('.rearrange-button')) return;
|
if (!$<HTMLElement>('.rearrange-button')) return;
|
||||||
|
|
||||||
const [ rearrangeEl, saveEl ] = $$('.rearrange-button');
|
const [ rearrangeEl, saveEl ] = $$<HTMLElement>('.rearrange-button');
|
||||||
const sortableEl = $('#sortable');
|
const sortableEl = $<HTMLDivElement>('#sortable');
|
||||||
const containerEl = $('.media-list');
|
const containerEl = $<HTMLDivElement>('.media-list');
|
||||||
|
|
||||||
|
if (!sortableEl || !containerEl || !saveEl || !rearrangeEl) { return; }
|
||||||
|
|
||||||
// Copy array
|
// Copy array
|
||||||
let oldImages = window.booru.galleryImages.slice();
|
let oldImages = window.booru.galleryImages.slice();
|
||||||
|
@ -20,7 +22,7 @@ export function setupGalleryEditing() {
|
||||||
|
|
||||||
initDraggables();
|
initDraggables();
|
||||||
|
|
||||||
$$('.media-box', containerEl).forEach(i => i.draggable = true);
|
$$<HTMLDivElement>('.media-box', containerEl).forEach(i => i.draggable = true);
|
||||||
|
|
||||||
rearrangeEl.addEventListener('click', () => {
|
rearrangeEl.addEventListener('click', () => {
|
||||||
sortableEl.classList.add('editing');
|
sortableEl.classList.add('editing');
|
||||||
|
@ -31,15 +33,17 @@ export function setupGalleryEditing() {
|
||||||
sortableEl.classList.remove('editing');
|
sortableEl.classList.remove('editing');
|
||||||
containerEl.classList.remove('drag-container');
|
containerEl.classList.remove('drag-container');
|
||||||
|
|
||||||
newImages = $$('.image-container', containerEl).map(i => parseInt(i.dataset.imageId, 10));
|
newImages = $$<HTMLDivElement>('.image-container', containerEl).map(i => parseInt(i.dataset.imageId || '-1', 10));
|
||||||
|
|
||||||
// If nothing changed, don't bother.
|
// If nothing changed, don't bother.
|
||||||
if (arraysEqual(newImages, oldImages)) return;
|
if (arraysEqual(newImages, oldImages)) return;
|
||||||
|
|
||||||
|
if (saveEl.dataset.reorderPath) {
|
||||||
fetchJson('PATCH', saveEl.dataset.reorderPath, {
|
fetchJson('PATCH', saveEl.dataset.reorderPath, {
|
||||||
image_ids: newImages,
|
image_ids: newImages,
|
||||||
|
|
||||||
// copy the array again so that we have the newly updated set
|
// copy the array again so that we have the newly updated set
|
||||||
}).then(() => oldImages = newImages.slice());
|
}).then(() => oldImages = newImages.slice());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,76 +0,0 @@
|
||||||
/**
|
|
||||||
* Client-side image filtering/spoilering.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { $$, escapeHtml } from './utils/dom';
|
|
||||||
import { setupInteractions } from './interactions';
|
|
||||||
import { showThumb, showBlock, spoilerThumb, spoilerBlock, hideThumb } from './utils/image';
|
|
||||||
import { getHiddenTags, getSpoileredTags, imageHitsTags, imageHitsComplex, displayTags } from './utils/tag';
|
|
||||||
|
|
||||||
function runFilter(img, test, runCallback) {
|
|
||||||
if (!test || test.length === 0) return false;
|
|
||||||
|
|
||||||
runCallback(img, test);
|
|
||||||
|
|
||||||
// I don't like this.
|
|
||||||
window.booru.imagesWithDownvotingDisabled.push(img.dataset.imageId);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---
|
|
||||||
|
|
||||||
function filterThumbSimple(img, tagsHit) { hideThumb(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `[HIDDEN] ${displayTags(tagsHit)}`); }
|
|
||||||
function spoilerThumbSimple(img, tagsHit) { spoilerThumb(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, displayTags(tagsHit)); }
|
|
||||||
function filterThumbComplex(img) { hideThumb(img, window.booru.hiddenTag, '[HIDDEN] <i>(Complex Filter)</i>'); }
|
|
||||||
function spoilerThumbComplex(img) { spoilerThumb(img, window.booru.hiddenTag, '<i>(Complex Filter)</i>'); }
|
|
||||||
|
|
||||||
function filterBlockSimple(img, tagsHit) { spoilerBlock(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is hidden by `); }
|
|
||||||
function spoilerBlockSimple(img, tagsHit) { spoilerBlock(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is spoilered by `); }
|
|
||||||
function filterBlockComplex(img) { spoilerBlock(img, window.booru.hiddenTag, 'This image was hidden by a complex tag expression in '); }
|
|
||||||
function spoilerBlockComplex(img) { spoilerBlock(img, window.booru.hiddenTag, 'This image was spoilered by a complex tag expression in '); }
|
|
||||||
|
|
||||||
// ---
|
|
||||||
|
|
||||||
function thumbTagFilter(tags, img) { return runFilter(img, imageHitsTags(img, tags), filterThumbSimple); }
|
|
||||||
function thumbComplexFilter(complex, img) { return runFilter(img, imageHitsComplex(img, complex), filterThumbComplex); }
|
|
||||||
function thumbTagSpoiler(tags, img) { return runFilter(img, imageHitsTags(img, tags), spoilerThumbSimple); }
|
|
||||||
function thumbComplexSpoiler(complex, img) { return runFilter(img, imageHitsComplex(img, complex), spoilerThumbComplex); }
|
|
||||||
|
|
||||||
function blockTagFilter(tags, img) { return runFilter(img, imageHitsTags(img, tags), filterBlockSimple); }
|
|
||||||
function blockComplexFilter(complex, img) { return runFilter(img, imageHitsComplex(img, complex), filterBlockComplex); }
|
|
||||||
function blockTagSpoiler(tags, img) { return runFilter(img, imageHitsTags(img, tags), spoilerBlockSimple); }
|
|
||||||
function blockComplexSpoiler(complex, img) { return runFilter(img, imageHitsComplex(img, complex), spoilerBlockComplex); }
|
|
||||||
|
|
||||||
// ---
|
|
||||||
|
|
||||||
function filterNode(node = document) {
|
|
||||||
const hiddenTags = getHiddenTags(), spoileredTags = getSpoileredTags();
|
|
||||||
const { hiddenFilter, spoileredFilter } = window.booru;
|
|
||||||
|
|
||||||
// Image thumb boxes with vote and fave buttons on them
|
|
||||||
$$('.image-container', node)
|
|
||||||
.filter(img => !thumbTagFilter(hiddenTags, img))
|
|
||||||
.filter(img => !thumbComplexFilter(hiddenFilter, img))
|
|
||||||
.filter(img => !thumbTagSpoiler(spoileredTags, img))
|
|
||||||
.filter(img => !thumbComplexSpoiler(spoileredFilter, img))
|
|
||||||
.forEach(img => showThumb(img));
|
|
||||||
|
|
||||||
// Individual image pages and images in posts/comments
|
|
||||||
$$('.image-show-container', node)
|
|
||||||
.filter(img => !blockTagFilter(hiddenTags, img))
|
|
||||||
.filter(img => !blockComplexFilter(hiddenFilter, img))
|
|
||||||
.filter(img => !blockTagSpoiler(spoileredTags, img))
|
|
||||||
.filter(img => !blockComplexSpoiler(spoileredFilter, img))
|
|
||||||
.forEach(img => showBlock(img));
|
|
||||||
}
|
|
||||||
|
|
||||||
function initImagesClientside() {
|
|
||||||
window.booru.imagesWithDownvotingDisabled = [];
|
|
||||||
// This fills the imagesWithDownvotingDisabled array
|
|
||||||
filterNode(document);
|
|
||||||
// Once the array is populated, we can initialize interactions
|
|
||||||
setupInteractions();
|
|
||||||
}
|
|
||||||
|
|
||||||
export { initImagesClientside, filterNode };
|
|
131
assets/js/imagesclientside.ts
Normal file
131
assets/js/imagesclientside.ts
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* Client-side image filtering/spoilering.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $$, escapeHtml } from './utils/dom';
|
||||||
|
import { setupInteractions } from './interactions';
|
||||||
|
import { showThumb, showBlock, spoilerThumb, spoilerBlock, hideThumb } from './utils/image';
|
||||||
|
import { TagData, getHiddenTags, getSpoileredTags, imageHitsTags, imageHitsComplex, displayTags } from './utils/tag';
|
||||||
|
import { AstMatcher } from './query/types';
|
||||||
|
|
||||||
|
type RunFilterCallback = (img: HTMLDivElement, test: TagData[]) => void;
|
||||||
|
|
||||||
|
function runFilter(img: HTMLDivElement, test: TagData[] | boolean, runCallback: RunFilterCallback) {
|
||||||
|
if (!test || typeof test !== 'boolean' && test.length === 0) { return false; }
|
||||||
|
|
||||||
|
runCallback(img, test as TagData[]);
|
||||||
|
|
||||||
|
// I don't like this.
|
||||||
|
img.dataset.imageId && window.booru.imagesWithDownvotingDisabled.push(img.dataset.imageId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
function filterThumbSimple(img: HTMLDivElement, tagsHit: TagData[]) {
|
||||||
|
hideThumb(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `[HIDDEN] ${displayTags(tagsHit)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function spoilerThumbSimple(img: HTMLDivElement, tagsHit: TagData[]) {
|
||||||
|
spoilerThumb(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, displayTags(tagsHit));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterThumbComplex(img: HTMLDivElement) {
|
||||||
|
hideThumb(img, window.booru.hiddenTag, '[HIDDEN] <i>(Complex Filter)</i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function spoilerThumbComplex(img: HTMLDivElement) {
|
||||||
|
spoilerThumb(img, window.booru.hiddenTag, '<i>(Complex Filter)</i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterBlockSimple(img: HTMLDivElement, tagsHit: TagData[]) {
|
||||||
|
spoilerBlock(
|
||||||
|
img,
|
||||||
|
tagsHit[0].spoiler_image_uri || window.booru.hiddenTag,
|
||||||
|
`This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is hidden by `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function spoilerBlockSimple(img: HTMLDivElement, tagsHit: TagData[]) {
|
||||||
|
spoilerBlock(
|
||||||
|
img,
|
||||||
|
tagsHit[0].spoiler_image_uri || window.booru.hiddenTag,
|
||||||
|
`This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is spoilered by `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterBlockComplex(img: HTMLDivElement) {
|
||||||
|
spoilerBlock(img, window.booru.hiddenTag, 'This image was hidden by a complex tag expression in ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function spoilerBlockComplex(img: HTMLDivElement) {
|
||||||
|
spoilerBlock(img, window.booru.hiddenTag, 'This image was spoilered by a complex tag expression in ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
function thumbTagFilter(tags: TagData[], img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsTags(img, tags), filterThumbSimple);
|
||||||
|
}
|
||||||
|
|
||||||
|
function thumbComplexFilter(complex: AstMatcher, img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsComplex(img, complex), filterThumbComplex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function thumbTagSpoiler(tags: TagData[], img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsTags(img, tags), spoilerThumbSimple);
|
||||||
|
}
|
||||||
|
|
||||||
|
function thumbComplexSpoiler(complex: AstMatcher, img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsComplex(img, complex), spoilerThumbComplex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockTagFilter(tags: TagData[], img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsTags(img, tags), filterBlockSimple);
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockComplexFilter(complex: AstMatcher, img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsComplex(img, complex), filterBlockComplex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockTagSpoiler(tags: TagData[], img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsTags(img, tags), spoilerBlockSimple);
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockComplexSpoiler(complex: AstMatcher, img: HTMLDivElement) {
|
||||||
|
return runFilter(img, imageHitsComplex(img, complex), spoilerBlockComplex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
function filterNode(node: Pick<Document, 'querySelectorAll'>) {
|
||||||
|
const hiddenTags = getHiddenTags(), spoileredTags = getSpoileredTags();
|
||||||
|
const { hiddenFilter, spoileredFilter } = window.booru;
|
||||||
|
|
||||||
|
// Image thumb boxes with vote and fave buttons on them
|
||||||
|
$$<HTMLDivElement>('.image-container', node)
|
||||||
|
.filter(img => !thumbTagFilter(hiddenTags, img))
|
||||||
|
.filter(img => !thumbComplexFilter(hiddenFilter, img))
|
||||||
|
.filter(img => !thumbTagSpoiler(spoileredTags, img))
|
||||||
|
.filter(img => !thumbComplexSpoiler(spoileredFilter, img))
|
||||||
|
.forEach(img => showThumb(img));
|
||||||
|
|
||||||
|
// Individual image pages and images in posts/comments
|
||||||
|
$$<HTMLDivElement>('.image-show-container', node)
|
||||||
|
.filter(img => !blockTagFilter(hiddenTags, img))
|
||||||
|
.filter(img => !blockComplexFilter(hiddenFilter, img))
|
||||||
|
.filter(img => !blockTagSpoiler(spoileredTags, img))
|
||||||
|
.filter(img => !blockComplexSpoiler(spoileredFilter, img))
|
||||||
|
.forEach(img => showBlock(img));
|
||||||
|
}
|
||||||
|
|
||||||
|
function initImagesClientside() {
|
||||||
|
window.booru.imagesWithDownvotingDisabled = [];
|
||||||
|
// This fills the imagesWithDownvotingDisabled array
|
||||||
|
filterNode(document);
|
||||||
|
// Once the array is populated, we can initialize interactions
|
||||||
|
setupInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { initImagesClientside, filterNode };
|
|
@ -7,8 +7,8 @@
|
||||||
import { $ } from './utils/dom';
|
import { $ } from './utils/dom';
|
||||||
|
|
||||||
function warnAboutPMs() {
|
function warnAboutPMs() {
|
||||||
const textarea = $('.js-toolbar-input');
|
const textarea = $<HTMLTextAreaElement>('.js-toolbar-input');
|
||||||
const warning = $('.js-hidden-warning');
|
const warning = $<HTMLDivElement>('.js-hidden-warning');
|
||||||
const imageEmbedRegex = /!+\[/g;
|
const imageEmbedRegex = /!+\[/g;
|
||||||
|
|
||||||
if (!warning || !textarea) return;
|
if (!warning || !textarea) return;
|
|
@ -1,6 +1,6 @@
|
||||||
import { inputDuplicatorCreator } from './input-duplicator';
|
import { inputDuplicatorCreator } from './input-duplicator';
|
||||||
|
|
||||||
function pollOptionCreator() {
|
export function pollOptionCreator() {
|
||||||
inputDuplicatorCreator({
|
inputDuplicatorCreator({
|
||||||
addButtonSelector: '.js-poll-add-option',
|
addButtonSelector: '.js-poll-add-option',
|
||||||
fieldSelector: '.js-poll-option',
|
fieldSelector: '.js-poll-option',
|
||||||
|
@ -8,5 +8,3 @@ function pollOptionCreator() {
|
||||||
removeButtonSelector: '.js-option-remove',
|
removeButtonSelector: '.js-option-remove',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { pollOptionCreator };
|
|
|
@ -1,45 +0,0 @@
|
||||||
import { $, $$ } from './utils/dom';
|
|
||||||
import { addTag } from './tagsinput';
|
|
||||||
|
|
||||||
function showHelp(subject, type) {
|
|
||||||
$$('[data-search-help]').forEach(helpBox => {
|
|
||||||
if (helpBox.getAttribute('data-search-help') === type) {
|
|
||||||
$('.js-search-help-subject', helpBox).textContent = subject;
|
|
||||||
helpBox.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
helpBox.classList.add('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function prependToLast(field, value) {
|
|
||||||
const separatorIndex = field.value.lastIndexOf(',');
|
|
||||||
const advanceBy = field.value[separatorIndex + 1] === ' ' ? 2 : 1;
|
|
||||||
field.value = field.value.slice(0, separatorIndex + advanceBy) + value + field.value.slice(separatorIndex + advanceBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectLast(field, characterCount) {
|
|
||||||
field.focus();
|
|
||||||
|
|
||||||
field.selectionStart = field.value.length - characterCount;
|
|
||||||
field.selectionEnd = field.value.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function executeFormHelper(e) {
|
|
||||||
const searchField = $('.js-search-field');
|
|
||||||
const attr = name => e.target.getAttribute(name);
|
|
||||||
|
|
||||||
attr('data-search-add') && addTag(searchField, attr('data-search-add'));
|
|
||||||
attr('data-search-show-help') && showHelp(e.target.textContent, attr('data-search-show-help'));
|
|
||||||
attr('data-search-select-last') && selectLast(searchField, parseInt(attr('data-search-select-last'), 10));
|
|
||||||
attr('data-search-prepend') && prependToLast(searchField, attr('data-search-prepend'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupSearch() {
|
|
||||||
const form = $('.js-search-form');
|
|
||||||
|
|
||||||
form && form.addEventListener('click', executeFormHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { setupSearch };
|
|
50
assets/js/search.ts
Normal file
50
assets/js/search.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { $, $$ } from './utils/dom';
|
||||||
|
import { addTag } from './tagsinput';
|
||||||
|
|
||||||
|
function showHelp(subject: string, type: string | null) {
|
||||||
|
$$<HTMLElement>('[data-search-help]').forEach(helpBox => {
|
||||||
|
if (helpBox.getAttribute('data-search-help') === type) {
|
||||||
|
const searchSubject = $<HTMLElement>('.js-search-help-subject', helpBox);
|
||||||
|
|
||||||
|
if (searchSubject) {
|
||||||
|
searchSubject.textContent = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
helpBox.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
helpBox.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prependToLast(field: HTMLInputElement, value: string) {
|
||||||
|
const separatorIndex = field.value.lastIndexOf(',');
|
||||||
|
const advanceBy = field.value[separatorIndex + 1] === ' ' ? 2 : 1;
|
||||||
|
field.value = field.value.slice(0, separatorIndex + advanceBy) + value + field.value.slice(separatorIndex + advanceBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectLast(field: HTMLInputElement, characterCount: number) {
|
||||||
|
field.focus();
|
||||||
|
|
||||||
|
field.selectionStart = field.value.length - characterCount;
|
||||||
|
field.selectionEnd = field.value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeFormHelper(e: PointerEvent) {
|
||||||
|
if (!e.target) { return; }
|
||||||
|
|
||||||
|
const searchField = $<HTMLInputElement>('.js-search-field');
|
||||||
|
const attr = (name: string) => e.target && (e.target as HTMLElement).getAttribute(name);
|
||||||
|
|
||||||
|
attr('data-search-add') && addTag(searchField, attr('data-search-add'));
|
||||||
|
attr('data-search-show-help') && showHelp((e.target as Node).textContent || '', attr('data-search-show-help'));
|
||||||
|
attr('data-search-select-last') && searchField && selectLast(searchField, parseInt(attr('data-search-select-last') || '', 10));
|
||||||
|
attr('data-search-prepend') && searchField && prependToLast(searchField, attr('data-search-prepend') || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupSearch() {
|
||||||
|
const form = $<HTMLInputElement>('.js-search-form');
|
||||||
|
|
||||||
|
form && form.addEventListener('click', executeFormHelper as EventListener);
|
||||||
|
}
|
|
@ -6,12 +6,11 @@ import { $, $$ } from './utils/dom';
|
||||||
import store from './utils/store';
|
import store from './utils/store';
|
||||||
|
|
||||||
export function setupSettings() {
|
export function setupSettings() {
|
||||||
|
if (!$<HTMLElement>('#js-setting-table')) return;
|
||||||
|
|
||||||
if (!$('#js-setting-table')) return;
|
const localCheckboxes = $$<HTMLInputElement>('[data-tab="local"] input[type="checkbox"]');
|
||||||
|
const themeSelect = $<HTMLSelectElement>('#user_theme');
|
||||||
const localCheckboxes = $$('[data-tab="local"] input[type="checkbox"]');
|
const styleSheet = $<HTMLLinkElement>('head link[rel="stylesheet"]');
|
||||||
const themeSelect = $('#user_theme');
|
|
||||||
const styleSheet = $('head link[rel="stylesheet"]');
|
|
||||||
|
|
||||||
// Local settings
|
// Local settings
|
||||||
localCheckboxes.forEach(checkbox => {
|
localCheckboxes.forEach(checkbox => {
|
||||||
|
@ -22,7 +21,8 @@ export function setupSettings() {
|
||||||
|
|
||||||
// Theme preview
|
// Theme preview
|
||||||
themeSelect && themeSelect.addEventListener('change', () => {
|
themeSelect && themeSelect.addEventListener('change', () => {
|
||||||
styleSheet.href = themeSelect.options[themeSelect.selectedIndex].dataset.themePath;
|
if (styleSheet) {
|
||||||
|
styleSheet.href = themeSelect.options[themeSelect.selectedIndex].dataset.themePath || '#';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,62 +0,0 @@
|
||||||
/**
|
|
||||||
* Keyboard shortcuts
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { $ } from './utils/dom';
|
|
||||||
|
|
||||||
function getHover() {
|
|
||||||
const thumbBoxHover = $('.media-box:hover');
|
|
||||||
if (thumbBoxHover) return thumbBoxHover.dataset.imageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openFullView() {
|
|
||||||
const imageHover = $('[data-uris]:hover');
|
|
||||||
if (!imageHover) return;
|
|
||||||
|
|
||||||
window.location = JSON.parse(imageHover.dataset.uris).full;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openFullViewNewTab() {
|
|
||||||
const imageHover = $('[data-uris]:hover');
|
|
||||||
if (!imageHover) return;
|
|
||||||
|
|
||||||
window.open(JSON.parse(imageHover.dataset.uris).full);
|
|
||||||
}
|
|
||||||
|
|
||||||
function click(selector) {
|
|
||||||
const el = $(selector);
|
|
||||||
if (el) el.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOK(event) {
|
|
||||||
return !event.altKey && !event.ctrlKey && !event.metaKey &&
|
|
||||||
document.activeElement.tagName !== 'INPUT' &&
|
|
||||||
document.activeElement.tagName !== 'TEXTAREA';
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyCodes = {
|
|
||||||
74() { click('.js-prev'); }, // J - go to previous image
|
|
||||||
73() { click('.js-up'); }, // I - go to index page
|
|
||||||
75() { click('.js-next'); }, // K - go to next image
|
|
||||||
82() { click('.js-rand'); }, // R - go to random image
|
|
||||||
83() { click('.js-source-link'); }, // S - go to image source
|
|
||||||
76() { click('.js-tag-sauce-toggle'); }, // L - edit tags
|
|
||||||
79() { openFullView(); }, // O - open original
|
|
||||||
86() { openFullViewNewTab(); }, // V - open original in a new tab
|
|
||||||
70() { // F - favourite image
|
|
||||||
getHover() ? click(`a.interaction--fave[data-image-id="${getHover()}"]`)
|
|
||||||
: click('.block__header a.interaction--fave');
|
|
||||||
},
|
|
||||||
85() { // U - upvote image
|
|
||||||
getHover() ? click(`a.interaction--upvote[data-image-id="${getHover()}"]`)
|
|
||||||
: click('.block__header a.interaction--upvote');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function listenForKeys() {
|
|
||||||
document.addEventListener('keydown', event => {
|
|
||||||
if (isOK(event) && keyCodes[event.keyCode]) { keyCodes[event.keyCode](); event.preventDefault(); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { listenForKeys };
|
|
76
assets/js/shortcuts.ts
Normal file
76
assets/js/shortcuts.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* Keyboard shortcuts
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $ } from './utils/dom';
|
||||||
|
|
||||||
|
interface ShortcutKeycodes {
|
||||||
|
[key: string]: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHover(): string | null {
|
||||||
|
const thumbBoxHover = $<HTMLDivElement>('.media-box:hover');
|
||||||
|
|
||||||
|
return thumbBoxHover && (thumbBoxHover.dataset.imageId || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFullView() {
|
||||||
|
const imageHover = $<HTMLDivElement>('[data-uris]:hover');
|
||||||
|
|
||||||
|
if (!imageHover || !imageHover.dataset.uris) return;
|
||||||
|
|
||||||
|
window.location = JSON.parse(imageHover.dataset.uris).full;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFullViewNewTab() {
|
||||||
|
const imageHover = $<HTMLDivElement>('[data-uris]:hover');
|
||||||
|
|
||||||
|
if (!imageHover || !imageHover.dataset.uris) return;
|
||||||
|
|
||||||
|
window.open(JSON.parse(imageHover.dataset.uris).full);
|
||||||
|
}
|
||||||
|
|
||||||
|
function click(selector: string) {
|
||||||
|
const el = $<HTMLElement>(selector);
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOK(event: KeyboardEvent): boolean {
|
||||||
|
return !event.altKey && !event.ctrlKey && !event.metaKey &&
|
||||||
|
document.activeElement !== null &&
|
||||||
|
document.activeElement.tagName !== 'INPUT' &&
|
||||||
|
document.activeElement.tagName !== 'TEXTAREA';
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyCodes: ShortcutKeycodes = {
|
||||||
|
KeyJ() { click('.js-prev'); }, // J - go to previous image
|
||||||
|
KeyI() { click('.js-up'); }, // I - go to index page
|
||||||
|
KeyK() { click('.js-next'); }, // K - go to next image
|
||||||
|
KeyR() { click('.js-rand'); }, // R - go to random image
|
||||||
|
KeyS() { click('.js-source-link'); }, // S - go to image source
|
||||||
|
KeyL() { click('.js-tag-sauce-toggle'); }, // L - edit tags
|
||||||
|
KeyO() { openFullView(); }, // O - open original
|
||||||
|
KeyV() { openFullViewNewTab(); }, // V - open original in a new tab
|
||||||
|
KeyF() { // F - favourite image
|
||||||
|
getHover() ? click(`a.interaction--fave[data-image-id="${getHover()}"]`)
|
||||||
|
: click('.block__header a.interaction--fave');
|
||||||
|
},
|
||||||
|
KeyU() { // U - upvote image
|
||||||
|
getHover() ? click(`a.interaction--upvote[data-image-id="${getHover()}"]`)
|
||||||
|
: click('.block__header a.interaction--upvote');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function listenForKeys() {
|
||||||
|
document.addEventListener('keydown', (event: KeyboardEvent) => {
|
||||||
|
if (isOK(event) && keyCodes[event.code]) {
|
||||||
|
keyCodes[event.code]();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { listenForKeys };
|
|
@ -1,5 +1,10 @@
|
||||||
|
import { $ } from './utils/dom';
|
||||||
import { inputDuplicatorCreator } from './input-duplicator';
|
import { inputDuplicatorCreator } from './input-duplicator';
|
||||||
|
|
||||||
|
export interface SourcesEvent extends CustomEvent<Response> {
|
||||||
|
target: HTMLElement,
|
||||||
|
}
|
||||||
|
|
||||||
function setupInputs() {
|
function setupInputs() {
|
||||||
inputDuplicatorCreator({
|
inputDuplicatorCreator({
|
||||||
addButtonSelector: '.js-image-add-source',
|
addButtonSelector: '.js-image-add-source',
|
||||||
|
@ -11,16 +16,17 @@ function setupInputs() {
|
||||||
|
|
||||||
function imageSourcesCreator() {
|
function imageSourcesCreator() {
|
||||||
setupInputs();
|
setupInputs();
|
||||||
document.addEventListener('fetchcomplete', ({ target, detail }) => {
|
|
||||||
const sourceSauce = document.querySelector('.js-sourcesauce');
|
|
||||||
|
|
||||||
if (target.matches('#source-form')) {
|
document.addEventListener('fetchcomplete', (({ target, detail }: SourcesEvent) => {
|
||||||
|
const sourceSauce = $<HTMLElement>('.js-sourcesauce');
|
||||||
|
|
||||||
|
if (sourceSauce && target && target.matches('#source-form')) {
|
||||||
detail.text().then(text => {
|
detail.text().then(text => {
|
||||||
sourceSauce.outerHTML = text;
|
sourceSauce.outerHTML = text;
|
||||||
setupInputs();
|
setupInputs();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}) as EventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { imageSourcesCreator };
|
export { imageSourcesCreator };
|
|
@ -1,56 +0,0 @@
|
||||||
/**
|
|
||||||
* Tags Misc
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { $$ } from './utils/dom';
|
|
||||||
import store from './utils/store';
|
|
||||||
import { initTagDropdown } from './tags';
|
|
||||||
import { setupTagsInput, reloadTagsInput } from './tagsinput';
|
|
||||||
|
|
||||||
function tagInputButtons({target}) {
|
|
||||||
const actions = {
|
|
||||||
save(tagInput) {
|
|
||||||
store.set('tag_input', tagInput.value);
|
|
||||||
},
|
|
||||||
load(tagInput) {
|
|
||||||
// If entry 'tag_input' does not exist, try to use the current list
|
|
||||||
tagInput.value = store.get('tag_input') || tagInput.value;
|
|
||||||
reloadTagsInput(tagInput);
|
|
||||||
},
|
|
||||||
clear(tagInput) {
|
|
||||||
tagInput.value = '';
|
|
||||||
reloadTagsInput(tagInput);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const action in actions) {
|
|
||||||
if (target.matches(`#tagsinput-${action}`)) actions[action](document.getElementById('image_tag_input'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupTags() {
|
|
||||||
$$('.js-tag-block').forEach(el => {
|
|
||||||
setupTagsInput(el);
|
|
||||||
el.classList.remove('js-tag-block');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTagSauce({target, detail}) {
|
|
||||||
const tagSauce = document.querySelector('.js-tagsauce');
|
|
||||||
|
|
||||||
if (target.matches('#tags-form')) {
|
|
||||||
detail.text().then(text => {
|
|
||||||
tagSauce.outerHTML = text;
|
|
||||||
setupTags();
|
|
||||||
initTagDropdown();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupTagEvents() {
|
|
||||||
setupTags();
|
|
||||||
document.addEventListener('fetchcomplete', updateTagSauce);
|
|
||||||
document.addEventListener('click', tagInputButtons);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { setupTagEvents };
|
|
70
assets/js/tagsmisc.ts
Normal file
70
assets/js/tagsmisc.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* Tags Misc
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { $, $$ } from './utils/dom';
|
||||||
|
import store from './utils/store';
|
||||||
|
import { initTagDropdown } from './tags';
|
||||||
|
import { setupTagsInput, reloadTagsInput } from './tagsinput';
|
||||||
|
import { SourcesEvent } from './sources';
|
||||||
|
|
||||||
|
type TagInputActionFunction = (tagInput: HTMLTextAreaElement | null) => void
|
||||||
|
type TagInputActionList = {
|
||||||
|
save: TagInputActionFunction,
|
||||||
|
load: TagInputActionFunction,
|
||||||
|
clear: TagInputActionFunction,
|
||||||
|
}
|
||||||
|
|
||||||
|
function tagInputButtons({target}: PointerEvent) {
|
||||||
|
const actions: TagInputActionList = {
|
||||||
|
save(tagInput: HTMLTextAreaElement | null) {
|
||||||
|
tagInput && store.set('tag_input', tagInput.value);
|
||||||
|
},
|
||||||
|
load(tagInput: HTMLTextAreaElement | null) {
|
||||||
|
if (!tagInput) { return; }
|
||||||
|
|
||||||
|
// If entry 'tag_input' does not exist, try to use the current list
|
||||||
|
tagInput.value = store.get('tag_input') || tagInput.value;
|
||||||
|
reloadTagsInput(tagInput);
|
||||||
|
},
|
||||||
|
clear(tagInput: HTMLTextAreaElement | null) {
|
||||||
|
if (!tagInput) { return; }
|
||||||
|
|
||||||
|
tagInput.value = '';
|
||||||
|
reloadTagsInput(tagInput);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const action in actions) {
|
||||||
|
if (target && (target as HTMLElement).matches(`#tagsinput-${action}`)) {
|
||||||
|
actions[action as keyof TagInputActionList]($<HTMLTextAreaElement>('image_tag_input'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupTags() {
|
||||||
|
$$<HTMLDivElement>('.js-tag-block').forEach(el => {
|
||||||
|
setupTagsInput(el);
|
||||||
|
el.classList.remove('js-tag-block');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTagSauce({target, detail}: SourcesEvent) {
|
||||||
|
const tagSauce = $<HTMLDivElement>('.js-tagsauce');
|
||||||
|
|
||||||
|
if (tagSauce && target.matches('#tags-form')) {
|
||||||
|
detail.text().then(text => {
|
||||||
|
tagSauce.outerHTML = text;
|
||||||
|
setupTags();
|
||||||
|
initTagDropdown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupTagEvents() {
|
||||||
|
setupTags();
|
||||||
|
document.addEventListener('fetchcomplete', updateTagSauce as EventListener);
|
||||||
|
document.addEventListener('click', tagInputButtons as EventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { setupTagEvents };
|
|
@ -28,13 +28,13 @@ function sortTags(hidden: boolean, a: TagData, b: TagData): number {
|
||||||
return a.spoiler_image_uri ? -1 : 1;
|
return a.spoiler_image_uri ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHiddenTags() {
|
export function getHiddenTags(): TagData[] {
|
||||||
return unique(window.booru.hiddenTagList)
|
return unique(window.booru.hiddenTagList)
|
||||||
.map(tagId => getTag(tagId))
|
.map(tagId => getTag(tagId))
|
||||||
.sort(sortTags.bind(null, true));
|
.sort(sortTags.bind(null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSpoileredTags() {
|
export function getSpoileredTags(): TagData[] {
|
||||||
if (window.booru.spoilerType === 'off') return [];
|
if (window.booru.spoilerType === 'off') return [];
|
||||||
|
|
||||||
return unique(window.booru.spoileredTagList)
|
return unique(window.booru.spoileredTagList)
|
||||||
|
|
|
@ -4,9 +4,6 @@
|
||||||
|
|
||||||
import { whenReady } from './utils/dom';
|
import { whenReady } from './utils/dom';
|
||||||
|
|
||||||
import { showOwnedComments } from './communications/comment';
|
|
||||||
import { showOwnedPosts } from './communications/post';
|
|
||||||
|
|
||||||
import { listenAutocomplete } from './autocomplete';
|
import { listenAutocomplete } from './autocomplete';
|
||||||
import { loadBooruData } from './booru';
|
import { loadBooruData } from './booru';
|
||||||
import { registerEvents } from './boorujs';
|
import { registerEvents } from './boorujs';
|
||||||
|
@ -40,8 +37,6 @@ import { setupSliders } from './slider';
|
||||||
|
|
||||||
whenReady(() => {
|
whenReady(() => {
|
||||||
|
|
||||||
showOwnedComments();
|
|
||||||
showOwnedPosts();
|
|
||||||
loadBooruData();
|
loadBooruData();
|
||||||
listenAutocomplete();
|
listenAutocomplete();
|
||||||
registerEvents();
|
registerEvents();
|
|
@ -21,7 +21,8 @@ window.booru = {
|
||||||
spoileredFilter: matchNone(),
|
spoileredFilter: matchNone(),
|
||||||
interactions: [],
|
interactions: [],
|
||||||
tagsVersion: 5,
|
tagsVersion: 5,
|
||||||
hideStaffTools: 'false'
|
hideStaffTools: 'false',
|
||||||
|
galleryImages: []
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/jsdom/jsdom/issues/1721#issuecomment-1484202038
|
// https://github.com/jsdom/jsdom/issues/1721#issuecomment-1484202038
|
||||||
|
|
4
assets/types/booru-object.d.ts
vendored
4
assets/types/booru-object.d.ts
vendored
|
@ -69,6 +69,10 @@ interface BooruObject {
|
||||||
* Indicates whether sensitive staff-only info should be hidden or not.
|
* Indicates whether sensitive staff-only info should be hidden or not.
|
||||||
*/
|
*/
|
||||||
hideStaffTools: string;
|
hideStaffTools: string;
|
||||||
|
/**
|
||||||
|
* List of image IDs in the current gallery.
|
||||||
|
*/
|
||||||
|
galleryImages: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
||||||
cssCodeSplit: true,
|
cssCodeSplit: true,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
'js/app': './js/app.js',
|
'js/app': './js/app.ts',
|
||||||
'css/application': './css/application.css',
|
'css/application': './css/application.css',
|
||||||
...Object.fromEntries(targets)
|
...Object.fromEntries(targets)
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,7 +24,7 @@ html lang="en"
|
||||||
|
|
||||||
= vite_hmr? do
|
= vite_hmr? do
|
||||||
script type="module" src="http://localhost:5173/@vite/client"
|
script type="module" src="http://localhost:5173/@vite/client"
|
||||||
script type="module" src="http://localhost:5173/js/app.js"
|
script type="module" src="http://localhost:5173/js/app.ts"
|
||||||
- else
|
- else
|
||||||
script type="text/javascript" src=Routes.static_path(@conn, "/js/app.js") async="async"
|
script type="text/javascript" src=Routes.static_path(@conn, "/js/app.js") async="async"
|
||||||
= render PhilomenaWeb.LayoutView, "_opengraph.html", assigns
|
= render PhilomenaWeb.LayoutView, "_opengraph.html", assigns
|
||||||
|
|
Loading…
Reference in a new issue