mirror of
https://github.com/discourse/discourse.git
synced 2025-03-31 11:25:40 +08:00
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 Component from "@glimmer/component";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
|
|
||||||
export default class BootstrapModeNotice extends Component {
|
export default class BootstrapModeNotice extends Component {
|
||||||
@ -12,4 +13,12 @@ export default class BootstrapModeNotice extends Component {
|
|||||||
`/t/-/${this.siteSettings.admin_quick_start_topic_id}`
|
`/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 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 {
|
export default class NewListHeaderControlsWrapper extends Component {
|
||||||
|
@action
|
||||||
click(e) {
|
click(e) {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
if (target.closest("button.topics-replies-toggle.--all")) {
|
if (target.closest("button.topics-replies-toggle.--all")) {
|
||||||
@ -11,4 +15,20 @@ export default class NewListHeaderControlsWrapper extends Component {
|
|||||||
this.args.changeNewListSubset("replies");
|
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 Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { cancel, next } from "@ember/runloop";
|
import { cancel, next } from "@ember/runloop";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { eq } from "truth-helpers";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default class extends Component {
|
export default class PageLoadingSlider extends Component {
|
||||||
@service loadingSlider;
|
@service loadingSlider;
|
||||||
@service capabilities;
|
@service capabilities;
|
||||||
|
|
||||||
@ -17,6 +20,11 @@ export default class extends Component {
|
|||||||
this.loadingSlider.on("stateChanged", this.stateChanged);
|
this.loadingSlider.on("stateChanged", this.stateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
super.willDestroy(...arguments);
|
||||||
|
this.loadingSlider.off("stateChange", this, "stateChange");
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
stateChanged(loading) {
|
stateChanged(loading) {
|
||||||
if (this._deferredStateChange) {
|
if (this._deferredStateChange) {
|
||||||
@ -34,9 +42,9 @@ export default class extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
get containerStyle() {
|
||||||
this.loadingSlider.off("stateChange", this, "stateChange");
|
const duration = this.loadingSlider.averageLoadingDuration.toFixed(2);
|
||||||
super.destroy();
|
return htmlSafe(`--loading-duration: ${duration}s`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -60,8 +68,23 @@ export default class extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get containerStyle() {
|
<template>
|
||||||
const duration = this.loadingSlider.averageLoadingDuration.toFixed(2);
|
{{#if (eq this.loadingSlider.mode "slider")}}
|
||||||
return htmlSafe(`--loading-duration: ${duration}s`);
|
<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 { service } from "@ember/service";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
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 {
|
export default class WatchRead extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
|
|
||||||
didInsertElement() {
|
constructor() {
|
||||||
super.didInsertElement(...arguments);
|
super(...arguments);
|
||||||
|
|
||||||
if (!this.currentUser || this.currentUser.read_faq) {
|
if (!this.currentUser || this.currentUser.read_faq) {
|
||||||
return;
|
return;
|
||||||
@ -20,8 +20,8 @@ export default class WatchRead extends Component {
|
|||||||
window.addEventListener("scroll", this._checkIfRead, false);
|
window.addEventListener("scroll", this._checkIfRead, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroyElement() {
|
willDestroy() {
|
||||||
super.willDestroyElement(...arguments);
|
super.willDestroy(...arguments);
|
||||||
|
|
||||||
window.removeEventListener("resize", this._checkIfRead);
|
window.removeEventListener("resize", this._checkIfRead);
|
||||||
window.removeEventListener("scroll", 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…
x
Reference in New Issue
Block a user