Merge pull request #3433 from BookStackApp/tiny_improvements

Bunch of tiny improvements
This commit is contained in:
Dan Brown 2022-05-30 16:51:59 +01:00 committed by GitHub
commit 6ce34fe6cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 651 additions and 332 deletions

View File

@ -1,8 +1,8 @@
{
"private": true,
"scripts": {
"build:css:dev": "sass ./resources/sass:./public/dist",
"build:css:watch": "sass ./resources/sass:./public/dist --watch",
"build:css:dev": "sass ./resources/sass:./public/dist --embed-sources",
"build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
"build:js:dev": "node dev/build/esbuild.js",
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71zM5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1z"/></svg>

After

Width:  |  Height:  |  Size: 297 B

View File

@ -242,6 +242,21 @@ export function popupEditor(elem, modeSuggestion) {
});
}
/**
* Create an inline editor to replace the given textarea.
* @param {HTMLTextAreaElement} textArea
* @param {String} mode
* @returns {CodeMirror3}
*/
export function inlineEditor(textArea, mode) {
return CodeMirror.fromTextArea(textArea, {
mode: getMode(mode, textArea.value),
lineNumbers: true,
lineWrapping: false,
theme: getTheme(),
});
}
/**
* Set the mode of a codemirror instance.
* @param cmInstance

View File

@ -0,0 +1,37 @@
import {slideUp, slideDown} from "../services/animations";
/**
* @extends {Component}
*/
class ChapterContents {
setup() {
this.list = this.$refs.list;
this.toggle = this.$refs.toggle;
this.isOpen = this.toggle.classList.contains('open');
this.toggle.addEventListener('click', this.click.bind(this));
}
open() {
this.toggle.classList.add('open');
this.toggle.setAttribute('aria-expanded', 'true');
slideDown(this.list, 180);
this.isOpen = true;
}
close() {
this.toggle.classList.remove('open');
this.toggle.setAttribute('aria-expanded', 'false');
slideUp(this.list, 180);
this.isOpen = false;
}
click(event) {
event.preventDefault();
this.isOpen ? this.close() : this.open();
}
}
export default ChapterContents;

View File

@ -1,33 +0,0 @@
import {slideUp, slideDown} from "../services/animations";
class ChapterToggle {
constructor(elem) {
this.elem = elem;
this.isOpen = elem.classList.contains('open');
elem.addEventListener('click', this.click.bind(this));
}
open() {
const list = this.elem.parentNode.querySelector('.inset-list');
this.elem.classList.add('open');
this.elem.setAttribute('aria-expanded', 'true');
slideDown(list, 240);
}
close() {
const list = this.elem.parentNode.querySelector('.inset-list');
this.elem.classList.remove('open');
this.elem.setAttribute('aria-expanded', 'false');
slideUp(list, 240);
}
click(event) {
event.preventDefault();
this.isOpen ? this.close() : this.open();
this.isOpen = !this.isOpen;
}
}
export default ChapterToggle;

View File

@ -0,0 +1,16 @@
/**
* A simple component to render a code editor within the textarea
* this exists upon.
* @extends {Component}
*/
class CodeTextarea {
async setup() {
const mode = this.$opts.mode;
const Code = await window.importVersioned('code');
Code.inlineEditor(this.$el, mode);
}
}
export default CodeTextarea;

View File

