| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | var DropZone = require('dropzone'); | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  | var markdown = require('marked'); | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | var toggleSwitchTemplate = require('./components/toggle-switch.html'); | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  | var imagePickerTemplate = require('./components/image-picker.html'); | 
					
						
							|  |  |  | var dropZoneTemplate = require('./components/drop-zone.html'); | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-07 18:14:11 +08:00
										 |  |  | module.exports = function (ngApp, events) { | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Toggle Switches | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |      * Has basic on/off functionality. | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  |      * Use string values of 'true' & 'false' to dictate the current state. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |     ngApp.directive('toggleSwitch', function () { | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  |         return { | 
					
						
							| 
									
										
										
										
											2016-04-24 17:28:47 +08:00
										 |  |  |             restrict: 'A', | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  |             template: toggleSwitchTemplate, | 
					
						
							|  |  |  |             scope: true, | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |             link: function (scope, element, attrs) { | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  |                 scope.name = attrs.name; | 
					
						
							|  |  |  |                 scope.value = attrs.value; | 
					
						
							|  |  |  |                 scope.isActive = scope.value == true && scope.value != 'false'; | 
					
						
							|  |  |  |                 scope.value = (scope.value == true && scope.value != 'false') ? 'true' : 'false'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                 scope.switch = function () { | 
					
						
							| 
									
										
										
										
											2015-12-30 00:39:25 +08:00
										 |  |  |                     scope.isActive = !scope.isActive; | 
					
						
							|  |  |  |                     scope.value = scope.isActive ? 'true' : 'false'; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Image Picker | 
					
						
							|  |  |  |      * Is a simple front-end interface that connects to an ImageManager if present. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |     ngApp.directive('imagePicker', ['$http', 'imageManagerService', function ($http, imageManagerService) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'E', | 
					
						
							|  |  |  |             template: imagePickerTemplate, | 
					
						
							|  |  |  |             scope: { | 
					
						
							|  |  |  |                 name: '@', | 
					
						
							|  |  |  |                 resizeHeight: '@', | 
					
						
							|  |  |  |                 resizeWidth: '@', | 
					
						
							|  |  |  |                 resizeCrop: '@', | 
					
						
							|  |  |  |                 showRemove: '=', | 
					
						
							|  |  |  |                 currentImage: '@', | 
					
						
							|  |  |  |                 currentId: '@', | 
					
						
							|  |  |  |                 defaultImage: '@', | 
					
						
							|  |  |  |                 imageClass: '@' | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |             link: function (scope, element, attrs) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                 var usingIds = typeof scope.currentId !== 'undefined' || scope.currentId === 'false'; | 
					
						
							|  |  |  |                 scope.image = scope.currentImage; | 
					
						
							|  |  |  |                 scope.value = scope.currentImage || ''; | 
					
						
							| 
									
										
										
										
											2016-01-17 23:19:26 +08:00
										 |  |  |                 if (usingIds) scope.value = scope.currentId; | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 function setImage(imageModel, imageUrl) { | 
					
						
							|  |  |  |                     scope.image = imageUrl; | 
					
						
							|  |  |  |                     scope.value = usingIds ? imageModel.id : imageUrl; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                 scope.reset = function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                     setImage({id: 0}, scope.defaultImage); | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                 scope.remove = function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                     scope.image = 'none'; | 
					
						
							|  |  |  |                     scope.value = 'none'; | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                 scope.showImageManager = function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                     imageManagerService.show((image) => { | 
					
						
							|  |  |  |                         scope.updateImageFromModel(image); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                 scope.updateImageFromModel = function (model) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                     var isResized = scope.resizeWidth && scope.resizeHeight; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (!isResized) { | 
					
						
							|  |  |  |                         scope.$apply(() => { | 
					
						
							|  |  |  |                             setImage(model, model.url); | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                         return; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     var cropped = scope.resizeCrop ? 'true' : 'false'; | 
					
						
							|  |  |  |                     var requestString = '/images/thumb/' + model.id + '/' + scope.resizeWidth + '/' + scope.resizeHeight + '/' + cropped; | 
					
						
							|  |  |  |                     $http.get(requestString).then((response) => { | 
					
						
							|  |  |  |                         setImage(model, response.data.url); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * DropZone | 
					
						
							|  |  |  |      * Used for uploading images | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |     ngApp.directive('dropZone', [function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'E', | 
					
						
							|  |  |  |             template: dropZoneTemplate, | 
					
						
							|  |  |  |             scope: { | 
					
						
							|  |  |  |                 uploadUrl: '@', | 
					
						
							|  |  |  |                 eventSuccess: '=', | 
					
						
							| 
									
										
										
										
											2016-03-13 21:30:47 +08:00
										 |  |  |                 eventError: '=', | 
					
						
							|  |  |  |                 uploadedTo: '@' | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |             }, | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |             link: function (scope, element, attrs) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                 var dropZone = new DropZone(element[0].querySelector('.dropzone-container'), { | 
					
						
							|  |  |  |                     url: scope.uploadUrl, | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                     init: function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                         var dz = this; | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                         dz.on('sending', function (file, xhr, data) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                             var token = window.document.querySelector('meta[name=token]').getAttribute('content'); | 
					
						
							|  |  |  |                             data.append('_token', token); | 
					
						
							| 
									
										
										
										
											2016-03-13 21:30:47 +08:00
										 |  |  |                             var uploadedTo = typeof scope.uploadedTo === 'undefined' ? 0 : scope.uploadedTo; | 
					
						
							|  |  |  |                             data.append('uploaded_to', uploadedTo); | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                         }); | 
					
						
							|  |  |  |                         if (typeof scope.eventSuccess !== 'undefined') dz.on('success', scope.eventSuccess); | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                         dz.on('success', function (file, data) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                             $(file.previewElement).fadeOut(400, function () { | 
					
						
							|  |  |  |                                 dz.removeFile(file); | 
					
						
							|  |  |  |                             }); | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                         if (typeof scope.eventError !== 'undefined') dz.on('error', scope.eventError); | 
					
						
							|  |  |  |                         dz.on('error', function (file, errorMessage, xhr) { | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                             console.log(errorMessage); | 
					
						
							|  |  |  |                             console.log(xhr); | 
					
						
							|  |  |  |                             function setMessage(message) { | 
					
						
							|  |  |  |                                 $(file.previewElement).find('[data-dz-errormessage]').text(message); | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                             } | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                             if (xhr.status === 413) setMessage('The server does not allow uploads of this size. Please try a smaller file.'); | 
					
						
							|  |  |  |                             if (errorMessage.file) setMessage(errorMessage.file[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                         }); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |     ngApp.directive('dropdown', [function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'A', | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |             link: function (scope, element, attrs) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                 var menu = element.find('ul'); | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                 element.find('[dropdown-toggle]').on('click', function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                     menu.show().addClass('anim menuIn'); | 
					
						
							| 
									
										
										
										
											2015-12-31 03:57:17 +08:00
										 |  |  |                     element.mouseleave(function () { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  |                         menu.hide(); | 
					
						
							|  |  |  |                         menu.removeClass('anim menuIn'); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     }]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |     ngApp.directive('tinymce', ['$timeout', function ($timeout) { | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'A', | 
					
						
							|  |  |  |             scope: { | 
					
						
							|  |  |  |                 tinymce: '=', | 
					
						
							| 
									
										
										
										
											2016-03-10 06:54:18 +08:00
										 |  |  |                 mceModel: '=', | 
					
						
							|  |  |  |                 mceChange: '=' | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  |             }, | 
					
						
							|  |  |  |             link: function (scope, element, attrs) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 function tinyMceSetup(editor) { | 
					
						
							| 
									
										
										
										
											2016-03-12 23:52:19 +08:00
										 |  |  |                     editor.on('ExecCommand change NodeChange ObjectResized', (e) => { | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  |                         var content = editor.getContent(); | 
					
						
							| 
									
										
										
										
											2016-03-12 23:52:19 +08:00
										 |  |  |                         $timeout(() => { | 
					
						
							| 
									
										
										
										
											2016-03-10 06:54:18 +08:00
										 |  |  |                             scope.mceModel = content; | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  |                         }); | 
					
						
							| 
									
										
										
										
											2016-03-10 06:54:18 +08:00
										 |  |  |                         scope.mceChange(content); | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  |                     }); | 
					
						
							| 
									
										
										
										
											2016-03-12 23:52:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:51:07 +08:00
										 |  |  |                     editor.on('keydown', (event) => { | 
					
						
							|  |  |  |                         scope.$emit('editor-keydown', event); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-12 23:52:19 +08:00
										 |  |  |                     editor.on('init', (e) => { | 
					
						
							|  |  |  |                         scope.mceModel = editor.getContent(); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     scope.$on('html-update', (event, value) => { | 
					
						
							|  |  |  |                         editor.setContent(value); | 
					
						
							|  |  |  |                         editor.selection.select(editor.getBody(), true); | 
					
						
							|  |  |  |                         editor.selection.collapse(false); | 
					
						
							|  |  |  |                         scope.mceModel = editor.getContent(); | 
					
						
							|  |  |  |                     }); | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 scope.tinymce.extraSetups.push(tinyMceSetup); | 
					
						
							| 
									
										
										
										
											2016-04-24 02:15:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 // Custom tinyMCE plugins
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                 tinymce.PluginManager.add('customhr', function (editor) { | 
					
						
							|  |  |  |                     editor.addCommand('InsertHorizontalRule', function () { | 
					
						
							| 
									
										
										
										
											2016-04-24 02:15:49 +08:00
										 |  |  |                         var hrElem = document.createElement('hr'); | 
					
						
							|  |  |  |                         var cNode = editor.selection.getNode(); | 
					
						
							|  |  |  |                         var parentNode = cNode.parentNode; | 
					
						
							|  |  |  |                         parentNode.insertBefore(hrElem, cNode); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     editor.addButton('hr', { | 
					
						
							|  |  |  |                         icon: 'hr', | 
					
						
							|  |  |  |                         tooltip: 'Horizontal line', | 
					
						
							|  |  |  |                         cmd: 'InsertHorizontalRule' | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     editor.addMenuItem('hr', { | 
					
						
							|  |  |  |                         icon: 'hr', | 
					
						
							|  |  |  |                         text: 'Horizontal line', | 
					
						
							|  |  |  |                         cmd: 'InsertHorizontalRule', | 
					
						
							|  |  |  |                         context: 'insert' | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  |                 tinymce.init(scope.tinymce); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |     }]); | 
					
						
							| 
									
										
										
										
											2016-03-10 06:32:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |     ngApp.directive('markdownInput', ['$timeout', function ($timeout) { | 
					
						
							| 
									
										
										
										
											2016-03-25 22:41:15 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'A', | 
					
						
							|  |  |  |             scope: { | 
					
						
							|  |  |  |                 mdModel: '=', | 
					
						
							|  |  |  |                 mdChange: '=' | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             link: function (scope, element, attrs) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Set initial model content
 | 
					
						
							|  |  |  |                 var content = element.val(); | 
					
						
							|  |  |  |                 scope.mdModel = content; | 
					
						
							|  |  |  |                 scope.mdChange(markdown(content)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 element.on('change input', (e) => { | 
					
						
							|  |  |  |                     content = element.val(); | 
					
						
							|  |  |  |                     $timeout(() => { | 
					
						
							|  |  |  |                         scope.mdModel = content; | 
					
						
							|  |  |  |                         scope.mdChange(markdown(content)); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 scope.$on('markdown-update', (event, value) => { | 
					
						
							|  |  |  |                     element.val(value); | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                     scope.mdModel = value; | 
					
						
							| 
									
										
										
										
											2016-03-25 22:41:15 +08:00
										 |  |  |                     scope.mdChange(markdown(value)); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |     ngApp.directive('markdownEditor', ['$timeout', function ($timeout) { | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'A', | 
					
						
							|  |  |  |             link: function (scope, element, attrs) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Elements
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:24:07 +08:00
										 |  |  |                 const input = element.find('textarea[markdown-input]'); | 
					
						
							|  |  |  |                 const display = element.find('.markdown-display').first(); | 
					
						
							|  |  |  |                 const insertImage = element.find('button[data-action="insertImage"]'); | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:24:07 +08:00
										 |  |  |                 let currentCaretPos = 0; | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:24:07 +08:00
										 |  |  |                 input.blur(event => { | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |                     currentCaretPos = input[0].selectionStart; | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:24:07 +08:00
										 |  |  |                 // Scroll sync
 | 
					
						
							|  |  |  |                 let inputScrollHeight, | 
					
						
							|  |  |  |                     inputHeight, | 
					
						
							|  |  |  |                     displayScrollHeight, | 
					
						
							|  |  |  |                     displayHeight; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 function setScrollHeights() { | 
					
						
							|  |  |  |                     inputScrollHeight = input[0].scrollHeight; | 
					
						
							|  |  |  |                     inputHeight = input.height(); | 
					
						
							|  |  |  |                     displayScrollHeight = display[0].scrollHeight; | 
					
						
							|  |  |  |                     displayHeight = display.height(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 setTimeout(() => { | 
					
						
							|  |  |  |                     setScrollHeights(); | 
					
						
							|  |  |  |                 }, 200); | 
					
						
							|  |  |  |                 window.addEventListener('resize', setScrollHeights); | 
					
						
							|  |  |  |                 let scrollDebounceTime = 800; | 
					
						
							|  |  |  |                 let lastScroll = 0; | 
					
						
							|  |  |  |                 input.on('scroll', event => { | 
					
						
							|  |  |  |                     let now = Date.now(); | 
					
						
							|  |  |  |                     if (now - lastScroll > scrollDebounceTime) { | 
					
						
							|  |  |  |                         setScrollHeights() | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                     let scrollPercent = (input.scrollTop() / (inputScrollHeight - inputHeight)); | 
					
						
							| 
									
										
										
										
											2016-05-28 20:24:07 +08:00
										 |  |  |                     let displayScrollY = (displayScrollHeight - displayHeight) * scrollPercent; | 
					
						
							|  |  |  |                     display.scrollTop(displayScrollY); | 
					
						
							|  |  |  |                     lastScroll = now; | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:51:07 +08:00
										 |  |  |                 // Editor key-presses
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:24:07 +08:00
										 |  |  |                 input.keydown(event => { | 
					
						
							| 
									
										
										
										
											2016-05-28 20:51:07 +08:00
										 |  |  |                     // Insert image shortcut
 | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |                     if (event.which === 73 && event.ctrlKey && event.shiftKey) { | 
					
						
							|  |  |  |                         event.preventDefault(); | 
					
						
							|  |  |  |                         var caretPos = input[0].selectionStart; | 
					
						
							|  |  |  |                         var currentContent = input.val(); | 
					
						
							|  |  |  |                         var mdImageText = ""; | 
					
						
							|  |  |  |                         input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos)); | 
					
						
							|  |  |  |                         input.focus(); | 
					
						
							|  |  |  |                         input[0].selectionStart = caretPos + ("; | 
					
						
							|  |  |  |                         input[0].selectionEnd = caretPos + ('; | 
					
						
							| 
									
										
										
										
											2016-05-28 20:51:07 +08:00
										 |  |  |                         return; | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2016-05-28 20:51:07 +08:00
										 |  |  |                     // Pass key presses to controller via event
 | 
					
						
							|  |  |  |                     scope.$emit('editor-keydown', event); | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Insert image from image manager
 | 
					
						
							| 
									
										
										
										
											2016-05-28 20:24:07 +08:00
										 |  |  |                 insertImage.click(event => { | 
					
						
							|  |  |  |                     window.ImageManager.showExternal(image => { | 
					
						
							| 
									
										
										
										
											2016-03-30 01:25:54 +08:00
										 |  |  |                         var caretPos = currentCaretPos; | 
					
						
							|  |  |  |                         var currentContent = input.val(); | 
					
						
							|  |  |  |                         var mdImageText = ""; | 
					
						
							|  |  |  |                         input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos)); | 
					
						
							|  |  |  |                         input.change(); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-25 22:41:15 +08:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2016-05-15 03:02:00 +08:00
										 |  |  |     }]); | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     ngApp.directive('toolbox', [function () { | 
					
						
							| 
									
										
										
										
											2016-05-15 03:02:00 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'A', | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |             link: function (scope, elem, attrs) { | 
					
						
							| 
									
										
										
										
											2016-05-15 03:02:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 // Get common elements
 | 
					
						
							|  |  |  |                 const $buttons = elem.find('[tab-button]'); | 
					
						
							|  |  |  |                 const $content = elem.find('[tab-content]'); | 
					
						
							|  |  |  |                 const $toggle = elem.find('[toolbox-toggle]'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Handle toolbox toggle click
 | 
					
						
							|  |  |  |                 $toggle.click((e) => { | 
					
						
							|  |  |  |                     elem.toggleClass('open'); | 
					
						
							|  |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-15 03:02:00 +08:00
										 |  |  |                 // Set an active tab/content by name
 | 
					
						
							|  |  |  |                 function setActive(tabName, openToolbox) { | 
					
						
							|  |  |  |                     $buttons.removeClass('active'); | 
					
						
							|  |  |  |                     $content.hide(); | 
					
						
							|  |  |  |                     $buttons.filter(`[tab-button="${tabName}"]`).addClass('active'); | 
					
						
							|  |  |  |                     $content.filter(`[tab-content="${tabName}"]`).show(); | 
					
						
							|  |  |  |                     if (openToolbox) elem.addClass('open'); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Set the first tab content active on load
 | 
					
						
							|  |  |  |                 setActive($content.first().attr('tab-content'), false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Handle tab button click
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                 $buttons.click(function (e) { | 
					
						
							| 
									
										
										
										
											2016-05-15 03:02:00 +08:00
										 |  |  |                     let name = $(this).attr('tab-button'); | 
					
						
							|  |  |  |                     setActive(name, true); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }]); | 
					
						
							| 
									
										
										
										
											2015-12-31 02:38:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |     ngApp.directive('tagAutosuggestions', ['$http', function ($http) { | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |         return { | 
					
						
							|  |  |  |             restrict: 'A', | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |             link: function (scope, elem, attrs) { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                 // Local storage for quick caching.
 | 
					
						
							|  |  |  |                 const localCache = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Create suggestion element
 | 
					
						
							|  |  |  |                 const suggestionBox = document.createElement('ul'); | 
					
						
							|  |  |  |                 suggestionBox.className = 'suggestion-box'; | 
					
						
							|  |  |  |                 suggestionBox.style.position = 'absolute'; | 
					
						
							|  |  |  |                 suggestionBox.style.display = 'none'; | 
					
						
							|  |  |  |                 const $suggestionBox = $(suggestionBox); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // General state tracking
 | 
					
						
							|  |  |  |                 let isShowing = false; | 
					
						
							|  |  |  |                 let currentInput = false; | 
					
						
							|  |  |  |                 let active = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Listen to input events on autosuggest fields
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                 elem.on('input focus', '[autosuggest]', function (event) { | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                     let $input = $(this); | 
					
						
							|  |  |  |                     let val = $input.val(); | 
					
						
							|  |  |  |                     let url = $input.attr('autosuggest'); | 
					
						
							| 
									
										
										
										
											2016-06-04 21:54:31 +08:00
										 |  |  |                     let type = $input.attr('autosuggest-type'); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Add name param to request if for a value
 | 
					
						
							|  |  |  |                     if (type.toLowerCase() === 'value') { | 
					
						
							|  |  |  |                         let $nameInput = $input.closest('tr').find('[autosuggest-type="name"]').first(); | 
					
						
							|  |  |  |                         let nameVal = $nameInput.val(); | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                         if (nameVal !== '') { | 
					
						
							|  |  |  |                             url += '?name=' + encodeURIComponent(nameVal); | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2016-06-04 21:54:31 +08:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     let suggestionPromise = getSuggestions(val.slice(0, 3), url); | 
					
						
							| 
									
										
										
										
											2016-06-04 21:54:31 +08:00
										 |  |  |                     suggestionPromise.then(suggestions => { | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                         if (val.length === 0) { | 
					
						
							|  |  |  |                             displaySuggestions($input, suggestions.slice(0, 6)); | 
					
						
							|  |  |  |                         } else  { | 
					
						
							|  |  |  |                             suggestions = suggestions.filter(item => { | 
					
						
							|  |  |  |                                 return item.toLowerCase().indexOf(val.toLowerCase()) !== -1; | 
					
						
							|  |  |  |                             }).slice(0, 4); | 
					
						
							|  |  |  |                             displaySuggestions($input, suggestions); | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                     }); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Hide autosuggestions when input loses focus.
 | 
					
						
							|  |  |  |                 // Slight delay to allow clicks.
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                 let lastFocusTime = 0; | 
					
						
							|  |  |  |                 elem.on('blur', '[autosuggest]', function (event) { | 
					
						
							|  |  |  |                     let startTime = Date.now(); | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                     setTimeout(() => { | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                         if (lastFocusTime < startTime) { | 
					
						
							|  |  |  |                             $suggestionBox.hide(); | 
					
						
							|  |  |  |                             isShowing = false; | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                     }, 200) | 
					
						
							|  |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                 elem.on('focus', '[autosuggest]', function (event) { | 
					
						
							|  |  |  |                     lastFocusTime = Date.now(); | 
					
						
							|  |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 elem.on('keydown', '[autosuggest]', function (event) { | 
					
						
							|  |  |  |                     if (!isShowing) return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     let suggestionElems = suggestionBox.childNodes; | 
					
						
							|  |  |  |                     let suggestCount = suggestionElems.length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Down arrow
 | 
					
						
							|  |  |  |                     if (event.keyCode === 40) { | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                         let newActive = (active === suggestCount - 1) ? 0 : active + 1; | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                         changeActiveTo(newActive, suggestionElems); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     // Up arrow
 | 
					
						
							|  |  |  |                     else if (event.keyCode === 38) { | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                         let newActive = (active === 0) ? suggestCount - 1 : active - 1; | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                         changeActiveTo(newActive, suggestionElems); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2016-06-04 21:54:31 +08:00
										 |  |  |                     // Enter or tab key
 | 
					
						
							|  |  |  |                     else if (event.keyCode === 13 || event.keyCode === 9) { | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                         let text = suggestionElems[active].textContent; | 
					
						
							|  |  |  |                         currentInput[0].value = text; | 
					
						
							|  |  |  |                         currentInput.focus(); | 
					
						
							|  |  |  |                         $suggestionBox.hide(); | 
					
						
							|  |  |  |                         isShowing = false; | 
					
						
							| 
									
										
										
										
											2016-06-04 21:54:31 +08:00
										 |  |  |                         if (event.keyCode === 13) { | 
					
						
							|  |  |  |                             event.preventDefault(); | 
					
						
							|  |  |  |                             return false; | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                     } | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Change the active suggestion to the given index
 | 
					
						
							|  |  |  |                 function changeActiveTo(index, suggestionElems) { | 
					
						
							|  |  |  |                     suggestionElems[active].className = ''; | 
					
						
							|  |  |  |                     active = index; | 
					
						
							|  |  |  |                     suggestionElems[active].className = 'active'; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Display suggestions on a field
 | 
					
						
							|  |  |  |                 let prevSuggestions = []; | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                 function displaySuggestions($input, suggestions) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Hide if no suggestions
 | 
					
						
							|  |  |  |                     if (suggestions.length === 0) { | 
					
						
							|  |  |  |                         $suggestionBox.hide(); | 
					
						
							|  |  |  |                         isShowing = false; | 
					
						
							|  |  |  |                         prevSuggestions = suggestions; | 
					
						
							|  |  |  |                         return; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Otherwise show and attach to input
 | 
					
						
							|  |  |  |                     if (!isShowing) { | 
					
						
							|  |  |  |                         $suggestionBox.show(); | 
					
						
							|  |  |  |                         isShowing = true; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     if ($input !== currentInput) { | 
					
						
							|  |  |  |                         $suggestionBox.detach(); | 
					
						
							|  |  |  |                         $input.after($suggestionBox); | 
					
						
							|  |  |  |                         currentInput = $input; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Return if no change
 | 
					
						
							|  |  |  |                     if (prevSuggestions.join() === suggestions.join()) { | 
					
						
							|  |  |  |                         prevSuggestions = suggestions; | 
					
						
							|  |  |  |                         return; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // Build suggestions
 | 
					
						
							|  |  |  |                     $suggestionBox[0].innerHTML = ''; | 
					
						
							|  |  |  |                     for (let i = 0; i < suggestions.length; i++) { | 
					
						
							|  |  |  |                         var suggestion = document.createElement('li'); | 
					
						
							|  |  |  |                         suggestion.textContent = suggestions[i]; | 
					
						
							|  |  |  |                         suggestion.onclick = suggestionClick; | 
					
						
							|  |  |  |                         if (i === 0) { | 
					
						
							|  |  |  |                             suggestion.className = 'active' | 
					
						
							|  |  |  |                             active = 0; | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                         } | 
					
						
							|  |  |  |                         ; | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                         $suggestionBox[0].appendChild(suggestion); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     prevSuggestions = suggestions; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Suggestion click event
 | 
					
						
							|  |  |  |                 function suggestionClick(event) { | 
					
						
							|  |  |  |                     let text = this.textContent; | 
					
						
							|  |  |  |                     currentInput[0].value = text; | 
					
						
							|  |  |  |                     currentInput.focus(); | 
					
						
							|  |  |  |                     $suggestionBox.hide(); | 
					
						
							|  |  |  |                     isShowing = false; | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Get suggestions & cache
 | 
					
						
							|  |  |  |                 function getSuggestions(input, url) { | 
					
						
							| 
									
										
										
										
											2016-06-04 21:54:31 +08:00
										 |  |  |                     let hasQuery = url.indexOf('?') !== -1; | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                     let searchUrl = url + (hasQuery ? '&' : '?') + 'search=' + encodeURIComponent(input); | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     // Get from local cache if exists
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                     if (typeof localCache[searchUrl] !== 'undefined') { | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                         return new Promise((resolve, reject) => { | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                             resolve(localCache[searchUrl]); | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                         }); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 22:37:28 +08:00
										 |  |  |                     return $http.get(searchUrl).then(response => { | 
					
						
							|  |  |  |                         localCache[searchUrl] = response.data; | 
					
						
							| 
									
										
										
										
											2016-05-16 03:12:53 +08:00
										 |  |  |                         return response.data; | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }]); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 |