DEV: Convert core components to native class syntax (batch 8) (#28602)

Changes made using the ember-native-class-codemod, plus some manual tweaks
This commit is contained in:
David Taylor 2024-08-28 16:20:04 +01:00 committed by GitHub
parent 4150ec960e
commit 54b281c4a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 605 additions and 545 deletions

View File

@ -1,23 +1,28 @@
import Component from "@ember/component";
import { schedule } from "@ember/runloop";
import { htmlSafe } from "@ember/template";
import {
attributeBindings,
classNames,
tagName,
} from "@ember-decorators/component";
import { iconHTML } from "discourse-common/lib/icon-library";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
export default Component.extend({
tagName: "div",
classNames: ["directory-table__column-header", "sortable"],
attributeBindings: ["title", "colspan", "ariaSort:aria-sort", "role"],
role: "columnheader",
labelKey: null,
chevronIcon: null,
columnIcon: null,
translated: false,
automatic: false,
onActiveRender: null,
pressedState: null,
ariaLabel: null,
@tagName("div")
@classNames("directory-table__column-header", "sortable")
@attributeBindings("title", "colspan", "ariaSort:aria-sort", "role")
export default class TableHeaderToggle extends Component {
role = "columnheader";
labelKey = null;
chevronIcon = null;
columnIcon = null;
translated = false;
automatic = false;
onActiveRender = null;
pressedState = null;
ariaLabel = null;
@discourseComputed("order", "field", "asc")
ariaSort() {
@ -26,14 +31,16 @@ export default Component.extend({
} else {
return "none";
}
},
}
toggleProperties() {
if (this.order === this.field) {
this.set("asc", this.asc ? null : true);
} else {
this.setProperties({ order: this.field, asc: null });
}
},
}
toggleChevron() {
if (this.order === this.field) {
let chevron = iconHTML(this.asc ? "chevron-up" : "chevron-down");
@ -41,32 +48,35 @@ export default Component.extend({
} else {
this.set("chevronIcon", null);
}
},
}
click() {
this.toggleProperties();
},
}
keyPress(e) {
if (e.which === 13) {
this.toggleProperties();
}
},
}
didReceiveAttrs() {
this._super(...arguments);
super.didReceiveAttrs(...arguments);
if (!this.automatic && !this.translated) {
this.set("labelKey", this.field);
}
this.set("id", `table-header-toggle-${this.field.replace(/\s/g, "")}`);
this.toggleChevron();
this._updateA11yAttributes();
},
}
didRender() {
this._super(...arguments);
super.didRender(...arguments);
if (this.onActiveRender && this.chevronIcon) {
this.onActiveRender(this.element);
}
},
}
_updateA11yAttributes() {
let criteria = "";
@ -99,10 +109,11 @@ export default Component.extend({
} else {
this.set("pressedState", "false");
}
},
}
_focusHeader() {
schedule("afterRender", () => {
document.getElementById(this.id)?.focus();
});
},
});
}
}

View File

@ -1,18 +1,22 @@
import Component from "@ember/component";
import {
attributeBindings,
classNameBindings,
tagName,
} from "@ember-decorators/component";
import DiscourseURL from "discourse/lib/url";
import getURL from "discourse-common/lib/get-url";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "a",
classNameBindings: [
":tag-badge-wrapper",
":badge-wrapper",
":bullet",
"tagClass",
],
attributeBindings: ["href"],
@tagName("a")
@classNameBindings(
":tag-badge-wrapper",
":badge-wrapper",
":bullet",
"tagClass"
)
@attributeBindings("href")
export default class TagDropLink extends Component {
@discourseComputed("tagId", "category")
href(tagId, category) {
let path;
@ -24,16 +28,16 @@ export default Component.extend({
}
return getURL(path);
},
}
@discourseComputed("tagId")
tagClass(tagId) {
return "tag-" + tagId;
},
}
click(e) {
e.preventDefault();
DiscourseURL.routeTo(this.href);
return true;
},
});
}
}

View File

@ -125,7 +125,7 @@
/>
<DButton
@action={{action "destroy"}}
@action={{this.destroyTagGroup}}
@disabled={{this.buffered.isNew}}
@icon="far-trash-alt"
@label="tagging.groups.delete"

View File

