From 677ecc265ce25418a86beacb5af56cd7e3c08661 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 23 Jun 2024 12:36:19 -0400 Subject: [PATCH 1/4] Fix API upload --- lib/philomena_web/controllers/api/json/image_controller.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/philomena_web/controllers/api/json/image_controller.ex b/lib/philomena_web/controllers/api/json/image_controller.ex index a142aeb42..82a0068bb 100644 --- a/lib/philomena_web/controllers/api/json/image_controller.ex +++ b/lib/philomena_web/controllers/api/json/image_controller.ex @@ -41,6 +41,8 @@ defmodule PhilomenaWeb.Api.Json.ImageController do case Images.create_image(attributes, image_params) do {:ok, %{image: image}} -> + image = Repo.preload(image, tags: :aliases) + PhilomenaWeb.Endpoint.broadcast!( "firehose", "image:create", From e30a1922ed1b231214ded330387b4ab28f9966ff Mon Sep 17 00:00:00 2001 From: "Luna D." Date: Sun, 23 Jun 2024 19:59:06 +0200 Subject: [PATCH 2/4] convert misc scripts to ts --- assets/js/{misc.js => misc.ts} | 45 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 19 deletions(-) rename assets/js/{misc.js => misc.ts} (60%) diff --git a/assets/js/misc.js b/assets/js/misc.ts similarity index 60% rename from assets/js/misc.js rename to assets/js/misc.ts index 5733e9540..c8365d228 100644 --- a/assets/js/misc.js +++ b/assets/js/misc.ts @@ -4,35 +4,40 @@ import store from './utils/store'; import { $, $$ } from './utils/dom'; +import { assertNotNull } from './utils/assert'; +import '../types/ujs'; let touchMoved = false; -function formResult({target, detail}) { - - const elements = { +function formResult({target, detail}: FetchcompleteEvent) { + const elements: {[key: string]: string} = { '#description-form': '.image-description', '#uploader-form': '.image_uploader' }; - function showResult(resultEl, formEl, response) { + function showResult(resultEl: HTMLElement, formEl: HTMLFormElement, response: string) { resultEl.innerHTML = response; resultEl.classList.remove('hidden'); formEl.classList.add('hidden'); - formEl.querySelector('input[type="submit"],button').disabled = false; + const inputEl = $('input[type="submit"]', formEl); + const buttonEl = $('button', formEl); + + if (inputEl) inputEl.disabled = false; + if (buttonEl) buttonEl.disabled = false; } for (const element in elements) { - if (target.matches(element)) detail.text().then(text => showResult($(elements[element]), target, text)); + if (target.matches(element)) { + detail.text().then(text => showResult(assertNotNull($(elements[element])), target as HTMLFormElement, text)); + } } - } -function revealSpoiler(event) { - - const { target } = event; +function revealSpoiler(event: MouseEvent | TouchEvent) { + const target = assertNotNull(event.target) as HTMLElement; const spoiler = target.closest('.spoiler'); - let imgspoiler = target.closest('.spoiler .imgspoiler, .spoiler-revealed .imgspoiler'); const showContainer = target.closest('.image-show-container'); + let imgspoiler = target.closest('.spoiler .imgspoiler, .spoiler-revealed .imgspoiler'); // Prevent reveal if touchend came after touchmove event if (touchMoved) { @@ -42,7 +47,8 @@ function revealSpoiler(event) { if (spoiler) { if (showContainer) { - const imageShow = showContainer.querySelector('.image-show'); + const imageShow = assertNotNull(showContainer.querySelector('.image-show')); + if (!imageShow.classList.contains('hidden') && imageShow.classList.contains('spoiler-pending')) { imageShow.classList.remove('spoiler-pending'); return; @@ -62,19 +68,22 @@ function revealSpoiler(event) { if (imgspoiler) { imgspoiler.classList.remove('imgspoiler'); imgspoiler.classList.add('imgspoiler-revealed'); + if (event.type === 'touchend' && !event.defaultPrevented) { event.preventDefault(); } } - } -function setupEvents() { - const extrameta = $('#extrameta'); +export function setupEvents() { + const extrameta = $('#extrameta'); + + if (extrameta && store.get('hide_uploader')) { + extrameta.classList.add('hidden'); + } - if (store.get('hide_uploader') && extrameta) extrameta.classList.add('hidden'); if (store.get('hide_score')) { - $$('.upvotes,.score,.downvotes').forEach(s => s.classList.add('hidden')); + $$('.upvotes,.score,.downvotes').forEach(s => s.classList.add('hidden')); } document.addEventListener('fetchcomplete', formResult); @@ -82,5 +91,3 @@ function setupEvents() { document.addEventListener('touchend', revealSpoiler); document.addEventListener('touchmove', () => touchMoved = true); } - -export { setupEvents }; From 97ef88ec1018b849b4a5963bfc85419a4dfc4dec Mon Sep 17 00:00:00 2001 From: "Luna D." Date: Sun, 23 Jun 2024 19:21:24 +0200 Subject: [PATCH 3/4] convert notifications to typescript --- .../js/{notifications.js => notifications.ts} | 40 +++++++++---------- assets/js/utils/events.ts | 5 ++- assets/js/utils/store.ts | 4 +- 3 files changed, 25 insertions(+), 24 deletions(-) rename assets/js/{notifications.js => notifications.ts} (56%) diff --git a/assets/js/notifications.js b/assets/js/notifications.ts similarity index 56% rename from assets/js/notifications.js rename to assets/js/notifications.ts index 2447debbf..d76cf533e 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.ts @@ -5,19 +5,17 @@ import { fetchJson, handleError } from './utils/requests'; import { $ } from './utils/dom'; import { delegate } from './utils/events'; +import { assertNotNull, assertNotUndefined } from './utils/assert'; import store from './utils/store'; const NOTIFICATION_INTERVAL = 600000, NOTIFICATION_EXPIRES = 300000; -function makeRequest(verb) { - return fetchJson(verb, '/notifications/unread').then(handleError); -} - function bindSubscriptionLinks() { delegate(document, 'fetchcomplete', { '.js-subscription-link': event => { - const target = event.target.closest('.js-subscription-target'); + const target = assertNotNull(event.target.closest('.js-subscription-target')); + event.detail.text().then(text => { target.outerHTML = text; }); @@ -30,42 +28,42 @@ function getNewNotifications() { return; } - makeRequest('GET').then(response => response.json()).then(({ notifications }) => { - updateNotificationTicker(notifications); - storeNotificationCount(notifications); + fetchJson('GET', '/notifications/unread') + .then(handleError) + .then(response => response.json()) + .then(({ notifications }) => { + updateNotificationTicker(notifications); + storeNotificationCount(notifications); - setTimeout(getNewNotifications, NOTIFICATION_INTERVAL); - }); + setTimeout(getNewNotifications, NOTIFICATION_INTERVAL); + }); } -function updateNotificationTicker(notificationCount) { - const ticker = $('.js-notification-ticker'); +function updateNotificationTicker(notificationCount: string | null) { + const ticker = assertNotNull($('.js-notification-ticker')); const parsedNotificationCount = Number(notificationCount); - ticker.dataset.notificationCount = parsedNotificationCount; - ticker.textContent = parsedNotificationCount; + ticker.dataset.notificationCount = parsedNotificationCount.toString(); + ticker.textContent = parsedNotificationCount.toString(); } - -function storeNotificationCount(notificationCount) { +function storeNotificationCount(notificationCount: string) { // The current number of notifications are stored along with the time when the data expires store.setWithExpireTime('notificationCount', notificationCount, NOTIFICATION_EXPIRES); } - -function setupNotifications() { +export function setupNotifications() { if (!window.booru.userIsSignedIn) return; // Fetch notifications from the server at a regular interval setTimeout(getNewNotifications, NOTIFICATION_INTERVAL); // Update the current number of notifications based on the latest page load - storeNotificationCount($('.js-notification-ticker').dataset.notificationCount); + const ticker = assertNotNull($('.js-notification-ticker')); + storeNotificationCount(assertNotUndefined(ticker.dataset.notificationCount)); // Update ticker when the stored value changes - this will occur in all open tabs store.watch('notificationCount', updateNotificationTicker); bindSubscriptionLinks(); } - -export { setupNotifications }; diff --git a/assets/js/utils/events.ts b/assets/js/utils/events.ts index 65c2c4960..e92b0109a 100644 --- a/assets/js/utils/events.ts +++ b/assets/js/utils/events.ts @@ -1,5 +1,7 @@ // DOM events +import '../../types/ujs'; + export interface PhilomenaAvailableEventsMap { dragstart: DragEvent, dragover: DragEvent, @@ -9,7 +11,8 @@ export interface PhilomenaAvailableEventsMap { drop: DragEvent, click: MouseEvent, submit: Event, - reset: Event + reset: Event, + fetchcomplete: FetchcompleteEvent } export interface PhilomenaEventElement { diff --git a/assets/js/utils/store.ts b/assets/js/utils/store.ts index c09a09cee..a71d4256b 100644 --- a/assets/js/utils/store.ts +++ b/assets/js/utils/store.ts @@ -38,9 +38,9 @@ export default { }, // Watch changes to a specified key - returns value on change - watch(key: string, callback: (value: unknown) => void) { + watch(key: string, callback: (value: Value | null) => void) { const handler = (event: StorageEvent) => { - if (event.key === key) callback(this.get(key)); + if (event.key === key) callback(this.get(key)); }; window.addEventListener('storage', handler); return () => window.removeEventListener('storage', handler); From e5b476f07f4ec4ca66355ce6f8daebc99ab5ab1c Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 23 Jun 2024 14:56:31 -0400 Subject: [PATCH 4/4] Further simplify formResult, setupEvents --- assets/js/misc.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/assets/js/misc.ts b/assets/js/misc.ts index c8365d228..3de3ef868 100644 --- a/assets/js/misc.ts +++ b/assets/js/misc.ts @@ -3,32 +3,34 @@ */ import store from './utils/store'; -import { $, $$ } from './utils/dom'; -import { assertNotNull } from './utils/assert'; +import { $, $$, hideEl, showEl } from './utils/dom'; +import { assertNotNull, assertType } from './utils/assert'; import '../types/ujs'; let touchMoved = false; function formResult({target, detail}: FetchcompleteEvent) { - const elements: {[key: string]: string} = { + const elements: Record = { '#description-form': '.image-description', '#uploader-form': '.image_uploader' }; - function showResult(resultEl: HTMLElement, formEl: HTMLFormElement, response: string) { + function showResult(formEl: HTMLFormElement, resultEl: HTMLElement, response: string) { resultEl.innerHTML = response; - resultEl.classList.remove('hidden'); - formEl.classList.add('hidden'); - const inputEl = $('input[type="submit"]', formEl); - const buttonEl = $('button', formEl); + hideEl(formEl); + showEl(resultEl); - if (inputEl) inputEl.disabled = false; - if (buttonEl) buttonEl.disabled = false; + $$('input[type="submit"],button', formEl).forEach(button => { + button.disabled = false; + }); } - for (const element in elements) { - if (target.matches(element)) { - detail.text().then(text => showResult(assertNotNull($(elements[element])), target as HTMLFormElement, text)); + for (const [ formSelector, resultSelector ] of Object.entries(elements)) { + if (target.matches(formSelector)) { + const form = assertType(target, HTMLFormElement); + const result = assertNotNull($(resultSelector)); + + detail.text().then(text => showResult(form, result, text)); } } } @@ -79,11 +81,11 @@ export function setupEvents() { const extrameta = $('#extrameta'); if (extrameta && store.get('hide_uploader')) { - extrameta.classList.add('hidden'); + hideEl(extrameta); } if (store.get('hide_score')) { - $$('.upvotes,.score,.downvotes').forEach(s => s.classList.add('hidden')); + $$('.upvotes,.score,.downvotes').forEach(s => hideEl(s)); } document.addEventListener('fetchcomplete', formResult);