diff --git a/js/src/admin/components/AddExtensionModal.tsx b/js/src/admin/components/AddExtensionModal.tsx
new file mode 100644
index 000000000..770793f71
--- /dev/null
+++ b/js/src/admin/components/AddExtensionModal.tsx
@@ -0,0 +1,24 @@
+import app from '../app';
+import Modal from '../../common/components/Modal';
+
+export default class AddExtensionModal extends Modal {
+ className() {
+ return 'AddExtensionModal Modal--small';
+ }
+
+ title() {
+ return app.translator.trans('core.admin.add_extension.title');
+ }
+
+ content() {
+ return (
+
+
{app.translator.trans('core.admin.add_extension.temporary_text')}
+
+ {app.translator.trans('core.admin.add_extension.install_text', { a: })}
+
+
{app.translator.trans('core.admin.add_extension.developer_text', { a: })}
+
+ );
+ }
+}
diff --git a/js/src/admin/components/AdminNav.tsx b/js/src/admin/components/AdminNav.tsx
index badf2b4d5..3f0309798 100644
--- a/js/src/admin/components/AdminNav.tsx
+++ b/js/src/admin/components/AdminNav.tsx
@@ -70,12 +70,15 @@ export default class AdminNav extends Component {
})
);
- // items.add('extensions', AdminLinkButton.component({
- // href: app.route('extensions'),
- // icon: 'fas fa-puzzle-piece',
- // children: app.translator.trans('core.admin.nav.extensions_button'),
- // description: app.translator.trans('core.admin.nav.extensions_text')
- // }));
+ items.add(
+ 'extensions',
+ AdminLinkButton.component({
+ href: app.route('extensions'),
+ icon: 'fas fa-puzzle-piece',
+ children: app.translator.trans('core.admin.nav.extensions_button'),
+ description: app.translator.trans('core.admin.nav.extensions_text'),
+ })
+ );
return items;
}
diff --git a/js/src/admin/components/ExtensionsPage.tsx b/js/src/admin/components/ExtensionsPage.tsx
new file mode 100644
index 000000000..c990e179d
--- /dev/null
+++ b/js/src/admin/components/ExtensionsPage.tsx
@@ -0,0 +1,130 @@
+import app from '../app';
+
+import Page from './Page';
+import Button from '../../common/components/Button';
+import Dropdown from '../../common/components/Dropdown';
+import AddExtensionModal from './AddExtensionModal';
+import LoadingModal from './LoadingModal';
+import ItemList from '../../common/utils/ItemList';
+import icon from '../../common/helpers/icon';
+
+export default class ExtensionsPage extends Page {
+ view() {
+ return (
+
+
+
+ {Button.component({
+ children: app.translator.trans('core.admin.extensions.add_button'),
+ icon: 'fas fa-plus',
+ className: 'Button Button--primary',
+ onclick: () => app.modal.show(AddExtensionModal),
+ })}
+
+
+
+
+
+
+ {Object.keys(app.data.extensions).map((id) => {
+ const extension = app.data.extensions[id];
+ const controls = this.controlItems(extension.id).toArray();
+
+ return (
+ -
+
+
+ {extension.icon ? icon(extension.icon.name) : ''}
+
+ {controls.length ? (
+
+ {controls}
+
+ ) : (
+ ''
+ )}
+
+
+
{extension.version}
+
{extension.description}
+
+
+
+ );
+ })}
+
+
+
+
+ );
+ }
+
+ controlItems(name) {
+ const items = new ItemList();
+ const enabled = this.isEnabled(name);
+
+ if (app.extensionSettings[name]) {
+ items.add(
+ 'settings',
+ Button.component({
+ icon: 'fas fa-cog',
+ children: app.translator.trans('core.admin.extensions.settings_button'),
+ onclick: app.extensionSettings[name],
+ })
+ );
+ }
+
+ if (!enabled) {
+ items.add(
+ 'uninstall',
+ Button.component({
+ icon: 'far fa-trash-alt',
+ children: app.translator.trans('core.admin.extensions.uninstall_button'),
+ onclick: () => {
+ app.request({
+ url: app.forum.attribute('apiUrl') + '/extensions/' + name,
+ method: 'DELETE',
+ }).then(() => window.location.reload());
+
+ app.modal.show(LoadingModal);
+ },
+ })
+ );
+ }
+
+ return items;
+ }
+
+ isEnabled(name) {
+ const enabled = JSON.parse(app.data.settings.extensions_enabled);
+
+ return enabled.indexOf(name) !== -1;
+ }
+
+ toggle(id) {
+ const enabled = this.isEnabled(id);
+
+ app.request({
+ url: app.forum.attribute('apiUrl') + '/extensions/' + id,
+ method: 'PATCH',
+ body: { enabled: !enabled },
+ }).then(() => {
+ if (!enabled) localStorage.setItem('enabledExtension', id);
+ window.location.reload();
+ });
+
+ app.modal.show(LoadingModal);
+ }
+}
diff --git a/js/src/admin/routes.ts b/js/src/admin/routes.ts
index a6fa8c869..0269787c2 100644
--- a/js/src/admin/routes.ts
+++ b/js/src/admin/routes.ts
@@ -3,6 +3,7 @@ import DashboardPage from './components/DashboardPage';
import MailPage from './components/MailPage';
import PermissionsPage from './components/PermissionsPage';
import AppearancePage from './components/AppearancePage';
+import ExtensionsPage from './components/ExtensionsPage';
export default (app) => {
app.routes = {
@@ -11,5 +12,6 @@ export default (app) => {
mail: { path: '/mail', component: MailPage },
permissions: { path: '/permissions', component: PermissionsPage },
appearance: { path: '/appearance', component: AppearancePage },
+ extensions: { path: '/extensions', component: ExtensionsPage },
};
};