@ -1,28 +1,33 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { isEmpty } from "@ember/utils";
import { tagName } from "@ember-decorators/component";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import Group from "discourse/models/group";
import PermissionType from "discourse/models/permission-type";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
export default Component.extend(bufferedProperty("model"), {
router: service(),
dialog: service(),
tagName: "",
allGroups: null,
@tagName("")
export default class TagGroupsForm extends Component.extend(
bufferedProperty("model")
) {
@service router;
@service dialog;
allGroups = null;
init() {
this._super(...arguments);
super.init(...arguments);
this.setGroupOptions();
},
}
setGroupOptions() {
Group.findAll().then((groups) => {
this.set("allGroups", groups);
});
},
}
@discourseComputed(
"buffered.name",
@ -36,7 +41,7 @@ export default Component.extend(bufferedProperty("model"), {
(!this.everyoneSelected(permissions) &&
isEmpty(this.selectedGroupNames(permissions)))
);
},
}
@discourseComputed("buffered.permissions", "allGroups")
selectedGroupIds(permissions, allGroups) {
@ -53,7 +58,7 @@ export default Component.extend(bufferedProperty("model"), {
});
return groupIds;
},
}
everyoneSelected(permissions) {
if (!permissions) {
@ -61,7 +66,7 @@ export default Component.extend(bufferedProperty("model"), {
}
return permissions.everyone === PermissionType.FULL;
},
}
selectedGroupNames(permissions) {
if (!permissions) {
@ -69,87 +74,89 @@ export default Component.extend(bufferedProperty("model"), {
}
return Object.keys(permissions).filter((name) => name !== "everyone");
},
}
actions: {
setPermissionsType(permissionName) {
let updatedPermissions = Object.assign(
{},
this.buffered.get("permissions")
);
@action
setPermissionsType(permissionName) {
let updatedPermissions = Object.assign(
{},
this.buffered.get("permissions")
);
if (permissionName === "private") {
delete updatedPermissions.everyone;
} else if (permissionName === "visible") {
updatedPermissions.everyone = PermissionType.READONLY;
if (permissionName === "private") {
delete updatedPermissions.everyone;
} else if (permissionName === "visible") {
updatedPermissions.everyone = PermissionType.READONLY;
} else {
updatedPermissions.everyone = PermissionType.FULL;
}
this.buffered.set("permissions", updatedPermissions);
}
@action
setPermissionsGroups(groupIds) {
let updatedPermissions = Object.assign(
{},
this.buffered.get("permissions")
);
this.allGroups.forEach((group) => {
if (groupIds.includes(group.id)) {
updatedPermissions[group.name] = PermissionType.FULL;
} else {
updatedPermissions.everyone = PermissionType.FULL;
delete updatedPermissions[group.name];
}
});
this.buffered.set("permissions", updatedPermissions);
},
this.buffered.set("permissions", updatedPermissions);
}
setPermissionsGroups(groupIds) {
let updatedPermissions = Object.assign(
{},
this.buffered.get("permissions")
);
@action
save() {
if (this.cannotSave) {
this.dialog.alert(I18n.t("tagging.groups.cannot_save"));
return false;
}
this.allGroups.forEach((group) => {
if (groupIds.includes(group.id)) {
updatedPermissions[group.name] = PermissionType.FULL;
} else {
delete updatedPermissions[group.name];
}
});
const attrs = this.buffered.getProperties(
"name",
"tag_names",
"parent_tag_name",
"one_per_topic",
"permissions"
);
this.buffered.set("permissions", updatedPermissions);
},
// If 'everyone' is set to full, we can remove any groups.
if (
!attrs.permissions ||
attrs.permissions.everyone === PermissionType.FULL
) {
attrs.permissions = { everyone: PermissionType.FULL };
}
save() {
if (this.cannotSave) {
this.dialog.alert(I18n.t("tagging.groups.cannot_save"));
return false;
this.model.save(attrs).then(() => {
this.commitBuffer();
if (this.onSave) {
this.onSave();
} else {
this.router.transitionTo("tagGroups.index");
}
});
}
const attrs = this.buffered.getProperties(
"name",
"tag_names",
"parent_tag_name",
"one_per_topic",
"permissions"
);
// If 'everyone' is set to full, we can remove any groups.
if (
!attrs.permissions ||
attrs.permissions.everyone === PermissionType.FULL
) {
attrs.permissions = { everyone: PermissionType.FULL };
}
this.model.save(attrs).then(() => {
this.commitBuffer();
if (this.onSave) {
this.onSave();
} else {
this.router.transitionTo("tagGroups.index");
}
});
},
destroy() {
return this.dialog.yesNoConfirm({
message: I18n.t("tagging.groups.confirm_delete"),
didConfirm: () => {
this.model.destroyRecord().then(() => {
if (this.onDestroy) {
this.onDestroy();
}
});
},
});
},
},
});
@action
destroyTagGroup() {
return this.dialog.yesNoConfirm({
message: I18n.t("tagging.groups.confirm_delete"),
didConfirm: () => {
this.model.destroyRecord().then(() => {
if (this.onDestroy) {
this.onDestroy();
}
});
},
});
}
}

View File

@ -4,24 +4,27 @@ import { and, reads } from "@ember/object/computed";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { isEmpty } from "@ember/utils";
import { tagName } from "@ember-decorators/component";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
export default Component.extend({
dialog: service(),
tagName: "",
loading: false,
tagInfo: null,
newSynonyms: null,
showEditControls: false,
canAdminTag: reads("currentUser.staff"),
editSynonymsMode: and("canAdminTag", "showEditControls"),
editing: false,
newTagName: null,
newTagDescription: null,
router: service(),
@tagName("")
export default class TagInfo extends Component {
@service dialog;
@service router;
loading = false;
tagInfo = null;
newSynonyms = null;
showEditControls = false;
editing = false;
newTagName = null;
newTagDescription = null;
@reads("currentUser.staff") canAdminTag;
@and("canAdminTag", "showEditControls") editSynonymsMode;
@discourseComputed("tagInfo.tag_group_names")
tagGroupsInfo(tagGroupNames) {
@ -29,14 +32,14 @@ export default Component.extend({
count: tagGroupNames.length,
tag_groups: tagGroupNames.join(", "),
});
},
}
@discourseComputed("tagInfo.categories")
categoriesInfo(categories) {
return I18n.t("tagging.category_restrictions", {
count: categories.length,
});
},
}
@discourseComputed(
"tagInfo.tag_group_names",
@ -45,25 +48,25 @@ export default Component.extend({
)
nothingToShow(tagGroupNames, categories, synonyms) {
return isEmpty(tagGroupNames) && isEmpty(categories) && isEmpty(synonyms);
},
}
@discourseComputed("newTagName")
updateDisabled(newTagName) {
const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g");
newTagName = newTagName ? newTagName.replace(filterRegexp, "").trim() : "";
return newTagName.length === 0;
},
}
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
this.loadTagInfo();
},
}
didUpdateAttrs() {
this._super(...arguments);
super.didUpdateAttrs(...arguments);
this.set("tagInfo", null);
this.loadTagInfo();
},
}
loadTagInfo() {
if (this.loading) {
@ -81,7 +84,7 @@ export default Component.extend({
})
.finally(() => this.set("loading", false))
.catch(popupAjaxError);
},
}
@action
edit(event) {
@ -95,7 +98,7 @@ export default Component.extend({
newTagName: this.tag.id,
newTagDescription: this.tagInfo.description,
});
},
}
@action
unlinkSynonym(tag, event) {
@ -105,7 +108,7 @@ export default Component.extend({
})
.then(() => this.tagInfo.synonyms.removeObject(tag))
.catch(popupAjaxError);
},
}
@action
deleteSynonym(tag, event) {
@ -122,17 +125,17 @@ export default Component.extend({
.catch(popupAjaxError);
},
});
},
}
@action
toggleEditControls() {
this.toggleProperty("showEditControls");
},
}
@action
cancelEditing() {
this.set("editing", false);
},
}
@action
finishedEditing() {
@ -151,7 +154,7 @@ export default Component.extend({
}
})
.catch(popupAjaxError);
},
}
@action
deleteTag() {
@ -182,7 +185,7 @@ export default Component.extend({
}
},
});
},
}
@action
addSynonyms() {
@ -217,5 +220,5 @@ export default Component.extend({
.catch(popupAjaxError);
},
});
},
});
}
}

View File

@ -1,34 +1,35 @@
import Component from "@ember/component";
import { sort } from "@ember/object/computed";
import { classNameBindings } from "@ember-decorators/component";
import Category from "discourse/models/category";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
export default Component.extend({
classNameBindings: [
":tags-list",
":tag-list",
"categoryClass",
"tagGroupNameClass",
],
@classNameBindings(
":tags-list",
":tag-list",
"categoryClass",
"tagGroupNameClass"
)
export default class TagList extends Component {
isPrivateMessage = false;
isPrivateMessage: false,
sortedTags: sort("tags", "sortProperties"),
@sort("tags", "sortProperties") sortedTags;
@discourseComputed("titleKey")
title(titleKey) {
return titleKey && I18n.t(titleKey);
},
}
@discourseComputed("categoryId")
category(categoryId) {
return categoryId && Category.findById(categoryId);
},
}
@discourseComputed("category.fullSlug")
categoryClass(slug) {
return slug && `tag-list-${slug}`;
},
}
@discourseComputed("tagGroupName")
tagGroupNameClass(groupName) {
@ -39,5 +40,5 @@ export default Component.extend({
.toLowerCase();
return groupName && `tag-group-${groupName}`;
}
},
});
}
}

