From 259edc83e68286ad8c14b9646f43c82d79a9c7a4 Mon Sep 17 00:00:00 2001 From: Ian Morland Date: Thu, 28 Sep 2023 12:10:36 +0100 Subject: [PATCH] fix: LanguageDropdown not updating it's state correctly, move all option entirely to the backend --- extend.php | 10 +- js/src/common/models/{index.js => index.ts} | 0 js/src/common/utils/flag.js | 2 +- js/src/forum/addLanguageComposer.js | 6 +- js/src/forum/addLanguageToDiscussionList.js | 25 ++-- js/src/forum/components/Language.js | 33 ------ .../components/LanguageDiscussionModal.js | 4 +- js/src/forum/components/LanguageDisplay.tsx | 33 ++++++ js/src/forum/components/LanguageDropdown.js | 40 ------- js/src/forum/components/LanguageDropdown.tsx | 109 ++++++++++++++++++ js/src/forum/components/index.js | 4 +- js/src/forum/extendSubscriptionModal.js | 24 +--- .../forum/utils/generateLanguageOptions.tsx | 17 +++ .../DiscussionLanguageSerializer.php | 20 +++- src/DiscussionLanguage.php | 2 + ...oadForumDiscussionLanguageRelationship.php | 52 +++++++++ 16 files changed, 256 insertions(+), 125 deletions(-) rename js/src/common/models/{index.js => index.ts} (100%) delete mode 100644 js/src/forum/components/Language.js create mode 100644 js/src/forum/components/LanguageDisplay.tsx delete mode 100644 js/src/forum/components/LanguageDropdown.js create mode 100644 js/src/forum/components/LanguageDropdown.tsx create mode 100644 js/src/forum/utils/generateLanguageOptions.tsx create mode 100644 src/LoadForumDiscussionLanguageRelationship.php diff --git a/extend.php b/extend.php index b45f1e9..6b829e0 100644 --- a/extend.php +++ b/extend.php @@ -76,14 +76,8 @@ }), (new Extend\ApiController(ShowForumController::class)) - ->prepareDataForSerialization(function (ShowForumController $controller, &$data) { - // Expose the complete tag list to clients by adding it as a - // relationship to the /api endpoint. Since the Forum model - // doesn't actually have a tags relationship, we will manually load and - // assign the tags data to it using an event listener. - $data['discussionLanguages'] = DiscussionLanguage::get(); - }) - ->addInclude(['discussionLanguages']), + ->addInclude(['discussionLanguages']) + ->prepareDataForSerialization(LoadForumDiscussionLanguageRelationship::class), (new Extend\ApiController(ListDiscussionsController::class)) ->addInclude(['language']), diff --git a/js/src/common/models/index.js b/js/src/common/models/index.ts similarity index 100% rename from js/src/common/models/index.js rename to js/src/common/models/index.ts diff --git a/js/src/common/utils/flag.js b/js/src/common/utils/flag.js index 408edaa..6749f4d 100644 --- a/js/src/common/utils/flag.js +++ b/js/src/common/utils/flag.js @@ -12,7 +12,7 @@ export default (language) => { className="emoji" draggable="false" loading="lazy" - src={`//cdn.jsdelivr.net/gh/twitter/twemoji@13/assets/72x72/${basename(emoji)}.png`} + src={`//cdn.jsdelivr.net/gh/twitter/twemoji@14/assets/72x72/${basename(emoji)}.png`} /> ) : ( icon('fas fa-globe') diff --git a/js/src/forum/addLanguageComposer.js b/js/src/forum/addLanguageComposer.js index 8d34e20..ded0a0d 100644 --- a/js/src/forum/addLanguageComposer.js +++ b/js/src/forum/addLanguageComposer.js @@ -4,7 +4,7 @@ import IndexPage from 'flarum/forum/components/IndexPage'; import DiscussionComposer from 'flarum/forum/components/DiscussionComposer'; import LanguageDiscussionModal from './components/LanguageDiscussionModal'; -import Language from './components/Language'; +import LanguageDisplay from './components/LanguageDisplay'; const sort = (a, b) => a.code().toLowerCase() > b.code().toLowerCase(); @@ -16,7 +16,7 @@ export default () => { promise.then((composer) => (composer.fields.language = app.store.getBy('discussion-languages', 'code', dislang))); } else { const localeComposer = app.forum.attribute('fof-discussion-language.composerLocaleDefault'); - app.composer.fields.language = localeComposer ? app.store.getBy('discussion-languages', 'code', app.translator.formatter.locale) : ''; + app.composer.fields.language = localeComposer ? app.store.getBy('discussion-languages', 'code', app.translator.getLocale()) : ''; } }); @@ -42,7 +42,7 @@ export default () => { {this.composer.fields.language - ? Language.component({ language: this.composer.fields.language, uppercase: true }) + ? LanguageDisplay.component({ language: this.composer.fields.language, uppercase: true }) : app.translator.trans('fof-discussion-language.forum.composer_discussion.choose_language_link')} , diff --git a/js/src/forum/addLanguageToDiscussionList.js b/js/src/forum/addLanguageToDiscussionList.js index 87e6044..3316592 100644 --- a/js/src/forum/addLanguageToDiscussionList.js +++ b/js/src/forum/addLanguageToDiscussionList.js @@ -51,7 +51,7 @@ export default () => { } const paramLang = app.search.params().language; - const locale = app.search.params().language ?? app.translator.formatter.locale; + const locale = app.search.params().language ?? app.translator.getLocale(); const showAnyOpt = app.forum.attribute('fof-discussion-language.showAnyLangOpt'); if (params.filter.q) { @@ -75,29 +75,24 @@ export default () => { // Don't add language controls to /private (fof/byobu) if (app.current.data.routeName === 'byobuPrivate') return; - let extra, defaultSelected; - if (app.forum.attribute('fof-discussion-language.showAnyLangOpt')) { - extra = { any: app.translator.trans('fof-discussion-language.forum.index_language.any') }; - defaultSelected = 'any'; - } else { - defaultSelected = app.translator.formatter.locale; - } + const defaultSelected = app.forum.attribute('fof-discussion-language.showAnyLangOpt') ? 'any' : app.translator.getLocale(); items.add( 'language', - LanguageDropdown.component({ - extra, - default: defaultSelected, - onclick: (key) => { + { + // if `key` is not a string, return early + if (typeof key !== 'string') return; + const params = app.search.params(); if (key === defaultSelected) delete params.language; else params.language = key; setRouteWithForcedRefresh(app.route(app.current.get('routeName'), params)); - }, - selected: app.search.params().language, - }) + }} + /> ); }); }; diff --git a/js/src/forum/components/Language.js b/js/src/forum/components/Language.js deleted file mode 100644 index 7d90ae4..0000000 --- a/js/src/forum/components/Language.js +++ /dev/null @@ -1,33 +0,0 @@ -import app from 'flarum/forum/app'; -import Component from 'flarum/common/Component'; - -import flag from '../../common/utils/flag'; - -export default class Language extends Component { - oninit(vnode) { - super.oninit(vnode); - this.languages = app.store.all('discussion-languages'); - this.options = this.languages.reduce((o, lang) => { - o[lang.code()] = ( - - {flag(lang)} {lang.name()} - - ); - - return o; - }, this.attrs.extra || {}); - } - - view() { - const { language, uppercase } = this.attrs; - const name = language.name() || ''; - - return ( - - {flag(language)} -   - {uppercase ? name.toUpperCase() : name} - - ); - } -} diff --git a/js/src/forum/components/LanguageDiscussionModal.js b/js/src/forum/components/LanguageDiscussionModal.js index 4d04a7f..ef1bfe5 100644 --- a/js/src/forum/components/LanguageDiscussionModal.js +++ b/js/src/forum/components/LanguageDiscussionModal.js @@ -3,7 +3,7 @@ import Modal from 'flarum/common/components/Modal'; import Button from 'flarum/common/components/Button'; import DiscussionPage from 'flarum/forum/components/DiscussionPage'; -import Language from './Language'; +import LanguageDisplay from './LanguageDisplay'; export default class LanguageDiscussionModal extends Modal { oninit(vnode) { @@ -31,7 +31,7 @@ export default class LanguageDiscussionModal extends Modal {
{this.languages.map((language) => ( ))}
diff --git a/js/src/forum/components/LanguageDisplay.tsx b/js/src/forum/components/LanguageDisplay.tsx new file mode 100644 index 0000000..a9b3932 --- /dev/null +++ b/js/src/forum/components/LanguageDisplay.tsx @@ -0,0 +1,33 @@ +import app from 'flarum/forum/app'; +import Component from 'flarum/common/Component'; +import flag from '../../common/utils/flag'; +import Language from '../../common/models/Language'; +import type Mithril from 'mithril'; +import generateLanguageOptions from '../utils/generateLanguageOptions'; + +interface LanguageDisplayAttrs { + language: Language; + uppercase?: boolean; + extra?: Record; +} + +export default class LanguageDisplay extends Component { + options!: Record; + + oninit(vnode: Mithril.Vnode): void { + super.oninit(vnode); + + this.options = generateLanguageOptions(this.attrs.extra); + } + + view(): Mithril.Child { + const { language, uppercase } = this.attrs; + const name = language.name() || ''; + + return ( + + {flag(language)} {uppercase ? name.toUpperCase() : name} + + ); + } +} diff --git a/js/src/forum/components/LanguageDropdown.js b/js/src/forum/components/LanguageDropdown.js deleted file mode 100644 index b1455d9..0000000 --- a/js/src/forum/components/LanguageDropdown.js +++ /dev/null @@ -1,40 +0,0 @@ -import app from 'flarum/forum/app'; - -import Component from 'flarum/common/Component'; -import Button from 'flarum/common/components/Button'; - -import Language from './Language'; -import SelectDropdown from 'flarum/common/components/SelectDropdown'; - -export default class LanguageDropdown extends Component { - oninit(vnode) { - super.oninit(vnode); - this.languages = app.store.all('discussion-languages'); - /** - * @type {Record} - */ - this.options = this.languages.reduce((o, lang) => { - o[lang.code()] = ; - - return o; - }, this.attrs.extra || {}); - } - - view() { - const defaultValue = app.forum.attribute('fof-discussion-language.showAnyLangOpt') ? 'any' : app.translator.formatter.locale; - - return ( - - {Object.keys(this.options).map((langId) => { - const isItemSelected = this.attrs.selected ? langId === this.attrs.selected : langId === defaultValue; - - return ( - - ); - })} - - ); - } -} diff --git a/js/src/forum/components/LanguageDropdown.tsx b/js/src/forum/components/LanguageDropdown.tsx new file mode 100644 index 0000000..ff2750d --- /dev/null +++ b/js/src/forum/components/LanguageDropdown.tsx @@ -0,0 +1,109 @@ +import app from 'flarum/forum/app'; +import Button from 'flarum/common/components/Button'; +import Dropdown, { IDropdownAttrs } from 'flarum/common/components/Dropdown'; +import type Mithril from 'mithril'; +import icon from 'flarum/common/helpers/icon'; +import classList from 'flarum/common/utils/classList'; +import generateLanguageOptions from '../utils/generateLanguageOptions'; +import Stream from 'flarum/common/utils/Stream'; + +export interface LanguageDropdownAttrs extends IDropdownAttrs { + className?: string; + selected?: string; + onclick?: (langId: string) => void; + extra?: Record; +} + +export default class LanguageDropdown extends Dropdown { + selected!: Stream; + options!: Record; + loaded: boolean = false; + + static initAttrs(attrs: LanguageDropdownAttrs): void { + attrs.buttonClassName = 'Button'; + attrs.className = classList(attrs.className, 'Dropdown--select', 'Dropdown--language'); + super.initAttrs(attrs); + } + + oninit(vnode: Mithril.Vnode): void { + super.oninit(vnode); + + this.loadLanguages(); + this.selected = Stream(this.initSelectedLanguage()); + } + + getDefaultLanguage(): string { + return (app.forum.attribute('fof-discussion-language.showAnyLangOpt') ? 'any' : app.translator.getLocale()) || ''; + } + + initSelectedLanguage(): string { + return this.attrs.selected || this.getDefaultLanguage(); + } + + loadLanguages(): void { + if (this.loaded === false) { + this.options = generateLanguageOptions(this.attrs.extra); + this.loaded = true; + } + } + + buildDropdownContent(): Mithril.ChildArray { + return Object.keys(this.options).map((langId) => { + const isItemSelected = this.selected() === langId; + + return ( + + ); + }); + } + + view(vnode: Mithril.Vnode) { + const children = this.buildDropdownContent(); + + return super.view({ ...vnode.attrs, children }); + } + + getButton(children: Mithril.ChildArray): Mithril.Vnode { + return ( + + ); + } + + getButtonContent(children: Mithril.ChildArray): Mithril.ChildArray { + return [ + {this.options[this.selected()]}, + this.attrs.caretIcon ? icon(this.attrs.caretIcon, { className: 'Button-caret' }) : null, + ]; + } + + getMenu(items: Mithril.Vnode[]): Mithril.Vnode { + return ( +
    + {items} +
+ ); + } +} diff --git a/js/src/forum/components/index.js b/js/src/forum/components/index.js index 15561b7..8147169 100644 --- a/js/src/forum/components/index.js +++ b/js/src/forum/components/index.js @@ -1,9 +1,9 @@ -import Language from './Language'; +import LanguageDisplay from './LanguageDisplay'; import LanguageDiscussionModal from './LanguageDiscussionModal'; import LanguageDropdown from './LanguageDropdown'; export const components = { - Language, + LanguageDisplay, LanguageDiscussionModal, LanguageDropdown, }; diff --git a/js/src/forum/extendSubscriptionModal.js b/js/src/forum/extendSubscriptionModal.js index b99ec60..1a42d82 100644 --- a/js/src/forum/extendSubscriptionModal.js +++ b/js/src/forum/extendSubscriptionModal.js @@ -10,22 +10,9 @@ export default function extendSubscriptionModal() { if (!('fof-follow-tags' in flarum.extensions)) return; extend(components.SubscriptionModal.prototype, 'oninit', function () { - this.language = Stream(); - - const showAnyLangOption = app.forum.attribute('fof-discussion-language.showAnyLangOpt'); - - this.additionalLanguages = showAnyLangOption ? { any: app.translator.trans('fof-discussion-language.forum.index_language.any') } : {}; - this.defaultLanguage = showAnyLangOption ? 'any' : app.translator.formatter.locale; - }); - - extend(components.SubscriptionModal.prototype, 'oncreate', function () { const tag = this.attrs.model; const subscriptionLanguage = tag.subscriptionLanguage(); - - // For some reason, having this here in `oncreate` works, but we get unexpected bahviour if we put it in `oninit`. - // It's only an issue when `subscriptionLanguage` is not empty/null. - // TODO: investigate further at some point. - this.language = Stream(subscriptionLanguage || this.defaultLanguage); + this.language = Stream(subscriptionLanguage); }); extend(components.SubscriptionModal.prototype, 'formOptionItems', function (items) { @@ -35,12 +22,11 @@ export default function extendSubscriptionModal() {

{app.translator.trans('fof-discussion-language.forum.sub_controls.subscription_language_help')}

{ - this.language(code); - m.redraw(); + onclick={(key) => { + if (typeof key !== 'string') return; + + this.language(key); }} /> , diff --git a/js/src/forum/utils/generateLanguageOptions.tsx b/js/src/forum/utils/generateLanguageOptions.tsx new file mode 100644 index 0000000..e5f2e45 --- /dev/null +++ b/js/src/forum/utils/generateLanguageOptions.tsx @@ -0,0 +1,17 @@ +import Language from '../../common/models/Language'; +import LanguageDisplay from '../components/LanguageDisplay'; +import app from 'flarum/forum/app'; +import type Mithril from 'mithril'; + +export default function generateLanguageOptions(extraOptions?: Record): Record { + const options: Record = {}; + const languages = app.store.all('discussion-languages'); + + // Populate the options with the languages + languages.forEach((lang) => { + options[lang.code()] = ; + }); + + // Merge extra options, if provided + return { ...options, ...extraOptions }; +} diff --git a/src/Api/Serializers/DiscussionLanguageSerializer.php b/src/Api/Serializers/DiscussionLanguageSerializer.php index 6658a82..59d5f48 100644 --- a/src/Api/Serializers/DiscussionLanguageSerializer.php +++ b/src/Api/Serializers/DiscussionLanguageSerializer.php @@ -11,6 +11,7 @@ namespace FoF\DiscussionLanguage\Api\Serializers; +use Carbon\Translator; use Flarum\Api\Serializer\AbstractSerializer; use Flarum\Api\Serializer\DiscussionSerializer; use Flarum\Discussion\Discussion; @@ -20,6 +21,7 @@ use League\Csv\Statement; use League\Csv\TabularDataReader; use Rinvex\Country\CountryLoader; +use Symfony\Contracts\Translation\TranslatorInterface; class DiscussionLanguageSerializer extends AbstractSerializer { @@ -40,11 +42,16 @@ class DiscussionLanguageSerializer extends AbstractSerializer */ protected $records; - public function __construct(SettingsRepositoryInterface $settings, ISO639 $iso) + /** + * @var TranslatorInterface + */ + protected $translator; + + public function __construct(SettingsRepositoryInterface $settings, ISO639 $iso, TranslatorInterface $translator) { $this->settings = $settings; - $this->iso = $iso; + $this->translator = $translator; } /** @@ -75,8 +82,17 @@ public function discussion() return $this->hasOne(Discussion::class, DiscussionSerializer::class); } + public function getId($model): string + { + return $model->code === 'any' ? 'any' : $model->id; + } + protected function getLanguageName(string $code, bool $native) { + if ($code === 'any') { + return $this->translator->trans('fof-discussion-language.forum.index_language.any'); + } + if (!$this->records) { $csv = Reader::createFromPath(__DIR__.'/../../../resources/wikipedia-iso-639-2-codes.csv'); $csv->setHeaderOffset(0); diff --git a/src/DiscussionLanguage.php b/src/DiscussionLanguage.php index 60f6661..35f1edd 100644 --- a/src/DiscussionLanguage.php +++ b/src/DiscussionLanguage.php @@ -21,6 +21,8 @@ */ class DiscussionLanguage extends AbstractModel { + public $fillable = ['code', 'country']; + public function discussion() { return $this->belongsTo(Discussion::class, 'language_id'); diff --git a/src/LoadForumDiscussionLanguageRelationship.php b/src/LoadForumDiscussionLanguageRelationship.php new file mode 100644 index 0000000..d78fcb9 --- /dev/null +++ b/src/LoadForumDiscussionLanguageRelationship.php @@ -0,0 +1,52 @@ +settings = $settings; + } + + /** + * @param ShowForumController $controller + * @param $data + * @param ServerRequestInterface $request + */ + public function __invoke(ShowForumController $controller, &$data, ServerRequestInterface $request) + { + $data['discussionLanguages'] = $this->getLanguages(); + + } + + protected function getLanguages(): Collection + { + $langs = DiscussionLanguage::query()->get(); + + if ($this->settings->get('fof-discussion-language.showAnyLangOpt')) { + $code = 'any'; + + // Add a fake "any" language to the list + $langs->prepend(new DiscussionLanguage(compact('code'))); + } + + return $langs; + } +}