"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",
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)
+++ /dev/null
-"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'));
- });
- };
-
- }]);
-};
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");
require("./vues/vues");
require("./components");
-// Load in angular specific items
-const Controllers = require('./controllers');
-Controllers(ngApp, window.$events);
//Global jQuery Config & Extensions
--- /dev/null
+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
'image-manager': require('./image-manager'),
'tag-manager': require('./tag-manager'),
'attachment-manager': require('./attachment-manager'),
+ 'page-editor': require('./page-editor'),
};
window.vues = {};
-<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() }}
</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> <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> <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>
</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>
<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"/>