From 28ead83b048d0391912a5b9e3eb9d9fd97099397 Mon Sep 17 00:00:00 2001 From: Ian Morland Date: Thu, 28 Oct 2021 02:56:56 +0100 Subject: [PATCH] Add README documentation to ExtensionPage (#3094) Co-authored-by: Alexander Skvortsov --- js/src/admin/components/ExtensionPage.js | 17 +++++++ js/src/admin/components/ReadmeModal.js | 50 +++++++++++++++++++ js/src/admin/models/ExtensionReadme.js | 5 ++ less/admin.less | 3 +- less/admin/ExtensionPage.less | 17 +++++++ less/admin/ReadmeModal.less | 5 ++ locale/core.yml | 4 ++ .../ShowExtensionReadmeController.php | 47 +++++++++++++++++ .../Serializer/ExtensionReadmeSerializer.php | 35 +++++++++++++ src/Api/routes.php | 7 +++ src/Extension/Extension.php | 25 ++++++++++ 11 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 js/src/admin/components/ReadmeModal.js create mode 100644 js/src/admin/models/ExtensionReadme.js create mode 100644 less/admin/ReadmeModal.less create mode 100644 src/Api/Controller/ShowExtensionReadmeController.php create mode 100644 src/Api/Serializer/ExtensionReadmeSerializer.php diff --git a/js/src/admin/components/ExtensionPage.js b/js/src/admin/components/ExtensionPage.js index c4c43c342a..de06ea8883 100644 --- a/js/src/admin/components/ExtensionPage.js +++ b/js/src/admin/components/ExtensionPage.js @@ -11,6 +11,7 @@ import LoadingModal from './LoadingModal'; import ExtensionPermissionGrid from './ExtensionPermissionGrid'; import isExtensionEnabled from '../utils/isExtensionEnabled'; import AdminPage from './AdminPage'; +import ReadmeModal from './ReadmeModal'; export default class ExtensionPage extends AdminPage { oninit(vnode) { @@ -196,6 +197,22 @@ export default class ExtensionPage extends AdminPage { } }); + const extension = this.extension; + items.add( + 'readme', + Button.component( + { + icon: 'fab fa-readme', + class: 'Readme-link', + onclick() { + app.modal.show(ReadmeModal, { extension }); + }, + }, + app.translator.trans('core.admin.extension.readme.button_label') + ), + 10 + ); + return items; } diff --git a/js/src/admin/components/ReadmeModal.js b/js/src/admin/components/ReadmeModal.js new file mode 100644 index 0000000000..100e8e2b32 --- /dev/null +++ b/js/src/admin/components/ReadmeModal.js @@ -0,0 +1,50 @@ +import app from '../../admin/app'; +import Modal from '../../common/components/Modal'; +import LoadingIndicator from '../../common/components/LoadingIndicator'; +import Placeholder from '../../common/components/Placeholder'; +import ExtensionReadme from '../models/ExtensionReadme'; + +export default class ReadmeModal extends Modal { + oninit(vnode) { + super.oninit(vnode); + + app.store.models['extension-readmes'] = ExtensionReadme; + + this.name = this.attrs.extension.id; + this.extName = this.attrs.extension.extra['flarum-extension'].title; + + this.loading = true; + + this.loadReadme(); + } + + className() { + return 'ReadmeModal Modal--large'; + } + + title() { + return app.translator.trans('core.admin.extension.readme.title', { + extName: this.extName, + }); + } + + content() { + const text = app.translator.trans('core.admin.extension.readme.no_readme'); + + return ( +
+ {this.loading ? ( +
{LoadingIndicator.component()}
+ ) : ( +
{this.readme.content() ? m.trust(this.readme.content()) : Placeholder.component({ text })}
+ )} +
+ ); + } + + async loadReadme() { + this.readme = await app.store.find('extension-readmes', this.name); + this.loading = false; + m.redraw(); + } +} diff --git a/js/src/admin/models/ExtensionReadme.js b/js/src/admin/models/ExtensionReadme.js new file mode 100644 index 0000000000..91d1c90e1a --- /dev/null +++ b/js/src/admin/models/ExtensionReadme.js @@ -0,0 +1,5 @@ +import Model from '../../common/Model'; + +export default class ExtensionReadme extends Model { + content = Model.attribute('content'); +} diff --git a/less/admin.less b/less/admin.less index 07f528fc0d..ac4d51eb41 100644 --- a/less/admin.less +++ b/less/admin.less @@ -11,4 +11,5 @@ @import "admin/AppearancePage"; @import "admin/MailPage"; @import "admin/NoJs"; -@import "admin/UsersListPage.less"; +@import "admin/ReadmeModal"; +@import "admin/UsersListPage"; diff --git a/less/admin/ExtensionPage.less b/less/admin/ExtensionPage.less index 5c10862b08..25e282fc37 100644 --- a/less/admin/ExtensionPage.less +++ b/less/admin/ExtensionPage.less @@ -149,3 +149,20 @@ display: inline-block; margin-left: 8px; } + +.Readme-link { + background: none; + border: none; + cursor: pointer; + color: @muted-color; +} + +.ReadmeModal { + .Modal-header { + background: @control-bg; + color: @muted-color + } + img { + max-width: 100%; + } +} diff --git a/less/admin/ReadmeModal.less b/less/admin/ReadmeModal.less new file mode 100644 index 0000000000..953b96aba2 --- /dev/null +++ b/less/admin/ReadmeModal.less @@ -0,0 +1,5 @@ +.ReadmeModal { + .Placeholder { + margin-bottom: 40px; + } +} diff --git a/locale/core.yml b/locale/core.yml index 3b7772d729..d68a39fce8 100644 --- a/locale/core.yml +++ b/locale/core.yml @@ -131,6 +131,10 @@ core: open_modal: Open Settings permissions_title: Permissions purge_button: Purge + readme: + button_label: README + no_readme: This extension does not appear to have a README file + title: "{extName} documentation" # These translations are used in the secondary header. header: diff --git a/src/Api/Controller/ShowExtensionReadmeController.php b/src/Api/Controller/ShowExtensionReadmeController.php new file mode 100644 index 0000000000..4a1e29823c --- /dev/null +++ b/src/Api/Controller/ShowExtensionReadmeController.php @@ -0,0 +1,47 @@ +extensions = $extensions; + } + + /** + * {@inheritdoc} + */ + protected function data(ServerRequestInterface $request, Document $document) + { + $extensionName = Arr::get($request->getQueryParams(), 'name'); + + RequestUtil::getActor($request)->assertAdmin(); + + return $this->extensions->getExtension($extensionName); + } +} diff --git a/src/Api/Serializer/ExtensionReadmeSerializer.php b/src/Api/Serializer/ExtensionReadmeSerializer.php new file mode 100644 index 0000000000..e59e4f74f4 --- /dev/null +++ b/src/Api/Serializer/ExtensionReadmeSerializer.php @@ -0,0 +1,35 @@ + $extension->getReadme() + ]; + + return $attributes; + } + + public function getId($extension) + { + return $extension->getId(); + } + + public function getType($extension) + { + return 'extension-readmes'; + } +} diff --git a/src/Api/routes.php b/src/Api/routes.php index 503a8a0db9..5863e4856e 100644 --- a/src/Api/routes.php +++ b/src/Api/routes.php @@ -265,6 +265,13 @@ $route->toController(Controller\UninstallExtensionController::class) ); + // Get readme for an extension + $map->get( + '/extension-readmes/{name}', + 'extension-readmes.show', + $route->toController(Controller\ShowExtensionReadmeController::class) + ); + // Update settings $map->post( '/settings', diff --git a/src/Extension/Extension.php b/src/Extension/Extension.php index 3398f51b46..0910eba73d 100644 --- a/src/Extension/Extension.php +++ b/src/Extension/Extension.php @@ -19,6 +19,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use s9e\TextFormatter\Bundles\Fatdown; use Throwable; /** @@ -525,4 +526,28 @@ public function toArray() 'links' => $this->getLinks(), ], $this->composerJson); } + + /** + * Gets the rendered contents of the extension README file as a HTML string. + * + * @return string|null + */ + public function getReadme(): ?string + { + $content = null; + + if (file_exists($file = "$this->path/README.md")) { + $content = file_get_contents($file); + } elseif (file_exists($file = "$this->path/README")) { + $content = file_get_contents($file); + } + + if ($content) { + $xml = Fatdown::parse($content); + + return Fatdown::render($xml); + } + + return null; + } }