mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Merge branch 'development' of github.com:BookStackApp/BookStack into development
This commit is contained in:
commit
43cbab2822
@ -28,10 +28,8 @@ class GroupSyncService
|
|||||||
*/
|
*/
|
||||||
protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
|
protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
|
||||||
{
|
{
|
||||||
$externalAuthIds = explode(',', strtolower($externalId));
|
foreach ($this->parseRoleExternalAuthId($externalId) as $externalAuthId) {
|
||||||
|
if (in_array($externalAuthId, $groupNames)) {
|
||||||
foreach ($externalAuthIds as $externalAuthId) {
|
|
||||||
if (in_array(trim($externalAuthId), $groupNames)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,6 +37,18 @@ class GroupSyncService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function parseRoleExternalAuthId(string $externalId): array
|
||||||
|
{
|
||||||
|
$inputIds = preg_split('/(?<!\\\),/', $externalId);
|
||||||
|
$cleanIds = [];
|
||||||
|
|
||||||
|
foreach ($inputIds as $inputId) {
|
||||||
|
$cleanIds[] = str_replace('\,', ',', trim($inputId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cleanIds;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Match an array of group names to BookStack system roles.
|
* Match an array of group names to BookStack system roles.
|
||||||
* Formats group names to be lower-case and hyphenated.
|
* Formats group names to be lower-case and hyphenated.
|
||||||
|
@ -23,6 +23,7 @@ class RoleFactory extends Factory
|
|||||||
return [
|
return [
|
||||||
'display_name' => $this->faker->sentence(3),
|
'display_name' => $this->faker->sentence(3),
|
||||||
'description' => $this->faker->sentence(10),
|
'description' => $this->faker->sentence(10),
|
||||||
|
'external_auth_id' => '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:css:dev": "sass ./resources/sass:./public/dist",
|
"build:css:dev": "sass ./resources/sass:./public/dist --embed-sources",
|
||||||
"build:css:watch": "sass ./resources/sass:./public/dist --watch",
|
"build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
|
||||||
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
|
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
|
||||||
"build:js:dev": "node dev/build/esbuild.js",
|
"build:js:dev": "node dev/build/esbuild.js",
|
||||||
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
|
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
|
||||||
|
1
resources/icons/download.svg
Normal file
1
resources/icons/download.svg
Normal 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 |
@ -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.
|
* Set the mode of a codemirror instance.
|
||||||
* @param cmInstance
|
* @param cmInstance
|
||||||
|
37
resources/js/components/chapter-contents.js
Normal file
37
resources/js/components/chapter-contents.js
Normal 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;
|
@ -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;
|
|
16
resources/js/components/code-textarea.js
Normal file
16
resources/js/components/code-textarea.js
Normal 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;
|
@ -1,4 +1,5 @@
|
|||||||
import {debounce} from "../services/util";
|
import {debounce} from "../services/util";
|
||||||
|
import {transitionHeight} from "../services/animations";
|
||||||
|
|
||||||
class DropdownSearch {
|
class DropdownSearch {
|
||||||
|
|
||||||
@ -51,7 +52,9 @@ class DropdownSearch {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await window.$http.get(this.getAjaxUrl(searchTerm));
|
const resp = await window.$http.get(this.getAjaxUrl(searchTerm));
|
||||||
|
const animate = transitionHeight(this.listContainerElem, 80);
|
||||||
this.listContainerElem.innerHTML = resp.data;
|
this.listContainerElem.innerHTML = resp.data;
|
||||||
|
animate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
@ -28,18 +28,31 @@ class DropDown {
|
|||||||
this.menu.classList.add('anim', 'menuIn');
|
this.menu.classList.add('anim', 'menuIn');
|
||||||
this.toggle.setAttribute('aria-expanded', 'true');
|
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) {
|
if (this.moveMenu) {
|
||||||
// Move to body to prevent being trapped within scrollable sections
|
|
||||||
this.rect = this.menu.getBoundingClientRect();
|
|
||||||
this.body.appendChild(this.menu);
|
this.body.appendChild(this.menu);
|
||||||
this.menu.style.position = 'fixed';
|
this.menu.style.position = 'fixed';
|
||||||
if (this.direction === 'right') {
|
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 {
|
} 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 = `${menuOriginalRect.width}px`;
|
||||||
this.menu.style.width = `${this.rect.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
|
// Set listener to hide on mouse leave or window click
|
||||||
@ -74,18 +87,21 @@ class DropDown {
|
|||||||
this.menu.style.display = 'none';
|
this.menu.style.display = 'none';
|
||||||
this.menu.classList.remove('anim', 'menuIn');
|
this.menu.classList.remove('anim', 'menuIn');
|
||||||
this.toggle.setAttribute('aria-expanded', 'false');
|
this.toggle.setAttribute('aria-expanded', 'false');
|
||||||
|
this.menu.style.top = '';
|
||||||
|
this.menu.style.bottom = '';
|
||||||
|
|
||||||
if (this.moveMenu) {
|
if (this.moveMenu) {
|
||||||
this.menu.style.position = '';
|
this.menu.style.position = '';
|
||||||
this.menu.style[this.direction] = '';
|
this.menu.style[this.direction] = '';
|
||||||
this.menu.style.top = '';
|
|
||||||
this.menu.style.width = '';
|
this.menu.style.width = '';
|
||||||
this.container.appendChild(this.menu);
|
this.container.appendChild(this.menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showing = false;
|
this.showing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFocusable() {
|
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() {
|
focusNext() {
|
||||||
|
@ -6,9 +6,10 @@ import attachmentsList from "./attachments-list.js"
|
|||||||
import autoSuggest from "./auto-suggest.js"
|
import autoSuggest from "./auto-suggest.js"
|
||||||
import backToTop from "./back-to-top.js"
|
import backToTop from "./back-to-top.js"
|
||||||
import bookSort from "./book-sort.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 codeEditor from "./code-editor.js"
|
||||||
import codeHighlighter from "./code-highlighter.js"
|
import codeHighlighter from "./code-highlighter.js"
|
||||||
|
import codeTextarea from "./code-textarea.js"
|
||||||
import collapsible from "./collapsible.js"
|
import collapsible from "./collapsible.js"
|
||||||
import confirmDialog from "./confirm-dialog"
|
import confirmDialog from "./confirm-dialog"
|
||||||
import customCheckbox from "./custom-checkbox.js"
|
import customCheckbox from "./custom-checkbox.js"
|
||||||
@ -62,9 +63,10 @@ const componentMapping = {
|
|||||||
"auto-suggest": autoSuggest,
|
"auto-suggest": autoSuggest,
|
||||||
"back-to-top": backToTop,
|
"back-to-top": backToTop,
|
||||||
"book-sort": bookSort,
|
"book-sort": bookSort,
|
||||||
"chapter-toggle": chapterToggle,
|
"chapter-contents": chapterContents,
|
||||||
"code-editor": codeEditor,
|
"code-editor": codeEditor,
|
||||||
"code-highlighter": codeHighlighter,
|
"code-highlighter": codeHighlighter,
|
||||||
|
"code-textarea": codeTextarea,
|
||||||
"collapsible": collapsible,
|
"collapsible": collapsible,
|
||||||
"confirm-dialog": confirmDialog,
|
"confirm-dialog": confirmDialog,
|
||||||
"custom-checkbox": customCheckbox,
|
"custom-checkbox": customCheckbox,
|
||||||
|
@ -49,7 +49,7 @@ export function slideUp(element, animTime = 400) {
|
|||||||
const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
|
const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
|
||||||
const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
|
const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
|
||||||
const animStyles = {
|
const animStyles = {
|
||||||
height: [`${currentHeight}px`, '0px'],
|
maxHeight: [`${currentHeight}px`, '0px'],
|
||||||
overflow: ['hidden', 'hidden'],
|
overflow: ['hidden', 'hidden'],
|
||||||
paddingTop: [currentPaddingTop, '0px'],
|
paddingTop: [currentPaddingTop, '0px'],
|
||||||
paddingBottom: [currentPaddingBottom, '0px'],
|
paddingBottom: [currentPaddingBottom, '0px'],
|
||||||
@ -73,7 +73,7 @@ export function slideDown(element, animTime = 400) {
|
|||||||
const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
|
const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
|
||||||
const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
|
const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
|
||||||
const animStyles = {
|
const animStyles = {
|
||||||
height: ['0px', `${targetHeight}px`],
|
maxHeight: ['0px', `${targetHeight}px`],
|
||||||
overflow: ['hidden', 'hidden'],
|
overflow: ['hidden', 'hidden'],
|
||||||
paddingTop: ['0px', targetPaddingTop],
|
paddingTop: ['0px', targetPaddingTop],
|
||||||
paddingBottom: ['0px', targetPaddingBottom],
|
paddingBottom: ['0px', targetPaddingBottom],
|
||||||
@ -82,6 +82,38 @@ export function slideDown(element, animTime = 400) {
|
|||||||
animateStyles(element, animStyles, animTime);
|
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.
|
* 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
|
* Styles must be an object where the keys are style properties, camelcase, and the values
|
||||||
|
@ -47,6 +47,8 @@ return [
|
|||||||
'previous' => 'Previous',
|
'previous' => 'Previous',
|
||||||
'filter_active' => 'Active Filter:',
|
'filter_active' => 'Active Filter:',
|
||||||
'filter_clear' => 'Clear Filter',
|
'filter_clear' => 'Clear Filter',
|
||||||
|
'download' => 'Download',
|
||||||
|
'open_in_tab' => 'Open in Tab',
|
||||||
|
|
||||||
// Sort Options
|
// Sort Options
|
||||||
'sort_options' => 'Sort Options',
|
'sort_options' => 'Sort Options',
|
||||||
|
@ -66,7 +66,6 @@
|
|||||||
@include lightDark(background-color, #FFF, #222);
|
@include lightDark(background-color, #FFF, #222);
|
||||||
box-shadow: $bs-card;
|
box-shadow: $bs-card;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid transparent;
|
|
||||||
.body, p.empty-text {
|
.body, p.empty-text {
|
||||||
padding: $-m;
|
padding: $-m;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[chapter-toggle] {
|
.chapter-contents-toggle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
transition: all ease-in-out 180ms;
|
transition: all ease-in-out 180ms;
|
||||||
@ -77,7 +77,7 @@
|
|||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
svg[data-icon="caret-right"] + * {
|
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 {
|
.dropdown-search-dropdown {
|
||||||
box-shadow: $bs-med;
|
box-shadow: $bs-med;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -739,7 +788,9 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
|||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 80;
|
z-index: 80;
|
||||||
right: -$-m;
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
margin-top: $-m;
|
||||||
@include rtl {
|
@include rtl {
|
||||||
right: auto;
|
right: auto;
|
||||||
left: -$-m;
|
left: -$-m;
|
||||||
@ -767,12 +818,15 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input {
|
input, input:focus {
|
||||||
padding-inline-start: $-xl;
|
padding-inline-start: $-xl;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid #DDD;
|
border-bottom: 1px solid #DDD;
|
||||||
}
|
}
|
||||||
|
input:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smaller-than($m) {
|
@include smaller-than($m) {
|
||||||
@ -785,9 +839,3 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
|||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select-input {
|
|
||||||
max-width: 280px;
|
|
||||||
border: 1px solid #D4D4D4;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
@ -7,7 +7,8 @@
|
|||||||
@include lightDark(color, #666, #AAA);
|
@include lightDark(color, #666, #AAA);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: $fs-m;
|
font-size: $fs-m;
|
||||||
padding: $-xs*1.5;
|
padding: $-xs*1.8;
|
||||||
|
height: 40px;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
@ -350,16 +351,13 @@ input[type=color] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-input-style {
|
.title-input input[type="text"] {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: $-s;
|
padding: $-s;
|
||||||
}
|
|
||||||
|
|
||||||
.title-input input[type="text"] {
|
|
||||||
@extend .inline-input-style;
|
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-input.page-title {
|
.title-input.page-title {
|
||||||
@ -373,6 +371,7 @@ input[type=color] {
|
|||||||
max-width: 840px;
|
max-width: 840px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border: none;
|
border: none;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,10 +382,12 @@ input[type=color] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.description-input textarea {
|
.description-input textarea {
|
||||||
@extend .inline-input-style;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: $-s;
|
||||||
font-size: $fs-m;
|
font-size: $fs-m;
|
||||||
color: #666;
|
color: #666;
|
||||||
width: 100%;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
div[editor-type="markdown"] .title-input.page-title input[type="text"] {
|
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 {
|
input {
|
||||||
display: block;
|
display: block;
|
||||||
|
padding: $-xs * 1.5;
|
||||||
padding-inline-start: $-l + 4px;
|
padding-inline-start: $-l + 4px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
&.flexible input {
|
&.flexible input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -21,19 +21,28 @@ header {
|
|||||||
color: rgb(250, 250, 250);
|
color: rgb(250, 250, 250);
|
||||||
border-bottom: 1px solid #DDD;
|
border-bottom: 1px solid #DDD;
|
||||||
box-shadow: $bs-card;
|
box-shadow: $bs-card;
|
||||||
padding: $-xxs 0;
|
|
||||||
@include lightDark(border-bottom-color, #DDD, #000);
|
@include lightDark(border-bottom-color, #DDD, #000);
|
||||||
@include whenDark {
|
@include whenDark {
|
||||||
filter: saturate(0.8) brightness(0.8);
|
filter: saturate(0.8) brightness(0.8);
|
||||||
}
|
}
|
||||||
|
.header-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
.links {
|
.links {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
.links a {
|
.links a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: $-m;
|
padding: 10px $-m;
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.links a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: rgba(255, 255, 255, .15);
|
||||||
}
|
}
|
||||||
.dropdown-container {
|
.dropdown-container {
|
||||||
padding-inline-start: $-m;
|
padding-inline-start: $-m;
|
||||||
@ -49,19 +58,25 @@ header {
|
|||||||
.user-name {
|
.user-name {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
> * {
|
padding: $-s;
|
||||||
vertical-align: top;
|
margin: 0 (-$-s);
|
||||||
}
|
border-radius: 3px;
|
||||||
|
gap: $-xs;
|
||||||
> span {
|
> span {
|
||||||
padding-inline-start: $-xs;
|
padding-inline-start: $-xs;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-top: $-xxs;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
> svg {
|
> svg {
|
||||||
padding-top: 4px;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
margin-top: -2px;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
@include between($l, $xl) {
|
@include between($l, $xl) {
|
||||||
padding-inline-start: $-xs;
|
padding-inline-start: $-xs;
|
||||||
@ -79,22 +94,26 @@ header {
|
|||||||
|
|
||||||
header .search-box {
|
header .search-box {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 10px;
|
|
||||||
input {
|
input {
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
color: #EEE;
|
color: #EEE;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
height: auto;
|
||||||
|
padding: $-xs*1.5;
|
||||||
padding-inline-start: 40px;
|
padding-inline-start: 40px;
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
|
top: 10px;
|
||||||
|
color: #FFF;
|
||||||
|
opacity: 0.6;
|
||||||
@include lightDark(color, rgba(255, 255, 255, 0.8), #AAA);
|
@include lightDark(color, rgba(255, 255, 255, 0.8), #AAA);
|
||||||
@include rtl {
|
@include rtl {
|
||||||
left: auto;
|
left: auto;
|
||||||
@ -104,36 +123,39 @@ header .search-box {
|
|||||||
margin-block-end: 0;
|
margin-block-end: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
|
input::placeholder {
|
||||||
color: #DDD;
|
color: #FFF;
|
||||||
}
|
opacity: 0.6;
|
||||||
::-moz-placeholder { /* Firefox 19+ */
|
|
||||||
color: #DDD;
|
|
||||||
}
|
}
|
||||||
@include between($l, $xl) {
|
@include between($l, $xl) {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
|
&:focus-within button {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
padding: ($-s - 6px) $-s;
|
||||||
|
margin: 6px (-$-s);
|
||||||
|
gap: $-s;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
background-color: rgba(255, 255, 255, .15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-text {
|
.logo-text {
|
||||||
display: inline-block;
|
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@include padding(14px, $-l, 14px, 0);
|
|
||||||
vertical-align: top;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.logo-image {
|
.logo-image {
|
||||||
@include margin($-xs, $-s, $-xs, 0);
|
|
||||||
vertical-align: top;
|
|
||||||
height: 43px;
|
height: 43px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,23 +194,29 @@ header .search-box {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-shadow: $bs-hover;
|
box-shadow: $bs-hover;
|
||||||
margin-top: -$-xs;
|
margin-top: $-m;
|
||||||
|
padding: $-xs 0;
|
||||||
&.show {
|
&.show {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header .links a, header .dropdown-container ul li a, header .dropdown-container ul li button {
|
header .links a, header .dropdown-container ul li a, header .dropdown-container ul li button {
|
||||||
text-align: start;
|
text-align: start;
|
||||||
display: block;
|
display: grid;
|
||||||
padding: $-s $-m;
|
align-items: center;
|
||||||
|
padding: 8px $-m;
|
||||||
|
gap: $-m;
|
||||||
color: $text-dark;
|
color: $text-dark;
|
||||||
|
grid-template-columns: 16px auto;
|
||||||
|
line-height: 1.4;
|
||||||
@include lightDark(color, $text-dark, #eee);
|
@include lightDark(color, $text-dark, #eee);
|
||||||
svg {
|
svg {
|
||||||
margin-inline-end: $-s;
|
margin-inline-end: $-s;
|
||||||
|
width: 16px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
@include lightDark(background-color, #eee, #333);
|
background-color: var(--color-primary-light);
|
||||||
@include lightDark(color, #000, #fff);
|
color: var(--color-primary);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
&:focus {
|
&: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 {
|
.faded {
|
||||||
a, button, span, span > div {
|
a, button, span, span > div {
|
||||||
color: #666;
|
color: #666;
|
||||||
|
@ -155,6 +155,13 @@ body.flexbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-m {
|
||||||
|
gap: $-m;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-flex-start {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
.justify-flex-end {
|
.justify-flex-end {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
@ -295,9 +302,9 @@ body.flexbox {
|
|||||||
}
|
}
|
||||||
@include larger-than($xxl) {
|
@include larger-than($xxl) {
|
||||||
.tri-layout-left-contents, .tri-layout-right-contents {
|
.tri-layout-left-contents, .tri-layout-right-contents {
|
||||||
padding: $-m;
|
padding: $-xl $-m;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: $-m;
|
top: 0;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
min-height: 50vh;
|
min-height: 50vh;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
justify-self: stretch;
|
justify-self: stretch;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-inline-end: $-l;
|
margin-inline-end: $-xs;
|
||||||
}
|
}
|
||||||
.icon:after {
|
.icon:after {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
@ -56,13 +56,13 @@
|
|||||||
> .content {
|
> .content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.chapter-expansion-toggle {
|
.chapter-contents-toggle {
|
||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
padding: $-xs $-m;
|
padding: $-xs ($-m + $-xxs);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
}
|
}
|
||||||
.chapter-expansion-toggle:hover {
|
.chapter-contents-toggle:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.06);
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,22 +157,6 @@
|
|||||||
@include margin($-xs, -$-s, 0, -$-s);
|
@include margin($-xs, -$-s, 0, -$-s);
|
||||||
padding-inline-start: 0;
|
padding-inline-start: 0;
|
||||||
padding-inline-end: 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 {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@ -181,19 +165,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.entity-list-item {
|
.entity-list-item {
|
||||||
padding-top: $-xxs;
|
padding-top: 2px;
|
||||||
padding-bottom: $-xxs;
|
padding-bottom: 2px;
|
||||||
background-clip: content-box;
|
background-clip: content-box;
|
||||||
border-radius: 0 3px 3px 0;
|
border-radius: 0 3px 3px 0;
|
||||||
padding-inline-end: 0;
|
padding-inline-end: 0;
|
||||||
.content {
|
.content {
|
||||||
|
width: 100%;
|
||||||
padding-top: $-xs;
|
padding-top: $-xs;
|
||||||
padding-bottom: $-xs;
|
padding-bottom: $-xs;
|
||||||
max-width: calc(100% - 20px);
|
max-width: calc(100% - 20px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.entity-list-item.selected {
|
.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 {
|
.entity-list-item.no-hover {
|
||||||
margin-top: -$-xs;
|
margin-top: -$-xs;
|
||||||
@ -209,9 +194,18 @@
|
|||||||
margin-top: -.2rem;
|
margin-top: -.2rem;
|
||||||
margin-inline-start: -1rem;
|
margin-inline-start: -1rem;
|
||||||
}
|
}
|
||||||
[chapter-toggle] {
|
.chapter-contents-toggle {
|
||||||
padding-inline-start: .7rem;
|
display: block;
|
||||||
padding-bottom: .2rem;
|
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 {
|
.entity-list-item .icon {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@ -220,7 +214,7 @@
|
|||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
opacity: 0.6;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
.entity-list-item .icon:after {
|
.entity-list-item .icon:after {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -230,15 +224,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chapter-child-menu {
|
.chapter-child-menu ul.sub-menu {
|
||||||
ul.sub-menu {
|
|
||||||
display: none;
|
display: none;
|
||||||
padding-inline-start: 0;
|
padding-inline-start: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
margin-bottom: 0;
|
||||||
[chapter-toggle].open + .sub-menu {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sortable Lists
|
// Sortable Lists
|
||||||
@ -415,6 +405,7 @@ ul.pagination {
|
|||||||
padding: $-s $-m;
|
padding: $-s $-m;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: $-m;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -424,7 +415,6 @@ ul.pagination {
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
> span:first-child {
|
> span:first-child {
|
||||||
margin-inline-end: $-m;
|
|
||||||
flex-basis: 1.88em;
|
flex-basis: 1.88em;
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
@ -439,8 +429,8 @@ ul.pagination {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
&:not(.no-hover):hover {
|
&:not(.no-hover):hover {
|
||||||
|
@include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
&.outline-hover:hover {
|
&.outline-hover:hover {
|
||||||
@ -463,19 +453,74 @@ ul.pagination {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .entity-list-item:not(.no-hover):hover {
|
.split-icon-list-item {
|
||||||
@include lightDark(background-color, #F2F2F2, #2d2d2d)
|
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 {
|
.card .entity-list-item .entity-list-item:hover {
|
||||||
background-color: #EEEEEE;
|
background-color: #EEEEEE;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-list-item-children {
|
.entity-list-item-children {
|
||||||
padding: $-m;
|
padding: $-m $-l;
|
||||||
> div {
|
> div {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: $-xs 0;
|
padding: 0 0 $-xs 0;
|
||||||
margin-top: -$-xs;
|
|
||||||
}
|
}
|
||||||
.entity-chip {
|
.entity-chip {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -485,6 +530,9 @@ ul.pagination {
|
|||||||
display: block;
|
display: block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
> .entity-list > .entity-list-item:last-child {
|
||||||
|
margin-bottom: -$-xs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entity-list-item-image {
|
.entity-list-item-image {
|
||||||
@ -531,6 +579,9 @@ ul.pagination {
|
|||||||
font-size: $fs-m * 0.8;
|
font-size: $fs-m * 0.8;
|
||||||
padding-top: $-xs;
|
padding-top: $-xs;
|
||||||
}
|
}
|
||||||
|
.entity-list-item p:empty {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@ -574,8 +625,8 @@ ul.pagination {
|
|||||||
right: 0;
|
right: 0;
|
||||||
margin: $-m 0;
|
margin: $-m 0;
|
||||||
@include lightDark(background-color, #fff, #333);
|
@include lightDark(background-color, #fff, #333);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
|
box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.18);
|
||||||
border-radius: 1px;
|
border-radius: 3px;
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
padding: $-xs 0;
|
padding: $-xs 0;
|
||||||
@include lightDark(color, #555, #eee);
|
@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
|
// Books grid view
|
||||||
.featured-image-container {
|
.featured-image-container {
|
||||||
position: relative;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
font-size: 0.8rem;
|
||||||
width: 1.88em;
|
width: 1.88em;
|
||||||
height: 1.88em;
|
height: 1.88em;
|
||||||
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -79,17 +79,17 @@ $loadingSize: 10px;
|
|||||||
animation-timing-function: cubic-bezier(.62, .28, .23, .99);
|
animation-timing-function: cubic-bezier(.62, .28, .23, .99);
|
||||||
margin-inline-end: 4px;
|
margin-inline-end: 4px;
|
||||||
background-color: var(--color-page);
|
background-color: var(--color-page);
|
||||||
animation-delay: 0.3s;
|
animation-delay: -300ms;
|
||||||
}
|
}
|
||||||
> div:first-child {
|
> div:first-child {
|
||||||
left: -($loadingSize+$-xs);
|
left: -($loadingSize+$-xs);
|
||||||
background-color: var(--color-book);
|
background-color: var(--color-book);
|
||||||
animation-delay: 0s;
|
animation-delay: -600ms;
|
||||||
}
|
}
|
||||||
> div:last-of-type {
|
> div:last-of-type {
|
||||||
left: $loadingSize+$-xs;
|
left: $loadingSize+$-xs;
|
||||||
background-color: var(--color-chapter);
|
background-color: var(--color-chapter);
|
||||||
animation-delay: 0.6s;
|
animation-delay: 0ms;
|
||||||
}
|
}
|
||||||
> span {
|
> span {
|
||||||
margin-inline-start: $-s;
|
margin-inline-start: $-s;
|
||||||
@ -138,7 +138,7 @@ $btt-size: 40px;
|
|||||||
|
|
||||||
.skip-to-content-link {
|
.skip-to-content-link {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: -$-xxl;
|
top: -52px;
|
||||||
left: 0;
|
left: 0;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
z-index: 15;
|
z-index: 15;
|
||||||
|
@ -1,10 +1,27 @@
|
|||||||
<div component="attachments-list">
|
<div component="attachments-list">
|
||||||
@foreach($attachments as $attachment)
|
@foreach($attachments as $attachment)
|
||||||
<div class="attachment icon-list">
|
<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>
|
<div class="split-icon-list-item attachment-{{ $attachment->external ? 'link' : 'file' }}">
|
||||||
<span class="icon">@icon($attachment->external ? 'export' : 'file')</span>
|
<a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
|
||||||
<span>{{ $attachment->name }}</span>
|
<div class="icon">@icon($attachment->external ? 'export' : 'file')</div>
|
||||||
|
<div class="label">{{ $attachment->name }}</div>
|
||||||
</a>
|
</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>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
@ -67,14 +67,20 @@
|
|||||||
@section('right')
|
@section('right')
|
||||||
<div class="mb-xl">
|
<div class="mb-xl">
|
||||||
<h5>{{ trans('common.details') }}</h5>
|
<h5>{{ trans('common.details') }}</h5>
|
||||||
<div class="text-small text-muted blended-links">
|
<div class="blended-links">
|
||||||
@include('entities.meta', ['entity' => $book])
|
@include('entities.meta', ['entity' => $book])
|
||||||
@if($book->restricted)
|
@if($book->restricted)
|
||||||
<div class="active-restriction">
|
<div class="active-restriction">
|
||||||
@if(userCan('restrictions-manage', $book))
|
@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
|
@else
|
||||||
@icon('lock'){{ trans('entities.books_permissions_active') }}
|
<div class="entity-meta-item">
|
||||||
|
@icon('lock')
|
||||||
|
<div>{{ trans('entities.books_permissions_active') }}</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
<div class="chapter-child-menu">
|
<div component="chapter-contents" class="chapter-child-menu">
|
||||||
<button chapter-toggle type="button" aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
|
<button type="button"
|
||||||
class="text-muted @if($isOpen) open @endif">
|
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>
|
@icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->visible_pages->count()) }}</span>
|
||||||
</button>
|
</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)
|
@foreach($bookChild->visible_pages as $childPage)
|
||||||
<li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation">
|
<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' : '' ])
|
@include('entities.list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
|
||||||
|
@ -5,18 +5,19 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<h4 class="entity-list-item-name break-text">{{ $chapter->name }}</h4>
|
<h4 class="entity-list-item-name break-text">{{ $chapter->name }}</h4>
|
||||||
<div class="entity-item-snippet">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@if ($chapter->visible_pages->count() > 0)
|
@if ($chapter->visible_pages->count() > 0)
|
||||||
<div class="chapter chapter-expansion">
|
<div class="chapter chapter-expansion">
|
||||||
<span class="icon text-chapter">@icon('page')</span>
|
<span class="icon text-chapter">@icon('page')</span>
|
||||||
<div class="content">
|
<div component="chapter-contents" class="content">
|
||||||
<button type="button" chapter-toggle
|
<button type="button"
|
||||||
|
refs="chapter-contents@toggle"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
|
class="text-muted chapter-contents-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
|
||||||
<div class="inset-list">
|
<div refs="chapter-contents@list" class="inset-list chapter-contents-list">
|
||||||
<div class="entity-list-item-children">
|
<div class="entity-list-item-children">
|
||||||
@include('entities.list', ['entities' => $chapter->visible_pages])
|
@include('entities.list', ['entities' => $chapter->visible_pages])
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,15 +64,21 @@
|
|||||||
|
|
||||||
<div class="mb-xl">
|
<div class="mb-xl">
|
||||||
<h5>{{ trans('common.details') }}</h5>
|
<h5>{{ trans('common.details') }}</h5>
|
||||||
<div class="blended-links text-small text-muted">
|
<div class="blended-links">
|
||||||
@include('entities.meta', ['entity' => $chapter])
|
@include('entities.meta', ['entity' => $chapter])
|
||||||
|
|
||||||
@if($book->restricted)
|
@if($book->restricted)
|
||||||
<div class="active-restriction">
|
<div class="active-restriction">
|
||||||
@if(userCan('restrictions-manage', $book))
|
@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
|
@else
|
||||||
@icon('lock'){{ trans('entities.books_permissions_active') }}
|
<div class="entity-meta-item">
|
||||||
|
@icon('lock')
|
||||||
|
<div>{{ trans('entities.books_permissions_active') }}</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@ -80,9 +86,15 @@
|
|||||||
@if($chapter->restricted)
|
@if($chapter->restricted)
|
||||||
<div class="active-restriction">
|
<div class="active-restriction">
|
||||||
@if(userCan('restrictions-manage', $chapter))
|
@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
|
@else
|
||||||
@icon('lock'){{ trans('entities.chapters_permissions_active') }}
|
<div class="entity-meta-item">
|
||||||
|
@icon('lock')
|
||||||
|
<div>{{ trans('entities.chapters_permissions_active') }}</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
class="mobile-menu-toggle hide-over-l">@icon('more')</button>
|
class="mobile-menu-toggle hide-over-l">@icon('more')</button>
|
||||||
</div>
|
</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())
|
@if (hasAppAccess())
|
||||||
<form action="{{ url('/search') }}" method="GET" class="search-box" role="search">
|
<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>
|
<button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button>
|
||||||
@ -28,7 +28,6 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-right">
|
|
||||||
<nav refs="header-mobile-toggle@menu" class="header-links">
|
<nav refs="header-mobile-toggle@menu" class="header-links">
|
||||||
<div class="links text-center">
|
<div class="links text-center">
|
||||||
@if (hasAppAccess())
|
@if (hasAppAccess())
|
||||||
@ -97,7 +96,6 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
option:dropdown-search:url="/search/entity/siblings?entity_type={{$entity->getType()}}&entity_id={{ $entity->id }}"
|
option:dropdown-search:url="/search/entity/siblings?entity_type={{$entity->getType()}}&entity_id={{ $entity->id }}"
|
||||||
option:dropdown-search:local-search-selector=".entity-list-item"
|
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">
|
aria-haspopup="true" aria-expanded="false" tabindex="0">
|
||||||
<div class="separator">@icon('chevron-right')</div>
|
<div class="separator">@icon('chevron-right')</div>
|
||||||
</div>
|
</div>
|
||||||
@ -18,6 +18,6 @@
|
|||||||
<div refs="dropdown-search@loading">
|
<div refs="dropdown-search@loading">
|
||||||
@include('common.loading-icon')
|
@include('common.loading-icon')
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
@ -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"
|
<div refs="dropdown@toggle" class="icon-list-item"
|
||||||
aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0">
|
aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0">
|
||||||
<span>@icon('export')</span>
|
<span>@icon('export')</span>
|
||||||
<span>{{ trans('entities.export') }}</span>
|
<span>{{ trans('entities.export') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
|
<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/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/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/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>
|
<li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,50 +1,62 @@
|
|||||||
<div class="entity-meta">
|
<div class="entity-meta">
|
||||||
@if($entity->isA('revision'))
|
@if($entity->isA('revision'))
|
||||||
|
<div class="entity-meta-item">
|
||||||
|
@icon('history')
|
||||||
<div>
|
<div>
|
||||||
@icon('history'){{ trans('entities.pages_revision') }}
|
{{ trans('entities.pages_revision') }}
|
||||||
{{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
|
{{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($entity->isA('page'))
|
@if ($entity->isA('page'))
|
||||||
<div>
|
@if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}" class="entity-meta-item"> @else <div class="entity-meta-item"> @endif
|
||||||
@if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
|
|
||||||
@icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
|
@icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
|
||||||
@if (userCan('page-update', $entity))</a>@endif
|
@if (userCan('page-update', $entity))</a> @else </div> @endif
|
||||||
</div>
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($entity->ownedBy && $entity->owned_by !== $entity->created_by)
|
@if ($entity->ownedBy && $entity->owned_by !== $entity->created_by)
|
||||||
|
<div class="entity-meta-item">
|
||||||
|
@icon('user')
|
||||||
<div>
|
<div>
|
||||||
@icon('user'){!! trans('entities.meta_owned_name', [
|
{!! trans('entities.meta_owned_name', [
|
||||||
'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
|
'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
|
||||||
]) !!}
|
]) !!}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($entity->createdBy)
|
@if ($entity->createdBy)
|
||||||
|
<div class="entity-meta-item">
|
||||||
|
@icon('star')
|
||||||
<div>
|
<div>
|
||||||
@icon('star'){!! trans('entities.meta_created_name', [
|
{!! trans('entities.meta_created_name', [
|
||||||
'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
|
'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
|
||||||
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
|
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
|
||||||
]) !!}
|
]) !!}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@else
|
@else
|
||||||
<div>
|
<div class="entity-meta-item">
|
||||||
@icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
|
@icon('star')
|
||||||
|
<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($entity->updatedBy)
|
@if ($entity->updatedBy)
|
||||||
|
<div class="entity-meta-item">
|
||||||
|
@icon('edit')
|
||||||
<div>
|
<div>
|
||||||
@icon('edit'){!! trans('entities.meta_updated_name', [
|
{!! trans('entities.meta_updated_name', [
|
||||||
'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
|
'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
|
||||||
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
|
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
|
||||||
]) !!}
|
]) !!}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@elseif (!$entity->isA('revision'))
|
@elseif (!$entity->isA('revision'))
|
||||||
<div>
|
<div class="entity-meta-item">
|
||||||
@icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
|
@icon('edit')
|
||||||
|
<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
@ -15,7 +15,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="owner">{{ trans('entities.permissions_owner') }}</label>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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"
|
option:dropdown-search:url="/search/users/select"
|
||||||
>
|
>
|
||||||
<input refs="user-select@input" type="hidden" name="{{ $name }}" value="{{ $user->id ?? '' }}">
|
<input refs="user-select@input" type="hidden" name="{{ $name }}" value="{{ $user->id ?? '' }}">
|
||||||
<div refs="dropdown@toggle"
|
<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">
|
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)
|
@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>
|
<span>{{ $user->name }}</span>
|
||||||
@else
|
@else
|
||||||
<span>{{ trans('settings.users_none_selected') }}</span>
|
<span>{{ trans('settings.users_none_selected') }}</span>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<span style="font-size: {{ $compact ? '1.15rem' : '1.5rem' }}; margin-left: auto;">
|
<span class="dropdown-search-toggle-select-caret">
|
||||||
@icon('caret-down')
|
@icon('caret-down')
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
|
<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">
|
<aside class="tri-layout-left-contents">
|
||||||
@yield('left')
|
@yield('left')
|
||||||
</aside>
|
</aside>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tri-layout-right print-hidden pt-m">
|
<div class="tri-layout-right print-hidden">
|
||||||
<aside class="tri-layout-right-contents">
|
<aside class="tri-layout-right-contents">
|
||||||
@yield('right')
|
@yield('right')
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -65,7 +65,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons px-m py-xs">
|
<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>
|
<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">
|
<ul refs="dropdown@menu" class="wide dropdown-menu">
|
||||||
<li class="px-l py-m">
|
<li class="px-l py-m">
|
||||||
|
@ -76,15 +76,21 @@
|
|||||||
@section('right')
|
@section('right')
|
||||||
<div id="page-details" class="entity-details mb-xl">
|
<div id="page-details" class="entity-details mb-xl">
|
||||||
<h5>{{ trans('common.details') }}</h5>
|
<h5>{{ trans('common.details') }}</h5>
|
||||||
<div class="body text-small blended-links">
|
<div class="blended-links">
|
||||||
@include('entities.meta', ['entity' => $page])
|
@include('entities.meta', ['entity' => $page])
|
||||||
|
|
||||||
@if($book->restricted)
|
@if($book->restricted)
|
||||||
<div class="active-restriction">
|
<div class="active-restriction">
|
||||||
@if(userCan('restrictions-manage', $book))
|
@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
|
@else
|
||||||
@icon('lock'){{ trans('entities.books_permissions_active') }}
|
<div class="entity-meta-item">
|
||||||
|
@icon('lock')
|
||||||
|
<div>{{ trans('entities.books_permissions_active') }}</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@ -92,9 +98,15 @@
|
|||||||
@if($page->chapter && $page->chapter->restricted)
|
@if($page->chapter && $page->chapter->restricted)
|
||||||
<div class="active-restriction">
|
<div class="active-restriction">
|
||||||
@if(userCan('restrictions-manage', $page->chapter))
|
@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
|
@else
|
||||||
@icon('lock'){{ trans('entities.chapters_permissions_active') }}
|
<div class="entity-meta-item">
|
||||||
|
@icon('lock')
|
||||||
|
<div>{{ trans('entities.chapters_permissions_active') }}</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@ -102,16 +114,23 @@
|
|||||||
@if($page->restricted)
|
@if($page->restricted)
|
||||||
<div class="active-restriction">
|
<div class="active-restriction">
|
||||||
@if(userCan('restrictions-manage', $page))
|
@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
|
@else
|
||||||
@icon('lock'){{ trans('entities.pages_permissions_active') }}
|
<div class="entity-meta-item">
|
||||||
|
@icon('lock')
|
||||||
|
<div>{{ trans('entities.pages_permissions_active') }}</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($page->template)
|
@if($page->template)
|
||||||
<div>
|
<div class="entity-meta-item">
|
||||||
@icon('template'){{ trans('entities.pages_is_template') }}
|
@icon('template')
|
||||||
|
<div>{{ trans('entities.pages_is_template') }}</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
<h1 class="list-heading">{{ trans('settings.audit') }}</h1>
|
<h1 class="list-heading">{{ trans('settings.audit') }}</h1>
|
||||||
<p class="text-muted">{{ trans('settings.audit_desc') }}</p>
|
<p class="text-muted">{{ trans('settings.audit_desc') }}</p>
|
||||||
|
|
||||||
<div class="flex-container-row">
|
<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 mr-m">
|
|
||||||
|
<div component="dropdown" class="list-sort-type dropdown-container">
|
||||||
<label for="">{{ trans('settings.audit_event_filter') }}</label>
|
<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>
|
<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">
|
<ul refs="dropdown@menu" class="dropdown-menu">
|
||||||
@ -21,13 +22,12 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row mr-m">
|
|
||||||
@if(!empty($listDetails['event']))
|
@if(!empty($listDetails['event']))
|
||||||
<input type="hidden" name="event" value="{{ $listDetails['event'] }}">
|
<input type="hidden" name="event" value="{{ $listDetails['event'] }}">
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@foreach(['date_from', 'date_to'] as $filterKey)
|
@foreach(['date_from', 'date_to'] as $filterKey)
|
||||||
<div class="mr-m">
|
<div class=>
|
||||||
<label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
|
<label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
|
||||||
<input id="audit_filter_{{ $filterKey }}"
|
<input id="audit_filter_{{ $filterKey }}"
|
||||||
component="submit-on-change"
|
component="submit-on-change"
|
||||||
@ -37,21 +37,20 @@
|
|||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
<div class="form-group ml-auto mr-m"
|
<div class="form-group"
|
||||||
component="submit-on-change"
|
component="submit-on-change"
|
||||||
option:submit-on-change:filter='[name="user"]'>
|
option:submit-on-change:filter='[name="user"]'>
|
||||||
<label for="owner">{{ trans('settings.audit_table_user') }}</label>
|
<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])
|
@include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user'])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group ml-auto">
|
<div class="form-group">
|
||||||
<label for="ip">{{ trans('settings.audit_table_ip') }}</label>
|
<label for="ip">{{ trans('settings.audit_table_ip') }}</label>
|
||||||
@include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
|
@include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
|
||||||
<input type="submit" style="display: none">
|
<input type="submit" style="display: none">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr class="mt-l mb-s">
|
<hr class="mt-l mb-s">
|
||||||
|
|
||||||
|
@ -119,7 +119,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<label for="setting-app-custom-head" class="setting-list-label">{{ trans('settings.app_custom_html') }}</label>
|
<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>
|
<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>
|
<p class="small text-right">{{ trans('settings.app_custom_html_disabled_notice') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -81,14 +81,20 @@
|
|||||||
|
|
||||||
<div id="details" class="mb-xl">
|
<div id="details" class="mb-xl">
|
||||||
<h5>{{ trans('common.details') }}</h5>
|
<h5>{{ trans('common.details') }}</h5>
|
||||||
<div class="text-small text-muted blended-links">
|
<div class="blended-links">
|
||||||
@include('entities.meta', ['entity' => $shelf])
|
@include('entities.meta', ['entity' => $shelf])
|
||||||
@if($shelf->restricted)
|
@if($shelf->restricted)
|
||||||
<div class="active-restriction">
|
<div class="active-restriction">
|
||||||
@if(userCan('restrictions-manage', $shelf))
|
@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
|
@else
|
||||||
@icon('lock'){{ trans('entities.shelves_permissions_active') }}
|
<div class="entity-meta-item">
|
||||||
|
@icon('lock')
|
||||||
|
<div>{{ trans('entities.shelves_permissions_active') }}</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p>
|
<p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
59
tests/Auth/GroupSyncServiceTest.php
Normal file
59
tests/Auth/GroupSyncServiceTest.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Auth;
|
||||||
|
|
||||||
|
use BookStack\Auth\Access\GroupSyncService;
|
||||||
|
use BookStack\Auth\Role;
|
||||||
|
use BookStack\Auth\User;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class GroupSyncServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function test_user_is_assigned_to_matching_roles()
|
||||||
|
{
|
||||||
|
$user = $this->getViewer();
|
||||||
|
|
||||||
|
$roleA = Role::factory()->create(['display_name' => 'Wizards']);
|
||||||
|
$roleB = Role::factory()->create(['display_name' => 'Gremlins']);
|
||||||
|
$roleC = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales']);
|
||||||
|
$roleD = Role::factory()->create(['display_name' => 'DEF456', 'external_auth_id' => 'admin-team']);
|
||||||
|
|
||||||
|
foreach([$roleA, $roleB, $roleC, $roleD] as $role) {
|
||||||
|
$this->assertFalse($user->hasRole($role->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
(new GroupSyncService())->syncUserWithFoundGroups($user, ['Wizards', 'Gremlinz', 'Sales', 'Admin Team'], false);
|
||||||
|
|
||||||
|
$user = User::query()->find($user->id);
|
||||||
|
$this->assertTrue($user->hasRole($roleA->id));
|
||||||
|
$this->assertFalse($user->hasRole($roleB->id));
|
||||||
|
$this->assertTrue($user->hasRole($roleC->id));
|
||||||
|
$this->assertTrue($user->hasRole($roleD->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_multiple_values_in_role_external_auth_id_handled()
|
||||||
|
{
|
||||||
|
$user = $this->getViewer();
|
||||||
|
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales, engineering, developers, marketers']);
|
||||||
|
$this->assertFalse($user->hasRole($role->id));
|
||||||
|
|
||||||
|
(new GroupSyncService())->syncUserWithFoundGroups($user, ['Developers'], false);
|
||||||
|
|
||||||
|
$user = User::query()->find($user->id);
|
||||||
|
$this->assertTrue($user->hasRole($role->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_commas_can_be_used_in_external_auth_id_if_escaped()
|
||||||
|
{
|
||||||
|
$user = $this->getViewer();
|
||||||
|
$role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales\,-developers, marketers']);
|
||||||
|
$this->assertFalse($user->hasRole($role->id));
|
||||||
|
|
||||||
|
(new GroupSyncService())->syncUserWithFoundGroups($user, ['Sales, Developers'], false);
|
||||||
|
|
||||||
|
$user = User::query()->find($user->id);
|
||||||
|
$this->assertTrue($user->hasRole($role->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user