diff --git a/package.json b/package.json index b49a2a07f..7a85af47a 100644 --- a/package.json +++ b/package.json @@ -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\"", diff --git a/resources/icons/download.svg b/resources/icons/download.svg new file mode 100644 index 000000000..6299571d6 --- /dev/null +++ b/resources/icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/code.mjs b/resources/js/code.mjs index 8e2ed72c8..537b0d108 100644 --- a/resources/js/code.mjs +++ b/resources/js/code.mjs @@ -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 diff --git a/resources/js/components/chapter-contents.js b/resources/js/components/chapter-contents.js new file mode 100644 index 000000000..c824d0f78 --- /dev/null +++ b/resources/js/components/chapter-contents.js @@ -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; diff --git a/resources/js/components/chapter-toggle.js b/resources/js/components/chapter-toggle.js deleted file mode 100644 index bfd0ac729..000000000 --- a/resources/js/components/chapter-toggle.js +++ /dev/null @@ -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; diff --git a/resources/js/components/code-textarea.js b/resources/js/components/code-textarea.js new file mode 100644 index 000000000..988e51f19 --- /dev/null +++ b/resources/js/components/code-textarea.js @@ -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; \ No newline at end of file diff --git a/resources/js/components/dropdown-search.js b/resources/js/components/dropdown-search.js index e2d55f969..81fa940c2 100644 --- a/resources/js/components/dropdown-search.js +++ b/resources/js/components/dropdown-search.js @@ -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); } diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js index f761ecf01..473db37d4 100644 --- a/resources/js/components/dropdown.js +++ b/resources/js/components/dropdown.js @@ -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() { diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 6a4a8c2b0..f360e2b0c 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -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, diff --git a/resources/js/services/animations.js b/resources/js/services/animations.js index 278a765d5..12b8077cf 100644 --- a/resources/js/services/animations.js +++ b/resources/js/services/animations.js @@ -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 diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index 2f09e53d1..703a70c7e 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -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', diff --git a/resources/sass/_blocks.scss b/resources/sass/_blocks.scss index 7d408cd1b..0398224ca 100644 --- a/resources/sass/_blocks.scss +++ b/resources/sass/_blocks.scss @@ -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; } diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index bce456cf2..e3c9d5eea 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -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; } \ No newline at end of file diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index 665b1213b..73799f0a0 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -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%; diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss index f070f5a18..923f026c2 100644 --- a/resources/sass/_header.scss +++ b/resources/sass/_header.scss @@ -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; diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index b1c80cb53..14a37dd4a 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -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; diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 26d12a25d..19060fbbf 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -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; + } +} diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss index 73819975f..3ceec61d0 100755 --- a/resources/sass/_pages.scss +++ b/resources/sass/_pages.scss @@ -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; diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index 582bf7c75..ee99d7668 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -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; diff --git a/resources/views/attachments/list.blade.php b/resources/views/attachments/list.blade.php index f0a1354ea..a6ffb709b 100644 --- a/resources/views/attachments/list.blade.php +++ b/resources/views/attachments/list.blade.php @@ -1,10 +1,27 @@
@foreach($attachments as $attachment)
- external) target="_blank" @endif> - @icon($attachment->external ? 'export' : 'file') - {{ $attachment->name }} - +
@endforeach
\ No newline at end of file diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 5263bc810..e0cb4b862 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -67,14 +67,20 @@ @section('right')
{{ trans('common.details') }}
-