Compare commits

...

22 Commits

Author SHA1 Message Date
Dan Brown
94c59c1e3d Updated version and assets for release v0.30.3 2020-10-13 22:50:52 +01:00
Dan Brown
4d2205853a Merge branch 'master' into release 2020-10-13 22:50:30 +01:00
Dan Brown
18bcafaee4 Updated translator attribution before release v0.30.3 2020-10-13 22:49:55 +01:00
Dan Brown
8d07b7cf1c Added alias for vbscript 2020-10-13 22:44:33 +01:00
Dan Brown
080f9c3025 Merge pull request #2302 from nutsflag/master
Add VBScript Codemirror
2020-10-13 22:41:09 +01:00
Dan Brown
617fe6bc8c Merge pull request #2303 from BookStackApp/l10n_master
New Crowdin updates
2020-10-13 22:39:52 +01:00
Dan Brown
bb1f1a9ecd Fixed error on drawing edit on markdown editor
Was preventing save of drawings.
For #2313
2020-10-13 22:36:07 +01:00
Dan Brown
d688e43197 New translations settings.php (Chinese Simplified) 2020-10-05 06:26:38 +01:00
Dan Brown
c82c3023c5 New translations settings.php (Spanish) 2020-10-02 17:18:27 +01:00
Dan Brown
d0d75afc66 New translations settings.php (Chinese Simplified) 2020-10-02 15:55:46 +01:00
nutsflag
467176ee78 Update code.js 2020-10-02 15:14:29 +02:00
nutsflag
521a002001 Update code-editor.blade.php 2020-10-02 15:13:31 +02:00
Dan Brown
751772b87a Updated version and assets for release v0.30.2 2020-09-30 22:44:58 +01:00
Dan Brown
76e30869e1 Merge branch 'master' into release 2020-09-30 22:44:17 +01:00
Dan Brown
f3ee8f2d4c Updated http service to not read 204 response data 2020-09-30 22:32:03 +01:00
Dan Brown
ea406690f5 Updated esbuild options and version & updated npm deps
Had to change way sortable is imported due to changes, Still
seemed to have functioning multi-select.
2020-09-30 22:28:53 +01:00
Dan Brown
465d405926 Updated page content related links on content id changes
For #2278
2020-09-28 22:26:50 +01:00
Dan Brown
1097c61d6d Fixed duplicate requests in attachment manager issue
Closes #2286
2020-09-28 21:55:24 +01:00
Dan Brown
def2d61ad8 Merge pull request #2272 from jakubboucek/feature/fix-invalid-canonical-redirect
Fixed canonical redirects on non-root url app instances
2020-09-28 21:15:23 +01:00
Dan Brown
8b0f5e7000 Updated draw.io references to diagrams.net
Related to #2044
2020-09-28 20:45:38 +01:00
Jakub Bouček
1e88e8086f Fixed canonical redirects on non-root url app instances
If BookStack instance is deployed to any non-root path, e.g. http://example.com/wiki/,
requests for http://example.com/wiki/shelves/
was redirected to http://example.com/shelves
instead of http://example.com/wiki/shelves

Synced with: https://github.com/laravel/laravel/blob/master/public/.htaccess
2020-09-27 02:50:37 +02:00
Dan Brown
d48ac0a37d Removed redundant test
Now replaced in recent commit by one that checks actual message gets
displayed on the redirect page.
Redirect page changed to login page.
2020-09-26 18:24:05 +01:00
23 changed files with 138 additions and 117 deletions

View File

@@ -238,9 +238,9 @@ DISABLE_EXTERNAL_SERVICES=false
# Example: AVATAR_URL=https://seccdn.libravatar.org/avatar/${hash}?s=${size}&d=identicon
AVATAR_URL=
# Enable draw.io integration
# Enable diagrams.net integration
# Can simply be true/false to enable/disable the integration.
# Alternatively, It can be URL to the draw.io instance you want to use.
# Alternatively, It can be URL to the diagrams.net instance you want to use.
# For URLs, The following URL parameters should be included: embed=1&proto=json&spin=1
DRAWIO=true

View File

@@ -122,3 +122,4 @@ fadiapp :: Arabic
Jakub Bouček (jakubboucek) :: Czech
Marco (cdrfun) :: German
10935336 :: Chinese Simplified
孟繁阳 (FanyangMeng) :: Chinese Simplified

