Dan Brown

Added link selector interface to WYSIWYG editor

...@@ -600,6 +600,58 @@ module.exports = function (ngApp, events) { ...@@ -600,6 +600,58 @@ module.exports = function (ngApp, events) {
600 } 600 }
601 }]); 601 }]);
602 602
603 + ngApp.directive('entityLinkSelector', [function($http) {
604 + return {
605 + restict: 'A',
606 + link: function(scope, element, attrs) {
607 +
608 + const selectButton = element.find('.entity-link-selector-confirm');
609 + let callback = false;
610 + let entitySelection = null;
611 +
612 + // Handle entity selection change, Stores the selected entity locally
613 + function entitySelectionChange(entity) {
614 + entitySelection = entity;
615 + if (entity === null) {
616 + selectButton.attr('disabled', 'true');
617 + } else {
618 + selectButton.removeAttr('disabled');
619 + }
620 + }
621 + events.listen('entity-select-change', entitySelectionChange);
622 +
623 + // Handle selection confirm button click
624 + selectButton.click(event => {
625 + hide();
626 + if (entitySelection !== null) callback(entitySelection);
627 + });
628 +
629 + // Show selector interface
630 + function show() {
631 + element.fadeIn(240);
632 + }
633 +
634 + // Hide selector interface
635 + function hide() {
636 + element.fadeOut(240);
637 + }
638 +
639 + // Listen to confirmation of entity selections (doubleclick)
640 + events.listen('entity-select-confirm', entity => {
641 + hide();
642 + callback(entity);
643 + });
644 +
645 + // Show entity selector, Accessible globally, and store the callback
646 + window.showEntityLinkSelector = function(passedCallback) {
647 + show();
648 + callback = passedCallback;
649 + };
650 +
651 + }
652 + };
653 + }]);
654 +
603 655
604 ngApp.directive('entitySelector', ['$http', '$sce', function ($http, $sce) { 656 ngApp.directive('entitySelector', ['$http', '$sce', function ($http, $sce) {
605 return { 657 return {
...@@ -613,26 +665,60 @@ module.exports = function (ngApp, events) { ...@@ -613,26 +665,60 @@ module.exports = function (ngApp, events) {
613 // Add input for forms 665 // Add input for forms
614 const input = element.find('[entity-selector-input]').first(); 666 const input = element.find('[entity-selector-input]').first();
615 667
668 + // Detect double click events
669 + var lastClick = 0;
670 + function isDoubleClick() {
671 + let now = Date.now();
672 + let answer = now - lastClick < 300;
673 + lastClick = now;
674 + return answer;
675 + }
676 +
616 // Listen to entity item clicks 677 // Listen to entity item clicks
617 element.on('click', '.entity-list a', function(event) { 678 element.on('click', '.entity-list a', function(event) {
618 event.preventDefault(); 679 event.preventDefault();
619 event.stopPropagation(); 680 event.stopPropagation();
620 let item = $(this).closest('[data-entity-type]'); 681 let item = $(this).closest('[data-entity-type]');
621 - itemSelect(item); 682 + itemSelect(item, isDoubleClick());
622 }); 683 });
623 element.on('click', '[data-entity-type]', function(event) { 684 element.on('click', '[data-entity-type]', function(event) {
624 - itemSelect($(this)); 685 + itemSelect($(this), isDoubleClick());
625 }); 686 });
626 687
627 // Select entity action 688 // Select entity action
628 - function itemSelect(item) { 689 + function itemSelect(item, doubleClick) {
629 let entityType = item.attr('data-entity-type'); 690 let entityType = item.attr('data-entity-type');
630 let entityId = item.attr('data-entity-id'); 691 let entityId = item.attr('data-entity-id');
631 - let isSelected = !item.hasClass('selected'); 692 + let isSelected = !item.hasClass('selected') || doubleClick;
632 element.find('.selected').removeClass('selected').removeClass('primary-background'); 693 element.find('.selected').removeClass('selected').removeClass('primary-background');
633 if (isSelected) item.addClass('selected').addClass('primary-background'); 694 if (isSelected) item.addClass('selected').addClass('primary-background');
634 let newVal = isSelected ? `${entityType}:${entityId}` : ''; 695 let newVal = isSelected ? `${entityType}:${entityId}` : '';
635 input.val(newVal); 696 input.val(newVal);
697 +
698 + if (!isSelected) {
699 + events.emit('entity-select-change', null);
700 + }
701 +
702 + if (!doubleClick && !isSelected) return;
703 +
704 + let link = item.find('.entity-list-item-link').attr('href');
705 + let name = item.find('.entity-list-item-name').text();
706 +
707 + if (doubleClick) {
708 + events.emit('entity-select-confirm', {
709 + id: Number(entityId),
710 + name: name,
711 + link: link
712 + });
713 + }
714 +
715 + if (isSelected) {
716 + events.emit('entity-select-change', {
717 + id: Number(entityId),
718 + name: name,
719 + link: link
720 + });
721 + }
636 } 722 }
637 723
638 // Get search url with correct types 724 // Get search url with correct types
......
...@@ -135,6 +135,11 @@ $(function () { ...@@ -135,6 +135,11 @@ $(function () {
135 $(this).closest('.overlay').fadeOut(240); 135 $(this).closest('.overlay').fadeOut(240);
136 }); 136 });
137 137
138 + $('.overlay').click(function(event) {
139 + if (!$(event.target).hasClass('overlay')) return;
140 + $(this).fadeOut(240);
141 + });
142 +
138 }); 143 });
139 144
140 // Page specific items 145 // Page specific items
......
...@@ -96,26 +96,37 @@ var mceOptions = module.exports = { ...@@ -96,26 +96,37 @@ var mceOptions = module.exports = {
96 }, 96 },
97 file_browser_callback: function (field_name, url, type, win) { 97 file_browser_callback: function (field_name, url, type, win) {
98 98
99 - // Show image manager 99 + if (type === 'file') {
100 - window.ImageManager.showExternal(function (image) { 100 + window.showEntityLinkSelector(function(entity) {
101 - 101 + var originalField = win.document.getElementById(field_name);
102 - // Set popover link input to image url then fire change event 102 + originalField.value = entity.link;
103 - // to ensure the new value sticks 103 + $(originalField).closest('.mce-form').find('input').eq(2).val(entity.name);
104 - win.document.getElementById(field_name).value = image.url; 104 + });
105 - if ("createEvent" in document) { 105 + }
106 - var evt = document.createEvent("HTMLEvents"); 106 +
107 - evt.initEvent("change", false, true); 107 + if (type === 'image') {
108 - win.document.getElementById(field_name).dispatchEvent(evt); 108 + // Show image manager
109 - } else { 109 + window.ImageManager.showExternal(function (image) {
110 - win.document.getElementById(field_name).fireEvent("onchange"); 110 +
111 - } 111 + // Set popover link input to image url then fire change event
112 + // to ensure the new value sticks
113 + win.document.getElementById(field_name).value = image.url;
114 + if ("createEvent" in document) {
115 + var evt = document.createEvent("HTMLEvents");
116 + evt.initEvent("change", false, true);
117 + win.document.getElementById(field_name).dispatchEvent(evt);
118 + } else {
119 + win.document.getElementById(field_name).fireEvent("onchange");
120 + }
121 +
122 + // Replace the actively selected content with the linked image
123 + var html = '<a href="' + image.url + '" target="_blank">';
124 + html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
125 + html += '</a>';
126 + win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
127 + });
128 + }
112 129
113 - // Replace the actively selected content with the linked image
114 - var html = '<a href="' + image.url + '" target="_blank">';
115 - html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
116 - html += '</a>';
117 - win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
118 - });
119 }, 130 },
120 paste_preprocess: function (plugin, args) { 131 paste_preprocess: function (plugin, args) {
121 var content = args.content; 132 var content = args.content;
......
...@@ -100,3 +100,13 @@ $button-border-radius: 2px; ...@@ -100,3 +100,13 @@ $button-border-radius: 2px;
100 } 100 }
101 } 101 }
102 102
103 +.button[disabled] {
104 + background-color: #BBB;
105 + cursor: default;
106 + &:hover {
107 + background-color: #BBB;
108 + cursor: default;
109 + box-shadow: none;
110 + }
111 +}
112 +
......
...@@ -39,25 +39,28 @@ ...@@ -39,25 +39,28 @@
39 } 39 }
40 } 40 }
41 41
42 -.popup-header { 42 +.corner-button {
43 + position: absolute;
44 + top: 0;
45 + right: 0;
46 + margin: 0;
47 + height: 40px;
48 + border-radius: 0;
49 + box-shadow: none;
50 +}
51 +
52 +.popup-header, .popup-footer {
43 display: block; 53 display: block;
44 position: relative; 54 position: relative;
45 height: 40px; 55 height: 40px;
46 - .popup-close {
47 - position: absolute;
48 - top: 0;
49 - right: 0;
50 - margin: 0;
51 - height: 40px;
52 - border-radius: 0;
53 - box-shadow: none;
54 - }
55 .popup-title { 56 .popup-title {
56 color: #FFF; 57 color: #FFF;
57 padding: 8px $-m; 58 padding: 8px $-m;
58 } 59 }
59 } 60 }
60 - 61 +#entity-selector-wrap .popup-body .form-group {
62 + margin: 0;
63 +}
61 .image-manager-body { 64 .image-manager-body {
62 min-height: 60vh; 65 min-height: 60vh;
63 } 66 }
......
1 <div class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}"> 1 <div class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
2 - <h3 class="text-book"><a class="text-book" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></h3> 2 + <h3 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->name}}</span></a></h3>
3 @if(isset($book->searchSnippet)) 3 @if(isset($book->searchSnippet))
4 <p class="text-muted">{!! $book->searchSnippet !!}</p> 4 <p class="text-muted">{!! $book->searchSnippet !!}</p>
5 @else 5 @else
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
6 </a> 6 </a>
7 <span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</span> 7 <span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</span>
8 @endif 8 @endif
9 - <a href="{{ $chapter->getUrl() }}" class="text-chapter"> 9 + <a href="{{ $chapter->getUrl() }}" class="text-chapter entity-list-item-link">
10 - <i class="zmdi zmdi-collection-bookmark"></i>{{ $chapter->name }} 10 + <i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
11 </a> 11 </a>
12 </h3> 12 </h3>
13 @if(isset($chapter->searchSnippet)) 13 @if(isset($chapter->searchSnippet))
......
...@@ -22,15 +22,24 @@ ...@@ -22,15 +22,24 @@
22 @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id]) 22 @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
23 23
24 <div id="entity-selector-wrap"> 24 <div id="entity-selector-wrap">
25 - <div class="overlay"> 25 + <div class="overlay" entity-link-selector>
26 <div class="popup-body small flex-child"> 26 <div class="popup-body small flex-child">
27 <div class="popup-header primary-background"> 27 <div class="popup-header primary-background">
28 <div class="popup-title">Entity Select</div> 28 <div class="popup-title">Entity Select</div>
29 - <button class="popup-close neg button">x</button> 29 + <button type="button" class="corner-button neg button">x</button>
30 </div> 30 </div>
31 @include('partials/entity-selector', ['name' => 'entity-selector']) 31 @include('partials/entity-selector', ['name' => 'entity-selector'])
32 + <div class="popup-footer">
33 + <button type="button" disabled="true" class="button entity-link-selector-confirm pos corner-button">Select</button>
34 + </div>
32 </div> 35 </div>
33 </div> 36 </div>
34 </div> 37 </div>
35 38
39 + <script>
40 + (function() {
41 +
42 + })();
43 + </script>
44 +
36 @stop 45 @stop
...\ No newline at end of file ...\ No newline at end of file
......
1 <div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}"> 1 <div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
2 <h3> 2 <h3>
3 - <a href="{{ $page->getUrl() }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ $page->name }}</a> 3 + <a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link"><i class="zmdi zmdi-file-text"></i><span class="entity-list-item-name">{{ $page->name }}</span></a>
4 </h3> 4 </h3>
5 5
6 @if(isset($page->searchSnippet)) 6 @if(isset($page->searchSnippet))
......