Showing
13 changed files
with
276 additions
and
188 deletions
| ... | @@ -63,22 +63,14 @@ class PageController extends Controller | ... | @@ -63,22 +63,14 @@ class PageController extends Controller |
| 63 | 'html' => 'required|string', | 63 | 'html' => 'required|string', |
| 64 | 'parent' => 'integer|exists:pages,id' | 64 | 'parent' => 'integer|exists:pages,id' |
| 65 | ]); | 65 | ]); |
| 66 | - $book = $this->bookRepo->getBySlug($bookSlug); | ||
| 67 | - $page = $this->pageRepo->newFromInput($request->all()); | ||
| 68 | 66 | ||
| 69 | - $page->slug = $this->pageRepo->findSuitableSlug($page->name, $book->id); | 67 | + $input = $request->all(); |
| 70 | - $page->priority = $this->bookRepo->getNewPriority($book); | 68 | + $book = $this->bookRepo->getBySlug($bookSlug); |
| 69 | + $chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null; | ||
| 70 | + $input['priority'] = $this->bookRepo->getNewPriority($book); | ||
| 71 | 71 | ||
| 72 | - if ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) { | 72 | + $page = $this->pageRepo->saveNew($input, $book, $chapterId); |
| 73 | - $page->chapter_id = $request->get('chapter'); | ||
| 74 | - } | ||
| 75 | 73 | ||
| 76 | - $page->book_id = $book->id; | ||
| 77 | - $page->text = strip_tags($page->html); | ||
| 78 | - $page->created_by = Auth::user()->id; | ||
| 79 | - $page->updated_by = Auth::user()->id; | ||
| 80 | - $page->save(); | ||
| 81 | - $this->pageRepo->saveRevision($page); | ||
| 82 | Activity::add($page, 'page_create', $book->id); | 74 | Activity::add($page, 'page_create', $book->id); |
| 83 | return redirect($page->getUrl()); | 75 | return redirect($page->getUrl()); |
| 84 | } | 76 | } | ... | ... |
| 1 | <?php namespace BookStack\Repos; | 1 | <?php namespace BookStack\Repos; |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | +use BookStack\Book; | ||
| 5 | +use BookStack\Chapter; | ||
| 6 | +use Illuminate\Http\Request; | ||
| 4 | use Illuminate\Support\Facades\Auth; | 7 | use Illuminate\Support\Facades\Auth; |
| 8 | +use Illuminate\Support\Facades\Log; | ||
| 5 | use Illuminate\Support\Str; | 9 | use Illuminate\Support\Str; |
| 6 | use BookStack\Page; | 10 | use BookStack\Page; |
| 7 | use BookStack\PageRevision; | 11 | use BookStack\PageRevision; |
| ... | @@ -42,6 +46,10 @@ class PageRepo | ... | @@ -42,6 +46,10 @@ class PageRepo |
| 42 | return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); | 46 | return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); |
| 43 | } | 47 | } |
| 44 | 48 | ||
| 49 | + /** | ||
| 50 | + * @param $input | ||
| 51 | + * @return Page | ||
| 52 | + */ | ||
| 45 | public function newFromInput($input) | 53 | public function newFromInput($input) |
| 46 | { | 54 | { |
| 47 | $page = $this->page->fill($input); | 55 | $page = $this->page->fill($input); |
| ... | @@ -53,6 +61,83 @@ class PageRepo | ... | @@ -53,6 +61,83 @@ class PageRepo |
| 53 | return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count(); | 61 | return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count(); |
| 54 | } | 62 | } |
| 55 | 63 | ||
| 64 | + /** | ||
| 65 | + * Save a new page into the system. | ||
| 66 | + * Input validation must be done beforehand. | ||
| 67 | + * @param array $input | ||
| 68 | + * @param Book $book | ||
| 69 | + * @param int $chapterId | ||
| 70 | + * @return Page | ||
| 71 | + */ | ||
| 72 | + public function saveNew(array $input, Book $book, $chapterId = null) | ||
| 73 | + { | ||
| 74 | + $page = $this->newFromInput($input); | ||
| 75 | + $page->slug = $this->findSuitableSlug($page->name, $book->id); | ||
| 76 | + | ||
| 77 | + if ($chapterId) $page->chapter_id = $chapterId; | ||
| 78 | + | ||
| 79 | + $page->html = $this->formatHtml($input['html']); | ||
| 80 | + $page->text = strip_tags($page->html); | ||
| 81 | + $page->created_by = auth()->user()->id; | ||
| 82 | + $page->updated_by = auth()->user()->id; | ||
| 83 | + | ||
| 84 | + $book->pages()->save($page); | ||
| 85 | + $this->saveRevision($page); | ||
| 86 | + return $page; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + /** | ||
| 90 | + * Formats a page's html to be tagged correctly | ||
| 91 | + * within the system. | ||
| 92 | + * @param string $htmlText | ||
| 93 | + * @return string | ||
| 94 | + */ | ||
| 95 | + protected function formatHtml($htmlText) | ||
| 96 | + { | ||
| 97 | + libxml_use_internal_errors(true); | ||
| 98 | + $doc = new \DOMDocument(); | ||
| 99 | + $doc->loadHTML($htmlText); | ||
| 100 | + | ||
| 101 | + $container = $doc->documentElement; | ||
| 102 | + $body = $container->childNodes[0]; | ||
| 103 | + $childNodes = $body->childNodes; | ||
| 104 | + | ||
| 105 | + // Ensure no duplicate ids are used | ||
| 106 | + $lastId = false; | ||
| 107 | + $idArray = []; | ||
| 108 | + | ||
| 109 | + foreach ($childNodes as $index => $childNode) { | ||
| 110 | + /** @var \DOMElement $childNode */ | ||
| 111 | + if (get_class($childNode) !== 'DOMElement') continue; | ||
| 112 | + | ||
| 113 | + // Overwrite id if not a bookstack custom id | ||
| 114 | + if ($childNode->hasAttribute('id')) { | ||
| 115 | + $id = $childNode->getAttribute('id'); | ||
| 116 | + if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) { | ||
| 117 | + $idArray[] = $id; | ||
| 118 | + continue; | ||
| 119 | + }; | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + // Create an unique id for the element | ||
| 123 | + do { | ||
| 124 | + $id = 'bkmrk-' . substr(uniqid(), -5); | ||
| 125 | + } while ($id == $lastId); | ||
| 126 | + $lastId = $id; | ||
| 127 | + | ||
| 128 | + $childNode->setAttribute('id', $id); | ||
| 129 | + $idArray[] = $id; | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + // Generate inner html as a string | ||
| 133 | + $html = ''; | ||
| 134 | + foreach ($childNodes as $childNode) { | ||
| 135 | + $html .= $doc->saveHTML($childNode); | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + return $html; | ||
| 139 | + } | ||
| 140 | + | ||
| 56 | public function destroyById($id) | 141 | public function destroyById($id) |
| 57 | { | 142 | { |
| 58 | $page = $this->getById($id); | 143 | $page = $this->getById($id); |
| ... | @@ -99,8 +184,8 @@ class PageRepo | ... | @@ -99,8 +184,8 @@ class PageRepo |
| 99 | */ | 184 | */ |
| 100 | public function searchForImage($imageString) | 185 | public function searchForImage($imageString) |
| 101 | { | 186 | { |
| 102 | - $pages = $this->page->where('html', 'like', '%'.$imageString.'%')->get(); | 187 | + $pages = $this->page->where('html', 'like', '%' . $imageString . '%')->get(); |
| 103 | - foreach($pages as $page) { | 188 | + foreach ($pages as $page) { |
| 104 | $page->url = $page->getUrl(); | 189 | $page->url = $page->getUrl(); |
| 105 | $page->html = ''; | 190 | $page->html = ''; |
| 106 | $page->text = ''; | 191 | $page->text = ''; |
| ... | @@ -111,14 +196,15 @@ class PageRepo | ... | @@ -111,14 +196,15 @@ class PageRepo |
| 111 | /** | 196 | /** |
| 112 | * Updates a page with any fillable data and saves it into the database. | 197 | * Updates a page with any fillable data and saves it into the database. |
| 113 | * @param Page $page | 198 | * @param Page $page |
| 114 | - * @param $book_id | 199 | + * @param int $book_id |
| 115 | - * @param $data | 200 | + * @param string $input |
| 116 | * @return Page | 201 | * @return Page |
| 117 | */ | 202 | */ |
| 118 | - public function updatePage(Page $page, $book_id, $data) | 203 | + public function updatePage(Page $page, $book_id, $input) |
| 119 | { | 204 | { |
| 120 | - $page->fill($data); | 205 | + $page->fill($input); |
| 121 | $page->slug = $this->findSuitableSlug($page->name, $book_id, $page->id); | 206 | $page->slug = $this->findSuitableSlug($page->name, $book_id, $page->id); |
| 207 | + $page->html = $this->formatHtml($input['html']); | ||
| 122 | $page->text = strip_tags($page->html); | 208 | $page->text = strip_tags($page->html); |
| 123 | $page->updated_by = Auth::user()->id; | 209 | $page->updated_by = Auth::user()->id; |
| 124 | $page->save(); | 210 | $page->save(); |
| ... | @@ -189,7 +275,7 @@ class PageRepo | ... | @@ -189,7 +275,7 @@ class PageRepo |
| 189 | public function setBookId($bookId, Page $page) | 275 | public function setBookId($bookId, Page $page) |
| 190 | { | 276 | { |
| 191 | $page->book_id = $bookId; | 277 | $page->book_id = $bookId; |
| 192 | - foreach($page->activity as $activity) { | 278 | + foreach ($page->activity as $activity) { |
| 193 | $activity->book_id = $bookId; | 279 | $activity->book_id = $bookId; |
| 194 | $activity->save(); | 280 | $activity->save(); |
| 195 | } | 281 | } | ... | ... |
| ... | @@ -11,6 +11,7 @@ | ... | @@ -11,6 +11,7 @@ |
| 11 | "dropzone": "^4.0.1", | 11 | "dropzone": "^4.0.1", |
| 12 | "laravel-elixir": "^3.3.1", | 12 | "laravel-elixir": "^3.3.1", |
| 13 | "vue": "^0.12.16", | 13 | "vue": "^0.12.16", |
| 14 | - "vue-resource": "^0.1.16" | 14 | + "vue-resource": "^0.1.16", |
| 15 | + "zeroclipboard": "^2.2.0" | ||
| 15 | } | 16 | } |
| 16 | } | 17 | } | ... | ... |
public/ZeroClipboard.swf
0 → 100644
No preview for this file type
| 1 | +window.ZeroClipboard = require('zeroclipboard'); | ||
| 2 | +window.ZeroClipboard.config({ | ||
| 3 | + swfPath: '/ZeroClipboard.swf' | ||
| 4 | +}); | ||
| 1 | 5 | ||
| 2 | // Global jQuery Elements | 6 | // Global jQuery Elements |
| 3 | $(function () { | 7 | $(function () { |
| ... | @@ -23,6 +27,12 @@ function elemExists(selector) { | ... | @@ -23,6 +27,12 @@ function elemExists(selector) { |
| 23 | return document.querySelector(selector) !== null; | 27 | return document.querySelector(selector) !== null; |
| 24 | } | 28 | } |
| 25 | 29 | ||
| 30 | +// TinyMCE editor | ||
| 31 | +if(elemExists('#html-editor')) { | ||
| 32 | + var tinyMceOptions = require('./pages/page-form'); | ||
| 33 | + tinymce.init(tinyMceOptions); | ||
| 34 | +} | ||
| 35 | + | ||
| 26 | // Vue JS elements | 36 | // Vue JS elements |
| 27 | var Vue = require('vue'); | 37 | var Vue = require('vue'); |
| 28 | Vue.use(require('vue-resource')); | 38 | Vue.use(require('vue-resource')); | ... | ... |
resources/assets/js/pages/page-form.js
0 → 100644
| 1 | + | ||
| 2 | +module.exports = { | ||
| 3 | + selector: '#html-editor', | ||
| 4 | + content_css: [ | ||
| 5 | + '/css/styles.css', | ||
| 6 | + '//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' | ||
| 7 | + ], | ||
| 8 | + body_class: 'page-content', | ||
| 9 | + relative_urls: false, | ||
| 10 | + statusbar: false, | ||
| 11 | + menubar: false, | ||
| 12 | + //height: 700, | ||
| 13 | + extended_valid_elements: 'pre[*]', | ||
| 14 | + valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]", | ||
| 15 | + plugins: "image table textcolor paste link imagetools fullscreen code hr", | ||
| 16 | + 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", | ||
| 17 | + content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}", | ||
| 18 | + style_formats: [ | ||
| 19 | + {title: "Header 1", format: "h1"}, | ||
| 20 | + {title: "Header 2", format: "h2"}, | ||
| 21 | + {title: "Header 3", format: "h3"}, | ||
| 22 | + {title: "Header 4", format: "h4"}, | ||
| 23 | + {title: "Paragraph", format: "p"}, | ||
| 24 | + {title: "Blockquote", format: "blockquote"}, | ||
| 25 | + {title: "Code Block", icon: "code", format: "pre"} | ||
| 26 | + ], | ||
| 27 | + file_browser_callback: function(field_name, url, type, win) { | ||
| 28 | + ImageManager.show(function(image) { | ||
| 29 | + win.document.getElementById(field_name).value = image.url; | ||
| 30 | + if ("createEvent" in document) { | ||
| 31 | + var evt = document.createEvent("HTMLEvents"); | ||
| 32 | + evt.initEvent("change", false, true); | ||
| 33 | + win.document.getElementById(field_name).dispatchEvent(evt); | ||
| 34 | + } else { | ||
| 35 | + win.document.getElementById(field_name).fireEvent("onchange"); | ||
| 36 | + } | ||
| 37 | + }); | ||
| 38 | + } | ||
| 39 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -110,3 +110,20 @@ | ... | @@ -110,3 +110,20 @@ |
| 110 | transform: translate3d(0, 0, 0); | 110 | transform: translate3d(0, 0, 0); |
| 111 | } | 111 | } |
| 112 | } | 112 | } |
| 113 | + | ||
| 114 | +@keyframes pointer { | ||
| 115 | + 0% { | ||
| 116 | + transform: translate3d(0, 20px, 0) scale3d(0, 0, 0); | ||
| 117 | + } | ||
| 118 | + 100% { | ||
| 119 | + transform: translate3d(0, 0, 0) scale3d(1, 1, 1); | ||
| 120 | + } | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +.anim.pointer { | ||
| 124 | + transform-origin: 50% 100%; | ||
| 125 | + animation-name: pointer; | ||
| 126 | + animation-duration: 180ms; | ||
| 127 | + animation-delay: 0s; | ||
| 128 | + animation-timing-function: cubic-bezier(.62, .28, .23, .99); | ||
| 129 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -85,29 +85,9 @@ $button-border-radius: 2px; | ... | @@ -85,29 +85,9 @@ $button-border-radius: 2px; |
| 85 | display: block; | 85 | display: block; |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | -// Floating action button | 88 | +.button.icon { |
| 89 | -//.fab { | 89 | + i { |
| 90 | -// $size: 70px; | 90 | + padding-right: 0; |
| 91 | -// button.button { | 91 | + } |
| 92 | -// border-radius: 100%; | 92 | +} |
| 93 | -// width: $size; | 93 | + |
| 94 | -// height: $size; | ||
| 95 | -// font-size: 48px; | ||
| 96 | -// text-align: center; | ||
| 97 | -// margin: 0; | ||
| 98 | -// padding: 0; | ||
| 99 | -// border: 0; | ||
| 100 | -// box-shadow: 0 0 2px 2px #DDD; | ||
| 101 | -// transition: all ease-in-out 160ms; | ||
| 102 | -// i { | ||
| 103 | -// transform: rotate(0deg); | ||
| 104 | -// transition: all ease-in-out 160ms; | ||
| 105 | -// } | ||
| 106 | -// &:hover { | ||
| 107 | -// box-shadow: 0 2px 4px 2px #CCC; | ||
| 108 | -// i { | ||
| 109 | -// transform: rotate(180deg); | ||
| 110 | -// } | ||
| 111 | -// } | ||
| 112 | -// } | ||
| 113 | -//} | ... | ... |
| ... | @@ -23,38 +23,65 @@ | ... | @@ -23,38 +23,65 @@ |
| 23 | } | 23 | } |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | -// Link hooks & popovers | 26 | +// Page content pointers |
| 27 | -a.link-hook { | 27 | +.pointer-container { |
| 28 | - position: absolute; | 28 | + position: relative; |
| 29 | + display: none; | ||
| 30 | + left: 2%; | ||
| 31 | +} | ||
| 32 | +.pointer { | ||
| 33 | + border: 1px solid #CCC; | ||
| 29 | display: inline-block; | 34 | display: inline-block; |
| 30 | - top: $-xs; | 35 | + padding: $-xs $-s; |
| 31 | - left: -$-l; | 36 | + border-radius: 4px; |
| 32 | - padding-bottom: 30px; | 37 | + box-shadow: 0 0 8px 1px rgba(212, 209, 209, 0.35); |
| 33 | - font-size: 20px; | 38 | + position: absolute; |
| 34 | - line-height: 20px; | 39 | + top: -60px; |
| 35 | - color: #BBB; | 40 | + background-color:#FFF; |
| 36 | - opacity: 0; | 41 | + &:before { |
| 37 | - transform: translate3d(10px, 0, 0); | 42 | + position: absolute; |
| 38 | - transition: all ease-in-out 240ms; | 43 | + left: 50%; |
| 39 | - background-color: transparent; | 44 | + bottom: -9px; |
| 40 | - &:hover { | 45 | + width: 16px; |
| 41 | - color: $primary; | 46 | + height: 16px; |
| 47 | + margin-left: -8px; | ||
| 48 | + content: ''; | ||
| 49 | + display: block; | ||
| 50 | + background-color:#FFF; | ||
| 51 | + transform: rotate(45deg); | ||
| 52 | + transform-origin: 50% 50%; | ||
| 53 | + border-bottom: 1px solid #CCC; | ||
| 54 | + border-right: 1px solid #CCC; | ||
| 55 | + z-index: 1; | ||
| 56 | + } | ||
| 57 | + input { | ||
| 58 | + background-color: #FFF; | ||
| 59 | + border: 1px solid #DDD; | ||
| 60 | + color: #666; | ||
| 61 | + width: 180px; | ||
| 62 | + z-index: 40; | ||
| 63 | + } | ||
| 64 | + input, button { | ||
| 65 | + position: relative; | ||
| 66 | + border-radius: 0; | ||
| 67 | + height: 28px; | ||
| 68 | + font-size: 12px; | ||
| 69 | + } | ||
| 70 | + > i { | ||
| 71 | + color: #888; | ||
| 72 | + font-size: 18px; | ||
| 73 | + padding-top: 4px; | ||
| 74 | + } | ||
| 75 | + .button { | ||
| 76 | + line-height: 1; | ||
| 77 | + margin: 0 0 0 -4px; | ||
| 78 | + box-shadow: none; | ||
| 42 | } | 79 | } |
| 43 | } | 80 | } |
| 81 | + | ||
| 44 | h1, h2, h3, h4, h5, h6 { | 82 | h1, h2, h3, h4, h5, h6 { |
| 45 | &:hover a.link-hook { | 83 | &:hover a.link-hook { |
| 46 | opacity: 1; | 84 | opacity: 1; |
| 47 | transform: translate3d(0, 0, 0); | 85 | transform: translate3d(0, 0, 0); |
| 48 | } | 86 | } |
| 49 | } | 87 | } |
| 50 | - | ||
| 51 | -// Side Navigation | ||
| 52 | -.side-nav { | ||
| 53 | - position: fixed; | ||
| 54 | - padding-left: $-m; | ||
| 55 | - opacity: 0.8; | ||
| 56 | - margin-top: $-xxl; | ||
| 57 | - margin-left: 0; | ||
| 58 | - max-width: 240px; | ||
| 59 | - display: none; | ||
| 60 | -} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -28,7 +28,7 @@ | ... | @@ -28,7 +28,7 @@ |
| 28 | </div> | 28 | </div> |
| 29 | </div> | 29 | </div> |
| 30 | <div class="edit-area flex-fill flex"> | 30 | <div class="edit-area flex-fill flex"> |
| 31 | - <textarea id="html" name="html" rows="5" | 31 | + <textarea id="html-editor" name="html" rows="5" |
| 32 | @if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea> | 32 | @if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea> |
| 33 | @if($errors->has('html')) | 33 | @if($errors->has('html')) |
| 34 | <div class="text-neg text-small">{{ $errors->first('html') }}</div> | 34 | <div class="text-neg text-small">{{ $errors->first('html') }}</div> |
| ... | @@ -36,68 +36,4 @@ | ... | @@ -36,68 +36,4 @@ |
| 36 | </div> | 36 | </div> |
| 37 | </div> | 37 | </div> |
| 38 | 38 | ||
| 39 | - | ||
| 40 | - | ||
| 41 | - | ||
| 42 | - | ||
| 43 | -<script> | ||
| 44 | - $(function() { | ||
| 45 | - //ImageManager.show('#image-manager'); | ||
| 46 | - | ||
| 47 | - tinymce.init({ | ||
| 48 | - selector: '.edit-area textarea', | ||
| 49 | - content_css: [ | ||
| 50 | - '/css/app.css', | ||
| 51 | - '//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' | ||
| 52 | - ], | ||
| 53 | - body_class: 'page-content', | ||
| 54 | - relative_urls: false, | ||
| 55 | - statusbar: false, | ||
| 56 | - menubar: false, | ||
| 57 | - //height: 700, | ||
| 58 | - extended_valid_elements: 'pre[*]', | ||
| 59 | - plugins: "image table textcolor paste link imagetools fullscreen code hr", | ||
| 60 | - 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", | ||
| 61 | - content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}", | ||
| 62 | - style_formats: [ | ||
| 63 | - {title: "Header 1", format: "h1"}, | ||
| 64 | - {title: "Header 2", format: "h2"}, | ||
| 65 | - {title: "Header 3", format: "h3"}, | ||
| 66 | - {title: "Header 4", format: "h4"}, | ||
| 67 | - {title: "Paragraph", format: "p"}, | ||
| 68 | - {title: "Blockquote", format: "blockquote"}, | ||
| 69 | - {title: "Code Block", icon: "code", format: "pre"} | ||
| 70 | - ], | ||
| 71 | - file_browser_callback: function(field_name, url, type, win) { | ||
| 72 | - ImageManager.show(function(image) { | ||
| 73 | - win.document.getElementById(field_name).value = image.url; | ||
| 74 | - if ("createEvent" in document) { | ||
| 75 | - var evt = document.createEvent("HTMLEvents"); | ||
| 76 | - evt.initEvent("change", false, true); | ||
| 77 | - win.document.getElementById(field_name).dispatchEvent(evt); | ||
| 78 | - } else { | ||
| 79 | - win.document.getElementById(field_name).fireEvent("onchange"); | ||
| 80 | - } | ||
| 81 | - }); | ||
| 82 | - } | ||
| 83 | -// setup: function(editor) { | ||
| 84 | -// editor.addButton('full', { | ||
| 85 | -// title: 'Expand Editor', | ||
| 86 | -// icon: 'fullscreen', | ||
| 87 | -// onclick: function() { | ||
| 88 | -// var container = $(editor.getContainer()).toggleClass('fullscreen'); | ||
| 89 | -// var isFull = container.hasClass('fullscreen'); | ||
| 90 | -// var iframe = container.find('iframe').first(); | ||
| 91 | -// var height = isFull ? $(window).height()-110 : 600; | ||
| 92 | -// iframe.css('height', height + 'px'); | ||
| 93 | -// } | ||
| 94 | -// }); | ||
| 95 | -// } | ||
| 96 | - }); | ||
| 97 | - | ||
| 98 | - | ||
| 99 | - | ||
| 100 | - }); | ||
| 101 | -</script> | ||
| 102 | - | ||
| 103 | <image-manager></image-manager> | 39 | <image-manager></image-manager> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -<h1>{{$page->name}}</h1> | 1 | +<h1 id="bkmrk-page-title">{{$page->name}}</h1> |
| 2 | -@if(count($page->children) > 0) | 2 | + |
| 3 | - <h4 class="text-muted">Sub-pages</h4> | ||
| 4 | - <div class="page-list"> | ||
| 5 | - @foreach($page->children as $childPage) | ||
| 6 | - <a href="{{ $childPage->getUrl() }}">{{ $childPage->name }}</a> | ||
| 7 | - @endforeach | ||
| 8 | - </div> | ||
| 9 | -@endif | ||
| 10 | {!! $page->html !!} | 3 | {!! $page->html !!} |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -37,22 +37,31 @@ | ... | @@ -37,22 +37,31 @@ |
| 37 | <div class="row"> | 37 | <div class="row"> |
| 38 | <div class="col-md-9"> | 38 | <div class="col-md-9"> |
| 39 | <div class="page-content anim fadeIn"> | 39 | <div class="page-content anim fadeIn"> |
| 40 | + | ||
| 41 | + <div class="pointer-container" id="pointer"> | ||
| 42 | + <div class="pointer anim"> | ||
| 43 | + <i class="zmdi zmdi-link"></i> | ||
| 44 | + <input readonly="readonly" type="text" placeholder="url"> | ||
| 45 | + <button class="button icon" title="Copy Link" data-clipboard-text=""><i class="zmdi zmdi-copy"></i></button> | ||
| 46 | + </div> | ||
| 47 | + </div> | ||
| 48 | + | ||
| 40 | @include('pages/page-display') | 49 | @include('pages/page-display') |
| 50 | + | ||
| 41 | <hr> | 51 | <hr> |
| 52 | + | ||
| 42 | <p class="text-muted small"> | 53 | <p class="text-muted small"> |
| 43 | Created {{$page->created_at->diffForHumans()}} @if($page->createdBy) by {{$page->createdBy->name}} @endif | 54 | Created {{$page->created_at->diffForHumans()}} @if($page->createdBy) by {{$page->createdBy->name}} @endif |
| 44 | <br> | 55 | <br> |
| 45 | Last Updated {{$page->updated_at->diffForHumans()}} @if($page->createdBy) by {{$page->updatedBy->name}} @endif | 56 | Last Updated {{$page->updated_at->diffForHumans()}} @if($page->createdBy) by {{$page->updatedBy->name}} @endif |
| 46 | </p> | 57 | </p> |
| 58 | + | ||
| 47 | </div> | 59 | </div> |
| 48 | </div> | 60 | </div> |
| 49 | <div class="col-md-3"> | 61 | <div class="col-md-3"> |
| 62 | + | ||
| 50 | @include('pages/sidebar-tree-list', ['book' => $book]) | 63 | @include('pages/sidebar-tree-list', ['book' => $book]) |
| 51 | - <div class="side-nav faded"> | 64 | + |
| 52 | - <h4>Page Navigation</h4> | ||
| 53 | - <ul class="page-nav-list"> | ||
| 54 | - </ul> | ||
| 55 | - </div> | ||
| 56 | </div> | 65 | </div> |
| 57 | </div> | 66 | </div> |
| 58 | </div> | 67 | </div> |
| ... | @@ -64,53 +73,53 @@ | ... | @@ -64,53 +73,53 @@ |
| 64 | <script> | 73 | <script> |
| 65 | $(document).ready(function() { | 74 | $(document).ready(function() { |
| 66 | 75 | ||
| 67 | - // Set up document navigation | ||
| 68 | - var pageNav = $('.page-nav-list'); | ||
| 69 | - var pageContent = $('.page-content'); | ||
| 70 | - var headers = pageContent.find('h1, h2, h3, h4, h5, h6'); | ||
| 71 | - if(headers.length > 5) { | ||
| 72 | - headers.each(function() { | ||
| 73 | - var header = $(this); | ||
| 74 | - var tag = header.prop('tagName'); | ||
| 75 | - var listElem = $('<li></li>').addClass('nav-'+tag); | ||
| 76 | - var link = $('<a></a>').text(header.text().trim()).attr('href', '#'); | ||
| 77 | - listElem.append(link); | ||
| 78 | - pageNav.append(listElem); | ||
| 79 | - link.click(function(e) { | ||
| 80 | - e.preventDefault(); | ||
| 81 | - header.smoothScrollTo(); | ||
| 82 | - }) | ||
| 83 | - }); | ||
| 84 | - $('.side-nav').fadeIn(); | ||
| 85 | - } else { | ||
| 86 | - $('.side-nav').hide(); | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | 76 | ||
| 90 | - // Set up link hooks | 77 | + // Set up pointer |
| 78 | + var $pointer = $('#pointer').detach(); | ||
| 91 | var pageId = {{$page->id}}; | 79 | var pageId = {{$page->id}}; |
| 92 | - headers.each(function() { | 80 | + var isSelection = false; |
| 93 | - var text = $(this).text().trim(); | 81 | + |
| 94 | - var link = '/link/' + pageId + '#' + encodeURIComponent(text); | 82 | + $pointer.find('input').click(function(e){$(this).select();e.stopPropagation();}); |
| 95 | - var linkHook = $('<a class="link-hook"><i class="zmdi zmdi-link"></i></a>') | 83 | + new ZeroClipboard( $pointer.find('button').first()[0] ); |
| 96 | - .attr({"data-content": link, href: link, target: '_blank'}); | 84 | + |
| 97 | - linkHook.click(function(e) { | 85 | + $(document.body).find('*').on('click focus', function(e) { |
| 98 | - e.preventDefault(); | 86 | + if(!isSelection) { |
| 99 | - goToText(text); | 87 | + $pointer.detach(); |
| 88 | + } | ||
| 100 | }); | 89 | }); |
| 101 | - $(this).append(linkHook); | 90 | + |
| 91 | + $('.page-content [id^="bkmrk"]').on('mouseup keyup', function(e) { | ||
| 92 | + var selection = window.getSelection(); | ||
| 93 | + if(selection.toString().length === 0) return; | ||
| 94 | + // Show pointer and set link | ||
| 95 | + var $elem = $(this); | ||
| 96 | + var link = window.location.protocol + "//" + window.location.host + '/link/' + pageId + '#' + $elem.attr('id'); | ||
| 97 | + $pointer.find('input').val(link); | ||
| 98 | + $pointer.find('button').first().attr('data-clipboard-text', link); | ||
| 99 | + $elem.before($pointer); | ||
| 100 | + $pointer.show(); | ||
| 101 | + e.stopPropagation(); | ||
| 102 | + | ||
| 103 | + isSelection = true; | ||
| 104 | + setTimeout(function() { | ||
| 105 | + isSelection = false; | ||
| 106 | + }, 100); | ||
| 102 | }); | 107 | }); |
| 103 | 108 | ||
| 104 | function goToText(text) { | 109 | function goToText(text) { |
| 110 | + var idElem = $('.page-content').find('#' + text).first(); | ||
| 111 | + if(idElem.length !== 0) { | ||
| 112 | + idElem.smoothScrollTo(); | ||
| 113 | + } else { | ||
| 105 | $('.page-content').find(':contains("'+text+'")').smoothScrollTo(); | 114 | $('.page-content').find(':contains("'+text+'")').smoothScrollTo(); |
| 106 | } | 115 | } |
| 116 | + } | ||
| 107 | 117 | ||
| 108 | if(window.location.hash) { | 118 | if(window.location.hash) { |
| 109 | var text = window.location.hash.replace(/\%20/g, ' ').substr(1); | 119 | var text = window.location.hash.replace(/\%20/g, ' ').substr(1); |
| 110 | goToText(text); | 120 | goToText(text); |
| 111 | } | 121 | } |
| 112 | 122 | ||
| 113 | - //$('[data-toggle="popover"]').popover() | ||
| 114 | }); | 123 | }); |
| 115 | </script> | 124 | </script> |
| 116 | 125 | ... | ... |
| ... | @@ -3,14 +3,12 @@ | ... | @@ -3,14 +3,12 @@ |
| 3 | <head> | 3 | <head> |
| 4 | <title>BookStack</title> | 4 | <title>BookStack</title> |
| 5 | <meta name="viewport" content="width=device-width"> | 5 | <meta name="viewport" content="width=device-width"> |
| 6 | - <link rel="stylesheet" href="/css/app.css"> | 6 | + <link rel="stylesheet" href="/css/styles.css"> |
| 7 | <link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'> | 7 | <link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'> |
| 8 | <link rel="stylesheet" href="/bower/material-design-iconic-font/dist/css/material-design-iconic-font.min.css"> | 8 | <link rel="stylesheet" href="/bower/material-design-iconic-font/dist/css/material-design-iconic-font.min.css"> |
| 9 | 9 | ||
| 10 | <!-- Scripts --> | 10 | <!-- Scripts --> |
| 11 | <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> | 11 | <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> |
| 12 | - <script src="/js/common.js"></script> | ||
| 13 | - | ||
| 14 | </head> | 12 | </head> |
| 15 | <body class="@yield('body-class')"> | 13 | <body class="@yield('body-class')"> |
| 16 | 14 | ... | ... |
-
Please register or sign in to post a comment