From 2060cdb931f14f03a9dfbeebd548f7933413af24 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 17 Jun 2017 12:41:18 +0100 Subject: [PATCH 01/27] Added code highlighting syntax modes --- resources/assets/js/code.js | 42 ++++++++++++++++++++++++++++++- resources/assets/js/directives.js | 2 +- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index 872b13426..020c38365 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -17,18 +17,58 @@ 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', + 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', + 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++) { + let innerCodeElem = codeBlocks[i].querySelector('code[class^=language-]'); + let mode = ''; + if (innerCodeElem !== null) { + let langName = innerCodeElem.className.replace('language-', ''); + if (typeof modeMap[langName] !== 'undefined') mode = modeMap[langName]; + } codeBlocks[i].innerHTML = codeBlocks[i].innerHTML.replace(//gi ,'\n'); let content = codeBlocks[i].textContent; + console.log('MODE', mode); CodeMirror(function(elt) { codeBlocks[i].parentNode.replaceChild(elt, codeBlocks[i]); }, { value: content, - mode: "", + mode: mode, lineNumbers: true, theme: 'base16-light', readOnly: true diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index 221e18b0e..9a38add9a 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -399,7 +399,7 @@ module.exports = function (ngApp, events) { // Handle image upload and add image into markdown content function uploadImage(file) { - if (file.type.indexOf('image') !== 0) return; + if (file === null || !file.type.indexOf('image') !== 0) return; let formData = new FormData(); let ext = 'png'; let xhr = new XMLHttpRequest(); From c8be214ee0a3582614db2c1d9444316b40f04b0e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 17 Jun 2017 15:07:55 +0100 Subject: [PATCH 02/27] Started tinymce code editor components --- resources/assets/js/code.js | 102 ++++++++++++++++++++----- resources/assets/js/pages/page-form.js | 78 ++++++++++++++++++- 2 files changed, 157 insertions(+), 23 deletions(-) diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index 020c38365..83cb664a1 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -52,31 +52,91 @@ const modeMap = { module.exports.highlight = function() { let codeBlocks = document.querySelectorAll('.page-content pre'); - for (let i = 0; i < codeBlocks.length; i++) { - let innerCodeElem = codeBlocks[i].querySelector('code[class^=language-]'); - let mode = ''; - if (innerCodeElem !== null) { - let langName = innerCodeElem.className.replace('language-', ''); - if (typeof modeMap[langName] !== 'undefined') mode = modeMap[langName]; - } - codeBlocks[i].innerHTML = codeBlocks[i].innerHTML.replace(//gi ,'\n'); - let content = codeBlocks[i].textContent; - console.log('MODE', mode); - - CodeMirror(function(elt) { - codeBlocks[i].parentNode.replaceChild(elt, codeBlocks[i]); - }, { - value: content, - mode: mode, - lineNumbers: true, - theme: 'base16-light', - readOnly: true - }); + highlightElem(codeBlocks[i]); } - }; +function highlightElem(elem) { + let innerCodeElem = elem.querySelector('code[class^=language-]'); + let mode = ''; + if (innerCodeElem !== null) { + let langName = innerCodeElem.className.replace('language-', ''); + if (typeof modeMap[langName] !== 'undefined') mode = modeMap[langName]; + } + elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); + let content = elem.textContent; + + CodeMirror(function(elt) { + elem.parentNode.replaceChild(elt, elem); + }, { + value: content, + mode: mode, + lineNumbers: true, + theme: 'base16-light', + readOnly: true + }); +} + +module.exports.highlightElem = highlightElem; + +module.exports.wysiwygView = function(elem) { + let doc = elem.ownerDocument; + elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); + let content = elem.textContent; + let newWrap = doc.createElement('div'); + let newTextArea = doc.createElement('textarea'); + + newWrap.className = 'CodeMirrorContainer'; + 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: '', + lineNumbers: true, + theme: 'base16-light', + readOnly: true + }); + setTimeout(() => { + cm.refresh(); + }, 300); + return newWrap; +}; + +// module.exports.wysiwygEditor = function(elem) { +// let doc = elem.ownerDocument; +// let newWrap = doc.createElement('div'); +// newWrap.className = 'CodeMirrorContainer'; +// let newTextArea = doc.createElement('textarea'); +// newTextArea.style.display = 'none'; +// elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); +// let content = elem.textContent; +// elem.parentNode.replaceChild(newWrap, elem); +// newWrap.appendChild(newTextArea); +// let cm = CodeMirror(function(elt) { +// newWrap.appendChild(elt); +// }, { +// value: content, +// mode: '', +// lineNumbers: true, +// theme: 'base16-light', +// readOnly: true +// }); +// cm.on('change', event => { +// newTextArea.innerText = cm.getValue(); +// }); +// setTimeout(() => { +// cm.refresh(); +// }, 300); +// }; + module.exports.markdownEditor = function(elem) { let content = elem.textContent; diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js index 04951b174..871d2b528 100644 --- a/resources/assets/js/pages/page-form.js +++ b/resources/assets/js/pages/page-form.js @@ -1,5 +1,7 @@ "use strict"; +const Code = require('../code'); + /** * Handle pasting images from clipboard. * @param e - event @@ -60,13 +62,85 @@ function registerEditorShortcuts(editor) { editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']); } + +function codePlugin() { + + function elemIsCodeBlock(elem) { + return elem.className === 'CodeMirrorContainer'; + } + + function showPopup(editor) { + let selectedNode = editor.selection.getNode(); + if (!elemIsCodeBlock(selectedNode)) { + return; + } + + let lang = selectedNode.hasAttribute('data-language') ? selectedNode.getAttribute('data-language') : ''; + let currentCode = selectedNode.querySelector('textarea').textContent; + console.log('SHOW POPUP'); + // TODO - Show custom editor + } + + window.tinymce.PluginManager.add('codeeditor', (editor, url) => { + + let $ = editor.$; + + editor.addButton('codeeditor', { + text: 'Code block', + icon: false, + onclick() { + showPopup(editor); + } + }); + + // Convert + editor.on('PreProcess', function (e) { + $('div.CodeMirrorContainer', e.node). + each((index, elem) => { + let $elem = $(elem); + let code = elem.querySelector('textarea').textContent; + + // $elem.attr('class', $.trim($elem.attr('class'))); + $elem.removeAttr('contentEditable'); + + $elem.empty().append('
').find('pre').first().append($('').each((index, elem) => {
+                    // Needs to be textContent since innerText produces BR:s
+                    elem.textContent = code;
+                }).attr('class', $elem.attr('class')));
+                console.log($elem[0].outerHTML);
+            });
+        });
+
+        editor.on('SetContent', function () {
+            let codeSamples = $('pre').filter((index, elem) => {
+                return elem.contentEditable !== "false";
+            });
+
+            if (codeSamples.length) {
+                editor.undoManager.transact(function () {
+                    codeSamples.each((index, elem) => {
+                        console.log(elem.textContent);
+                        let outerWrap = Code.wysiwygView(elem);
+                        outerWrap.addEventListener('dblclick', () => {
+                            showPopup(editor);
+                        })
+                    });
+                });
+            }
+        });
+
+    });
+}
+
 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,
