From 61fad6a665013f079c9ff2a88c2dd32d5b87bdda Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Sat, 30 Sep 2017 13:26:48 +0100
Subject: [PATCH] Finished migration of last angular code

---
 package.json                            |   5 -
 readme.md                               |   1 -
 resources/assets/js/controllers.js      | 144 -----------------------
 resources/assets/js/global.js           |  13 ---
 resources/assets/js/vues/page-editor.js | 149 ++++++++++++++++++++++++
 resources/assets/js/vues/vues.js        |   1 +
 resources/views/pages/form.blade.php    |  26 ++---
 7 files changed, 163 insertions(+), 176 deletions(-)
 delete mode 100644 resources/assets/js/controllers.js
 create mode 100644 resources/assets/js/vues/page-editor.js

diff --git a/package.json b/package.json
index ed8338abb..23b01cf6e 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,6 @@
     "yargs": "^7.1.0"
   },
   "dependencies": {
-    "angular": "^1.5.5",
-    "angular-animate": "^1.5.5",
-    "angular-resource": "^1.5.5",
-    "angular-sanitize": "^1.5.5",
-    "angular-ui-sortable": "^0.17.0",
     "axios": "^0.16.1",
     "babel-polyfill": "^6.23.0",
     "babel-preset-es2015": "^6.24.1",
diff --git a/readme.md b/readme.md
index 1d4ed682e..e048e8ea6 100644
--- a/readme.md
+++ b/readme.md
@@ -76,7 +76,6 @@ The BookStack source is provided under the MIT License.
 These are the great open-source projects used to help build BookStack:
 
 * [Laravel](http://laravel.com/)
-* [AngularJS](https://angularjs.org/)
 * [jQuery](https://jquery.com/)
 * [TinyMCE](https://www.tinymce.com/)
 * [CodeMirror](https://codemirror.net)
diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js
deleted file mode 100644
index 6a7fa9890..000000000
--- a/resources/assets/js/controllers.js
+++ /dev/null
@@ -1,144 +0,0 @@
-"use strict";
-
-const moment = require('moment');
-require('moment/locale/en-gb');
-
-moment.locale('en-gb');
-
-module.exports = function (ngApp, events) {
-
-
-    ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce',
-        function ($scope, $http, $attrs, $interval, $timeout, $sce) {
-
-        $scope.editorHTML = '';
-        $scope.editorMarkdown = '';
-
-        $scope.draftText = '';
-        let pageId = Number($attrs.pageId);
-        let isEdit = pageId !== 0;
-        let autosaveFrequency = 30; // AutoSave interval in seconds.
-        let isMarkdown = $attrs.editorType === 'markdown';
-        $scope.draftsEnabled = $attrs.draftsEnabled === 'true';
-        $scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1;
-        $scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1;
-
-        // Set initial header draft text
-        if ($scope.isUpdateDraft || $scope.isNewPageDraft) {
-            $scope.draftText = trans('entities.pages_editing_draft');
-        } else {
-            $scope.draftText = trans('entities.pages_editing_page');
-        }
-
-        let autoSave = false;
-
-        let currentContent = {
-            title: false,
-            html: false
-        };
-
-        if (isEdit && $scope.draftsEnabled) {
-            setTimeout(() => {
-                startAutoSave();
-            }, 1000);
-        }
-
-        let lastSave = 0;
-
-        /**
-         * Start the AutoSave loop, Checks for content change
-         * before performing the costly AJAX request.
-         */
-        function startAutoSave() {
-            currentContent.title = $('#name').val();
-            currentContent.html = $scope.editorHTML;
-
-            autoSave = $interval(() => {
-                // Return if manually saved recently to prevent bombarding the server
-                if (Date.now() - lastSave < (1000*autosaveFrequency)/2) return;
-                let newTitle = $('#name').val();
-                let newHtml = $scope.editorHTML;
-
-                if (newTitle !== currentContent.title || newHtml !== currentContent.html) {
-                    currentContent.html = newHtml;
-                    currentContent.title = newTitle;
-                    saveDraft();
-                }
-
-            }, 1000 * autosaveFrequency);
-        }
-
-        let draftErroring = false;
-        /**
-         * Save a draft update into the system via an AJAX request.
-         */
-        function saveDraft() {
-            if (!$scope.draftsEnabled) return;
-
-            let data = {
-                name: $('#name').val(),
-                html: $scope.editorHTML
-            };
-            if (isMarkdown) data.markdown = $scope.editorMarkdown;
-
-            let url = window.baseUrl('/ajax/page/' + pageId + '/save-draft');
-            $http.put(url, data).then(responseData => {
-                draftErroring = false;
-                let updateTime = moment.utc(moment.unix(responseData.data.timestamp)).toDate();
-                $scope.draftText = responseData.data.message + moment(updateTime).format('HH:mm');
-                if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true;
-                showDraftSaveNotification();
-                lastSave = Date.now();
-            }, errorRes => {
-                if (draftErroring) return;
-                events.emit('error', trans('errors.page_draft_autosave_fail'));
-                draftErroring = true;
-            });
-        }
-
-        function showDraftSaveNotification() {
-            $scope.draftUpdated = true;
-            $timeout(() => {
-                $scope.draftUpdated = false;
-            }, 2000)
-        }
-
-        $scope.forceDraftSave = function() {
-            saveDraft();
-        };
-
-        // Listen to save draft events from editor
-        window.$events.listen('editor-save-draft', saveDraft);
-
-        // Listen to content changes from the editor
-        window.$events.listen('editor-html-change', html => {
-            $scope.editorHTML = html;
-        });
-        window.$events.listen('editor-markdown-change', markdown => {
-            $scope.editorMarkdown = markdown;
-        });
-
-        /**
-         * Discard the current draft and grab the current page
-         * content from the system via an AJAX request.
-         */
-        $scope.discardDraft = function () {
-            let url = window.baseUrl('/ajax/page/' + pageId);
-            $http.get(url).then(responseData => {
-                if (autoSave) $interval.cancel(autoSave);
-
-                $scope.draftText = trans('entities.pages_editing_page');
-                $scope.isUpdateDraft = false;
-                window.$events.emit('editor-html-update', responseData.data.html);
-                window.$events.emit('editor-markdown-update', responseData.data.markdown || responseData.data.html);
-
-                $('#name').val(responseData.data.name);
-                $timeout(() => {
-                    startAutoSave();
-                }, 1000);
-                events.emit('success', trans('entities.pages_draft_discarded'));
-            });
-        };
-
-    }]);
-};
diff --git a/resources/assets/js/global.js b/resources/assets/js/global.js
index faf5a499d..352616c5a 100644
--- a/resources/assets/js/global.js
+++ b/resources/assets/js/global.js
@@ -58,16 +58,6 @@ window.$http = axiosInstance;
 Vue.prototype.$http = axiosInstance;
 Vue.prototype.$events = window.$events;
 
-
-// AngularJS - Create application and load components
-const angular = require("angular");
-require("angular-resource");
-require("angular-animate");
-require("angular-sanitize");
-require("angular-ui-sortable");
-
-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
 const Translations = require("./translations");
@@ -79,9 +69,6 @@ window.trans_choice = translator.getPlural.bind(translator);
 require("./vues/vues");
 require("./components");
 
-// Load in angular specific items
-const Controllers = require('./controllers');
-Controllers(ngApp, window.$events);
 
 //Global jQuery Config & Extensions
 
diff --git a/resources/assets/js/vues/page-editor.js b/resources/assets/js/vues/page-editor.js
new file mode 100644
index 000000000..11413131f
--- /dev/null
+++ b/resources/assets/js/vues/page-editor.js
@@ -0,0 +1,149 @@
+const moment = require('moment');
+require('moment/locale/en-gb');
+moment.locale('en-gb');
+
+let autoSaveFrequency = 30;
+
+let autoSave = false;
+let draftErroring = false;
+
+let currentContent = {
+    title: false,
+    html: false
+};
+
+let lastSave = 0;
+
+function mounted() {
+    let elem = this.$el;
+    this.draftsEnabled = elem.getAttribute('drafts-enabled') === 'true';
+    this.editorType = elem.getAttribute('editor-type');
+    this.pageId= Number(elem.getAttribute('page-id'));
+    this.isNewDraft = Number(elem.getAttribute('page-new-draft')) === 1;
+    this.isUpdateDraft = Number(elem.getAttribute('page-update-draft')) === 1;
+
+    if (this.pageId !== 0 && this.draftsEnabled) {
+        window.setTimeout(() => {
+            this.startAutoSave();
+        }, 1000);
+    }
+
+    if (this.isUpdateDraft || this.isNewDraft) {
+        this.draftText = trans('entities.pages_editing_draft');
+    } else {
+        this.draftText = trans('entities.pages_editing_page');
+    }
+
+    // Listen to save draft events from editor
+    window.$events.listen('editor-save-draft', this.saveDraft);
+
+    // Listen to content changes from the editor
+    window.$events.listen('editor-html-change', html => {
+        this.editorHTML = html;
+    });
+    window.$events.listen('editor-markdown-change', markdown => {
+        this.editorMarkdown = markdown;
+    });
+}
+
+let data = {
+    draftsEnabled: false,
+    editorType: 'wysiwyg',
+    pagedId: 0,
+    isNewDraft: false,
+    isUpdateDraft: false,
+
+    draftText: '',
+    draftUpdated : false,
+    changeSummary: '',
+
+    editorHTML: '',
+    editorMarkdown: '',
+};
+
+let methods = {
+
+    startAutoSave() {
+        currentContent.title = document.getElementById('name').value.trim();
+        currentContent.html = this.editorHTML;
+
+        autoSave = window.setInterval(() => {
+            // Return if manually saved recently to prevent bombarding the server
+            if (Date.now() - lastSave < (1000 * autoSaveFrequency)/2) return;
+            let newTitle = document.getElementById('name').value.trim();
+            let newHtml = this.editorHTML;
+
+            if (newTitle !== currentContent.title || newHtml !== currentContent.html) {
+                currentContent.html = newHtml;
+                currentContent.title = newTitle;
+                this.saveDraft();
+            }
+
+        }, 1000 * autoSaveFrequency);
+    },
+
+    saveDraft() {
+        if (!this.draftsEnabled) return;
+
+        let data = {
+            name: document.getElementById('name').value.trim(),
+            html: this.editorHTML
+        };
+
+        if (this.editorType === 'markdown') data.markdown = this.editorMarkdown;
+
+        let url = window.baseUrl(`/ajax/page/${this.pageId}/save-draft`);
+        window.$http.put(url, data).then(response => {
+            draftErroring = false;
+            let updateTime = moment.utc(moment.unix(response.data.timestamp)).toDate();
+            if (!this.isNewPageDraft) this.isUpdateDraft = true;
+            this.draftNotifyChange(response.data.message + moment(updateTime).format('HH:mm'));
+            lastSave = Date.now();
+        }, errorRes => {
+            if (draftErroring) return;
+            window.$events('error', trans('errors.page_draft_autosave_fail'));
+            draftErroring = true;
+        });
+    },
+
+
+    draftNotifyChange(text) {
+        this.draftText = text;
+        this.draftUpdated = true;
+        window.setTimeout(() => {
+            this.draftUpdated = false;
+        }, 2000);
+    },
+
+    discardDraft() {
+        let url = window.baseUrl(`/ajax/page/${this.pageId}`);
+        window.$http.get(url).then(response => {
+            if (autoSave) window.clearInterval(autoSave);
+
+            this.draftText = trans('entities.pages_editing_page');
+            this.isUpdateDraft = false;
+            window.$events.emit('editor-html-update', response.data.html);
+            window.$events.emit('editor-markdown-update', response.data.markdown || response.data.html);
+
+            document.getElementById('name').value = response.data.name;
+            window.setTimeout(() => {
+                this.startAutoSave();
+            }, 1000);
+            window.$events.emit('success', trans('entities.pages_draft_discarded'));
+        });
+    },
+
+};
+
+let computed = {
+    changeSummaryShort() {
+        let len = this.changeSummary.length;
+        if (len === 0) return trans('entities.pages_edit_set_changelog');
+        if (len <= 16) return this.changeSummary;
+        return this.changeSummary.slice(0, 16) + '...';
+    }
+};
+
+module.exports = {
+    mounted, data, methods, computed,
+};
\ No newline at end of file
diff --git a/resources/assets/js/vues/vues.js b/resources/assets/js/vues/vues.js
index a70d32009..be6092287 100644
--- a/resources/assets/js/vues/vues.js
+++ b/resources/assets/js/vues/vues.js
@@ -11,6 +11,7 @@ let vueMapping = {
     'image-manager': require('./image-manager'),
     'tag-manager': require('./tag-manager'),
     'attachment-manager': require('./attachment-manager'),
+    'page-editor': require('./page-editor'),
 };
 
 window.vues = {};
diff --git a/resources/views/pages/form.blade.php b/resources/views/pages/form.blade.php
index 6769240d5..c4a639898 100644
--- a/resources/views/pages/form.blade.php
+++ b/resources/views/pages/form.blade.php
@@ -1,5 +1,5 @@
 
-<div class="page-editor flex-fill flex" ng-controller="PageEditController" drafts-enabled="{{ $draftsEnabled ? 'true' : 'false' }}" editor-type="{{ setting('app-editor') }}" page-id="{{ $model->id or 0 }}" page-new-draft="{{ $model->draft or 0 }}" page-update-draft="{{ $model->isDraft or 0 }}">
+<div class="page-editor flex-fill flex" id="page-editor" drafts-enabled="{{ $draftsEnabled ? 'true' : 'false' }}" editor-type="{{ setting('app-editor') }}" page-id="{{ $model->id or 0 }}" page-new-draft="{{ $model->draft or 0 }}" page-update-draft="{{ $model->isDraft or 0 }}">
 
     {{ csrf_field() }}
 
@@ -15,30 +15,30 @@
                 </div>
                 <div class="col-sm-4 faded text-center">
 
-                    <div ng-show="draftsEnabled" dropdown class="dropdown-container draft-display">
-                        <a dropdown-toggle class="text-primary text-button"><span class="faded-text" ng-bind="draftText"></span>&nbsp; <i class="zmdi zmdi-more-vert"></i></a>
-                        <i class="zmdi zmdi-check-circle text-pos draft-notification" ng-class="{visible: draftUpdated}"></i>
+                    <div v-show="draftsEnabled" dropdown class="dropdown-container draft-display">
+                        <a dropdown-toggle class="text-primary text-button"><span class="faded-text" v-text="draftText"></span>&nbsp; <i class="zmdi zmdi-more-vert"></i></a>
+                        <i class="zmdi zmdi-check-circle text-pos draft-notification" :class="{visible: draftUpdated}"></i>
                         <ul>
                             <li>
-                                <a ng-click="forceDraftSave()" class="text-pos"><i class="zmdi zmdi-save"></i>{{ trans('entities.pages_edit_save_draft') }}</a>
+                                <a @click="saveDraft()" class="text-pos"><i class="zmdi zmdi-save"></i>{{ trans('entities.pages_edit_save_draft') }}</a>
                             </li>
-                            <li ng-if="isNewPageDraft">
+                            <li v-if="isNewDraft">
                                 <a href="{{ $model->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>{{ trans('entities.pages_edit_delete_draft') }}</a>
                             </li>
-                            <li>
-                                <a type="button" ng-if="isUpdateDraft" ng-click="discardDraft()" class="text-neg"><i class="zmdi zmdi-close-circle"></i>{{ trans('entities.pages_edit_discard_draft') }}</a>
+                            <li v-if="isUpdateDraft">
+                                <a type="button" @click="discardDraft" class="text-neg"><i class="zmdi zmdi-close-circle"></i>{{ trans('entities.pages_edit_discard_draft') }}</a>
                             </li>
                         </ul>
                     </div>
                 </div>
                 <div class="col-sm-4 faded">
-                    <div class="action-buttons" ng-cloak>
+                    <div class="action-buttons" v-cloak>
                         <div dropdown class="dropdown-container">
-                            <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-edit"></i> <span ng-bind="(changeSummary | limitTo:16) + (changeSummary.length>16?'...':'') || '{{ trans('entities.pages_edit_set_changelog') }}'"></span></a>
+                            <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-edit"></i> <span v-text="changeSummaryShort"></span></a>
                             <ul class="wide">
                                 <li class="padded">
                                     <p class="text-muted">{{ trans('entities.pages_edit_enter_changelog_desc') }}</p>
-                                    <input name="summary" id="summary-input" type="text" placeholder="{{ trans('entities.pages_edit_enter_changelog') }}" ng-model="changeSummary" />
+                                    <input name="summary" id="summary-input" type="text" placeholder="{{ trans('entities.pages_edit_enter_changelog') }}" v-model="changeSummary" />
                                 </li>
                             </ul>
                         </div>
@@ -86,7 +86,7 @@
                         </div>
                     </div>
 
-                    <div markdown-input md-change="editorChange" md-model="editContent" class="flex flex-fill">
+                    <div markdown-input class="flex flex-fill">
                         <textarea  id="markdown-editor-input"  name="markdown" rows="5"
                             @if($errors->has('markdown')) class="neg" @endif>@if(isset($model) || old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
                     </div>
@@ -98,7 +98,7 @@
                         <div class="">{{ trans('entities.pages_md_preview') }}</div>
                     </div>
                     <div class="markdown-display">
-                        <div class="page-content" ng-bind-html="displayContent"></div>
+                        <div class="page-content"></div>
                     </div>
                 </div>
                 <input type="hidden" name="html"/>