diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index 28a45e591..fcaba2914 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -69,7 +69,7 @@ module.exports = function (ngApp, events) { */ function callbackAndHide(returnData) { if (callback) callback(returnData); - $scope.showing = false; + $scope.hide(); } /** @@ -109,6 +109,7 @@ module.exports = function (ngApp, events) { function show(doneCallback) { callback = doneCallback; $scope.showing = true; + $('#image-manager').find('.overlay').css('display', 'flex').hide().fadeIn(240); // Get initial images if they have not yet been loaded in. if (!dataLoaded) { fetchData(); @@ -131,6 +132,7 @@ module.exports = function (ngApp, events) { */ $scope.hide = function () { $scope.showing = false; + $('#image-manager').find('.overlay').fadeOut(240); }; var baseUrl = window.baseUrl('/images/' + $scope.imageType + '/all/'); diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index 286854832..1271b3112 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -271,8 +271,6 @@ module.exports = function (ngApp, events) { scope.mdModel = content; scope.mdChange(markdown(content)); - console.log('test'); - element.on('change input', (event) => { content = element.val(); $timeout(() => { @@ -304,6 +302,7 @@ module.exports = function (ngApp, events) { 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"]') let currentCaretPos = 0; @@ -355,6 +354,13 @@ module.exports = function (ngApp, events) { 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); }); @@ -370,6 +376,26 @@ module.exports = function (ngApp, events) { }); }); + 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; @@ -677,6 +703,58 @@ module.exports = function (ngApp, events) { } }]); + ngApp.directive('entityLinkSelector', [function($http) { + return { + restict: 'A', + link: function(scope, element, attrs) { + + const selectButton = element.find('.entity-link-selector-confirm'); + let callback = false; + let entitySelection = null; + + // Handle entity selection change, Stores the selected entity locally + function entitySelectionChange(entity) { + entitySelection = entity; + if (entity === null) { + selectButton.attr('disabled', 'true'); + } else { + selectButton.removeAttr('disabled'); + } + } + events.listen('entity-select-change', entitySelectionChange); + + // Handle selection confirm button click + selectButton.click(event => { + hide(); + if (entitySelection !== null) callback(entitySelection); + }); + + // Show selector interface + function show() { + element.fadeIn(240); + } + + // Hide selector interface + function hide() { + element.fadeOut(240); + } + + // Listen to confirmation of entity selections (doubleclick) + events.listen('entity-select-confirm', entity => { + hide(); + callback(entity); + }); + + // Show entity selector, Accessible globally, and store the callback + window.showEntityLinkSelector = function(passedCallback) { + show(); + callback = passedCallback; + }; + + } + }; + }]); + ngApp.directive('entitySelector', ['$http', '$sce', function ($http, $sce) { return { @@ -690,26 +768,60 @@ module.exports = function (ngApp, events) { // Add input for forms const input = element.find('[entity-selector-input]').first(); + // Detect double click events + var lastClick = 0; + function isDoubleClick() { + let now = Date.now(); + let answer = now - lastClick < 300; + lastClick = now; + return answer; + } + // Listen to entity item clicks element.on('click', '.entity-list a', function(event) { event.preventDefault(); event.stopPropagation(); let item = $(this).closest('[data-entity-type]'); - itemSelect(item); + itemSelect(item, isDoubleClick()); }); element.on('click', '[data-entity-type]', function(event) { - itemSelect($(this)); + itemSelect($(this), isDoubleClick()); }); // Select entity action - function itemSelect(item) { + function itemSelect(item, doubleClick) { let entityType = item.attr('data-entity-type'); let entityId = item.attr('data-entity-id'); - let isSelected = !item.hasClass('selected'); + let isSelected = !item.hasClass('selected') || doubleClick; element.find('.selected').removeClass('selected').removeClass('primary-background'); if (isSelected) item.addClass('selected').addClass('primary-background'); let newVal = isSelected ? `${entityType}:${entityId}` : ''; input.val(newVal); + + if (!isSelected) { + events.emit('entity-select-change', null); + } + + if (!doubleClick && !isSelected) return; + + let link = item.find('.entity-list-item-link').attr('href'); + let name = item.find('.entity-list-item-name').text(); + + if (doubleClick) { + events.emit('entity-select-confirm', { + id: Number(entityId), + name: name, + link: link + }); + } + + if (isSelected) { + events.emit('entity-select-change', { + id: Number(entityId), + name: name, + link: link + }); + } } // Get search url with correct types diff --git a/resources/assets/js/global.js b/resources/assets/js/global.js index 3a107afa8..9ca335ee7 100644 --- a/resources/assets/js/global.js +++ b/resources/assets/js/global.js @@ -18,7 +18,7 @@ window.baseUrl = function(path) { var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']); // Global Event System -class Events { +class EventManager { constructor() { this.listeners = {}; } @@ -39,12 +39,12 @@ class Events { return this; } }; -window.Events = new Events(); +window.Events = new EventManager(); -var services = require('./services')(ngApp, Events); -var directives = require('./directives')(ngApp, Events); -var controllers = require('./controllers')(ngApp, Events); +var services = require('./services')(ngApp, window.Events); +var directives = require('./directives')(ngApp, window.Events); +var controllers = require('./controllers')(ngApp, window.Events); //Global jQuery Config & Extensions @@ -130,6 +130,27 @@ $(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')); + }); + + // Detect IE for css + if(navigator.userAgent.indexOf('MSIE')!==-1 + || navigator.appVersion.indexOf('Trident/') > 0 + || navigator.userAgent.indexOf('Safari') !== -1){ + $('body').addClass('flexbox-support'); + } }); diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js index 15fcdbb8e..daf9639d7 100644 --- a/resources/assets/js/pages/page-form.js +++ b/resources/assets/js/pages/page-form.js @@ -95,20 +95,38 @@ var mceOptions = module.exports = { alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'}, }, file_browser_callback: function (field_name, url, type, win) { - window.ImageManager.showExternal(function (image) { - win.document.getElementById(field_name).value = image.url; - if ("createEvent" in document) { - var evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", false, true); - win.document.getElementById(field_name).dispatchEvent(evt); - } else { - win.document.getElementById(field_name).fireEvent("onchange"); - } - var html = ''; - html += '' + image.name + ''; - html += ''; - win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html); - }); + + if (type === 'file') { + window.showEntityLinkSelector(function(entity) { + var originalField = win.document.getElementById(field_name); + originalField.value = entity.link; + $(originalField).closest('.mce-form').find('input').eq(2).val(entity.name); + }); + } + + if (type === 'image') { + // Show image manager + window.ImageManager.showExternal(function (image) { + + // Set popover link input to image url then fire change event + // to ensure the new value sticks + win.document.getElementById(field_name).value = image.url; + if ("createEvent" in document) { + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", false, true); + win.document.getElementById(field_name).dispatchEvent(evt); + } else { + win.document.getElementById(field_name).fireEvent("onchange"); + } + + // Replace the actively selected content with the linked image + var html = ''; + html += '' + image.name + ''; + html += ''; + win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html); + }); + } + }, paste_preprocess: function (plugin, args) { var content = args.content; @@ -119,6 +137,8 @@ var mceOptions = module.exports = { extraSetups: [], setup: function (editor) { + // Run additional setup actions + // Used by the angular side of things for (var i = 0; i < mceOptions.extraSetups.length; i++) { mceOptions.extraSetups[i](editor); } diff --git a/resources/assets/sass/_buttons.scss b/resources/assets/sass/_buttons.scss index 5bdb0cf28..5de889673 100644 --- a/resources/assets/sass/_buttons.scss +++ b/resources/assets/sass/_buttons.scss @@ -100,3 +100,13 @@ $button-border-radius: 2px; } } +.button[disabled] { + background-color: #BBB; + cursor: default; + &:hover { + background-color: #BBB; + cursor: default; + box-shadow: none; + } +} + diff --git a/resources/assets/sass/_image-manager.scss b/resources/assets/sass/_components.scss similarity index 86% rename from resources/assets/sass/_image-manager.scss rename to resources/assets/sass/_components.scss index 73b3b59d6..ccb69b44e 100644 --- a/resources/assets/sass/_image-manager.scss +++ b/resources/assets/sass/_components.scss @@ -1,5 +1,5 @@ .overlay { - background-color: rgba(0, 0, 0, 0.2); + background-color: rgba(0, 0, 0, 0.333); position: fixed; z-index: 95536; width: 100%; @@ -10,26 +10,76 @@ left: 0; right: 0; bottom: 0; + display: flex; + align-items: center; + justify-content: center; + display: none; } -.image-manager-body { +.popup-body-wrap { + display: flex; +} + +.popup-body { background-color: #FFF; max-height: 90%; - width: 90%; - height: 90%; + width: 1200px; + height: auto; margin: 2% 5%; border-radius: 4px; box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3); overflow: hidden; - position: fixed; - top: 0; - bottom: 0; - left: 0; z-index: 999; display: flex; - h1, h2, h3 { - font-weight: 300; + flex-direction: column; + &.small { + margin: 2% auto; + width: 800px; + max-width: 90%; } + &:before { + display: flex; + align-self: flex-start; + } +} + +//body.ie .popup-body { +// min-height: 100%; +//} + +.corner-button { + position: absolute; + top: 0; + right: 0; + margin: 0; + height: 40px; + border-radius: 0; + box-shadow: none; +} + +.popup-header, .popup-footer { + display: block !important; + position: relative; + height: 40px; + flex: none !important; + .popup-title { + color: #FFF; + padding: 8px $-m; + } +} +body.flexbox-support #entity-selector-wrap .popup-body .form-group { + height: 444px; + min-height: 444px; +} +#entity-selector-wrap .popup-body .form-group { + margin: 0; +} +//body.ie #entity-selector-wrap .popup-body .form-group { +// min-height: 60vh; +//} + +.image-manager-body { + min-height: 70vh; } #image-manager .dropzone-container { @@ -37,12 +87,6 @@ border: 3px dashed #DDD; } -.image-manager-bottom { - position: absolute; - bottom: 0; - right: 0; -} - .image-manager-list .image { display: block; position: relative; @@ -103,18 +147,13 @@ .image-manager-sidebar { width: 300px; - height: 100%; margin-left: 1px; - padding: 0 $-l; + padding: $-m $-l; + overflow-y: auto; border-left: 1px solid #DDD; -} - -.image-manager-close { - position: absolute; - top: 0; - right: 0; - margin: 0; - border-radius: 0; + .dropzone-container { + margin-top: $-m; + } } .image-manager-list { @@ -125,7 +164,6 @@ .image-manager-content { display: flex; flex-direction: column; - height: 100%; flex: 1; .container { width: 100%; @@ -141,12 +179,13 @@ * Copyright (c) 2012 Matias Meno */ .dz-message { - font-size: 1.4em; + font-size: 1.2em; + line-height: 1.1; font-style: italic; color: #aaa; text-align: center; cursor: pointer; - padding: $-xl $-m; + padding: $-l $-m; transition: all ease-in-out 120ms; } diff --git a/resources/assets/sass/_grid.scss b/resources/assets/sass/_grid.scss index 1a1321e58..231c12d4d 100644 --- a/resources/assets/sass/_grid.scss +++ b/resources/assets/sass/_grid.scss @@ -25,6 +25,14 @@ body.flexbox { } } +.flex-child > div { + flex: 1; +} + +//body.ie .flex-child > div { +// flex: 1 0 0px; +//} + /** Rules for all columns */ div[class^="col-"] img { max-width: 100%; diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index 210432588..7d33bd0a6 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -12,7 +12,7 @@ @import "animations"; @import "tinymce"; @import "highlightjs"; -@import "image-manager"; +@import "components"; @import "header"; @import "lists"; @import "pages"; diff --git a/resources/views/books/list-item.blade.php b/resources/views/books/list-item.blade.php index 945eb9015..2eefdfbf5 100644 --- a/resources/views/books/list-item.blade.php +++ b/resources/views/books/list-item.blade.php @@ -1,5 +1,5 @@
-

{{$book->name}}

+

{{$book->name}}

@if(isset($book->searchSnippet))

{!! $book->searchSnippet !!}

@else diff --git a/resources/views/chapters/list-item.blade.php b/resources/views/chapters/list-item.blade.php index 3677851df..35d3a7589 100644 --- a/resources/views/chapters/list-item.blade.php +++ b/resources/views/chapters/list-item.blade.php @@ -6,8 +6,8 @@   »   @endif - - {{ $chapter->name }} + + {{ $chapter->name }} @if(isset($chapter->searchSnippet)) diff --git a/resources/views/pages/edit.blade.php b/resources/views/pages/edit.blade.php index 9fe6a6a19..58df580a5 100644 --- a/resources/views/pages/edit.blade.php +++ b/resources/views/pages/edit.blade.php @@ -19,6 +19,14 @@
+ @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id]) + @include('partials/entity-selector-popup') + + @stop \ No newline at end of file diff --git a/resources/views/pages/form.blade.php b/resources/views/pages/form.blade.php index 5aee9c596..0e0c3672e 100644 --- a/resources/views/pages/form.blade.php +++ b/resources/views/pages/form.blade.php @@ -74,6 +74,8 @@ Editor
+  |  +
diff --git a/resources/views/pages/list-item.blade.php b/resources/views/pages/list-item.blade.php index a95870db0..98243f6fa 100644 --- a/resources/views/pages/list-item.blade.php +++ b/resources/views/pages/list-item.blade.php @@ -1,6 +1,6 @@

- {{ $page->name }} + {{ $page->name }}

@if(isset($page->searchSnippet)) diff --git a/resources/views/partials/entity-selector-popup.blade.php b/resources/views/partials/entity-selector-popup.blade.php new file mode 100644 index 000000000..b9166896a --- /dev/null +++ b/resources/views/partials/entity-selector-popup.blade.php @@ -0,0 +1,14 @@ +
+
+ +
+
\ No newline at end of file diff --git a/resources/views/partials/image-manager.blade.php b/resources/views/partials/image-manager.blade.php index 69928e119..83625ad88 100644 --- a/resources/views/partials/image-manager.blade.php +++ b/resources/views/partials/image-manager.blade.php @@ -1,84 +1,94 @@
-
-
+
+