mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-05-24 15:49:59 +08:00
Converted entity-dash from vue to a component
This commit is contained in:
@ -1,16 +1,22 @@
|
|||||||
# JavaScript Components
|
# 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
|
#### Defining a Component in JS
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class Dropdown {
|
class Dropdown {
|
||||||
setup() {
|
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
|
#### Using a Component in HTML
|
||||||
|
|
||||||
A component is used like so:
|
A component is used like so:
|
||||||
|
59
resources/js/components/entity-search.js
Normal file
59
resources/js/components/entity-search.js
Normal 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;
|
@ -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
|
|
||||||
};
|
|
@ -4,14 +4,12 @@ function exists(id) {
|
|||||||
return document.getElementById(id) !== null;
|
return document.getElementById(id) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
import entityDashboard from "./entity-dashboard";
|
|
||||||
import imageManager from "./image-manager";
|
import imageManager from "./image-manager";
|
||||||
import tagManager from "./tag-manager";
|
import tagManager from "./tag-manager";
|
||||||
import attachmentManager from "./attachment-manager";
|
import attachmentManager from "./attachment-manager";
|
||||||
import pageEditor from "./page-editor";
|
import pageEditor from "./page-editor";
|
||||||
|
|
||||||
let vueMapping = {
|
let vueMapping = {
|
||||||
'entity-dashboard': entityDashboard,
|
|
||||||
'image-manager': imageManager,
|
'image-manager': imageManager,
|
||||||
'tag-manager': tagManager,
|
'tag-manager': tagManager,
|
||||||
'attachment-manager': attachmentManager,
|
'attachment-manager': attachmentManager,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
@extends('tri-layout')
|
@extends('tri-layout')
|
||||||
|
|
||||||
@section('container-attrs')
|
@section('container-attrs')
|
||||||
id="entity-dashboard"
|
component="entity-search"
|
||||||
entity-id="{{ $book->id }}"
|
option:entity-search:entity-id="{{ $book->id }}"
|
||||||
entity-type="book"
|
option:entity-search:entity-type="book"
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
@section('body')
|
@section('body')
|
||||||
@ -15,11 +15,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="content-wrap card">
|
<main class="content-wrap card">
|
||||||
<h1 class="break-text" v-pre>{{$book->name}}</h1>
|
<h1 class="break-text">{{$book->name}}</h1>
|
||||||
<div class="book-content" v-show="!searching">
|
<div refs="entity-search@contentView" class="book-content">
|
||||||
<p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>
|
<p class="text-muted">{!! nl2br(e($book->description)) !!}</p>
|
||||||
@if(count($bookChildren) > 0)
|
@if(count($bookChildren) > 0)
|
||||||
<div class="entity-list book-contents" v-pre>
|
<div class="entity-list book-contents">
|
||||||
@foreach($bookChildren as $childElement)
|
@foreach($bookChildren as $childElement)
|
||||||
@if($childElement->isA('chapter'))
|
@if($childElement->isA('chapter'))
|
||||||
@include('chapters.list-item', ['chapter' => $childElement])
|
@include('chapters.list-item', ['chapter' => $childElement])
|
||||||
@ -29,7 +29,7 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="mt-xl" v-pre>
|
<div class="mt-xl">
|
||||||
<hr>
|
<hr>
|
||||||
<p class="text-muted italic mb-m mt-xl">{{ trans('entities.books_empty_contents') }}</p>
|
<p class="text-muted italic mb-m mt-xl">{{ trans('entities.books_empty_contents') }}</p>
|
||||||
|
|
||||||
@ -52,7 +52,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@include('partials.entity-dashboard-search-results')
|
@include('partials.entity-search-results')
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
@ -126,7 +126,7 @@
|
|||||||
|
|
||||||
@section('left')
|
@section('left')
|
||||||
|
|
||||||
@include('partials.entity-dashboard-search-box')
|
@include('partials.entity-search-form', ['label' => trans('entities.books_search_this')])
|
||||||
|
|
||||||
@if($book->tags->count() > 0)
|
@if($book->tags->count() > 0)
|
||||||
<div class="mb-xl">
|
<div class="mb-xl">
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
@extends('tri-layout')
|
@extends('tri-layout')
|
||||||
|
|
||||||
@section('container-attrs')
|
@section('container-attrs')
|
||||||
id="entity-dashboard"
|
component="entity-search"
|
||||||
entity-id="{{ $chapter->id }}"
|
option:entity-search:entity-id="{{ $chapter->id }}"
|
||||||
entity-type="chapter"
|
option:entity-search:entity-type="chapter"
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
@section('body')
|
@section('body')
|
||||||
@ -16,17 +16,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="content-wrap card">
|
<main class="content-wrap card">
|
||||||
<h1 class="break-text" v-pre>{{ $chapter->name }}</h1>
|
<h1 class="break-text">{{ $chapter->name }}</h1>
|
||||||
<div class="chapter-content" v-show="!searching">
|
<div refs="entity-search@contentView" class="chapter-content">
|
||||||
<p v-pre class="text-muted break-text">{!! nl2br(e($chapter->description)) !!}</p>
|
<p class="text-muted break-text">{!! nl2br(e($chapter->description)) !!}</p>
|
||||||
@if(count($pages) > 0)
|
@if(count($pages) > 0)
|
||||||
<div v-pre class="entity-list book-contents">
|
<div class="entity-list book-contents">
|
||||||
@foreach($pages as $page)
|
@foreach($pages as $page)
|
||||||
@include('pages.list-item', ['page' => $page])
|
@include('pages.list-item', ['page' => $page])
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="mt-xl" v-pre>
|
<div class="mt-xl">
|
||||||
<hr>
|
<hr>
|
||||||
<p class="text-muted italic mb-m mt-xl">{{ trans('entities.chapters_empty') }}</p>
|
<p class="text-muted italic mb-m mt-xl">{{ trans('entities.chapters_empty') }}</p>
|
||||||
|
|
||||||
@ -49,7 +49,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@include('partials.entity-dashboard-search-results')
|
@include('partials.entity-search-results')
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
@ -130,7 +130,7 @@
|
|||||||
|
|
||||||
@section('left')
|
@section('left')
|
||||||
|
|
||||||
@include('partials.entity-dashboard-search-box')
|
@include('partials.entity-search-form', ['label' => trans('entities.chapters_search_this')])
|
||||||
|
|
||||||
@if($chapter->tags->count() > 0)
|
@if($chapter->tags->count() > 0)
|
||||||
<div class="mb-xl">
|
<div class="mb-xl">
|
||||||
|
@ -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>
|
|
@ -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>
|
|
10
resources/views/partials/entity-search-form.blade.php
Normal file
10
resources/views/partials/entity-search-form.blade.php
Normal 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>
|
15
resources/views/partials/entity-search-results.blade.php
Normal file
15
resources/views/partials/entity-search-results.blade.php
Normal 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>
|
Reference in New Issue
Block a user