return $this->morphMany(View::class, 'viewable');
}
+ public function viewCountQuery()
+ {
+ return $this->views()->selectRaw('viewable_id, sum(views) as view_count')->groupBy('viewable_id');
+ }
+
/**
* Get the Tag models that have been user assigned to this entity.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
return $this->{$this->textField};
}
+ /**
+ * Get an excerpt of this entity's descriptive content to the specified length.
+ * @param int $length
+ * @return mixed
+ */
+ public function getExcerpt(int $length = 100)
+ {
+ $text = $this->getText();
+ if (mb_strlen($text) > $length) {
+ $text = mb_substr($text, 0, $length-3) . '...';
+ }
+ return trim($text);
+ }
+
/**
* Return a generalised, common raw query that can be 'unioned' across entities.
* @return string
return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent);
}
- /**
- * Get an excerpt of this page's content to the specified length.
- * @param int $length
- * @return mixed
- */
- public function getExcerpt($length = 100)
- {
- $text = strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text;
- return mb_convert_encoding($text, 'UTF-8');
- }
-
/**
* Return a generalised, common raw query that can be 'unioned' across entities.
* @param bool $withContent
use BookStack\Exceptions\NotifyException;
use BookStack\Uploads\AttachmentService;
use DOMDocument;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
* Get all entities in a paginated format
* @param $type
* @param int $count
+ * @param string $sort
+ * @param string $order
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
- public function getAllPaginated($type, $count = 10)
+ public function getAllPaginated($type, int $count = 10, string $sort = 'name', string $order = 'asc')
{
- return $this->entityQuery($type)->orderBy('name', 'asc')->paginate($count);
+ $query = $this->entityQuery($type);
+ $query = $this->addSortToQuery($query, $sort, $order);
+ return $query->paginate($count);
+ }
+
+ protected function addSortToQuery(Builder $query, string $sort = 'name', string $order = 'asc')
+ {
+ $order = ($order === 'asc') ? 'asc' : 'desc';
+ $propertySorts = ['name', 'created_at', 'updated_at'];
+
+ if (in_array($sort, $propertySorts)) {
+ return $query->orderBy($sort, $order);
+ }
+
+ return $query;
}
/**
*/
public function index()
{
- $books = $this->entityRepo->getAllPaginated('book', 18);
+ $view = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books'));
+ $sort = setting()->getUser($this->currentUser, 'books_sort', 'name');
+ $order = setting()->getUser($this->currentUser, 'books_sort_order', 'asc');
+ $sortOptions = [
+ 'name' => trans('common.sort_name'),
+ 'created_at' => trans('common.sort_created_at'),
+ 'updated_at' => trans('common.sort_updated_at'),
+ ];
+
+ $books = $this->entityRepo->getAllPaginated('book', 18, $sort, $order);
$recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
$popular = $this->entityRepo->getPopular('book', 4, 0);
$new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
- $booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
+
$this->setPageTitle(trans('entities.books'));
return view('books/index', [
'books' => $books,
'recents' => $recents,
'popular' => $popular,
'new' => $new,
- 'booksViewType' => $booksViewType
+ 'view' => $view,
+ 'sort' => $sort,
+ 'order' => $order,
+ 'sortOptions' => $sortOptions,
]);
}
return true;
}
+ /**
+ * Check if the current user has a permission or bypass if the provided user
+ * id matches the current user.
+ * @param string $permissionName
+ * @param int $userId
+ * @return bool
+ */
+ protected function checkPermissionOrCurrentUser(string $permissionName, int $userId)
+ {
+ return $this->checkPermissionOr($permissionName, function() use ($userId) {
+ return $userId === $this->currentUser->id;
+ });
+ }
+
/**
* Send back a json error message.
* @param string $messageText
*/
public function switchBookView($id, Request $request)
{
- $this->checkPermissionOr('users-manage', function () use ($id) {
- return $this->currentUser->id == $id;
- });
+ return $this->switchViewType($id, $request, 'books');
+ }
+
+ /**
+ * Update the user's preferred shelf-list display setting.
+ * @param $id
+ * @param Request $request
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function switchShelfView($id, Request $request)
+ {
+ return $this->switchViewType($id, $request, 'bookshelves');
+ }
+
+ /**
+ * For a type of list, switch with stored view type for a user.
+ * @param integer $userId
+ * @param Request $request
+ * @param string $listName
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ protected function switchViewType($userId, Request $request, string $listName)
+ {
+ $this->checkPermissionOrCurrentUser('users-manage', $userId);
$viewType = $request->get('view_type');
if (!in_array($viewType, ['grid', 'list'])) {
$viewType = 'list';
}
- $user = $this->user->findOrFail($id);
- setting()->putUser($user, 'books_view_type', $viewType);
+ $user = $this->userRepo->getById($id);
+ $key = $listName . '_view_type';
+ setting()->putUser($user, $key, $viewType);
- return redirect()->back(302, [], "/settings/users/$id");
+ return redirect()->back(302, [], "/settings/users/$userId");
}
/**
- * Update the user's preferred shelf-list display setting.
+ * Change the stored sort type for the books view.
* @param $id
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
- public function switchShelfView($id, Request $request)
+ public function changeBooksSort($id, Request $request)
{
- $this->checkPermissionOr('users-manage', function () use ($id) {
- return $this->currentUser->id == $id;
- });
+ // TODO - Test this endpoint
+ return $this->changeListSort($id, $request, 'books');
+ }
- $viewType = $request->get('view_type');
- if (!in_array($viewType, ['grid', 'list'])) {
- $viewType = 'list';
+ /**
+ * Changed the stored preference for a list sort order.
+ * @param int $userId
+ * @param Request $request
+ * @param string $listName
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ protected function changeListSort(int $userId, Request $request, string $listName)
+ {
+ $this->checkPermissionOrCurrentUser('users-manage', $userId);
+
+ $sort = $request->get('sort');
+ if (!in_array($sort, ['name', 'created_at', 'updated_at'])) {
+ $sort = 'name';
}
- $user = $this->userRepo->getById($id);
- setting()->putUser($user, 'bookshelves_view_type', $viewType);
+ $order = $request->get('order');
+ if (!in_array($order, ['asc', 'desc'])) {
+ $order = 'asc';
+ }
- return redirect()->back(302, [], "/settings/users/$id");
+ $user = $this->user->findOrFail($userId);
+ $sortKey = $listName . '_sort';
+ $orderKey = $listName . '_sort_order';
+ setting()->putUser($user, $sortKey, $sort);
+ setting()->putUser($user, $orderKey, $order);
+
+ return redirect()->back(302, [], "/settings/users/$userId");
}
+
}
*/
public function getUser($user, $key, $default = false)
{
+ if ($user->isDefault()) {
+ return session()->get($key, $default);
+ }
return $this->get($this->userKey($user->id, $key), $default);
}
*/
public function putUser($user, $key, $value)
{
+ if ($user->isDefault()) {
+ return session()->put($key, $value);
+ }
return $this->put($this->userKey($user->id, $key), $value);
}
-<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
- <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
- <path d="M0 0h24v24H0z" fill="none"/>
-</svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 13.3h-5.7V19h-2.6v-5.7H5v-2.6h5.7V5h2.6v5.7H19z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
\ No newline at end of file
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M1.088 2.566h17.42v17.42H1.088z" fill="none"/><path d="M4 20.058h15.892V22H4z"/><path d="M2.902 1.477h17.42v17.42H2.903z" fill="none"/><g><path d="M6.658 3.643V18h-2.38V3.643zM11.326 3.643V18H8.947V3.643zM14.722 3.856l5.613 13.214-2.19.93-5.613-13.214z"/></g></svg>
-
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M1.088 2.566h17.42v17.42H1.088z" fill="none"/><path d="M4 20.058h15.892V22H4z"/><path d="M2.902 1.477h17.42v17.42H2.903z" fill="none"/><g><path d="M6.658 3.643V18h-2.38V3.643zM11.326 3.643V18H8.947V3.643zM14.722 3.856l5.613 13.214-2.19.93-5.613-13.214z"/></g></svg>
\ No newline at end of file
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
\ No newline at end of file
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.034l6.57-6.554h-4.927V2.966h-3.286V14.48H5.43z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
\ No newline at end of file
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2.966L5.43 9.52h4.927v11.514h3.286V9.52h4.927z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
\ No newline at end of file
--- /dev/null
+
+class HeaderMobileToggle {
+
+ constructor(elem) {
+ this.elem = elem;
+ this.toggleButton = elem.querySelector('.mobile-menu-toggle');
+ this.menu = elem.querySelector('.header-links');
+ this.open = false;
+
+ this.toggleButton.addEventListener('click', this.onToggle.bind(this));
+ this.onWindowClick = this.onWindowClick.bind(this);
+ }
+
+ onToggle(event) {
+ this.open = !this.open;
+ this.menu.classList.toggle('show', this.open);
+ if (this.open) {
+ window.addEventListener('click', this.onWindowClick)
+ } else {
+ window.removeEventListener('click', this.onWindowClick)
+ }
+ event.stopPropagation();
+ }
+
+ onWindowClick(event) {
+ this.onToggle(event);
+ }
+
+}
+
+module.exports = HeaderMobileToggle;
\ No newline at end of file
import pageDisplay from "./page-display";
import shelfSort from "./shelf-sort";
import homepageControl from "./homepage-control";
+import headerMobileToggle from "./header-mobile-toggle";
+import listSortControl from "./list-sort-control";
+import triLayout from "./tri-layout";
const componentMapping = {
'page-display': pageDisplay,
'shelf-sort': shelfSort,
'homepage-control': homepageControl,
+ 'header-mobile-toggle': headerMobileToggle,
+ 'list-sort-control': listSortControl,
+ 'tri-layout': triLayout,
};
window.components = {};
window.components.init = initAll;
-export default initAll;
\ No newline at end of file
+export default initAll;
--- /dev/null
+/**
+ * ListSortControl
+ * Manages the logic for the control which provides list sorting options.
+ */
+class ListSortControl {
+
+ constructor(elem) {
+ this.elem = elem;
+
+ this.sortInput = elem.querySelector('[name="sort"]');
+ this.orderInput = elem.querySelector('[name="order"]');
+ this.form = elem.querySelector('form');
+
+ this.elem.addEventListener('click', event => {
+ if (event.target.closest('[data-sort-value]') !== null) {
+ this.sortOptionClick(event);
+ }
+ if (event.target.closest('[data-sort-dir]') !== null) {
+ this.sortDirectionClick(event);
+ }
+ })
+
+ }
+
+ sortOptionClick(event) {
+ const sortOption = event.target.closest('[data-sort-value]');
+ this.sortInput.value = sortOption.getAttribute('data-sort-value');
+ event.preventDefault();
+ this.form.submit();
+ }
+
+ sortDirectionClick(event) {
+ const currentDir = this.orderInput.value;
+ const newDir = (currentDir === 'asc') ? 'desc' : 'asc';
+ this.orderInput.value = newDir;
+ event.preventDefault();
+ this.form.submit();
+ }
+
+}
+
+export default ListSortControl;
\ No newline at end of file
// Fix the tree as a sidebar
function stickTree() {
- $sidebar.width($bookTreeParent.width() + 15);
+ $sidebar.css('width', $bookTreeParent.width());
$sidebar.addClass("fixed");
isFixed = true;
}
--- /dev/null
+
+class TriLayout {
+
+ constructor(elem) {
+ this.elem = elem;
+ this.middle = elem.querySelector('.tri-layout-middle');
+ this.right = elem.querySelector('.tri-layout-right');
+ this.left = elem.querySelector('.tri-layout-left');
+
+ this.lastLayoutType = 'none';
+ this.onDestroy = null;
+
+
+ this.updateLayout();
+ window.addEventListener('resize', event => {
+ this.updateLayout();
+ });
+ }
+
+ updateLayout() {
+ let newLayout = 'tablet';
+ if (window.innerWidth <= 1000) newLayout = 'mobile';
+ if (window.innerWidth >= 1400) newLayout = 'desktop';
+ if (newLayout === this.lastLayoutType) return;
+
+ if (this.onDestroy) {
+ this.onDestroy();
+ this.onDestroy = null;
+ }
+
+ if (newLayout === 'desktop') {
+ this.setupDesktop();
+ } else if (newLayout === 'mobile') {
+ this.setupMobile();
+ }
+
+ this.lastLayoutType = newLayout;
+ }
+
+ setupMobile() {
+ const mobileSidebarClickBound = this.mobileSidebarClick.bind(this);
+ const mobileContentClickBound = this.mobileContentClick.bind(this);
+ this.left.addEventListener('click', mobileSidebarClickBound);
+ this.right.addEventListener('click', mobileSidebarClickBound);
+ this.middle.addEventListener('click', mobileContentClickBound);
+
+ this.onDestroy = () => {
+ this.left.removeEventListener('click', mobileSidebarClickBound);
+ this.right.removeEventListener('click', mobileSidebarClickBound);
+ this.middle.removeEventListener('click', mobileContentClickBound);
+ }
+ }
+
+ setupDesktop() {
+ //
+ }
+
+ /**
+ * Slide the main content back into view if clicked and
+ * currently slid out of view.
+ * @param event
+ */
+ mobileContentClick(event) {
+ this.elem.classList.remove('mobile-open');
+ }
+
+ /**
+ * On sidebar click, Show the content by sliding the main content out.
+ * @param event
+ */
+ mobileSidebarClick(event) {
+ if (this.elem.classList.contains('mobile-open')) {
+ this.elem.classList.remove('mobile-open');
+ } else {
+ event.preventDefault();
+ event.stopPropagation();
+ this.elem.classList.add('mobile-open');
+ }
+ }
+
+}
+
+export default TriLayout;
\ No newline at end of file
/*
-* This file container all block styling including background shading,
-* margins, paddings & borders.
+* This file container all block styling including margins, paddings & borders.
*/
-/*
-* Background Shading
-*/
-.shaded {
- background-color: #f1f1f1;
- &.pos {
- background-color: lighten($positive, 40%);
- }
- &.neg {
- background-color: lighten($negative, 20%);
- }
- &.primary {
- background-color: lighten($primary, 40%);
- }
- &.secondary {
- background-color: lighten($secondary, 30%);
- }
-}
-
-/*
-* Bordering
-*/
-.bordered {
- border: 1px solid #BBB;
- &.pos {
- border-color: $positive;
- }
- &.neg {
- border-color: $negative;
- }
- &.primary {
- border-color: $primary;
- }
- &.secondary {
- border-color: $secondary;
- }
- &.thick {
- border-width: 2px;
- }
-}
-.rounded {
- border-radius: 3px;
-}
-
/*
* Padding
+* TODO - Remove these older styles
*/
.nopadding {
padding: 0;
/*
* Margins
+* TODO - Remove these older styles
*/
.margins {
margin: $-l;
}
}
+@mixin spacing($prop, $propLetter) {
+ @each $sizeLetter, $size in $spacing {
+ .#{$propLetter}-#{$sizeLetter} {
+ #{$prop}: $size;
+ }
+ .#{$propLetter}x-#{$sizeLetter} {
+ #{$prop}-left: $size;
+ #{$prop}-right: $size;
+ }
+ .#{$propLetter}y-#{$sizeLetter} {
+ #{$prop}-top: $size;
+ #{$prop}-bottom: $size;
+ }
+ .#{$propLetter}t-#{$sizeLetter} {
+ #{$prop}-top: $size;
+ }
+ .#{$propLetter}r-#{$sizeLetter} {
+ #{$prop}-right: $size;
+ }
+ .#{$propLetter}b-#{$sizeLetter} {
+ #{$prop}-bottom: $size;
+ }
+ .#{$propLetter}l-#{$sizeLetter} {
+ #{$prop}-left: $size;
+ }
+ }
+
+}
+
+@include spacing('margin', 'm')
+@include spacing('padding', 'p')
+
/**
* Callouts
}
.card {
- margin: $-m;
background-color: #FFF;
- box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.2);
+ box-shadow: $bs-card;
+ border-radius: 3px;
+ padding-bottom: $-xs;
h3 {
padding: $-m;
- border-bottom: 1px solid #E8E8E8;
+ padding-bottom: $-xs;
margin: 0;
- font-size: $fs-s;
- color: #888;
- fill: #888;
+ font-size: $fs-m;
+ color: #222;
+ fill: #222;
font-weight: 400;
- text-transform: uppercase;
}
h3 a {
line-height: 1;
}
.sidebar .card {
- h3, .body, .empty-text {
+ .body, .empty-text {
padding: $-s $-m;
}
+ h3 + .body {
+ padding-top: $-xs;
+ }
}
.card.drag-card {
--- /dev/null
+/*
+ * Text colors
+ */
+p.pos, p .pos, span.pos, .text-pos {
+ color: $positive;
+ fill: $positive;
+ &:hover {
+ color: $positive;
+ fill: $positive;
+ }
+}
+
+p.neg, p .neg, span.neg, .text-neg {
+ color: $negative;
+ fill: $negative;
+ &:hover {
+ color: $negative;
+ fill: $negative;
+ }
+}
+
+p.muted, p .muted, span.muted, .text-muted {
+ color: lighten($text-dark, 26%);
+ fill: lighten($text-dark, 26%);
+ &.small, .small {
+ color: lighten($text-dark, 32%);
+ fill: lighten($text-dark, 32%);
+ }
+}
+
+p.primary, p .primary, span.primary, .text-primary {
+ color: $primary;
+ fill: $primary;
+ &:hover {
+ color: $primary;
+ fill: $primary;
+ }
+}
+
+p.secondary, p .secondary, span.secondary, .text-secondary {
+ color: $secondary;
+ fill: $secondary;
+ &:hover {
+ color: $secondary;
+ fill: $secondary;
+ }
+}
+
+.text-bookshelf {
+ color: $color-bookshelf;
+ fill: $color-bookshelf;
+ &:hover {
+ color: $color-bookshelf;
+ fill: $color-bookshelf;
+ }
+}
+.text-book {
+ color: $color-book;
+ fill: $color-book;
+ &:hover {
+ color: $color-book;
+ fill: $color-book;
+ }
+}
+.text-page {
+ color: $color-page;
+ fill: $color-page;
+ &:hover {
+ color: $color-page;
+ fill: $color-page;
+ }
+ &.draft {
+ color: $color-page-draft;
+ fill: $color-page-draft;
+ }
+ &.draft:hover {
+ color: $color-page-draft;
+ fill: $color-page-draft;
+ }
+}
+.text-chapter {
+ color: $color-chapter;
+ fill: $color-chapter;
+ &:hover {
+ color: $color-chapter;
+ fill: $color-chapter;
+ }
+}
+.faded .text-book:hover {
+ color: $color-book !important;
+ fill: $color-book !important;
+}
+.faded .text-chapter:hover {
+ color: $color-chapter !important;
+ fill: $color-chapter !important;
+}
+.faded .text-page:hover {
+ color: $color-page !important;
+ fill: $color-page !important;
+}
+
+.bg-book {
+ background-color: $color-book;
+}
\ No newline at end of file
}
.form-group[collapsible] {
- margin-left: -$-m;
- margin-right: -$-m;
padding: 0 $-m;
- border-top: 1px solid #DDD;
- border-bottom: 1px solid #DDD;
+ border: 1px solid #DDD;
+ border-radius: 4px;
.collapse-title {
margin-left: -$-m;
margin-right: -$-m;
&.open .collapse-title label:before {
transform: rotate(90deg);
}
- &+.form-group[collapsible] {
- margin-top: -($-s + 1);
- }
}
.inline-input-style {
flex: 1;
}
-.flex.sidebar {
- flex: 1;
- background-color: #F2F2F2;
- max-width: 360px;
- min-height: 90vh;
- section {
- margin: $-m;
- }
-}
-.flex.sidebar + .flex.content {
- flex: 3;
- background-color: #FFFFFF;
- padding: 0 $-l;
- border-left: 1px solid #DDD;
- max-width: 100%;
+.content-wrap.card {
+ padding: $-l $-xxl;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: $-xl;
+ overflow: auto;
}
-.flex.sidebar .sidebar-toggle {
- display: none;
-}
-
-@include smaller-than($xl) {
- body.sidebar-layout {
- padding-left: 30px;
- }
- .flex.sidebar {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- z-index: 100;
- padding-right: 30px;
- width: 360px;
- box-shadow: none;
- transform: translate3d(-330px, 0, 0);
- transition: transform ease-in-out 120ms;
- display: flex;
- flex-direction: column;
+
+.tri-layout-container {
+ display: grid;
+ grid-template-columns: 1fr minmax(auto, 940px) 1fr;
+ grid-template-areas: "a b c";
+ .tri-layout-right-contents, .tri-layout-left-contents {
+ padding-right: $-xl;
+ padding-left: $-xl;
+ }
+ .tri-layout-right {
+ grid-area: c;
+ }
+ .tri-layout-left {
+ grid-area: a;
+ }
+ .tri-layout-middle {
+ grid-area: b;
+ }
+}
+@include smaller-than($xxl) {
+ .tri-layout-container {
+ grid-template-areas: "c b b"
+ "a b b";
+ grid-template-columns: 1fr 3fr;
+ grid-template-rows: max-content min-content;
+ padding-right: $-l;
+ .content-wrap.card {
+ padding: $-l $-l;
+ }
}
- .flex.sidebar.open {
- box-shadow: 1px 2px 2px 1px rgba(0,0,0,.10);
- transform: translate3d(0, 0, 0);
- .sidebar-toggle i {
- transform: rotate(180deg);
+}
+@include larger-than($xxl) {
+ .tri-layout-left-contents, .tri-layout-right-contents {
+ position: sticky;
+ top: $-m;
+ max-height: 100vh;
+ min-height: 50vh;
+ overflow-y: scroll;
+ overflow-x: visible;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ &::-webkit-scrollbar {
+ display: none;
}
}
- .flex.sidebar .sidebar-toggle {
- display: block;
- position: absolute;
- opacity: 0.9;
- right: 0;
- top: 0;
- bottom: 0;
- width: 30px;
- fill: #666;
- font-size: 20px;
- vertical-align: middle;
- text-align: center;
- border: 1px solid #DDD;
- border-top: 1px solid #BBB;
- padding-top: $-m;
- cursor: pointer;
- svg {
- opacity: 0.5;
- transition: all ease-in-out 120ms;
- margin: 0;
+}
+
+@include smaller-than($l) {
+ .tri-layout-container {
+ grid-template-areas: none;
+ grid-template-columns: 10% 90%;
+ grid-column-gap: 0;
+ .tri-layout-left-contents, .tri-layout-right-contents {
+ padding-left: $-m;
+ padding-right: $-m;
}
- &:hover i {
- opacity: 1;
+ .tri-layout-right, .tri-layout-left {
+ opacity: 0.6;
+ z-index: 0;
+ }
+ .tri-layout-left > *, .tri-layout-right > * {
+ pointer-events: none;
+ }
+ .tri-layout-right, .tri-layout-left, .tri-layout-middle {
+ grid-area: none;
+ grid-column: 1/3;
+ grid-row: 1;
+ }
+ .tri-layout-middle {
+ grid-row: 1/3;
+ grid-column: 2/3;
+ z-index: 1;
+ transition: transform ease-in-out 240ms;
+ }
+ .tri-layout-left {
+ grid-row: 2;
+ }
+ &.mobile-open {
+ overflow: hidden;
+ .tri-layout-middle {
+ transform: translateX(90%);
+ }
+ .tri-layout-right > *, .tri-layout-left > * {
+ pointer-events: auto;
+ }
}
- }
- .sidebar .scroll-body {
- flex: 1;
- overflow-y: scroll;
- }
- #sidebar .scroll-body.fixed {
- width: auto !important;
}
}
-@include larger-than($xl) {
- #sidebar .scroll-body.fixed {
- z-index: 5;
- position: fixed;
- top: 0;
- padding-right: $-m;
- width: 30%;
- left: 0;
- height: 100%;
- overflow-y: auto;
- -ms-overflow-style: none;
- //background-color: $primary-faded;
- border-left: 1px solid #DDD;
- &::-webkit-scrollbar { width: 0 !important }
+.tri-layout-left, .tri-layout-right {
+ opacity: 0.7;
+ transition: opacity ease-in-out 120ms;
+ &:hover {
+ opacity: 1;
}
}
-
/** Rules for all columns */
div[class^="col-"] img {
max-width: 100%;
}
.container {
- max-width: $max-width;
+ max-width: $xxl;
margin-left: auto;
margin-right: auto;
padding-left: $-m;
display: grid;
grid-column-gap: $-l;
grid-row-gap: $-l;
+ &.half {
+ grid-template-columns: 1fr 1fr;
+ }
&.third {
grid-template-columns: 1fr 1fr 1fr;
}
display: flex;
flex-direction: column;
border: 1px solid #ddd;
+ margin-bottom: $-l;
+ border-radius: 4px;
+ overflow: hidden;
min-width: 100px;
+ color: $text-dark;
+ transition: border-color ease-in-out 120ms, box-shadow ease-in-out 120ms;
+ &:hover {
+ color: $text-dark;
+ text-decoration: none;
+ box-shadow: $bs-card;
+ }
h2 {
width: 100%;
font-size: 1.5em;
margin: 0 0 10px;
}
- h2 a {
- display: block;
- width: 100%;
- line-height: 1.2;
- text-decoration: none;
- }
p {
font-size: .85em;
margin: 0;
}
}
-.book-grid-item .grid-card-content h2 a {
- color: $color-book;
- fill: $color-book;
-}
-
.bookshelf-grid-item .grid-card-content h2 a {
color: $color-bookshelf;
fill: $color-bookshelf;
display: inline-block;
}
+
+// TODO - Remove old BS grid system
.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
position: relative;
min-height: 1px;
}
.clearfix:before,
.clearfix:after,
-.container:before,
-.container:after,
-.container-fluid:before,
-.container-fluid:after,
.row:before,
.row:after {
content: " ";
display: table;
}
.clearfix:after,
-.container:after,
-.container-fluid:after,
.row:after {
clear: both;
}
margin-left: auto;
margin-right: auto;
}
+
+
+
+
+
+
+.grid {
+ display: grid;
+ grid-column-gap: $-m;
+ grid-row-gap: 0;
+ &.contained {
+ max-width: $xxl;
+ padding-left: $-m;
+ padding-right: $-m;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ &.v-center {
+ align-items: center;
+ }
+}
+
+@each $sizeLetter, $size in $spacing {
+ .grid.contained.space-#{$sizeLetter} {
+ padding-left: $size;
+ padding-right: $size;
+ grid-column-gap: $size;
+ }
+}
+
+@mixin grid-layout($name, $times) {
+ .grid.#{$name} {
+ grid-template-columns: repeat(#{$times}, 1fr);
+ }
+}
+
+@include grid-layout('thirds', 3)
+@include grid-layout('halves', 2)
+
+@each $sizeLetter, $size in $screen-sizes {
+ @include smaller-than($size) {
+ .grid.break-#{$sizeLetter} {
+ grid-template-columns: 1fr;
+ }
+ }
+}
+
+
+/**
+ * Visibility
+ */
+
+@each $sizeLetter, $size in $screen-sizes {
+ @include smaller-than($size) {
+ .hide-under-#{$sizeLetter} {
+ display: none !important;
+ }
+ }
+ @include larger-than($size) {
+ .hide-over-#{$sizeLetter} {
+ display: none !important;
+ }
+ }
+}
\ No newline at end of file
* Includes the main navigation header and the faded toolbar.
*/
+header .grid {
+ grid-template-columns: auto min-content auto;
+}
+
header {
+ position: relative;
display: block;
z-index: 2;
top: 0;
background-color: $primary-dark;
color: #fff;
fill: #fff;
- .padded {
- padding: $-m;
- }
border-bottom: 1px solid #DDD;
+ box-shadow: $bs-card;
+ padding: $-xxs 0;
.links {
display: inline-block;
vertical-align: top;
- margin-left: $-m;
}
.links a {
display: inline-block;
padding-left: $-m;
padding-right: 0;
}
- @include smaller-than($screen-md) {
- .links a {
- padding-left: $-s;
- padding-right: $-s;
- }
- .dropdown-container {
- padding-left: $-s;
- }
- }
.avatar, .user-name {
display: inline-block;
}
padding-top: 4px;
font-size: 18px;
}
- @include smaller-than($screen-md) {
+ @include between($l, $xl) {
padding-left: $-xs;
.name {
display: none;
}
}
}
- @include smaller-than($screen-sm) {
- text-align: center;
- .float.right {
- float: none;
- }
- .links a {
- padding: $-s;
- }
- .user-name {
- padding-top: $-s;
- }
- }
}
+
+
.header-search {
display: inline-block;
}
margin-top: 10px;
input {
background-color: rgba(0, 0, 0, 0.2);
- border: 1px solid rgba(255, 255, 255, 0.3);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 40px;
color: #EEE;
z-index: 2;
+ padding-left: 40px;
}
button {
fill: #EEE;
z-index: 1;
+ left: 16px;
svg {
margin-right: 0;
}
:-moz-placeholder { /* Firefox 18- */
color: #DDD;
}
- @include smaller-than($screen-lg) {
- max-width: 250px;
- }
- @include smaller-than($l) {
+ @include between($l, $xl) {
max-width: 200px;
}
}
-@include smaller-than($s) {
- .header-search {
- display: block;
- }
-}
-
.logo {
display: inline-block;
&:hover {
height: 43px;
}
-.breadcrumbs span.sep {
- color: #aaa;
+.mobile-menu-toggle {
+ color: #FFF;
+ fill: #FFF;
+ font-size: 2em;
+ border: 2px solid rgba(255, 255, 255, 0.8);
+ border-radius: 4px;
padding: 0 $-xs;
+ position: absolute;
+ right: $-m;
+ top: 8px;
+ line-height: 1;
+ cursor: pointer;
+ user-select: none;
+ svg {
+ margin: 0;
+ }
}
+
+@include smaller-than($l) {
+ header .header-links {
+ display: none;
+ background-color: #FFF;
+ z-index: 10;
+ right: $-m;
+ border-radius: 4px;
+ overflow: hidden;
+ position: absolute;
+ box-shadow: $bs-hover;
+ margin-top: -$-xs;
+ &.show {
+ display: block;
+ }
+ }
+ header .links a, header .dropdown-container ul li a {
+ text-align: left;
+ display: block;
+ padding: $-s $-m;
+ color: $text-dark;
+ fill: $text-dark;
+ svg {
+ margin-right: $-s;
+ }
+ &:hover {
+ background-color: #EEE;
+ color: #444;
+ fill: #444;
+ text-decoration: none;
+ }
+ }
+ header .dropdown-container {
+ display: block;
+ padding-left: 0;
+ }
+ header .links {
+ display: block;
+ }
+ header .dropdown-container ul {
+ display: block !important;
+ position: relative;
+ background-color: transparent;
+ border: 0;
+ padding: 0;
+ margin: 0;
+ box-shadow: none;
+ }
+}
+
+.breadcrumbs {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.breadcrumbs .separator {
+ fill: #aaa;
+ font-size: 1.6em;
+ line-height: 0.8;
+ margin: 0 $-xs;
+ margin-top: -2px;
+}
+
.faded {
a, button, span, span > div {
color: #666;
background-color: $primary-faded;
}
-.toolbar-container {
- background-color: #FFF;
+.toolbar {
+ position: relative;
+ > .grid > div {
+ opacity: 0.8;
+ transition: opacity ease-in-out 120ms;
+ &:hover {
+ opacity: 1;
+ }
+ }
+ .text-button {
+ color: #666;
+ fill: #666;
+ }
}
-.breadcrumbs .text-button, .action-buttons .text-button {
+.action-buttons .text-button {
display: inline-block;
- padding: $-s;
+ padding: $-xs $-s;
&:last-child {
padding-right: 0;
}
}
@include smaller-than($m) {
- .breadcrumbs .text-button, .action-buttons .text-button {
+ .action-buttons .text-button {
padding: $-xs $-xs;
}
.action-buttons .dropdown-container:last-child a {
padding-left: $-xs;
}
- .breadcrumbs .text-button {
- font-size: 0;
- }
- .breadcrumbs .text-button svg {
- font-size: $fs-m;
- }
- .breadcrumbs a i {
- font-size: $fs-m;
- padding-right: 0;
- }
- .breadcrumbs span.sep {
- padding: 0 $-xxs;
- }
.toolbar .col-xs-1:first-child {
padding-right: 0;
}
}
html {
- background-color: #FFFFFF;
height: 100%;
overflow-y: scroll;
+ background-color: #F2F2F2;
&.flexbox {
overflow-y: hidden;
}
- &.shaded {
- background-color: #F2F2F2;
- }
}
body {
font-size: $fs-m;
line-height: 1.6;
- color: #616161;
+ color: #444;
-webkit-font-smoothing: antialiased;
- &.shaded {
- background-color: #F2F2F2;
- }
+ background-color: #F2F2F2;
}
button {
}
// Sidebar list
-.book-tree {
- transition: ease-in-out 240ms;
- transition-property: right, border;
-}
-.book-tree h4 {
- padding: $-m $-s 0 $-s;
- i {
- padding-right: $-s;
+.book-tree .book.entity-list-item {
+ font-size: 0.6rem;
+ h4 {
+ font-size: 1rem;
+ margin: 0;
}
}
.book-tree .sidebar-page-list {
list-style: none;
margin: $-xs 0 0;
padding-left: 0;
- border-left: 5px solid $color-book;
- li a {
- display: block;
- border-bottom: none;
- padding: $-xs 0 $-xs $-s;
- &:hover {
- text-decoration: none;
- }
- }
- li a i {
- padding-right: $-xs + 2px;
- }
- li, a {
- display: block;
- }
- a.bold {
- color: #EEE !important;
- fill: #EEE !important;
- }
+ padding-right: 0;
+ position: relative;
ul {
list-style: none;
+ padding-left: 1rem;
+ padding-right: 0;
+ }
+ .entity-icon {
+ font-size: 12px;
+ z-index: 2;
+ background-color: #FFF;
+ }
+ .entity-list-item-name {
+ font-size: 1em;
margin: 0;
}
- .book {
- color: $color-book !important;
- fill: $color-book !important;
- &.selected {
- background-color: rgba($color-book, 0.29);
- }
+ .entity-list-item {
+ font-size: 0.8rem;
}
- .chapter {
- color: $color-chapter !important;
- fill: $color-chapter !important;
- &.selected {
- background-color: rgba($color-chapter, 0.12);
- }
+ .entity-list-item.selected {
+ background-color: #F2F2F2;
}
- .page {
- color: $color-page !important;
- fill: $color-page !important;
- border-bottom: none;
- &.selected {
- background-color: rgba($color-page, 0.1);
- }
+ .chapter-child-menu {
+ font-size: 12px;
+ padding-left: 2rem;
+ margin-top: -.2rem;
}
[chapter-toggle] {
- padding-left: $-s;
+ padding-left: 1.5rem;
+ padding-bottom: .2rem;
}
- .list-item-chapter {
- border-left: 5px solid $color-chapter;
- margin: 10px 10px;
+ &:after, .sub-menu:after {
+ content: '';
display: block;
+ position: absolute;
+ left: 1.6rem;
+ top: 1rem;
+ bottom: 1rem;
+ border-left: 2px solid #DDD;
+ opacity: 0.6;
+ z-index: 1;
}
- .list-item-page {
- border-bottom: none;
- border-left: 5px solid $color-page;
- margin: 10px 10px;
- }
- .list-item-page.draft {
- border-left: 5px solid $color-page-draft;
- }
- .page.draft .page, .list-item-page.draft a.page {
- color: $color-page-draft !important;
- fill: $color-page-draft !important;
- }
- .sub-menu {
+}
+
+.chapter-child-menu {
+ ul.sub-menu {
display: none;
padding-left: 0;
+ position: relative;
}
[chapter-toggle].open + .sub-menu {
display: block;
}
.activity-list-item {
- padding: $-s 0;
+ padding: $-s $-m;
+ display: grid;
+ grid-template-columns: min-content 1fr;
+ grid-column-gap: $-m;
color: #888;
fill: #888;
- border-bottom: 1px solid #EEE;
font-size: 0.9em;
- .left {
- float: left;
- }
- .left + .right {
- margin-left: 30px + $-s;
- }
- &:last-of-type {
- border-bottom: 0;
- }
}
ul.pagination {
margin: 0;
}
-.entity-list {
- > div {
- padding: $-m 0;
- }
+.entity-list, .icon-list {
+ margin: 0 (-$-m);
h4 {
margin: 0;
}
color: $color-page-draft;
fill: $color-page-draft;
}
+ > .dropdown-container {
+ display: block;
+ }
+}
+
+.entity-list-item, .icon-list-item {
+ padding: $-s $-m;
+ display: grid;
+ grid-template-columns: min-content 1fr;
+ grid-column-gap: $-m;
+ align-items: top;
+ > .content {
+ padding-top: 2px;
+ }
+ .icon {
+ font-size: 1rem;
+ }
+ h4 a {
+ color: #666;
+ }
+ &:hover {
+ text-decoration: none;
+ background-color: #DDD;
+ border-radius: 4px;
+ }
}
-.card .entity-list-item, .card .activity-list-item {
- padding-left: $-m;
- padding-right: $-m;
+.card a.entity-list-item:hover {
+ background-color: #F2F2F2;
+}
+
+.entity-list-item-image {
+ width: 140px;
+ background-size: cover;
+ background-position: 50% 50%;
+ border-radius: 3px;
+ @include smaller-than($m) {
+ width: 80px;
+ }
}
.entity-list.compact {
- font-size: 0.6em;
+ font-size: 0.6 * $fs-m;
h4, a {
line-height: 1.2;
}
hr {
margin: 0;
}
+ @include smaller-than($m) {
+ h4 {
+ font-size: 1.666em;
+ }
+ }
}
.dropdown-container {
padding: $-xs $-m;
line-height: 1.2;
}
+ li.active a {
+ font-weight: 600;
+ }
a, button {
display: block;
padding: $-xs $-m;
.featured-image-container {
position: relative;
overflow: hidden;
- background: #F2F2F2;
a {
display: block;
}
height: auto;
transition: all .5s ease-in-out;
}
- img:hover {
- transform: scale(1.15);
- opacity: .5;
- }
+}
+.grid-card:hover .featured-image-container img {
+ transform: scale(1.15);
+ opacity: .5;
+}
+
+.action-link-list {
+ //padding: $-s 0;
+}
+.action-link {
+ background: transparent;
+ border: none;
+ color: currentColor;
+ padding: $-m 0;
}
@mixin larger-than($size) {
@media screen and (min-width: $size) { @content; }
}
+@mixin between($min, $max) {
+ @media screen and (min-width: $min) and (max-width: $max) { @content; }
+}
@mixin clearfix() {
&:after {
display: block;
width: 100%;
max-width: 840px;
margin: 0 auto;
- margin-top: $-xxl;
overflow-wrap: break-word;
- &.flex {
- margin-top: $-m;
- }
.align-left {
text-align: left;
}
}
}
-.comments-container {
- width: 100%;
- border-top: 1px solid #DDD;
- margin-top: $-xl;
- margin-bottom: $-m;
- h5 {
- color: #888;
- font-weight: normal;
- margin-top: 0.5em;
- }
+.comments-container h5 {
+ color: #888;
+ font-weight: normal;
+ margin-top: 0.5em;
}
.comment-editor .CodeMirror, .comment-editor .CodeMirror-scroll {
.mce-open {
display: none;
}
+}
+
+.entity-list-item > .icon, .icon-list-item > .icon {
+ font-size: 0.8rem;
+ width: 1.88em;
+ height: 1.88em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ border-radius: 1em;
+ position: relative;
+ overflow: hidden;
+ svg {
+ margin: 0;
+ bottom: 0;
+ }
+ &:after {
+ content: '';
+ position: absolute;
+ background-color: currentColor;
+ opacity: 0.2;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ }
+}
+
+.entity-chip {
+ display: inline-block;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ font-size: 0.9em;
+ border-radius: 2em;
+ position: relative;
+ overflow: hidden;
+ padding: $-xs $-m;
+ color: #666;
+ fill: currentColor;
+ &:after {
+ content: '';
+ position: absolute;
+ background-color: currentColor;
+ opacity: 0.2;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ }
}
\ No newline at end of file
font-weight: 400;
position: relative;
display: block;
- color: #555;
+ color: #222;
.subheader {
font-size: 0.5em;
line-height: 1em;
}
}
+.list-heading {
+ font-size: 2rem;
+}
+
/*
* Link styling
*/
display: block;
line-height: 1.6;
}
-/*
- * Text colors
- */
-p.pos, p .pos, span.pos, .text-pos {
- color: $positive;
- fill: $positive;
- &:hover {
- color: $positive;
- fill: $positive;
- }
-}
-
-p.neg, p .neg, span.neg, .text-neg {
- color: $negative;
- fill: $negative;
- &:hover {
- color: $negative;
- fill: $negative;
- }
-}
-
-p.muted, p .muted, span.muted, .text-muted {
- color: lighten($text-dark, 26%);
- fill: lighten($text-dark, 26%);
- &.small, .small {
- color: lighten($text-dark, 32%);
- fill: lighten($text-dark, 32%);
- }
-}
-
-p.primary, p .primary, span.primary, .text-primary {
- color: $primary;
- fill: $primary;
- &:hover {
- color: $primary;
- fill: $primary;
- }
-}
-
-p.secondary, p .secondary, span.secondary, .text-secondary {
- color: $secondary;
- fill: $secondary;
- &:hover {
- color: $secondary;
- fill: $secondary;
- }
-}
-
-.text-bookshelf {
- color: $color-bookshelf;
- fill: $color-bookshelf;
- &:hover {
- color: $color-bookshelf;
- fill: $color-bookshelf;
- }
-}
-.text-book {
- color: $color-book;
- fill: $color-book;
- &:hover {
- color: $color-book;
- fill: $color-book;
- }
-}
-.text-page {
- color: $color-page;
- fill: $color-page;
- &:hover {
- color: $color-page;
- fill: $color-page;
- }
- &.draft {
- color: $color-page-draft;
- fill: $color-page-draft;
- }
- &.draft:hover {
- color: $color-page-draft;
- fill: $color-page-draft;
- }
-}
-.text-chapter {
- color: $color-chapter;
- fill: $color-chapter;
- &:hover {
- color: $color-chapter;
- fill: $color-chapter;
- }
-}
-.faded .text-book:hover {
- color: $color-book !important;
- fill: $color-book !important;
-}
-.faded .text-chapter:hover {
- color: $color-chapter !important;
- fill: $color-chapter !important;
-}
-.faded .text-page:hover {
- color: $color-page !important;
- fill: $color-page !important;
-}
span.highlight {
//background-color: rgba($primary, 0.2);
// Variables
///////////////
-// Sizes
-$max-width: 1400px;
-
// Screen breakpoints
+$xxl: 1400px;
$xl: 1100px;
$ipad-width: 1028px; // Is actually 1024 but we go over to ensure functionality.
$l: 1000px;
-$m: 800px;
+$m: 880px;
$s: 600px;
$xs: 400px;
$xxs: 360px;
$screen-md: 992px;
$screen-sm: 768px;
+$screen-sizes: (('xxs', $xxs), ('xs', $xs), ('s', $s), ('m', $m), ('l', $l), ('xl', $xl));
+
// Spacing (Margins+Padding)
$-xxxl: 64px;
$-xxl: 48px;
$-xs: 6px;
$-xxs: 3px;
+$spacing: (('none', 0), ('xxs', $-xxs), ('xs', $-xs), ('s', $-s), ('m', $-m), ('l', $-l), ('xl', $-xl), ('xxl', $-xxl));
+
// Fonts
$text: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Oxygen", "Ubuntu", "Roboto", "Cantarell",
sans-serif;
$mono: "Lucida Console", "DejaVu Sans Mono", "Ubunto Mono", Monaco, monospace;
$heading: $text;
-$fs-m: 15px;
-$fs-s: 14px;
+$fs-m: 14px;
+$fs-s: 12px;
// Colours
$primary: #0288D1;
// Item Colors
$color-bookshelf: #af5a5a;
$color-book: #009688;
-$color-chapter: #ef7c3c;
+$color-chapter: #d7804a;
$color-page: $primary;
$color-page-draft: #9A60DA;
// Shadows
$bs-light: 0 0 4px 1px #CCC;
$bs-med: 0 1px 3px 1px rgba(76, 76, 76, 0.26);
-$bs-card: 0 1px 3px 1px rgba(76, 76, 76, 0.26), 0 1px 12px 0px rgba(76, 76, 76, 0.2);
+$bs-card: 0 1px 6px -1px rgba(0, 0, 0, 0.1);
$bs-hover: 0 2px 2px 1px rgba(0,0,0,.13);
\ No newline at end of file
@import "mixins";
@import "html";
@import "text";
+@import "colors";
@import "grid";
@import "blocks";
@import "buttons";
height:100%;
z-index: 150;
}
+
+.list-sort-container {
+ display: inline-block;
+ form {
+ display: inline-block;
+ }
+ .list-sort {
+ display: inline-grid;
+ margin-left: $-s;
+ grid-template-columns: 120px 40px;
+ border: 2px solid #DDD;
+ border-radius: 4px;
+ }
+ .list-sort-label {
+ font-weight: bold;
+ display: inline-block;
+ color: #888;
+ }
+ .list-sort-type {
+ text-align: left;
+ }
+ .list-sort-type, .list-sort-dir {
+ padding: $-xs $-s;
+ cursor: pointer;
+ }
+ .list-sort-dir {
+ border-left: 2px solid #DDD;
+ fill: #888;
+ .svg-icon {
+ transition: transform ease-in-out 120ms;
+ }
+ &:hover .svg-icon {
+ transform: rotate(180deg);
+ }
+ }
+}
\ No newline at end of file
'remove' => 'Remove',
'add' => 'Add',
+ // Sort Options
+ 'sort_name' => 'Name',
+ 'sort_created_at' => 'Created Date',
+ 'sort_updated_at' => 'Updated Date',
+
// Misc
'deleted_user' => 'Deleted User',
'no_activity' => 'No activity to show',
// Email Content
'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
'email_rights' => 'All rights reserved',
-];
\ No newline at end of file
+];
<script src="{{ baseUrl('/translations') }}"></script>
@yield('head')
-
- @include('partials/custom-styles')
-
+ @include('partials.custom-styles')
@include('partials.custom-head')
-</head>
-<body class="@yield('body-class')" ng-app="bookStack">
-
- @include('partials/notifications')
- <header id="header">
- <div class="container fluid">
- <div class="row">
- <div class="col-sm-4 col-md-3">
- <a href="{{ baseUrl('/') }}" class="logo">
- @if(setting('app-logo', '') !== 'none')
- <img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
- @endif
- @if (setting('app-name-header'))
- <span class="logo-text">{{ setting('app-name') }}</span>
- @endif
- </a>
- </div>
- <div class="col-sm-8 col-md-9">
- <div class="float right">
- <div class="header-search">
- <form action="{{ baseUrl('/search') }}" method="GET" class="search-box">
- <button id="header-search-box-button" type="submit">@icon('search') </button>
- <input id="header-search-box-input" type="text" name="term" tabindex="2" placeholder="{{ trans('common.search') }}" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
- </form>
- </div>
- <div class="links text-center">
- @if(userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
- <a href="{{ baseUrl('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
- @endif
- <a href="{{ baseUrl('/books') }}">@icon('book'){{ trans('entities.books') }}</a>
- @if(signedInUser() && userCan('settings-manage'))
- <a href="{{ baseUrl('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
- @endif
- @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
- <a href="{{ baseUrl('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
- @endif
- @if(!signedInUser())
- @if(setting('registration-enabled', false))
- <a href="{{ baseUrl("/register") }}">@icon('new-user') {{ trans('auth.sign_up') }}</a>
- @endif
- <a href="{{ baseUrl('/login') }}">@icon('login') {{ trans('auth.log_in') }}</a>
- @endif
- </div>
- @if(signedInUser())
- @include('partials._header-dropdown', ['currentUser' => user()])
- @endif
+</head>
+<body class="@yield('body-class')">
- </div>
- </div>
- </div>
- </div>
- </header>
+ @include('partials.notifications')
+ @include('common.header')
<section id="content" class="block">
@yield('content')
@icon('chevron-up') <span>{{ trans('common.back_to_top') }}</span>
</div>
</div>
-@yield('bottom')
-<script src="{{ versioned_asset('dist/app.js') }}"></script>
-@yield('scripts')
+
+ @yield('bottom')
+ <script src="{{ versioned_asset('dist/app.js') }}"></script>
+ @yield('scripts')
+
</body>
</html>
@extends('simple-layout')
-@section('toolbar')
- <div class="col-sm-8 faded">
- <div class="breadcrumbs">
- <a href="{{ baseUrl('/books') }}" class="text-button">@icon('book'){{ trans('entities.books') }}</a>
- <span class="sep">»</span>
- <a href="{{ baseUrl('/create-book') }}" class="text-button">@icon('add'){{ trans('entities.books_create') }}</a>
- </div>
- </div>
-@stop
-
@section('body')
+ <div class="container small">
+ <div class="breadcrumbs my-l">
+ <a href="{{ baseUrl('/books') }}" class="">
+ @icon('book'){{ trans('entities.books') }}
+ </a>
+ <div class="separator">@icon('chevron-right')</div>
+ <a href="{{ baseUrl('/create-book') }}" class="">
+ @icon('add'){{ trans('entities.books_create') }}
+ </a>
+ </div>
-<div class="container small">
- <p> </p>
- <div class="card">
- <h3>@icon('add') {{ trans('entities.books_create') }}</h3>
- <div class="body">
+ <div class="content-wrap card">
+ <h1 class="list-heading">{{ trans('entities.books_create') }}</h1>
<form action="{{ baseUrl("/books") }}" method="POST" enctype="multipart/form-data">
@include('books/form')
</form>
</div>
</div>
-</div>
-<p class="margin-top large"><br></p>
+
@include('components.image-manager', ['imageType' => 'cover'])
@stop
\ No newline at end of file
<div class="form-group text-right">
<a href="{{ isset($book) ? $book->getUrl() : baseUrl('/books') }}" class="button outline">{{ trans('common.cancel') }}</a>
- <button type="submit" class="button pos">{{ trans('entities.books_save') }}</button>
+ <button type="submit" class="button primary">{{ trans('entities.books_save') }}</button>
</div>
\ No newline at end of file
-<div class="book-grid-item grid-card" data-entity-type="book" data-entity-id="{{$book->id}}">
- <div class="featured-image-container">
- <a href="{{$book->getUrl()}}" title="{{$book->name}}">
- <img src="{{$book->getBookCover()}}" alt="{{$book->name}}">
- </a>
+<a href="{{$book->getUrl()}}" class="grid-card" data-entity-type="book" data-entity-id="{{$book->id}}">
+ <div class="featured-image-container bg-book">
+ <img src="{{$book->getBookCover()}}" alt="{{$book->name}}">
</div>
<div class="grid-card-content">
- <h2><a class="break-text" href="{{$book->getUrl()}}" title="{{$book->name}}">{{$book->getShortName(35)}}</a></h2>
+ <h2>{{$book->getShortName(35)}}</h2>
@if(isset($book->searchSnippet))
- <p >{!! $book->searchSnippet !!}</p>
+ <p class="text-muted">{!! $book->searchSnippet !!}</p>
@else
- <p >{{ $book->getExcerpt(130) }}</p>
+ <p class="text-muted">{{ $book->getExcerpt(130) }}</p>
@endif
</div>
<div class="grid-card-footer text-muted text-small">
- <span>@include('partials.entity-meta', ['entity' => $book])</span>
+ @icon('star')<span title="{{$book->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $book->created_at->diffForHumans()]) }}</span>
+ <br>
+ @icon('edit')<span title="{{ $book->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $book->updated_at->diffForHumans()]) }}</span>
</div>
-</div>
\ No newline at end of file
+</a>
\ No newline at end of file
-@extends('sidebar-layout')
+@extends('tri-layout')
-@section('toolbar')
- <div class="col-xs-6">
- <div class="action-buttons text-left">
- @include('books/view-toggle', ['booksViewType' => $booksViewType])
- </div>
- </div>
- <div class="col-xs-6 faded">
- <div class="action-buttons">
- @if($currentUser->can('book-create-all'))
- <a href="{{ baseUrl("/create-book") }}" class="text-pos text-button">@icon('add'){{ trans('entities.books_create') }}</a>
- @endif
- </div>
- </div>
-@stop
+@section('container-classes', 'mt-xl')
-@section('sidebar')
+@section('left')
@if($recents)
- <div id="recents" class="card">
- <h3>@icon('view') {{ trans('entities.recently_viewed') }}</h3>
- @include('partials/entity-list', ['entities' => $recents, 'style' => 'compact'])
+ <div id="recents" class="mb-xl">
+ <h5>{{ trans('entities.recently_viewed') }}</h5>
+ @include('partials.entity-list', ['entities' => $recents, 'style' => 'compact'])
</div>
@endif
- <div id="popular" class="card">
- <h3>@icon('popular') {{ trans('entities.books_popular') }}</h3>
+ <div id="popular" class="mb-xl">
+ <h5>{{ trans('entities.books_popular') }}</h5>
@if(count($popular) > 0)
- @include('partials/entity-list', ['entities' => $popular, 'style' => 'compact'])
+ @include('partials.entity-list', ['entities' => $popular, 'style' => 'compact'])
@else
<div class="body text-muted">{{ trans('entities.books_popular_empty') }}</div>
@endif
</div>
- <div id="new" class="card">
- <h3>@icon('star-circle') {{ trans('entities.books_new') }}</h3>
+ <div id="new" class="mb-xl">
+ <h5>{{ trans('entities.books_new') }}</h5>
@if(count($popular) > 0)
- @include('partials/entity-list', ['entities' => $new, 'style' => 'compact'])
+ @include('partials.entity-list', ['entities' => $new, 'style' => 'compact'])
@else
<div class="body text-muted">{{ trans('entities.books_new_empty') }}</div>
@endif
@stop
@section('body')
- @include('books/list', ['books' => $books, 'bookViewType' => $booksViewType])
+ @include('books.list', ['books' => $books, 'view' => $view])
+@stop
+
+@section('right')
+
+ <div class="actions mb-xl">
+ <h5>Actions</h5>
+ <div class="icon-list text-primary">
+ @if($currentUser->can('book-create-all'))
+ <a href="{{ baseUrl("/create-book") }}" class="icon-list-item">
+ <span class="icon">@icon('add')</span>
+ <span>{{ trans('entities.books_create') }}</span>
+ </a>
+ @endif
+ @include('books.view-toggle', ['view' => $view])
+ </div>
+ </div>
+
@stop
\ No newline at end of file
-<div class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
- <h4 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}">@icon('book')<span class="entity-list-item-name break-text">{{$book->name}}</span></a></h4>
- <div class="entity-item-snippet">
- @if(isset($book->searchSnippet))
- <p class="text-muted break-text">{!! $book->searchSnippet !!}</p>
- @else
- <p class="text-muted break-text">{{ $book->getExcerpt() }}</p>
- @endif
+<a href="{{ $book->getUrl() }}" class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
+ <div class="entity-list-item-image bg-book" style="background-image: url('{{ $book->getBookCover() }}')">
</div>
-</div>
\ No newline at end of file
+ <div class="content">
+ <h4 class="entity-list-item-name break-text">{{ $book->name }}</h4>
+ <div class="entity-item-snippet">
+ <p class="text-muted break-text mb-s">{{ $book->getExcerpt() }}</p>
+ </div>
+ </div>
+</a>
\ No newline at end of file
-<div class="container{{ $booksViewType === 'list' ? ' small' : '' }}">
- <h1>{{ trans('entities.books') }}</h1>
+<div class="content-wrap card">
+ <div class="grid halves v-center">
+ <h1 class="list-heading">{{ trans('entities.books') }}</h1>
+ <div class="text-right">
+
+ @include('partials.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort])
+
+ </div>
+ </div>
@if(count($books) > 0)
- @if($booksViewType === 'list')
- @foreach($books as $book)
- @include('books/list-item', ['book' => $book])
- <hr>
- @endforeach
- {!! $books->render() !!}
+ @if($view === 'list')
+ <div class="entity-list">
+ @foreach($books as $book)
+ @include('books.list-item', ['book' => $book])
+ @endforeach
+ </div>
@else
<div class="grid third">
@foreach($books as $key => $book)
- @include('books/grid-item', ['book' => $book])
+ @include('books.grid-item', ['book' => $book])
@endforeach
</div>
- <div>
- {!! $books->render() !!}
- </div>
@endif
+ <div>
+ {!! $books->render() !!}
+ </div>
@else
<p class="text-muted">{{ trans('entities.books_empty') }}</p>
@if(userCan('books-create-all'))
-<form action="{{ baseUrl("/settings/users/{$currentUser->id}/switch-book-view") }}" method="POST" class="inline">
- {!! csrf_field() !!}
- {!! method_field('PATCH') !!}
- <input type="hidden" value="{{ $booksViewType === 'list'? 'grid' : 'list' }}" name="view_type">
- @if ($booksViewType === 'list')
- <button type="submit" class="text-pos text-button">@icon('grid'){{ trans('common.grid_view') }}</button>
- @else
- <button type="submit" class="text-pos text-button">@icon('list'){{ trans('common.list_view') }}</button>
- @endif
-</form>
\ No newline at end of file
+<div>
+ <form action="{{ baseUrl("/settings/users/{$currentUser->id}/switch-book-view") }}" method="POST" class="inline">
+ {!! csrf_field() !!}
+ {!! method_field('PATCH') !!}
+ <input type="hidden" value="{{ $view === 'list'? 'grid' : 'list' }}" name="view_type">
+ @if ($view === 'list')
+ <a onclick="this.closest('form').submit()" type="submit" class="icon-list-item">
+ <span class="icon">@icon('grid')</span>
+ <span>{{ trans('common.grid_view') }}</span>
+ </a>
+ @else
+ <a onclick="this.closest('form').submit()" type="submit" class="icon-list-item">
+ <span class="icon">@icon('list')</span>
+ <span>{{ trans('common.list_view') }}</span>
+ </a>
+ @endif
+ </form>
+</div>
\ No newline at end of file
--- /dev/null
+<div class="chapter-child-menu">
+ <p chapter-toggle class="text-muted @if($bookChild->matchesOrContains($current)) open @endif">
+ @icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->pages->count()) }}</span>
+ </p>
+ <ul class="sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
+ @foreach($bookChild->pages as $childPage)
+ <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}">
+ @include('partials.entity-list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
+ </li>
+ @endforeach
+ </ul>
+</div>
\ No newline at end of file
--- /dev/null
+<header id="header" header-mobile-toggle>
+ <div class="grid break-l mx-l">
+ <div>
+ <a href="{{ baseUrl('/') }}" class="logo">
+ @if(setting('app-logo', '') !== 'none')
+ <img class="logo-image" src="{{ setting('app-logo', '') === '' ? baseUrl('/logo.png') : baseUrl(setting('app-logo', '')) }}" alt="Logo">
+ @endif
+ @if (setting('app-name-header'))
+ <span class="logo-text">{{ setting('app-name') }}</span>
+ @endif
+ </a>
+ <div class="mobile-menu-toggle hide-over-l">@icon('more')</div>
+ </div>
+ <div class="header-search hide-under-l">
+ <form action="{{ baseUrl('/search') }}" method="GET" class="search-box">
+ <button id="header-search-box-button" type="submit">@icon('search') </button>
+ <input id="header-search-box-input" type="text" name="term" tabindex="2" placeholder="{{ trans('common.search') }}" value="{{ isset($searchTerm) ? $searchTerm : '' }}">
+ </form>
+ </div>
+ <div class="text-right">
+ <div class="header-links">
+ <div class="links text-center">
+ <a class="hide-over-l" href="{{ baseUrl('/search') }}">@icon('search'){{ trans('common.search') }}</a>
+ @if(userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
+ <a href="{{ baseUrl('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
+ @endif
+ <a href="{{ baseUrl('/books') }}">@icon('book'){{ trans('entities.books') }}</a>
+ @if(signedInUser() && userCan('settings-manage'))
+ <a href="{{ baseUrl('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
+ @endif
+ @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
+ <a href="{{ baseUrl('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
+ @endif
+ @if(!signedInUser())
+ @if(setting('registration-enabled', false))
+ <a href="{{ baseUrl("/register") }}">@icon('new-user') {{ trans('auth.sign_up') }}</a>
+ @endif
+ <a href="{{ baseUrl('/login') }}">@icon('login') {{ trans('auth.log_in') }}</a>
+ @endif
+ </div>
+ @if(signedInUser())
+ <?php $currentUser = user(); ?>
+ <div class="dropdown-container" dropdown>
+ <span class="user-name hide-under-l" dropdown-toggle>
+ <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
+ <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
+ </span>
+ <ul>
+ <li>
+ <a href="{{ baseUrl("/user/{$currentUser->id}") }}" class="text-primary">@icon('user'){{ trans('common.view_profile') }}</a>
+ </li>
+ <li>
+ <a href="{{ baseUrl("/settings/users/{$currentUser->id}") }}" class="text-primary">@icon('edit'){{ trans('common.edit_profile') }}</a>
+ </li>
+ <li>
+ <a href="{{ baseUrl('/logout') }}" class="text-neg">@icon('logout'){{ trans('auth.logout') }}</a>
+ </li>
+ </ul>
+ </div>
+ @endif
+ </div>
+ </div>
+ </div>
+</header>
\ No newline at end of file
@extends('simple-layout')
-@section('toolbar')
- <div class="col-sm-6 faded">
- <div class="action-buttons text-left">
- <a expand-toggle=".entity-list.compact .entity-item-snippet" class="text-primary text-button">@icon('expand-text'){{ trans('common.toggle_details') }}</a>
- </div>
- </div>
-@stop
@section('body')
- <div class="container" id="home-default">
- <div class="row">
+ <div class="container px-xl py-l">
+ <a expand-toggle=".entity-list.compact .entity-item-snippet" class="text-muted">@icon('expand-text'){{ trans('common.toggle_details') }}</a>
+ </div>
- <div class="col-sm-4">
- @if(count($draftPages) > 0)
- <div id="recent-drafts" class="card">
- <h3>@icon('edit') {{ trans('entities.my_recent_drafts') }}</h3>
+ <div class="grid contained thirds space-xl break-m" id="home-default">
+ <div>
+ @if(count($draftPages) > 0)
+ <div id="recent-drafts" class="card mb-xl">
+ <h3>{{ trans('entities.my_recent_drafts') }}</h3>
+ <div class="px-m">
@include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact'])
</div>
- @endif
+ </div>
+ @endif
- <div class="card">
- <h3>@icon($signedIn ? 'view' : 'star-circle') {{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}</h3>
+ <div id="{{ $signedIn ? 'recently-viewed' : 'recent-books' }}" class="card mb-xl">
+ <h3>{{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}</h3>
+ <div class="px-m">
@include('partials/entity-list', [
- 'entities' => $recents,
- 'style' => 'compact',
- 'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty')
- ])
+ 'entities' => $recents,
+ 'style' => 'compact',
+ 'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty')
+ ])
</div>
</div>
+ </div>
- <div class="col-sm-4">
- <div class="card">
- <h3>@icon('file') <a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h3>
- <div id="recently-updated-pages">
- @include('partials/entity-list', [
- 'entities' => $recentlyUpdatedPages,
- 'style' => 'compact',
- 'emptyText' => trans('entities.no_pages_recently_updated')
- ])
- </div>
+ <div>
+ <div id="recent-pages" class="card mb-xl">
+ <h3><a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h3>
+ <div id="recently-updated-pages" class="px-m">
+ @include('partials/entity-list', [
+ 'entities' => $recentlyUpdatedPages,
+ 'style' => 'compact',
+ 'emptyText' => trans('entities.no_pages_recently_updated')
+ ])
</div>
</div>
+ </div>
- <div class="col-sm-4" id="recent-activity">
- <div class="card">
- <h3>@icon('time') {{ trans('entities.recent_activity') }}</h3>
+ <div>
+ <div id="recent-activity">
+ <div class="card mb-xl">
+ <h3>{{ trans('entities.recent_activity') }}</h3>
@include('partials/activity-list', ['activity' => $activity])
</div>
</div>
-
</div>
+
</div>
-<div class="breadcrumbs">
- @if (userCan('view', $page->book))
- <a href="{{ $page->book->getUrl() }}" class="text-book text-button">@icon('book'){{ $page->book->getShortName() }}</a>
- <span class="sep">»</span>
- @endif
- @if($page->hasChapter() && userCan('view', $page->chapter))
- <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
- @icon('chapter')
- {{ $page->chapter->getShortName() }}
- </a>
- <span class="sep">»</span>
- @endif
- <a href="{{ $page->getUrl() }}" class="text-page text-button">@icon('page'){{ $page->getShortName() }}</a>
-</div>
\ No newline at end of file
+@include('partials.breadcrumbs', [
+ 'page' => $page,
+ 'chapter' => $page->hasChapter() ? $page->chapter : null,
+ 'book' => $page->book,
+])
\ No newline at end of file
<div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
- <h4>
- @if (isset($showPath) && $showPath)
- <a href="{{ $page->book->getUrl() }}" class="text-book">
- @icon('book'){{ $page->book->getShortName() }}
- </a>
- <span class="text-muted"> » </span>
- @if($page->chapter)
- <a href="{{ $page->chapter->getUrl() }}" class="text-chapter">
- @icon('chapter'){{ $page->chapter->getShortName() }}
+ <div class="entity-icon text-page">@icon('page')</div>
+ <div class="content">
+
+ <h4>
+ @if (isset($showPath) && $showPath)
+ <a href="{{ $page->book->getUrl() }}" class="text-book">
+ @icon('book'){{ $page->book->getShortName() }}
</a>
<span class="text-muted"> » </span>
+ @if($page->chapter)
+ <a href="{{ $page->chapter->getUrl() }}" class="text-chapter">
+ @icon('chapter'){{ $page->chapter->getShortName() }}
+ </a>
+ <span class="text-muted"> » </span>
+ @endif
@endif
+ <a href="{{ $page->getUrl() }}" class="entity-list-item-link"><span class="entity-list-item-name break-text">{{ $page->name }}</span></a>
+ </h4>
+
+
+ <div class="entity-item-snippet">
+ @if(isset($page->searchSnippet))
+ <p class="text-muted break-text">{!! $page->searchSnippet !!}</p>
+ @else
+ <p class="text-muted break-text">{{ $page->getExcerpt() }}</p>
+ @endif
+ </div>
+
+ @if(isset($style) && $style === 'detailed')
+ <div class="row meta text-muted text-small">
+ <div class="col-md-6">
+ @include('partials.entity-meta', ['entity' => $page])
+ </div>
+ <div class="col-md-6">
+ <a class="text-book" href="{{ $page->book->getUrl() }}">@icon('book'){{ $page->book->getShortName(30) }}</a>
+ <br>
+ @if($page->chapter)
+ <a class="text-chapter" href="{{ $page->chapter->getUrl() }}">@icon('chapter'){{ $page->chapter->getShortName(30) }}</a>
+ @else
+ @icon('chapter') {{ trans('entities.pages_not_in_chapter') }}
+ @endif
+ </div>
+ </div>
@endif
- <a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link">@icon('page')<span class="entity-list-item-name break-text">{{ $page->name }}</span></a>
- </h4>
-
- <div class="entity-item-snippet">
- @if(isset($page->searchSnippet))
- <p class="text-muted break-text">{!! $page->searchSnippet !!}</p>
- @else
- <p class="text-muted break-text">{{ $page->getExcerpt() }}</p>
- @endif
+
</div>
- @if(isset($style) && $style === 'detailed')
- <div class="row meta text-muted text-small">
- <div class="col-md-6">
- @include('partials.entity-meta', ['entity' => $page])
- </div>
- <div class="col-md-6">
- <a class="text-book" href="{{ $page->book->getUrl() }}">@icon('book'){{ $page->book->getShortName(30) }}</a>
- <br>
- @if($page->chapter)
- <a class="text-chapter" href="{{ $page->chapter->getUrl() }}">@icon('chapter'){{ $page->chapter->getShortName(30) }}</a>
- @else
- @icon('chapter') {{ trans('entities.pages_not_in_chapter') }}
- @endif
- </div>
- </div>
- @endif
</div>
\ No newline at end of file
-@extends('sidebar-layout')
+@extends('tri-layout')
+@section('container-classes', 'mt-xl')
-@section('toolbar')
- <div class="col-sm-8 col-xs-5 faded">
- @include('pages._breadcrumbs', ['page' => $page])
- </div>
- <div class="col-sm-4 col-xs-7 faded">
- <div class="action-buttons">
- <span dropdown class="dropdown-container">
- <div dropdown-toggle class="text-button text-primary">@icon('export'){{ trans('entities.export') }}</div>
- <ul class="wide">
- <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
- <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
- <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
- </ul>
- </span>
- @if(userCan('page-update', $page))
- <a href="{{ $page->getUrl('/edit') }}" class="text-primary text-button" >@icon('edit'){{ trans('common.edit') }}</a>
- @endif
- @if(userCan('page-update', $page) || userCan('restrictions-manage', $page) || userCan('page-delete', $page))
- <div dropdown class="dropdown-container">
- <a dropdown-toggle class="text-primary text-button">@icon('more') {{ trans('common.more') }}</a>
- <ul>
- @if(userCan('page-update', $page))
- <li><a href="{{ $page->getUrl('/copy') }}" class="text-primary" >@icon('copy'){{ trans('common.copy') }}</a></li>
- @if(userCan('page-delete', $page))
- <li><a href="{{ $page->getUrl('/move') }}" class="text-primary" >@icon('folder'){{ trans('common.move') }}</a></li>
- @endif
- <li><a href="{{ $page->getUrl('/revisions') }}" class="text-primary">@icon('history'){{ trans('entities.revisions') }}</a></li>
- @endif
- @if(userCan('restrictions-manage', $page))
- <li><a href="{{ $page->getUrl('/permissions') }}" class="text-primary">@icon('lock'){{ trans('entities.permissions') }}</a></li>
- @endif
- @if(userCan('page-delete', $page))
- <li><a href="{{ $page->getUrl('/delete') }}" class="text-neg">@icon('delete'){{ trans('common.delete') }}</a></li>
- @endif
- </ul>
- </div>
- @endif
-
- </div>
- </div>
-@stop
-
-@section('sidebar')
+@section('left')
@if($page->tags->count() > 0)
<section>
@endif
@if ($page->attachments->count() > 0)
- <div class="card">
- <h3>@icon('attach') {{ trans('entities.pages_attachments') }}</h3>
+ <div id="page-attachments" class="mb-xl">
+ <h5>{{ trans('entities.pages_attachments') }}</h5>
<div class="body">
@foreach($page->attachments as $attachment)
<div class="attachment">
@endif
@if (isset($pageNav) && count($pageNav))
- <div class="card">
- <h3>@icon('open-book') {{ trans('entities.pages_navigation') }}</h3>
+ <div id="page-navigation" class="mb-xl">
+ <h5>{{ trans('entities.pages_navigation') }}</h5>
<div class="body">
<div class="sidebar-page-nav menu">
@foreach($pageNav as $navItem)
</div>
@endif
- <div class="card entity-details">
- <h3>@icon('info') {{ trans('common.details') }}</h3>
+ <div id="page-details" class="entity-details mb-xl">
+ <h5>{{ trans('common.details') }}</h5>
<div class="body text-muted text-small blended-links">
@include('partials.entity-meta', ['entity' => $page])
</div>
</div>
- @include('partials/book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
-
+ @include('partials.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
@stop
-@section('body-wrap-classes', 'flex-fill columns')
-
@section('body')
- <div class="page-content flex" page-display="{{ $page->id }}">
+ <div class="mb-m">
+ @include('pages._breadcrumbs', ['page' => $page])
+ </div>
+
+ <div class="content-wrap card">
+ <div class="page-content flex" page-display="{{ $page->id }}">
- <div class="pointer-container" id="pointer">
- <div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
- <span class="icon text-primary">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
- <span class="input-group">
+ <div class="pointer-container" id="pointer">
+ <div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
+ <span class="icon text-primary">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
+ <span class="input-group">
<input readonly="readonly" type="text" id="pointer-url" placeholder="url">
<button class="button icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
</span>
- @if(userCan('page-update', $page))
- <a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
- class="button icon heading-edit-icon" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
- @endif
+ @if(userCan('page-update', $page))
+ <a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
+ class="button icon heading-edit-icon" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
+ @endif
+ </div>
</div>
- </div>
- @include('pages/page-display')
+ @include('pages.page-display')
+ </div>
</div>
@if ($commentsEnabled)
- <div class="container small nopad comments-container">
- @include('comments/comments', ['page' => $page])
+ <div class="container small nopad comments-container mb-l">
+ @include('comments.comments', ['page' => $page])
+ <div class="clearfix"></div>
</div>
@endif
@stop
+
+@section('right')
+ <div class="actions mb-xl">
+ <h5>Actions</h5>
+
+ <div class="icon-list text-primary">
+ {{--Export--}}
+ <div dropdown class="dropdown-container block">
+ <div dropdown-toggle class="icon-list-item">
+ <span class="icon">@icon('export')</span>
+ <span>{{ trans('entities.export') }}</span>
+ </div>
+ <ul class="wide">
+ <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
+ <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
+ <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
+ </ul>
+ </div>
+
+ {{--User Actions--}}
+ @if(userCan('page-update', $page))
+ <a href="{{ $page->getUrl('/edit') }}" class="icon-list-item">
+ <span class="icon">@icon('edit')</span>
+ <span>{{ trans('common.edit') }}</span>
+ </a>
+ <a href="{{ $page->getUrl('/copy') }}" class="icon-list-item">
+ <span class="icon">@icon('copy')</span>
+ <span>{{ trans('common.copy') }}</span>
+ </a>
+ @if(userCan('page-delete', $page))
+ <a href="{{ $page->getUrl('/move') }}" class="icon-list-item">
+ <span class="icon">@icon('folder')</span>
+ <span>{{ trans('common.move') }}</span>
+ </a>
+ @endif
+ <a href="{{ $page->getUrl('/revisions') }}" class="icon-list-item">
+ <span class="icon">@icon('history')</span>
+ <span>{{ trans('entities.revisions') }}</span>
+ </a>
+ @endif
+ @if(userCan('restrictions-manage', $page))
+ <a href="{{ $page->getUrl('/permissions') }}" class="icon-list-item">
+ <span class="icon">@icon('lock')</span>
+ <span>{{ trans('entities.permissions') }}</span>
+ </a>
+ @endif
+ @if(userCan('page-delete', $page))
+ <a href="{{ $page->getUrl('/delete') }}" class="icon-list-item">
+ <span class="icon">@icon('delete')</span>
+ <span>{{ trans('common.delete') }}</span>
+ </a>
+ @endif
+ </div>
+
+ </div>
+@stop
+++ /dev/null
-<div class="dropdown-container" dropdown>
- <span class="user-name" dropdown-toggle>
- <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
- <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
- </span>
- <ul>
- <li>
- <a href="{{ baseUrl("/user/{$currentUser->id}") }}" class="text-primary">@icon('user') {{ trans('common.view_profile') }}</a>
- </li>
- <li>
- <a href="{{ baseUrl("/settings/users/{$currentUser->id}") }}" class="text-primary">@icon('edit') {{ trans('common.edit_profile') }}</a>
- </li>
- <li>
- <a href="{{ baseUrl('/logout') }}" class="text-neg">@icon('logout') {{ trans('auth.logout') }}</a>
- </li>
- </ul>
-</div>
\ No newline at end of file
{{--Requires an Activity item with the name $activity passed in--}}
-@if($activity->user)
- <div class="left">
- <img class="avatar" src="{{ $activity->user->getAvatar(30) }}" alt="{{ $activity->user->name }}">
- </div>
-@endif
+<div>
+ @if($activity->user)
+ <img class="avatar" src="{{ $activity->user->getAvatar(30) }}" alt="{{ $activity->user->name }}">
+ @endif
+</div>
-<div class="right" v-pre>
+<div v-pre>
@if($activity->user)
<a href="{{ $activity->user->getProfileUrl() }}">{{ $activity->user->name }}</a>
@else
-<div class="card book-tree" v-pre>
- <h3>@icon('book') {{ trans('entities.books_navigation') }}</h3>
- <div class="body">
- <ul class="sidebar-page-list menu">
+<div id="book-tree" class="book-tree mb-xl" v-pre>
+ <h5>{{ trans('entities.books_navigation') }}</h5>
+ @if (userCan('view', $book))
+ <div class="entity-list">
+ @include('partials.entity-list-item-basic', ['entity' => $book, 'classes' => ($current->matches($book)? 'selected' : '')])
+ </div>
+ @endif
- @if (userCan('view', $book))
- <li class="book-header"><a href="{{ $book->getUrl() }}" class="book {{ $current->matches($book)? 'selected' : '' }}">@icon('book'){{$book->name}}</a></li>
- @endif
+ <ul class="sidebar-page-list menu entity-list">
- @foreach($sidebarTree as $bookChild)
- <li class="list-item-{{ $bookChild->getClassName() }} {{ $bookChild->getClassName() }} {{ $bookChild->isA('page') && $bookChild->draft ? 'draft' : '' }}">
- <a href="{{ $bookChild->getUrl() }}" class="{{ $bookChild->getClassName() }} {{ $current->matches($bookChild)? 'selected' : '' }}">
- @if($bookChild->isA('chapter'))@icon('chapter')@else @icon('page')@endif{{ $bookChild->name }}
- </a>
+ @foreach($sidebarTree as $bookChild)
+ <li class="list-item-{{ $bookChild->getClassName() }} {{ $bookChild->getClassName() }} {{ $bookChild->isA('page') && $bookChild->draft ? 'draft' : '' }}">
+ @include('partials.entity-list-item-basic', ['entity' => $bookChild, 'classes' => $current->matches($bookChild)? 'selected' : ''])
- @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
- <p chapter-toggle class="text-muted @if($bookChild->matchesOrContains($current)) open @endif">
- @icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->pages->count()) }}</span>
- </p>
- <ul class="menu sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
- @foreach($bookChild->pages as $childPage)
- <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}">
- <a href="{{ $childPage->getUrl() }}" class="page {{ $current->matches($childPage)? 'selected' : '' }}">
- @icon('page') {{ $childPage->name }}
- </a>
- </li>
- @endforeach
- </ul>
- @endif
+ @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
+ @include('chapters.child-menu', ['chapter' => $bookChild, 'current' => $current])
+ @endif
-
- </li>
- @endforeach
- </ul>
- </div>
+ </li>
+ @endforeach
+ </ul>
</div>
\ No newline at end of file
--- /dev/null
+<div class="breadcrumbs text-center">
+ @if (isset($book) && userCan('view', $book))
+ <a href="{{ $book->getUrl() }}" class="text-book">
+ @icon('book'){{ $book->getShortName() }}
+ </a>
+ <div class="separator">@icon('chevron-right')</div>
+ @endif
+ @if(isset($chapter) && userCan('view', $chapter))
+ <a href="{{ $chapter->getUrl() }}" class="text-chapter">
+ @icon('chapter'){{ $chapter->getShortName() }}
+ </a>
+ <div class="separator">@icon('chevron-right')</div>
+ @endif
+ @if(isset($page) && userCan('view', $page))
+ <a href="{{ $page->getUrl() }}" class="text-page">
+ @icon('page'){{ $page->getShortName() }}
+ </a>
+ @endif
+</div>
\ No newline at end of file
--- /dev/null
+<?php $type = $entity->getType(); ?>
+<a href="{{ $entity->getUrl() }}" class="{{$type}} {{$type === 'page' && $entity->draft ? 'draft' : ''}} {{$classes ?? ''}} entity-list-item" data-entity-type="{{$type}}" data-entity-id="{{$entity->id}}">
+ <div class="icon text-{{$type}}">@icon($type)</div>
+ <div class="content">
+ <h4 class="entity-list-item-name break-text">{{ $entity->name }}</h4>
+ {{ $slot ?? '' }}
+ </div>
+</a>
\ No newline at end of file
--- /dev/null
+@component('partials.entity-list-item-basic', ['entity' => $entity])
+<div class="entity-item-snippet">
+ <p class="text-muted break-text">{{ $entity->getExcerpt() }}</p>
+</div>
+@endcomponent
\ No newline at end of file
-<div class="entity-list @if(isset($style)){{ $style }}@endif">
+<div class="entity-list {{ $style ?? '' }}">
@if(count($entities) > 0)
@foreach($entities as $index => $entity)
- @if($entity->isA('page'))
- @include('pages/list-item', ['page' => $entity])
- @elseif($entity->isA('book'))
- @include('books/list-item', ['book' => $entity])
- @elseif($entity->isA('chapter'))
- @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true])
- @elseif($entity->isA('bookshelf'))
- @include('shelves/list-item', ['bookshelf' => $entity])
- @endif
-
- @if($index !== count($entities) - 1)
- <hr>
- @endif
-
+ @include('partials.entity-list-item', ['entity' => $entity])
@endforeach
@else
<p class="text-muted empty-text">
- {{ $emptyText or trans('common.no_items') }}
+ {{ $emptyText ?? trans('common.no_items') }}
</p>
@endif
</div>
\ No newline at end of file
--- /dev/null
+<?php
+ $selectedSort = (isset($sort) && array_key_exists($sort, $options)) ? $sort : array_keys($options)[0];
+ $order = (isset($order) && in_array($order, ['asc', 'desc'])) ? $order : 'asc';
+?>
+<div class="list-sort-container" list-sort-control>
+ <div class="list-sort-label">{{ trans('common.sort') }}</div>
+ <form action="{{ baseUrl("/settings/users/{$currentUser->id}/change-books-sort") }}" method="post">
+
+ {!! csrf_field() !!}
+ {!! method_field('PATCH') !!}
+ <input type="hidden" value="{{ $selectedSort }}" name="sort">
+ <input type="hidden" value="{{ $order }}" name="order">
+
+ <div class="list-sort">
+ <div class="list-sort-type dropdown-container" dropdown>
+ <div dropdown-toggle>{{ $options[$selectedSort] }}</div>
+ <ul>
+ @foreach($options as $key => $label)
+ <li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}">{{ $label }}</a></li>
+ @endforeach
+ </ul>
+ </div>
+ <div class="list-sort-dir" data-sort-dir>
+ @if($order === 'desc')
+ @icon('sort-up')
+ @else
+ @icon('sort-down')
+ @endif
+ </div>
+ </div>
+ </form>
+</div>
\ No newline at end of file
<!DOCTYPE html>
-<html class="shaded">
+<html>
<head>
<title>{{ setting('app-name') }}</title>
@section('content')
- <div class="toolbar-container">
- <div class="faded-small toolbar">
- <div class="container fluid">
- <div class="row">
- @yield('toolbar')
- </div>
- </div>
- </div>
+ <div class="toolbar px-xl py-m">
+ @yield('toolbar')
</div>
<div sidebar class="sidebar flex print-hidden" id="sidebar">
<div class="sidebar-toggle primary-background-light">@icon('caret-right-circle')
</div>
- <div class="scroll-body">
+ <div class="scroll-body px-xl">
@yield('sidebar')
</div>
</div>
- <div class="content flex @yield('body-wrap-classes')">
+ <div class="mr-xl flex @yield('body-wrap-classes')">
@yield('body')
</div>
</div>
@section('content')
<div class="toolbar-container">
- <div class="faded-small toolbar">
- <div class="container fluid">
- <div class="row">
- @yield('toolbar')
- </div>
- </div>
- </div>
+ @yield('toolbar')
</div>
-
<div class="flex-fill flex">
<div class="content flex">
<div class="scroll-body">
--- /dev/null
+@extends('base')
+
+@section('body-class', 'tri-layout')
+
+@section('content')
+
+ <div class="toolbar px-xl">
+ @yield('toolbar')
+ </div>
+
+ <div class="tri-layout-container @yield('container-classes')" tri-layout @yield('container-attrs') >
+
+ <div class="tri-layout-left print-hidden " id="sidebar">
+ <div class="tri-layout-left-contents">
+ @yield('left')
+ </div>
+ </div>
+
+ <div class="@yield('body-wrap-classes') tri-layout-middle">
+ @yield('body')
+ </div>
+
+ <div class="tri-layout-right print-hidden">
+ <div class="tri-layout-right-contents">
+ @yield('right')
+ </div>
+ </div>
+ </div>
+
+@stop
Route::get('/users/{id}/delete', 'UserController@delete');
Route::patch('/users/{id}/switch-book-view', 'UserController@switchBookView');
Route::patch('/users/{id}/switch-shelf-view', 'UserController@switchShelfView');
+ Route::patch('/users/{id}/change-books-sort', 'UserController@changeBooksSort');
Route::post('/users/create', 'UserController@store');
Route::get('/users/{id}', 'UserController@edit');
Route::put('/users/{id}', 'UserController@update');