View File

@ -1,6 +1,7 @@
import Component from "@ember/component";
import { classNames } from "@ember-decorators/component";
export default Component.extend({
classNames: ["tap-tile-grid"],
activeTile: null,
});
@classNames("tap-tile-grid")
export default class TapTileGrid extends Component {
activeTile = null;
}

View File

@ -1,30 +1,35 @@
import Component from "@ember/component";
import { reads } from "@ember/object/computed";
import {
attributeBindings,
classNameBindings,
classNames,
} from "@ember-decorators/component";
import { propertyEqual } from "discourse/lib/computed";
export default Component.extend({
init() {
this._super(...arguments);
this.set("elementId", `tap_tile_${this.tileId}`);
},
@classNames("tap-tile")
@classNameBindings("active")
@attributeBindings("role", "ariaPressed", "tabIndex")
export default class TapTile extends Component {
role = "button";
tabIndex = 0;
classNames: ["tap-tile"],
classNameBindings: ["active"],
attributeBindings: ["role", "ariaPressed", "tabIndex"],
role: "button",
tabIndex: 0,
ariaPressed: reads("active"),
@reads("active") ariaPressed;
@propertyEqual("activeTile", "tileId") active;
init() {
super.init(...arguments);
this.set("elementId", `tap_tile_${this.tileId}`);
}
click() {
this.onChange(this.tileId);
},
}
keyDown(e) {
if (e.key === "Enter") {
e.stopPropagation();
this.onChange(this.tileId);
}
},
active: propertyEqual("activeTile", "tileId"),
});
}
}

View File

@ -1,37 +1,33 @@
import { TextField } from "@ember/legacy-built-in-components";
import { computed } from "@ember/object";
import { cancel, next } from "@ember/runloop";
import { attributeBindings } from "@ember-decorators/component";
import discourseDebounce from "discourse-common/lib/debounce";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
const DEBOUNCE_MS = 500;
export default TextField.extend({
attributeBindings: [
"autocorrect",
"autocapitalize",
"autofocus",
"maxLength",
"dir",
"aria-label",
"aria-controls",
],
init() {
this._super(...arguments);
this._prevValue = null;
this._timer = null;
},
@attributeBindings(
"autocorrect",
"autocapitalize",
"autofocus",
"maxLength",
"dir",
"aria-label",
"aria-controls"
)
export default class DiscourseTextField extends TextField {
_prevValue = null;
_timer = null;
didReceiveAttrs() {
this._super(...arguments);
super.didReceiveAttrs(...arguments);
this._prevValue = this.value;
},
}
didUpdateAttrs() {
this._super(...arguments);
super.didUpdateAttrs(...arguments);
if (this._prevValue !== this.value) {
if (this.onChangeImmediate) {
@ -46,33 +42,32 @@ export default TextField.extend({
);
}
}
},
}
_debouncedChange() {
next(() => this.onChange(this.value));
},
}
get dir() {
if (this.siteSettings.support_mixed_text_direction) {
return "auto";
}
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
cancel(this._timer);
},
}
@discourseComputed("placeholderKey")
placeholder: {
get() {
if (this._placeholder) {
return this._placeholder;
}
return this.placeholderKey ? I18n.t(this.placeholderKey) : "";
},
set(value) {
return (this._placeholder = value);
},
},
});
@computed("placeholderKey", "_placeholder")
get placeholder() {
if (this._placeholder) {
return this._placeholder;
}
return this.placeholderKey ? I18n.t(this.placeholderKey) : "";
}
set placeholder(value) {
this.set("_placeholder", value);
}
}

View File

@ -2,6 +2,7 @@ import Component from "@ember/component";
import { action, computed } from "@ember/object";
import { htmlSafe } from "@ember/template";
import { isPresent } from "@ember/utils";
import { classNames } from "@ember-decorators/component";
function convertMinutes(num) {
return { hours: Math.floor(num / 60), minutes: num % 60 };
@ -32,17 +33,14 @@ function convertMinutesToDurationString(n) {
return output;
}
export default Component.extend({
classNames: ["d-time-input"],
hours: null,
minutes: null,
relativeDate: null,
@classNames("d-time-input")
export default class TimeInput extends Component {
hours = null;
minutes = null;
relativeDate = null;
didReceiveAttrs() {
this._super(...arguments);
super.didReceiveAttrs(...arguments);
if (isPresent(this.date)) {
this.setProperties({
@ -61,9 +59,10 @@ export default Component.extend({
minutes: null,
});
}
},
}
minimumTime: computed("relativeDate", "date", function () {
@computed("relativeDate", "date")
get minimumTime() {
if (this.relativeDate) {
if (this.date) {
if (!this.date.isSame(this.relativeDate, "day")) {
@ -75,9 +74,10 @@ export default Component.extend({
return this.relativeDate.hours() * 60 + this.relativeDate.minutes();
}
}
}),
}
timeOptions: computed("minimumTime", "hours", "minutes", function () {
@computed("minimumTime", "hours", "minutes")
get timeOptions() {
let options = [];
const start = this.minimumTime
@ -136,20 +136,21 @@ export default Component.extend({
title: name,
};
});
}),
}
time: computed("minimumTime", "hours", "minutes", function () {
@computed("minimumTime", "hours", "minutes")
get time() {
if (isPresent(this.hours) && isPresent(this.minutes)) {
return parseInt(this.hours, 10) * 60 + parseInt(this.minutes, 10);
}
}),
}
@action
onFocusIn(value, event) {
if (value && event.target) {
event.target.select();
}
},
}
@action
onChangeTime(time) {
@ -182,5 +183,5 @@ export default Component.extend({
});
}
}
},
});
}
}

View File

