Added core editor switching functionality

This commit is contained in:
Dan Brown 2022-04-18 17:39:28 +01:00
parent 956eb1308f
commit 492ffff0a4
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
9 changed files with 101 additions and 40 deletions

View File

@ -27,7 +27,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
*/ */
class PageRevision extends Model class PageRevision extends Model
{ {
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary']; protected $fillable = ['name', 'text', 'summary'];
protected $hidden = ['html', 'markdown', 'restricted', 'text']; protected $hidden = ['html', 'markdown', 'restricted', 'text'];
/** /**

View File

@ -260,10 +260,15 @@ class PageRepo
return $page; return $page;
} }
// Otherwise save the data to a revision // Otherwise, save the data to a revision
$draft = $this->getPageRevisionToUpdate($page); $draft = $this->getPageRevisionToUpdate($page);
$draft->fill($input); $draft->fill($input);
if (setting('app-editor') !== 'markdown') {
if (!empty($input['markdown'])) {
$draft->markdown = $input['markdown'];
$draft->html = '';
} else {
$draft->html = $input['html'];
$draft->markdown = ''; $draft->markdown = '';
} }

View File

@ -21,7 +21,7 @@ use League\HTMLToMarkdown\HtmlConverter;
class HtmlToMarkdown class HtmlToMarkdown
{ {
protected $html; protected string $html;
public function __construct(string $html) public function __construct(string $html)
{ {

View File

@ -0,0 +1,37 @@
<?php
namespace BookStack\Entities\Tools\Markdown;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use League\CommonMark\Block\Element\ListItem;
use League\CommonMark\CommonMarkConverter;
use League\CommonMark\Environment;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\Extension\TaskList\TaskListExtension;
class MarkdownToHtml
{
protected string $markdown;
public function __construct(string $markdown)
{
$this->markdown = $markdown;
}
public function convert(): string
{
$environment = Environment::createCommonMarkEnvironment();
$environment->addExtension(new TableExtension());
$environment->addExtension(new TaskListExtension());
$environment->addExtension(new CustomStrikeThroughExtension());
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
$converter = new CommonMarkConverter([], $environment);
$environment->addBlockRenderer(ListItem::class, new CustomListItemRenderer(), 10);
return $converter->convertToHtml($this->markdown);
}
}

View File

@ -3,11 +3,8 @@
namespace BookStack\Entities\Tools; namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\Markdown\CustomListItemRenderer; use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageRepo;
use BookStack\Uploads\ImageService; use BookStack\Uploads\ImageService;
use BookStack\Util\HtmlContentFilter; use BookStack\Util\HtmlContentFilter;
@ -17,15 +14,10 @@ use DOMNode;
use DOMNodeList; use DOMNodeList;
use DOMXPath; use DOMXPath;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use League\CommonMark\Block\Element\ListItem;
use League\CommonMark\CommonMarkConverter;
use League\CommonMark\Environment;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\Extension\TaskList\TaskListExtension;
class PageContent class PageContent
{ {
protected $page; protected Page $page;
/** /**
* PageContent constructor. * PageContent constructor.
@ -53,28 +45,11 @@ class PageContent
{ {
$markdown = $this->extractBase64ImagesFromMarkdown($markdown); $markdown = $this->extractBase64ImagesFromMarkdown($markdown);
$this->page->markdown = $markdown; $this->page->markdown = $markdown;
$html = $this->markdownToHtml($markdown); $html = (new MarkdownToHtml($markdown))->convert();
$this->page->html = $this->formatHtml($html); $this->page->html = $this->formatHtml($html);
$this->page->text = $this->toPlainText(); $this->page->text = $this->toPlainText();
} }
/**
* Convert the given Markdown content to a HTML string.
*/
protected function markdownToHtml(string $markdown): string
{
$environment = Environment::createCommonMarkEnvironment();
$environment->addExtension(new TableExtension());
$environment->addExtension(new TaskListExtension());
$environment->addExtension(new CustomStrikeThroughExtension());
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
$converter = new CommonMarkConverter([], $environment);
$environment->addBlockRenderer(ListItem::class, new CustomListItemRenderer(), 10);
return $converter->convertToHtml($markdown);
}
/** /**
* Convert all base64 image data to saved images. * Convert all base64 image data to saved images.
*/ */

View File

@ -4,19 +4,24 @@ namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Repos\PageRepo;
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
class PageEditorData class PageEditorData
{ {
protected Page $page; protected Page $page;
protected PageRepo $pageRepo; protected PageRepo $pageRepo;
protected string $requestedEditor;
protected array $viewData; protected array $viewData;
protected array $warnings; protected array $warnings;
public function __construct(Page $page, PageRepo $pageRepo) public function __construct(Page $page, PageRepo $pageRepo, string $requestedEditor)
{ {
$this->page = $page; $this->page = $page;
$this->pageRepo = $pageRepo; $this->pageRepo = $pageRepo;
$this->requestedEditor = $requestedEditor;
$this->viewData = $this->build(); $this->viewData = $this->build();
} }
@ -53,6 +58,9 @@ class PageEditorData
$this->warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft); $this->warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
} }
$editorType = $this->getEditorType($page);
$this->updateContentForEditor($page, $editorType);
return [ return [
'page' => $page, 'page' => $page,
'book' => $page->book, 'book' => $page->book,
@ -60,8 +68,44 @@ class PageEditorData
'isDraftRevision' => $isDraftRevision, 'isDraftRevision' => $isDraftRevision,
'draftsEnabled' => $draftsEnabled, 'draftsEnabled' => $draftsEnabled,
'templates' => $templates, 'templates' => $templates,
'editor' => setting('app-editor') === 'wysiwyg' ? 'wysiwyg' : 'markdown', 'editor' => $editorType,
]; ];
} }
protected function updateContentForEditor(Page $page, string $editorType): void
{
$isHtml = !empty($page->html) && empty($page->markdown);
// HTML to markdown-clean conversion
if ($editorType === 'markdown' && $isHtml && $this->requestedEditor === 'markdown-clean') {
$page->markdown = (new HtmlToMarkdown($page->html))->convert();
}
// Markdown to HTML conversion if we don't have HTML
if ($editorType === 'wysiwyg' && !$isHtml) {
$page->html = (new MarkdownToHtml($page->markdown))->convert();
}
}
/**
* Get the type of editor to show for editing the given page.
* Defaults based upon the current content of the page otherwise will fall back
* to system default but will take a requested type (if provided) if permissions allow.
*/
protected function getEditorType(Page $page): string
{
$emptyPage = empty($page->html) && empty($page->markdown);
$pageType = (!empty($page->html) && empty($page->markdown)) ? 'wysiwyg' : 'markdown';
$systemDefault = setting('app-editor') === 'wysiwyg' ? 'wysiwyg' : 'markdown';
$editorType = $emptyPage ? $systemDefault : $pageType;
// Use requested editor if valid and if we have permission
$requestedType = explode('-', $this->requestedEditor)[0];
if (($requestedType === 'markdown' || $requestedType === 'wysiwyg') && userCan('editor-change')) {
$editorType = $requestedType;
}
return $editorType;
}
} }

