Skip to content

Commit 5c60f27

Browse files
committed
Merge branch 'page_transclusion'
Closes #123
2 parents 26da81a + 2d4034f commit 5c60f27

File tree

17 files changed

+170
-97
lines changed

17 files changed

+170
-97
lines changed

app/Http/Controllers/PageController.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,16 @@ public function show($bookSlug, $pageSlug)
158158

159159
$this->checkOwnablePermission('page-view', $page);
160160

161+
$pageContent = $this->entityRepo->renderPage($page);
161162
$sidebarTree = $this->entityRepo->getBookChildren($page->book);
162-
$pageNav = $this->entityRepo->getPageNav($page);
163+
$pageNav = $this->entityRepo->getPageNav($pageContent);
163164

164165
Views::add($page);
165166
$this->setPageTitle($page->getShortName());
166-
return view('pages/show', ['page' => $page, 'book' => $page->book,
167-
'current' => $page, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav]);
167+
return view('pages/show', [
168+
'page' => $page,'book' => $page->book,
169+
'current' => $page, 'sidebarTree' => $sidebarTree,
170+
'pageNav' => $pageNav, 'pageContent' => $pageContent]);
168171
}
169172

170173
/**
@@ -430,6 +433,7 @@ public function exportPdf($bookSlug, $pageSlug)
430433
{
431434
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
432435
$pdfContent = $this->exportService->pageToPdf($page);
436+
// return $pdfContent;
433437
return response()->make($pdfContent, 200, [
434438
'Content-Type' => 'application/octet-stream',
435439
'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf'

app/Repos/EntityRepo.php

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public function exists($type, $id)
139139
*/
140140
public function getById($type, $id, $allowDrafts = false)
141141
{
142-
return $this->entityQuery($type, $allowDrafts)->findOrFail($id);
142+
return $this->entityQuery($type, $allowDrafts)->find($id);
143143
}
144144