@ -1,6 +1,8 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { and, equal } from "@ember/object/computed";
import { tagName } from "@ember-decorators/component";
import { observes, on } from "@ember-decorators/object";
import {
defaultTimeShortcuts,
formatTime,
@ -9,10 +11,7 @@ import {
TIME_SHORTCUT_TYPES,
} from "discourse/lib/time-shortcut";
import { laterToday, now, parseCustomDatetime } from "discourse/lib/time-utils";
import discourseComputed, {
observes,
on,
} from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
const BINDINGS = {
@ -40,30 +39,34 @@ const BINDINGS = {
"n r": { handler: "selectShortcut", args: [TIME_SHORTCUT_TYPES.NONE] },
};
export default Component.extend({
tagName: "",
@tagName("")
export default class TimeShortcutPicker extends Component {
@equal("selectedShortcut", TIME_SHORTCUT_TYPES.CUSTOM) customDatetimeSelected;
@equal("selectedShortcut", TIME_SHORTCUT_TYPES.RELATIVE)
relativeTimeSelected;
@and("customDate", "customTime") customDatetimeFilled;
userTimezone: null,
userTimezone = null;
onTimeSelected: null,
onTimeSelected = null;
selectedShortcut: null,
selectedTime: null,
selectedDate: null,
selectedDatetime: null,
prefilledDatetime: null,
selectedDurationMins: null,
selectedShortcut = null;
selectedTime = null;
selectedDate = null;
selectedDatetime = null;
prefilledDatetime = null;
selectedDurationMins = null;
hiddenOptions: null,
customOptions: null,
hiddenOptions = null;
customOptions = null;
lastCustomDate: null,
lastCustomTime: null,
parsedLastCustomDatetime: null,
customDate: null,
customTime: null,
lastCustomDate = null;
lastCustomTime = null;
parsedLastCustomDatetime = null;
customDate = null;
customTime = null;
_itsatrap: null,
_itsatrap = null;
@on("init")
_setupPicker() {
@ -79,7 +82,7 @@ export default Component.extend({
}
this._bindKeyboardShortcuts();
},
}
@observes("prefilledDatetime")
prefilledDatetimeChanged() {
@ -92,13 +95,13 @@ export default Component.extend({
selectedShortcut: null,
});
}
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
this._itsatrap.unbind(Object.keys(BINDINGS));
},
}
parsePrefilledDatetime() {
let parsedDatetime = parseCustomDatetime(
@ -116,7 +119,7 @@ export default Component.extend({
customTime: parsedDatetime.format("HH:mm"),
selectedShortcut: TIME_SHORTCUT_TYPES.CUSTOM,
});
},
}
_loadLastUsedCustomDatetime() {
const lastTime = this.keyValueStore.lastCustomTime;
@ -135,7 +138,7 @@ export default Component.extend({
parsedLastCustomDatetime: parsed,
});
}
},
}
_bindKeyboardShortcuts() {
Object.keys(BINDINGS).forEach((shortcut) => {
@ -145,11 +148,7 @@ export default Component.extend({
return false;
});
});
},
customDatetimeSelected: equal("selectedShortcut", TIME_SHORTCUT_TYPES.CUSTOM),
relativeTimeSelected: equal("selectedShortcut", TIME_SHORTCUT_TYPES.RELATIVE),
customDatetimeFilled: and("customDate", "customTime"),
}
@observes("customDate", "customTime")
customDatetimeChanged() {
@ -157,7 +156,7 @@ export default Component.extend({
return;
}
this.selectShortcut(TIME_SHORTCUT_TYPES.CUSTOM);
},
}
@discourseComputed(
"timeShortcuts",
@ -203,7 +202,7 @@ export default Component.extend({
this._applyCustomLabels(options, customLabels);
options.forEach((o) => (o.timeFormatted = formatTime(o)));
return options;
},
}
@action
relativeTimeChanged(relativeTimeMins) {
@ -215,7 +214,7 @@ export default Component.extend({
});
this.onTimeSelected?.(TIME_SHORTCUT_TYPES.RELATIVE, dateTime);
},
}
@action
selectShortcut(type) {
@ -259,7 +258,7 @@ export default Component.extend({
if (this.onTimeSelected) {
this.onTimeSelected(type, dateTime);
}
},
}
_applyCustomLabels(options, customLabels) {
options.forEach((option) => {
@ -267,7 +266,7 @@ export default Component.extend({
option.label = customLabels[option.id];
}
});
},
}
_formatTime(options) {
options.forEach((option) => {
@ -275,9 +274,9 @@ export default Component.extend({
option.timeFormatted = option.time.format(I18n.t(option.timeFormatKey));
}
});
},
}
_defaultCustomDateTime() {
return moment.tz(this.userTimezone).add(1, "hour");
},
});
}
}

View File

@ -1,17 +1,17 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { classNames } from "@ember-decorators/component";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["top-title-buttons"],
@classNames("top-title-buttons")
export default class TopPeriodButtons extends Component {
@discourseComputed("period")
periods(period) {
return this.site.get("periods").filter((p) => p !== period);
},
}
@action
changePeriod(p) {
this.action(p);
},
});
}
}

View File

@ -1,3 +1,4 @@
import Component from "@ember/component";
// Injections don't occur without a class
export default Component.extend();
export default class TopicCategory extends Component {}

View File

@ -1,6 +1,8 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { scheduleOnce } from "@ember/runloop";
import { service } from "@ember/service";
import { classNameBindings } from "@ember-decorators/component";
import $ from "jquery";
import DiscourseURL from "discourse/lib/url";
import CleansUp from "discourse/mixins/cleans-up";
@ -30,41 +32,50 @@ function entranceDate(dt, showTime) {
);
}
export default Component.extend(CleansUp, {
router: service(),
session: service(),
historyStore: service(),
elementId: "topic-entrance",
classNameBindings: ["visible::hidden"],
topic: null,
visible: null,
_position: null,
_originalActiveElement: null,
_activeButton: null,
@classNameBindings("visible::hidden")
export default class TopicEntrance extends Component.extend(CleansUp) {
@service router;
@service session;
@service historyStore;
elementId = "topic-entrance";
topic = null;
visible = null;
_position = null;
_originalActiveElement = null;
_activeButton = null;
@discourseComputed("topic.created_at")
createdDate: (createdAt) => new Date(createdAt),
createdDate(createdAt) {
return new Date(createdAt);
}
@discourseComputed("topic.bumped_at")
bumpedDate: (bumpedAt) => new Date(bumpedAt),
bumpedDate(bumpedAt) {
return new Date(bumpedAt);
}
@discourseComputed("createdDate", "bumpedDate")
showTime(createdDate, bumpedDate) {
return (
bumpedDate.getTime() - createdDate.getTime() < 1000 * 60 * 60 * 24 * 2
);
},
}
@discourseComputed("createdDate", "showTime")
topDate: (createdDate, showTime) => entranceDate(createdDate, showTime),
topDate(createdDate, showTime) {
return entranceDate(createdDate, showTime);
}
@discourseComputed("bumpedDate", "showTime")
bottomDate: (bumpedDate, showTime) => entranceDate(bumpedDate, showTime),
bottomDate(bumpedDate, showTime) {
return entranceDate(bumpedDate, showTime);
}
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
this.appEvents.on("topic-entrance:show", this, "_show");
},
}
_setCSS() {
const pos = this._position;
@ -79,7 +90,7 @@ export default Component.extend(CleansUp, {
pos.left = windowWidth - width - 15;
}
$self.css(pos);
},
}
@bind
_escListener(e) {
@ -96,42 +107,42 @@ export default Component.extend(CleansUp, {
e.preventDefault();
}
}
},
}
_jumpTopButton() {
return this.element.querySelector(".jump-top");
},
}
_jumpBottomButton() {
return this.element.querySelector(".jump-bottom");
},
}
_setupEscListener() {
document.body.addEventListener("keydown", this._escListener);
},
}
_removeEscListener() {
document.body.removeEventListener("keydown", this._escListener);
},
}
_trapFocus() {
this._originalActiveElement = document.activeElement;
this._jumpTopButton().focus();
this._activeButton = "top";
},
}
_releaseFocus() {
if (this._originalActiveElement) {
this._originalActiveElement.focus();
this._originalActiveElement = null;
}
},
}
_applyDomChanges() {
this._setCSS();
this._setupEscListener();
this._trapFocus();
},
}
_show(data) {
this._position = data.position;
@ -152,34 +163,34 @@ export default Component.extend(CleansUp, {
}
this.cleanUp();
});
},
}
cleanUp() {
this.setProperties({ topic: null, visible: false });
$("html").off("mousedown.topic-entrance");
this._removeEscListener();
this._releaseFocus();
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
this.appEvents.off("topic-entrance:show", this, "_show");
},
}
_jumpTo(destination) {
this.historyStore.set("lastTopicIdViewed", this.topic.id);
this.cleanUp();
DiscourseURL.routeTo(destination);
},
}
actions: {
enterTop() {
this._jumpTo(this.get("topic.url"));
},
@action
enterTop() {
this._jumpTo(this.get("topic.url"));
}
enterBottom() {
this._jumpTo(this.get("topic.lastPostUrl"));
},
},
});
@action
enterBottom() {
this._jumpTo(this.get("topic.lastPostUrl"));
}
}