@@ -78,9 +152,9 @@ module.exports = function() {
         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",
+        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 codeeditor",
         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"},

From 26e703a1cdb61be7da66e0967b17cec9b29f7c28 Mon Sep 17 00:00:00 2001
From: Shuma Yoshioka 
Date: Mon, 19 Jun 2017 00:02:09 +0900
Subject: [PATCH 03/27] translate activities.php

---
 resources/lang/ja/activities.php | 40 ++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 resources/lang/ja/activities.php

diff --git a/resources/lang/ja/activities.php b/resources/lang/ja/activities.php
new file mode 100644
index 000000000..b907e0c63
--- /dev/null
+++ b/resources/lang/ja/activities.php
@@ -0,0 +1,40 @@
+ 'がページを作成:',
+    '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'      => '並び順を変更しました',
+
+];

From 168c66815e10a16832d894762f0b4bb15dbf2a57 Mon Sep 17 00:00:00 2001
From: Shuma Yoshioka 
Date: Sat, 24 Jun 2017 20:44:45 +0900
Subject: [PATCH 04/27] add `ja` to settings

---
 resources/lang/en/settings.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php
index 31163e87e..4529b1978 100644
--- a/resources/lang/en/settings.php
+++ b/resources/lang/en/settings.php
@@ -121,6 +121,7 @@ return [
         'nl' => 'Nederlands',
         'pt_BR' => 'Português do Brasil',
         'sk' => 'Slovensky',
+        'ja' => '日本語',
     ]
     ///////////////////////////////////
 ];

From b8ebefd803696377fd585da89adf9d0c321f2596 Mon Sep 17 00:00:00 2001
From: Shuma Yoshioka 
Date: Sat, 24 Jun 2017 20:47:41 +0900
Subject: [PATCH 05/27] Translate components.php

---
 resources/lang/ja/components.php | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 resources/lang/ja/components.php

diff --git a/resources/lang/ja/components.php b/resources/lang/ja/components.php
new file mode 100644
index 000000000..89ed33ef3
--- /dev/null
+++ b/resources/lang/ja/components.php
@@ -0,0 +1,24 @@
+ '画像を選択',
+    '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' => '画像が削除されました'
+];

From 75eca03b0530a90775d9a646db639ff5ed4ca65b Mon Sep 17 00:00:00 2001
From: Shuma Yoshioka 
Date: Sat, 24 Jun 2017 20:49:06 +0900
Subject: [PATCH 06/27] Translate settings.php

