protected $simpleAttributes = ['name', 'id', 'slug'];
protected $with = ['book'];
-
- protected $fieldsToSearch = ['name', 'text'];
+ public $textField = 'text';
/**
* Converts this page into a simplified array.
{
return $this->belongsTo(Chapter::class);
}
+
+ /**
+ * Get the comments in the page.
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function comment()
+ {
+ return $this->hasMany(Comment::class);
+ }
/**
* Check if this page has a chapter.
return mb_convert_encoding($text, 'UTF-8');
}
+ /**
+ * Return a generalised, common raw query that can be 'unioned' across entities.
+ * @param bool $withContent
+ * @return string
+ */
+ public function entityRawQuery($withContent = false)
+ { $htmlQuery = $withContent ? 'html' : "'' as html";
+ return "'BookStack\\\\Page' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, {$htmlQuery}, book_id, priority, chapter_id, draft, created_by, updated_by, updated_at, created_at";
+ }
+
}
$action = end($explodedPermission);
$this->currentAction = $action;
- $nonJointPermissions = ['restrictions', 'image', 'attachment'];
+ $nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
// Handle non entity specific jointPermissions
if (in_array($explodedPermission[0], $nonJointPermissions)) {
* @return \Illuminate\Database\Query\Builder
*/
public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false) {
- $pageContentSelect = $fetchPageContent ? 'html' : "''";
- $pageSelect = $this->db->table('pages')->selectRaw("'BookStack\\\\Page' as entity_type, id, slug, name, text, {$pageContentSelect} as description, book_id, priority, chapter_id, draft")->where('book_id', '=', $book_id)->where(function($query) use ($filterDrafts) {
+ $pageSelect = $this->db->table('pages')->selectRaw($this->page->entityRawQuery($fetchPageContent))->where('book_id', '=', $book_id)->where(function($query) use ($filterDrafts) {
$query->where('draft', '=', 0);
if (!$filterDrafts) {
$query->orWhere(function($query) {
});
}
});
- $chapterSelect = $this->db->table('chapters')->selectRaw("'BookStack\\\\Chapter' as entity_type, id, slug, name, '' as text, description, book_id, priority, 0 as chapter_id, 0 as draft")->where('book_id', '=', $book_id);
+ $chapterSelect = $this->db->table('chapters')->selectRaw($this->chapter->entityRawQuery())->where('book_id', '=', $book_id);
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
* @param string $entityType
* @param Builder|Entity $query
* @param string $action
- * @return mixed
+ * @return Builder
*/
public function enforceEntityRestrictions($entityType, $query, $action = 'view')
{
}
/**
- * Filter items that have entities set a a polymorphic relation.
+ * Filter items that have entities set as a polymorphic relation.
* @param $query
* @param string $tableName
* @param string $entityIdColumn
"use strict";
- import moment from 'moment';
- import 'moment/locale/en-gb';
- import editorOptions from "./pages/page-form";
+ const moment = require('moment');
+ require('moment/locale/en-gb');
+ const editorOptions = require("./pages/page-form");
moment.locale('en-gb');
- export default function (ngApp, events) {
+ module.exports = function (ngApp, events) {
ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
function ($scope, $attrs, $http, $timeout, imageManagerService) {
}]);
-
- ngApp.controller('BookShowController', ['$scope', '$http', '$attrs', '$sce', function ($scope, $http, $attrs, $sce) {
- $scope.searching = false;
- $scope.searchTerm = '';
- $scope.searchResults = '';
-
- $scope.searchBook = function (e) {
- e.preventDefault();
- let term = $scope.searchTerm;
- if (term.length == 0) return;
- $scope.searching = true;
- $scope.searchResults = '';
- let searchUrl = window.baseUrl('/search/book/' + $attrs.bookId);
- searchUrl += '?term=' + encodeURIComponent(term);
- $http.get(searchUrl).then((response) => {
- $scope.searchResults = $sce.trustAsHtml(response.data);
- });
- };
-
- $scope.checkSearchForm = function () {
- if ($scope.searchTerm.length < 1) {
- $scope.searching = false;
- }
- };
-
- $scope.clearSearch = function () {
- $scope.searching = false;
- $scope.searchTerm = '';
- };
-
- }]);
-
-
ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce',
function ($scope, $http, $attrs, $interval, $timeout, $sce) {
$scope.draftsEnabled = $attrs.draftsEnabled === 'true';
$scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1;
$scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1;
+ $scope.commentsLoaded = false;
// Set initial header draft text
if ($scope.isUpdateDraft || $scope.isNewPageDraft) {
}]);
+ // CommentCrudController
+ ngApp.controller('CommentAddController', ['$scope', '$http', function ($scope, $http) {
+ let vm = this;
+ let comment = {};
+ $scope.errors = {};
+ vm.saveComment = function () {
+ let pageId = $scope.comment.pageId;
+ let comment = $scope.comment.newComment;
+ let commentHTML = $scope.getCommentHTML();
+
+ $http.post(window.baseUrl(`/ajax/page/${pageId}/comment/`), {
+ text: comment,
+ html: commentHTML
+ }).then(resp => {
+ $scope.clearInput();
+ if (!resp.data || resp.data.status !== 'success') {
+ return events.emit('error', trans('error'));
+ }
+ events.emit('success', trans(resp.data.message));
+ }, checkError('add'));
+
+ };
+
+ function checkError(errorGroupName) {
+ $scope.errors[errorGroupName] = {};
+ return function(response) {
+ if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') {
+ events.emit('error', response.data.error);
+ }
+ if (typeof response.data !== 'undefined' && typeof response.data.validation !== 'undefined') {
+ $scope.errors[errorGroupName] = response.data.validation;
+ console.log($scope.errors[errorGroupName])
+ }
+ }
+ }
+ }]);
+
+
+ // CommentListController
+ ngApp.controller('CommentListController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {
+ let vm = this;
+ $scope.errors = {};
+ $scope.defaultAvatar = defaultAvatar;
+ vm.totalCommentsStr = 'Loading...';
+ $scope.editorChange = function (content) {
+ console.log(content);
+ }
+
+ $timeout(function() {
+ console.log($scope.pageId);
+ $http.get(window.baseUrl(`/ajax/page/${$scope.pageId}/comments/`)).then(resp => {
+ if (!resp.data || resp.data.success !== true) {
+ // TODO : Handle error
+ return;
+ }
+ vm.comments = resp.data.comments.data;
+ vm.totalComments = resp.data.comments.total;
+ if (vm.totalComments === 0) {
+ vm.totalCommentsStr = 'No comments found.';
+ } else if (vm.totalComments === 1) {
+ vm.totalCommentsStr = '1 Comments';
+ } else {
+ vm.totalCommentsStr = vm.totalComments + ' Comments'
+ }
+ }, checkError('app'));
+ });
+
+ function checkError(errorGroupName) {
+ $scope.errors[errorGroupName] = {};
+ return function(response) {
+ console.log(resp);
+ }
+ }
+ }]);
+
};
"use strict";
- import DropZone from "dropzone";
- import markdown from "marked";
+ const DropZone = require("dropzone");
+ const MarkdownIt = require("markdown-it");
+ const mdTasksLists = require('markdown-it-task-lists');
- export default function (ngApp, events) {
+ module.exports = function (ngApp, events) {
/**
* Common tab controls using simple jQuery functions.
}
}]);
- let renderer = new markdown.Renderer();
- // Custom markdown checkbox list item
- // Attribution: https://github.com/chjj/marked/issues/107#issuecomment-44542001
- renderer.listitem = function(text) {
- if (/^\s*\[[x ]\]\s*/.test(text)) {
- text = text
- .replace(/^\s*\[ \]\s*/, '<input type="checkbox"/>')
- .replace(/^\s*\[x\]\s*/, '<input type="checkbox" checked/>');
- return `<li class="checkbox-item">${text}</li>`;
- }
- return `<li>${text}</li>`;
- };
+ const md = new MarkdownIt();
+ md.use(mdTasksLists, {label: true});
/**
* Markdown input
element = element.find('textarea').first();
let content = element.val();
scope.mdModel = content;
- scope.mdChange(markdown(content, {renderer: renderer}));
+ scope.mdChange(md.render(content));
element.on('change input', (event) => {
content = element.val();
$timeout(() => {
scope.mdModel = content;
- scope.mdChange(markdown(content, {renderer: renderer}));
+ scope.mdChange(md.render(content));
});
});
scope.$on('markdown-update', (event, value) => {
element.val(value);
scope.mdModel = value;
- scope.mdChange(markdown(value));
+ scope.mdChange(md.render(value));
});
}
}
};
}]);
+
+
+ ngApp.directive('simpleMarkdownInput', ['$timeout', function ($timeout) {
+ return {
+ restrict: 'A',
+ scope: {
+ smdModel: '=',
+ smdChange: '=',
+ smdGetContent: '=',
+ smdClear: '='
+ },
+ link: function (scope, element, attrs) {
+ // Set initial model content
+ element = element.find('textarea').first();
+ let simplemde = new SimpleMDE({
+ element: element[0],
+ status: []
+ });
+ let content = element.val();
+ simplemde.value(content)
+ scope.smdModel = content;
+
+ simplemde.codemirror.on('change', (event) => {
+ content = simplemde.value();
+ $timeout(() => {
+ scope.smdModel = content;
+ if (scope.smdChange) {
+ scope.smdChange(element, content);
+ }
+ });
+ });
+
+ if ('smdGetContent' in attrs) {
+ scope.smdGetContent = function () {
+ return simplemde.options.previewRender(simplemde.value());
+ };
+ }
+
+ if ('smdClear' in attrs) {
+ scope.smdClear = function () {
+ simplemde.value('');
+ scope.smdModel = '';
+ };
+ }
+ }
+ }
+ }]);
+
};
@import "grid";
@import "blocks";
@import "buttons";
- @import "forms";
@import "tables";
+ @import "forms";
@import "animations";
@import "tinymce";
@import "highlightjs";
+@import "simplemde";
@import "components";
@import "header";
@import "lists";
@import "pages";
+@import "comments";
- [v-cloak], [v-show] {display: none;}
+ [v-cloak], [v-show] {
+ display: none; opacity: 0;
+ animation-name: none !important;
+ }
+
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
* Search
*/
'search_results' => 'Search Results',
- 'search_results_page' => 'Page Search Results',
- 'search_results_chapter' => 'Chapter Search Results',
- 'search_results_book' => 'Book Search Results',
+ 'search_total_results_found' => ':count result found|:count total results found',
'search_clear' => 'Clear Search',
- 'search_view_pages' => 'View all matches pages',
- 'search_view_chapters' => 'View all matches chapters',
- 'search_view_books' => 'View all matches books',
'search_no_pages' => 'No pages matched this search',
'search_for_term' => 'Search for :term',
- 'search_page_for_term' => 'Page search for :term',
- 'search_chapter_for_term' => 'Chapter search for :term',
- 'search_book_for_term' => 'Books search for :term',
+ 'search_more' => 'More Results',
+ 'search_filters' => 'Search Filters',
+ 'search_content_type' => 'Content Type',
+ 'search_exact_matches' => 'Exact Matches',
+ 'search_tags' => 'Tag Searches',
+ 'search_viewed_by_me' => 'Viewed by me',
+ 'search_not_viewed_by_me' => 'Not viewed by me',
+ 'search_permissions_set' => 'Permissions set',
+ 'search_created_by_me' => 'Created by me',
+ 'search_updated_by_me' => 'Updated by me',
+ 'search_updated_before' => 'Updated before',
+ 'search_updated_after' => 'Updated after',
+ 'search_created_before' => 'Created before',
+ 'search_created_after' => 'Created after',
+ 'search_set_date' => 'Set Date',
+ 'search_update' => 'Update Search',
/**
* Books
'chapters_empty' => 'No pages are currently in this chapter.',
'chapters_permissions_active' => 'Chapter Permissions Active',
'chapters_permissions_success' => 'Chapter Permissions Updated',
+ 'chapters_search_this' => 'Search this chapter',
/**
* Pages
'profile_not_created_pages' => ':userName has not created any pages',
'profile_not_created_chapters' => ':userName has not created any chapters',
'profile_not_created_books' => ':userName has not created any books',
+
+ /**
+ * Comments
+ */
+ 'comment' => 'Comment',
+ 'comments' => 'Comments'
];
Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax');
+ // Comments
+ Route::post('/ajax/page/{pageId}/comment/', 'CommentController@save');
+ Route::put('/ajax/page/{pageId}/comment/{commentId}', 'CommentController@save');
+ Route::delete('/ajax/comment/{id}', 'CommentController@destroy');
+ Route::get('/ajax/page/{pageId}/comments/{commentId}/sub-comments', 'CommentController@getComments');
+ Route::get('/ajax/page/{pageId}/comments/', 'CommentController@getComments');
+
// Links
Route::get('/link/{id}', 'PageController@redirectFromLink');
// Search
- Route::get('/search/all', 'SearchController@searchAll');
- Route::get('/search/pages', 'SearchController@searchPages');
- Route::get('/search/books', 'SearchController@searchBooks');
- Route::get('/search/chapters', 'SearchController@searchChapters');
+ Route::get('/search', 'SearchController@search');
Route::get('/search/book/{bookId}', 'SearchController@searchBook');
+ Route::get('/search/chapter/{bookId}', 'SearchController@searchChapter');
// Other Pages
Route::get('/', 'HomeController@index');