mirror of
https://github.com/discourse/discourse.git
synced 2025-04-17 00:39:31 +08:00
DEV: Drop legacy topic-list and raw-handlebars compilation system (#32081)
This commit is contained in:
parent
13cb472ec8
commit
f0057c7353
@ -1,9 +1,6 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { renderIcon } from "discourse/lib/icon-library";
|
||||
|
||||
registerRawHelper("check-icon", checkIcon);
|
||||
|
||||
export default function checkIcon(value) {
|
||||
let icon = value ? "check" : "xmark";
|
||||
return htmlSafe(renderIcon("string", icon));
|
||||
|
@ -1 +0,0 @@
|
||||
engine-strict = true
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "discourse-hbr",
|
||||
"version": "1.0.0",
|
||||
"description": "Support for Discourse's raw Handlebars templates (hbr)",
|
||||
"author": "Discourse",
|
||||
"license": "GPL-2.0-only",
|
||||
"dependencies": {
|
||||
"handlebars": "^4.7.8",
|
||||
"broccoli-filter": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18",
|
||||
"npm": "please-use-pnpm",
|
||||
"yarn": "please-use-pnpm",
|
||||
"pnpm": "^9"
|
||||
},
|
||||
"exports": {
|
||||
"./raw-handlebars-compiler": "./raw-handlebars-compiler.js"
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const Filter = require("broccoli-filter");
|
||||
const Handlebars = require("handlebars");
|
||||
|
||||
const RawHandlebars = Handlebars.create();
|
||||
|
||||
function buildPath(blk, args) {
|
||||
let result = {
|
||||
type: "PathExpression",
|
||||
data: false,
|
||||
depth: blk.path.depth,
|
||||
loc: blk.path.loc,
|
||||
};
|
||||
|
||||
// Server side precompile doesn't have jquery.extend
|
||||
Object.keys(args).forEach(function (a) {
|
||||
result[a] = args[a];
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function replaceGet(ast) {
|
||||
let visitor = new Handlebars.Visitor();
|
||||
visitor.mutating = true;
|
||||
|
||||
visitor.MustacheStatement = function (mustache) {
|
||||
if (!(mustache.params.length || mustache.hash)) {
|
||||
mustache.params[0] = mustache.path;
|
||||
mustache.path = buildPath(mustache, {
|
||||
parts: ["get"],
|
||||
original: "get",
|
||||
strict: true,
|
||||
falsy: true,
|
||||
});
|
||||
}
|
||||
return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache);
|
||||
};
|
||||
|
||||
// rewrite `each x as |y|` as each y in x`
|
||||
// This allows us to use the same syntax in all templates
|
||||
visitor.BlockStatement = function (block) {
|
||||
if (block.path.original === "each" && block.params.length === 1) {
|
||||
let paramName = block.program.blockParams[0];
|
||||
block.params = [
|
||||
buildPath(block, { original: paramName }),
|
||||
{ type: "CommentStatement", value: "in" },
|
||||
block.params[0],
|
||||
];
|
||||
delete block.program.blockParams;
|
||||
}
|
||||
|
||||
return Handlebars.Visitor.prototype.BlockStatement.call(this, block);
|
||||
};
|
||||
|
||||
visitor.accept(ast);
|
||||
}
|
||||
|
||||
RawHandlebars.Compiler = function () {};
|
||||
RawHandlebars.Compiler.prototype = Object.create(Handlebars.Compiler.prototype);
|
||||
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
|
||||
|
||||
RawHandlebars.JavaScriptCompiler = function () {};
|
||||
|
||||
RawHandlebars.JavaScriptCompiler.prototype = Object.create(
|
||||
Handlebars.JavaScriptCompiler.prototype
|
||||
);
|
||||
RawHandlebars.JavaScriptCompiler.prototype.compiler =
|
||||
RawHandlebars.JavaScriptCompiler;
|
||||
RawHandlebars.JavaScriptCompiler.prototype.namespace = "RawHandlebars";
|
||||
|
||||
RawHandlebars.precompile = function (value, asObject) {
|
||||
let ast = Handlebars.parse(value);
|
||||
replaceGet(ast);
|
||||
|
||||
let options = {
|
||||
knownHelpers: {
|
||||
get: true,
|
||||
},
|
||||
data: true,
|
||||
stringParams: true,
|
||||
};
|
||||
|
||||
asObject = asObject === undefined ? true : asObject;
|
||||
|
||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
||||
return new RawHandlebars.JavaScriptCompiler().compile(
|
||||
environment,
|
||||
options,
|
||||
undefined,
|
||||
asObject
|
||||
);
|
||||
};
|
||||
|
||||
RawHandlebars.compile = function (string) {
|
||||
let ast = Handlebars.parse(string);
|
||||
replaceGet(ast);
|
||||
|
||||
// this forces us to rewrite helpers
|
||||
let options = { data: true, stringParams: true };
|
||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
||||
let templateSpec = new RawHandlebars.JavaScriptCompiler().compile(
|
||||
environment,
|
||||
options,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
let t = RawHandlebars.template(templateSpec);
|
||||
t.isMethod = false;
|
||||
|
||||
return t;
|
||||
};
|
||||
function TemplateCompiler(inputTree, options) {
|
||||
if (!(this instanceof TemplateCompiler)) {
|
||||
return new TemplateCompiler(inputTree, options);
|
||||
}
|
||||
|
||||
Filter.call(this, inputTree, options); // this._super()
|
||||
|
||||
this.options = options || {};
|
||||
this.inputTree = inputTree;
|
||||
}
|
||||
|
||||
TemplateCompiler.prototype = Object.create(Filter.prototype);
|
||||
TemplateCompiler.prototype.constructor = TemplateCompiler;
|
||||
TemplateCompiler.prototype.extensions = ["hbr"];
|
||||
TemplateCompiler.prototype.targetExtension = "js";
|
||||
|
||||
TemplateCompiler.prototype.registerPlugins = function registerPlugins() {};
|
||||
|
||||
TemplateCompiler.prototype.initializeFeatures =
|
||||
function initializeFeatures() {};
|
||||
|
||||
TemplateCompiler.prototype.processString = function (string, relativePath) {
|
||||
let filename;
|
||||
|
||||
const pluginName = relativePath.match(/^discourse\/plugins\/([^\/]+)\//)?.[1];
|
||||
|
||||
if (pluginName) {
|
||||
filename = relativePath
|
||||
.replace(`discourse/plugins/${pluginName}/`, "")
|
||||
.replace(/^(discourse\/)?raw-templates\//, "javascripts/");
|
||||
} else {
|
||||
filename = relativePath.replace(/^raw-templates\//, "");
|
||||
}
|
||||
|
||||
filename = filename.replace(/\.hbr$/, "");
|
||||
const hasModernReplacement = string.includes(
|
||||
"{{!-- has-modern-replacement --}}"
|
||||
);
|
||||
|
||||
return `
|
||||
import { template as compiler } from "discourse/lib/raw-handlebars";
|
||||
import { addRawTemplate } from "discourse/lib/raw-templates";
|
||||
|
||||
let template = compiler(${this.precompile(string, false)});
|
||||
|
||||
addRawTemplate("${filename}", template, {
|
||||
core: ${!pluginName},
|
||||
pluginName: ${JSON.stringify(pluginName)},
|
||||
hasModernReplacement: ${hasModernReplacement},
|
||||
});
|
||||
|
||||
export default template;
|
||||
`;
|
||||
};
|
||||
|
||||
TemplateCompiler.prototype.precompile = function (value, asObject) {
|
||||
return RawHandlebars.precompile(value, asObject);
|
||||
};
|
||||
|
||||
module.exports = TemplateCompiler;
|
@ -6,7 +6,6 @@ const Funnel = require("broccoli-funnel");
|
||||
const mergeTrees = require("broccoli-merge-trees");
|
||||
const fs = require("fs");
|
||||
const concat = require("broccoli-concat");
|
||||
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
|
||||
const DiscoursePluginColocatedTemplateProcessor = require("./colocated-template-compiler");
|
||||
const EmberApp = require("ember-cli/lib/broccoli/ember-app");
|
||||
|
||||
@ -15,19 +14,6 @@ function fixLegacyExtensions(tree) {
|
||||
getDestinationPath: function (relativePath) {
|
||||
if (relativePath.endsWith(".es6")) {
|
||||
return relativePath.slice(0, -4);
|
||||
} else if (relativePath.endsWith(".raw.hbs")) {
|
||||
relativePath = relativePath.replace(".raw.hbs", ".hbr");
|
||||
}
|
||||
|
||||
if (relativePath.endsWith(".hbr")) {
|
||||
if (relativePath.includes("/templates/")) {
|
||||
relativePath = relativePath.replace("/templates/", "/raw-templates/");
|
||||
} else if (relativePath.includes("/connectors/")) {
|
||||
relativePath = relativePath.replace(
|
||||
"/connectors/",
|
||||
"/raw-templates/connectors/"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return relativePath;
|
||||
@ -201,8 +187,6 @@ module.exports = {
|
||||
tree = unColocateConnectors(tree);
|
||||
tree = namespaceModules(tree, pluginName);
|
||||
|
||||
tree = RawHandlebarsCompiler(tree);
|
||||
|
||||
const colocateBase = `discourse/plugins/${pluginName}`;
|
||||
tree = new DiscoursePluginColocatedTemplateProcessor(
|
||||
tree,
|
||||
|
@ -10,7 +10,6 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"deprecation-silencer": "workspace:1.0.0",
|
||||
"discourse-hbr": "workspace:1.0.0",
|
||||
"discourse-widget-hbs": "workspace:1.0.0",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
|
@ -17,8 +17,7 @@
|
||||
"@babel/core": "^7.26.10",
|
||||
"ember-auto-import": "^2.10.0",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"handlebars": "^4.7.8"
|
||||
"ember-cli-htmlbars": "^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.2.0",
|
||||
|
@ -4,7 +4,6 @@ import { service } from "@ember/service";
|
||||
import { observes } from "@ember-decorators/object";
|
||||
import $ from "jquery";
|
||||
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
|
||||
import TopicList from "discourse/components/topic-list";
|
||||
import List from "discourse/components/topic-list/list";
|
||||
import discourseComputed, { bind } from "discourse/lib/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
@ -122,35 +121,19 @@ export default class BasicTopicList extends Component {
|
||||
<template>
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
||||
{{#if this.topics}}
|
||||
{{#if this.site.useGlimmerTopicList}}
|
||||
<List
|
||||
@showPosters={{this.showPosters}}
|
||||
@hideCategory={{this.hideCategory}}
|
||||
@topics={{this.topics}}
|
||||
@expandExcerpts={{this.expandExcerpts}}
|
||||
@bulkSelectHelper={{this.bulkSelectHelper}}
|
||||
@canBulkSelect={{this.canBulkSelect}}
|
||||
@tagsForUser={{this.tagsForUser}}
|
||||
@changeSort={{this.changeSort}}
|
||||
@order={{this.order}}
|
||||
@ascending={{this.ascending}}
|
||||
@focusLastVisitedTopic={{this.focusLastVisitedTopic}}
|
||||
/>
|
||||
{{else}}
|
||||
<TopicList
|
||||
@showPosters={{this.showPosters}}
|
||||
@hideCategory={{this.hideCategory}}
|
||||
@topics={{this.topics}}
|
||||
@expandExcerpts={{this.expandExcerpts}}
|
||||
@bulkSelectHelper={{this.bulkSelectHelper}}
|
||||
@canBulkSelect={{this.canBulkSelect}}
|
||||
@tagsForUser={{this.tagsForUser}}
|
||||
@changeSort={{this.changeSort}}
|
||||
@order={{this.order}}
|
||||
@ascending={{this.ascending}}
|
||||
@focusLastVisitedTopic={{this.focusLastVisitedTopic}}
|
||||
/>
|
||||
{{/if}}
|
||||
<List
|
||||
@showPosters={{this.showPosters}}
|
||||
@hideCategory={{this.hideCategory}}
|
||||
@topics={{this.topics}}
|
||||
@expandExcerpts={{this.expandExcerpts}}
|
||||
@bulkSelectHelper={{this.bulkSelectHelper}}
|
||||
@canBulkSelect={{this.canBulkSelect}}
|
||||
@tagsForUser={{this.tagsForUser}}
|
||||
@changeSort={{this.changeSort}}
|
||||
@order={{this.order}}
|
||||
@ascending={{this.ascending}}
|
||||
@focusLastVisitedTopic={{this.focusLastVisitedTopic}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#unless this.loadingMore}}
|
||||
<div class="alert alert-info">
|
||||
|
@ -19,11 +19,7 @@ export default class CategoriesTopicList extends Component {
|
||||
|
||||
{{#if this.topics}}
|
||||
{{#each this.topics as |t|}}
|
||||
{{#if this.site.useGlimmerTopicList}}
|
||||
<LatestTopicListItem @topic={{t}} />
|
||||
{{else}}
|
||||
<LatestTopicListItem @topic={{t}} />
|
||||
{{/if}}
|
||||
<LatestTopicListItem @topic={{t}} />
|
||||
{{/each}}
|
||||
|
||||
<div class="more-topics">
|
||||
|
@ -14,7 +14,6 @@ import NewListHeaderControlsWrapper from "discourse/components/new-list-header-c
|
||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||
import TopPeriodButtons from "discourse/components/top-period-buttons";
|
||||
import TopicDismissButtons from "discourse/components/topic-dismiss-buttons";
|
||||
import TopicList from "discourse/components/topic-list";
|
||||
import List from "discourse/components/topic-list/list";
|
||||
import basePath from "discourse/helpers/base-path";
|
||||
import hideApplicationFooter from "discourse/helpers/hide-application-footer";
|
||||
@ -209,27 +208,15 @@ export default class DiscoveryTopics extends Component {
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.sharedDrafts}}
|
||||
{{#if this.site.useGlimmerTopicList}}
|
||||
<List
|
||||
@listTitle="shared_drafts.title"
|
||||
@top={{this.top}}
|
||||
@hideCategory="true"
|
||||
@category={{@category}}
|
||||
@topics={{@model.sharedDrafts}}
|
||||
@discoveryList={{true}}
|
||||
class="shared-drafts"
|
||||
/>
|
||||
{{else}}
|
||||
<TopicList
|
||||
@listTitle="shared_drafts.title"
|
||||
@top={{this.top}}
|
||||
@hideCategory="true"
|
||||
@category={{@category}}
|
||||
@topics={{@model.sharedDrafts}}
|
||||
@discoveryList={{true}}
|
||||
class="shared-drafts"
|
||||
/>
|
||||
{{/if}}
|
||||
<List
|
||||
@listTitle="shared_drafts.title"
|
||||
@top={{this.top}}
|
||||
@hideCategory="true"
|
||||
@category={{@category}}
|
||||
@topics={{@model.sharedDrafts}}
|
||||
@discoveryList={{true}}
|
||||
class="shared-drafts"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<DiscoveryTopicsList
|
||||
@ -290,57 +277,30 @@ export default class DiscoveryTopics extends Component {
|
||||
</span>
|
||||
|
||||
{{#if this.hasTopics}}
|
||||
{{#if this.site.useGlimmerTopicList}}
|
||||
<List
|
||||
@highlightLastVisited={{true}}
|
||||
@top={{this.top}}
|
||||
@hot={{this.hot}}
|
||||
@showTopicPostBadges={{this.showTopicPostBadges}}
|
||||
@showPosters={{true}}
|
||||
@canBulkSelect={{@canBulkSelect}}
|
||||
@bulkSelectHelper={{@bulkSelectHelper}}
|
||||
@changeSort={{@changeSort}}
|
||||
@hideCategory={{@model.hideCategory}}
|
||||
@order={{this.order}}
|
||||
@ascending={{this.ascending}}
|
||||
@expandGloballyPinned={{this.expandGloballyPinned}}
|
||||
@expandAllPinned={{this.expandAllPinned}}
|
||||
@category={{@category}}
|
||||
@topics={{@model.topics}}
|
||||
@discoveryList={{true}}
|
||||
@focusLastVisitedTopic={{true}}
|
||||
@showTopicsAndRepliesToggle={{this.showTopicsAndRepliesToggle}}
|
||||
@newListSubset={{@model.params.subset}}
|
||||
@changeNewListSubset={{@changeNewListSubset}}
|
||||
@newRepliesCount={{this.newRepliesCount}}
|
||||
@newTopicsCount={{this.newTopicsCount}}
|
||||
/>
|
||||
{{else}}
|
||||
<TopicList
|
||||
@highlightLastVisited={{true}}
|
||||
@top={{this.top}}
|
||||
@hot={{this.hot}}
|
||||
@showTopicPostBadges={{this.showTopicPostBadges}}
|
||||
@showPosters={{true}}
|
||||
@canBulkSelect={{@canBulkSelect}}
|
||||
@bulkSelectHelper={{@bulkSelectHelper}}
|
||||
@changeSort={{@changeSort}}
|
||||
@hideCategory={{@model.hideCategory}}
|
||||
@order={{this.order}}
|
||||
@ascending={{this.ascending}}
|
||||
@expandGloballyPinned={{this.expandGloballyPinned}}
|
||||
@expandAllPinned={{this.expandAllPinned}}
|
||||
@category={{@category}}
|
||||
@topics={{@model.topics}}
|
||||
@discoveryList={{true}}
|
||||
@focusLastVisitedTopic={{true}}
|
||||
@showTopicsAndRepliesToggle={{this.showTopicsAndRepliesToggle}}
|
||||
@newListSubset={{@model.params.subset}}
|
||||
@changeNewListSubset={{@changeNewListSubset}}
|
||||
@newRepliesCount={{this.newRepliesCount}}
|
||||
@newTopicsCount={{this.newTopicsCount}}
|
||||
/>
|
||||
{{/if}}
|
||||
<List
|
||||
@highlightLastVisited={{true}}
|
||||
@top={{this.top}}
|
||||
@hot={{this.hot}}
|
||||
@showTopicPostBadges={{this.showTopicPostBadges}}
|
||||
@showPosters={{true}}
|
||||
@canBulkSelect={{@canBulkSelect}}
|
||||
@bulkSelectHelper={{@bulkSelectHelper}}
|
||||
@changeSort={{@changeSort}}
|
||||
@hideCategory={{@model.hideCategory}}
|
||||
@order={{this.order}}
|
||||
@ascending={{this.ascending}}
|
||||
@expandGloballyPinned={{this.expandGloballyPinned}}
|
||||
@expandAllPinned={{this.expandAllPinned}}
|
||||
@category={{@category}}
|
||||
@topics={{@model.topics}}
|
||||
@discoveryList={{true}}
|
||||
@focusLastVisitedTopic={{true}}
|
||||
@showTopicsAndRepliesToggle={{this.showTopicsAndRepliesToggle}}
|
||||
@newListSubset={{@model.params.subset}}
|
||||
@changeNewListSubset={{@changeNewListSubset}}
|
||||
@newRepliesCount={{this.newRepliesCount}}
|
||||
@newTopicsCount={{this.newTopicsCount}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<span class="after-topic-list-plugin-outlet-wrapper">
|
||||
|
@ -1,127 +0,0 @@
|
||||
import Component from "@ember/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import {
|
||||
attributeBindings,
|
||||
classNameBindings,
|
||||
} from "@ember-decorators/component";
|
||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||
import ItemRepliesCell from "discourse/components/topic-list/item/replies-cell";
|
||||
import {
|
||||
navigateToTopic,
|
||||
showEntrance,
|
||||
} from "discourse/components/topic-list-item";
|
||||
import TopicPostBadges from "discourse/components/topic-post-badges";
|
||||
import TopicStatus from "discourse/components/topic-status";
|
||||
import UserAvatarFlair from "discourse/components/user-avatar-flair";
|
||||
import UserLink from "discourse/components/user-link";
|
||||
import avatar from "discourse/helpers/avatar";
|
||||
import categoryLink from "discourse/helpers/category-link";
|
||||
import discourseTags from "discourse/helpers/discourse-tags";
|
||||
import formatDate from "discourse/helpers/format-date";
|
||||
import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
||||
import topicLink from "discourse/helpers/topic-link";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
@attributeBindings("topic.id:data-topic-id")
|
||||
@classNameBindings(":latest-topic-list-item", "unboundClassNames")
|
||||
export default class LatestTopicListItem extends Component {
|
||||
showEntrance = showEntrance;
|
||||
navigateToTopic = navigateToTopic;
|
||||
|
||||
click(e) {
|
||||
// for events undefined has a different meaning than false
|
||||
if (this.showEntrance(e) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.unhandledRowClick(e, this.topic);
|
||||
}
|
||||
|
||||
// Can be overwritten by plugins to handle clicks on other parts of the row
|
||||
unhandledRowClick() {}
|
||||
|
||||
@discourseComputed("topic")
|
||||
unboundClassNames(topic) {
|
||||
let classes = [];
|
||||
|
||||
if (topic.get("category")) {
|
||||
classes.push("category-" + topic.get("category.fullSlug"));
|
||||
}
|
||||
|
||||
if (topic.get("tags")) {
|
||||
topic.get("tags").forEach((tagName) => classes.push("tag-" + tagName));
|
||||
}
|
||||
|
||||
["liked", "archived", "bookmarked", "pinned", "closed", "visited"].forEach(
|
||||
(name) => {
|
||||
if (topic.get(name)) {
|
||||
classes.push(name);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="above-latest-topic-list-item"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash topic=this.topic}}
|
||||
/>
|
||||
<div class="topic-poster">
|
||||
<UserLink
|
||||
@user={{this.topic.lastPosterUser}}
|
||||
aria-label={{if
|
||||
this.topic.lastPosterUser.username
|
||||
(i18n
|
||||
"latest_poster_link" username=this.topic.lastPosterUser.username
|
||||
)
|
||||
}}
|
||||
>
|
||||
{{avatar this.topic.lastPosterUser imageSize="large"}}
|
||||
</UserLink>
|
||||
<UserAvatarFlair @user={{this.topic.lastPosterUser}} />
|
||||
</div>
|
||||
<div class="main-link">
|
||||
<div class="top-row">
|
||||
<TopicStatus @topic={{this.topic}} />
|
||||
{{topicLink this.topic}}
|
||||
{{~#if this.topic.featured_link}}
|
||||
{{topicFeaturedLink this.topic}}
|
||||
{{/if}}{{! intentionally inline
|
||||
to avoid whitespace}}<TopicPostBadges
|
||||
@unreadPosts={{this.topic.unread_posts}}
|
||||
@unseen={{this.topic.unseen}}
|
||||
@url={{this.topic.lastUnreadUrl}}
|
||||
/>
|
||||
</div>
|
||||
<div class="bottom-row">
|
||||
{{categoryLink this.topic.category}}{{discourseTags
|
||||
this.topic
|
||||
mode="list"
|
||||
}}{{! intentionally inline to avoid whitespace}}
|
||||
<PluginOutlet
|
||||
@name="below-latest-topic-list-item-bottom-row"
|
||||
@connectorTagName="span"
|
||||
@outletArgs={{hash topic=this.topic}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topic-stats">
|
||||
<PluginOutlet
|
||||
@name="above-latest-topic-list-item-post-count"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash topic=this.topic}}
|
||||
/>
|
||||
<ItemRepliesCell @topic={{this.topic}} @tagName="div" />
|
||||
<div class="topic-last-activity">
|
||||
<a
|
||||
href={{this.topic.lastPostUrl}}
|
||||
title={{this.topic.bumpedAtTitle}}
|
||||
>{{formatDate this.topic.bumpedAt format="tiny" noTitle="true"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
}
|
@ -1,13 +1,32 @@
|
||||
import Component from "@ember/component";
|
||||
import { classNameBindings, tagName } from "@ember-decorators/component";
|
||||
import $ from "jquery";
|
||||
import PostCountOrBadges from "discourse/components/topic-list/post-count-or-badges";
|
||||
import { showEntrance } from "discourse/components/topic-list-item";
|
||||
import TopicStatus from "discourse/components/topic-status";
|
||||
import coldAgeClass from "discourse/helpers/cold-age-class";
|
||||
import formatAge from "discourse/helpers/format-age";
|
||||
import rawDate from "discourse/helpers/raw-date";
|
||||
import topicLink from "discourse/helpers/topic-link";
|
||||
|
||||
export function showEntrance(e) {
|
||||
let target = $(e.target);
|
||||
|
||||
if (target.hasClass("posts-map") || target.parents(".posts-map").length > 0) {
|
||||
if (target.prop("tagName") !== "A") {
|
||||
target = target.find("a");
|
||||
if (target.length === 0) {
|
||||
target = target.end();
|
||||
}
|
||||
}
|
||||
|
||||
this.appEvents.trigger("topic-entrance:show", {
|
||||
topic: this.topic,
|
||||
position: target.offset(),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@tagName("tr")
|
||||
@classNameBindings(":category-topic-link", "topic.archived", "topic.visited")
|
||||
export default class MobileCategoryTopic extends Component {
|
||||
|
@ -1,9 +1,7 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import NewListHeaderControls from "discourse/components/topic-list/new-list-header-controls";
|
||||
import raw from "discourse/helpers/raw";
|
||||
|
||||
export default class NewListHeaderControlsWrapper extends Component {
|
||||
@service site;
|
||||
@ -21,30 +19,14 @@ export default class NewListHeaderControlsWrapper extends Component {
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.site.useGlimmerTopicList}}
|
||||
<div class="topic-replies-toggle-wrapper">
|
||||
<NewListHeaderControls
|
||||
@current={{@current}}
|
||||
@newRepliesCount={{@newRepliesCount}}
|
||||
@newTopicsCount={{@newTopicsCount}}
|
||||
@noStaticLabel={{true}}
|
||||
@changeNewListSubset={{@changeNewListSubset}}
|
||||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
<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>
|
||||
{{/if}}
|
||||
<div class="topic-replies-toggle-wrapper">
|
||||
<NewListHeaderControls
|
||||
@current={{@current}}
|
||||
@newRepliesCount={{@newRepliesCount}}
|
||||
@newTopicsCount={{@newTopicsCount}}
|
||||
@noStaticLabel={{true}}
|
||||
@changeNewListSubset={{@changeNewListSubset}}
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
}
|
||||
|
@ -222,11 +222,7 @@ export default class ParentCategoryRow extends CategoryListItem {
|
||||
{{#if this.showTopics}}
|
||||
<td class="latest">
|
||||
{{#each this.category.featuredTopics as |t|}}
|
||||
{{#if this.site.useGlimmerTopicList}}
|
||||
<FeaturedTopic @topic={{t}} />
|
||||
{{else}}
|
||||
<FeaturedTopic @topic={{t}} />
|
||||
{{/if}}
|
||||
<FeaturedTopic @topic={{t}} />
|
||||
{{/each}}
|
||||
</td>
|
||||
<PluginOutlet
|
||||
|
@ -1,418 +0,0 @@
|
||||
import Component from "@ember/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { alias } from "@ember/object/computed";
|
||||
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 PluginOutlet from "discourse/components/plugin-outlet";
|
||||
import discourseComputed, { bind } from "discourse/lib/decorators";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
||||
import { RUNTIME_OPTIONS } from "discourse/lib/raw-handlebars-helpers";
|
||||
import { findRawTemplate } from "discourse/lib/raw-templates";
|
||||
import { applyValueTransformer } from "discourse/lib/transformer";
|
||||
import DiscourseURL, { groupPath } from "discourse/lib/url";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export function showEntrance(e) {
|
||||
let target = $(e.target);
|
||||
|
||||
if (target.hasClass("posts-map") || target.parents(".posts-map").length > 0) {
|
||||
if (target.prop("tagName") !== "A") {
|
||||
target = target.find("a");
|
||||
if (target.length === 0) {
|
||||
target = target.end();
|
||||
}
|
||||
}
|
||||
|
||||
this.appEvents.trigger("topic-entrance:show", {
|
||||
topic: this.topic,
|
||||
position: target.offset(),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function navigateToTopic(topic, href) {
|
||||
const historyStore = getOwner(this).lookup("service:history-store");
|
||||
historyStore.set("lastTopicIdViewed", topic.id);
|
||||
|
||||
DiscourseURL.routeTo(href || topic.get("url"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@tagName("tr")
|
||||
@classNameBindings(":topic-list-item", "unboundClassNames", "topic.visited")
|
||||
@attributeBindings("dataTopicId:data-topic-id", "role", "ariaLevel:aria-level")
|
||||
export default class TopicListItem extends Component {
|
||||
static reopen() {
|
||||
deprecated(
|
||||
"Modifying topic-list-item with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
|
||||
return super.reopen(...arguments);
|
||||
}
|
||||
|
||||
static reopenClass() {
|
||||
deprecated(
|
||||
"Modifying topic-list-item with `reopenClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
|
||||
return super.reopenClass(...arguments);
|
||||
}
|
||||
|
||||
@service router;
|
||||
@service historyStore;
|
||||
|
||||
@alias("topic.id") dataTopicId;
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs(...arguments);
|
||||
this.renderTopicListItem();
|
||||
}
|
||||
|
||||
// Already-rendered topic is marked as highlighted
|
||||
// Ideally this should be a modifier... but we can't do that
|
||||
// until this component has its tagName removed.
|
||||
@observes("topic.highlight")
|
||||
topicHighlightChanged() {
|
||||
if (this.topic.highlight) {
|
||||
this._highlightIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@observes("topic.pinned", "expandGloballyPinned", "expandAllPinned")
|
||||
renderTopicListItem() {
|
||||
const template = findRawTemplate("list/topic-list-item");
|
||||
if (template) {
|
||||
this.set(
|
||||
"topicListItemContents",
|
||||
htmlSafe(template(this, RUNTIME_OPTIONS))
|
||||
);
|
||||
schedule("afterRender", () => {
|
||||
if (this.isDestroyed || this.isDestroying) {
|
||||
return;
|
||||
}
|
||||
if (this.selected && this.selected.includes(this.topic)) {
|
||||
this.element.querySelector("input.bulk-select").checked = true;
|
||||
}
|
||||
if (this._shouldFocusLastVisited()) {
|
||||
const title = this._titleElement();
|
||||
if (title) {
|
||||
title.addEventListener("focus", this._onTitleFocus);
|
||||
title.addEventListener("blur", this._onTitleBlur);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
|
||||
if (this.includeUnreadIndicator) {
|
||||
this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage);
|
||||
}
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
|
||||
this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage);
|
||||
|
||||
if (this._shouldFocusLastVisited()) {
|
||||
const title = this._titleElement();
|
||||
if (title) {
|
||||
title.removeEventListener("focus", this._onTitleFocus);
|
||||
title.removeEventListener("blur", this._onTitleBlur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
onMessage(data) {
|
||||
const nodeClassList = document.querySelector(
|
||||
`.indicator-topic-${data.topic_id}`
|
||||
).classList;
|
||||
|
||||
nodeClassList.toggle("read", !data.show_indicator);
|
||||
}
|
||||
|
||||
@discourseComputed("topic.participant_groups")
|
||||
participantGroups(groupNames) {
|
||||
if (!groupNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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("filters.new.lower_title");
|
||||
}
|
||||
|
||||
@discourseComputed("topic", "lastVisitedTopic")
|
||||
unboundClassNames(topic, lastVisitedTopic) {
|
||||
let classes = [];
|
||||
|
||||
if (topic.get("category")) {
|
||||
classes.push("category-" + topic.get("category.fullSlug"));
|
||||
}
|
||||
|
||||
if (topic.get("tags")) {
|
||||
topic.get("tags").forEach((tag) => classes.push("tag-" + tag));
|
||||
}
|
||||
|
||||
if (topic.get("hasExcerpt")) {
|
||||
classes.push("has-excerpt");
|
||||
}
|
||||
|
||||
if (topic.get("unseen")) {
|
||||
classes.push("unseen-topic");
|
||||
}
|
||||
|
||||
if (topic.unread_posts) {
|
||||
classes.push("unread-posts");
|
||||
}
|
||||
|
||||
["liked", "archived", "bookmarked", "pinned", "closed"].forEach((name) => {
|
||||
if (topic.get(name)) {
|
||||
classes.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
if (topic === lastVisitedTopic) {
|
||||
classes.push("last-visit");
|
||||
}
|
||||
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
hasLikes() {
|
||||
return this.get("topic.like_count") > 0;
|
||||
}
|
||||
|
||||
hasOpLikes() {
|
||||
return this.get("topic.op_like_count") > 0;
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
expandPinned() {
|
||||
return applyValueTransformer(
|
||||
"topic-list-item-expand-pinned",
|
||||
this._expandPinned,
|
||||
{
|
||||
topic: this.topic,
|
||||
mobileView: this.site.mobileView,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
get _expandPinned() {
|
||||
const pinned = this.get("topic.pinned");
|
||||
if (!pinned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.site.mobileView) {
|
||||
if (!this.siteSettings.show_pinned_excerpt_mobile) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!this.siteSettings.show_pinned_excerpt_desktop) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.expandGloballyPinned && this.get("topic.pinned_globally")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.expandAllPinned) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
showEntrance() {
|
||||
return showEntrance.call(this, ...arguments);
|
||||
}
|
||||
|
||||
click(e) {
|
||||
const result = this.showEntrance(e);
|
||||
if (result === false) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const topic = this.topic;
|
||||
const target = e.target;
|
||||
const classList = target.classList;
|
||||
if (classList.contains("bulk-select")) {
|
||||
const selected = this.selected;
|
||||
|
||||
if (target.checked) {
|
||||
selected.addObject(topic);
|
||||
|
||||
if (this.lastChecked && e.shiftKey) {
|
||||
const bulkSelects = Array.from(
|
||||
document.querySelectorAll("input.bulk-select")
|
||||
),
|
||||
from = bulkSelects.indexOf(target),
|
||||
to = bulkSelects.findIndex((el) => el.id === this.lastChecked.id),
|
||||
start = Math.min(from, to),
|
||||
end = Math.max(from, to);
|
||||
|
||||
bulkSelects
|
||||
.slice(start, end)
|
||||
.filter((el) => el.checked !== true)
|
||||
.forEach((checkbox) => {
|
||||
checkbox.click();
|
||||
});
|
||||
}
|
||||
|
||||
this.set("lastChecked", target);
|
||||
} else {
|
||||
selected.removeObject(topic);
|
||||
this.set("lastChecked", null);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
classList.contains("raw-topic-link") ||
|
||||
classList.contains("post-activity")
|
||||
) {
|
||||
if (wantsNewWindow(e)) {
|
||||
return true;
|
||||
}
|
||||
e.preventDefault();
|
||||
return this.navigateToTopic(topic, target.getAttribute("href"));
|
||||
}
|
||||
|
||||
// make full row click target on mobile, due to size constraints
|
||||
if (
|
||||
this.site.mobileView &&
|
||||
e.target.matches(
|
||||
".topic-list-data, .main-link, .right, .topic-item-stats, .topic-item-stats__category-tags, .discourse-tags"
|
||||
)
|
||||
) {
|
||||
if (wantsNewWindow(e)) {
|
||||
return true;
|
||||
}
|
||||
e.preventDefault();
|
||||
return this.navigateToTopic(topic, topic.lastUnreadUrl);
|
||||
}
|
||||
|
||||
return this.unhandledRowClick(e, topic);
|
||||
}
|
||||
|
||||
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() {
|
||||
return navigateToTopic.call(this, ...arguments);
|
||||
}
|
||||
|
||||
highlight(opts = { isLastViewedTopic: false }) {
|
||||
schedule("afterRender", () => {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.element.classList.add("highlighted");
|
||||
this.element.setAttribute(
|
||||
"data-is-last-viewed-topic",
|
||||
opts.isLastViewedTopic
|
||||
);
|
||||
this.element.addEventListener("animationend", () => {
|
||||
this.element.classList.remove("highlighted");
|
||||
});
|
||||
if (opts.isLastViewedTopic && this._shouldFocusLastVisited()) {
|
||||
this._titleElement()?.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@on("didInsertElement")
|
||||
_highlightIfNeeded() {
|
||||
// highlight the last topic viewed
|
||||
const lastViewedTopicId = this.historyStore.get("lastTopicIdViewed");
|
||||
const isLastViewedTopic = lastViewedTopicId === this.topic.id;
|
||||
|
||||
if (isLastViewedTopic) {
|
||||
this.historyStore.delete("lastTopicIdViewed");
|
||||
this.highlight({ isLastViewedTopic: true });
|
||||
} else if (this.get("topic.highlight")) {
|
||||
// highlight new topics that have been loaded from the server or the one we just created
|
||||
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");
|
||||
}
|
||||
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="above-topic-list-item"
|
||||
@outletArgs={{hash topic=this.topic}}
|
||||
/>
|
||||
{{this.topicListItemContents}}
|
||||
</template>
|
||||
}
|
@ -1,339 +1,20 @@
|
||||
import Component from "@ember/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { dependentKeyCompat } from "@ember/object/compat";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import { service } from "@ember/service";
|
||||
import {
|
||||
classNameBindings,
|
||||
classNames,
|
||||
tagName,
|
||||
} from "@ember-decorators/component";
|
||||
import { observes, on } from "@ember-decorators/object";
|
||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||
import TopicListItem from "discourse/components/topic-list-item";
|
||||
import raw from "discourse/helpers/raw";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import Component from "@glimmer/component";
|
||||
import curryComponent from "ember-curry-component";
|
||||
import List from "discourse/components/topic-list/list";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
@tagName("table")
|
||||
@classNames("topic-list")
|
||||
@classNameBindings("bulkSelectEnabled:sticky-header")
|
||||
export default class TopicList extends Component.extend(LoadMore) {
|
||||
static reopen() {
|
||||
export default class TopicListShim extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
deprecated(
|
||||
"Modifying topic-list with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
`components/topic-list is deprecated, and should be replaced with components/topics-list/list`,
|
||||
{ id: "discourse.legacy-topic-list" }
|
||||
);
|
||||
|
||||
return super.reopen(...arguments);
|
||||
}
|
||||
|
||||
static reopenClass() {
|
||||
deprecated(
|
||||
"Modifying topic-list with `reopenClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
|
||||
return super.reopenClass(...arguments);
|
||||
}
|
||||
|
||||
@service modal;
|
||||
@service router;
|
||||
@service siteSettings;
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
// for the classNameBindings
|
||||
@dependentKeyCompat
|
||||
get bulkSelectEnabled() {
|
||||
return (
|
||||
this.get("canBulkSelect") && 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() {
|
||||
// special case so we don't keep scanning huge lists
|
||||
if (!this.lastVisitedTopic) {
|
||||
this.refreshLastVisited();
|
||||
}
|
||||
}
|
||||
|
||||
@observes("topics", "order", "ascending", "category", "top", "hot")
|
||||
lastVisitedTopicChanged() {
|
||||
this.refreshLastVisited();
|
||||
}
|
||||
|
||||
scrolled() {
|
||||
super.scrolled(...arguments);
|
||||
let onScroll = this.onScroll;
|
||||
if (!onScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
onScroll.call(this);
|
||||
}
|
||||
|
||||
_updateLastVisitedTopic(topics, order, ascending, top, hot) {
|
||||
this.set("lastVisitedTopic", null);
|
||||
|
||||
if (!this.highlightLastVisited) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (order && order !== "activity") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (top || hot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!topics || topics.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ascending) {
|
||||
return;
|
||||
}
|
||||
|
||||
let user = this.currentUser;
|
||||
if (!user || !user.previous_visit_at) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lastVisitedTopic, topic;
|
||||
|
||||
let prevVisit = user.get("previousVisitAt");
|
||||
|
||||
// this is more efficient cause we keep appending to list
|
||||
// work backwards
|
||||
let start = 0;
|
||||
while (topics[start] && topics[start].get("pinned")) {
|
||||
start++;
|
||||
}
|
||||
|
||||
let i;
|
||||
for (i = topics.length - 1; i >= start; i--) {
|
||||
if (topics[i].get("bumpedAt") > prevVisit) {
|
||||
lastVisitedTopic = topics[i];
|
||||
break;
|
||||
}
|
||||
topic = topics[i];
|
||||
}
|
||||
|
||||
if (!lastVisitedTopic || !topic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end of list that was scanned
|
||||
if (topic.get("bumpedAt") > prevVisit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("lastVisitedTopic", lastVisitedTopic);
|
||||
}
|
||||
|
||||
refreshLastVisited() {
|
||||
this._updateLastVisitedTopic(
|
||||
this.topics,
|
||||
this.order,
|
||||
this.ascending,
|
||||
this.top,
|
||||
this.hot
|
||||
);
|
||||
}
|
||||
|
||||
click(e) {
|
||||
const onClick = (sel, callback) => {
|
||||
let target = e.target.closest(sel);
|
||||
|
||||
if (target) {
|
||||
callback(target);
|
||||
}
|
||||
};
|
||||
|
||||
onClick("button.bulk-select", () => {
|
||||
this.bulkSelectHelper.toggleBulkSelect();
|
||||
this.rerender();
|
||||
});
|
||||
|
||||
onClick("button.bulk-select-all", () => {
|
||||
this.bulkSelectHelper.autoAddTopicsToBulkSelect = true;
|
||||
document
|
||||
.querySelectorAll("input.bulk-select:not(:checked)")
|
||||
.forEach((el) => el.click());
|
||||
});
|
||||
|
||||
onClick("button.bulk-clear-all", () => {
|
||||
this.bulkSelectHelper.autoAddTopicsToBulkSelect = false;
|
||||
document
|
||||
.querySelectorAll("input.bulk-select:checked")
|
||||
.forEach((el) => el.click());
|
||||
});
|
||||
|
||||
onClick("th.sortable", (element) => {
|
||||
this.changeSort(element.dataset.sortOrder);
|
||||
this.rerender();
|
||||
});
|
||||
|
||||
onClick("button.topics-replies-toggle", (element) => {
|
||||
if (element.classList.contains("--all")) {
|
||||
this.changeNewListSubset(null);
|
||||
} else if (element.classList.contains("--topics")) {
|
||||
this.changeNewListSubset("topics");
|
||||
} else if (element.classList.contains("--replies")) {
|
||||
this.changeNewListSubset("replies");
|
||||
}
|
||||
this.rerender();
|
||||
});
|
||||
}
|
||||
|
||||
keyDown(e) {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
let onKeyDown = (sel, callback) => {
|
||||
let target = e.target.closest(sel);
|
||||
|
||||
if (target) {
|
||||
callback.call(this, target);
|
||||
}
|
||||
};
|
||||
|
||||
onKeyDown("th.sortable", (element) => {
|
||||
e.preventDefault();
|
||||
this.changeSort(element.dataset.sortOrder);
|
||||
this.rerender();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<caption class="sr-only">{{i18n "sr_topic_list_caption"}}</caption>
|
||||
|
||||
<thead class="topic-list-header">
|
||||
{{raw
|
||||
"topic-list-header"
|
||||
canBulkSelect=this.canBulkSelect
|
||||
toggleInTitle=this.toggleInTitle
|
||||
hideCategory=this.hideCategory
|
||||
showPosters=this.showPosters
|
||||
showLikes=this.showLikes
|
||||
showOpLikes=this.showOpLikes
|
||||
order=this.order
|
||||
ascending=this.ascending
|
||||
sortable=this.sortable
|
||||
listTitle=this.listTitle
|
||||
bulkSelectEnabled=this.bulkSelectEnabled
|
||||
bulkSelectHelper=this.bulkSelectHelper
|
||||
canDoBulkActions=this.canDoBulkActions
|
||||
showTopicsAndRepliesToggle=this.showTopicsAndRepliesToggle
|
||||
newListSubset=this.newListSubset
|
||||
newRepliesCount=this.newRepliesCount
|
||||
newTopicsCount=this.newTopicsCount
|
||||
}}
|
||||
</thead>
|
||||
|
||||
<PluginOutlet
|
||||
@name="before-topic-list-body"
|
||||
@outletArgs={{hash
|
||||
topics=this.topics
|
||||
selected=this.selected
|
||||
bulkSelectEnabled=this.bulkSelectEnabled
|
||||
lastVisitedTopic=this.lastVisitedTopic
|
||||
discoveryList=this.discoveryList
|
||||
hideCategory=this.hideCategory
|
||||
}}
|
||||
/>
|
||||
|
||||
<tbody class="topic-list-body">
|
||||
{{#each this.filteredTopics as |topic index|}}
|
||||
<TopicListItem
|
||||
@topic={{topic}}
|
||||
@bulkSelectEnabled={{this.bulkSelectEnabled}}
|
||||
@showTopicPostBadges={{this.showTopicPostBadges}}
|
||||
@hideCategory={{this.hideCategory}}
|
||||
@showPosters={{this.showPosters}}
|
||||
@showLikes={{this.showLikes}}
|
||||
@showOpLikes={{this.showOpLikes}}
|
||||
@expandGloballyPinned={{this.expandGloballyPinned}}
|
||||
@expandAllPinned={{this.expandAllPinned}}
|
||||
@lastVisitedTopic={{this.lastVisitedTopic}}
|
||||
@selected={{this.selected}}
|
||||
@lastChecked={{this.lastChecked}}
|
||||
@tagsForUser={{this.tagsForUser}}
|
||||
@focusLastVisitedTopic={{this.focusLastVisitedTopic}}
|
||||
@index={{index}}
|
||||
/>
|
||||
{{raw
|
||||
"list/visited-line"
|
||||
lastVisitedTopic=this.lastVisitedTopic
|
||||
topic=topic
|
||||
}}
|
||||
<PluginOutlet
|
||||
@name="after-topic-list-item"
|
||||
@outletArgs={{hash topic=topic index=index}}
|
||||
@connectorTagName="tr"
|
||||
/>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
|
||||
<PluginOutlet
|
||||
@name="after-topic-list-body"
|
||||
@outletArgs={{hash
|
||||
topics=this.topics
|
||||
selected=this.selected
|
||||
bulkSelectEnabled=this.bulkSelectEnabled
|
||||
lastVisitedTopic=this.lastVisitedTopic
|
||||
discoveryList=this.discoveryList
|
||||
hideCategory=this.hideCategory
|
||||
}}
|
||||
/>
|
||||
{{#let (curryComponent List this.args) as |CurriedComponent|}}
|
||||
<CurriedComponent />
|
||||
{{/let}}
|
||||
</template>
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { concat, get, hash } from "@ember/helper";
|
||||
import { hash } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
@ -7,7 +7,6 @@ import { and } from "truth-helpers";
|
||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import element from "discourse/helpers/element";
|
||||
import TopicStatusIcons from "discourse/helpers/topic-status-icons";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class TopicStatus extends Component {
|
||||
@ -108,22 +107,10 @@ export default class TopicStatus extends Component {
|
||||
class="topic-status"
|
||||
>{{icon "far-eye-slash"}}</span>
|
||||
{{~/if~}}
|
||||
|
||||
{{~#if this.site.useGlimmerTopicList~}}
|
||||
<PluginOutlet
|
||||
@name="after-topic-status"
|
||||
@outletArgs={{hash topic=@topic context=@context}}
|
||||
/>
|
||||
{{~else~}}
|
||||
{{~#each TopicStatusIcons.entries as |entry|~}}
|
||||
{{~#if (get @topic entry.attribute)~}}
|
||||
<span
|
||||
title={{i18n (concat "topic_statuses." entry.titleKey ".help")}}
|
||||
class="topic-status"
|
||||
>{{icon entry.iconName}}</span>
|
||||
{{~/if~}}
|
||||
{{~/each~}}
|
||||
{{~/if~}}
|
||||
<PluginOutlet
|
||||
@name="after-topic-status"
|
||||
@outletArgs={{hash topic=@topic context=@context}}
|
||||
/>
|
||||
{{~! no whitespace ~}}
|
||||
</this.wrapperElement>
|
||||
{{~! no whitespace ~}}
|
||||
|
@ -84,15 +84,6 @@ loaderShim("discourse-common/lib/object", () =>
|
||||
loaderShim("discourse-common/lib/popular-themes", () =>
|
||||
importSync("discourse/lib/popular-themes")
|
||||
);
|
||||
loaderShim("discourse-common/lib/raw-handlebars-helpers", () =>
|
||||
importSync("discourse/lib/raw-handlebars-helpers")
|
||||
);
|
||||
loaderShim("discourse-common/lib/raw-handlebars", () =>
|
||||
importSync("discourse/lib/raw-handlebars")
|
||||
);
|
||||
loaderShim("discourse-common/lib/raw-templates", () =>
|
||||
importSync("discourse/lib/raw-templates")
|
||||
);
|
||||
loaderShim("discourse-common/lib/suffix-trie", () =>
|
||||
importSync("discourse/lib/suffix-trie")
|
||||
);
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("age-with-tooltip", ageWithTooltip);
|
||||
|
||||
export default function ageWithTooltip(dt, params = {}) {
|
||||
return htmlSafe(
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { get } from "@ember/object";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { avatarImg } from "discourse/lib/avatar-utils";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { prioritizeNameInUx } from "discourse/lib/settings";
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import { i18n } from "discourse-i18n";
|
||||
@ -81,7 +80,6 @@ export function renderAvatar(user, options) {
|
||||
}
|
||||
}
|
||||
|
||||
registerRawHelper("avatar", avatar);
|
||||
export default function avatar(user, params) {
|
||||
return htmlSafe(renderAvatar.call(this, user, params));
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
import getUrl from "discourse/lib/get-url";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("base-path", basePath);
|
||||
|
||||
export default function basePath() {
|
||||
return getUrl("");
|
||||
|
@ -1,8 +1,5 @@
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import getUrl from "discourse/lib/get-url";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("base-url", baseUrl);
|
||||
|
||||
export default function baseUrl() {
|
||||
deprecated("Use `{{base-path}}` instead of `{{base-url}}`", {
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { categoryLinkHTML } from "discourse/helpers/category-link";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("category-badge", categoryBadge);
|
||||
|
||||
export default function categoryBadge(cat, options = {}) {
|
||||
return categoryLinkHTML(cat, {
|
||||
|
@ -3,7 +3,7 @@ import { htmlSafe } from "@ember/template";
|
||||
import categoryVariables from "discourse/helpers/category-variables";
|
||||
import replaceEmoji from "discourse/helpers/replace-emoji";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import { helperContext, registerRawHelper } from "discourse/lib/helpers";
|
||||
import { helperContext } from "discourse/lib/helpers";
|
||||
import { iconHTML } from "discourse/lib/icon-library";
|
||||
import { applyValueTransformer } from "discourse/lib/transformer";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
@ -116,7 +116,6 @@ export function categoryLinkHTML(category, options) {
|
||||
}
|
||||
|
||||
export default categoryLinkHTML;
|
||||
registerRawHelper("category-link", categoryLinkHTML);
|
||||
|
||||
function buildTopicCount(count) {
|
||||
return `<span class="topic-count" aria-label="${i18n(
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { helperContext, registerRawHelper } from "discourse/lib/helpers";
|
||||
import { helperContext } from "discourse/lib/helpers";
|
||||
|
||||
function daysSinceEpoch(dt) {
|
||||
// 1000 * 60 * 60 * 24 = days since epoch
|
||||
return dt.getTime() / 86400000;
|
||||
}
|
||||
|
||||
registerRawHelper("cold-age-class", coldAgeClass);
|
||||
|
||||
export default function coldAgeClass(dt, params = {}) {
|
||||
let className = params["class"] || "age";
|
||||
|
||||
|
@ -1,7 +1,3 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("component-for-collection", componentForCollection);
|
||||
|
||||
export default function componentForCollection(
|
||||
collectionIdentifier,
|
||||
selectKit
|
||||
|
@ -1,7 +1,3 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("component-for-row", componentForRow);
|
||||
|
||||
export default function componentForRow(
|
||||
collectionForIdentifier,
|
||||
item,
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { renderIcon } from "discourse/lib/icon-library";
|
||||
|
||||
export default function icon(id, options = {}) {
|
||||
return htmlSafe(renderIcon("string", id, options));
|
||||
}
|
||||
|
||||
registerRawHelper("d-icon", icon);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
let usernameDecorators = [];
|
||||
export function addUsernameSelectorDecorator(decorator) {
|
||||
@ -20,8 +19,6 @@ export function decorateUsername(username) {
|
||||
return decorations.length ? htmlSafe(decorations.join("")) : "";
|
||||
}
|
||||
|
||||
registerRawHelper("decorate-username-selector", decorateUsernameSelector);
|
||||
|
||||
export default function decorateUsernameSelector(username) {
|
||||
return decorateUsername(username);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { helperContext, registerRawHelper } from "discourse/lib/helpers";
|
||||
import { helperContext } from "discourse/lib/helpers";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
|
||||
function setDir(text) {
|
||||
@ -9,8 +9,6 @@ function setDir(text) {
|
||||
return `<span ${mixed ? 'dir="auto"' : ""}>${content}</span>`;
|
||||
}
|
||||
|
||||
registerRawHelper("dir-span", dirSpan);
|
||||
|
||||
export default function dirSpan(str, params = {}) {
|
||||
let isHtmlSafe = false;
|
||||
if (params.htmlSafe) {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import renderTag from "discourse/lib/render-tag";
|
||||
|
||||
registerRawHelper("discourse-tag", discourseTag);
|
||||
export default function discourseTag(name, params) {
|
||||
return htmlSafe(renderTag(name, params));
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import renderTags from "discourse/lib/render-tags";
|
||||
|
||||
registerRawHelper("discourse-tags", discourseTags);
|
||||
export default function discourseTags(topic, params) {
|
||||
return htmlSafe(renderTags(topic, params));
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { emojiUnescape } from "discourse/lib/text";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
|
||||
registerRawHelper("emoji", emoji);
|
||||
export default function emoji(code, options) {
|
||||
const escaped = escapeExpression(`:${code}:`);
|
||||
return htmlSafe(emojiUnescape(escaped, options));
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { renderIcon } from "discourse/lib/icon-library";
|
||||
|
||||
export function iconHTML(id, params) {
|
||||
return renderIcon("string", id, params);
|
||||
}
|
||||
|
||||
registerRawHelper("fa-icon", faIcon);
|
||||
export default function faIcon(icon, params) {
|
||||
deprecated("Use `{{d-icon}}` instead of `{{fa-icon}}", {
|
||||
id: "discourse.fa-icon",
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("format-age", formatAge);
|
||||
export default function formatAge(dt) {
|
||||
dt = new Date(dt);
|
||||
return htmlSafe(autoUpdatingRelativeAge(dt));
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
/**
|
||||
Display logic for dates. It is unbound in Ember but will use jQuery to
|
||||
update the dates on a regular interval.
|
||||
**/
|
||||
|
||||
registerRawHelper("format-date", formatDate);
|
||||
export default function formatDate(val, params = {}) {
|
||||
let leaveAgo,
|
||||
format = "medium",
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { durationTiny } from "discourse/lib/formatter";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("format-duration", formatDuration);
|
||||
export default function formatDuration(seconds) {
|
||||
return htmlSafe(durationTiny(seconds));
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
|
||||
export default formatUsername;
|
||||
registerRawHelper("format-username", formatUsername);
|
||||
|
@ -1,7 +1,4 @@
|
||||
import { default as emberGetUrl } from "discourse/lib/get-url";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("get-url", getUrl);
|
||||
|
||||
export default function getUrl(value) {
|
||||
return emberGetUrl(value);
|
||||
|
@ -1,7 +1,4 @@
|
||||
import { htmlSafe as emberHtmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("html-safe", htmlSafe);
|
||||
|
||||
export default function htmlSafe(string) {
|
||||
return emberHtmlSafe(string);
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
registerRawHelper("i18n-yes-no", i18nYesNo);
|
||||
|
||||
export default function i18nYesNo(value, params) {
|
||||
return i18n(value ? "yes_value" : "no_value", params);
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
registerRawHelper("i18n", i18n);
|
||||
|
||||
export default i18n;
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { number as numberFormatter } from "discourse/lib/formatter";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
import I18n, { i18n } from "discourse-i18n";
|
||||
|
||||
registerRawHelper("number", number);
|
||||
|
||||
export default function number(orig, params = {}) {
|
||||
orig = Math.round(parseFloat(orig));
|
||||
if (isNaN(orig)) {
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||
import { connectorsExist } from "discourse/lib/plugin-connectors";
|
||||
import RawHandlebars from "discourse/lib/raw-handlebars";
|
||||
import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
||||
|
||||
const GlimmerPluginOutletWrapper = <template>
|
||||
{{~! no whitespace ~}}
|
||||
<PluginOutlet @name={{@data.name}} @outletArgs={{@data.outletArgs}} />
|
||||
{{~! no whitespace ~}}
|
||||
</template>;
|
||||
|
||||
RawHandlebars.registerHelper("plugin-outlet", function (options) {
|
||||
const { name, tagName, outletArgs } = options.hash;
|
||||
|
||||
if (!connectorsExist(name)) {
|
||||
return htmlSafe("");
|
||||
}
|
||||
|
||||
return htmlSafe(
|
||||
rawRenderGlimmer(
|
||||
this,
|
||||
`${tagName || "span"}.hbr-ember-outlet`,
|
||||
GlimmerPluginOutletWrapper,
|
||||
{ name, outletArgs }
|
||||
)
|
||||
);
|
||||
});
|
@ -1,8 +1,5 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { longDate } from "discourse/lib/formatter";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("raw-date", rawDate);
|
||||
|
||||
export default function rawDate(dt) {
|
||||
return htmlSafe(longDate(new Date(dt)));
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("raw-hash", function (params) {
|
||||
return params;
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { rawConnectorsFor } from "discourse/lib/plugin-connectors";
|
||||
import RawHandlebars from "discourse/lib/raw-handlebars";
|
||||
|
||||
RawHandlebars.registerHelper("raw-plugin-outlet", function (args) {
|
||||
const connectors = rawConnectorsFor(args.hash.name);
|
||||
if (connectors.length) {
|
||||
const output = connectors.map((c) => c.template({ context: this }));
|
||||
return htmlSafe(output.join(""));
|
||||
}
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
import Helper from "@ember/component/helper";
|
||||
import { registerDestructor } from "@ember/destroyable";
|
||||
import { getOwner, setOwner } from "@ember/owner";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { bind } from "discourse/lib/decorators";
|
||||
import { helperContext, registerRawHelper } from "discourse/lib/helpers";
|
||||
import { RUNTIME_OPTIONS } from "discourse/lib/raw-handlebars-helpers";
|
||||
import { findRawTemplate } from "discourse/lib/raw-templates";
|
||||
|
||||
function renderRaw(ctx, template, templateName, params) {
|
||||
params = { ...params };
|
||||
params.parent ||= ctx;
|
||||
|
||||
let context = helperContext();
|
||||
if (!params.view) {
|
||||
const viewClass = context.registry.resolve(`raw-view:${templateName}`);
|
||||
|
||||
if (viewClass) {
|
||||
setOwner(params, getOwner(context));
|
||||
params.view = viewClass.create(params, context);
|
||||
}
|
||||
|
||||
if (!params.view) {
|
||||
params = { ...params, ...context };
|
||||
}
|
||||
}
|
||||
|
||||
return htmlSafe(template(params, RUNTIME_OPTIONS));
|
||||
}
|
||||
|
||||
const helperFunction = function (templateName, params) {
|
||||
templateName = templateName.replace(".", "/");
|
||||
|
||||
const template = findRawTemplate(templateName);
|
||||
if (!template) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Could not find raw template: " + templateName);
|
||||
return;
|
||||
}
|
||||
return renderRaw(this, template, templateName, params);
|
||||
};
|
||||
|
||||
registerRawHelper("raw", helperFunction);
|
||||
|
||||
export default class RawHelper extends Helper {
|
||||
@service renderGlimmer;
|
||||
|
||||
compute(args, params) {
|
||||
registerDestructor(this, this.cleanup);
|
||||
return helperFunction(...args, params);
|
||||
}
|
||||
|
||||
@bind
|
||||
cleanup() {
|
||||
schedule("afterRender", () => this.renderGlimmer.cleanup());
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
import { htmlSafe, isHTMLSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { emojiUnescape } from "discourse/lib/text";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
|
||||
registerRawHelper("replace-emoji", replaceEmoji);
|
||||
|
||||
export default function replaceEmoji(text, options) {
|
||||
text = isHTMLSafe(text) ? text.toString() : escapeExpression(text);
|
||||
return htmlSafe(emojiUnescape(text, options));
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("shorten-url", shortenUrl);
|
||||
export default function shortenUrl(url) {
|
||||
let matches = url.match(/\//g);
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
registerRawHelper("theme-i18n", themeI18n);
|
||||
export default function themeI18n(themeId, key, params) {
|
||||
if (typeof themeId !== "number") {
|
||||
throw new Error(
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("theme-prefix", themePrefix);
|
||||
export default function themePrefix(themeId, key) {
|
||||
return `theme_translations.${themeId}.${key}`;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import { getSetting as getThemeSetting } from "discourse/lib/theme-settings-store";
|
||||
|
||||
registerRawHelper("theme-setting", themeSetting);
|
||||
export default function themeSetting(themeId, key) {
|
||||
if (typeof themeId !== "number") {
|
||||
throw new Error(
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
import renderTopicFeaturedLink from "discourse/lib/render-topic-featured-link";
|
||||
|
||||
registerRawHelper("topic-featured-link", topicFeaturedLink);
|
||||
export default function topicFeaturedLink(topic, params) {
|
||||
return htmlSafe(renderTopicFeaturedLink(topic, params));
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("topic-link", topicLink);
|
||||
export default function topicLink(topic, args = {}) {
|
||||
const title = topic.get("fancyTitle");
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
||||
|
||||
const TopicStatusIcons = new (class {
|
||||
entries = [];
|
||||
|
||||
addObject(entry) {
|
||||
deprecated(
|
||||
"TopicStatusIcons is deprecated. Use 'after-topic-status' plugin outlet instead.",
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
|
||||
const [attribute, iconName, titleKey] = entry;
|
||||
this.entries.push({ attribute, iconName, titleKey });
|
||||
}
|
||||
})();
|
||||
|
||||
export default TopicStatusIcons;
|
@ -1,6 +1,3 @@
|
||||
import { registerRawHelper } from "discourse/lib/helpers";
|
||||
|
||||
registerRawHelper("value-entered", valueEntered);
|
||||
export default function valueEntered(value) {
|
||||
if (!value) {
|
||||
return "";
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { setOwner } from "@ember/owner";
|
||||
import Handlebars from "handlebars";
|
||||
import { createHelperContext, registerHelpers } from "discourse/lib/helpers";
|
||||
import RawHandlebars from "discourse/lib/raw-handlebars";
|
||||
import { registerRawHelpers } from "discourse/lib/raw-handlebars-helpers";
|
||||
|
||||
function isThemeOrPluginHelper(path) {
|
||||
return (
|
||||
@ -38,7 +35,6 @@ export function autoLoadModules(owner, registry) {
|
||||
|
||||
createHelperContext(context);
|
||||
registerHelpers(registry);
|
||||
registerRawHelpers(RawHandlebars, Handlebars, owner);
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -2,15 +2,12 @@ import * as GlimmerManager from "@glimmer/manager";
|
||||
import ClassicComponent from "@ember/component";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import DiscourseTemplateMap from "discourse/lib/discourse-template-map";
|
||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
||||
import { getThemeInfo } from "discourse/lib/source-identifier";
|
||||
|
||||
// We're using a patched version of Ember with a modified GlimmerManager to make the code below work.
|
||||
// This patch is not ideal, but Ember does not allow us to change a component template after initial association
|
||||
// https://github.com/glimmerjs/glimmer-vm/blob/03a4b55c03/packages/%40glimmer/manager/lib/public/template.ts#L14-L20
|
||||
|
||||
const LEGACY_TOPIC_LIST_OVERRIDES = ["topic-list", "topic-list-item"];
|
||||
|
||||
function sourceForModuleName(name) {
|
||||
const pluginMatch = name.match(/^discourse\/plugins\/([^\/]+)\//)?.[1];
|
||||
if (pluginMatch) {
|
||||
@ -74,25 +71,14 @@ export default {
|
||||
const originalTemplate = GlimmerManager.getComponentTemplate(component);
|
||||
|
||||
if (originalTemplate) {
|
||||
if (LEGACY_TOPIC_LIST_OVERRIDES.includes(componentName)) {
|
||||
// Special handling for these, with a different deprecation id, so the auto-feature-flag works correctly
|
||||
deprecated(
|
||||
`Overriding '${componentName}' template is deprecated. Use the value transformer 'topic-list-columns' and other new topic-list plugin APIs instead.`,
|
||||
{
|
||||
...RAW_TOPIC_LIST_DEPRECATION_OPTIONS,
|
||||
source: sourceForModuleName(finalOverrideModuleName),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
deprecated(
|
||||
`Overriding component templates is deprecated, and will soon be disabled. Use plugin outlets, CSS, or other customization APIs instead. [${finalOverrideModuleName}]`,
|
||||
{
|
||||
id: "discourse.component-template-overrides",
|
||||
url: "https://meta.discourse.org/t/355668",
|
||||
source: sourceForModuleName(finalOverrideModuleName),
|
||||
}
|
||||
);
|
||||
}
|
||||
deprecated(
|
||||
`Overriding component templates is deprecated, and will soon be disabled. Use plugin outlets, CSS, or other customization APIs instead. [${finalOverrideModuleName}]`,
|
||||
{
|
||||
id: "discourse.component-template-overrides",
|
||||
url: "https://meta.discourse.org/t/355668",
|
||||
source: sourceForModuleName(finalOverrideModuleName),
|
||||
}
|
||||
);
|
||||
|
||||
const overrideTemplate = require(finalOverrideModuleName).default;
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { eagerLoadRawTemplateModules } from "discourse/lib/raw-templates";
|
||||
|
||||
export default {
|
||||
initialize() {
|
||||
eagerLoadRawTemplateModules();
|
||||
},
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
import { registerDeprecationHandler } from "discourse/lib/deprecated";
|
||||
import { needsHbrTopicList } from "discourse/lib/raw-templates";
|
||||
|
||||
export default {
|
||||
before: "inject-objects",
|
||||
|
||||
initialize() {
|
||||
registerDeprecationHandler((message, opts) => {
|
||||
if (opts?.id === "discourse.hbr-topic-list-overrides") {
|
||||
needsHbrTopicList(true);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
@ -1,9 +1,7 @@
|
||||
import Helper from "@ember/component/helper";
|
||||
import { get } from "@ember/object";
|
||||
import { dasherize } from "@ember/string";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import RawHandlebars from "discourse/lib/raw-handlebars";
|
||||
|
||||
export function makeArray(obj) {
|
||||
if (obj === null || obj === undefined) {
|
||||
@ -27,17 +25,6 @@ export function htmlHelper(fn) {
|
||||
|
||||
const _helpers = {};
|
||||
|
||||
function rawGet(ctx, property, options) {
|
||||
if (options.types && options.data.view) {
|
||||
let view = options.data.view;
|
||||
return view.getStream
|
||||
? view.getStream(property).value()
|
||||
: view.getAttr(property);
|
||||
} else {
|
||||
return get(ctx, property);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerHelper(name, fn) {
|
||||
_helpers[name] = Helper.helper(fn);
|
||||
}
|
||||
@ -63,31 +50,6 @@ export function helperContext() {
|
||||
return _helperContext;
|
||||
}
|
||||
|
||||
function resolveParams(ctx, options) {
|
||||
let params = {};
|
||||
const hash = options.hash;
|
||||
|
||||
if (hash) {
|
||||
if (options.hashTypes) {
|
||||
Object.keys(hash).forEach(function (k) {
|
||||
const type = options.hashTypes[k];
|
||||
if (
|
||||
type === "STRING" ||
|
||||
type === "StringLiteral" ||
|
||||
type === "SubExpression"
|
||||
) {
|
||||
params[k] = hash[k];
|
||||
} else if (type === "ID" || type === "PathExpression") {
|
||||
params[k] = rawGet(ctx, hash[k], options);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
params = hash;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a helper for Ember and raw-hbs. This exists for
|
||||
* legacy reasons, and should be avoided in new code. Instead, you should
|
||||
@ -95,7 +57,7 @@ function resolveParams(ctx, options) {
|
||||
*/
|
||||
export function registerUnbound(name, fn) {
|
||||
deprecated(
|
||||
`[registerUnbound ${name}] registerUnbound is deprecated. Instead, you should export a default function from 'discourse/helpers/${name}.js'. If the helper is also used in raw-hbs, you can register it using 'registerRawHelper'.`,
|
||||
`[registerUnbound ${name}] registerUnbound is deprecated. Instead, you should export a default function from 'discourse/helpers/${name}.js'.`,
|
||||
{ id: "discourse.register-unbound" }
|
||||
);
|
||||
|
||||
@ -104,29 +66,14 @@ export function registerUnbound(name, fn) {
|
||||
return fn(...params, args);
|
||||
}
|
||||
};
|
||||
|
||||
registerRawHelper(name, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a helper for raw-hbs only
|
||||
*/
|
||||
export function registerRawHelper(name, fn) {
|
||||
const func = function (...args) {
|
||||
const options = args.pop();
|
||||
const properties = args;
|
||||
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (
|
||||
options.types &&
|
||||
(options.types[i] === "ID" || options.types[i] === "PathExpression")
|
||||
) {
|
||||
properties[i] = rawGet(this, properties[i], options);
|
||||
}
|
||||
}
|
||||
|
||||
return fn.call(this, ...properties, resolveParams(this, options));
|
||||
};
|
||||
|
||||
RawHandlebars.registerHelper(name, func);
|
||||
export function registerRawHelper(name) {
|
||||
deprecated(
|
||||
`[registerRawHelper ${name}] the raw handlebars system has been removed, so calls to registerRawHelper should be removed.`,
|
||||
{ id: "discourse.register-raw-helper" }
|
||||
);
|
||||
}
|
||||
|
@ -199,11 +199,7 @@ const POST_STREAM_DEPRECATION_OPTIONS = {
|
||||
// url: "", // TODO (glimmer-post-stream) uncomment when the topic is created on meta
|
||||
};
|
||||
|
||||
export const RAW_TOPIC_LIST_DEPRECATION_OPTIONS = {
|
||||
since: "v3.4.0.beta4-dev",
|
||||
id: "discourse.hbr-topic-list-overrides",
|
||||
url: "https://meta.discourse.org/t/343404",
|
||||
};
|
||||
const blockedModifications = ["component:topic-list"];
|
||||
|
||||
const appliedModificationIds = new WeakMap();
|
||||
|
||||
@ -295,7 +291,11 @@ class PluginApi {
|
||||
return;
|
||||
}
|
||||
|
||||
const klass = this.container.factoryFor(normalized);
|
||||
let klass;
|
||||
if (!blockedModifications.includes(normalized)) {
|
||||
klass = this.container.factoryFor(normalized);
|
||||
}
|
||||
|
||||
if (!klass) {
|
||||
if (!opts.ignoreMissing) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -328,17 +328,6 @@ class PluginApi {
|
||||
* ```
|
||||
**/
|
||||
modifyClass(resolverName, changes, opts) {
|
||||
if (
|
||||
resolverName === "component:topic-list" ||
|
||||
resolverName === "component:topic-list-item" ||
|
||||
resolverName === "raw-view:topic-status"
|
||||
) {
|
||||
deprecated(
|
||||
`Modifying '${resolverName}' with 'modifyClass' is deprecated. Use the value transformer 'topic-list-columns' and other new topic-list plugin APIs instead.`,
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
const klass = this._resolveClass(resolverName, opts);
|
||||
if (!klass) {
|
||||
return;
|
||||
@ -376,17 +365,6 @@ class PluginApi {
|
||||
* ```
|
||||
**/
|
||||
modifyClassStatic(resolverName, changes, opts) {
|
||||
if (
|
||||
resolverName === "component:topic-list" ||
|
||||
resolverName === "component:topic-list-item" ||
|
||||
resolverName === "raw-view:topic-status"
|
||||
) {
|
||||
deprecated(
|
||||
`Modifying '${resolverName}' with 'modifyClass' is deprecated. Use the value transformer 'topic-list-columns' and other new topic-list plugin APIs instead.`,
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
const klass = this._resolveClass(resolverName, opts);
|
||||
if (!klass) {
|
||||
return;
|
||||
|
@ -6,10 +6,8 @@ import {
|
||||
import templateOnly from "@ember/component/template-only";
|
||||
import { isDeprecatedOutletArgument } from "discourse/helpers/deprecated-outlet-argument";
|
||||
import deprecated, { withSilencedDeprecations } from "discourse/lib/deprecated";
|
||||
import { buildRawConnectorCache } from "discourse/lib/raw-templates";
|
||||
|
||||
let _connectorCache;
|
||||
let _rawConnectorCache;
|
||||
let _extraConnectorClasses = {};
|
||||
let _extraConnectorComponents = {};
|
||||
let debugOutletCallback;
|
||||
@ -64,7 +62,6 @@ function findOutlets(keys, callback) {
|
||||
|
||||
export function clearCache() {
|
||||
_connectorCache = null;
|
||||
_rawConnectorCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -235,13 +232,6 @@ export function renderedConnectorsFor(outletName, args, context, owner) {
|
||||
});
|
||||
}
|
||||
|
||||
export function rawConnectorsFor(outletName) {
|
||||
if (!_rawConnectorCache) {
|
||||
_rawConnectorCache = buildRawConnectorCache();
|
||||
}
|
||||
return _rawConnectorCache[outletName] || [];
|
||||
}
|
||||
|
||||
export function buildArgsWithDeprecations(args, deprecatedArgs, opts = {}) {
|
||||
const output = {};
|
||||
|
||||
|
@ -1,112 +0,0 @@
|
||||
import { get } from "@ember/object";
|
||||
|
||||
export const RUNTIME_OPTIONS = {
|
||||
allowProtoPropertiesByDefault: true,
|
||||
};
|
||||
|
||||
export function registerRawHelpers(hbs, handlebarsClass, owner) {
|
||||
if (!hbs.helpers) {
|
||||
hbs.helpers = Object.create(handlebarsClass.helpers);
|
||||
}
|
||||
|
||||
lazyLoadHelpers(hbs, owner);
|
||||
|
||||
if (hbs.__helpers_registered) {
|
||||
return;
|
||||
}
|
||||
hbs.__helpers_registered = true;
|
||||
|
||||
hbs.helpers["get"] = function (context, options) {
|
||||
if (!context || !options.contexts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof context !== "string") {
|
||||
return context;
|
||||
}
|
||||
|
||||
let firstContext = options.contexts[0];
|
||||
let val = firstContext[context];
|
||||
|
||||
if (context.toString().startsWith("controller.")) {
|
||||
context = context.slice(context.indexOf(".") + 1);
|
||||
}
|
||||
|
||||
return val === undefined ? get(firstContext, context) : val;
|
||||
};
|
||||
|
||||
// #each .. in support (as format is transformed to this)
|
||||
hbs.registerHelper(
|
||||
"each",
|
||||
function (localName, inKeyword, contextName, options) {
|
||||
if (typeof contextName === "undefined") {
|
||||
return;
|
||||
}
|
||||
let list = get(this, contextName);
|
||||
let output = [];
|
||||
let innerContext = options.contexts[0];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
innerContext[localName] = list[i];
|
||||
output.push(options.fn(innerContext));
|
||||
}
|
||||
delete innerContext[localName];
|
||||
return output.join("");
|
||||
}
|
||||
);
|
||||
|
||||
function stringCompatHelper(fn) {
|
||||
const old = hbs.helpers[fn];
|
||||
hbs.helpers[fn] = function (context, options) {
|
||||
return old.apply(this, [hbs.helpers.get(context, options), options]);
|
||||
};
|
||||
}
|
||||
|
||||
// HACK: Ensure that the variable is resolved only once.
|
||||
// The "get" function will be called twice because both `if` and `unless`
|
||||
// helpers are patched to resolve the variable and `unless` is implemented
|
||||
// as not `if`. For example, for {{#unless var}} will generate a stack
|
||||
// trace like:
|
||||
//
|
||||
// - patched-unless("var") "var" is resolved to its value, val
|
||||
// - unless(val) unless is implemented as !if
|
||||
// - !patched-if(val) val is already resolved, but it is resolved again
|
||||
// - !if(???) at this point, ??? usually stands for undefined
|
||||
//
|
||||
// The following code ensures that patched-unless will call `if` directly,
|
||||
// `patched-unless("var")` will return `!if(val)`.
|
||||
const oldIf = hbs.helpers["if"];
|
||||
hbs.helpers["unless"] = function (context, options) {
|
||||
return oldIf.apply(this, [
|
||||
hbs.helpers.get(context, options),
|
||||
{
|
||||
fn: options.inverse,
|
||||
inverse: options.fn,
|
||||
hash: options.hash,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
stringCompatHelper("if");
|
||||
stringCompatHelper("with");
|
||||
}
|
||||
|
||||
function lazyLoadHelpers(hbs, owner) {
|
||||
// Reimplements `helperMissing` so that it triggers a lookup() for
|
||||
// a helper of that name. Means we don't need to eagerly load all
|
||||
// helpers/* files during boot.
|
||||
hbs.registerHelper("helperMissing", function (...args) {
|
||||
const opts = args[args.length - 1];
|
||||
if (opts?.name) {
|
||||
// Lookup and evaluate the relevant module. Raw helpers may be registered as a side effect
|
||||
owner.lookup(`helper:${opts.name}`);
|
||||
|
||||
if (hbs.helpers[opts.name]) {
|
||||
// Helper now exists, invoke it
|
||||
return hbs.helpers[opts.name]?.call(this, ...arguments);
|
||||
} else {
|
||||
// Not a helper, treat as property
|
||||
return hbs.helpers["get"].call(this, ...arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
// This is a mechanism for quickly rendering templates which is Ember aware
|
||||
// templates are highly compatible with Ember so you don't need to worry about calling "get"
|
||||
// and discourseComputed properties function, additionally it uses stringParams like Ember does
|
||||
const RawHandlebars = Handlebars.create();
|
||||
|
||||
function buildPath(blk, args) {
|
||||
let result = {
|
||||
type: "PathExpression",
|
||||
data: false,
|
||||
depth: blk.path.depth,
|
||||
loc: blk.path.loc,
|
||||
};
|
||||
|
||||
// Server side precompile doesn't have jquery.extend
|
||||
Object.keys(args).forEach(function (a) {
|
||||
result[a] = args[a];
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function replaceGet(ast) {
|
||||
let visitor = new Handlebars.Visitor();
|
||||
visitor.mutating = true;
|
||||
|
||||
visitor.MustacheStatement = function (mustache) {
|
||||
if (!(mustache.params.length || mustache.hash)) {
|
||||
mustache.params[0] = mustache.path;
|
||||
mustache.path = buildPath(mustache, {
|
||||
parts: ["get"],
|
||||
original: "get",
|
||||
strict: true,
|
||||
falsy: true,
|
||||
});
|
||||
}
|
||||
return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache);
|
||||
};
|
||||
|
||||
// rewrite `each x as |y|` as each y in x`
|
||||
// This allows us to use the same syntax in all templates
|
||||
visitor.BlockStatement = function (block) {
|
||||
if (block.path.original === "each" && block.params.length === 1) {
|
||||
let paramName = block.program.blockParams[0];
|
||||
block.params = [
|
||||
buildPath(block, { original: paramName }),
|
||||
{ type: "CommentStatement", value: "in" },
|
||||
block.params[0],
|
||||
];
|
||||
delete block.program.blockParams;
|
||||
}
|
||||
|
||||
return Handlebars.Visitor.prototype.BlockStatement.call(this, block);
|
||||
};
|
||||
|
||||
visitor.accept(ast);
|
||||
}
|
||||
|
||||
if (Handlebars.Compiler) {
|
||||
RawHandlebars.Compiler = function () {};
|
||||
RawHandlebars.Compiler.prototype = Object.create(
|
||||
Handlebars.Compiler.prototype
|
||||
);
|
||||
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
|
||||
|
||||
RawHandlebars.JavaScriptCompiler = function () {};
|
||||
|
||||
RawHandlebars.JavaScriptCompiler.prototype = Object.create(
|
||||
Handlebars.JavaScriptCompiler.prototype
|
||||
);
|
||||
RawHandlebars.JavaScriptCompiler.prototype.compiler =
|
||||
RawHandlebars.JavaScriptCompiler;
|
||||
RawHandlebars.JavaScriptCompiler.prototype.namespace = "RawHandlebars";
|
||||
|
||||
RawHandlebars.precompile = function (value, asObject, { plugins = [] } = {}) {
|
||||
let ast = Handlebars.parse(value);
|
||||
replaceGet(ast);
|
||||
plugins.forEach((plugin) => plugin(ast));
|
||||
|
||||
let options = {
|
||||
knownHelpers: {
|
||||
get: true,
|
||||
},
|
||||
data: true,
|
||||
stringParams: true,
|
||||
};
|
||||
|
||||
asObject = asObject === undefined ? true : asObject;
|
||||
|
||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
||||
return new RawHandlebars.JavaScriptCompiler().compile(
|
||||
environment,
|
||||
options,
|
||||
undefined,
|
||||
asObject
|
||||
);
|
||||
};
|
||||
|
||||
RawHandlebars.compile = function (string, { plugins = [] } = {}) {
|
||||
let ast = Handlebars.parse(string);
|
||||
replaceGet(ast);
|
||||
plugins.forEach((plugin) => plugin(ast));
|
||||
|
||||
// this forces us to rewrite helpers
|
||||
let options = { data: true, stringParams: true };
|
||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
||||
let templateSpec = new RawHandlebars.JavaScriptCompiler().compile(
|
||||
environment,
|
||||
options,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
let t = RawHandlebars.template(templateSpec);
|
||||
t.isMethod = false;
|
||||
|
||||
return t;
|
||||
};
|
||||
}
|
||||
|
||||
export function template() {
|
||||
return RawHandlebars.template.apply(this, arguments);
|
||||
}
|
||||
|
||||
export function precompile() {
|
||||
return RawHandlebars.precompile.apply(this, arguments);
|
||||
}
|
||||
|
||||
export function compile() {
|
||||
return RawHandlebars.compile.apply(this, arguments);
|
||||
}
|
||||
|
||||
export default RawHandlebars;
|
@ -1,55 +0,0 @@
|
||||
import { getOwner } from "@ember/owner";
|
||||
import { schedule } from "@ember/runloop";
|
||||
|
||||
let counter = 0;
|
||||
|
||||
/**
|
||||
* Generate HTML which can be inserted into a raw-hbs template to render a Glimmer component.
|
||||
* The result of this function must be rendered immediately, so that an `afterRender` hook
|
||||
* can access the element in the DOM and attach the glimmer component.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```hbs
|
||||
* {{! raw-templates/something-cool.hbr }}
|
||||
* {{html-safe view.html}}
|
||||
* ```
|
||||
*
|
||||
* ```gjs
|
||||
* // raw-views/something-cool.gjs
|
||||
* import EmberObject from "@ember/object";
|
||||
* import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
||||
*
|
||||
* export default class SomethingCool extends EmberObject {
|
||||
* get html() {
|
||||
* return rawRenderGlimmer(this, "div", <template>Hello {{@data.name}}</template>, { name: this.name });
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* And then this can be invoked from any other raw view (including raw plugin outlets) like:
|
||||
*
|
||||
* ```hbs
|
||||
* {{raw "something-cool" name="david"}}
|
||||
* ```
|
||||
*/
|
||||
export default function rawRenderGlimmer(owner, renderInto, component, data) {
|
||||
const renderGlimmerService = getOwner(owner).lookup("service:render-glimmer");
|
||||
|
||||
counter++;
|
||||
const id = `_render_glimmer_${counter}`;
|
||||
const [type, ...classNames] = renderInto.split(".");
|
||||
|
||||
schedule("afterRender", () => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
const componentInfo = {
|
||||
element,
|
||||
component,
|
||||
data,
|
||||
};
|
||||
renderGlimmerService.add(componentInfo);
|
||||
}
|
||||
});
|
||||
|
||||
return `<${type} id="${id}" class="${classNames.join(" ")}"></${type}>`;
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
import require from "require";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
||||
import { getResolverOption } from "discourse/resolver";
|
||||
|
||||
export const __DISCOURSE_RAW_TEMPLATES = {};
|
||||
let _needsHbrTopicList = false;
|
||||
|
||||
export function needsHbrTopicList(value) {
|
||||
if (value === undefined) {
|
||||
return _needsHbrTopicList;
|
||||
} else {
|
||||
_needsHbrTopicList = value;
|
||||
}
|
||||
}
|
||||
|
||||
export function resetNeedsHbrTopicList() {
|
||||
_needsHbrTopicList = false;
|
||||
}
|
||||
|
||||
const TOPIC_LIST_TEMPLATE_NAMES = [
|
||||
"list/action-list",
|
||||
"list/activity-column",
|
||||
"list/category-column",
|
||||
"list/new-list-header-controls",
|
||||
"list/participant-groups",
|
||||
"list/post-count-or-badges",
|
||||
"list/posters-column",
|
||||
"list/posts-count-column",
|
||||
"list/topic-excerpt",
|
||||
"list/topic-list-item",
|
||||
"list/unread-indicator",
|
||||
"list/visited-line",
|
||||
"mobile/list/topic-list-item",
|
||||
"topic-bulk-select-dropdown",
|
||||
"topic-list-header-column",
|
||||
"topic-list-header",
|
||||
"topic-post-badges",
|
||||
"topic-status",
|
||||
];
|
||||
|
||||
export function addRawTemplate(name, template, opts = {}) {
|
||||
const cleanName = name.replace(/^javascripts\//, "");
|
||||
if (
|
||||
(TOPIC_LIST_TEMPLATE_NAMES.includes(cleanName) ||
|
||||
name.includes("/connectors/")) &&
|
||||
!opts.core &&
|
||||
!opts.hasModernReplacement
|
||||
) {
|
||||
const message = `[${name}] hbr topic-list template overrides and connectors are deprecated. Use the value transformer \`topic-list-columns\` and other new topic-list plugin APIs instead.`;
|
||||
|
||||
// NOTE: addRawTemplate is called too early for deprecation handlers to process this:
|
||||
deprecated(message, RAW_TOPIC_LIST_DEPRECATION_OPTIONS);
|
||||
|
||||
needsHbrTopicList(true);
|
||||
}
|
||||
|
||||
// Core templates should never overwrite themes / plugins
|
||||
if (opts.core && __DISCOURSE_RAW_TEMPLATES[name]) {
|
||||
return;
|
||||
}
|
||||
__DISCOURSE_RAW_TEMPLATES[name] = template;
|
||||
}
|
||||
|
||||
export function removeRawTemplate(name) {
|
||||
delete __DISCOURSE_RAW_TEMPLATES[name];
|
||||
}
|
||||
|
||||
export function findRawTemplate(name) {
|
||||
if (getResolverOption("mobileView")) {
|
||||
return (
|
||||
__DISCOURSE_RAW_TEMPLATES[`javascripts/mobile/${name}`] ||
|
||||
__DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] ||
|
||||
__DISCOURSE_RAW_TEMPLATES[`mobile/${name}`] ||
|
||||
__DISCOURSE_RAW_TEMPLATES[name]
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
__DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] ||
|
||||
__DISCOURSE_RAW_TEMPLATES[name]
|
||||
);
|
||||
}
|
||||
|
||||
export function buildRawConnectorCache() {
|
||||
let result = {};
|
||||
Object.keys(__DISCOURSE_RAW_TEMPLATES).forEach((resource) => {
|
||||
const segments = resource.split("/");
|
||||
const connectorIndex = segments.indexOf("connectors");
|
||||
|
||||
if (connectorIndex >= 0) {
|
||||
const outletName = segments[connectorIndex + 1];
|
||||
result[outletName] ??= [];
|
||||
result[outletName].push({
|
||||
template: __DISCOURSE_RAW_TEMPLATES[resource],
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function eagerLoadRawTemplateModules() {
|
||||
for (const key of Object.keys(requirejs.entries)) {
|
||||
if (key.includes("/raw-templates/")) {
|
||||
require(key);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import Handlebars from "handlebars";
|
||||
import $ from "jquery";
|
||||
import * as AvatarUtils from "discourse/lib/avatar-utils";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
@ -41,11 +40,6 @@ export function escapeExpression(string) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
return escape(string);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ loaderShim("a11y-dialog", () => importSync("a11y-dialog"));
|
||||
loaderShim("discourse-i18n", () => importSync("discourse-i18n"));
|
||||
loaderShim("ember-modifier", () => importSync("ember-modifier"));
|
||||
loaderShim("ember-route-template", () => importSync("ember-route-template"));
|
||||
loaderShim("handlebars", () => importSync("handlebars"));
|
||||
loaderShim("jquery", () => importSync("jquery"));
|
||||
loaderShim("js-yaml", () => importSync("js-yaml"));
|
||||
loaderShim("message-bus-client", () => importSync("message-bus-client"));
|
||||
|
@ -89,7 +89,6 @@ export default class Site extends RestModel {
|
||||
@sort("categories", "topicCountDesc") categoriesByCount;
|
||||
|
||||
#glimmerPostStreamEnabled;
|
||||
#glimmerTopicDecision;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
@ -161,51 +160,6 @@ export default class Site extends RestModel {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
get useGlimmerTopicList() {
|
||||
if (this.#glimmerTopicDecision !== undefined) {
|
||||
// Caches the decision after the first call, and avoids re-printing the same message
|
||||
return this.#glimmerTopicDecision;
|
||||
}
|
||||
|
||||
let decision;
|
||||
|
||||
const { needsHbrTopicList } = require("discourse/lib/raw-templates");
|
||||
|
||||
/* eslint-disable no-console */
|
||||
const settingValue = this.siteSettings.glimmer_topic_list_mode;
|
||||
if (settingValue === "enabled") {
|
||||
if (needsHbrTopicList()) {
|
||||
console.log(
|
||||
"⚠️ Using the new 'glimmer' topic list, even though some themes/plugins are not ready"
|
||||
);
|
||||
} else {
|
||||
console.log("✅ Using the new 'glimmer' topic list");
|
||||
}
|
||||
|
||||
decision = true;
|
||||
} else if (settingValue === "disabled") {
|
||||
decision = false;
|
||||
} else {
|
||||
// auto
|
||||
if (needsHbrTopicList()) {
|
||||
console.log(
|
||||
"⚠️ Detected themes/plugins which are incompatible with the new 'glimmer' topic-list. Falling back to old implementation."
|
||||
);
|
||||
decision = false;
|
||||
} else {
|
||||
if (!isTesting() && !isRailsTesting()) {
|
||||
console.log("✅ Using the new 'glimmer' topic list");
|
||||
}
|
||||
decision = true;
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
|
||||
this.#glimmerTopicDecision = decision;
|
||||
|
||||
return decision;
|
||||
}
|
||||
|
||||
@computed("categories.[]")
|
||||
get categoriesById() {
|
||||
const map = new Map();
|
||||
|
@ -1,3 +0,0 @@
|
||||
<button class='btn-transparent {{class}}' title='{{i18n "topics.bulk.toggle"}}'>
|
||||
{{d-icon icon}}
|
||||
</button>
|
@ -1,8 +0,0 @@
|
||||
{{#if postNumbers}}
|
||||
<div class='post-actions {{className}}'>
|
||||
{{d-icon icon}}
|
||||
{{#each postNumbers as |postNumber|}}
|
||||
<a href='{{topic.url}}/{{postNumber}}'>#{{postNumber}}</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
@ -1,7 +0,0 @@
|
||||
<{{tagName}} class="{{class}} {{cold-age-class topic.createdAt startDate=topic.bumpedAt class=""}} activity" title="{{html-safe topic.bumpedAtTitle}}">
|
||||
<a class="post-activity" href="{{topic.lastPostUrl}}">
|
||||
{{~raw-plugin-outlet name="topic-list-before-relative-date"~}}
|
||||
{{~plugin-outlet name="topic-list-before-relative-date" outletArgs=(raw-hash topic=topic)~}}
|
||||
{{~format-date topic.bumpedAt format="tiny" noTitle="true"~}}
|
||||
</a>
|
||||
</{{tagName}}>
|
@ -1 +0,0 @@
|
||||
<td class='category topic-list-data'>{{category-link category}}</td>
|
@ -1,13 +0,0 @@
|
||||
{{#if view.staticLabel}}
|
||||
<span class="static-label">{{view.staticLabel}}</span>
|
||||
{{else}}
|
||||
<button class="topics-replies-toggle --all{{#if view.allActive}} active{{/if}}">
|
||||
{{i18n "filters.new.all"}}
|
||||
</button>
|
||||
<button class="topics-replies-toggle --topics{{#if view.topicsActive}} active{{/if}}">
|
||||
{{view.topicsButtonLabel}}
|
||||
</button>
|
||||
<button class="topics-replies-toggle --replies{{#if view.repliesActive}} active{{/if}}">
|
||||
{{view.repliesButtonLabel}}
|
||||
</button>
|
||||
{{/if}}
|
@ -1,18 +0,0 @@
|
||||
<div
|
||||
class="participant-group-wrapper"
|
||||
role="list"
|
||||
aria-label="{{i18n 'topic.participant_groups'}}"
|
||||
>
|
||||
{{#each groups as |group|}}
|
||||
<div class="participant-group">
|
||||
<a
|
||||
class="user-group trigger-group-card"
|
||||
href="{{group.url}}"
|
||||
data-group-card="{{group.name}}"
|
||||
>
|
||||
{{d-icon "users"}}
|
||||
{{group.name}}
|
||||
</a>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
{{#if view.showBadges}}
|
||||
{{raw "topic-post-badges" unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
|
||||
{{else}}
|
||||
{{raw "list/posts-count-column" topic=topic tagName="div"}}
|
||||
{{/if}}
|
@ -1,9 +0,0 @@
|
||||
<td class='posters topic-list-data'>
|
||||
{{#each posters as |poster|}}
|
||||
{{#if poster.moreCount}}
|
||||
<a class="posters-more-count">{{poster.moreCount}}</a>
|
||||
{{else}}
|
||||
<a href="{{poster.user.path}}" data-user-card="{{poster.user.username}}" class="{{poster.extraClasses}}" aria-label="{{i18n "user.profile_possessive" username=poster.user.username}}">{{avatar poster avatarTemplatePath="user.avatar_template" usernamePath="user.username" namePath="user.name" imageSize="small"}}</a>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</td>
|
@ -1,7 +0,0 @@
|
||||
<{{view.tagName}} class='num posts-map posts {{view.likesHeat}} topic-list-data'>
|
||||
<button class="btn-link posts-map badge-posts {{view.likesHeat}}" title="{{view.title}}" aria-label="{{view.title}}">
|
||||
{{raw-plugin-outlet name="topic-list-before-reply-count"}}
|
||||
{{plugin-outlet name="topic-list-before-reply-count" outletArgs=(raw-hash topic=topic)}}
|
||||
{{number topic.replyCount noTitle="true"}}
|
||||
</button>
|
||||
</{{view.tagName}}>
|
@ -1,8 +0,0 @@
|
||||
{{#if topic.hasExcerpt}}
|
||||
<a href="{{topic.url}}" class="topic-excerpt">
|
||||
{{dir-span topic.escapedExcerpt htmlSafe="true"}}
|
||||
{{#if topic.excerptTruncated}}
|
||||
<span class="topic-excerpt-more">{{i18n 'read_more'}}</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
{{/if}}
|
@ -1,97 +0,0 @@
|
||||
{{~raw-plugin-outlet name="topic-list-before-columns"}}
|
||||
|
||||
{{#if bulkSelectEnabled}}
|
||||
<td class="bulk-select topic-list-data">
|
||||
<label for="bulk-select-{{topic.id}}">
|
||||
<input type="checkbox" class="bulk-select" id="bulk-select-{{topic.id}}">
|
||||
</label>
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
{{!--
|
||||
The `~` syntax strip spaces between the elements, making it produce
|
||||
`<a class=topic-post-badges>Some text</a><span class=topic-post-badges>`,
|
||||
with no space between them.
|
||||
This causes the topic-post-badge to be considered the same word as "text"
|
||||
at the end of the link, preventing it from line wrapping onto its own line.
|
||||
--}}
|
||||
<td class='main-link clearfix topic-list-data' colspan="1">
|
||||
{{~raw-plugin-outlet name="topic-list-before-link"}}
|
||||
{{~plugin-outlet name="topic-list-before-link" outletArgs=(raw-hash topic=topic)}}
|
||||
<span class="link-top-line" role="heading" aria-level="2">
|
||||
{{~raw-plugin-outlet name="topic-list-before-status"}}
|
||||
{{~plugin-outlet name="topic-list-before-status" outletArgs=(raw-hash topic=topic)}}
|
||||
{{~raw "topic-status" topic=topic}}
|
||||
{{~topic-link topic class="raw-link raw-topic-link"}}
|
||||
{{~#if topic.featured_link}}
|
||||
{{~topic-featured-link topic}}
|
||||
{{~/if}}
|
||||
{{~raw-plugin-outlet name="topic-list-after-title"}}
|
||||
{{~plugin-outlet name="topic-list-after-title" outletArgs=(raw-hash topic=topic)}}
|
||||
{{~raw "list/unread-indicator" includeUnreadIndicator=includeUnreadIndicator
|
||||
topicId=topic.id
|
||||
unreadClass=unreadClass~}}
|
||||
{{~#if showTopicPostBadges}}
|
||||
{{~raw "topic-post-badges" unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
|
||||
{{~/if}}
|
||||
</span>
|
||||
<div class="link-bottom-line">
|
||||
{{#unless hideCategory}}
|
||||
{{#unless topic.isPinnedUncategorized}}
|
||||
{{~raw-plugin-outlet name="topic-list-before-category"}}
|
||||
{{~plugin-outlet name="topic-list-before-category" outletArgs=(raw-hash topic=topic)}}
|
||||
{{category-link topic.category}}
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
{{discourse-tags topic mode="list" tagsForUser=tagsForUser}}
|
||||
{{#if participantGroups}}
|
||||
{{raw "list/participant-groups" groups=participantGroups}}
|
||||
{{/if}}
|
||||
{{raw "list/action-list" topic=topic postNumbers=topic.liked_post_numbers className="likes" icon="heart"}}
|
||||
</div>
|
||||
{{#if expandPinned}}
|
||||
{{raw "list/topic-excerpt" topic=topic}}
|
||||
{{/if}}
|
||||
|
||||
{{~raw-plugin-outlet name="topic-list-main-link-bottom"}}
|
||||
{{~plugin-outlet name="topic-list-main-link-bottom" outletArgs=(raw-hash topic=topic)}}
|
||||
</td>
|
||||
|
||||
{{~raw-plugin-outlet name="topic-list-after-main-link"}}
|
||||
{{~plugin-outlet name="topic-list-after-main-link" outletArgs=(raw-hash topic=topic)}}
|
||||
|
||||
{{#if showPosters}}
|
||||
{{raw "list/posters-column" posters=topic.featuredUsers}}
|
||||
{{/if}}
|
||||
|
||||
{{raw "list/posts-count-column" topic=topic}}
|
||||
|
||||
{{#if showLikes}}
|
||||
<td class="num likes topic-list-data">
|
||||
{{#if hasLikes}}
|
||||
<a href='{{topic.summaryUrl}}'>
|
||||
{{number topic.like_count}} {{d-icon "heart"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
{{#if showOpLikes}}
|
||||
<td class="num likes">
|
||||
{{#if hasOpLikes}}
|
||||
<a href='{{topic.summaryUrl}}'>
|
||||
{{number topic.op_like_count}} {{d-icon "heart"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
<td class="num views {{topic.viewsHeat}} topic-list-data">
|
||||
{{raw-plugin-outlet name="topic-list-before-view-count"}}
|
||||
{{plugin-outlet name="topic-list-before-view-count" outletArgs=(raw-hash topic=topic)}}
|
||||
{{number topic.views numberKey="views_long"}}
|
||||
</td>
|
||||
|
||||
{{raw "list/activity-column" topic=topic class="num topic-list-data" tagName="td"}}
|
||||
|
||||
{{~raw-plugin-outlet name="topic-list-after-columns"}}
|
@ -1,5 +0,0 @@
|
||||
{{~#if includeUnreadIndicator~}}
|
||||
<span class='badge badge-notification unread-indicator indicator-topic-{{topicId}} {{unreadClass}}' title='{{i18n "topic.unread_indicator"}}'>
|
||||
{{~d-icon "asterisk"}}
|
||||
</span>
|
||||
{{~/if}}
|
@ -1,9 +0,0 @@
|
||||
{{#if view.isLastVisited}}
|
||||
<tr class='topic-list-item-separator'>
|
||||
<td class="topic-list-data" colspan="6">
|
||||
<span>
|
||||
{{i18n 'topics.new_messages_marker'}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
@ -1,63 +0,0 @@
|
||||
<td class="topic-list-data">
|
||||
{{~raw-plugin-outlet name="topic-list-before-columns"}}
|
||||
<div class='pull-left'>
|
||||
{{#if bulkSelectEnabled}}
|
||||
<label for="bulk-select-{{topic.id}}">
|
||||
<input type="checkbox" class="bulk-select" id="bulk-select-{{topic.id}}">
|
||||
</label>
|
||||
{{else}}
|
||||
<a href="{{topic.lastPostUrl}}" aria-label="{{i18n 'latest_poster_link' username=topic.lastPosterUser.username}}" data-user-card="{{topic.lastPosterUser.username}}">{{avatar topic.lastPosterUser imageSize="large"}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='topic-item-metadata right'>
|
||||
{{!--
|
||||
The `~` syntax strip spaces between the elements, making it produce
|
||||
`<a class=topic-post-badges>Some text</a><span class=topic-post-badges>`,
|
||||
with no space between them.
|
||||
This causes the topic-post-badge to be considered the same word as "text"
|
||||
at the end of the link, preventing it from line wrapping onto its own line.
|
||||
--}}
|
||||
{{~raw-plugin-outlet name="topic-list-before-link"}}
|
||||
{{~plugin-outlet name="topic-list-before-link" outletArgs=(raw-hash topic=topic)}}
|
||||
<div class='main-link'>
|
||||
{{~raw-plugin-outlet name="topic-list-before-status"}}
|
||||
{{~plugin-outlet name="topic-list-before-status" outletArgs=(raw-hash topic=topic)}}
|
||||
{{~raw "topic-status" topic=topic~}}
|
||||
{{~topic-link topic class="raw-link raw-topic-link"}}
|
||||
{{~#if topic.featured_link~}}
|
||||
{{~topic-featured-link topic~}}
|
||||
{{~/if~}}
|
||||
{{~raw-plugin-outlet name="topic-list-after-title"}}
|
||||
{{~plugin-outlet name="topic-list-after-title" outletArgs=(raw-hash topic=topic)}}
|
||||
{{~#if topic.unseen~}}
|
||||
<span class="topic-post-badges"> <span class="badge-notification new-topic"></span></span>
|
||||
{{~/if~}}
|
||||
{{~#if expandPinned~}}
|
||||
{{~raw "list/topic-excerpt" topic=topic~}}
|
||||
{{~/if~}}
|
||||
{{~raw-plugin-outlet name="topic-list-main-link-bottom"}}
|
||||
{{~plugin-outlet name="topic-list-main-link-bottom" outletArgs=(raw-hash topic=topic)}}
|
||||
</div>
|
||||
{{~raw-plugin-outlet name="topic-list-after-main-link"}}
|
||||
{{~plugin-outlet name="topic-list-after-main-link" outletArgs=(raw-hash topic=topic)}}
|
||||
<div class='pull-right'>
|
||||
{{raw "list/post-count-or-badges" topic=topic postBadgesEnabled=showTopicPostBadges}}
|
||||
</div>
|
||||
<div class="topic-item-stats clearfix">
|
||||
<span class="topic-item-stats__category-tags">
|
||||
{{#unless hideCategory}}
|
||||
{{~raw-plugin-outlet name="topic-list-before-category"}}
|
||||
{{~plugin-outlet name="topic-list-before-category" outletArgs=(raw-hash topic=topic)}}
|
||||
{{category-link topic.category~}}
|
||||
{{~/unless}}
|
||||
{{~discourse-tags topic mode="list"}}
|
||||
</span>
|
||||
<div class='num activity last'>
|
||||
<span class="age activity" title="{{topic.bumpedAtTitle}}"><a
|
||||
href="{{topic.lastPostUrl}}">{{format-date topic.bumpedAt format="tiny" noTitle="true"}}</a>
|
||||
</span>
|
||||
</div>
|
||||
{{~plugin-outlet name="topic-list-after-topic-item-stats" outletArgs=(raw-hash topic=topic)}}
|
||||
</div>
|
||||
{{~raw-plugin-outlet name="topic-list-after-columns"}}
|
||||
</td>
|
@ -1 +0,0 @@
|
||||
{{{view.html}}}
|
@ -1,35 +0,0 @@
|
||||
<th data-sort-order='{{order}}' class='{{view.className}} topic-list-data' scope="col" {{#if view.ariaSort}}aria-sort='{{view.ariaSort}}'{{/if}}>
|
||||
{{~#if canBulkSelect}}
|
||||
{{~#if showBulkToggle}}
|
||||
{{raw "flat-button" class="bulk-select" icon="list-check" title="topics.bulk.toggle"}}
|
||||
{{/if ~}}
|
||||
{{~#if bulkSelectEnabled}}
|
||||
<span class='bulk-select-topics'>
|
||||
{{~#if canDoBulkActions}}
|
||||
{{raw "topic-bulk-select-dropdown" bulkSelectHelper=bulkSelectHelper}}
|
||||
{{/if ~}}
|
||||
<button class='btn btn-default bulk-select-all'>{{i18n "topics.bulk.select_all"}}</button>
|
||||
<button class='btn btn-default bulk-clear-all'>{{i18n "topics.bulk.clear_all"}}</button>
|
||||
</span>
|
||||
{{/if ~}}
|
||||
{{/if ~}}
|
||||
{{~#unless bulkSelectEnabled}}
|
||||
{{~#if view.showTopicsAndRepliesToggle}}
|
||||
{{raw "list/new-list-header-controls" current=newListSubset newRepliesCount=newRepliesCount newTopicsCount=newTopicsCount}}
|
||||
{{else}}
|
||||
{{#if sortable}}
|
||||
<button aria-pressed='{{view.ariaPressed}}'>
|
||||
{{view.localizedName}}
|
||||
{{~#if view.isSorting}}
|
||||
{{d-icon view.sortIcon}}
|
||||
{{/if ~}}
|
||||
</button>
|
||||
{{else}}
|
||||
<span {{#if view.screenreaderOnly}}class="sr-only"{{/if}}>
|
||||
{{view.localizedName}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/if ~}}
|
||||
{{/unless ~}}
|
||||
{{~plugin-outlet name="topic-list-heading-bottom" outletArgs=(raw-hash name=view.name bulkSelectEnabled=bulkSelectEnabled)~}}
|
||||
</th>
|
@ -1,26 +0,0 @@
|
||||
{{~raw-plugin-outlet name="topic-list-header-before"~}}
|
||||
{{~plugin-outlet name="topic-list-header-before"~}}
|
||||
{{#if bulkSelectEnabled}}
|
||||
<th class="bulk-select topic-list-data">
|
||||
{{#if canBulkSelect}}
|
||||
{{raw "flat-button" class="bulk-select" icon="list-check" title="topics.bulk.toggle"}}
|
||||
{{/if}}
|
||||
</th>
|
||||
{{/if}}
|
||||
{{raw "topic-list-header-column" order='default' name=listTitle bulkSelectEnabled=bulkSelectEnabled showBulkToggle=toggleInTitle canBulkSelect=canBulkSelect canDoBulkActions=canDoBulkActions showTopicsAndRepliesToggle=showTopicsAndRepliesToggle newListSubset=newListSubset newRepliesCount=newRepliesCount newTopicsCount=newTopicsCount bulkSelectHelper=bulkSelectHelper }}
|
||||
{{raw-plugin-outlet name="topic-list-header-after-main-link"}}
|
||||
{{plugin-outlet name="topic-list-header-after-main-link"}}
|
||||
{{#if showPosters}}
|
||||
{{raw "topic-list-header-column" order='posters' name='posters' screenreaderOnly='true'}}
|
||||
{{/if}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='posts' name='replies'}}
|
||||
{{#if showLikes}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='likes' name='likes'}}
|
||||
{{/if}}
|
||||
{{#if showOpLikes}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='op_likes' name='likes'}}
|
||||
{{/if}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='views' name='views'}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='activity' name='activity'}}
|
||||
{{~raw-plugin-outlet name="topic-list-header-after"~}}
|
||||
{{~plugin-outlet name="topic-list-header-after"~}}
|
@ -1,26 +0,0 @@
|
||||
<span class="topic-post-badges">
|
||||
{{~#if newPosts~}}
|
||||
<a
|
||||
href="{{url}}"
|
||||
class="badge badge-notification unread-posts"
|
||||
title="{{i18n 'topic.unread_posts' count=newPosts}}"
|
||||
aria-label="{{i18n 'topic.unread_posts' count=newPosts}}"
|
||||
>{{newPosts}}</a>
|
||||
{{~/if}}
|
||||
{{~#if unreadPosts~}}
|
||||
<a
|
||||
href="{{url}}"
|
||||
class="badge badge-notification unread-posts"
|
||||
title="{{i18n 'topic.unread_posts' count=unreadPosts}}"
|
||||
aria-label="{{i18n 'topic.unread_posts' count=unreadPosts}}"
|
||||
>{{unreadPosts}}</a>
|
||||
{{~/if}}
|
||||
{{~#if unseen~}}
|
||||
<a
|
||||
href="{{url}}"
|
||||
class="badge badge-notification new-topic"
|
||||
title="{{i18n 'topic.new'}}"
|
||||
aria-label="{{i18n 'topic.new'}}"
|
||||
>{{newDotText}}</a>
|
||||
{{~/if}}
|
||||
</span>
|
@ -1,14 +0,0 @@
|
||||
{{~#if view.renderDiv ~}}
|
||||
<div class='topic-statuses'>
|
||||
{{/if ~}}
|
||||
{{~#each view.statuses as |status|~}}
|
||||
{{~#if status.href ~}}
|
||||
<a href='{{status.href}}' title='{{status.title}}' class='topic-status {{status.extraClasses}}'>{{d-icon status.icon}}</a>
|
||||
{{~else ~}}
|
||||
<{{status.openTag}} title='{{status.title}}' class='topic-status'>{{d-icon status.icon class=status.key}}</{{status.closeTag}}>
|
||||
{{~/if ~}}
|
||||
{{~/each}}
|
||||
{{~#if view.showDefault~}}{{d-icon view.showDefault}}{{~/if ~}}
|
||||
{{~#if view.renderDiv ~}}
|
||||
</div>
|
||||
{{/if ~}}
|
@ -1,57 +0,0 @@
|
||||
import EmberObject from "@ember/object";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class NewListHeaderControls extends EmberObject {
|
||||
@discourseComputed
|
||||
topicsActive() {
|
||||
return this.current === "topics";
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
repliesActive() {
|
||||
return this.current === "replies";
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
allActive() {
|
||||
return !this.topicsActive && !this.repliesActive;
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
repliesButtonLabel() {
|
||||
if (this.newRepliesCount > 0) {
|
||||
return i18n("filters.new.replies_with_count", {
|
||||
count: this.newRepliesCount,
|
||||
});
|
||||
} else {
|
||||
return i18n("filters.new.replies");
|
||||
}
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
topicsButtonLabel() {
|
||||
if (this.newTopicsCount > 0) {
|
||||
return i18n("filters.new.topics_with_count", {
|
||||
count: this.newTopicsCount,
|
||||
});
|
||||
} else {
|
||||
return i18n("filters.new.topics");
|
||||
}
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
staticLabel() {
|
||||
if (this.noStaticLabel) {
|
||||
return null;
|
||||
}
|
||||
if (this.newTopicsCount > 0 && this.newRepliesCount > 0) {
|
||||
return null;
|
||||
}
|
||||
if (this.newTopicsCount > 0) {
|
||||
return this.topicsButtonLabel;
|
||||
} else {
|
||||
return this.repliesButtonLabel;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import EmberObject from "@ember/object";
|
||||
import { and } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class PostCountOrBadges extends EmberObject {
|
||||
@and("postBadgesEnabled", "topic.unread_posts") showBadges;
|
||||
|
||||
@discourseComputed
|
||||
newDotText() {
|
||||
return this.currentUser && this.currentUser.trust_level > 0
|
||||
? ""
|
||||
: i18n("filters.new.lower_title");
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import EmberObject from "@ember/object";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class PostsCountColumn extends EmberObject {
|
||||
tagName = "td";
|
||||
|
||||
@discourseComputed("topic.like_count", "topic.posts_count")
|
||||
ratio(likeCount, postCount) {
|
||||
const likes = parseFloat(likeCount);
|
||||
const posts = parseFloat(postCount);
|
||||
|
||||
if (posts < 10) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (likes || 0) / posts;
|
||||
}
|
||||
|
||||
@discourseComputed("topic.replyCount", "ratioText")
|
||||
title(count, ratio) {
|
||||
return I18n.messageFormat("posts_likes_MF", {
|
||||
count,
|
||||
ratio,
|
||||
});
|
||||
}
|
||||
|
||||
@discourseComputed("ratio")
|
||||
ratioText(ratio) {
|
||||
const settings = this.siteSettings;
|
||||
if (ratio > settings.topic_post_like_heat_high) {
|
||||
return "high";
|
||||
}
|
||||
if (ratio > settings.topic_post_like_heat_medium) {
|
||||
return "med";
|
||||
}
|
||||
if (ratio > settings.topic_post_like_heat_low) {
|
||||
return "low";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@discourseComputed("ratioText")
|
||||
likesHeat(ratioText) {
|
||||
if (ratioText && ratioText.length) {
|
||||
return `heatmap-${ratioText}`;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import EmberObject from "@ember/object";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
|
||||
export default class VisitedLine extends EmberObject {
|
||||
@discourseComputed
|
||||
isLastVisited() {
|
||||
return this.lastVisitedTopic === this.topic;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import EmberObject, { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import BulkSelectTopicsDropdown from "discourse/components/bulk-select-topics-dropdown";
|
||||
import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
const BulkSelectGlimmerWrapper = <template>
|
||||
<span class="bulk-select-topic-dropdown__count">
|
||||
{{i18n "topics.bulk.selected_count" count=@data.selectedCount}}
|
||||
</span>
|
||||
<BulkSelectTopicsDropdown
|
||||
@bulkSelectHelper={{@data.bulkSelectHelper}}
|
||||
@afterBulkActionComplete={{@data.afterBulkAction}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export default class extends EmberObject {
|
||||
@service router;
|
||||
|
||||
get selectedCount() {
|
||||
return this.bulkSelectHelper.selected.length;
|
||||
}
|
||||
|
||||
@action
|
||||
afterBulkAction() {
|
||||
return this.router.refresh();
|
||||
}
|
||||
|
||||
get html() {
|
||||
return rawRenderGlimmer(
|
||||
this,
|
||||
"div.bulk-select-topics-dropdown",
|
||||
BulkSelectGlimmerWrapper,
|
||||
{
|
||||
bulkSelectHelper: this.bulkSelectHelper,
|
||||
selectedCount: this.selectedCount,
|
||||
afterBulkAction: this.afterBulkAction,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
import EmberObject from "@ember/object";
|
||||
import { and } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class TopicListHeaderColumn extends EmberObject {
|
||||
sortable = null;
|
||||
|
||||
@and("sortable", "isSorting") ariaPressed;
|
||||
|
||||
@discourseComputed
|
||||
localizedName() {
|
||||
if (this.forceName) {
|
||||
return this.forceName;
|
||||
}
|
||||
|
||||
return this.name ? i18n(this.name) : "";
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
sortIcon() {
|
||||
const isAscending =
|
||||
(
|
||||
this.parent.ascending ||
|
||||
this.parent.context?.ascending ||
|
||||
""
|
||||
).toString() === "true";
|
||||
|
||||
return `chevron-${isAscending ? "up" : "down"}`;
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
isSorting() {
|
||||
return (
|
||||
this.sortable &&
|
||||
(this.parent.order === this.order ||
|
||||
this.parent.context?.order === this.order)
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
className() {
|
||||
const name = [];
|
||||
|
||||
if (this.order) {
|
||||
name.push(this.order);
|
||||
}
|
||||
|
||||
if (this.sortable) {
|
||||
name.push("sortable");
|
||||
|
||||
if (this.isSorting) {
|
||||
name.push("sorting");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.number) {
|
||||
name.push("num");
|
||||
}
|
||||
|
||||
return name.join(" ");
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
ariaSort() {
|
||||
if (this.isSorting) {
|
||||
return this.parent.ascending ? "ascending" : "descending";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
import EmberObject from "@ember/object";
|
||||
import discourseComputed from "discourse/lib/decorators";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class TopicStatus extends EmberObject {
|
||||
static reopen() {
|
||||
deprecated(
|
||||
"Modifying raw-view:topic-status with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
|
||||
return super.reopen(...arguments);
|
||||
}
|
||||
|
||||
static reopenClass() {
|
||||
deprecated(
|
||||
"Modifying raw-view:topic-status with `reopenClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
||||
);
|
||||
|
||||
return super.reopenClass(...arguments);
|
||||
}
|
||||
|
||||
showDefault = null;
|
||||
|
||||
@discourseComputed("defaultIcon")
|
||||
renderDiv(defaultIcon) {
|
||||
return (defaultIcon || this.statuses.length > 0) && !this.noDiv;
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
statuses() {
|
||||
const topic = this.topic;
|
||||
const results = [];
|
||||
|
||||
// TODO, custom statuses? via override?
|
||||
if (topic.is_warning) {
|
||||
results.push({ icon: "envelope", key: "warning" });
|
||||
}
|
||||
|
||||
if (topic.bookmarked) {
|
||||
const postNumbers = topic.bookmarked_post_numbers;
|
||||
let url = topic.url;
|
||||
let extraClasses = "";
|
||||
if (postNumbers && postNumbers[0] > 1) {
|
||||
url += "/" + postNumbers[0];
|
||||
} else {
|
||||
extraClasses = "op-bookmark";
|
||||
}
|
||||
|
||||
results.push({
|
||||
extraClasses,
|
||||
icon: "bookmark",
|
||||
key: "bookmarked",
|
||||
href: url,
|
||||
});
|
||||
}
|
||||
|
||||
if (topic.closed && topic.archived) {
|
||||
results.push({ icon: "lock", key: "locked_and_archived" });
|
||||
} else if (topic.closed) {
|
||||
results.push({ icon: "lock", key: "locked" });
|
||||
} else if (topic.archived) {
|
||||
results.push({ icon: "lock", key: "archived" });
|
||||
}
|
||||
|
||||
if (topic.pinned) {
|
||||
results.push({ icon: "thumbtack", key: "pinned" });
|
||||
}
|
||||
|
||||
if (topic.unpinned) {
|
||||
results.push({ icon: "thumbtack", key: "unpinned" });
|
||||
}
|
||||
|
||||
if (topic.invisible) {
|
||||
results.push({ icon: "far-eye-slash", key: "unlisted" });
|
||||
}
|
||||
|
||||
if (
|
||||
this.showPrivateMessageIcon &&
|
||||
topic.isPrivateMessage &&
|
||||
!topic.is_warning
|
||||
) {
|
||||
results.push({ icon: "envelope", key: "personal_message" });
|
||||
}
|
||||
|
||||
results.forEach((result) => {
|
||||
const translationParams = {};
|
||||
|
||||
if (result.key === "unlisted") {
|
||||
translationParams.unlistedReason = topic.visibilityReasonTranslated;
|
||||
}
|
||||
|
||||
result.title = i18n(
|
||||
`topic_statuses.${result.key}.help`,
|
||||
translationParams
|
||||
);
|
||||
|
||||
if (
|
||||
this.currentUser &&
|
||||
(result.key === "pinned" || result.key === "unpinned")
|
||||
) {
|
||||
result.openTag = "a href";
|
||||
result.closeTag = "a";
|
||||
} else {
|
||||
result.openTag = "span";
|
||||
result.closeTag = "span";
|
||||
}
|
||||
});
|
||||
|
||||
let defaultIcon = this.defaultIcon;
|
||||
if (results.length === 0 && defaultIcon) {
|
||||
this.set("showDefault", defaultIcon);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ export const CRITICAL_DEPRECATIONS = [
|
||||
"discourse.qunit.acceptance-function",
|
||||
"discourse.qunit.global-exists",
|
||||
"discourse.post-stream.trigger-new-post",
|
||||
"discourse.hbr-topic-list-overrides",
|
||||
"discourse.mobile-templates",
|
||||
"discourse.mobile-view",
|
||||
"discourse.mobile-templates",
|
||||
|
@ -1,31 +0,0 @@
|
||||
import Service from "@ember/service";
|
||||
import { TrackedSet } from "@ember-compat/tracked-built-ins";
|
||||
|
||||
/**
|
||||
* This service is responsible for rendering glimmer components into HTML generated
|
||||
* by raw-hbs. It is not intended to be used directly.
|
||||
*
|
||||
* See discourse/lib/raw-render-glimmer.js for usage instructions.
|
||||
*/
|
||||
export default class RenderGlimmerService extends Service {
|
||||
_registrations = new TrackedSet();
|
||||
|
||||
add(info) {
|
||||
this._registrations.add(info);
|
||||
}
|
||||
|
||||
remove(info) {
|
||||
this._registrations.delete(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes registrations for elements which are no longer in the DOM.
|
||||
*/
|
||||
cleanup() {
|
||||
this._registrations.forEach((info) => {
|
||||
if (!document.body.contains(info.element)) {
|
||||
this.remove(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ const { Webpack } = require("@embroider/webpack");
|
||||
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
||||
const { RetryChunkLoadPlugin } = require("webpack-retry-chunk-load-plugin");
|
||||
const withSideWatch = require("./lib/with-side-watch");
|
||||
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
|
||||
const crypto = require("crypto");
|
||||
const commonBabelConfig = require("./lib/common-babel-config");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
@ -65,11 +64,9 @@ module.exports = function (defaults) {
|
||||
...commonBabelConfig(),
|
||||
|
||||
trees: {
|
||||
app: RawHandlebarsCompiler(
|
||||
withSideWatch("app", {
|
||||
watching: ["../discourse-markdown-it", "../truth-helpers"],
|
||||
})
|
||||
),
|
||||
app: withSideWatch("app", {
|
||||
watching: ["../discourse-markdown-it", "../truth-helpers"],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user