View File

@@ -2,7 +2,6 @@
use BookStack\Entities\Page;
use DOMDocument;
use DOMElement;
use DOMNodeList;
use DOMXPath;
@@ -44,18 +43,24 @@ class PageContent
$container = $doc->documentElement;
$body = $container->childNodes->item(0);
$childNodes = $body->childNodes;
$xPath = new DOMXPath($doc);
// Set ids on top-level nodes
$idMap = [];
foreach ($childNodes as $index => $childNode) {
$this->setUniqueId($childNode, $idMap);
[$oldId, $newId] = $this->setUniqueId($childNode, $idMap);
if ($newId && $newId !== $oldId) {
$this->updateLinks($xPath, '#' . $oldId, '#' . $newId);
}
}
// Ensure no duplicate ids within child items
$xPath = new DOMXPath($doc);
$idElems = $xPath->query('//body//*//*[@id]');
foreach ($idElems as $domElem) {
$this->setUniqueId($domElem, $idMap);
[$oldId, $newId] = $this->setUniqueId($domElem, $idMap);
if ($newId && $newId !== $oldId) {
$this->updateLinks($xPath, '#' . $oldId, '#' . $newId);
}
}
// Generate inner html as a string
@@ -67,23 +72,34 @@ class PageContent
return $html;
}
/**
* Update the all links to the $old location to instead point to $new.
*/
protected function updateLinks(DOMXPath $xpath, string $old, string $new)
{
$old = str_replace('"', '', $old);
$matchingLinks = $xpath->query('//body//*//*[@href="'.$old.'"]');
foreach ($matchingLinks as $domElem) {
$domElem->setAttribute('href', $new);
}
}
/**
* Set a unique id on the given DOMElement.
* A map for existing ID's should be passed in to check for current existence.
* @param DOMElement $element
* @param array $idMap
* Returns a pair of strings in the format [old_id, new_id]
*/
protected function setUniqueId($element, array &$idMap)
protected function setUniqueId(\DOMNode $element, array &$idMap): array
{
if (get_class($element) !== 'DOMElement') {
return;
return ['', ''];
}
// Overwrite id if not a BookStack custom id
// Stop if there's an existing valid id that has not already been used.
$existingId = $element->getAttribute('id');
if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
$idMap[$existingId] = true;
return;
return [$existingId, $existingId];
}
// Create an unique id for the element
@@ -100,6 +116,7 @@ class PageContent
$element->setAttribute('id', $newId);
$idMap[$newId] = true;
return [$existingId, $newId];
}
/**

24
package-lock.json generated
View File

@@ -253,9 +253,9 @@
}
},
"esbuild": {
"version": "0.6.30",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.6.30.tgz",
"integrity": "sha512-ZSZY461UPzTYYC3rqy1QiMtngk2WyXf+58MgC7tC22jkI90FXNgEl0hN3ipfn/UgZYzTW2GBcHiO7t0rSbHT7g==",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.7.8.tgz",
"integrity": "sha512-6UT1nZB+8ja5avctUC6d3kGOUAhy6/ZYHljL4nk3++1ipadghBhUCAcwsTHsmUvdu04CcGKzo13mE+ZQ2O3zrA==",
"dev": true
},
"escape-string-regexp": {
@@ -496,9 +496,9 @@
"dev": true
},
"markdown-it": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.0.tgz",
"integrity": "sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.1.tgz",
"integrity": "sha512-aU1TzmBKcWNNYvH9pjq6u92BML+Hz3h5S/QpfTFwiQF852pLT+9qHsrhM9JYipkOXZxGn+sGH8oyJE9FD9WezQ==",
"requires": {
"argparse": "^1.0.7",
"entities": "~2.0.0",
@@ -730,9 +730,9 @@
}
},
"sass": {
"version": "1.26.10",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz",
"integrity": "sha512-bzN0uvmzfsTvjz0qwccN1sPm2HxxpNI/Xa+7PlUEMS+nQvbyuEK7Y0qFqxlPHhiNHb1Ze8WQJtU31olMObkAMw==",
"version": "1.26.11",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.11.tgz",
"integrity": "sha512-W1l/+vjGjIamsJ6OnTe0K37U2DBO/dgsv2Z4c89XQ8ZOO6l/VwkqwLSqoYzJeJs6CLuGSTRWc91GbQFL3lvrvw==",
"dev": true,
"requires": {
"chokidar": ">=2.0.0 <4.0.0"
@@ -777,9 +777,9 @@
"dev": true
},
"sortablejs": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
"integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.12.0.tgz",
"integrity": "sha512-bPn57rCjBRlt2sC24RBsu40wZsmLkSo2XeqG8k6DC1zru5eObQUIPPZAQG7W2SJ8FZQYq+BEJmvuw1Zxb3chqg=="
},
"spdx-correct": {
"version": "3.1.1",

View File

@@ -4,9 +4,9 @@
"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/index.js --outfile=public/dist/app.js --sourcemap --target=es2020",
"build:js:dev": "esbuild --bundle ./resources/js/index.js --outfile=public/dist/app.js --sourcemap --target=es2019 --main-fields=module,main",
"build:js:watch": "chokidar \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
"build:js:production": "NODE_ENV=production esbuild --bundle ./resources/js/index.js --outfile=public/dist/app.js --sourcemap --minify",
"build:js:production": "NODE_ENV=production esbuild --bundle ./resources/js/index.js --outfile=public/dist/app.js --sourcemap --target=es2019 --main-fields=module,main --minify",
"build": "npm-run-all --parallel build:*:dev",
"production": "npm-run-all --parallel build:*:production",
"dev": "npm-run-all --parallel watch livereload",
@@ -16,18 +16,18 @@
},
"devDependencies": {
"chokidar-cli": "^2.1.0",
"esbuild": "0.6.30",
"esbuild": "0.7.8",
"livereload": "^0.9.1",
"npm-run-all": "^4.1.5",
"punycode": "^2.1.1",
"sass": "^1.26.10"
"sass": "^1.26.11"
},
"dependencies": {
"clipboard": "^2.0.6",
"codemirror": "^5.58.1",
"dropzone": "^5.7.2",
"markdown-it": "^11.0.0",
"markdown-it": "^11.0.1",
"markdown-it-task-lists": "^2.1.1",
"sortablejs": "^1.10.2"
"sortablejs": "^1.12.0"
}
}

View File

@@ -11,9 +11,10 @@
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle Front Controller...
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]

88
public/dist/app.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -168,6 +168,6 @@ These are the great open-source projects used to help build BookStack:
* [Snappy (WKHTML2PDF)](https://github.com/barryvdh/laravel-snappy)
* [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper)
* [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html)
* [Draw.io](https://github.com/jgraph/drawio)
* [diagrams.net](https://github.com/jgraph/drawio)
* [Laravel Stats](https://github.com/stefanzweifel/laravel-stats)
* [OneLogin's SAML PHP Toolkit](https://github.com/onelogin/php-saml)

View File

@@ -1,4 +1,4 @@
import {Sortable, MultiDrag} from "sortablejs";
import Sortable from "sortablejs";
// Auto sort control
const sortOperations = {
@@ -43,7 +43,6 @@ class BookSort {
this.input = elem.querySelector('[book-sort-input]');
const initialSortBox = elem.querySelector('.sort-box');
Sortable.mount(new MultiDrag());
this.setupBookSortable(initialSortBox);
this.setupSortPresets();

View File

@@ -440,10 +440,10 @@ class MarkdownEditor {
const data = {
image: pngData,
uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id'))
uploaded_to: Number(this.pageId),
};
window.$http.post(window.baseUrl('/images/drawio'), data).then(resp => {
window.$http.post("/images/drawio", data).then(resp => {
this.insertDrawing(resp.data, cursorPos);
DrawIO.close();
}).catch(err => {
@@ -476,10 +476,10 @@ class MarkdownEditor {
let data = {
image: pngData,
uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id'))
uploaded_to: Number(this.pageId),
};
window.$http.post(window.baseUrl(`/images/drawio`), data).then(resp => {
window.$http.post("/images/drawio", data).then(resp => {
let newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
let newContent = this.cm.getValue().split('\n').map(line => {
if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) {

View File

@@ -26,6 +26,7 @@ import 'codemirror/mode/rust/rust';
import 'codemirror/mode/shell/shell';
import 'codemirror/mode/sql/sql';
import 'codemirror/mode/toml/toml';
import 'codemirror/mode/vbscript/vbscript';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/yaml/yaml';
@@ -84,6 +85,8 @@ const modeMap = {
bash: 'shell',
toml: 'toml',
sql: 'text/x-sql',
vbs: 'vbscript',
vbscript: 'vbscript',
xml: 'xml',
yaml: 'yaml',
yml: 'yaml',

View File

@@ -141,10 +141,14 @@ async function request(url, options = {}) {
/**
* Get the content from a fetch response.
* Checks the content-type header to determine the format.
* @param response
* @param {Response} response
* @returns {Promise<Object|String>}
*/
async function getResponseContent(response) {
if (response.status === 204) {
return null;
}
const responseContentType = response.headers.get('Content-Type');
const subType = responseContentType.split('/').pop();

View File

@@ -179,7 +179,7 @@ return [
'user_api_token_name_desc' => 'Dale a tu token un nombre legible como un recordatorio futuro de su propósito.',
'user_api_token_expiry' => 'Fecha de expiración',
'user_api_token_expiry_desc' => 'Establece una fecha en la que este token expira. Después de esta fecha, las solicitudes realizadas usando este token ya no funcionarán. Dejar este campo en blanco fijará un vencimiento de 100 años en el futuro.',
'user_api_token_create_secret_message' => 'Immediately after creating this token a "Token ID" & "Token Secret" will be generated and displayed. The secret will only be shown a single time so be sure to copy the value to somewhere safe and secure before proceeding.',
'user_api_token_create_secret_message' => 'Inmediatamente después de crear este token se generarán y mostrarán sus correspondientes "Token ID" y "Token Secret". El "Token Secret" sólo se mostrará una vez, así que asegúrese de copiar el valor a un lugar seguro antes de proceder.',
'user_api_token_create_success' => 'Token API creado correctamente',
'user_api_token_update_success' => 'Token API actualizado correctamente',
'user_api_token' => 'Token API',
@@ -187,8 +187,8 @@ return [
'user_api_token_id_desc' => 'Este es un identificador no editable generado por el sistema y único para este token que necesitará ser proporcionado en solicitudes de API.',
'user_api_token_secret' => 'Token Secret',
'user_api_token_secret_desc' => 'Esta es una clave no editable generada por el sistema que necesitará ser proporcionada en solicitudes de API. Solo se monstraré esta vez así que guarde su valor en un lugar seguro.',
'user_api_token_created' => 'Token created :timeAgo',
'user_api_token_updated' => 'Token updated :timeAgo',
'user_api_token_created' => 'Token creado :timeAgo',
'user_api_token_updated' => 'Token actualizado :timeAgo',
'user_api_token_delete' => 'Borrar token',
'user_api_token_delete_warning' => 'Esto eliminará completamente este token API con el nombre \':tokenName\' del sistema.',
'user_api_token_delete_confirm' => '¿Está seguro de que desea borrar este API token?',

View File

@@ -57,7 +57,7 @@ return [
'reg_enable_desc' => '启用注册后,用户将可以自己注册为站点用户。 注册后,他们将获得一个默认的单一用户角色。',
'reg_default_role' => '注册后的默认用户角色',
'reg_enable_external_warning' => '当启用外部LDAP或者SAML认证时上面的选项会被忽略。当使用外部系统认证认证成功时将自动创建非现有会员的用户账户。',
'reg_email_confirmation' => '邮确认n',
'reg_email_confirmation' => '邮确认',
'reg_email_confirmation_toggle' => '需要电子邮件确认',
'reg_confirm_email_desc' => '如果使用域名限制则需要Email验证并且该值将被忽略。',
'reg_confirm_restrict_domain' => '域名限制',
@@ -92,8 +92,8 @@ return [
'audit_table_event' => '事件',
'audit_table_item' => '相关项目',
'audit_table_date' => '活动日期',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
'audit_date_from' => '日期范围从',
'audit_date_to' => '日期范围至',
// Role Settings
'roles' => '角色',

View File

@@ -1,6 +1,7 @@
<div component="ajax-form"
option:ajax-form:url="/attachments/{{ $attachment->id }}"
option:ajax-form:method="put"
option:ajax-form:response-container=".attachment-edit-container"
option:ajax-form:success-message="{{ trans('entities.attachments_updated_success') }}">
<h5>{{ trans('entities.attachments_edit_file') }}</h5>

View File

@@ -4,6 +4,7 @@
<div component="ajax-form"
option:ajax-form:url="/attachments/link"
option:ajax-form:method="post"
option:ajax-form:response-container=".link-form-container"
option:ajax-form:success-message="{{ trans('entities.attachments_link_attached') }}">
<input type="hidden" name="attachment_link_uploaded_to" value="{{ $pageId }}">
<p class="text-muted small">{{ trans('entities.attachments_explain_link') }}</p>

View File

@@ -24,14 +24,14 @@
'successMessage' => trans('entities.attachments_file_uploaded'),
])
</div>
<div refs="tabs@contentLinks" class="hidden">
<div refs="tabs@contentLinks" class="hidden link-form-container">
@include('attachments.manager-link-form', ['pageId' => $page->id])
</div>
</div>
</div>
<div refs="attachments@editContainer" class="hidden">
<div refs="attachments@editContainer" class="hidden attachment-edit-container">
</div>

View File

@@ -34,6 +34,7 @@
<a refs="code-editor@languageLink" data-lang="Ruby">Ruby</a>
<a refs="code-editor@languageLink" data-lang="shell">Shell/Bash</a>
<a refs="code-editor@languageLink" data-lang="SQL">SQL</a>
<a refs="code-editor@languageLink" data-lang="VBScript">VBScript</a>
<a refs="code-editor@languageLink" data-lang="XML">XML</a>
<a refs="code-editor@languageLink" data-lang="YAML">YAML</a>
</small>
@@ -66,4 +67,4 @@
</div>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
<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://www.draw.io/?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' }}"
@endif
@if($model->name === trans('entities.pages_initial_name'))
option:page-editor:has-default-title="true"

View File

@@ -196,24 +196,6 @@ class Saml2Test extends TestCase
});
}
public function test_user_registration_with_existing_email()
{
config()->set([
'saml2.onelogin.strict' => false,
]);
$viewer = $this->getViewer();
$viewer->email = 'user@example.com';
$viewer->save();
$this->withPost(['SAMLResponse' => $this->acsPostData], function () {
$acsPost = $this->post('/saml2/acs');
$acsPost->assertRedirect('/');
$errorMessage = session()->get('error');
$this->assertEquals('A user with the email user@example.com already exists but with different credentials.', $errorMessage);
});
}
public function test_saml_routes_are_only_active_if_saml_enabled()
{
config()->set(['auth.method' => 'standard']);

View File

@@ -262,6 +262,23 @@ class PageContentTest extends TestCase
$this->assertEquals(substr_count($updatedPage->html, "bkmrk-test\""), 1);
}
public function test_anchors_referencing_non_bkmrk_ids_rewritten_after_save()
{
$this->asEditor();
$page = Page::first();
$content = '<h1 id="non-standard-id">test</h1><p><a href="#non-standard-id">link</a></p>';
$this->put($page->getUrl(), [
'name' => $page->name,
'html' => $content,
'summary' => ''
]);
$updatedPage = Page::where('id', '=', $page->id)->first();
$this->assertStringContainsString('id="bkmrk-test"', $updatedPage->html);
$this->assertStringContainsString('href="#bkmrk-test"', $updatedPage->html);
}
public function test_get_page_nav_sets_correct_properties()
{
$content = '<h1 id="testa">Hello</h1><h2 id="testb">There</h2><h3 id="testc">Donkey</h3>';

View File

@@ -69,7 +69,7 @@ class DrawioTest extends TestCase
$editor = $this->getEditor();
$resp = $this->actingAs($editor)->get($page->getUrl('/edit'));
$resp->assertSee('drawio-url="https://www.draw.io/?embed=1&amp;proto=json&amp;spin=1"');
$resp->assertSee('drawio-url="https://embed.diagrams.net/?embed=1&amp;proto=json&amp;spin=1"');
config()->set('services.drawio', false);
$resp = $this->actingAs($editor)->get($page->getUrl('/edit'));

View File

@@ -1 +1 @@
v0.30.1
v0.30.3