mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-11-28 09:20:52 +01:00
Restructured content observables
This commit is contained in:
parent
86a25e802d
commit
bac1301710
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
1
material/assets/stylesheets/main.9f2461a2.min.css.map
Normal file
1
material/assets/stylesheets/main.9f2461a2.min.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -34,7 +34,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.c31396f4.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.9f2461a2.min.css' | url }}">
|
||||||
{% if config.theme.palette %}
|
{% if config.theme.palette %}
|
||||||
{% set palette = config.theme.palette %}
|
{% set palette = config.theme.palette %}
|
||||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.9204c3b2.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.9204c3b2.min.css' | url }}">
|
||||||
@ -213,7 +213,7 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ 'assets/javascripts/bundle.7ca7dfaa.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/bundle.195ba817.min.js' | url }}"></script>
|
||||||
{% for path in config["extra_javascript"] %}
|
{% for path in config["extra_javascript"] %}
|
||||||
<script src="{{ path | url }}"></script>
|
<script src="{{ path | url }}"></script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -25,11 +25,8 @@ import { Observable, merge } from "rxjs"
|
|||||||
import { getElements } from "~/browser"
|
import { getElements } from "~/browser"
|
||||||
|
|
||||||
import { Component } from "../../_"
|
import { Component } from "../../_"
|
||||||
import {
|
import { Annotation } from "../annotation"
|
||||||
Annotation,
|
import { CodeBlock, mountCodeBlock } from "../code"
|
||||||
CodeBlock,
|
|
||||||
mountCodeBlock
|
|
||||||
} from "../code"
|
|
||||||
import { Details, mountDetails } from "../details"
|
import { Details, mountDetails } from "../details"
|
||||||
import { DataTable, mountDataTable } from "../table"
|
import { DataTable, mountDataTable } from "../table"
|
||||||
import { ContentTabs, mountContentTabs } from "../tabs"
|
import { ContentTabs, mountContentTabs } from "../tabs"
|
||||||
|
@ -45,18 +45,18 @@ import {
|
|||||||
watchElementOffset
|
watchElementOffset
|
||||||
} from "~/browser"
|
} from "~/browser"
|
||||||
|
|
||||||
import { Component } from "../../../../_"
|
import { Component } from "../../../_"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Types
|
* Types
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Code annotation
|
* Annotation
|
||||||
*/
|
*/
|
||||||
export interface Annotation {
|
export interface Annotation {
|
||||||
active: boolean /* Code annotation is visible */
|
active: boolean /* Annotation is active */
|
||||||
offset: ElementOffset /* Code annotation offset */
|
offset: ElementOffset /* Annotation offset */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -64,12 +64,12 @@ export interface Annotation {
|
|||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watch code annotation
|
* Watch annotation
|
||||||
*
|
*
|
||||||
* @param el - Code annotation element
|
* @param el - Annotation element
|
||||||
* @param container - Containing code block element
|
* @param container - Containing element
|
||||||
*
|
*
|
||||||
* @returns Code annotation observable
|
* @returns Annotation observable
|
||||||
*/
|
*/
|
||||||
export function watchAnnotation(
|
export function watchAnnotation(
|
||||||
el: HTMLElement, container: HTMLElement
|
el: HTMLElement, container: HTMLElement
|
||||||
@ -88,7 +88,7 @@ export function watchAnnotation(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Actively watch code annotation on focus */
|
/* Actively watch annotation on focus */
|
||||||
return watchElementFocus(el)
|
return watchElementFocus(el)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(active => offset$
|
switchMap(active => offset$
|
||||||
@ -101,12 +101,12 @@ export function watchAnnotation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mount code annotation
|
* Mount annotation
|
||||||
*
|
*
|
||||||
* @param el - Code annotation element
|
* @param el - Annotation element
|
||||||
* @param container - Containing code block element
|
* @param container - Containing element
|
||||||
*
|
*
|
||||||
* @returns Code annotation component observable
|
* @returns Annotation component observable
|
||||||
*/
|
*/
|
||||||
export function mountAnnotation(
|
export function mountAnnotation(
|
||||||
el: HTMLElement, container: HTMLElement
|
el: HTMLElement, container: HTMLElement
|
@ -38,7 +38,7 @@ import {
|
|||||||
} from "~/browser"
|
} from "~/browser"
|
||||||
import { renderAnnotation } from "~/templates"
|
import { renderAnnotation } from "~/templates"
|
||||||
|
|
||||||
import { Component } from "../../../../_"
|
import { Component } from "../../../_"
|
||||||
import {
|
import {
|
||||||
Annotation,
|
Annotation,
|
||||||
mountAnnotation
|
mountAnnotation
|
||||||
@ -60,11 +60,11 @@ interface MountOptions {
|
|||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all code annotation markers in the given code block
|
* Find all annotation markers in the given code block
|
||||||
*
|
*
|
||||||
* @param container - Containing code block element
|
* @param container - Containing element
|
||||||
*
|
*
|
||||||
* @returns Code annotation markers
|
* @returns Annotation markers
|
||||||
*/
|
*/
|
||||||
function findAnnotationMarkers(container: HTMLElement): Text[] {
|
function findAnnotationMarkers(container: HTMLElement): Text[] {
|
||||||
const markers: Text[] = []
|
const markers: Text[] = []
|
||||||
@ -97,18 +97,18 @@ function swap(source: HTMLElement, target: HTMLElement): void {
|
|||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mount code annotation list
|
* Mount annotation list
|
||||||
*
|
*
|
||||||
* This function analyzes the given container code block and checks for markers
|
* This function analyzes the containing code block and checks for markers
|
||||||
* referring to elements in the given code annotation list. If no markers are
|
* referring to elements in the given annotation list. If no markers are found,
|
||||||
* found, the list is left untouched. Otherwise, list elements are rendered as
|
* the list is left untouched. Otherwise, list elements are rendered as
|
||||||
* code annotations inside the code block.
|
* annotations inside the code block.
|
||||||
*
|
*
|
||||||
* @param el - Code annotation list element
|
* @param el - Annotation list element
|
||||||
* @param container - Containing code block element
|
* @param container - Containing element
|
||||||
* @param options - Options
|
* @param options - Options
|
||||||
*
|
*
|
||||||
* @returns Code annotation list component observable
|
* @returns Annotation component observable
|
||||||
*/
|
*/
|
||||||
export function mountAnnotationList(
|
export function mountAnnotationList(
|
||||||
el: HTMLElement, container: HTMLElement, { print$ }: MountOptions
|
el: HTMLElement, container: HTMLElement, { print$ }: MountOptions
|
||||||
@ -138,7 +138,7 @@ export function mountAnnotationList(
|
|||||||
.subscribe(active => {
|
.subscribe(active => {
|
||||||
el.hidden = !active
|
el.hidden = !active
|
||||||
|
|
||||||
/* Move annotation contents back into list */
|
/* Show annotations in code block or list (print) */
|
||||||
for (const [id, annotation] of annotations) {
|
for (const [id, annotation] of annotations) {
|
||||||
const inner = getElement(".md-typeset", annotation)
|
const inner = getElement(".md-typeset", annotation)
|
||||||
const child = getElement(`li:nth-child(${id})`, el)
|
const child = getElement(`li:nth-child(${id})`, el)
|
@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ClipboardJS from "clipboard"
|
|
||||||
import {
|
|
||||||
EMPTY,
|
|
||||||
Observable,
|
|
||||||
Subject,
|
|
||||||
defer,
|
|
||||||
distinctUntilChanged,
|
|
||||||
distinctUntilKeyChanged,
|
|
||||||
finalize,
|
|
||||||
map,
|
|
||||||
mergeWith,
|
|
||||||
switchMap,
|
|
||||||
takeLast,
|
|
||||||
takeUntil,
|
|
||||||
tap
|
|
||||||
} from "rxjs"
|
|
||||||
|
|
||||||
import { feature } from "~/_"
|
|
||||||
import {
|
|
||||||
getElementContentSize,
|
|
||||||
watchElementSize
|
|
||||||
} from "~/browser"
|
|
||||||
import { renderClipboardButton } from "~/templates"
|
|
||||||
|
|
||||||
import { Component } from "../../../_"
|
|
||||||
import {
|
|
||||||
Annotation,
|
|
||||||
mountAnnotationList
|
|
||||||
} from "../annotation"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Types
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Code block
|
|
||||||
*/
|
|
||||||
export interface CodeBlock {
|
|
||||||
scrollable: boolean /* Code block overflows */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Helper types
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mount options
|
|
||||||
*/
|
|
||||||
interface MountOptions {
|
|
||||||
print$: Observable<boolean> /* Media print observable */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Data
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global sequence number for Clipboard.js integration
|
|
||||||
*/
|
|
||||||
let sequence = 0
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Helper functions
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find candidate list element directly following a code block
|
|
||||||
*
|
|
||||||
* @param el - Code block element
|
|
||||||
*
|
|
||||||
* @returns List element or nothing
|
|
||||||
*/
|
|
||||||
function findCandidateList(el: HTMLElement): HTMLElement | undefined {
|
|
||||||
if (el.nextElementSibling) {
|
|
||||||
const sibling = el.nextElementSibling as HTMLElement
|
|
||||||
if (sibling.tagName === "OL")
|
|
||||||
return sibling
|
|
||||||
|
|
||||||
/* Skip empty paragraphs - see https://bit.ly/3r4ZJ2O */
|
|
||||||
else if (sibling.tagName === "P" && !sibling.children.length)
|
|
||||||
return findCandidateList(sibling)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Everything else */
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Functions
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch code block
|
|
||||||
*
|
|
||||||
* This function monitors size changes of the viewport, as well as switches of
|
|
||||||
* content tabs with embedded code blocks, as both may trigger overflow.
|
|
||||||
*
|
|
||||||
* @param el - Code block element
|
|
||||||
*
|
|
||||||
* @returns Code block observable
|
|
||||||
*/
|
|
||||||
export function watchCodeBlock(
|
|
||||||
el: HTMLElement
|
|
||||||
): Observable<CodeBlock> {
|
|
||||||
return watchElementSize(el)
|
|
||||||
.pipe(
|
|
||||||
map(({ width }) => {
|
|
||||||
const content = getElementContentSize(el)
|
|
||||||
return {
|
|
||||||
scrollable: content.width > width
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
distinctUntilKeyChanged("scrollable")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mount code block
|
|
||||||
*
|
|
||||||
* This function ensures that an overflowing code block is focusable through
|
|
||||||
* keyboard, so it can be scrolled without a mouse to improve on accessibility.
|
|
||||||
* Furthermore, if code annotations are enabled, they are mounted if and only
|
|
||||||
* if the code block is currently visible, e.g., not in a hidden content tab.
|
|
||||||
*
|
|
||||||
* @param el - Code block element
|
|
||||||
* @param options - Options
|
|
||||||
*
|
|
||||||
* @returns Code block and annotation component observable
|
|
||||||
*/
|
|
||||||
export function mountCodeBlock(
|
|
||||||
el: HTMLElement, options: MountOptions
|
|
||||||
): Observable<Component<CodeBlock | Annotation>> {
|
|
||||||
const { matches: hover } = matchMedia("(hover)")
|
|
||||||
return defer(() => {
|
|
||||||
const push$ = new Subject<CodeBlock>()
|
|
||||||
push$.subscribe(({ scrollable }) => {
|
|
||||||
if (scrollable && hover)
|
|
||||||
el.setAttribute("tabindex", "0")
|
|
||||||
else
|
|
||||||
el.removeAttribute("tabindex")
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Render button for Clipboard.js integration */
|
|
||||||
if (ClipboardJS.isSupported()) {
|
|
||||||
const parent = el.closest("pre")!
|
|
||||||
parent.id = `__code_${++sequence}`
|
|
||||||
parent.insertBefore(
|
|
||||||
renderClipboardButton(parent.id),
|
|
||||||
el
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle code annotations */
|
|
||||||
const container = el.closest([
|
|
||||||
":not(td.code) > .highlight", /* Code blocks */
|
|
||||||
".highlighttable" /* Code blocks with line numbers */
|
|
||||||
].join(", "))
|
|
||||||
if (container instanceof HTMLElement) {
|
|
||||||
const list = findCandidateList(container)
|
|
||||||
|
|
||||||
/* Mount code annotations, if enabled */
|
|
||||||
if (typeof list !== "undefined" && (
|
|
||||||
container.classList.contains("annotate") ||
|
|
||||||
feature("content.code.annotate")
|
|
||||||
)) {
|
|
||||||
const annotations$ = mountAnnotationList(list, el, options)
|
|
||||||
|
|
||||||
/* Create and return component */
|
|
||||||
return watchCodeBlock(el)
|
|
||||||
.pipe(
|
|
||||||
tap(state => push$.next(state)),
|
|
||||||
finalize(() => push$.complete()),
|
|
||||||
map(state => ({ ref: el, ...state })),
|
|
||||||
mergeWith(watchElementSize(container)
|
|
||||||
.pipe(
|
|
||||||
takeUntil(push$.pipe(takeLast(1))),
|
|
||||||
map(({ width, height }) => width && height),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
switchMap(active => active ? annotations$ : EMPTY)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create and return component */
|
|
||||||
return watchCodeBlock(el)
|
|
||||||
.pipe(
|
|
||||||
tap(state => push$.next(state)),
|
|
||||||
finalize(() => push$.complete()),
|
|
||||||
map(state => ({ ref: el, ...state }))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
@ -20,5 +20,197 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./_"
|
import ClipboardJS from "clipboard"
|
||||||
export * from "./annotation"
|
import {
|
||||||
|
EMPTY,
|
||||||
|
Observable,
|
||||||
|
Subject,
|
||||||
|
defer,
|
||||||
|
distinctUntilChanged,
|
||||||
|
distinctUntilKeyChanged,
|
||||||
|
finalize,
|
||||||
|
map,
|
||||||
|
mergeWith,
|
||||||
|
switchMap,
|
||||||
|
takeLast,
|
||||||
|
takeUntil,
|
||||||
|
tap
|
||||||
|
} from "rxjs"
|
||||||
|
|
||||||
|
import { feature } from "~/_"
|
||||||
|
import {
|
||||||
|
getElementContentSize,
|
||||||
|
watchElementSize
|
||||||
|
} from "~/browser"
|
||||||
|
import { renderClipboardButton } from "~/templates"
|
||||||
|
|
||||||
|
import { Component } from "../../_"
|
||||||
|
import {
|
||||||
|
Annotation,
|
||||||
|
mountAnnotationList
|
||||||
|
} from "../annotation"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Types
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code block
|
||||||
|
*/
|
||||||
|
export interface CodeBlock {
|
||||||
|
scrollable: boolean /* Code block overflows */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Helper types
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount options
|
||||||
|
*/
|
||||||
|
interface MountOptions {
|
||||||
|
print$: Observable<boolean> /* Media print observable */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Data
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global sequence number for Clipboard.js integration
|
||||||
|
*/
|
||||||
|
let sequence = 0
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Helper functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find candidate list element directly following a code block
|
||||||
|
*
|
||||||
|
* @param el - Code block element
|
||||||
|
*
|
||||||
|
* @returns List element or nothing
|
||||||
|
*/
|
||||||
|
function findCandidateList(el: HTMLElement): HTMLElement | undefined {
|
||||||
|
if (el.nextElementSibling) {
|
||||||
|
const sibling = el.nextElementSibling as HTMLElement
|
||||||
|
if (sibling.tagName === "OL")
|
||||||
|
return sibling
|
||||||
|
|
||||||
|
/* Skip empty paragraphs - see https://bit.ly/3r4ZJ2O */
|
||||||
|
else if (sibling.tagName === "P" && !sibling.children.length)
|
||||||
|
return findCandidateList(sibling)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Everything else */
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch code block
|
||||||
|
*
|
||||||
|
* This function monitors size changes of the viewport, as well as switches of
|
||||||
|
* content tabs with embedded code blocks, as both may trigger overflow.
|
||||||
|
*
|
||||||
|
* @param el - Code block element
|
||||||
|
*
|
||||||
|
* @returns Code block observable
|
||||||
|
*/
|
||||||
|
export function watchCodeBlock(
|
||||||
|
el: HTMLElement
|
||||||
|
): Observable<CodeBlock> {
|
||||||
|
return watchElementSize(el)
|
||||||
|
.pipe(
|
||||||
|
map(({ width }) => {
|
||||||
|
const content = getElementContentSize(el)
|
||||||
|
return {
|
||||||
|
scrollable: content.width > width
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
distinctUntilKeyChanged("scrollable")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount code block
|
||||||
|
*
|
||||||
|
* This function ensures that an overflowing code block is focusable through
|
||||||
|
* keyboard, so it can be scrolled without a mouse to improve on accessibility.
|
||||||
|
* Furthermore, if code annotations are enabled, they are mounted if and only
|
||||||
|
* if the code block is currently visible, e.g., not in a hidden content tab.
|
||||||
|
*
|
||||||
|
* @param el - Code block element
|
||||||
|
* @param options - Options
|
||||||
|
*
|
||||||
|
* @returns Code block and annotation component observable
|
||||||
|
*/
|
||||||
|
export function mountCodeBlock(
|
||||||
|
el: HTMLElement, options: MountOptions
|
||||||
|
): Observable<Component<CodeBlock | Annotation>> {
|
||||||
|
const { matches: hover } = matchMedia("(hover)")
|
||||||
|
return defer(() => {
|
||||||
|
const push$ = new Subject<CodeBlock>()
|
||||||
|
push$.subscribe(({ scrollable }) => {
|
||||||
|
if (scrollable && hover)
|
||||||
|
el.setAttribute("tabindex", "0")
|
||||||
|
else
|
||||||
|
el.removeAttribute("tabindex")
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Render button for Clipboard.js integration */
|
||||||
|
if (ClipboardJS.isSupported()) {
|
||||||
|
const parent = el.closest("pre")!
|
||||||
|
parent.id = `__code_${++sequence}`
|
||||||
|
parent.insertBefore(
|
||||||
|
renderClipboardButton(parent.id),
|
||||||
|
el
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle code annotations */
|
||||||
|
const container = el.closest([
|
||||||
|
":not(td.code) > .highlight", /* Code blocks */
|
||||||
|
".highlighttable" /* Code blocks with line numbers */
|
||||||
|
].join(", "))
|
||||||
|
if (container instanceof HTMLElement) {
|
||||||
|
const list = findCandidateList(container)
|
||||||
|
|
||||||
|
/* Mount code annotations, if enabled */
|
||||||
|
if (typeof list !== "undefined" && (
|
||||||
|
container.classList.contains("annotate") ||
|
||||||
|
feature("content.code.annotate")
|
||||||
|
)) {
|
||||||
|
const annotations$ = mountAnnotationList(list, el, options)
|
||||||
|
|
||||||
|
/* Create and return component */
|
||||||
|
return watchCodeBlock(el)
|
||||||
|
.pipe(
|
||||||
|
tap(state => push$.next(state)),
|
||||||
|
finalize(() => push$.complete()),
|
||||||
|
map(state => ({ ref: el, ...state })),
|
||||||
|
mergeWith(watchElementSize(container)
|
||||||
|
.pipe(
|
||||||
|
takeUntil(push$.pipe(takeLast(1))),
|
||||||
|
map(({ width, height }) => width && height),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
switchMap(active => active ? annotations$ : EMPTY)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create and return component */
|
||||||
|
return watchCodeBlock(el)
|
||||||
|
.pipe(
|
||||||
|
tap(state => push$.next(state)),
|
||||||
|
finalize(() => push$.complete()),
|
||||||
|
map(state => ({ ref: el, ...state }))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -43,7 +43,7 @@ import { Component } from "../../_"
|
|||||||
*/
|
*/
|
||||||
export interface Details {
|
export interface Details {
|
||||||
action: "open" | "close" /* Details state */
|
action: "open" | "close" /* Details state */
|
||||||
scroll?: boolean /* Scroll into view */
|
reveal?: boolean /* Details is revealed */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -89,7 +89,7 @@ export function watchDetails(
|
|||||||
.pipe(
|
.pipe(
|
||||||
map(target => target.closest("details:not([open])")!),
|
map(target => target.closest("details:not([open])")!),
|
||||||
filter(details => el === details),
|
filter(details => el === details),
|
||||||
mapTo<Details>({ action: "open", scroll: true })
|
mapTo<Details>({ action: "open", reveal: true })
|
||||||
),
|
),
|
||||||
|
|
||||||
/* Open details on print and close afterwards */
|
/* Open details on print and close afterwards */
|
||||||
@ -120,12 +120,12 @@ export function mountDetails(
|
|||||||
): Observable<Component<Details>> {
|
): Observable<Component<Details>> {
|
||||||
return defer(() => {
|
return defer(() => {
|
||||||
const push$ = new Subject<Details>()
|
const push$ = new Subject<Details>()
|
||||||
push$.subscribe(({ action, scroll }) => {
|
push$.subscribe(({ action, reveal }) => {
|
||||||
if (action === "open")
|
if (action === "open")
|
||||||
el.setAttribute("open", "")
|
el.setAttribute("open", "")
|
||||||
else
|
else
|
||||||
el.removeAttribute("open")
|
el.removeAttribute("open")
|
||||||
if (scroll)
|
if (reveal)
|
||||||
el.scrollIntoView()
|
el.scrollIntoView()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./_"
|
export * from "./_"
|
||||||
|
export * from "./annotation"
|
||||||
export * from "./code"
|
export * from "./code"
|
||||||
export * from "./details"
|
export * from "./details"
|
||||||
export * from "./table"
|
export * from "./table"
|
||||||
|
@ -46,7 +46,7 @@ import { Component } from "../_"
|
|||||||
*/
|
*/
|
||||||
export interface Dialog {
|
export interface Dialog {
|
||||||
message: string /* Dialog message */
|
message: string /* Dialog message */
|
||||||
open: boolean /* Dialog is visible */
|
active: boolean /* Dialog is active */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -89,7 +89,7 @@ export function watchDialog(
|
|||||||
of(false).pipe(delay(2000))
|
of(false).pipe(delay(2000))
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(open => ({ message, open }))
|
map(active => ({ message, active }))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -112,9 +112,9 @@ export function mountDialog(
|
|||||||
const inner = getElement(".md-typeset", el)
|
const inner = getElement(".md-typeset", el)
|
||||||
return defer(() => {
|
return defer(() => {
|
||||||
const push$ = new Subject<Dialog>()
|
const push$ = new Subject<Dialog>()
|
||||||
push$.subscribe(({ message, open }) => {
|
push$.subscribe(({ message, active }) => {
|
||||||
inner.textContent = message
|
inner.textContent = message
|
||||||
if (open)
|
if (active)
|
||||||
el.setAttribute("data-md-state", "open")
|
el.setAttribute("data-md-state", "open")
|
||||||
else
|
else
|
||||||
el.removeAttribute("data-md-state")
|
el.removeAttribute("data-md-state")
|
||||||
|
@ -59,7 +59,7 @@ import { Main } from "../../main"
|
|||||||
export interface Header {
|
export interface Header {
|
||||||
height: number /* Header visible height */
|
height: number /* Header visible height */
|
||||||
sticky: boolean /* Header stickyness */
|
sticky: boolean /* Header stickyness */
|
||||||
hidden: boolean /* User scrolled past threshold */
|
hidden: boolean /* Header is hidden */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
|
@ -49,7 +49,7 @@ import { Header } from "../_"
|
|||||||
* Header
|
* Header
|
||||||
*/
|
*/
|
||||||
export interface HeaderTitle {
|
export interface HeaderTitle {
|
||||||
active: boolean /* User scrolled past first headline */
|
active: boolean /* Header title is active */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
|
@ -46,7 +46,7 @@ import { Header } from "../header"
|
|||||||
export interface Main {
|
export interface Main {
|
||||||
offset: number /* Main area top offset */
|
offset: number /* Main area top offset */
|
||||||
height: number /* Main area visible height */
|
height: number /* Main area visible height */
|
||||||
active: boolean /* User scrolled past header */
|
active: boolean /* Main area is active */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
|
@ -53,7 +53,7 @@ import { Main } from "../main"
|
|||||||
*/
|
*/
|
||||||
export interface Sidebar {
|
export interface Sidebar {
|
||||||
height: number /* Sidebar height */
|
height: number /* Sidebar height */
|
||||||
locked: boolean /* User scrolled past header */
|
locked: boolean /* Sidebar is locked */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
|
@ -50,7 +50,7 @@ import { Header } from "../header"
|
|||||||
* Navigation tabs
|
* Navigation tabs
|
||||||
*/
|
*/
|
||||||
export interface Tabs {
|
export interface Tabs {
|
||||||
hidden: boolean /* User scrolled past tabs */
|
hidden: boolean /* Navigation tabs are hidden */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
|
@ -49,7 +49,7 @@ import { Main } from "../main"
|
|||||||
* Back-to-top button
|
* Back-to-top button
|
||||||
*/
|
*/
|
||||||
export interface BackToTop {
|
export interface BackToTop {
|
||||||
hidden: boolean /* User scrolled up */
|
hidden: boolean /* Back-to-top button is hidden */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
|
@ -27,7 +27,7 @@ import { h } from "~/utilities"
|
|||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render an empty code annotation
|
* Render an empty annotation
|
||||||
*
|
*
|
||||||
* @param id - Annotation identifier
|
* @param id - Annotation identifier
|
||||||
*
|
*
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
@import "main/icons";
|
@import "main/icons";
|
||||||
@import "main/typeset";
|
@import "main/typeset";
|
||||||
|
|
||||||
@import "main/layout/base";
|
|
||||||
@import "main/layout/banner";
|
@import "main/layout/banner";
|
||||||
|
@import "main/layout/base";
|
||||||
@import "main/layout/clipboard";
|
@import "main/layout/clipboard";
|
||||||
@import "main/layout/content";
|
@import "main/layout/content";
|
||||||
@import "main/layout/dialog";
|
@import "main/layout/dialog";
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Rules: layout
|
// Rules
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Tooltip variables
|
// Tooltip variables
|
||||||
@ -137,12 +137,12 @@
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Code annotation
|
// Annotation
|
||||||
.md-annotation {
|
.md-annotation {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
// Code annotation is not hidden (e.g. when copying)
|
// Annotation is not hidden (e.g. when copying)
|
||||||
&:not([hidden]) {
|
&:not([hidden]) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@ -152,19 +152,19 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code annotation wrapper (= tooltip)
|
// Annotation wrapper (= tooltip)
|
||||||
&__inner {
|
&__inner {
|
||||||
top: calc(var(--md-tooltip-y) + 1.2ch);
|
top: calc(var(--md-tooltip-y) + 1.2ch);
|
||||||
font-family: var(--md-text-font-family);
|
font-family: var(--md-text-font-family);
|
||||||
|
|
||||||
// Code annotation tooltip when not focused
|
// Annotation tooltip when not focused
|
||||||
:not(:focus-within) > & {
|
:not(:focus-within) > & {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code annotation index
|
// Annotation index
|
||||||
&__index {
|
&__index {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
@ -174,10 +174,10 @@
|
|||||||
transition: z-index 250ms;
|
transition: z-index 250ms;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
// Code annotation marker – the marker must be positioned absolutely behind
|
// Annotation marker – the marker must be positioned absolutely behind
|
||||||
// the index, because it shouldn't impact the rendering of a code block.
|
// the index, because it shouldn't impact the rendering of a code block.
|
||||||
// Otherwise, small rounding differences in browsers can sometimes mess up
|
// Otherwise, small rounding differences in browsers can sometimes mess up
|
||||||
// alignment of text following a code annotation.
|
// alignment of text following a Annotation.
|
||||||
&::after {
|
&::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.025em;
|
top: 0.025em;
|
||||||
@ -201,12 +201,12 @@
|
|||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code annotation marker on focus/hover
|
// Annotation marker on focus/hover
|
||||||
:is(:focus-within, :hover) > & {
|
:is(:focus-within, :hover) > & {
|
||||||
background-color: var(--md-accent-fg-color);
|
background-color: var(--md-accent-fg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code annotation marker on focus
|
// Annotation marker on focus
|
||||||
:focus-within > & {
|
:focus-within > & {
|
||||||
transition:
|
transition:
|
||||||
color 250ms,
|
color 250ms,
|
||||||
@ -220,12 +220,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code annotation index on focus/hover
|
// Annotation index on focus/hover
|
||||||
:is(:focus-within, :hover) > & {
|
:is(:focus-within, :hover) > & {
|
||||||
color: var(--md-accent-bg-color);
|
color: var(--md-accent-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code annotation index on focus
|
// Annotation index on focus
|
||||||
:focus-within > & {
|
:focus-within > & {
|
||||||
transition: none;
|
transition: none;
|
||||||
animation: none;
|
animation: none;
|
||||||
|
Loading…
Reference in New Issue
Block a user