View File

@ -2,79 +2,81 @@ import Component from "@ember/component";
import { computed } from "@ember/object";
import { alias, or } from "@ember/object/computed";
import { getOwner } from "@ember/owner";
import { attributeBindings } from "@ember-decorators/component";
import { NotificationLevels } from "discourse/lib/notification-levels";
import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
import { getTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown";
import TopicBookmarkManager from "discourse/lib/topic-bookmark-manager";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
elementId: "topic-footer-buttons",
@attributeBindings("role")
export default class TopicFooterButtons extends Component {
elementId = "topic-footer-buttons";
role = "region";
attributeBindings: ["role"],
@getTopicFooterButtons() inlineButtons;
@getTopicFooterDropdowns() inlineDropdowns;
role: "region",
@alias("currentUser.can_send_private_messages") canSendPms;
@alias("topic.details.can_invite_to") canInviteTo;
@alias("currentUser.user_option.enable_defer") canDefer;
@or("topic.archived", "topic.closed", "topic.deleted") inviteDisabled;
@discourseComputed("canSendPms", "topic.isPrivateMessage")
canArchive(canSendPms, isPM) {
return canSendPms && isPM;
},
}
inlineButtons: getTopicFooterButtons(),
inlineDropdowns: getTopicFooterDropdowns(),
@computed("inlineButtons.[]", "inlineDropdowns.[]")
get inlineActionables() {
return this.inlineButtons
.filterBy("dropdown", false)
.filterBy("anonymousOnly", false)
.concat(this.inlineDropdowns)
.sortBy("priority")
.reverse();
}
inlineActionables: computed(
"inlineButtons.[]",
"inlineDropdowns.[]",
function () {
return this.inlineButtons
.filterBy("dropdown", false)
.filterBy("anonymousOnly", false)
.concat(this.inlineDropdowns)
.sortBy("priority")
.reverse();
}
),
topicBookmarkManager: computed("topic", function () {
@computed("topic")
get topicBookmarkManager() {
return new TopicBookmarkManager(getOwner(this), this.topic);
}),
}
// topic.assigned_to_user is for backward plugin support
@discourseComputed("inlineButtons.[]", "topic.assigned_to_user")
dropdownButtons(inlineButtons) {
return inlineButtons.filter((button) => button.dropdown);
},
}
@discourseComputed("topic.isPrivateMessage")
showNotificationsButton(isPM) {
return !isPM || this.canSendPms;
},
}
@discourseComputed("topic.details.notification_level")
showNotificationUserTip(notificationLevel) {
return notificationLevel >= NotificationLevels.TRACKING;
},
canSendPms: alias("currentUser.can_send_private_messages"),
canInviteTo: alias("topic.details.can_invite_to"),
canDefer: alias("currentUser.user_option.enable_defer"),
inviteDisabled: or("topic.archived", "topic.closed", "topic.deleted"),
}
@discourseComputed("topic.message_archived")
archiveIcon: (archived) => (archived ? "envelope" : "folder"),
archiveIcon(archived) {
return archived ? "envelope" : "folder";
}
@discourseComputed("topic.message_archived")
archiveTitle: (archived) =>
archived ? "topic.move_to_inbox.help" : "topic.archive_message.help",
archiveTitle(archived) {
return archived ? "topic.move_to_inbox.help" : "topic.archive_message.help";
}
@discourseComputed("topic.message_archived")
archiveLabel: (archived) =>
archived ? "topic.move_to_inbox.title" : "topic.archive_message.title",
archiveLabel(archived) {
return archived
? "topic.move_to_inbox.title"
: "topic.archive_message.title";
}
@discourseComputed("topic.isPrivateMessage")
showBookmarkLabel: (isPM) => !isPM,
});
showBookmarkLabel(isPM) {
return !isPM;
}
}

View File