@ -1,4 +1,5 @@
import {debounce} from "../services/util";
import {transitionHeight} from "../services/animations";
class DropdownSearch {
@ -51,7 +52,9 @@ class DropdownSearch {
try {
const resp = await window.$http.get(this.getAjaxUrl(searchTerm));
const animate = transitionHeight(this.listContainerElem, 80);
this.listContainerElem.innerHTML = resp.data;
animate();
} catch (err) {
console.error(err);
}

View File

@ -28,18 +28,31 @@ class DropDown {
this.menu.classList.add('anim', 'menuIn');
this.toggle.setAttribute('aria-expanded', 'true');
const menuOriginalRect = this.menu.getBoundingClientRect();
let heightOffset = 0;
const toggleHeight = this.toggle.getBoundingClientRect().height;
const dropUpwards = menuOriginalRect.bottom > window.innerHeight;
// If enabled, Move to body to prevent being trapped within scrollable sections
if (this.moveMenu) {
// Move to body to prevent being trapped within scrollable sections
this.rect = this.menu.getBoundingClientRect();
this.body.appendChild(this.menu);
this.menu.style.position = 'fixed';
if (this.direction === 'right') {
this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
this.menu.style.right = `${(menuOriginalRect.right - menuOriginalRect.width)}px`;
} else {
this.menu.style.left = `${this.rect.left}px`;
this.menu.style.left = `${menuOriginalRect.left}px`;
}
this.menu.style.top = `${this.rect.top}px`;
this.menu.style.width = `${this.rect.width}px`;
this.menu.style.width = `${menuOriginalRect.width}px`;
heightOffset = dropUpwards ? (window.innerHeight - menuOriginalRect.top - toggleHeight / 2) : menuOriginalRect.top;
}
// Adjust menu to display upwards if near the bottom of the screen
if (dropUpwards) {
this.menu.style.top = 'initial';
this.menu.style.bottom = `${heightOffset}px`;
} else {
this.menu.style.top = `${heightOffset}px`;
this.menu.style.bottom = 'initial';
}
// Set listener to hide on mouse leave or window click
@ -74,18 +87,21 @@ class DropDown {
this.menu.style.display = 'none';
this.menu.classList.remove('anim', 'menuIn');
this.toggle.setAttribute('aria-expanded', 'false');
this.menu.style.top = '';
this.menu.style.bottom = '';
if (this.moveMenu) {
this.menu.style.position = '';
this.menu.style[this.direction] = '';
this.menu.style.top = '';
this.menu.style.width = '';
this.container.appendChild(this.menu);
}
this.showing = false;
}
getFocusable() {
return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
return Array.from(this.menu.querySelectorAll('[tabindex]:not([tabindex="-1"]),[href],button,input:not([type=hidden])'));
}
focusNext() {

View File

@ -6,9 +6,10 @@ import attachmentsList from "./attachments-list.js"
import autoSuggest from "./auto-suggest.js"
import backToTop from "./back-to-top.js"
import bookSort from "./book-sort.js"
import chapterToggle from "./chapter-toggle.js"
import chapterContents from "./chapter-contents.js"
import codeEditor from "./code-editor.js"
import codeHighlighter from "./code-highlighter.js"
import codeTextarea from "./code-textarea.js"
import collapsible from "./collapsible.js"
import confirmDialog from "./confirm-dialog"
import customCheckbox from "./custom-checkbox.js"
@ -62,9 +63,10 @@ const componentMapping = {
"auto-suggest": autoSuggest,
"back-to-top": backToTop,
"book-sort": bookSort,
"chapter-toggle": chapterToggle,
"chapter-contents": chapterContents,
"code-editor": codeEditor,
"code-highlighter": codeHighlighter,
"code-textarea": codeTextarea,
"collapsible": collapsible,
"confirm-dialog": confirmDialog,
"custom-checkbox": customCheckbox,

View File

@ -49,7 +49,7 @@ export function slideUp(element, animTime = 400) {
const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
const animStyles = {
height: [`${currentHeight}px`, '0px'],
maxHeight: [`${currentHeight}px`, '0px'],
overflow: ['hidden', 'hidden'],
paddingTop: [currentPaddingTop, '0px'],
paddingBottom: [currentPaddingBottom, '0px'],
@ -73,7 +73,7 @@ export function slideDown(element, animTime = 400) {
const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
const animStyles = {
height: ['0px', `${targetHeight}px`],
maxHeight: ['0px', `${targetHeight}px`],
overflow: ['hidden', 'hidden'],
paddingTop: ['0px', targetPaddingTop],
paddingBottom: ['0px', targetPaddingBottom],
@ -82,6 +82,38 @@ export function slideDown(element, animTime = 400) {
animateStyles(element, animStyles, animTime);
}
/**
* Transition the height of the given element between two states.
* Call with first state, and you'll receive a function in return.
* Call the returned function in the second state to animate between those two states.
* If animating to/from 0-height use the slide-up/slide down as easier alternatives.
* @param {Element} element - Element to animate
* @param {Number} animTime - Animation time in ms
* @returns {function} - Function to run in second state to trigger animation.
*/
export function transitionHeight(element, animTime = 400) {
const startHeight = element.getBoundingClientRect().height;
const initialComputedStyles = getComputedStyle(element);
const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top');
const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom');
return () => {
cleanupExistingElementAnimation(element);
const targetHeight = element.getBoundingClientRect().height;
const computedStyles = getComputedStyle(element);
const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
const animStyles = {
height: [`${startHeight}px`, `${targetHeight}px`],
overflow: ['hidden', 'hidden'],
paddingTop: [startPaddingTop, targetPaddingTop],
paddingBottom: [startPaddingBottom, targetPaddingBottom],
};
animateStyles(element, animStyles, animTime);
};
}
/**
* Animate the css styles of an element using FLIP animation techniques.
* Styles must be an object where the keys are style properties, camelcase, and the values

View File

@ -47,6 +47,8 @@ return [
'previous' => 'Previous',
'filter_active' => 'Active Filter:',
'filter_clear' => 'Clear Filter',
'download' => 'Download',
'open_in_tab' => 'Open in Tab',
// Sort Options
'sort_options' => 'Sort Options',

View File

@ -66,7 +66,6 @@
@include lightDark(background-color, #FFF, #222);
box-shadow: $bs-card;
border-radius: 3px;
border: 1px solid transparent;
.body, p.empty-text {
padding: $-m;
}

View File

@ -61,7 +61,7 @@
}
}
[chapter-toggle] {
.chapter-contents-toggle {
cursor: pointer;
margin: 0;
transition: all ease-in-out 180ms;
@ -77,7 +77,7 @@
transform: rotate(90deg);
}
svg[data-icon="caret-right"] + * {
margin-inline-start: $-xs;
margin-inline-start: $-xxs;
}
}
@ -731,6 +731,55 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
}
}
.dropdown-search {
position: relative;
}
.dropdown-search-toggle-breadcrumb {
border: 1px solid transparent;
border-radius: 4px;
line-height: normal;
padding: $-xs;
&:hover {
border-color: #DDD;
}
.svg-icon {
margin-inline-end: 0;
}
}
.dropdown-search-toggle-select {
display: flex;
gap: $-s;
line-height: normal;
.svg-icon {
height: 16px;
margin: 0;
}
.avatar {
height: 22px;
width: 22px;
}
.avatar + span {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dropdown-search-toggle-caret {
font-size: 1.15rem;
}
}
.dropdown-search-toggle-select-label {
min-width: 0;
white-space: nowrap;
}
.dropdown-search-toggle-select-caret {
font-size: 1.5rem;
line-height: 0;
margin-left: auto;
margin-top: -2px;
}
.dropdown-search-dropdown {
box-shadow: $bs-med;
overflow: hidden;
@ -739,7 +788,9 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
display: none;
position: absolute;
z-index: 80;
right: -$-m;
right: 0;
top: 0;
margin-top: $-m;
@include rtl {
right: auto;
left: -$-m;
@ -767,12 +818,15 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
text-decoration: none;
}
}
input {
input, input:focus {
padding-inline-start: $-xl;
border-radius: 0;
border: 0;
border-bottom: 1px solid #DDD;
}
input:focus {
outline: 0;
}
}
@include smaller-than($m) {
@ -784,10 +838,4 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
.dropdown-search-dropdown .dropdown-search-list {
max-height: 240px;
}
}
.custom-select-input {
max-width: 280px;
border: 1px solid #D4D4D4;
border-radius: 3px;
}

View File

@ -7,7 +7,8 @@
@include lightDark(color, #666, #AAA);
display: inline-block;
font-size: $fs-m;
padding: $-xs*1.5;
padding: $-xs*1.8;
height: 40px;
width: 250px;
max-width: 100%;
@ -350,16 +351,13 @@ input[type=color] {
}
}
.inline-input-style {
.title-input input[type="text"] {
display: block;
width: 100%;
padding: $-s;
}
.title-input input[type="text"] {
@extend .inline-input-style;
margin-top: 0;
font-size: 2em;
height: auto;
}
.title-input.page-title {
@ -373,6 +371,7 @@ input[type=color] {
max-width: 840px;
margin: 0 auto;
border: none;
height: auto;
}
}
@ -383,10 +382,12 @@ input[type=color] {
}
.description-input textarea {
@extend .inline-input-style;
display: block;
width: 100%;
padding: $-s;
font-size: $fs-m;
color: #666;
width: 100%;
height: auto;
}
div[editor-type="markdown"] .title-input.page-title input[type="text"] {
@ -413,9 +414,11 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
}
input {
display: block;
padding: $-xs * 1.5;
padding-inline-start: $-l + 4px;
width: 300px;
max-width: 100%;
height: auto;
}
&.flexible input {
width: 100%;

View File

@ -21,19 +21,28 @@ header {
color: rgb(250, 250, 250);
border-bottom: 1px solid #DDD;
box-shadow: $bs-card;
padding: $-xxs 0;
@include lightDark(border-bottom-color, #DDD, #000);
@include whenDark {
filter: saturate(0.8) brightness(0.8);
}
.header-links {
display: flex;
align-items: center;
justify-content: end;
}
.links {
display: inline-block;
vertical-align: top;
}
.links a {
display: inline-block;
padding: $-m;
padding: 10px $-m;
color: #FFF;
border-radius: 3px;
}
.links a:hover {
text-decoration: none;
background-color: rgba(255, 255, 255, .15);
}
.dropdown-container {
padding-inline-start: $-m;
@ -49,19 +58,25 @@ header {
.user-name {
vertical-align: top;
position: relative;
display: inline-block;
display: inline-flex;
align-items: center;
cursor: pointer;
> * {
vertical-align: top;
}
padding: $-s;
margin: 0 (-$-s);
border-radius: 3px;
gap: $-xs;
> span {
padding-inline-start: $-xs;
display: inline-block;
padding-top: $-xxs;
line-height: 1;
}
> svg {
padding-top: 4px;
font-size: 18px;
margin-top: -2px;
margin-inline-end: 0;
}
&:hover {
background-color: rgba(255, 255, 255, 0.15);
}
@include between($l, $xl) {
padding-inline-start: $-xs;
@ -79,22 +94,26 @@ header {
header .search-box {
display: inline-block;
margin-top: 10px;
input {
background-color: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 40px;
color: #EEE;
z-index: 2;
height: auto;
padding: $-xs*1.5;
padding-inline-start: 40px;
&:focus {
outline: none;
border: 1px solid rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.4);
}
}
button {
z-index: 1;
left: 16px;
top: 10px;
color: #FFF;
opacity: 0.6;
@include lightDark(color, rgba(255, 255, 255, 0.8), #AAA);
@include rtl {
left: auto;
@ -104,36 +123,39 @@ header .search-box {
margin-block-end: 0;
}
}
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: #DDD;
}
::-moz-placeholder { /* Firefox 19+ */
color: #DDD;
input::placeholder {
color: #FFF;
opacity: 0.6;
}
@include between($l, $xl) {
max-width: 200px;
}
&:focus-within button {
opacity: 1;
}
}
.logo {
display: inline-block;
display: inline-flex;
padding: ($-s - 6px) $-s;
margin: 6px (-$-s);
gap: $-s;
align-items: center;
border-radius: 4px;
&:hover {
color: #FFF;
text-decoration: none;
background-color: rgba(255, 255, 255, .15);
}
}
.logo-text {
display: inline-block;
font-size: 1.8em;
color: #fff;
font-weight: 400;
@include padding(14px, $-l, 14px, 0);
vertical-align: top;
line-height: 1;
}
.logo-image {
@include margin($-xs, $-s, $-xs, 0);
vertical-align: top;
height: 43px;
}
@ -172,23 +194,29 @@ header .search-box {
overflow: hidden;
position: absolute;
box-shadow: $bs-hover;
margin-top: -$-xs;
margin-top: $-m;
padding: $-xs 0;
&.show {
display: block;
}
}
header .links a, header .dropdown-container ul li a, header .dropdown-container ul li button {
text-align: start;
display: block;
padding: $-s $-m;
display: grid;
align-items: center;
padding: 8px $-m;
gap: $-m;
color: $text-dark;
grid-template-columns: 16px auto;
line-height: 1.4;
@include lightDark(color, $text-dark, #eee);
svg {
margin-inline-end: $-s;
width: 16px;
}
&:hover {
@include lightDark(background-color, #eee, #333);
@include lightDark(color, #000, #fff);
background-color: var(--color-primary-light);
color: var(--color-primary);
text-decoration: none;
}
&:focus {
@ -279,29 +307,6 @@ header .search-box {
}
}
.dropdown-search {
position: relative;
.dropdown-search-toggle {
padding: $-xs;
border: 1px solid transparent;
border-radius: 4px;
&:hover {
border-color: #DDD;
}
}
.svg-icon {
margin-inline-end: 0;
}
}
.dropdown-search-toggle.compact {
padding: $-xxs $-xs;
.avatar {
height: 22px;
width: 22px;
}
}
.faded {
a, button, span, span > div {
color: #666;

View File

@ -155,6 +155,13 @@ body.flexbox {
}
}
.gap-m {
gap: $-m;
}
.justify-flex-start {
justify-content: flex-start;
}
.justify-flex-end {
justify-content: flex-end;
}
@ -295,9 +302,9 @@ body.flexbox {
}
@include larger-than($xxl) {
.tri-layout-left-contents, .tri-layout-right-contents {
padding: $-m;
padding: $-xl $-m;
position: sticky;
top: $-m;
top: 0;
max-height: 100vh;
min-height: 50vh;
overflow-y: scroll;

View File

@ -6,7 +6,7 @@
justify-self: stretch;
align-self: stretch;
height: auto;
margin-inline-end: $-l;
margin-inline-end: $-xs;
}
.icon:after {
opacity: 0.5;
@ -56,13 +56,13 @@
> .content {
flex: 1;
}
.chapter-expansion-toggle {
.chapter-contents-toggle {
border-radius: 0 4px 4px 0;
padding: $-xs $-m;
padding: $-xs ($-m + $-xxs);
width: 100%;
text-align: start;
}
.chapter-expansion-toggle:hover {
.chapter-contents-toggle:hover {
background-color: rgba(0, 0, 0, 0.06);
}
}
@ -157,22 +157,6 @@
@include margin($-xs, -$-s, 0, -$-s);
padding-inline-start: 0;
padding-inline-end: 0;
position: relative;
&:after, .sub-menu:after {
content: '';
display: block;
position: absolute;
left: $-m;
top: 1rem;
bottom: 1rem;
border-inline-start: 4px solid rgba(0, 0, 0, 0.1);
z-index: 0;
@include rtl {
left: auto;
right: $-m;
}
}
ul {
list-style: none;
@ -181,19 +165,20 @@
}
.entity-list-item {
padding-top: $-xxs;
padding-bottom: $-xxs;
padding-top: 2px;
padding-bottom: 2px;
background-clip: content-box;
border-radius: 0 3px 3px 0;
padding-inline-end: 0;
.content {
width: 100%;
padding-top: $-xs;
padding-bottom: $-xs;
max-width: calc(100% - 20px);
}
}
.entity-list-item.selected {
@include lightDark(background-color, rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.08));
@include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
}
.entity-list-item.no-hover {
margin-top: -$-xs;
@ -209,9 +194,18 @@
margin-top: -.2rem;
margin-inline-start: -1rem;
}
[chapter-toggle] {
padding-inline-start: .7rem;
padding-bottom: .2rem;
.chapter-contents-toggle {
display: block;
width: 100%;
text-align: left;
padding: $-xxs $-s ($-xxs * 2) $-s;
border-radius: 0 3px 3px 0;
line-height: 1;
margin-top: -$-xxs;
margin-bottom: -$-xxs;
&:hover {
@include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
}
}
.entity-list-item .icon {
z-index: 2;
@ -220,7 +214,7 @@
align-self: stretch;
flex-shrink: 0;
border-radius: 1px;
opacity: 0.6;
opacity: 0.8;
}
.entity-list-item .icon:after {
opacity: 1;
@ -230,15 +224,11 @@
}
}
.chapter-child-menu {
ul.sub-menu {
display: none;
padding-inline-start: 0;
position: relative;
}
[chapter-toggle].open + .sub-menu {
display: block;
}
.chapter-child-menu ul.sub-menu {
display: none;
padding-inline-start: 0;
position: relative;
margin-bottom: 0;
}
// Sortable Lists
@ -415,6 +405,7 @@ ul.pagination {
padding: $-s $-m;
display: flex;
align-items: center;
gap: $-m;
background-color: transparent;
border: 0;
width: 100%;
@ -424,7 +415,6 @@ ul.pagination {
color: #666;
}
> span:first-child {
margin-inline-end: $-m;
flex-basis: 1.88em;
flex: none;
}
@ -439,8 +429,8 @@ ul.pagination {
cursor: pointer;
}
&:not(.no-hover):hover {
@include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
text-decoration: none;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
&.outline-hover:hover {
@ -463,19 +453,74 @@ ul.pagination {
}
}
.card .entity-list-item:not(.no-hover):hover {
@include lightDark(background-color, #F2F2F2, #2d2d2d)
.split-icon-list-item {
display: flex;
align-items: center;
gap: $-m;
background-color: transparent;
border: 0;
width: 100%;
position: relative;
word-break: break-word;
border-radius: 4px;
> a {
padding: $-s $-m;
display: flex;
align-items: center;
gap: $-m;
flex: 1;
}
> a:hover {
text-decoration: none;
}
.icon {
flex-basis: 1.88em;
flex: none;
}
&:hover {
@include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
}
}
.icon-list-item-dropdown {
margin-inline-start: auto;
align-self: stretch;
display: flex;
align-items: stretch;
border-inline-start: 1px solid rgba(0, 0, 0, .1);
visibility: hidden;
}
.split-icon-list-item:hover .icon-list-item-dropdown,
.split-icon-list-item:focus-within .icon-list-item-dropdown {
visibility: visible;
}
.icon-list-item-dropdown-toggle {
padding: $-xs;
display: flex;
align-items: center;
cursor: pointer;
@include lightDark(color, #888, #999);
svg {
margin: 0;
}
&:hover {
@include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
}
}
.card .entity-list-item:not(.no-hover, .book-contents .entity-list-item):hover {
@include lightDark(background-color, #F2F2F2, #2d2d2d);
border-radius: 0;
}
.card .entity-list-item .entity-list-item:hover {
background-color: #EEEEEE;
}
.entity-list-item-children {
padding: $-m;
padding: $-m $-l;
> div {
overflow: hidden;
padding: $-xs 0;
margin-top: -$-xs;
padding: 0 0 $-xs 0;
}
.entity-chip {
text-overflow: ellipsis;
@ -485,6 +530,9 @@ ul.pagination {
display: block;
white-space: nowrap;
}
> .entity-list > .entity-list-item:last-child {
margin-bottom: -$-xs;
}
}
.entity-list-item-image {
@ -531,6 +579,9 @@ ul.pagination {
font-size: $fs-m * 0.8;
padding-top: $-xs;
}
.entity-list-item p:empty {
padding-top: 0;
}
p {
margin: 0;
}
@ -574,8 +625,8 @@ ul.pagination {
right: 0;
margin: $-m 0;
@include lightDark(background-color, #fff, #333);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
border-radius: 1px;
box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.18);
border-radius: 3px;
min-width: 180px;
padding: $-xs 0;
@include lightDark(color, #555, #eee);
@ -652,6 +703,13 @@ ul.pagination {
}
}
// Shift in sidebar dropdown menus to prevent shadows
// being cut by scrollable container.
.tri-layout-right .dropdown-menu,
.tri-layout-left .dropdown-menu {
right: $-xs;
}
// Books grid view
.featured-image-container {
position: relative;
@ -719,3 +777,19 @@ ul.pagination {
}
}
}
.entity-meta-item {
display: flex;
line-height: 1.2;
margin: 0.6em 0;
align-content: start;
gap: $-s;
a {
line-height: 1.2;
}
svg {
flex-shrink: 0;
width: 1em;
margin: 0;
}
}

View File

@ -396,10 +396,14 @@ body.tox-fullscreen, body.markdown-fullscreen {
}
}
.entity-list-item > span:first-child, .icon-list-item > span:first-child, .chapter-expansion > .icon {
.entity-list-item > span:first-child,
.icon-list-item > span:first-child,
.split-icon-list-item > a > .icon,
.chapter-expansion > .icon {
font-size: 0.8rem;
width: 1.88em;
height: 1.88em;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;

View File

@ -79,17 +79,17 @@ $loadingSize: 10px;
animation-timing-function: cubic-bezier(.62, .28, .23, .99);
margin-inline-end: 4px;
background-color: var(--color-page);
animation-delay: 0.3s;
animation-delay: -300ms;
}
> div:first-child {
left: -($loadingSize+$-xs);
background-color: var(--color-book);
animation-delay: 0s;
animation-delay: -600ms;
}
> div:last-of-type {
left: $loadingSize+$-xs;
background-color: var(--color-chapter);
animation-delay: 0.6s;
animation-delay: 0ms;
}
> span {
margin-inline-start: $-s;
@ -138,7 +138,7 @@ $btt-size: 40px;
.skip-to-content-link {
position: fixed;
top: -$-xxl;
top: -52px;
left: 0;
background-color: #FFF;
z-index: 15;

View File

@ -1,10 +1,27 @@
<div component="attachments-list">
@foreach($attachments as $attachment)
<div class="attachment icon-list">
<a class="icon-list-item py-xs attachment-{{ $attachment->external ? 'link' : 'file' }}" href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
<span class="icon">@icon($attachment->external ? 'export' : 'file')</span>
<span>{{ $attachment->name }}</span>
</a>
<div class="split-icon-list-item attachment-{{ $attachment->external ? 'link' : 'file' }}">
<a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
<div class="icon">@icon($attachment->external ? 'export' : 'file')</div>
<div class="label">{{ $attachment->name }}</div>
</a>
@if(!$attachment->external)
<div component="dropdown" class="icon-list-item-dropdown">
<button refs="dropdown@toggle" type="button" class="icon-list-item-dropdown-toggle">@icon('caret-down')</button>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<a href="{{ $attachment->getUrl(false) }}" class="icon-item">
@icon('download')
<div>{{ trans('common.download') }}</div>
</a>
<a href="{{ $attachment->getUrl(true) }}" target="_blank" class="icon-item">
@icon('export')
<div>{{ trans('common.open_in_tab') }}</div>
</a>
</ul>
</div>
@endif
</div>
</div>
@endforeach
</div>

View File

@ -67,14 +67,20 @@
@section('right')
<div class="mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="text-small text-muted blended-links">
<div class="blended-links">
@include('entities.meta', ['entity' => $book])
@if($book->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $book))
<a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
<a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.books_permissions_active') }}</div>
</a>
@else
@icon('lock'){{ trans('entities.books_permissions_active') }}
<div class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.books_permissions_active') }}</div>
</div>
@endif
</div>
@endif

View File

@ -1,9 +1,14 @@
<div class="chapter-child-menu">
<button chapter-toggle type="button" aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
class="text-muted @if($isOpen) open @endif">
<div component="chapter-contents" class="chapter-child-menu">
<button type="button"
refs="chapter-contents@toggle"
aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
class="text-muted chapter-contents-toggle @if($isOpen) open @endif">
@icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->visible_pages->count()) }}</span>
</button>
<ul class="sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) style="display: block;" @endif role="menu">
<ul refs="chapter-contents@list"
class="chapter-contents-list sub-menu inset-list @if($isOpen) open @endif" @if($isOpen)
style="display: block;" @endif
role="menu">
@foreach($bookChild->visible_pages as $childPage)
<li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation">
@include('entities.list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])

View File

@ -5,18 +5,19 @@
<div class="content">
<h4 class="entity-list-item-name break-text">{{ $chapter->name }}</h4>
<div class="entity-item-snippet">
<p class="text-muted break-text mb-s">{{ $chapter->getExcerpt() }}</p>
<p class="text-muted break-text">{{ $chapter->getExcerpt() }}</p>
</div>
</div>
</a>
@if ($chapter->visible_pages->count() > 0)
<div class="chapter chapter-expansion">
<span class="icon text-chapter">@icon('page')</span>
<div class="content">
<button type="button" chapter-toggle
<div component="chapter-contents" class="content">
<button type="button"
refs="chapter-contents@toggle"
aria-expanded="false"
class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
<div class="inset-list">
class="text-muted chapter-contents-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
<div refs="chapter-contents@list" class="inset-list chapter-contents-list">
<div class="entity-list-item-children">
@include('entities.list', ['entities' => $chapter->visible_pages])
</div>

View File

@ -64,15 +64,21 @@
<div class="mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="blended-links text-small text-muted">
<div class="blended-links">
@include('entities.meta', ['entity' => $chapter])
@if($book->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $book))
<a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
<a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.books_permissions_active') }}</div>
</a>
@else
@icon('lock'){{ trans('entities.books_permissions_active') }}
<div class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.books_permissions_active') }}</div>
</div>
@endif
</div>
@endif
@ -80,9 +86,15 @@
@if($chapter->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $chapter))
<a href="{{ $chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
<a href="{{ $chapter->getUrl('/permissions') }}" class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.chapters_permissions_active') }}</div>
</a>
@else
@icon('lock'){{ trans('entities.chapters_permissions_active') }}
<div class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.chapters_permissions_active') }}</div>
</div>
@endif
</div>
@endif

View File

@ -17,7 +17,7 @@
class="mobile-menu-toggle hide-over-l">@icon('more')</button>
</div>
<div class="flex-container-row justify-center hide-under-l">
<div class="flex-container-column items-center justify-center hide-under-l">
@if (hasAppAccess())
<form action="{{ url('/search') }}" method="GET" class="search-box" role="search">
<button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button>
@ -28,76 +28,74 @@
@endif
</div>
<div class="text-right">
<nav refs="header-mobile-toggle@menu" class="header-links">
<div class="links text-center">
@if (hasAppAccess())
<a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
@if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
<a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
@endif
<a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a>
@if(signedInUser() && userCan('settings-manage'))
<a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
@endif
@if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
<a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
@endif
<nav refs="header-mobile-toggle@menu" class="header-links">
<div class="links text-center">
@if (hasAppAccess())
<a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
@if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
<a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
@endif
<a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a>
@if(signedInUser() && userCan('settings-manage'))
<a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
@endif
@if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
<a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
@endif
@endif
@if(!signedInUser())
@if(setting('registration-enabled') && config('auth.method') === 'standard')
<a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a>
@endif
<a href="{{ url('/login') }}">@icon('login'){{ trans('auth.log_in') }}</a>
@if(!signedInUser())
@if(setting('registration-enabled') && config('auth.method') === 'standard')
<a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a>
@endif
</div>
@if(signedInUser())
<?php $currentUser = user(); ?>
<div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
<a href="{{ url('/login') }}">@icon('login'){{ trans('auth.log_in') }}</a>
@endif
</div>
@if(signedInUser())
<?php $currentUser = user(); ?>
<div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
<span class="user-name py-s hide-under-l" refs="dropdown@toggle"
aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
<img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
<span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
</span>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li>
<a href="{{ url('/favourites') }}" class="icon-item">
@icon('star')
<div>{{ trans('entities.my_favourites') }}</div>
</a>
</li>
<li>
<a href="{{ $currentUser->getProfileUrl() }}" class="icon-item">
@icon('user')
<div>{{ trans('common.view_profile') }}</div>
</a>
</li>
<li>
<a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
@icon('edit')
<div>{{ trans('common.edit_profile') }}</div>
</a>
</li>
<li>
<form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
method="post">
{{ csrf_field() }}
<button class="icon-item">
@icon('logout')
<div>{{ trans('auth.logout') }}</div>
</button>
</form>
</li>
<li><hr></li>
<li>
@include('common.dark-mode-toggle', ['classes' => 'icon-item'])
</li>
</ul>
</div>
@endif
</nav>
</div>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li>
<a href="{{ url('/favourites') }}" class="icon-item">
@icon('star')
<div>{{ trans('entities.my_favourites') }}</div>
</a>
</li>
<li>
<a href="{{ $currentUser->getProfileUrl() }}" class="icon-item">
@icon('user')
<div>{{ trans('common.view_profile') }}</div>
</a>
</li>
<li>
<a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
@icon('edit')
<div>{{ trans('common.edit_profile') }}</div>
</a>
</li>
<li>
<form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
method="post">
{{ csrf_field() }}
<button class="icon-item">
@icon('logout')
<div>{{ trans('auth.logout') }}</div>
</button>
</form>
</li>
<li><hr></li>
<li>
@include('common.dark-mode-toggle', ['classes' => 'icon-item'])
</li>
</ul>
</div>
@endif
</nav>
</div>
</header>

View File

@ -2,7 +2,7 @@
option:dropdown-search:url="/search/entity/siblings?entity_type={{$entity->getType()}}&entity_id={{ $entity->id }}"
option:dropdown-search:local-search-selector=".entity-list-item"
>
<div class="dropdown-search-toggle" refs="dropdown@toggle"
<div class="dropdown-search-toggle-breadcrumb" refs="dropdown@toggle"
aria-haspopup="true" aria-expanded="false" tabindex="0">
<div class="separator">@icon('chevron-right')</div>
</div>
@ -18,6 +18,6 @@
<div refs="dropdown-search@loading">
@include('common.loading-icon')
</div>
<div refs="dropdown-search@listContainer" class="dropdown-search-list px-m"></div>
<div refs="dropdown-search@listContainer" class="dropdown-search-list px-m" tabindex="-1"></div>
</div>
</div>

View File

@ -1,13 +1,18 @@
<div component="dropdown" class="dropdown-container" id="export-menu">
<div component="dropdown"
class="dropdown-container"
id="export-menu">
<div refs="dropdown@toggle" class="icon-list-item"
aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0">
<span>@icon('export')</span>
<span>{{ trans('entities.export') }}</span>
</div>
<ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
<li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_html') }}</span><span>.html</span></a></li>
<li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</span></a></li>
<li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_text') }}</span><span>.txt</span></a></li>
<li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
</ul>
</div>

View File

@ -1,50 +1,62 @@
<div class="entity-meta">
@if($entity->isA('revision'))
<div>
@icon('history'){{ trans('entities.pages_revision') }}
{{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
<div class="entity-meta-item">
@icon('history')
<div>
{{ trans('entities.pages_revision') }}
{{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
</div>
</div>
@endif
@if ($entity->isA('page'))
<div>
@if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
@icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
@if (userCan('page-update', $entity))</a>@endif
</div>
@if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}" class="entity-meta-item"> @else <div class="entity-meta-item"> @endif
@icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
@if (userCan('page-update', $entity))</a> @else </div> @endif
@endif
@if ($entity->ownedBy && $entity->owned_by !== $entity->created_by)
<div>
@icon('user'){!! trans('entities.meta_owned_name', [
'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
]) !!}
<div class="entity-meta-item">
@icon('user')
<div>
{!! trans('entities.meta_owned_name', [
'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
]) !!}
</div>
</div>
@endif
@if ($entity->createdBy)
<div>
@icon('star'){!! trans('entities.meta_created_name', [
'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
]) !!}
<div class="entity-meta-item">
@icon('star')
<div>
{!! trans('entities.meta_created_name', [
'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
]) !!}
</div>
</div>
@else
<div>
@icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
<div class="entity-meta-item">
@icon('star')
<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
</div>
@endif
@if ($entity->updatedBy)
<div>
@icon('edit'){!! trans('entities.meta_updated_name', [
'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
]) !!}
<div class="entity-meta-item">
@icon('edit')
<div>
{!! trans('entities.meta_updated_name', [
'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
]) !!}
</div>
</div>
@elseif (!$entity->isA('revision'))
<div>
@icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
<div class="entity-meta-item">
@icon('edit')
<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
</div>
@endif
</div>

View File

@ -15,7 +15,7 @@
<div>
<div class="form-group">
<label for="owner">{{ trans('entities.permissions_owner') }}</label>
@include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by', 'compact' => false])
@include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by'])
</div>
</div>
</div>

View File

@ -1,19 +1,19 @@
<div class="dropdown-search custom-select-input" components="dropdown dropdown-search user-select"
<div class="dropdown-search" components="dropdown dropdown-search user-select"
option:dropdown-search:url="/search/users/select"
>
<input refs="user-select@input" type="hidden" name="{{ $name }}" value="{{ $user->id ?? '' }}">
<div refs="dropdown@toggle"
class="dropdown-search-toggle {{ $compact ? 'compact' : '' }} flex-container-row items-center"
class="dropdown-search-toggle-select input-base"
aria-haspopup="true" aria-expanded="false" tabindex="0">
<div refs="user-select@user-info" class="flex-container-row items-center px-s">
<div refs="user-select@user-info" class="dropdown-search-toggle-select-label flex-container-row items-center">
@if($user)
<img class="avatar small mr-m" src="{{ $user->getAvatar($compact ? 22 : 30) }}" alt="{{ $user->name }}">
<img class="avatar small mr-m" src="{{ $user->getAvatar(30) }}" width="30" height="30" alt="{{ $user->name }}">
<span>{{ $user->name }}</span>
@else
<span>{{ trans('settings.users_none_selected') }}</span>
@endif
</div>
<span style="font-size: {{ $compact ? '1.15rem' : '1.5rem' }}; margin-left: auto;">
<span class="dropdown-search-toggle-select-caret">
@icon('caret-down')
</span>
</div>

View File

@ -27,7 +27,7 @@
<div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
<div class="tri-layout-left print-hidden pt-m" id="sidebar">
<div class="tri-layout-left print-hidden" id="sidebar">
<aside class="tri-layout-left-contents">
@yield('left')
</aside>
@ -39,7 +39,7 @@
</div>
</div>
<div class="tri-layout-right print-hidden pt-m">
<div class="tri-layout-right print-hidden">
<aside class="tri-layout-right-contents">
@yield('right')
</aside>

View File

@ -65,7 +65,9 @@
</div>
<div class="action-buttons px-m py-xs">
<div component="dropdown" dropdown-move-menu class="dropdown-container">
<div component="dropdown"
option:dropdown:move-menu="true"
class="dropdown-container">
<button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span refs="page-editor@changelogDisplay">{{ trans('entities.pages_edit_set_changelog') }}</span></button>
<ul refs="dropdown@menu" class="wide dropdown-menu">
<li class="px-l py-m">

View File

@ -76,15 +76,21 @@
@section('right')
<div id="page-details" class="entity-details mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="body text-small blended-links">
<div class="blended-links">
@include('entities.meta', ['entity' => $page])
@if($book->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $book))
<a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
<a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.books_permissions_active') }}</div>
</a>
@else
@icon('lock'){{ trans('entities.books_permissions_active') }}
<div class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.books_permissions_active') }}</div>
</div>
@endif
</div>
@endif
@ -92,9 +98,15 @@
@if($page->chapter && $page->chapter->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $page->chapter))
<a href="{{ $page->chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
<a href="{{ $page->chapter->getUrl('/permissions') }}" class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.chapters_permissions_active') }}</div>
</a>
@else
@icon('lock'){{ trans('entities.chapters_permissions_active') }}
<div class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.chapters_permissions_active') }}</div>
</div>
@endif
</div>
@endif
@ -102,16 +114,23 @@
@if($page->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $page))
<a href="{{ $page->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.pages_permissions_active') }}</a>
<a href="{{ $page->getUrl('/permissions') }}" class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.pages_permissions_active') }}</div>
</a>
@else
@icon('lock'){{ trans('entities.pages_permissions_active') }}
<div class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.pages_permissions_active') }}</div>
</div>
@endif
</div>
@endif
@if($page->template)
<div>
@icon('template'){{ trans('entities.pages_is_template') }}
<div class="entity-meta-item">
@icon('template')
<div>{{ trans('entities.pages_is_template') }}</div>
</div>
@endif
</div>

View File

@ -9,8 +9,9 @@
<h1 class="list-heading">{{ trans('settings.audit') }}</h1>
<p class="text-muted">{{ trans('settings.audit_desc') }}</p>
<div class="flex-container-row">
<div component="dropdown" class="list-sort-type dropdown-container mr-m">
<form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-m">
<div component="dropdown" class="list-sort-type dropdown-container">
<label for="">{{ trans('settings.audit_event_filter') }}</label>
<button refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
<ul refs="dropdown@menu" class="dropdown-menu">
@ -21,37 +22,35 @@
</ul>
</div>
<form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row mr-m">
@if(!empty($listDetails['event']))
<input type="hidden" name="event" value="{{ $listDetails['event'] }}">
@endif
@if(!empty($listDetails['event']))
<input type="hidden" name="event" value="{{ $listDetails['event'] }}">
@endif
@foreach(['date_from', 'date_to'] as $filterKey)
<div class="mr-m">
<label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
<input id="audit_filter_{{ $filterKey }}"
component="submit-on-change"
type="date"
name="{{ $filterKey }}"
value="{{ $listDetails[$filterKey] ?? '' }}">
</div>
@endforeach
<div class="form-group ml-auto mr-m"
component="submit-on-change"
option:submit-on-change:filter='[name="user"]'>
<label for="owner">{{ trans('settings.audit_table_user') }}</label>
@include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' => true])
@foreach(['date_from', 'date_to'] as $filterKey)
<div class=>
<label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
<input id="audit_filter_{{ $filterKey }}"
component="submit-on-change"
type="date"
name="{{ $filterKey }}"
value="{{ $listDetails[$filterKey] ?? '' }}">
</div>
@endforeach
<div class="form-group"
component="submit-on-change"
option:submit-on-change:filter='[name="user"]'>
<label for="owner">{{ trans('settings.audit_table_user') }}</label>
@include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user'])
</div>
<div class="form-group ml-auto">
<label for="ip">{{ trans('settings.audit_table_ip') }}</label>
@include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
<input type="submit" style="display: none">
</div>
</form>
</div>
<div class="form-group">
<label for="ip">{{ trans('settings.audit_table_ip') }}</label>
@include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
<input type="submit" style="display: none">
</div>
</form>
<hr class="mt-l mb-s">

View File

@ -119,7 +119,13 @@
<div>
<label for="setting-app-custom-head" class="setting-list-label">{{ trans('settings.app_custom_html') }}</label>
<p class="small">{{ trans('settings.app_custom_html_desc') }}</p>
<textarea name="setting-app-custom-head" id="setting-app-custom-head" class="simple-code-input mt-m">{{ setting('app-custom-head', '') }}</textarea>
<div class="mt-m">
<textarea component="code-textarea"
option:code-textarea:mode="html"
name="setting-app-custom-head"
id="setting-app-custom-head"
class="simple-code-input">{{ setting('app-custom-head', '') }}</textarea>
</div>
<p class="small text-right">{{ trans('settings.app_custom_html_disabled_notice') }}</p>
</div>

View File

@ -81,14 +81,20 @@
<div id="details" class="mb-xl">
<h5>{{ trans('common.details') }}</h5>
<div class="text-small text-muted blended-links">
<div class="blended-links">
@include('entities.meta', ['entity' => $shelf])
@if($shelf->restricted)
<div class="active-restriction">
@if(userCan('restrictions-manage', $shelf))
<a href="{{ $shelf->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.shelves_permissions_active') }}</a>
<a href="{{ $shelf->getUrl('/permissions') }}" class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.shelves_permissions_active') }}</div>
</a>
@else
@icon('lock'){{ trans('entities.shelves_permissions_active') }}
<div class="entity-meta-item">
@icon('lock')
<div>{{ trans('entities.shelves_permissions_active') }}</div>
</div>
@endif
</div>
@endif

View File

@ -19,7 +19,7 @@
<p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p>
</div>
<div>
@include('form.user-select', ['name' => 'new_owner_id', 'user' => null, 'compact' => false])
@include('form.user-select', ['name' => 'new_owner_id', 'user' => null])
</div>
</div>
@endif