145145
/**
@@ -796,6 +796,52 @@ protected function formatHtml($htmlText)
796796
return $html;
797797
}
798798

799+
800+
/**
801+
* Render the page for viewing, Parsing and performing features such as page transclusion.
802+
* @param Page $page
803+
* @return mixed|string
804+
*/
805+
public function renderPage(Page $page)
806+
{
807+
$content = $page->html;
808+
$matches = [];
809+
preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
810+
if (count($matches[0]) === 0) return $content;
811+
812+
foreach ($matches[1] as $index => $includeId) {
813+
$splitInclude = explode('#', $includeId, 2);
814+
$pageId = intval($splitInclude[0]);
815+
if (is_nan($pageId)) continue;
816+
817+
$page = $this->getById('page', $pageId);
818+
if ($page === null) {
819+
$content = str_replace($matches[0][$index], '', $content);
820+
continue;
821+
}
822+
823+
if (count($splitInclude) === 1) {
824+
$content = str_replace($matches[0][$index], $page->html, $content);
825+
continue;
826+
}
827+
828+
$doc = new DOMDocument();
829+
$doc->loadHTML(mb_convert_encoding('<body>'.$page->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
830+
$matchingElem = $doc->getElementById($splitInclude[1]);
831+
if ($matchingElem === null) {
832+
$content = str_replace($matches[0][$index], '', $content);
833+
continue;
834+
}
835+
$innerContent = '';
836+
foreach ($matchingElem->childNodes as $childNode) {
837+
$innerContent .= $doc->saveHTML($childNode);
838+
}
839+
$content = str_replace($matches[0][$index], trim($innerContent), $content);
840+
}
841+
842+
return $content;
843+
}
844+
799845
/**
800846
* Get a new draft page instance.
801847
* @param Book $book
@@ -835,15 +881,15 @@ public function searchForImage($imageString)
835881

836882
/**
837883
* Parse the headers on the page to get a navigation menu
838-
* @param Page $page
884+
* @param String $pageContent
839885
* @return array
840886
*/
841-
public function getPageNav(Page $page)
887+
public function getPageNav($pageContent)
842888
{
843-
if ($page->html == '') return [];
889+
if ($pageContent == '') return [];
844890
libxml_use_internal_errors(true);
845891
$doc = new DOMDocument();
846-
$doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8'));
892+
$doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8'));
847893
$xPath = new DOMXPath($doc);
848894
$headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
849895

app/Services/ExportService.php

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
<?php namespace BookStack\Services;
22

33
use BookStack\Page;
4+
use BookStack\Repos\EntityRepo;
45

56
class ExportService
67
{
78

9+
protected $entityRepo;
10+
11+
/**
12+
* ExportService constructor.
13+
* @param $entityRepo
14+
*/
15+
public function __construct(EntityRepo $entityRepo)
16+
{
17+
$this->entityRepo = $entityRepo;
18+
}
19+
820
/**
921
* Convert a page to a self-contained HTML file.
1022
* Includes required CSS & image content. Images are base64 encoded into the HTML.
@@ -14,7 +26,7 @@ class ExportService
1426
public function pageToContainedHtml(Page $page)
1527
{
1628
$cssContent = file_get_contents(public_path('/css/export-styles.css'));
17-
$pageHtml = view('pages/export', ['page' => $page, 'css' => $cssContent])->render();
29+
$pageHtml = view('pages/export', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
1830
return $this->containHtml($pageHtml);
1931
}
2032

@@ -26,7 +38,8 @@ public function pageToContainedHtml(Page $page)
2638
public function pageToPdf(Page $page)
2739
{
2840
$cssContent = file_get_contents(public_path('/css/export-styles.css'));
29-
$pageHtml = view('pages/pdf', ['page' => $page, 'css' => $cssContent])->render();
41+
$pageHtml = view('pages/pdf', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
42+
// return $pageHtml;
3043
$useWKHTML = config('snappy.pdf.binary') !== false;
3144
$containedHtml = $this->containHtml($pageHtml);
3245
if ($useWKHTML) {
@@ -59,9 +72,13 @@ protected function containHtml($htmlContent)
5972
$pathString = $srcString;
6073
}
6174
if ($isLocal && !file_exists($pathString)) continue;
62-
$imageContent = file_get_contents($pathString);
63-
$imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
64-
$newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
75+
try {
76+
$imageContent = file_get_contents($pathString);
77+
$imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
78+
$newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
79+
} catch (\ErrorException $e) {
80+
$newImageString = '';
81+
}
6582
$htmlContent = str_replace($oldImgString, $newImageString, $htmlContent);
6683
}
6784
}
@@ -88,14 +105,14 @@ protected function containHtml($htmlContent)
88105

89106
/**
90107
* Converts the page contents into simple plain text.
91-
* This method filters any bad looking content to
92-
* provide a nice final output.
108+
* This method filters any bad looking content to provide a nice final output.
93109
* @param Page $page
94110
* @return mixed
95111
*/
96112
public function pageToPlainText(Page $page)
97113
{
98-
$text = $page->text;
114+
$html = $this->entityRepo->renderPage($page);
115+
$text = strip_tags($html);
99116
// Replace multiple spaces with single spaces
100117
$text = preg_replace('/\ {2,}/', ' ', $text);
101118
// Reduce multiple horrid whitespace characters.

app/Services/PermissionService.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public function buildJointPermissions()
157157
*/
158158
public function buildJointPermissionsForEntity(Entity $entity)
159159
{
160-
$roles = $this->role->with('jointPermissions')->get();
160+
$roles = $this->role->get();
161161
$entities = collect([$entity]);
162162

163163
if ($entity->isA('book')) {
@@ -177,7 +177,7 @@ public function buildJointPermissionsForEntity(Entity $entity)
177177
*/
178178
public function buildJointPermissionsForEntities(Collection $entities)
179179
{
180-
$roles = $this->role->with('jointPermissions')->get();
180+
$roles = $this->role->get();
181181
$this->deleteManyJointPermissionsForEntities($entities);
182182
$this->createManyJointPermissions($entities, $roles);
183183
}
@@ -564,6 +564,7 @@ public function filterRestrictedEntityRelations($query, $tableName, $entityIdCol
564564
});
565565
});
566566
});
567+
$this->clean();
567568
return $q;
568569
}
569570

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"laravel-elixir": "^6.0.0-11",
1717
"laravel-elixir-browserify-official": "^0.1.3",
1818
"marked": "^0.3.5",
19-
"moment": "^2.12.0",
20-
"zeroclipboard": "^2.2.0"
19+
"moment": "^2.12.0"
20+
},
21+
"dependencies": {
22+
"clipboard": "^1.5.16"
2123
}
2224
}

public/ZeroClipboard.swf

