}
$attachment = $this->attachmentService->updateFile($attachment, $request->all());
- return $this->jsonSuccess($attachment, trans('entities.attachments_updated_success'));
+ return response()->json($attachment);
}
/**
return true;
}
- /**
- * Send a json respons with a message attached as a header.
- * @param $data
- * @param string $successMessage
- * @return $this
- */
- protected function jsonSuccess($data, $successMessage = "")
- {
- return response()->json($data)->header('message-success', $successMessage);
- }
-
/**
* Send back a json error message.
* @param string $messageText
]);
}
+ /**
+ * Get a js representation of the current translations
+ * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
+ */
+ public function getTranslations() {
+ $locale = trans()->getLocale();
+ $cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
+ if (cache()->has($cacheKey) && config('app.env') !== 'development') {
+ $resp = cache($cacheKey);
+ } else {
+ $translations = [
+ // Get only translations which might be used in JS
+ 'common' => trans('common'),
+ 'components' => trans('components'),
+ 'entities' => trans('entities'),
+ 'errors' => trans('errors')
+ ];
+ if ($locale !== 'en') {
+ $enTrans = [
+ 'common' => trans('common', [], null, 'en'),
+ 'components' => trans('components', [], null, 'en'),
+ 'entities' => trans('entities', [], null, 'en'),
+ 'errors' => trans('errors', [], null, 'en')
+ ];
+ $translations = array_replace_recursive($enTrans, $translations);
+ }
+ $resp = 'window.translations = ' . json_encode($translations);
+ cache()->put($cacheKey, $resp, 120);
+ }
+
+ return response($resp, 200, [
+ 'Content-Type' => 'application/javascript'
+ ]);
+ }
+
}
$scope.$apply(() => {
$scope.images.unshift(data);
});
- events.emit('success', 'Image uploaded');
+ events.emit('success', trans('components.image_upload_success'));
};
/**
if ($scope.searching) components['term'] = $scope.searchTerm;
- let urlQueryString = Object.keys(components).map((key) => {
+ url += Object.keys(components).map((key) => {
return key + '=' + encodeURIComponent(components[key]);
}).join('&');
- url += urlQueryString;
$http.get(url).then((response) => {
$scope.images = $scope.images.concat(response.data.images);
event.preventDefault();
let url = window.baseUrl('/images/update/' + $scope.selectedImage.id);
$http.put(url, this.selectedImage).then(response => {
- events.emit('success', 'Image details updated');
+ events.emit('success', trans('components.image_update_success'));
}, (response) => {
if (response.status === 422) {
let errors = response.data;
$http.delete(url).then((response) => {
$scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
$scope.selectedImage = false;
- events.emit('success', 'Image successfully deleted');
+ events.emit('success', trans('components.image_delete_success'));
}, (response) => {
// Pages failure
if (response.status === 400) {
// Set initial header draft text
if ($scope.isUpdateDraft || $scope.isNewPageDraft) {
- $scope.draftText = 'Editing Draft'
+ $scope.draftText = trans('entities.pages_editing_draft');
} else {
- $scope.draftText = 'Editing Page'
+ $scope.draftText = trans('entities.pages_editing_page');
}
let autoSave = false;
lastSave = Date.now();
}, errorRes => {
if (draftErroring) return;
- events.emit('error', 'Failed to save draft. Ensure you have internet connection before saving this page.')
+ events.emit('error', trans('errors.page_draft_autosave_fail'));
draftErroring = true;
});
}
let url = window.baseUrl('/ajax/page/' + pageId);
$http.get(url).then((responseData) => {
if (autoSave) $interval.cancel(autoSave);
- $scope.draftText = 'Editing Page';
+ $scope.draftText = trans('entities.pages_editing_page');
$scope.isUpdateDraft = false;
$scope.$broadcast('html-update', responseData.data.html);
$scope.$broadcast('markdown-update', responseData.data.markdown || responseData.data.html);
$timeout(() => {
startAutoSave();
}, 1000);
- events.emit('success', 'Draft discarded, The editor has been updated with the current page content');
+ events.emit('success', trans('entities.pages_draft_discarded'));
});
};
$scope.$apply(() => {
$scope.files.push(data);
});
- events.emit('success', 'File uploaded');
+ events.emit('success', trans('entities.attachments_file_uploaded'));
};
/**
data.link = '';
}
});
- events.emit('success', 'File updated');
+ events.emit('success', trans('entities.attachments_file_updated'));
};
/**
file.uploaded_to = pageId;
$http.post(window.baseUrl('/attachments/link'), file).then(resp => {
$scope.files.push(resp.data);
- events.emit('success', 'Link attached');
+ events.emit('success', trans('entities.attachments_link_attached'));
$scope.file = getCleanFile();
}, checkError('link'));
};
$scope.editFile.link = '';
}
$scope.editFile = false;
- events.emit('success', resp.headers('message-success'));
+ events.emit('success', trans('entities.attachments_updated_success'));
}, checkError('edit'));
};
$(file.previewElement).find('[data-dz-errormessage]').text(message);
}
- if (xhr.status === 413) setMessage('The server does not allow uploads of this size. Please try a smaller file.');
+ if (xhr.status === 413) setMessage(trans('errors.server_upload_limit'));
if (errorMessage.file) setMessage(errorMessage.file[0]);
});
link: function (scope, element, attrs) {
// Elements
- const input = element.find('[markdown-input] textarea').first();
- const display = element.find('.markdown-display').first();
- const insertImage = element.find('button[data-action="insertImage"]');
- const insertEntityLink = element.find('button[data-action="insertEntityLink"]')
+ const $input = element.find('[markdown-input] textarea').first();
+ const $display = element.find('.markdown-display').first();
+ const $insertImage = element.find('button[data-action="insertImage"]');
+ const $insertEntityLink = element.find('button[data-action="insertEntityLink"]');
+
+ // Prevent markdown display link click redirect
+ $display.on('click', 'a', function(event) {
+ event.preventDefault();
+ window.open(this.getAttribute('href'));
+ });
let currentCaretPos = 0;
- input.blur(event => {
- currentCaretPos = input[0].selectionStart;
+ $input.blur(event => {
+ currentCaretPos = $input[0].selectionStart;
});
// Scroll sync
displayHeight;
function setScrollHeights() {
- inputScrollHeight = input[0].scrollHeight;
- inputHeight = input.height();
- displayScrollHeight = display[0].scrollHeight;
- displayHeight = display.height();
+ inputScrollHeight = $input[0].scrollHeight;
+ inputHeight = $input.height();
+ displayScrollHeight = $display[0].scrollHeight;
+ displayHeight = $display.height();
}
setTimeout(() => {
window.addEventListener('resize', setScrollHeights);
let scrollDebounceTime = 800;
let lastScroll = 0;
- input.on('scroll', event => {
+ $input.on('scroll', event => {
let now = Date.now();
if (now - lastScroll > scrollDebounceTime) {
setScrollHeights()
}
- let scrollPercent = (input.scrollTop() / (inputScrollHeight - inputHeight));
+ let scrollPercent = ($input.scrollTop() / (inputScrollHeight - inputHeight));
let displayScrollY = (displayScrollHeight - displayHeight) * scrollPercent;
- display.scrollTop(displayScrollY);
+ $display.scrollTop(displayScrollY);
lastScroll = now;
});
// Editor key-presses
- input.keydown(event => {
+ $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();
+ let caretPos = $input[0].selectionStart;
+ let currentContent = $input.val();
const mdImageText = "";
- input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
- input.focus();
- input[0].selectionStart = caretPos + (";
- input[0].selectionEnd = caretPos + (';
+ $input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
+ $input.focus();
+ $input[0].selectionStart = caretPos + (";
+ $input[0].selectionEnd = caretPos + (';
return;
}
});
// Insert image from image manager
- insertImage.click(event => {
+ $insertImage.click(event => {
window.ImageManager.showExternal(image => {
let caretPos = currentCaretPos;
- let currentContent = input.val();
+ let currentContent = $input.val();
let mdImageText = "";
- input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
- input.change();
+ $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 selectionEnd = $input[0].selectionEnd;
let textSelected = (selectionEnd !== selectionStart);
- let currentContent = input.val();
+ 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));
+ $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.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionStart))
}
- input.change();
+ $input.change();
});
}
- insertEntityLink.click(showLinkSelector);
+ $insertEntityLink.click(showLinkSelector);
// Upload and insert image on paste
function editorPaste(e) {
}
}
- input.on('paste', editorPaste);
+ $input.on('paste', editorPaste);
// Handle image drop, Uploads images to BookStack.
function handleImageDrop(event) {
}
}
- input.on('drop', handleImageDrop);
+ $input.on('drop', handleImageDrop);
// Handle image upload and add image into markdown content
function uploadImage(file) {
// 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 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[0].value = content.substring(0, selectStart) + innerContent + content.substring(selectEnd);
- input.focus();
- input[0].selectionStart = selectStart;
- input[0].selectionEnd = selectStart;
+ $input.focus();
+ $input[0].selectionStart = selectStart;
+ $input[0].selectionEnd = selectStart;
let remoteFilename = "image-" + Date.now() + "." + ext;
formData.append('file', file, remoteFilename);
xhr.open('POST', window.baseUrl('/images/gallery/upload'));
xhr.onload = function () {
- let selectStart = input[0].selectionStart;
+ 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();
+ $input[0].value = $input[0].value.replace(placeholderImage, result.thumbs.display);
+ $input.change();
} else {
- console.log('An error occurred uploading the image');
+ console.log(trans('errors.image_upload_error'));
console.log(xhr.responseText);
- input[0].value = input[0].value.replace(innerContent, '');
- input.change();
+ $input[0].value = $input[0].value.replace(innerContent, '');
+ $input.change();
}
- input.focus();
- input[0].selectionStart = selectStart;
- input[0].selectionEnd = selectStart;
+ $input.focus();
+ $input[0].selectionStart = selectStart;
+ $input[0].selectionEnd = selectStart;
};
xhr.send(formData);
}
}
// Enter or tab key
else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) {
- let text = suggestionElems[active].textContent;
- currentInput[0].value = text;
+ currentInput[0].value = suggestionElems[active].textContent;
currentInput.focus();
$suggestionBox.hide();
isShowing = false;
ngApp.directive('entityLinkSelector', [function($http) {
return {
- restict: 'A',
+ restrict: 'A',
link: function(scope, element, attrs) {
const selectButton = element.find('.entity-link-selector-confirm');
let ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']);
+// Translation setup
+// Creates a global function with name 'trans' to be used in the same way as Laravel's translation system
+import Translations from "./translations"
+let translator = new Translations(window.translations);
+window.trans = translator.get.bind(translator);
+
// Global Event System
class EventManager {
constructor() {
});
// Global jQuery Elements
-$(function () {
-
- let notifications = $('.notification');
- let successNotification = notifications.filter('.pos');
- let errorNotification = notifications.filter('.neg');
- let warningNotification = notifications.filter('.warning');
- // Notification Events
- window.Events.listen('success', function (text) {
- successNotification.hide();
- successNotification.find('span').text(text);
- setTimeout(() => {
- successNotification.show();
- }, 1);
- });
- window.Events.listen('warning', function (text) {
- warningNotification.find('span').text(text);
- warningNotification.show();
- });
- window.Events.listen('error', function (text) {
- errorNotification.find('span').text(text);
- errorNotification.show();
- });
-
- // Notification hiding
- notifications.click(function () {
- $(this).fadeOut(100);
- });
-
- // Chapter page list toggles
- $('.chapter-toggle').click(function (e) {
- e.preventDefault();
- $(this).toggleClass('open');
- $(this).closest('.chapter').find('.inset-list').slideToggle(180);
- });
-
- // Back to top button
- $('#back-to-top').click(function() {
- $('#header').smoothScrollTo();
- });
- let scrollTopShowing = false;
- let scrollTop = document.getElementById('back-to-top');
- let scrollTopBreakpoint = 1200;
- window.addEventListener('scroll', function() {
- let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
- if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) {
- scrollTop.style.display = 'block';
- scrollTopShowing = true;
- setTimeout(() => {
- scrollTop.style.opacity = 0.4;
- }, 1);
- } else if (scrollTopShowing && scrollTopPos < scrollTopBreakpoint) {
- scrollTop.style.opacity = 0;
- scrollTopShowing = false;
- setTimeout(() => {
- scrollTop.style.display = 'none';
- }, 500);
- }
- });
-
- // Common jQuery actions
- $('[data-action="expand-entity-list-details"]').click(function() {
- $('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
- });
-
- // Popup close
- $('.popup-close').click(function() {
- $(this).closest('.overlay').fadeOut(240);
- });
- $('.overlay').click(function(event) {
- if (!$(event.target).hasClass('overlay')) return;
- $(this).fadeOut(240);
- });
-
- // Prevent markdown display link click redirect
- $('.markdown-display').on('click', 'a', function(event) {
- event.preventDefault();
- window.open($(this).attr('href'));
- });
-
- // Toggle Switches
- let $switches = $('[toggle-switch]');
- if ($switches.length > 0) {
- $switches.click(event => {
- let $switch = $(event.target);
- let input = $switch.find('input').first()[0];
- let checked = input.value !== 'true';
- input.value = checked ? 'true' : 'false';
- $switch.toggleClass('active', checked);
- });
- }
-
- // Image pickers
- $('.image-picker').on('click', 'button', event => {
- let button = event.target;
- let picker = $(button).closest('.image-picker')[0];
- let action = button.getAttribute('data-action');
- let resize = picker.getAttribute('data-resize-height') && picker.getAttribute('data-resize-width');
- let usingIds = picker.getAttribute('data-current-id') !== '';
- let resizeCrop = picker.getAttribute('data-resize-crop') !== '';
- let imageElem = picker.querySelector('img');
- let input = picker.querySelector('input');
-
- function setImage(image) {
-
- if (image === 'none') {
- imageElem.src = picker.getAttribute('data-default-image');
- imageElem.classList.add('none');
- input.value = 'none';
- return;
- }
-
- imageElem.src = image.url;
- input.value = usingIds ? image.id : image.url;
- imageElem.classList.remove('none');
- }
+let notifications = $('.notification');
+let successNotification = notifications.filter('.pos');
+let errorNotification = notifications.filter('.neg');
+let warningNotification = notifications.filter('.warning');
+// Notification Events
+window.Events.listen('success', function (text) {
+ successNotification.hide();
+ successNotification.find('span').text(text);
+ setTimeout(() => {
+ successNotification.show();
+ }, 1);
+});
+window.Events.listen('warning', function (text) {
+ warningNotification.find('span').text(text);
+ warningNotification.show();
+});
+window.Events.listen('error', function (text) {
+ errorNotification.find('span').text(text);
+ errorNotification.show();
+});
- if (action === 'show-image-manager') {
- window.ImageManager.showExternal((image) => {
- if (!resize) {
- setImage(image);
- return;
- }
- let requestString = '/images/thumb/' + image.id + '/' + picker.getAttribute('data-resize-width') + '/' + picker.getAttribute('data-resize-height') + '/' + (resizeCrop ? 'true' : 'false');
- $.get(window.baseUrl(requestString), resp => {
- image.url = resp.url;
- setImage(image);
- });
- });
- } else if (action === 'reset-image') {
- setImage({id: 0, url: picker.getAttribute('data-default-image')});
- } else if (action === 'remove-image') {
- setImage('none');
- }
+// Notification hiding
+notifications.click(function () {
+ $(this).fadeOut(100);
+});
- });
+// Chapter page list toggles
+$('.chapter-toggle').click(function (e) {
+ e.preventDefault();
+ $(this).toggleClass('open');
+ $(this).closest('.chapter').find('.inset-list').slideToggle(180);
+});
- // Detect IE for css
- if(navigator.userAgent.indexOf('MSIE')!==-1
- || navigator.appVersion.indexOf('Trident/') > 0
- || navigator.userAgent.indexOf('Safari') !== -1){
- $('body').addClass('flexbox-support');
+// Back to top button
+$('#back-to-top').click(function() {
+ $('#header').smoothScrollTo();
+});
+let scrollTopShowing = false;
+let scrollTop = document.getElementById('back-to-top');
+let scrollTopBreakpoint = 1200;
+window.addEventListener('scroll', function() {
+ let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
+ if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) {
+ scrollTop.style.display = 'block';
+ scrollTopShowing = true;
+ setTimeout(() => {
+ scrollTop.style.opacity = 0.4;
+ }, 1);
+ } else if (scrollTopShowing && scrollTopPos < scrollTopBreakpoint) {
+ scrollTop.style.opacity = 0;
+ scrollTopShowing = false;
+ setTimeout(() => {
+ scrollTop.style.display = 'none';
+ }, 500);
}
+});
+// Common jQuery actions
+$('[data-action="expand-entity-list-details"]').click(function() {
+ $('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
});
+// Popup close
+$('.popup-close').click(function() {
+ $(this).closest('.overlay').fadeOut(240);
+});
+$('.overlay').click(function(event) {
+ if (!$(event.target).hasClass('overlay')) return;
+ $(this).fadeOut(240);
+});
+
+// Detect IE for css
+if(navigator.userAgent.indexOf('MSIE')!==-1
+ || navigator.appVersion.indexOf('Trident/') > 0
+ || navigator.userAgent.indexOf('Safari') !== -1){
+ $('body').addClass('flexbox-support');
+}
+
// Page specific items
import "./pages/page-show";
--- /dev/null
+/**
+ * Translation Manager
+ * Handles the JavaScript side of translating strings
+ * in a way which fits with Laravel.
+ */
+class Translator {
+
+ /**
+ * Create an instance, Passing in the required translations
+ * @param translations
+ */
+ constructor(translations) {
+ this.store = translations;
+ }
+
+ /**
+ * Get a translation, Same format as laravel's 'trans' helper
+ * @param key
+ * @param replacements
+ * @returns {*}
+ */
+ get(key, replacements) {
+ let splitKey = key.split('.');
+ let value = splitKey.reduce((a, b) => {
+ return a != undefined ? a[b] : a;
+ }, this.store);
+
+ if (value === undefined) {
+ console.log(`Translation with key "${key}" does not exist`);
+ value = key;
+ }
+
+ if (replacements === undefined) return value;
+
+ let replaceMatches = value.match(/:([\S]+)/g);
+ if (replaceMatches === null) return value;
+ replaceMatches.forEach(match => {
+ let key = match.substring(1);
+ if (typeof replacements[key] === 'undefined') return;
+ value = value.replace(match, replacements[key]);
+ });
+ return value;
+ }
+
+}
+
+export default Translator
tr:hover {
background-color: #EEE;
}
+ .text-right {
+ text-align: right;
+ }
+ .text-center {
+ text-align: center;
+ }
}
table.no-style {
'app_name_desc' => 'Dieser Name wird im Header und E-Mails angezeigt.',
'app_name_header' => 'Anwendungsname im Header anzeigen?',
'app_public_viewing' => 'Öffentliche Ansicht erlauben?',
- 'app_secure_images' => 'Erh&oml;hte Sicherheit für Bilduploads aktivieren?',
+ 'app_secure_images' => 'Erhöhte Sicherheit für Bilduploads aktivieren?',
'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu eratene, Zeichenketten vor die Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnindexes deaktiviert sind, um einen einfachen Zugrif zu verhindern.',
'app_editor' => 'Seiteneditor',
'app_editor_desc' => 'Wählen sie den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.',
/**
* Image Manager
*/
- 'imagem_select' => 'Image Select',
- 'imagem_all' => 'All',
- 'imagem_all_title' => 'View all images',
- 'imagem_book_title' => 'View images uploaded to this book',
- 'imagem_page_title' => 'View images uploaded to this page',
- 'imagem_search_hint' => 'Search by image name',
- 'imagem_uploaded' => 'Uploaded :uploadedDate',
- 'imagem_load_more' => 'Load More',
- 'imagem_image_name' => 'Image Name',
- 'imagem_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.',
- 'imagem_select_image' => 'Select Image',
- 'imagem_dropzone' => 'Drop images or click here to upload',
+ 'image_select' => 'Image Select',
+ 'image_all' => 'All',
+ 'image_all_title' => 'View all images',
+ 'image_book_title' => 'View images uploaded to this book',
+ 'image_page_title' => 'View images uploaded to this page',
+ 'image_search_hint' => 'Search by image name',
+ 'image_uploaded' => 'Uploaded :uploadedDate',
+ 'image_load_more' => 'Load More',
+ 'image_image_name' => 'Image Name',
+ 'image_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.',
+ 'image_select_image' => 'Select Image',
+ 'image_dropzone' => 'Drop images or click here to upload',
'images_deleted' => 'Images Deleted',
'image_preview' => 'Image Preview',
+ 'image_upload_success' => 'Image uploaded successfully',
+ 'image_update_success' => 'Image details successfully updated',
+ 'image_delete_success' => 'Image successfully deleted'
];
\ No newline at end of file
'pages_edit_toggle_header' => 'Toggle header',
'pages_edit_save_draft' => 'Save Draft',
'pages_edit_draft' => 'Edit Page Draft',
+ 'pages_editing_draft' => 'Editing Draft',
+ 'pages_editing_page' => 'Editing Page',
'pages_edit_draft_save_at' => 'Draft saved at ',
'pages_edit_delete_draft' => 'Delete Draft',
'pages_edit_discard_draft' => 'Discard Draft',
'time_b' => 'in the last :minCount minutes',
'message' => ':start :time. Take care not to overwrite each other\'s updates!',
],
+ 'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
/**
* Editor sidebar
'attachments_order_updated' => 'Attachment order updated',
'attachments_updated_success' => 'Attachment details updated',
'attachments_deleted' => 'Attachment deleted',
+ 'attachments_file_uploaded' => 'File successfully uploaded',
+ 'attachments_file_updated' => 'File successfully updated',
+ 'attachments_link_attached' => 'Link successfully attached to page',
/**
* Profile View
'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
'cannot_get_image_from_url' => 'Cannot get image from :url',
'cannot_create_thumbs' => 'The server cannot create thumbnails. Please check you have the GD PHP extension installed.',
+ 'server_upload_limit' => 'The server does not allow uploads of this size. Please try a smaller file size.',
+ 'image_upload_error' => 'An error occurred uploading the image',
// Attachments
'attachment_page_mismatch' => 'Page mismatch during attachment update',
+ // Pages
+ 'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
+
// Entities
'entity_not_found' => 'Entity not found',
'book_not_found' => 'Book not found',
<!-- Scripts -->
<script src="{{ baseUrl('/libs/jquery/jquery.min.js?version=2.1.4') }}"></script>
<script src="{{ baseUrl('/libs/jquery/jquery-ui.min.js?version=1.11.4') }}"></script>
+ <script src="{{ baseUrl('/translations.js') }}"></script>
@yield('head')
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
- @include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book'])
+ @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book'])
<a href="{{ $chapter->getUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
<button type="submit" class="button pos">{{ trans('entities.chapters_move') }}</button>
<div class="popup-title">{{ trans('entities.entity_select') }}</div>
<button type="button" class="corner-button neg button popup-close">x</button>
</div>
- @include('partials/entity-selector', ['name' => 'entity-selector'])
+ @include('components.entity-selector', ['name' => 'entity-selector'])
<div class="popup-footer">
<button type="button" disabled="true" class="button entity-link-selector-confirm pos corner-button">{{ trans('common.select') }}</button>
</div>
<div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}">
<input type="hidden" entity-selector-input name="{{$name}}" value="">
<input type="text" placeholder="{{ trans('common.search') }}" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()">
- <div class="text-center loading" ng-show="loading">@include('partials/loading-icon')</div>
+ <div class="text-center loading" ng-show="loading">@include('partials.loading-icon')</div>
<div ng-show="!loading" ng-bind-html="entityResults"></div>
</div>
</div>
\ No newline at end of file
<div class="popup-body" ng-click="$event.stopPropagation()">
<div class="popup-header primary-background">
- <div class="popup-title">{{ trans('components.imagem_select') }}</div>
+ <div class="popup-title">{{ trans('components.image_select') }}</div>
<button class="popup-close neg corner-button button">x</button>
</div>
<div class="image-manager-content">
<div ng-if="imageType === 'gallery'" class="container">
<div class="image-manager-header row faded-small nav-tabs">
- <div class="col-xs-4 tab-item" title="{{ trans('components.imagem_all_title') }}" ng-class="{selected: (view=='all')}" ng-click="setView('all')"><i class="zmdi zmdi-collection-image"></i> {{ trans('components.imagem_all') }}</div>
- <div class="col-xs-4 tab-item" title="{{ trans('components.imagem_book_title') }}" ng-class="{selected: (view=='book')}" ng-click="setView('book')"><i class="zmdi zmdi-book text-book"></i> {{ trans('entities.book') }}</div>
- <div class="col-xs-4 tab-item" title="{{ trans('components.imagem_page_title') }}" ng-class="{selected: (view=='page')}" ng-click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> {{ trans('entities.page') }}</div>
+ <div class="col-xs-4 tab-item" title="{{ trans('components.image_all_title') }}" ng-class="{selected: (view=='all')}" ng-click="setView('all')"><i class="zmdi zmdi-collection-image"></i> {{ trans('components.image_all') }}</div>
+ <div class="col-xs-4 tab-item" title="{{ trans('components.image_book_title') }}" ng-class="{selected: (view=='book')}" ng-click="setView('book')"><i class="zmdi zmdi-book text-book"></i> {{ trans('entities.book') }}</div>
+ <div class="col-xs-4 tab-item" title="{{ trans('components.image_page_title') }}" ng-class="{selected: (view=='page')}" ng-click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> {{ trans('entities.page') }}</div>
</div>
</div>
<div ng-show="view === 'all'" >
<form ng-submit="searchImages()" class="contained-search-box">
- <input type="text" placeholder="{{ trans('components.imagem_search_hint') }}" ng-model="searchTerm">
+ <input type="text" placeholder="{{ trans('components.image_search_hint') }}" ng-model="searchTerm">
<button ng-class="{active: searching}" title="{{ trans('common.search_clear') }}" type="button" ng-click="cancelSearch()" class="text-button cancel"><i class="zmdi zmdi-close-circle-o"></i></button>
<button title="{{ trans('common.search') }}" class="text-button" type="submit"><i class="zmdi zmdi-search"></i></button>
</form>
<img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
<div class="image-meta">
<span class="name" ng-bind="image.name"></span>
- <span class="date">{{ trans('components.imagem_uploaded', ['uploadedDate' => "{{ getDate(image.created_at) }" . "}"]) }}</span>
+ <span class="date">{{ trans('components.image_uploaded', ['uploadedDate' => "{{ getDate(image.created_at) }" . "}"]) }}</span>
</div>
</div>
</div>
- <div class="load-more" ng-show="hasMore" ng-click="fetchData()">{{ trans('components.imagem_load_more') }}</div>
+ <div class="load-more" ng-show="hasMore" ng-click="fetchData()">{{ trans('components.image_load_more') }}</div>
</div>
</div>
</a>
</div>
<div class="form-group">
- <label for="name">{{ trans('components.imagem_image_name') }}</label>
+ <label for="name">{{ trans('components.image_image_name') }}</label>
<input type="text" id="name" name="name" ng-model="selectedImage.name">
</div>
</form>
<div ng-show="dependantPages">
<p class="text-neg text-small">
- {{ trans('components.imagem_delete_confirm') }}
+ {{ trans('components.image_delete_confirm') }}
</p>
<ul class="text-neg">
<li ng-repeat="page in dependantPages">
<button class="button icon neg"><i class="zmdi zmdi-delete"></i></button>
</form>
<button class="button pos anim fadeIn float right" ng-show="selectedImage" ng-click="selectButtonClick()">
- <i class="zmdi zmdi-square-right"></i>{{ trans('components.imagem_select_image') }}
+ <i class="zmdi zmdi-square-right"></i>{{ trans('components.image_select_image') }}
</button>
</div>
</div>
- <drop-zone message="{{ trans('components.imagem_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
+ <drop-zone message="{{ trans('components.image_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
</div>
-<div class="image-picker" data-default-image="{{ $defaultImage }}" data-resize-height="{{ $resizeHeight }}" data-resize-width="{{ $resizeWidth }}" data-current-id="{{ $currentId or '' }}" data-resize-crop="{{ $resizeCrop or '' }}">
+<div class="image-picker" image-picker="{{$name}}" data-default-image="{{ $defaultImage }}" data-resize-height="{{ $resizeHeight }}" data-resize-width="{{ $resizeWidth }}" data-current-id="{{ $currentId or '' }}" data-resize-crop="{{ $resizeCrop or '' }}">
<div>
<img @if($currentImage && $currentImage !== 'none') src="{{$currentImage}}" @else src="{{$defaultImage}}" @endif class="{{$imageClass}} @if($currentImage=== 'none') none @endif" alt="{{ trans('components.image_preview') }}">
</div>
- <button class="button" type="button" data-action="show-image-manager">{{ trans('components.imagem_select_image') }}</button>
+ <button class="button" type="button" data-action="show-image-manager">{{ trans('components.image_select_image') }}</button>
<br>
<button class="text-button" data-action="reset-image" type="button">{{ trans('common.reset') }}</button>
<button class="text-button neg" data-action="remove-image" type="button">{{ trans('common.remove') }}</button>
@endif
- <input type="hidden" name="{{$name}}" id="{{$name}}" value="{{$currentId or $currentImage or ''}}">
-</div>
\ No newline at end of file
+ <input type="hidden" name="{{$name}}" id="{{$name}}" value="{{ isset($currentId) && ($currentId !== '' && $currentId !== false) ? $currentId : $currentImage}}">
+</div>
+
+<script>
+ (function(){
+
+ var picker = document.querySelector('[image-picker="{{$name}}"]');
+ picker.addEventListener('click', function(event) {
+ if (event.target.nodeName.toLowerCase() !== 'button') return;
+ var button = event.target;
+ var action = button.getAttribute('data-action');
+ var resize = picker.getAttribute('data-resize-height') && picker.getAttribute('data-resize-width');
+ var usingIds = picker.getAttribute('data-current-id') !== '';
+ var resizeCrop = picker.getAttribute('data-resize-crop') !== '';
+ var imageElem = picker.querySelector('img');
+ var input = picker.querySelector('input');
+
+ function setImage(image) {
+ if (image === 'none') {
+ imageElem.src = picker.getAttribute('data-default-image');
+ imageElem.classList.add('none');
+ input.value = 'none';
+ return;
+ }
+ imageElem.src = image.url;
+ input.value = usingIds ? image.id : image.url;
+ imageElem.classList.remove('none');
+ }
+
+ if (action === 'show-image-manager') {
+ window.ImageManager.showExternal((image) => {
+ if (!resize) {
+ setImage(image);
+ return;
+ }
+ var requestString = '/images/thumb/' + image.id + '/' + picker.getAttribute('data-resize-width') + '/' + picker.getAttribute('data-resize-height') + '/' + (resizeCrop ? 'true' : 'false');
+ $.get(window.baseUrl(requestString), resp => {
+ image.url = resp.url;
+ setImage(image);
+ });
+ });
+ } else if (action === 'reset-image') {
+ setImage({id: 0, url: picker.getAttribute('data-default-image')});
+ } else if (action === 'remove-image') {
+ setImage('none');
+ }
+
+ });
+
+ })();
+</script>
\ No newline at end of file
-<div toggle-switch class="toggle-switch @if($value) active @endif">
+<div toggle-switch="{{$name}}" class="toggle-switch @if($value) active @endif">
<input type="hidden" name="{{$name}}" value="{{$value?'true':'false'}}"/>
<div class="switch-handle"></div>
-</div>
\ No newline at end of file
+</div>
+<script>
+ (function() {
+ var toggle = document.querySelector('[toggle-switch="{{$name}}"]');
+ var toggleInput = toggle.querySelector('input');
+ toggle.onclick = function(event) {
+ var checked = toggleInput.value !== 'true';
+ toggleInput.value = checked ? 'true' : 'false';
+ checked ? toggle.classList.add('active') : toggle.classList.remove('active');
+ };
+ })()
+</script>
\ No newline at end of file
</div>
- @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
- @include('partials/entity-selector-popup')
+ @include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
+ @include('components.entity-selector-popup')
@stop
\ No newline at end of file
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
- @include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter'])
+ @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter'])
<a href="{{ $page->getUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
<button type="submit" class="button pos">{{ trans('entities.pages_move') }}</button>
-<style>
+<style id="custom-styles" data-color="{{ setting('app-color') }}" data-color-light="{{ setting('app-color-light') }}">
header, #back-to-top, .primary-background {
background-color: {{ setting('app-color') }} !important;
}
'defaultImage' => baseUrl('/logo.png'),
'currentImage' => setting('app-logo'),
'name' => 'setting-app-logo',
- 'imageClass' => 'logo-image'
+ 'imageClass' => 'logo-image',
+ 'currentId' => false
])
</div>
</div>
-@include('partials/image-manager', ['imageType' => 'system'])
+@include('components.image-manager', ['imageType' => 'system'])
@stop
var isEmpty = $.trim($elm.val()).length === 0;
if (!isEmpty) $elm.val(hexVal);
$('#setting-app-color-light').val(isEmpty ? '' : rgbLightVal);
- // Set page elements to provide preview
- $('#header, .image-picker .button').attr('style', 'background-color:'+ hexVal+'!important;');
- $('.faded-small').css('background-color', rgbLightVal);
- $('.setting-nav a.selected').css('border-bottom-color', hexVal + '!important');
+
+ var customStyles = document.getElementById('custom-styles');
+ var oldColor = customStyles.getAttribute('data-color');
+ var oldColorLight = customStyles.getAttribute('data-color-light');
+
+ customStyles.innerHTML = customStyles.innerHTML.split(oldColor).join(hexVal);
+ customStyles.innerHTML = customStyles.innerHTML.split(oldColorLight).join(rgbLightVal);
+
+ customStyles.setAttribute('data-color', hexVal);
+ customStyles.setAttribute('data-color-light', rgbLightVal);
}
});
</script>
<tr>
<th>{{ trans('settings.role_name') }}</th>
<th></th>
- <th class="text-right">{{ trans('settings.users') }}</th>
+ <th class="text-center">{{ trans('settings.users') }}</th>
</tr>
@foreach($roles as $role)
<tr>
<td><a href="{{ baseUrl("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td>
<td>{{ $role->description }}</td>
- <td class="text-right">{{ $role->users->count() }}</td>
+ <td class="text-center">{{ $role->users->count() }}</td>
</tr>
@endforeach
</table>
</div>
<p class="margin-top large"><br></p>
- @include('partials/image-manager', ['imageType' => 'user'])
+ @include('components.image-manager', ['imageType' => 'user'])
@stop
<?php
+Route::get('/translations.js', 'HomeController@getTranslations');
+
// Authenticated routes...
Route::group(['middleware' => 'auth'], function () {