mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 08:43:25 +08:00
FEATURE: Admin plugin list redesign (#24363)
* Remove checkmark for official plugins * Add author for plugin, which is By Discourse for all discourse and discourse-org github plugins * Link to meta topic instead of github repo * Add experimental flag for plugin metadata and show this as a badge on the plugin list if present --------- Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
This commit is contained in:
parent
10b546d8c7
commit
e37fb3042d
|
@ -0,0 +1,115 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { concat, fn, hash } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import { inject as service } from "@ember/service";
|
||||
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import icon from "discourse-common/helpers/d-icon";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import SiteSetting from "admin/models/site-setting";
|
||||
import PluginCommitHash from "./plugin-commit-hash";
|
||||
|
||||
export default class AdminPluginsListItem extends Component {
|
||||
@service session;
|
||||
@service currentUser;
|
||||
|
||||
@action
|
||||
async togglePluginEnabled(plugin) {
|
||||
const oldValue = plugin.enabled;
|
||||
const newValue = !oldValue;
|
||||
|
||||
try {
|
||||
plugin.enabled = newValue;
|
||||
await SiteSetting.update(plugin.enabledSetting, newValue);
|
||||
this.session.requiresRefresh = true;
|
||||
} catch (err) {
|
||||
plugin.enabled = oldValue;
|
||||
popupAjaxError(err);
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<tr data-plugin-name={{@plugin.name}}>
|
||||
<td class="admin-plugins-list__row">
|
||||
<div class="admin-plugins-list__name-with-badges">
|
||||
<div class="admin-plugins-list__name">
|
||||
{{#if @plugin.linkUrl}}
|
||||
<a
|
||||
href={{@plugin.linkUrl}}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>{{@plugin.nameTitleized}}</a>
|
||||
{{else}}
|
||||
{{@plugin.nameTitleized}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
{{#if @plugin.isExperimental}}
|
||||
<span
|
||||
class="admin-plugins-list__badge -experimental"
|
||||
title={{i18n "admin.plugins.experimental"}}
|
||||
>
|
||||
{{i18n "admin.plugins.experimental_badge"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-plugins-list__author">
|
||||
{{@plugin.author}}
|
||||
{{#if @plugin.isOfficial}}
|
||||
<span title={{i18n "admin.plugins.official"}}>{{icon
|
||||
"fab-discourse"
|
||||
}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="admin-plugins-list__about">
|
||||
{{@plugin.about}}
|
||||
{{#if @plugin.linkUrl}}
|
||||
<a
|
||||
href={{@plugin.linkUrl}}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{{i18n "learn_more"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="admin-plugins-list__version">
|
||||
<div class="label">{{i18n "admin.plugins.version"}}</div>
|
||||
{{@plugin.version}}<br />
|
||||
<PluginCommitHash @plugin={{@plugin}} />
|
||||
</td>
|
||||
<td class="admin-plugins-list__enabled">
|
||||
<div class="label">{{i18n "admin.plugins.enabled"}}</div>
|
||||
{{#if @plugin.enabledSetting}}
|
||||
<DToggleSwitch
|
||||
@state={{@plugin.enabled}}
|
||||
{{on "click" (fn this.togglePluginEnabled @plugin)}}
|
||||
/>
|
||||
{{else}}
|
||||
<DToggleSwitch @state={{@plugin.enabled}} disabled={{true}} />
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="admin-plugins-list__settings">
|
||||
{{#if this.currentUser.admin}}
|
||||
{{#if @plugin.hasSettings}}
|
||||
<LinkTo
|
||||
class="btn-default btn btn-icon-text"
|
||||
@route="adminSiteSettingsCategory"
|
||||
@model={{@plugin.settingCategoryName}}
|
||||
@query={{hash filter=(concat "plugin:" @plugin.name)}}
|
||||
data-plugin-setting-button={{@plugin.name}}
|
||||
>
|
||||
{{icon "cog"}}
|
||||
{{i18n "admin.plugins.change_settings_short"}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import Component from "@glimmer/component";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import AdminPluginsListItem from "./admin-plugins-list-item";
|
||||
|
||||
export default class AdminPluginsList extends Component {
|
||||
<template>
|
||||
<table class="admin-plugins-list grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n "admin.plugins.name"}}</th>
|
||||
<th>{{i18n "admin.plugins.version"}}</th>
|
||||
<th>{{i18n "admin.plugins.enabled"}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each @plugins as |plugin|}}
|
||||
<AdminPluginsListItem @plugin={{plugin}} />
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { capitalize } from "@ember/string";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class AdminPlugin {
|
||||
|
@ -18,10 +19,13 @@ export default class AdminPlugin {
|
|||
this.hasSettings = args.has_settings;
|
||||
this.id = args.id;
|
||||
this.isOfficial = args.is_official;
|
||||
this.isDiscourseOwned = args.is_discourse_owned;
|
||||
this.isExperimental = args.is_experimental;
|
||||
this.name = args.name;
|
||||
this.url = args.url;
|
||||
this.version = args.version;
|
||||
this.metaUrl = args.meta_url;
|
||||
this.authors = args.authors;
|
||||
}
|
||||
|
||||
get settingCategoryName() {
|
||||
|
@ -44,4 +48,25 @@ export default class AdminPlugin {
|
|||
|
||||
return "plugins";
|
||||
}
|
||||
|
||||
get nameTitleized() {
|
||||
return this.name
|
||||
.split("-")
|
||||
.map((word) => {
|
||||
return capitalize(word);
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
get author() {
|
||||
if (this.isOfficial || this.isDiscourseOwned) {
|
||||
return I18n.t("admin.plugins.author", { author: "Discourse" });
|
||||
}
|
||||
|
||||
return I18n.t("admin.plugins.author", { author: this.authors });
|
||||
}
|
||||
|
||||
get linkUrl() {
|
||||
return this.metaUrl || this.url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +1,15 @@
|
|||
{{#if this.model.length}}
|
||||
<h3>{{i18n "admin.plugins.installed"}}</h3>
|
||||
|
||||
<table class="admin-plugins grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{i18n "admin.plugins.name"}}</th>
|
||||
<th>{{i18n "admin.plugins.version"}}</th>
|
||||
<th>{{i18n "admin.plugins.enabled"}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.model as |plugin|}}
|
||||
<tr data-plugin-name={{plugin.name}}>
|
||||
<td>
|
||||
{{#if plugin.isOfficial}}
|
||||
{{d-icon
|
||||
"check-circle"
|
||||
title="admin.plugins.official"
|
||||
class="admin-plugins-official-badge"
|
||||
}}
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
<td class="plugin-name">
|
||||
<div class="name">
|
||||
{{#if plugin.url}}
|
||||
<a
|
||||
href={{plugin.url}}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>{{plugin.name}} {{d-icon "external-link-alt"}}</a>
|
||||
{{else}}
|
||||
{{plugin.name}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="about">
|
||||
{{plugin.about}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="version">
|
||||
<div class="label">{{i18n "admin.plugins.version"}}</div>
|
||||
{{plugin.version}}<br />
|
||||
<PluginCommitHash @plugin={{plugin}} />
|
||||
</td>
|
||||
<td class="col-enabled">
|
||||
<div class="label">{{i18n "admin.plugins.enabled"}}</div>
|
||||
{{#if plugin.enabledSetting}}
|
||||
<DToggleSwitch
|
||||
@state={{plugin.enabled}}
|
||||
{{on "click" (fn this.togglePluginEnabled plugin)}}
|
||||
/>
|
||||
{{else}}
|
||||
<DToggleSwitch @state={{plugin.enabled}} disabled={{true}} />
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="settings">
|
||||
{{#if this.currentUser.admin}}
|
||||
{{#if plugin.hasSettings}}
|
||||
<LinkTo
|
||||
class="btn-default btn btn-icon-text"
|
||||
@route="adminSiteSettingsCategory"
|
||||
@model={{plugin.settingCategoryName}}
|
||||
@query={{hash filter=(concat "plugin:" plugin.name)}}
|
||||
data-plugin-setting-button={{plugin.name}}
|
||||
>
|
||||
{{d-icon "cog"}}
|
||||
{{i18n "admin.plugins.change_settings_short"}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<AdminPluginsList @plugins={{this.model}} />
|
||||
{{else}}
|
||||
<p>{{i18n "admin.plugins.none_installed"}}</p>
|
||||
{{/if}}
|
||||
|
||||
<p class="admin-plugins-howto"><a
|
||||
href="https://meta.discourse.org/t/install-a-plugin/19157"
|
||||
>{{i18n "admin.plugins.howto"}}</a></p>
|
||||
<p class="admin-plugins-howto">
|
||||
<a href="https://meta.discourse.org/t/install-a-plugin/19157">
|
||||
{{i18n "admin.plugins.howto"}}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<span>
|
||||
<PluginOutlet
|
||||
|
|
|
@ -41,22 +41,27 @@ acceptance("Admin - Plugins", function (needs) {
|
|||
await visit("/admin/plugins");
|
||||
|
||||
assert
|
||||
.dom("table.admin-plugins tr .plugin-name .name")
|
||||
.hasText("some-test-plugin", "displays the plugin in the table");
|
||||
.dom(
|
||||
"table.admin-plugins-list tr .admin-plugins-list__row .admin-plugins-list__name-with-badges .admin-plugins-list__name"
|
||||
)
|
||||
.hasText("Some Test Plugin", "displays the plugin in the table");
|
||||
|
||||
assert
|
||||
.dom(".admin-plugins .admin-container .alert-error")
|
||||
.exists("shows an error for unknown routes");
|
||||
|
||||
assert
|
||||
.dom("table.admin-plugins tr .version a.commit-hash")
|
||||
.dom(
|
||||
"table.admin-plugins-list tr .admin-plugins-list__version a.commit-hash"
|
||||
)
|
||||
.hasAttribute(
|
||||
"href",
|
||||
"https://github.com/username/some-test-plugin/commit/1234567890abcdef",
|
||||
"displays a commit hash with a link to commit url"
|
||||
);
|
||||
|
||||
const toggleSelector = "table.admin-plugins tr .col-enabled button";
|
||||
const toggleSelector =
|
||||
"table.admin-plugins-list tr .admin-plugins-list__enabled button";
|
||||
assert
|
||||
.dom(toggleSelector)
|
||||
.hasAttribute("aria-checked", "true", "displays the plugin as enabled");
|
||||
|
|
|
@ -6,18 +6,8 @@
|
|||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
td.plugin-name {
|
||||
.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
td.version {
|
||||
.commit-hash {
|
||||
color: var(--primary-low-mid);
|
||||
font-size: var(--font-down-1);
|
||||
}
|
||||
}
|
||||
.grid {
|
||||
|
||||
.admin-plugins-list {
|
||||
@media screen and (min-width: 550px) {
|
||||
tr {
|
||||
grid-template-columns: 0.25fr repeat(4, 1fr);
|
||||
|
@ -27,11 +17,12 @@
|
|||
tr {
|
||||
grid-template-columns: 0.25fr repeat(3, 1fr);
|
||||
}
|
||||
td.plugin-name {
|
||||
.admin-plugins-list {
|
||||
&__row {
|
||||
grid-column-start: 2;
|
||||
grid-column-end: -1;
|
||||
}
|
||||
td.settings {
|
||||
&__settings {
|
||||
grid-row: 2;
|
||||
grid-column-start: 4;
|
||||
text-align: right;
|
||||
|
@ -39,18 +30,52 @@
|
|||
display: flex;
|
||||
}
|
||||
}
|
||||
td.version {
|
||||
&__version {
|
||||
grid-row: 2;
|
||||
grid-column-start: 3;
|
||||
}
|
||||
td.col-enabled {
|
||||
&__enabled {
|
||||
grid-row: 2;
|
||||
grid-column-start: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-plugins-official-badge {
|
||||
color: var(--success);
|
||||
&__author {
|
||||
font-size: var(--font-down-1);
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
&__name-with-badges {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-weight: bold;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
&__badge {
|
||||
font-weight: 400;
|
||||
font-size: var(--font-down-2);
|
||||
background-color: var(--primary-low);
|
||||
color: var(--primary-medium);
|
||||
padding: 0.2em 0.8em;
|
||||
|
||||
.d-icon {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
& + .admin-plugins-list__badge {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&__version {
|
||||
.commit-hash {
|
||||
color: var(--primary-low-mid);
|
||||
font-size: var(--font-down-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,12 @@ class AdminPluginSerializer < ApplicationSerializer
|
|||
:enabled_setting,
|
||||
:has_settings,
|
||||
:is_official,
|
||||
:is_experimental,
|
||||
:is_discourse_owned,
|
||||
:commit_hash,
|
||||
:commit_url,
|
||||
:meta_url
|
||||
:meta_url,
|
||||
:authors
|
||||
|
||||
def id
|
||||
object.directory_name
|
||||
|
@ -35,6 +38,10 @@ class AdminPluginSerializer < ApplicationSerializer
|
|||
object.metadata.url
|
||||
end
|
||||
|
||||
def authors
|
||||
object.metadata.authors
|
||||
end
|
||||
|
||||
def enabled
|
||||
object.enabled?
|
||||
end
|
||||
|
@ -72,6 +79,14 @@ class AdminPluginSerializer < ApplicationSerializer
|
|||
Plugin::Metadata::OFFICIAL_PLUGINS.include?(object.name)
|
||||
end
|
||||
|
||||
def is_experimental
|
||||
object.metadata.experimental
|
||||
end
|
||||
|
||||
def is_discourse_owned
|
||||
object.discourse_owned?
|
||||
end
|
||||
|
||||
def commit_hash
|
||||
object.commit_hash
|
||||
end
|
||||
|
|
|
@ -2057,7 +2057,7 @@ en:
|
|||
other {# errors/minute}
|
||||
}.
|
||||
|
||||
learn_more: "learn more…"
|
||||
learn_more: "Learn more…"
|
||||
|
||||
mute: Mute
|
||||
unmute: Unmute
|
||||
|
@ -5076,8 +5076,11 @@ en:
|
|||
not_enabled: "N"
|
||||
change_settings_short: "Settings"
|
||||
howto: "How do I install plugins?"
|
||||
official: "Official Plugin"
|
||||
official: "Official Discourse Plugin"
|
||||
experimental: "Experimental Plugin"
|
||||
broken_route: "Unable to configure link to '%{name}'. Ensure ad-blockers are disabled and try reloading the page."
|
||||
author: "By %{author}"
|
||||
experimental_badge: "experimental"
|
||||
|
||||
navigation_menu:
|
||||
sidebar: "Sidebar"
|
||||
|
|
|
@ -516,6 +516,14 @@ class Plugin::Instance
|
|||
@git_repo ||= GitRepo.new(directory, name)
|
||||
end
|
||||
|
||||
def discourse_owned?
|
||||
parsed_commit_url = UrlHelper.relaxed_parse(self.commit_url)
|
||||
return false if !parsed_commit_url
|
||||
github_org = parsed_commit_url.path.split("/")[1]
|
||||
(github_org == "discourse" || github_org == "discourse-org") &&
|
||||
parsed_commit_url.host == "github.com"
|
||||
end
|
||||
|
||||
# A proxy to `DiscourseEvent.on` which does nothing if the plugin is disabled
|
||||
def on(event_name, &block)
|
||||
DiscourseEvent.on(event_name) { |*args, **kwargs| block.call(*args, **kwargs) if enabled? }
|
||||
|
|
|
@ -111,6 +111,7 @@ class Plugin::Metadata
|
|||
required_version
|
||||
transpile_js
|
||||
meta_topic_id
|
||||
experimental
|
||||
]
|
||||
attr_accessor(*FIELDS)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# about: Add checklist support to Discourse
|
||||
# version: 1.0
|
||||
# authors: Discourse Team
|
||||
# meta_topic_id: 36362
|
||||
# url: https://github.com/discourse/discourse/tree/main/plugins/checklist
|
||||
|
||||
enabled_site_setting :checklist_enabled
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# about: Adds markdown.it footnote support to Discourse
|
||||
# version: 1.0
|
||||
# authors: Discourse Team
|
||||
# meta_topic_id: 84533
|
||||
# url: https://github.com/discourse/discourse/tree/main/plugins/footnote
|
||||
|
||||
enabled_site_setting :enable_markdown_footnotes
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
# name: spoiler-alert
|
||||
# about: Uses the Spoiler Alert plugin to blur text when spoiling it.
|
||||
# meta_topic_id: 12650
|
||||
# version: 1.1.0
|
||||
# authors: Discourse Team
|
||||
# url: https://github.com/discourse/discourse/tree/main/plugins/spoiler-alert
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
# name: styleguide
|
||||
# about: Preview how Widgets are Styled in Discourse
|
||||
# meta_topic_id: 167293
|
||||
# version: 0.2
|
||||
# author: Robin Ward
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ RSpec.describe Plugin::Instance do
|
|||
expect(plugin.name).to eq("plugin-name")
|
||||
expect(plugin.path).to eq("#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb")
|
||||
|
||||
git_repo = plugin.git_repo
|
||||
plugin.git_repo.stubs(:latest_local_commit).returns("123456")
|
||||
plugin.git_repo.stubs(:url).returns("http://github.com/discourse/discourse-plugin")
|
||||
|
||||
expect(plugin.commit_hash).to eq("123456")
|
||||
expect(plugin.commit_url).to eq("http://github.com/discourse/discourse-plugin/commit/123456")
|
||||
expect(plugin.discourse_owned?).to eq(true)
|
||||
end
|
||||
|
||||
it "does not blow up on missing directory" do
|
||||
|
@ -28,6 +28,23 @@ RSpec.describe Plugin::Instance do
|
|||
end
|
||||
end
|
||||
|
||||
describe "git repo details" do
|
||||
describe ".discourse_owned?" do
|
||||
it "returns true if the plugin is on github in discourse-org or discourse orgs" do
|
||||
plugin = Plugin::Instance.find_all("#{Rails.root}/spec/fixtures/plugins")[3]
|
||||
plugin.git_repo.stubs(:latest_local_commit).returns("123456")
|
||||
plugin.git_repo.stubs(:url).returns("http://github.com/discourse/discourse-plugin")
|
||||
expect(plugin.discourse_owned?).to eq(true)
|
||||
|
||||
plugin.git_repo.stubs(:url).returns("http://github.com/discourse-org/discourse-plugin")
|
||||
expect(plugin.discourse_owned?).to eq(true)
|
||||
|
||||
plugin.git_repo.stubs(:url).returns("http://github.com/someguy/someguy-plugin")
|
||||
expect(plugin.discourse_owned?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "enabling/disabling" do
|
||||
it "is enabled by default" do
|
||||
expect(Plugin::Instance.new.enabled?).to eq(true)
|
||||
|
|
33
spec/system/admin_plugins_list_spec.rb
Normal file
33
spec/system/admin_plugins_list_spec.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "Admin Plugins List", type: :system, js: true do
|
||||
fab!(:current_user) { Fabricate(:admin) }
|
||||
|
||||
before { sign_in(current_user) }
|
||||
|
||||
let(:spoiler_alert_plugin) do
|
||||
plugins = Plugin::Instance.find_all("#{Rails.root}/plugins")
|
||||
plugins.find { |p| p.name == "spoiler-alert" }
|
||||
end
|
||||
|
||||
xit "shows the list of plugins" do
|
||||
visit "/admin/plugins"
|
||||
|
||||
plugin_row = find(".admin-plugins-list tr[data-plugin-name=\"spoiler-alert\"]")
|
||||
expect(plugin_row).to have_css(
|
||||
"td.admin-plugins-list__row .admin-plugins__name-with-badges .admin-plugins__name",
|
||||
text: "Spoiler Alert",
|
||||
)
|
||||
expect(plugin_row).to have_css(
|
||||
"td.admin-plugins-list__row .admin-plugins__author",
|
||||
text: I18n.t("admin_js.admin.plugins.author", { author: "Discourse" }),
|
||||
)
|
||||
expect(plugin_row).to have_css(
|
||||
"td.admin-plugins-list__row .admin-plugins__name-with-badges .admin-plugins__name a[href=\"https://meta.discourse.org/t/12650\"]",
|
||||
)
|
||||
expect(plugin_row).to have_css(
|
||||
"td.admin-plugins-list__row .admin-plugins__about",
|
||||
text: spoiler_alert_plugin.metadata.about,
|
||||
)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user