-6.43 KB
Binary file not shown.

resources/assets/js/pages/page-show.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,51 @@
11
"use strict";
22
// Configure ZeroClipboard
3-
import ZeroClipBoard from "zeroclipboard";
3+
import Clipboard from "clipboard";
44

55
export default window.setupPageShow = function (pageId) {
66

77
// Set up pointer
88
let $pointer = $('#pointer').detach();
9+
let pointerShowing = false;
910
let $pointerInner = $pointer.children('div.pointer').first();
1011
let isSelection = false;
12+
let pointerModeLink = true;
13+
let pointerSectionId = '';
1114

1215
// Select all contents on input click
1316
$pointer.on('click', 'input', function (e) {
1417
$(this).select();
1518
e.stopPropagation();
1619
});
1720

18-
// Set up copy-to-clipboard
19-
ZeroClipBoard.config({
20-
swfPath: window.baseUrl('/ZeroClipboard.swf')
21+
// Pointer mode toggle
22+
$pointer.on('click', 'span.icon', event => {
23+
let $icon = $(event.currentTarget);
24+
pointerModeLink = !pointerModeLink;
25+
$icon.html(pointerModeLink ? '<i class="zmdi zmdi-link"></i>' : '<i class="zmdi zmdi-square-down"></i>');
26+
updatePointerContent();
2127
});
22-
new ZeroClipBoard($pointer.find('button').first()[0]);
28+
29+
// Set up clipboard
30+
let clipboard = new Clipboard('#pointer button');
2331

2432
// Hide pointer when clicking away
25-
$(document.body).find('*').on('click focus', function (e) {
26-
if (!isSelection) {
27-
$pointer.detach();
28-
}
33+
$(document.body).find('*').on('click focus', event => {
34+
if (!pointerShowing || isSelection) return;
35+
let target = $(event.target);
36+
if (target.is('.zmdi') || $(event.target).closest('#pointer').length === 1) return;
37+
38+
$pointer.detach();
39+
pointerShowing = false;
2940
});
3041

42+
function updatePointerContent() {
43+
let inputText = pointerModeLink ? window.baseUrl(`/link/${pageId}#${pointerSectionId}`) : `{{@${pageId}#${pointerSectionId}}}`;
44+
if (pointerModeLink && inputText.indexOf('http') !== 0) inputText = window.location.protocol + "//" + window.location.host + inputText;
45+
46+
$pointer.find('input').val(inputText);
47+
}
48+
3149
// Show pointer when selecting a single block of tagged content
3250
$('.page-content [id^="bkmrk"]').on('mouseup keyup', function (e) {
3351
e.stopPropagation();
@@ -36,12 +54,12 @@ export default window.setupPageShow = function (pageId) {
3654

3755
// Show pointer and set link
3856
let $elem = $(this);
39-
let link = window.baseUrl('/link/' + pageId + '#' + $elem.attr('id'));
40-
if (link.indexOf('http') !== 0) link = window.location.protocol + "//" + window.location.host + link;
41-
$pointer.find('input').val(link);
42-
$pointer.find('button').first().attr('data-clipboard-text', link);
57+
pointerSectionId = $elem.attr('id');
58+
updatePointerContent();
59+
4360
$elem.before($pointer);
4461
$pointer.show();
62+
pointerShowing = true;
4563

4664
// Set pointer to sit near mouse-up position
4765
let pointerLeftOffset = (e.pageX - $elem.offset().left - ($pointerInner.width() / 2));

resources/assets/sass/_blocks.scss

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,6 @@
136136
background-color: #EEE;
137137
padding: $-s;
138138
display: block;
139-
> * {
140-
display: inline-block;
141-
}
142139
&:before {
143140
font-family: 'Material-Design-Iconic-Font';
144141
padding-right: $-s;

resources/assets/sass/_buttons.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,4 @@ $button-border-radius: 2px;
108108
cursor: default;
109109
box-shadow: none;
110110
}
111-
}
112-
111+
}

resources/assets/sass/_components.scss

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
7070
#entity-selector-wrap .popup-body .form-group {
7171
margin: 0;
7272
}
73-
//body.ie #entity-selector-wrap .popup-body .form-group {
74-
// min-height: 60vh;
75-
//}
7673

7774
.image-manager-body {
7875
min-height: 70vh;

0 commit comments

Comments
 (0)