---
 resources/lang/ja/settings.php | 112 +++++++++++++++++++++++++++++++++
 1 file changed, 112 insertions(+)
 create mode 100644 resources/lang/ja/settings.php

diff --git a/resources/lang/ja/settings.php b/resources/lang/ja/settings.php
new file mode 100644
index 000000000..b4cf57aeb
--- /dev/null
+++ b/resources/lang/ja/settings.php
@@ -0,0 +1,112 @@
+ '設定',
+    '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' => 'スタイルシートやアナリティクスコード追加したい場合、ここを編集します。これはの最下部に挿入されます。',
+    '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メール認証が必須になります。
登録後、ユーザは自由に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」がプロフィールから接続解除されました。' + +]; From 52010e5820e08e8b1ab2ce17bced210207220fca Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:29:28 +0900 Subject: [PATCH 07/27] Translate pagination.php --- resources/lang/ja/pagination.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 resources/lang/ja/pagination.php diff --git a/resources/lang/ja/pagination.php b/resources/lang/ja/pagination.php new file mode 100644 index 000000000..1ebcef722 --- /dev/null +++ b/resources/lang/ja/pagination.php @@ -0,0 +1,19 @@ + '« 前', + 'next' => '次 »', + +]; From b02052036619469ac3f7c92b39657bdc41bdac36 Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:29:35 +0900 Subject: [PATCH 08/27] Translate passwords.php --- resources/lang/ja/passwords.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 resources/lang/ja/passwords.php diff --git a/resources/lang/ja/passwords.php b/resources/lang/ja/passwords.php new file mode 100644 index 000000000..17c82e299 --- /dev/null +++ b/resources/lang/ja/passwords.php @@ -0,0 +1,22 @@ + 'パスワードは6文字以上である必要があります。', + 'user' => "このEメールアドレスに一致するユーザが見つかりませんでした。", + 'token' => 'このパスワードリセットトークンは無効です。', + 'sent' => 'パスワードリセットリンクを送信しました。', + 'reset' => 'パスワードはリセットされました。', + +]; From f10d3a85640575a56117ae8f4e9c4f9db74a61f2 Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:29:43 +0900 Subject: [PATCH 09/27] Translate validation.php --- resources/lang/ja/validation.php | 108 +++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 resources/lang/ja/validation.php diff --git a/resources/lang/ja/validation.php b/resources/lang/ja/validation.php new file mode 100644 index 000000000..e0fa3cb2c --- /dev/null +++ b/resources/lang/ja/validation.php @@ -0,0 +1,108 @@ + ':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' => [], + +]; From 60710e7fb4ec3973c7d4433088808603b553de0c Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:33:54 +0900 Subject: [PATCH 10/27] Translate auth.php --- resources/lang/ja/auth.php | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 resources/lang/ja/auth.php diff --git a/resources/lang/ja/auth.php b/resources/lang/ja/auth.php new file mode 100644 index 000000000..4d5aee8b3 --- /dev/null +++ b/resources/lang/ja/auth.php @@ -0,0 +1,76 @@ + 'この資格情報は登録されていません。', + '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' => '確認メールを再送信', +]; From 7bec70d429659e9c68c18df2c1d3cb10861bb72f Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:37:07 +0900 Subject: [PATCH 11/27] Translate common.php --- resources/lang/ja/common.php | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 resources/lang/ja/common.php diff --git a/resources/lang/ja/common.php b/resources/lang/ja/common.php new file mode 100644 index 000000000..383294880 --- /dev/null +++ b/resources/lang/ja/common.php @@ -0,0 +1,59 @@ + 'キャンセル', + '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', +]; From 2d345fe45428c1d6ee9feb4d4272bfb61c52bae3 Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:38:20 +0900 Subject: [PATCH 12/27] Translate entities.php --- resources/lang/ja/entities.php | 236 +++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 resources/lang/ja/entities.php diff --git a/resources/lang/ja/entities.php b/resources/lang/ja/entities.php new file mode 100644 index 000000000..8d215516d --- /dev/null +++ b/resources/lang/ja/entities.php @@ -0,0 +1,236 @@ + '最近作成', + '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はブックを作成していません', +]; From b2b8706d052abad95b27eb289efb3e8d0e8e50e8 Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:38:43 +0900 Subject: [PATCH 13/27] Translate errors.php --- resources/lang/ja/errors.php | 70 ++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 resources/lang/ja/errors.php diff --git a/resources/lang/ja/errors.php b/resources/lang/ja/errors.php new file mode 100644 index 000000000..5905237fd --- /dev/null +++ b/resources/lang/ja/errors.php @@ -0,0 +1,70 @@ + 'リクエストされたページへの権限がありません。', + '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' => '回復までしばらくお待ちください。', +]; From 5f461c9796a6e6e642b96abcc13748fa81ee0d43 Mon Sep 17 00:00:00 2001 From: Shuma Yoshioka Date: Sat, 1 Jul 2017 16:29:12 +0900 Subject: [PATCH 14/27] Add `ja` locale to config --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index 54cdca21b..48348f837 100644 --- a/config/app.php +++ b/config/app.php @@ -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'], /* |-------------------------------------------------------------------------- From 968e7b8b72dba148166253c00e6b0d09b714888f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 1 Jul 2017 13:23:46 +0100 Subject: [PATCH 15/27] Finished off main functionality of custom tinymce code editor --- resources/assets/js/code.js | 84 +++++++++++-------- resources/assets/js/pages/page-form.js | 69 ++++++++++----- resources/assets/js/vues/code-editor.js | 39 +++++++++ resources/assets/js/vues/vues.js | 5 +- resources/assets/sass/_codemirror.scss | 4 + resources/assets/sass/_components.scss | 4 + resources/assets/sass/_text.scss | 16 ++++ resources/lang/en/components.php | 10 ++- .../views/components/code-editor.blade.php | 29 +++++++ resources/views/pages/edit.blade.php | 1 + 10 files changed, 204 insertions(+), 57 deletions(-) create mode 100644 resources/assets/js/vues/code-editor.js create mode 100644 resources/views/components/code-editor.blade.php diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index 83cb664a1..ef6bca2e2 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -62,7 +62,7 @@ function highlightElem(elem) { let mode = ''; if (innerCodeElem !== null) { let langName = innerCodeElem.className.replace('language-', ''); - if (typeof modeMap[langName] !== 'undefined') mode = modeMap[langName]; + mode = getMode(langName); } elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); let content = elem.textContent; @@ -78,16 +78,35 @@ function highlightElem(elem) { }); } +/** + * 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) { + console.log(codeElem.className); + lang = (codeElem.className || '').replace('language-', '') + } + elem.innerHTML = elem.innerHTML.replace(//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); @@ -99,7 +118,7 @@ module.exports.wysiwygView = function(elem) { newWrap.appendChild(elt); }, { value: content, - mode: '', + mode: getMode(lang), lineNumbers: true, theme: 'base16-light', readOnly: true @@ -107,50 +126,47 @@ module.exports.wysiwygView = function(elem) { setTimeout(() => { cm.refresh(); }, 300); - return newWrap; + return {wrap: newWrap, editor: cm}; }; -// module.exports.wysiwygEditor = function(elem) { -// let doc = elem.ownerDocument; -// let newWrap = doc.createElement('div'); -// newWrap.className = 'CodeMirrorContainer'; -// let newTextArea = doc.createElement('textarea'); -// newTextArea.style.display = 'none'; -// elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); -// let content = elem.textContent; -// elem.parentNode.replaceChild(newWrap, elem); -// newWrap.appendChild(newTextArea); -// let cm = CodeMirror(function(elt) { -// newWrap.appendChild(elt); -// }, { -// value: content, -// mode: '', -// lineNumbers: true, -// theme: 'base16-light', -// readOnly: true -// }); -// cm.on('change', event => { -// newTextArea.innerText = cm.getValue(); -// }); -// setTimeout(() => { -// cm.refresh(); -// }, 300); -// }; - -module.exports.markdownEditor = function(elem) { +module.exports.popupEditor = function(elem, modeSuggestion) { let content = elem.textContent; - let cm = CodeMirror(function(elt) { + return CodeMirror(function(elt) { elem.parentNode.insertBefore(elt, elem); elem.style.display = 'none'; }, { value: content, - mode: "markdown", + 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; + + return CodeMirror(function (elt) { + elem.parentNode.insertBefore(elt, elem); + elem.style.display = 'none'; + }, { + value: content, + mode: "markdown", lineNumbers: true, theme: 'base16-light', lineWrapping: true }); - return cm; }; diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js index 871d2b528..a443213bf 100644 --- a/resources/assets/js/pages/page-form.js +++ b/resources/assets/js/pages/page-form.js @@ -58,11 +58,14 @@ 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) { @@ -71,14 +74,35 @@ function codePlugin() { 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 = `
`; + 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-language') ? selectedNode.getAttribute('data-language') : ''; + let lang = selectedNode.hasAttribute('data-lang') ? selectedNode.getAttribute('data-lang') : ''; let currentCode = selectedNode.querySelector('textarea').textContent; - console.log('SHOW POPUP'); - // TODO - Show custom editor + + 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); + }); } window.tinymce.PluginManager.add('codeeditor', (editor, url) => { @@ -88,9 +112,11 @@ function codePlugin() { editor.addButton('codeeditor', { text: 'Code block', icon: false, - onclick() { - showPopup(editor); - } + cmd: 'codeeditor' + }); + + editor.addCommand('codeeditor', () => { + showPopup(editor); }); // Convert @@ -98,32 +124,33 @@ function codePlugin() { $('div.CodeMirrorContainer', e.node). each((index, elem) => { let $elem = $(elem); - let code = elem.querySelector('textarea').textContent; + let textArea = elem.querySelector('textarea'); + let code = textArea.textContent; + let lang = elem.getAttribute('data-lang'); // $elem.attr('class', $.trim($elem.attr('class'))); $elem.removeAttr('contentEditable'); - - $elem.empty().append('
').find('pre').first().append($('').each((index, elem) => {
+                let $pre = $('
');
+                $pre.append($('').each((index, elem) => {
                     // Needs to be textContent since innerText produces BR:s
                     elem.textContent = code;
-                }).attr('class', $elem.attr('class')));
-                console.log($elem[0].outerHTML);
+                }).attr('class', `language-${lang}`));
+                $elem.replaceWith($pre);
             });
         });
 
         editor.on('SetContent', function () {
-            let codeSamples = $('pre').filter((index, elem) => {
+            let codeSamples = $('body > pre').filter((index, elem) => {
                 return elem.contentEditable !== "false";
             });
 
             if (codeSamples.length) {
                 editor.undoManager.transact(function () {
                     codeSamples.each((index, elem) => {
-                        console.log(elem.textContent);
-                        let outerWrap = Code.wysiwygView(elem);
-                        outerWrap.addEventListener('dblclick', () => {
-                            showPopup(editor);
-                        })
+                        let editDetails = Code.wysiwygView(elem);
+                        editDetails.wrap.addEventListener('dblclick', () => {
+                            showPopup(editor, editDetails.wrap, editDetails.editor);
+                        });
                     });
                 });
             }
@@ -154,7 +181,7 @@ module.exports = function() {
         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 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 codeeditor",
+        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"},
@@ -163,14 +190,14 @@ 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'},
             {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: {
diff --git a/resources/assets/js/vues/code-editor.js b/resources/assets/js/vues/code-editor.js
new file mode 100644
index 000000000..87bb28cce
--- /dev/null
+++ b/resources/assets/js/vues/code-editor.js
@@ -0,0 +1,39 @@
+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);
+    },
+    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
+};
\ No newline at end of file
diff --git a/resources/assets/js/vues/vues.js b/resources/assets/js/vues/vues.js
index 8cc1dd656..31d833bfb 100644
--- a/resources/assets/js/vues/vues.js
+++ b/resources/assets/js/vues/vues.js
@@ -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);
     }
 });
\ No newline at end of file
diff --git a/resources/assets/sass/_codemirror.scss b/resources/assets/sass/_codemirror.scss
index 9f9e38f55..bd85218a5 100644
--- a/resources/assets/sass/_codemirror.scss
+++ b/resources/assets/sass/_codemirror.scss
@@ -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;
diff --git a/resources/assets/sass/_components.scss b/resources/assets/sass/_components.scss
index 5328057d9..f45db84b7 100644
--- a/resources/assets/sass/_components.scss
+++ b/resources/assets/sass/_components.scss
@@ -466,4 +466,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 
 .image-picker .none {
   display: none;
+}
+
+#code-editor .CodeMirror {
+  height: 400px;
 }
\ No newline at end of file
diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss
index 4eaa492e7..ccef2a70f 100644
--- a/resources/assets/sass/_text.scss
+++ b/resources/assets/sass/_text.scss
@@ -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
diff --git a/resources/lang/en/components.php b/resources/lang/en/components.php
index b9108702a..334502d05 100644
--- a/resources/lang/en/components.php
+++ b/resources/lang/en/components.php
@@ -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',
 ];
\ No newline at end of file
diff --git a/resources/views/components/code-editor.blade.php b/resources/views/components/code-editor.blade.php
new file mode 100644
index 000000000..23deaad99
--- /dev/null
+++ b/resources/views/components/code-editor.blade.php
@@ -0,0 +1,29 @@
+
+
+ +
+
\ No newline at end of file diff --git a/resources/views/pages/edit.blade.php b/resources/views/pages/edit.blade.php index 5ab25d1cc..6de47aaf1 100644 --- a/resources/views/pages/edit.blade.php +++ b/resources/views/pages/edit.blade.php @@ -21,6 +21,7 @@ @include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id]) + @include('components.code-editor') @include('components.entity-selector-popup') @stop \ No newline at end of file From a94844b6b756f6ef5e928b05f41a90a734541b89 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 1 Jul 2017 15:38:11 +0100 Subject: [PATCH 16/27] Fixed code block selection and drag/drop issues --- resources/assets/js/code.js | 1 - resources/assets/js/pages/page-form.js | 63 ++++++++++++++++---------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index ef6bca2e2..24d520b76 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -96,7 +96,6 @@ module.exports.wysiwygView = function(elem) { let lang = (elem.className || '').replace('language-', ''); if (lang === '' && codeElem) { - console.log(codeElem.className); lang = (codeElem.className || '').replace('language-', '') } diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js index a443213bf..9b3c90a28 100644 --- a/resources/assets/js/pages/page-form.js +++ b/resources/assets/js/pages/page-form.js @@ -74,6 +74,7 @@ function codePlugin() { function showPopup(editor) { let selectedNode = editor.selection.getNode(); + console.log('show ppoe'); if (!elemIsCodeBlock(selectedNode)) { let providedCode = editor.selection.getNode().textContent; @@ -81,6 +82,7 @@ function codePlugin() { let wrap = document.createElement('div'); wrap.innerHTML = `
`; wrap.querySelector('code').innerText = code; + editor.formatter.toggle('pre'); let node = editor.selection.getNode(); editor.dom.setHTML(node, wrap.querySelector('pre').innerHTML); @@ -105,6 +107,20 @@ function codePlugin() { }); } + 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.append($('').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.$;
@@ -124,36 +140,36 @@ function codePlugin() {
             $('div.CodeMirrorContainer', e.node).
             each((index, elem) => {
                 let $elem = $(elem);
-                let textArea = elem.querySelector('textarea');
-                let code = textArea.textContent;
-                let lang = elem.getAttribute('data-lang');
-
-                // $elem.attr('class', $.trim($elem.attr('class')));
-                $elem.removeAttr('contentEditable');
-                let $pre = $('
');
-                $pre.append($('').each((index, elem) => {
-                    // Needs to be textContent since innerText produces BR:s
-                    elem.textContent = code;
-                }).attr('class', `language-${lang}`));
-                $elem.replaceWith($pre);
+                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) => {
+                console.log('COVERT');
+                codeMirrorContainerToPre($(elem));
+            });
+
             let codeSamples = $('body > pre').filter((index, elem) => {
                 return elem.contentEditable !== "false";
             });
 
-            if (codeSamples.length) {
-                editor.undoManager.transact(function () {
-                    codeSamples.each((index, elem) => {
-                        let editDetails = Code.wysiwygView(elem);
-                        editDetails.wrap.addEventListener('dblclick', () => {
-                            showPopup(editor, editDetails.wrap, editDetails.editor);
-                        });
-                    });
+            if (!codeSamples.length) return;
+            editor.undoManager.transact(function () {
+                codeSamples.each((index, elem) => {
+                    Code.wysiwygView(elem);
                 });
-            }
+            });
         });
 
     });
@@ -178,7 +194,7 @@ 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]",
+        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",
@@ -190,7 +206,7 @@ module.exports = function() {
             {title: "Header Tiny", format: "h5"},
             {title: "Paragraph", format: "p", exact: true, classes: ''},
             {title: "Blockquote", format: "blockquote"},
-            {title: "Code Block", icon: "code", cmd: 'codeeditor'},
+            {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'}},
@@ -201,6 +217,7 @@ module.exports = function() {
         ],
         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'},

From de6d8a811c1c5731e1edb67e2ed541fa1ada4bc7 Mon Sep 17 00:00:00 2001
From: Dan Brown 
Date: Sat, 1 Jul 2017 15:50:28 +0100
Subject: [PATCH 17/27] Added quick lang-selection options to code editor

---
 resources/assets/js/vues/code-editor.js       |  4 ++++
 resources/assets/sass/_components.scss        |  9 ++++++++
 .../views/components/code-editor.blade.php    | 22 +++++++++++++++++++
 3 files changed, 35 insertions(+)

diff --git a/resources/assets/js/vues/code-editor.js b/resources/assets/js/vues/code-editor.js
index 87bb28cce..35a98cc77 100644
--- a/resources/assets/js/vues/code-editor.js
+++ b/resources/assets/js/vues/code-editor.js
@@ -11,6 +11,10 @@ const methods = {
     updateEditorMode(language) {
         codeLib.setMode(this.editor, language);
     },
+    updateLanguage(lang) {
+        this.language = lang;
+        this.updateEditorMode(lang);
+    },
     open(code, language, callback) {
         this.show();
         this.updateEditorMode(language);
diff --git a/resources/assets/sass/_components.scss b/resources/assets/sass/_components.scss
index f45db84b7..12babae73 100644
--- a/resources/assets/sass/_components.scss
+++ b/resources/assets/sass/_components.scss
@@ -470,4 +470,13 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 
 #code-editor .CodeMirror {
   height: 400px;
+}
+
+#code-editor .lang-options {
+  max-width: 400px;
+  margin-bottom: $-s;
+  a {
+    margin-right: $-xs;
+    text-decoration: underline;
+  }
 }
\ No newline at end of file
diff --git a/resources/views/components/code-editor.blade.php b/resources/views/components/code-editor.blade.php
index 23deaad99..5a385ef49 100644
--- a/resources/views/components/code-editor.blade.php
+++ b/resources/views/components/code-editor.blade.php
@@ -10,6 +10,28 @@
             
+
+ + CSS + C + C++ + C# + Go + HTML + Java + JavaScript + JSON + PHP + MarkDown + Nginx + Python + Ruby + Shell/Bash + SQL + XML + YAML + +
From d5d83da7665a33f643cf17e09f984b10099ccd7f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 1 Jul 2017 16:10:52 +0100 Subject: [PATCH 18/27] Added diff and sh code types Verified changes to ensure fixes #296. --- resources/assets/js/code.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index 24d520b76..777baf81d 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -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'); @@ -26,6 +27,7 @@ const modeMap = { 'c++': 'clike', 'c#': 'clike', csharp: 'clike', + diff: 'diff', go: 'go', html: 'htmlmixed', javascript: 'javascript', @@ -42,6 +44,7 @@ const modeMap = { ruby: 'ruby', rb: 'ruby', shell: 'shell', + sh: 'shell', bash: 'shell', toml: 'toml', sql: 'sql', From cbff801aec1e69a49705cbf70f6c2c5b132e7efb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Jul 2017 15:40:42 +0100 Subject: [PATCH 19/27] Added test to cover f99c8ff. Closes #409 --- tests/Permissions/RolesTest.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index 83d1b98a8..d0e42c6ee 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -1,7 +1,10 @@ see('Cannot be deleted'); } - - public function test_image_delete_own_permission() { $this->giveUserPermissions($this->user, ['image-update-all']); @@ -620,4 +621,22 @@ 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); + } + } From 4db2c274e21fb4a3900f057d3ff9d5d7f09b65be Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Jul 2017 15:59:40 +0100 Subject: [PATCH 20/27] Prevent empty-state actions visible without permission. Fixes #411 --- resources/views/books/show.blade.php | 6 ++++++ tests/BrowserKitTest.php | 11 +++++++++++ tests/Permissions/RolesTest.php | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index adfec4525..ddbe7a0a4 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -72,9 +72,15 @@ @else

{{ trans('entities.books_empty_contents') }}

+ @if(userCan('page-create', $book)) {{ trans('entities.books_empty_create_page') }} + @endif + @if(userCan('page-create', $book) && userCan('chapter-create', $book))   -{{ trans('entities.books_empty_or') }}-    + @endif + @if(userCan('chapter-create', $book)) {{ trans('entities.books_empty_add_chapter') }} + @endif


@endif diff --git a/tests/BrowserKitTest.php b/tests/BrowserKitTest.php index c665bfc23..98259dea9 100644 --- a/tests/BrowserKitTest.php +++ b/tests/BrowserKitTest.php @@ -1,5 +1,6 @@ app[PermissionService::class]; + $restrictionService->buildJointPermissionsForEntity($entity); + } + /** * Quick way to create a new user * @param array $attributes diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index d0e42c6ee..eda5d092a 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -639,4 +639,22 @@ class RolesTest extends BrowserKitTest $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'); + } + } From 005f0eb4fcfc7b3f9239bc094204d9ba65d0145b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Jul 2017 17:20:05 +0100 Subject: [PATCH 21/27] Updated default encoding and added conversion migration. Also updated how DB port is defined so that the DB_PORT env var can be used or it can be take from the host name. Fixes #405 --- app/Providers/AppServiceProvider.php | 3 ++ config/database.php | 15 ++++-- ...02_152834_update_db_encoding_to_ut8mb4.php | 46 +++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 49cc15dd6..ef3ee6c48 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -23,6 +23,9 @@ class AppServiceProvider extends ServiceProvider \Blade::directive('icon', function($expression) { return ""; }); + + // Allow longer string lengths after upgrade to utf8mb4 + \Schema::defaultStringLength(191); } /** diff --git a/config/database.php b/config/database.php index 92c768245..64dc89864 100644 --- a/config/database.php +++ b/config/database.php @@ -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) > 0) { + $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, ], diff --git a/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php b/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php new file mode 100644 index 000000000..8d06d92b1 --- /dev/null +++ b/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php @@ -0,0 +1,46 @@ +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'); + } + } +} From f101e4f010e5744c6cb3651ed9f393448eb2d5ec Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Jul 2017 17:34:32 +0100 Subject: [PATCH 22/27] Fixed quoting db/table names in encoding migration. Also fixed incorrect if statement in db config. --- config/database.php | 2 +- .../2017_07_02_152834_update_db_encoding_to_ut8mb4.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/database.php b/config/database.php index 64dc89864..3883b5868 100644 --- a/config/database.php +++ b/config/database.php @@ -19,7 +19,7 @@ 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) > 0) { +if (count($mysql_host_exploded) > 1) { $mysql_host = $mysql_host_exploded[0]; $mysql_port = intval($mysql_host_exploded[1]); } diff --git a/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php b/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php index 8d06d92b1..550c95826 100644 --- a/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php +++ b/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php @@ -17,11 +17,11 @@ class UpdateDbEncodingToUt8mb4 extends Migration $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'); + $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'); + $pdo->exec('ALTER TABLE `'.$tableName.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci'); } } @@ -36,11 +36,11 @@ class UpdateDbEncodingToUt8mb4 extends Migration $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'); + $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'); + $pdo->exec('ALTER TABLE `'.$tableName.'` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci'); } } } From 69c50b9b48eeb230a7f65ad64333cce3ca3c0dcf Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Jul 2017 19:35:13 +0100 Subject: [PATCH 23/27] Migrated markdown editor actions to new cm editor --- resources/assets/js/code.js | 6 +- resources/assets/js/controllers.js | 10 +- resources/assets/js/directives.js | 255 +++++++++++++---------------- resources/assets/js/global.js | 1 + 4 files changed, 119 insertions(+), 153 deletions(-) diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index 777baf81d..014c4eb77 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -169,6 +169,10 @@ module.exports.markdownEditor = function(elem) { theme: 'base16-light', lineWrapping: true }); - +}; + +module.exports.getMetaKey = function() { + let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; + return mac ? "Cmd" : "Ctrl"; }; diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index 6a88aa811..ac1c3487c 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -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 diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index 9a38add9a..51f1b7579 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -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 === null || !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); - } - } } }]); diff --git a/resources/assets/js/global.js b/resources/assets/js/global.js index dc6802e12..2ef062a5b 100644 --- a/resources/assets/js/global.js +++ b/resources/assets/js/global.js @@ -17,6 +17,7 @@ let axiosInstance = axios.create({ 'baseURL': window.baseUrl('') } }); +window.$http = axiosInstance; Vue.prototype.$http = axiosInstance; From b2dfc069c5f3acc4290bbf1731f04955fe7eb842 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Jul 2017 19:38:28 +0100 Subject: [PATCH 24/27] Updated readme attribution --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index e2f16e171..4f025e3c2 100644 --- a/readme.md +++ b/readme.md @@ -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. From 314d98abc301d0f8e9c9c0570d9a2ef1fdac3772 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 2 Jul 2017 20:33:32 +0100 Subject: [PATCH 25/27] Removed logs, Updated version, Fixed inconsistent subheader --- resources/assets/js/pages/page-form.js | 2 -- resources/assets/sass/_header.scss | 8 +++++++- version | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js index 9b3c90a28..81d1cdea6 100644 --- a/resources/assets/js/pages/page-form.js +++ b/resources/assets/js/pages/page-form.js @@ -74,7 +74,6 @@ function codePlugin() { function showPopup(editor) { let selectedNode = editor.selection.getNode(); - console.log('show ppoe'); if (!elemIsCodeBlock(selectedNode)) { let providedCode = editor.selection.getNode().textContent; @@ -156,7 +155,6 @@ function codePlugin() { $('.CodeMirrorContainer').filter((index ,elem) => { return typeof elem.querySelector('.CodeMirror').CodeMirror === 'undefined'; }).each((index, elem) => { - console.log('COVERT'); codeMirrorContainerToPre($(elem)); }); diff --git a/resources/assets/sass/_header.scss b/resources/assets/sass/_header.scss index 12bd17076..16ed75545 100644 --- a/resources/assets/sass/_header.scss +++ b/resources/assets/sass/_header.scss @@ -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; diff --git a/version b/version index 973dcf5ac..1365ed940 100644 --- a/version +++ b/version @@ -1 +1 @@ -v0.16-dev +v0.17-dev From e7f8188ee539f76adcd511733a7b749801d676b2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 10 Jul 2017 19:29:35 +0100 Subject: [PATCH 26/27] Prevented textarea styles interfering with codemirror Closes #424 --- resources/assets/sass/_forms.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/sass/_forms.scss b/resources/assets/sass/_forms.scss index 1fc812896..392e9ec3e 100644 --- a/resources/assets/sass/_forms.scss +++ b/resources/assets/sass/_forms.scss @@ -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; From 2ea7e1092336558809fc790c05adc6060e66973b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 10 Jul 2017 19:43:49 +0100 Subject: [PATCH 27/27] Set ldap to not follow referrals by default Added LDAP_FOLLOW_REFERRALS .env option to override. Fixes #317 --- app/Services/LdapService.php | 2 ++ config/services.php | 1 + 2 files changed, 3 insertions(+) diff --git a/app/Services/LdapService.php b/app/Services/LdapService.php index 71dc9c0e1..598efc19d 100644 --- a/app/Services/LdapService.php +++ b/app/Services/LdapService.php @@ -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; diff --git a/config/services.php b/config/services.php index 99022e5f2..b4959c724 100644 --- a/config/services.php +++ b/config/services.php @@ -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), ] ];