From 63eefad78d9d390ee40a28abf656d6e732c828f4 Mon Sep 17 00:00:00 2001 From: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com> Date: Sun, 14 May 2023 15:47:00 -0400 Subject: [PATCH] Make sticky an optional dependency, remove old modal override (#158) * fix: sticky should be an optional dependency Fixes #146 Signed-off-by: Alexander Skvortsov * fix: remove hacky modal override stacking modals is now in core, and this implementation is buggy Signed-off-by: Alexander Skvortsov * Update js/src/forum/components/FeaturedBlogItem.tsx --------- Signed-off-by: Alexander Skvortsov Co-authored-by: David Wheatley --- composer.json | 1 + js/src/forum/components/FeaturedBlogItem.tsx | 4 +- js/src/forum/index.ts | 11 -- js/src/forum/states/OverrideModalState.ts | 141 ------------------- js/src/forum/utils/overrideModalManager.js | 84 ----------- 5 files changed, 4 insertions(+), 237 deletions(-) delete mode 100644 js/src/forum/states/OverrideModalState.ts delete mode 100644 js/src/forum/utils/overrideModalManager.js diff --git a/composer.json b/composer.json index fa41749..4f5b0b8 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "color": "#ffffff" }, "optional-dependencies": [ + "flarum/sticky", "fof/discussion-language", "askvortsov/flarum-rich-text" ] diff --git a/js/src/forum/components/FeaturedBlogItem.tsx b/js/src/forum/components/FeaturedBlogItem.tsx index f7011e8..26d36ef 100644 --- a/js/src/forum/components/FeaturedBlogItem.tsx +++ b/js/src/forum/components/FeaturedBlogItem.tsx @@ -30,7 +30,9 @@ export default class FeaturedBlogItem extends Component { 100 ); - if (article.isSticky()) { + // Sticky is an optional dependency, so we can't + // assume method existence. + if (article.isSticky?.()) { items.add('sticky', {icon('fas fa-thumbtack')}, 80); } diff --git a/js/src/forum/index.ts b/js/src/forum/index.ts index d5ade7d..0e24477 100644 --- a/js/src/forum/index.ts +++ b/js/src/forum/index.ts @@ -13,10 +13,6 @@ import addSidebarNav from './utils/addSidebarNav'; import BlogItem from './pages/BlogItem'; -// Create our own modal manager -import OverrideModalState from './states/OverrideModalState'; -import overrideModalManager from './utils/overrideModalManager'; - // Register Flarum Blog app.initializers.add( 'v17development-flarum-blog', @@ -57,13 +53,6 @@ app.initializers.add( // Add a link to the blog to the IndexPage sidebar, if enabled. addSidebarNav(); - - /** - * Notice from V17: Temporary override due to lack of multi-dialogs! - * - * We'll open a PR to support multi-dialog to the Flarum main repo - */ - overrideModalManager(); }, -100000 ); diff --git a/js/src/forum/states/OverrideModalState.ts b/js/src/forum/states/OverrideModalState.ts deleted file mode 100644 index c00b5cf..0000000 --- a/js/src/forum/states/OverrideModalState.ts +++ /dev/null @@ -1,141 +0,0 @@ -import type Component from 'flarum/common/Component'; -import Modal from 'flarum/common/components/Modal'; - -/** - * Notice from V17: Temporary override due to lack of multi-dialogs! - * - * We'll open a PR to support multi-dialog to the Flarum main repo - */ - -/** - * Ideally, `show` would take a higher-kinded generic, ala: - * `show(componentClass: C, attrs: Attrs): void` - * Unfortunately, TypeScript does not support this: - * https://github.com/Microsoft/TypeScript/issues/1213 - * Therefore, we have to use this ugly, messy workaround. - */ -type UnsafeModalClass = ComponentClass & { - isDismissible: boolean; - component: typeof Component.component; -}; - -/** - * Class used to manage modal state. - * - * Accessible on the `app` object via `app.modal` property. - */ -export default class OverrideModalState { - /** - * @internal - */ - modal: null | { - componentClass: UnsafeModalClass; - attrs?: Record; - key: number; - } = null; - - modalList: OverrideModalState['modal'][] = []; - - /** - * Used to force re-initialization of modals if a modal - * is replaced by another of the same type. - */ - private key = 0; - - private closeTimeout?: number; - - /** - * Shows a modal dialog. - * - * If a modal is already open, the existing one will close and the new modal will replace it. - * - * @example Show a modal - * app.modal.show(MyCoolModal, { attr: 'value' }); - * - * @example Show a modal from a lifecycle method (`oncreate`, `view`, etc.) - * // This "hack" is needed due to quirks with nested redraws in Mithril. - * setTimeout(() => app.modal.show(MyCoolModal, { attr: 'value' }), 0); - */ - show(componentClass: UnsafeModalClass, attrs: Record = {}, keepLast: Boolean = false): void { - if (!(componentClass.prototype instanceof Modal)) { - // This is duplicated so that if the error is caught, an error message still shows up in the debug console. - const invalidModalWarning = 'The ModalManager can only show Modals.'; - console.error(invalidModalWarning); - throw new Error(invalidModalWarning); - } - - if (this.closeTimeout) clearTimeout(this.closeTimeout); - - // Open new modal - this.modal = { componentClass, attrs, key: this.key++ }; - - // Remember previously opened modal - if (keepLast) { - this.modalList.push(this.modal); - } else { - // Override last modals - this.modalList = [this.modal]; - } - - m.redraw.sync(); - } - - /** - * Closes the currently open dialog, if one is open. - */ - close(): void { - if (!this.modal) return; - - // There are more modals currently opened - // Close current and open last in list - if (this.modalList.length >= 2) { - $(`.modal[modal-key=${this.modal?.key}]`) - .one('hide.bs.modal', () => { - const currentModalPosition = this.modalList.indexOf(this.modal); - - // Remove last modal from list - this.modalList.splice(currentModalPosition, 1); - - // Open last modal from list - this.modal = this.modalList[this.modalList.length - 1]; - - setTimeout(() => { - $(document.body).addClass('modal-open'); - }, 1000); - - m.redraw(); - }) - // @ts-expect-error no typings for bootstrap modals - .modal('hide'); - - m.redraw(); - return; - } - - // Don't hide the modal immediately, because if the consumer happens to call - // the `show` method straight after to show another modal dialog, it will - // cause Bootstrap's modal JS to misbehave. Instead we will wait for a tiny - // bit to give the `show` method the opportunity to prevent this from going - // ahead. - - this.closeTimeout = window.setTimeout(() => { - $(`.modal[modal-key=${this.modal?.key}]`) - // @ts-expect-error no typings for bootstrap modals - .modal('hide') - .one('hide.bs.modal', () => { - this.modal = null; - - m.redraw(); - }); - }); - } - - /** - * Checks if a modal is currently open. - * - * @return `true` if a modal dialog is currently open, otherwise `false`. - */ - isModalOpen(): boolean { - return !!this.modal; - } -} diff --git a/js/src/forum/utils/overrideModalManager.js b/js/src/forum/utils/overrideModalManager.js deleted file mode 100644 index 1d3e3fa..0000000 --- a/js/src/forum/utils/overrideModalManager.js +++ /dev/null @@ -1,84 +0,0 @@ -import ModalManager from 'flarum/forum/components/ModalManager'; -import { override } from 'flarum/common/extend'; -import OverrideModalState from '../states/OverrideModalState'; -import app from 'flarum/forum/app'; - -/** - * Notice from V17: Temporary override due to lack of multi-dialogs! - * - * We'll open a PR to support multi-dialog to the Flarum main repo - */ - -export default function overrideModalManager() { - if (!$.fn.modal) { - // Bootstrap modals not defined, so we can assume this is Flarum 1.5 or later, - // where modal stacking is available natively. - // - // In this case, we do not need to override any modal code for the extension. - - return; - } - - const style = document.createElement('style'); - style.innerHTML = ` -#modal .ModalManager:not(:last-child), -.modal-backdrop.in:not(:last-child) { - opacity: 0; -}`; - document.head.appendChild(style); - - app.modal = new OverrideModalState(); - - override(ModalManager.prototype, 'view', function () { - return this.attrs.state.modalList.map((modal) => { - const Tag = modal?.componentClass; - - return ( -
- {!!Tag && ( - - )} -
- ); - }); - }); - - override(ModalManager.prototype, 'animateHide', function () { - if (this.attrs.state.modalList.length === 1) { - this.modalShown = false; - } - }); - - override(ModalManager.prototype, 'animateShow', function (readyCallback = () => {}) { - if (!this.attrs.state.modal) return; - - const dismissible = !!this.attrs.state.modal.componentClass.isDismissible; - - this.modalShown = true; - - // If we are opening this modal while another modal is already open, - // the shown event will not run, because the modal is already open. - // So, we need to manually trigger the readyCallback. - if ($(`.modal[modal-key=${this.attrs.state.modal.key}]`).hasClass('in')) { - readyCallback(); - return; - } - - setTimeout(() => { - $(`.modal[modal-key=${this.attrs.state.modal.key}]`) - .one('shown.bs.modal', readyCallback) - // @ts-expect-error: No typings available for Bootstrap modals. - .modal({ - backdrop: dismissible || 'static', - keyboard: dismissible, - show: true, - }); - }, 1); - }); -}