@ -1,20 +1,22 @@
import Component from "@ember/component";
import { alias } from "@ember/object/computed";
import { on } from "@ember/object/evented";
import { getOwner } from "@ember/owner";
import { schedule } from "@ember/runloop";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import {
attributeBindings,
classNameBindings,
tagName,
} from "@ember-decorators/component";
import { observes, on } from "@ember-decorators/object";
import $ from "jquery";
import { topicTitleDecorators } from "discourse/components/topic-title";
import { wantsNewWindow } from "discourse/lib/intercept-click";
import DiscourseURL, { groupPath } from "discourse/lib/url";
import { RUNTIME_OPTIONS } from "discourse-common/lib/raw-handlebars-helpers";
import { findRawTemplate } from "discourse-common/lib/raw-templates";
import discourseComputed, {
bind,
observes,
} from "discourse-common/utils/decorators";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
export function showEntrance(e) {
@ -44,18 +46,19 @@ export function navigateToTopic(topic, href) {
return false;
}
export default Component.extend({
router: service(),
historyStore: service(),
tagName: "tr",
classNameBindings: [":topic-list-item", "unboundClassNames", "topic.visited"],
attributeBindings: ["data-topic-id", "role", "ariaLevel:aria-level"],
"data-topic-id": alias("topic.id"),
@tagName("tr")
@classNameBindings(":topic-list-item", "unboundClassNames", "topic.visited")
@attributeBindings("dataTopicId:data-topic-id", "role", "ariaLevel:aria-level")
export default class TopicListItem extends Component {
@service router;
@service historyStore;
@alias("topic.id") dataTopicId;
didReceiveAttrs() {
this._super(...arguments);
super.didReceiveAttrs(...arguments);
this.renderTopicListItem();
},
}
// Already-rendered topic is marked as highlighted
// Ideally this should be a modifier... but we can't do that
@ -65,7 +68,7 @@ export default Component.extend({
if (this.topic.highlight) {
this._highlightIfNeeded();
}
},
}
@observes("topic.pinned", "expandGloballyPinned", "expandAllPinned")
renderTopicListItem() {
@ -91,10 +94,10 @@ export default Component.extend({
}
});
}
},
}
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
if (this.includeUnreadIndicator) {
this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage);
@ -110,10 +113,10 @@ export default Component.extend({
);
}
});
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage);
@ -124,7 +127,7 @@ export default Component.extend({
title.removeEventListener("blur", this._onTitleBlur);
}
}
},
}
@bind
onMessage(data) {
@ -133,7 +136,7 @@ export default Component.extend({
).classList;
nodeClassList.toggle("read", !data.show_indicator);
},
}
@discourseComputed("topic.participant_groups")
participantGroups(groupNames) {
@ -144,29 +147,29 @@ export default Component.extend({
return groupNames.map((name) => {
return { name, url: groupPath(name) };
});
},
}
@discourseComputed("topic.id")
unreadIndicatorChannel(topicId) {
return `/private-messages/unread-indicator/${topicId}`;
},
}
@discourseComputed("topic.unread_by_group_member")
unreadClass(unreadByGroupMember) {
return unreadByGroupMember ? "" : "read";
},
}
@discourseComputed("topic.unread_by_group_member")
includeUnreadIndicator(unreadByGroupMember) {
return typeof unreadByGroupMember !== "undefined";
},
}
@discourseComputed
newDotText() {
return this.currentUser && this.currentUser.trust_level > 0
? ""
: I18n.t("filters.new.lower_title");
},
}
@discourseComputed("topic", "lastVisitedTopic")
unboundClassNames(topic, lastVisitedTopic) {
@ -177,7 +180,7 @@ export default Component.extend({
}
if (topic.get("tags")) {
topic.get("tags").forEach((tagName) => classes.push("tag-" + tagName));
topic.get("tags").forEach((tag) => classes.push("tag-" + tag));
}
if (topic.get("hasExcerpt")) {
@ -203,15 +206,15 @@ export default Component.extend({
}
return classes.join(" ");
},
}
hasLikes() {
return this.get("topic.like_count") > 0;
},
}
hasOpLikes() {
return this.get("topic.op_like_count") > 0;
},
}
@discourseComputed
expandPinned() {
@ -239,9 +242,11 @@ export default Component.extend({
}
return false;
},
}
showEntrance,
showEntrance() {
return showEntrance.call(this, ...arguments);
}
click(e) {
const result = this.showEntrance(e);
@ -316,18 +321,20 @@ export default Component.extend({
}
return this.unhandledRowClick(e, topic);
},
}
unhandledRowClick() {},
unhandledRowClick() {}
keyDown(e) {
if (e.key === "Enter" && e.target.classList.contains("post-activity")) {
e.preventDefault();
return this.navigateToTopic(this.topic, e.target.getAttribute("href"));
}
},
}
navigateToTopic,
navigateToTopic() {
return navigateToTopic.call(this, ...arguments);
}
highlight(opts = { isLastViewedTopic: false }) {
schedule("afterRender", () => {
@ -347,9 +354,10 @@ export default Component.extend({
this._titleElement()?.focus();
}
});
},
}
_highlightIfNeeded: on("didInsertElement", function () {
@on("didInsertElement")
_highlightIfNeeded() {
// highlight the last topic viewed
const lastViewedTopicId = this.historyStore.get("lastTopicIdViewed");
const isLastViewedTopic = lastViewedTopicId === this.topic.id;
@ -362,27 +370,27 @@ export default Component.extend({
this.set("topic.highlight", false);
this.highlight();
}
}),
}
@bind
_onTitleFocus() {
if (this.element && !this.isDestroying && !this.isDestroyed) {
this.element.classList.add("selected");
}
},
}
@bind
_onTitleBlur() {
if (this.element && !this.isDestroying && !this.isDestroyed) {
this.element.classList.remove("selected");
}
},
}
_shouldFocusLastVisited() {
return this.site.desktopView && this.focusLastVisitedTopic;
},
}
_titleElement() {
return this.element.querySelector(".main-link .title");
},
});
}
}

View File

