DEV: Detect hbr topic list customizations (#29793)

This commit is contained in:
Jarek Radosz 2024-11-21 16:00:49 +01:00 committed by GitHub
parent db15e11cb9
commit 2589545623
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 306 additions and 20 deletions

View File

@ -16,6 +16,10 @@ const DEPRECATION_WORKFLOW = [
handler: "silence", handler: "silence",
matchId: "discourse.post-menu-widget-overrides", matchId: "discourse.post-menu-widget-overrides",
}, },
{
handler: "silence",
matchId: "discourse.hbr-topic-list-overrides",
},
]; ];
export default DEPRECATION_WORKFLOW; export default DEPRECATION_WORKFLOW;

View File

@ -1,9 +1,75 @@
import require from "require"; import require from "require";
import { consolePrefix } from "discourse/lib/source-identifier";
import deprecated from "discourse-common/lib/deprecated";
import { getResolverOption } from "discourse-common/resolver"; import { getResolverOption } from "discourse-common/resolver";
export const __DISCOURSE_RAW_TEMPLATES = {}; 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 = {}) { 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.`;
deprecated(message, {
since: "v3.4.0.beta3-dev",
id: "discourse.hbr-topic-list-overrides",
});
let prefix;
if (opts.themeId) {
prefix = consolePrefix(null, {
type: "theme",
id: opts.themeId,
name: opts.themeName,
});
} else if (opts.pluginName) {
prefix = consolePrefix(null, {
type: "plugin",
name: opts.pluginName,
});
}
// eslint-disable-next-line no-console
console.debug(prefix, message);
}
// Core templates should never overwrite themes / plugins // Core templates should never overwrite themes / plugins
if (opts.core && __DISCOURSE_RAW_TEMPLATES[name]) { if (opts.core && __DISCOURSE_RAW_TEMPLATES[name]) {
return; return;

View File

@ -147,18 +147,24 @@ TemplateCompiler.prototype.processString = function (string, relativePath) {
} }
filename = filename.replace(/\.hbr$/, ""); filename = filename.replace(/\.hbr$/, "");
const hasModernReplacement = string.includes(
return ( "{{!-- has-modern-replacement --}}"
'import { template as compiler } from "discourse-common/lib/raw-handlebars";\n' +
'import { addRawTemplate } from "discourse-common/lib/raw-templates";\n\n' +
"let template = compiler(" +
this.precompile(string, false) +
");\n\n" +
'addRawTemplate("' +
filename +
'", template, { core: true });\n' +
"export default template;"
); );
return `
import { template as compiler } from "discourse-common/lib/raw-handlebars";
import { addRawTemplate } from "discourse-common/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) { TemplateCompiler.prototype.precompile = function (value, asObject) {

View File

@ -1,6 +1,6 @@
<ConditionalLoadingSpinner @condition={{this.loading}}> <ConditionalLoadingSpinner @condition={{this.loading}}>
{{#if this.topics}} {{#if this.topics}}
{{#if this.currentUser.use_glimmer_topic_list}} {{#if this.currentUser.canUseGlimmerTopicList}}
<TopicList::List <TopicList::List
@showPosters={{this.showPosters}} @showPosters={{this.showPosters}}
@hideCategory={{this.hideCategory}} @hideCategory={{this.hideCategory}}

View File

@ -8,7 +8,7 @@
{{#if this.topics}} {{#if this.topics}}
{{#each this.topics as |t|}} {{#each this.topics as |t|}}
{{#if this.currentUser.use_glimmer_topic_list}} {{#if this.currentUser.canUseGlimmerTopicList}}
<TopicList::LatestTopicListItem @topic={{t}} /> <TopicList::LatestTopicListItem @topic={{t}} />
{{else}} {{else}}
<LatestTopicListItem @topic={{t}} /> <LatestTopicListItem @topic={{t}} />

View File

@ -7,7 +7,7 @@
{{/if}} {{/if}}
{{#if @model.sharedDrafts}} {{#if @model.sharedDrafts}}
{{#if this.currentUser.use_glimmer_topic_list}} {{#if this.currentUser.canUseGlimmerTopicList}}
<TopicList::List <TopicList::List
@listTitle="shared_drafts.title" @listTitle="shared_drafts.title"
@top={{this.top}} @top={{this.top}}
@ -88,7 +88,7 @@
</span> </span>
{{#if this.hasTopics}} {{#if this.hasTopics}}
{{#if this.currentUser.use_glimmer_topic_list}} {{#if this.currentUser.canUseGlimmerTopicList}}
<TopicList::List <TopicList::List
@highlightLastVisited={{true}} @highlightLastVisited={{true}}
@top={{this.top}} @top={{this.top}}

View File

@ -21,7 +21,7 @@ export default class NewListHeaderControlsWrapper extends Component {
} }
<template> <template>
{{#if this.currentUser.use_glimmer_topic_list}} {{#if this.currentUser.canUseGlimmerTopicList}}
<div class="topic-replies-toggle-wrapper"> <div class="topic-replies-toggle-wrapper">
<NewListHeaderControls <NewListHeaderControls
@current={{@current}} @current={{@current}}

View File

@ -158,7 +158,7 @@
{{#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.currentUser.use_glimmer_topic_list}} {{#if this.currentUser.canUseGlimmerTopicList}}
<TopicList::FeaturedTopic @topic={{t}} /> <TopicList::FeaturedTopic @topic={{t}} />
{{else}} {{else}}
<FeaturedTopic @topic={{t}} /> <FeaturedTopic @topic={{t}} />

View File

@ -13,7 +13,9 @@ import { observes, on } from "@ember-decorators/object";
import $ from "jquery"; import $ from "jquery";
import { topicTitleDecorators } from "discourse/components/topic-title"; import { topicTitleDecorators } from "discourse/components/topic-title";
import { wantsNewWindow } from "discourse/lib/intercept-click"; import { wantsNewWindow } from "discourse/lib/intercept-click";
import { consolePrefix } from "discourse/lib/source-identifier";
import DiscourseURL, { groupPath } from "discourse/lib/url"; import DiscourseURL, { groupPath } from "discourse/lib/url";
import deprecated from "discourse-common/lib/deprecated";
import { RUNTIME_OPTIONS } from "discourse-common/lib/raw-handlebars-helpers"; import { RUNTIME_OPTIONS } from "discourse-common/lib/raw-handlebars-helpers";
import { findRawTemplate } from "discourse-common/lib/raw-templates"; import { findRawTemplate } from "discourse-common/lib/raw-templates";
import discourseComputed, { bind } from "discourse-common/utils/decorators"; import discourseComputed, { bind } from "discourse-common/utils/decorators";
@ -50,6 +52,32 @@ export function navigateToTopic(topic, href) {
@classNameBindings(":topic-list-item", "unboundClassNames", "topic.visited") @classNameBindings(":topic-list-item", "unboundClassNames", "topic.visited")
@attributeBindings("dataTopicId:data-topic-id", "role", "ariaLevel:aria-level") @attributeBindings("dataTopicId:data-topic-id", "role", "ariaLevel:aria-level")
export default class TopicListItem extends Component { export default class TopicListItem extends Component {
static reopen() {
const message =
"Modifying topic-list-item with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.";
deprecated(message, {
since: "v3.4.0.beta3-dev",
id: "discourse.hbr-topic-list-overrides",
});
// eslint-disable-next-line no-console
console.debug(consolePrefix(), message);
return super.reopen(...arguments);
}
static reopenClass() {
const message =
"Modifying topic-list-item with `reopenClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.";
deprecated(message, {
since: "v3.4.0.beta3-dev",
id: "discourse.hbr-topic-list-overrides",
});
// eslint-disable-next-line no-console
console.debug(consolePrefix(), message);
return super.reopenClass(...arguments);
}
@service router; @service router;
@service historyStore; @service historyStore;

View File

@ -8,13 +8,50 @@ import {
tagName, tagName,
} from "@ember-decorators/component"; } from "@ember-decorators/component";
import { observes, on } from "@ember-decorators/object"; import { observes, on } from "@ember-decorators/object";
import { consolePrefix } from "discourse/lib/source-identifier";
import LoadMore from "discourse/mixins/load-more"; import LoadMore from "discourse/mixins/load-more";
import deprecated, {
registerDeprecationHandler,
} from "discourse-common/lib/deprecated";
import { needsHbrTopicList } from "discourse-common/lib/raw-templates";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
registerDeprecationHandler((message, opts) => {
if (opts?.id === "discourse.hbr-topic-list-overrides") {
needsHbrTopicList(true);
}
});
@tagName("table") @tagName("table")
@classNames("topic-list") @classNames("topic-list")
@classNameBindings("bulkSelectEnabled:sticky-header") @classNameBindings("bulkSelectEnabled:sticky-header")
export default class TopicList extends Component.extend(LoadMore) { export default class TopicList extends Component.extend(LoadMore) {
static reopen() {
const message =
"Modifying topic-list with `reopen` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.";
deprecated(message, {
since: "v3.4.0.beta3-dev",
id: "discourse.hbr-topic-list-overrides",
});
// eslint-disable-next-line no-console
console.debug(consolePrefix(), message);
return super.reopen(...arguments);
}
static reopenClass() {
const message =
"Modifying topic-list with `reopenClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.";
deprecated(message, {
since: "v3.4.0.beta3-dev",
id: "discourse.hbr-topic-list-overrides",
});
// eslint-disable-next-line no-console
console.debug(consolePrefix(), message);
return super.reopenClass(...arguments);
}
@service modal; @service modal;
@service router; @service router;
@service siteSettings; @service siteSettings;

View File

@ -293,6 +293,20 @@ class PluginApi {
* ``` * ```
**/ **/
modifyClass(resolverName, changes, opts) { modifyClass(resolverName, changes, opts) {
if (
resolverName === "component:topic-list" ||
resolverName === "component:topic-list-item"
) {
const message =
"Modifying topic-list and topic-list-item with `modifyClass` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.";
deprecated(message, {
since: "v3.4.0.beta3-dev",
id: "discourse.hbr-topic-list-overrides",
});
// eslint-disable-next-line no-console
console.debug(consolePrefix(), message);
}
const klass = this._resolveClass(resolverName, opts); const klass = this._resolveClass(resolverName, opts);
if (!klass) { if (!klass) {
return; return;
@ -330,6 +344,20 @@ class PluginApi {
* ``` * ```
**/ **/
modifyClassStatic(resolverName, changes, opts) { modifyClassStatic(resolverName, changes, opts) {
if (
resolverName === "component:topic-list" ||
resolverName === "component:topic-list-item"
) {
const message =
"Modifying topic-list and topic-list-item with `modifyClassStatic` is deprecated. Use the value transformer `topic-list-columns` and other new topic-list plugin APIs instead.";
deprecated(message, {
since: "v3.4.0.beta3-dev",
id: "discourse.hbr-topic-list-overrides",
});
// eslint-disable-next-line no-console
console.debug(consolePrefix(), message);
}
const klass = this._resolveClass(resolverName, opts); const klass = this._resolveClass(resolverName, opts);
if (!klass) { if (!klass) {
return; return;

View File

@ -38,6 +38,7 @@ import deprecated from "discourse-common/lib/deprecated";
import { getOwnerWithFallback } from "discourse-common/lib/get-owner"; import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url"; import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
import discourseLater from "discourse-common/lib/later"; import discourseLater from "discourse-common/lib/later";
import { needsHbrTopicList } from "discourse-common/lib/raw-templates";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
@ -1253,6 +1254,10 @@ export default class User extends RestModel.extend(Evented) {
trackedTags(trackedTags, watchedTags, watchingFirstPostTags) { trackedTags(trackedTags, watchedTags, watchingFirstPostTags) {
return [...trackedTags, ...watchedTags, ...watchingFirstPostTags]; return [...trackedTags, ...watchedTags, ...watchingFirstPostTags];
} }
get canUseGlimmerTopicList() {
return this.use_glimmer_topic_list && !needsHbrTopicList();
}
} }
User.reopenClass(Singleton, { User.reopenClass(Singleton, {

View File

@ -98,6 +98,7 @@ import deprecated from "discourse-common/lib/deprecated";
import { getOwnerWithFallback } from "discourse-common/lib/get-owner"; import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
import { restoreBaseUri } from "discourse-common/lib/get-url"; import { restoreBaseUri } from "discourse-common/lib/get-url";
import { cloneJSON, deepMerge } from "discourse-common/lib/object"; import { cloneJSON, deepMerge } from "discourse-common/lib/object";
import { resetNeedsHbrTopicList } from "discourse-common/lib/raw-templates";
import { clearResolverOptions } from "discourse-common/resolver"; import { clearResolverOptions } from "discourse-common/resolver";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
import { _clearSnapshots } from "select-kit/components/composer-actions"; import { _clearSnapshots } from "select-kit/components/composer-actions";
@ -255,6 +256,7 @@ export function testCleanup(container, app) {
resetWidgetCleanCallbacks(); resetWidgetCleanCallbacks();
clearPluginHeaderActionComponents(); clearPluginHeaderActionComponents();
clearRegisteredTabs(); clearRegisteredTabs();
resetNeedsHbrTopicList();
} }
function cleanupCssGeneratorTags() { function cleanupCssGeneratorTags() {

View File

@ -0,0 +1,109 @@
import { module, test } from "qunit";
import TopicList from "discourse/components/topic-list";
import TopicListItem from "discourse/components/topic-list-item";
import { withPluginApi } from "discourse/lib/plugin-api";
import { rawConnectorsFor } from "discourse/lib/plugin-connectors";
import {
disableRaiseOnDeprecation,
enableRaiseOnDeprecation,
} from "discourse/tests/helpers/raise-on-deprecation";
import { withSilencedDeprecations } from "discourse-common/lib/deprecated";
import {
addRawTemplate,
needsHbrTopicList,
removeRawTemplate,
} from "discourse-common/lib/raw-templates";
module("Integration | Lib | hbr topic list detection", function (hooks) {
hooks.beforeEach(function () {
disableRaiseOnDeprecation();
});
hooks.afterEach(function () {
enableRaiseOnDeprecation();
});
test("template overrides", async function (assert) {
try {
addRawTemplate("flat-button", "non-topic list override");
assert.false(needsHbrTopicList());
addRawTemplate("list/topic-list-item", "topic list override");
assert.true(needsHbrTopicList());
} finally {
removeRawTemplate("flat-button");
removeRawTemplate("list/topic-list-item");
}
});
test("hbr connectors", async function (assert) {
assert.false(needsHbrTopicList());
// all raw connectors are topic list connectors
addRawTemplate(
"javascripts/raw-test/connectors/topic-list-after-title/foo",
"topic list connector"
);
assert.strictEqual(rawConnectorsFor("topic-list-after-title").length, 1);
assert.true(needsHbrTopicList());
});
test("reopen", async function (assert) {
withSilencedDeprecations("discourse.hbr-topic-list-overrides", () => {
TopicList.reopen({});
});
assert.false(needsHbrTopicList());
TopicList.reopen({});
assert.true(needsHbrTopicList());
});
test("reopenClass", async function (assert) {
withSilencedDeprecations("discourse.hbr-topic-list-overrides", () => {
TopicListItem.reopenClass({});
});
assert.false(needsHbrTopicList());
TopicListItem.reopenClass({});
assert.true(needsHbrTopicList());
});
test("modifyClass", async function (assert) {
withPluginApi("1.0.0", (api) => {
api.modifyClass(
"component:mobile-nav",
(Superclass) => class extends Superclass {}
);
assert.false(needsHbrTopicList());
withSilencedDeprecations("discourse.hbr-topic-list-overrides", () => {
api.modifyClass(
"component:topic-list-item",
(Superclass) => class extends Superclass {}
);
});
assert.false(needsHbrTopicList());
api.modifyClass(
"component:topic-list-item",
(Superclass) => class extends Superclass {}
);
assert.true(needsHbrTopicList());
});
});
test("modifyClassStatic", async function (assert) {
withPluginApi("1.0.0", (api) => {
api.modifyClassStatic("component:mobile-nav", { pluginId: "test" });
assert.false(needsHbrTopicList());
withSilencedDeprecations("discourse.hbr-topic-list-overrides", () => {
api.modifyClassStatic("component:topic-list", { pluginId: "test" });
});
assert.false(needsHbrTopicList());
api.modifyClassStatic("component:topic-list", { pluginId: "test" });
assert.true(needsHbrTopicList());
});
});
});

View File

@ -6,7 +6,7 @@ require "json_schemer"
class Theme < ActiveRecord::Base class Theme < ActiveRecord::Base
include GlobalPath include GlobalPath
BASE_COMPILER_VERSION = 84 BASE_COMPILER_VERSION = 85
class SettingsMigrationError < StandardError class SettingsMigrationError < StandardError
end end

View File

@ -217,6 +217,7 @@ class ThemeJavascriptCompiler
compiled = compiled =
DiscourseJsProcessor::Transpiler.new.compile_raw_template(hbs_template, theme_id: @theme_id) DiscourseJsProcessor::Transpiler.new.compile_raw_template(hbs_template, theme_id: @theme_id)
source_for_comment = hbs_template.gsub("*/", '*\/').indent(4, " ") source_for_comment = hbs_template.gsub("*/", '*\/').indent(4, " ")
modern_replacement_marker = hbs_template.include?("{{!-- has-modern-replacement --}}")
@output_tree << ["#{name}.js", <<~JS] @output_tree << ["#{name}.js", <<~JS]
(function() { (function() {
/* /*
@ -224,7 +225,7 @@ class ThemeJavascriptCompiler
*/ */
const addRawTemplate = requirejs('discourse-common/lib/raw-templates').addRawTemplate; const addRawTemplate = requirejs('discourse-common/lib/raw-templates').addRawTemplate;
const template = requirejs('discourse-common/lib/raw-handlebars').template(#{compiled}); const template = requirejs('discourse-common/lib/raw-handlebars').template(#{compiled});
addRawTemplate(#{raw_template_name(name)}, template); addRawTemplate(#{raw_template_name(name)}, template, { themeId: #{@theme_id}, themeName: #{@theme_name.to_json}, hasModernReplacement: #{modern_replacement_marker} });
})(); })();
JS JS
rescue MiniRacer::RuntimeError, DiscourseJsProcessor::TranspileError => ex rescue MiniRacer::RuntimeError, DiscourseJsProcessor::TranspileError => ex

View File

@ -31,7 +31,7 @@
</StyleguideExample> </StyleguideExample>
<StyleguideExample @title="<TopicListItem> - latest" class="half-size"> <StyleguideExample @title="<TopicListItem> - latest" class="half-size">
{{#if this.currentUser.use_glimmer_topic_list}} {{#if this.currentUser.canUseGlimmerTopicList}}
<TopicList::LatestTopicListItem @topic={{@dummy.topic}} /> <TopicList::LatestTopicListItem @topic={{@dummy.topic}} />
{{else}} {{else}}
<LatestTopicListItem @topic={{@dummy.topic}} /> <LatestTopicListItem @topic={{@dummy.topic}} />