Merge branch 'develop' into feature

This commit is contained in:
jeremystretch
2023-03-13 11:58:37 -04:00
63 changed files with 502 additions and 212 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,7 @@ import { initMoveButtons } from './moveOptions';
import { initReslug } from './reslug';
import { initSelectAll } from './selectAll';
import { initSelectMultiple } from './selectMultiple';
import { initMarkdownPreviews } from './markdownPreview';
export function initButtons(): void {
for (const func of [
@@ -13,6 +14,7 @@ export function initButtons(): void {
initSelectAll,
initSelectMultiple,
initMoveButtons,
initMarkdownPreviews,
]) {
func();
}

View File

@@ -0,0 +1,45 @@
import { isTruthy } from 'src/util';
/**
* interface for htmx configRequest event
*/
declare global {
interface HTMLElementEventMap {
'htmx:configRequest': CustomEvent<{
parameters: Record<string, string>;
headers: Record<string, string>;
}>;
}
}
function initMarkdownPreview(markdownWidget: HTMLDivElement) {
const previewButton = markdownWidget.querySelector('button.preview-button') as HTMLButtonElement;
const textarea = markdownWidget.querySelector('textarea') as HTMLTextAreaElement;
const preview = markdownWidget.querySelector('div.preview') as HTMLDivElement;
/**
* Make sure the textarea has style attribute height
* So that it can be copied over to preview div.
*/
if (!isTruthy(textarea.style.height)) {
const { height } = textarea.getBoundingClientRect();
textarea.style.height = `${height}px`;
}
/**
* Add the value of the textarea to the body of the htmx request
* and copy the height of text are to the preview div
*/
previewButton.addEventListener('htmx:configRequest', e => {
e.detail.parameters = { text: textarea.value || '' };
e.detail.headers['X-CSRFToken'] = window.CSRF_TOKEN;
preview.style.minHeight = textarea.style.height;
preview.innerHTML = '';
});
}
export function initMarkdownPreviews(): void {
for (const markdownWidget of document.querySelectorAll<HTMLDivElement>('.markdown-widget')) {
initMarkdownPreview(markdownWidget);
}
}

View File

@@ -1,6 +1,5 @@
import { getElements, replaceAll, findFirstAdjacent } from '../util';
type InterfaceState = 'enabled' | 'disabled';
type ShowHide = 'show' | 'hide';
function isShowHide(value: unknown): value is ShowHide {
@@ -27,54 +26,23 @@ class ButtonState {
* Underlying Button DOM Element
*/
public button: HTMLButtonElement;
/**
* Table rows with `data-enabled` set to `"enabled"`
*/
private enabledRows: NodeListOf<HTMLTableRowElement>;
/**
* Table rows with `data-enabled` set to `"disabled"`
*/
private disabledRows: NodeListOf<HTMLTableRowElement>;
constructor(button: HTMLButtonElement, table: HTMLTableElement) {
/**
* Table rows provided in constructor
*/
private rows: NodeListOf<HTMLTableRowElement>;
constructor(button: HTMLButtonElement, rows: NodeListOf<HTMLTableRowElement>) {
this.button = button;
this.enabledRows = table.querySelectorAll<HTMLTableRowElement>('tr[data-enabled="enabled"]');
this.disabledRows = table.querySelectorAll<HTMLTableRowElement>('tr[data-enabled="disabled"]');
this.rows = rows;
}
/**
* This button's controlled type. For example, a button with the class `toggle-disabled` has
* directive 'disabled' because it controls the visibility of rows with
* `data-enabled="disabled"`. Likewise, `toggle-enabled` controls rows with
* `data-enabled="enabled"`.
* Remove visibility of button state rows.
*/
private get directive(): InterfaceState {
if (this.button.classList.contains('toggle-disabled')) {
return 'disabled';
} else if (this.button.classList.contains('toggle-enabled')) {
return 'enabled';
}
// If this class has been instantiated but doesn't contain these classes, it's probably because
// the classes are missing in the HTML template.
console.warn(this.button);
throw new Error('Toggle button does not contain expected class');
}
/**
* Toggle visibility of rows with `data-enabled="enabled"`.
*/
private toggleEnabledRows(): void {
for (const row of this.enabledRows) {
row.classList.toggle('d-none');
}
}
/**
* Toggle visibility of rows with `data-enabled="disabled"`.
*/
private toggleDisabledRows(): void {
for (const row of this.disabledRows) {
row.classList.toggle('d-none');
private hideRows(): void {
for (const row of this.rows) {
row.classList.add('d-none');
}
}
@@ -111,17 +79,6 @@ class ButtonState {
}
}
/**
* Toggle visibility for the rows this element controls.
*/
private toggleRows(): void {
if (this.directive === 'enabled') {
this.toggleEnabledRows();
} else if (this.directive === 'disabled') {
this.toggleDisabledRows();
}
}
/**
* Toggle the DOM element's `data-state` attribute.
*/
@@ -139,17 +96,20 @@ class ButtonState {
private toggle(): void {
this.toggleState();
this.toggleButton();
this.toggleRows();
}
/**
* When the button is clicked, toggle all controlled elements.
* When the button is clicked, toggle all controlled elements and hide rows based on
* buttonstate.
*/
public handleClick(event: Event): void {
const button = event.currentTarget as HTMLButtonElement;
if (button.isEqualNode(this.button)) {
this.toggle();
}
if (this.buttonState === 'hide') {
this.hideRows();
}
}
}
@@ -174,14 +134,25 @@ class TableState {
// @ts-expect-error null handling is performed in the constructor
private disabledButton: ButtonState;
/**
* Instance of ButtonState for the 'show/hide virtual rows' button.
*/
// @ts-expect-error null handling is performed in the constructor
private virtualButton: ButtonState;
/**
* Underlying DOM Table Caption Element.
*/
private caption: Nullable<HTMLTableCaptionElement> = null;
/**
* All table rows in table
*/
private rows: NodeListOf<HTMLTableRowElement>;
constructor(table: HTMLTableElement) {
this.table = table;
this.rows = this.table.querySelectorAll('tr');
try {
const toggleEnabledButton = findFirstAdjacent<HTMLButtonElement>(
this.table,
@@ -191,6 +162,10 @@ class TableState {
this.table,
'button.toggle-disabled',
);
const toggleVirtualButton = findFirstAdjacent<HTMLButtonElement>(
this.table,
'button.toggle-virtual',
);
const caption = this.table.querySelector('caption');
this.caption = caption;
@@ -203,13 +178,28 @@ class TableState {
throw new TableStateError("Table is missing a 'toggle-disabled' button.", table);
}
if (toggleVirtualButton === null) {
throw new TableStateError("Table is missing a 'toggle-virtual' button.", table);
}
// Attach event listeners to the buttons elements.
toggleEnabledButton.addEventListener('click', event => this.handleClick(event, this));
toggleDisabledButton.addEventListener('click', event => this.handleClick(event, this));
toggleVirtualButton.addEventListener('click', event => this.handleClick(event, this));
// Instantiate ButtonState for each button for state management.
this.enabledButton = new ButtonState(toggleEnabledButton, this.table);
this.disabledButton = new ButtonState(toggleDisabledButton, this.table);
this.enabledButton = new ButtonState(
toggleEnabledButton,
table.querySelectorAll<HTMLTableRowElement>('tr[data-enabled="enabled"]'),
);
this.disabledButton = new ButtonState(
toggleDisabledButton,
table.querySelectorAll<HTMLTableRowElement>('tr[data-enabled="disabled"]'),
);
this.virtualButton = new ButtonState(
toggleVirtualButton,
table.querySelectorAll<HTMLTableRowElement>('tr[data-type="virtual"]'),
);
} catch (err) {
if (err instanceof TableStateError) {
// This class is useless for tables that don't have toggle buttons.
@@ -246,37 +236,42 @@ class TableState {
private toggleCaption(): void {
const showEnabled = this.enabledButton.buttonState === 'show';
const showDisabled = this.disabledButton.buttonState === 'show';
const showVirtual = this.virtualButton.buttonState === 'show';
if (showEnabled && !showDisabled) {
if (showEnabled && !showDisabled && !showVirtual) {
this.captionText = 'Showing Enabled Interfaces';
} else if (showEnabled && showDisabled) {
} else if (showEnabled && showDisabled && !showVirtual) {
this.captionText = 'Showing Enabled & Disabled Interfaces';
} else if (!showEnabled && showDisabled) {
} else if (!showEnabled && showDisabled && !showVirtual) {
this.captionText = 'Showing Disabled Interfaces';
} else if (!showEnabled && !showDisabled) {
this.captionText = 'Hiding Enabled & Disabled Interfaces';
} else if (!showEnabled && !showDisabled && !showVirtual) {
this.captionText = 'Hiding Enabled, Disabled & Virtual Interfaces';
} else if (!showEnabled && !showDisabled && showVirtual) {
this.captionText = 'Showing Virtual Interfaces';
} else if (showEnabled && !showDisabled && showVirtual) {
this.captionText = 'Showing Enabled & Virtual Interfaces';
} else if (showEnabled && showDisabled && showVirtual) {
this.captionText = 'Showing Enabled, Disabled & Virtual Interfaces';
} else {
this.captionText = '';
}
}
/**
* When toggle buttons are clicked, pass the event to the relevant button's handler and update
* this instance's state.
* When toggle buttons are clicked, reapply visability all rows and
* pass the event to all button handlers
*
* @param event onClick event for toggle buttons.
* @param instance Instance of TableState (`this` cannot be used since that's context-specific).
*/
public handleClick(event: Event, instance: TableState): void {
const button = event.currentTarget as HTMLButtonElement;
const enabled = button.isEqualNode(instance.enabledButton.button);
const disabled = button.isEqualNode(instance.disabledButton.button);
if (enabled) {
instance.enabledButton.handleClick(event);
} else if (disabled) {
instance.disabledButton.handleClick(event);
for (const row of this.rows) {
row.classList.remove('d-none');
}
instance.enabledButton.handleClick(event);
instance.disabledButton.handleClick(event);
instance.virtualButton.handleClick(event);
instance.toggleCaption();
}
}

View File

@@ -236,12 +236,12 @@ table {
}
th.asc > a::after {
content: "\f0140";
content: '\f0140';
font-family: 'Material Design Icons';
}
th.desc > a::after {
content: "\f0143";
content: '\f0143';
font-family: 'Material Design Icons';
}
@@ -416,18 +416,18 @@ nav.search {
}
}
// Styles for the quicksearch and its clear button;
// Styles for the quicksearch and its clear button;
// Overrides input-group styles and adds transition effects
.quicksearch {
input[type="search"] {
border-radius: $border-radius !important;
input[type='search'] {
border-radius: $border-radius !important;
}
button {
margin-left: -32px !important;
z-index: 100 !important;
outline: none !important;
border-radius: $border-radius !important;
border-radius: $border-radius !important;
transition: visibility 0s, opacity 0.2s linear;
}
@@ -998,9 +998,24 @@ div.card-overlay {
padding: 8px;
}
/* Markdown widget */
.markdown-widget {
.nav-link {
border-bottom: 0;
&.active {
background-color: var(--nbx-body-bg);
}
}
.nav-tabs {
background-color: var(--nbx-pre-bg);
}
}
// Preformatted text blocks
td pre {
margin-bottom: 0
margin-bottom: 0;
}
pre.block {
padding: $spacer;