mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-10 19:06:16 +03:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a1a6fb16 | ||
|
|
882c609296 | ||
|
|
77ad819970 | ||
|
|
2835e5be93 | ||
|
|
856fca8289 | ||
|
|
48d0095aa2 | ||
|
|
176a0dcd59 | ||
|
|
94b0f70bfa | ||
|
|
36d7ff77a9 | ||
|
|
fb16ac326f | ||
|
|
5947f59a04 | ||
|
|
1843d80fb7 | ||
|
|
08b2a77d41 | ||
|
|
3e8e9a23cf | ||
|
|
1253711c7d | ||
|
|
963d8f4693 | ||
|
|
0de4d6d223 | ||
|
|
06f694bad2 |
@@ -42,7 +42,7 @@ APP_TIMEZONE=UTC
|
||||
# overrides can be made. Defaults to disabled.
|
||||
APP_THEME=false
|
||||
|
||||
# Trusted Proxies
|
||||
# Trusted proxies
|
||||
# Used to indicate trust of systems that proxy to the application so
|
||||
# certain header values (Such as "X-Forwarded-For") can be used from the
|
||||
# incoming proxy request to provide origin detail.
|
||||
@@ -58,6 +58,13 @@ DB_DATABASE=database_database
|
||||
DB_USERNAME=database_username
|
||||
DB_PASSWORD=database_user_password
|
||||
|
||||
# MySQL specific connection options
|
||||
# Path to Certificate Authority (CA) certificate file for your MySQL instance.
|
||||
# When this option is used host name identity verification will be performed
|
||||
# which checks the hostname, used by the client, against names within the
|
||||
# certificate itself (Common Name or Subject Alternative Name).
|
||||
MYSQL_ATTR_SSL_CA="/path/to/ca.pem"
|
||||
|
||||
# Mail system to use
|
||||
# Can be 'smtp' or 'sendmail'
|
||||
MAIL_DRIVER=smtp
|
||||
@@ -324,6 +331,13 @@ ALLOW_UNTRUSTED_SERVER_FETCHING=false
|
||||
# Setting this option will also auto-adjust cookies to be SameSite=None.
|
||||
ALLOWED_IFRAME_HOSTS=null
|
||||
|
||||
# A list of sources/hostnames that can be loaded within iframes within BookStack.
|
||||
# Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
# Can be set to a lone "*" to allow all sources for iframe content (Not advised).
|
||||
# Defaults to a set of common services.
|
||||
# Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
|
||||
ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com"
|
||||
|
||||
# The default and maximum item-counts for listing API requests.
|
||||
API_DEFAULT_ITEM_COUNT=100
|
||||
API_MAX_ITEM_COUNT=500
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -35,6 +35,15 @@ body:
|
||||
description: Provide any additional context and screenshots here to help us solve this issue
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: browserdetails
|
||||
attributes:
|
||||
label: Browser Details
|
||||
description: |
|
||||
If this is an issue that occurs when using the BookStack interface, please provide details of the browser used which presents the reported issue.
|
||||
placeholder: (eg. Firefox 97 (64-bit) on Windows 11)
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: bsversion
|
||||
attributes:
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -33,8 +33,7 @@ body:
|
||||
attributes:
|
||||
label: Have you searched for an existing open/closed issue?
|
||||
description: |
|
||||
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue)
|
||||
for any existing issues that cover the fundemental benefit/goal of your request.
|
||||
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundemental benefit/goal of your request.
|
||||
options:
|
||||
- label: I have searched for existing issues and none cover my fundemental request
|
||||
required: true
|
||||
|
||||
2
.github/translators.txt
vendored
2
.github/translators.txt
vendored
@@ -230,3 +230,5 @@ roncallyt :: Portuguese, Brazilian
|
||||
goegol :: Dutch
|
||||
msevgen :: Turkish
|
||||
Khroners :: French
|
||||
MASOUD HOSSEINY (masoudme) :: Persian
|
||||
Thomerson Roncally (roncallyt) :: Portuguese, Brazilian
|
||||
|
||||
@@ -57,6 +57,13 @@ return [
|
||||
// Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
'iframe_hosts' => env('ALLOWED_IFRAME_HOSTS', null),
|
||||
|
||||
// A list of sources/hostnames that can be loaded within iframes within BookStack.
|
||||
// Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
// Can be set to a lone "*" to allow all sources for iframe content (Not advised).
|
||||
// Defaults to a set of common services.
|
||||
// Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
|
||||
'iframe_sources' => env('ALLOWED_IFRAME_SOURCES', 'https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com'),
|
||||
|
||||
// Application timezone for back-end date functions.
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
|
||||
use BookStack\Uploads\ImageService;
|
||||
use BookStack\Util\CspService;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMXPath;
|
||||
@@ -15,16 +16,18 @@ use Throwable;
|
||||
|
||||
class ExportFormatter
|
||||
{
|
||||
protected $imageService;
|
||||
protected $pdfGenerator;
|
||||
protected ImageService $imageService;
|
||||
protected PdfGenerator $pdfGenerator;
|
||||
protected CspService $cspService;
|
||||
|
||||
/**
|
||||
* ExportService constructor.
|
||||
*/
|
||||
public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator)
|
||||
public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator, CspService $cspService)
|
||||
{
|
||||
$this->imageService = $imageService;
|
||||
$this->pdfGenerator = $pdfGenerator;
|
||||
$this->cspService = $cspService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,8 +40,9 @@ class ExportFormatter
|
||||
{
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$pageHtml = view('pages.export', [
|
||||
'page' => $page,
|
||||
'format' => 'html',
|
||||
'page' => $page,
|
||||
'format' => 'html',
|
||||
'cspContent' => $this->cspService->getCspMetaTagValue(),
|
||||
])->render();
|
||||
|
||||
return $this->containHtml($pageHtml);
|
||||
@@ -56,9 +60,10 @@ class ExportFormatter
|
||||
$page->html = (new PageContent($page))->render();
|
||||
});
|
||||
$html = view('chapters.export', [
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages,
|
||||
'format' => 'html',
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages,
|
||||
'format' => 'html',
|
||||
'cspContent' => $this->cspService->getCspMetaTagValue(),
|
||||
])->render();
|
||||
|
||||
return $this->containHtml($html);
|
||||
@@ -76,6 +81,7 @@ class ExportFormatter
|
||||
'book' => $book,
|
||||
'bookChildren' => $bookTree,
|
||||
'format' => 'html',
|
||||
'cspContent' => $this->cspService->getCspMetaTagValue(),
|
||||
])->render();
|
||||
|
||||
return $this->containHtml($html);
|
||||
|
||||
@@ -239,6 +239,9 @@ class PageContent
|
||||
$html .= $doc->saveHTML($childNode);
|
||||
}
|
||||
|
||||
// Perform required string-level tweaks
|
||||
$html = str_replace(' ', ' ', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ use Illuminate\Http\Request;
|
||||
|
||||
class ApplyCspRules
|
||||
{
|
||||
/**
|
||||
* @var CspService
|
||||
*/
|
||||
protected $cspService;
|
||||
protected CspService $cspService;
|
||||
|
||||
public function __construct(CspService $cspService)
|
||||
{
|
||||
@@ -35,10 +32,8 @@ class ApplyCspRules
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
$this->cspService->setFrameAncestors($response);
|
||||
$this->cspService->setScriptSrc($response);
|
||||
$this->cspService->setObjectSrc($response);
|
||||
$this->cspService->setBaseUri($response);
|
||||
$cspHeader = $this->cspService->getCspHeader();
|
||||
$response->headers->set('Content-Security-Policy', $cspHeader, false);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
namespace BookStack\Util;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CspService
|
||||
{
|
||||
/** @var string */
|
||||
protected $nonce;
|
||||
protected string $nonce;
|
||||
|
||||
public function __construct(string $nonce = '')
|
||||
{
|
||||
@@ -24,37 +22,34 @@ class CspService
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP 'script-src' headers to restrict the forms of script that can
|
||||
* run on the page.
|
||||
* Get the CSP headers for the application
|
||||
*/
|
||||
public function setScriptSrc(Response $response)
|
||||
public function getCspHeader(): string
|
||||
{
|
||||
if (config('app.allow_content_scripts')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parts = [
|
||||
'http:',
|
||||
'https:',
|
||||
'\'nonce-' . $this->nonce . '\'',
|
||||
'\'strict-dynamic\'',
|
||||
$headers = [
|
||||
$this->getFrameAncestors(),
|
||||
$this->getFrameSrc(),
|
||||
$this->getScriptSrc(),
|
||||
$this->getObjectSrc(),
|
||||
$this->getBaseUri(),
|
||||
];
|
||||
|
||||
$value = 'script-src ' . implode(' ', $parts);
|
||||
$response->headers->set('Content-Security-Policy', $value, false);
|
||||
return implode('; ', array_filter($headers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP "frame-ancestors" headers to restrict the hosts that BookStack can be
|
||||
* iframed within. Also adjusts the cookie samesite options so that cookies will
|
||||
* operate in the third-party context.
|
||||
* Get the CSP rules for the application for a HTML meta tag.
|
||||
*/
|
||||
public function setFrameAncestors(Response $response)
|
||||
public function getCspMetaTagValue(): string
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeHosts();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
$cspValue = 'frame-ancestors ' . implode(' ', $iframeHosts);
|
||||
$response->headers->set('Content-Security-Policy', $cspValue, false);
|
||||
$headers = [
|
||||
$this->getFrameSrc(),
|
||||
$this->getScriptSrc(),
|
||||
$this->getObjectSrc(),
|
||||
$this->getBaseUri(),
|
||||
];
|
||||
|
||||
return implode('; ', array_filter($headers));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,25 +61,65 @@ class CspService
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP 'object-src' headers to restrict the types of dynamic content
|
||||
* that can be embedded on the page.
|
||||
* Create CSP 'script-src' rule to restrict the forms of script that can run on the page.
|
||||
*/
|
||||
public function setObjectSrc(Response $response)
|
||||
protected function getScriptSrc(): string
|
||||
{
|
||||
if (config('app.allow_content_scripts')) {
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
$response->headers->set('Content-Security-Policy', 'object-src \'self\'', false);
|
||||
$parts = [
|
||||
'http:',
|
||||
'https:',
|
||||
'\'nonce-' . $this->nonce . '\'',
|
||||
'\'strict-dynamic\'',
|
||||
];
|
||||
|
||||
return 'script-src ' . implode(' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP 'base-uri' headers to restrict what base tags can be set on
|
||||
* Create CSP "frame-ancestors" rule to restrict the hosts that BookStack can be iframed within.
|
||||
*/
|
||||
protected function getFrameAncestors(): string
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeHosts();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
return 'frame-ancestors ' . implode(' ', $iframeHosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP "frame-src" rule to restrict what hosts/sources can be loaded
|
||||
* within iframes to provide an allow-list-style approach to iframe content.
|
||||
*/
|
||||
protected function getFrameSrc(): string
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeSources();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
return 'frame-src ' . implode(' ', $iframeHosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP 'object-src' rule to restrict the types of dynamic content
|
||||
* that can be embedded on the page.
|
||||
*/
|
||||
protected function getObjectSrc(): string
|
||||
{
|
||||
if (config('app.allow_content_scripts')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return "object-src 'self'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP 'base-uri' rule to restrict what base tags can be set on
|
||||
* the page to prevent manipulation of relative links.
|
||||
*/
|
||||
public function setBaseUri(Response $response)
|
||||
protected function getBaseUri(): string
|
||||
{
|
||||
$response->headers->set('Content-Security-Policy', 'base-uri \'self\'', false);
|
||||
return "base-uri 'self'";
|
||||
}
|
||||
|
||||
protected function getAllowedIframeHosts(): array
|
||||
@@ -93,4 +128,21 @@ class CspService
|
||||
|
||||
return array_filter(explode(' ', $hosts));
|
||||
}
|
||||
|
||||
protected function getAllowedIframeSources(): array
|
||||
{
|
||||
$sources = config('app.iframe_sources', '');
|
||||
$hosts = array_filter(explode(' ', $sources));
|
||||
|
||||
// Extract drawing service url to allow embedding if active
|
||||
$drawioConfigValue = config('services.drawio');
|
||||
if ($drawioConfigValue) {
|
||||
$drawioSource = is_string($drawioConfigValue) ? $drawioConfigValue : 'https://embed.diagrams.net/';
|
||||
$drawioSourceParsed = parse_url($drawioSource);
|
||||
$drawioHost = $drawioSourceParsed['scheme'] . '://' . $drawioSourceParsed['host'];
|
||||
$hosts[] = $drawioHost;
|
||||
}
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
}
|
||||
|
||||
6
public/dist/app.js
vendored
6
public/dist/app.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/export-styles.css
vendored
2
public/dist/export-styles.css
vendored
File diff suppressed because one or more lines are too long
2
public/dist/styles.css
vendored
2
public/dist/styles.css
vendored
File diff suppressed because one or more lines are too long
@@ -63,7 +63,7 @@ Below is a high-level road map view for BookStack to provide a sense of directio
|
||||
|
||||
- **Platform REST API** - *(Most actions implemented, maturing)*
|
||||
- *A REST API covering, at minimum, control of core content models (Books, Chapters, Pages) for automation and platform extension.*
|
||||
- **Editor Alignment & Review** - *(Started)*
|
||||
- **Editor Alignment & Review** - *(In Progress)*
|
||||
- *Review the page editors with goal of achieving increased interoperability & feature parity while also considering collaborative editing potential.*
|
||||
- **Permission System Review**
|
||||
- *Improvement in how permissions are applied and a review of the efficiency of the permission & roles system.*
|
||||
|
||||
@@ -59,12 +59,10 @@ class CodeEditor {
|
||||
this.languageInput.value = language;
|
||||
this.callback = callback;
|
||||
|
||||
this.show();
|
||||
this.updateEditorMode(language);
|
||||
|
||||
window.importVersioned('code').then(Code => {
|
||||
Code.setContent(this.editor, code);
|
||||
});
|
||||
this.show()
|
||||
.then(() => this.updateEditorMode(language))
|
||||
.then(() => window.importVersioned('code'))
|
||||
.then(Code => Code.setContent(this.editor, code));
|
||||
}
|
||||
|
||||
async show() {
|
||||
|
||||
@@ -221,11 +221,15 @@ export function build(options) {
|
||||
// Build toolbar content
|
||||
const {toolbar, groupButtons: toolBarGroupButtons} = buildToolbar(options);
|
||||
|
||||
// BookStack Version
|
||||
const version = document.querySelector('script[src*="/dist/app.js"]').getAttribute('src').split('?version=')[1];
|
||||
|
||||
// Return config object
|
||||
return {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
selector: '#html-editor',
|
||||
cache_suffix: '?version=' + version,
|
||||
content_css: [
|
||||
window.baseUrl('/dist/styles.css'),
|
||||
],
|
||||
@@ -239,6 +243,7 @@ export function build(options) {
|
||||
remove_script_host: false,
|
||||
document_base_url: window.baseUrl('/'),
|
||||
end_container_on_empty_block: true,
|
||||
remove_trailing_brs: false,
|
||||
statusbar: false,
|
||||
menubar: false,
|
||||
paste_data_images: false,
|
||||
|
||||
@@ -91,15 +91,35 @@ function defineCodeBlockCustomElement(editor) {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const connectedTime = Date.now();
|
||||
if (this.cm) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cleanChildContent();
|
||||
|
||||
const container = this.shadowRoot.querySelector('.CodeMirrorContainer');
|
||||
importVersioned('code').then(Code => {
|
||||
const renderCodeMirror = (Code) => {
|
||||
this.cm = Code.wysiwygView(container, this.getContent(), this.getLanguage());
|
||||
Code.updateLayout(this.cm);
|
||||
};
|
||||
|
||||
window.importVersioned('code').then((Code) => {
|
||||
const timeout = (Date.now() - connectedTime < 20) ? 20 : 0;
|
||||
setTimeout(() => renderCodeMirror(Code), timeout);
|
||||
});
|
||||
}
|
||||
|
||||
cleanChildContent() {
|
||||
const pre = this.querySelector('pre');
|
||||
if (!pre) return;
|
||||
|
||||
for (const preChild of pre.childNodes) {
|
||||
if (preChild.nodeName === '#text' && preChild.textContent === '') {
|
||||
preChild.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
win.customElements.define('code-block', CodeBlockElement);
|
||||
@@ -130,15 +150,13 @@ function register(editor, url) {
|
||||
} else {
|
||||
const textContent = editor.selection.getContent({format: 'text'});
|
||||
showPopup(editor, textContent, '', (newCode, newLang) => {
|
||||
const wrap = doc.createElement('code-block');
|
||||
const pre = doc.createElement('pre');
|
||||
const code = doc.createElement('code');
|
||||
code.classList.add(`language-${newLang}`);
|
||||
code.innerText = newCode;
|
||||
pre.append(code);
|
||||
wrap.append(pre);
|
||||
|
||||
editor.insertContent(wrap.outerHTML);
|
||||
editor.insertContent(pre.outerHTML);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -168,7 +186,7 @@ function register(editor, url) {
|
||||
|
||||
editor.parser.addNodeFilter('code-block', function(elms) {
|
||||
for (const el of elms) {
|
||||
el.attr('content-editable', 'false');
|
||||
el.attr('contenteditable', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,42 +7,42 @@
|
||||
*/
|
||||
return [
|
||||
// General editor terms
|
||||
'general' => 'General',
|
||||
'advanced' => 'Advanced',
|
||||
'none' => 'None',
|
||||
'cancel' => 'Cancel',
|
||||
'save' => 'Save',
|
||||
'close' => 'Close',
|
||||
'undo' => 'Undo',
|
||||
'redo' => 'Redo',
|
||||
'left' => 'Left',
|
||||
'center' => 'Center',
|
||||
'right' => 'Right',
|
||||
'top' => 'Top',
|
||||
'middle' => 'Middle',
|
||||
'bottom' => 'Bottom',
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'More' => 'More',
|
||||
'general' => 'عمومی',
|
||||
'advanced' => 'پیشرفته',
|
||||
'none' => 'هیچ کدام',
|
||||
'cancel' => 'لغو',
|
||||
'save' => 'ذخیره',
|
||||
'close' => 'بستن',
|
||||
'undo' => 'برگشت',
|
||||
'redo' => 'از نو',
|
||||
'left' => 'چپ',
|
||||
'center' => 'مرکز',
|
||||
'right' => 'راست',
|
||||
'top' => 'بالا',
|
||||
'middle' => 'میانه',
|
||||
'bottom' => 'پایین',
|
||||
'width' => 'عرض',
|
||||
'height' => 'ارتفاع',
|
||||
'More' => 'بیشتر',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
'header_large' => 'Large Header',
|
||||
'header_medium' => 'Medium Header',
|
||||
'header_small' => 'Small Header',
|
||||
'header_tiny' => 'Tiny Header',
|
||||
'paragraph' => 'Paragraph',
|
||||
'blockquote' => 'Blockquote',
|
||||
'inline_code' => 'Inline code',
|
||||
'callouts' => 'Callouts',
|
||||
'callout_information' => 'Information',
|
||||
'callout_success' => 'Success',
|
||||
'callout_warning' => 'Warning',
|
||||
'callout_danger' => 'Danger',
|
||||
'bold' => 'Bold',
|
||||
'italic' => 'Italic',
|
||||
'underline' => 'Underline',
|
||||
'strikethrough' => 'Strikethrough',
|
||||
'formats' => 'الگو',
|
||||
'header_large' => 'عنوان بزرگ',
|
||||
'header_medium' => 'عنوان متوسط',
|
||||
'header_small' => 'عنوان کوچک',
|
||||
'header_tiny' => 'هدر کوچک',
|
||||
'paragraph' => 'پاراگراف',
|
||||
'blockquote' => 'نقل قول',
|
||||
'inline_code' => 'کد درون خطی',
|
||||
'callouts' => 'تعليق تفسيري',
|
||||
'callout_information' => 'اطلاعات',
|
||||
'callout_success' => 'موفق',
|
||||
'callout_warning' => 'هشدار',
|
||||
'callout_danger' => 'خطر',
|
||||
'bold' => 'توپر',
|
||||
'italic' => 'حروف کج(ایتالیک)',
|
||||
'underline' => 'زیرخط',
|
||||
'strikethrough' => 'خط خورده',
|
||||
'superscript' => 'Superscript',
|
||||
'subscript' => 'Subscript',
|
||||
'text_color' => 'Text color',
|
||||
|
||||
@@ -131,12 +131,12 @@ return [
|
||||
'open_link' => 'Apri collegamento in...',
|
||||
'open_link_current' => 'Finestra corrente',
|
||||
'open_link_new' => 'Nuova finestra',
|
||||
'insert_collapsible' => 'Insert collapsible block',
|
||||
'collapsible_unwrap' => 'Unwrap',
|
||||
'edit_label' => 'Edit label',
|
||||
'toggle_open_closed' => 'Toggle open/closed',
|
||||
'collapsible_edit' => 'Edit collapsible block',
|
||||
'toggle_label' => 'Toggle label',
|
||||
'insert_collapsible' => 'Inserisci blocco collassabile',
|
||||
'collapsible_unwrap' => 'Espandi',
|
||||
'edit_label' => 'Modifica etichetta',
|
||||
'toggle_open_closed' => 'Espandi/Comprimi',
|
||||
'collapsible_edit' => 'Modifica blocco collassabile',
|
||||
'toggle_label' => 'Attiva/Disattiva etichetta',
|
||||
|
||||
// About view
|
||||
'about_title' => 'Informazioni sull\'editor di WYSIWYG',
|
||||
|
||||
@@ -7,148 +7,148 @@
|
||||
*/
|
||||
return [
|
||||
// General editor terms
|
||||
'general' => 'General',
|
||||
'advanced' => 'Advanced',
|
||||
'none' => 'None',
|
||||
'cancel' => 'Cancel',
|
||||
'save' => 'Save',
|
||||
'close' => 'Close',
|
||||
'undo' => 'Undo',
|
||||
'redo' => 'Redo',
|
||||
'left' => 'Left',
|
||||
'center' => 'Center',
|
||||
'right' => 'Right',
|
||||
'top' => 'Top',
|
||||
'middle' => 'Middle',
|
||||
'bottom' => 'Bottom',
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'More' => 'More',
|
||||
'general' => '一般',
|
||||
'advanced' => '詳細設定',
|
||||
'none' => 'なし',
|
||||
'cancel' => '取消',
|
||||
'save' => '保存',
|
||||
'close' => '閉じる',
|
||||
'undo' => '元に戻す',
|
||||
'redo' => 'やり直し',
|
||||
'left' => '左寄せ',
|
||||
'center' => '中央揃え',
|
||||
'right' => '右寄せ',
|
||||
'top' => '上',
|
||||
'middle' => '中央',
|
||||
'bottom' => '下',
|
||||
'width' => '幅',
|
||||
'height' => '高さ',
|
||||
'More' => 'さらに表示',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
'header_large' => 'Large Header',
|
||||
'header_medium' => 'Medium Header',
|
||||
'header_small' => 'Small Header',
|
||||
'header_tiny' => 'Tiny Header',
|
||||
'paragraph' => 'Paragraph',
|
||||
'blockquote' => 'Blockquote',
|
||||
'inline_code' => 'Inline code',
|
||||
'callouts' => 'Callouts',
|
||||
'callout_information' => 'Information',
|
||||
'callout_success' => 'Success',
|
||||
'callout_warning' => 'Warning',
|
||||
'callout_danger' => 'Danger',
|
||||
'bold' => 'Bold',
|
||||
'italic' => 'Italic',
|
||||
'underline' => 'Underline',
|
||||
'strikethrough' => 'Strikethrough',
|
||||
'superscript' => 'Superscript',
|
||||
'subscript' => 'Subscript',
|
||||
'text_color' => 'Text color',
|
||||
'custom_color' => 'Custom color',
|
||||
'remove_color' => 'Remove color',
|
||||
'background_color' => 'Background color',
|
||||
'align_left' => 'Align left',
|
||||
'align_center' => 'Align center',
|
||||
'align_right' => 'Align right',
|
||||
'align_justify' => 'Align justify',
|
||||
'list_bullet' => 'Bullet list',
|
||||
'list_numbered' => 'Numbered list',
|
||||
'indent_increase' => 'Increase indent',
|
||||
'indent_decrease' => 'Decrease indent',
|
||||
'table' => 'Table',
|
||||
'insert_image' => 'Insert image',
|
||||
'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',
|
||||
'formats' => '書式',
|
||||
'header_large' => '大見出し',
|
||||
'header_medium' => '中見出し',
|
||||
'header_small' => '小見出し',
|
||||
'header_tiny' => '極小見出し',
|
||||
'paragraph' => '段落',
|
||||
'blockquote' => '引用',
|
||||
'inline_code' => 'インラインコード',
|
||||
'callouts' => 'コールアウト',
|
||||
'callout_information' => '情報',
|
||||
'callout_success' => '成功',
|
||||
'callout_warning' => '警告',
|
||||
'callout_danger' => '危険',
|
||||
'bold' => '太字',
|
||||
'italic' => '斜体',
|
||||
'underline' => '下線',
|
||||
'strikethrough' => '取消線',
|
||||
'superscript' => '上付き',
|
||||
'subscript' => '下付き',
|
||||
'text_color' => 'テキストの色',
|
||||
'custom_color' => 'カスタムカラー',
|
||||
'remove_color' => '色設定を解除',
|
||||
'background_color' => '背景色',
|
||||
'align_left' => '左揃え',
|
||||
'align_center' => '中央揃え',
|
||||
'align_right' => '右揃え',
|
||||
'align_justify' => '両端揃え',
|
||||
'list_bullet' => '箇条書き',
|
||||
'list_numbered' => '番号付き箇条書き',
|
||||
'indent_increase' => 'インデントを増やす',
|
||||
'indent_decrease' => 'インデントを減らす',
|
||||
'table' => '表',
|
||||
'insert_image' => '画像の挿入',
|
||||
'insert_image_title' => '画像の挿入・編集',
|
||||
'insert_link' => 'リンクの挿入・編集',
|
||||
'insert_link_title' => 'リンクの挿入・編集',
|
||||
'insert_horizontal_line' => '水平線を挿入',
|
||||
'insert_code_block' => 'コードブロックを挿入',
|
||||
'insert_drawing' => '描画を挿入・編集',
|
||||
'drawing_manager' => '描画マネージャー',
|
||||
'insert_media' => 'メディアの挿入・編集',
|
||||
'insert_media_title' => 'メディアの挿入・編集',
|
||||
'clear_formatting' => '書式をクリア',
|
||||
'source_code' => 'ソースコード',
|
||||
'source_code_title' => 'ソースコード',
|
||||
'fullscreen' => '全画面表示',
|
||||
'image_options' => '画像オプション',
|
||||
|
||||
// Tables
|
||||
'table_properties' => 'Table properties',
|
||||
'table_properties_title' => 'Table Properties',
|
||||
'delete_table' => 'Delete table',
|
||||
'insert_row_before' => 'Insert row before',
|
||||
'insert_row_after' => 'Insert row after',
|
||||
'delete_row' => 'Delete row',
|
||||
'insert_column_before' => 'Insert column before',
|
||||
'insert_column_after' => 'Insert column after',
|
||||
'delete_column' => 'Delete column',
|
||||
'table_cell' => 'Cell',
|
||||
'table_row' => 'Row',
|
||||
'table_column' => 'Column',
|
||||
'cell_properties' => 'Cell properties',
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
'vertical_align' => 'Vertical align',
|
||||
'border_width' => 'Border width',
|
||||
'border_style' => 'Border style',
|
||||
'border_color' => 'Border color',
|
||||
'row_properties' => 'Row properties',
|
||||
'row_properties_title' => 'Row Properties',
|
||||
'cut_row' => 'Cut row',
|
||||
'copy_row' => 'Copy row',
|
||||
'paste_row_before' => 'Paste row before',
|
||||
'paste_row_after' => 'Paste row after',
|
||||
'row_type' => 'Row type',
|
||||
'row_type_header' => 'Header',
|
||||
'row_type_body' => 'Body',
|
||||
'row_type_footer' => 'Footer',
|
||||
'alignment' => 'Alignment',
|
||||
'cut_column' => 'Cut column',
|
||||
'copy_column' => 'Copy column',
|
||||
'paste_column_before' => 'Paste column before',
|
||||
'paste_column_after' => 'Paste column after',
|
||||
'cell_padding' => 'Cell padding',
|
||||
'cell_spacing' => 'Cell spacing',
|
||||
'caption' => 'Caption',
|
||||
'show_caption' => 'Show caption',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'table_properties' => '表の詳細設定',
|
||||
'table_properties_title' => '表の詳細設定',
|
||||
'delete_table' => '表の削除',
|
||||
'insert_row_before' => '上側に行を挿入',
|
||||
'insert_row_after' => '下側に行を挿入',
|
||||
'delete_row' => '行の削除',
|
||||
'insert_column_before' => '左側に列を挿入',
|
||||
'insert_column_after' => '右側に列を挿入',
|
||||
'delete_column' => '列の削除',
|
||||
'table_cell' => 'セル',
|
||||
'table_row' => '行',
|
||||
'table_column' => '列',
|
||||
'cell_properties' => 'セルの詳細設定',
|
||||
'cell_properties_title' => 'セルの詳細設定',
|
||||
'cell_type' => 'セルタイプ',
|
||||
'cell_type_cell' => 'セル',
|
||||
'cell_type_header' => 'ヘッダーセル',
|
||||
'table_row_group' => '行グループ',
|
||||
'table_column_group' => '列グループ',
|
||||
'horizontal_align' => '水平方向の配置',
|
||||
'vertical_align' => '垂直方向の配置',
|
||||
'border_width' => '枠線幅',
|
||||
'border_style' => '枠線スタイル',
|
||||
'border_color' => '枠線の色',
|
||||
'row_properties' => '行の詳細設定',
|
||||
'row_properties_title' => '行の詳細設定',
|
||||
'cut_row' => '行の切り取り',
|
||||
'copy_row' => '行のコピー',
|
||||
'paste_row_before' => '上側に行を貼り付け',
|
||||
'paste_row_after' => '下側に行を貼り付け',
|
||||
'row_type' => '行タイプ',
|
||||
'row_type_header' => 'ヘッダー',
|
||||
'row_type_body' => 'ボディー',
|
||||
'row_type_footer' => 'フッター',
|
||||
'alignment' => '配置',
|
||||
'cut_column' => '列の切り取り',
|
||||
'copy_column' => '列のコピー',
|
||||
'paste_column_before' => '左側に列を貼り付け',
|
||||
'paste_column_after' => '右側に列を貼り付け',
|
||||
'cell_padding' => 'セル内余白(パディング)',
|
||||
'cell_spacing' => 'セルの間隔',
|
||||
'caption' => '表題',
|
||||
'show_caption' => 'キャプションの表示',
|
||||
'constrain' => '縦横比を保持する',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
'alt_desc' => 'Alternative description',
|
||||
'embed' => 'Embed',
|
||||
'paste_embed' => 'Paste your embed code below:',
|
||||
'url' => 'URL',
|
||||
'text_to_display' => 'Text to display',
|
||||
'title' => 'Title',
|
||||
'open_link' => 'Open link in...',
|
||||
'open_link_current' => 'Current window',
|
||||
'open_link_new' => 'New window',
|
||||
'insert_collapsible' => 'Insert collapsible block',
|
||||
'collapsible_unwrap' => 'Unwrap',
|
||||
'edit_label' => 'Edit label',
|
||||
'toggle_open_closed' => 'Toggle open/closed',
|
||||
'collapsible_edit' => 'Edit collapsible block',
|
||||
'toggle_label' => 'Toggle label',
|
||||
'source' => '画像のソース',
|
||||
'alt_desc' => '代替の説明文',
|
||||
'embed' => '埋め込み',
|
||||
'paste_embed' => '埋め込み用コードを下記に貼り付けてください。',
|
||||
'url' => 'リンク先URL',
|
||||
'text_to_display' => 'リンク元テキスト',
|
||||
'title' => 'タイトル',
|
||||
'open_link' => 'リンクの開き方...',
|
||||
'open_link_current' => '同じウィンドウ',
|
||||
'open_link_new' => '新規ウィンドウ',
|
||||
'insert_collapsible' => '折りたたみブロックを追加',
|
||||
'collapsible_unwrap' => 'ブロックの解除',
|
||||
'edit_label' => 'ラベルを編集',
|
||||
'toggle_open_closed' => '折りたたみ状態の切替',
|
||||
'collapsible_edit' => '折りたたみブロックを編集',
|
||||
'toggle_label' => 'ブロックのラベル',
|
||||
|
||||
// About view
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
'about_title' => 'WYSIWYGエディタについて',
|
||||
'editor_license' => 'エディタのライセンスと著作権',
|
||||
'editor_tiny_license' => 'このエディタはLGPL v2.1ライセンスの下で提供される:tinyLinkを利用して構築されています。',
|
||||
'editor_tiny_license_link' => 'TinyMCEの著作権およびライセンスの詳細は、こちらをご覧ください。',
|
||||
'save_continue' => 'ページを保存して続行',
|
||||
'callouts_cycle' => '(押し続けて種類を切り替え)',
|
||||
'shortcuts' => 'ショートカット',
|
||||
'shortcut' => 'ショートカット',
|
||||
'shortcuts_intro' => 'エディタでは次に示すショートカットが利用できます。',
|
||||
'windows_linux' => '(Windows/Linux)',
|
||||
'mac' => '(Mac)',
|
||||
'description' => 'Description',
|
||||
'description' => 'テンプレートの内容',
|
||||
];
|
||||
|
||||
@@ -32,7 +32,7 @@ return [
|
||||
'digits_between' => ':attributeは:min〜:maxである必要があります。',
|
||||
'email' => ':attributeは正しいEメールアドレスである必要があります。',
|
||||
'ends_with' => ':attributeは:valuesのいずれかで終わる必要があります。',
|
||||
'file' => 'The :attribute must be provided as a valid file.',
|
||||
'file' => ':attributeは有効なファイルである必要があります。',
|
||||
'filled' => ':attributeは必須です。',
|
||||
'gt' => [
|
||||
'numeric' => ':attributeは:valueより大きな値である必要があります。',
|
||||
|
||||
@@ -60,8 +60,8 @@ return [
|
||||
'webhook_delete_notification' => 'Вебхук успешно удален',
|
||||
|
||||
// Users
|
||||
'user_update_notification' => 'User successfully updated',
|
||||
'user_delete_notification' => 'User successfully removed',
|
||||
'user_update_notification' => 'Пользователь успешно обновлен',
|
||||
'user_delete_notification' => 'Пользователь успешно удален',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'прокомментировал',
|
||||
|
||||
@@ -15,30 +15,30 @@ return [
|
||||
'close' => 'Закрыть',
|
||||
'undo' => 'Отменить',
|
||||
'redo' => 'Повторить',
|
||||
'left' => 'Left',
|
||||
'center' => 'Center',
|
||||
'right' => 'Right',
|
||||
'top' => 'Top',
|
||||
'middle' => 'Middle',
|
||||
'bottom' => 'Bottom',
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'left' => 'Слева',
|
||||
'center' => 'По центру',
|
||||
'right' => 'Справа',
|
||||
'top' => 'Сверху',
|
||||
'middle' => 'Посередине',
|
||||
'bottom' => 'Снизу',
|
||||
'width' => 'Ширина',
|
||||
'height' => 'Высота',
|
||||
'More' => 'Еще',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
'header_large' => 'Крупный заголовок',
|
||||
'header_medium' => 'Средний заголовок',
|
||||
'header_small' => 'Небольшой заголовок',
|
||||
'header_tiny' => 'Маленький заголовок',
|
||||
'paragraph' => 'Абзац',
|
||||
'formats' => 'Форматы',
|
||||
'header_large' => 'Большой',
|
||||
'header_medium' => 'Средний',
|
||||
'header_small' => 'Маленький',
|
||||
'header_tiny' => 'Крошечный',
|
||||
'paragraph' => 'Обычный текст',
|
||||
'blockquote' => 'Цитата',
|
||||
'inline_code' => 'Встроенный код',
|
||||
'callouts' => 'Выноска',
|
||||
'callout_information' => 'Информация',
|
||||
'callout_success' => 'Успешно',
|
||||
'callout_success' => 'Успех',
|
||||
'callout_warning' => 'Предупреждение',
|
||||
'callout_danger' => 'Опасность',
|
||||
'callout_danger' => 'Ошибка',
|
||||
'bold' => 'Жирный',
|
||||
'italic' => 'Курсив',
|
||||
'underline' => 'Подчёркнутый',
|
||||
@@ -47,8 +47,8 @@ return [
|
||||
'subscript' => 'Подстрочный',
|
||||
'text_color' => 'Цвет текста',
|
||||
'custom_color' => 'Пользовательский цвет',
|
||||
'remove_color' => 'Убрать цвет',
|
||||
'background_color' => 'Фоновый цвет',
|
||||
'remove_color' => 'Удалить цвет',
|
||||
'background_color' => 'Цвет фона',
|
||||
'align_left' => 'По левому краю',
|
||||
'align_center' => 'По центру',
|
||||
'align_right' => 'По правому краю',
|
||||
@@ -71,7 +71,7 @@ return [
|
||||
'clear_formatting' => 'Очистить форматирование',
|
||||
'source_code' => 'Исходный код',
|
||||
'source_code_title' => 'Исходный код',
|
||||
'fullscreen' => 'Полный экран',
|
||||
'fullscreen' => 'Полноэкранный режим',
|
||||
'image_options' => 'Параметры изображения',
|
||||
|
||||
// Tables
|
||||
@@ -91,7 +91,7 @@ return [
|
||||
'cell_properties_title' => 'Свойства ячейки',
|
||||
'cell_type' => 'Тип ячейки',
|
||||
'cell_type_cell' => 'Ячейка',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'cell_type_header' => 'Заголовок ячейки',
|
||||
'table_row_group' => 'Объединить строки',
|
||||
'table_column_group' => 'Объединить столбцы',
|
||||
'horizontal_align' => 'Выровнять по горизонтали',
|
||||
@@ -107,44 +107,44 @@ return [
|
||||
'paste_row_after' => 'Вставить строку ниже',
|
||||
'row_type' => 'Тип строки',
|
||||
'row_type_header' => 'Заголовок',
|
||||
'row_type_body' => 'Body',
|
||||
'row_type_footer' => 'Footer',
|
||||
'row_type_body' => 'Тело',
|
||||
'row_type_footer' => 'Нижняя часть',
|
||||
'alignment' => 'Выравнивание',
|
||||
'cut_column' => 'Вырезать столбец',
|
||||
'copy_column' => 'Копировать столбец',
|
||||
'paste_column_before' => 'Вставить столбец слева',
|
||||
'paste_column_after' => 'Вставить столбец справа',
|
||||
'cell_padding' => 'Cell padding',
|
||||
'cell_spacing' => 'Cell spacing',
|
||||
'caption' => 'Caption',
|
||||
'show_caption' => 'Show caption',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'cell_padding' => 'Расстояние между границей и содержимым',
|
||||
'cell_spacing' => 'Расстояние между ячейками',
|
||||
'caption' => 'Подпись',
|
||||
'show_caption' => 'Показать подпись',
|
||||
'constrain' => 'Сохранять пропорции',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
'alt_desc' => 'Alternative description',
|
||||
'embed' => 'Embed',
|
||||
'paste_embed' => 'Paste your embed code below:',
|
||||
'source' => 'Источник',
|
||||
'alt_desc' => 'Альтернативное описание',
|
||||
'embed' => 'Код для вставки',
|
||||
'paste_embed' => 'Введите код для вставки ниже:',
|
||||
'url' => 'URL-адрес',
|
||||
'text_to_display' => 'Текст для отображения',
|
||||
'title' => 'Заголовок',
|
||||
'open_link' => 'Открыть ссылку в...',
|
||||
'open_link_current' => 'В текущем окне',
|
||||
'open_link_new' => 'Новое окно',
|
||||
'open_link_new' => 'В новом окне',
|
||||
'insert_collapsible' => 'Вставить свернутый блок',
|
||||
'collapsible_unwrap' => 'Unwrap',
|
||||
'collapsible_unwrap' => 'Удалить блок',
|
||||
'edit_label' => 'Изменить метку',
|
||||
'toggle_open_closed' => 'Toggle open/closed',
|
||||
'collapsible_edit' => 'Edit collapsible block',
|
||||
'toggle_label' => 'Toggle label',
|
||||
'toggle_open_closed' => 'Развернуть/свернуть',
|
||||
'collapsible_edit' => 'Редактировать свернутый блок',
|
||||
'toggle_label' => 'Метка',
|
||||
|
||||
// About view
|
||||
'about_title' => 'О редакторе WYSIWYG',
|
||||
'editor_license' => 'Лицензия редактора и авторские права',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'Авторские права и подробности лицензии TinyMCE Вы можете найти здесь.',
|
||||
'editor_tiny_license' => 'Этот редактор собран с помощью :tinyLink, который предоставляется под лицензией LGPL v2.1.',
|
||||
'editor_tiny_license_link' => 'Авторские права и подробности лицензии TinyMCE вы можете найти здесь.',
|
||||
'save_continue' => 'Сохранить страницу и продолжить',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'callouts_cycle' => '(Держите нажатым для переключения типов)',
|
||||
'shortcuts' => 'Сочетания клавиш',
|
||||
'shortcut' => 'Сочетания клавиш',
|
||||
'shortcuts_intro' => 'Следующие сочетания клавиш доступны в редакторе:',
|
||||
|
||||
@@ -57,7 +57,7 @@ return [
|
||||
'list_numbered' => '有序列表',
|
||||
'indent_increase' => '增加缩进',
|
||||
'indent_decrease' => '减少缩进',
|
||||
'table' => '表',
|
||||
'table' => '表格',
|
||||
'insert_image' => '插入图片',
|
||||
'insert_image_title' => '插入/编辑图片',
|
||||
'insert_link' => '插入/编辑链接',
|
||||
|
||||
@@ -238,13 +238,13 @@
|
||||
}
|
||||
|
||||
.fade-in-when-active {
|
||||
opacity: 0.6;
|
||||
@include lightDark(opacity, 0.6, 0.7);
|
||||
transition: opacity ease-in-out 120ms;
|
||||
&:hover, &:focus-within {
|
||||
opacity: 1;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
@media (prefers-contrast: more) {
|
||||
opacity: 1;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -361,16 +361,13 @@ body.flexbox {
|
||||
display: none;
|
||||
}
|
||||
.tri-layout-left-contents > *, .tri-layout-right-contents > * {
|
||||
opacity: 0.6;
|
||||
@include lightDark(opacity, 0.6, 0.7);
|
||||
transition: opacity ease-in-out 120ms;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
&:focus-within {
|
||||
opacity: 1;
|
||||
&:hover, &:focus-within {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
@media (prefers-contrast: more) {
|
||||
opacity: 1;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
}
|
||||
}
|
||||
.entity-list-item.selected {
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
@include lightDark(background-color, rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
.entity-list-item.no-hover {
|
||||
margin-top: -$-xs;
|
||||
|
||||
@@ -164,6 +164,10 @@ body.tox-fullscreen, body.markdown-fullscreen {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
p:empty {
|
||||
min-height: 1.6em;
|
||||
}
|
||||
|
||||
&.page-revision {
|
||||
pre code {
|
||||
white-space: pre-wrap;
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>@yield('title')</title>
|
||||
|
||||
@if($cspContent ?? false)
|
||||
<meta http-equiv="Content-Security-Policy" content="{{ $cspContent }}">
|
||||
@endif
|
||||
|
||||
@include('common.export-styles', ['format' => $format, 'engine' => $engine ?? ''])
|
||||
@include('common.export-custom-head')
|
||||
</head>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@extends('layouts.base')
|
||||
|
||||
@section('head')
|
||||
<script src="{{ url('/libs/tinymce/tinymce.min.js?ver=4.9.4') }}" nonce="{{ $cspNonce }}"></script>
|
||||
<script src="{{ url('/libs/tinymce/tinymce.min.js?ver=5.10.2') }}" nonce="{{ $cspNonce }}"></script>
|
||||
@stop
|
||||
|
||||
@section('body-class', 'flexbox')
|
||||
|
||||
@@ -268,7 +268,7 @@ class ExportTest extends TestCase
|
||||
foreach ($entities as $entity) {
|
||||
$resp = $this->asEditor()->get($entity->getUrl('/export/html'));
|
||||
$resp->assertDontSee('window.donkey');
|
||||
$resp->assertDontSee('script');
|
||||
$resp->assertDontSee('<script', false);
|
||||
$resp->assertSee('.my-test-class { color: red; }');
|
||||
}
|
||||
}
|
||||
@@ -448,4 +448,18 @@ class ExportTest extends TestCase
|
||||
$resp = $this->get($page->getUrl('/export/pdf'));
|
||||
$resp->assertStatus(500); // Bad response indicates wkhtml usage
|
||||
}
|
||||
|
||||
public function test_html_exports_contain_csp_meta_tag()
|
||||
{
|
||||
$entities = [
|
||||
Page::query()->first(),
|
||||
Book::query()->first(),
|
||||
Chapter::query()->first(),
|
||||
];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$resp = $this->asEditor()->get($entity->getUrl('/export/html'));
|
||||
$resp->assertElementExists('head meta[http-equiv="Content-Security-Policy"][content*="script-src "]');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,35 +692,43 @@ class PageContentTest extends TestCase
|
||||
|
||||
public function test_base64_images_within_markdown_blanked_if_not_supported_extension_for_extract()
|
||||
{
|
||||
$this->asEditor();
|
||||
$page = Page::query()->first();
|
||||
|
||||
$this->put($page->getUrl(), [
|
||||
$this->asEditor()->put($page->getUrl(), [
|
||||
'name' => $page->name, 'summary' => '',
|
||||
'markdown' => 'test ',
|
||||
]);
|
||||
|
||||
$page->refresh();
|
||||
$this->assertStringContainsString('<img src=""', $page->html);
|
||||
$this->assertStringContainsString('<img src=""', $page->refresh()->html);
|
||||
}
|
||||
|
||||
public function test_nested_headers_gets_assigned_an_id()
|
||||
{
|
||||
$this->asEditor();
|
||||
$page = Page::query()->first();
|
||||
|
||||
$content = '<table><tbody><tr><td><h5>Simple Test</h5></td></tr></tbody></table>';
|
||||
$this->put($page->getUrl(), [
|
||||
$this->asEditor()->put($page->getUrl(), [
|
||||
'name' => $page->name,
|
||||
'html' => $content,
|
||||
'summary' => '',
|
||||
]);
|
||||
|
||||
$updatedPage = Page::query()->where('id', '=', $page->id)->first();
|
||||
|
||||
// The top level <table> node will get assign the bkmrk-simple-test id because the system will
|
||||
// take the node value of h5
|
||||
// So the h5 should get the bkmrk-simple-test-1 id
|
||||
$this->assertStringContainsString('<h5 id="bkmrk-simple-test-1">Simple Test</h5>', $updatedPage->html);
|
||||
$this->assertStringContainsString('<h5 id="bkmrk-simple-test-1">Simple Test</h5>', $page->refresh()->html);
|
||||
}
|
||||
|
||||
public function test_non_breaking_spaces_are_preserved()
|
||||
{
|
||||
/** @var Page $page */
|
||||
$page = Page::query()->first();
|
||||
|
||||
$content = '<p> </p>';
|
||||
$this->asEditor()->put($page->getUrl(), [
|
||||
'name' => $page->name,
|
||||
'html' => $content,
|
||||
]);
|
||||
|
||||
$this->assertStringContainsString('<p id="bkmrk-%C2%A0"> </p>', $page->refresh()->html);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,25 @@ class SecurityHeaderTest extends TestCase
|
||||
$this->assertEquals('base-uri \'self\'', $scriptHeader);
|
||||
}
|
||||
|
||||
public function test_frame_src_csp_header_set()
|
||||
{
|
||||
$resp = $this->get('/');
|
||||
$scriptHeader = $this->getCspHeader($resp, 'frame-src');
|
||||
$this->assertEquals('frame-src \'self\' https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com', $scriptHeader);
|
||||
}
|
||||
|
||||
public function test_frame_src_csp_header_has_drawio_host_added()
|
||||
{
|
||||
config()->set([
|
||||
'app.iframe_sources' => 'https://example.com',
|
||||
'services.drawio' => 'https://diagrams.example.com/testing?cat=dog',
|
||||
]);
|
||||
|
||||
$resp = $this->get('/');
|
||||
$scriptHeader = $this->getCspHeader($resp, 'frame-src');
|
||||
$this->assertEquals('frame-src \'self\' https://example.com https://diagrams.example.com', $scriptHeader);
|
||||
}
|
||||
|
||||
public function test_cache_control_headers_are_strict_on_responses_when_logged_in()
|
||||
{
|
||||
$this->asEditor();
|
||||
@@ -133,10 +152,14 @@ class SecurityHeaderTest extends TestCase
|
||||
*/
|
||||
protected function getCspHeader(TestResponse $resp, string $type): string
|
||||
{
|
||||
$cspHeaders = collect($resp->headers->all('Content-Security-Policy'));
|
||||
$cspHeaders = explode('; ', $resp->headers->get('Content-Security-Policy'));
|
||||
|
||||
return $cspHeaders->filter(function ($val) use ($type) {
|
||||
return strpos($val, $type) === 0;
|
||||
})->first() ?? '';
|
||||
foreach ($cspHeaders as $cspHeader) {
|
||||
if (strpos($cspHeader, $type) === 0) {
|
||||
return $cspHeader;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user