DEV: Drop legacy topic-list and raw-handlebars compilation system (#32081)

This commit is contained in:
David Taylor
2025-04-14 10:42:40 +01:00
committed by GitHub
parent 13cb472ec8
commit f0057c7353
127 changed files with 156 additions and 3420 deletions

View File

@ -1,9 +1,6 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
import { renderIcon } from "discourse/lib/icon-library"; import { renderIcon } from "discourse/lib/icon-library";
registerRawHelper("check-icon", checkIcon);
export default function checkIcon(value) { export default function checkIcon(value) {
let icon = value ? "check" : "xmark"; let icon = value ? "check" : "xmark";
return htmlSafe(renderIcon("string", icon)); return htmlSafe(renderIcon("string", icon));

View File

@ -1 +0,0 @@
engine-strict = true

View File

@ -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"
}
}

View File

@ -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;

View File

@ -6,7 +6,6 @@ const Funnel = require("broccoli-funnel");
const mergeTrees = require("broccoli-merge-trees"); const mergeTrees = require("broccoli-merge-trees");
const fs = require("fs"); const fs = require("fs");
const concat = require("broccoli-concat"); const concat = require("broccoli-concat");
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
const DiscoursePluginColocatedTemplateProcessor = require("./colocated-template-compiler"); const DiscoursePluginColocatedTemplateProcessor = require("./colocated-template-compiler");
const EmberApp = require("ember-cli/lib/broccoli/ember-app"); const EmberApp = require("ember-cli/lib/broccoli/ember-app");
@ -15,19 +14,6 @@ function fixLegacyExtensions(tree) {
getDestinationPath: function (relativePath) { getDestinationPath: function (relativePath) {
if (relativePath.endsWith(".es6")) { if (relativePath.endsWith(".es6")) {
return relativePath.slice(0, -4); 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; return relativePath;
@ -201,8 +187,6 @@ module.exports = {
tree = unColocateConnectors(tree); tree = unColocateConnectors(tree);
tree = namespaceModules(tree, pluginName); tree = namespaceModules(tree, pluginName);
tree = RawHandlebarsCompiler(tree);
const colocateBase = `discourse/plugins/${pluginName}`; const colocateBase = `discourse/plugins/${pluginName}`;
tree = new DiscoursePluginColocatedTemplateProcessor( tree = new DiscoursePluginColocatedTemplateProcessor(
tree, tree,

View File

@ -10,7 +10,6 @@
"dependencies": { "dependencies": {
"@babel/core": "^7.26.10", "@babel/core": "^7.26.10",
"deprecation-silencer": "workspace:1.0.0", "deprecation-silencer": "workspace:1.0.0",
"discourse-hbr": "workspace:1.0.0",
"discourse-widget-hbs": "workspace:1.0.0", "discourse-widget-hbs": "workspace:1.0.0",
"ember-cli-babel": "^8.2.0", "ember-cli-babel": "^8.2.0",
"ember-cli-htmlbars": "^6.3.0", "ember-cli-htmlbars": "^6.3.0",

View File

@ -17,8 +17,7 @@
"@babel/core": "^7.26.10", "@babel/core": "^7.26.10",
"ember-auto-import": "^2.10.0", "ember-auto-import": "^2.10.0",
"ember-cli-babel": "^8.2.0", "ember-cli-babel": "^8.2.0",
"ember-cli-htmlbars": "^6.3.0", "ember-cli-htmlbars": "^6.3.0"
"handlebars": "^4.7.8"
}, },
"devDependencies": { "devDependencies": {
"@ember/optional-features": "^2.2.0", "@ember/optional-features": "^2.2.0",

View File

@ -4,7 +4,6 @@ import { service } from "@ember/service";
import { observes } from "@ember-decorators/object"; import { observes } from "@ember-decorators/object";
import $ from "jquery"; import $ from "jquery";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import TopicList from "discourse/components/topic-list";
import List from "discourse/components/topic-list/list"; import List from "discourse/components/topic-list/list";
import discourseComputed, { bind } from "discourse/lib/decorators"; import discourseComputed, { bind } from "discourse/lib/decorators";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
@ -122,35 +121,19 @@ export default class BasicTopicList extends Component {
<template> <template>
<ConditionalLoadingSpinner @condition={{this.loading}}> <ConditionalLoadingSpinner @condition={{this.loading}}>
{{#if this.topics}} {{#if this.topics}}
{{#if this.site.useGlimmerTopicList}} <List
<List @showPosters={{this.showPosters}}
@showPosters={{this.showPosters}} @hideCategory={{this.hideCategory}}
@hideCategory={{this.hideCategory}} @topics={{this.topics}}
@topics={{this.topics}} @expandExcerpts={{this.expandExcerpts}}
@expandExcerpts={{this.expandExcerpts}} @bulkSelectHelper={{this.bulkSelectHelper}}
@bulkSelectHelper={{this.bulkSelectHelper}} @canBulkSelect={{this.canBulkSelect}}
@canBulkSelect={{this.canBulkSelect}} @tagsForUser={{this.tagsForUser}}
@tagsForUser={{this.tagsForUser}} @changeSort={{this.changeSort}}
@changeSort={{this.changeSort}} @order={{this.order}}
@order={{this.order}} @ascending={{this.ascending}}
@ascending={{this.ascending}} @focusLastVisitedTopic={{this.focusLastVisitedTopic}}
@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}}
{{else}} {{else}}
{{#unless this.loadingMore}} {{#unless this.loadingMore}}
<div class="alert alert-info"> <div class="alert alert-info">

View File

@ -19,11 +19,7 @@ export default class CategoriesTopicList extends Component {
{{#if this.topics}} {{#if this.topics}}
{{#each this.topics as |t|}} {{#each this.topics as |t|}}
{{#if this.site.useGlimmerTopicList}} <LatestTopicListItem @topic={{t}} />
<LatestTopicListItem @topic={{t}} />
{{else}}
<LatestTopicListItem @topic={{t}} />
{{/if}}
{{/each}} {{/each}}
<div class="more-topics"> <div class="more-topics">

View File

@ -14,7 +14,6 @@ import NewListHeaderControlsWrapper from "discourse/components/new-list-header-c
import PluginOutlet from "discourse/components/plugin-outlet"; import PluginOutlet from "discourse/components/plugin-outlet";
import TopPeriodButtons from "discourse/components/top-period-buttons"; import TopPeriodButtons from "discourse/components/top-period-buttons";
import TopicDismissButtons from "discourse/components/topic-dismiss-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 List from "discourse/components/topic-list/list";
import basePath from "discourse/helpers/base-path"; import basePath from "discourse/helpers/base-path";
import hideApplicationFooter from "discourse/helpers/hide-application-footer"; import hideApplicationFooter from "discourse/helpers/hide-application-footer";
@ -209,27 +208,15 @@ export default class DiscoveryTopics extends Component {
{{/if}} {{/if}}
{{#if @model.sharedDrafts}} {{#if @model.sharedDrafts}}
{{#if this.site.useGlimmerTopicList}} <List
<List @listTitle="shared_drafts.title"
@listTitle="shared_drafts.title" @top={{this.top}}
@top={{this.top}} @hideCategory="true"
@hideCategory="true" @category={{@category}}
@category={{@category}} @topics={{@model.sharedDrafts}}
@topics={{@model.sharedDrafts}} @discoveryList={{true}}
@discoveryList={{true}} class="shared-drafts"
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}}
{{/if}} {{/if}}
<DiscoveryTopicsList <DiscoveryTopicsList
@ -290,57 +277,30 @@ export default class DiscoveryTopics extends Component {
</span> </span>
{{#if this.hasTopics}} {{#if this.hasTopics}}
{{#if this.site.useGlimmerTopicList}} <List
<List @highlightLastVisited={{true}}
@highlightLastVisited={{true}} @top={{this.top}}
@top={{this.top}} @hot={{this.hot}}
@hot={{this.hot}} @showTopicPostBadges={{this.showTopicPostBadges}}
@showTopicPostBadges={{this.showTopicPostBadges}} @showPosters={{true}}
@showPosters={{true}} @canBulkSelect={{@canBulkSelect}}
@canBulkSelect={{@canBulkSelect}} @bulkSelectHelper={{@bulkSelectHelper}}
@bulkSelectHelper={{@bulkSelectHelper}} @changeSort={{@changeSort}}
@changeSort={{@changeSort}} @hideCategory={{@model.hideCategory}}
@hideCategory={{@model.hideCategory}} @order={{this.order}}
@order={{this.order}} @ascending={{this.ascending}}
@ascending={{this.ascending}} @expandGloballyPinned={{this.expandGloballyPinned}}
@expandGloballyPinned={{this.expandGloballyPinned}} @expandAllPinned={{this.expandAllPinned}}
@expandAllPinned={{this.expandAllPinned}} @category={{@category}}
@category={{@category}} @topics={{@model.topics}}
@topics={{@model.topics}} @discoveryList={{true}}
@discoveryList={{true}} @focusLastVisitedTopic={{true}}
@focusLastVisitedTopic={{true}} @showTopicsAndRepliesToggle={{this.showTopicsAndRepliesToggle}}
@showTopicsAndRepliesToggle={{this.showTopicsAndRepliesToggle}} @newListSubset={{@model.params.subset}}
@newListSubset={{@model.params.subset}} @changeNewListSubset={{@changeNewListSubset}}
@changeNewListSubset={{@changeNewListSubset}} @newRepliesCount={{this.newRepliesCount}}
@newRepliesCount={{this.newRepliesCount}} @newTopicsCount={{this.newTopicsCount}}
@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}}
{{/if}} {{/if}}
<span class="after-topic-list-plugin-outlet-wrapper"> <span class="after-topic-list-plugin-outlet-wrapper">

View File

@ -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}}
&nbsp;{{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>
}

View File

@ -1,13 +1,32 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { classNameBindings, tagName } from "@ember-decorators/component"; import { classNameBindings, tagName } from "@ember-decorators/component";
import $ from "jquery";
import PostCountOrBadges from "discourse/components/topic-list/post-count-or-badges"; 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 TopicStatus from "discourse/components/topic-status";
import coldAgeClass from "discourse/helpers/cold-age-class"; import coldAgeClass from "discourse/helpers/cold-age-class";
import formatAge from "discourse/helpers/format-age"; import formatAge from "discourse/helpers/format-age";
import rawDate from "discourse/helpers/raw-date"; import rawDate from "discourse/helpers/raw-date";
import topicLink from "discourse/helpers/topic-link"; 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") @tagName("tr")
@classNameBindings(":category-topic-link", "topic.archived", "topic.visited") @classNameBindings(":category-topic-link", "topic.archived", "topic.visited")
export default class MobileCategoryTopic extends Component { export default class MobileCategoryTopic extends Component {

View File

@ -1,9 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { service } from "@ember/service"; import { service } from "@ember/service";
import NewListHeaderControls from "discourse/components/topic-list/new-list-header-controls"; import NewListHeaderControls from "discourse/components/topic-list/new-list-header-controls";
import raw from "discourse/helpers/raw";
export default class NewListHeaderControlsWrapper extends Component { export default class NewListHeaderControlsWrapper extends Component {
@service site; @service site;
@ -21,30 +19,14 @@ export default class NewListHeaderControlsWrapper extends Component {
} }
<template> <template>
{{#if this.site.useGlimmerTopicList}} <div class="topic-replies-toggle-wrapper">
<div class="topic-replies-toggle-wrapper"> <NewListHeaderControls
<NewListHeaderControls @current={{@current}}
@current={{@current}} @newRepliesCount={{@newRepliesCount}}
@newRepliesCount={{@newRepliesCount}} @newTopicsCount={{@newTopicsCount}}
@newTopicsCount={{@newTopicsCount}} @noStaticLabel={{true}}
@noStaticLabel={{true}} @changeNewListSubset={{@changeNewListSubset}}
@changeNewListSubset={{@changeNewListSubset}} />
/> </div>
</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}}
</template> </template>
} }

View File

@ -222,11 +222,7 @@ export default class ParentCategoryRow extends CategoryListItem {
{{#if this.showTopics}} {{#if this.showTopics}}
<td class="latest"> <td class="latest">
{{#each this.category.featuredTopics as |t|}} {{#each this.category.featuredTopics as |t|}}
{{#if this.site.useGlimmerTopicList}} <FeaturedTopic @topic={{t}} />
<FeaturedTopic @topic={{t}} />
{{else}}
<FeaturedTopic @topic={{t}} />
{{/if}}
{{/each}} {{/each}}
</td> </td>
<PluginOutlet <PluginOutlet

View File

@ -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>
}

View File

@ -1,339 +1,20 @@
import Component from "@ember/component"; import Component from "@glimmer/component";
import { hash } from "@ember/helper"; import curryComponent from "ember-curry-component";
import { dependentKeyCompat } from "@ember/object/compat"; import List from "discourse/components/topic-list/list";
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 deprecated from "discourse/lib/deprecated"; 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") export default class TopicListShim extends Component {
@classNames("topic-list") constructor() {
@classNameBindings("bulkSelectEnabled:sticky-header") super(...arguments);
export default class TopicList extends Component.extend(LoadMore) {
static reopen() {
deprecated( deprecated(
"Modifying topic-list with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.", `components/topic-list is deprecated, and should be replaced with components/topics-list/list`,
RAW_TOPIC_LIST_DEPRECATION_OPTIONS { 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> <template>
<caption class="sr-only">{{i18n "sr_topic_list_caption"}}</caption> {{#let (curryComponent List this.args) as |CurriedComponent|}}
<CurriedComponent />
<thead class="topic-list-header"> {{/let}}
{{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
}}
/>
</template> </template>
} }

View File

@ -1,5 +1,5 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { concat, get, hash } from "@ember/helper"; import { hash } from "@ember/helper";
import { on } from "@ember/modifier"; import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { service } from "@ember/service"; import { service } from "@ember/service";
@ -7,7 +7,6 @@ import { and } from "truth-helpers";
import PluginOutlet from "discourse/components/plugin-outlet"; import PluginOutlet from "discourse/components/plugin-outlet";
import icon from "discourse/helpers/d-icon"; import icon from "discourse/helpers/d-icon";
import element from "discourse/helpers/element"; import element from "discourse/helpers/element";
import TopicStatusIcons from "discourse/helpers/topic-status-icons";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
export default class TopicStatus extends Component { export default class TopicStatus extends Component {
@ -108,22 +107,10 @@ export default class TopicStatus extends Component {
class="topic-status" class="topic-status"
>{{icon "far-eye-slash"}}</span> >{{icon "far-eye-slash"}}</span>
{{~/if~}} {{~/if~}}
<PluginOutlet
{{~#if this.site.useGlimmerTopicList~}} @name="after-topic-status"
<PluginOutlet @outletArgs={{hash topic=@topic context=@context}}
@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~}}
{{~! no whitespace ~}} {{~! no whitespace ~}}
</this.wrapperElement> </this.wrapperElement>
{{~! no whitespace ~}} {{~! no whitespace ~}}

View File

@ -84,15 +84,6 @@ loaderShim("discourse-common/lib/object", () =>
loaderShim("discourse-common/lib/popular-themes", () => loaderShim("discourse-common/lib/popular-themes", () =>
importSync("discourse/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", () => loaderShim("discourse-common/lib/suffix-trie", () =>
importSync("discourse/lib/suffix-trie") importSync("discourse/lib/suffix-trie")
); );

View File

@ -1,8 +1,5 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { autoUpdatingRelativeAge } from "discourse/lib/formatter"; import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("age-with-tooltip", ageWithTooltip);
export default function ageWithTooltip(dt, params = {}) { export default function ageWithTooltip(dt, params = {}) {
return htmlSafe( return htmlSafe(

View File

@ -1,7 +1,6 @@
import { get } from "@ember/object"; import { get } from "@ember/object";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { avatarImg } from "discourse/lib/avatar-utils"; import { avatarImg } from "discourse/lib/avatar-utils";
import { registerRawHelper } from "discourse/lib/helpers";
import { prioritizeNameInUx } from "discourse/lib/settings"; import { prioritizeNameInUx } from "discourse/lib/settings";
import { formatUsername } from "discourse/lib/utilities"; import { formatUsername } from "discourse/lib/utilities";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
@ -81,7 +80,6 @@ export function renderAvatar(user, options) {
} }
} }
registerRawHelper("avatar", avatar);
export default function avatar(user, params) { export default function avatar(user, params) {
return htmlSafe(renderAvatar.call(this, user, params)); return htmlSafe(renderAvatar.call(this, user, params));
} }

View File

@ -1,7 +1,4 @@
import getUrl from "discourse/lib/get-url"; import getUrl from "discourse/lib/get-url";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("base-path", basePath);
export default function basePath() { export default function basePath() {
return getUrl(""); return getUrl("");

View File

@ -1,8 +1,5 @@
import deprecated from "discourse/lib/deprecated"; import deprecated from "discourse/lib/deprecated";
import getUrl from "discourse/lib/get-url"; import getUrl from "discourse/lib/get-url";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("base-url", baseUrl);
export default function baseUrl() { export default function baseUrl() {
deprecated("Use `{{base-path}}` instead of `{{base-url}}`", { deprecated("Use `{{base-path}}` instead of `{{base-url}}`", {

View File

@ -1,8 +1,5 @@
import { isPresent } from "@ember/utils"; import { isPresent } from "@ember/utils";
import { categoryLinkHTML } from "discourse/helpers/category-link"; import { categoryLinkHTML } from "discourse/helpers/category-link";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("category-badge", categoryBadge);
export default function categoryBadge(cat, options = {}) { export default function categoryBadge(cat, options = {}) {
return categoryLinkHTML(cat, { return categoryLinkHTML(cat, {

View File

@ -3,7 +3,7 @@ import { htmlSafe } from "@ember/template";
import categoryVariables from "discourse/helpers/category-variables"; import categoryVariables from "discourse/helpers/category-variables";
import replaceEmoji from "discourse/helpers/replace-emoji"; import replaceEmoji from "discourse/helpers/replace-emoji";
import getURL from "discourse/lib/get-url"; 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 { iconHTML } from "discourse/lib/icon-library";
import { applyValueTransformer } from "discourse/lib/transformer"; import { applyValueTransformer } from "discourse/lib/transformer";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
@ -116,7 +116,6 @@ export function categoryLinkHTML(category, options) {
} }
export default categoryLinkHTML; export default categoryLinkHTML;
registerRawHelper("category-link", categoryLinkHTML);
function buildTopicCount(count) { function buildTopicCount(count) {
return `<span class="topic-count" aria-label="${i18n( return `<span class="topic-count" aria-label="${i18n(

View File

@ -1,12 +1,10 @@
import { helperContext, registerRawHelper } from "discourse/lib/helpers"; import { helperContext } from "discourse/lib/helpers";
function daysSinceEpoch(dt) { function daysSinceEpoch(dt) {
// 1000 * 60 * 60 * 24 = days since epoch // 1000 * 60 * 60 * 24 = days since epoch
return dt.getTime() / 86400000; return dt.getTime() / 86400000;
} }
registerRawHelper("cold-age-class", coldAgeClass);
export default function coldAgeClass(dt, params = {}) { export default function coldAgeClass(dt, params = {}) {
let className = params["class"] || "age"; let className = params["class"] || "age";

View File

@ -1,7 +1,3 @@
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("component-for-collection", componentForCollection);
export default function componentForCollection( export default function componentForCollection(
collectionIdentifier, collectionIdentifier,
selectKit selectKit

View File

@ -1,7 +1,3 @@
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("component-for-row", componentForRow);
export default function componentForRow( export default function componentForRow(
collectionForIdentifier, collectionForIdentifier,
item, item,

View File

@ -1,9 +1,6 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
import { renderIcon } from "discourse/lib/icon-library"; import { renderIcon } from "discourse/lib/icon-library";
export default function icon(id, options = {}) { export default function icon(id, options = {}) {
return htmlSafe(renderIcon("string", id, options)); return htmlSafe(renderIcon("string", id, options));
} }
registerRawHelper("d-icon", icon);

View File

@ -1,5 +1,4 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
let usernameDecorators = []; let usernameDecorators = [];
export function addUsernameSelectorDecorator(decorator) { export function addUsernameSelectorDecorator(decorator) {
@ -20,8 +19,6 @@ export function decorateUsername(username) {
return decorations.length ? htmlSafe(decorations.join("")) : ""; return decorations.length ? htmlSafe(decorations.join("")) : "";
} }
registerRawHelper("decorate-username-selector", decorateUsernameSelector);
export default function decorateUsernameSelector(username) { export default function decorateUsernameSelector(username) {
return decorateUsername(username); return decorateUsername(username);
} }

View File

@ -1,5 +1,5 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { helperContext, registerRawHelper } from "discourse/lib/helpers"; import { helperContext } from "discourse/lib/helpers";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
function setDir(text) { function setDir(text) {
@ -9,8 +9,6 @@ function setDir(text) {
return `<span ${mixed ? 'dir="auto"' : ""}>${content}</span>`; return `<span ${mixed ? 'dir="auto"' : ""}>${content}</span>`;
} }
registerRawHelper("dir-span", dirSpan);
export default function dirSpan(str, params = {}) { export default function dirSpan(str, params = {}) {
let isHtmlSafe = false; let isHtmlSafe = false;
if (params.htmlSafe) { if (params.htmlSafe) {

View File

@ -1,8 +1,6 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
import renderTag from "discourse/lib/render-tag"; import renderTag from "discourse/lib/render-tag";
registerRawHelper("discourse-tag", discourseTag);
export default function discourseTag(name, params) { export default function discourseTag(name, params) {
return htmlSafe(renderTag(name, params)); return htmlSafe(renderTag(name, params));
} }

View File

@ -1,8 +1,6 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
import renderTags from "discourse/lib/render-tags"; import renderTags from "discourse/lib/render-tags";
registerRawHelper("discourse-tags", discourseTags);
export default function discourseTags(topic, params) { export default function discourseTags(topic, params) {
return htmlSafe(renderTags(topic, params)); return htmlSafe(renderTags(topic, params));
} }

View File

@ -1,9 +1,7 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
import { emojiUnescape } from "discourse/lib/text"; import { emojiUnescape } from "discourse/lib/text";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
registerRawHelper("emoji", emoji);
export default function emoji(code, options) { export default function emoji(code, options) {
const escaped = escapeExpression(`:${code}:`); const escaped = escapeExpression(`:${code}:`);
return htmlSafe(emojiUnescape(escaped, options)); return htmlSafe(emojiUnescape(escaped, options));

View File

@ -1,13 +1,11 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import deprecated from "discourse/lib/deprecated"; import deprecated from "discourse/lib/deprecated";
import { registerRawHelper } from "discourse/lib/helpers";
import { renderIcon } from "discourse/lib/icon-library"; import { renderIcon } from "discourse/lib/icon-library";
export function iconHTML(id, params) { export function iconHTML(id, params) {
return renderIcon("string", id, params); return renderIcon("string", id, params);
} }
registerRawHelper("fa-icon", faIcon);
export default function faIcon(icon, params) { export default function faIcon(icon, params) {
deprecated("Use `{{d-icon}}` instead of `{{fa-icon}}", { deprecated("Use `{{d-icon}}` instead of `{{fa-icon}}", {
id: "discourse.fa-icon", id: "discourse.fa-icon",

View File

@ -1,8 +1,6 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { autoUpdatingRelativeAge } from "discourse/lib/formatter"; import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("format-age", formatAge);
export default function formatAge(dt) { export default function formatAge(dt) {
dt = new Date(dt); dt = new Date(dt);
return htmlSafe(autoUpdatingRelativeAge(dt)); return htmlSafe(autoUpdatingRelativeAge(dt));

View File

@ -1,13 +1,11 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { autoUpdatingRelativeAge } from "discourse/lib/formatter"; 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 Display logic for dates. It is unbound in Ember but will use jQuery to
update the dates on a regular interval. update the dates on a regular interval.
**/ **/
registerRawHelper("format-date", formatDate);
export default function formatDate(val, params = {}) { export default function formatDate(val, params = {}) {
let leaveAgo, let leaveAgo,
format = "medium", format = "medium",

View File

@ -1,8 +1,6 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { durationTiny } from "discourse/lib/formatter"; import { durationTiny } from "discourse/lib/formatter";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("format-duration", formatDuration);
export default function formatDuration(seconds) { export default function formatDuration(seconds) {
return htmlSafe(durationTiny(seconds)); return htmlSafe(durationTiny(seconds));
} }

View File

@ -1,5 +1,3 @@
import { registerRawHelper } from "discourse/lib/helpers";
import { formatUsername } from "discourse/lib/utilities"; import { formatUsername } from "discourse/lib/utilities";
export default formatUsername; export default formatUsername;
registerRawHelper("format-username", formatUsername);

View File

@ -1,7 +1,4 @@
import { default as emberGetUrl } from "discourse/lib/get-url"; import { default as emberGetUrl } from "discourse/lib/get-url";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("get-url", getUrl);
export default function getUrl(value) { export default function getUrl(value) {
return emberGetUrl(value); return emberGetUrl(value);

View File

@ -1,7 +1,4 @@
import { htmlSafe as emberHtmlSafe } from "@ember/template"; import { htmlSafe as emberHtmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("html-safe", htmlSafe);
export default function htmlSafe(string) { export default function htmlSafe(string) {
return emberHtmlSafe(string); return emberHtmlSafe(string);

View File

@ -1,8 +1,5 @@
import { registerRawHelper } from "discourse/lib/helpers";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
registerRawHelper("i18n-yes-no", i18nYesNo);
export default function i18nYesNo(value, params) { export default function i18nYesNo(value, params) {
return i18n(value ? "yes_value" : "no_value", params); return i18n(value ? "yes_value" : "no_value", params);
} }

View File

@ -1,6 +1,3 @@
import { registerRawHelper } from "discourse/lib/helpers";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
registerRawHelper("i18n", i18n);
export default i18n; export default i18n;

View File

@ -1,11 +1,8 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { number as numberFormatter } from "discourse/lib/formatter"; import { number as numberFormatter } from "discourse/lib/formatter";
import { registerRawHelper } from "discourse/lib/helpers";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
import I18n, { i18n } from "discourse-i18n"; import I18n, { i18n } from "discourse-i18n";
registerRawHelper("number", number);
export default function number(orig, params = {}) { export default function number(orig, params = {}) {
orig = Math.round(parseFloat(orig)); orig = Math.round(parseFloat(orig));
if (isNaN(orig)) { if (isNaN(orig)) {

View File

@ -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 }
)
);
});

View File

@ -1,8 +1,5 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { longDate } from "discourse/lib/formatter"; import { longDate } from "discourse/lib/formatter";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("raw-date", rawDate);
export default function rawDate(dt) { export default function rawDate(dt) {
return htmlSafe(longDate(new Date(dt))); return htmlSafe(longDate(new Date(dt)));

View File

@ -1,5 +0,0 @@
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("raw-hash", function (params) {
return params;
});

View File

@ -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(""));
}
});

View File

@ -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());
}
}

View File

@ -1,10 +1,7 @@
import { htmlSafe, isHTMLSafe } from "@ember/template"; import { htmlSafe, isHTMLSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
import { emojiUnescape } from "discourse/lib/text"; import { emojiUnescape } from "discourse/lib/text";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
registerRawHelper("replace-emoji", replaceEmoji);
export default function replaceEmoji(text, options) { export default function replaceEmoji(text, options) {
text = isHTMLSafe(text) ? text.toString() : escapeExpression(text); text = isHTMLSafe(text) ? text.toString() : escapeExpression(text);
return htmlSafe(emojiUnescape(text, options)); return htmlSafe(emojiUnescape(text, options));

View File

@ -1,6 +1,3 @@
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("shorten-url", shortenUrl);
export default function shortenUrl(url) { export default function shortenUrl(url) {
let matches = url.match(/\//g); let matches = url.match(/\//g);

View File

@ -1,7 +1,5 @@
import { registerRawHelper } from "discourse/lib/helpers";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
registerRawHelper("theme-i18n", themeI18n);
export default function themeI18n(themeId, key, params) { export default function themeI18n(themeId, key, params) {
if (typeof themeId !== "number") { if (typeof themeId !== "number") {
throw new Error( throw new Error(

View File

@ -1,6 +1,3 @@
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("theme-prefix", themePrefix);
export default function themePrefix(themeId, key) { export default function themePrefix(themeId, key) {
return `theme_translations.${themeId}.${key}`; return `theme_translations.${themeId}.${key}`;
} }

View File

@ -1,7 +1,5 @@
import { registerRawHelper } from "discourse/lib/helpers";
import { getSetting as getThemeSetting } from "discourse/lib/theme-settings-store"; import { getSetting as getThemeSetting } from "discourse/lib/theme-settings-store";
registerRawHelper("theme-setting", themeSetting);
export default function themeSetting(themeId, key) { export default function themeSetting(themeId, key) {
if (typeof themeId !== "number") { if (typeof themeId !== "number") {
throw new Error( throw new Error(

View File

@ -1,8 +1,6 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
import renderTopicFeaturedLink from "discourse/lib/render-topic-featured-link"; import renderTopicFeaturedLink from "discourse/lib/render-topic-featured-link";
registerRawHelper("topic-featured-link", topicFeaturedLink);
export default function topicFeaturedLink(topic, params) { export default function topicFeaturedLink(topic, params) {
return htmlSafe(renderTopicFeaturedLink(topic, params)); return htmlSafe(renderTopicFeaturedLink(topic, params));
} }

View File

@ -1,7 +1,5 @@
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("topic-link", topicLink);
export default function topicLink(topic, args = {}) { export default function topicLink(topic, args = {}) {
const title = topic.get("fancyTitle"); const title = topic.get("fancyTitle");

View File

@ -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;

View File

@ -1,6 +1,3 @@
import { registerRawHelper } from "discourse/lib/helpers";
registerRawHelper("value-entered", valueEntered);
export default function valueEntered(value) { export default function valueEntered(value) {
if (!value) { if (!value) {
return ""; return "";

View File

@ -1,8 +1,5 @@
import { setOwner } from "@ember/owner"; import { setOwner } from "@ember/owner";
import Handlebars from "handlebars";
import { createHelperContext, registerHelpers } from "discourse/lib/helpers"; 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) { function isThemeOrPluginHelper(path) {
return ( return (
@ -38,7 +35,6 @@ export function autoLoadModules(owner, registry) {
createHelperContext(context); createHelperContext(context);
registerHelpers(registry); registerHelpers(registry);
registerRawHelpers(RawHandlebars, Handlebars, owner);
} }
export default { export default {

View File

@ -2,15 +2,12 @@ import * as GlimmerManager from "@glimmer/manager";
import ClassicComponent from "@ember/component"; import ClassicComponent from "@ember/component";
import deprecated from "discourse/lib/deprecated"; import deprecated from "discourse/lib/deprecated";
import DiscourseTemplateMap from "discourse/lib/discourse-template-map"; 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"; 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. // 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 // 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 // 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) { function sourceForModuleName(name) {
const pluginMatch = name.match(/^discourse\/plugins\/([^\/]+)\//)?.[1]; const pluginMatch = name.match(/^discourse\/plugins\/([^\/]+)\//)?.[1];
if (pluginMatch) { if (pluginMatch) {
@ -74,25 +71,14 @@ export default {
const originalTemplate = GlimmerManager.getComponentTemplate(component); const originalTemplate = GlimmerManager.getComponentTemplate(component);
if (originalTemplate) { if (originalTemplate) {
if (LEGACY_TOPIC_LIST_OVERRIDES.includes(componentName)) { deprecated(
// Special handling for these, with a different deprecation id, so the auto-feature-flag works correctly `Overriding component templates is deprecated, and will soon be disabled. Use plugin outlets, CSS, or other customization APIs instead. [${finalOverrideModuleName}]`,
deprecated( {
`Overriding '${componentName}' template is deprecated. Use the value transformer 'topic-list-columns' and other new topic-list plugin APIs instead.`, id: "discourse.component-template-overrides",
{ url: "https://meta.discourse.org/t/355668",
...RAW_TOPIC_LIST_DEPRECATION_OPTIONS, source: sourceForModuleName(finalOverrideModuleName),
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),
}
);
}
const overrideTemplate = require(finalOverrideModuleName).default; const overrideTemplate = require(finalOverrideModuleName).default;

View File

@ -1,7 +0,0 @@
import { eagerLoadRawTemplateModules } from "discourse/lib/raw-templates";
export default {
initialize() {
eagerLoadRawTemplateModules();
},
};

View File

@ -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);
}
});
},
};

View File

@ -1,9 +1,7 @@
import Helper from "@ember/component/helper"; import Helper from "@ember/component/helper";
import { get } from "@ember/object";
import { dasherize } from "@ember/string"; import { dasherize } from "@ember/string";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import deprecated from "discourse/lib/deprecated"; import deprecated from "discourse/lib/deprecated";
import RawHandlebars from "discourse/lib/raw-handlebars";
export function makeArray(obj) { export function makeArray(obj) {
if (obj === null || obj === undefined) { if (obj === null || obj === undefined) {
@ -27,17 +25,6 @@ export function htmlHelper(fn) {
const _helpers = {}; 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) { export function registerHelper(name, fn) {
_helpers[name] = Helper.helper(fn); _helpers[name] = Helper.helper(fn);
} }
@ -63,31 +50,6 @@ export function helperContext() {
return _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 * Register a helper for Ember and raw-hbs. This exists for
* legacy reasons, and should be avoided in new code. Instead, you should * 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) { export function registerUnbound(name, fn) {
deprecated( 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" } { id: "discourse.register-unbound" }
); );
@ -104,29 +66,14 @@ export function registerUnbound(name, fn) {
return fn(...params, args); return fn(...params, args);
} }
}; };
registerRawHelper(name, fn);
} }
/** /**
* Register a helper for raw-hbs only * Register a helper for raw-hbs only
*/ */
export function registerRawHelper(name, fn) { export function registerRawHelper(name) {
const func = function (...args) { deprecated(
const options = args.pop(); `[registerRawHelper ${name}] the raw handlebars system has been removed, so calls to registerRawHelper should be removed.`,
const properties = args; { id: "discourse.register-raw-helper" }
);
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);
} }

View File

@ -199,11 +199,7 @@ const POST_STREAM_DEPRECATION_OPTIONS = {
// url: "", // TODO (glimmer-post-stream) uncomment when the topic is created on meta // url: "", // TODO (glimmer-post-stream) uncomment when the topic is created on meta
}; };
export const RAW_TOPIC_LIST_DEPRECATION_OPTIONS = { const blockedModifications = ["component:topic-list"];
since: "v3.4.0.beta4-dev",
id: "discourse.hbr-topic-list-overrides",
url: "https://meta.discourse.org/t/343404",
};
const appliedModificationIds = new WeakMap(); const appliedModificationIds = new WeakMap();
@ -295,7 +291,11 @@ class PluginApi {
return; return;
} }
const klass = this.container.factoryFor(normalized); let klass;
if (!blockedModifications.includes(normalized)) {
klass = this.container.factoryFor(normalized);
}
if (!klass) { if (!klass) {
if (!opts.ignoreMissing) { if (!opts.ignoreMissing) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -328,17 +328,6 @@ class PluginApi {
* ``` * ```
**/ **/
modifyClass(resolverName, changes, opts) { 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); const klass = this._resolveClass(resolverName, opts);
if (!klass) { if (!klass) {
return; return;
@ -376,17 +365,6 @@ class PluginApi {
* ``` * ```
**/ **/
modifyClassStatic(resolverName, changes, opts) { 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); const klass = this._resolveClass(resolverName, opts);
if (!klass) { if (!klass) {
return; return;

View File

@ -6,10 +6,8 @@ import {
import templateOnly from "@ember/component/template-only"; import templateOnly from "@ember/component/template-only";
import { isDeprecatedOutletArgument } from "discourse/helpers/deprecated-outlet-argument"; import { isDeprecatedOutletArgument } from "discourse/helpers/deprecated-outlet-argument";
import deprecated, { withSilencedDeprecations } from "discourse/lib/deprecated"; import deprecated, { withSilencedDeprecations } from "discourse/lib/deprecated";
import { buildRawConnectorCache } from "discourse/lib/raw-templates";
let _connectorCache; let _connectorCache;
let _rawConnectorCache;
let _extraConnectorClasses = {}; let _extraConnectorClasses = {};
let _extraConnectorComponents = {}; let _extraConnectorComponents = {};
let debugOutletCallback; let debugOutletCallback;
@ -64,7 +62,6 @@ function findOutlets(keys, callback) {
export function clearCache() { export function clearCache() {
_connectorCache = null; _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 = {}) { export function buildArgsWithDeprecations(args, deprecatedArgs, opts = {}) {
const output = {}; const output = {};

View File

@ -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);
}
}
});
}

View File

@ -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;

View File

@ -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}>`;
}

View File

@ -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);
}
}
}

View File

@ -1,4 +1,3 @@
import Handlebars from "handlebars";
import $ from "jquery"; import $ from "jquery";
import * as AvatarUtils from "discourse/lib/avatar-utils"; import * as AvatarUtils from "discourse/lib/avatar-utils";
import deprecated from "discourse/lib/deprecated"; import deprecated from "discourse/lib/deprecated";
@ -41,11 +40,6 @@ export function escapeExpression(string) {
return ""; return "";
} }
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
}
return escape(string); return escape(string);
} }

View File

@ -27,7 +27,6 @@ loaderShim("a11y-dialog", () => importSync("a11y-dialog"));
loaderShim("discourse-i18n", () => importSync("discourse-i18n")); loaderShim("discourse-i18n", () => importSync("discourse-i18n"));
loaderShim("ember-modifier", () => importSync("ember-modifier")); loaderShim("ember-modifier", () => importSync("ember-modifier"));
loaderShim("ember-route-template", () => importSync("ember-route-template")); loaderShim("ember-route-template", () => importSync("ember-route-template"));
loaderShim("handlebars", () => importSync("handlebars"));
loaderShim("jquery", () => importSync("jquery")); loaderShim("jquery", () => importSync("jquery"));
loaderShim("js-yaml", () => importSync("js-yaml")); loaderShim("js-yaml", () => importSync("js-yaml"));
loaderShim("message-bus-client", () => importSync("message-bus-client")); loaderShim("message-bus-client", () => importSync("message-bus-client"));

View File

@ -89,7 +89,6 @@ export default class Site extends RestModel {
@sort("categories", "topicCountDesc") categoriesByCount; @sort("categories", "topicCountDesc") categoriesByCount;
#glimmerPostStreamEnabled; #glimmerPostStreamEnabled;
#glimmerTopicDecision;
init() { init() {
super.init(...arguments); super.init(...arguments);
@ -161,51 +160,6 @@ export default class Site extends RestModel {
return enabled; 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.[]") @computed("categories.[]")
get categoriesById() { get categoriesById() {
const map = new Map(); const map = new Map();

View File

@ -1,3 +0,0 @@
<button class='btn-transparent {{class}}' title='{{i18n "topics.bulk.toggle"}}'>
{{d-icon icon}}
</button>

View File

@ -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}}

View File

@ -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}}>

View File

@ -1 +0,0 @@
<td class='category topic-list-data'>{{category-link category}}</td>

View File

@ -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}}

View File

@ -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>

View File

@ -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}}

View File

@ -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>

View File

@ -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}}>

View File

@ -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}}

View File

@ -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}}
&nbsp;{{~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"}}

View File

@ -1,5 +0,0 @@
{{~#if includeUnreadIndicator~}}
&nbsp;<span class='badge badge-notification unread-indicator indicator-topic-{{topicId}} {{unreadClass}}' title='{{i18n "topic.unread_indicator"}}'>
{{~d-icon "asterisk"}}
</span>
{{~/if}}

View File

@ -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}}

View File

@ -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~}}
&nbsp;{{~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">&nbsp;<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>

View File

@ -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>

View File

@ -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"~}}

View File

@ -1,26 +0,0 @@
<span class="topic-post-badges">
{{~#if newPosts~}}
&nbsp;<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~}}
&nbsp;<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~}}
&nbsp;<a
href="{{url}}"
class="badge badge-notification new-topic"
title="{{i18n 'topic.new'}}"
aria-label="{{i18n 'topic.new'}}"
>{{newDotText}}</a>
{{~/if}}
</span>

View File

@ -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 ~}}

View File

@ -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;
}
}
}

View File

@ -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");
}
}

View File

@ -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}`;
}
}
}

View File

@ -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;
}
}

View File

@ -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,
}
);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -28,7 +28,6 @@ export const CRITICAL_DEPRECATIONS = [
"discourse.qunit.acceptance-function", "discourse.qunit.acceptance-function",
"discourse.qunit.global-exists", "discourse.qunit.global-exists",
"discourse.post-stream.trigger-new-post", "discourse.post-stream.trigger-new-post",
"discourse.hbr-topic-list-overrides",
"discourse.mobile-templates", "discourse.mobile-templates",
"discourse.mobile-view", "discourse.mobile-view",
"discourse.mobile-templates", "discourse.mobile-templates",

View File

@ -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);
}
});
}
}

View File

@ -13,7 +13,6 @@ const { Webpack } = require("@embroider/webpack");
const { StatsWriterPlugin } = require("webpack-stats-plugin"); const { StatsWriterPlugin } = require("webpack-stats-plugin");
const { RetryChunkLoadPlugin } = require("webpack-retry-chunk-load-plugin"); const { RetryChunkLoadPlugin } = require("webpack-retry-chunk-load-plugin");
const withSideWatch = require("./lib/with-side-watch"); const withSideWatch = require("./lib/with-side-watch");
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
const crypto = require("crypto"); const crypto = require("crypto");
const commonBabelConfig = require("./lib/common-babel-config"); const commonBabelConfig = require("./lib/common-babel-config");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
@ -65,11 +64,9 @@ module.exports = function (defaults) {
...commonBabelConfig(), ...commonBabelConfig(),
trees: { trees: {
app: RawHandlebarsCompiler( app: withSideWatch("app", {
withSideWatch("app", { watching: ["../discourse-markdown-it", "../truth-helpers"],
watching: ["../discourse-markdown-it", "../truth-helpers"], }),
})
),
}, },
}); });

Some files were not shown because too many files have changed in this diff Show More