* @property bool $template
* @property bool $draft
* @property int $revision_count
+ * @property string $editor
* @property Chapter $chapter
* @property Collection $attachments
* @property Collection $revisions
*
* @property mixed $id
* @property int $page_id
+ * @property string $name
* @property string $slug
* @property string $book_slug
* @property int $created_by
* @property string $summary
* @property string $markdown
* @property string $html
+ * @property string $text
* @property int $revision_number
* @property Page $page
* @property-read ?User $createdBy
*/
class PageRevision extends Model
{
- protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
+ protected $fillable = ['name', 'text', 'summary'];
protected $hidden = ['html', 'markdown', 'restricted', 'text'];
/**
class BaseRepo
{
- protected $tagRepo;
- protected $imageRepo;
+ protected TagRepo $tagRepo;
+ protected ImageRepo $imageRepo;
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
{
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($entity, $input['tags']);
+ $entity->touch();
}
$entity->rebuildPermissions();
use BookStack\Entities\Models\PageRevision;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\PageContent;
+use BookStack\Entities\Tools\PageEditorData;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException;
}
$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']);
} elseif (isset($input['html'])) {
+ $newEditor = 'wysiwyg';
$pageContent->setNewHTML($input['html']);
}
+
+ if ($newEditor !== $currentEditor && userCan('editor-change')) {
+ $page->editor = $newEditor;
+ }
}
/**
*/
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->slug = $page->slug;
$revision->book_slug = $page->book->slug;
return $page;
}
- // Otherwise save the data to a revision
+ // Otherwise, save the data to a revision
$draft = $this->getPageRevisionToUpdate($page);
$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 = '';
}
class HtmlToMarkdown
{
- protected $html;
+ protected string $html;
public function __construct(string $html)
{
--- /dev/null
+<?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);
+ }
+
+}
\ No newline at end of file
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Page;
-use BookStack\Entities\Tools\Markdown\CustomListItemRenderer;
-use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
+use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
use BookStack\Exceptions\ImageUploadException;
-use BookStack\Facades\Theme;
-use BookStack\Theming\ThemeEvents;
use BookStack\Uploads\ImageRepo;
use BookStack\Uploads\ImageService;
use BookStack\Util\HtmlContentFilter;
use DOMNodeList;
use DOMXPath;
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
{
- protected $page;
+ protected Page $page;
/**
* PageContent constructor.
{
$markdown = $this->extractBase64ImagesFromMarkdown($markdown);
$this->page->markdown = $markdown;
- $html = $this->markdownToHtml($markdown);
+ $html = (new MarkdownToHtml($markdown))->convert();
$this->page->html = $this->formatHtml($html);
$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.
*/
class PageEditActivity
{
- protected $page;
+ protected Page $page;
/**
* PageEditActivity constructor.
--- /dev/null
+<?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';
+ }
+
+}
\ No newline at end of file
'markdown' => $attachment->markdownLink(),
]);
- if (!$attachment->external) {
- $attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
- $attachment->setAttribute('content', base64_encode($attachmentContents));
- } else {
+ // Simply return a JSON response of the attachment for link-based attachments
+ if ($attachment->external) {
$attachment->setAttribute('content', $attachment->path);
+ return response()->json($attachment);
}
- return response()->json($attachment);
+ // Build and split our core JSON, at point of content.
+ $splitter = 'CONTENT_SPLIT_LOCATION_' . time() . '_' . rand(1, 40000);
+ $attachment->setAttribute('content', $splitter);
+ $json = $attachment->toJson();
+ $jsonParts = explode($splitter, $json);
+ // Get a stream for the file data from storage
+ $stream = $this->attachmentService->streamAttachmentFromStorage($attachment);
+
+ return response()->stream(function () use ($jsonParts, $stream) {
+ // Output the pre-content JSON data
+ echo $jsonParts[0];
+
+ // Stream out our attachment data as base64 content
+ stream_filter_append($stream, 'convert.base64-encode', STREAM_FILTER_READ);
+ fpassthru($stream);
+ fclose($stream);
+
+ // Output our post-content JSON data
+ echo $jsonParts[1];
+ }, 200, ['Content-Type' => 'application/json']);
}
/**
class BookshelfApiController extends ApiController
{
- /**
- * @var BookshelfRepo
- */
- protected $bookshelfRepo;
+ protected BookshelfRepo $bookshelfRepo;
protected $rules = [
'create' => [
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'books' => ['array'],
+ 'tags' => ['array'],
],
'update' => [
'name' => ['string', 'min:1', 'max:255'],
'description' => ['string', 'max:1000'],
'books' => ['array'],
+ 'tags' => ['array'],
],
];
class PageApiController extends ApiController
{
- protected $pageRepo;
+ protected PageRepo $pageRepo;
protected $rules = [
'create' => [
'tags' => ['array'],
],
'update' => [
- 'book_id' => ['required', 'integer'],
- 'chapter_id' => ['required', 'integer'],
+ 'book_id' => ['integer'],
+ 'chapter_id' => ['integer'],
'name' => ['string', 'min:1', 'max:255'],
'html' => ['string'],
'markdown' => ['string'],
*/
public function update(Request $request, string $id)
{
+ $requestData = $this->validate($request, $this->rules['update']);
+
$page = $this->pageRepo->getById($id, []);
$this->checkOwnablePermission('page-update', $page);
}
}
- $updatedPage = $this->pageRepo->update($page, $request->all());
+ $updatedPage = $this->pageRepo->update($page, $requestData);
return response()->json($updatedPage->forJsonDisplay());
}
class AttachmentController extends Controller
{
- protected $attachmentService;
- protected $pageRepo;
+ protected AttachmentService $attachmentService;
+ protected PageRepo $pageRepo;
/**
* AttachmentController constructor.
}
$fileName = $attachment->getFileName();
- $attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
+ $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
if ($request->get('open') === 'true') {
- return $this->inlineDownloadResponse($attachmentContents, $fileName);
+ return $this->streamedInlineDownloadResponse($attachmentStream, $fileName);
}
- return $this->downloadResponse($attachmentContents, $fileName);
+ return $this->streamedDownloadResponse($attachmentStream, $fileName);
}
/**
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller as BaseController;
+use Symfony\Component\HttpFoundation\StreamedResponse;
abstract class Controller extends BaseController
{
{
return response()->make($content, 200, [
'Content-Type' => 'application/octet-stream',
- 'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
+ 'Content-Disposition' => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
+ 'X-Content-Type-Options' => 'nosniff',
+ ]);
+ }
+
+ /**
+ * Create a response that forces a download, from a given stream of content.
+ */
+ protected function streamedDownloadResponse($stream, string $fileName): StreamedResponse
+ {
+ return response()->stream(function() use ($stream) {
+ // End & flush the output buffer otherwise we still seem to use memory.
+ // Ignore in testing since output buffers are used to gather a response.
+ if (!app()->runningUnitTests()) {
+ ob_end_clean();
+ }
+
+ fpassthru($stream);
+ fclose($stream);
+ }, 200, [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
'X-Content-Type-Options' => 'nosniff',
]);
}
return response()->make($content, 200, [
'Content-Type' => $mime,
- 'Content-Disposition' => 'inline; filename="' . $fileName . '"',
+ 'Content-Disposition' => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
+ 'X-Content-Type-Options' => 'nosniff',
+ ]);
+ }
+
+ /**
+ * Create a file download response that provides the file with a content-type
+ * correct for the file, in a way so the browser can show the content in browser,
+ * for a given content stream.
+ */
+ protected function streamedInlineDownloadResponse($stream, string $fileName): StreamedResponse
+ {
+ $sniffContent = fread($stream, 1000);
+ $mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
+
+ return response()->stream(function() use ($sniffContent, $stream) {
+ echo $sniffContent;
+ fpassthru($stream);
+ fclose($stream);
+ }, 200, [
+ 'Content-Type' => $mime,
+ 'Content-Disposition' => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
'X-Content-Type-Options' => 'nosniff',
]);
}
use BookStack\Entities\Tools\NextPreviousContentLocator;
use BookStack\Entities\Tools\PageContent;
use BookStack\Entities\Tools\PageEditActivity;
+use BookStack\Entities\Tools\PageEditorData;
use BookStack\Entities\Tools\PermissionsUpdater;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\PermissionsException;
class PageController extends Controller
{
- protected $pageRepo;
+ protected PageRepo $pageRepo;
/**
* PageController constructor.
*
* @throws NotFoundException
*/
- public function editDraft(string $bookSlug, int $pageId)
+ public function editDraft(Request $request, string $bookSlug, int $pageId)
{
$draft = $this->pageRepo->getById($pageId);
$this->checkOwnablePermission('page-create', $draft->getParent());
- $this->setPageTitle(trans('entities.pages_edit_draft'));
- $draftsEnabled = $this->isSignedIn();
- $templates = $this->pageRepo->getTemplates(10);
+ $editorData = new PageEditorData($draft, $this->pageRepo, $request->query('editor', ''));
+ $this->setPageTitle(trans('entities.pages_edit_draft'));
- return view('pages.edit', [
- 'page' => $draft,
- 'book' => $draft->book,
- 'isDraft' => true,
- 'draftsEnabled' => $draftsEnabled,
- 'templates' => $templates,
- ]);
+ return view('pages.edit', $editorData->getViewData());
}
/**
*
* @throws NotFoundException
*/
- public function edit(string $bookSlug, string $pageSlug)
+ public function edit(Request $request, string $bookSlug, string $pageSlug)
{
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
$this->checkOwnablePermission('page-update', $page);
- $page->isDraft = false;
- $editActivity = new PageEditActivity($page);
-
- // Check for active editing
- $warnings = [];
- if ($editActivity->hasActiveEditing()) {
- $warnings[] = $editActivity->activeEditingMessage();
+ $editorData = new PageEditorData($page, $this->pageRepo, $request->query('editor', ''));
+ if ($editorData->getWarnings()) {
+ $this->showWarningNotification(implode("\n", $editorData->getWarnings()));
}
- // 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()]));
- return view('pages.edit', [
- 'page' => $page,
- 'book' => $page->book,
- 'current' => $page,
- 'draftsEnabled' => $draftsEnabled,
- 'templates' => $templates,
- ]);
+ return view('pages.edit', $editorData->getViewData());
}
/**
class AttachmentService
{
- protected $fileSystem;
+ protected FilesystemManager $fileSystem;
/**
* AttachmentService constructor.
return $this->getStorageDisk()->get($this->adjustPathForStorageDisk($attachment->path));
}
+ /**
+ * Stream an attachment from storage.
+ *
+ * @return resource|null
+ * @throws FileNotFoundException
+ */
+ public function streamAttachmentFromStorage(Attachment $attachment)
+ {
+
+ return $this->getStorageDisk()->readStream($this->adjustPathForStorageDisk($attachment->path));
+ }
+
/**
* Store a new attachment upon user upload.
*
*/
protected function putFileInStorage(UploadedFile $uploadedFile): string
{
- $attachmentData = file_get_contents($uploadedFile->getRealPath());
-
$storage = $this->getStorageDisk();
$basePath = 'uploads/files/' . date('Y-m-M') . '/';
$uploadFileName = Str::random(3) . $uploadFileName;
}
+ $attachmentStream = fopen($uploadedFile->getRealPath(), 'r');
$attachmentPath = $basePath . $uploadFileName;
try {
- $storage->put($this->adjustPathForStorageDisk($attachmentPath), $attachmentData);
+ $storage->writeStream($this->adjustPathForStorageDisk($attachmentPath), $attachmentStream);
} catch (Exception $e) {
Log::error('Error when attempting file upload:' . $e->getMessage());
--- /dev/null
+<?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();
+ }
+}
--- /dev/null
+#!/usr/bin/env node
+
+const esbuild = require('esbuild');
+const fs = require('fs');
+const path = require('path');
+
+// Check if we're building for production
+// (Set via passing `production` as first argument)
+const isProd = process.argv[2] === 'production';
+
+// Gather our input files
+const jsInDir = path.join(__dirname, '../../resources/js');
+const jsInDirFiles = fs.readdirSync(jsInDir, 'utf8');
+const entryFiles = jsInDirFiles
+ .filter(f => f.endsWith('.js') || f.endsWith('.mjs'))
+ .map(f => path.join(jsInDir, f));
+
+// Locate our output directory
+const outDir = path.join(__dirname, '../../public/dist');
+
+// Build via esbuild
+esbuild.build({
+ bundle: true,
+ entryPoints: entryFiles,
+ outdir: outDir,
+ sourcemap: true,
+ target: 'es2020',
+ mainFields: ['module', 'main'],
+ format: 'esm',
+ minify: isProd,
+ logLevel: "info",
+}).catch(() => process.exit(1));
\ No newline at end of file
},
"devDependencies": {
"chokidar-cli": "^3.0",
- "esbuild": "0.14.27",
+ "esbuild": "0.14.36",
"livereload": "^0.9.3",
"npm-run-all": "^4.1.5",
"punycode": "^2.1.1",
- "sass": "^1.49.9"
+ "sass": "^1.50.0"
}
},
"node_modules/ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/esbuild": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz",
- "integrity": "sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
+ "integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
"dev": true,
"hasInstallScript": true,
"bin": {
"node": ">=12"
},
"optionalDependencies": {
- "esbuild-android-64": "0.14.27",
- "esbuild-android-arm64": "0.14.27",
- "esbuild-darwin-64": "0.14.27",
- "esbuild-darwin-arm64": "0.14.27",
- "esbuild-freebsd-64": "0.14.27",
- "esbuild-freebsd-arm64": "0.14.27",
- "esbuild-linux-32": "0.14.27",
- "esbuild-linux-64": "0.14.27",
- "esbuild-linux-arm": "0.14.27",
- "esbuild-linux-arm64": "0.14.27",
- "esbuild-linux-mips64le": "0.14.27",
- "esbuild-linux-ppc64le": "0.14.27",
- "esbuild-linux-riscv64": "0.14.27",
- "esbuild-linux-s390x": "0.14.27",
- "esbuild-netbsd-64": "0.14.27",
- "esbuild-openbsd-64": "0.14.27",
- "esbuild-sunos-64": "0.14.27",
- "esbuild-windows-32": "0.14.27",
- "esbuild-windows-64": "0.14.27",
- "esbuild-windows-arm64": "0.14.27"
+ "esbuild-android-64": "0.14.36",
+ "esbuild-android-arm64": "0.14.36",
+ "esbuild-darwin-64": "0.14.36",
+ "esbuild-darwin-arm64": "0.14.36",
+ "esbuild-freebsd-64": "0.14.36",
+ "esbuild-freebsd-arm64": "0.14.36",
+ "esbuild-linux-32": "0.14.36",
+ "esbuild-linux-64": "0.14.36",
+ "esbuild-linux-arm": "0.14.36",
+ "esbuild-linux-arm64": "0.14.36",
+ "esbuild-linux-mips64le": "0.14.36",
+ "esbuild-linux-ppc64le": "0.14.36",
+ "esbuild-linux-riscv64": "0.14.36",
+ "esbuild-linux-s390x": "0.14.36",
+ "esbuild-netbsd-64": "0.14.36",
+ "esbuild-openbsd-64": "0.14.36",
+ "esbuild-sunos-64": "0.14.36",
+ "esbuild-windows-32": "0.14.36",
+ "esbuild-windows-64": "0.14.36",
+ "esbuild-windows-arm64": "0.14.36"
}
},
"node_modules/esbuild-android-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz",
- "integrity": "sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
+ "integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-android-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz",
- "integrity": "sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
+ "integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
"cpu": [
"arm64"
],
}
},
"node_modules/esbuild-darwin-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz",
- "integrity": "sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
+ "integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-darwin-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz",
- "integrity": "sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
+ "integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
"cpu": [
"arm64"
],
}
},
"node_modules/esbuild-freebsd-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz",
- "integrity": "sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
+ "integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-freebsd-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz",
- "integrity": "sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
+ "integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
"cpu": [
"arm64"
],
}
},
"node_modules/esbuild-linux-32": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz",
- "integrity": "sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
+ "integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
"cpu": [
"ia32"
],
}
},
"node_modules/esbuild-linux-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz",
- "integrity": "sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
+ "integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-linux-arm": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz",
- "integrity": "sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
+ "integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
"cpu": [
"arm"
],
}
},
"node_modules/esbuild-linux-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz",
- "integrity": "sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
+ "integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
"cpu": [
"arm64"
],
}
},
"node_modules/esbuild-linux-mips64le": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz",
- "integrity": "sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
+ "integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
"cpu": [
"mips64el"
],
}
},
"node_modules/esbuild-linux-ppc64le": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz",
- "integrity": "sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
+ "integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
"cpu": [
"ppc64"
],
}
},
"node_modules/esbuild-linux-riscv64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz",
- "integrity": "sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
+ "integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
"cpu": [
"riscv64"
],
}
},
"node_modules/esbuild-linux-s390x": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz",
- "integrity": "sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
+ "integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
"cpu": [
"s390x"
],
}
},
"node_modules/esbuild-netbsd-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz",
- "integrity": "sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
+ "integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-openbsd-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz",
- "integrity": "sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
+ "integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-sunos-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz",
- "integrity": "sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
+ "integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-windows-32": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz",
- "integrity": "sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
+ "integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
"cpu": [
"ia32"
],
}
},
"node_modules/esbuild-windows-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz",
- "integrity": "sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
+ "integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
"cpu": [
"x64"
],
}
},
"node_modules/esbuild-windows-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz",
- "integrity": "sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
+ "integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
"cpu": [
"arm64"
],
}
},
"node_modules/sass": {
- "version": "1.49.9",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz",
- "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==",
+ "version": "1.50.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
+ "integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"dev": true
},
"ansi-styles": {
}
},
"esbuild": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz",
- "integrity": "sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==",
- "dev": true,
- "requires": {
- "esbuild-android-64": "0.14.27",
- "esbuild-android-arm64": "0.14.27",
- "esbuild-darwin-64": "0.14.27",
- "esbuild-darwin-arm64": "0.14.27",
- "esbuild-freebsd-64": "0.14.27",
- "esbuild-freebsd-arm64": "0.14.27",
- "esbuild-linux-32": "0.14.27",
- "esbuild-linux-64": "0.14.27",
- "esbuild-linux-arm": "0.14.27",
- "esbuild-linux-arm64": "0.14.27",
- "esbuild-linux-mips64le": "0.14.27",
- "esbuild-linux-ppc64le": "0.14.27",
- "esbuild-linux-riscv64": "0.14.27",
- "esbuild-linux-s390x": "0.14.27",
- "esbuild-netbsd-64": "0.14.27",
- "esbuild-openbsd-64": "0.14.27",
- "esbuild-sunos-64": "0.14.27",
- "esbuild-windows-32": "0.14.27",
- "esbuild-windows-64": "0.14.27",
- "esbuild-windows-arm64": "0.14.27"
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
+ "integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
+ "dev": true,
+ "requires": {
+ "esbuild-android-64": "0.14.36",
+ "esbuild-android-arm64": "0.14.36",
+ "esbuild-darwin-64": "0.14.36",
+ "esbuild-darwin-arm64": "0.14.36",
+ "esbuild-freebsd-64": "0.14.36",
+ "esbuild-freebsd-arm64": "0.14.36",
+ "esbuild-linux-32": "0.14.36",
+ "esbuild-linux-64": "0.14.36",
+ "esbuild-linux-arm": "0.14.36",
+ "esbuild-linux-arm64": "0.14.36",
+ "esbuild-linux-mips64le": "0.14.36",
+ "esbuild-linux-ppc64le": "0.14.36",
+ "esbuild-linux-riscv64": "0.14.36",
+ "esbuild-linux-s390x": "0.14.36",
+ "esbuild-netbsd-64": "0.14.36",
+ "esbuild-openbsd-64": "0.14.36",
+ "esbuild-sunos-64": "0.14.36",
+ "esbuild-windows-32": "0.14.36",
+ "esbuild-windows-64": "0.14.36",
+ "esbuild-windows-arm64": "0.14.36"
}
},
"esbuild-android-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz",
- "integrity": "sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
+ "integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
"dev": true,
"optional": true
},
"esbuild-android-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz",
- "integrity": "sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
+ "integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
"dev": true,
"optional": true
},
"esbuild-darwin-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz",
- "integrity": "sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
+ "integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
"dev": true,
"optional": true
},
"esbuild-darwin-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz",
- "integrity": "sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
+ "integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
"dev": true,
"optional": true
},
"esbuild-freebsd-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz",
- "integrity": "sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
+ "integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
"dev": true,
"optional": true
},
"esbuild-freebsd-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz",
- "integrity": "sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
+ "integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
"dev": true,
"optional": true
},
"esbuild-linux-32": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz",
- "integrity": "sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
+ "integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
"dev": true,
"optional": true
},
"esbuild-linux-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz",
- "integrity": "sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
+ "integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
"dev": true,
"optional": true
},
"esbuild-linux-arm": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz",
- "integrity": "sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
+ "integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
"dev": true,
"optional": true
},
"esbuild-linux-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz",
- "integrity": "sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
+ "integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
"dev": true,
"optional": true
},
"esbuild-linux-mips64le": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz",
- "integrity": "sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
+ "integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
"dev": true,
"optional": true
},
"esbuild-linux-ppc64le": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz",
- "integrity": "sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
+ "integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
"dev": true,
"optional": true
},
"esbuild-linux-riscv64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz",
- "integrity": "sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
+ "integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
"dev": true,
"optional": true
},
"esbuild-linux-s390x": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz",
- "integrity": "sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
+ "integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
"dev": true,
"optional": true
},
"esbuild-netbsd-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz",
- "integrity": "sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
+ "integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
"dev": true,
"optional": true
},
"esbuild-openbsd-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz",
- "integrity": "sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
+ "integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
"dev": true,
"optional": true
},
"esbuild-sunos-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz",
- "integrity": "sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
+ "integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
"dev": true,
"optional": true
},
"esbuild-windows-32": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz",
- "integrity": "sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
+ "integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
"dev": true,
"optional": true
},
"esbuild-windows-64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz",
- "integrity": "sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
+ "integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
"dev": true,
"optional": true
},
"esbuild-windows-arm64": {
- "version": "0.14.27",
- "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz",
- "integrity": "sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==",
+ "version": "0.14.36",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
+ "integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
"dev": true,
"optional": true
},
}
},
"sass": {
- "version": "1.49.9",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz",
- "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==",
+ "version": "1.50.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
+ "integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"build:css:dev": "sass ./resources/sass:./public/dist",
"build:css:watch": "sass ./resources/sass:./public/dist --watch",
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
- "build:js:dev": "esbuild --bundle ./resources/js/*.{js,mjs} --outdir=public/dist/ --sourcemap --target=es2020 --main-fields=module,main --format=esm",
+ "build:js:dev": "node dev/build/esbuild.js",
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
- "build:js:production": "NODE_ENV=production esbuild --bundle ./resources/js/*.{js,mjs} --outdir=public/dist/ --sourcemap --target=es2020 --main-fields=module,main --minify --format=esm",
+ "build:js:production": "node dev/build/esbuild.js production",
"build": "npm-run-all --parallel build:*:dev",
"production": "npm-run-all --parallel build:*:production",
"dev": "npm-run-all --parallel watch livereload",
},
"devDependencies": {
"chokidar-cli": "^3.0",
- "esbuild": "0.14.27",
+ "esbuild": "0.14.36",
"livereload": "^0.9.3",
"npm-run-all": "^4.1.5",
"punycode": "^2.1.1",
- "sass": "^1.49.9"
+ "sass": "^1.50.0"
},
"dependencies": {
"clipboard": "^2.0.10",
--- /dev/null
+<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>
\ No newline at end of file
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';
for (const button of this.list.querySelectorAll('button')) {
button.addEventListener('blur', this.hideSuggestionsIfFocusedLost.bind(this));
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
this.historyList.innerHTML = historyKeys.map(key => {
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('');
}
--- /dev/null
+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;
\ No newline at end of file
import codeEditor from "./code-editor.js"
import codeHighlighter from "./code-highlighter.js"
import collapsible from "./collapsible.js"
+import confirmDialog from "./confirm-dialog"
import customCheckbox from "./custom-checkbox.js"
import detailsHighlighter from "./details-highlighter.js"
import dropdown from "./dropdown.js"
import homepageControl from "./homepage-control.js"
import imageManager from "./image-manager.js"
import imagePicker from "./image-picker.js"
-import index from "./index.js"
import listSortControl from "./list-sort-control.js"
import markdownEditor from "./markdown-editor.js"
import newUserPassword from "./new-user-password.js"
"code-editor": codeEditor,
"code-highlighter": codeHighlighter,
"collapsible": collapsible,
+ "confirm-dialog": confirmDialog,
"custom-checkbox": customCheckbox,
"details-highlighter": detailsHighlighter,
"dropdown": dropdown,
"homepage-control": homepageControl,
"image-manager": imageManager,
"image-picker": imagePicker,
- "index": index,
"list-sort-control": listSortControl,
"markdown-editor": markdownEditor,
"new-user-password": newUserPassword,
this.draftDisplayIcon = this.$refs.draftDisplayIcon;
this.changelogInput = this.$refs.changelogInput;
this.changelogDisplay = this.$refs.changelogDisplay;
+ this.changeEditorButtons = this.$manyRefs.changeEditor;
+ this.switchDialogContainer = this.$refs.switchDialog;
// Translations
this.draftText = this.$opts.draftText;
// Draft Controls
onSelect(this.saveDraftButton, this.saveDraft.bind(this));
onSelect(this.discardDraftButton, this.discardDraft.bind(this));
+
+ // Change editor controls
+ onSelect(this.changeEditorButtons, this.changeEditor.bind(this));
}
setInitialFocus() {
data.markdown = this.editorMarkdown;
}
+ let didSave = false;
try {
const resp = await window.$http.put(`/ajax/page/${this.pageId}/save-draft`, data);
if (!this.isNewDraft) {
this.toggleDiscardDraftVisibility(true);
}
+
this.draftNotifyChange(`${resp.data.message} ${Dates.utcTimeStampToLocalTime(resp.data.timestamp)}`);
this.autoSave.last = Date.now();
if (resp.data.warning && !this.shownWarningsCache.has(resp.data.warning)) {
window.$events.emit('warning', resp.data.warning);
this.shownWarningsCache.add(resp.data.warning);
}
+
+ didSave = true;
} catch (err) {
// Save the editor content in LocalStorage as a last resort, just in case.
try {
window.$events.emit('error', this.autosaveFailText);
}
+ return didSave;
}
draftNotifyChange(text) {
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;
\ No newline at end of file
}
hide(onComplete = null) {
- fadeOut(this.container, 240, onComplete);
+ fadeOut(this.container, 120, onComplete);
if (this.onkeyup) {
window.removeEventListener('keyup', this.onkeyup);
this.onkeyup = null;
}
show(onComplete = null, onHide = null) {
- fadeIn(this.container, 240, onComplete);
+ fadeIn(this.container, 120, onComplete);
this.onkeyup = (event) => {
if (event.key === 'Escape') {
drawEventSave(message);
} else if (message.event === 'export') {
drawEventExport(message);
+ } else if (message.event === 'configure') {
+ drawEventConfigure();
}
}
});
}
+function drawEventConfigure() {
+ const config = {};
+ window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config});
+ drawPostMessage({action: 'configure', config});
+}
+
function drawEventClose() {
window.removeEventListener('message', drawReceive);
if (iFrame) document.body.removeChild(iFrame);
'users_social_accounts' => 'Social-Media Konten',
'users_social_accounts_info' => 'Hier können Sie andere Social-Media-Konten für eine schnellere und einfachere Anmeldung verknüpfen. Wenn Sie ein Social-Media Konto lösen, bleibt der Zugriff erhalten. Entfernen Sie in diesem Falle die Berechtigung in Ihren Profil-Einstellungen des verknüpften Social-Media-Kontos.',
'users_social_connect' => 'Social-Media-Konto verknüpfen',
- 'users_social_disconnect' => 'Social-Media-Konto lösen',
+ 'users_social_disconnect' => 'Social-Media-Konto löschen',
'users_social_connected' => ':socialAccount-Konto wurde erfolgreich mit dem Profil verknüpft.',
'users_social_disconnected' => ':socialAccount-Konto wurde erfolgreich vom Profil gelöst.',
'users_api_tokens' => 'API-Token',
'pages_edit_draft_save_at' => 'Draft saved at ',
'pages_edit_delete_draft' => 'Delete 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_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made',
'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_title' => 'Page Title',
'pages_name' => 'Page Name',
'pages_revisions_number' => '#',
'pages_revisions_numbered' => 'Revision #:id',
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
+ 'pages_revisions_editor' => 'Editor Type',
'pages_revisions_changelog' => 'Changelog',
'pages_revisions_changes' => 'Changes',
'pages_revisions_current' => 'Current Version',
'app_secure_images' => '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_editor' => 'Page Editor',
- 'app_editor_desc' => 'Select which editor will be used by all users to edit pages.',
+ 'app_default_editor' => 'Default Page Editor',
+ '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_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.',
'role_access_api' => 'Access system API',
'role_manage_settings' => 'Manage app settings',
'role_export_content' => 'Export content',
+ 'role_editor_change' => 'Change page editor',
'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.',
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
'width' => 'Zabalera',
'height' => 'Altuera',
'More' => 'Gehiago',
- 'select' => 'Select...',
+ 'select' => 'Aukeratu...',
// Toolbar
'formats' => 'Formatuak',
'align_left' => 'Lerrokatu ezkerrean',
'align_center' => 'Lerrokatu erdian',
'align_right' => 'Lerrokatu eskuinean',
- 'align_justify' => 'Justify',
+ 'align_justify' => 'Justifikatuta',
'list_bullet' => 'Buletdun zerrenda',
'list_numbered' => 'Zenbakitutako zerrenda',
- 'list_task' => 'Task list',
+ 'list_task' => 'Zereginen zerrenda',
'indent_increase' => 'Handitu koska',
'indent_decrease' => 'Txikitu koska',
'table' => 'Taula',
'insert_image' => 'Irudia txertatu',
- 'insert_image_title' => 'Insert/Edit Image',
- 'insert_link' => 'Insert/edit link',
- 'insert_link_title' => 'Insert/Edit Link',
- 'insert_horizontal_line' => 'Insert horizontal line',
- 'insert_code_block' => 'Insert code block',
- 'insert_drawing' => 'Insert/edit drawing',
- 'drawing_manager' => 'Drawing manager',
- 'insert_media' => 'Insert/edit media',
- 'insert_media_title' => 'Insert/Edit Media',
- 'clear_formatting' => 'Clear formatting',
- 'source_code' => 'Source code',
- 'source_code_title' => 'Source Code',
- 'fullscreen' => 'Fullscreen',
- 'image_options' => 'Image options',
+ 'insert_image_title' => 'Aldatu/Txertatu irudia',
+ 'insert_link' => 'Txertatu/aldatu esteka',
+ 'insert_link_title' => 'Txertatu/Aldatu esteka',
+ 'insert_horizontal_line' => 'Txertatu linea horizontala',
+ 'insert_code_block' => 'Txertatu kode-blokea',
+ 'insert_drawing' => 'Txertatu marrazki berria',
+ 'drawing_manager' => 'Marrazki kudeaketa',
+ 'insert_media' => 'Txertatu/aldatu media',
+ 'insert_media_title' => 'Aldatu/Txertatu irudia',
+ 'clear_formatting' => 'Garbitu formatua',
+ 'source_code' => 'Iturburu kodea',
+ 'source_code_title' => 'Iturburu kodea',
+ 'fullscreen' => 'Pantaila osoa',
+ 'image_options' => 'Irudiaren aukerak',
// Tables
- 'table_properties' => 'Table properties',
- 'table_properties_title' => 'Table Properties',
- 'delete_table' => 'Delete table',
+ 'table_properties' => 'Taularen propietateak',
+ 'table_properties_title' => 'Taularen propietateak',
+ 'delete_table' => 'Ezabatu taula',
'insert_row_before' => 'Insert row before',
'insert_row_after' => 'Insert row after',
'delete_row' => 'Delete row',
'my_recently_viewed' => 'Nik Ikusitako azkenak',
'my_most_viewed_favourites' => 'Nire gehien ikusitako gogokoak',
'my_favourites' => 'Nire Gogokoenak',
- 'no_pages_viewed' => 'You have not viewed any pages',
+ 'no_pages_viewed' => 'Ez daukazu ikusiriko orririk',
'no_pages_recently_created' => 'Ez da orrialderik sortu azkenaldian',
'no_pages_recently_updated' => 'Ez da orrialderik aldatu azkenaldian',
'export' => 'Esportatu',
- 'export_html' => 'Contained Web File',
+ 'export_html' => 'Daukan web artxiboa',
'export_pdf' => 'PDF fitxategia',
'export_text' => 'Testu lauko fitxategiak',
- 'export_md' => 'Markdown File',
+ 'export_md' => 'Markdown fitxategia',
// Permissions and restrictions
'permissions' => 'Baimenak',
- 'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.',
+ 'permissions_intro' => 'Behin hau aktibatuta, baimen hauek lehentasuna izango dute beste edozein rol-engainetik.',
'permissions_enable' => 'Baimena pertsonalizatuak Gaitu',
'permissions_save' => 'Gorde baimenak',
'permissions_owner' => 'Jabea',
// Search
'search_results' => 'Bilaketaren emaitzak',
- 'search_total_results_found' => ':count result found|:count total results found',
+ 'search_total_results_found' => ':count emaitza aurkitu dira|:count emaitza aurkitu dira guztira',
'search_clear' => 'Bilaketa testua garbitu',
'search_no_pages' => 'Ez da orririk aurkitu zure bilaketan',
- 'search_for_term' => 'Search for :term',
+ 'search_for_term' => 'Bilatu honen arabera :term',
'search_more' => 'Emaitza gehiago',
'search_advanced' => 'Bilaketa aurreratua',
'search_terms' => 'Bilaketa-hitza',
'search_content_type' => 'Eduki Mota',
'search_exact_matches' => 'Bat etortze zehatza',
- 'search_tags' => 'Tag Searches',
+ 'search_tags' => 'Etiketa bilaketak',
'search_options' => 'Aukerak',
'search_viewed_by_me' => 'Nik ikusiak',
'search_not_viewed_by_me' => 'Nik ikusi ez ditudanak',
'search_permissions_set' => 'Baimenak',
- 'search_created_by_me' => 'Created by me',
- 'search_updated_by_me' => 'Updated by me',
- 'search_owned_by_me' => 'Owned by me',
- 'search_date_options' => 'Date Options',
- 'search_updated_before' => 'Updated before',
- 'search_updated_after' => 'Updated after',
- 'search_created_before' => 'Created before',
- 'search_created_after' => 'Created after',
- 'search_set_date' => 'Set Date',
- 'search_update' => 'Update Search',
+ 'search_created_by_me' => 'Nik sortuak',
+ 'search_updated_by_me' => 'Nik eguneratuak',
+ 'search_owned_by_me' => 'Nire jabetazkoak',
+ 'search_date_options' => 'Data aukerak',
+ 'search_updated_before' => 'Aurretik eguneratuak',
+ 'search_updated_after' => 'Ondoren eguneratuak',
+ 'search_created_before' => 'Aurretik sortuak',
+ 'search_created_after' => 'Ondoren sortuak',
+ 'search_set_date' => 'Data finkatu',
+ 'search_update' => 'Eguneratu bilaketa',
// Shelves
- 'shelf' => 'Shelf',
- 'shelves' => 'Shelves',
- 'x_shelves' => ':count Shelf|:count Shelves',
- 'shelves_long' => 'Bookshelves',
- 'shelves_empty' => 'No shelves have been created',
- 'shelves_create' => 'Create New Shelf',
- 'shelves_popular' => 'Popular Shelves',
- 'shelves_new' => 'New Shelves',
- 'shelves_new_action' => 'New Shelf',
- 'shelves_popular_empty' => 'The most popular shelves will appear here.',
- 'shelves_new_empty' => 'The most recently created shelves will appear here.',
- 'shelves_save' => 'Save Shelf',
- 'shelves_books' => 'Books on this shelf',
- 'shelves_add_books' => 'Add books to this shelf',
- 'shelves_drag_books' => 'Drag books here to add them to this shelf',
- 'shelves_empty_contents' => 'This shelf has no books assigned to it',
- 'shelves_edit_and_assign' => 'Edit shelf to assign books',
- 'shelves_edit_named' => 'Edit Bookshelf :name',
- 'shelves_edit' => 'Edit Bookshelf',
- 'shelves_delete' => 'Delete Bookshelf',
- 'shelves_delete_named' => 'Delete Bookshelf :name',
- 'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
- 'shelves_delete_confirmation' => 'Are you sure you want to delete this bookshelf?',
+ 'shelf' => 'Apalategia',
+ 'shelves' => 'Apalategiak',
+ 'x_shelves' => ':count Apalategi|:count Apalategi',
+ 'shelves_long' => 'Liburu-Apalategi',
+ 'shelves_empty' => 'Ez da inolako apalategirik sortu',
+ 'shelves_create' => 'Apalategi berria sortu',
+ 'shelves_popular' => 'Apalategi esanguratsuak',
+ 'shelves_new' => 'Apalategi berriak',
+ 'shelves_new_action' => 'Apalategi berria',
+ 'shelves_popular_empty' => 'Apalategi ikusienak hemen agertuko dira.',
+ 'shelves_new_empty' => 'Berriki sorturiko apalategiak hemen agertuko dira.',
+ 'shelves_save' => 'Gorde apalategia',
+ 'shelves_books' => 'Apalategi honetako liburuak',
+ 'shelves_add_books' => 'Gehitu liburuak apalategi honetara',
+ 'shelves_drag_books' => 'Bota hona liburuak apalategi honetara gehitzeko',
+ 'shelves_empty_contents' => 'Apalategi honek ez dauka libururik',
+ 'shelves_edit_and_assign' => 'Apalategia editatu liburuak gehitzeko',
+ 'shelves_edit_named' => ':name liburu-apalategia editatu',
+ 'shelves_edit' => 'Liburu-apalategia editatu',
+ 'shelves_delete' => 'Apalategia ezabatu',
+ 'shelves_delete_named' => ':name apalategia ezabatu',
+ 'shelves_delete_explain' => "':name' apalategia ezabatuko du ekintza honek. bertan dauden liburuak ez dira ezabatuko.",
+ 'shelves_delete_confirmation' => 'Ziur zaude apalategi hau ezabatu nahi duzula?',
'shelves_permissions' => 'Bookshelf Permissions',
'shelves_permissions_updated' => 'Bookshelf Permissions Updated',
'shelves_permissions_active' => 'Bookshelf Permissions Active',
'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
// Books
- 'book' => 'Book',
- 'books' => 'Books',
+ 'book' => 'Liburua',
+ 'books' => 'Liburuak',
'x_books' => ':count Book|:count Books',
'books_empty' => 'Ez da orrialderik sortu',
- 'books_popular' => 'Popular Books',
+ 'books_popular' => 'Liburu ikusienak',
'books_recent' => 'Azken liburuak',
'books_new' => 'Liburu berriak',
'books_new_action' => 'Liburu berria',
'books_sort_name' => 'Ordenatu izenaren arabera',
'books_sort_created' => 'Ordenatu argitaratze-dataren arabera',
'books_sort_updated' => 'Sort by Updated Date',
- 'books_sort_chapters_first' => 'Chapters First',
- 'books_sort_chapters_last' => 'Chapters Last',
- 'books_sort_show_other' => 'Show Other Books',
+ 'books_sort_chapters_first' => 'Lehen kapitulua',
+ 'books_sort_chapters_last' => 'Azken kapitulua',
+ 'books_sort_show_other' => 'Erakutsi beste liburuak',
'books_sort_save' => 'Save New Order',
'books_copy' => 'Copy Book',
'books_copy_success' => 'Book successfully copied',
'chapter' => 'Kapitulua',
'chapters' => 'Kapituluak',
'x_chapters' => ':count Chapter|:count Chapters',
- 'chapters_popular' => 'Popular Chapters',
- 'chapters_new' => 'New Chapter',
- 'chapters_create' => 'Create New Chapter',
+ 'chapters_popular' => 'Kapitulu ikusienak',
+ 'chapters_new' => 'Kopiatu kapitulua',
+ 'chapters_create' => 'Sortu kapitulu berria',
'chapters_delete' => 'Kapitulua ezabatu',
'chapters_delete_named' => 'Delete Chapter :chapterName',
'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.',
'chapters_edit' => 'Kapitulua aldatu',
'chapters_edit_named' => 'Edit Chapter :chapterName',
'chapters_save' => 'Kapitulua gorde',
- 'chapters_move' => 'Move Chapter',
+ 'chapters_move' => 'Kapitulua mugitu',
'chapters_move_named' => 'Move Chapter :chapterName',
'chapter_move_success' => 'Chapter moved to :bookName',
- 'chapters_copy' => 'Copy Chapter',
- 'chapters_copy_success' => 'Chapter successfully copied',
+ 'chapters_copy' => 'Kapitulua kopiatu',
+ 'chapters_copy_success' => 'Kapitulua egoki kopiatua',
'chapters_permissions' => 'Chapter Permissions',
'chapters_empty' => 'No pages are currently in this chapter.',
'chapters_permissions_active' => 'Chapter Permissions Active',
'chapters_search_this' => 'Search this chapter',
// Pages
- 'page' => 'Page',
- 'pages' => 'Pages',
+ 'page' => 'Orria',
+ 'pages' => 'Orriak',
'x_pages' => ':count Page|:count Pages',
'pages_popular' => 'Popular Pages',
- 'pages_new' => 'New Page',
- 'pages_attachments' => 'Attachments',
- 'pages_navigation' => 'Page Navigation',
- 'pages_delete' => 'Delete Page',
+ 'pages_new' => 'Orrialde berria',
+ 'pages_attachments' => 'Eranskinak',
+ 'pages_navigation' => 'Nabigazio orrialdea',
+ 'pages_delete' => 'Ezabatu orria',
'pages_delete_named' => 'Delete Page :pageName',
'pages_delete_draft_named' => 'Delete Draft Page :pageName',
'pages_delete_draft' => 'Delete Draft Page',
- 'pages_delete_success' => 'Page deleted',
+ 'pages_delete_success' => 'Orria ezabatua',
'pages_delete_draft_success' => 'Draft page deleted',
- 'pages_delete_confirm' => 'Are you sure you want to delete this page?',
+ 'pages_delete_confirm' => 'Ziur al zaude orri hau ezabatu nahi duzula?',
'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?',
'pages_editing_named' => 'Editing Page :pageName',
'pages_edit_draft_options' => 'Draft Options',
- 'pages_edit_save_draft' => 'Save Draft',
+ 'pages_edit_save_draft' => 'Gorde zirriborroa',
'pages_edit_draft' => 'Edit Page Draft',
- 'pages_editing_draft' => 'Editing Draft',
- 'pages_editing_page' => 'Editing Page',
+ 'pages_editing_draft' => 'Editatu zirriborroa',
+ 'pages_editing_page' => 'Editatu orrialdea',
'pages_edit_draft_save_at' => 'Draft saved at ',
- 'pages_edit_delete_draft' => 'Delete Draft',
- 'pages_edit_discard_draft' => 'Discard Draft',
+ 'pages_edit_delete_draft' => 'Ezabatu zirriborroa',
+ 'pages_edit_discard_draft' => 'Baztertu zirriborroa',
'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' => 'Enter Changelog',
- 'pages_save' => 'Save Page',
- 'pages_title' => 'Page Title',
- 'pages_name' => 'Page Name',
- 'pages_md_editor' => 'Editor',
- 'pages_md_preview' => 'Preview',
- 'pages_md_insert_image' => 'Insert Image',
+ 'pages_save' => 'Gorde orrialdea',
+ 'pages_title' => 'Orrialdearen titulua',
+ 'pages_name' => 'Orrialdearen izena',
+ 'pages_md_editor' => 'Editorea',
+ 'pages_md_preview' => 'Aurrebista',
+ 'pages_md_insert_image' => 'Txertatu irudia',
'pages_md_insert_link' => 'Insert Entity Link',
- 'pages_md_insert_drawing' => 'Insert Drawing',
+ 'pages_md_insert_drawing' => 'Txertatu marrazki berria',
'pages_not_in_chapter' => 'Page is not in a chapter',
'pages_move' => 'Move Page',
'pages_move_success' => 'Page moved to ":parentName"',
'pages_revisions_named' => 'Page Revisions for :pageName',
'pages_revision_named' => 'Page Revision for :pageName',
'pages_revision_restored_from' => 'Restored from #:id; :summary',
- 'pages_revisions_created_by' => 'Created By',
- 'pages_revisions_date' => 'Revision Date',
+ 'pages_revisions_created_by' => 'Sortzailea',
+ 'pages_revisions_date' => 'Berrikuspen data',
'pages_revisions_number' => '#',
'pages_revisions_numbered' => 'Revision #:id',
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
'pages_revisions_changelog' => 'Changelog',
- 'pages_revisions_changes' => 'Changes',
+ 'pages_revisions_changes' => 'Aldaketak',
'pages_revisions_current' => 'Current Version',
- 'pages_revisions_preview' => 'Preview',
- 'pages_revisions_restore' => 'Restore',
+ 'pages_revisions_preview' => 'Aurrebista',
+ 'pages_revisions_restore' => 'Berreskuratu',
'pages_revisions_none' => 'This page has no revisions',
'pages_copy_link' => 'Copy Link',
- 'pages_edit_content_link' => 'Edit Content',
+ 'pages_edit_content_link' => 'Editatu edukia',
'pages_permissions_active' => 'Page Permissions Active',
'pages_initial_revision' => 'Initial publish',
- 'pages_initial_name' => 'New Page',
+ 'pages_initial_name' => 'Orrialde berria',
'pages_editing_draft_notification' => 'You are currently editing a draft that was last saved :timeDiff.',
'pages_draft_edited_notification' => 'This page has been updated by since that time. It is recommended that you discard this draft.',
'pages_draft_page_changed_since_creation' => 'This page has been updated since this draft was created. It is recommended that you discard this draft or take care not to overwrite any page changes.',
],
'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
'pages_specific' => 'Specific Page',
- 'pages_is_template' => 'Page Template',
+ 'pages_is_template' => 'Orrialde txantiloia',
// Editor Sidebar
- 'page_tags' => 'Page Tags',
- 'chapter_tags' => 'Chapter Tags',
- 'book_tags' => 'Book Tags',
- 'shelf_tags' => 'Shelf Tags',
- 'tag' => 'Tag',
- 'tags' => 'Tags',
- 'tag_name' => 'Tag Name',
+ 'page_tags' => 'Orrialde etiketak',
+ 'chapter_tags' => 'Kapitulu etiketak',
+ 'book_tags' => 'Liburu etiketak',
+ 'shelf_tags' => 'Apalategi etiketak',
+ 'tag' => 'Etiketa',
+ 'tags' => 'Etiketak',
+ 'tag_name' => 'Etiketa izena',
'tag_value' => 'Tag Value (Optional)',
'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",
- 'tags_add' => 'Add another tag',
+ 'tags_add' => 'Beste bat gehitu',
'tags_remove' => 'Remove this tag',
'tags_usages' => 'Total tag usages',
'tags_assigned_pages' => 'Assigned to Pages',
'tags_assigned_books' => 'Assigned to Books',
'tags_assigned_shelves' => 'Assigned to Shelves',
'tags_x_unique_values' => ':count unique values',
- 'tags_all_values' => 'All values',
+ 'tags_all_values' => 'Balio guztiak',
'tags_view_tags' => 'View Tags',
'tags_view_existing_tags' => 'View existing tags',
'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.',
- 'attachments' => 'Attachments',
+ 'attachments' => 'Eranskinak',
'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.',
'attachments_explain_instant_save' => 'Changes here are saved instantly.',
- 'attachments_items' => 'Attached Items',
- 'attachments_upload' => 'Upload File',
+ 'attachments_items' => 'Atxikiak',
+ 'attachments_upload' => 'Kargatu artxiboak',
'attachments_link' => 'Attach Link',
'attachments_set_link' => 'Set Link',
'attachments_delete' => 'Are you sure you want to delete this attachment?',
'attachments_dropzone' => 'Drop files or click here to attach a file',
- 'attachments_no_files' => 'No files have been uploaded',
+ 'attachments_no_files' => 'Ez da igo fitxategirik',
'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.',
- 'attachments_link_name' => 'Link Name',
+ 'attachments_link_name' => 'Loturaren izena',
'attachment_link' => 'Attachment link',
- 'attachments_link_url' => 'Link to file',
+ 'attachments_link_url' => 'Fitxategiarentzako esteka',
'attachments_link_url_hint' => 'Url of site or file',
'attach' => 'Attach',
'attachments_insert_link' => 'Add Attachment Link to Page',
'attachments_edit_file' => 'Edit File',
- 'attachments_edit_file_name' => 'File Name',
+ 'attachments_edit_file_name' => 'Fitxategi izena',
'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite',
'attachments_order_updated' => 'Attachment order updated',
'attachments_updated_success' => 'Attachment details updated',
'profile_not_created_shelves' => ':userName has not created any shelves',
// Comments
- 'comment' => 'Comment',
- 'comments' => 'Comments',
- 'comment_add' => 'Add Comment',
- 'comment_placeholder' => 'Leave a comment here',
+ 'comment' => 'Iruzkina',
+ 'comments' => 'Iruzkinak',
+ 'comment_add' => 'Iruzkina gehitu',
+ 'comment_placeholder' => 'Utzi iruzkin bat hemen',
'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
- 'comment_save' => 'Save Comment',
+ 'comment_save' => 'Iruzkina gorde',
'comment_saving' => 'Saving comment...',
'comment_deleting' => 'Deleting comment...',
- 'comment_new' => 'New Comment',
+ 'comment_new' => 'Iruzkin berria',
'comment_created' => 'commented :createDiff',
'comment_updated' => 'Updated :updateDiff by :username',
'comment_deleted_success' => 'Comment deleted',
- 'comment_created_success' => 'Comment added',
- 'comment_updated_success' => 'Comment updated',
- 'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
+ 'comment_created_success' => 'Iruzkina gehituta',
+ 'comment_updated_success' => 'Iruzkina gehituta',
+ 'comment_delete_confirm' => 'Ziur zaude iruzkin hau ezabatu nahi duzula?',
'comment_in_reply_to' => 'In reply to :commentId',
// Revision
- 'revision_delete_confirm' => 'Are you sure you want to delete this revision?',
+ 'revision_delete_confirm' => 'Ziur zaude hau ezabatu nahi duzula?',
'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
'revision_delete_success' => 'Revision deleted',
'revision_cannot_delete_latest' => 'Cannot delete the latest revision.',
'ldap_cannot_connect' => 'Ezin izan da ldap zerbitzarira konektatu, hasierako konexioak huts egin du',
'saml_already_logged_in' => 'Saioa aurretik hasita dago',
'saml_user_not_registered' => ':name erabiltzailea ez dago erregistratua eta erregistro automatikoa ezgaituta dago',
- 'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
- 'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.',
+ 'saml_no_email_address' => 'Ezin izan dugu posta helbiderik aurkitu erabiltzaile honentzat, kanpoko autentifikazio zerbitzuak bidalitako datuetan',
+ 'saml_invalid_response_id' => 'Kanpoko egiazkotasun-sistemaren eskaria ez du onartzen aplikazio honek abiarazitako prozesu batek. Loginean atzera egitea izan daiteke arrazoia.',
'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
- 'oidc_already_logged_in' => 'Already logged in',
+ 'oidc_already_logged_in' => 'Dagoeneko saioa hasita',
'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled',
'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
'empty_comment' => 'Cannot add an empty comment.',
// Error pages
- '404_page_not_found' => 'Page Not Found',
+ '404_page_not_found' => 'Ez da orrialdea aurkitu',
'sorry_page_not_found' => 'Sorry, The page you were looking for could not be found.',
'sorry_page_not_found_permission_warning' => 'If you expected this page to exist, you might not have permission to view it.',
- 'image_not_found' => 'Image Not Found',
+ 'image_not_found' => 'Irudia Ez da Aurkitu',
'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
- 'return_home' => 'Return to home',
+ 'return_home' => 'Itzuli hasierara',
'error_occurred' => 'Akats bat gertatu da',
'app_down' => ':appName is down right now',
'back_soon' => 'It will be back up soon.',
'app_editor_desc' => 'Aukeratu zein editore erabiliko duten erabiltzaile guztiek orriak editatzeko.',
'app_custom_html' => 'HTML pertsonalizatuko goiburu edukia',
'app_custom_html_desc' => 'Hemen sarturiko edozein eduki <head> eremuko behekaldean sartuko da orrialde guztietan. Honek estiloak gainditzeko edo analitika-kodea gehitzeko balio du.',
- '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' => 'HTML edukiera desgaituta dago konfigurazio-orri honetan, edozein aldaketa eten daitekeela bermatzeko.',
'app_logo' => 'Aplikazioaren logoa',
- 'app_logo_desc' => 'This image should be 43px in height. <br>Large images will be scaled down.',
+ 'app_logo_desc' => 'Irudi honek 43px izan behar du altueran.<br>Irudi handiagoak txikitu egingo dira.',
'app_primary_color' => 'Aplikazioaren kolore lehenetsia',
- 'app_primary_color_desc' => 'Sets the primary color for the application including the banner, buttons, and links.',
+ 'app_primary_color_desc' => 'Konfiguratu aplikaziorako kolore nagusia, botoi, banner eta estekak barne.',
'app_homepage' => 'Aplikazioko hasiera orria',
- 'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
+ 'app_homepage_desc' => 'Aukeratu hasierako orriko bista, defektuzkoa beharrean. Orrialde baimenak ez dira kontutan hartuko aukeratutako orrialdeentzat.',
'app_homepage_select' => 'Aukeratu Orria',
'app_footer_links' => 'Beheko aldeko estekak',
'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::<key>" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
// Audit Log
- 'audit' => 'Audit Log',
+ 'audit' => 'Auditoretza erregistroak',
'audit_desc' => 'This audit log displays a list of activities tracked in the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
- 'audit_event_filter' => 'Event Filter',
+ 'audit_event_filter' => 'Gertakari filtroa',
'audit_event_filter_no_filter' => 'Filtrorik ez',
- 'audit_deleted_item' => 'Deleted Item',
- 'audit_deleted_item_name' => 'Name: :name',
+ 'audit_deleted_item' => 'Ezabatutako edukiak',
+ 'audit_deleted_item_name' => 'Izena :name',
'audit_table_user' => 'Erabiltzailea',
'audit_table_event' => 'Gertaera',
'audit_table_related' => 'Related Item or Detail',
'audit_table_ip' => 'IP helbidea',
'audit_table_date' => 'Azken aktibitate data',
- 'audit_date_from' => 'Date Range From',
+ 'audit_date_from' => 'Data tartea',
'audit_date_to' => 'Data tartea',
// Role Settings
'role_user_roles' => 'Erabiltzailearen rola',
'role_create' => 'Rol berria sortu',
'role_create_success' => 'Rola ondo sortu da',
- 'role_delete' => 'Delete Role',
+ 'role_delete' => 'Ezabatu Rol-a',
'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.',
'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.',
- 'role_delete_no_migration' => "Don't migrate users",
- 'role_delete_sure' => 'Are you sure you want to delete this role?',
- 'role_delete_success' => 'Role successfully deleted',
- 'role_edit' => 'Edit Role',
- 'role_details' => 'Role Details',
- 'role_name' => 'Role Name',
+ 'role_delete_no_migration' => "Ez migratu erabiltzaileak",
+ 'role_delete_sure' => 'Ziur zaude rol hau ezabatu nahi duzula?',
+ 'role_delete_success' => 'Rola ezabatua',
+ 'role_edit' => 'Editatu rola',
+ 'role_details' => 'Ireki xehetasunak',
+ 'role_name' => 'Rol izena',
'role_desc' => 'Short Description of Role',
'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'System Permissions',
- 'role_manage_users' => 'Manage users',
+ 'role_manage_users' => 'Erabiltzaileak kudeatu',
'role_manage_roles' => 'Manage roles & role permissions',
'role_manage_entity_permissions' => 'Manage all book, chapter & page permissions',
'role_manage_own_entity_permissions' => 'Manage permissions on own book, chapter & pages',
'users_password' => 'Erabiltzaile pasahitza',
'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 8 characters long.',
'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
- 'users_send_invite_option' => 'Send user invite email',
- 'users_external_auth_id' => 'External Authentication ID',
+ 'users_send_invite_option' => 'Erabiltzailea gonbidatzeko emaila bidali',
+ 'users_external_auth_id' => 'Kanpo autentikazioa IDa',
'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your external authentication system.',
'users_password_warning' => 'Only fill the below if you would like to change your password.',
'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
'users_delete' => 'Ezabatu erabiltzailea',
- 'users_delete_named' => 'Delete user :userName',
+ 'users_delete_named' => ':userName erabiltzailea ezabatu',
'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.',
'users_delete_confirm' => 'Are you sure you want to delete this user?',
'users_migrate_ownership' => 'Migrate Ownership',
'users_none_selected' => 'Erabiltzailerik ez duzu aukeratu',
'users_edit' => 'Erabiltzaile editatu',
'users_edit_profile' => 'Editatu profila',
- 'users_avatar' => 'User Avatar',
+ 'users_avatar' => 'Erabiltzaile avatarra',
'users_avatar_desc' => 'Select an image to represent this user. This should be approx 256px square.',
'users_preferred_language' => 'Hobetsitako hizkuntza',
'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
'users_social_disconnect' => 'Deskonektatu kontua',
'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
- 'users_api_tokens' => 'API Tokens',
+ 'users_api_tokens' => 'API tokenak',
'users_api_tokens_none' => 'No API tokens have been created for this user',
- 'users_api_tokens_create' => 'Create Token',
- 'users_api_tokens_expires' => 'Expires',
+ 'users_api_tokens_create' => 'Sortu Tokena',
+ 'users_api_tokens_expires' => 'Iraungita',
'users_api_tokens_docs' => 'API dokumentazioa',
'users_mfa' => 'Multi-Factor Authentication',
'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
'users_mfa_configure' => 'Configure Methods',
// API Tokens
- 'user_api_token_create' => 'Create API Token',
+ 'user_api_token_create' => 'Sortu Tokena',
'user_api_token_name' => 'Izena',
'user_api_token_name_desc' => 'Give your token a readable name as a future reminder of its intended purpose.',
'user_api_token_expiry' => 'Iraungitze data',
'email' => 'Email',
'password' => 'Kata Sandi',
'password_confirm' => 'Konfirmasi Kata Sandi',
- 'password_hint' => 'Must be at least 8 characters',
+ 'password_hint' => 'Harus minimal 8 karakter',
'forgot_password' => 'Lupa Password?',
'remember_me' => 'Ingat saya',
'ldap_email_hint' => 'Harap masukkan email yang akan digunakan untuk akun ini.',
'mfa_setup' => 'Setup Multi-Factor Authentication',
'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
'mfa_setup_configured' => 'Already configured',
- 'mfa_setup_reconfigure' => 'Reconfigure',
+ 'mfa_setup_reconfigure' => 'Konfigurasi ulang',
'mfa_setup_remove_confirmation' => 'Apakah Anda yakin ingin menghapus metode autentikasi multi-faktor ini?',
'mfa_setup_action' => 'Setup',
'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.',
- 'mfa_option_totp_title' => 'Mobile App',
+ 'mfa_option_totp_title' => 'Aplikasi Seluler',
'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.',
- 'mfa_option_backup_codes_title' => 'Backup Codes',
+ 'mfa_option_backup_codes_title' => 'Kode Cadangan',
'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.',
'mfa_gen_confirm_and_enable' => 'Confirm and Enable',
'mfa_gen_backup_codes_title' => 'Backup Codes Setup',
'mfa_verify_access_desc' => 'Your user account requires you to confirm your identity via an additional level of verification before you\'re granted access. Verify using one of your configured methods to continue.',
'mfa_verify_no_methods' => 'No Methods Configured',
'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.',
- 'mfa_verify_use_totp' => 'Verify using a mobile app',
- 'mfa_verify_use_backup_codes' => 'Verify using a backup code',
- 'mfa_verify_backup_code' => 'Backup Code',
+ 'mfa_verify_use_totp' => 'Verifikasi menggunakan aplikasi seluler',
+ 'mfa_verify_use_backup_codes' => 'Verifikasi menggunakan kode cadangan',
+ 'mfa_verify_backup_code' => 'Kode Cadangan',
'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:',
'mfa_verify_backup_code_enter_here' => 'Enter backup code here',
'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:',
*/
return [
// General editor terms
- 'general' => 'General',
- 'advanced' => 'Advanced',
- 'none' => 'None',
- 'cancel' => 'Cancel',
- 'save' => 'Save',
- 'close' => 'Close',
+ 'general' => 'Umum',
+ 'advanced' => 'Lanjutan',
+ 'none' => 'Tidak Ada',
+ 'cancel' => 'Batal',
+ 'save' => 'Simpan',
+ 'close' => 'Tutup',
'undo' => 'Undo',
- 'redo' => 'Redo',
- 'left' => 'Left',
- 'center' => 'Center',
- 'right' => 'Right',
- 'top' => 'Top',
- 'middle' => 'Middle',
- 'bottom' => 'Bottom',
- 'width' => 'Width',
- 'height' => 'Height',
- 'More' => 'More',
- 'select' => 'Select...',
+ 'redo' => 'Ulangi',
+ 'left' => 'Kiri',
+ 'center' => 'Tengah',
+ 'right' => 'Kanan',
+ 'top' => 'Atas',
+ 'middle' => 'Sedang',
+ 'bottom' => 'Bawah',
+ 'width' => 'Lebar',
+ 'height' => 'Tinggi',
+ 'More' => 'Lebih Banyak',
+ 'select' => 'Pilih...',
// Toolbar
- 'formats' => 'Formats',
+ 'formats' => 'Format',
'header_large' => 'Large Header',
'header_medium' => 'Medium Header',
'header_small' => 'Small Header',
'inline_code' => 'Inline code',
'callouts' => 'Callouts',
'callout_information' => 'Information',
- 'callout_success' => 'Success',
- 'callout_warning' => 'Warning',
- 'callout_danger' => 'Danger',
- 'bold' => 'Bold',
+ 'callout_success' => 'Sukses',
+ 'callout_warning' => 'Peringatan',
+ 'callout_danger' => 'Bahaya',
+ 'bold' => 'Berani',
'italic' => 'Italic',
- 'underline' => 'Underline',
+ 'underline' => 'Garis Bawah',
'strikethrough' => 'Strikethrough',
'superscript' => 'Superscript',
'subscript' => 'Subscript',
'alpha_dash' => ':attribute hanya boleh berisi huruf, angka, tanda hubung, dan garis bawah.',
'alpha_num' => ':attribute hanya boleh berisi huruf dan angka.',
'array' => ':attribute harus berupa larik.',
- 'backup_codes' => 'The provided code is not valid or has already been used.',
+ 'backup_codes' => 'Kode yang diberikan tidak valid atau telah digunakan.',
'before' => ':attribute harus tanggal sebelum :date.',
'between' => [
'numeric' => ':attribute harus di antara :min dan :max.',
'digits_between' => ':attribute harus diantara :min dan :max digit.',
'email' => ':attrtibute Harus alamat e-mail yang valid.',
'ends_with' => ':attribute harus diakhiri dengan salah satu dari berikut ini: :values',
- 'file' => 'The :attribute must be provided as a valid file.',
+ 'file' => ':attribute harus diberikan sebagai file yang valid.',
'filled' => ':attribute bidang diperlukan.',
'gt' => [
'numeric' => ':attribute harus lebih besar dari :value.',
],
'string' => ':attribute harus berupa string.',
'timezone' => ':attribute harus menjadi zona yang valid.',
- 'totp' => 'The provided code is not valid or has expired.',
+ 'totp' => 'Kode yang diberikan tidak valid atau telah kedaluwarsa.',
'unique' => ':attribute sudah diambil.',
'url' => ':attribute format tidak valid.',
'uploaded' => 'Berkas tidak dapat diunggah. Server mungkin tidak menerima berkas dengan ukuran ini.',
'settings' => 'Instellingen',
'settings_save' => 'Instellingen opslaan',
'settings_save_success' => 'Instellingen Opgeslagen',
- 'system_version' => 'System Version',
- 'categories' => 'Categories',
+ 'system_version' => 'Systeem versie',
+ 'categories' => 'Categorieën',
// App Settings
'app_customization' => 'Aanpassingen',
'page_delete' => 'página eliminada',
'page_delete_notification' => 'Página excluída com sucesso.',
'page_restore' => 'página restaurada',
- 'page_restore_notification' => 'Imagem restaurada com sucesso',
+ 'page_restore_notification' => 'Página restaurada com sucesso',
'page_move' => 'página movida',
// Chapters
'favourite_remove_notification' => '":name" foi removido dos seus favoritos',
// MFA
- 'mfa_setup_method_notification' => 'Método de múltiplos-fatores configurado com sucesso',
- 'mfa_remove_method_notification' => 'Método de múltiplos-fatores removido com sucesso',
+ 'mfa_setup_method_notification' => 'Método de autenticação por múltiplos-fatores configurado com sucesso',
+ 'mfa_remove_method_notification' => 'Método de autenticação por múltiplos-fatores removido com sucesso',
// Webhooks
- 'webhook_create' => 'criar webhook',
+ 'webhook_create' => 'webhook criado',
'webhook_create_notification' => 'Webhook criado com sucesso',
'webhook_update' => 'atualizar um webhook',
'webhook_update_notification' => 'Webhook criado com sucesso',
'description' => 'Descrição',
'role' => 'Cargo',
'cover_image' => 'Imagem de capa',
- 'cover_image_description' => 'Esta imagem deve ser aproximadamente 440x250px.',
+ 'cover_image_description' => 'Esta imagem deve ter aproximadamente 440x250px.',
// Actions
'actions' => 'Ações',
'header_large' => 'Cabeçalho grande',
'header_medium' => 'Cabeçalho médio',
'header_small' => 'Cabeçalho pequeno',
- 'header_tiny' => 'Cabeçalho pequeno',
+ 'header_tiny' => 'Cabeçalho minúsculo',
'paragraph' => 'Parágrafo',
'blockquote' => 'Citação',
'inline_code' => 'Código embutido',
'width' => 'Ширина',
'height' => 'Высота',
'More' => 'Еще',
- 'select' => 'Select...',
+ 'select' => 'Выбрать...',
// Toolbar
'formats' => 'Форматы',
'cell_type_cell' => 'Ячейка',
'cell_scope' => 'Scope',
'cell_type_header' => 'Заголовок ячейки',
- 'merge_cells' => 'Merge cells',
- 'split_cell' => 'Split cell',
+ 'merge_cells' => 'Объединить ячейки',
+ 'split_cell' => 'Разделить ячейку',
'table_row_group' => 'Объединить строки',
'table_column_group' => 'Объединить столбцы',
'horizontal_align' => 'Выровнять по горизонтали',
'caption' => 'Подпись',
'show_caption' => 'Показать подпись',
'constrain' => 'Сохранять пропорции',
- 'cell_border_solid' => 'Solid',
- 'cell_border_dotted' => 'Dotted',
- 'cell_border_dashed' => 'Dashed',
- 'cell_border_double' => 'Double',
+ 'cell_border_solid' => 'Сплошная',
+ 'cell_border_dotted' => 'Точками',
+ 'cell_border_dashed' => 'Пунктирная',
+ 'cell_border_double' => 'Двойная сплошная',
'cell_border_groove' => 'Groove',
'cell_border_ridge' => 'Ridge',
'cell_border_inset' => 'Inset',
'cell_border_outset' => 'Outset',
- 'cell_border_none' => 'None',
- 'cell_border_hidden' => 'Hidden',
+ 'cell_border_none' => 'Нет',
+ 'cell_border_hidden' => 'Прозрачная',
// Images, links, details/summary & embed
'source' => 'Источник',
'toggle_label' => 'Метка',
// About view
- 'about' => 'About the editor',
+ 'about' => 'О редакторе',
'about_title' => 'О редакторе WYSIWYG',
'editor_license' => 'Лицензия редактора и авторские права',
'editor_tiny_license' => 'Этот редактор собран с помощью :tinyLink, который предоставляется под лицензией LGPL v2.1.',
'settings' => 'Настройки',
'settings_save' => 'Сохранить настройки',
'settings_save_success' => 'Настройки сохранены',
- 'system_version' => 'System Version',
- 'categories' => 'Categories',
+ 'system_version' => 'Версия системы',
+ 'categories' => 'Категории',
// App Settings
'app_customization' => 'Настройки',
'digits_between' => ':attribute должен иметь от :min до :max цифр.',
'email' => ':attribute должен быть корректным email адресом.',
'ends_with' => ':attribute должен заканчиваться одним из следующих: :values',
- 'file' => 'The :attribute must be provided as a valid file.',
+ 'file' => ':attribute должен быть указан как допустимый файл.',
'filled' => ':attribute поле необходимо.',
'gt' => [
'numeric' => 'Значение :attribute должно быть больше чем :value.',
'width' => '宽度',
'height' => '高度',
'More' => '更多',
- 'select' => 'Select...',
+ 'select' => '选择...',
// Toolbar
'formats' => '格式',
'align_left' => '左对齐',
'align_center' => '居中',
'align_right' => '右对齐',
- 'align_justify' => 'Justify',
+ 'align_justify' => '两端对齐',
'list_bullet' => '无序列表',
'list_numbered' => '有序列表',
- 'list_task' => 'Task list',
+ 'list_task' => '任务列表',
'indent_increase' => '增加缩进',
'indent_decrease' => '减少缩进',
'table' => '表格',
'cell_properties_title' => '单元格属性',
'cell_type' => '单元格类型',
'cell_type_cell' => '单元格',
- 'cell_scope' => 'Scope',
+ 'cell_scope' => '范围',
'cell_type_header' => '表头',
- 'merge_cells' => 'Merge cells',
- 'split_cell' => 'Split cell',
+ 'merge_cells' => '合并单元格',
+ 'split_cell' => '拆分单元格',
'table_row_group' => '按行分组',
'table_column_group' => '按列分组',
'horizontal_align' => '水平对齐',
'caption' => '标题',
'show_caption' => '显示标题',
'constrain' => '保持宽高比',
- 'cell_border_solid' => 'Solid',
- 'cell_border_dotted' => 'Dotted',
- 'cell_border_dashed' => 'Dashed',
- 'cell_border_double' => 'Double',
- 'cell_border_groove' => 'Groove',
- 'cell_border_ridge' => 'Ridge',
- 'cell_border_inset' => 'Inset',
- 'cell_border_outset' => 'Outset',
- 'cell_border_none' => 'None',
- 'cell_border_hidden' => 'Hidden',
+ 'cell_border_solid' => '实线',
+ 'cell_border_dotted' => '点虚线',
+ 'cell_border_dashed' => '短虚线',
+ 'cell_border_double' => '双实线',
+ 'cell_border_groove' => '浮入',
+ 'cell_border_ridge' => '浮出',
+ 'cell_border_inset' => '陷入',
+ 'cell_border_outset' => '突出',
+ 'cell_border_none' => '无边框',
+ 'cell_border_hidden' => '隐藏边框',
// Images, links, details/summary & embed
'source' => '来源',
'toggle_label' => '切换标签',
// About view
- 'about' => 'About the editor',
+ 'about' => '关于编辑器',
'about_title' => '关于所见即所得(WYSIWYG)编辑器',
'editor_license' => '编辑器许可证与版权信息',
'editor_tiny_license' => '此编辑器是在 LGPL v2.1 许可证下使用 :tinyLink 构建的。',
'settings' => '设置',
'settings_save' => '保存设置',
'settings_save_success' => '设置已保存',
- 'system_version' => 'System Version',
- 'categories' => 'Categories',
+ 'system_version' => '系统版本',
+ 'categories' => '类别',
// App Settings
'app_customization' => '定制',
'audit_table_user' => '用户',
'audit_table_event' => '事件',
'audit_table_related' => '相关项目或详细信息',
- 'audit_table_ip' => 'IP地址',
+ 'audit_table_ip' => 'IP 地址',
'audit_table_date' => '活动日期',
'audit_date_from' => '日期范围从',
'audit_date_to' => '日期范围至',
width: 800px;
max-width: 90%;
}
+ &.very-small {
+ margin: 2% auto;
+ width: 600px;
+ max-width: 90%;
+ }
&:before {
display: flex;
align-self: flex-start;
li.active a {
font-weight: 600;
}
- a, button {
- display: block;
- padding: $-xs $-m;
+ button {
+ width: 100%;
+ 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);
fill: currentColor;
white-space: nowrap;
- line-height: 1.6;
+ line-height: 1.4;
cursor: pointer;
&:hover, &:focus {
text-decoration: none;
width: 16px;
}
}
- button {
- width: 100%;
- text-align: start;
+ .text-item {
+ display: block;
}
- li.border-bottom {
- border-bottom: 1px solid #DDD;
+ .label-item {
+ display: grid;
+ align-items: center;
+ grid-template-columns: auto min-content;
+ gap: $-m;
}
- li hr {
- margin: $-xs 0;
+ .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;
+ }
}
}
small, p.small, span.small, .text-small {
font-size: 0.75rem;
- @include lightDark(color, #5e5e5e, #999);
}
sup, .superscript {
class="drag-card-action text-center text-neg">@icon('close')</button>
<div refs="dropdown@menu" class="dropdown-menu">
<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>
<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">
<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>
</div>
@endif
--- /dev/null
+<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>
\ No newline at end of file
<style>
- @if (!app()->environment('testing'))
+ @if (!app()->runningUnitTests())
{!! file_get_contents(public_path('/dist/export-styles.css')) !!}
@endif
</style>
</span>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<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>
- <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>
- <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>
<form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
method="post">
{{ csrf_field() }}
- <button class="text-muted icon-list-item text-primary">
- @icon('logout'){{ trans('auth.logout') }}
+ <button class="icon-item">
+ @icon('logout')
+ <div>{{ trans('auth.logout') }}</div>
</button>
</form>
</li>
<li><hr></li>
<li>
- @include('common.dark-mode-toggle')
+ @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
</li>
</ul>
</div>
<span>{{ trans('entities.export') }}</span>
</div>
<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/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/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/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/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" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</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" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
</ul>
</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">
@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
</ul>
</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="grid third gap-xxl no-row-gap" >
<div>
<form action="{{ url('/mfa/' . $method . '/remove') }}" method="post">
{{ csrf_field() }}
{{ 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>
</div>
</div>
@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('content')
<form action="{{ $page->getUrl() }}" autocomplete="off" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
{{ csrf_field() }}
- @if(!isset($isDraft))
- <input type="hidden" name="_method" value="PUT">
- @endif
+ @if(!$isDraft) {{ method_field('PUT') }} @endif
@include('pages.parts.form', ['model' => $page])
@include('pages.parts.editor-toolbox')
</form>
--- /dev/null
+<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>
\ No newline at end of file
<div component="page-editor" class="page-editor flex-fill flex"
option:page-editor:drafts-enabled="{{ $draftsEnabled ? 'true' : 'false' }}"
@if(config('services.drawio'))
- drawio-url="{{ is_string(config('services.drawio')) ? config('services.drawio') : 'https://embed.diagrams.net/?embed=1&proto=json&spin=1' }}"
+ drawio-url="{{ is_string(config('services.drawio')) ? config('services.drawio') : 'https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1' }}"
@endif
@if($model->name === trans('entities.pages_initial_name'))
option:page-editor:has-default-title="true"
@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-new-draft="{{ ($model->draft ?? false) ? 'true' : 'false' }}"
- option:page-editor:draft-text="{{ ($model->draft || $model->isDraft) ? trans('entities.pages_editing_draft') : trans('entities.pages_editing_page') }}"
+ 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:autosave-fail-text="{{ trans('errors.page_draft_autosave_fail') }}"
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:set-changelog-text="{{ trans('entities.pages_edit_set_changelog') }}">
- {{--Header Bar--}}
- <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="{{ $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>
+ {{--Header Toolbar--}}
+ @include('pages.parts.editor-toolbar', ['model' => $model, 'editor' => $editor, 'isDraft' => $isDraft, 'draftsEnabled' => $draftsEnabled])
{{--Title input--}}
<div class="title-input page-title clearfix">
<div class="edit-area flex-fill flex">
{{--WYSIWYG Editor--}}
- @if(setting('app-editor') === 'wysiwyg')
+ @if($editor === 'wysiwyg')
@include('pages.parts.wysiwyg-editor', ['model' => $model])
@endif
{{--Markdown Editor--}}
- @if(setting('app-editor') === 'markdown')
+ @if($editor === 'markdown')
@include('pages.parts.markdown-editor', ['model' => $model])
@endif
</div>
+ {{--Mobile Save Button--}}
<button type="submit"
id="save-button-mobile"
title="{{ trans('entities.pages_save') }}"
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>
\ No newline at end of file
+@push('head')
+ <script src="{{ url('/libs/tinymce/tinymce.min.js?ver=5.10.2') }}" nonce="{{ $cspNonce }}"></script>
+@endpush
+
<div component="wysiwyg-editor"
option:wysiwyg-editor:language="{{ config('app.lang') }}"
option:wysiwyg-editor:page-id="{{ $model->id ?? 0 }}"
<table class="table">
<tr>
- <th width="3%">{{ trans('entities.pages_revisions_number') }}</th>
- <th width="23%">{{ trans('entities.pages_name') }}</th>
- <th colspan="2" width="8%">{{ trans('entities.pages_revisions_created_by') }}</th>
- <th width="15%">{{ trans('entities.pages_revisions_date') }}</th>
- <th width="25%">{{ trans('entities.pages_revisions_changelog') }}</th>
- <th width="20%">{{ trans('common.actions') }}</th>
+ <th width="40">{{ trans('entities.pages_revisions_number') }}</th>
+ <th>
+ {{ trans('entities.pages_name') }} / {{ trans('entities.pages_revisions_editor') }}
+ </th>
+ <th colspan="2">{{ trans('entities.pages_revisions_created_by') }} / {{ trans('entities.pages_revisions_date') }}</th>
+ <th>{{ trans('entities.pages_revisions_changelog') }}</th>
+ <th class="text-right">{{ trans('common.actions') }}</th>
</tr>
@foreach($page->revisions as $index => $revision)
<tr>
<td>{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}</td>
- <td>{{ $revision->name }}</td>
- <td style="line-height: 0;">
+ <td>
+ {{ $revision->name }}
+ <br>
+ <small class="text-muted">({{ $revision->markdown ? 'Markdown' : 'WYSIWYG' }})</small>
+ </td>
+ <td style="line-height: 0;" width="30">
@if($revision->createdBy)
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
@endif
</td>
- <td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif</td>
- <td><small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td>
- <td>{{ $revision->summary }}</td>
- <td class="actions">
+ <td width="260">
+ @if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
+ <br>
+ <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>
<span class="text-muted"> | </span>
<form action="{{ $revision->getUrl('/restore') }}" method="POST">
{!! csrf_field() !!}
<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>
</li>
</ul>
<form action="{{ $revision->getUrl('/delete/') }}" method="POST">
{!! csrf_field() !!}
<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>
</li>
</ul>
<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>
<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)
- <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
</ul>
</div>
</div>
</div>
- <div class="grid half gap-xl">
+ <div class="grid half gap-xl items-center">
<div>
- <label class="setting-list-label">{{ trans('settings.app_editor') }}</label>
- <p class="small">{{ trans('settings.app_editor_desc') }}</p>
+ <label class="setting-list-label" for="setting-app-editor">{{ trans('settings.app_default_editor') }}</label>
+ <p class="small">{{ trans('settings.app_default_editor_desc') }}</p>
</div>
- <div class="pt-xs">
+ <div>
<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') === 'markdown') selected @endif value="markdown">Markdown</option>
</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>
- <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>
</div>
- <div class="pt-xs">
+ <div>
<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') === 'books') selected @endif value="books">{{ trans('entities.books') }}</option>
<form action="{{ url('/settings/recycle-bin/empty') }}" method="POST">
{!! 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>
</div>
</div>
<div component="dropdown" class="dropdown-container">
<button type="button" refs="dropdown@toggle" class="button outline">{{ trans('common.actions') }}</button>
<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="block" href="{{ $deletion->getUrl('/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
+ <li><a class="text-item" href="{{ $deletion->getUrl('/restore') }}">{{ trans('settings.recycle_bin_restore') }}</a></li>
+ <li><a class="text-item" href="{{ $deletion->getUrl('/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
</ul>
</div>
</td>
<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' => '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>@include('settings.roles.parts.checkbox', ['permission' => 'settings-manage', 'label' => trans('settings.role_manage_settings')])</div>
use BookStack\Entities\Models\Page;
use BookStack\Uploads\Attachment;
use Illuminate\Http\UploadedFile;
+use Illuminate\Testing\AssertableJsonString;
use Tests\TestCase;
class AttachmentsApiTest extends TestCase
$attachment = Attachment::query()->orderByDesc('id')->where('name', '=', $details['name'])->firstOrFail();
$resp = $this->getJson("{$this->baseEndpoint}/{$attachment->id}");
-
$resp->assertStatus(200);
- $resp->assertJson([
+ $resp->assertHeader('Content-Type', 'application/json');
+
+ $json = new AssertableJsonString($resp->streamedContent());
+ $json->assertSubset([
'id' => $attachment->id,
'content' => base64_encode(file_get_contents(storage_path($attachment->path))),
'external' => false,
namespace Tests\Api;
use BookStack\Entities\Models\Book;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class BooksApiTest extends TestCase
{
use TestsApi;
- protected $baseEndpoint = '/api/books';
+ protected string $baseEndpoint = '/api/books';
public function test_index_endpoint_returns_expected_book()
{
$this->assertActivityExists('book_update', $book);
}
+ public function test_update_increments_updated_date_if_only_tags_are_sent()
+ {
+ $this->actingAsApiEditor();
+ $book = Book::visible()->first();
+ DB::table('books')->where('id', '=', $book->id)->update(['updated_at' => Carbon::now()->subWeek()]);
+
+ $details = [
+ 'tags' => [['name' => 'Category', 'value' => 'Testing']]
+ ];
+
+ $this->putJson($this->baseEndpoint . "/{$book->id}", $details);
+ $book->refresh();
+ $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $book->updated_at->unix());
+ }
+
public function test_delete_endpoint()
{
$this->actingAsApiEditor();
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class ChaptersApiTest extends TestCase
{
use TestsApi;
- protected $baseEndpoint = '/api/chapters';
+ protected string $baseEndpoint = '/api/chapters';
public function test_index_endpoint_returns_expected_chapter()
{
$this->assertActivityExists('chapter_update', $chapter);
}
+ public function test_update_increments_updated_date_if_only_tags_are_sent()
+ {
+ $this->actingAsApiEditor();
+ $chapter = Chapter::visible()->first();
+ DB::table('chapters')->where('id', '=', $chapter->id)->update(['updated_at' => Carbon::now()->subWeek()]);
+
+ $details = [
+ 'tags' => [['name' => 'Category', 'value' => 'Testing']]
+ ];
+
+ $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
+ $chapter->refresh();
+ $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $chapter->updated_at->unix());
+ }
+
public function test_delete_endpoint()
{
$this->actingAsApiEditor();
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class PagesApiTest extends TestCase
{
use TestsApi;
- protected $baseEndpoint = '/api/pages';
+ protected string $baseEndpoint = '/api/pages';
public function test_index_endpoint_returns_expected_page()
{
$this->assertEquals($originalContent, $page->html);
}
+ public function test_update_increments_updated_date_if_only_tags_are_sent()
+ {
+ $this->actingAsApiEditor();
+ $page = Page::visible()->first();
+ DB::table('pages')->where('id', '=', $page->id)->update(['updated_at' => Carbon::now()->subWeek()]);
+
+ $details = [
+ 'tags' => [['name' => 'Category', 'value' => 'Testing']]
+ ];
+
+ $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
+ $resp->assertOk();
+
+ $page->refresh();
+ $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $page->updated_at->unix());
+ }
+
public function test_delete_endpoint()
{
$this->actingAsApiEditor();
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class ShelvesApiTest extends TestCase
{
use TestsApi;
- protected $baseEndpoint = '/api/shelves';
+ protected string $baseEndpoint = '/api/shelves';
public function test_index_endpoint_returns_expected_shelf()
{
$this->assertActivityExists('bookshelf_update', $shelf);
}
+ public function test_update_increments_updated_date_if_only_tags_are_sent()
+ {
+ $this->actingAsApiEditor();
+ $shelf = Bookshelf::visible()->first();
+ DB::table('bookshelves')->where('id', '=', $shelf->id)->update(['updated_at' => Carbon::now()->subWeek()]);
+
+ $details = [
+ 'tags' => [['name' => 'Category', 'value' => 'Testing']]
+ ];
+
+ $this->putJson($this->baseEndpoint . "/{$shelf->id}", $details);
+ $shelf->refresh();
+ $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $shelf->updated_at->unix());
+ }
+
public function test_update_only_assigns_books_if_param_provided()
{
$this->actingAsApiEditor();
$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->asAdmin()->get($this->page->getUrl() . '/edit')
- ->assertElementExists('#html-editor');
+ $resp = $this->asAdmin()->get($this->page->book->getUrl('/create-page'));
+ $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->asAdmin()->get($this->page->getUrl() . '/edit')
+
+ $resp = $this->asAdmin()->get($this->page->book->getUrl('/create-page'));
+ $this->followRedirects($resp)
->assertElementNotExists('#html-editor')
->assertElementExists('#markdown-editor');
}
public function test_markdown_content_given_to_editor()
{
- $this->setSettings(['app-editor' => 'markdown']);
-
$mdContent = '# hello. This is a test';
$this->page->markdown = $mdContent;
+ $this->page->editor = 'markdown';
$this->page->save();
- $this->asAdmin()->get($this->page->getUrl() . '/edit')
+ $this->asAdmin()->get($this->page->getUrl('/edit'))
->assertElementContains('[name="markdown"]', $mdContent);
}
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')
->assertElementContains('[name="markdown"]', $this->page->html);
}
$resp = $this->get($draft->getUrl('/edit'));
$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);
+ }
+
}
$revisionCount = $page->revisions()->count();
$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)');
+ }
}
$pageGet->assertSee($attachment->getUrl());
$attachmentGet = $this->get($attachment->getUrl());
- $attachmentGet->assertSee('Hi, This is a test file for testing the upload process.');
+ $content = $attachmentGet->streamedContent();
+ $this->assertStringContainsString('Hi, This is a test file for testing the upload process.', $content);
$this->deleteUploads();
}
$editor = $this->getEditor();
$resp = $this->actingAs($editor)->get($page->getUrl('/edit'));
- $resp->assertSee('drawio-url="https://embed.diagrams.net/?embed=1&proto=json&spin=1"', false);
+ $resp->assertSee('drawio-url="https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1"', false);
config()->set('services.drawio', false);
$resp = $this->actingAs($editor)->get($page->getUrl('/edit'));