View File

@ -83,12 +83,12 @@ class PageController extends Controller
* *
* @throws NotFoundException * @throws NotFoundException
*/ */
public function editDraft(string $bookSlug, int $pageId) public function editDraft(Request $request, string $bookSlug, int $pageId)
{ {
$draft = $this->pageRepo->getById($pageId); $draft = $this->pageRepo->getById($pageId);
$this->checkOwnablePermission('page-create', $draft->getParent()); $this->checkOwnablePermission('page-create', $draft->getParent());
$editorData = new PageEditorData($draft, $this->pageRepo); $editorData = new PageEditorData($draft, $this->pageRepo, $request->query('editor', ''));
$this->setPageTitle(trans('entities.pages_edit_draft')); $this->setPageTitle(trans('entities.pages_edit_draft'));
return view('pages.edit', $editorData->getViewData()); return view('pages.edit', $editorData->getViewData());
@ -182,12 +182,12 @@ class PageController extends Controller
* *
* @throws NotFoundException * @throws NotFoundException
*/ */
public function edit(string $bookSlug, string $pageSlug) public function edit(Request $request, string $bookSlug, string $pageSlug)
{ {
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
$this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-update', $page);
$editorData = new PageEditorData($page, $this->pageRepo); $editorData = new PageEditorData($page, $this->pageRepo, $request->query('editor', ''));
if ($editorData->getWarnings()) { if ($editorData->getWarnings()) {
$this->showWarningNotification(implode("\n", $editorData->getWarnings())); $this->showWarningNotification(implode("\n", $editorData->getWarnings()));
} }

View File

@ -8,7 +8,7 @@
<form action="{{ $page->getUrl() }}" autocomplete="off" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill"> <form action="{{ $page->getUrl() }}" autocomplete="off" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
{{ csrf_field() }} {{ csrf_field() }}
@if($isDraft) {{ method_field('PUT') }} @endif @if(!$isDraft) {{ method_field('PUT') }} @endif
@include('pages.parts.form', ['model' => $page]) @include('pages.parts.form', ['model' => $page])
@include('pages.parts.editor-toolbox') @include('pages.parts.editor-toolbox')
</form> </form>

View File

@ -6,7 +6,7 @@
@if($model->name === trans('entities.pages_initial_name')) @if($model->name === trans('entities.pages_initial_name'))
option:page-editor:has-default-title="true" option:page-editor:has-default-title="true"
@endif @endif
option:page-editor:editor-type="{{ setting('app-editor') }}" option:page-editor:editor-type="{{ $editor }}"
option:page-editor:page-id="{{ $model->id ?? '0' }}" option:page-editor:page-id="{{ $model->id ?? '0' }}"
option:page-editor:page-new-draft="{{ $isDraft ? 'true' : 'false' }}" option:page-editor:page-new-draft="{{ $isDraft ? 'true' : 'false' }}"
option:page-editor:draft-text="{{ ($isDraft || $isDraftRevision) ? trans('entities.pages_editing_draft') : trans('entities.pages_editing_page') }}" option:page-editor:draft-text="{{ ($isDraft || $isDraftRevision) ? trans('entities.pages_editing_draft') : trans('entities.pages_editing_page') }}"