mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-05-15 13:10:29 +00:00
Added tags to page display and simplified editing flow
Tags now save with the page content data
This commit is contained in:
parent
78564ec61d
commit
7932069535
6 changed files with 200 additions and 144 deletions
app/Repos
resources
assets/sass
views/pages
|
@ -14,14 +14,17 @@ class PageRepo extends EntityRepo
|
||||||
{
|
{
|
||||||
|
|
||||||
protected $pageRevision;
|
protected $pageRevision;
|
||||||
|
protected $tagRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PageRepo constructor.
|
* PageRepo constructor.
|
||||||
* @param PageRevision $pageRevision
|
* @param PageRevision $pageRevision
|
||||||
|
* @param TagRepo $tagRepo
|
||||||
*/
|
*/
|
||||||
public function __construct(PageRevision $pageRevision)
|
public function __construct(PageRevision $pageRevision, TagRepo $tagRepo)
|
||||||
{
|
{
|
||||||
$this->pageRevision = $pageRevision;
|
$this->pageRevision = $pageRevision;
|
||||||
|
$this->tagRepo = $tagRepo;
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +311,11 @@ class PageRepo extends EntityRepo
|
||||||
$page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id);
|
$page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save page tags if present
|
||||||
|
if(isset($input['tags'])) {
|
||||||
|
$this->tagRepo->saveTagsToEntity($page, $input['tags']);
|
||||||
|
}
|
||||||
|
|
||||||
// Update with new details
|
// Update with new details
|
||||||
$userId = auth()->user()->id;
|
$userId = auth()->user()->id;
|
||||||
$page->fill($input);
|
$page->fill($input);
|
||||||
|
|
|
@ -122,9 +122,153 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
// Attribute form
|
||||||
&:hover a.link-hook {
|
.floating-toolbox {
|
||||||
opacity: 1;
|
background-color: #FFF;
|
||||||
transform: translate3d(0, 0, 0);
|
border: 1px solid #DDD;
|
||||||
|
right: $-xl*2;
|
||||||
|
z-index: 99;
|
||||||
|
width: 48px;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
transition: width ease-in-out 180ms;
|
||||||
|
margin-top: -1px;
|
||||||
|
&.open {
|
||||||
|
width: 480px;
|
||||||
|
}
|
||||||
|
[toolbox-toggle] i {
|
||||||
|
transition: transform ease-in-out 180ms;
|
||||||
|
}
|
||||||
|
[toolbox-toggle] {
|
||||||
|
transition: background-color ease-in-out 180ms;
|
||||||
|
}
|
||||||
|
&.open [toolbox-toggle] {
|
||||||
|
background-color: rgba(255, 0, 0, 0.29);
|
||||||
|
}
|
||||||
|
&.open [toolbox-toggle] i {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
> div {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
display: block;
|
||||||
|
border-right: 1px solid #DDD;
|
||||||
|
width: 54px;
|
||||||
|
flex: 0;
|
||||||
|
}
|
||||||
|
.tabs i {
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.tabs > span {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: $-s $-m;
|
||||||
|
font-size: 13.5px;
|
||||||
|
line-height: 1.6;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
&.open .tabs > span.active {
|
||||||
|
color: #444;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
div[tab-content] {
|
||||||
|
padding-bottom: 45px;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
div[tab-content] .padded {
|
||||||
|
flex: 1;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: $-m 0 0 0;
|
||||||
|
padding: 0 $-l $-s $-l;
|
||||||
|
}
|
||||||
|
.tags input {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
.tags td {
|
||||||
|
padding-right: $-s;
|
||||||
|
padding-top: $-s;
|
||||||
|
}
|
||||||
|
button.pos {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: $-s;
|
||||||
|
height: 45px;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
&:hover{
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.handle {
|
||||||
|
user-select: none;
|
||||||
|
cursor: move;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[tab-content] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-display {
|
||||||
|
margin: $-xl $-xs;
|
||||||
|
border: 1px solid #DDD;
|
||||||
|
min-width: 180px;
|
||||||
|
max-width: 320px;
|
||||||
|
opacity: 0.7;
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: #666;
|
||||||
|
margin-left: $-s;
|
||||||
|
}
|
||||||
|
.heading {
|
||||||
|
padding: $-xs $-s;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid #DDD;
|
||||||
|
padding: $-xs $-s;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
.tag-value {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
td i {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
padding: $-s;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -207,113 +207,3 @@ $btt-size: 40px;
|
||||||
color: #EEE;
|
color: #EEE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attribute form
|
|
||||||
.floating-toolbox {
|
|
||||||
background-color: #FFF;
|
|
||||||
border: 1px solid #DDD;
|
|
||||||
right: $-xl*2;
|
|
||||||
z-index: 99;
|
|
||||||
width: 48px;
|
|
||||||
overflow: hidden;
|
|
||||||
align-items: stretch;
|
|
||||||
flex-direction: row;
|
|
||||||
display: flex;
|
|
||||||
transition: width ease-in-out 180ms;
|
|
||||||
margin-top: -1px;
|
|
||||||
&.open {
|
|
||||||
width: 480px;
|
|
||||||
}
|
|
||||||
[toolbox-toggle] i {
|
|
||||||
transition: transform ease-in-out 180ms;
|
|
||||||
}
|
|
||||||
[toolbox-toggle] {
|
|
||||||
transition: background-color ease-in-out 180ms;
|
|
||||||
}
|
|
||||||
&.open [toolbox-toggle] {
|
|
||||||
background-color: rgba(255, 0, 0, 0.29);
|
|
||||||
}
|
|
||||||
&.open [toolbox-toggle] i {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
> div {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.tabs {
|
|
||||||
display: block;
|
|
||||||
border-right: 1px solid #DDD;
|
|
||||||
width: 54px;
|
|
||||||
flex: 0;
|
|
||||||
}
|
|
||||||
.tabs i {
|
|
||||||
color: rgba(0, 0, 0, 0.5);
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.tabs > span {
|
|
||||||
display: block;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: $-s $-m;
|
|
||||||
font-size: 13.5px;
|
|
||||||
line-height: 1.6;
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
&.open .tabs > span.active {
|
|
||||||
color: #444;
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
div[tab-content] {
|
|
||||||
padding-bottom: 45px;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
div[tab-content] .padded {
|
|
||||||
flex: 1;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-size: 24px;
|
|
||||||
margin: $-m 0 0 0;
|
|
||||||
padding: 0 $-l $-s $-l;
|
|
||||||
}
|
|
||||||
.tags input {
|
|
||||||
max-width: 100%;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 50px;
|
|
||||||
}
|
|
||||||
.tags td {
|
|
||||||
padding-right: $-s;
|
|
||||||
padding-top: $-s;
|
|
||||||
}
|
|
||||||
button.pos {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: $-s;
|
|
||||||
height: 45px;
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 0;
|
|
||||||
&:hover{
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.handle {
|
|
||||||
user-select: none;
|
|
||||||
cursor: move;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[tab-content] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
|
@ -14,9 +14,9 @@
|
||||||
<input type="hidden" name="_method" value="PUT">
|
<input type="hidden" name="_method" value="PUT">
|
||||||
@endif
|
@endif
|
||||||
@include('pages/form', ['model' => $page])
|
@include('pages/form', ['model' => $page])
|
||||||
|
@include('pages/form-toolbox')
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@include('pages/form-toolbox')
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
|
@include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
|
||||||
|
|
|
@ -5,17 +5,16 @@
|
||||||
<span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
|
<span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
|
<div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
|
||||||
<form ng-submit="saveTags()" >
|
|
||||||
<h4>Page Tags</h4>
|
<h4>Page Tags</h4>
|
||||||
<div class="padded tags">
|
<div class="padded tags">
|
||||||
<p class="muted small">Add some tags to better categorise your content. <br> You can assign a value to a tag for more in-depth organisation.</p>
|
<p class="muted small">Add some tags to better categorise your content. <br> You can assign a value to a tag for more in-depth organisation.</p>
|
||||||
<table class="no-style" style="width: 100%;">
|
<table class="no-style" style="width: 100%;">
|
||||||
<tbody ui-sortable="sortOptions" ng-model="tags" >
|
<tbody ui-sortable="sortOptions" ng-model="tags" >
|
||||||
<tr ng-repeat="tag in tags">
|
<tr ng-repeat="tag in tags track by $index">
|
||||||
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
|
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
|
||||||
<td><input class="outline" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td>
|
<td><input class="outline" ng-attr-name="tags[@{{$index}}][name]" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td>
|
||||||
<td><input class="outline" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td>
|
<td><input class="outline" ng-attr-name="tags[@{{$index}}][value]" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td>
|
||||||
<td width="10" class="text-center text-neg" style="padding: 0;" ng-click="removeTag(tag)"><i class="zmdi zmdi-close"></i></td>
|
<td width="10" ng-show="tags.length != 1" class="text-center text-neg" style="padding: 0;" ng-click="removeTag(tag)"><i class="zmdi zmdi-close"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -31,7 +30,5 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<button class="button pos" type="submit">Save Tags</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,5 +1,22 @@
|
||||||
<div ng-non-bindable>
|
<div ng-non-bindable>
|
||||||
<h1 id="bkmrk-page-title">{{$page->name}}</h1>
|
|
||||||
|
<h1 id="bkmrk-page-title" class="float left">{{$page->name}}</h1>
|
||||||
|
|
||||||
|
@if(count($page->tags) > 0)
|
||||||
|
<div class="tag-display float right">
|
||||||
|
<div class="heading primary-background-light">Page Tags</div>
|
||||||
|
<table>
|
||||||
|
@foreach($page->tags as $tag)
|
||||||
|
<tr class="tag">
|
||||||
|
<td @if(!$tag->value) colspan="2" @endif> {{ $tag->name }}</td>
|
||||||
|
@if($tag->value) <td class="tag-value">{{$tag->value}}</td> @endif
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div style="clear:left;"></div>
|
||||||
|
|
||||||
{!! $page->html !!}
|
{!! $page->html !!}
|
||||||
</div>
|
</div>
|
Loading…
Add table
Add a link
Reference in a new issue