mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
start
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { cached } from "@glimmer/tracking";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { fn } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import BackButton from "discourse/components/back-button";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import Form from "discourse/components/form";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { bind } from "discourse/lib/decorators";
|
||||
import getURL from "discourse/lib/get-url";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AdminConfigAreaCardSection from "admin/components/admin-config-area-card-section";
|
||||
import SimpleList from "admin/components/simple-list";
|
||||
|
||||
export default class AdminBrandingLogoForm extends Component {
|
||||
@service router;
|
||||
@service site;
|
||||
|
||||
@action
|
||||
handleUpload(type, upload, { set }) {
|
||||
if (upload) {
|
||||
set(type, getURL(upload.url));
|
||||
} else {
|
||||
set(type, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<@form.Field
|
||||
@name="logo"
|
||||
@title={{i18n "admin.config.branding.logo.form.logo.title"}}
|
||||
@description={{i18n "admin.config.branding.logo.form.logo.description"}}
|
||||
@helpText={{i18n "admin.config.branding.logo.form.logo.help_text"}}
|
||||
@onSet={{(fn this.handleUpload "logo")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
<@form.Field
|
||||
@name="logo_dark_required"
|
||||
@title={{i18n "admin.config.branding.logo.form.logo_dark.required"}}
|
||||
@format="full"
|
||||
as |field|
|
||||
>
|
||||
<field.Toggle />
|
||||
</@form.Field>
|
||||
{{#if @transientData.logo_dark_required}}
|
||||
<@form.Field
|
||||
@name="logo_dark"
|
||||
@title={{i18n "admin.config.branding.logo.form.logo_dark.title"}}
|
||||
@helpText={{i18n "admin.config.branding.logo.form.logo_dark.help_text"}}
|
||||
@onSet={{(fn this.handleUpload "logo_dark")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
{{/if}}
|
||||
<@form.Field
|
||||
@name="large_icon"
|
||||
@title={{i18n "admin.config.branding.logo.form.large_icon.title"}}
|
||||
@description={{i18n
|
||||
"admin.config.branding.logo.form.large_icon.description"
|
||||
}}
|
||||
@helpText={{i18n "admin.config.branding.logo.form.large_icon.help_text"}}
|
||||
@onSet={{(fn this.handleUpload "large_icon")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
<@form.Field
|
||||
@name="favicon"
|
||||
@title={{i18n "admin.config.branding.logo.form.favicon.title"}}
|
||||
@description={{i18n
|
||||
"admin.config.branding.logo.form.favicon.description"
|
||||
}}
|
||||
@onSet={{(fn this.handleUpload "square_icon_light")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
<@form.Field
|
||||
@name="logo_small"
|
||||
@title={{i18n "admin.config.branding.logo.form.logo_small.title"}}
|
||||
@description={{i18n
|
||||
"admin.config.branding.logo.form.logo_small.description"
|
||||
}}
|
||||
@helpText={{i18n "admin.config.branding.logo.form.logo_small.help_text"}}
|
||||
@onSet={{(fn this.handleUpload "logo_small")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
<@form.Field
|
||||
@name="logo_small_dark_required"
|
||||
@title={{i18n "admin.config.branding.logo.form.logo_small_dark.required"}}
|
||||
@format="full"
|
||||
as |field|
|
||||
>
|
||||
<field.Toggle />
|
||||
</@form.Field>
|
||||
{{#if @transientData.logo_small_dark_required}}
|
||||
<@form.Field
|
||||
@name="logo_small_dark"
|
||||
@title={{i18n "admin.config.branding.logo.form.logo_small_dark.title"}}
|
||||
@helpText={{i18n
|
||||
"admin.config.branding.logo.form.logo_small_dark.help_text"
|
||||
}}
|
||||
@onSet={{(fn this.handleUpload "logo_small_dark")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
{{/if}}
|
||||
|
||||
<AdminConfigAreaCardSection
|
||||
@heading="admin.config.branding.logo.form.mobile"
|
||||
@collapsable={{true}}
|
||||
@collapsed={{true}}
|
||||
>
|
||||
<:content>
|
||||
<@form.Field
|
||||
@name="mobile_logo"
|
||||
@title={{i18n "admin.config.branding.logo.form.mobile_logo.title"}}
|
||||
@description={{i18n
|
||||
"admin.config.branding.logo.form.mobile_logo.description"
|
||||
}}
|
||||
@helpText={{i18n
|
||||
"admin.config.branding.logo.form.mobile_logo.help_text"
|
||||
}}
|
||||
@onSet={{(fn this.handleUpload "mobile_logo")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
<@form.Field
|
||||
@name="mobile_logo_dark_required"
|
||||
@title={{i18n
|
||||
"admin.config.branding.logo.form.mobile_logo_dark.required"
|
||||
}}
|
||||
@format="full"
|
||||
as |field|
|
||||
>
|
||||
<field.Toggle />
|
||||
</@form.Field>
|
||||
{{#if @transientData.mobile_logo_dark_required}}
|
||||
<@form.Field
|
||||
@name="mobile_logo_dark"
|
||||
@title={{i18n
|
||||
"admin.config.branding.logo.form.mobile_logo_dark.title"
|
||||
}}
|
||||
@helpText={{i18n
|
||||
"admin.config.branding.logo.form.mobile_logo_dark.help_text"
|
||||
}}
|
||||
@onSet={{(fn this.handleUpload "mobile_logo_dark")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
{{/if}}
|
||||
<@form.Field
|
||||
@name="manifest_icon"
|
||||
@title={{i18n "admin.config.branding.logo.form.manifest_icon.title"}}
|
||||
@description={{i18n
|
||||
"admin.config.branding.logo.form.manifest_icon.description"
|
||||
}}
|
||||
@helpText={{i18n
|
||||
"admin.config.branding.logo.form.manifest_icon.help_text"
|
||||
}}
|
||||
@onSet={{(fn this.handleUpload "manifest_icon")}}
|
||||
as |field|
|
||||
>
|
||||
<field.Image @type="branding" />
|
||||
</@form.Field>
|
||||
<@form.Field
|
||||
@name="manifest_screenshots"
|
||||
@title={{i18n
|
||||
"admin.config.branding.logo.form.manifest_screenshots.title"
|
||||
}}
|
||||
@description={{i18n
|
||||
"admin.config.branding.logo.form.manifest_screenshots.description"
|
||||
}}
|
||||
@format="full"
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<SimpleList
|
||||
@id={{field.id}}
|
||||
@onChange={{field.set}}
|
||||
@inputDelimiter="|"
|
||||
@values={{field.value}}
|
||||
@allowAny={{true}}
|
||||
/>
|
||||
</field.Custom>
|
||||
</@form.Field>
|
||||
</:content>
|
||||
</AdminConfigAreaCardSection>
|
||||
<AdminConfigAreaCardSection
|
||||
@heading="admin.config.branding.logo.form.email"
|
||||
@collapsable={{true}}
|
||||
@collapsed={{true}}
|
||||
>
|
||||
<:content>
|
||||
fsdfgsdfsdf ds fsdf sdfsd
|
||||
</:content>
|
||||
</AdminConfigAreaCardSection>
|
||||
<AdminConfigAreaCardSection
|
||||
@heading="admin.config.branding.logo.form.social_media"
|
||||
@collapsable={{true}}
|
||||
@collapsed={{true}}
|
||||
>
|
||||
<:content>
|
||||
fsdfgsdfsdf ds fsdf sdfsd
|
||||
</:content>
|
||||
</AdminConfigAreaCardSection>
|
||||
</template>
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class AdminConfigAreaCardSection extends Component {
|
||||
@tracked collapsed = this.args.collapsed;
|
||||
|
||||
get computedHeading() {
|
||||
if (this.args.heading) {
|
||||
return i18n(this.args.heading);
|
||||
}
|
||||
return this.args.translatedHeading;
|
||||
}
|
||||
|
||||
get headerCaretIcon() {
|
||||
return this.collapsed ? "plus" : "minus";
|
||||
}
|
||||
|
||||
@action
|
||||
toggleSectionDisplay() {
|
||||
this.collapsed = !this.collapsed;
|
||||
}
|
||||
|
||||
<template>
|
||||
<section class="admin-config-area-card-section" ...attributes>
|
||||
<div class="admin-config-area-card-section__header-wrapper">
|
||||
<h4 class="admin-config-area-card__title">{{this.computedHeading}}</h4>
|
||||
{{#if @collapsable}}
|
||||
<DButton
|
||||
@title="sidebar.toggle_section"
|
||||
@action={{this.toggleSectionDisplay}}
|
||||
class="admin-config-area-card-section__toggle-button btn-transparent"
|
||||
>
|
||||
{{icon this.headerCaretIcon}}
|
||||
</DButton>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#unless this.collapsed}}
|
||||
<div class="admin-config-area-card-section__content">
|
||||
{{yield to="content"}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
</section>
|
||||
</template>
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { cached } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import Form from "discourse/components/form";
|
||||
|
||||
export default class AdminConfigAreasBrandingForm extends Component {
|
||||
@service siteSettings;
|
||||
@action
|
||||
submit(args) {
|
||||
console.log("args", args);
|
||||
}
|
||||
|
||||
@cached
|
||||
get formData() {
|
||||
return {
|
||||
logo: this.siteSettings.logo,
|
||||
logo_dark_required: !!this.siteSettings.logo_dark,
|
||||
logo_dark: this.siteSettings.logo_dark,
|
||||
large_icon: this.siteSettings.large_icon,
|
||||
favicon: this.siteSettings.favicon,
|
||||
logo_small: this.siteSettings.logo_small,
|
||||
logo_small_dark_required: !!this.siteSettings.logo_small_dark,
|
||||
logo_small_dark: this.siteSettings.logo_small_dark,
|
||||
mobile_logo: this.siteSettings.mobile_logo,
|
||||
mobile_logo_dark_required: !!this.siteSettings.mobile_logo_dark,
|
||||
mobile_logo_dark: this.siteSettings.mobile_logo_dark,
|
||||
manifest_icon: this.siteSettings.manifest_icon,
|
||||
manifest_screenshots: this.siteSettings.manifest_screenshots,
|
||||
};
|
||||
}
|
||||
|
||||
<template>
|
||||
<Form
|
||||
@onSubmit={{this.submit}}
|
||||
@data={{this.formData}}
|
||||
as |form transientData|
|
||||
>
|
||||
{{yield form transientData to="content"}}
|
||||
<form.Submit />
|
||||
</Form>
|
||||
</template>
|
||||
}
|
@@ -316,6 +316,7 @@ export default function () {
|
||||
this.route("logo", function () {
|
||||
this.route("settings", { path: "/" });
|
||||
});
|
||||
this.route("branding");
|
||||
this.route("navigation", function () {
|
||||
this.route("settings", { path: "/" });
|
||||
});
|
||||
|
@@ -0,0 +1,42 @@
|
||||
import RouteTemplate from "ember-route-template";
|
||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||
import DPageHeader from "discourse/components/d-page-header";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AdminBrandingLogoForm from "admin/components/admin-branding-logo-form";
|
||||
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
|
||||
import AdminConfigAreasBrandingForm from "admin/components/admin-config-areas/branding-form";
|
||||
|
||||
export default RouteTemplate(
|
||||
<template>
|
||||
<DPageHeader
|
||||
@hideTabs={{true}}
|
||||
@titleLabel={{i18n "admin.config.branding.title"}}
|
||||
>
|
||||
<:breadcrumbs>
|
||||
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||
<DBreadcrumbsItem
|
||||
@path="/admin/config/branding"
|
||||
@label={{i18n "admin.config.branding.title"}}
|
||||
/>
|
||||
</:breadcrumbs>
|
||||
</DPageHeader>
|
||||
|
||||
<div class="admin-config-area">
|
||||
<div class="admin-config-area__primary-content">
|
||||
<AdminConfigAreasBrandingForm>
|
||||
<:content as |form transientData|>
|
||||
<AdminConfigAreaCard
|
||||
@heading="admin.config.branding.logo.title"
|
||||
@collapsable={{true}}
|
||||
class="admin-config-area-branding__logo"
|
||||
>
|
||||
<:content>
|
||||
<AdminBrandingLogoForm @form={{form}} @transientData={{transientData}} />
|
||||
</:content>
|
||||
</AdminConfigAreaCard>
|
||||
</:content>
|
||||
</AdminConfigAreasBrandingForm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
);
|
@@ -114,6 +114,12 @@ export const ADMIN_NAV_MAP = [
|
||||
label: "admin.appearance.sidebar_link.font_style",
|
||||
icon: "italic",
|
||||
},
|
||||
{
|
||||
name: "admin_branding",
|
||||
route: "adminConfig.branding",
|
||||
label: "admin.config.branding.title",
|
||||
icon: "fab-discourse",
|
||||
},
|
||||
{
|
||||
name: "admin_site_logo",
|
||||
route: "adminConfig.logo.settings",
|
||||
|
@@ -1206,6 +1206,7 @@ a.inline-editable-field {
|
||||
}
|
||||
|
||||
// Styles for subtabs in admin
|
||||
@import "common/admin/branding";
|
||||
@import "common/admin/dashboard";
|
||||
@import "common/admin/sidebar";
|
||||
@import "common/admin/settings";
|
||||
|
@@ -122,3 +122,30 @@
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-config-area-card-section {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--space-4);
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__header-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-top: 0.5rem;
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
28
app/assets/stylesheets/common/admin/branding.scss
Normal file
28
app/assets/stylesheets/common/admin/branding.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
.admin-config-area-branding {
|
||||
&__logo {
|
||||
width: 100%;
|
||||
|
||||
.admin-config-area-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5em;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// #control-dark_logo_required {
|
||||
// width: 100%;
|
||||
//
|
||||
// .form-kit__container-title {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
.admin-config-area-card-section__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5em;
|
||||
align-items: flex-start;
|
||||
}
|
@@ -5,6 +5,10 @@
|
||||
width: var(--form-kit-medium-input);
|
||||
}
|
||||
|
||||
&.--large {
|
||||
width: var(--form-kit-large-input);
|
||||
}
|
||||
|
||||
&.--full {
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -386,41 +386,42 @@ class Upload < ActiveRecord::Base
|
||||
color = ""
|
||||
end
|
||||
|
||||
color ||=
|
||||
begin
|
||||
data =
|
||||
Discourse::Utils.execute_command(
|
||||
"nice",
|
||||
"-n",
|
||||
"10",
|
||||
"convert",
|
||||
local_path,
|
||||
"-depth",
|
||||
"8",
|
||||
"-resize",
|
||||
"1x1",
|
||||
"-define",
|
||||
"histogram:unique-colors=true",
|
||||
"-format",
|
||||
"%c",
|
||||
"histogram:info:",
|
||||
timeout: DOMINANT_COLOR_COMMAND_TIMEOUT_SECONDS,
|
||||
)
|
||||
|
||||
# Output format:
|
||||
# 1: (110.873,116.226,93.8821) #6F745E srgb(43.4798%,45.5789%,36.8165%)
|
||||
|
||||
color = data[/#([0-9A-F]{6})/, 1]
|
||||
|
||||
raise "Calculated dominant color but unable to parse output:\n#{data}" if color.nil?
|
||||
|
||||
color
|
||||
rescue Discourse::Utils::CommandError => e
|
||||
# Timeout or unable to parse image
|
||||
# This can happen due to bad user input - ignore and save
|
||||
# an empty string to prevent re-evaluation
|
||||
""
|
||||
end
|
||||
# color ||=
|
||||
# begin
|
||||
# data =
|
||||
# Discourse::Utils.execute_command(
|
||||
# "nice",
|
||||
# "-n",
|
||||
# "10",
|
||||
# "convert",
|
||||
# local_path,
|
||||
# "-depth",
|
||||
# "8",
|
||||
# "-resize",
|
||||
# "1x1",
|
||||
# "-define",
|
||||
# "histogram:unique-colors=true",
|
||||
# "-format",
|
||||
# "%c",
|
||||
# "histogram:info:",
|
||||
# timeout: DOMINANT_COLOR_COMMAND_TIMEOUT_SECONDS,
|
||||
# )
|
||||
#
|
||||
# # Output format:
|
||||
# # 1: (110.873,116.226,93.8821) #6F745E srgb(43.4798%,45.5789%,36.8165%)
|
||||
#
|
||||
# color = data[/#([0-9A-F]{6})/, 1]
|
||||
#
|
||||
# raise "Calculated dominant color but unable to parse output:\n#{data}" if color.nil?
|
||||
#
|
||||
# color
|
||||
# rescue Discourse::Utils::CommandError => e
|
||||
# # Timeout or unable to parse image
|
||||
# # This can happen due to bad user input - ignore and save
|
||||
# # an empty string to prevent re-evaluation
|
||||
# ""
|
||||
# end
|
||||
"#000000"
|
||||
end
|
||||
|
||||
if persisted?
|
||||
|
@@ -5184,6 +5184,56 @@ en:
|
||||
logo:
|
||||
title: "Site logo"
|
||||
header_description: "Customize the variations of your site logo"
|
||||
branding:
|
||||
title: "Branding"
|
||||
logo:
|
||||
title: "Logo"
|
||||
form:
|
||||
logo:
|
||||
title: "Primary logo"
|
||||
description: "Appears on the site's top navigation, as well as the top of the site's Email Notifications."
|
||||
help_text: "Recommended size is 600 x 80 pixels"
|
||||
logo_dark:
|
||||
required: "Use a different logo for dark mode?"
|
||||
title: "Primary logo dark"
|
||||
help_text: "Recommended size is 600 x 80 pixels"
|
||||
large_icon:
|
||||
title: "Square icon"
|
||||
description: "A squared version of the logo image appears at the top of the administration and is also the mobile home screen app logo."
|
||||
help_text: "Recommended size is 512 x 512 pixels"
|
||||
square_icon_dark:
|
||||
required: "Use a different square icon for dark mode?"
|
||||
title: "Square icon dark"
|
||||
help_text: "Recommended size is 512 x 512 pixels"
|
||||
favicon:
|
||||
title: "Favicon"
|
||||
description: "The logo will appear as the icon in the browser tab and the browser favorites/bookmarks."
|
||||
logo_small:
|
||||
title: "Small logo"
|
||||
description: "The small logo image at the top left of your site, seen when scrolling down. If left blank, a home glyph will be shown."
|
||||
help_text: "Recommended size is 120 x 120 pixels"
|
||||
logo_small_dark:
|
||||
required: "Use a different small logo for dark mode?"
|
||||
title: "Small logo dark"
|
||||
help_text: "Recommended size is 120 x 120 pixels"
|
||||
mobile: "Mobile"
|
||||
email: "Email"
|
||||
social_media: "Social media"
|
||||
mobile_logo:
|
||||
title: "Mobile logo"
|
||||
description: "The logo used on mobile version of your site. If left blank, the image from the `logo` setting will be used."
|
||||
help_text: "Use a wide rectangular image with a height of 120 and an aspect ratio greater than 3:1."
|
||||
mobile_logo_dark:
|
||||
required: "Use a different mobile logo for dark mode?"
|
||||
title: "Mobile logo dark"
|
||||
help_text: "Use a wide rectangular image with a height of 120 and an aspect ratio greater than 3:1."
|
||||
manifest_icon:
|
||||
title: "Manifest icon"
|
||||
description: "Image used as logo/splash image on Android. If left blank, large_icon will be used."
|
||||
help_text: "Will be automatically resized to 512 × 512."
|
||||
manifest_screenshots:
|
||||
title: "Manifest screenshots"
|
||||
description: "Screenshots that showcase your instance features and functionality on its install prompt page. All images should be local uploads and of the same dimensions."
|
||||
navigation:
|
||||
title: "Navigation"
|
||||
header_description: "Configure the navigation links and menu items for your site. This includes the location and behavior of the primary navigation menu, the quick links at the top of the homepage, as well as the admin sidebar"
|
||||
|
@@ -409,6 +409,9 @@ Discourse::Application.routes.draw do
|
||||
get "trust-levels" => "site_settings#index"
|
||||
get "group-permissions" => "site_settings#index"
|
||||
|
||||
#TODO
|
||||
get "branding" => "site_settings#index"
|
||||
|
||||
resources :flags, only: %i[index new create update destroy] do
|
||||
put "toggle"
|
||||
put "reorder/:direction" => "flags#reorder"
|
||||
|
@@ -125,10 +125,12 @@ branding:
|
||||
type: upload
|
||||
manifest_icon:
|
||||
default: ""
|
||||
client: true
|
||||
type: upload
|
||||
manifest_screenshots:
|
||||
type: list
|
||||
list_type: simple
|
||||
client: true
|
||||
default: ""
|
||||
favicon:
|
||||
default: ""
|
||||
|
Reference in New Issue
Block a user