1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
2// See the LICENCE file in the repository root for full licence text.
3
4import { route } from 'laroute';
5import { debounce } from 'lodash';
6import { fail } from 'utils/fail';
7import { htmlElementOrNull } from 'utils/html';
8
9export default class BbcodeAutoPreview {
10 private readonly debouncedLoadPreview;
11 private readonly xhr = new Map<HTMLElement, JQuery.jqXHR<string>>();
12
13 constructor() {
14 this.debouncedLoadPreview = debounce(this.loadPreview, 500);
15 document.addEventListener('input', this.onInput);
16 }
17
18 private readonly loadPreview = (target: HTMLTextAreaElement) => {
19 const form = target.closest('form') ?? fail('form element is missing');
20 const body = target.value;
21 const preview = htmlElementOrNull(form.querySelector('.js-post-preview--preview'));
22 const previewBox = form.querySelector('.js-post-preview--box');
23
24 if (preview == null) {
25 return;
26 }
27
28 this.xhr.get(preview)?.abort();
29
30 if (body === '') {
31 preview.dataset.raw = '';
32 preview.innerHTML = '';
33 previewBox?.classList.add('hidden');
34 return;
35 }
36
37 if (preview.dataset.raw === body) {
38 previewBox?.classList.remove('hidden');
39 return;
40 }
41
42 const xhr = $.post(route('bbcode-preview'), { text: body }) as JQuery.jqXHR<string>;
43 xhr.done((data) => {
44 preview.dataset.raw = body;
45 preview.innerHTML = data;
46 previewBox?.classList.remove('hidden');
47 }).always(() => {
48 this.xhr.delete(preview);
49 });
50 };
51
52 private readonly onInput = (e: InputEvent) => {
53 const target = htmlElementOrNull(e.target)?.closest('.js-post-preview--auto');
54
55 if (target instanceof HTMLTextAreaElement) {
56 this.debouncedLoadPreview(target);
57 }
58 };
59}