From: Abijeet Date: Tue, 18 Apr 2017 19:53:27 +0000 (+0530) Subject: Merge branch 'master' of https://github.com/Abijeet/BookStack X-Git-Tag: v0.18.0~1^2~45^2^2~30 X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/8e2437498f879f79f222119f8afeacf22f640f3d?ds=inline;hp=-c Merge branch 'master' of https://github.com/Abijeet/BookStack --- 8e2437498f879f79f222119f8afeacf22f640f3d diff --combined app/Page.php index 83ef6f350,c9823e7e4..4a8d32780 --- a/app/Page.php +++ b/app/Page.php @@@ -8,8 -8,7 +8,7 @@@ class Page extends Entit protected $simpleAttributes = ['name', 'id', 'slug']; protected $with = ['book']; - - protected $fieldsToSearch = ['name', 'text']; + public $textField = 'text'; /** * Converts this page into a simplified array. @@@ -39,15 -38,6 +38,15 @@@ { 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. @@@ -105,4 -95,14 +104,14 @@@ 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"; + } + } diff --combined app/Services/PermissionService.php index 6f27db9b7,1e75308a0..cb0b68026 --- a/app/Services/PermissionService.php +++ b/app/Services/PermissionService.php @@@ -406,7 -406,7 +406,7 @@@ class PermissionServic $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)) { @@@ -479,8 -479,7 +479,7 @@@ * @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) { @@@ -488,7 -487,7 +487,7 @@@ }); } }); - $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); @@@ -514,7 -513,7 +513,7 @@@ * @param string $entityType * @param Builder|Entity $query * @param string $action - * @return mixed + * @return Builder */ public function enforceEntityRestrictions($entityType, $query, $action = 'view') { @@@ -540,7 -539,7 +539,7 @@@ } /** - * 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 diff --combined resources/assets/js/controllers.js index 7df130f3f,6a88aa811..65dc50e99 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@@ -1,12 -1,12 +1,12 @@@ "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) { @@@ -259,39 -259,6 +259,6 @@@ }]); - - 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) { @@@ -305,7 -272,6 +272,7 @@@ $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) { @@@ -715,79 -681,4 +682,79 @@@ }]); + // 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); + } + } + }]); + }; diff --combined resources/assets/js/directives.js index 537a016f5,0bc664200..f30a09778 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@@ -1,8 -1,9 +1,9 @@@ "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. @@@ -214,18 -215,8 +215,8 @@@ } }]); - 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*/, '') - .replace(/^\s*\[x\]\s*/, ''); - return `
  • ${text}
  • `; - } - return `
  • ${text}
  • `; - }; + const md = new MarkdownIt(); + md.use(mdTasksLists, {label: true}); /** * Markdown input @@@ -244,20 -235,20 +235,20 @@@ 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)); }); } @@@ -827,52 -818,4 +818,52 @@@ } }; }]); + + + 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 = ''; + }; + } + } + } + }]); + }; diff --combined resources/assets/sass/styles.scss index cd43650ee,50c3a50b2..60071b9ef --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@@ -7,19 -7,21 +7,23 @@@ @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; diff --combined resources/lang/en/entities.php index afe6f7ec1,8644f7a4a..ec0e17306 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@@ -43,18 -43,26 +43,26 @@@ return * 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 @@@ -112,6 -120,7 +120,7 @@@ '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 @@@ -223,10 -232,4 +232,10 @@@ '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' ]; diff --combined routes/web.php index 4b57138e2,8ecfd9465..2ac212e62 --- a/routes/web.php +++ b/routes/web.php @@@ -119,22 -119,13 +119,20 @@@ Route::group(['middleware' => 'auth'], 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');