mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 13:06:56 +08:00
DEV: Drop legacy topic-list and raw-handlebars compilation system (#32081)
This commit is contained in:
@ -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));
|
||||||
|
@ -1 +0,0 @@
|
|||||||
engine-strict = true
|
|
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "discourse-hbr",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Support for Discourse's raw Handlebars templates (hbr)",
|
|
||||||
"author": "Discourse",
|
|
||||||
"license": "GPL-2.0-only",
|
|
||||||
"dependencies": {
|
|
||||||
"handlebars": "^4.7.8",
|
|
||||||
"broccoli-filter": "^1.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18",
|
|
||||||
"npm": "please-use-pnpm",
|
|
||||||
"yarn": "please-use-pnpm",
|
|
||||||
"pnpm": "^9"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"./raw-handlebars-compiler": "./raw-handlebars-compiler.js"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const Filter = require("broccoli-filter");
|
|
||||||
const Handlebars = require("handlebars");
|
|
||||||
|
|
||||||
const RawHandlebars = Handlebars.create();
|
|
||||||
|
|
||||||
function buildPath(blk, args) {
|
|
||||||
let result = {
|
|
||||||
type: "PathExpression",
|
|
||||||
data: false,
|
|
||||||
depth: blk.path.depth,
|
|
||||||
loc: blk.path.loc,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Server side precompile doesn't have jquery.extend
|
|
||||||
Object.keys(args).forEach(function (a) {
|
|
||||||
result[a] = args[a];
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceGet(ast) {
|
|
||||||
let visitor = new Handlebars.Visitor();
|
|
||||||
visitor.mutating = true;
|
|
||||||
|
|
||||||
visitor.MustacheStatement = function (mustache) {
|
|
||||||
if (!(mustache.params.length || mustache.hash)) {
|
|
||||||
mustache.params[0] = mustache.path;
|
|
||||||
mustache.path = buildPath(mustache, {
|
|
||||||
parts: ["get"],
|
|
||||||
original: "get",
|
|
||||||
strict: true,
|
|
||||||
falsy: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache);
|
|
||||||
};
|
|
||||||
|
|
||||||
// rewrite `each x as |y|` as each y in x`
|
|
||||||
// This allows us to use the same syntax in all templates
|
|
||||||
visitor.BlockStatement = function (block) {
|
|
||||||
if (block.path.original === "each" && block.params.length === 1) {
|
|
||||||
let paramName = block.program.blockParams[0];
|
|
||||||
block.params = [
|
|
||||||
buildPath(block, { original: paramName }),
|
|
||||||
{ type: "CommentStatement", value: "in" },
|
|
||||||
block.params[0],
|
|
||||||
];
|
|
||||||
delete block.program.blockParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Handlebars.Visitor.prototype.BlockStatement.call(this, block);
|
|
||||||
};
|
|
||||||
|
|
||||||
visitor.accept(ast);
|
|
||||||
}
|
|
||||||
|
|
||||||
RawHandlebars.Compiler = function () {};
|
|
||||||
RawHandlebars.Compiler.prototype = Object.create(Handlebars.Compiler.prototype);
|
|
||||||
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
|
|
||||||
|
|
||||||
RawHandlebars.JavaScriptCompiler = function () {};
|
|
||||||
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype = Object.create(
|
|
||||||
Handlebars.JavaScriptCompiler.prototype
|
|
||||||
);
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype.compiler =
|
|
||||||
RawHandlebars.JavaScriptCompiler;
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype.namespace = "RawHandlebars";
|
|
||||||
|
|
||||||
RawHandlebars.precompile = function (value, asObject) {
|
|
||||||
let ast = Handlebars.parse(value);
|
|
||||||
replaceGet(ast);
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
knownHelpers: {
|
|
||||||
get: true,
|
|
||||||
},
|
|
||||||
data: true,
|
|
||||||
stringParams: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
asObject = asObject === undefined ? true : asObject;
|
|
||||||
|
|
||||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
|
||||||
return new RawHandlebars.JavaScriptCompiler().compile(
|
|
||||||
environment,
|
|
||||||
options,
|
|
||||||
undefined,
|
|
||||||
asObject
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
RawHandlebars.compile = function (string) {
|
|
||||||
let ast = Handlebars.parse(string);
|
|
||||||
replaceGet(ast);
|
|
||||||
|
|
||||||
// this forces us to rewrite helpers
|
|
||||||
let options = { data: true, stringParams: true };
|
|
||||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
|
||||||
let templateSpec = new RawHandlebars.JavaScriptCompiler().compile(
|
|
||||||
environment,
|
|
||||||
options,
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
let t = RawHandlebars.template(templateSpec);
|
|
||||||
t.isMethod = false;
|
|
||||||
|
|
||||||
return t;
|
|
||||||
};
|
|
||||||
function TemplateCompiler(inputTree, options) {
|
|
||||||
if (!(this instanceof TemplateCompiler)) {
|
|
||||||
return new TemplateCompiler(inputTree, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
Filter.call(this, inputTree, options); // this._super()
|
|
||||||
|
|
||||||
this.options = options || {};
|
|
||||||
this.inputTree = inputTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
TemplateCompiler.prototype = Object.create(Filter.prototype);
|
|
||||||
TemplateCompiler.prototype.constructor = TemplateCompiler;
|
|
||||||
TemplateCompiler.prototype.extensions = ["hbr"];
|
|
||||||
TemplateCompiler.prototype.targetExtension = "js";
|
|
||||||
|
|
||||||
TemplateCompiler.prototype.registerPlugins = function registerPlugins() {};
|
|
||||||
|
|
||||||
TemplateCompiler.prototype.initializeFeatures =
|
|
||||||
function initializeFeatures() {};
|
|
||||||
|
|
||||||
TemplateCompiler.prototype.processString = function (string, relativePath) {
|
|
||||||
let filename;
|
|
||||||
|
|
||||||
const pluginName = relativePath.match(/^discourse\/plugins\/([^\/]+)\//)?.[1];
|
|
||||||
|
|
||||||
if (pluginName) {
|
|
||||||
filename = relativePath
|
|
||||||
.replace(`discourse/plugins/${pluginName}/`, "")
|
|
||||||
.replace(/^(discourse\/)?raw-templates\//, "javascripts/");
|
|
||||||
} else {
|
|
||||||
filename = relativePath.replace(/^raw-templates\//, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = filename.replace(/\.hbr$/, "");
|
|
||||||
const hasModernReplacement = string.includes(
|
|
||||||
"{{!-- has-modern-replacement --}}"
|
|
||||||
);
|
|
||||||
|
|
||||||
return `
|
|
||||||
import { template as compiler } from "discourse/lib/raw-handlebars";
|
|
||||||
import { addRawTemplate } from "discourse/lib/raw-templates";
|
|
||||||
|
|
||||||
let template = compiler(${this.precompile(string, false)});
|
|
||||||
|
|
||||||
addRawTemplate("${filename}", template, {
|
|
||||||
core: ${!pluginName},
|
|
||||||
pluginName: ${JSON.stringify(pluginName)},
|
|
||||||
hasModernReplacement: ${hasModernReplacement},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default template;
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
TemplateCompiler.prototype.precompile = function (value, asObject) {
|
|
||||||
return RawHandlebars.precompile(value, asObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = TemplateCompiler;
|
|
@ -6,7 +6,6 @@ const Funnel = require("broccoli-funnel");
|
|||||||
const mergeTrees = require("broccoli-merge-trees");
|
const 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,
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
import Component from "@ember/component";
|
|
||||||
import { hash } from "@ember/helper";
|
|
||||||
import {
|
|
||||||
attributeBindings,
|
|
||||||
classNameBindings,
|
|
||||||
} from "@ember-decorators/component";
|
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
|
||||||
import ItemRepliesCell from "discourse/components/topic-list/item/replies-cell";
|
|
||||||
import {
|
|
||||||
navigateToTopic,
|
|
||||||
showEntrance,
|
|
||||||
} from "discourse/components/topic-list-item";
|
|
||||||
import TopicPostBadges from "discourse/components/topic-post-badges";
|
|
||||||
import TopicStatus from "discourse/components/topic-status";
|
|
||||||
import UserAvatarFlair from "discourse/components/user-avatar-flair";
|
|
||||||
import UserLink from "discourse/components/user-link";
|
|
||||||
import avatar from "discourse/helpers/avatar";
|
|
||||||
import categoryLink from "discourse/helpers/category-link";
|
|
||||||
import discourseTags from "discourse/helpers/discourse-tags";
|
|
||||||
import formatDate from "discourse/helpers/format-date";
|
|
||||||
import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
|
||||||
import topicLink from "discourse/helpers/topic-link";
|
|
||||||
import discourseComputed from "discourse/lib/decorators";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
@attributeBindings("topic.id:data-topic-id")
|
|
||||||
@classNameBindings(":latest-topic-list-item", "unboundClassNames")
|
|
||||||
export default class LatestTopicListItem extends Component {
|
|
||||||
showEntrance = showEntrance;
|
|
||||||
navigateToTopic = navigateToTopic;
|
|
||||||
|
|
||||||
click(e) {
|
|
||||||
// for events undefined has a different meaning than false
|
|
||||||
if (this.showEntrance(e) === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.unhandledRowClick(e, this.topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can be overwritten by plugins to handle clicks on other parts of the row
|
|
||||||
unhandledRowClick() {}
|
|
||||||
|
|
||||||
@discourseComputed("topic")
|
|
||||||
unboundClassNames(topic) {
|
|
||||||
let classes = [];
|
|
||||||
|
|
||||||
if (topic.get("category")) {
|
|
||||||
classes.push("category-" + topic.get("category.fullSlug"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.get("tags")) {
|
|
||||||
topic.get("tags").forEach((tagName) => classes.push("tag-" + tagName));
|
|
||||||
}
|
|
||||||
|
|
||||||
["liked", "archived", "bookmarked", "pinned", "closed", "visited"].forEach(
|
|
||||||
(name) => {
|
|
||||||
if (topic.get(name)) {
|
|
||||||
classes.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return classes.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="above-latest-topic-list-item"
|
|
||||||
@connectorTagName="div"
|
|
||||||
@outletArgs={{hash topic=this.topic}}
|
|
||||||
/>
|
|
||||||
<div class="topic-poster">
|
|
||||||
<UserLink
|
|
||||||
@user={{this.topic.lastPosterUser}}
|
|
||||||
aria-label={{if
|
|
||||||
this.topic.lastPosterUser.username
|
|
||||||
(i18n
|
|
||||||
"latest_poster_link" username=this.topic.lastPosterUser.username
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{{avatar this.topic.lastPosterUser imageSize="large"}}
|
|
||||||
</UserLink>
|
|
||||||
<UserAvatarFlair @user={{this.topic.lastPosterUser}} />
|
|
||||||
</div>
|
|
||||||
<div class="main-link">
|
|
||||||
<div class="top-row">
|
|
||||||
<TopicStatus @topic={{this.topic}} />
|
|
||||||
{{topicLink this.topic}}
|
|
||||||
{{~#if this.topic.featured_link}}
|
|
||||||
{{topicFeaturedLink this.topic}}
|
|
||||||
{{/if}}{{! intentionally inline
|
|
||||||
to avoid whitespace}}<TopicPostBadges
|
|
||||||
@unreadPosts={{this.topic.unread_posts}}
|
|
||||||
@unseen={{this.topic.unseen}}
|
|
||||||
@url={{this.topic.lastUnreadUrl}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="bottom-row">
|
|
||||||
{{categoryLink this.topic.category}}{{discourseTags
|
|
||||||
this.topic
|
|
||||||
mode="list"
|
|
||||||
}}{{! intentionally inline to avoid whitespace}}
|
|
||||||
<PluginOutlet
|
|
||||||
@name="below-latest-topic-list-item-bottom-row"
|
|
||||||
@connectorTagName="span"
|
|
||||||
@outletArgs={{hash topic=this.topic}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="topic-stats">
|
|
||||||
<PluginOutlet
|
|
||||||
@name="above-latest-topic-list-item-post-count"
|
|
||||||
@connectorTagName="div"
|
|
||||||
@outletArgs={{hash topic=this.topic}}
|
|
||||||
/>
|
|
||||||
<ItemRepliesCell @topic={{this.topic}} @tagName="div" />
|
|
||||||
<div class="topic-last-activity">
|
|
||||||
<a
|
|
||||||
href={{this.topic.lastPostUrl}}
|
|
||||||
title={{this.topic.bumpedAtTitle}}
|
|
||||||
>{{formatDate this.topic.bumpedAt format="tiny" noTitle="true"}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
}
|
|
@ -1,13 +1,32 @@
|
|||||||
import Component from "@ember/component";
|
import 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 {
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -1,418 +0,0 @@
|
|||||||
import Component from "@ember/component";
|
|
||||||
import { hash } from "@ember/helper";
|
|
||||||
import { alias } from "@ember/object/computed";
|
|
||||||
import { getOwner } from "@ember/owner";
|
|
||||||
import { schedule } from "@ember/runloop";
|
|
||||||
import { service } from "@ember/service";
|
|
||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import {
|
|
||||||
attributeBindings,
|
|
||||||
classNameBindings,
|
|
||||||
tagName,
|
|
||||||
} from "@ember-decorators/component";
|
|
||||||
import { observes, on } from "@ember-decorators/object";
|
|
||||||
import $ from "jquery";
|
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
|
||||||
import discourseComputed, { bind } from "discourse/lib/decorators";
|
|
||||||
import deprecated from "discourse/lib/deprecated";
|
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
|
||||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
|
||||||
import { RUNTIME_OPTIONS } from "discourse/lib/raw-handlebars-helpers";
|
|
||||||
import { findRawTemplate } from "discourse/lib/raw-templates";
|
|
||||||
import { applyValueTransformer } from "discourse/lib/transformer";
|
|
||||||
import DiscourseURL, { groupPath } from "discourse/lib/url";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export function showEntrance(e) {
|
|
||||||
let target = $(e.target);
|
|
||||||
|
|
||||||
if (target.hasClass("posts-map") || target.parents(".posts-map").length > 0) {
|
|
||||||
if (target.prop("tagName") !== "A") {
|
|
||||||
target = target.find("a");
|
|
||||||
if (target.length === 0) {
|
|
||||||
target = target.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.appEvents.trigger("topic-entrance:show", {
|
|
||||||
topic: this.topic,
|
|
||||||
position: target.offset(),
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function navigateToTopic(topic, href) {
|
|
||||||
const historyStore = getOwner(this).lookup("service:history-store");
|
|
||||||
historyStore.set("lastTopicIdViewed", topic.id);
|
|
||||||
|
|
||||||
DiscourseURL.routeTo(href || topic.get("url"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@tagName("tr")
|
|
||||||
@classNameBindings(":topic-list-item", "unboundClassNames", "topic.visited")
|
|
||||||
@attributeBindings("dataTopicId:data-topic-id", "role", "ariaLevel:aria-level")
|
|
||||||
export default class TopicListItem extends Component {
|
|
||||||
static reopen() {
|
|
||||||
deprecated(
|
|
||||||
"Modifying topic-list-item with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
|
||||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
|
||||||
);
|
|
||||||
|
|
||||||
return super.reopen(...arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
static reopenClass() {
|
|
||||||
deprecated(
|
|
||||||
"Modifying topic-list-item with `reopenClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
|
||||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
|
||||||
);
|
|
||||||
|
|
||||||
return super.reopenClass(...arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@service router;
|
|
||||||
@service historyStore;
|
|
||||||
|
|
||||||
@alias("topic.id") dataTopicId;
|
|
||||||
|
|
||||||
didReceiveAttrs() {
|
|
||||||
super.didReceiveAttrs(...arguments);
|
|
||||||
this.renderTopicListItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Already-rendered topic is marked as highlighted
|
|
||||||
// Ideally this should be a modifier... but we can't do that
|
|
||||||
// until this component has its tagName removed.
|
|
||||||
@observes("topic.highlight")
|
|
||||||
topicHighlightChanged() {
|
|
||||||
if (this.topic.highlight) {
|
|
||||||
this._highlightIfNeeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@observes("topic.pinned", "expandGloballyPinned", "expandAllPinned")
|
|
||||||
renderTopicListItem() {
|
|
||||||
const template = findRawTemplate("list/topic-list-item");
|
|
||||||
if (template) {
|
|
||||||
this.set(
|
|
||||||
"topicListItemContents",
|
|
||||||
htmlSafe(template(this, RUNTIME_OPTIONS))
|
|
||||||
);
|
|
||||||
schedule("afterRender", () => {
|
|
||||||
if (this.isDestroyed || this.isDestroying) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.selected && this.selected.includes(this.topic)) {
|
|
||||||
this.element.querySelector("input.bulk-select").checked = true;
|
|
||||||
}
|
|
||||||
if (this._shouldFocusLastVisited()) {
|
|
||||||
const title = this._titleElement();
|
|
||||||
if (title) {
|
|
||||||
title.addEventListener("focus", this._onTitleFocus);
|
|
||||||
title.addEventListener("blur", this._onTitleBlur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
didInsertElement() {
|
|
||||||
super.didInsertElement(...arguments);
|
|
||||||
|
|
||||||
if (this.includeUnreadIndicator) {
|
|
||||||
this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
willDestroyElement() {
|
|
||||||
super.willDestroyElement(...arguments);
|
|
||||||
|
|
||||||
this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage);
|
|
||||||
|
|
||||||
if (this._shouldFocusLastVisited()) {
|
|
||||||
const title = this._titleElement();
|
|
||||||
if (title) {
|
|
||||||
title.removeEventListener("focus", this._onTitleFocus);
|
|
||||||
title.removeEventListener("blur", this._onTitleBlur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
onMessage(data) {
|
|
||||||
const nodeClassList = document.querySelector(
|
|
||||||
`.indicator-topic-${data.topic_id}`
|
|
||||||
).classList;
|
|
||||||
|
|
||||||
nodeClassList.toggle("read", !data.show_indicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("topic.participant_groups")
|
|
||||||
participantGroups(groupNames) {
|
|
||||||
if (!groupNames) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupNames.map((name) => {
|
|
||||||
return { name, url: groupPath(name) };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("topic.id")
|
|
||||||
unreadIndicatorChannel(topicId) {
|
|
||||||
return `/private-messages/unread-indicator/${topicId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("topic.unread_by_group_member")
|
|
||||||
unreadClass(unreadByGroupMember) {
|
|
||||||
return unreadByGroupMember ? "" : "read";
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("topic.unread_by_group_member")
|
|
||||||
includeUnreadIndicator(unreadByGroupMember) {
|
|
||||||
return typeof unreadByGroupMember !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
newDotText() {
|
|
||||||
return this.currentUser && this.currentUser.trust_level > 0
|
|
||||||
? ""
|
|
||||||
: i18n("filters.new.lower_title");
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("topic", "lastVisitedTopic")
|
|
||||||
unboundClassNames(topic, lastVisitedTopic) {
|
|
||||||
let classes = [];
|
|
||||||
|
|
||||||
if (topic.get("category")) {
|
|
||||||
classes.push("category-" + topic.get("category.fullSlug"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.get("tags")) {
|
|
||||||
topic.get("tags").forEach((tag) => classes.push("tag-" + tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.get("hasExcerpt")) {
|
|
||||||
classes.push("has-excerpt");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.get("unseen")) {
|
|
||||||
classes.push("unseen-topic");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.unread_posts) {
|
|
||||||
classes.push("unread-posts");
|
|
||||||
}
|
|
||||||
|
|
||||||
["liked", "archived", "bookmarked", "pinned", "closed"].forEach((name) => {
|
|
||||||
if (topic.get(name)) {
|
|
||||||
classes.push(name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (topic === lastVisitedTopic) {
|
|
||||||
classes.push("last-visit");
|
|
||||||
}
|
|
||||||
|
|
||||||
return classes.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
hasLikes() {
|
|
||||||
return this.get("topic.like_count") > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasOpLikes() {
|
|
||||||
return this.get("topic.op_like_count") > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
expandPinned() {
|
|
||||||
return applyValueTransformer(
|
|
||||||
"topic-list-item-expand-pinned",
|
|
||||||
this._expandPinned,
|
|
||||||
{
|
|
||||||
topic: this.topic,
|
|
||||||
mobileView: this.site.mobileView,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get _expandPinned() {
|
|
||||||
const pinned = this.get("topic.pinned");
|
|
||||||
if (!pinned) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.site.mobileView) {
|
|
||||||
if (!this.siteSettings.show_pinned_excerpt_mobile) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!this.siteSettings.show_pinned_excerpt_desktop) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.expandGloballyPinned && this.get("topic.pinned_globally")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.expandAllPinned) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
showEntrance() {
|
|
||||||
return showEntrance.call(this, ...arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
click(e) {
|
|
||||||
const result = this.showEntrance(e);
|
|
||||||
if (result === false) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topic = this.topic;
|
|
||||||
const target = e.target;
|
|
||||||
const classList = target.classList;
|
|
||||||
if (classList.contains("bulk-select")) {
|
|
||||||
const selected = this.selected;
|
|
||||||
|
|
||||||
if (target.checked) {
|
|
||||||
selected.addObject(topic);
|
|
||||||
|
|
||||||
if (this.lastChecked && e.shiftKey) {
|
|
||||||
const bulkSelects = Array.from(
|
|
||||||
document.querySelectorAll("input.bulk-select")
|
|
||||||
),
|
|
||||||
from = bulkSelects.indexOf(target),
|
|
||||||
to = bulkSelects.findIndex((el) => el.id === this.lastChecked.id),
|
|
||||||
start = Math.min(from, to),
|
|
||||||
end = Math.max(from, to);
|
|
||||||
|
|
||||||
bulkSelects
|
|
||||||
.slice(start, end)
|
|
||||||
.filter((el) => el.checked !== true)
|
|
||||||
.forEach((checkbox) => {
|
|
||||||
checkbox.click();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set("lastChecked", target);
|
|
||||||
} else {
|
|
||||||
selected.removeObject(topic);
|
|
||||||
this.set("lastChecked", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
classList.contains("raw-topic-link") ||
|
|
||||||
classList.contains("post-activity")
|
|
||||||
) {
|
|
||||||
if (wantsNewWindow(e)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
return this.navigateToTopic(topic, target.getAttribute("href"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// make full row click target on mobile, due to size constraints
|
|
||||||
if (
|
|
||||||
this.site.mobileView &&
|
|
||||||
e.target.matches(
|
|
||||||
".topic-list-data, .main-link, .right, .topic-item-stats, .topic-item-stats__category-tags, .discourse-tags"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (wantsNewWindow(e)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
return this.navigateToTopic(topic, topic.lastUnreadUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.unhandledRowClick(e, topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
unhandledRowClick() {}
|
|
||||||
|
|
||||||
keyDown(e) {
|
|
||||||
if (e.key === "Enter" && e.target.classList.contains("post-activity")) {
|
|
||||||
e.preventDefault();
|
|
||||||
return this.navigateToTopic(this.topic, e.target.getAttribute("href"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateToTopic() {
|
|
||||||
return navigateToTopic.call(this, ...arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
highlight(opts = { isLastViewedTopic: false }) {
|
|
||||||
schedule("afterRender", () => {
|
|
||||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.classList.add("highlighted");
|
|
||||||
this.element.setAttribute(
|
|
||||||
"data-is-last-viewed-topic",
|
|
||||||
opts.isLastViewedTopic
|
|
||||||
);
|
|
||||||
this.element.addEventListener("animationend", () => {
|
|
||||||
this.element.classList.remove("highlighted");
|
|
||||||
});
|
|
||||||
if (opts.isLastViewedTopic && this._shouldFocusLastVisited()) {
|
|
||||||
this._titleElement()?.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@on("didInsertElement")
|
|
||||||
_highlightIfNeeded() {
|
|
||||||
// highlight the last topic viewed
|
|
||||||
const lastViewedTopicId = this.historyStore.get("lastTopicIdViewed");
|
|
||||||
const isLastViewedTopic = lastViewedTopicId === this.topic.id;
|
|
||||||
|
|
||||||
if (isLastViewedTopic) {
|
|
||||||
this.historyStore.delete("lastTopicIdViewed");
|
|
||||||
this.highlight({ isLastViewedTopic: true });
|
|
||||||
} else if (this.get("topic.highlight")) {
|
|
||||||
// highlight new topics that have been loaded from the server or the one we just created
|
|
||||||
this.set("topic.highlight", false);
|
|
||||||
this.highlight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
_onTitleFocus() {
|
|
||||||
if (this.element && !this.isDestroying && !this.isDestroyed) {
|
|
||||||
this.element.classList.add("selected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
_onTitleBlur() {
|
|
||||||
if (this.element && !this.isDestroying && !this.isDestroyed) {
|
|
||||||
this.element.classList.remove("selected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_shouldFocusLastVisited() {
|
|
||||||
return this.site.desktopView && this.focusLastVisitedTopic;
|
|
||||||
}
|
|
||||||
|
|
||||||
_titleElement() {
|
|
||||||
return this.element.querySelector(".main-link .title");
|
|
||||||
}
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<PluginOutlet
|
|
||||||
@name="above-topic-list-item"
|
|
||||||
@outletArgs={{hash topic=this.topic}}
|
|
||||||
/>
|
|
||||||
{{this.topicListItemContents}}
|
|
||||||
</template>
|
|
||||||
}
|
|
@ -1,339 +1,20 @@
|
|||||||
import Component from "@ember/component";
|
import 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>
|
||||||
}
|
}
|
||||||
|
@ -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 ~}}
|
||||||
|
@ -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")
|
||||||
);
|
);
|
||||||
|
@ -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(
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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("");
|
||||||
|
@ -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}}`", {
|
||||||
|
@ -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, {
|
||||||
|
@ -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(
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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",
|
||||||
|
@ -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));
|
||||||
|
@ -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",
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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)) {
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
|
||||||
import { connectorsExist } from "discourse/lib/plugin-connectors";
|
|
||||||
import RawHandlebars from "discourse/lib/raw-handlebars";
|
|
||||||
import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
|
||||||
|
|
||||||
const GlimmerPluginOutletWrapper = <template>
|
|
||||||
{{~! no whitespace ~}}
|
|
||||||
<PluginOutlet @name={{@data.name}} @outletArgs={{@data.outletArgs}} />
|
|
||||||
{{~! no whitespace ~}}
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
RawHandlebars.registerHelper("plugin-outlet", function (options) {
|
|
||||||
const { name, tagName, outletArgs } = options.hash;
|
|
||||||
|
|
||||||
if (!connectorsExist(name)) {
|
|
||||||
return htmlSafe("");
|
|
||||||
}
|
|
||||||
|
|
||||||
return htmlSafe(
|
|
||||||
rawRenderGlimmer(
|
|
||||||
this,
|
|
||||||
`${tagName || "span"}.hbr-ember-outlet`,
|
|
||||||
GlimmerPluginOutletWrapper,
|
|
||||||
{ name, outletArgs }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,8 +1,5 @@
|
|||||||
import { htmlSafe } from "@ember/template";
|
import { 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)));
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { registerRawHelper } from "discourse/lib/helpers";
|
|
||||||
|
|
||||||
registerRawHelper("raw-hash", function (params) {
|
|
||||||
return params;
|
|
||||||
});
|
|
@ -1,11 +0,0 @@
|
|||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import { rawConnectorsFor } from "discourse/lib/plugin-connectors";
|
|
||||||
import RawHandlebars from "discourse/lib/raw-handlebars";
|
|
||||||
|
|
||||||
RawHandlebars.registerHelper("raw-plugin-outlet", function (args) {
|
|
||||||
const connectors = rawConnectorsFor(args.hash.name);
|
|
||||||
if (connectors.length) {
|
|
||||||
const output = connectors.map((c) => c.template({ context: this }));
|
|
||||||
return htmlSafe(output.join(""));
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,59 +0,0 @@
|
|||||||
import Helper from "@ember/component/helper";
|
|
||||||
import { registerDestructor } from "@ember/destroyable";
|
|
||||||
import { getOwner, setOwner } from "@ember/owner";
|
|
||||||
import { schedule } from "@ember/runloop";
|
|
||||||
import { service } from "@ember/service";
|
|
||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import { bind } from "discourse/lib/decorators";
|
|
||||||
import { helperContext, registerRawHelper } from "discourse/lib/helpers";
|
|
||||||
import { RUNTIME_OPTIONS } from "discourse/lib/raw-handlebars-helpers";
|
|
||||||
import { findRawTemplate } from "discourse/lib/raw-templates";
|
|
||||||
|
|
||||||
function renderRaw(ctx, template, templateName, params) {
|
|
||||||
params = { ...params };
|
|
||||||
params.parent ||= ctx;
|
|
||||||
|
|
||||||
let context = helperContext();
|
|
||||||
if (!params.view) {
|
|
||||||
const viewClass = context.registry.resolve(`raw-view:${templateName}`);
|
|
||||||
|
|
||||||
if (viewClass) {
|
|
||||||
setOwner(params, getOwner(context));
|
|
||||||
params.view = viewClass.create(params, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!params.view) {
|
|
||||||
params = { ...params, ...context };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return htmlSafe(template(params, RUNTIME_OPTIONS));
|
|
||||||
}
|
|
||||||
|
|
||||||
const helperFunction = function (templateName, params) {
|
|
||||||
templateName = templateName.replace(".", "/");
|
|
||||||
|
|
||||||
const template = findRawTemplate(templateName);
|
|
||||||
if (!template) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn("Could not find raw template: " + templateName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return renderRaw(this, template, templateName, params);
|
|
||||||
};
|
|
||||||
|
|
||||||
registerRawHelper("raw", helperFunction);
|
|
||||||
|
|
||||||
export default class RawHelper extends Helper {
|
|
||||||
@service renderGlimmer;
|
|
||||||
|
|
||||||
compute(args, params) {
|
|
||||||
registerDestructor(this, this.cleanup);
|
|
||||||
return helperFunction(...args, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
cleanup() {
|
|
||||||
schedule("afterRender", () => this.renderGlimmer.cleanup());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,7 @@
|
|||||||
import { htmlSafe, isHTMLSafe } from "@ember/template";
|
import { 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));
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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}`;
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import deprecated from "discourse/lib/deprecated";
|
|
||||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
|
||||||
|
|
||||||
const TopicStatusIcons = new (class {
|
|
||||||
entries = [];
|
|
||||||
|
|
||||||
addObject(entry) {
|
|
||||||
deprecated(
|
|
||||||
"TopicStatusIcons is deprecated. Use 'after-topic-status' plugin outlet instead.",
|
|
||||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
|
||||||
);
|
|
||||||
|
|
||||||
const [attribute, iconName, titleKey] = entry;
|
|
||||||
this.entries.push({ attribute, iconName, titleKey });
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
export default TopicStatusIcons;
|
|
@ -1,6 +1,3 @@
|
|||||||
import { registerRawHelper } from "discourse/lib/helpers";
|
|
||||||
|
|
||||||
registerRawHelper("value-entered", valueEntered);
|
|
||||||
export default function valueEntered(value) {
|
export default function valueEntered(value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { eagerLoadRawTemplateModules } from "discourse/lib/raw-templates";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
initialize() {
|
|
||||||
eagerLoadRawTemplateModules();
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,14 +0,0 @@
|
|||||||
import { registerDeprecationHandler } from "discourse/lib/deprecated";
|
|
||||||
import { needsHbrTopicList } from "discourse/lib/raw-templates";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
before: "inject-objects",
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
registerDeprecationHandler((message, opts) => {
|
|
||||||
if (opts?.id === "discourse.hbr-topic-list-overrides") {
|
|
||||||
needsHbrTopicList(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,9 +1,7 @@
|
|||||||
import Helper from "@ember/component/helper";
|
import 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);
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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 = {};
|
||||||
|
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
import { get } from "@ember/object";
|
|
||||||
|
|
||||||
export const RUNTIME_OPTIONS = {
|
|
||||||
allowProtoPropertiesByDefault: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function registerRawHelpers(hbs, handlebarsClass, owner) {
|
|
||||||
if (!hbs.helpers) {
|
|
||||||
hbs.helpers = Object.create(handlebarsClass.helpers);
|
|
||||||
}
|
|
||||||
|
|
||||||
lazyLoadHelpers(hbs, owner);
|
|
||||||
|
|
||||||
if (hbs.__helpers_registered) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hbs.__helpers_registered = true;
|
|
||||||
|
|
||||||
hbs.helpers["get"] = function (context, options) {
|
|
||||||
if (!context || !options.contexts) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof context !== "string") {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
let firstContext = options.contexts[0];
|
|
||||||
let val = firstContext[context];
|
|
||||||
|
|
||||||
if (context.toString().startsWith("controller.")) {
|
|
||||||
context = context.slice(context.indexOf(".") + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return val === undefined ? get(firstContext, context) : val;
|
|
||||||
};
|
|
||||||
|
|
||||||
// #each .. in support (as format is transformed to this)
|
|
||||||
hbs.registerHelper(
|
|
||||||
"each",
|
|
||||||
function (localName, inKeyword, contextName, options) {
|
|
||||||
if (typeof contextName === "undefined") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let list = get(this, contextName);
|
|
||||||
let output = [];
|
|
||||||
let innerContext = options.contexts[0];
|
|
||||||
for (let i = 0; i < list.length; i++) {
|
|
||||||
innerContext[localName] = list[i];
|
|
||||||
output.push(options.fn(innerContext));
|
|
||||||
}
|
|
||||||
delete innerContext[localName];
|
|
||||||
return output.join("");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function stringCompatHelper(fn) {
|
|
||||||
const old = hbs.helpers[fn];
|
|
||||||
hbs.helpers[fn] = function (context, options) {
|
|
||||||
return old.apply(this, [hbs.helpers.get(context, options), options]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK: Ensure that the variable is resolved only once.
|
|
||||||
// The "get" function will be called twice because both `if` and `unless`
|
|
||||||
// helpers are patched to resolve the variable and `unless` is implemented
|
|
||||||
// as not `if`. For example, for {{#unless var}} will generate a stack
|
|
||||||
// trace like:
|
|
||||||
//
|
|
||||||
// - patched-unless("var") "var" is resolved to its value, val
|
|
||||||
// - unless(val) unless is implemented as !if
|
|
||||||
// - !patched-if(val) val is already resolved, but it is resolved again
|
|
||||||
// - !if(???) at this point, ??? usually stands for undefined
|
|
||||||
//
|
|
||||||
// The following code ensures that patched-unless will call `if` directly,
|
|
||||||
// `patched-unless("var")` will return `!if(val)`.
|
|
||||||
const oldIf = hbs.helpers["if"];
|
|
||||||
hbs.helpers["unless"] = function (context, options) {
|
|
||||||
return oldIf.apply(this, [
|
|
||||||
hbs.helpers.get(context, options),
|
|
||||||
{
|
|
||||||
fn: options.inverse,
|
|
||||||
inverse: options.fn,
|
|
||||||
hash: options.hash,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
stringCompatHelper("if");
|
|
||||||
stringCompatHelper("with");
|
|
||||||
}
|
|
||||||
|
|
||||||
function lazyLoadHelpers(hbs, owner) {
|
|
||||||
// Reimplements `helperMissing` so that it triggers a lookup() for
|
|
||||||
// a helper of that name. Means we don't need to eagerly load all
|
|
||||||
// helpers/* files during boot.
|
|
||||||
hbs.registerHelper("helperMissing", function (...args) {
|
|
||||||
const opts = args[args.length - 1];
|
|
||||||
if (opts?.name) {
|
|
||||||
// Lookup and evaluate the relevant module. Raw helpers may be registered as a side effect
|
|
||||||
owner.lookup(`helper:${opts.name}`);
|
|
||||||
|
|
||||||
if (hbs.helpers[opts.name]) {
|
|
||||||
// Helper now exists, invoke it
|
|
||||||
return hbs.helpers[opts.name]?.call(this, ...arguments);
|
|
||||||
} else {
|
|
||||||
// Not a helper, treat as property
|
|
||||||
return hbs.helpers["get"].call(this, ...arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
import Handlebars from "handlebars";
|
|
||||||
|
|
||||||
// This is a mechanism for quickly rendering templates which is Ember aware
|
|
||||||
// templates are highly compatible with Ember so you don't need to worry about calling "get"
|
|
||||||
// and discourseComputed properties function, additionally it uses stringParams like Ember does
|
|
||||||
const RawHandlebars = Handlebars.create();
|
|
||||||
|
|
||||||
function buildPath(blk, args) {
|
|
||||||
let result = {
|
|
||||||
type: "PathExpression",
|
|
||||||
data: false,
|
|
||||||
depth: blk.path.depth,
|
|
||||||
loc: blk.path.loc,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Server side precompile doesn't have jquery.extend
|
|
||||||
Object.keys(args).forEach(function (a) {
|
|
||||||
result[a] = args[a];
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceGet(ast) {
|
|
||||||
let visitor = new Handlebars.Visitor();
|
|
||||||
visitor.mutating = true;
|
|
||||||
|
|
||||||
visitor.MustacheStatement = function (mustache) {
|
|
||||||
if (!(mustache.params.length || mustache.hash)) {
|
|
||||||
mustache.params[0] = mustache.path;
|
|
||||||
mustache.path = buildPath(mustache, {
|
|
||||||
parts: ["get"],
|
|
||||||
original: "get",
|
|
||||||
strict: true,
|
|
||||||
falsy: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache);
|
|
||||||
};
|
|
||||||
|
|
||||||
// rewrite `each x as |y|` as each y in x`
|
|
||||||
// This allows us to use the same syntax in all templates
|
|
||||||
visitor.BlockStatement = function (block) {
|
|
||||||
if (block.path.original === "each" && block.params.length === 1) {
|
|
||||||
let paramName = block.program.blockParams[0];
|
|
||||||
block.params = [
|
|
||||||
buildPath(block, { original: paramName }),
|
|
||||||
{ type: "CommentStatement", value: "in" },
|
|
||||||
block.params[0],
|
|
||||||
];
|
|
||||||
delete block.program.blockParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Handlebars.Visitor.prototype.BlockStatement.call(this, block);
|
|
||||||
};
|
|
||||||
|
|
||||||
visitor.accept(ast);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Handlebars.Compiler) {
|
|
||||||
RawHandlebars.Compiler = function () {};
|
|
||||||
RawHandlebars.Compiler.prototype = Object.create(
|
|
||||||
Handlebars.Compiler.prototype
|
|
||||||
);
|
|
||||||
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
|
|
||||||
|
|
||||||
RawHandlebars.JavaScriptCompiler = function () {};
|
|
||||||
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype = Object.create(
|
|
||||||
Handlebars.JavaScriptCompiler.prototype
|
|
||||||
);
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype.compiler =
|
|
||||||
RawHandlebars.JavaScriptCompiler;
|
|
||||||
RawHandlebars.JavaScriptCompiler.prototype.namespace = "RawHandlebars";
|
|
||||||
|
|
||||||
RawHandlebars.precompile = function (value, asObject, { plugins = [] } = {}) {
|
|
||||||
let ast = Handlebars.parse(value);
|
|
||||||
replaceGet(ast);
|
|
||||||
plugins.forEach((plugin) => plugin(ast));
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
knownHelpers: {
|
|
||||||
get: true,
|
|
||||||
},
|
|
||||||
data: true,
|
|
||||||
stringParams: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
asObject = asObject === undefined ? true : asObject;
|
|
||||||
|
|
||||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
|
||||||
return new RawHandlebars.JavaScriptCompiler().compile(
|
|
||||||
environment,
|
|
||||||
options,
|
|
||||||
undefined,
|
|
||||||
asObject
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
RawHandlebars.compile = function (string, { plugins = [] } = {}) {
|
|
||||||
let ast = Handlebars.parse(string);
|
|
||||||
replaceGet(ast);
|
|
||||||
plugins.forEach((plugin) => plugin(ast));
|
|
||||||
|
|
||||||
// this forces us to rewrite helpers
|
|
||||||
let options = { data: true, stringParams: true };
|
|
||||||
let environment = new RawHandlebars.Compiler().compile(ast, options);
|
|
||||||
let templateSpec = new RawHandlebars.JavaScriptCompiler().compile(
|
|
||||||
environment,
|
|
||||||
options,
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
let t = RawHandlebars.template(templateSpec);
|
|
||||||
t.isMethod = false;
|
|
||||||
|
|
||||||
return t;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function template() {
|
|
||||||
return RawHandlebars.template.apply(this, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function precompile() {
|
|
||||||
return RawHandlebars.precompile.apply(this, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compile() {
|
|
||||||
return RawHandlebars.compile.apply(this, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RawHandlebars;
|
|
@ -1,55 +0,0 @@
|
|||||||
import { getOwner } from "@ember/owner";
|
|
||||||
import { schedule } from "@ember/runloop";
|
|
||||||
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate HTML which can be inserted into a raw-hbs template to render a Glimmer component.
|
|
||||||
* The result of this function must be rendered immediately, so that an `afterRender` hook
|
|
||||||
* can access the element in the DOM and attach the glimmer component.
|
|
||||||
*
|
|
||||||
* Example usage:
|
|
||||||
*
|
|
||||||
* ```hbs
|
|
||||||
* {{! raw-templates/something-cool.hbr }}
|
|
||||||
* {{html-safe view.html}}
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* ```gjs
|
|
||||||
* // raw-views/something-cool.gjs
|
|
||||||
* import EmberObject from "@ember/object";
|
|
||||||
* import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
|
||||||
*
|
|
||||||
* export default class SomethingCool extends EmberObject {
|
|
||||||
* get html() {
|
|
||||||
* return rawRenderGlimmer(this, "div", <template>Hello {{@data.name}}</template>, { name: this.name });
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* And then this can be invoked from any other raw view (including raw plugin outlets) like:
|
|
||||||
*
|
|
||||||
* ```hbs
|
|
||||||
* {{raw "something-cool" name="david"}}
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export default function rawRenderGlimmer(owner, renderInto, component, data) {
|
|
||||||
const renderGlimmerService = getOwner(owner).lookup("service:render-glimmer");
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
const id = `_render_glimmer_${counter}`;
|
|
||||||
const [type, ...classNames] = renderInto.split(".");
|
|
||||||
|
|
||||||
schedule("afterRender", () => {
|
|
||||||
const element = document.getElementById(id);
|
|
||||||
if (element) {
|
|
||||||
const componentInfo = {
|
|
||||||
element,
|
|
||||||
component,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
renderGlimmerService.add(componentInfo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return `<${type} id="${id}" class="${classNames.join(" ")}"></${type}>`;
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
import require from "require";
|
|
||||||
import deprecated from "discourse/lib/deprecated";
|
|
||||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
|
||||||
import { getResolverOption } from "discourse/resolver";
|
|
||||||
|
|
||||||
export const __DISCOURSE_RAW_TEMPLATES = {};
|
|
||||||
let _needsHbrTopicList = false;
|
|
||||||
|
|
||||||
export function needsHbrTopicList(value) {
|
|
||||||
if (value === undefined) {
|
|
||||||
return _needsHbrTopicList;
|
|
||||||
} else {
|
|
||||||
_needsHbrTopicList = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resetNeedsHbrTopicList() {
|
|
||||||
_needsHbrTopicList = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TOPIC_LIST_TEMPLATE_NAMES = [
|
|
||||||
"list/action-list",
|
|
||||||
"list/activity-column",
|
|
||||||
"list/category-column",
|
|
||||||
"list/new-list-header-controls",
|
|
||||||
"list/participant-groups",
|
|
||||||
"list/post-count-or-badges",
|
|
||||||
"list/posters-column",
|
|
||||||
"list/posts-count-column",
|
|
||||||
"list/topic-excerpt",
|
|
||||||
"list/topic-list-item",
|
|
||||||
"list/unread-indicator",
|
|
||||||
"list/visited-line",
|
|
||||||
"mobile/list/topic-list-item",
|
|
||||||
"topic-bulk-select-dropdown",
|
|
||||||
"topic-list-header-column",
|
|
||||||
"topic-list-header",
|
|
||||||
"topic-post-badges",
|
|
||||||
"topic-status",
|
|
||||||
];
|
|
||||||
|
|
||||||
export function addRawTemplate(name, template, opts = {}) {
|
|
||||||
const cleanName = name.replace(/^javascripts\//, "");
|
|
||||||
if (
|
|
||||||
(TOPIC_LIST_TEMPLATE_NAMES.includes(cleanName) ||
|
|
||||||
name.includes("/connectors/")) &&
|
|
||||||
!opts.core &&
|
|
||||||
!opts.hasModernReplacement
|
|
||||||
) {
|
|
||||||
const message = `[${name}] hbr topic-list template overrides and connectors are deprecated. Use the value transformer \`topic-list-columns\` and other new topic-list plugin APIs instead.`;
|
|
||||||
|
|
||||||
// NOTE: addRawTemplate is called too early for deprecation handlers to process this:
|
|
||||||
deprecated(message, RAW_TOPIC_LIST_DEPRECATION_OPTIONS);
|
|
||||||
|
|
||||||
needsHbrTopicList(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core templates should never overwrite themes / plugins
|
|
||||||
if (opts.core && __DISCOURSE_RAW_TEMPLATES[name]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
__DISCOURSE_RAW_TEMPLATES[name] = template;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeRawTemplate(name) {
|
|
||||||
delete __DISCOURSE_RAW_TEMPLATES[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findRawTemplate(name) {
|
|
||||||
if (getResolverOption("mobileView")) {
|
|
||||||
return (
|
|
||||||
__DISCOURSE_RAW_TEMPLATES[`javascripts/mobile/${name}`] ||
|
|
||||||
__DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] ||
|
|
||||||
__DISCOURSE_RAW_TEMPLATES[`mobile/${name}`] ||
|
|
||||||
__DISCOURSE_RAW_TEMPLATES[name]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
__DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] ||
|
|
||||||
__DISCOURSE_RAW_TEMPLATES[name]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildRawConnectorCache() {
|
|
||||||
let result = {};
|
|
||||||
Object.keys(__DISCOURSE_RAW_TEMPLATES).forEach((resource) => {
|
|
||||||
const segments = resource.split("/");
|
|
||||||
const connectorIndex = segments.indexOf("connectors");
|
|
||||||
|
|
||||||
if (connectorIndex >= 0) {
|
|
||||||
const outletName = segments[connectorIndex + 1];
|
|
||||||
result[outletName] ??= [];
|
|
||||||
result[outletName].push({
|
|
||||||
template: __DISCOURSE_RAW_TEMPLATES[resource],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function eagerLoadRawTemplateModules() {
|
|
||||||
for (const key of Object.keys(requirejs.entries)) {
|
|
||||||
if (key.includes("/raw-templates/")) {
|
|
||||||
require(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
import Handlebars from "handlebars";
|
|
||||||
import $ from "jquery";
|
import $ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"));
|
||||||
|
@ -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();
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
<button class='btn-transparent {{class}}' title='{{i18n "topics.bulk.toggle"}}'>
|
|
||||||
{{d-icon icon}}
|
|
||||||
</button>
|
|
@ -1,8 +0,0 @@
|
|||||||
{{#if postNumbers}}
|
|
||||||
<div class='post-actions {{className}}'>
|
|
||||||
{{d-icon icon}}
|
|
||||||
{{#each postNumbers as |postNumber|}}
|
|
||||||
<a href='{{topic.url}}/{{postNumber}}'>#{{postNumber}}</a>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
@ -1,7 +0,0 @@
|
|||||||
<{{tagName}} class="{{class}} {{cold-age-class topic.createdAt startDate=topic.bumpedAt class=""}} activity" title="{{html-safe topic.bumpedAtTitle}}">
|
|
||||||
<a class="post-activity" href="{{topic.lastPostUrl}}">
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-relative-date"~}}
|
|
||||||
{{~plugin-outlet name="topic-list-before-relative-date" outletArgs=(raw-hash topic=topic)~}}
|
|
||||||
{{~format-date topic.bumpedAt format="tiny" noTitle="true"~}}
|
|
||||||
</a>
|
|
||||||
</{{tagName}}>
|
|
@ -1 +0,0 @@
|
|||||||
<td class='category topic-list-data'>{{category-link category}}</td>
|
|
@ -1,13 +0,0 @@
|
|||||||
{{#if view.staticLabel}}
|
|
||||||
<span class="static-label">{{view.staticLabel}}</span>
|
|
||||||
{{else}}
|
|
||||||
<button class="topics-replies-toggle --all{{#if view.allActive}} active{{/if}}">
|
|
||||||
{{i18n "filters.new.all"}}
|
|
||||||
</button>
|
|
||||||
<button class="topics-replies-toggle --topics{{#if view.topicsActive}} active{{/if}}">
|
|
||||||
{{view.topicsButtonLabel}}
|
|
||||||
</button>
|
|
||||||
<button class="topics-replies-toggle --replies{{#if view.repliesActive}} active{{/if}}">
|
|
||||||
{{view.repliesButtonLabel}}
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
@ -1,18 +0,0 @@
|
|||||||
<div
|
|
||||||
class="participant-group-wrapper"
|
|
||||||
role="list"
|
|
||||||
aria-label="{{i18n 'topic.participant_groups'}}"
|
|
||||||
>
|
|
||||||
{{#each groups as |group|}}
|
|
||||||
<div class="participant-group">
|
|
||||||
<a
|
|
||||||
class="user-group trigger-group-card"
|
|
||||||
href="{{group.url}}"
|
|
||||||
data-group-card="{{group.name}}"
|
|
||||||
>
|
|
||||||
{{d-icon "users"}}
|
|
||||||
{{group.name}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||||||
{{#if view.showBadges}}
|
|
||||||
{{raw "topic-post-badges" unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
|
|
||||||
{{else}}
|
|
||||||
{{raw "list/posts-count-column" topic=topic tagName="div"}}
|
|
||||||
{{/if}}
|
|
@ -1,9 +0,0 @@
|
|||||||
<td class='posters topic-list-data'>
|
|
||||||
{{#each posters as |poster|}}
|
|
||||||
{{#if poster.moreCount}}
|
|
||||||
<a class="posters-more-count">{{poster.moreCount}}</a>
|
|
||||||
{{else}}
|
|
||||||
<a href="{{poster.user.path}}" data-user-card="{{poster.user.username}}" class="{{poster.extraClasses}}" aria-label="{{i18n "user.profile_possessive" username=poster.user.username}}">{{avatar poster avatarTemplatePath="user.avatar_template" usernamePath="user.username" namePath="user.name" imageSize="small"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
</td>
|
|
@ -1,7 +0,0 @@
|
|||||||
<{{view.tagName}} class='num posts-map posts {{view.likesHeat}} topic-list-data'>
|
|
||||||
<button class="btn-link posts-map badge-posts {{view.likesHeat}}" title="{{view.title}}" aria-label="{{view.title}}">
|
|
||||||
{{raw-plugin-outlet name="topic-list-before-reply-count"}}
|
|
||||||
{{plugin-outlet name="topic-list-before-reply-count" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{number topic.replyCount noTitle="true"}}
|
|
||||||
</button>
|
|
||||||
</{{view.tagName}}>
|
|
@ -1,8 +0,0 @@
|
|||||||
{{#if topic.hasExcerpt}}
|
|
||||||
<a href="{{topic.url}}" class="topic-excerpt">
|
|
||||||
{{dir-span topic.escapedExcerpt htmlSafe="true"}}
|
|
||||||
{{#if topic.excerptTruncated}}
|
|
||||||
<span class="topic-excerpt-more">{{i18n 'read_more'}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</a>
|
|
||||||
{{/if}}
|
|
@ -1,97 +0,0 @@
|
|||||||
{{~raw-plugin-outlet name="topic-list-before-columns"}}
|
|
||||||
|
|
||||||
{{#if bulkSelectEnabled}}
|
|
||||||
<td class="bulk-select topic-list-data">
|
|
||||||
<label for="bulk-select-{{topic.id}}">
|
|
||||||
<input type="checkbox" class="bulk-select" id="bulk-select-{{topic.id}}">
|
|
||||||
</label>
|
|
||||||
</td>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{!--
|
|
||||||
The `~` syntax strip spaces between the elements, making it produce
|
|
||||||
`<a class=topic-post-badges>Some text</a><span class=topic-post-badges>`,
|
|
||||||
with no space between them.
|
|
||||||
This causes the topic-post-badge to be considered the same word as "text"
|
|
||||||
at the end of the link, preventing it from line wrapping onto its own line.
|
|
||||||
--}}
|
|
||||||
<td class='main-link clearfix topic-list-data' colspan="1">
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-link"}}
|
|
||||||
{{~plugin-outlet name="topic-list-before-link" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
<span class="link-top-line" role="heading" aria-level="2">
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-status"}}
|
|
||||||
{{~plugin-outlet name="topic-list-before-status" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{~raw "topic-status" topic=topic}}
|
|
||||||
{{~topic-link topic class="raw-link raw-topic-link"}}
|
|
||||||
{{~#if topic.featured_link}}
|
|
||||||
{{~topic-featured-link topic}}
|
|
||||||
{{~/if}}
|
|
||||||
{{~raw-plugin-outlet name="topic-list-after-title"}}
|
|
||||||
{{~plugin-outlet name="topic-list-after-title" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{~raw "list/unread-indicator" includeUnreadIndicator=includeUnreadIndicator
|
|
||||||
topicId=topic.id
|
|
||||||
unreadClass=unreadClass~}}
|
|
||||||
{{~#if showTopicPostBadges}}
|
|
||||||
{{~raw "topic-post-badges" unreadPosts=topic.unread_posts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
|
|
||||||
{{~/if}}
|
|
||||||
</span>
|
|
||||||
<div class="link-bottom-line">
|
|
||||||
{{#unless hideCategory}}
|
|
||||||
{{#unless topic.isPinnedUncategorized}}
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-category"}}
|
|
||||||
{{~plugin-outlet name="topic-list-before-category" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{category-link topic.category}}
|
|
||||||
{{/unless}}
|
|
||||||
{{/unless}}
|
|
||||||
{{discourse-tags topic mode="list" tagsForUser=tagsForUser}}
|
|
||||||
{{#if participantGroups}}
|
|
||||||
{{raw "list/participant-groups" groups=participantGroups}}
|
|
||||||
{{/if}}
|
|
||||||
{{raw "list/action-list" topic=topic postNumbers=topic.liked_post_numbers className="likes" icon="heart"}}
|
|
||||||
</div>
|
|
||||||
{{#if expandPinned}}
|
|
||||||
{{raw "list/topic-excerpt" topic=topic}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{~raw-plugin-outlet name="topic-list-main-link-bottom"}}
|
|
||||||
{{~plugin-outlet name="topic-list-main-link-bottom" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{{~raw-plugin-outlet name="topic-list-after-main-link"}}
|
|
||||||
{{~plugin-outlet name="topic-list-after-main-link" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
|
|
||||||
{{#if showPosters}}
|
|
||||||
{{raw "list/posters-column" posters=topic.featuredUsers}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{raw "list/posts-count-column" topic=topic}}
|
|
||||||
|
|
||||||
{{#if showLikes}}
|
|
||||||
<td class="num likes topic-list-data">
|
|
||||||
{{#if hasLikes}}
|
|
||||||
<a href='{{topic.summaryUrl}}'>
|
|
||||||
{{number topic.like_count}} {{d-icon "heart"}}
|
|
||||||
</a>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if showOpLikes}}
|
|
||||||
<td class="num likes">
|
|
||||||
{{#if hasOpLikes}}
|
|
||||||
<a href='{{topic.summaryUrl}}'>
|
|
||||||
{{number topic.op_like_count}} {{d-icon "heart"}}
|
|
||||||
</a>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<td class="num views {{topic.viewsHeat}} topic-list-data">
|
|
||||||
{{raw-plugin-outlet name="topic-list-before-view-count"}}
|
|
||||||
{{plugin-outlet name="topic-list-before-view-count" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{number topic.views numberKey="views_long"}}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{{raw "list/activity-column" topic=topic class="num topic-list-data" tagName="td"}}
|
|
||||||
|
|
||||||
{{~raw-plugin-outlet name="topic-list-after-columns"}}
|
|
@ -1,5 +0,0 @@
|
|||||||
{{~#if includeUnreadIndicator~}}
|
|
||||||
<span class='badge badge-notification unread-indicator indicator-topic-{{topicId}} {{unreadClass}}' title='{{i18n "topic.unread_indicator"}}'>
|
|
||||||
{{~d-icon "asterisk"}}
|
|
||||||
</span>
|
|
||||||
{{~/if}}
|
|
@ -1,9 +0,0 @@
|
|||||||
{{#if view.isLastVisited}}
|
|
||||||
<tr class='topic-list-item-separator'>
|
|
||||||
<td class="topic-list-data" colspan="6">
|
|
||||||
<span>
|
|
||||||
{{i18n 'topics.new_messages_marker'}}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/if}}
|
|
@ -1,63 +0,0 @@
|
|||||||
<td class="topic-list-data">
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-columns"}}
|
|
||||||
<div class='pull-left'>
|
|
||||||
{{#if bulkSelectEnabled}}
|
|
||||||
<label for="bulk-select-{{topic.id}}">
|
|
||||||
<input type="checkbox" class="bulk-select" id="bulk-select-{{topic.id}}">
|
|
||||||
</label>
|
|
||||||
{{else}}
|
|
||||||
<a href="{{topic.lastPostUrl}}" aria-label="{{i18n 'latest_poster_link' username=topic.lastPosterUser.username}}" data-user-card="{{topic.lastPosterUser.username}}">{{avatar topic.lastPosterUser imageSize="large"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class='topic-item-metadata right'>
|
|
||||||
{{!--
|
|
||||||
The `~` syntax strip spaces between the elements, making it produce
|
|
||||||
`<a class=topic-post-badges>Some text</a><span class=topic-post-badges>`,
|
|
||||||
with no space between them.
|
|
||||||
This causes the topic-post-badge to be considered the same word as "text"
|
|
||||||
at the end of the link, preventing it from line wrapping onto its own line.
|
|
||||||
--}}
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-link"}}
|
|
||||||
{{~plugin-outlet name="topic-list-before-link" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
<div class='main-link'>
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-status"}}
|
|
||||||
{{~plugin-outlet name="topic-list-before-status" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{~raw "topic-status" topic=topic~}}
|
|
||||||
{{~topic-link topic class="raw-link raw-topic-link"}}
|
|
||||||
{{~#if topic.featured_link~}}
|
|
||||||
{{~topic-featured-link topic~}}
|
|
||||||
{{~/if~}}
|
|
||||||
{{~raw-plugin-outlet name="topic-list-after-title"}}
|
|
||||||
{{~plugin-outlet name="topic-list-after-title" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{~#if topic.unseen~}}
|
|
||||||
<span class="topic-post-badges"> <span class="badge-notification new-topic"></span></span>
|
|
||||||
{{~/if~}}
|
|
||||||
{{~#if expandPinned~}}
|
|
||||||
{{~raw "list/topic-excerpt" topic=topic~}}
|
|
||||||
{{~/if~}}
|
|
||||||
{{~raw-plugin-outlet name="topic-list-main-link-bottom"}}
|
|
||||||
{{~plugin-outlet name="topic-list-main-link-bottom" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
</div>
|
|
||||||
{{~raw-plugin-outlet name="topic-list-after-main-link"}}
|
|
||||||
{{~plugin-outlet name="topic-list-after-main-link" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
<div class='pull-right'>
|
|
||||||
{{raw "list/post-count-or-badges" topic=topic postBadgesEnabled=showTopicPostBadges}}
|
|
||||||
</div>
|
|
||||||
<div class="topic-item-stats clearfix">
|
|
||||||
<span class="topic-item-stats__category-tags">
|
|
||||||
{{#unless hideCategory}}
|
|
||||||
{{~raw-plugin-outlet name="topic-list-before-category"}}
|
|
||||||
{{~plugin-outlet name="topic-list-before-category" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
{{category-link topic.category~}}
|
|
||||||
{{~/unless}}
|
|
||||||
{{~discourse-tags topic mode="list"}}
|
|
||||||
</span>
|
|
||||||
<div class='num activity last'>
|
|
||||||
<span class="age activity" title="{{topic.bumpedAtTitle}}"><a
|
|
||||||
href="{{topic.lastPostUrl}}">{{format-date topic.bumpedAt format="tiny" noTitle="true"}}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{{~plugin-outlet name="topic-list-after-topic-item-stats" outletArgs=(raw-hash topic=topic)}}
|
|
||||||
</div>
|
|
||||||
{{~raw-plugin-outlet name="topic-list-after-columns"}}
|
|
||||||
</td>
|
|
@ -1 +0,0 @@
|
|||||||
{{{view.html}}}
|
|
@ -1,35 +0,0 @@
|
|||||||
<th data-sort-order='{{order}}' class='{{view.className}} topic-list-data' scope="col" {{#if view.ariaSort}}aria-sort='{{view.ariaSort}}'{{/if}}>
|
|
||||||
{{~#if canBulkSelect}}
|
|
||||||
{{~#if showBulkToggle}}
|
|
||||||
{{raw "flat-button" class="bulk-select" icon="list-check" title="topics.bulk.toggle"}}
|
|
||||||
{{/if ~}}
|
|
||||||
{{~#if bulkSelectEnabled}}
|
|
||||||
<span class='bulk-select-topics'>
|
|
||||||
{{~#if canDoBulkActions}}
|
|
||||||
{{raw "topic-bulk-select-dropdown" bulkSelectHelper=bulkSelectHelper}}
|
|
||||||
{{/if ~}}
|
|
||||||
<button class='btn btn-default bulk-select-all'>{{i18n "topics.bulk.select_all"}}</button>
|
|
||||||
<button class='btn btn-default bulk-clear-all'>{{i18n "topics.bulk.clear_all"}}</button>
|
|
||||||
</span>
|
|
||||||
{{/if ~}}
|
|
||||||
{{/if ~}}
|
|
||||||
{{~#unless bulkSelectEnabled}}
|
|
||||||
{{~#if view.showTopicsAndRepliesToggle}}
|
|
||||||
{{raw "list/new-list-header-controls" current=newListSubset newRepliesCount=newRepliesCount newTopicsCount=newTopicsCount}}
|
|
||||||
{{else}}
|
|
||||||
{{#if sortable}}
|
|
||||||
<button aria-pressed='{{view.ariaPressed}}'>
|
|
||||||
{{view.localizedName}}
|
|
||||||
{{~#if view.isSorting}}
|
|
||||||
{{d-icon view.sortIcon}}
|
|
||||||
{{/if ~}}
|
|
||||||
</button>
|
|
||||||
{{else}}
|
|
||||||
<span {{#if view.screenreaderOnly}}class="sr-only"{{/if}}>
|
|
||||||
{{view.localizedName}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
|
||||||
{{/if ~}}
|
|
||||||
{{/unless ~}}
|
|
||||||
{{~plugin-outlet name="topic-list-heading-bottom" outletArgs=(raw-hash name=view.name bulkSelectEnabled=bulkSelectEnabled)~}}
|
|
||||||
</th>
|
|
@ -1,26 +0,0 @@
|
|||||||
{{~raw-plugin-outlet name="topic-list-header-before"~}}
|
|
||||||
{{~plugin-outlet name="topic-list-header-before"~}}
|
|
||||||
{{#if bulkSelectEnabled}}
|
|
||||||
<th class="bulk-select topic-list-data">
|
|
||||||
{{#if canBulkSelect}}
|
|
||||||
{{raw "flat-button" class="bulk-select" icon="list-check" title="topics.bulk.toggle"}}
|
|
||||||
{{/if}}
|
|
||||||
</th>
|
|
||||||
{{/if}}
|
|
||||||
{{raw "topic-list-header-column" order='default' name=listTitle bulkSelectEnabled=bulkSelectEnabled showBulkToggle=toggleInTitle canBulkSelect=canBulkSelect canDoBulkActions=canDoBulkActions showTopicsAndRepliesToggle=showTopicsAndRepliesToggle newListSubset=newListSubset newRepliesCount=newRepliesCount newTopicsCount=newTopicsCount bulkSelectHelper=bulkSelectHelper }}
|
|
||||||
{{raw-plugin-outlet name="topic-list-header-after-main-link"}}
|
|
||||||
{{plugin-outlet name="topic-list-header-after-main-link"}}
|
|
||||||
{{#if showPosters}}
|
|
||||||
{{raw "topic-list-header-column" order='posters' name='posters' screenreaderOnly='true'}}
|
|
||||||
{{/if}}
|
|
||||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='posts' name='replies'}}
|
|
||||||
{{#if showLikes}}
|
|
||||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='likes' name='likes'}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if showOpLikes}}
|
|
||||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='op_likes' name='likes'}}
|
|
||||||
{{/if}}
|
|
||||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='views' name='views'}}
|
|
||||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='activity' name='activity'}}
|
|
||||||
{{~raw-plugin-outlet name="topic-list-header-after"~}}
|
|
||||||
{{~plugin-outlet name="topic-list-header-after"~}}
|
|
@ -1,26 +0,0 @@
|
|||||||
<span class="topic-post-badges">
|
|
||||||
{{~#if newPosts~}}
|
|
||||||
<a
|
|
||||||
href="{{url}}"
|
|
||||||
class="badge badge-notification unread-posts"
|
|
||||||
title="{{i18n 'topic.unread_posts' count=newPosts}}"
|
|
||||||
aria-label="{{i18n 'topic.unread_posts' count=newPosts}}"
|
|
||||||
>{{newPosts}}</a>
|
|
||||||
{{~/if}}
|
|
||||||
{{~#if unreadPosts~}}
|
|
||||||
<a
|
|
||||||
href="{{url}}"
|
|
||||||
class="badge badge-notification unread-posts"
|
|
||||||
title="{{i18n 'topic.unread_posts' count=unreadPosts}}"
|
|
||||||
aria-label="{{i18n 'topic.unread_posts' count=unreadPosts}}"
|
|
||||||
>{{unreadPosts}}</a>
|
|
||||||
{{~/if}}
|
|
||||||
{{~#if unseen~}}
|
|
||||||
<a
|
|
||||||
href="{{url}}"
|
|
||||||
class="badge badge-notification new-topic"
|
|
||||||
title="{{i18n 'topic.new'}}"
|
|
||||||
aria-label="{{i18n 'topic.new'}}"
|
|
||||||
>{{newDotText}}</a>
|
|
||||||
{{~/if}}
|
|
||||||
</span>
|
|
@ -1,14 +0,0 @@
|
|||||||
{{~#if view.renderDiv ~}}
|
|
||||||
<div class='topic-statuses'>
|
|
||||||
{{/if ~}}
|
|
||||||
{{~#each view.statuses as |status|~}}
|
|
||||||
{{~#if status.href ~}}
|
|
||||||
<a href='{{status.href}}' title='{{status.title}}' class='topic-status {{status.extraClasses}}'>{{d-icon status.icon}}</a>
|
|
||||||
{{~else ~}}
|
|
||||||
<{{status.openTag}} title='{{status.title}}' class='topic-status'>{{d-icon status.icon class=status.key}}</{{status.closeTag}}>
|
|
||||||
{{~/if ~}}
|
|
||||||
{{~/each}}
|
|
||||||
{{~#if view.showDefault~}}{{d-icon view.showDefault}}{{~/if ~}}
|
|
||||||
{{~#if view.renderDiv ~}}
|
|
||||||
</div>
|
|
||||||
{{/if ~}}
|
|
@ -1,57 +0,0 @@
|
|||||||
import EmberObject from "@ember/object";
|
|
||||||
import discourseComputed from "discourse/lib/decorators";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class NewListHeaderControls extends EmberObject {
|
|
||||||
@discourseComputed
|
|
||||||
topicsActive() {
|
|
||||||
return this.current === "topics";
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
repliesActive() {
|
|
||||||
return this.current === "replies";
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
allActive() {
|
|
||||||
return !this.topicsActive && !this.repliesActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
repliesButtonLabel() {
|
|
||||||
if (this.newRepliesCount > 0) {
|
|
||||||
return i18n("filters.new.replies_with_count", {
|
|
||||||
count: this.newRepliesCount,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return i18n("filters.new.replies");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
topicsButtonLabel() {
|
|
||||||
if (this.newTopicsCount > 0) {
|
|
||||||
return i18n("filters.new.topics_with_count", {
|
|
||||||
count: this.newTopicsCount,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return i18n("filters.new.topics");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
staticLabel() {
|
|
||||||
if (this.noStaticLabel) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (this.newTopicsCount > 0 && this.newRepliesCount > 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (this.newTopicsCount > 0) {
|
|
||||||
return this.topicsButtonLabel;
|
|
||||||
} else {
|
|
||||||
return this.repliesButtonLabel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import EmberObject from "@ember/object";
|
|
||||||
import { and } from "@ember/object/computed";
|
|
||||||
import discourseComputed from "discourse/lib/decorators";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class PostCountOrBadges extends EmberObject {
|
|
||||||
@and("postBadgesEnabled", "topic.unread_posts") showBadges;
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
newDotText() {
|
|
||||||
return this.currentUser && this.currentUser.trust_level > 0
|
|
||||||
? ""
|
|
||||||
: i18n("filters.new.lower_title");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import EmberObject from "@ember/object";
|
|
||||||
import discourseComputed from "discourse/lib/decorators";
|
|
||||||
import I18n from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class PostsCountColumn extends EmberObject {
|
|
||||||
tagName = "td";
|
|
||||||
|
|
||||||
@discourseComputed("topic.like_count", "topic.posts_count")
|
|
||||||
ratio(likeCount, postCount) {
|
|
||||||
const likes = parseFloat(likeCount);
|
|
||||||
const posts = parseFloat(postCount);
|
|
||||||
|
|
||||||
if (posts < 10) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (likes || 0) / posts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("topic.replyCount", "ratioText")
|
|
||||||
title(count, ratio) {
|
|
||||||
return I18n.messageFormat("posts_likes_MF", {
|
|
||||||
count,
|
|
||||||
ratio,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("ratio")
|
|
||||||
ratioText(ratio) {
|
|
||||||
const settings = this.siteSettings;
|
|
||||||
if (ratio > settings.topic_post_like_heat_high) {
|
|
||||||
return "high";
|
|
||||||
}
|
|
||||||
if (ratio > settings.topic_post_like_heat_medium) {
|
|
||||||
return "med";
|
|
||||||
}
|
|
||||||
if (ratio > settings.topic_post_like_heat_low) {
|
|
||||||
return "low";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed("ratioText")
|
|
||||||
likesHeat(ratioText) {
|
|
||||||
if (ratioText && ratioText.length) {
|
|
||||||
return `heatmap-${ratioText}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import EmberObject from "@ember/object";
|
|
||||||
import discourseComputed from "discourse/lib/decorators";
|
|
||||||
|
|
||||||
export default class VisitedLine extends EmberObject {
|
|
||||||
@discourseComputed
|
|
||||||
isLastVisited() {
|
|
||||||
return this.lastVisitedTopic === this.topic;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
import EmberObject, { action } from "@ember/object";
|
|
||||||
import { service } from "@ember/service";
|
|
||||||
import BulkSelectTopicsDropdown from "discourse/components/bulk-select-topics-dropdown";
|
|
||||||
import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
const BulkSelectGlimmerWrapper = <template>
|
|
||||||
<span class="bulk-select-topic-dropdown__count">
|
|
||||||
{{i18n "topics.bulk.selected_count" count=@data.selectedCount}}
|
|
||||||
</span>
|
|
||||||
<BulkSelectTopicsDropdown
|
|
||||||
@bulkSelectHelper={{@data.bulkSelectHelper}}
|
|
||||||
@afterBulkActionComplete={{@data.afterBulkAction}}
|
|
||||||
/>
|
|
||||||
</template>;
|
|
||||||
|
|
||||||
export default class extends EmberObject {
|
|
||||||
@service router;
|
|
||||||
|
|
||||||
get selectedCount() {
|
|
||||||
return this.bulkSelectHelper.selected.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
afterBulkAction() {
|
|
||||||
return this.router.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
get html() {
|
|
||||||
return rawRenderGlimmer(
|
|
||||||
this,
|
|
||||||
"div.bulk-select-topics-dropdown",
|
|
||||||
BulkSelectGlimmerWrapper,
|
|
||||||
{
|
|
||||||
bulkSelectHelper: this.bulkSelectHelper,
|
|
||||||
selectedCount: this.selectedCount,
|
|
||||||
afterBulkAction: this.afterBulkAction,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
import EmberObject from "@ember/object";
|
|
||||||
import { and } from "@ember/object/computed";
|
|
||||||
import discourseComputed from "discourse/lib/decorators";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class TopicListHeaderColumn extends EmberObject {
|
|
||||||
sortable = null;
|
|
||||||
|
|
||||||
@and("sortable", "isSorting") ariaPressed;
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
localizedName() {
|
|
||||||
if (this.forceName) {
|
|
||||||
return this.forceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.name ? i18n(this.name) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
sortIcon() {
|
|
||||||
const isAscending =
|
|
||||||
(
|
|
||||||
this.parent.ascending ||
|
|
||||||
this.parent.context?.ascending ||
|
|
||||||
""
|
|
||||||
).toString() === "true";
|
|
||||||
|
|
||||||
return `chevron-${isAscending ? "up" : "down"}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
isSorting() {
|
|
||||||
return (
|
|
||||||
this.sortable &&
|
|
||||||
(this.parent.order === this.order ||
|
|
||||||
this.parent.context?.order === this.order)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
className() {
|
|
||||||
const name = [];
|
|
||||||
|
|
||||||
if (this.order) {
|
|
||||||
name.push(this.order);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.sortable) {
|
|
||||||
name.push("sortable");
|
|
||||||
|
|
||||||
if (this.isSorting) {
|
|
||||||
name.push("sorting");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.number) {
|
|
||||||
name.push("num");
|
|
||||||
}
|
|
||||||
|
|
||||||
return name.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
ariaSort() {
|
|
||||||
if (this.isSorting) {
|
|
||||||
return this.parent.ascending ? "ascending" : "descending";
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
import EmberObject from "@ember/object";
|
|
||||||
import discourseComputed from "discourse/lib/decorators";
|
|
||||||
import deprecated from "discourse/lib/deprecated";
|
|
||||||
import { RAW_TOPIC_LIST_DEPRECATION_OPTIONS } from "discourse/lib/plugin-api";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class TopicStatus extends EmberObject {
|
|
||||||
static reopen() {
|
|
||||||
deprecated(
|
|
||||||
"Modifying raw-view:topic-status with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
|
||||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
|
||||||
);
|
|
||||||
|
|
||||||
return super.reopen(...arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
static reopenClass() {
|
|
||||||
deprecated(
|
|
||||||
"Modifying raw-view:topic-status with `reopenClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.",
|
|
||||||
RAW_TOPIC_LIST_DEPRECATION_OPTIONS
|
|
||||||
);
|
|
||||||
|
|
||||||
return super.reopenClass(...arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
showDefault = null;
|
|
||||||
|
|
||||||
@discourseComputed("defaultIcon")
|
|
||||||
renderDiv(defaultIcon) {
|
|
||||||
return (defaultIcon || this.statuses.length > 0) && !this.noDiv;
|
|
||||||
}
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
statuses() {
|
|
||||||
const topic = this.topic;
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
// TODO, custom statuses? via override?
|
|
||||||
if (topic.is_warning) {
|
|
||||||
results.push({ icon: "envelope", key: "warning" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.bookmarked) {
|
|
||||||
const postNumbers = topic.bookmarked_post_numbers;
|
|
||||||
let url = topic.url;
|
|
||||||
let extraClasses = "";
|
|
||||||
if (postNumbers && postNumbers[0] > 1) {
|
|
||||||
url += "/" + postNumbers[0];
|
|
||||||
} else {
|
|
||||||
extraClasses = "op-bookmark";
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
extraClasses,
|
|
||||||
icon: "bookmark",
|
|
||||||
key: "bookmarked",
|
|
||||||
href: url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.closed && topic.archived) {
|
|
||||||
results.push({ icon: "lock", key: "locked_and_archived" });
|
|
||||||
} else if (topic.closed) {
|
|
||||||
results.push({ icon: "lock", key: "locked" });
|
|
||||||
} else if (topic.archived) {
|
|
||||||
results.push({ icon: "lock", key: "archived" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.pinned) {
|
|
||||||
results.push({ icon: "thumbtack", key: "pinned" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.unpinned) {
|
|
||||||
results.push({ icon: "thumbtack", key: "unpinned" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.invisible) {
|
|
||||||
results.push({ icon: "far-eye-slash", key: "unlisted" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.showPrivateMessageIcon &&
|
|
||||||
topic.isPrivateMessage &&
|
|
||||||
!topic.is_warning
|
|
||||||
) {
|
|
||||||
results.push({ icon: "envelope", key: "personal_message" });
|
|
||||||
}
|
|
||||||
|
|
||||||
results.forEach((result) => {
|
|
||||||
const translationParams = {};
|
|
||||||
|
|
||||||
if (result.key === "unlisted") {
|
|
||||||
translationParams.unlistedReason = topic.visibilityReasonTranslated;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.title = i18n(
|
|
||||||
`topic_statuses.${result.key}.help`,
|
|
||||||
translationParams
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.currentUser &&
|
|
||||||
(result.key === "pinned" || result.key === "unpinned")
|
|
||||||
) {
|
|
||||||
result.openTag = "a href";
|
|
||||||
result.closeTag = "a";
|
|
||||||
} else {
|
|
||||||
result.openTag = "span";
|
|
||||||
result.closeTag = "span";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let defaultIcon = this.defaultIcon;
|
|
||||||
if (results.length === 0 && defaultIcon) {
|
|
||||||
this.set("showDefault", defaultIcon);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,7 +28,6 @@ export const CRITICAL_DEPRECATIONS = [
|
|||||||
"discourse.qunit.acceptance-function",
|
"discourse.qunit.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",
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import Service from "@ember/service";
|
|
||||||
import { TrackedSet } from "@ember-compat/tracked-built-ins";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This service is responsible for rendering glimmer components into HTML generated
|
|
||||||
* by raw-hbs. It is not intended to be used directly.
|
|
||||||
*
|
|
||||||
* See discourse/lib/raw-render-glimmer.js for usage instructions.
|
|
||||||
*/
|
|
||||||
export default class RenderGlimmerService extends Service {
|
|
||||||
_registrations = new TrackedSet();
|
|
||||||
|
|
||||||
add(info) {
|
|
||||||
this._registrations.add(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(info) {
|
|
||||||
this._registrations.delete(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes registrations for elements which are no longer in the DOM.
|
|
||||||
*/
|
|
||||||
cleanup() {
|
|
||||||
this._registrations.forEach((info) => {
|
|
||||||
if (!document.body.contains(info.element)) {
|
|
||||||
this.remove(info);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,7 +13,6 @@ const { Webpack } = require("@embroider/webpack");
|
|||||||
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
const { 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
Reference in New Issue
Block a user