Merge pull request #3387 from BookStackApp/editor_switching
Page editor switching
This commit is contained in:
commit
5a7fb20116
|
@ -22,6 +22,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
* @property bool $template
|
* @property bool $template
|
||||||
* @property bool $draft
|
* @property bool $draft
|
||||||
* @property int $revision_count
|
* @property int $revision_count
|
||||||
|
* @property string $editor
|
||||||
* @property Chapter $chapter
|
* @property Chapter $chapter
|
||||||
* @property Collection $attachments
|
* @property Collection $attachments
|
||||||
* @property Collection $revisions
|
* @property Collection $revisions
|
||||||
|
|
|
@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
*
|
*
|
||||||
* @property mixed $id
|
* @property mixed $id
|
||||||
* @property int $page_id
|
* @property int $page_id
|
||||||
|
* @property string $name
|
||||||
* @property string $slug
|
* @property string $slug
|
||||||
* @property string $book_slug
|
* @property string $book_slug
|
||||||
* @property int $created_by
|
* @property int $created_by
|
||||||
|
@ -21,13 +22,14 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
* @property string $summary
|
* @property string $summary
|
||||||
* @property string $markdown
|
* @property string $markdown
|
||||||
* @property string $html
|
* @property string $html
|
||||||
|
* @property string $text
|
||||||
* @property int $revision_number
|
* @property int $revision_number
|
||||||
* @property Page $page
|
* @property Page $page
|
||||||
* @property-read ?User $createdBy
|
* @property-read ?User $createdBy
|
||||||
*/
|
*/
|
||||||
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'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,6 +10,7 @@ use BookStack\Entities\Models\Page;
|
||||||
use BookStack\Entities\Models\PageRevision;
|
use BookStack\Entities\Models\PageRevision;
|
||||||
use BookStack\Entities\Tools\BookContents;
|
use BookStack\Entities\Tools\BookContents;
|
||||||
use BookStack\Entities\Tools\PageContent;
|
use BookStack\Entities\Tools\PageContent;
|
||||||
|
use BookStack\Entities\Tools\PageEditorData;
|
||||||
use BookStack\Entities\Tools\TrashCan;
|
use BookStack\Entities\Tools\TrashCan;
|
||||||
use BookStack\Exceptions\MoveOperationException;
|
use BookStack\Exceptions\MoveOperationException;
|
||||||
use BookStack\Exceptions\NotFoundException;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
|
@ -217,11 +218,25 @@ class PageRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
$pageContent = new PageContent($page);
|
$pageContent = new PageContent($page);
|
||||||
if (!empty($input['markdown'] ?? '')) {
|
$currentEditor = $page->editor ?: PageEditorData::getSystemDefaultEditor();
|
||||||
|
$newEditor = $currentEditor;
|
||||||
|
|
||||||
|
$haveInput = isset($input['markdown']) || isset($input['html']);
|
||||||
|
$inputEmpty = empty($input['markdown']) && empty($input['html']);
|
||||||
|
|
||||||
|
if ($haveInput && $inputEmpty) {
|
||||||
|
$pageContent->setNewHTML('');
|
||||||
|
} elseif (!empty($input['markdown']) && is_string($input['markdown'])) {
|
||||||
|
$newEditor = 'markdown';
|
||||||
$pageContent->setNewMarkdown($input['markdown']);
|
$pageContent->setNewMarkdown($input['markdown']);
|
||||||
} elseif (isset($input['html'])) {
|
} elseif (isset($input['html'])) {
|
||||||
|
$newEditor = 'wysiwyg';
|
||||||
$pageContent->setNewHTML($input['html']);
|
$pageContent->setNewHTML($input['html']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($newEditor !== $currentEditor && userCan('editor-change')) {
|
||||||
|
$page->editor = $newEditor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -229,8 +244,12 @@ class PageRepo
|
||||||
*/
|
*/
|
||||||
protected function savePageRevision(Page $page, string $summary = null): PageRevision
|
protected function savePageRevision(Page $page, string $summary = null): PageRevision
|
||||||
{
|
{
|
||||||
$revision = new PageRevision($page->getAttributes());
|
$revision = new PageRevision();
|
||||||
|
|
||||||
|
$revision->name = $page->name;
|
||||||
|
$revision->html = $page->html;
|
||||||
|
$revision->markdown = $page->markdown;
|
||||||
|
$revision->text = $page->text;
|
||||||
$revision->page_id = $page->id;
|
$revision->page_id = $page->id;
|
||||||
$revision->slug = $page->slug;
|
$revision->slug = $page->slug;
|
||||||
$revision->book_slug = $page->book->slug;
|
$revision->book_slug = $page->book->slug;
|
||||||
|
@ -260,10 +279,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 = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class PageEditActivity
|
class PageEditActivity
|
||||||
{
|
{
|
||||||
protected $page;
|
protected Page $page;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PageEditActivity constructor.
|
* PageEditActivity constructor.
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Entities\Tools;
|
||||||
|
|
||||||
|
use BookStack\Entities\Models\Page;
|
||||||
|
use BookStack\Entities\Repos\PageRepo;
|
||||||
|
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
|
||||||
|
use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
|
||||||
|
|
||||||
|
class PageEditorData
|
||||||
|
{
|
||||||
|
protected Page $page;
|
||||||
|
protected PageRepo $pageRepo;
|
||||||
|
protected string $requestedEditor;
|
||||||
|
|
||||||
|
protected array $viewData;
|
||||||
|
protected array $warnings;
|
||||||
|
|
||||||
|
public function __construct(Page $page, PageRepo $pageRepo, string $requestedEditor)
|
||||||
|
{
|
||||||
|
$this->page = $page;
|
||||||
|
$this->pageRepo = $pageRepo;
|
||||||
|
$this->requestedEditor = $requestedEditor;
|
||||||
|
|
||||||
|
$this->viewData = $this->build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getViewData(): array
|
||||||
|
{
|
||||||
|
return $this->viewData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarnings(): array
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function build(): array
|
||||||
|
{
|
||||||
|
$page = clone $this->page;
|
||||||
|
$isDraft = boolval($this->page->draft);
|
||||||
|
$templates = $this->pageRepo->getTemplates(10);
|
||||||
|
$draftsEnabled = auth()->check();
|
||||||
|
|
||||||
|
$isDraftRevision = false;
|
||||||
|
$this->warnings = [];
|
||||||
|
$editActivity = new PageEditActivity($page);
|
||||||
|
|
||||||
|
if ($editActivity->hasActiveEditing()) {
|
||||||
|
$this->warnings[] = $editActivity->activeEditingMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a current draft version for this user
|
||||||
|
$userDraft = $this->pageRepo->getUserDraft($page);
|
||||||
|
if ($userDraft !== null) {
|
||||||
|
$page->forceFill($userDraft->only(['name', 'html', 'markdown']));
|
||||||
|
$isDraftRevision = true;
|
||||||
|
$this->warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
|
||||||
|
}
|
||||||
|
|
||||||
|
$editorType = $this->getEditorType($page);
|
||||||
|
$this->updateContentForEditor($page, $editorType);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'page' => $page,
|
||||||
|
'book' => $page->book,
|
||||||
|
'isDraft' => $isDraft,
|
||||||
|
'isDraftRevision' => $isDraftRevision,
|
||||||
|
'draftsEnabled' => $draftsEnabled,
|
||||||
|
'templates' => $templates,
|
||||||
|
'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
|
||||||
|
{
|
||||||
|
$editorType = $page->editor ?: self::getSystemDefaultEditor();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the configured system default editor.
|
||||||
|
*/
|
||||||
|
public static function getSystemDefaultEditor(): string
|
||||||
|
{
|
||||||
|
return setting('app-editor') === 'markdown' ? 'markdown' : 'wysiwyg';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use BookStack\Entities\Tools\Cloner;
|
||||||
use BookStack\Entities\Tools\NextPreviousContentLocator;
|
use BookStack\Entities\Tools\NextPreviousContentLocator;
|
||||||
use BookStack\Entities\Tools\PageContent;
|
use BookStack\Entities\Tools\PageContent;
|
||||||
use BookStack\Entities\Tools\PageEditActivity;
|
use BookStack\Entities\Tools\PageEditActivity;
|
||||||
|
use BookStack\Entities\Tools\PageEditorData;
|
||||||
use BookStack\Entities\Tools\PermissionsUpdater;
|
use BookStack\Entities\Tools\PermissionsUpdater;
|
||||||
use BookStack\Exceptions\NotFoundException;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
use BookStack\Exceptions\PermissionsException;
|
use BookStack\Exceptions\PermissionsException;
|
||||||
|
@ -21,7 +22,7 @@ use Throwable;
|
||||||
|
|
||||||
class PageController extends Controller
|
class PageController extends Controller
|
||||||
{
|
{
|
||||||
protected $pageRepo;
|
protected PageRepo $pageRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PageController constructor.
|
* PageController constructor.
|
||||||
|
@ -82,22 +83,15 @@ 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, $request->query('editor', ''));
|
||||||
$this->setPageTitle(trans('entities.pages_edit_draft'));
|
$this->setPageTitle(trans('entities.pages_edit_draft'));
|
||||||
|
|
||||||
$draftsEnabled = $this->isSignedIn();
|
return view('pages.edit', $editorData->getViewData());
|
||||||
$templates = $this->pageRepo->getTemplates(10);
|
|
||||||
|
|
||||||
return view('pages.edit', [
|
|
||||||
'page' => $draft,
|
|
||||||
'book' => $draft->book,
|
|
||||||
'isDraft' => true,
|
|
||||||
'draftsEnabled' => $draftsEnabled,
|
|
||||||
'templates' => $templates,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,43 +182,19 @@ 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);
|
||||||
|
|
||||||
$page->isDraft = false;
|
$editorData = new PageEditorData($page, $this->pageRepo, $request->query('editor', ''));
|
||||||
$editActivity = new PageEditActivity($page);
|
if ($editorData->getWarnings()) {
|
||||||
|
$this->showWarningNotification(implode("\n", $editorData->getWarnings()));
|
||||||
// Check for active editing
|
|
||||||
$warnings = [];
|
|
||||||
if ($editActivity->hasActiveEditing()) {
|
|
||||||
$warnings[] = $editActivity->activeEditingMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a current draft version for this user
|
|
||||||
$userDraft = $this->pageRepo->getUserDraft($page);
|
|
||||||
if ($userDraft !== null) {
|
|
||||||
$page->forceFill($userDraft->only(['name', 'html', 'markdown']));
|
|
||||||
$page->isDraft = true;
|
|
||||||
$warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($warnings) > 0) {
|
|
||||||
$this->showWarningNotification(implode("\n", $warnings));
|
|
||||||
}
|
|
||||||
|
|
||||||
$templates = $this->pageRepo->getTemplates(10);
|
|
||||||
$draftsEnabled = $this->isSignedIn();
|
|
||||||
$this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
|
$this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
|
||||||
|
|
||||||
return view('pages.edit', [
|
return view('pages.edit', $editorData->getViewData());
|
||||||
'page' => $page,
|
|
||||||
'book' => $page->book,
|
|
||||||
'current' => $page,
|
|
||||||
'draftsEnabled' => $draftsEnabled,
|
|
||||||
'templates' => $templates,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddEditorChangeFieldAndPermission extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
// Add the new 'editor' column to the pages table
|
||||||
|
Schema::table('pages', function(Blueprint $table) {
|
||||||
|
$table->string('editor', 50)->default('');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Populate the new 'editor' column
|
||||||
|
// We set it to 'markdown' for pages currently with markdown content
|
||||||
|
DB::table('pages')->where('markdown', '!=', '')->update(['editor' => 'markdown']);
|
||||||
|
// We set it to 'wysiwyg' where we have HTML but no markdown
|
||||||
|
DB::table('pages')->where('markdown', '=', '')
|
||||||
|
->where('html', '!=', '')
|
||||||
|
->update(['editor' => 'wysiwyg']);
|
||||||
|
|
||||||
|
// Give the admin user permission to change the editor
|
||||||
|
$adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
|
||||||
|
|
||||||
|
$permissionId = DB::table('role_permissions')->insertGetId([
|
||||||
|
'name' => 'editor-change',
|
||||||
|
'display_name' => 'Change page editor',
|
||||||
|
'created_at' => Carbon::now()->toDateTimeString(),
|
||||||
|
'updated_at' => Carbon::now()->toDateTimeString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('permission_role')->insert([
|
||||||
|
'role_id' => $adminRoleId,
|
||||||
|
'permission_id' => $permissionId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
// Drop the new column from the pages table
|
||||||
|
Schema::table('pages', function(Blueprint $table) {
|
||||||
|
$table->dropColumn('editor');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove traces of the role permission
|
||||||
|
DB::table('role_permissions')->where('name', '=', 'editor-change')->delete();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.99 16H14v-2H6.99v-3L3 15l3.99 4ZM21 9l-3.99-4v3H10v2h7.01v3z"/></svg>
|
After Width: | Height: | Size: 141 B |
|
@ -131,7 +131,7 @@ class AutoSuggest {
|
||||||
return this.hideSuggestions();
|
return this.hideSuggestions();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.list.innerHTML = suggestions.map(value => `<li><button type="button">${escapeHtml(value)}</button></li>`).join('');
|
this.list.innerHTML = suggestions.map(value => `<li><button type="button" class="text-item">${escapeHtml(value)}</button></li>`).join('');
|
||||||
this.list.style.display = 'block';
|
this.list.style.display = 'block';
|
||||||
for (const button of this.list.querySelectorAll('button')) {
|
for (const button of this.list.querySelectorAll('button')) {
|
||||||
button.addEventListener('blur', this.hideSuggestionsIfFocusedLost.bind(this));
|
button.addEventListener('blur', this.hideSuggestionsIfFocusedLost.bind(this));
|
||||||
|
|
|
@ -96,7 +96,7 @@ class CodeEditor {
|
||||||
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
|
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
|
||||||
this.historyList.innerHTML = historyKeys.map(key => {
|
this.historyList.innerHTML = historyKeys.map(key => {
|
||||||
const localTime = (new Date(parseInt(key))).toLocaleTimeString();
|
const localTime = (new Date(parseInt(key))).toLocaleTimeString();
|
||||||
return `<li><button type="button" data-time="${key}">${localTime}</button></li>`;
|
return `<li><button type="button" data-time="${key}" class="text-item">${localTime}</button></li>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {onSelect} from "../services/dom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom equivalent of window.confirm() using our popup component.
|
||||||
|
* Is promise based so can be used like so:
|
||||||
|
* `const result = await dialog.show()`
|
||||||
|
* @extends {Component}
|
||||||
|
*/
|
||||||
|
class ConfirmDialog {
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.container = this.$el;
|
||||||
|
this.confirmButton = this.$refs.confirm;
|
||||||
|
|
||||||
|
this.res = null;
|
||||||
|
|
||||||
|
onSelect(this.confirmButton, () => {
|
||||||
|
this.sendResult(true);
|
||||||
|
this.getPopup().hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.getPopup().show(null, () => {
|
||||||
|
this.sendResult(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
this.res = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Popup}
|
||||||
|
*/
|
||||||
|
getPopup() {
|
||||||
|
return this.container.components.popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Boolean} result
|
||||||
|
*/
|
||||||
|
sendResult(result) {
|
||||||
|
if (this.res) {
|
||||||
|
this.res(result)
|
||||||
|
this.res = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConfirmDialog;
|
|
@ -10,6 +10,7 @@ import chapterToggle from "./chapter-toggle.js"
|
||||||
import codeEditor from "./code-editor.js"
|
import codeEditor from "./code-editor.js"
|
||||||
import codeHighlighter from "./code-highlighter.js"
|
import codeHighlighter from "./code-highlighter.js"
|
||||||
import collapsible from "./collapsible.js"
|
import collapsible from "./collapsible.js"
|
||||||
|
import confirmDialog from "./confirm-dialog"
|
||||||
import customCheckbox from "./custom-checkbox.js"
|
import customCheckbox from "./custom-checkbox.js"
|
||||||
import detailsHighlighter from "./details-highlighter.js"
|
import detailsHighlighter from "./details-highlighter.js"
|
||||||
import dropdown from "./dropdown.js"
|
import dropdown from "./dropdown.js"
|
||||||
|
@ -26,7 +27,6 @@ import headerMobileToggle from "./header-mobile-toggle.js"
|
||||||
import homepageControl from "./homepage-control.js"
|
import homepageControl from "./homepage-control.js"
|
||||||
import imageManager from "./image-manager.js"
|
import imageManager from "./image-manager.js"
|
||||||
import imagePicker from "./image-picker.js"
|
import imagePicker from "./image-picker.js"
|
||||||
import index from "./index.js"
|
|
||||||
import listSortControl from "./list-sort-control.js"
|
import listSortControl from "./list-sort-control.js"
|
||||||
import markdownEditor from "./markdown-editor.js"
|
import markdownEditor from "./markdown-editor.js"
|
||||||
import newUserPassword from "./new-user-password.js"
|
import newUserPassword from "./new-user-password.js"
|
||||||
|
@ -66,6 +66,7 @@ const componentMapping = {
|
||||||
"code-editor": codeEditor,
|
"code-editor": codeEditor,
|
||||||
"code-highlighter": codeHighlighter,
|
"code-highlighter": codeHighlighter,
|
||||||
"collapsible": collapsible,
|
"collapsible": collapsible,
|
||||||
|
"confirm-dialog": confirmDialog,
|
||||||
"custom-checkbox": customCheckbox,
|
"custom-checkbox": customCheckbox,
|
||||||
"details-highlighter": detailsHighlighter,
|
"details-highlighter": detailsHighlighter,
|
||||||
"dropdown": dropdown,
|
"dropdown": dropdown,
|
||||||
|
@ -82,7 +83,6 @@ const componentMapping = {
|
||||||
"homepage-control": homepageControl,
|
"homepage-control": homepageControl,
|
||||||
"image-manager": imageManager,
|
"image-manager": imageManager,
|
||||||
"image-picker": imagePicker,
|
"image-picker": imagePicker,
|
||||||
"index": index,
|
|
||||||
"list-sort-control": listSortControl,
|
"list-sort-control": listSortControl,
|
||||||
"markdown-editor": markdownEditor,
|
"markdown-editor": markdownEditor,
|
||||||
"new-user-password": newUserPassword,
|
"new-user-password": newUserPassword,
|
||||||
|
|
|
@ -24,6 +24,8 @@ class PageEditor {
|
||||||
this.draftDisplayIcon = this.$refs.draftDisplayIcon;
|
this.draftDisplayIcon = this.$refs.draftDisplayIcon;
|
||||||
this.changelogInput = this.$refs.changelogInput;
|
this.changelogInput = this.$refs.changelogInput;
|
||||||
this.changelogDisplay = this.$refs.changelogDisplay;
|
this.changelogDisplay = this.$refs.changelogDisplay;
|
||||||
|
this.changeEditorButtons = this.$manyRefs.changeEditor;
|
||||||
|
this.switchDialogContainer = this.$refs.switchDialog;
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
this.draftText = this.$opts.draftText;
|
this.draftText = this.$opts.draftText;
|
||||||
|
@ -72,6 +74,9 @@ class PageEditor {
|
||||||
// Draft Controls
|
// Draft Controls
|
||||||
onSelect(this.saveDraftButton, this.saveDraft.bind(this));
|
onSelect(this.saveDraftButton, this.saveDraft.bind(this));
|
||||||
onSelect(this.discardDraftButton, this.discardDraft.bind(this));
|
onSelect(this.discardDraftButton, this.discardDraft.bind(this));
|
||||||
|
|
||||||
|
// Change editor controls
|
||||||
|
onSelect(this.changeEditorButtons, this.changeEditor.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialFocus() {
|
setInitialFocus() {
|
||||||
|
@ -113,17 +118,21 @@ class PageEditor {
|
||||||
data.markdown = this.editorMarkdown;
|
data.markdown = this.editorMarkdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let didSave = false;
|
||||||
try {
|
try {
|
||||||
const resp = await window.$http.put(`/ajax/page/${this.pageId}/save-draft`, data);
|
const resp = await window.$http.put(`/ajax/page/${this.pageId}/save-draft`, data);
|
||||||
if (!this.isNewDraft) {
|
if (!this.isNewDraft) {
|
||||||
this.toggleDiscardDraftVisibility(true);
|
this.toggleDiscardDraftVisibility(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.draftNotifyChange(`${resp.data.message} ${Dates.utcTimeStampToLocalTime(resp.data.timestamp)}`);
|
this.draftNotifyChange(`${resp.data.message} ${Dates.utcTimeStampToLocalTime(resp.data.timestamp)}`);
|
||||||
this.autoSave.last = Date.now();
|
this.autoSave.last = Date.now();
|
||||||
if (resp.data.warning && !this.shownWarningsCache.has(resp.data.warning)) {
|
if (resp.data.warning && !this.shownWarningsCache.has(resp.data.warning)) {
|
||||||
window.$events.emit('warning', resp.data.warning);
|
window.$events.emit('warning', resp.data.warning);
|
||||||
this.shownWarningsCache.add(resp.data.warning);
|
this.shownWarningsCache.add(resp.data.warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
didSave = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Save the editor content in LocalStorage as a last resort, just in case.
|
// Save the editor content in LocalStorage as a last resort, just in case.
|
||||||
try {
|
try {
|
||||||
|
@ -134,6 +143,7 @@ class PageEditor {
|
||||||
window.$events.emit('error', this.autosaveFailText);
|
window.$events.emit('error', this.autosaveFailText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return didSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
draftNotifyChange(text) {
|
draftNotifyChange(text) {
|
||||||
|
@ -185,6 +195,18 @@ class PageEditor {
|
||||||
this.discardDraftWrap.classList.toggle('hidden', !show);
|
this.discardDraftWrap.classList.toggle('hidden', !show);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async changeEditor(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const link = event.target.closest('a').href;
|
||||||
|
const dialog = this.switchDialogContainer.components['confirm-dialog'];
|
||||||
|
const [saved, confirmed] = await Promise.all([this.saveDraft(), dialog.show()]);
|
||||||
|
|
||||||
|
if (saved && confirmed) {
|
||||||
|
window.location = link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PageEditor;
|
export default PageEditor;
|
|
@ -34,7 +34,7 @@ class Popup {
|
||||||
}
|
}
|
||||||
|
|
||||||
hide(onComplete = null) {
|
hide(onComplete = null) {
|
||||||
fadeOut(this.container, 240, onComplete);
|
fadeOut(this.container, 120, onComplete);
|
||||||
if (this.onkeyup) {
|
if (this.onkeyup) {
|
||||||
window.removeEventListener('keyup', this.onkeyup);
|
window.removeEventListener('keyup', this.onkeyup);
|
||||||
this.onkeyup = null;
|
this.onkeyup = null;
|
||||||
|
@ -45,7 +45,7 @@ class Popup {
|
||||||
}
|
}
|
||||||
|
|
||||||
show(onComplete = null, onHide = null) {
|
show(onComplete = null, onHide = null) {
|
||||||
fadeIn(this.container, 240, onComplete);
|
fadeIn(this.container, 120, onComplete);
|
||||||
|
|
||||||
this.onkeyup = (event) => {
|
this.onkeyup = (event) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
|
|
|
@ -196,9 +196,19 @@ return [
|
||||||
'pages_edit_draft_save_at' => 'Draft saved at ',
|
'pages_edit_draft_save_at' => 'Draft saved at ',
|
||||||
'pages_edit_delete_draft' => 'Delete Draft',
|
'pages_edit_delete_draft' => 'Delete Draft',
|
||||||
'pages_edit_discard_draft' => 'Discard Draft',
|
'pages_edit_discard_draft' => 'Discard Draft',
|
||||||
|
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
|
||||||
|
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
|
||||||
|
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||||
|
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||||
'pages_edit_set_changelog' => 'Set Changelog',
|
'pages_edit_set_changelog' => 'Set Changelog',
|
||||||
'pages_edit_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made',
|
'pages_edit_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made',
|
||||||
'pages_edit_enter_changelog' => 'Enter Changelog',
|
'pages_edit_enter_changelog' => 'Enter Changelog',
|
||||||
|
'pages_editor_switch_title' => 'Switch Editor',
|
||||||
|
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
|
||||||
|
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
|
||||||
|
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
|
||||||
|
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
|
||||||
|
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
|
||||||
'pages_save' => 'Save Page',
|
'pages_save' => 'Save Page',
|
||||||
'pages_title' => 'Page Title',
|
'pages_title' => 'Page Title',
|
||||||
'pages_name' => 'Page Name',
|
'pages_name' => 'Page Name',
|
||||||
|
@ -225,6 +235,7 @@ return [
|
||||||
'pages_revisions_number' => '#',
|
'pages_revisions_number' => '#',
|
||||||
'pages_revisions_numbered' => 'Revision #:id',
|
'pages_revisions_numbered' => 'Revision #:id',
|
||||||
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
|
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
|
||||||
|
'pages_revisions_editor' => 'Editor Type',
|
||||||
'pages_revisions_changelog' => 'Changelog',
|
'pages_revisions_changelog' => 'Changelog',
|
||||||
'pages_revisions_changes' => 'Changes',
|
'pages_revisions_changes' => 'Changes',
|
||||||
'pages_revisions_current' => 'Current Version',
|
'pages_revisions_current' => 'Current Version',
|
||||||
|
|
|
@ -27,8 +27,8 @@ return [
|
||||||
'app_secure_images' => 'Higher Security Image Uploads',
|
'app_secure_images' => 'Higher Security Image Uploads',
|
||||||
'app_secure_images_toggle' => 'Enable higher security image uploads',
|
'app_secure_images_toggle' => 'Enable higher security image uploads',
|
||||||
'app_secure_images_desc' => 'For performance reasons, all images are public. This option adds a random, hard-to-guess string in front of image urls. Ensure directory indexes are not enabled to prevent easy access.',
|
'app_secure_images_desc' => 'For performance reasons, all images are public. This option adds a random, hard-to-guess string in front of image urls. Ensure directory indexes are not enabled to prevent easy access.',
|
||||||
'app_editor' => 'Page Editor',
|
'app_default_editor' => 'Default Page Editor',
|
||||||
'app_editor_desc' => 'Select which editor will be used by all users to edit pages.',
|
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
|
||||||
'app_custom_html' => 'Custom HTML Head Content',
|
'app_custom_html' => 'Custom HTML Head Content',
|
||||||
'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.',
|
'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.',
|
||||||
'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
|
'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
|
||||||
|
@ -152,6 +152,7 @@ return [
|
||||||
'role_access_api' => 'Access system API',
|
'role_access_api' => 'Access system API',
|
||||||
'role_manage_settings' => 'Manage app settings',
|
'role_manage_settings' => 'Manage app settings',
|
||||||
'role_export_content' => 'Export content',
|
'role_export_content' => 'Export content',
|
||||||
|
'role_editor_change' => 'Change page editor',
|
||||||
'role_asset' => 'Asset Permissions',
|
'role_asset' => 'Asset Permissions',
|
||||||
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
|
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
|
||||||
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
|
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
|
||||||
|
|
|
@ -120,6 +120,11 @@
|
||||||
width: 800px;
|
width: 800px;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
}
|
}
|
||||||
|
&.very-small {
|
||||||
|
margin: 2% auto;
|
||||||
|
width: 600px;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
&:before {
|
&:before {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
|
|
|
@ -593,13 +593,22 @@ ul.pagination {
|
||||||
li.active a {
|
li.active a {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
a, button {
|
button {
|
||||||
display: block;
|
width: 100%;
|
||||||
padding: $-xs $-m;
|
text-align: start;
|
||||||
|
}
|
||||||
|
li.border-bottom {
|
||||||
|
border-bottom: 1px solid #DDD;
|
||||||
|
}
|
||||||
|
li hr {
|
||||||
|
margin: $-xs 0;
|
||||||
|
}
|
||||||
|
.icon-item, .text-item, .label-item {
|
||||||
|
padding: 8px $-m;
|
||||||
@include lightDark(color, #555, #eee);
|
@include lightDark(color, #555, #eee);
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
line-height: 1.6;
|
line-height: 1.4;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -616,15 +625,30 @@ ul.pagination {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
.text-item {
|
||||||
width: 100%;
|
display: block;
|
||||||
text-align: start;
|
|
||||||
}
|
}
|
||||||
li.border-bottom {
|
.label-item {
|
||||||
border-bottom: 1px solid #DDD;
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: auto min-content;
|
||||||
|
gap: $-m;
|
||||||
|
}
|
||||||
|
.label-item > *:nth-child(2) {
|
||||||
|
opacity: 0.7;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-item {
|
||||||
|
display: grid;
|
||||||
|
align-items: start;
|
||||||
|
grid-template-columns: 16px auto;
|
||||||
|
gap: $-m;
|
||||||
|
svg {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
margin-block-start: 1px;
|
||||||
}
|
}
|
||||||
li hr {
|
|
||||||
margin: $-xs 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,6 @@ em, i, .italic {
|
||||||
|
|
||||||
small, p.small, span.small, .text-small {
|
small, p.small, span.small, .text-small {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
@include lightDark(color, #5e5e5e, #999);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sup, .superscript {
|
sup, .superscript {
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
class="drag-card-action text-center text-neg">@icon('close')</button>
|
class="drag-card-action text-center text-neg">@icon('close')</button>
|
||||||
<div refs="dropdown@menu" class="dropdown-menu">
|
<div refs="dropdown@menu" class="dropdown-menu">
|
||||||
<p class="text-neg small px-m mb-xs">{{ trans('entities.attachments_delete') }}</p>
|
<p class="text-neg small px-m mb-xs">{{ trans('entities.attachments_delete') }}</p>
|
||||||
<button refs="ajax-delete-row@delete" type="button" class="text-primary small delete">{{ trans('common.confirm') }}</button>
|
<button refs="ajax-delete-row@delete" type="button" class="text-primary small delete text-item">{{ trans('common.confirm') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,7 +31,12 @@
|
||||||
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" class="text-button" title="{{ trans('common.delete') }}">@icon('delete')</button>
|
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" class="text-button" title="{{ trans('common.delete') }}">@icon('delete')</button>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||||
<li class="px-m text-small text-muted pb-s">{{trans('entities.comment_delete_confirm')}}</li>
|
<li class="px-m text-small text-muted pb-s">{{trans('entities.comment_delete_confirm')}}</li>
|
||||||
<li><button action="delete" type="button" class="text-button text-neg" >@icon('delete'){{ trans('common.delete') }}</button></li>
|
<li>
|
||||||
|
<button action="delete" type="button" class="text-button text-neg icon-item">
|
||||||
|
@icon('delete')
|
||||||
|
<div>{{ trans('common.delete') }}</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div components="popup confirm-dialog"
|
||||||
|
refs="confirm-dialog@popup {{ $ref }}"
|
||||||
|
class="popup-background">
|
||||||
|
<div class="popup-body very-small" tabindex="-1">
|
||||||
|
|
||||||
|
<div class="popup-header primary-background">
|
||||||
|
<div class="popup-title">{{ $title }}</div>
|
||||||
|
<button refs="popup@hide" type="button" class="popup-header-close">x</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-m py-m">
|
||||||
|
{{ $slot }}
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
<button type="button" class="button outline" refs="popup@hide">{{ trans('common.cancel') }}</button>
|
||||||
|
<button type="button" class="button" refs="confirm-dialog@confirm">{{ trans('common.continue') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -62,26 +62,36 @@
|
||||||
</span>
|
</span>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url('/favourites') }}">@icon('star'){{ trans('entities.my_favourites') }}</a>
|
<a href="{{ url('/favourites') }}" class="icon-item">
|
||||||
|
@icon('star')
|
||||||
|
<div>{{ trans('entities.my_favourites') }}</div>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ $currentUser->getProfileUrl() }}">@icon('user'){{ trans('common.view_profile') }}</a>
|
<a href="{{ $currentUser->getProfileUrl() }}" class="icon-item">
|
||||||
|
@icon('user')
|
||||||
|
<div>{{ trans('common.view_profile') }}</div>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ $currentUser->getEditUrl() }}">@icon('edit'){{ trans('common.edit_profile') }}</a>
|
<a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
|
||||||
|
@icon('edit')
|
||||||
|
<div>{{ trans('common.edit_profile') }}</div>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
|
<form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
|
||||||
method="post">
|
method="post">
|
||||||
{{ csrf_field() }}
|
{{ csrf_field() }}
|
||||||
<button class="text-muted icon-list-item text-primary">
|
<button class="icon-item">
|
||||||
@icon('logout'){{ trans('auth.logout') }}
|
@icon('logout')
|
||||||
|
<div>{{ trans('auth.logout') }}</div>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
<li><hr></li>
|
<li><hr></li>
|
||||||
<li>
|
<li>
|
||||||
@include('common.dark-mode-toggle')
|
@include('common.dark-mode-toggle', ['classes' => 'icon-item'])
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<span>{{ trans('entities.export') }}</span>
|
<span>{{ trans('entities.export') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
|
<ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
|
||||||
<li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" rel="noopener">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
|
<li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_html') }}</span><span>.html</span></a></li>
|
||||||
<li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" rel="noopener">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
|
<li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</span></a></li>
|
||||||
<li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" rel="noopener">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
|
<li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_text') }}</span><span>.txt</span></a></li>
|
||||||
<li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" rel="noopener">{{ trans('entities.export_md') }} <span class="text-muted float right">.md</span></a></li>
|
<li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<div refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" tabindex="0">{{ $options[$selectedSort] }}</div>
|
<div refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" tabindex="0">{{ $options[$selectedSort] }}</div>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu">
|
<ul refs="dropdown@menu" class="dropdown-menu">
|
||||||
@foreach($options as $key => $label)
|
@foreach($options as $key => $label)
|
||||||
<li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}">{{ $label }}</a></li>
|
<li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}" class="text-item">{{ $label }}</a></li>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,6 +17,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script nonce="{{ $cspNonce }}">
|
||||||
|
setTimeout(async () => {
|
||||||
|
const result = await window.components["confirm-dialog"][0].show();
|
||||||
|
console.log({result});
|
||||||
|
}, 1000);
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="container" id="home-default">
|
<div class="container" id="home-default">
|
||||||
<div class="grid third gap-xxl no-row-gap" >
|
<div class="grid third gap-xxl no-row-gap" >
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<form action="{{ url('/mfa/' . $method . '/remove') }}" method="post">
|
<form action="{{ url('/mfa/' . $method . '/remove') }}" method="post">
|
||||||
{{ csrf_field() }}
|
{{ csrf_field() }}
|
||||||
{{ method_field('delete') }}
|
{{ method_field('delete') }}
|
||||||
<button class="text-primary small delete">{{ trans('common.confirm') }}</button>
|
<button class="text-primary small text-item">{{ trans('common.confirm') }}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
@extends('layouts.base')
|
@extends('layouts.base')
|
||||||
|
|
||||||
@section('head')
|
|
||||||
<script src="{{ url('/libs/tinymce/tinymce.min.js?ver=5.10.2') }}" nonce="{{ $cspNonce }}"></script>
|
|
||||||
@stop
|
|
||||||
|
|
||||||
@section('body-class', 'flexbox')
|
@section('body-class', 'flexbox')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
@ -12,9 +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(!isset($isDraft))
|
@if(!$isDraft) {{ method_field('PUT') }} @endif
|
||||||
<input type="hidden" name="_method" value="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>
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
<div class="primary-background-light toolbar page-edit-toolbar">
|
||||||
|
<div class="grid third no-break v-center">
|
||||||
|
|
||||||
|
<div class="action-buttons text-left px-m py-xs">
|
||||||
|
<a href="{{ $isDraft ? $page->getParent()->getUrl() : $page->getUrl() }}"
|
||||||
|
class="text-button text-primary">@icon('back')<span class="hide-under-l">{{ trans('common.back') }}</span></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center px-m">
|
||||||
|
<div component="dropdown"
|
||||||
|
option:dropdown:move-menu="true"
|
||||||
|
class="dropdown-container draft-display text {{ $draftsEnabled ? '' : 'hidden' }}">
|
||||||
|
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" title="{{ trans('entities.pages_edit_draft_options') }}" class="text-primary text-button py-s px-m"><span refs="page-editor@draftDisplay" class="faded-text"></span> @icon('more')</button>
|
||||||
|
@icon('check-circle', ['class' => 'text-pos draft-notification svg-icon', 'refs' => 'page-editor@draftDisplayIcon'])
|
||||||
|
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||||
|
<li>
|
||||||
|
<button refs="page-editor@saveDraft" type="button" class="text-pos icon-item">
|
||||||
|
@icon('save')
|
||||||
|
<div>{{ trans('entities.pages_edit_save_draft') }}</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
@if($isDraft)
|
||||||
|
<li>
|
||||||
|
<a href="{{ $model->getUrl('/delete') }}" class="text-neg icon-item">
|
||||||
|
@icon('delete')
|
||||||
|
{{ trans('entities.pages_edit_delete_draft') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
<li refs="page-editor@discardDraftWrap" class="{{ $isDraft ? '' : 'hidden' }}">
|
||||||
|
<button refs="page-editor@discardDraft" type="button" class="text-neg icon-item">
|
||||||
|
@icon('cancel')
|
||||||
|
<div>{{ trans('entities.pages_edit_discard_draft') }}</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
@if(userCan('editor-change'))
|
||||||
|
<li>
|
||||||
|
@if($editor === 'wysiwyg')
|
||||||
|
<a href="{{ $model->getUrl($isDraft ? '' : '/edit') }}?editor=markdown-clean" refs="page-editor@changeEditor" class="icon-item">
|
||||||
|
@icon('swap-horizontal')
|
||||||
|
<div>
|
||||||
|
{{ trans('entities.pages_edit_switch_to_markdown') }}
|
||||||
|
<br>
|
||||||
|
<small>{{ trans('entities.pages_edit_switch_to_markdown_clean') }}</small>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="{{ $model->getUrl($isDraft ? '' : '/edit') }}?editor=markdown-stable" refs="page-editor@changeEditor" class="icon-item">
|
||||||
|
@icon('swap-horizontal')
|
||||||
|
<div>
|
||||||
|
{{ trans('entities.pages_edit_switch_to_markdown') }}
|
||||||
|
<br>
|
||||||
|
<small>{{ trans('entities.pages_edit_switch_to_markdown_stable') }}</small>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<a href="{{ $model->getUrl($isDraft ? '' : '/edit') }}?editor=wysiwyg" refs="page-editor@changeEditor" class="icon-item">
|
||||||
|
@icon('swap-horizontal')
|
||||||
|
<div>{{ trans('entities.pages_edit_switch_to_wysiwyg') }}</div>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons px-m py-xs">
|
||||||
|
<div component="dropdown" dropdown-move-menu class="dropdown-container">
|
||||||
|
<button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span refs="page-editor@changelogDisplay">{{ trans('entities.pages_edit_set_changelog') }}</span></button>
|
||||||
|
<ul refs="dropdown@menu" class="wide dropdown-menu">
|
||||||
|
<li class="px-l py-m">
|
||||||
|
<p class="text-muted pb-s">{{ trans('entities.pages_edit_enter_changelog_desc') }}</p>
|
||||||
|
<input refs="page-editor@changelogInput"
|
||||||
|
name="summary"
|
||||||
|
id="summary-input"
|
||||||
|
type="text"
|
||||||
|
placeholder="{{ trans('entities.pages_edit_enter_changelog') }}" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<span>{{-- Prevents button jumping on menu show --}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" id="save-button" class="float-left text-primary text-button text-pos-hover hide-under-m">@icon('save')<span>{{ trans('entities.pages_save') }}</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -6,66 +6,17 @@
|
||||||
@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="{{ ($model->draft ?? false) ? 'true' : 'false' }}"
|
option:page-editor:page-new-draft="{{ $isDraft ? 'true' : 'false' }}"
|
||||||
option:page-editor:draft-text="{{ ($model->draft || $model->isDraft) ? 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') }}"
|
||||||
option:page-editor:autosave-fail-text="{{ trans('errors.page_draft_autosave_fail') }}"
|
option:page-editor:autosave-fail-text="{{ trans('errors.page_draft_autosave_fail') }}"
|
||||||
option:page-editor:editing-page-text="{{ trans('entities.pages_editing_page') }}"
|
option:page-editor:editing-page-text="{{ trans('entities.pages_editing_page') }}"
|
||||||
option:page-editor:draft-discarded-text="{{ trans('entities.pages_draft_discarded') }}"
|
option:page-editor:draft-discarded-text="{{ trans('entities.pages_draft_discarded') }}"
|
||||||
option:page-editor:set-changelog-text="{{ trans('entities.pages_edit_set_changelog') }}">
|
option:page-editor:set-changelog-text="{{ trans('entities.pages_edit_set_changelog') }}">
|
||||||
|
|
||||||
{{--Header Bar--}}
|
{{--Header Toolbar--}}
|
||||||
<div class="primary-background-light toolbar page-edit-toolbar">
|
@include('pages.parts.editor-toolbar', ['model' => $model, 'editor' => $editor, 'isDraft' => $isDraft, 'draftsEnabled' => $draftsEnabled])
|
||||||
<div class="grid third no-break v-center">
|
|
||||||
|
|
||||||
<div class="action-buttons text-left px-m py-xs">
|
|
||||||
<a href="{{ $page->draft ? $page->getParent()->getUrl() : $page->getUrl() }}"
|
|
||||||
class="text-button text-primary">@icon('back')<span class="hide-under-l">{{ trans('common.back') }}</span></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center px-m py-xs">
|
|
||||||
<div component="dropdown"
|
|
||||||
option:dropdown:move-menu="true"
|
|
||||||
class="dropdown-container draft-display text {{ $draftsEnabled ? '' : 'hidden' }}">
|
|
||||||
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" title="{{ trans('entities.pages_edit_draft_options') }}" class="text-primary text-button"><span refs="page-editor@draftDisplay" class="faded-text"></span> @icon('more')</button>
|
|
||||||
@icon('check-circle', ['class' => 'text-pos draft-notification svg-icon', 'refs' => 'page-editor@draftDisplayIcon'])
|
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
|
||||||
<li>
|
|
||||||
<button refs="page-editor@saveDraft" type="button" class="text-pos">@icon('save'){{ trans('entities.pages_edit_save_draft') }}</button>
|
|
||||||
</li>
|
|
||||||
@if ($model->draft)
|
|
||||||
<li>
|
|
||||||
<a href="{{ $model->getUrl('/delete') }}" class="text-neg">@icon('delete'){{ trans('entities.pages_edit_delete_draft') }}</a>
|
|
||||||
</li>
|
|
||||||
@endif
|
|
||||||
<li refs="page-editor@discardDraftWrap" class="{{ ($model->isDraft ?? false) ? '' : 'hidden' }}">
|
|
||||||
<button refs="page-editor@discardDraft" type="button" class="text-neg">@icon('cancel'){{ trans('entities.pages_edit_discard_draft') }}</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons px-m py-xs">
|
|
||||||
<div component="dropdown" dropdown-move-menu class="dropdown-container">
|
|
||||||
<button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span refs="page-editor@changelogDisplay">{{ trans('entities.pages_edit_set_changelog') }}</span></button>
|
|
||||||
<ul refs="dropdown@menu" class="wide dropdown-menu">
|
|
||||||
<li class="px-l py-m">
|
|
||||||
<p class="text-muted pb-s">{{ trans('entities.pages_edit_enter_changelog_desc') }}</p>
|
|
||||||
<input refs="page-editor@changelogInput"
|
|
||||||
name="summary"
|
|
||||||
id="summary-input"
|
|
||||||
type="text"
|
|
||||||
placeholder="{{ trans('entities.pages_edit_enter_changelog') }}" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<span>{{-- Prevents button jumping on menu show --}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" id="save-button" class="float-left text-primary text-button text-pos-hover hide-under-m">@icon('save')<span>{{ trans('entities.pages_save') }}</span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{--Title input--}}
|
{{--Title input--}}
|
||||||
<div class="title-input page-title clearfix">
|
<div class="title-input page-title clearfix">
|
||||||
|
@ -78,19 +29,35 @@
|
||||||
<div class="edit-area flex-fill flex">
|
<div class="edit-area flex-fill flex">
|
||||||
|
|
||||||
{{--WYSIWYG Editor--}}
|
{{--WYSIWYG Editor--}}
|
||||||
@if(setting('app-editor') === 'wysiwyg')
|
@if($editor === 'wysiwyg')
|
||||||
@include('pages.parts.wysiwyg-editor', ['model' => $model])
|
@include('pages.parts.wysiwyg-editor', ['model' => $model])
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{--Markdown Editor--}}
|
{{--Markdown Editor--}}
|
||||||
@if(setting('app-editor') === 'markdown')
|
@if($editor === 'markdown')
|
||||||
@include('pages.parts.markdown-editor', ['model' => $model])
|
@include('pages.parts.markdown-editor', ['model' => $model])
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{--Mobile Save Button--}}
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
id="save-button-mobile"
|
id="save-button-mobile"
|
||||||
title="{{ trans('entities.pages_save') }}"
|
title="{{ trans('entities.pages_save') }}"
|
||||||
class="text-primary text-button hide-over-m page-save-mobile-button">@icon('save')</button>
|
class="text-primary text-button hide-over-m page-save-mobile-button">@icon('save')</button>
|
||||||
|
|
||||||
|
{{--Editor Change Dialog--}}
|
||||||
|
@component('common.confirm-dialog', ['title' => trans('entities.pages_editor_switch_title'), 'ref' => 'page-editor@switchDialog'])
|
||||||
|
<p>
|
||||||
|
{{ trans('entities.pages_editor_switch_are_you_sure') }}
|
||||||
|
<br>
|
||||||
|
{{ trans('entities.pages_editor_switch_consider_following') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>{{ trans('entities.pages_editor_switch_consideration_a') }}</li>
|
||||||
|
<li>{{ trans('entities.pages_editor_switch_consideration_b') }}</li>
|
||||||
|
<li>{{ trans('entities.pages_editor_switch_consideration_c') }}</li>
|
||||||
|
</ul>
|
||||||
|
@endcomponent
|
||||||
</div>
|
</div>
|
|
@ -1,3 +1,7 @@
|
||||||
|
@push('head')
|
||||||
|
<script src="{{ url('/libs/tinymce/tinymce.min.js?ver=5.10.2') }}" nonce="{{ $cspNonce }}"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
<div component="wysiwyg-editor"
|
<div component="wysiwyg-editor"
|
||||||
option:wysiwyg-editor:language="{{ config('app.lang') }}"
|
option:wysiwyg-editor:language="{{ config('app.lang') }}"
|
||||||
option:wysiwyg-editor:page-id="{{ $model->id ?? 0 }}"
|
option:wysiwyg-editor:page-id="{{ $model->id ?? 0 }}"
|
||||||
|
|
|
@ -21,26 +21,39 @@
|
||||||
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th width="3%">{{ trans('entities.pages_revisions_number') }}</th>
|
<th width="40">{{ trans('entities.pages_revisions_number') }}</th>
|
||||||
<th width="23%">{{ trans('entities.pages_name') }}</th>
|
<th>
|
||||||
<th colspan="2" width="8%">{{ trans('entities.pages_revisions_created_by') }}</th>
|
{{ trans('entities.pages_name') }} / {{ trans('entities.pages_revisions_editor') }}
|
||||||
<th width="15%">{{ trans('entities.pages_revisions_date') }}</th>
|
</th>
|
||||||
<th width="25%">{{ trans('entities.pages_revisions_changelog') }}</th>
|
<th colspan="2">{{ trans('entities.pages_revisions_created_by') }} / {{ trans('entities.pages_revisions_date') }}</th>
|
||||||
<th width="20%">{{ trans('common.actions') }}</th>
|
<th>{{ trans('entities.pages_revisions_changelog') }}</th>
|
||||||
|
<th class="text-right">{{ trans('common.actions') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
@foreach($page->revisions as $index => $revision)
|
@foreach($page->revisions as $index => $revision)
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}</td>
|
<td>{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}</td>
|
||||||
<td>{{ $revision->name }}</td>
|
<td>
|
||||||
<td style="line-height: 0;">
|
{{ $revision->name }}
|
||||||
|
<br>
|
||||||
|
<small class="text-muted">({{ $revision->markdown ? 'Markdown' : 'WYSIWYG' }})</small>
|
||||||
|
</td>
|
||||||
|
<td style="line-height: 0;" width="30">
|
||||||
@if($revision->createdBy)
|
@if($revision->createdBy)
|
||||||
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
|
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif</td>
|
<td width="260">
|
||||||
<td><small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td>
|
@if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
|
||||||
<td>{{ $revision->summary }}</td>
|
<br>
|
||||||
<td class="actions">
|
<div class="text-muted">
|
||||||
|
<small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }}</small>
|
||||||
|
<small>({{ $revision->created_at->diffForHumans() }})</small>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $revision->summary }}
|
||||||
|
</td>
|
||||||
|
<td class="actions text-small text-right">
|
||||||
<a href="{{ $revision->getUrl('changes') }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_changes') }}</a>
|
<a href="{{ $revision->getUrl('changes') }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_changes') }}</a>
|
||||||
<span class="text-muted"> | </span>
|
<span class="text-muted"> | </span>
|
||||||
|
|
||||||
|
@ -58,7 +71,10 @@
|
||||||
<form action="{{ $revision->getUrl('/restore') }}" method="POST">
|
<form action="{{ $revision->getUrl('/restore') }}" method="POST">
|
||||||
{!! csrf_field() !!}
|
{!! csrf_field() !!}
|
||||||
<input type="hidden" name="_method" value="PUT">
|
<input type="hidden" name="_method" value="PUT">
|
||||||
<button type="submit" class="text-button text-primary">@icon('history'){{ trans('entities.pages_revisions_restore') }}</button>
|
<button type="submit" class="text-primary icon-item">
|
||||||
|
@icon('history')
|
||||||
|
<div>{{ trans('entities.pages_revisions_restore') }}</div>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -72,7 +88,10 @@
|
||||||
<form action="{{ $revision->getUrl('/delete/') }}" method="POST">
|
<form action="{{ $revision->getUrl('/delete/') }}" method="POST">
|
||||||
{!! csrf_field() !!}
|
{!! csrf_field() !!}
|
||||||
<input type="hidden" name="_method" value="DELETE">
|
<input type="hidden" name="_method" value="DELETE">
|
||||||
<button type="submit" class="text-button text-neg">@icon('delete'){{ trans('common.delete') }}</button>
|
<button type="submit" class="text-neg icon-item">
|
||||||
|
@icon('delete')
|
||||||
|
<div>{{ trans('common.delete') }}</div>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
<label for="">{{ trans('settings.audit_event_filter') }}</label>
|
<label for="">{{ trans('settings.audit_event_filter') }}</label>
|
||||||
<button refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
|
<button refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu">
|
<ul refs="dropdown@menu" class="dropdown-menu">
|
||||||
<li @if($listDetails['event'] === '') class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => '']) }}">{{ trans('settings.audit_event_filter_no_filter') }}</a></li>
|
<li @if($listDetails['event'] === '') class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => '']) }}" class="text-item">{{ trans('settings.audit_event_filter_no_filter') }}</a></li>
|
||||||
@foreach($activityTypes as $type)
|
@foreach($activityTypes as $type)
|
||||||
<li @if($type === $listDetails['event']) class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => $type]) }}">{{ $type }}</a></li>
|
<li @if($type === $listDetails['event']) class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => $type]) }}" class="text-item">{{ $type }}</a></li>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,12 +23,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid half gap-xl">
|
<div class="grid half gap-xl items-center">
|
||||||
<div>
|
<div>
|
||||||
<label class="setting-list-label">{{ trans('settings.app_editor') }}</label>
|
<label class="setting-list-label" for="setting-app-editor">{{ trans('settings.app_default_editor') }}</label>
|
||||||
<p class="small">{{ trans('settings.app_editor_desc') }}</p>
|
<p class="small">{{ trans('settings.app_default_editor_desc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-xs">
|
<div>
|
||||||
<select name="setting-app-editor" id="setting-app-editor">
|
<select name="setting-app-editor" id="setting-app-editor">
|
||||||
<option @if(setting('app-editor') === 'wysiwyg') selected @endif value="wysiwyg">WYSIWYG</option>
|
<option @if(setting('app-editor') === 'wysiwyg') selected @endif value="wysiwyg">WYSIWYG</option>
|
||||||
<option @if(setting('app-editor') === 'markdown') selected @endif value="markdown">Markdown</option>
|
<option @if(setting('app-editor') === 'markdown') selected @endif value="markdown">Markdown</option>
|
||||||
|
@ -90,12 +90,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div homepage-control id="homepage-control" class="grid half gap-xl">
|
<div homepage-control id="homepage-control" class="grid half gap-xl items-center">
|
||||||
<div>
|
<div>
|
||||||
<label for="setting-app-homepage" class="setting-list-label">{{ trans('settings.app_homepage') }}</label>
|
<label for="setting-app-homepage-type" class="setting-list-label">{{ trans('settings.app_homepage') }}</label>
|
||||||
<p class="small">{{ trans('settings.app_homepage_desc') }}</p>
|
<p class="small">{{ trans('settings.app_homepage_desc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-xs">
|
<div>
|
||||||
<select name="setting-app-homepage-type" id="setting-app-homepage-type">
|
<select name="setting-app-homepage-type" id="setting-app-homepage-type">
|
||||||
<option @if(setting('app-homepage-type') === 'default') selected @endif value="default">{{ trans('common.default') }}</option>
|
<option @if(setting('app-homepage-type') === 'default') selected @endif value="default">{{ trans('common.default') }}</option>
|
||||||
<option @if(setting('app-homepage-type') === 'books') selected @endif value="books">{{ trans('entities.books') }}</option>
|
<option @if(setting('app-homepage-type') === 'books') selected @endif value="books">{{ trans('entities.books') }}</option>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
<form action="{{ url('/settings/recycle-bin/empty') }}" method="POST">
|
<form action="{{ url('/settings/recycle-bin/empty') }}" method="POST">
|
||||||
{!! csrf_field() !!}
|
{!! csrf_field() !!}
|
||||||
<button type="submit" class="text-primary small delete">{{ trans('common.confirm') }}</button>
|
<button type="submit" class="text-primary small delete text-item">{{ trans('common.confirm') }}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,8 +93,8 @@
|
||||||
<div component="dropdown" class="dropdown-container">
|
<div component="dropdown" class="dropdown-container">
|
||||||
<button type="button" refs="dropdown@toggle" class="button outline">{{ trans('common.actions') }}</button>
|
<button type="button" refs="dropdown@toggle" class="button outline">{{ trans('common.actions') }}</button>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu">
|
<ul refs="dropdown@menu" class="dropdown-menu">
|
||||||
<li><a class="block" href="{{ $deletion->getUrl('/restore') }}">{{ trans('settings.recycle_bin_restore') }}</a></li>
|
<li><a class="text-item" href="{{ $deletion->getUrl('/restore') }}">{{ trans('settings.recycle_bin_restore') }}</a></li>
|
||||||
<li><a class="block" href="{{ $deletion->getUrl('/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
|
<li><a class="text-item" href="{{ $deletion->getUrl('/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<div>@include('settings.roles.parts.checkbox', ['permission' => 'templates-manage', 'label' => trans('settings.role_manage_page_templates')])</div>
|
<div>@include('settings.roles.parts.checkbox', ['permission' => 'templates-manage', 'label' => trans('settings.role_manage_page_templates')])</div>
|
||||||
<div>@include('settings.roles.parts.checkbox', ['permission' => 'access-api', 'label' => trans('settings.role_access_api')])</div>
|
<div>@include('settings.roles.parts.checkbox', ['permission' => 'access-api', 'label' => trans('settings.role_access_api')])</div>
|
||||||
<div>@include('settings.roles.parts.checkbox', ['permission' => 'content-export', 'label' => trans('settings.role_export_content')])</div>
|
<div>@include('settings.roles.parts.checkbox', ['permission' => 'content-export', 'label' => trans('settings.role_export_content')])</div>
|
||||||
|
<div>@include('settings.roles.parts.checkbox', ['permission' => 'editor-change', 'label' => trans('settings.role_editor_change')])</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>@include('settings.roles.parts.checkbox', ['permission' => 'settings-manage', 'label' => trans('settings.role_manage_settings')])</div>
|
<div>@include('settings.roles.parts.checkbox', ['permission' => 'settings-manage', 'label' => trans('settings.role_manage_settings')])</div>
|
||||||
|
|
|
@ -252,7 +252,9 @@ class PagesApiTest extends TestCase
|
||||||
'tags' => [['name' => 'Category', 'value' => 'Testing']]
|
'tags' => [['name' => 'Category', 'value' => 'Testing']]
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->putJson($this->baseEndpoint . "/{$page->id}", $details);
|
$resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
|
||||||
|
$resp->assertOk();
|
||||||
|
|
||||||
$page->refresh();
|
$page->refresh();
|
||||||
$this->assertGreaterThan(Carbon::now()->subDay()->unix(), $page->updated_at->unix());
|
$this->assertGreaterThan(Carbon::now()->subDay()->unix(), $page->updated_at->unix());
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,36 +18,39 @@ class PageEditorTest extends TestCase
|
||||||
$this->page = Page::query()->first();
|
$this->page = Page::query()->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_default_editor_is_wysiwyg()
|
public function test_default_editor_is_wysiwyg_for_new_pages()
|
||||||
{
|
{
|
||||||
$this->assertEquals('wysiwyg', setting('app-editor'));
|
$this->assertEquals('wysiwyg', setting('app-editor'));
|
||||||
$this->asAdmin()->get($this->page->getUrl() . '/edit')
|
$resp = $this->asAdmin()->get($this->page->book->getUrl('/create-page'));
|
||||||
->assertElementExists('#html-editor');
|
$this->followRedirects($resp)->assertElementExists('#html-editor');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_markdown_setting_shows_markdown_editor()
|
public function test_markdown_setting_shows_markdown_editor_for_new_pages()
|
||||||
{
|
{
|
||||||
$this->setSettings(['app-editor' => 'markdown']);
|
$this->setSettings(['app-editor' => 'markdown']);
|
||||||
$this->asAdmin()->get($this->page->getUrl() . '/edit')
|
|
||||||
|
$resp = $this->asAdmin()->get($this->page->book->getUrl('/create-page'));
|
||||||
|
$this->followRedirects($resp)
|
||||||
->assertElementNotExists('#html-editor')
|
->assertElementNotExists('#html-editor')
|
||||||
->assertElementExists('#markdown-editor');
|
->assertElementExists('#markdown-editor');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_markdown_content_given_to_editor()
|
public function test_markdown_content_given_to_editor()
|
||||||
{
|
{
|
||||||
$this->setSettings(['app-editor' => 'markdown']);
|
|
||||||
|
|
||||||
$mdContent = '# hello. This is a test';
|
$mdContent = '# hello. This is a test';
|
||||||
$this->page->markdown = $mdContent;
|
$this->page->markdown = $mdContent;
|
||||||
|
$this->page->editor = 'markdown';
|
||||||
$this->page->save();
|
$this->page->save();
|
||||||
|
|
||||||
$this->asAdmin()->get($this->page->getUrl() . '/edit')
|
$this->asAdmin()->get($this->page->getUrl('/edit'))
|
||||||
->assertElementContains('[name="markdown"]', $mdContent);
|
->assertElementContains('[name="markdown"]', $mdContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_html_content_given_to_editor_if_no_markdown()
|
public function test_html_content_given_to_editor_if_no_markdown()
|
||||||
{
|
{
|
||||||
$this->setSettings(['app-editor' => 'markdown']);
|
$this->page->editor = 'markdown';
|
||||||
|
$this->page->save();
|
||||||
|
|
||||||
$this->asAdmin()->get($this->page->getUrl() . '/edit')
|
$this->asAdmin()->get($this->page->getUrl() . '/edit')
|
||||||
->assertElementContains('[name="markdown"]', $this->page->html);
|
->assertElementContains('[name="markdown"]', $this->page->html);
|
||||||
}
|
}
|
||||||
|
@ -102,4 +105,102 @@ class PageEditorTest extends TestCase
|
||||||
$resp = $this->get($draft->getUrl('/edit'));
|
$resp = $this->get($draft->getUrl('/edit'));
|
||||||
$resp->assertElementContains('a[href="' . $draft->getUrl() . '"]', 'Back');
|
$resp->assertElementContains('a[href="' . $draft->getUrl() . '"]', 'Back');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_switching_from_html_to_clean_markdown_works()
|
||||||
|
{
|
||||||
|
/** @var Page $page */
|
||||||
|
$page = Page::query()->first();
|
||||||
|
$page->html = '<h2>A Header</h2><p>Some <strong>bold</strong> content.</p>';
|
||||||
|
$page->save();
|
||||||
|
|
||||||
|
$resp = $this->asAdmin()->get($page->getUrl('/edit?editor=markdown-clean'));
|
||||||
|
$resp->assertStatus(200);
|
||||||
|
$resp->assertSee("## A Header\n\nSome **bold** content.");
|
||||||
|
$resp->assertElementExists('#markdown-editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_switching_from_html_to_stable_markdown_works()
|
||||||
|
{
|
||||||
|
/** @var Page $page */
|
||||||
|
$page = Page::query()->first();
|
||||||
|
$page->html = '<h2>A Header</h2><p>Some <strong>bold</strong> content.</p>';
|
||||||
|
$page->save();
|
||||||
|
|
||||||
|
$resp = $this->asAdmin()->get($page->getUrl('/edit?editor=markdown-stable'));
|
||||||
|
$resp->assertStatus(200);
|
||||||
|
$resp->assertSee("<h2>A Header</h2><p>Some <strong>bold</strong> content.</p>", true);
|
||||||
|
$resp->assertElementExists('[component="markdown-editor"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_switching_from_markdown_to_wysiwyg_works()
|
||||||
|
{
|
||||||
|
/** @var Page $page */
|
||||||
|
$page = Page::query()->first();
|
||||||
|
$page->html = '';
|
||||||
|
$page->markdown = "## A Header\n\nSome content with **bold** text!";
|
||||||
|
$page->save();
|
||||||
|
|
||||||
|
$resp = $this->asAdmin()->get($page->getUrl('/edit?editor=wysiwyg'));
|
||||||
|
$resp->assertStatus(200);
|
||||||
|
$resp->assertElementExists('[component="wysiwyg-editor"]');
|
||||||
|
$resp->assertSee("<h2>A Header</h2>\n<p>Some content with <strong>bold</strong> text!</p>", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_editor_changes_with_editor_property()
|
||||||
|
{
|
||||||
|
$resp = $this->asAdmin()->get($this->page->getUrl('/edit'));
|
||||||
|
$resp->assertElementExists('[component="wysiwyg-editor"]');
|
||||||
|
|
||||||
|
$this->page->markdown = "## A Header\n\nSome content with **bold** text!";
|
||||||
|
$this->page->editor = 'markdown';
|
||||||
|
$this->page->save();
|
||||||
|
|
||||||
|
$resp = $this->asAdmin()->get($this->page->getUrl('/edit'));
|
||||||
|
$resp->assertElementExists('[component="markdown-editor"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_editor_type_switch_options_show()
|
||||||
|
{
|
||||||
|
$resp = $this->asAdmin()->get($this->page->getUrl('/edit'));
|
||||||
|
$editLink = $this->page->getUrl('/edit') . '?editor=';
|
||||||
|
$resp->assertElementContains("a[href=\"${editLink}markdown-clean\"]", '(Clean Content)');
|
||||||
|
$resp->assertElementContains("a[href=\"${editLink}markdown-stable\"]", '(Stable Content)');
|
||||||
|
|
||||||
|
$resp = $this->asAdmin()->get($this->page->getUrl('/edit?editor=markdown-stable'));
|
||||||
|
$editLink = $this->page->getUrl('/edit') . '?editor=';
|
||||||
|
$resp->assertElementContains("a[href=\"${editLink}wysiwyg\"]", 'Switch to WYSIWYG Editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_editor_type_switch_options_dont_show_if_without_change_editor_permissions()
|
||||||
|
{
|
||||||
|
$resp = $this->asEditor()->get($this->page->getUrl('/edit'));
|
||||||
|
$editLink = $this->page->getUrl('/edit') . '?editor=';
|
||||||
|
$resp->assertElementNotExists("a[href*=\"${editLink}\"]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_editor_type_switch_does_not_work_without_change_editor_permissions()
|
||||||
|
{
|
||||||
|
/** @var Page $page */
|
||||||
|
$page = Page::query()->first();
|
||||||
|
$page->html = '<h2>A Header</h2><p>Some <strong>bold</strong> content.</p>';
|
||||||
|
$page->save();
|
||||||
|
|
||||||
|
$resp = $this->asEditor()->get($page->getUrl('/edit?editor=markdown-stable'));
|
||||||
|
$resp->assertStatus(200);
|
||||||
|
$resp->assertElementExists('[component="wysiwyg-editor"]');
|
||||||
|
$resp->assertElementNotExists('[component="markdown-editor"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_page_save_does_not_change_active_editor_without_change_editor_permissions()
|
||||||
|
{
|
||||||
|
/** @var Page $page */
|
||||||
|
$page = Page::query()->first();
|
||||||
|
$page->html = '<h2>A Header</h2><p>Some <strong>bold</strong> content.</p>';
|
||||||
|
$page->editor = 'wysiwyg';
|
||||||
|
$page->save();
|
||||||
|
|
||||||
|
$this->asEditor()->put($page->getUrl(), ['name' => $page->name, 'markdown' => '## Updated content abc']);
|
||||||
|
$this->assertEquals('wysiwyg', $page->refresh()->editor);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,4 +203,19 @@ class PageRevisionTest extends TestCase
|
||||||
$revisionCount = $page->revisions()->count();
|
$revisionCount = $page->revisions()->count();
|
||||||
$this->assertEquals(12, $revisionCount);
|
$this->assertEquals(12, $revisionCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_revision_list_shows_editor_type()
|
||||||
|
{
|
||||||
|
/** @var Page $page */
|
||||||
|
$page = Page::first();
|
||||||
|
$this->asAdmin()->put($page->getUrl(), ['name' => 'Updated page', 'html' => 'new page html']);
|
||||||
|
|
||||||
|
$resp = $this->get($page->refresh()->getUrl('/revisions'));
|
||||||
|
$resp->assertElementContains('td', '(WYSIWYG)');
|
||||||
|
$resp->assertElementNotContains('td', '(Markdown)');
|
||||||
|
|
||||||
|
$this->asAdmin()->put($page->getUrl(), ['name' => 'Updated page', 'markdown' => '# Some markdown content']);
|
||||||
|
$resp = $this->get($page->refresh()->getUrl('/revisions'));
|
||||||
|
$resp->assertElementContains('td', '(Markdown)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue