DEV: Convert various components to gjs (#26782)
Those were all low hanging fruits - all were already glimmer components, so this was mostly merging js and hbs files and adding imports. (occasionally also adds/fixes class names)
This commit is contained in:
parent
5d1f38a592
commit
3930064fd1
|
@ -0,0 +1,19 @@
|
|||
import Component from "@glimmer/component";
|
||||
|
||||
export default class PluginCommitHash extends Component {
|
||||
get shortCommitHash() {
|
||||
return this.args.plugin.commitHash?.slice(0, 7);
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @plugin.commitHash}}
|
||||
<a
|
||||
href={{@plugin.commitUrl}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="current commit-hash"
|
||||
title={{@plugin.commitHash}}
|
||||
>{{this.shortCommitHash}}</a>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{{#if this.commitHash}}
|
||||
<a
|
||||
href={{@plugin.commitUrl}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="current commit-hash"
|
||||
title={{this.commitHash}}
|
||||
>{{this.shortCommitHash}}</a>
|
||||
{{/if}}
|
|
@ -1,11 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
|
||||
export default class PluginCommitHash extends Component {
|
||||
get shortCommitHash() {
|
||||
return this.commitHash?.slice(0, 7);
|
||||
}
|
||||
|
||||
get commitHash() {
|
||||
return this.args.plugin.commitHash;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import Component from "@glimmer/component";
|
||||
import iconOrImage from "discourse/helpers/icon-or-image";
|
||||
import domFromString from "discourse-common/lib/dom-from-string";
|
||||
|
||||
export default class BadgeButton extends Component {
|
||||
get title() {
|
||||
const description = this.args.badge?.description;
|
||||
if (description) {
|
||||
return domFromString(`<div>${description}</div>`)[0].innerText;
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<span
|
||||
title={{this.title}}
|
||||
data-badge-name={{@badge.name}}
|
||||
class="user-badge
|
||||
{{@badge.badgeTypeClassName}}
|
||||
{{unless @badge.enabled 'disabled'}}"
|
||||
...attributes
|
||||
>
|
||||
{{iconOrImage @badge}}
|
||||
<span class="badge-display-name">{{@badge.name}}</span>
|
||||
{{yield}}
|
||||
</span>
|
||||
</template>
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<span
|
||||
class="user-badge
|
||||
{{@badge.badgeTypeClassName}}
|
||||
{{unless @badge.enabled 'disabled'}}"
|
||||
title={{this.title}}
|
||||
data-badge-name={{@badge.name}}
|
||||
>
|
||||
{{icon-or-image @badge}}
|
||||
<span class="badge-display-name">{{@badge.name}}</span>
|
||||
{{yield}}
|
||||
</span>
|
|
@ -1,12 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import domFromString from "discourse-common/lib/dom-from-string";
|
||||
|
||||
// Takes @badge as argument.
|
||||
export default class BadgeButtonComponent extends Component {
|
||||
get title() {
|
||||
const description = this.args.badge?.description;
|
||||
if (description) {
|
||||
return domFromString(`<div>${description}</div>`)[0].innerText;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
|
||||
export default class BootstrapModeNotice extends Component {
|
||||
|
@ -12,4 +13,12 @@ export default class BootstrapModeNotice extends Component {
|
|||
`/t/-/${this.siteSettings.admin_quick_start_topic_id}`
|
||||
);
|
||||
}
|
||||
|
||||
<template>
|
||||
<DButton
|
||||
@action={{this.routeToAdminGuide}}
|
||||
@label="bootstrap_mode"
|
||||
class="btn-default bootstrap-mode"
|
||||
/>
|
||||
</template>
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<DButton
|
||||
class="btn-default bootstrap-mode"
|
||||
@label="bootstrap_mode"
|
||||
@action={{this.routeToAdminGuide}}
|
||||
/>
|
|
@ -0,0 +1,51 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { inject as controller } from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import GroupCardContents from "discourse/components/group-card-contents";
|
||||
import UserCardContents from "discourse/components/user-card-contents";
|
||||
import routeAction from "discourse/helpers/route-action";
|
||||
import DiscourseURL, { groupPath, userPath } from "discourse/lib/url";
|
||||
import PluginOutlet from "./plugin-outlet";
|
||||
|
||||
export default class CardContainer extends Component {
|
||||
@service site;
|
||||
@controller topic;
|
||||
|
||||
@action
|
||||
filterPosts(user) {
|
||||
this.topic.send("filterParticipant", user);
|
||||
}
|
||||
|
||||
@action
|
||||
showUser(user) {
|
||||
DiscourseURL.routeTo(userPath(user.username_lower));
|
||||
}
|
||||
|
||||
@action
|
||||
showGroup(group) {
|
||||
DiscourseURL.routeTo(groupPath(group.name));
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.site.mobileView}}
|
||||
<div class="card-cloak hidden"></div>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet @name="user-card-content-container">
|
||||
<UserCardContents
|
||||
@topic={{this.topic.model}}
|
||||
@showUser={{this.showUser}}
|
||||
@filterPosts={{this.filterPosts}}
|
||||
@composePrivateMessage={{routeAction "composePrivateMessage"}}
|
||||
role="dialog"
|
||||
/>
|
||||
</PluginOutlet>
|
||||
|
||||
<GroupCardContents
|
||||
@topic={{this.topic.model}}
|
||||
@showUser={{this.showUser}}
|
||||
@showGroup={{this.showGroup}}
|
||||
/>
|
||||
</template>
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
{{#if this.site.mobileView}}
|
||||
<div class="card-cloak hidden"></div>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet @name="user-card-content-container">
|
||||
<UserCardContents
|
||||
@topic={{this.topic.model}}
|
||||
@showUser={{this.showUser}}
|
||||
@filterPosts={{this.filterPosts}}
|
||||
@composePrivateMessage={{route-action "composePrivateMessage"}}
|
||||
role="dialog"
|
||||
/>
|
||||
</PluginOutlet>
|
||||
|
||||
<GroupCardContents
|
||||
@topic={{this.topic.model}}
|
||||
@showUser={{this.showUser}}
|
||||
@showGroup={{this.showGroup}}
|
||||
/>
|
|
@ -1,26 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { inject as controller } from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import DiscourseURL, { groupPath, userPath } from "discourse/lib/url";
|
||||
|
||||
export default class CardWrapper extends Component {
|
||||
@service site;
|
||||
@controller topic;
|
||||
|
||||
@action
|
||||
filterPosts(user) {
|
||||
const topicController = this.topic;
|
||||
topicController.send("filterParticipant", user);
|
||||
}
|
||||
|
||||
@action
|
||||
showUser(user) {
|
||||
DiscourseURL.routeTo(userPath(user.username_lower));
|
||||
}
|
||||
|
||||
@action
|
||||
showGroup(group) {
|
||||
DiscourseURL.routeTo(groupPath(group.name));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import Component from "@glimmer/component";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import { translateModKey } from "discourse/lib/utilities";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class ComposerSaveButton extends Component {
|
||||
get translatedTitle() {
|
||||
return I18n.t("composer.title", { modifier: translateModKey("Meta+") });
|
||||
}
|
||||
|
||||
<template>
|
||||
<DButton
|
||||
@action={{@action}}
|
||||
@label={{@label}}
|
||||
@icon={{@icon}}
|
||||
@translatedTitle={{this.translatedTitle}}
|
||||
@forwardEvent={{@forwardEvent}}
|
||||
class={{concatClass "btn-primary create" (if @disabledSubmit "disabled")}}
|
||||
...attributes
|
||||
/>
|
||||
</template>
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<DButton
|
||||
@translatedTitle={{this.translatedTitle}}
|
||||
@label={{@label}}
|
||||
@action={{@action}}
|
||||
@icon={{@icon}}
|
||||
@forwardEvent={{@forwardEvent}}
|
||||
class="btn-primary create {{if @disabledSubmit 'disabled'}}"
|
||||
...attributes
|
||||
/>
|
|
@ -1,9 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { translateModKey } from "discourse/lib/utilities";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class ComposerSaveButton extends Component {
|
||||
get translatedTitle() {
|
||||
return I18n.t("composer.title", { modifier: translateModKey("Meta+") });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import icon from "discourse-common/helpers/d-icon";
|
||||
|
||||
export default class FormTemplateFieldMultiSelect extends Component {
|
||||
@action
|
||||
isSelected(option) {
|
||||
return this.args.value?.includes(option);
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-field-type="multi-select"
|
||||
class="control-group form-template-field"
|
||||
>
|
||||
{{#if @attributes.label}}
|
||||
<label class="form-template-field__label">
|
||||
{{@attributes.label}}
|
||||
{{#if @validations.required}}
|
||||
{{icon "asterisk" class="form-template-field__required-indicator"}}
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
{{#if @attributes.description}}
|
||||
<span class="form-template-field__description">
|
||||
{{htmlSafe @attributes.description}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{! TODO(@keegan): Update implementation to use <MultiSelect/> instead }}
|
||||
{{! Current using <select multiple> as it integrates easily with FormData (will update in v2) }}
|
||||
<select
|
||||
name={{@id}}
|
||||
required={{if @validations.required "required" ""}}
|
||||
multiple="multiple"
|
||||
class="form-template-field__multi-select"
|
||||
>
|
||||
{{#if @attributes.none_label}}
|
||||
<option
|
||||
class="form-template-field__multi-select-placeholder"
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
>{{@attributes.none_label}}</option>
|
||||
{{/if}}
|
||||
{{#each @choices as |choice|}}
|
||||
<option
|
||||
value={{choice}}
|
||||
selected={{this.isSelected choice}}
|
||||
>{{choice}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<div class="control-group form-template-field" data-field-type="multi-select">
|
||||
{{#if @attributes.label}}
|
||||
<label class="form-template-field__label">
|
||||
{{@attributes.label}}
|
||||
{{#if @validations.required}}
|
||||
{{d-icon "asterisk" class="form-template-field__required-indicator"}}
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
{{#if @attributes.description}}
|
||||
<span class="form-template-field__description">
|
||||
{{html-safe @attributes.description}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{! TODO(@keegan): Update implementation to use <MultiSelect/> instead }}
|
||||
{{! Current using <select multiple> as it integrates easily with FormData (will update in v2) }}
|
||||
<select
|
||||
name={{@id}}
|
||||
class="form-template-field__multi-select"
|
||||
required={{if @validations.required "required" ""}}
|
||||
multiple="multiple"
|
||||
>
|
||||
{{#if @attributes.none_label}}
|
||||
<option
|
||||
class="form-template-field__multi-select-placeholder"
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
>{{@attributes.none_label}}</option>
|
||||
{{/if}}
|
||||
{{#each @choices as |choice|}}
|
||||
<option
|
||||
value={{choice}}
|
||||
selected={{this.isSelected choice}}
|
||||
>{{choice}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class FormTemplateFieldMultiSelect extends Component {
|
||||
@action
|
||||
isSelected(option) {
|
||||
return this.args.value?.includes(option);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
import bodyClass from "discourse/helpers/body-class";
|
||||
import hideApplicationFooter from "discourse/helpers/hide-application-footer";
|
||||
import loadingSpinner from "discourse/helpers/loading-spinner";
|
||||
|
||||
export default class LoadingSliderFallbackSpinner extends Component {
|
||||
@service loadingSlider;
|
||||
|
||||
get shouldDisplay() {
|
||||
const { mode, loading, stillLoading } = this.loadingSlider;
|
||||
return (
|
||||
(mode === "spinner" && loading) || (mode === "slider" && stillLoading)
|
||||
);
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.shouldDisplay}}
|
||||
<div class="route-loading-spinner">{{loadingSpinner}}</div>
|
||||
{{bodyClass "has-route-loading-spinner"}}
|
||||
{{hideApplicationFooter}}
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{{#if this.shouldDisplay}}
|
||||
<div class="route-loading-spinner">{{loading-spinner}}</div>
|
||||
{{body-class "has-route-loading-spinner"}}
|
||||
{{hide-application-footer}}
|
||||
{{/if}}
|
|
@ -1,13 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
|
||||
export default class LoadingSliderFallbackSpinner extends Component {
|
||||
@service loadingSlider;
|
||||
|
||||
get shouldDisplay() {
|
||||
const { mode, loading, stillLoading } = this.loadingSlider;
|
||||
return (
|
||||
(mode === "spinner" && loading) || (mode === "slider" && stillLoading)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { array } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import { service } from "@ember/service";
|
||||
|
||||
export default class ModalContainer extends Component {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
closeModal(data) {
|
||||
this.modal.close(data);
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
{{didInsert this.modal.setContainerElement}}
|
||||
class="modal-container"
|
||||
></div>
|
||||
|
||||
{{#if this.modal.activeModal}}
|
||||
{{#each (array this.modal.activeModal) as |activeModal|}}
|
||||
{{! #each ensures that the activeModal component/model are updated atomically }}
|
||||
<activeModal.component
|
||||
@model={{activeModal.opts.model}}
|
||||
@closeModal={{this.closeModal}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<div class="modal-container" {{did-insert this.modal.setContainerElement}}>
|
||||
</div>
|
||||
|
||||
{{#if this.modal.activeModal}}
|
||||
{{#each (array this.modal.activeModal) as |activeModal|}}
|
||||
{{! #each ensures that the activeModal component/model are updated atomically }}
|
||||
<activeModal.component
|
||||
@model={{activeModal.opts.model}}
|
||||
@closeModal={{this.closeModal}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{/if}}
|
|
@ -1,12 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
|
||||
export default class ModalContainer extends Component {
|
||||
@service modal;
|
||||
|
||||
@action
|
||||
closeModal(data) {
|
||||
this.modal.close(data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { fn } from "@ember/helper";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DModal from "discourse/components/d-modal";
|
||||
import PreferenceCheckbox from "discourse/components/preference-checkbox";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
|
||||
export default class DismissRead extends Component {
|
||||
@tracked dismissTopics = false;
|
||||
|
||||
<template>
|
||||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
@title={{i18n @model.title count=@model.count}}
|
||||
class="dismiss-read-modal"
|
||||
>
|
||||
<:body>
|
||||
<p>
|
||||
<PreferenceCheckbox
|
||||
@labelKey="topics.bulk.also_dismiss_topics"
|
||||
@checked={{this.dismissTopics}}
|
||||
class="dismiss-read-modal__stop-tracking"
|
||||
/>
|
||||
</p>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
@action={{fn @model.dismissRead this.dismissTopics}}
|
||||
@label="topics.bulk.dismiss"
|
||||
@icon="check"
|
||||
id="dismiss-read-confirm"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</:footer>
|
||||
</DModal>
|
||||
</template>
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<DModal
|
||||
@closeModal={{@closeModal}}
|
||||
@title={{i18n @model.title count=@model.count}}
|
||||
class="dismiss-read-modal"
|
||||
>
|
||||
<:body>
|
||||
<p>
|
||||
<PreferenceCheckbox
|
||||
@labelKey="topics.bulk.also_dismiss_topics"
|
||||
@checked={{this.dismissTopics}}
|
||||
class="dismiss-read-modal__stop-tracking"
|
||||
/>
|
||||
</p>
|
||||
</:body>
|
||||
<:footer>
|
||||
<DButton
|
||||
class="btn-primary"
|
||||
@action={{fn @model.dismissRead this.dismissTopics}}
|
||||
@icon="check"
|
||||
id="dismiss-read-confirm"
|
||||
@label="topics.bulk.dismiss"
|
||||
/>
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -1,6 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
|
||||
export default class DismissRead extends Component {
|
||||
@tracked dismissTopics = false;
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import raw from "discourse/helpers/raw";
|
||||
|
||||
export default class NewListHeaderControlsWrapper extends Component {
|
||||
@action
|
||||
click(e) {
|
||||
const target = e.target;
|
||||
if (target.closest("button.topics-replies-toggle.--all")) {
|
||||
|
@ -11,4 +15,20 @@ export default class NewListHeaderControlsWrapper extends Component {
|
|||
this.args.changeNewListSubset("replies");
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
{{! template-lint-disable no-invalid-interactive }}
|
||||
{{on "click" this.click}}
|
||||
class="topic-replies-toggle-wrapper"
|
||||
>
|
||||
{{raw
|
||||
"list/new-list-header-controls"
|
||||
current=@current
|
||||
newRepliesCount=@newRepliesCount
|
||||
newTopicsCount=@newTopicsCount
|
||||
noStaticLabel=true
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<div
|
||||
class="topic-replies-toggle-wrapper"
|
||||
{{on "click" (action this.click)}}
|
||||
{{! template-lint-disable no-invalid-interactive }}
|
||||
>
|
||||
{{raw
|
||||
"list/new-list-header-controls"
|
||||
current=@current
|
||||
newRepliesCount=@newRepliesCount
|
||||
newTopicsCount=@newTopicsCount
|
||||
noStaticLabel=true
|
||||
}}
|
||||
</div>
|
|
@ -0,0 +1,31 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
|
||||
export default class OfflineIndicator extends Component {
|
||||
@service networkConnectivity;
|
||||
|
||||
get showing() {
|
||||
return !this.networkConnectivity.connected;
|
||||
}
|
||||
|
||||
@action
|
||||
refresh() {
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.showing}}
|
||||
<div class="offline-indicator">
|
||||
<span>{{i18n "offline_indicator.no_internet"}}</span>
|
||||
<DButton
|
||||
@label="offline_indicator.refresh_page"
|
||||
@display="link"
|
||||
@action={{this.refresh}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{{#if this.showing}}
|
||||
<div class="offline-indicator">
|
||||
<span>{{i18n "offline_indicator.no_internet"}}</span>
|
||||
<DButton
|
||||
@label="offline_indicator.refresh_page"
|
||||
@display="link"
|
||||
@action={{this.refresh}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -1,16 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
|
||||
export default class OfflineIndicator extends Component {
|
||||
@service networkConnectivity;
|
||||
|
||||
get showing() {
|
||||
return !this.networkConnectivity.connected;
|
||||
}
|
||||
|
||||
@action
|
||||
refresh() {
|
||||
window.location.reload(true);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { cancel, next } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { eq } from "truth-helpers";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default class extends Component {
|
||||
export default class PageLoadingSlider extends Component {
|
||||
@service loadingSlider;
|
||||
@service capabilities;
|
||||
|
||||
|
@ -17,6 +20,11 @@ export default class extends Component {
|
|||
this.loadingSlider.on("stateChanged", this.stateChanged);
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
this.loadingSlider.off("stateChange", this, "stateChange");
|
||||
}
|
||||
|
||||
@bind
|
||||
stateChanged(loading) {
|
||||
if (this._deferredStateChange) {
|
||||
|
@ -34,9 +42,9 @@ export default class extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.loadingSlider.off("stateChange", this, "stateChange");
|
||||
super.destroy();
|
||||
get containerStyle() {
|
||||
const duration = this.loadingSlider.averageLoadingDuration.toFixed(2);
|
||||
return htmlSafe(`--loading-duration: ${duration}s`);
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -60,8 +68,23 @@ export default class extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
get containerStyle() {
|
||||
const duration = this.loadingSlider.averageLoadingDuration.toFixed(2);
|
||||
return htmlSafe(`--loading-duration: ${duration}s`);
|
||||
}
|
||||
<template>
|
||||
{{#if (eq this.loadingSlider.mode "slider")}}
|
||||
<div
|
||||
{{on "transitionend" this.onContainerTransitionEnd}}
|
||||
style={{this.containerStyle}}
|
||||
class={{concatClass
|
||||
"loading-indicator-container"
|
||||
this.state
|
||||
(if this.capabilities.isAppWebview "discourse-hub-webview")
|
||||
}}
|
||||
>
|
||||
<div
|
||||
{{on "transitionend" this.onBarTransitionEnd}}
|
||||
class="loading-indicator"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{{#if (eq this.loadingSlider.mode "slider")}}
|
||||
<div
|
||||
class={{concat-class
|
||||
"loading-indicator-container"
|
||||
this.state
|
||||
(if this.capabilities.isAppWebview "discourse-hub-webview")
|
||||
}}
|
||||
{{on "transitionend" this.onContainerTransitionEnd}}
|
||||
style={{this.containerStyle}}
|
||||
>
|
||||
<div
|
||||
class="loading-indicator"
|
||||
{{on "transitionend" this.onBarTransitionEnd}}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -0,0 +1,13 @@
|
|||
import Component from "@glimmer/component";
|
||||
|
||||
export default class MessagesSecondaryNav extends Component {
|
||||
get messagesNav() {
|
||||
return document.getElementById("user-navigation-secondary__horizontal-nav");
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#in-element this.messagesNav}}
|
||||
{{yield}}
|
||||
{{/in-element}}
|
||||
</template>
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{{#in-element this.messagesNav}}
|
||||
{{yield}}
|
||||
{{/in-element}}
|
|
@ -1,10 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
|
||||
export default class extends Component {
|
||||
@service currentUser;
|
||||
|
||||
get messagesNav() {
|
||||
return document.getElementById("user-navigation-secondary__horizontal-nav");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
import emoji from "discourse/helpers/emoji";
|
||||
import { until } from "discourse/lib/formatter";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
export default class UserStatusMessage extends Component {
|
||||
@service currentUser;
|
||||
|
||||
get until() {
|
||||
if (!this.args.status.ends_at) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timezone = this.currentUser
|
||||
? this.currentUser.user_option?.timezone
|
||||
: moment.tz.guess();
|
||||
|
||||
return until(this.args.status.ends_at, timezone, this.currentUser?.locale);
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @status}}
|
||||
<DTooltip
|
||||
@identifier="user-status-message-tooltip"
|
||||
class="user-status-message"
|
||||
...attributes
|
||||
>
|
||||
<:trigger>
|
||||
{{emoji @status.emoji skipTitle=true}}
|
||||
{{#if @showDescription}}
|
||||
<span class="user-status-message-description">
|
||||
{{@status.description}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</:trigger>
|
||||
<:content>
|
||||
{{emoji @status.emoji skipTitle=true}}
|
||||
<span class="user-status-tooltip-description">
|
||||
{{@status.description}}
|
||||
</span>
|
||||
{{#if this.until}}
|
||||
<div class="user-status-tooltip-until">
|
||||
{{this.until}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</:content>
|
||||
</DTooltip>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{{#if @status}}
|
||||
<DTooltip
|
||||
@identifier="user-status-message-tooltip"
|
||||
class="user-status-message"
|
||||
...attributes
|
||||
>
|
||||
<:trigger>
|
||||
{{emoji @status.emoji skipTitle=true}}
|
||||
{{#if @showDescription}}
|
||||
<span class="user-status-message-description">
|
||||
{{@status.description}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</:trigger>
|
||||
<:content>
|
||||
{{emoji @status.emoji skipTitle=true}}
|
||||
<span class="user-status-tooltip-description">
|
||||
{{@status.description}}
|
||||
</span>
|
||||
{{#if this.until}}
|
||||
<div class="user-status-tooltip-until">
|
||||
{{this.until}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</:content>
|
||||
</DTooltip>
|
||||
{{/if}}
|
|
@ -1,19 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
import { until } from "discourse/lib/formatter";
|
||||
|
||||
export default class UserStatusMessage extends Component {
|
||||
@service currentUser;
|
||||
|
||||
get until() {
|
||||
if (!this.args.status.ends_at) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timezone = this.currentUser
|
||||
? this.currentUser.user_option?.timezone
|
||||
: moment.tz.guess();
|
||||
|
||||
return until(this.args.status.ends_at, timezone, this.currentUser?.locale);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Component from "@ember/component";
|
||||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
||||
|
@ -8,8 +8,8 @@ import { bind } from "discourse-common/utils/decorators";
|
|||
export default class WatchRead extends Component {
|
||||
@service currentUser;
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
if (!this.currentUser || this.currentUser.read_faq) {
|
||||
return;
|
||||
|
@ -20,8 +20,8 @@ export default class WatchRead extends Component {
|
|||
window.addEventListener("scroll", this._checkIfRead, false);
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
|
||||
window.removeEventListener("resize", this._checkIfRead);
|
||||
window.removeEventListener("scroll", this._checkIfRead);
|
|
@ -0,0 +1,48 @@
|
|||
import Component from "@glimmer/component";
|
||||
|
||||
function convertToSeconds(time) {
|
||||
const match = time.toString().match(/(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/);
|
||||
const [hours, minutes, seconds] = match.slice(1);
|
||||
|
||||
if (hours || minutes || seconds) {
|
||||
const h = parseInt(hours, 10) || 0;
|
||||
const m = parseInt(minutes, 10) || 0;
|
||||
const s = parseInt(seconds, 10) || 0;
|
||||
|
||||
return h * 3600 + m * 60 + s;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
export default class LazyIframe extends Component {
|
||||
get iframeSrc() {
|
||||
switch (this.args.providerName) {
|
||||
case "youtube":
|
||||
let url = `https://www.youtube.com/embed/${this.args.videoId}?autoplay=1&rel=0`;
|
||||
if (this.args.startTime) {
|
||||
url += `&start=${convertToSeconds(this.args.startTime)}`;
|
||||
}
|
||||
return url;
|
||||
case "vimeo":
|
||||
return `https://player.vimeo.com/video/${this.args.videoId}${
|
||||
this.args.videoId.includes("?") ? "&" : "?"
|
||||
}autoplay=1`;
|
||||
case "tiktok":
|
||||
return `https://www.tiktok.com/embed/v2/${this.args.videoId}`;
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @providerName}}
|
||||
<iframe
|
||||
src={{this.iframeSrc}}
|
||||
title={{@title}}
|
||||
allowFullScreen
|
||||
scrolling="no"
|
||||
frameborder="0"
|
||||
seamless="seamless"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
></iframe>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{{#if @providerName}}
|
||||
<iframe
|
||||
src={{this.iframeSrc}}
|
||||
title={{@title}}
|
||||
allowFullScreen
|
||||
scrolling="no"
|
||||
frameborder="0"
|
||||
seamless="seamless"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
></iframe>
|
||||
{{/if}}
|
|
@ -1,34 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
|
||||
export default class LazyVideo extends Component {
|
||||
get iframeSrc() {
|
||||
switch (this.args.providerName) {
|
||||
case "youtube":
|
||||
let url = `https://www.youtube.com/embed/${this.args.videoId}?autoplay=1&rel=0`;
|
||||
if (this.args.startTime) {
|
||||
url += `&start=${this.convertToSeconds(this.args.startTime)}`;
|
||||
}
|
||||
return url;
|
||||
case "vimeo":
|
||||
return `https://player.vimeo.com/video/${this.args.videoId}${
|
||||
this.args.videoId.includes("?") ? "&" : "?"
|
||||
}autoplay=1`;
|
||||
case "tiktok":
|
||||
return `https://www.tiktok.com/embed/v2/${this.args.videoId}`;
|
||||
}
|
||||
}
|
||||
|
||||
convertToSeconds(time) {
|
||||
const match = time.toString().match(/(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/);
|
||||
const [hours, minutes, seconds] = match.slice(1);
|
||||
|
||||
if (hours || minutes || seconds) {
|
||||
const h = parseInt(hours, 10) || 0;
|
||||
const m = parseInt(minutes, 10) || 0;
|
||||
const s = parseInt(seconds, 10) || 0;
|
||||
|
||||
return h * 3600 + m * 60 + s;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { concat } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import LazyIframe from "./lazy-iframe";
|
||||
|
||||
export default class LazyVideo extends Component {
|
||||
@tracked isLoaded = false;
|
||||
|
||||
get thumbnailStyle() {
|
||||
const color = this.args.videoAttributes.dominantColor;
|
||||
if (color?.match(/^[0-9A-Fa-f]+$/)) {
|
||||
return htmlSafe(`background-color: #${color};`);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
loadEmbed() {
|
||||
if (!this.isLoaded) {
|
||||
this.isLoaded = true;
|
||||
this.args.onLoadedVideo?.();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyPress(event) {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.loadEmbed();
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-video-id={{@videoAttributes.id}}
|
||||
data-video-title={{@videoAttributes.title}}
|
||||
data-video-start-time={{@videoAttributes.startTime}}
|
||||
data-provider-name={{@videoAttributes.providerName}}
|
||||
class={{concatClass
|
||||
"lazy-video-container"
|
||||
(concat @videoAttributes.providerName "-onebox")
|
||||
(if this.isLoaded "video-loaded")
|
||||
}}
|
||||
>
|
||||
{{#if this.isLoaded}}
|
||||
<LazyIframe
|
||||
@providerName={{@videoAttributes.providerName}}
|
||||
@title={{@videoAttributes.title}}
|
||||
@videoId={{@videoAttributes.id}}
|
||||
@startTime={{@videoAttributes.startTime}}
|
||||
/>
|
||||
{{else}}
|
||||
<div
|
||||
{{on "click" this.loadEmbed}}
|
||||
{{on "keypress" this.loadEmbed}}
|
||||
tabindex="0"
|
||||
style={{this.thumbnailStyle}}
|
||||
class={{concatClass "video-thumbnail" @videoAttributes.providerName}}
|
||||
>
|
||||
<img
|
||||
src={{@videoAttributes.thumbnail}}
|
||||
title={{@videoAttributes.title}}
|
||||
loading="lazy"
|
||||
class={{concat @videoAttributes.providerName "-thumbnail"}}
|
||||
/>
|
||||
<div
|
||||
class={{concatClass
|
||||
"icon"
|
||||
(concat @videoAttributes.providerName "-icon")
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div class="title-container">
|
||||
<div class="title-wrapper">
|
||||
<a
|
||||
href={{@videoAttributes.url}}
|
||||
title={{@videoAttributes.title}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="title-link"
|
||||
>
|
||||
{{@videoAttributes.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<div
|
||||
class={{concat-class
|
||||
"lazy-video-container"
|
||||
(concat @videoAttributes.providerName "-onebox")
|
||||
(if this.isLoaded "video-loaded")
|
||||
}}
|
||||
data-video-id={{@videoAttributes.id}}
|
||||
data-video-title={{@videoAttributes.title}}
|
||||
data-video-start-time={{@videoAttributes.startTime}}
|
||||
data-provider-name={{@videoAttributes.providerName}}
|
||||
>
|
||||
{{#if this.isLoaded}}
|
||||
<LazyIframe
|
||||
@providerName={{@videoAttributes.providerName}}
|
||||
@title={{@videoAttributes.title}}
|
||||
@videoId={{@videoAttributes.id}}
|
||||
@startTime={{@videoAttributes.startTime}}
|
||||
/>
|
||||
{{else}}
|
||||
<div
|
||||
class={{concat-class "video-thumbnail" @videoAttributes.providerName}}
|
||||
tabindex="0"
|
||||
style={{this.thumbnailStyle}}
|
||||
{{on "click" this.loadEmbed}}
|
||||
{{on "keypress" this.loadEmbed}}
|
||||
>
|
||||
<img
|
||||
class={{concat @videoAttributes.providerName "-thumbnail"}}
|
||||
src={{@videoAttributes.thumbnail}}
|
||||
title={{@videoAttributes.title}}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div
|
||||
class={{concat-class
|
||||
"icon"
|
||||
(concat @videoAttributes.providerName "-icon")
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div class="title-container">
|
||||
<div class="title-wrapper">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="title-link"
|
||||
href={{@videoAttributes.url}}
|
||||
title={{@videoAttributes.title}}
|
||||
>
|
||||
{{@videoAttributes.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,31 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
|
||||
export default class LazyVideo extends Component {
|
||||
@tracked isLoaded = false;
|
||||
|
||||
@action
|
||||
loadEmbed() {
|
||||
if (!this.isLoaded) {
|
||||
this.isLoaded = true;
|
||||
this.args.onLoadedVideo?.();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyPress(event) {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.loadEmbed();
|
||||
}
|
||||
}
|
||||
|
||||
get thumbnailStyle() {
|
||||
const color = this.args.videoAttributes.dominantColor;
|
||||
if (color?.match(/^[0-9A-Fa-f]+$/)) {
|
||||
return htmlSafe(`background-color: #${color};`);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue