Converted entity-dash from vue to a component

This commit is contained in:
Dan Brown 2020-06-28 21:15:00 +01:00
parent a5fa745749
commit 10305a4446
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
10 changed files with 111 additions and 90 deletions

View File

@ -1,16 +1,22 @@
# JavaScript Components
This document details the format for JavaScript components in BookStack.
This document details the format for JavaScript components in BookStack. This is a really simple class-based setup with a few helpers provided.
#### Defining a Component in JS
```js
class Dropdown {
setup() {
this.toggle = this.$refs.toggle;
this.menu = this.$refs.menu;
this.speed = parseInt(this.$opts.speed);
}
}
```
All usage of $refs, $manyRefs and $opts should be done at the top of the `setup` function so any requirements can be easily seen.
#### Using a Component in HTML
A component is used like so:

View File

@ -0,0 +1,59 @@
import {onSelect} from "../services/dom";
/**
* Class EntitySearch
* @extends {Component}
*/
class EntitySearch {
setup() {
this.entityId = this.$opts.entityId;
this.entityType = this.$opts.entityType;
this.contentView = this.$refs.contentView;
this.searchView = this.$refs.searchView;
this.searchResults = this.$refs.searchResults;
this.searchInput = this.$refs.searchInput;
this.searchForm = this.$refs.searchForm;
this.clearButton = this.$refs.clearButton;
this.loadingBlock = this.$refs.loadingBlock;
this.setupListeners();
}
setupListeners() {
this.searchInput.addEventListener('change', this.runSearch.bind(this));
this.searchForm.addEventListener('submit', e => {
e.preventDefault();
this.runSearch();
});
onSelect(this.clearButton, this.clearSearch.bind(this));
}
runSearch() {
const term = this.searchInput.value.trim();
if (term.length === 0) {
return this.clearSearch();
}
this.searchView.classList.remove('hidden');
this.contentView.classList.add('hidden');
this.loadingBlock.classList.remove('hidden');
const url = window.baseUrl(`/search/${this.entityType}/${this.entityId}`);
window.$http.get(url, {term}).then(resp => {
this.searchResults.innerHTML = resp.data;
}).catch(console.error).then(() => {
this.loadingBlock.classList.add('hidden');
});
}
clearSearch() {
this.searchView.classList.add('hidden');
this.contentView.classList.remove('hidden');
this.loadingBlock.classList.add('hidden');
this.searchInput.value = '';
}
}
export default EntitySearch;

View File

@ -1,44 +0,0 @@
let data = {
id: null,
type: '',
searching: false,
searchTerm: '',
searchResults: '',
};
let computed = {
};
let methods = {
searchBook() {
if (this.searchTerm.trim().length === 0) return;
this.searching = true;
this.searchResults = '';
let url = window.baseUrl(`/search/${this.type}/${this.id}`);
url += `?term=${encodeURIComponent(this.searchTerm)}`;
this.$http.get(url).then(resp => {
this.searchResults = resp.data;
});
},
checkSearchForm() {
this.searching = this.searchTerm > 0;
},
clearSearch() {
this.searching = false;
this.searchTerm = '';
}
};
function mounted() {
this.id = Number(this.$el.getAttribute('entity-id'));
this.type = this.$el.getAttribute('entity-type');
}
export default {
data, computed, methods, mounted
};

View File

@ -4,14 +4,12 @@ function exists(id) {
return document.getElementById(id) !== null;
}
import entityDashboard from "./entity-dashboard";
import imageManager from "./image-manager";
import tagManager from "./tag-manager";
import attachmentManager from "./attachment-manager";
import pageEditor from "./page-editor";
let vueMapping = {
'entity-dashboard': entityDashboard,
'image-manager': imageManager,
'tag-manager': tagManager,
'attachment-manager': attachmentManager,

View File

@ -1,9 +1,9 @@
@extends('tri-layout')
@section('container-attrs')
id="entity-dashboard"
entity-id="{{ $book->id }}"
entity-type="book"
component="entity-search"
option:entity-search:entity-id="{{ $book->id }}"
option:entity-search:entity-type="book"
@stop
@section('body')
@ -15,11 +15,11 @@
</div>
<main class="content-wrap card">
<h1 class="break-text" v-pre>{{$book->name}}</h1>
<div class="book-content" v-show="!searching">
<p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>
<h1 class="break-text">{{$book->name}}</h1>
<div refs="entity-search@contentView" class="book-content">
<p class="text-muted">{!! nl2br(e($book->description)) !!}</p>
@if(count($bookChildren) > 0)
<div class="entity-list book-contents" v-pre>
<div class="entity-list book-contents">
@foreach($bookChildren as $childElement)
@if($childElement->isA('chapter'))
@include('chapters.list-item', ['chapter' => $childElement])
@ -29,7 +29,7 @@
@endforeach
</div>
@else
<div class="mt-xl" v-pre>
<div class="mt-xl">
<hr>
<p class="text-muted italic mb-m mt-xl">{{ trans('entities.books_empty_contents') }}</p>
@ -52,7 +52,7 @@
@endif
</div>
@include('partials.entity-dashboard-search-results')
@include('partials.entity-search-results')
</main>
@stop
@ -126,7 +126,7 @@
@section('left')
@include('partials.entity-dashboard-search-box')
@include('partials.entity-search-form', ['label' => trans('entities.books_search_this')])
@if($book->tags->count() > 0)
<div class="mb-xl">

View File

@ -1,9 +1,9 @@
@extends('tri-layout')
@section('container-attrs')
id="entity-dashboard"
entity-id="{{ $chapter->id }}"
entity-type="chapter"
component="entity-search"
option:entity-search:entity-id="{{ $chapter->id }}"
option:entity-search:entity-type="chapter"
@stop
@section('body')
@ -16,17 +16,17 @@
</div>
<main class="content-wrap card">
<h1 class="break-text" v-pre>{{ $chapter->name }}</h1>
<div class="chapter-content" v-show="!searching">
<p v-pre class="text-muted break-text">{!! nl2br(e($chapter->description)) !!}</p>
<h1 class="break-text">{{ $chapter->name }}</h1>
<div refs="entity-search@contentView" class="chapter-content">
<p class="text-muted break-text">{!! nl2br(e($chapter->description)) !!}</p>
@if(count($pages) > 0)
<div v-pre class="entity-list book-contents">
<div class="entity-list book-contents">
@foreach($pages as $page)
@include('pages.list-item', ['page' => $page])
@endforeach
</div>
@else
<div class="mt-xl" v-pre>
<div class="mt-xl">
<hr>
<p class="text-muted italic mb-m mt-xl">{{ trans('entities.chapters_empty') }}</p>
@ -49,7 +49,7 @@
@endif
</div>
@include('partials.entity-dashboard-search-results')
@include('partials.entity-search-results')
</main>
@stop
@ -130,7 +130,7 @@
@section('left')
@include('partials.entity-dashboard-search-box')
@include('partials.entity-search-form', ['label' => trans('entities.chapters_search_this')])
@if($chapter->tags->count() > 0)
<div class="mb-xl">

View File

@ -1,8 +0,0 @@
<div class="mb-xl">
<form v-on:submit.prevent="searchBook" class="search-box flexible" role="search">
<input v-model="searchTerm" v-on:change="checkSearchForm" type="text" aria-label="{{ trans('entities.books_search_this') }}" name="term" placeholder="{{ trans('entities.books_search_this') }}">
<button type="submit" aria-label="{{ trans('common.search') }}">@icon('search')</button>
<button v-if="searching" v-cloak class="search-box-cancel text-neg" v-on:click="clearSearch"
type="button" aria-label="{{ trans('common.search_clear') }}">@icon('close')</button>
</form>
</div>

View File

@ -1,15 +0,0 @@
<div class="search-results" v-cloak v-show="searching">
<div class="grid half v-center">
<h3 class="text-muted px-none">
{{ trans('entities.search_results') }}
</h3>
<div class="text-right">
<a v-if="searching" v-on:click="clearSearch" class="button outline">{{ trans('entities.search_clear') }}</a>
</div>
</div>
<div v-if="!searchResults">
@include('partials.loading-icon')
</div>
<div class="book-contents" v-html="searchResults"></div>
</div>

View File

@ -0,0 +1,10 @@
{{--
@label - Placeholder/aria-label text
--}}
<div class="mb-xl">
<form refs="entity-search@searchForm" class="search-box flexible" role="search">
<input refs="entity-search@searchInput" type="text"
aria-label="{{ $label }}" name="term" placeholder="{{ $label }}">
<button type="submit" aria-label="{{ trans('common.search') }}">@icon('search')</button>
</form>
</div>

View File

@ -0,0 +1,15 @@
<div refs="entity-search@searchView" class="search-results hidden">
<div class="grid half v-center">
<h3 class="text-muted px-none">
{{ trans('entities.search_results') }}
</h3>
<div class="text-right">
<a refs="entity-search@clearButton" class="button outline">{{ trans('entities.search_clear') }}</a>
</div>
</div>
<div refs="entity-search@loadingBlock">
@include('partials.loading-icon')
</div>
<div class="book-contents" refs="entity-search@searchResults"></div>
</div>