From b7caaeaa19dbd5a062af3b2194bd4a023dadeb54 Mon Sep 17 00:00:00 2001 From: Charlie <13856015+KyrneDev@users.noreply.github.com> Date: Sat, 24 Oct 2020 13:55:18 -0700 Subject: [PATCH 01/61] Admin Component Changes --- js/src/admin/AdminApplication.js | 16 ++ js/src/admin/compat.js | 6 +- js/src/admin/components/AdminNav.js | 157 ++++++++--- js/src/admin/components/AppearancePage.js | 10 + js/src/admin/components/BasicsPage.js | 10 + js/src/admin/components/DashboardPage.js | 10 + .../admin/components/ExtensionLinkButton.js | 36 +++ js/src/admin/components/ExtensionPage.js | 251 ++++++++++++++++++ .../components/ExtensionPermissionGrid.js | 44 +++ js/src/admin/components/MailPage.js | 10 + js/src/admin/components/PermissionGrid.js | 39 +++ js/src/admin/components/PermissionsPage.js | 9 + js/src/admin/components/StatusWidget.js | 2 +- js/src/admin/routes.js | 2 - js/src/admin/utils/addExtensionPermission.js | 26 ++ 15 files changed, 590 insertions(+), 38 deletions(-) create mode 100644 js/src/admin/components/ExtensionLinkButton.js create mode 100644 js/src/admin/components/ExtensionPage.js create mode 100644 js/src/admin/components/ExtensionPermissionGrid.js create mode 100644 js/src/admin/utils/addExtensionPermission.js diff --git a/js/src/admin/AdminApplication.js b/js/src/admin/AdminApplication.js index 1e75a3c308..a0f0f5190f 100644 --- a/js/src/admin/AdminApplication.js +++ b/js/src/admin/AdminApplication.js @@ -1,6 +1,7 @@ import HeaderPrimary from './components/HeaderPrimary'; import HeaderSecondary from './components/HeaderSecondary'; import routes from './routes'; +import ExtensionPage from './components/ExtensionPage'; import Application from '../common/Application'; import Navigation from '../common/components/Navigation'; import AdminNav from './components/AdminNav'; @@ -8,6 +9,8 @@ import AdminNav from './components/AdminNav'; export default class AdminApplication extends Application { extensionSettings = {}; + extensionPermissions = {}; + history = { canGoBack: () => true, getPrevious: () => {}, @@ -27,6 +30,9 @@ export default class AdminApplication extends Application { * @inheritdoc */ mount() { + // Add the default extension pages + this.setDefaultExtensionPages(); + // Mithril does not render the home route on https://example.com/admin, so // we need to go to https://example.com/admin#/ explicitly. if (!document.location.hash) document.location.hash = '#/'; @@ -64,4 +70,14 @@ export default class AdminApplication extends Application { return required; } + + setDefaultExtensionPages() { + Object.keys(app.data.extensions).map((id) => { + const extension = app.data.extensions[id]; + + if (!app.routes[extension.id]) { + app.routes[extension.id] = { path: '/' + extension.id, component: ExtensionPage }; + } + }); + } } diff --git a/js/src/admin/compat.js b/js/src/admin/compat.js index 84bc61671c..df210b85a6 100644 --- a/js/src/admin/compat.js +++ b/js/src/admin/compat.js @@ -11,7 +11,8 @@ import HeaderSecondary from './components/HeaderSecondary'; import SettingsModal from './components/SettingsModal'; import DashboardWidget from './components/DashboardWidget'; import AddExtensionModal from './components/AddExtensionModal'; -import ExtensionsPage from './components/ExtensionsPage'; +import ExtensionPage from './components/ExtensionPage'; +import ExtensionLinkButton from './components/ExtensionLinkButton'; import AdminLinkButton from './components/AdminLinkButton'; import PermissionGrid from './components/PermissionGrid'; import MailPage from './components/MailPage'; @@ -40,7 +41,8 @@ export default Object.assign(compat, { 'components/SettingsModal': SettingsModal, 'components/DashboardWidget': DashboardWidget, 'components/AddExtensionModal': AddExtensionModal, - 'components/ExtensionsPage': ExtensionsPage, + 'components/ExtensionPage': ExtensionPage, + 'components/ExtensionLinkButton': ExtensionLinkButton, 'components/AdminLinkButton': AdminLinkButton, 'components/PermissionGrid': PermissionGrid, 'components/MailPage': MailPage, diff --git a/js/src/admin/components/AdminNav.js b/js/src/admin/components/AdminNav.js index f41d2a1a67..14e3f2f1f0 100644 --- a/js/src/admin/components/AdminNav.js +++ b/js/src/admin/components/AdminNav.js @@ -1,41 +1,33 @@ -/* - * This file is part of Flarum. - * - * (c) Toby Zerner - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - +import ExtensionLinkButton from './ExtensionLinkButton'; import Component from '../../common/Component'; -import AdminLinkButton from './AdminLinkButton'; +import LinkButton from '../../common/components/LinkButton'; import SelectDropdown from '../../common/components/SelectDropdown'; import ItemList from '../../common/utils/ItemList'; export default class AdminNav extends Component { view() { return ( - - {this.items().toArray()} + + {this.mainItems().toArray().concat(this.extensionItems().toArray())} ); } /** - * Build an item list of links to show in the admin navigation. + * Build an item list of main links to show in the admin navigation. * * @return {ItemList} */ - items() { + mainItems() { const items = new ItemList(); items.add( 'dashboard', - AdminLinkButton.component( + LinkButton.component( { href: app.route('dashboard'), icon: 'far fa-chart-bar', - description: app.translator.trans('core.admin.nav.dashboard_text'), + title: app.translator.trans('core.admin.nav.dashboard_title'), }, app.translator.trans('core.admin.nav.dashboard_button') ) @@ -43,11 +35,11 @@ export default class AdminNav extends Component { items.add( 'basics', - AdminLinkButton.component( + LinkButton.component( { href: app.route('basics'), icon: 'fas fa-pencil-alt', - description: app.translator.trans('core.admin.nav.basics_text'), + title: app.translator.trans('core.admin.nav.basics_title'), }, app.translator.trans('core.admin.nav.basics_button') ) @@ -55,11 +47,11 @@ export default class AdminNav extends Component { items.add( 'mail', - AdminLinkButton.component( + LinkButton.component( { href: app.route('mail'), icon: 'fas fa-envelope', - description: app.translator.trans('core.admin.nav.email_text'), + title: app.translator.trans('core.admin.nav.email_title'), }, app.translator.trans('core.admin.nav.email_button') ) @@ -67,11 +59,11 @@ export default class AdminNav extends Component { items.add( 'permissions', - AdminLinkButton.component( + LinkButton.component( { href: app.route('permissions'), icon: 'fas fa-key', - description: app.translator.trans('core.admin.nav.permissions_text'), + title: app.translator.trans('core.admin.nav.permissions_title'), }, app.translator.trans('core.admin.nav.permissions_button') ) @@ -79,28 +71,127 @@ export default class AdminNav extends Component { items.add( 'appearance', - AdminLinkButton.component( + LinkButton.component( { href: app.route('appearance'), icon: 'fas fa-paint-brush', - description: app.translator.trans('core.admin.nav.appearance_text'), + title: app.translator.trans('core.admin.nav.appearance_title'), }, app.translator.trans('core.admin.nav.appearance_button') ) ); items.add( - 'extensions', - AdminLinkButton.component( - { - href: app.route('extensions'), - icon: 'fas fa-puzzle-piece', - description: app.translator.trans('core.admin.nav.extensions_text'), - }, - app.translator.trans('core.admin.nav.extensions_button') - ) + 'search', +
+ +
); return items; } + + oncreate(vnode) { + $('.SearchBar').on('keyup', () => { + const filter = $('.SearchBar').val().toUpperCase(); + const list = $('.Dropdown-menu'); + if (!filter) { + list.children('li').show(); + } else { + list.children('li').map((key) => { + const element = $(list.children('li')[key]); + if ( + (!element + .attr('class') + .replace(/item-|ExtensionItem|active/gi, '') + .toUpperCase() + .includes(filter) && + element.attr('class').includes('ExtensionItem')) || + element.attr('class').includes('NavDivider') + ) { + $(list.children('li')[key]).hide(); + } else { + element.show(); + } + }); + } + }); + } + + extensionCategories() { + return { + discussion: 70, + moderation: 60, + feature: 50, + formatting: 40, + theme: 30, + authentication: 20, + language: 10, + other: 0, + }; + } + + getCategorizedExtensions() { + let extensions = {}; + + Object.keys(app.data.extensions).map((id) => { + const extension = app.data.extensions[id]; + let category = extension.extra['flarum-extension'].category; + + if (!extension.extra['flarum-extension'].category) { + category = 'other'; + } + + // Wrap languages packs into new system + if (extension.extra['flarum-locale']) { + category = 'language'; + } + + if (category in this.extensionCategories()) { + extensions[category] = extensions[category] || {}; + + extensions[category][id] = extension; + } else { + // If the extension doesn't fit + // into a category add it to other + extensions.other[id] = extension; + } + }); + + return extensions; + } + + extensionItems() { + const items = new ItemList(); + + const categorizedExtensions = this.getCategorizedExtensions(); + const categories = this.extensionCategories(); + + Object.keys(categorizedExtensions).map((category) => { + items.add( + `${category} NavDivider`, +

{app.translator.trans(`core.admin.nav.categories.${category}`)}

, + categories[category] + ); + + Object.keys(categorizedExtensions[category]).map((id) => { + const extension = categorizedExtensions[category][id]; + items.add( + `${extension.id} ExtensionItem`, + ExtensionLinkButton.component( + { + href: app.route(extension.id), + extensionId: extension.id, + className: 'ExtensionNavButton', + title: extension.description, + }, + extension.extra['flarum-extension'].title + ), + categories[category] + ); + }); + }); + + return items; + } } diff --git a/js/src/admin/components/AppearancePage.js b/js/src/admin/components/AppearancePage.js index 106a88659c..05fb54add2 100644 --- a/js/src/admin/components/AppearancePage.js +++ b/js/src/admin/components/AppearancePage.js @@ -2,6 +2,7 @@ import Page from '../../common/components/Page'; import Button from '../../common/components/Button'; import Switch from '../../common/components/Switch'; import Stream from '../../common/utils/Stream'; +import icon from '../../common/helpers/icon'; import EditCustomCssModal from './EditCustomCssModal'; import EditCustomHeaderModal from './EditCustomHeaderModal'; import EditCustomFooterModal from './EditCustomFooterModal'; @@ -21,6 +22,15 @@ export default class AppearancePage extends Page { view() { return (
+
+
+

+ {icon('fas fa-paint-brush')} + {app.translator.trans('core.admin.appearance.title')} +

+
{app.translator.trans('core.admin.appearance.description')}
+
+
diff --git a/js/src/admin/components/BasicsPage.js b/js/src/admin/components/BasicsPage.js index 56c1c4cfb4..ee2914983c 100644 --- a/js/src/admin/components/BasicsPage.js +++ b/js/src/admin/components/BasicsPage.js @@ -7,6 +7,7 @@ import ItemList from '../../common/utils/ItemList'; import Switch from '../../common/components/Switch'; import Stream from '../../common/utils/Stream'; import withAttr from '../../common/utils/withAttr'; +import icon from '../../common/helpers/icon'; export default class BasicsPage extends Page { oninit(vnode) { @@ -49,6 +50,15 @@ export default class BasicsPage extends Page { view() { return (
+
+
+

+ {icon('fas fa-pencil-alt')} + {app.translator.trans('core.admin.basics.title')} +

+
{app.translator.trans('core.admin.basics.description')}
+
+
{FieldSet.component( diff --git a/js/src/admin/components/DashboardPage.js b/js/src/admin/components/DashboardPage.js index fdcade871e..aefaef8a54 100644 --- a/js/src/admin/components/DashboardPage.js +++ b/js/src/admin/components/DashboardPage.js @@ -1,10 +1,20 @@ import Page from '../../common/components/Page'; import StatusWidget from './StatusWidget'; +import icon from '../../Common/helpers/icon'; export default class DashboardPage extends Page { view() { return (
+
+
+

+ {icon('far fa-chart-bar')} + {app.translator.trans('core.admin.dashboard.title')} +

+
{app.translator.trans('core.admin.dashboard.description')}
+
+
{this.availableWidgets()}
); diff --git a/js/src/admin/components/ExtensionLinkButton.js b/js/src/admin/components/ExtensionLinkButton.js new file mode 100644 index 0000000000..c63b3aa50c --- /dev/null +++ b/js/src/admin/components/ExtensionLinkButton.js @@ -0,0 +1,36 @@ +import LinkButton, { LinkButtonProps } from '../../common/components/LinkButton'; +import Stream from '../../common/utils/Stream'; + +import icon from '../../common/helpers/icon'; +import ItemList from '../../common/utils/ItemList'; + +export default class ExtensionLinkButton extends LinkButton { + getButtonContent(children) { + const content = super.getButtonContent(children); + const extension = app.data.extensions[this.attrs.extensionId]; + const statuses = this.statusItems(extension.id).toArray(); + + content.unshift( + + {extension.icon ? icon(extension.icon.name) : ''} + + ); + content.push(statuses); + + return content; + } + + statusItems(name) { + const items = new ItemList(); + + items.add('enabled', ); + + return items; + } + + isEnabled(name) { + const enabled = JSON.parse(app.data.settings.extensions_enabled); + + return enabled.indexOf(name) !== -1; + } +} diff --git a/js/src/admin/components/ExtensionPage.js b/js/src/admin/components/ExtensionPage.js new file mode 100644 index 0000000000..10b4fbfffc --- /dev/null +++ b/js/src/admin/components/ExtensionPage.js @@ -0,0 +1,251 @@ +import Alert from '../../common/components/Alert'; +import Page from '../../common/components/Page'; +import Button from '../../common/components/Button'; +import icon from '../../common/helpers/icon'; +import ItemList from '../../common/utils/ItemList'; +import listItems from '../../common/helpers/listItems'; +import LoadingModal from './LoadingModal'; +import ExtensionPermissionGrid from './ExtensionPermissionGrid'; +import addExtensionPermission from '../utils/addExtensionPermission'; +import Switch from '../../common/components/Switch'; +import saveSettings from '../utils/saveSettings'; + +export default class ExtensionPage extends Page { + oninit(vnode) { + super.oninit(vnode); + + this.settings = {}; + this.loading = false; + this.extension = app.data.extensions[this.attrs.routeName]; + this.changingState = false; + } + + className() { + return this.extension.id + '-Page'; + } + + view() { + new addExtensionPermission('flarum-flags') + .add('discussion.viewFlags', 'fas fa-flag', app.translator.trans('flarum-flags.admin.permissions.view_flags_label'), 'moderate') + .add('discussion.flagPosts', 'fas fa-flag', app.translator.trans('flarum-flags.admin.permissions.flag_posts_label'), 'reply'); + return ( +
+
+
+
+
    {listItems(this.headerItems().toArray())}
+
+

+ + {this.extension.icon ? icon(this.extension.icon.name) : ''} + + {this.extension.extra['flarum-extension'].title} + {this.extension.version} +

+
{this.extension.description}
+
+ + {Switch.component( + { + state: this.isEnabled(), + onchange: this.toggle.bind(this, this.extension.id), + }, + this.isEnabled(this.extension.id) + ? app.translator.trans('core.admin.extension.enabled') + : app.translator.trans('core.admin.extension.disabled') + )} +
+
+
+ {!this.isEnabled() ? ( +
+

{app.translator.trans('core.admin.extension.enable_to_see')}

+
+ ) : ( +
+
+
+ {this.form() ? ( +
+ {this.form()} + +
{this.submitButton()}
+
+ ) : app.extensionSettings[this.extension.id] ? ( + + ) : ( +

{app.translator.trans('core.admin.extension.no_settings')}

+ )} +
+
+
+
+

{app.translator.trans('core.admin.extension.permissions_title')}

+
+
+ {app.extensionPermissions[this.extension.id] ? ( + ExtensionPermissionGrid.component({ extensionId: this.extension.id }) + ) : ( +

{app.translator.trans('core.admin.extension.no_permissions')}

+ )} +
+
+
+ )} +
+ ); + } + + form() { + return ''; + } + + headerItems() { + const items = new ItemList(); + + items.add( + 'uninstall', + Button.component( + { + icon: 'far fa-trash-alt', + onclick: () => { + app + .request({ + url: app.forum.attribute('apiUrl') + '/extensions/' + this.extension.id, + method: 'DELETE', + }) + .then(() => window.location.reload()); + + app.modal.show(LoadingModal); + }, + }, + app.translator.trans('core.admin.extension.uninstall_button') + ) + ); + + return items; + } + + infoItems() { + const items = new ItemList(); + + if (this.extension.authors) { + console.log(this.extension); + let authors = []; + console.log(this.extension.authors); + + Object.keys(this.extension.authors).map((author) => { + authors.push(this.extension.authors[author].name); + }); + + items.add('authors', [icon('fas fa-user'), {authors.join(', ')}]); + } + + if (this.extension.source || this.extension.support) { + items.add( + 'source', + Button.component( + { + href: this.extension.source ? this.extension.source.url : this.extension.support.source, + icon: 'fas fa-code', + }, + app.translator.trans('core.admin.extension.source') + ) + ); + } + + return items; + } + + isEnabled() { + const enabled = JSON.parse(app.data.settings.extensions_enabled); + + let isEnabled = enabled.indexOf(this.extension.id) !== -1; + + if (this.changingState) { + return !isEnabled; + } else { + return isEnabled; + } + } + + toggle() { + const enabled = this.isEnabled(); + + this.changingState = true; + + app + .request({ + url: app.forum.attribute('apiUrl') + '/extensions/' + this.extension.id, + method: 'PATCH', + body: { enabled: !enabled }, + }) + .then(() => { + if (!enabled) localStorage.setItem('enabledExtension', this.extension.id); + window.location.reload(); + }); + + app.modal.show(LoadingModal); + } + + submitButton() { + return ( + + ); + } + + // Simple version to allow copy + // paste from old exts + setting(key, fallback = '') { + this.settings[key] = this.settings[key] || m.prop(app.data.settings[key] || fallback); + + return this.settings[key]; + } + + dirty() { + const dirty = {}; + + Object.keys(this.settings).forEach((key) => { + const value = this.settings[key](); + + if (value !== app.data.settings[key]) { + dirty[key] = value; + } + }); + + return dirty; + } + + changed() { + return Object.keys(this.dirty()).length; + } + + saveSettings(e) { + e.preventDefault(); + + app.alerts.clear(); + + this.loading = true; + + saveSettings(this.dirty()).then(this.onsaved.bind(this)); + } + + onsaved() { + this.loading = false; + + app.alerts.show( + new Alert( + { + type: 'success', + }, + app.translator.trans('core.admin.extension.saved_message') + ) + ); + } +} diff --git a/js/src/admin/components/ExtensionPermissionGrid.js b/js/src/admin/components/ExtensionPermissionGrid.js new file mode 100644 index 0000000000..00b02a2164 --- /dev/null +++ b/js/src/admin/components/ExtensionPermissionGrid.js @@ -0,0 +1,44 @@ +import PermissionGrid from './PermissionGrid'; +import ItemList from '../../common/utils/ItemList'; + +export default class ExtensionPermissionGrid extends PermissionGrid { + permissionItems() { + const items = super.permissionItems(); + + Object.keys(items.items).map((item) => { + if (items.items[item].content.children.length === 0) { + items.remove([item]); + } + }); + + return items; + } + + viewItems() { + return this.getExtensionPermissions('view'); + } + + startItems() { + return this.getExtensionPermissions('start'); + } + + replyItems() { + return this.getExtensionPermissions('reply'); + } + + moderateItems() { + return this.getExtensionPermissions('moderate'); + } + + getExtensionPermissions(type) { + const items = new ItemList(); + + const extensionId = this.attrs.extensionId; + + if (app.extensionPermissions[extensionId] && app.extensionPermissions[extensionId][type]) { + items.merge(app.extensionPermissions[extensionId][type]); + } + + return items; + } +} diff --git a/js/src/admin/components/MailPage.js b/js/src/admin/components/MailPage.js index f20027db74..052fe75b4e 100644 --- a/js/src/admin/components/MailPage.js +++ b/js/src/admin/components/MailPage.js @@ -6,6 +6,7 @@ import Select from '../../common/components/Select'; import LoadingIndicator from '../../common/components/LoadingIndicator'; import saveSettings from '../utils/saveSettings'; import Stream from '../../common/utils/Stream'; +import icon from '../../common/helpers/icon'; export default class MailPage extends Page { oninit(vnode) { @@ -65,6 +66,15 @@ export default class MailPage extends Page { return (
+
+
+

+ {icon('fas fa-envelope')} + {app.translator.trans('core.admin.email.title')} +

+
{app.translator.trans('core.admin.email.description')}
+
+

{app.translator.trans('core.admin.email.heading')}

diff --git a/js/src/admin/components/PermissionGrid.js b/js/src/admin/components/PermissionGrid.js index fe45859bda..da5e11e3d6 100644 --- a/js/src/admin/components/PermissionGrid.js +++ b/js/src/admin/components/PermissionGrid.js @@ -10,6 +10,7 @@ export default class PermissionGrid extends Component { super.oninit(vnode); this.permissions = this.permissionItems().toArray(); + this.extensionId = this.attrs.extensionId; } view() { @@ -103,6 +104,10 @@ export default class PermissionGrid extends Component { viewItems() { const items = new ItemList(); + if (this.extensionId) { + return this.getExtensionPermissions('view'); + } + items.add( 'viewDiscussions', { @@ -158,12 +163,18 @@ export default class PermissionGrid extends Component { permission: 'user.viewLastSeenAt', }); + items.merge(this.getExtensionPermissions('view')); + return items; } startItems() { const items = new ItemList(); + if (this.extensionId) { + return this.getExtensionPermissions('start'); + } + items.add( 'start', { @@ -198,12 +209,18 @@ export default class PermissionGrid extends Component { 90 ); + items.merge(this.getExtensionPermissions('start')); + return items; } replyItems() { const items = new ItemList(); + if (this.extensionId) { + return this.getExtensionPermissions('reply'); + } + items.add( 'reply', { @@ -238,12 +255,18 @@ export default class PermissionGrid extends Component { 90 ); + items.merge(this.getExtensionPermissions('start')); + return items; } moderateItems() { const items = new ItemList(); + if (this.extensionId) { + return this.getExtensionPermissions('reply'); + } + items.add( 'viewIpsPosts', { @@ -334,6 +357,8 @@ export default class PermissionGrid extends Component { 60 ); + items.merge(this.getExtensionPermissions('start')); + return items; } @@ -366,4 +391,18 @@ export default class PermissionGrid extends Component { scopeControlItems() { return new ItemList(); } + + getExtensionPermissions(type) { + const items = new ItemList(); + + if (this.extensionId) { + items.merge(app.extensionPermissions[this.extensionId][type]); + } else { + Object.keys(app.extensionPermissions).map((extension) => { + items.merge(app.extensionPermissions[extension][type]); + }); + } + + return items; + } } diff --git a/js/src/admin/components/PermissionsPage.js b/js/src/admin/components/PermissionsPage.js index cc3eb30311..6e79f38a01 100644 --- a/js/src/admin/components/PermissionsPage.js +++ b/js/src/admin/components/PermissionsPage.js @@ -9,6 +9,15 @@ export default class PermissionsPage extends Page { view() { return (
+
+
+

+ {icon('fas fa-key')} + {app.translator.trans('core.admin.permissions.title')} +

+
{app.translator.trans('core.admin.permissions.description')}
+
+
{app.store diff --git a/js/src/admin/components/StatusWidget.js b/js/src/admin/components/StatusWidget.js index d195c121ec..8dbc26168d 100644 --- a/js/src/admin/components/StatusWidget.js +++ b/js/src/admin/components/StatusWidget.js @@ -16,7 +16,7 @@ import LoadingModal from './LoadingModal'; export default class StatusWidget extends DashboardWidget { className() { - return 'StatusWidget'; + return 'Widget StatusWidget'; } content() { diff --git a/js/src/admin/routes.js b/js/src/admin/routes.js index c2012ddcf9..0b7fc64494 100644 --- a/js/src/admin/routes.js +++ b/js/src/admin/routes.js @@ -2,7 +2,6 @@ import DashboardPage from './components/DashboardPage'; import BasicsPage from './components/BasicsPage'; import PermissionsPage from './components/PermissionsPage'; import AppearancePage from './components/AppearancePage'; -import ExtensionsPage from './components/ExtensionsPage'; import MailPage from './components/MailPage'; /** @@ -16,7 +15,6 @@ export default function (app) { basics: { path: '/basics', component: BasicsPage }, permissions: { path: '/permissions', component: PermissionsPage }, appearance: { path: '/appearance', component: AppearancePage }, - extensions: { path: '/extensions', component: ExtensionsPage }, mail: { path: '/mail', component: MailPage }, }; } diff --git a/js/src/admin/utils/addExtensionPermission.js b/js/src/admin/utils/addExtensionPermission.js new file mode 100644 index 0000000000..fa9970552b --- /dev/null +++ b/js/src/admin/utils/addExtensionPermission.js @@ -0,0 +1,26 @@ +import ItemList from '../../common/utils/ItemList'; + +export default class addExtensionPermission { + constructor(extension) { + this.extension = extension; + app.extensionPermissions[extension] = {}; + } + + add(permission, icon, label, type, priority = 0) { + if (app.extensionPermissions[this.extension][type] === undefined) { + app.extensionPermissions[this.extension][type] = new ItemList(); + } + + app.extensionPermissions[this.extension][type].add( + permission, + { + icon, + label, + permission, + }, + priority + ); + + return this; + } +} From abf0229c8affbb87002094c2745ec70bc66c0171 Mon Sep 17 00:00:00 2001 From: Charlie <13856015+KyrneDev@users.noreply.github.com> Date: Sat, 24 Oct 2020 14:04:29 -0700 Subject: [PATCH 02/61] AdminUX --- less/admin.less | 2 +- less/admin/AdminNav.less | 190 +++++++++++++++++++++------ less/admin/AppearancePage.less | 222 +++++++++++++++++++++++++++++++- less/admin/BasicsPage.less | 24 +++- less/admin/DashboardPage.less | 32 ++++- less/admin/ExtensionPage.less | 104 +++++++++++++++ less/admin/MailPage.less | 24 +++- less/admin/PermissionsPage.less | 30 ++++- 8 files changed, 581 insertions(+), 47 deletions(-) create mode 100644 less/admin/ExtensionPage.less diff --git a/less/admin.less b/less/admin.less index d2b56ea216..1c2503c71e 100644 --- a/less/admin.less +++ b/less/admin.less @@ -5,6 +5,6 @@ @import "admin/BasicsPage"; @import "admin/PermissionsPage"; @import "admin/EditGroupModal"; -@import "admin/ExtensionsPage"; +@import "admin/ExtensionPage"; @import "admin/AppearancePage"; @import "admin/MailPage"; diff --git a/less/admin/AdminNav.less b/less/admin/AdminNav.less index c0770ac640..38f5f6d8ce 100644 --- a/less/admin/AdminNav.less +++ b/less/admin/AdminNav.less @@ -1,10 +1,10 @@ -@admin-pane-width: 300px; +@admin-pane-width: 210px; .App { padding-bottom: 0; } -.AdminLinkButton-description { - display: none; +.App-content { + border-top: none; } .AdminContent { padding: 20px 0; @@ -12,6 +12,72 @@ .App-content .sideNavOffset { margin-top: 0; } + +@media @tablet { + .item-search{ + display: none; + } +} + +@media @phone { + .Dropdown-menu { + height: 70vh; + + .item-search { + margin: 10px; + + .SearchBar { + width: 100% + } + } + } + + .ExtensionNavButton { + .Button-label { + margin-left: 30px; + } + .ExtensionIcon { + margin: 0 0 0 -4px !important; + } + } +} + +@media @phone, @tablet { + .App-nav .AdminNav { + .Dropdown-menu { + .NavDivider { + display: none; + } + > li { + .ExtensionListTitle { + color: @muted-color; + text-transform: uppercase; + margin: 25px 0 10px 15px; + } + + .ExtensionIcon { + width: 25px; + height: 25px; + background: @control-bg; + color: @control-color; + border-radius: 4px; + display: inline-block; + font-size: 15px; + line-height: 25px; + text-align: center; + margin: -2px -29px; + position: absolute; + + .icon { + margin: 0; + } + } + } + } + } +} + + @media @desktop, @desktop-hd { .App-nav { position: absolute; @@ -20,59 +86,88 @@ width: @admin-pane-width; .box-shadow(0 6px 6px @shadow-color); background: @body-bg; - border-top: 1px solid @control-bg; z-index: @zindex-pane; - overflow: auto; + overflow-y: scroll; + padding-bottom: 40px; .affix & { position: fixed; bottom: 0; - height: auto; } } .App-content .sideNavOffset { margin-left: @admin-pane-width; } .App-nav .AdminNav { - .Dropdown-menu > li { - > a { - padding: 15px 15px 15px 45px; - display: block; - text-decoration: none; - white-space: normal; - } - > a, > a:hover, &.active > a { - color: @muted-color; - } - > a:hover { - background: @control-bg; + .Dropdown-menu { + .item-search { + margin-top: 10px; + margin-bottom: 20px; } - &.active > a { - background: @control-bg; - font-weight: normal; - .Button-label, .Button-icon { + > li { + > a { + padding: 10px 10px 10px 45px; + display: block; + text-decoration: none; + } + > a, + > a:hover, + &.active > a { color: @text-color; - font-weight: bold; + } + > a:hover { + background: @control-bg; + } + &.active > a { + background: @primary-color; + font-weight: normal; + + .Button-label, + .Button-icon { + filter: invert(1); + font-weight: bold; + } + } + .Button-icon { + float: left; + font-size: 12px !important; + margin-left: -25px !important; + margin-top: 4px !important; + } + .Button-label { + padding-left: 5px; + font-size: 13px; + font-weight: normal; + } + + .Search-input, + .SearchBar { + max-width: 175px; + margin: 0 auto; + } + + .ExtensionListTitle { + color: @muted-color; + text-transform: uppercase; + margin: 25px 0 15px 15px; + } + + .ExtensionIcon { + width: 25px; + height: 25px; + background: @control-bg; + color: @control-color; + border-radius: 4px; + display: inline-block; + font-size: 15px; + line-height: 25px; + text-align: center; + margin: -2px -29px; + position: absolute; } } } - .Button-icon { - float: left; - margin-left: -30px; - font-size: 14px; - margin-top: 4px !important; - } - .Button-label { - display: block; - font-size: 15px; - font-weight: normal; - margin: 0 0 5px; - } - .AdminLinkButton-description { - display: block; - font-size: 12px; - } } .container { width: 100%; @@ -86,3 +181,20 @@ } } } + +.ExtensionListItem-Dot { + height: 10px; + width: 10px; + border-radius: 50%; + display: inline-block; + right: 13px; + margin: 6px 5px; + position: absolute; +} + +.ExtensionListItem-Dot.enabled { + background-color: #2ECC40; +} +.ExtensionListItem-Dot.disabled { + background-color: #FF4136; +} diff --git a/less/admin/AppearancePage.less b/less/admin/AppearancePage.less index 5593263c21..de285d896f 100644 --- a/less/admin/AppearancePage.less +++ b/less/admin/AppearancePage.less @@ -1,8 +1,31 @@ .AppearancePage { + .AppearancePage-header { + background: @control-bg; + margin-bottom: 20px; + + .container{ + padding: 30px; + } + + h2 { + margin-top: 0; + margin-bottom: 10px; + color: @muted-color; + } + + .helpText { + margin: 0; + } + + i { + margin-right: 15px; + } + } + @media @desktop-up { .container { max-width: 600px; - padding: 30px; + padding: 0 30px; margin: 0; } } @@ -34,3 +57,200 @@ line-height: 1; } } +.StatisticsWidget-table { + margin-top: -20px; +} +.StatisticsWidget-labels { + float: left; + min-width: 130px; + padding-right: 10px; + padding-top: 45px; + font-size: 12px; + font-weight: bold; + color: @muted-color; +} +.StatisticsWidget-label { + padding-top: 8px; +} +.StatisticsWidget-entity { + float: left; + min-width: 130px; + padding: 15px 20px; + color: @text-color; + font-size: 20px; + + &:hover { + background: mix(@control-bg, @body-bg, 50%); + text-decoration: none; + } + &.active { + border-top: 4px solid @primary-color; + padding-top: 15 - 4px; + } +} +.StatisticsWidget-change { + font-size: 11px; +} +.StatisticsWidget-change--up { + color: #00a502; +} +.StatisticsWidget-change--down { + color: #d0011b; +} +.StatisticsWidget-heading { + height: 30px; + padding-top: 5px; + margin: 0; + font-weight: bold; + text-transform: uppercase; + font-size: 12px; + color: @muted-color; + + .active & { + color: @primary-color; + } +} +.StatisticsWidget-total, +.StatisticsWidget-period, +.StatisticsWidget-label { + height: 35px; +} +.StatisticsWidget-total { + font-weight: bold; +} +.StatisticsWidget-chart { + clear: left; + margin: -20px -10px; +} +.StatisticsWidget-chart .chart-container { + .dataset-0 { + opacity: 0.2; + } + .chart-legend { + display: none; + } + // Hide the "last period" data from the tooltip + .graph-svg-tip ul.data-point-list > li:first-child { + display: none; + } +} + +/*! + * Frappe Charts 1.0.0 by @frappe - https://frappe.io/charts + * License - MIT https://github.com/frappe/charts/blob/master/LICENSE + */ +.chart-container { + position: relative; /* for absolutely positioned tooltip */ + + /* https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ */ + font-family: -apple-system, BlinkMacSystemFont, + 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', + 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + + .axis, .chart-label { + fill: #555b51; + + line { + stroke: #dadada; + } + } + .dataset-units { + circle { + stroke: #fff; + stroke-width: 2; + } + + path { + fill: none; + stroke-opacity: 1; + stroke-width: 2px; + } + } + .dataset-path { + stroke-width: 2px; + } + .path-group { + path { + fill: none; + stroke-opacity: 1; + stroke-width: 2px; + } + } + line.dashed { + stroke-dasharray: 5, 3; + } + .axis-line { + .specific-value { + text-anchor: start; + } + .y-line { + text-anchor: end; + } + .x-line { + text-anchor: middle; + } + } + .legend-dataset-text { + fill: #6c7680; + font-weight: 600; + } +} +.graph-svg-tip { + position: absolute; + z-index: 99999; + padding: 10px; + font-size: 12px; + color: #959da5; + text-align: center; + background: rgba(0, 0, 0, 0.8); + border-radius: 3px; + ul { + padding-left: 0; + display: flex; + } + ol { + padding-left: 0; + display: flex; + } + ul.data-point-list { + li { + min-width: 90px; + flex: 1; + font-weight: 600; + } + } + strong { + color: #dfe2e5; + font-weight: 600; + } + .svg-pointer { + position: absolute; + height: 5px; + margin: 0 0 0 -5px; + content: ' '; + border: 5px solid transparent; + border-top-color: rgba(0, 0, 0, 0.8); + } + &.comparison { + padding: 0; + text-align: left; + pointer-events: none; + .title { + display: block; + padding: 10px; + margin: 0; + font-weight: 600; + line-height: 1; + pointer-events: none; + } + ul { + margin: 0; + white-space: nowrap; + list-style: none; + } + li { + display: inline-block; + padding: 5px 10px; + } + } +} diff --git a/less/admin/BasicsPage.less b/less/admin/BasicsPage.less index c22ad472dc..1efeef240a 100644 --- a/less/admin/BasicsPage.less +++ b/less/admin/BasicsPage.less @@ -1,5 +1,27 @@ .BasicsPage { - padding: 20px 0; + + .BasicsPage-header { + background: @control-bg; + margin-bottom: 20px; + + .container{ + padding: 30px; + } + + h2 { + margin-top: 0; + margin-bottom: 10px; + color: @muted-color; + } + + .helpText { + margin: 0; + } + + i { + margin-right: 15px; + } + } @media @desktop-up { .container { diff --git a/less/admin/DashboardPage.less b/less/admin/DashboardPage.less index 9bab2986d4..aa83971128 100644 --- a/less/admin/DashboardPage.less +++ b/less/admin/DashboardPage.less @@ -1,8 +1,34 @@ .DashboardPage { - background: @control-bg; + background: @body-bg; color: @control-color; min-height: 100vh; + .DashboardPage-header { + background: @control-bg; + + @media @phone, @tablet { + margin-bottom: 20px; + } + + .container{ + padding: 30px; + } + + h2 { + margin-top: 0; + margin-bottom: 10px; + color: @muted-color; + } + + .helpText { + margin: 0; + } + + i { + margin-right: 15px; + } + } + @media @desktop-up { .container { padding: 30px; @@ -11,8 +37,8 @@ } } -.DashboardWidget { - background: @body-bg; +.Widget { + background: @control-bg; color: @text-color; border-radius: @border-radius; padding: 20px; diff --git a/less/admin/ExtensionPage.less b/less/admin/ExtensionPage.less new file mode 100644 index 0000000000..7b007de96e --- /dev/null +++ b/less/admin/ExtensionPage.less @@ -0,0 +1,104 @@ +@extension-list-column-width: 410px; + +.ExtensionPage-header, +.ExtensionPage-permissions-header { + padding: 20px 0; + background: @control-bg; + + h2 { + color: @muted-color; + margin-left: 35px; + + span { + margin-left: 10px; + font-size: 13px; + color: @muted-color; + font-weight: normal; + } + } + + button { + background: none; + border: none; + + &:hover { + text-decoration: underline; + } + } + + ul { + list-style-type: none; + padding: 0; + margin: 0; + + > li { + display: inline; + color: @muted-color; + margin-right: 7px; + + > a { + color: @muted-color; + } + + > .icon { + margin-right: 5px; + } + } + } + + .ExtensionPage-headerItems { + float: right; + padding-top: 21px; + } + + .ExtensionPage-actionItems{ + padding-top: 5px; + + .ExtensionInfo { + padding-top: 5px; + } + } + + .Checkbox-display { + background: @muted-more-color; + } + + .ExtensionInfo { + float: right; + } + + .ExtensionIcon { + width: 25px; + height: 25px; + background: @control-bg; + color: @control-color; + border-radius: 4px; + display: inline-block; + font-size: 12.5px; + line-height: 25px; + text-align: center; + position: absolute; + margin-left: -35px; + } +} + +.ExtensionPage-settings { + margin-top: 20px; + + input { + max-width: 400px; + } +} + +.ExtensionPage-subHeader { + color: @muted-color; + font-weight: normal; +} + + +.ExtensionPage-permissions { + .ExtensionPage-permissions-header { + margin: 20px 0 20px; + padding: 5px 0; + } +} diff --git a/less/admin/MailPage.less b/less/admin/MailPage.less index 63348b21f8..796cff628a 100644 --- a/less/admin/MailPage.less +++ b/less/admin/MailPage.less @@ -1,5 +1,27 @@ .MailPage { - padding: 20px 0; + + .MailPage-header { + background: @control-bg; + margin-bottom: 20px; + + .container{ + padding: 30px; + } + + h2 { + margin-top: 0; + margin-bottom: 10px; + color: @muted-color; + } + + .helpText { + margin: 0; + } + + i { + margin-right: 15px; + } + } @media @desktop-up { .container { diff --git a/less/admin/PermissionsPage.less b/less/admin/PermissionsPage.less index 223885965c..c699c494f8 100644 --- a/less/admin/PermissionsPage.less +++ b/less/admin/PermissionsPage.less @@ -1,6 +1,34 @@ +.PermissionsPage-header { + background: @control-bg; + margin-bottom: 20px; + + .container{ + padding: 30px; + } + + h2 { + margin-top: 0; + margin-bottom: 10px; + color: @muted-color; + } + + .helpText { + margin: 0; + } + + i { + margin-right: 15px; + } +} + .PermissionsPage-groups { background: @control-bg; - padding: 20px 0; + border-radius: @border-radius; + max-width: calc(~'100% - 60px'); + display: block; + margin-left: 30px; + overflow-x: auto; + padding: 8px 0 8px; } .Group { width: 90px; From 4e16808af5a1dc31252c7c193aaf01a04177d7aa Mon Sep 17 00:00:00 2001 From: Charlie <13856015+KyrneDev@users.noreply.github.com> Date: Sat, 24 Oct 2020 14:04:40 -0700 Subject: [PATCH 03/61] Common Changes --- js/src/common/components/Button.js | 5 +++++ js/src/common/components/SelectDropdown.js | 2 ++ 2 files changed, 7 insertions(+) diff --git a/js/src/common/components/Button.js b/js/src/common/components/Button.js index bf756b0dde..6ae10a5303 100644 --- a/js/src/common/components/Button.js +++ b/js/src/common/components/Button.js @@ -35,6 +35,11 @@ export default class Button extends Component { attrs['aria-label'] = attrs.title; } + // If given a translation object, extract the text. + if (typeof attrs.title === 'object') { + attrs.title = extractText(attrs.title); + } + // If nothing else is provided, we use the textual button content as tooltip if (!attrs.title && vnode.children) { attrs.title = extractText(vnode.children); diff --git a/js/src/common/components/SelectDropdown.js b/js/src/common/components/SelectDropdown.js index c61fdb5f1e..31fb8147a6 100644 --- a/js/src/common/components/SelectDropdown.js +++ b/js/src/common/components/SelectDropdown.js @@ -12,6 +12,8 @@ import icon from '../helpers/icon'; function isActive(vnode) { const tag = vnode.tag; + if (typeof tag === 'string') return false; + if ('initAttrs' in tag) { tag.initAttrs(vnode.attrs); } From 387af7a518742789db706c8427d4736555847846 Mon Sep 17 00:00:00 2001 From: Charlie <13856015+KyrneDev@users.noreply.github.com> Date: Tue, 27 Oct 2020 11:59:06 -0700 Subject: [PATCH 04/61] WIP Admin changes --- js/src/admin/AdminApplication.js | 19 +- js/src/admin/compat.js | 6 + js/src/admin/components/AdminHeader.js | 21 +++ js/src/admin/components/AdminNav.js | 17 +- js/src/admin/components/AppearancePage.js | 16 +- js/src/admin/components/BasicsPage.js | 90 +++++---- js/src/admin/components/DashboardPage.js | 19 +- js/src/admin/components/ExtensionPage.js | 176 ++++++++++-------- .../components/ExtensionPermissionGrid.js | 4 +- js/src/admin/components/ExtensionSetting.js | 30 +++ js/src/admin/components/MailPage.js | 16 +- js/src/admin/components/PermissionGrid.js | 6 +- js/src/admin/components/PermissionsPage.js | 16 +- .../admin/resolvers/ExtensionPageResolver.ts | 21 +++ js/src/admin/routes.js | 3 + js/src/admin/utils/extensionData.js | 35 ++++ 16 files changed, 311 insertions(+), 184 deletions(-) create mode 100644 js/src/admin/components/AdminHeader.js create mode 100644 js/src/admin/components/ExtensionSetting.js create mode 100644 js/src/admin/resolvers/ExtensionPageResolver.ts create mode 100644 js/src/admin/utils/extensionData.js diff --git a/js/src/admin/AdminApplication.js b/js/src/admin/AdminApplication.js index a0f0f5190f..75176f2d9e 100644 --- a/js/src/admin/AdminApplication.js +++ b/js/src/admin/AdminApplication.js @@ -7,9 +7,13 @@ import Navigation from '../common/components/Navigation'; import AdminNav from './components/AdminNav'; export default class AdminApplication extends Application { + + // Deprecated as of beta 15 extensionSettings = {}; - extensionPermissions = {}; + extensionData = {}; + + pendingSettings = {}; history = { canGoBack: () => true, @@ -30,9 +34,6 @@ export default class AdminApplication extends Application { * @inheritdoc */ mount() { - // Add the default extension pages - this.setDefaultExtensionPages(); - // Mithril does not render the home route on https://example.com/admin, so // we need to go to https://example.com/admin#/ explicitly. if (!document.location.hash) document.location.hash = '#/'; @@ -70,14 +71,4 @@ export default class AdminApplication extends Application { return required; } - - setDefaultExtensionPages() { - Object.keys(app.data.extensions).map((id) => { - const extension = app.data.extensions[id]; - - if (!app.routes[extension.id]) { - app.routes[extension.id] = { path: '/' + extension.id, component: ExtensionPage }; - } - }); - } } diff --git a/js/src/admin/compat.js b/js/src/admin/compat.js index df210b85a6..5f9dc7bb7f 100644 --- a/js/src/admin/compat.js +++ b/js/src/admin/compat.js @@ -1,6 +1,7 @@ import compat from '../common/compat'; import saveSettings from './utils/saveSettings'; +import extensionData from "./utils/extensionData"; import SettingDropdown from './components/SettingDropdown'; import EditCustomFooterModal from './components/EditCustomFooterModal'; import SessionDropdown from './components/SessionDropdown'; @@ -14,6 +15,7 @@ import AddExtensionModal from './components/AddExtensionModal'; import ExtensionPage from './components/ExtensionPage'; import ExtensionLinkButton from './components/ExtensionLinkButton'; import AdminLinkButton from './components/AdminLinkButton'; +import ExtensionSetting from "./components/ExtensionSetting"; import PermissionGrid from './components/PermissionGrid'; import MailPage from './components/MailPage'; import UploadImageButton from './components/UploadImageButton'; @@ -24,6 +26,7 @@ import EditCustomHeaderModal from './components/EditCustomHeaderModal'; import PermissionsPage from './components/PermissionsPage'; import PermissionDropdown from './components/PermissionDropdown'; import AdminNav from './components/AdminNav'; +import AdminHeader from "./components/AdminHeader"; import EditCustomCssModal from './components/EditCustomCssModal'; import EditGroupModal from './components/EditGroupModal'; import routes from './routes'; @@ -31,6 +34,7 @@ import AdminApplication from './AdminApplication'; export default Object.assign(compat, { 'utils/saveSettings': saveSettings, + 'utils/extensionData': extensionData, 'components/SettingDropdown': SettingDropdown, 'components/EditCustomFooterModal': EditCustomFooterModal, 'components/SessionDropdown': SessionDropdown, @@ -44,6 +48,7 @@ export default Object.assign(compat, { 'components/ExtensionPage': ExtensionPage, 'components/ExtensionLinkButton': ExtensionLinkButton, 'components/AdminLinkButton': AdminLinkButton, + 'components/ExtensionSetting': ExtensionSetting, 'components/PermissionGrid': PermissionGrid, 'components/MailPage': MailPage, 'components/UploadImageButton': UploadImageButton, @@ -54,6 +59,7 @@ export default Object.assign(compat, { 'components/PermissionsPage': PermissionsPage, 'components/PermissionDropdown': PermissionDropdown, 'components/AdminNav': AdminNav, + 'components/AdminHeader': AdminHeader, 'components/EditCustomCssModal': EditCustomCssModal, 'components/EditGroupModal': EditGroupModal, routes: routes, diff --git a/js/src/admin/components/AdminHeader.js b/js/src/admin/components/AdminHeader.js new file mode 100644 index 0000000000..acb125fc32 --- /dev/null +++ b/js/src/admin/components/AdminHeader.js @@ -0,0 +1,21 @@ +import Component from '../../common/Component'; +import classList from '../../common/utils/classList'; +import icon from "../../common/helpers/icon"; + +export default class AdminHeader extends Component { + view(vnode) { + const attrs = Object.assign({}, this.attrs); + + return [ +
+
+

+ {icon(attrs.icon)} + {vnode.children} +

+
{attrs.description}
+
+
+ ] + } +} diff --git a/js/src/admin/components/AdminNav.js b/js/src/admin/components/AdminNav.js index 14e3f2f1f0..cf64616b3a 100644 --- a/js/src/admin/components/AdminNav.js +++ b/js/src/admin/components/AdminNav.js @@ -84,7 +84,8 @@ export default class AdminNav extends Component { items.add( 'search',
- +
); @@ -100,13 +101,15 @@ export default class AdminNav extends Component { } else { list.children('li').map((key) => { const element = $(list.children('li')[key]); + const child = $(element.children()[0]); if ( (!element - .attr('class') - .replace(/item-|ExtensionItem|active/gi, '') - .toUpperCase() - .includes(filter) && - element.attr('class').includes('ExtensionItem')) || + .attr('class') + .replace(/item-|ExtensionItem|active/gi, '') + .toUpperCase() + .includes(filter) && + !(child && child.attr('title') && child.attr('title').toUpperCase().includes(filter))) && + element.attr('class').includes('ExtensionItem') || element.attr('class').includes('NavDivider') ) { $(list.children('li')[key]).hide(); @@ -180,7 +183,7 @@ export default class AdminNav extends Component { `${extension.id} ExtensionItem`, ExtensionLinkButton.component( { - href: app.route(extension.id), + href: app.route('extension', {id: extension.id}), extensionId: extension.id, className: 'ExtensionNavButton', title: extension.description, diff --git a/js/src/admin/components/AppearancePage.js b/js/src/admin/components/AppearancePage.js index 05fb54add2..9e91483b5a 100644 --- a/js/src/admin/components/AppearancePage.js +++ b/js/src/admin/components/AppearancePage.js @@ -8,6 +8,7 @@ import EditCustomHeaderModal from './EditCustomHeaderModal'; import EditCustomFooterModal from './EditCustomFooterModal'; import UploadImageButton from './UploadImageButton'; import saveSettings from '../utils/saveSettings'; +import AdminHeader from "./AdminHeader"; export default class AppearancePage extends Page { oninit(vnode) { @@ -22,15 +23,12 @@ export default class AppearancePage extends Page { view() { return (
-
-
-

- {icon('fas fa-paint-brush')} - {app.translator.trans('core.admin.appearance.title')} -

-
{app.translator.trans('core.admin.appearance.description')}
-
-
+ {AdminHeader.component({ + icon: 'fas fa-paint-brush', + description: app.translator.trans('core.admin.appearance.description'), + className: 'AppearancePage-header' + }, app.translator.trans('core.admin.appearance.title') + )}
diff --git a/js/src/admin/components/BasicsPage.js b/js/src/admin/components/BasicsPage.js index ee2914983c..839dff314a 100644 --- a/js/src/admin/components/BasicsPage.js +++ b/js/src/admin/components/BasicsPage.js @@ -7,7 +7,7 @@ import ItemList from '../../common/utils/ItemList'; import Switch from '../../common/components/Switch'; import Stream from '../../common/utils/Stream'; import withAttr from '../../common/utils/withAttr'; -import icon from '../../common/helpers/icon'; +import AdminHeader from './AdminHeader'; export default class BasicsPage extends Page { oninit(vnode) { @@ -50,22 +50,19 @@ export default class BasicsPage extends Page { view() { return (
-
-
-

- {icon('fas fa-pencil-alt')} - {app.translator.trans('core.admin.basics.title')} -

-
{app.translator.trans('core.admin.basics.description')}
-
-
+ {AdminHeader.component({ + icon: 'fas fa-pencil-alt', + description: app.translator.trans('core.admin.basics.description'), + className: 'BasicsPage-header' + }, app.translator.trans('core.admin.basics.title') + )}
{FieldSet.component( { label: app.translator.trans('core.admin.basics.forum_title_heading'), }, - [] + [] )} {FieldSet.component( @@ -74,30 +71,30 @@ export default class BasicsPage extends Page { }, [
{app.translator.trans('core.admin.basics.forum_description_text')}
, -