mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-24 20:02:35 +01:00
commit
f0d914abbf
@ -23,6 +23,9 @@ class AppServiceProvider extends ServiceProvider
|
||||
\Blade::directive('icon', function($expression) {
|
||||
return "<?php echo icon($expression); ?>";
|
||||
});
|
||||
|
||||
// Allow longer string lengths after upgrade to utf8mb4
|
||||
\Schema::defaultStringLength(191);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,6 +42,8 @@ class LdapService
|
||||
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
||||
$baseDn = $this->config['base_dn'];
|
||||
$emailAttr = $this->config['email_attribute'];
|
||||
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
|
||||
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
|
||||
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
|
||||
if ($users['count'] === 0) return null;
|
||||
|
||||
|
@ -58,7 +58,7 @@ return [
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LANG', 'en'),
|
||||
'locales' => ['en', 'de', 'es', 'fr', 'nl', 'pt_BR', 'sk'],
|
||||
'locales' => ['en', 'de', 'es', 'fr', 'nl', 'pt_BR', 'sk', 'ja'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -16,6 +16,14 @@ if (env('REDIS_SERVERS', false)) {
|
||||
}
|
||||
}
|
||||
|
||||
$mysql_host = env('DB_HOST', 'localhost');
|
||||
$mysql_host_exploded = explode(':', $mysql_host);
|
||||
$mysql_port = env('DB_PORT', 3306);
|
||||
if (count($mysql_host_exploded) > 1) {
|
||||
$mysql_host = $mysql_host_exploded[0];
|
||||
$mysql_port = intval($mysql_host_exploded[1]);
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@ -70,12 +78,13 @@ return [
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'host' => $mysql_host,
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'collation' => 'utf8_unicode_ci',
|
||||
'port' => $mysql_port,
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'strict' => false,
|
||||
],
|
||||
|
@ -80,6 +80,7 @@ return [
|
||||
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
|
||||
'version' => env('LDAP_VERSION', false),
|
||||
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
|
||||
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
|
||||
]
|
||||
|
||||
];
|
||||
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateDbEncodingToUt8mb4 extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$database = DB::getDatabaseName();
|
||||
$tables = DB::select('SHOW TABLES');
|
||||
$pdo = DB::getPdo();
|
||||
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
|
||||
$pdo->exec('ALTER DATABASE `'.$database.'` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
|
||||
$key = 'Tables_in_' . $database;
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table->$key;
|
||||
$pdo->exec('ALTER TABLE `'.$tableName.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$database = DB::getDatabaseName();
|
||||
$tables = DB::select('SHOW TABLES');
|
||||
$pdo = DB::getPdo();
|
||||
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
|
||||
$pdo->exec('ALTER DATABASE `'.$database.'` CHARACTER SET utf8 COLLATE utf8_unicode_ci');
|
||||
$key = 'Tables_in_' . $database;
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table->$key;
|
||||
$pdo->exec('ALTER TABLE `'.$tableName.'` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci');
|
||||
}
|
||||
}
|
||||
}
|
@ -64,13 +64,15 @@ The BookStack source is provided under the MIT License.
|
||||
|
||||
## Attribution
|
||||
|
||||
These are the great projects used to help build BookStack:
|
||||
These are the great open-source projects used to help build BookStack:
|
||||
|
||||
* [Laravel](http://laravel.com/)
|
||||
* [AngularJS](https://angularjs.org/)
|
||||
* [jQuery](https://jquery.com/)
|
||||
* [TinyMCE](https://www.tinymce.com/)
|
||||
* [highlight.js](https://highlightjs.org/)
|
||||
* [CodeMirror](https://codemirror.net)
|
||||
* [Vue.js](http://vuejs.org/)
|
||||
* [Axios](https://github.com/mzabriskie/axios)
|
||||
* [jQuery Sortable](https://johnny.github.io/jquery-sortable/)
|
||||
* [Material Design Iconic Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html)
|
||||
* [Dropzone.js](http://www.dropzonejs.com/)
|
||||
@ -84,5 +86,3 @@ These are the great 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)
|
||||
|
||||
Additionally, Thank you [BrowserStack](https://www.browserstack.com/) for supporting us and making cross-browser testing easy.
|
||||
|
@ -1,5 +1,6 @@
|
||||
require('codemirror/mode/css/css');
|
||||
require('codemirror/mode/clike/clike');
|
||||
require('codemirror/mode/diff/diff');
|
||||
require('codemirror/mode/go/go');
|
||||
require('codemirror/mode/htmlmixed/htmlmixed');
|
||||
require('codemirror/mode/javascript/javascript');
|
||||
@ -17,30 +18,148 @@ require('codemirror/mode/yaml/yaml');
|
||||
|
||||
const CodeMirror = require('codemirror');
|
||||
|
||||
const modeMap = {
|
||||
css: 'css',
|
||||
c: 'clike',
|
||||
java: 'clike',
|
||||
scala: 'clike',
|
||||
kotlin: 'clike',
|
||||
'c++': 'clike',
|
||||
'c#': 'clike',
|
||||
csharp: 'clike',
|
||||
diff: 'diff',
|
||||
go: 'go',
|
||||
html: 'htmlmixed',
|
||||
javascript: 'javascript',
|
||||
json: {name: 'javascript', json: true},
|
||||
js: 'javascript',
|
||||
php: 'php',
|
||||
md: 'markdown',
|
||||
mdown: 'markdown',
|
||||
markdown: 'markdown',
|
||||
nginx: 'nginx',
|
||||
powershell: 'powershell',
|
||||
py: 'python',
|
||||
python: 'python',
|
||||
ruby: 'ruby',
|
||||
rb: 'ruby',
|
||||
shell: 'shell',
|
||||
sh: 'shell',
|
||||
bash: 'shell',
|
||||
toml: 'toml',
|
||||
sql: 'sql',
|
||||
xml: 'xml',
|
||||
yaml: 'yaml',
|
||||
yml: 'yaml',
|
||||
};
|
||||
|
||||
module.exports.highlight = function() {
|
||||
let codeBlocks = document.querySelectorAll('.page-content pre');
|
||||
|
||||
for (let i = 0; i < codeBlocks.length; i++) {
|
||||
codeBlocks[i].innerHTML = codeBlocks[i].innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
|
||||
let content = codeBlocks[i].textContent;
|
||||
highlightElem(codeBlocks[i]);
|
||||
}
|
||||
};
|
||||
|
||||
function highlightElem(elem) {
|
||||
let innerCodeElem = elem.querySelector('code[class^=language-]');
|
||||
let mode = '';
|
||||
if (innerCodeElem !== null) {
|
||||
let langName = innerCodeElem.className.replace('language-', '');
|
||||
mode = getMode(langName);
|
||||
}
|
||||
elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
|
||||
let content = elem.textContent;
|
||||
|
||||
CodeMirror(function(elt) {
|
||||
codeBlocks[i].parentNode.replaceChild(elt, codeBlocks[i]);
|
||||
elem.parentNode.replaceChild(elt, elem);
|
||||
}, {
|
||||
value: content,
|
||||
mode: "",
|
||||
mode: mode,
|
||||
lineNumbers: true,
|
||||
theme: 'base16-light',
|
||||
readOnly: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a codemirror code based off a user suggestion
|
||||
* @param suggestion
|
||||
* @returns {string}
|
||||
*/
|
||||
function getMode(suggestion) {
|
||||
suggestion = suggestion.trim().replace(/^\./g, '').toLowerCase();
|
||||
return (typeof modeMap[suggestion] !== 'undefined') ? modeMap[suggestion] : '';
|
||||
}
|
||||
|
||||
module.exports.highlightElem = highlightElem;
|
||||
|
||||
module.exports.wysiwygView = function(elem) {
|
||||
let doc = elem.ownerDocument;
|
||||
let codeElem = elem.querySelector('code');
|
||||
|
||||
let lang = (elem.className || '').replace('language-', '');
|
||||
if (lang === '' && codeElem) {
|
||||
lang = (codeElem.className || '').replace('language-', '')
|
||||
}
|
||||
|
||||
elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
|
||||
let content = elem.textContent;
|
||||
let newWrap = doc.createElement('div');
|
||||
let newTextArea = doc.createElement('textarea');
|
||||
|
||||
newWrap.className = 'CodeMirrorContainer';
|
||||
newWrap.setAttribute('data-lang', lang);
|
||||
newTextArea.style.display = 'none';
|
||||
elem.parentNode.replaceChild(newWrap, elem);
|
||||
|
||||
newWrap.appendChild(newTextArea);
|
||||
newWrap.contentEditable = false;
|
||||
newTextArea.textContent = content;
|
||||
|
||||
let cm = CodeMirror(function(elt) {
|
||||
newWrap.appendChild(elt);
|
||||
}, {
|
||||
value: content,
|
||||
mode: getMode(lang),
|
||||
lineNumbers: true,
|
||||
theme: 'base16-light',
|
||||
readOnly: true
|
||||
});
|
||||
setTimeout(() => {
|
||||
cm.refresh();
|
||||
}, 300);
|
||||
return {wrap: newWrap, editor: cm};
|
||||
};
|
||||
|
||||
module.exports.popupEditor = function(elem, modeSuggestion) {
|
||||
let content = elem.textContent;
|
||||
|
||||
return CodeMirror(function(elt) {
|
||||
elem.parentNode.insertBefore(elt, elem);
|
||||
elem.style.display = 'none';
|
||||
}, {
|
||||
value: content,
|
||||
mode: getMode(modeSuggestion),
|
||||
lineNumbers: true,
|
||||
theme: 'base16-light',
|
||||
lineWrapping: true
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.setMode = function(cmInstance, modeSuggestion) {
|
||||
cmInstance.setOption('mode', getMode(modeSuggestion));
|
||||
};
|
||||
module.exports.setContent = function(cmInstance, codeContent) {
|
||||
cmInstance.setValue(codeContent);
|
||||
setTimeout(() => {
|
||||
cmInstance.refresh();
|
||||
}, 10);
|
||||
};
|
||||
|
||||
module.exports.markdownEditor = function(elem) {
|
||||
let content = elem.textContent;
|
||||
|
||||
let cm = CodeMirror(function(elt) {
|
||||
return CodeMirror(function (elt) {
|
||||
elem.parentNode.insertBefore(elt, elem);
|
||||
elem.style.display = 'none';
|
||||
}, {
|
||||
@ -50,7 +169,10 @@ module.exports.markdownEditor = function(elem) {
|
||||
theme: 'base16-light',
|
||||
lineWrapping: true
|
||||
});
|
||||
return cm;
|
||||
|
||||
};
|
||||
|
||||
module.exports.getMetaKey = function() {
|
||||
let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
|
||||
return mac ? "Cmd" : "Ctrl";
|
||||
};
|
||||
|
||||
|
@ -370,14 +370,8 @@ module.exports = function (ngApp, events) {
|
||||
saveDraft();
|
||||
};
|
||||
|
||||
// Listen to shortcuts coming via events
|
||||
$scope.$on('editor-keydown', (event, data) => {
|
||||
// Save shortcut (ctrl+s)
|
||||
if (data.keyCode == 83 && (navigator.platform.match("Mac") ? data.metaKey : data.ctrlKey)) {
|
||||
data.preventDefault();
|
||||
saveDraft();
|
||||
}
|
||||
});
|
||||
// Listen to save draft events from editor
|
||||
$scope.$on('save-draft', saveDraft);
|
||||
|
||||
/**
|
||||
* Discard the current draft and grab the current page
|
||||
|
@ -232,15 +232,33 @@ module.exports = function (ngApp, events) {
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
// Set initial model content
|
||||
element = element.find('textarea').first();
|
||||
|
||||
// Codemirror Setup
|
||||
element = element.find('textarea').first();
|
||||
let cm = code.markdownEditor(element[0]);
|
||||
|
||||
// Custom key commands
|
||||
let metaKey = code.getMetaKey();
|
||||
const extraKeys = {};
|
||||
// Insert Image shortcut
|
||||
extraKeys[`${metaKey}-Alt-I`] = function(cm) {
|
||||
let selectedText = cm.getSelection();
|
||||
let newText = `![${selectedText}](http://)`;
|
||||
let cursorPos = cm.getCursor('from');
|
||||
cm.replaceSelection(newText);
|
||||
cm.setCursor(cursorPos.line, cursorPos.ch + newText.length -1);
|
||||
};
|
||||
// Save draft
|
||||
extraKeys[`${metaKey}-S`] = function(cm) {scope.$emit('save-draft');};
|
||||
// Show link selector
|
||||
extraKeys[`Shift-${metaKey}-K`] = function(cm) {showLinkSelector()};
|
||||
cm.setOption('extraKeys', extraKeys);
|
||||
|
||||
// Update data on content change
|
||||
cm.on('change', (instance, changeObj) => {
|
||||
update(instance);
|
||||
});
|
||||
|
||||
// Handle scroll to sync display view
|
||||
cm.on('scroll', instance => {
|
||||
// Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html
|
||||
let scroll = instance.getScrollInfo();
|
||||
@ -257,6 +275,89 @@ module.exports = function (ngApp, events) {
|
||||
scope.$emit('markdown-scroll', totalLines.length);
|
||||
});
|
||||
|
||||
// Handle image paste
|
||||
cm.on('paste', (cm, event) => {
|
||||
if (!event.clipboardData || !event.clipboardData.items) return;
|
||||
for (let i = 0; i < event.clipboardData.items.length; i++) {
|
||||
uploadImage(event.clipboardData.items[i].getAsFile());
|
||||
}
|
||||
});
|
||||
|
||||
// Handle images on drag-drop
|
||||
cm.on('drop', (cm, event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let cursorPos = cm.coordsChar({left: event.pageX, top: event.pageY});
|
||||
cm.setCursor(cursorPos);
|
||||
if (!event.dataTransfer || !event.dataTransfer.files) return;
|
||||
for (let i = 0; i < event.dataTransfer.files.length; i++) {
|
||||
uploadImage(event.dataTransfer.files[i]);
|
||||
}
|
||||
});
|
||||
|
||||
// Helper to replace editor content
|
||||
function replaceContent(search, replace) {
|
||||
let text = cm.getValue();
|
||||
let cursor = cm.listSelections();
|
||||
cm.setValue(text.replace(search, replace));
|
||||
cm.setSelections(cursor);
|
||||
}
|
||||
|
||||
// Handle image upload and add image into markdown content
|
||||
function uploadImage(file) {
|
||||
if (file === null || file.type.indexOf('image') !== 0) return;
|
||||
let ext = 'png';
|
||||
|
||||
if (file.name) {
|
||||
let fileNameMatches = file.name.match(/\.(.+)$/);
|
||||
if (fileNameMatches.length > 1) ext = fileNameMatches[1];
|
||||
}
|
||||
|
||||
// Insert image into markdown
|
||||
let id = "image-" + Math.random().toString(16).slice(2);
|
||||
let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
|
||||
let selectedText = cm.getSelection();
|
||||
let placeHolderText = `![${selectedText}](${placeholderImage})`;
|
||||
cm.replaceSelection(placeHolderText);
|
||||
|
||||
let remoteFilename = "image-" + Date.now() + "." + ext;
|
||||
let formData = new FormData();
|
||||
formData.append('file', file, remoteFilename);
|
||||
|
||||
window.$http.post('/images/gallery/upload', formData).then(resp => {
|
||||
replaceContent(placeholderImage, resp.data.thumbs.display);
|
||||
}).catch(err => {
|
||||
events.emit('error', trans('errors.image_upload_error'));
|
||||
replaceContent(placeHolderText, selectedText);
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
// Show the popup link selector and insert a link when finished
|
||||
function showLinkSelector() {
|
||||
let cursorPos = cm.getCursor('from');
|
||||
window.showEntityLinkSelector(entity => {
|
||||
let selectedText = cm.getSelection() || entity.name;
|
||||
let newText = `[${selectedText}](${entity.link})`;
|
||||
cm.focus();
|
||||
cm.replaceSelection(newText);
|
||||
cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
|
||||
});
|
||||
}
|
||||
|
||||
// Show the image manager and handle image insertion
|
||||
function showImageManager() {
|
||||
let cursorPos = cm.getCursor('from');
|
||||
window.ImageManager.showExternal(image => {
|
||||
let selectedText = cm.getSelection();
|
||||
let newText = "![" + (selectedText || image.name) + "](" + image.thumbs.display + ")";
|
||||
cm.focus();
|
||||
cm.replaceSelection(newText);
|
||||
cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
|
||||
});
|
||||
}
|
||||
|
||||
// Update the data models and rendered output
|
||||
function update(instance) {
|
||||
let content = instance.getValue();
|
||||
element.val(content);
|
||||
@ -267,6 +368,9 @@ module.exports = function (ngApp, events) {
|
||||
}
|
||||
update(cm);
|
||||
|
||||
// Listen to commands from parent scope
|
||||
scope.$on('md-insert-link', showLinkSelector);
|
||||
scope.$on('md-insert-image', showImageManager);
|
||||
scope.$on('markdown-update', (event, value) => {
|
||||
cm.setValue(value);
|
||||
element.val(value);
|
||||
@ -287,8 +391,7 @@ module.exports = function (ngApp, events) {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
// Elements
|
||||
const $input = element.find('[markdown-input] textarea').first();
|
||||
// Editor Elements
|
||||
const $display = element.find('.markdown-display').first();
|
||||
const $insertImage = element.find('button[data-action="insertImage"]');
|
||||
const $insertEntityLink = element.find('button[data-action="insertEntityLink"]');
|
||||
@ -299,11 +402,9 @@ module.exports = function (ngApp, events) {
|
||||
window.open(this.getAttribute('href'));
|
||||
});
|
||||
|
||||
let currentCaretPos = 0;
|
||||
|
||||
$input.blur(event => {
|
||||
currentCaretPos = $input[0].selectionStart;
|
||||
});
|
||||
// Editor UI Actions
|
||||
$insertEntityLink.click(e => {scope.$broadcast('md-insert-link');});
|
||||
$insertImage.click(e => {scope.$broadcast('md-insert-image');});
|
||||
|
||||
// Handle scroll sync event from editor scroll
|
||||
$rootScope.$on('markdown-scroll', (event, lineCount) => {
|
||||
@ -315,140 +416,6 @@ module.exports = function (ngApp, events) {
|
||||
}, {queue: false, duration: 200, easing: 'linear'});
|
||||
}
|
||||
});
|
||||
|
||||
// Editor key-presses
|
||||
$input.keydown(event => {
|
||||
// Insert image shortcut
|
||||
if (event.which === 73 && event.ctrlKey && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
let caretPos = $input[0].selectionStart;
|
||||
let currentContent = $input.val();
|
||||
const mdImageText = "![](http://)";
|
||||
$input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
|
||||
$input.focus();
|
||||
$input[0].selectionStart = caretPos + ("![](".length);
|
||||
$input[0].selectionEnd = caretPos + ('![](http://'.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert entity link shortcut
|
||||
if (event.which === 75 && event.ctrlKey && event.shiftKey) {
|
||||
showLinkSelector();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass key presses to controller via event
|
||||
scope.$emit('editor-keydown', event);
|
||||
});
|
||||
|
||||
// Insert image from image manager
|
||||
$insertImage.click(event => {
|
||||
window.ImageManager.showExternal(image => {
|
||||
let caretPos = currentCaretPos;
|
||||
let currentContent = $input.val();
|
||||
let mdImageText = "![" + image.name + "](" + image.thumbs.display + ")";
|
||||
$input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
|
||||
$input.change();
|
||||
});
|
||||
});
|
||||
|
||||
function showLinkSelector() {
|
||||
window.showEntityLinkSelector((entity) => {
|
||||
let selectionStart = currentCaretPos;
|
||||
let selectionEnd = $input[0].selectionEnd;
|
||||
let textSelected = (selectionEnd !== selectionStart);
|
||||
let currentContent = $input.val();
|
||||
|
||||
if (textSelected) {
|
||||
let selectedText = currentContent.substring(selectionStart, selectionEnd);
|
||||
let linkText = `[${selectedText}](${entity.link})`;
|
||||
$input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionEnd));
|
||||
} else {
|
||||
let linkText = ` [${entity.name}](${entity.link}) `;
|
||||
$input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionStart))
|
||||
}
|
||||
$input.change();
|
||||
});
|
||||
}
|
||||
$insertEntityLink.click(showLinkSelector);
|
||||
|
||||
// Upload and insert image on paste
|
||||
function editorPaste(e) {
|
||||
e = e.originalEvent;
|
||||
if (!e.clipboardData) return
|
||||
let items = e.clipboardData.items;
|
||||
if (!items) return;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
uploadImage(items[i].getAsFile());
|
||||
}
|
||||
}
|
||||
|
||||
$input.on('paste', editorPaste);
|
||||
|
||||
// Handle image drop, Uploads images to BookStack.
|
||||
function handleImageDrop(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let files = event.originalEvent.dataTransfer.files;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
uploadImage(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
$input.on('drop', handleImageDrop);
|
||||
|
||||
// Handle image upload and add image into markdown content
|
||||
function uploadImage(file) {
|
||||
if (file.type.indexOf('image') !== 0) return;
|
||||
let formData = new FormData();
|
||||
let ext = 'png';
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
if (file.name) {
|
||||
let fileNameMatches = file.name.match(/\.(.+)$/);
|
||||
if (fileNameMatches) {
|
||||
ext = fileNameMatches[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Insert image into markdown
|
||||
let id = "image-" + Math.random().toString(16).slice(2);
|
||||
let selectStart = $input[0].selectionStart;
|
||||
let selectEnd = $input[0].selectionEnd;
|
||||
let content = $input[0].value;
|
||||
let selectText = content.substring(selectStart, selectEnd);
|
||||
let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
|
||||
let innerContent = ((selectEnd > selectStart) ? `![${selectText}]` : '![]') + `(${placeholderImage})`;
|
||||
$input[0].value = content.substring(0, selectStart) + innerContent + content.substring(selectEnd);
|
||||
|
||||
$input.focus();
|
||||
$input[0].selectionStart = selectStart;
|
||||
$input[0].selectionEnd = selectStart;
|
||||
|
||||
let remoteFilename = "image-" + Date.now() + "." + ext;
|
||||
formData.append('file', file, remoteFilename);
|
||||
formData.append('_token', document.querySelector('meta[name="token"]').getAttribute('content'));
|
||||
|
||||
xhr.open('POST', window.baseUrl('/images/gallery/upload'));
|
||||
xhr.onload = function () {
|
||||
let selectStart = $input[0].selectionStart;
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
let result = JSON.parse(xhr.responseText);
|
||||
$input[0].value = $input[0].value.replace(placeholderImage, result.thumbs.display);
|
||||
$input.change();
|
||||
} else {
|
||||
console.log(trans('errors.image_upload_error'));
|
||||
console.log(xhr.responseText);
|
||||
$input[0].value = $input[0].value.replace(innerContent, '');
|
||||
$input.change();
|
||||
}
|
||||
$input.focus();
|
||||
$input[0].selectionStart = selectStart;
|
||||
$input[0].selectionEnd = selectStart;
|
||||
};
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
@ -17,6 +17,7 @@ let axiosInstance = axios.create({
|
||||
'baseURL': window.baseUrl('')
|
||||
}
|
||||
});
|
||||
window.$http = axiosInstance;
|
||||
|
||||
Vue.prototype.$http = axiosInstance;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const Code = require('../code');
|
||||
|
||||
/**
|
||||
* Handle pasting images from clipboard.
|
||||
* @param e - event
|
||||
@ -56,17 +58,130 @@ function registerEditorShortcuts(editor) {
|
||||
// Other block shortcuts
|
||||
editor.addShortcut('meta+q', '', ['FormatBlock', false, 'blockquote']);
|
||||
editor.addShortcut('meta+d', '', ['FormatBlock', false, 'p']);
|
||||
editor.addShortcut('meta+e', '', ['FormatBlock', false, 'pre']);
|
||||
editor.addShortcut('meta+e', '', ['codeeditor', false, 'pre']);
|
||||
editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create and enable our custom code plugin
|
||||
*/
|
||||
function codePlugin() {
|
||||
|
||||
function elemIsCodeBlock(elem) {
|
||||
return elem.className === 'CodeMirrorContainer';
|
||||
}
|
||||
|
||||
function showPopup(editor) {
|
||||
let selectedNode = editor.selection.getNode();
|
||||
|
||||
if (!elemIsCodeBlock(selectedNode)) {
|
||||
let providedCode = editor.selection.getNode().textContent;
|
||||
window.vues['code-editor'].open(providedCode, '', (code, lang) => {
|
||||
let wrap = document.createElement('div');
|
||||
wrap.innerHTML = `<pre><code class="language-${lang}"></code></pre>`;
|
||||
wrap.querySelector('code').innerText = code;
|
||||
|
||||
editor.formatter.toggle('pre');
|
||||
let node = editor.selection.getNode();
|
||||
editor.dom.setHTML(node, wrap.querySelector('pre').innerHTML);
|
||||
editor.fire('SetContent');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let lang = selectedNode.hasAttribute('data-lang') ? selectedNode.getAttribute('data-lang') : '';
|
||||
let currentCode = selectedNode.querySelector('textarea').textContent;
|
||||
|
||||
window.vues['code-editor'].open(currentCode, lang, (code, lang) => {
|
||||
let editorElem = selectedNode.querySelector('.CodeMirror');
|
||||
let cmInstance = editorElem.CodeMirror;
|
||||
if (cmInstance) {
|
||||
Code.setContent(cmInstance, code);
|
||||
Code.setMode(cmInstance, lang);
|
||||
}
|
||||
let textArea = selectedNode.querySelector('textarea');
|
||||
if (textArea) textArea.textContent = code;
|
||||
selectedNode.setAttribute('data-lang', lang);
|
||||
});
|
||||
}
|
||||
|
||||
function codeMirrorContainerToPre($codeMirrorContainer) {
|
||||
let textArea = $codeMirrorContainer[0].querySelector('textarea');
|
||||
let code = textArea.textContent;
|
||||
let lang = $codeMirrorContainer[0].getAttribute('data-lang');
|
||||
|
||||
$codeMirrorContainer.removeAttr('contentEditable');
|
||||
let $pre = $('<pre></pre>');
|
||||
$pre.append($('<code></code>').each((index, elem) => {
|
||||
// Needs to be textContent since innerText produces BR:s
|
||||
elem.textContent = code;
|
||||
}).attr('class', `language-${lang}`));
|
||||
$codeMirrorContainer.replaceWith($pre);
|
||||
}
|
||||
|
||||
window.tinymce.PluginManager.add('codeeditor', (editor, url) => {
|
||||
|
||||
let $ = editor.$;
|
||||
|
||||
editor.addButton('codeeditor', {
|
||||
text: 'Code block',
|
||||
icon: false,
|
||||
cmd: 'codeeditor'
|
||||
});
|
||||
|
||||
editor.addCommand('codeeditor', () => {
|
||||
showPopup(editor);
|
||||
});
|
||||
|
||||
// Convert
|
||||
editor.on('PreProcess', function (e) {
|
||||
$('div.CodeMirrorContainer', e.node).
|
||||
each((index, elem) => {
|
||||
let $elem = $(elem);
|
||||
codeMirrorContainerToPre($elem);
|
||||
});
|
||||
});
|
||||
|
||||
editor.on('dblclick', event => {
|
||||
let selectedNode = editor.selection.getNode();
|
||||
if (!elemIsCodeBlock(selectedNode)) return;
|
||||
showPopup(editor);
|
||||
});
|
||||
|
||||
editor.on('SetContent', function () {
|
||||
|
||||
// Recover broken codemirror instances
|
||||
$('.CodeMirrorContainer').filter((index ,elem) => {
|
||||
return typeof elem.querySelector('.CodeMirror').CodeMirror === 'undefined';
|
||||
}).each((index, elem) => {
|
||||
codeMirrorContainerToPre($(elem));
|
||||
});
|
||||
|
||||
let codeSamples = $('body > pre').filter((index, elem) => {
|
||||
return elem.contentEditable !== "false";
|
||||
});
|
||||
|
||||
if (!codeSamples.length) return;
|
||||
editor.undoManager.transact(function () {
|
||||
codeSamples.each((index, elem) => {
|
||||
Code.wysiwygView(elem);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function() {
|
||||
codePlugin();
|
||||
let settings = {
|
||||
selector: '#html-editor',
|
||||
content_css: [
|
||||
window.baseUrl('/css/styles.css'),
|
||||
window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
|
||||
],
|
||||
branding: false,
|
||||
body_class: 'page-content',
|
||||
browser_spellcheck: true,
|
||||
relative_urls: false,
|
||||
@ -77,10 +192,10 @@ module.exports = function() {
|
||||
paste_data_images: false,
|
||||
extended_valid_elements: 'pre[*]',
|
||||
automatic_uploads: false,
|
||||
valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
|
||||
plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codesample",
|
||||
valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre]",
|
||||
plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor",
|
||||
imagetools_toolbar: 'imageoptions',
|
||||
toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen codesample",
|
||||
toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
|
||||
content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
|
||||
style_formats: [
|
||||
{title: "Header Large", format: "h2"},
|
||||
@ -89,17 +204,18 @@ module.exports = function() {
|
||||
{title: "Header Tiny", format: "h5"},
|
||||
{title: "Paragraph", format: "p", exact: true, classes: ''},
|
||||
{title: "Blockquote", format: "blockquote"},
|
||||
{title: "Code Block", icon: "code", format: "pre"},
|
||||
{title: "Code Block", icon: "code", cmd: 'codeeditor', format: 'codeeditor'},
|
||||
{title: "Inline Code", icon: "code", inline: "code"},
|
||||
{title: "Callouts", items: [
|
||||
{title: "Success", block: 'p', exact: true, attributes : {'class' : 'callout success'}},
|
||||
{title: "Info", block: 'p', exact: true, attributes : {'class' : 'callout info'}},
|
||||
{title: "Warning", block: 'p', exact: true, attributes : {'class' : 'callout warning'}},
|
||||
{title: "Danger", block: 'p', exact: true, attributes : {'class' : 'callout danger'}}
|
||||
]}
|
||||
]},
|
||||
],
|
||||
style_formats_merge: false,
|
||||
formats: {
|
||||
codeeditor: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div'},
|
||||
alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'},
|
||||
aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'},
|
||||
alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
|
||||
|
43
resources/assets/js/vues/code-editor.js
Normal file
43
resources/assets/js/vues/code-editor.js
Normal file
@ -0,0 +1,43 @@
|
||||
const codeLib = require('../code');
|
||||
|
||||
const methods = {
|
||||
show() {
|
||||
if (!this.editor) this.editor = codeLib.popupEditor(this.$refs.editor, this.language);
|
||||
this.$refs.overlay.style.display = 'flex';
|
||||
},
|
||||
hide() {
|
||||
this.$refs.overlay.style.display = 'none';
|
||||
},
|
||||
updateEditorMode(language) {
|
||||
codeLib.setMode(this.editor, language);
|
||||
},
|
||||
updateLanguage(lang) {
|
||||
this.language = lang;
|
||||
this.updateEditorMode(lang);
|
||||
},
|
||||
open(code, language, callback) {
|
||||
this.show();
|
||||
this.updateEditorMode(language);
|
||||
this.language = language;
|
||||
codeLib.setContent(this.editor, code);
|
||||
this.code = code;
|
||||
this.callback = callback;
|
||||
},
|
||||
save() {
|
||||
if (!this.callback) return;
|
||||
this.callback(this.editor.getValue(), this.language);
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
const data = {
|
||||
editor: null,
|
||||
language: '',
|
||||
code: '',
|
||||
callback: null
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
methods,
|
||||
data
|
||||
};
|
@ -7,12 +7,15 @@ function exists(id) {
|
||||
let vueMapping = {
|
||||
'search-system': require('./search'),
|
||||
'entity-dashboard': require('./entity-search'),
|
||||
'code-editor': require('./code-editor')
|
||||
};
|
||||
|
||||
window.vues = {};
|
||||
|
||||
Object.keys(vueMapping).forEach(id => {
|
||||
if (exists(id)) {
|
||||
let config = vueMapping[id];
|
||||
config.el = '#' + id;
|
||||
new Vue(config);
|
||||
window.vues[id] = new Vue(config);
|
||||
}
|
||||
});
|
@ -248,6 +248,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
&:after {
|
||||
content: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
|
@ -467,3 +467,16 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
.image-picker .none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#code-editor .CodeMirror {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#code-editor .lang-options {
|
||||
max-width: 400px;
|
||||
margin-bottom: $-s;
|
||||
a {
|
||||
margin-right: $-xs;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@
|
||||
#markdown-editor {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
textarea {
|
||||
#markdown-editor-input {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
@ -155,7 +155,6 @@ form.search-box {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.faded span.faded-text {
|
||||
@ -175,6 +174,13 @@ form.search-box {
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
.action-buttons .dropdown-container:last-child a {
|
||||
padding-right: 0;
|
||||
padding-left: $-s;
|
||||
}
|
||||
.action-buttons {
|
||||
text-align: right;
|
||||
|
@ -135,6 +135,21 @@ pre {
|
||||
font-size: 12px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #DDD;
|
||||
padding-left: 31px;
|
||||
position: relative;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 29px;
|
||||
left: 0;
|
||||
background-color: #f5f5f5;
|
||||
height: 100%;
|
||||
border-right: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -182,6 +197,7 @@ pre code {
|
||||
border: 0;
|
||||
font-size: 1em;
|
||||
display: block;
|
||||
line-height: 1.6;
|
||||
}
|
||||
/*
|
||||
* Text colors
|
||||
|
@ -20,5 +20,13 @@ return [
|
||||
'image_preview' => 'Image Preview',
|
||||
'image_upload_success' => 'Image uploaded successfully',
|
||||
'image_update_success' => 'Image details successfully updated',
|
||||
'image_delete_success' => 'Image successfully deleted'
|
||||
'image_delete_success' => 'Image successfully deleted',
|
||||
|
||||
/**
|
||||
* Code editor
|
||||
*/
|
||||
'code_editor' => 'Edit Code',
|
||||
'code_language' => 'Code Language',
|
||||
'code_content' => 'Code Content',
|
||||
'code_save' => 'Save Code',
|
||||
];
|
@ -121,6 +121,7 @@ return [
|
||||
'nl' => 'Nederlands',
|
||||
'pt_BR' => 'Português do Brasil',
|
||||
'sk' => 'Slovensky',
|
||||
'ja' => '日本語',
|
||||
]
|
||||
///////////////////////////////////
|
||||
];
|
||||
|
40
resources/lang/ja/activities.php
Normal file
40
resources/lang/ja/activities.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* Activity text strings.
|
||||
* Is used for all the text within activity logs & notifications.
|
||||
*/
|
||||
|
||||
// Pages
|
||||
'page_create' => 'がページを作成:',
|
||||
'page_create_notification' => 'ページを作成しました',
|
||||
'page_update' => 'がページを更新:',
|
||||
'page_update_notification' => 'ページを更新しました',
|
||||
'page_delete' => 'がページを削除:',
|
||||
'page_delete_notification' => 'ページを削除しました',
|
||||
'page_restore' => 'がページを復元:',
|
||||
'page_restore_notification' => 'ページを復元しました',
|
||||
'page_move' => 'がページを移動:',
|
||||
|
||||
// Chapters
|
||||
'chapter_create' => 'がチャプターを作成:',
|
||||
'chapter_create_notification' => 'チャプターを作成しました',
|
||||
'chapter_update' => 'がチャプターを更新:',
|
||||
'chapter_update_notification' => 'チャプターを更新しました',
|
||||
'chapter_delete' => 'がチャプターを削除:',
|
||||
'chapter_delete_notification' => 'チャプターを削除しました',
|
||||
'chapter_move' => 'がチャプターを移動:',
|
||||
|
||||
// Books
|
||||
'book_create' => 'がブックを作成:',
|
||||
'book_create_notification' => 'ブックを作成しました',
|
||||
'book_update' => 'がブックを更新:',
|
||||
'book_update_notification' => 'ブックを更新しました',
|
||||
'book_delete' => 'がブックを削除:',
|
||||
'book_delete_notification' => 'ブックを削除しました',
|
||||
'book_sort' => 'がブックの並び順を変更:',
|
||||
'book_sort_notification' => '並び順を変更しました',
|
||||
|
||||
];
|
76
resources/lang/ja/auth.php
Normal file
76
resources/lang/ja/auth.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
'failed' => 'この資格情報は登録されていません。',
|
||||
'throttle' => 'ログイン試行回数が制限を超えました。:seconds秒後に再試行してください。',
|
||||
|
||||
/**
|
||||
* Login & Register
|
||||
*/
|
||||
'sign_up' => '新規登録',
|
||||
'log_in' => 'ログイン',
|
||||
'log_in_with' => ':socialDriverでログイン',
|
||||
'sign_up_with' => ':socialDriverで登録',
|
||||
'logout' => 'ログアウト',
|
||||
|
||||
'name' => '名前',
|
||||
'username' => 'ユーザ名',
|
||||
'email' => 'メールアドレス',
|
||||
'password' => 'パスワード',
|
||||
'password_confirm' => 'パスワード (確認)',
|
||||
'password_hint' => '5文字以上である必要があります',
|
||||
'forgot_password' => 'パスワードをお忘れですか?',
|
||||
'remember_me' => 'ログイン情報を保存する',
|
||||
'ldap_email_hint' => 'このアカウントで使用するEメールアドレスを入力してください。',
|
||||
'create_account' => 'アカウント作成',
|
||||
'social_login' => 'SNSログイン',
|
||||
'social_registration' => 'SNS登録',
|
||||
'social_registration_text' => '他のサービスで登録 / ログインする',
|
||||
|
||||
'register_thanks' => '登録が完了しました!',
|
||||
'register_confirm' => 'メール内の確認ボタンを押して、:appNameへアクセスしてください。',
|
||||
'registrations_disabled' => '登録は現在停止中です。',
|
||||
'registration_email_domain_invalid' => 'このEmailドメインでの登録は許可されていません。',
|
||||
'register_success' => '登録が完了し、ログインできるようになりました!',
|
||||
|
||||
|
||||
/**
|
||||
* Password Reset
|
||||
*/
|
||||
'reset_password' => 'パスワードリセット',
|
||||
'reset_password_send_instructions' => '以下にEメールアドレスを入力すると、パスワードリセットリンクが記載されたメールが送信されます。',
|
||||
'reset_password_send_button' => 'リセットリンクを送信',
|
||||
'reset_password_sent_success' => ':emailへリセットリンクを送信しました。',
|
||||
'reset_password_success' => 'パスワードがリセットされました。',
|
||||
|
||||
'email_reset_subject' => ':appNameのパスワードをリセット',
|
||||
'email_reset_text' => 'このメールは、パスワードリセットがリクエストされたため送信されています。',
|
||||
'email_reset_not_requested' => 'もしパスワードリセットを希望しない場合、操作は不要です。',
|
||||
|
||||
|
||||
/**
|
||||
* Email Confirmation
|
||||
*/
|
||||
'email_confirm_subject' => ':appNameのメールアドレス確認',
|
||||
'email_confirm_greeting' => ':appNameへ登録してくださりありがとうございます!',
|
||||
'email_confirm_text' => '以下のボタンを押し、メールアドレスを確認してください:',
|
||||
'email_confirm_action' => 'メールアドレスを確認',
|
||||
'email_confirm_send_error' => 'Eメールの確認が必要でしたが、システム上でEメールの送信ができませんでした。管理者に連絡し、Eメールが正しく設定されていることを確認してください。',
|
||||
'email_confirm_success' => 'Eメールアドレスが確認されました。',
|
||||
'email_confirm_resent' => '確認メールを再送信しました。受信トレイを確認してください。',
|
||||
|
||||
'email_not_confirmed' => 'Eメールアドレスが確認できていません',
|
||||
'email_not_confirmed_text' => 'Eメールアドレスの確認が完了していません。',
|
||||
'email_not_confirmed_click_link' => '登録時に受信したメールを確認し、確認リンクをクリックしてください。',
|
||||
'email_not_confirmed_resend' => 'Eメールが見つからない場合、以下のフォームから再送信してください。',
|
||||
'email_not_confirmed_resend_button' => '確認メールを再送信',
|
||||
];
|
59
resources/lang/ja/common.php
Normal file
59
resources/lang/ja/common.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
/**
|
||||
* Buttons
|
||||
*/
|
||||
'cancel' => 'キャンセル',
|
||||
'confirm' => '確認',
|
||||
'back' => '戻る',
|
||||
'save' => '保存',
|
||||
'continue' => '続ける',
|
||||
'select' => '選択',
|
||||
|
||||
/**
|
||||
* Form Labels
|
||||
*/
|
||||
'name' => '名称',
|
||||
'description' => '概要',
|
||||
'role' => '権限',
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
'actions' => '実行',
|
||||
'view' => '表示',
|
||||
'create' => '作成',
|
||||
'update' => '更新',
|
||||
'edit' => '編集',
|
||||
'sort' => '並び順',
|
||||
'move' => '移動',
|
||||
'delete' => '削除',
|
||||
'search' => '検索',
|
||||
'search_clear' => '検索をクリア',
|
||||
'reset' => 'リセット',
|
||||
'remove' => '削除',
|
||||
'add' => '追加',
|
||||
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
'deleted_user' => '削除済みユーザ',
|
||||
'no_activity' => '表示するアクティビティがありません',
|
||||
'no_items' => 'アイテムはありません',
|
||||
'back_to_top' => '上に戻る',
|
||||
'toggle_details' => '概要の表示切替',
|
||||
|
||||
/**
|
||||
* Header
|
||||
*/
|
||||
'view_profile' => 'プロフィール表示',
|
||||
'edit_profile' => 'プロフィール編集',
|
||||
|
||||
/**
|
||||
* Email Content
|
||||
*/
|
||||
'email_action_help' => '":actionText" をクリックできない場合、以下のURLをコピーしブラウザで開いてください:',
|
||||
'email_rights' => 'All rights reserved',
|
||||
];
|
24
resources/lang/ja/components.php
Normal file
24
resources/lang/ja/components.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
/**
|
||||
* Image Manager
|
||||
*/
|
||||
'image_select' => '画像を選択',
|
||||
'image_all' => 'すべて',
|
||||
'image_all_title' => '全ての画像を表示',
|
||||
'image_book_title' => 'このブックにアップロードされた画像を表示',
|
||||
'image_page_title' => 'このページにアップロードされた画像を表示',
|
||||
'image_search_hint' => '画像名で検索',
|
||||
'image_uploaded' => 'アップロード日時: :uploadedDate',
|
||||
'image_load_more' => 'さらに読み込む',
|
||||
'image_image_name' => '画像名',
|
||||
'image_delete_confirm' => 'この画像は以下のページで利用されています。削除してもよろしければ、再度ボタンを押して下さい。',
|
||||
'image_select_image' => '選択',
|
||||
'image_dropzone' => '画像をドロップするか、クリックしてアップロード',
|
||||
'images_deleted' => '画像を削除しました',
|
||||
'image_preview' => '画像プレビュー',
|
||||
'image_upload_success' => '画像がアップロードされました',
|
||||
'image_update_success' => '画像が更新されました',
|
||||
'image_delete_success' => '画像が削除されました'
|
||||
];
|
236
resources/lang/ja/entities.php
Normal file
236
resources/lang/ja/entities.php
Normal file
@ -0,0 +1,236 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
/**
|
||||
* Shared
|
||||
*/
|
||||
'recently_created' => '最近作成',
|
||||
'recently_created_pages' => '最近作成されたページ',
|
||||
'recently_updated_pages' => '最近更新されたページ',
|
||||
'recently_created_chapters' => '最近作成されたチャプター',
|
||||
'recently_created_books' => '最近作成されたブック',
|
||||
'recently_update' => '最近更新',
|
||||
'recently_viewed' => '閲覧履歴',
|
||||
'recent_activity' => 'アクティビティ',
|
||||
'create_now' => '作成する',
|
||||
'revisions' => '編集履歴',
|
||||
'meta_revision' => 'リビジョン #:revisionCount',
|
||||
'meta_created' => '作成: :timeLength',
|
||||
'meta_created_name' => '作成: :timeLength (:user)',
|
||||
'meta_updated' => '更新: :timeLength',
|
||||
'meta_updated_name' => '更新: :timeLength (:user)',
|
||||
'x_pages' => ':countページ',
|
||||
'entity_select' => 'エンティティ選択',
|
||||
'images' => '画像',
|
||||
'my_recent_drafts' => '最近の下書き',
|
||||
'my_recently_viewed' => '閲覧履歴',
|
||||
'no_pages_viewed' => 'なにもページを閲覧していません',
|
||||
'no_pages_recently_created' => '最近作成されたページはありません',
|
||||
'no_pages_recently_updated' => '最近更新されたページはありません。',
|
||||
'export' => 'エクスポート',
|
||||
'export_html' => 'Webページ',
|
||||
'export_pdf' => 'PDF',
|
||||
'export_text' => 'テキストファイル',
|
||||
|
||||
/**
|
||||
* Permissions and restrictions
|
||||
*/
|
||||
'permissions' => '権限',
|
||||
'permissions_intro' => 'この設定は各ユーザの役割よりも優先して適用されます。',
|
||||
'permissions_enable' => 'カスタム権限設定を有効にする',
|
||||
'permissions_save' => '権限を保存',
|
||||
|
||||
/**
|
||||
* Search
|
||||
*/
|
||||
'search_results' => '検索結果',
|
||||
'search_total_results_found' => ':count件見つかりました',
|
||||
'search_clear' => '検索をクリア',
|
||||
'search_no_pages' => 'ページが見つかりませんでした。',
|
||||
'search_for_term' => ':term の検索結果',
|
||||
'search_more' => 'さらに表示',
|
||||
'search_filters' => '検索フィルタ',
|
||||
'search_content_type' => '種類',
|
||||
'search_exact_matches' => '完全一致',
|
||||
'search_tags' => 'タグ検索',
|
||||
'search_viewed_by_me' => '自分が閲覧したことがある',
|
||||
'search_not_viewed_by_me' => '自分が閲覧したことがない',
|
||||
'search_permissions_set' => '権限が設定されている',
|
||||
'search_created_by_me' => '自分が作成した',
|
||||
'search_updated_by_me' => '自分が更新した',
|
||||
'search_updated_before' => '以前に更新',
|
||||
'search_updated_after' => '以降に更新',
|
||||
'search_created_before' => '以前に作成',
|
||||
'search_created_after' => '以降に更新',
|
||||
'search_set_date' => '日付を設定',
|
||||
'search_update' => 'フィルタを更新',
|
||||
|
||||
/**
|
||||
* Books
|
||||
*/
|
||||
'book' => 'Book',
|
||||
'books' => 'ブック',
|
||||
'books_empty' => 'まだブックは作成されていません',
|
||||
'books_popular' => '人気のブック',
|
||||
'books_recent' => '最近のブック',
|
||||
'books_popular_empty' => 'ここに人気のブックが表示されます。',
|
||||
'books_create' => '新しいブックを作成',
|
||||
'books_delete' => 'ブックを削除',
|
||||
'books_delete_named' => 'ブック「:bookName」を削除',
|
||||
'books_delete_explain' => '「:bookName」を削除すると、ブック内のページとチャプターも削除されます。',
|
||||
'books_delete_confirmation' => '本当にこのブックを削除してよろしいですか?',
|
||||
'books_edit' => 'ブックを編集',
|
||||
'books_edit_named' => 'ブック「:bookName」を編集',
|
||||
'books_form_book_name' => 'ブック名',
|
||||
'books_save' => 'ブックを保存',
|
||||
'books_permissions' => 'ブックの権限',
|
||||
'books_permissions_updated' => 'ブックの権限を更新しました',
|
||||
'books_empty_contents' => 'まだページまたはチャプターが作成されていません。',
|
||||
'books_empty_create_page' => '新しいページを作成',
|
||||
'books_empty_or' => 'または',
|
||||
'books_empty_sort_current_book' => 'ブックの並び順を変更',
|
||||
'books_empty_add_chapter' => 'チャプターを追加',
|
||||
'books_permissions_active' => 'ブックの権限は有効です',
|
||||
'books_search_this' => 'このブックから検索',
|
||||
'books_navigation' => '目次',
|
||||
'books_sort' => '並び順を変更',
|
||||
'books_sort_named' => 'ブック「:bookName」を並び替え',
|
||||
'books_sort_show_other' => '他のブックを表示',
|
||||
'books_sort_save' => '並び順を保存',
|
||||
|
||||
/**
|
||||
* Chapters
|
||||
*/
|
||||
'chapter' => 'チャプター',
|
||||
'chapters' => 'チャプター',
|
||||
'chapters_popular' => '人気のチャプター',
|
||||
'chapters_new' => 'チャプターを作成',
|
||||
'chapters_create' => 'チャプターを作成',
|
||||
'chapters_delete' => 'チャプターを削除',
|
||||
'chapters_delete_named' => 'チャプター「:chapterName」を削除',
|
||||
'chapters_delete_explain' => 'チャプター「:chapterName」を削除すると、チャプター内のすべてのページはブック内に直接追加されます。',
|
||||
'chapters_delete_confirm' => 'チャプターを削除してよろしいですか?',
|
||||
'chapters_edit' => 'チャプターを編集',
|
||||
'chapters_edit_named' => 'チャプター「:chapterName」を編集',
|
||||
'chapters_save' => 'チャプターを保存',
|
||||
'chapters_move' => 'チャプターを移動',
|
||||
'chapters_move_named' => 'チャプター「:chapterName」を移動',
|
||||
'chapter_move_success' => 'チャプターを「:bookName」に移動しました',
|
||||
'chapters_permissions' => 'チャプター権限',
|
||||
'chapters_empty' => 'まだチャプター内にページはありません。',
|
||||
'chapters_permissions_active' => 'チャプターの権限は有効です',
|
||||
'chapters_permissions_success' => 'チャプターの権限を更新しました',
|
||||
'chapters_search_this' => 'このチャプターを検索',
|
||||
|
||||
/**
|
||||
* Pages
|
||||
*/
|
||||
'page' => 'ページ',
|
||||
'pages' => 'ページ',
|
||||
'pages_popular' => '人気のページ',
|
||||
'pages_new' => 'ページを作成',
|
||||
'pages_attachments' => '添付',
|
||||
'pages_navigation' => 'ページナビゲーション',
|
||||
'pages_delete' => 'ページを削除',
|
||||
'pages_delete_named' => 'ページ :pageName を削除',
|
||||
'pages_delete_draft_named' => 'ページ :pageName の下書きを削除',
|
||||
'pages_delete_draft' => 'ページの下書きを削除',
|
||||
'pages_delete_success' => 'ページを削除しました',
|
||||
'pages_delete_draft_success' => 'ページの下書きを削除しました',
|
||||
'pages_delete_confirm' => 'このページを削除してもよろしいですか?',
|
||||
'pages_delete_draft_confirm' => 'このページの下書きを削除してもよろしいですか?',
|
||||
'pages_editing_named' => 'ページ :pageName を編集',
|
||||
'pages_edit_toggle_header' => 'ヘッダーの表示切替',
|
||||
'pages_edit_save_draft' => '下書きを保存',
|
||||
'pages_edit_draft' => 'ページの下書きを編集',
|
||||
'pages_editing_draft' => '下書きを編集中',
|
||||
'pages_editing_page' => 'ページを編集中',
|
||||
'pages_edit_draft_save_at' => '下書きを保存済み: ',
|
||||
'pages_edit_delete_draft' => '下書きを削除',
|
||||
'pages_edit_discard_draft' => '下書きを破棄',
|
||||
'pages_edit_set_changelog' => '編集内容についての説明',
|
||||
'pages_edit_enter_changelog_desc' => 'どのような変更を行ったのかを記録してください',
|
||||
'pages_edit_enter_changelog' => '編集内容を入力',
|
||||
'pages_save' => 'ページを保存',
|
||||
'pages_title' => 'ページタイトル',
|
||||
'pages_name' => 'ページ名',
|
||||
'pages_md_editor' => 'エディター',
|
||||
'pages_md_preview' => 'プレビュー',
|
||||
'pages_md_insert_image' => '画像を挿入',
|
||||
'pages_md_insert_link' => 'エンティティへのリンクを挿入',
|
||||
'pages_not_in_chapter' => 'チャプターが設定されていません',
|
||||
'pages_move' => 'ページを移動',
|
||||
'pages_move_success' => 'ページを ":parentName" へ移動しました',
|
||||
'pages_permissions' => 'ページの権限設定',
|
||||
'pages_permissions_success' => 'ページの権限を更新しました',
|
||||
'pages_revisions' => '編集履歴',
|
||||
'pages_revisions_named' => ':pageName のリビジョン',
|
||||
'pages_revision_named' => ':pageName のリビジョン',
|
||||
'pages_revisions_created_by' => '作成者',
|
||||
'pages_revisions_date' => '日付',
|
||||
'pages_revisions_number' => 'リビジョン',
|
||||
'pages_revisions_changelog' => '説明',
|
||||
'pages_revisions_changes' => '変更点',
|
||||
'pages_revisions_current' => '現在のバージョン',
|
||||
'pages_revisions_preview' => 'プレビュー',
|
||||
'pages_revisions_restore' => '復元',
|
||||
'pages_revisions_none' => 'このページにはリビジョンがありません',
|
||||
'pages_copy_link' => 'リンクをコピー',
|
||||
'pages_permissions_active' => 'ページの権限は有効です',
|
||||
'pages_initial_revision' => '初回の公開',
|
||||
'pages_initial_name' => '新規ページ',
|
||||
'pages_editing_draft_notification' => ':timeDiffに保存された下書きを編集しています。',
|
||||
'pages_draft_edited_notification' => 'このページは更新されています。下書きを破棄することを推奨します。',
|
||||
'pages_draft_edit_active' => [
|
||||
'start_a' => ':count人のユーザがページの編集を開始しました',
|
||||
'start_b' => ':userNameがページの編集を開始しました',
|
||||
'time_a' => '数秒前に保存されました',
|
||||
'time_b' => ':minCount分前に保存されました',
|
||||
'message' => ':start :time. 他のユーザによる更新を上書きしないよう注意してください。',
|
||||
],
|
||||
'pages_draft_discarded' => '下書きが破棄されました。エディタは現在の内容へ復元されています。',
|
||||
|
||||
/**
|
||||
* Editor sidebar
|
||||
*/
|
||||
'page_tags' => 'タグ',
|
||||
'tag' => 'タグ',
|
||||
'tags' => '',
|
||||
'tag_value' => '内容 (オプション)',
|
||||
'tags_explain' => "タグを設定すると、コンテンツの管理が容易になります。\nより高度な管理をしたい場合、タグに内容を設定できます。",
|
||||
'tags_add' => 'タグを追加',
|
||||
'attachments' => '添付ファイル',
|
||||
'attachments_explain' => 'ファイルをアップロードまたはリンクを添付することができます。これらはサイドバーで確認できます。',
|
||||
'attachments_explain_instant_save' => 'この変更は即座に保存されます。',
|
||||
'attachments_items' => 'アイテム',
|
||||
'attachments_upload' => 'アップロード',
|
||||
'attachments_link' => 'リンクを添付',
|
||||
'attachments_set_link' => 'リンクを設定',
|
||||
'attachments_delete_confirm' => 'もう一度クリックし、削除を確認してください。',
|
||||
'attachments_dropzone' => 'ファイルをドロップするか、クリックして選択',
|
||||
'attachments_no_files' => 'ファイルはアップロードされていません',
|
||||
'attachments_explain_link' => 'ファイルをアップロードしたくない場合、他のページやクラウド上のファイルへのリンクを添付できます。',
|
||||
'attachments_link_name' => 'リンク名',
|
||||
'attachment_link' => '添付リンク',
|
||||
'attachments_link_url' => 'ファイルURL',
|
||||
'attachments_link_url_hint' => 'WebサイトまたはファイルへのURL',
|
||||
'attach' => '添付',
|
||||
'attachments_edit_file' => 'ファイルを編集',
|
||||
'attachments_edit_file_name' => 'ファイル名',
|
||||
'attachments_edit_drop_upload' => 'ファイルをドロップするか、クリックしてアップロード',
|
||||
'attachments_order_updated' => '添付ファイルの並び順が変更されました',
|
||||
'attachments_updated_success' => '添付ファイルが更新されました',
|
||||
'attachments_deleted' => '添付は削除されました',
|
||||
'attachments_file_uploaded' => 'ファイルがアップロードされました',
|
||||
'attachments_file_updated' => 'ファイルが更新されました',
|
||||
'attachments_link_attached' => 'リンクがページへ添付されました',
|
||||
|
||||
/**
|
||||
* Profile View
|
||||
*/
|
||||
'profile_user_for_x' => ':time前に作成',
|
||||
'profile_created_content' => '作成したコンテンツ',
|
||||
'profile_not_created_pages' => ':userNameはページを作成していません',
|
||||
'profile_not_created_chapters' => ':userNameはチャプターを作成していません',
|
||||
'profile_not_created_books' => ':userNameはブックを作成していません',
|
||||
];
|
70
resources/lang/ja/errors.php
Normal file
70
resources/lang/ja/errors.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* Error text strings.
|
||||
*/
|
||||
|
||||
// Permissions
|
||||
'permission' => 'リクエストされたページへの権限がありません。',
|
||||
'permissionJson' => '要求されたアクションを実行する権限がありません。',
|
||||
|
||||
// Auth
|
||||
'error_user_exists_different_creds' => ':emailを持つユーザは既に存在しますが、資格情報が異なります。',
|
||||
'email_already_confirmed' => 'Eメールは既に確認済みです。ログインしてください。',
|
||||
'email_confirmation_invalid' => 'この確認トークンは無効か、または既に使用済みです。登録を再試行してください。',
|
||||
'email_confirmation_expired' => '確認トークンは有効期限切れです。確認メールを再送しました。',
|
||||
'ldap_fail_anonymous' => '匿名バインドを用いたLDAPアクセスに失敗しました',
|
||||
'ldap_fail_authed' => '識別名, パスワードを用いたLDAPアクセスに失敗しました',
|
||||
'ldap_extension_not_installed' => 'LDAP PHP extensionがインストールされていません',
|
||||
'ldap_cannot_connect' => 'LDAPサーバに接続できませんでした',
|
||||
'social_no_action_defined' => 'アクションが定義されていません',
|
||||
'social_account_in_use' => ':socialAccountアカウントは既に使用されています。:socialAccountのオプションからログインを試行してください。',
|
||||
'social_account_email_in_use' => ':emailは既に使用されています。ログイン後、プロフィール設定から:socialAccountアカウントを接続できます。',
|
||||
'social_account_existing' => 'アカウント:socialAccountは既にあなたのプロフィールに接続されています。',
|
||||
'social_account_already_used_existing' => 'この:socialAccountアカウントは既に他のユーザが使用しています。',
|
||||
'social_account_not_used' => 'この:socialAccountアカウントはどのユーザにも接続されていません。プロフィール設定から接続できます。',
|
||||
'social_account_register_instructions' => 'まだアカウントをお持ちでない場合、:socialAccountオプションから登録できます。',
|
||||
'social_driver_not_found' => 'Social driverが見つかりません。',
|
||||
'social_driver_not_configured' => 'あなたの:socialAccount設定は正しく構成されていません。',
|
||||
|
||||
// System
|
||||
'path_not_writable' => 'ファイルパス :filePath へアップロードできませんでした。サーバ上での書き込みを許可してください。',
|
||||
'cannot_get_image_from_url' => ':url から画像を取得できませんでした。',
|
||||
'cannot_create_thumbs' => 'このサーバはサムネイルを作成できません。GD PHP extensionがインストールされていることを確認してください。',
|
||||
'server_upload_limit' => 'このサイズの画像をアップロードすることは許可されていません。ファイルサイズを小さくし、再試行してください。',
|
||||
'image_upload_error' => '画像アップロード時にエラーが発生しました。',
|
||||
|
||||
// Attachments
|
||||
'attachment_page_mismatch' => '添付を更新するページが一致しません',
|
||||
|
||||
// Pages
|
||||
'page_draft_autosave_fail' => '下書きの保存に失敗しました。インターネットへ接続してください。',
|
||||
|
||||
// Entities
|
||||
'entity_not_found' => 'エンティティが見つかりません',
|
||||
'book_not_found' => 'ブックが見つかりません',
|
||||
'page_not_found' => 'ページが見つかりません',
|
||||
'chapter_not_found' => 'チャプターが見つかりません',
|
||||
'selected_book_not_found' => '選択されたブックが見つかりません',
|
||||
'selected_book_chapter_not_found' => '選択されたブック、またはチャプターが見つかりません',
|
||||
'guests_cannot_save_drafts' => 'ゲストは下書きを保存できません',
|
||||
|
||||
// Users
|
||||
'users_cannot_delete_only_admin' => '唯一の管理者を削除することはできません',
|
||||
'users_cannot_delete_guest' => 'ゲストユーザを削除することはできません',
|
||||
|
||||
// Roles
|
||||
'role_cannot_be_edited' => 'この役割は編集できません',
|
||||
'role_system_cannot_be_deleted' => 'この役割はシステムで管理されているため、削除できません',
|
||||
'role_registration_default_cannot_delete' => 'この役割を登録時のデフォルトに設定することはできません',
|
||||
|
||||
// Error pages
|
||||
'404_page_not_found' => 'ページが見つかりません',
|
||||
'sorry_page_not_found' => 'ページを見つけることができませんでした。',
|
||||
'return_home' => 'ホームに戻る',
|
||||
'error_occurred' => 'エラーが発生しました',
|
||||
'app_down' => ':appNameは現在停止しています',
|
||||
'back_soon' => '回復までしばらくお待ちください。',
|
||||
];
|
19
resources/lang/ja/pagination.php
Normal file
19
resources/lang/ja/pagination.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'previous' => '« 前',
|
||||
'next' => '次 »',
|
||||
|
||||
];
|
22
resources/lang/ja/passwords.php
Normal file
22
resources/lang/ja/passwords.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reminder Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
|
||||
'password' => 'パスワードは6文字以上である必要があります。',
|
||||
'user' => "このEメールアドレスに一致するユーザが見つかりませんでした。",
|
||||
'token' => 'このパスワードリセットトークンは無効です。',
|
||||
'sent' => 'パスワードリセットリンクを送信しました。',
|
||||
'reset' => 'パスワードはリセットされました。',
|
||||
|
||||
];
|
112
resources/lang/ja/settings.php
Normal file
112
resources/lang/ja/settings.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* Settings text strings
|
||||
* Contains all text strings used in the general settings sections of BookStack
|
||||
* including users and roles.
|
||||
*/
|
||||
|
||||
'settings' => '設定',
|
||||
'settings_save' => '設定を保存',
|
||||
'settings_save_success' => '設定を保存しました',
|
||||
|
||||
/**
|
||||
* App settings
|
||||
*/
|
||||
|
||||
'app_settings' => 'アプリケーション設定',
|
||||
'app_name' => 'アプリケーション名',
|
||||
'app_name_desc' => 'この名前はヘッダーやEメール内で表示されます。',
|
||||
'app_name_header' => 'ヘッダーにアプリケーション名を表示する',
|
||||
'app_public_viewing' => 'アプリケーションを公開する',
|
||||
'app_secure_images' => '画像アップロード時のセキュリティを強化',
|
||||
'app_secure_images_desc' => 'パフォーマンスの観点から、全ての画像が公開になっています。このオプションを有効にすると、画像URLの先頭にランダムで推測困難な文字列が追加され、アクセスを困難にします。',
|
||||
'app_editor' => 'ページエディタ',
|
||||
'app_editor_desc' => 'ここで選択されたエディタを全ユーザが使用します。',
|
||||
'app_custom_html' => 'カスタムheadタグ',
|
||||
'app_custom_html_desc' => 'スタイルシートやアナリティクスコード追加したい場合、ここを編集します。これは<head>の最下部に挿入されます。',
|
||||
'app_logo' => 'ロゴ',
|
||||
'app_logo_desc' => '高さ43pxで表示されます。これを上回る場合、自動で縮小されます。',
|
||||
'app_primary_color' => 'プライマリカラー',
|
||||
'app_primary_color_desc' => '16進数カラーコードで入力します。空にした場合、デフォルトの色にリセットされます。',
|
||||
|
||||
/**
|
||||
* Registration settings
|
||||
*/
|
||||
|
||||
'reg_settings' => '登録設定',
|
||||
'reg_allow' => '新規登録を許可',
|
||||
'reg_default_role' => '新規登録時のデフォルト役割',
|
||||
'reg_confirm_email' => 'Eメール認証を必須にする',
|
||||
'reg_confirm_email_desc' => 'ドメイン制限を有効にしている場合はEメール認証が必須となり、この項目は無視されます。',
|
||||
'reg_confirm_restrict_domain' => 'ドメイン制限',
|
||||
'reg_confirm_restrict_domain_desc' => '特定のドメインのみ登録できるようにする場合、以下にカンマ区切りで入力します。設定された場合、Eメール認証が必須になります。<br>登録後、ユーザは自由にEメールアドレスを変更できます。',
|
||||
'reg_confirm_restrict_domain_placeholder' => '制限しない',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
|
||||
'roles' => '役割',
|
||||
'role_user_roles' => '役割',
|
||||
'role_create' => '役割を作成',
|
||||
'role_create_success' => '役割を作成しました',
|
||||
'role_delete' => '役割を削除',
|
||||
'role_delete_confirm' => '役割「:roleName」を削除します。',
|
||||
'role_delete_users_assigned' => 'この役割は:userCount人のユーザに付与されています。該当するユーザを他の役割へ移行できます。',
|
||||
'role_delete_no_migration' => "ユーザを移行しない",
|
||||
'role_delete_sure' => '本当に役割を削除してよろしいですか?',
|
||||
'role_delete_success' => '役割を削除しました',
|
||||
'role_edit' => '役割を編集',
|
||||
'role_details' => '概要',
|
||||
'role_name' => '役割名',
|
||||
'role_desc' => '役割の説明',
|
||||
'role_system' => 'システム権限',
|
||||
'role_manage_users' => 'ユーザ管理',
|
||||
'role_manage_roles' => '役割と権限の管理',
|
||||
'role_manage_entity_permissions' => '全てのブック, チャプター, ページに対する権限の管理',
|
||||
'role_manage_own_entity_permissions' => '自身のブック, チャプター, ページに対する権限の管理',
|
||||
'role_manage_settings' => 'アプリケーション設定の管理',
|
||||
'role_asset' => 'アセット権限',
|
||||
'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。',
|
||||
'role_all' => '全て',
|
||||
'role_own' => '自身',
|
||||
'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:',
|
||||
'role_save' => '役割を保存',
|
||||
'role_update_success' => '役割を更新しました',
|
||||
'role_users' => 'この役割を持つユーザ',
|
||||
'role_users_none' => 'この役割が付与されたユーザは居ません',
|
||||
|
||||
/**
|
||||
* Users
|
||||
*/
|
||||
|
||||
'users' => 'ユーザ',
|
||||
'user_profile' => 'ユーザプロフィール',
|
||||
'users_add_new' => 'ユーザを追加',
|
||||
'users_search' => 'ユーザ検索',
|
||||
'users_role' => 'ユーザ役割',
|
||||
'users_external_auth_id' => '外部認証ID',
|
||||
'users_password_warning' => 'パスワードを変更したい場合のみ入力してください',
|
||||
'users_system_public' => 'このユーザはアプリケーションにアクセスする全てのゲストを表します。ログインはできませんが、自動的に割り当てられます。',
|
||||
'users_delete' => 'ユーザを削除',
|
||||
'users_delete_named' => 'ユーザ「:userName」を削除',
|
||||
'users_delete_warning' => 'ユーザ「:userName」を完全に削除します。',
|
||||
'users_delete_confirm' => '本当にこのユーザを削除してよろしいですか?',
|
||||
'users_delete_success' => 'ユーザを削除しました',
|
||||
'users_edit' => 'ユーザ編集',
|
||||
'users_edit_profile' => 'プロフィール編集',
|
||||
'users_edit_success' => 'ユーザを更新しました',
|
||||
'users_avatar' => 'アバター',
|
||||
'users_avatar_desc' => '256pxの正方形である必要があります。',
|
||||
'users_preferred_language' => '使用言語',
|
||||
'users_social_accounts' => 'ソーシャルアカウント',
|
||||
'users_social_accounts_info' => 'アカウントを接続すると、ログインが簡単になります。ここでアカウントの接続を解除すると、そのアカウントを経由したログインを禁止できます。接続解除後、各ソーシャルアカウントの設定にてこのアプリケーションへのアクセス許可を解除してください。',
|
||||
'users_social_connect' => 'アカウントを接続',
|
||||
'users_social_disconnect' => 'アカウントを接続解除',
|
||||
'users_social_connected' => '「:socialAccount」がプロフィールに接続されました。',
|
||||
'users_social_disconnected' => '「:socialAccount」がプロフィールから接続解除されました。'
|
||||
|
||||
];
|
108
resources/lang/ja/validation.php
Normal file
108
resources/lang/ja/validation.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages here.
|
||||
|
|
||||
*/
|
||||
|
||||
'accepted' => ':attributeに同意する必要があります。',
|
||||
'active_url' => ':attributeは正しいURLではありません。',
|
||||
'after' => ':attributeは:date以降である必要があります。',
|
||||
'alpha' => ':attributeは文字のみが含められます。',
|
||||
'alpha_dash' => ':attributeは文字, 数値, ハイフンのみが含められます。',
|
||||
'alpha_num' => ':attributeは文字と数値のみが含められます。',
|
||||
'array' => ':attributeは配列である必要があります。',
|
||||
'before' => ':attributeは:date以前である必要があります。',
|
||||
'between' => [
|
||||
'numeric' => ':attributeは:min〜:maxである必要があります。',
|
||||
'file' => ':attributeは:min〜:maxキロバイトである必要があります。',
|
||||
'string' => ':attributeは:min〜:max文字である必要があります。',
|
||||
'array' => ':attributeは:min〜:max個である必要があります。',
|
||||
],
|
||||
'boolean' => ':attributeはtrueまたはfalseである必要があります。',
|
||||
'confirmed' => ':attributeの確認が一致しません。',
|
||||
'date' => ':attributeは正しい日時ではありません。',
|
||||
'date_format' => ':attributeが:formatのフォーマットと一致しません。',
|
||||
'different' => ':attributeと:otherは異なる必要があります。',
|
||||
'digits' => ':attributeは:digitsデジットである必要があります',
|
||||
'digits_between' => ':attributeは:min〜:maxである必要があります。',
|
||||
'email' => ':attributeは正しいEメールアドレスである必要があります。',
|
||||
'filled' => ':attributeは必須です。',
|
||||
'exists' => '選択された:attributeは不正です。',
|
||||
'image' => ':attributeは画像である必要があります。',
|
||||
'in' => '選択された:attributeは不正です。',
|
||||
'integer' => ':attributeは数値である必要があります。',
|
||||
'ip' => ':attributeは正しいIPアドレスである必要があります。',
|
||||
'max' => [
|
||||
'numeric' => ':attributeは:maxを越えることができません。',
|
||||
'file' => ':attributeは:maxキロバイトを越えることができません。',
|
||||
'string' => ':attributeは:max文字をこえることができません。',
|
||||
'array' => ':attributeは:max個を越えることができません。',
|
||||
],
|
||||
'mimes' => ':attributeのファイルタイプは以下のみが許可されています: :values.',
|
||||
'min' => [
|
||||
'numeric' => ':attributeは:min以上である必要があります。',
|
||||
'file' => ':attributeは:minキロバイト以上である必要があります。',
|
||||
'string' => ':attributeは:min文字以上である必要があります。',
|
||||
'array' => ':attributeは:min個以上である必要があります。',
|
||||
],
|
||||
'not_in' => '選択された:attributeは不正です。',
|
||||
'numeric' => ':attributeは数値である必要があります。',
|
||||
'regex' => ':attributeのフォーマットは不正です。',
|
||||
'required' => ':attributeは必須です。',
|
||||
'required_if' => ':otherが:valueである場合、:attributeは必須です。',
|
||||
'required_with' => ':valuesが設定されている場合、:attributeは必須です。',
|
||||
'required_with_all' => ':valuesが設定されている場合、:attributeは必須です。',
|
||||
'required_without' => ':valuesが設定されていない場合、:attributeは必須です。',
|
||||
'required_without_all' => ':valuesが設定されていない場合、:attributeは必須です。',
|
||||
'same' => ':attributeと:otherは一致している必要があります。',
|
||||
'size' => [
|
||||
'numeric' => ':attributeは:sizeである必要があります。',
|
||||
'file' => ':attributeは:sizeキロバイトである必要があります。',
|
||||
'string' => ':attributeは:size文字である必要があります。',
|
||||
'array' => ':attributeは:size個である必要があります。',
|
||||
],
|
||||
'string' => ':attributeは文字列である必要があります。',
|
||||
'timezone' => ':attributeは正しいタイムゾーンである必要があります。',
|
||||
'unique' => ':attributeは既に使用されています。',
|
||||
'url' => ':attributeのフォーマットは不正です。',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
|
||||
'custom' => [
|
||||
'password-confirm' => [
|
||||
'required_with' => 'パスワードの確認は必須です。',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap attribute place-holders
|
||||
| with something more reader friendly such as E-Mail Address instead
|
||||
| of "email". This simply helps us make messages a little cleaner.
|
||||
|
|
||||
*/
|
||||
|
||||
'attributes' => [],
|
||||
|
||||
];
|
@ -72,9 +72,15 @@
|
||||
@else
|
||||
<p class="text-muted">{{ trans('entities.books_empty_contents') }}</p>
|
||||
<p>
|
||||
@if(userCan('page-create', $book))
|
||||
<a href="{{ $book->getUrl('/page/create') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ trans('entities.books_empty_create_page') }}</a>
|
||||
@endif
|
||||
@if(userCan('page-create', $book) && userCan('chapter-create', $book))
|
||||
<em class="text-muted">-{{ trans('entities.books_empty_or') }}-</em>
|
||||
@endif
|
||||
@if(userCan('chapter-create', $book))
|
||||
<a href="{{ $book->getUrl('/chapter/create') }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>{{ trans('entities.books_empty_add_chapter') }}</a>
|
||||
@endif
|
||||
</p>
|
||||
<hr>
|
||||
@endif
|
||||
|
51
resources/views/components/code-editor.blade.php
Normal file
51
resources/views/components/code-editor.blade.php
Normal file
@ -0,0 +1,51 @@
|
||||
<div id="code-editor">
|
||||
<div class="overlay" ref="overlay" v-cloak @click="hide()">
|
||||
<div class="popup-body" @click.stop>
|
||||
|
||||
<div class="popup-header primary-background">
|
||||
<div class="popup-title">{{ trans('components.code_editor') }}</div>
|
||||
<button class="popup-close neg corner-button button" @click="hide()">x</button>
|
||||
</div>
|
||||
|
||||
<div class="padded">
|
||||
<div class="form-group">
|
||||
<label for="code-editor-language">{{ trans('components.code_language') }}</label>
|
||||
<div class="lang-options">
|
||||
<small>
|
||||
<a @click="updateLanguage('CSS')">CSS</a>
|
||||
<a @click="updateLanguage('C')">C</a>
|
||||
<a @click="updateLanguage('C++')">C++</a>
|
||||
<a @click="updateLanguage('C#')">C#</a>
|
||||
<a @click="updateLanguage('Go')">Go</a>
|
||||
<a @click="updateLanguage('HTML')">HTML</a>
|
||||
<a @click="updateLanguage('Java')">Java</a>
|
||||
<a @click="updateLanguage('JavaScript')">JavaScript</a>
|
||||
<a @click="updateLanguage('JSON')">JSON</a>
|
||||
<a @click="updateLanguage('PHP')">PHP</a>
|
||||
<a @click="updateLanguage('MarkDown')">MarkDown</a>
|
||||
<a @click="updateLanguage('Nginx')">Nginx</a>
|
||||
<a @click="updateLanguage('Python')">Python</a>
|
||||
<a @click="updateLanguage('Ruby')">Ruby</a>
|
||||
<a @click="updateLanguage('shell')">Shell/Bash</a>
|
||||
<a @click="updateLanguage('SQL')">SQL</a>
|
||||
<a @click="updateLanguage('XML')">XML</a>
|
||||
<a @click="updateLanguage('YAML')">YAML</a>
|
||||
</small>
|
||||
</div>
|
||||
<input @keypress.enter="save()" id="code-editor-language" type="text" @input="updateEditorMode(language)" v-model="language">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="code-editor-content">{{ trans('components.code_content') }}</label>
|
||||
<textarea ref="editor" v-model="code"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="button" class="button pos" @click="save()">{{ trans('components.code_save') }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -21,6 +21,7 @@
|
||||
</div>
|
||||
|
||||
@include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
|
||||
@include('components.code-editor')
|
||||
@include('components.entity-selector-popup')
|
||||
|
||||
@stop
|
@ -1,5 +1,6 @@
|
||||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Entity;
|
||||
use BookStack\Role;
|
||||
use BookStack\Services\PermissionService;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
@ -117,6 +118,16 @@ abstract class BrowserKitTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for updating entity permissions.
|
||||
* @param Entity $entity
|
||||
*/
|
||||
protected function updateEntityPermissions(Entity $entity)
|
||||
{
|
||||
$restrictionService = $this->app[PermissionService::class];
|
||||
$restrictionService->buildJointPermissionsForEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick way to create a new user
|
||||
* @param array $attributes
|
||||
|
@ -1,7 +1,10 @@
|
||||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Page;
|
||||
use BookStack\Repos\PermissionsRepo;
|
||||
use BookStack\Role;
|
||||
use Laravel\BrowserKitTesting\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class RolesTest extends BrowserKitTest
|
||||
{
|
||||
@ -580,8 +583,6 @@ class RolesTest extends BrowserKitTest
|
||||
->see('Cannot be deleted');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function test_image_delete_own_permission()
|
||||
{
|
||||
$this->giveUserPermissions($this->user, ['image-update-all']);
|
||||
@ -620,4 +621,40 @@ class RolesTest extends BrowserKitTest
|
||||
->dontSeeInDatabase('images', ['id' => $image->id]);
|
||||
}
|
||||
|
||||
public function test_role_permission_removal()
|
||||
{
|
||||
// To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
|
||||
$page = Page::first();
|
||||
$viewerRole = \BookStack\Role::getRole('viewer');
|
||||
$viewer = $this->getViewer();
|
||||
$this->actingAs($viewer)->visit($page->getUrl())->assertResponseOk();
|
||||
|
||||
$this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
|
||||
'display_name' => $viewerRole->display_name,
|
||||
'description' => $viewerRole->description,
|
||||
'permission' => []
|
||||
])->assertResponseStatus(302);
|
||||
|
||||
$this->expectException(HttpException::class);
|
||||
$this->actingAs($viewer)->visit($page->getUrl())->assertResponseStatus(404);
|
||||
}
|
||||
|
||||
public function test_empty_state_actions_not_visible_without_permission()
|
||||
{
|
||||
$admin = $this->getAdmin();
|
||||
// Book links
|
||||
$book = factory(\BookStack\Book::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
|
||||
$this->updateEntityPermissions($book);
|
||||
$this->actingAs($this->getViewer())->visit($book->getUrl())
|
||||
->dontSee('Create a new page')
|
||||
->dontSee('Add a chapter');
|
||||
|
||||
// Chapter links
|
||||
$chapter = factory(\BookStack\Chapter::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
|
||||
$this->updateEntityPermissions($chapter);
|
||||
$this->actingAs($this->getViewer())->visit($chapter->getUrl())
|
||||
->dontSee('Create a new page')
|
||||
->dontSee('Sort the current book');
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user