@ -1,68 +1,75 @@
import Component from "@ember/component";
import { dependentKeyCompat } from "@ember/object/compat";
import { alias } from "@ember/object/computed";
import { on } from "@ember/object/evented";
import { service } from "@ember/service";
import {
classNameBindings,
classNames,
tagName,
} from "@ember-decorators/component";
import { observes, on } from "@ember-decorators/object";
import LoadMore from "discourse/mixins/load-more";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend(LoadMore, {
modal: service(),
router: service(),
siteSettings: service(),
@tagName("table")
@classNames("topic-list")
@classNameBindings("bulkSelectEnabled:sticky-header")
export default class TopicList extends Component.extend(LoadMore) {
@service modal;
@service router;
@service siteSettings;
tagName: "table",
classNames: ["topic-list"],
classNameBindings: ["bulkSelectEnabled:sticky-header"],
showTopicPostBadges: true,
listTitle: "topic.title",
lastCheckedElementId: null,
showTopicPostBadges = true;
listTitle = "topic.title";
lastCheckedElementId = null;
// Overwrite this to perform client side filtering of topics, if desired
@alias("topics") filteredTopics;
get canDoBulkActions() {
return (
this.currentUser?.canManageTopic && this.bulkSelectHelper?.selected.length
);
},
}
// Overwrite this to perform client side filtering of topics, if desired
filteredTopics: alias("topics"),
_init: on("init", function () {
@on("init")
_init() {
this.addObserver("hideCategory", this.rerender);
this.addObserver("order", this.rerender);
this.addObserver("ascending", this.rerender);
this.refreshLastVisited();
}),
}
get selected() {
return this.bulkSelectHelper?.selected;
},
}
@dependentKeyCompat // for the classNameBindings
// for the classNameBindings
@dependentKeyCompat
get bulkSelectEnabled() {
return this.bulkSelectHelper?.bulkSelectEnabled;
},
}
get toggleInTitle() {
return (
!this.bulkSelectHelper?.bulkSelectEnabled && this.get("canBulkSelect")
);
},
}
@discourseComputed
sortable() {
return !!this.changeSort;
},
}
@discourseComputed("order")
showLikes(order) {
return order === "likes";
},
}
@discourseComputed("order")
showOpLikes(order) {
return order === "op_likes";
},
}
@observes("topics.[]")
topicsAdded() {
@ -70,22 +77,22 @@ export default Component.extend(LoadMore, {
if (!this.lastVisitedTopic) {
this.refreshLastVisited();
}
},
}
@observes("topics", "order", "ascending", "category", "top", "hot")
lastVisitedTopicChanged() {
this.refreshLastVisited();
},
}
scrolled() {
this._super(...arguments);
super.scrolled(...arguments);
let onScroll = this.onScroll;
if (!onScroll) {
return;
}
onScroll.call(this);
},
}
_updateLastVisitedTopic(topics, order, ascending, top, hot) {
this.set("lastVisitedTopic", null);
@ -145,7 +152,7 @@ export default Component.extend(LoadMore, {
}
this.set("lastVisitedTopic", lastVisitedTopic);
},
}
refreshLastVisited() {
this._updateLastVisitedTopic(
@ -155,7 +162,7 @@ export default Component.extend(LoadMore, {
this.top,
this.hot
);
},
}
click(e) {
const onClick = (sel, callback) => {
@ -200,7 +207,7 @@ export default Component.extend(LoadMore, {
}
this.rerender();
});
},
}
keyDown(e) {
if (e.key === "Enter" || e.key === " ") {
@ -217,5 +224,5 @@ export default Component.extend(LoadMore, {
this.rerender();
});
}
},
});
}
}

View File

@ -1,14 +1,15 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { tagName } from "@ember-decorators/component";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "",
popupId: null,
hidden: false,
@tagName("")
export default class TopicNavigationPopup extends Component {
popupId = null;
hidden = false;
init() {
this._super(...arguments);
super.init(...arguments);
if (this.popupKey) {
const value = this.keyValueStore.getItem(this.popupKey);
@ -18,14 +19,14 @@ export default Component.extend({
this.keyValueStore.removeItem(this.popupKey);
}
}
},
}
@discourseComputed("popupId")
popupKey(popupId) {
if (popupId) {
return `dismiss_topic_nav_popup_${popupId}`;
}
},
}
@action
close() {
@ -39,5 +40,5 @@ export default Component.extend({
this.keyValueStore.setItem(this.popupKey, true);
}
}
},
});
}
}

View File

@ -2,43 +2,40 @@ import Component from "@ember/component";
import EmberObject from "@ember/object";
import { next } from "@ember/runloop";
import { service } from "@ember/service";
import { classNameBindings } from "@ember-decorators/component";
import { observes } from "@ember-decorators/object";
import $ from "jquery";
import { headerOffset } from "discourse/lib/offset-calculator";
import SwipeEvents from "discourse/lib/swipe-events";
import discourseDebounce from "discourse-common/lib/debounce";
import discourseLater from "discourse-common/lib/later";
import { bind, observes } from "discourse-common/utils/decorators";
import { bind } from "discourse-common/utils/decorators";
import JumpToPost from "./modal/jump-to-post";
const MIN_WIDTH_TIMELINE = 925;
const MIN_HEIGHT_TIMELINE = 325;
export default Component.extend({
modal: service(),
@classNameBindings(
"info.topicProgressExpanded:topic-progress-expanded",
"info.renderTimeline:with-timeline:with-topic-progress"
)
export default class TopicNavigation extends Component {
@service modal;
classNameBindings: [
"info.topicProgressExpanded:topic-progress-expanded",
"info.renderTimeline:with-timeline:with-topic-progress",
],
composerOpen: null,
info: null,
canRender: true,
_lastTopicId: null,
_swipeEvents: null,
init() {
this._super(...arguments);
this.set("info", EmberObject.create());
},
composerOpen = null;
info = EmberObject.create();
canRender = true;
_lastTopicId = null;
_swipeEvents = null;
didUpdateAttrs() {
this._super(...arguments);
super.didUpdateAttrs(...arguments);
if (this._lastTopicId !== this.topic.id) {
this._lastTopicId = this.topic.id;
this.set("canRender", false);
next(() => this.set("canRender", true));
}
},
}
_performCheckSize() {
if (!this.element || this.isDestroying || this.isDestroyed) {
@ -60,17 +57,17 @@ export default Component.extend({
this.mediaQuery.matches && verticalSpace > MIN_HEIGHT_TIMELINE
);
}
},
}
@bind
_checkSize() {
discourseDebounce(this, this._performCheckSize, 200, true);
},
}
// we need to store this so topic progress has something to init with
_topicScrolled(event) {
this.set("info.prevEvent", event);
},
}
@observes("info.topicProgressExpanded")
_expanded() {
@ -95,17 +92,17 @@ export default Component.extend({
$(window).off("click.hide-fullscreen");
}
this._checkSize();
},
}
composerOpened() {
this.set("composerOpen", true);
this._checkSize();
},
}
composerClosed() {
this.set("composerOpen", false);
this._checkSize();
},
}
_collapseFullscreen(delay = 500) {
if (this.get("info.topicProgressExpanded")) {
@ -119,7 +116,7 @@ export default Component.extend({
this._checkSize();
}, delay);
}
},
}
keyboardTrigger(e) {
if (e.type === "jump") {
@ -131,7 +128,7 @@ export default Component.extend({
},
});
}
},
}
@bind
onSwipeStart(event) {
@ -153,7 +150,7 @@ export default Component.extend({
} else if (e.direction === "up" || e.direction === "down") {
this.movingElement = document.querySelector(".timeline-container");
}
},
}
@bind
onSwipeCancel() {
@ -164,7 +161,7 @@ export default Component.extend({
fill: "forwards",
easing: "ease-out",
});
},
}
@bind
onSwipeEnd(event) {
@ -195,7 +192,7 @@ export default Component.extend({
easing: "ease-out",
});
}
},
}
@bind
onSwipe(event) {
@ -207,10 +204,10 @@ export default Component.extend({
[{ transform: `translate3d(0, ${this.pxClosed}px, 0)` }],
{ fill: "forwards" }
);
},
}
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
this._lastTopicId = this.topic.id;
@ -237,10 +234,10 @@ export default Component.extend({
this.element.addEventListener("swipecancel", this.onSwipeCancel);
this.element.addEventListener("swipe", this.onSwipe);
}
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
this.appEvents
.off("topic:current-post-scrolled", this, this._topicScrolled)
@ -263,5 +260,5 @@ export default Component.extend({
this.element.removeEventListener("swipe", this.onSwipe);
this._swipeEvents.removeTouchListeners();
}
},
});
}
}

