diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index c2867caa8..56ea6992c 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -63,22 +63,14 @@ class PageController extends Controller 'html' => 'required|string', 'parent' => 'integer|exists:pages,id' ]); + + $input = $request->all(); $book = $this->bookRepo->getBySlug($bookSlug); - $page = $this->pageRepo->newFromInput($request->all()); + $chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null; + $input['priority'] = $this->bookRepo->getNewPriority($book); - $page->slug = $this->pageRepo->findSuitableSlug($page->name, $book->id); - $page->priority = $this->bookRepo->getNewPriority($book); + $page = $this->pageRepo->saveNew($input, $book, $chapterId); - if ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) { - $page->chapter_id = $request->get('chapter'); - } - - $page->book_id = $book->id; - $page->text = strip_tags($page->html); - $page->created_by = Auth::user()->id; - $page->updated_by = Auth::user()->id; - $page->save(); - $this->pageRepo->saveRevision($page); Activity::add($page, 'page_create', $book->id); return redirect($page->getUrl()); } diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index 441d5980e..1f0c61819 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -1,7 +1,11 @@ page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); } + /** + * @param $input + * @return Page + */ public function newFromInput($input) { $page = $this->page->fill($input); @@ -53,6 +61,83 @@ class PageRepo return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count(); } + /** + * Save a new page into the system. + * Input validation must be done beforehand. + * @param array $input + * @param Book $book + * @param int $chapterId + * @return Page + */ + public function saveNew(array $input, Book $book, $chapterId = null) + { + $page = $this->newFromInput($input); + $page->slug = $this->findSuitableSlug($page->name, $book->id); + + if ($chapterId) $page->chapter_id = $chapterId; + + $page->html = $this->formatHtml($input['html']); + $page->text = strip_tags($page->html); + $page->created_by = auth()->user()->id; + $page->updated_by = auth()->user()->id; + + $book->pages()->save($page); + $this->saveRevision($page); + return $page; + } + + /** + * Formats a page's html to be tagged correctly + * within the system. + * @param string $htmlText + * @return string + */ + protected function formatHtml($htmlText) + { + libxml_use_internal_errors(true); + $doc = new \DOMDocument(); + $doc->loadHTML($htmlText); + + $container = $doc->documentElement; + $body = $container->childNodes[0]; + $childNodes = $body->childNodes; + + // Ensure no duplicate ids are used + $lastId = false; + $idArray = []; + + foreach ($childNodes as $index => $childNode) { + /** @var \DOMElement $childNode */ + if (get_class($childNode) !== 'DOMElement') continue; + + // Overwrite id if not a bookstack custom id + if ($childNode->hasAttribute('id')) { + $id = $childNode->getAttribute('id'); + if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) { + $idArray[] = $id; + continue; + }; + } + + // Create an unique id for the element + do { + $id = 'bkmrk-' . substr(uniqid(), -5); + } while ($id == $lastId); + $lastId = $id; + + $childNode->setAttribute('id', $id); + $idArray[] = $id; + } + + // Generate inner html as a string + $html = ''; + foreach ($childNodes as $childNode) { + $html .= $doc->saveHTML($childNode); + } + + return $html; + } + public function destroyById($id) { $page = $this->getById($id); @@ -99,8 +184,8 @@ class PageRepo */ public function searchForImage($imageString) { - $pages = $this->page->where('html', 'like', '%'.$imageString.'%')->get(); - foreach($pages as $page) { + $pages = $this->page->where('html', 'like', '%' . $imageString . '%')->get(); + foreach ($pages as $page) { $page->url = $page->getUrl(); $page->html = ''; $page->text = ''; @@ -110,15 +195,16 @@ class PageRepo /** * Updates a page with any fillable data and saves it into the database. - * @param Page $page - * @param $book_id - * @param $data + * @param Page $page + * @param int $book_id + * @param string $input * @return Page */ - public function updatePage(Page $page, $book_id, $data) + public function updatePage(Page $page, $book_id, $input) { - $page->fill($data); + $page->fill($input); $page->slug = $this->findSuitableSlug($page->name, $book_id, $page->id); + $page->html = $this->formatHtml($input['html']); $page->text = strip_tags($page->html); $page->updated_by = Auth::user()->id; $page->save(); @@ -189,7 +275,7 @@ class PageRepo public function setBookId($bookId, Page $page) { $page->book_id = $bookId; - foreach($page->activity as $activity) { + foreach ($page->activity as $activity) { $activity->book_id = $bookId; $activity->save(); } diff --git a/package.json b/package.json index af40e256f..09edc2b20 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dropzone": "^4.0.1", "laravel-elixir": "^3.3.1", "vue": "^0.12.16", - "vue-resource": "^0.1.16" + "vue-resource": "^0.1.16", + "zeroclipboard": "^2.2.0" } } diff --git a/public/ZeroClipboard.swf b/public/ZeroClipboard.swf new file mode 100644 index 000000000..8bad6a3e3 Binary files /dev/null and b/public/ZeroClipboard.swf differ diff --git a/resources/assets/js/global.js b/resources/assets/js/global.js index 519ce7412..a6fca8dbd 100644 --- a/resources/assets/js/global.js +++ b/resources/assets/js/global.js @@ -1,3 +1,7 @@ +window.ZeroClipboard = require('zeroclipboard'); +window.ZeroClipboard.config({ + swfPath: '/ZeroClipboard.swf' +}); // Global jQuery Elements $(function () { @@ -23,6 +27,12 @@ function elemExists(selector) { return document.querySelector(selector) !== null; } +// TinyMCE editor +if(elemExists('#html-editor')) { + var tinyMceOptions = require('./pages/page-form'); + tinymce.init(tinyMceOptions); +} + // Vue JS elements var Vue = require('vue'); Vue.use(require('vue-resource')); diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js new file mode 100644 index 000000000..de53a497a --- /dev/null +++ b/resources/assets/js/pages/page-form.js @@ -0,0 +1,39 @@ + +module.exports = { + selector: '#html-editor', + content_css: [ + '/css/styles.css', + '//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' + ], + body_class: 'page-content', + relative_urls: false, + statusbar: false, + menubar: false, + //height: 700, + extended_valid_elements: 'pre[*]', + valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]", + plugins: "image table textcolor paste link imagetools fullscreen code hr", + toolbar: "code undo | styleselect | hr bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image link | fullscreen", + content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}", + style_formats: [ + {title: "Header 1", format: "h1"}, + {title: "Header 2", format: "h2"}, + {title: "Header 3", format: "h3"}, + {title: "Header 4", format: "h4"}, + {title: "Paragraph", format: "p"}, + {title: "Blockquote", format: "blockquote"}, + {title: "Code Block", icon: "code", format: "pre"} + ], + file_browser_callback: function(field_name, url, type, win) { + ImageManager.show(function(image) { + win.document.getElementById(field_name).value = image.url; + if ("createEvent" in document) { + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", false, true); + win.document.getElementById(field_name).dispatchEvent(evt); + } else { + win.document.getElementById(field_name).fireEvent("onchange"); + } + }); + } +}; \ No newline at end of file diff --git a/resources/assets/sass/_animations.scss b/resources/assets/sass/_animations.scss index e6e85ef8e..582d718c8 100644 --- a/resources/assets/sass/_animations.scss +++ b/resources/assets/sass/_animations.scss @@ -110,3 +110,20 @@ transform: translate3d(0, 0, 0); } } + +@keyframes pointer { + 0% { + transform: translate3d(0, 20px, 0) scale3d(0, 0, 0); + } + 100% { + transform: translate3d(0, 0, 0) scale3d(1, 1, 1); + } +} + +.anim.pointer { + transform-origin: 50% 100%; + animation-name: pointer; + animation-duration: 180ms; + animation-delay: 0s; + animation-timing-function: cubic-bezier(.62, .28, .23, .99); +} \ No newline at end of file diff --git a/resources/assets/sass/_buttons.scss b/resources/assets/sass/_buttons.scss index bafb68165..72fa33cfe 100644 --- a/resources/assets/sass/_buttons.scss +++ b/resources/assets/sass/_buttons.scss @@ -85,29 +85,9 @@ $button-border-radius: 2px; display: block; } -// Floating action button -//.fab { -// $size: 70px; -// button.button { -// border-radius: 100%; -// width: $size; -// height: $size; -// font-size: 48px; -// text-align: center; -// margin: 0; -// padding: 0; -// border: 0; -// box-shadow: 0 0 2px 2px #DDD; -// transition: all ease-in-out 160ms; -// i { -// transform: rotate(0deg); -// transition: all ease-in-out 160ms; -// } -// &:hover { -// box-shadow: 0 2px 4px 2px #CCC; -// i { -// transform: rotate(180deg); -// } -// } -// } -//} +.button.icon { + i { + padding-right: 0; + } +} + diff --git a/resources/assets/sass/_pages.scss b/resources/assets/sass/_pages.scss index c7d41a5d9..c4b2b05e1 100644 --- a/resources/assets/sass/_pages.scss +++ b/resources/assets/sass/_pages.scss @@ -23,38 +23,65 @@ } } -// Link hooks & popovers -a.link-hook { - position: absolute; +// Page content pointers +.pointer-container { + position: relative; + display: none; + left: 2%; +} +.pointer { + border: 1px solid #CCC; display: inline-block; - top: $-xs; - left: -$-l; - padding-bottom: 30px; - font-size: 20px; - line-height: 20px; - color: #BBB; - opacity: 0; - transform: translate3d(10px, 0, 0); - transition: all ease-in-out 240ms; - background-color: transparent; - &:hover { - color: $primary; + padding: $-xs $-s; + border-radius: 4px; + box-shadow: 0 0 8px 1px rgba(212, 209, 209, 0.35); + position: absolute; + top: -60px; + background-color:#FFF; + &:before { + position: absolute; + left: 50%; + bottom: -9px; + width: 16px; + height: 16px; + margin-left: -8px; + content: ''; + display: block; + background-color:#FFF; + transform: rotate(45deg); + transform-origin: 50% 50%; + border-bottom: 1px solid #CCC; + border-right: 1px solid #CCC; + z-index: 1; + } + input { + background-color: #FFF; + border: 1px solid #DDD; + color: #666; + width: 180px; + z-index: 40; + } + input, button { + position: relative; + border-radius: 0; + height: 28px; + font-size: 12px; + } + > i { + color: #888; + font-size: 18px; + padding-top: 4px; + } + .button { + line-height: 1; + margin: 0 0 0 -4px; + box-shadow: none; } } + h1, h2, h3, h4, h5, h6 { &:hover a.link-hook { opacity: 1; transform: translate3d(0, 0, 0); } } - -// Side Navigation -.side-nav { - position: fixed; - padding-left: $-m; - opacity: 0.8; - margin-top: $-xxl; - margin-left: 0; - max-width: 240px; - display: none; -} \ No newline at end of file diff --git a/resources/views/pages/form.blade.php b/resources/views/pages/form.blade.php index 44e396c84..de88dec0a 100644 --- a/resources/views/pages/form.blade.php +++ b/resources/views/pages/form.blade.php @@ -28,7 +28,7 @@
- @if($errors->has('html'))
{{ $errors->first('html') }}
@@ -36,68 +36,4 @@
- - - - - - \ No newline at end of file diff --git a/resources/views/pages/page-display.blade.php b/resources/views/pages/page-display.blade.php index 6eb623b39..8d3625db8 100644 --- a/resources/views/pages/page-display.blade.php +++ b/resources/views/pages/page-display.blade.php @@ -1,10 +1,3 @@ -

{{$page->name}}

-@if(count($page->children) > 0) -

Sub-pages

-
- @foreach($page->children as $childPage) - {{ $childPage->name }} - @endforeach -
-@endif +

{{$page->name}}

+ {!! $page->html !!} \ No newline at end of file diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 5fa41eff8..e30cfef82 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -37,22 +37,31 @@
+ +
+
+ + + +
+
+ @include('pages/page-display') +
+

Created {{$page->created_at->diffForHumans()}} @if($page->createdBy) by {{$page->createdBy->name}} @endif
Last Updated {{$page->updated_at->diffForHumans()}} @if($page->createdBy) by {{$page->updatedBy->name}} @endif

+
+ @include('pages/sidebar-tree-list', ['book' => $book]) -
-

Page Navigation

- -
+
@@ -64,45 +73,46 @@ diff --git a/resources/views/public.blade.php b/resources/views/public.blade.php index 7606f936a..39bdab1ef 100644 --- a/resources/views/public.blade.php +++ b/resources/views/public.blade.php @@ -3,14 +3,12 @@ BookStack - + - -