View File

@ -2,20 +2,23 @@ import Component from "@ember/component";
import { action } from "@ember/object";
import { alias } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import { classNameBindings } from "@ember-decorators/component";
import { isTesting } from "discourse-common/config/environment";
import discourseLater from "discourse-common/lib/later";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
const CSS_TRANSITION_DELAY = isTesting() ? 0 : 500;
export default Component.extend({
elementId: "topic-progress-wrapper",
classNameBindings: ["docked", "withTransitions"],
docked: false,
withTransitions: null,
progressPosition: null,
postStream: alias("topic.postStream"),
_streamPercentage: null,
@classNameBindings("docked", "withTransitions")
export default class TopicProgress extends Component {
elementId = "topic-progress-wrapper";
docked = false;
withTransitions = null;
progressPosition = null;
@alias("topic.postStream") postStream;
_streamPercentage = null;
@discourseComputed(
"postStream.loaded",
@ -25,14 +28,14 @@ export default Component.extend({
hideProgress(loaded, currentPost, filteredPostsCount) {
const hideOnShortStream = this.site.desktopView && filteredPostsCount < 2;
return !loaded || !currentPost || hideOnShortStream;
},
}
@discourseComputed("postStream.filteredPostsCount")
hugeNumberOfPosts(filteredPostsCount) {
return (
filteredPostsCount >= this.siteSettings.short_progress_text_threshold
);
},
}
@discourseComputed("progressPosition", "topic.last_read_post_id")
showBackButton(position, lastReadId) {
@ -43,7 +46,7 @@ export default Component.extend({
const stream = this.get("postStream.stream");
const readPos = stream.indexOf(lastReadId) || 0;
return readPos < stream.length - 1 && readPos > position;
},
}
_topicScrolled(event) {
if (this.docked) {
@ -57,15 +60,15 @@ export default Component.extend({
_streamPercentage: (event.percent * 100).toFixed(2),
});
}
},
}
@discourseComputed("_streamPercentage")
progressStyle(_streamPercentage) {
return `--progress-bg-width: ${_streamPercentage || 0}%`;
},
}
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
this.appEvents
.on("composer:resized", this, this._composerEvent)
@ -79,15 +82,15 @@ export default Component.extend({
// start CSS transitions a tiny bit later
// to avoid jumpiness on initial topic load
discourseLater(this._addCssTransitions, CSS_TRANSITION_DELAY);
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
this._topicBottomObserver?.disconnect();
this.appEvents
.off("composer:resized", this, this._composerEvent)
.off("topic:current-post-scrolled", this, this._topicScrolled);
},
}
@bind
_addCssTransitions() {
@ -95,7 +98,7 @@ export default Component.extend({
return;
}
this.set("withTransitions", true);
},
}
_startObserver() {
if ("IntersectionObserver" in window) {
@ -104,7 +107,7 @@ export default Component.extend({
document.querySelector("#topic-bottom")
);
}
},
}
_setupObserver() {
// minimum 50px here ensures element is not docked when
@ -117,7 +120,7 @@ export default Component.extend({
threshold: 1,
rootMargin: `0px 0px -${bottomIntersectionMargin}px 0px`,
});
},
}
_composerEvent() {
// reinitializing needed to account for composer height
@ -127,7 +130,7 @@ export default Component.extend({
this._topicBottomObserver?.disconnect();
this._startObserver();
}
},
}
@bind
_intersectionHandler(entries) {
@ -167,16 +170,16 @@ export default Component.extend({
}
}
}
},
}
click(e) {
if (e.target.closest("#topic-progress")) {
this.toggleProperty("expanded");
}
},
}
@action
goBack() {
this.jumpToPost(this.get("topic.last_read_post_number"));
},
});
}
}

View File

@ -1,55 +1,58 @@
import Component from "@ember/component";
import { cancel, next } from "@ember/runloop";
import { htmlSafe } from "@ember/template";
import { classNames } from "@ember-decorators/component";
import { on } from "@ember-decorators/object";
import { DELETE_REPLIES_TYPE } from "discourse/components/modal/edit-topic-timer";
import Category from "discourse/models/category";
import { isTesting } from "discourse-common/config/environment";
import { iconHTML } from "discourse-common/lib/icon-library";
import discourseLater from "discourse-common/lib/later";
import discourseComputed, { on } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
export default Component.extend({
classNames: ["topic-timer-info"],
_delayedRerender: null,
clockIcon: htmlSafe(`${iconHTML("far-clock")}`),
trashLabel: I18n.t("post.controls.remove_timer"),
title: null,
notice: null,
showTopicTimer: null,
showTopicTimerModal: null,
removeTopicTimer: null,
@classNames("topic-timer-info")
export default class TopicTimerInfo extends Component {
clockIcon = htmlSafe(`${iconHTML("far-clock")}`);
trashLabel = I18n.t("post.controls.remove_timer");
title = null;
notice = null;
showTopicTimer = null;
showTopicTimerModal = null;
removeTopicTimer = null;
_delayedRerender = null;
@on("didReceiveAttrs")
setupRenderer() {
this.renderTopicTimer();
},
}
@on("willDestroyElement")
cancelDelayedRenderer() {
if (this._delayedRerender) {
cancel(this._delayedRerender);
}
},
}
@discourseComputed
canModifyTimer() {
return this.currentUser && this.currentUser.get("canManageTopic");
},
}
@discourseComputed("canModifyTimer", "removeTopicTimer")
showTrashCan(canModifyTimer, removeTopicTimer) {
return canModifyTimer && removeTopicTimer;
},
}
@discourseComputed("canModifyTimer", "showTopicTimerModal")
showEdit(canModifyTimer, showTopicTimerModal) {
return canModifyTimer && showTopicTimerModal;
},
}
additionalOpts() {
return {};
},
}
renderTopicTimer() {
const isDeleteRepliesType = this.statusType === DELETE_REPLIES_TYPE;
@ -130,7 +133,7 @@ export default Component.extend({
} else {
this.set("showTopicTimer", null);
}
},
}
rerenderDelay(minutesLeft) {
if (minutesLeft > 2160) {
@ -144,7 +147,7 @@ export default Component.extend({
}
return 1000;
},
}
_noticeKey() {
let statusType = this.statusType;
@ -156,5 +159,5 @@ export default Component.extend({
}
return `topic.status_update_notice.auto_${statusType}`;
},
});
}
}