diff --git a/packages/muya/.stylelintrc b/packages/muya/.stylelintrc index 05ccf33a41..539f731700 100644 --- a/packages/muya/.stylelintrc +++ b/packages/muya/.stylelintrc @@ -1,8 +1,288 @@ { "extends": ["stylelint-config-standard"], - "plugins": ["stylelint-order", "stylelint-config-rational-order/plugin"], + "plugins": ["stylelint-order"], "rules": { - "order/properties-order": [[], { "severity": "warning" }], + "order/properties-order": [ + [ + { + "groupName": "Special", + "emptyLineBefore": "always", + "properties": [ + "composes", + "@import", + "@extend", + "@mixin", + "@at-root" + ] + }, + { + "groupName": "Positioning", + "emptyLineBefore": "always", + "properties": [ + "position", + "top", + "right", + "bottom", + "left", + "z-index" + ] + }, + { + "groupName": "Box Model", + "emptyLineBefore": "always", + "properties": [ + "display", + "flex", + "flex-basis", + "flex-direction", + "flex-flow", + "flex-grow", + "flex-shrink", + "flex-wrap", + "grid", + "grid-area", + "grid-auto-rows", + "grid-auto-columns", + "grid-auto-flow", + "grid-gap", + "grid-row", + "grid-row-start", + "grid-row-end", + "grid-row-gap", + "grid-column", + "grid-column-start", + "grid-column-end", + "grid-column-gap", + "grid-template", + "grid-template-areas", + "grid-template-rows", + "grid-template-columns", + "gap", + "align-content", + "align-items", + "align-self", + "justify-content", + "justify-items", + "justify-self", + "order", + "float", + "clear", + "box-sizing", + "width", + "min-width", + "max-width", + "height", + "min-height", + "max-height", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + "object-fit", + "object-position", + "overflow", + "overflow-x", + "overflow-y" + ] + }, + { + "groupName": "Typography", + "emptyLineBefore": "always", + "properties": [ + "color", + "font", + "font-weight", + "font-size", + "font-family", + "font-style", + "font-variant", + "font-size-adjust", + "font-stretch", + "font-effect", + "font-emphasize", + "font-emphasize-position", + "font-emphasize-style", + "font-smooth", + "line-height", + "direction", + "letter-spacing", + "white-space", + "text-align", + "text-align-last", + "text-transform", + "text-decoration", + "text-emphasis", + "text-emphasis-color", + "text-emphasis-style", + "text-emphasis-position", + "text-indent", + "text-justify", + "text-outline", + "text-wrap", + "text-overflow", + "text-overflow-ellipsis", + "text-overflow-mode", + "text-orientation", + "text-shadow", + "vertical-align", + "word-wrap", + "word-break", + "word-spacing", + "overflow-wrap", + "tab-size", + "hyphens", + "unicode-bidi", + "columns", + "column-count", + "column-fill", + "column-gap", + "column-rule", + "column-rule-color", + "column-rule-style", + "column-rule-width", + "column-span", + "column-width", + "page-break-after", + "page-break-before", + "page-break-inside", + "src" + ] + }, + { + "groupName": "Visual", + "emptyLineBefore": "always", + "properties": [ + "list-style", + "list-style-position", + "list-style-type", + "list-style-image", + "table-layout", + "empty-cells", + "caption-side", + "background", + "background-color", + "background-image", + "background-repeat", + "background-position", + "background-position-x", + "background-position-y", + "background-size", + "background-clip", + "background-origin", + "background-attachment", + "background-blend-mode", + "border", + "border-color", + "border-style", + "border-width", + "border-top", + "border-top-color", + "border-top-width", + "border-top-style", + "border-right", + "border-right-color", + "border-right-width", + "border-right-style", + "border-bottom", + "border-bottom-color", + "border-bottom-width", + "border-bottom-style", + "border-left", + "border-left-color", + "border-left-width", + "border-left-style", + "border-radius", + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius", + "border-image", + "border-image-source", + "border-image-slice", + "border-image-width", + "border-image-outset", + "border-image-repeat", + "border-collapse", + "border-spacing", + "outline", + "outline-width", + "outline-style", + "outline-color", + "outline-offset", + "box-shadow", + "box-decoration-break", + "transform", + "transform-origin", + "transform-style", + "backface-visibility", + "perspective", + "perspective-origin", + "visibility", + "cursor", + "opacity", + "filter", + "isolation", + "backdrop-filter", + "mix-blend-mode" + ] + }, + { + "groupName": "Animation", + "emptyLineBefore": "always", + "properties": [ + "transition", + "transition-delay", + "transition-timing-function", + "transition-duration", + "transition-property", + "animation", + "animation-name", + "animation-duration", + "animation-play-state", + "animation-timing-function", + "animation-delay", + "animation-iteration-count", + "animation-direction", + "animation-fill-mode" + ] + }, + { + "groupName": "Misc", + "emptyLineBefore": "always", + "properties": [ + "appearance", + "content", + "clip", + "clip-path", + "counter-reset", + "counter-increment", + "resize", + "user-select", + "nav-index", + "nav-up", + "nav-right", + "nav-down", + "nav-left", + "pointer-events", + "quotes", + "touch-action", + "will-change", + "zoom", + "fill", + "fill-rule", + "clip-rule", + "stroke" + ] + } + ], + { "severity": "warning", "unspecified": "bottomAlphabetical" } + ], "declaration-empty-line-before": [ "never", { @@ -15,14 +295,6 @@ ] } ], - "plugin/rational-order": [ - true, - { - "border-in-box-model": false, - "empty-line-between-groups": true, - "severity": "warning" - } - ], "unit-no-unknown": [ true, { diff --git a/packages/muya/e2e/tests/helpers/selectors.ts b/packages/muya/e2e/tests/helpers/selectors.ts index 1879953806..4e71693f29 100644 --- a/packages/muya/e2e/tests/helpers/selectors.ts +++ b/packages/muya/e2e/tests/helpers/selectors.ts @@ -8,6 +8,9 @@ export const editor = { container: '#editor', root: '.mu-editor', + // Editor root carries this class while focus mode is enabled (toggled by + // `Muya#setFocusMode` / applied at construction for `focusMode: true`). + focusModeRoot: '.mu-editor.mu-focus-mode', paragraph: '.mu-paragraph', atxHeading: '.mu-atx-heading', setextHeading: '.mu-setext-heading', diff --git a/packages/muya/e2e/tests/options/focus-mode.spec.ts b/packages/muya/e2e/tests/options/focus-mode.spec.ts index 138f029811..b27f018210 100644 --- a/packages/muya/e2e/tests/options/focus-mode.spec.ts +++ b/packages/muya/e2e/tests/options/focus-mode.spec.ts @@ -2,52 +2,81 @@ import { expect, test } from '../fixtures/muya'; import { editor } from '../helpers/selectors'; /** - * `focusMode: true` constructor option. + * `focusMode` option + `Muya#setFocusMode`. * - * Current state of the codebase: `focusMode` is declared in - * `IMuyaOptions`, defaulted to `false`, and reserved a class name - * `CLASS_NAMES.MU_FOCUS_MODE` ('mu-focus-mode') — but no code path - * actually applies that class to the DOM when the option is enabled. - * This makes focus-mode currently a no-op option. + * Focus mode dims every top-level block except the one holding the cursor. + * It is driven by the `mu-focus-mode` class on the `.mu-editor` root: + * - applied at construction when `new Muya(el, { focusMode: true })` + * (source: `packages/core/src/muya.ts::getContainer`), + * - toggled at runtime by `muya.setFocusMode(bool)`. + * The dimming itself lives in CSS (`.mu-focus-mode .mu-container > *`). * - * What this spec asserts (the option *is* respected by the constructor): - * 1. `new Muya(container, { focusMode: true })` boots without crashing. - * 2. `muya.options.focusMode === true` after rebuild. - * 3. The active paragraph can still be focused and typed into. - * - * What this spec deliberately does NOT assert: a visual marker on - * non-active paragraphs. There is no such marker today. See BACKLOG - * Phase 5 follow-up — once focus-mode renders a marker class, this - * spec should be tightened to assert it. + * This used to be a no-op (the option was declared and the class reserved but + * never applied); these specs lock in the implemented behavior. */ test.describe('options / focus-mode', () => { - test('focusMode: true — rebuild boots, option reflected, editor usable', async ({ page }) => { + test('focusMode: true — root carries mu-focus-mode, option reflected, editor usable', async ({ page }) => { await page.evaluate(() => { window.__e2e!.rebuildMuya({ focusMode: true }); window.muya!.setContent('# heading\n\nparagraph A\n\nparagraph B\n'); }); + const focusModeOption = await page.evaluate(() => window.muya!.options.focusMode); expect(focusModeOption).toBe(true); + // The class is applied to the editor root at construction. + await expect(page.locator(editor.focusModeRoot)).toBeVisible(); + // Sanity: editor renders multiple blocks and can be focused. await expect(page.locator(editor.atxHeading).first()).toBeVisible(); await expect(page.locator(editor.paragraph).nth(0)).toContainText('paragraph A'); await expect(page.locator(editor.paragraph).nth(1)).toContainText('paragraph B'); - // Click into paragraph B; the editor should remain alive. + // Click into paragraph B; the editor should remain alive and the active + // block keeps full opacity while the others are dimmed. await page.locator(editor.paragraph).nth(1).click(); const focused = await page.evaluate(() => { const active = window.muya!.editor.activeContentBlock; return active != null; }); expect(focused).toBe(true); + + // Exactly one top-level block (the active one) is at full opacity; the + // rest are dimmed to 0.25. Use auto-retrying `toHaveCSS` so the + // `opacity 0.2s` transition has settled before we read the value. + const activeBlock = page.locator('.mu-container > .mu-active'); + await expect(activeBlock).toHaveCount(1); + await expect(activeBlock).toHaveCSS('opacity', '1'); + + const inactiveBlocks = page.locator('.mu-container > :not(.mu-active)'); + const inactiveCount = await inactiveBlocks.count(); + expect(inactiveCount).toBeGreaterThan(0); + for (let i = 0; i < inactiveCount; i++) + await expect(inactiveBlocks.nth(i)).toHaveCSS('opacity', '0.25'); }); - test('focusMode: false (default) — option reflected as false', async ({ page }) => { + test('focusMode: false (default) — option reflected as false, no class', async ({ page }) => { await page.evaluate(() => { window.__e2e!.rebuildMuya({ focusMode: false }); }); + const value = await page.evaluate(() => window.muya!.options.focusMode); expect(value).toBe(false); + + await expect(page.locator(editor.root)).toBeVisible(); + await expect(page.locator(editor.focusModeRoot)).toHaveCount(0); + }); + + test('setFocusMode toggles the class and option at runtime', async ({ page }) => { + await page.evaluate(() => window.__e2e!.rebuildMuya({ focusMode: false })); + await expect(page.locator(editor.focusModeRoot)).toHaveCount(0); + + await page.evaluate(() => window.muya!.setFocusMode(true)); + expect(await page.evaluate(() => window.muya!.options.focusMode)).toBe(true); + await expect(page.locator(editor.focusModeRoot)).toBeVisible(); + + await page.evaluate(() => window.muya!.setFocusMode(false)); + expect(await page.evaluate(() => window.muya!.options.focusMode)).toBe(false); + await expect(page.locator(editor.focusModeRoot)).toHaveCount(0); }); }); diff --git a/packages/muya/eslint.config.mjs b/packages/muya/eslint.config.mjs index 1a0a277015..aaad306f4c 100644 --- a/packages/muya/eslint.config.mjs +++ b/packages/muya/eslint.config.mjs @@ -99,6 +99,11 @@ export default antfu( 'e2e/**', 'lib/**', 'docs/**', + // Vendored third-party library (js-sequence-diagrams, bramp, BSD) + // wired to snap.svg. Kept verbatim for feature parity with the + // legacy muyajs engine; not subject to our lint/format rules. + 'src/utils/diagram/sequence/sequence-diagram-snap.js', + 'src/utils/diagram/sequence/sequence-diagram.css', ], }, { diff --git a/packages/muya/package.json b/packages/muya/package.json index 2042267300..ce79d543b0 100644 --- a/packages/muya/package.json +++ b/packages/muya/package.json @@ -62,6 +62,7 @@ "dompurify": "^3.4.5", "execall": "^3.0.0", "fast-diff": "^1.3.0", + "flowchart.js": "^1.18.0", "fuse.js": "^7.3.0", "html-tags": "^5.1.0", "joplin-turndown-plugin-gfm": "^1.0.12", @@ -76,10 +77,13 @@ "rxjs": "^7.8.2", "snabbdom": "^3.6.3", "snabbdom-to-html": "^7.1.0", + "snapsvg-cjs": "^0.0.6", "turndown": "^7.2.4", + "underscore": "^1.13.8", "vega": "^6.2.0", "vega-embed": "^7.1.0", - "vega-lite": "^6.4.3" + "vega-lite": "^6.4.3", + "webfontloader": "^1.6.28" }, "devDependencies": { "@antfu/eslint-config": "^9.0.0", @@ -88,6 +92,8 @@ "@types/plantuml-encoder": "^1.4.2", "@types/prismjs": "^1.26.6", "@types/turndown": "^5.0.4", + "@types/underscore": "^1.13.0", + "@types/webfontloader": "^1.6.38", "@typescript-eslint/parser": "^8.59.4", "@vitest/coverage-istanbul": "^4.1.6", "commonmark-spec": "^0.31.2", @@ -96,7 +102,6 @@ "happy-dom": "^15.11.7", "madge": "^8.0.0", "stylelint": "^17.11.1", - "stylelint-config-rational-order": "^0.1.2", "stylelint-config-standard": "^40.0.0", "stylelint-order": "^8.1.1", "typescript": "^6.0.3", diff --git a/packages/muya/src/__tests__/blockEditing.spec.ts b/packages/muya/src/__tests__/blockEditing.spec.ts new file mode 100644 index 0000000000..a3149af12d --- /dev/null +++ b/packages/muya/src/__tests__/blockEditing.spec.ts @@ -0,0 +1,115 @@ +// @vitest-environment happy-dom + +import type Content from '../block/base/content'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Muya } from '../muya'; + +// Coverage for the programmatic block-editing API added for the +// muyajs -> @muyajs/core desktop migration: duplicate / insertParagraph / +// deleteParagraph. These drive the desktop Edit/Paragraph menu actions, +// which previously had no public entrypoint on @muyajs/core. +// +// Block-tree mutations dispatch json1 ops that flush to the document state on +// the next animation frame (see JSONState._emitStateChange), so assertions on +// getState() are wrapped in vi.waitFor to await that flush. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +// Block-level ops resolve their target via the active content block's +// outMostBlock — the way the editor tracks the cursor after a click. Set it +// directly to simulate the cursor sitting in the first block. +function placeCursorOnFirstBlock(muya: Muya): Content { + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + muya.editor.activeContentBlock = first; + return first; +} + +describe('muya block editing api', () => { + it('duplicate() copies the current block in place', async () => { + const muya = bootMuya('# Title\n\nbody\n'); + placeCursorOnFirstBlock(muya); + muya.duplicate(); + await vi.waitFor(() => { + const after = muya.getState(); + expect(after.length).toBe(3); + expect(after[0].name).toBe('atx-heading'); + expect(after[1].name).toBe('atx-heading'); + expect(after[2].name).toBe('paragraph'); + }); + }); + + it('insertParagraph() inserts an empty paragraph after by default', async () => { + const muya = bootMuya('# Title\n'); + placeCursorOnFirstBlock(muya); + muya.insertParagraph(); + await vi.waitFor(() => { + const after = muya.getState(); + expect(after.length).toBe(2); + expect(after[0].name).toBe('atx-heading'); + expect(after[1].name).toBe('paragraph'); + }); + }); + + it('insertParagraph("before", text) inserts before with the given text', async () => { + const muya = bootMuya('# Title\n'); + placeCursorOnFirstBlock(muya); + muya.insertParagraph('before', 'intro'); + await vi.waitFor(() => { + const after = muya.getState(); + expect(after.length).toBe(2); + expect(after[0].name).toBe('paragraph'); + expect(after[1].name).toBe('atx-heading'); + }); + expect(muya.getMarkdown()).toContain('intro'); + }); + + it('deleteParagraph() removes the current block and keeps the rest', async () => { + const muya = bootMuya('# Title\n\nbody\n'); + placeCursorOnFirstBlock(muya); + muya.deleteParagraph(); + await vi.waitFor(() => { + const after = muya.getState(); + expect(after.length).toBe(1); + expect(after[0].name).toBe('paragraph'); + }); + }); + + it('deleteParagraph() on the only block leaves a single empty paragraph', async () => { + const muya = bootMuya('# Title\n'); + placeCursorOnFirstBlock(muya); + muya.deleteParagraph(); + await vi.waitFor(() => { + const after = muya.getState(); + expect(after.length).toBe(1); + expect(after[0].name).toBe('paragraph'); + }); + }); +}); diff --git a/packages/muya/src/__tests__/createTableImageCursor.spec.ts b/packages/muya/src/__tests__/createTableImageCursor.spec.ts new file mode 100644 index 0000000000..59faee0dd4 --- /dev/null +++ b/packages/muya/src/__tests__/createTableImageCursor.spec.ts @@ -0,0 +1,292 @@ +// @vitest-environment happy-dom + +import type Content from '../block/base/content'; +import type Parent from '../block/base/parent'; +import type { ITableState, TState } from '../state/types'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Muya } from '../muya'; +import { isTableState } from '../state/types'; + +// Coverage for the programmatic editing API added for the muyajs -> +// @muyajs/core desktop migration: createTable / insertImage / setCursor. +// These complete the block-editing surface the desktop drives (table insert, +// image insert from the image tool, and programmatic cursor placement). +// +// Tree/text mutations dispatch json1 ops that flush to the document state on +// the next animation frame (see JSONState._emitStateChange), so assertions on +// getState()/getMarkdown() are wrapped in vi.waitFor to await that flush. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +// Place a collapsed caret on the first content block (and mark it active so the +// block-level ops resolve their target the same way the editor does after a +// click). +function placeCursorOnFirstBlock(muya: Muya, offset = 0): Content { + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + first.setCursor(offset, offset, true); + muya.editor.activeContentBlock = first; + return first; +} + +function firstBlock(muya: Muya): TState { + return muya.getState()[0]; +} + +function firstTable(muya: Muya): ITableState { + const b = firstBlock(muya); + if (!isTableState(b)) + throw new Error(`expected a table, got ${b.name}`); + return b; +} + +describe('muya.createTable()', () => { + it('replaces the current block with a table of the requested dimensions', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya); + muya.createTable({ rows: 3, columns: 4 }); + await vi.waitFor(() => { + const b = firstTable(muya); + expect(b.children.length).toBe(3); // rows (header + 2 body) + expect(b.children.every(row => row.children.length === 4)).toBe(true); // columns + }); + }); + + it('builds empty cells with align none', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya); + muya.createTable({ rows: 2, columns: 2 }); + await vi.waitFor(() => { + const cells = firstTable(muya).children.flatMap(row => row.children); + expect(cells.every(c => c.name === 'table.cell')).toBe(true); + expect(cells.every(c => c.text === '')).toBe(true); + expect(cells.every(c => c.meta.align === 'none')).toBe(true); + }); + }); + + it('places the cursor in the first cell of the new table', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya); + muya.createTable({ rows: 2, columns: 2 }); + await vi.waitFor(() => { + expect(firstBlock(muya).name).toBe('table'); + }); + const sel = muya.editor.selection.getSelection(); + expect(sel).not.toBeNull(); + // The caret lands on a table-cell content block. + expect(sel!.anchorBlock.blockName).toBe('table.cell.content'); + }); + + it('is a no-op when there is no current block', () => { + const muya = bootMuya('hello\n'); + muya.editor.activeContentBlock = null; + muya.editor.selection.anchorBlock = null; + expect(() => muya.createTable({ rows: 2, columns: 2 })).not.toThrow(); + expect(firstBlock(muya).name).toBe('paragraph'); + }); + + it('clamps zero/negative dimensions to a valid table (rows >= 2, columns >= 1)', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya); + // rows = 0 would otherwise build a table with no rows and crash + // `Table.columnCount` (which reads `firstChild.firstChild`). + expect(() => muya.createTable({ rows: 0, columns: 0 })).not.toThrow(); + await vi.waitFor(() => { + const b = firstTable(muya); + expect(b.children.length).toBe(2); // header + one body row + expect(b.children.every(row => row.children.length === 1)).toBe(true); + }); + }); + + it('coerces non-finite / fractional dimensions to integers', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya); + expect(() => + muya.createTable({ rows: Number.NaN, columns: Number.POSITIVE_INFINITY }), + ).not.toThrow(); + await vi.waitFor(() => { + const b = firstTable(muya); + // NaN -> clamped to 2 rows; Infinity column count is not finite so it + // also normalises to the minimum of 1 column rather than allocating + // an array of non-integer length. + expect(b.children.length).toBe(2); + expect(b.children.every(row => row.children.length === 1)).toBe(true); + }); + }); + + it('floors fractional dimensions instead of building a ragged table', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya); + muya.createTable({ rows: 3.9, columns: 2.9 }); + await vi.waitFor(() => { + const b = firstTable(muya); + expect(b.children.length).toBe(3); // floor(3.9) + expect(b.children.every(row => row.children.length === 2)).toBe(true); // floor(2.9) + }); + }); +}); + +describe('muya.insertImage()', () => { + it('inserts an inline image at the cursor', async () => { + const muya = bootMuya('hello\n'); + placeCursorOnFirstBlock(muya, 5); // caret at end of "hello" + muya.insertImage({ src: 'https://example.com/cat.png' }); + await vi.waitFor(() => { + const md = muya.getMarkdown(); + expect(md).toContain('https://example.com/cat.png'); + expect(md).toContain('!['); + }); + }); + + it('derives alt text from the file name when none is given', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya, 0); + muya.insertImage({ src: '/tmp/photos/sunset.jpg' }); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('![sunset]('); + }); + }); + + it('uses the provided alt text', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya, 0); + muya.insertImage({ src: 'https://example.com/x.png', alt: 'My Pic' }); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('![My Pic](https://example.com/x.png)'); + }); + }); + + it('percent-encodes spaces in local paths', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya, 0); + muya.insertImage({ src: '/my photos/a b.png', alt: 'pic' }); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('/my%20photos/a%20b.png'); + }); + }); + + it('is a no-op when there is no active formattable block', () => { + const muya = bootMuya('hello\n'); + muya.editor.activeContentBlock = null; + muya.editor.selection.anchorBlock = null; + expect(() => muya.insertImage({ src: 'https://example.com/x.png' })).not.toThrow(); + expect(muya.getMarkdown()).not.toContain('!['); + }); + + it('embeds a well-formed base64 data URL verbatim', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya, 0); + const dataUrl + = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='; + muya.insertImage({ src: dataUrl, alt: 'dot' }); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain(`![dot](${dataUrl})`); + }); + }); + + it('does not embed a malformed data: src verbatim (aligns with strict DATA_URL_REG)', async () => { + const muya = bootMuya('\n'); + placeCursorOnFirstBlock(muya, 0); + // `data:image/` prefix with no comma/payload — the old loose + // `^data:image/` check would have embedded it verbatim. It must instead + // fall through to the plain-path branch (spaces and '#' percent-encoded). + const malformed = 'data:image/png not-a-real#payload'; + muya.insertImage({ src: malformed, alt: 'bad' }); + await vi.waitFor(() => { + const md = muya.getMarkdown(); + // Treated as a plain path: spaces and '#' are percent-encoded, so the + // raw malformed string is not present verbatim. + expect(md).not.toContain(`(${malformed})`); + expect(md).toContain('data:image/png%20not-a-real%23payload'); + }); + }); +}); + +describe('muya.setCursor()', () => { + it('positions the caret in the same block (anchor/focus/path shape)', async () => { + const muya = bootMuya('hello world\n'); + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + muya.setCursor({ + anchor: { offset: 3 }, + focus: { offset: 3 }, + anchorPath: first.path, + focusPath: first.path, + }); + await vi.waitFor(() => { + const sel = muya.editor.selection.getSelection(); + expect(sel).not.toBeNull(); + expect(sel!.anchorBlock).toBe(first); + expect(sel!.anchor.offset).toBe(3); + }); + }); + + it('accepts the start/end/path shape', async () => { + const muya = bootMuya('hello world\n'); + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + muya.setCursor({ + start: { offset: 2 }, + end: { offset: 2 }, + path: first.path, + }); + await vi.waitFor(() => { + const sel = muya.editor.selection.getSelection(); + expect(sel!.anchorBlock).toBe(first); + expect(sel!.anchor.offset).toBe(2); + }); + }); + + it('resolves the target block across two paragraphs', async () => { + const muya = bootMuya('first\n\nsecond\n'); + const blocks = muya.editor.scrollPage!; + const secondPara = blocks.find(1) as Parent; + const secondContent = secondPara.firstContentInDescendant()!; + muya.setCursor({ + anchor: { offset: 1 }, + focus: { offset: 1 }, + anchorPath: secondContent.path, + focusPath: secondContent.path, + }); + await vi.waitFor(() => { + const sel = muya.editor.selection.getSelection(); + expect(sel!.anchorBlock).toBe(secondContent); + expect(sel!.anchor.offset).toBe(1); + }); + }); + + it('does not throw and leaves the document intact for an unresolvable path', () => { + const muya = bootMuya('hello\n'); + expect(() => muya.setCursor({ + anchor: { offset: 0 }, + focus: { offset: 0 }, + anchorPath: [99, 'text'], + focusPath: [99, 'text'], + })).not.toThrow(); + }); +}); diff --git a/packages/muya/src/__tests__/focusMode.spec.ts b/packages/muya/src/__tests__/focusMode.spec.ts new file mode 100644 index 0000000000..87fbb4764a --- /dev/null +++ b/packages/muya/src/__tests__/focusMode.spec.ts @@ -0,0 +1,87 @@ +// @vitest-environment happy-dom + +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { Muya } from '../muya'; + +// Coverage for focus mode. marktext muyajs declared a `focusMode` option and +// reserved the `mu-focus-mode` class name but never applied it — focus mode was +// a complete no-op. `Muya#setFocusMode` now toggles `mu-focus-mode` on the +// editor container (and the constructor applies it when `focusMode: true` is +// passed), with the dimming itself driven by CSS in blockSyntax.css. These +// tests lock in the class-toggling contract; the visual dimming is verified by +// the e2e suite. + +const FOCUS_MODE_CLASS = 'mu-focus-mode'; + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(options: Partial[1]> = {}): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown: '# heading\n\nparagraph A\n\nparagraph B\n', ...options } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +describe('muya focus mode', () => { + it('does not apply mu-focus-mode by default', () => { + const muya = bootMuya(); + expect(muya.options.focusMode).toBe(false); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(false); + }); + + it('applies mu-focus-mode at construction when focusMode: true', () => { + const muya = bootMuya({ focusMode: true }); + expect(muya.options.focusMode).toBe(true); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(true); + }); + + it('setFocusMode(false) removes the class and clears the option', () => { + const muya = bootMuya({ focusMode: true }); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(true); + + muya.setFocusMode(false); + expect(muya.options.focusMode).toBe(false); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(false); + }); + + it('setFocusMode(true) adds the class and sets the option', () => { + const muya = bootMuya(); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(false); + + muya.setFocusMode(true); + expect(muya.options.focusMode).toBe(true); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(true); + }); + + it('toggling is idempotent (re-adding keeps a single class)', () => { + const muya = bootMuya(); + muya.setFocusMode(true); + muya.setFocusMode(true); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(true); + + muya.setFocusMode(false); + muya.setFocusMode(false); + expect(muya.domNode.classList.contains(FOCUS_MODE_CLASS)).toBe(false); + }); +}); diff --git a/packages/muya/src/__tests__/formatClickEvents.spec.ts b/packages/muya/src/__tests__/formatClickEvents.spec.ts new file mode 100644 index 0000000000..a0ada0a551 --- /dev/null +++ b/packages/muya/src/__tests__/formatClickEvents.spec.ts @@ -0,0 +1,159 @@ +// @vitest-environment happy-dom + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { CLASS_NAMES } from '../config'; +import { Muya } from '../muya'; + +// Coverage for the desktop-migration interaction events ported from legacy +// `packages/muyajs` (`clickCtrl.js` link path + `clickEvent.js` image path): +// +// - `format-click` { event, formatType: 'link', data } on a Cmd/Ctrl-click +// of a rendered link. `data` is the `getLinkInfo` payload (superset of +// the legacy `{ text, href }`). +// - `format-click` { event, formatType: 'image', data: } on a +// Cmd/Ctrl-click of a rendered . +// +// The desktop renderer (`editor.vue`) re-checks the modifier and opens the +// link / image viewer; muya only emits, leaving the plain-click +// cursor-placement / image-toolbar behaviour untouched. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +function dispatchClick(target: Element, init: MouseEventInit = {}): void { + target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, ...init })); +} + +describe('format-click on links', () => { + it('emits format-click with the link payload on a Cmd/Ctrl-click', () => { + const muya = bootMuya('[hello](https://example.com)'); + const link = muya.domNode.querySelector(`span.${CLASS_NAMES.MU_LINK}`)!; + expect(link).toBeTruthy(); + + const handler = vi.fn(); + muya.on('format-click', handler); + + dispatchClick(link, { metaKey: true }); + + expect(handler).toHaveBeenCalledTimes(1); + const payload = handler.mock.calls[0][0]; + expect(payload.formatType).toBe('link'); + expect(payload.event).toBeInstanceOf(MouseEvent); + expect(payload.data.href).toBe('https://example.com'); + expect(payload.data.text).toBe('hello'); + }); + + it('also fires for a Ctrl-click (non-macOS modifier)', () => { + const muya = bootMuya('[hello](https://example.com)'); + const link = muya.domNode.querySelector(`span.${CLASS_NAMES.MU_LINK}`)!; + + const handler = vi.fn(); + muya.on('format-click', handler); + + dispatchClick(link, { ctrlKey: true }); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler.mock.calls[0][0].formatType).toBe('link'); + }); + + it('does NOT emit format-click on a plain (no-modifier) link click', () => { + const muya = bootMuya('[hello](https://example.com)'); + const link = muya.domNode.querySelector(`span.${CLASS_NAMES.MU_LINK}`)!; + + const handler = vi.fn(); + muya.on('format-click', handler); + + dispatchClick(link); + + expect(handler).not.toHaveBeenCalled(); + }); +}); + +describe('format-click on images', () => { + // Boot an image and inject a loaded into the rendered container. + // The async `loadImageAsync` path never resolves in happy-dom (no real + // network / Image decode), so we stand in the the renderer would + // have produced and drive the click through the real handler. + function bootImage(src: string): { muya: Muya; img: HTMLImageElement } { + const muya = bootMuya(`![alt](${src})`); + const wrapper = muya.domNode.querySelector( + `span.${CLASS_NAMES.MU_INLINE_IMAGE}`, + )!; + const container = wrapper.querySelector( + `.${CLASS_NAMES.MU_IMAGE_CONTAINER}`, + )!; + const img = document.createElement('img'); + img.setAttribute('src', src); + container.appendChild(img); + return { muya, img }; + } + + it('emits format-click with the image src on a Cmd/Ctrl-click', () => { + const src = 'https://example.com/x.png'; + const { muya, img } = bootImage(src); + + const handler = vi.fn(); + muya.on('format-click', handler); + + dispatchClick(img, { metaKey: true }); + + expect(handler).toHaveBeenCalledTimes(1); + const payload = handler.mock.calls[0][0]; + expect(payload.formatType).toBe('image'); + expect(payload.event).toBeInstanceOf(MouseEvent); + expect(payload.data).toBe(src); + }); + + it('emits image format-click on a Ctrl-click (non-macOS modifier)', () => { + const src = 'https://example.com/x.png'; + const { muya, img } = bootImage(src); + + const handler = vi.fn(); + muya.on('format-click', handler); + + dispatchClick(img, { ctrlKey: true }); + + expect(handler).toHaveBeenCalledTimes(1); + const payload = handler.mock.calls[0][0]; + expect(payload.formatType).toBe('image'); + expect(payload.data).toBe(src); + }); + + it('does NOT emit format-click on a plain (no-modifier) image click', () => { + const { muya, img } = bootImage('https://example.com/x.png'); + + const handler = vi.fn(); + muya.on('format-click', handler); + + dispatchClick(img); + + expect(handler).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/muya/src/__tests__/historySerialization.spec.ts b/packages/muya/src/__tests__/historySerialization.spec.ts new file mode 100644 index 0000000000..255c5f9041 --- /dev/null +++ b/packages/muya/src/__tests__/historySerialization.spec.ts @@ -0,0 +1,192 @@ +// @vitest-environment happy-dom + +import type Content from '../block/base/content'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Muya } from '../muya'; + +// Coverage for the undo-history serialization API added for the +// muyajs -> @muyajs/core desktop migration: getHistory / setHistory / +// clearHistory. The desktop shell persists each tab's undo/redo history +// across tab switches — it reads getHistory() before deactivating a tab and +// restores it via setHistory() when the tab is re-selected. +// +// Block-tree mutations dispatch json1 ops that flow through the History +// recorder on the next animation frame (see JSONState._emitStateChange and +// History._record), so assertions on getState()/getMarkdown() and on the +// recorded stacks are wrapped in vi.waitFor to await that flush. +// +// History._record coalesces ops recorded within `options.delay` (1s) of the +// previous one into a single undo entry, so tests call `cutoff()` between +// edits to force one undo entry per edit and keep stack-depth assertions +// deterministic. The live-DOM selection that History reads while applying an +// undo can point at a block removed by the re-render, so we re-seat the +// cursor on a known-attached block right before each undo()/redo(). + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +// Block-level ops resolve their target via the active content block's +// outMostBlock — the way the editor tracks the cursor after a click. Set it +// directly to simulate the cursor sitting in the first block, and seat the +// live DOM caret there so History reads a valid selection. +function placeCursorOnFirstBlock(muya: Muya): Content { + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + muya.editor.activeContentBlock = first; + first.setCursor(0, 0, true); + return first; +} + +// Undo entries coalesce within options.delay; cutoff() forces the next edit +// into its own undo entry so depth assertions are deterministic. +function undoDepth(muya: Muya): number { + // @ts-expect-error — reach into the private stack for test assertions. + return muya.editor.history._stack.undo.length; +} + +describe('muya history serialization api', () => { + it('getHistory() returns a JSON-serializable snapshot of the undo/redo stacks', async () => { + const muya = bootMuya('# Title\n'); + placeCursorOnFirstBlock(muya); + muya.insertParagraph('after', 'one'); + await vi.waitFor(() => { + expect(undoDepth(muya)).toBe(1); + }); + + const snapshot = muya.getHistory(); + // It must survive a JSON round-trip with no loss (no live block refs, + // functions, or DOM nodes leaking into the serialized form). + expect(() => JSON.stringify(snapshot)).not.toThrow(); + expect(JSON.parse(JSON.stringify(snapshot))).toEqual(snapshot); + + expect(snapshot.stack.undo).toHaveLength(1); + expect(snapshot.stack.redo).toHaveLength(0); + // The recorded selection is path-only — no live anchorBlock / focusBlock. + const recorded = snapshot.stack.undo[0]; + expect(Array.isArray(recorded.operation)).toBe(true); + if (recorded.selection) { + expect(recorded.selection).not.toHaveProperty('anchorBlock'); + expect(recorded.selection).not.toHaveProperty('focusBlock'); + expect(Array.isArray(recorded.selection.anchorPath)).toBe(true); + } + }); + + it('setHistory(getHistory()) then undo() reproduces the snapshot-point state', async () => { + const muya = bootMuya('# Title\n'); + + // Make two edits, capturing the markdown + a history snapshot after the + // SECOND one — this is the state the desktop persists when a tab is + // deactivated (document + undo stack both at the same point). + placeCursorOnFirstBlock(muya); + muya.insertParagraph('after', 'one'); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('one'); + expect(undoDepth(muya)).toBe(1); + }); + muya.editor.history.cutoff(); + placeCursorOnFirstBlock(muya); + muya.insertParagraph('after', 'two'); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('two'); + expect(undoDepth(muya)).toBe(2); + }); + + const snapshot = muya.getHistory(); + expect(snapshot.stack.undo).toHaveLength(2); + + // Simulate the tab round-trip: the in-memory history is dropped (as it + // is when another tab takes over the editor) while the document stays + // put, then the persisted snapshot is restored. + muya.clearHistory(); + expect(undoDepth(muya)).toBe(0); + muya.setHistory(snapshot); + expect(undoDepth(muya)).toBe(2); + + // Undoing twice against the restored stack must walk the document back + // through "two" then "one" to the original "# Title" — proving the + // restored ops reproduce the prior document states losslessly. + placeCursorOnFirstBlock(muya); + muya.undo(); + await vi.waitFor(() => { + const md = muya.getMarkdown(); + expect(md).toContain('one'); + expect(md).not.toContain('two'); + }); + placeCursorOnFirstBlock(muya); + muya.undo(); + await vi.waitFor(() => { + expect(muya.getMarkdown().trim()).toBe('# Title'); + }); + expect(muya.editor.history.canUndo()).toBe(false); + expect(muya.editor.history.canRedo()).toBe(true); + }); + + it('restored history round-trips through redo() back to the snapshot state', async () => { + const muya = bootMuya('# Title\n'); + placeCursorOnFirstBlock(muya); + muya.insertParagraph('after', 'alpha'); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('alpha'); + expect(undoDepth(muya)).toBe(1); + }); + const markdownAtSnapshot = muya.getMarkdown(); + // Persist exactly as the desktop shell would — through JSON. + const snapshot = JSON.parse(JSON.stringify(muya.getHistory())); + + muya.setHistory(snapshot); + placeCursorOnFirstBlock(muya); + muya.undo(); + await vi.waitFor(() => { + expect(muya.getMarkdown().trim()).toBe('# Title'); + }); + + // redo must restore the exact pre-undo document state, proving the + // serialized op is lossless in both directions. + placeCursorOnFirstBlock(muya); + muya.redo(); + await vi.waitFor(() => { + expect(muya.getMarkdown()).toBe(markdownAtSnapshot); + }); + }); + + it('clearHistory() empties both stacks', async () => { + const muya = bootMuya('# Title\n'); + placeCursorOnFirstBlock(muya); + muya.insertParagraph('after', 'one'); + await vi.waitFor(() => { + expect(undoDepth(muya)).toBe(1); + }); + + muya.clearHistory(); + expect(muya.editor.history.canUndo()).toBe(false); + expect(muya.editor.history.canRedo()).toBe(false); + expect(muya.getHistory().stack.undo).toHaveLength(0); + expect(muya.getHistory().stack.redo).toHaveLength(0); + }); +}); diff --git a/packages/muya/src/__tests__/invalidateImageCache.spec.ts b/packages/muya/src/__tests__/invalidateImageCache.spec.ts new file mode 100644 index 0000000000..a61c931fe8 --- /dev/null +++ b/packages/muya/src/__tests__/invalidateImageCache.spec.ts @@ -0,0 +1,117 @@ +// @vitest-environment happy-dom + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Muya } from '../muya'; + +// Coverage for `muya.invalidateImageCache()` — the public API the desktop +// shell calls to force inline images to reload (e.g. after a watched image +// file changes on disk, or on the `mt::invalidate-image-cache` IPC). +// +// The inline renderer memoises loaded images in two maps keyed by src: +// - `loadImageMap` (skipped on the next render once `isSuccess` is true) +// - `urlMap` (resolved/inflight URLs) +// `invalidateImageCache()` clears both and re-renders every content block so +// `loadImageAsync` runs afresh. happy-dom's `Image` never fires load/error +// for a `file://` src, so we seed the caches by hand rather than relying on a +// real load resolving. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +function renderer(muya: Muya) { + return muya.editor.inlineRenderer.renderer; +} + +describe('muya.invalidateImageCache()', () => { + it('exposes the method on the Muya instance', () => { + const muya = bootMuya('# hi'); + expect(typeof muya.invalidateImageCache).toBe('function'); + }); + + it('clears the loadImageMap and urlMap caches', () => { + const muya = bootMuya('![alt](/tmp/pic.png)'); + const { loadImageMap, urlMap } = renderer(muya); + + loadImageMap.set('file:///tmp/pic.png', { + id: 'img-1', + isSuccess: true, + url: 'file:///tmp/pic.png', + width: 320, + height: 240, + }); + urlMap.set('file:///tmp/pic.png', 'data:image/png;base64,AAAA'); + + expect(loadImageMap.size).toBe(1); + expect(urlMap.size).toBe(1); + + muya.invalidateImageCache(); + + expect(loadImageMap.size).toBe(0); + expect(urlMap.size).toBe(0); + }); + + it('re-renders content blocks so inline images load again', async () => { + const muya = bootMuya('![alt](/tmp/pic.png)'); + const inlineRenderer = muya.editor.inlineRenderer; + const { loadImageMap } = renderer(muya); + + loadImageMap.set('file:///tmp/pic.png', { + id: 'img-1', + isSuccess: true, + url: 'file:///tmp/pic.png', + }); + + const patchSpy = vi.spyOn(inlineRenderer, 'patch'); + + muya.invalidateImageCache(); + + // The image cache is flushed synchronously... + expect(loadImageMap.size).toBe(0); + // ...and the content block carrying the image is re-patched, which + // re-runs `loadImageAsync` for it. + await vi.waitFor(() => { + expect(patchSpy).toHaveBeenCalled(); + }); + + patchSpy.mockRestore(); + }); + + it('does not throw on a document with no images', () => { + const muya = bootMuya('just a paragraph, no images here'); + expect(() => muya.invalidateImageCache()).not.toThrow(); + expect(renderer(muya).loadImageMap.size).toBe(0); + expect(renderer(muya).urlMap.size).toBe(0); + }); + + it('does not throw on an empty document', () => { + const muya = bootMuya(''); + expect(() => muya.invalidateImageCache()).not.toThrow(); + }); +}); diff --git a/packages/muya/src/__tests__/replaceCurrentWord.spec.ts b/packages/muya/src/__tests__/replaceCurrentWord.spec.ts new file mode 100644 index 0000000000..6f788d0aaa --- /dev/null +++ b/packages/muya/src/__tests__/replaceCurrentWord.spec.ts @@ -0,0 +1,125 @@ +// @vitest-environment happy-dom + +import type Content from '../block/base/content'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Muya } from '../muya'; + +// Coverage for muya.replaceCurrentWordInlineUnsafe — the spellcheck word +// replacement API added for the muyajs -> @muyajs/core desktop migration. +// Legacy muyajs exposed `_replaceCurrentWordInlineUnsafe(word, replacement)`; +// the desktop spell checker calls it when the user picks a suggestion from the +// misspelled-word context menu (Chromium has already selected the whole word). +// +// The method finds the word at the cursor, asserts it matches `word`, replaces +// it inline through the text setter (which dispatches a json edit op), and +// places the cursor after the replacement. State flushes on rAF, so markdown +// assertions wait via vi.waitFor. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +// Place the cursor inside the first content block at `offset` and mark it as +// the active block — mirrors the editor state after a click lands the caret. +function placeCursorAt(muya: Muya, offset: number): Content { + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + muya.editor.activeContentBlock = first; + first.setCursor(offset, offset, true); + return first; +} + +describe('muya.replaceCurrentWordInlineUnsafe()', () => { + it('replaces the misspelled word at the cursor and updates markdown', async () => { + const muya = bootMuya('teh quick brown fox\n'); + // Cursor sits inside `teh`. + placeCursorAt(muya, 1); + + const ok = muya.replaceCurrentWordInlineUnsafe('teh', 'the'); + expect(ok).toBe(true); + + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('the quick brown fox'); + }); + expect(muya.getMarkdown()).not.toContain('teh'); + }); + + it('replaces a word in the middle of the line', async () => { + const muya = bootMuya('the quikc brown fox\n'); + // Cursor inside `quikc` (offset of the `i`). + placeCursorAt(muya, 'the qu'.length); + + const ok = muya.replaceCurrentWordInlineUnsafe('quikc', 'quick'); + expect(ok).toBe(true); + + await vi.waitFor(() => { + expect(muya.getMarkdown()).toContain('the quick brown fox'); + }); + }); + + it('places the cursor after the replacement', () => { + const muya = bootMuya('teh end\n'); + const block = placeCursorAt(muya, 0); + + muya.replaceCurrentWordInlineUnsafe('teh', 'the'); + + const cursor = block.getCursor(); + expect(cursor).not.toBeNull(); + // `the` is 3 chars, so the caret should sit at offset 3. + expect(cursor!.start.offset).toBe(3); + expect(cursor!.end.offset).toBe(3); + }); + + it('is a no-op when the word at the cursor does not match (Chromium mismatch)', async () => { + const muya = bootMuya('teh quick\n'); + placeCursorAt(muya, 1); + + const ok = muya.replaceCurrentWordInlineUnsafe('different', 'the'); + expect(ok).toBe(false); + + await new Promise(resolve => requestAnimationFrame(() => resolve())); + expect(muya.getMarkdown()).toContain('teh quick'); + }); + + it('returns false when there is no active content block', () => { + const muya = bootMuya('teh quick\n'); + muya.editor.activeContentBlock = null; + + expect(muya.replaceCurrentWordInlineUnsafe('teh', 'the')).toBe(false); + }); + + it('returns false when there is no cursor in the active block', () => { + const muya = bootMuya('teh quick\n'); + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + muya.editor.activeContentBlock = first; + // No selection set — getCursor() returns null. + document.getSelection()?.removeAllRanges(); + + expect(muya.replaceCurrentWordInlineUnsafe('teh', 'the')).toBe(false); + }); +}); diff --git a/packages/muya/src/__tests__/setOptions.spec.ts b/packages/muya/src/__tests__/setOptions.spec.ts new file mode 100644 index 0000000000..eac11b5929 --- /dev/null +++ b/packages/muya/src/__tests__/setOptions.spec.ts @@ -0,0 +1,102 @@ +// @vitest-environment happy-dom + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Muya } from '../muya'; + +// Coverage for the runtime option API added for the muyajs -> @muyajs/core +// migration: setOptions / setFont / setTabSize / setListIndentation. Every +// desktop Preferences toggle depends on options updating live. setOptions with +// forceRender re-renders from current state (so render-affecting options take +// effect) WITHOUT clearing undo history, and preserves the document content. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +describe('muya runtime options', () => { + it('setOptions merges into muya.options', () => { + const muya = bootMuya('hello\n'); + muya.setOptions({ footnote: true, superSubScript: false }); + expect(muya.options.footnote).toBe(true); + expect(muya.options.superSubScript).toBe(false); + }); + + it('setOptions with forceRender preserves the document content', () => { + const muya = bootMuya('# Heading\n\nsome text\n'); + const before = muya.getMarkdown(); + muya.setOptions({ footnote: true }, true); + // A forced re-render rebuilds the block tree from current state, so the + // serialized document is unchanged. + expect(muya.getMarkdown()).toBe(before); + }); + + it('setOptions with forceRender does not clear the undo history', async () => { + const muya = bootMuya('one\n'); + muya.editor.activeContentBlock = muya.editor.scrollPage!.firstContentInDescendant()!; + // Make an edit so the undo stack is non-empty. + muya.insertParagraph(); + await vi.waitFor(() => { + expect(muya.getState().length).toBe(2); + // the edit was recorded onto the undo stack + expect(muya.editor.history.canUndo()).toBe(true); + }); + + // A forced re-render rebuilds the tree via ScrollPage.updateState, which + // uses the 'api' source (no json-change dispatch), so it neither clears + // the history (unlike setContent) nor pollutes it with re-render ops. + muya.setOptions({ footnote: true }, true); + + expect(muya.editor.history.canUndo()).toBe(true); + }); + + it('setOptions reflects spellcheckEnabled on the container', () => { + const muya = bootMuya('x\n'); + muya.setOptions({ spellcheckEnabled: true }); + expect(muya.domNode.getAttribute('spellcheck')).toBe('true'); + muya.setOptions({ spellcheckEnabled: false }); + expect(muya.domNode.getAttribute('spellcheck')).toBe('false'); + }); + + it('setFont and setTabSize update options', () => { + const muya = bootMuya('x\n'); + muya.setFont({ fontSize: 18, lineHeight: 1.8 }); + expect(muya.options.fontSize).toBe(18); + expect(muya.options.lineHeight).toBe(1.8); + muya.setTabSize(2); + expect(muya.options.tabSize).toBe(2); + }); + + it('setListIndentation updates options and preserves content', () => { + const muya = bootMuya('- a\n- b\n'); + const before = muya.getMarkdown(); + muya.setListIndentation(2); + expect(muya.options.listIndentation).toBe(2); + expect(muya.getMarkdown()).toBe(before); + }); +}); diff --git a/packages/muya/src/__tests__/updateParagraph.spec.ts b/packages/muya/src/__tests__/updateParagraph.spec.ts new file mode 100644 index 0000000000..c1faa6cd4a --- /dev/null +++ b/packages/muya/src/__tests__/updateParagraph.spec.ts @@ -0,0 +1,183 @@ +// @vitest-environment happy-dom + +import type Content from '../block/base/content'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { Muya } from '../muya'; + +// Coverage for muya.updateParagraph — the block-type conversions the desktop +// Paragraph menu drives (added for the muyajs -> @muyajs/core migration). It +// accepts the marktext/muyajs label vocabulary and maps onto muya's +// replaceBlockByLabel + list/heading handling. State flushes on rAF, so +// assertions wait via vi.waitFor. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +function placeCursorOnFirstBlock(muya: Muya): Content { + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + muya.editor.activeContentBlock = first; + return first; +} + +// eslint-disable-next-line ts/no-explicit-any +function firstBlock(muya: Muya): any { + return muya.getState()[0]; +} + +describe('muya.updateParagraph()', () => { + it('turns a paragraph into a heading (text preserved)', async () => { + const muya = bootMuya('hello world\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('heading 1'); + await vi.waitFor(() => { + const b = firstBlock(muya); + expect(b.name).toBe('atx-heading'); + expect(b.meta.level).toBe(1); + }); + expect(muya.getMarkdown()).toContain('hello world'); + }); + + it('reset-to-paragraph turns a heading back into a paragraph', async () => { + const muya = bootMuya('## a heading\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('reset-to-paragraph'); + await vi.waitFor(() => { + expect(firstBlock(muya).name).toBe('paragraph'); + }); + }); + + it('upgrade heading cycles paragraph -> h6 and h2 -> h1', async () => { + const muya = bootMuya('plain\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('upgrade heading'); + await vi.waitFor(() => { + expect(firstBlock(muya).name).toBe('atx-heading'); + expect(firstBlock(muya).meta.level).toBe(6); + }); + + const muya2 = bootMuya('## two\n'); + placeCursorOnFirstBlock(muya2); + muya2.updateParagraph('upgrade heading'); + await vi.waitFor(() => { + expect(firstBlock(muya2).meta.level).toBe(1); + }); + }); + + it('degrade heading lowers h1 -> h2', async () => { + const muya = bootMuya('# one\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('degrade heading'); + await vi.waitFor(() => { + expect(firstBlock(muya).meta.level).toBe(2); + }); + }); + + it('turns a paragraph into a blockquote', async () => { + const muya = bootMuya('quote me\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('blockquote'); + await vi.waitFor(() => { + expect(firstBlock(muya).name).toBe('block-quote'); + }); + }); + + it('turns a paragraph into a bullet list', async () => { + const muya = bootMuya('item\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('ul-bullet'); + await vi.waitFor(() => { + expect(firstBlock(muya).name).toBe('bullet-list'); + }); + }); + + it('converts a bullet list to an ordered list, preserving items', async () => { + const muya = bootMuya('- one\n- two\n'); + placeCursorOnFirstBlock(muya); + expect(firstBlock(muya).name).toBe('bullet-list'); + muya.updateParagraph('ol-order'); + await vi.waitFor(() => { + const b = firstBlock(muya); + expect(b.name).toBe('order-list'); + expect(b.children.length).toBe(2); + }); + }); + + it('toggles loose/tight on the current list', async () => { + const muya = bootMuya('- a\n- b\n'); + placeCursorOnFirstBlock(muya); + const before = firstBlock(muya).meta.loose; + muya.updateParagraph('loose-list-item'); + await vi.waitFor(() => { + expect(firstBlock(muya).meta.loose).toBe(!before); + }); + }); + + it('maps the command-palette ol-bullet label to an ordered list', async () => { + const muya = bootMuya('item\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('ol-bullet'); + await vi.waitFor(() => { + expect(firstBlock(muya).name).toBe('order-list'); + }); + }); + + it('reset-to-paragraph unwraps a list into paragraphs, preserving items', async () => { + const muya = bootMuya('- one\n- two\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('reset-to-paragraph'); + await vi.waitFor(() => { + const state = muya.getState(); + expect(state.length).toBe(2); + expect(state.every(b => b.name === 'paragraph')).toBe(true); + }); + expect(muya.getMarkdown()).toContain('one'); + expect(muya.getMarkdown()).toContain('two'); + }); + + it('selecting the active list type toggles the list off, preserving items', async () => { + const muya = bootMuya('- a\n- b\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('ul-bullet'); + await vi.waitFor(() => { + const state = muya.getState(); + expect(state.length).toBe(2); + expect(state.every(b => b.name === 'paragraph')).toBe(true); + }); + }); + + it('does not convert a non-empty paragraph to hr (content guard)', async () => { + const muya = bootMuya('keep me\n'); + placeCursorOnFirstBlock(muya); + muya.updateParagraph('hr'); + await new Promise(resolve => requestAnimationFrame(() => resolve())); + expect(firstBlock(muya).name).toBe('paragraph'); + expect(muya.getMarkdown()).toContain('keep me'); + }); +}); diff --git a/packages/muya/src/assets/icons/sequence/1.png b/packages/muya/src/assets/icons/sequence/1.png new file mode 100755 index 0000000000..4ebfd40122 Binary files /dev/null and b/packages/muya/src/assets/icons/sequence/1.png differ diff --git a/packages/muya/src/assets/icons/sequence/2.png b/packages/muya/src/assets/icons/sequence/2.png new file mode 100755 index 0000000000..96cf5148c3 Binary files /dev/null and b/packages/muya/src/assets/icons/sequence/2.png differ diff --git a/packages/muya/src/assets/icons/sequence/3.png b/packages/muya/src/assets/icons/sequence/3.png new file mode 100755 index 0000000000..2cbaeba3f0 Binary files /dev/null and b/packages/muya/src/assets/icons/sequence/3.png differ diff --git a/packages/muya/src/assets/styles/blockSyntax.css b/packages/muya/src/assets/styles/blockSyntax.css index a3915a194c..f2ec99e03b 100644 --- a/packages/muya/src/assets/styles/blockSyntax.css +++ b/packages/muya/src/assets/styles/blockSyntax.css @@ -4,7 +4,7 @@ .mu-container { box-sizing: border-box; - max-width: 800px; + max-width: var(--editor-area-width, 800px); min-height: 100%; margin: 0 auto; padding: 0 50px 100px; @@ -43,26 +43,32 @@ } .mu-container h1 { + color: var(--h1-color, var(--editor-color-80)); font-size: 1.875em; } .mu-container h2 { + color: var(--h2-color, var(--editor-color-80)); font-size: 1.5em; } .mu-container h3 { + color: var(--h3-color, var(--editor-color-80)); font-size: 1.375em; } .mu-container h4 { + color: var(--h4-color, var(--editor-color-80)); font-size: 1.25em; } .mu-container h5 { + color: var(--h5-color, var(--editor-color-80)); font-size: 1.125em; } .mu-container h6 { + color: var(--h6-color, var(--editor-color-80)); font-size: 1em; } @@ -74,7 +80,7 @@ margin-left: 0; padding: 0 1em; - color: var(--editor-color-50); + color: var(--blockquote-text-color, var(--editor-color-50)); font-size: 1em; } @@ -88,7 +94,7 @@ width: 3px; height: calc(100% - 1em); - background: var(--editor-color-30); + background: var(--blockquote-border-color, var(--editor-color-30)); content: ''; } @@ -111,7 +117,7 @@ width: 100%; height: 1px; - background: var(--editor-color-10); + background: var(--hr-color, var(--editor-color-10)); content: ''; } @@ -311,6 +317,13 @@ ul.mu-bullet-list { list-style: disc outside none; } +/* list markers — default to `inherit` (current text color) so the bundled + light theme is unchanged; themes can recolor the bullets / numbers. */ +ol.mu-order-list > li::marker, +ul.mu-bullet-list > li::marker { + color: var(--list-marker-color, inherit); +} + ul.mu-bullet-list:first-child, ol.mu-order-list:first-child { margin-top: 0; @@ -580,6 +593,33 @@ figure.mu-math-block div.mu-math-preview > svg { width: 100%; } +/* Diagram SVG recoloring — port of marktext's legacy theme block + (packages/muyajs/themes/default.css `figure[data-role] svg …`, ~lines + 689-720) adapted to muya's DOM and kebab-cased vars. Mermaid and + vega-lite render an inline inside `.mu-diagram-preview`; these + rules rewrite the renderer's default black-on-white fills/strokes to the + editor's theme colors so diagrams follow the active theme (including + dark themes that override `--editor-bg-color` / `--editor-color`). + Additive and entirely theme-driven — on the bundled light theme + `--editor-bg-color` is white and `--editor-color` is the editor text + color, matching the diagrams' original look. */ +figure.mu-diagram-block .mu-diagram-preview svg rect[fill='#ffffff'], +figure.mu-diagram-block .mu-diagram-preview svg path[fill='#ffffff'] { + fill: var(--editor-bg-color); +} + +figure.mu-diagram-block .mu-diagram-preview svg text[fill='#000000'], +figure.mu-diagram-block .mu-diagram-preview svg path[fill='#000000'], +figure.mu-diagram-block .mu-diagram-preview svg use[fill='black'], +figure.mu-diagram-block .mu-diagram-preview svg use[fill='#000000'] { + fill: var(--editor-color); +} + +figure.mu-diagram-block .mu-diagram-preview svg rect[stroke='#000000'], +figure.mu-diagram-block .mu-diagram-preview svg path[stroke='#000000'] { + stroke: var(--editor-color); +} + figure.mu-active.mu-math-block > div.mu-math-preview, figure.mu-active.mu-diagram-block > div.mu-diagram-preview { position: absolute; @@ -813,3 +853,18 @@ pre.mu-active.mu-fenced-code::after { .mu-container figure.mu-footnote > i.mu-footnote-backlink:hover { color: var(--theme-color); } + +/* Focus mode (mirrors marktext muyajs `.ag-focus-mode`): when the editor + container carries `mu-focus-mode`, dim every top-level block and restore full + opacity on the active one. `mu-active` is applied to the whole ancestor chain + of the focused content block, so the top-level block containing the cursor + keeps `opacity: 1`. The class is toggled by `Muya#setFocusMode`. */ +.mu-focus-mode .mu-container > * { + opacity: 0.25; + + transition: opacity 0.2s ease-in-out; +} + +.mu-focus-mode .mu-container > .mu-active { + opacity: 1; +} diff --git a/packages/muya/src/assets/styles/exportStyle.css b/packages/muya/src/assets/styles/exportStyle.css index a68df17aad..e1c3907c3c 100644 --- a/packages/muya/src/assets/styles/exportStyle.css +++ b/packages/muya/src/assets/styles/exportStyle.css @@ -60,7 +60,9 @@ .markdown-body div.plantuml, .markdown-body div.mermaid, -.markdown-body div.vega-lite { +.markdown-body div.vega-lite, +.markdown-body div.flowchart, +.markdown-body div.sequence { display: block; padding: 20px 0; diff --git a/packages/muya/src/assets/styles/index.css b/packages/muya/src/assets/styles/index.css index 9f52537046..643b134299 100644 --- a/packages/muya/src/assets/styles/index.css +++ b/packages/muya/src/assets/styles/index.css @@ -10,6 +10,9 @@ --editor-color-10: rgb(0 0 0 / 10%); --editor-color-04: rgb(0 0 0 / 3%); --editor-bg-color: rgb(255 255 255 / 100%); + + /* width of the centered editing column (`.mu-container` max-width) */ + --editor-area-width: 800px; --delete-color: #ff6969; --icon-color: #6b737b; --code-block-bg-color: rgb(0 0 0 / 3%); @@ -20,10 +23,35 @@ --button-border: 1px solid #dcdfe6; --button-bg-color-hover: linear-gradient(#f9f9f9, #f2f2f2); --button-border-hover: var(--button-border); + + /* active / focus button variants — default to the resting button look so + the bundled light theme is unchanged; desktop themes can override. */ + --button-bg-color-active: var(--button-bg-color); + --button-border-active: var(--button-border); + --button-border-focus: var(--button-border); --float-bg-color: #fff; --float-hover-color: rgb(0 0 0 / 4%); --float-border-color: rgb(0 0 0 / 10%); --float-shadow: rgb(15 15 15 / 3%) 0 0 0 1px, rgb(15 15 15 / 4%) 0 3px 6px, rgb(15 15 15 / 5%) 0 9px 24px; + + /* markdown element colors — defaults mirror the current hardcoded / + opacity-tier values so the bundled light theme renders identically. + Consumers override them per theme. The desktop themes currently use + camelCase names (e.g. --linkColor, --h1Color); they will be migrated + to these kebab-case names as part of the muyajs -> @muyajs/core move. */ + --link-color: rgb(20 86 240); + --h1-color: var(--editor-color-80); + --h2-color: var(--editor-color-80); + --h3-color: var(--editor-color-80); + --h4-color: var(--editor-color-80); + --h5-color: var(--editor-color-80); + --h6-color: var(--editor-color-80); + --blockquote-text-color: var(--editor-color-50); + --blockquote-border-color: var(--editor-color-30); + --strong-color: inherit; + --em-color: inherit; + --list-marker-color: inherit; + --hr-color: var(--editor-color-10); } /* diff --git a/packages/muya/src/assets/styles/inlineSyntax.css b/packages/muya/src/assets/styles/inlineSyntax.css index f38c71fbe9..443a5417ce 100644 --- a/packages/muya/src/assets/styles/inlineSyntax.css +++ b/packages/muya/src/assets/styles/inlineSyntax.css @@ -17,7 +17,7 @@ /* link and auto-link and reference link */ a.mu-inline-rule, span.mu-inline-rule.mu-link { - color: rgb(20 86 240); + color: var(--link-color, rgb(20 86 240)); } /* Note: previously this rule also set `pointer-events: none` to suppress @@ -31,6 +31,16 @@ span.mu-inline-rule.mu-link { color: var(--editor-color-50); } +/* strong and emphasis — default to `inherit` (the surrounding editor color) + so the bundled light theme is unchanged; themes can recolor them. */ +strong.mu-inline-rule { + color: var(--strong-color, inherit); +} + +em.mu-inline-rule { + color: var(--em-color, inherit); +} + /* inline code */ code.mu-inline-rule { margin: 0; diff --git a/packages/muya/src/block/base/content.ts b/packages/muya/src/block/base/content.ts index 311327ed5a..8368902005 100644 --- a/packages/muya/src/block/base/content.ts +++ b/packages/muya/src/block/base/content.ts @@ -20,6 +20,82 @@ import { // const debug = logger('block.content:') +// Word boundary regexes ported from legacy muyajs +// (lib/marktext/spellchecker.js), which in turn derive from VSCode's wordHelper. +// Used by `extractWord` to find the word at the cursor for spell-check +// replacement. +const WORD_SEPARATORS = /[`~!@#$%^&*()\-=+[{\]}\\|;:'",.<>/?\s]/g; +const WORD_DEFINITION = /-?\d*\.\d\w*|[^`~!@#$%^&*()\-=+[{\]}\\|;:'",.<>/?\s]+/g; + +/** + * Extract the word at the given offset from the text. + * + * Ported from legacy muyajs `extractWord` (lib/marktext/spellchecker.js). + * + * @param text The line text. + * @param offset Normalized cursor offset (e.g. `ab|c def` -> 2). + * @returns The matched word with its `left`/`right` offsets, or null when the + * cursor is not inside a word. + */ +function extractWord( + text: string, + offset: number, +): { left: number; right: number; word: string } | null { + if (!text || text.length === 0) { + return null; + } + else if (offset < 0) { + offset = 0; + } + else if (offset >= text.length) { + offset = text.length - 1; + } + + // Matches all words starting at a good position. + WORD_DEFINITION.lastIndex = text.lastIndexOf(' ', offset - 1) + 1; + let match: RegExpExecArray | null = null; + let left = -1; + // eslint-disable-next-line no-cond-assign + while ((match = WORD_DEFINITION.exec(text))) { + if (match && match.index <= offset) { + if (WORD_DEFINITION.lastIndex > offset) + left = match.index; + } + else { + break; + } + } + WORD_DEFINITION.lastIndex = 0; + + // Cursor is between two word separators (e.g. `*|*` or ` |*`). + if (left <= -1) + return null; + + // Find word ending. + WORD_SEPARATORS.lastIndex = offset; + match = WORD_SEPARATORS.exec(text); + let right = -1; + if (match) + right = match.index; + + WORD_SEPARATORS.lastIndex = 0; + + // The last word in the string is a special case. + if (right < 0) { + return { + left, + right: text.length, + word: text.slice(left), + }; + } + + return { + left, + right, + word: text.slice(left, right), + }; +} + class Content extends TreeNode { public _text: string; public isComposed: boolean; @@ -432,6 +508,48 @@ class Content extends TreeNode { } } + /** + * Replace the word at/around the current cursor with `replacement`. + * + * Ported from legacy muyajs `ContentState._replaceCurrentWordInlineUnsafe` + * (lib/contentState/marktext.js). Used by the desktop spell checker: right + * clicking a misspelled word selects the whole word via Chromium, and + * choosing a suggestion replaces it inline. `extractWord` mirrors the + * VSCode-derived word boundaries muyajs relied on. + * + * Unsafe: the caller asserts that exactly the word `word` is selected. If + * the word found at the cursor does not match `word` the call is a no-op + * (returns false) — this guards against a Chromium selection mismatch. + * + * @param word The expected word at the cursor; the whole word must be selected. + * @param replacement The replacement text. + * @returns True when the replacement was applied. + */ + replaceCurrentWordInlineUnsafe(word: string, replacement: string): boolean { + const cursor = this.getCursor(); + if (cursor == null) + return false; + + const { text } = this; + // Use the start offset of the (possibly whole-word) selection as the + // probe point, matching the legacy `start.offset` behaviour. + const wordInfo = extractWord(text, cursor.start.offset); + if (wordInfo == null) + return false; + + const { left, right, word: selectedWord } = wordInfo; + if (selectedWord !== word) + return false; + + // Reuse the text setter so the change dispatches a json edit op. + this.text = text.substring(0, left) + replacement + text.substring(right); + + const offset = left + replacement.length; + this.setCursor(offset, offset, true); + + return true; + } + keydownHandler = (event: Event) => { if (!isKeyboardEvent(event)) return; diff --git a/packages/muya/src/block/extra/diagram/diagramPreview.ts b/packages/muya/src/block/extra/diagram/diagramPreview.ts index b548c213ae..5476caef9c 100644 --- a/packages/muya/src/block/extra/diagram/diagramPreview.ts +++ b/packages/muya/src/block/extra/diagram/diagramPreview.ts @@ -15,6 +15,7 @@ interface IRenderOptions { target: HTMLElement; vegaTheme: string; mermaidTheme: string; + sequenceTheme: 'hand' | 'simple'; } async function renderDiagram({ @@ -23,6 +24,7 @@ async function renderDiagram({ target, vegaTheme, mermaidTheme, + sequenceTheme, }: IRenderOptions) { const render = await loadRenderer(type); const options = {}; @@ -34,6 +36,9 @@ async function renderDiagram({ theme: vegaTheme, }); } + else if (type === 'sequence') { + Object.assign(options, { theme: sequenceTheme }); + } if (type === 'plantuml') { const diagram = render.parse(code); @@ -43,6 +48,11 @@ async function renderDiagram({ else if (type === 'vega-lite') { await render(target, JSON.parse(code), options); } + else if (type === 'flowchart' || type === 'sequence') { + const diagram = render.parse(code); + target.innerHTML = ''; + diagram.drawSVG(target, options); + } else if (type === 'mermaid') { render.initialize({ startOnLoad: false, @@ -117,7 +127,7 @@ class DiagramPreview extends Parent { if (code) { this.domNode!.innerHTML = i18n.t('Loading...'); - const { mermaidTheme, vegaTheme } = this.muya.options; + const { mermaidTheme, vegaTheme, sequenceTheme } = this.muya.options; const { type } = this; try { @@ -127,6 +137,7 @@ class DiagramPreview extends Parent { type, mermaidTheme, vegaTheme, + sequenceTheme, }); } catch { diff --git a/packages/muya/src/clipboard/__tests__/clipboardFilePath.spec.ts b/packages/muya/src/clipboard/__tests__/clipboardFilePath.spec.ts new file mode 100644 index 0000000000..8791e99a19 --- /dev/null +++ b/packages/muya/src/clipboard/__tests__/clipboardFilePath.spec.ts @@ -0,0 +1,186 @@ +import type Content from '../../block/base/content'; +import type { Muya } from '../../muya'; +import { describe, expect, it, vi } from 'vitest'; + +// The clipboard module pulls in CodeBlockContent → utils/prism which touches +// `window` at import time. Stub the prism shim so the test can run under Node +// (same stub as copyHandler.spec / getClipboardData.spec). +vi.mock('../../utils/prism/index', () => ({ + default: {}, + walkTokens: () => null, + loadedLanguages: new Set(), + transformAliasToOrigin: (s: string) => s, + loadLanguage: () => null, + search: () => [], +})); + +// Keep the real `resolveClipboardImagePath` (the decision under test) but neuter +// `normalizePastedHTML`, whose DOMPurify call needs a DOM the default `node` +// test environment doesn't provide. Stubbing it lets the fall-through paste +// path run far enough to prove the image hook short-circuited (or didn't). +vi.mock('../../utils/paste', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + normalizePastedHTML: async (html: string) => html, + }; +}); + +const Clipboard = (await import('../index')).default; + +// Ported behaviour from the legacy `@muyajs` `clipboardFilePath` paste hook: +// when the OS clipboard holds a file (e.g. an image copied from a file +// manager), the embedder resolves it to a local path and muya inserts that +// path as an inline image at the cursor instead of running the normal +// text/HTML paste. Returning '' (or omitting the hook) falls through to the +// default paste. + +// A minimal stand-in for the anchor content block. The clipboardFilePath path +// only touches `text`, `getCursor()` and `setCursor()`; the extra `blockName` +// and `getAnchor()` members keep the fall-through text-paste path from +// crashing in the tests that assert the hook did NOT short-circuit. +function makeAnchorBlock(initialText = '', cursor = 0) { + const block = { + text: initialText, + blockName: 'paragraph.content', + getCursor: () => ({ + start: { offset: cursor }, + end: { offset: cursor }, + }), + setCursor: vi.fn(), + getAnchor: () => null, + }; + return block as unknown as Content & { setCursor: ReturnType }; +} + +function makeClipboard( + options: Record, + anchorBlock: Content, +) { + const clipboard = new Clipboard({ options } as unknown as Muya); + Object.defineProperty(clipboard, 'selection', { + get: () => ({ + getSelection: () => ({ + isSelectionInSameBlock: true, + anchorBlock, + }), + }), + }); + return clipboard; +} + +// A clipboard event whose getData returns '' by default. Pass a map keyed by +// MIME type (e.g. { 'text/plain': 'hi' }) to simulate a clipboard that holds +// real text/HTML, proving the synchronous snapshot survives the async hook. +function makePasteEvent(data: Record = {}) { + const getData = vi.fn((type: string) => data[type] ?? ''); + return { + event: { + preventDefault: vi.fn(), + stopPropagation: vi.fn(), + clipboardData: { getData }, + } as unknown as ClipboardEvent, + getData, + }; +} + +describe('clipboard.pasteHandler — clipboardFilePath hook', () => { + it('invokes the hook and inserts the resolved path as an inline image', async () => { + const clipboardFilePath = vi.fn().mockResolvedValue('/tmp/shot.png'); + const anchorBlock = makeAnchorBlock('', 0); + const clipboard = makeClipboard({ clipboardFilePath }, anchorBlock); + const { event, getData } = makePasteEvent(); + + await clipboard.pasteHandler(event); + + expect(clipboardFilePath).toHaveBeenCalledOnce(); + expect(anchorBlock.text).toBe('![](/tmp/shot.png)'); + // Cursor lands right after the inserted image markdown. + expect(anchorBlock.setCursor).toHaveBeenCalledWith(18, 18, true); + // text/html is snapshotted synchronously up front (before the async + // hook detaches the clipboard), but the resolved image still + // short-circuits the normal text/HTML paste so nothing else inserts. + expect(getData).toHaveBeenCalled(); + }); + + it('splices the image into existing text at the cursor offset', async () => { + const clipboardFilePath = vi.fn().mockResolvedValue('/tmp/a.png'); + // Cursor between "ab" and "cd". + const anchorBlock = makeAnchorBlock('abcd', 2); + const clipboard = makeClipboard({ clipboardFilePath }, anchorBlock); + const { event } = makePasteEvent(); + + await clipboard.pasteHandler(event); + + expect(anchorBlock.text).toBe('ab![](/tmp/a.png)cd'); + }); + + it('escapes spaces and # in the resolved path', async () => { + const clipboardFilePath = vi + .fn() + .mockResolvedValue('/tmp/my shot#1.png'); + const anchorBlock = makeAnchorBlock('', 0); + const clipboard = makeClipboard({ clipboardFilePath }, anchorBlock); + const { event } = makePasteEvent(); + + await clipboard.pasteHandler(event); + + expect(anchorBlock.text).toBe('![](/tmp/my%20shot%231.png)'); + }); + + it('falls through to the normal paste when the hook returns ""', async () => { + const clipboardFilePath = vi.fn().mockResolvedValue(''); + const anchorBlock = makeAnchorBlock('', 0); + const clipboard = makeClipboard({ clipboardFilePath }, anchorBlock); + const { event, getData } = makePasteEvent(); + + await clipboard.pasteHandler(event); + + expect(clipboardFilePath).toHaveBeenCalledOnce(); + // No image inserted; the text/HTML branch was reached (getData read). + expect(anchorBlock.text).toBe(''); + expect(getData).toHaveBeenCalled(); + }); + + it('pastes the snapshotted text/plain when the hook is present but returns ""', async () => { + // Regression: the hook is configured, so `pasteHandler` awaits it. The + // snapshot of `event.clipboardData` must be taken synchronously BEFORE + // that await — otherwise the detached DataTransfer would yield '' here + // and the paste would silently insert nothing. + const clipboardFilePath = vi.fn().mockResolvedValue(''); + const anchorBlock = makeAnchorBlock('', 0); + const clipboard = makeClipboard({ clipboardFilePath }, anchorBlock); + const { event, getData } = makePasteEvent({ 'text/plain': 'hello world' }); + + await clipboard.pasteHandler(event); + + expect(clipboardFilePath).toHaveBeenCalledOnce(); + expect(getData).toHaveBeenCalledWith('text/plain'); + // The captured text survived the async hook and was pasted in. + expect(anchorBlock.text).toBe('hello world'); + expect(anchorBlock.setCursor).toHaveBeenCalledWith(11, 11, true); + }); + + it('falls through to the normal paste when the resolved path is not an image', async () => { + const clipboardFilePath = vi.fn().mockResolvedValue('/tmp/notes.txt'); + const anchorBlock = makeAnchorBlock('', 0); + const clipboard = makeClipboard({ clipboardFilePath }, anchorBlock); + const { event, getData } = makePasteEvent(); + + await clipboard.pasteHandler(event); + + expect(anchorBlock.text).toBe(''); + expect(getData).toHaveBeenCalled(); + }); + + it('does nothing special when the hook is absent', async () => { + const anchorBlock = makeAnchorBlock('', 0); + const clipboard = makeClipboard({}, anchorBlock); + const { event, getData } = makePasteEvent(); + + await clipboard.pasteHandler(event); + + expect(anchorBlock.text).toBe(''); + expect(getData).toHaveBeenCalled(); + }); +}); diff --git a/packages/muya/src/clipboard/index.ts b/packages/muya/src/clipboard/index.ts index 99353807e4..5983b16a8e 100644 --- a/packages/muya/src/clipboard/index.ts +++ b/packages/muya/src/clipboard/index.ts @@ -15,7 +15,7 @@ import StateToMarkdown from '../state/stateToMarkdown'; import { isAnyListState, isParagraphState } from '../state/types'; import { deepClone, isClipboardEvent, isKeyboardEvent } from '../utils'; import { getClipBoardHtml } from '../utils/marked'; -import { getCopyTextType, isStandaloneTableHtml, normalizePastedHTML } from '../utils/paste'; +import { getCopyTextType, isStandaloneTableHtml, normalizePastedHTML, resolveClipboardImagePath } from '../utils/paste'; import { mergePasteIntoHeading } from './mergePasteIntoHeading'; class Clipboard { @@ -585,7 +585,19 @@ class Clipboard { } // eslint-disable-next-line complexity - async pasteHandler(event: ClipboardEvent): Promise { + async pasteHandler( + event: ClipboardEvent, + // `event.clipboardData` is only valid synchronously while the paste + // event is being dispatched. Once `pasteHandler` yields at its first + // `await` (the `clipboardFilePath` hook), the browser may detach the + // DataTransfer and subsequent `getData()` calls return ''. We snapshot + // text/html synchronously below and thread the snapshot through the + // `!isSelectionInSameBlock` recursion via these optional params so the + // re-entry doesn't read a detached clipboard. Mirrors the legacy + // `@muyajs` `pasteHandler(event, type, rawText, rawHtml)` signature. + rawText?: string, + rawHtml?: string, + ): Promise { event.preventDefault(); event.stopPropagation(); @@ -604,17 +616,35 @@ class Clipboard { const { isSelectionInSameBlock, anchorBlock } = selection; + if (!anchorBlock || !event.clipboardData) + return; + + // Snapshot everything we need from `event.clipboardData` + // synchronously, BEFORE any `await` — after the first yield the + // DataTransfer can be detached and `getData()` returns ''. On the + // `!isSelectionInSameBlock` recursion we reuse the snapshot captured + // by the outer call rather than re-reading the (now possibly + // detached) clipboard. + const text = rawText ?? event.clipboardData.getData('text/plain'); + let html = rawHtml ?? event.clipboardData.getData('text/html'); + if (!isSelectionInSameBlock) { this.cutHandler(); - return this.pasteHandler(event); + return this.pasteHandler(event, text, html); } - if (!anchorBlock || !event.clipboardData) + // When the OS clipboard holds a file (e.g. an image copied from a + // file manager), let the embedder resolve it to a local path and + // insert it as an inline image, short-circuiting the text/HTML paste. + // Ported from the legacy `@muyajs` `clipboardFilePath` hook. + const imagePath = await resolveClipboardImagePath( + muya.options.clipboardFilePath, + ); + if (imagePath) { + this.insertImagePath(anchorBlock, imagePath); return; - - const text = event.clipboardData.getData('text/plain'); - let html = event.clipboardData.getData('text/html'); + } // Support pasted URLs from Firefox. if (URL_REG.test(text) && !/\s/.test(text) && !html) @@ -740,6 +770,37 @@ class Clipboard { } } + /** + * Insert a resolved clipboard file path as an inline image at the cursor. + * + * Inline images in muya are plain markdown text (`![](src)`) on a content + * block; rendering turns the token into an image. We splice the image + * markdown into the anchor block at the current selection (replacing any + * collapsed/expanded range) and place the cursor after it. The src is + * escaped the same way as {@link Format.replaceImage} so spaces and `#` + * survive in the path. + */ + private insertImagePath(anchorBlock: Content, src: string): void { + const cursor = anchorBlock.getCursor(); + if (!cursor) + return; + + const { start, end } = cursor; + const { text: content } = anchorBlock; + const escapedSrc = src + .replace(/ /g, encodeURI(' ')) + .replace(/#/g, encodeURIComponent('#')); + const imageText = `![](${escapedSrc})`; + + anchorBlock.text + = content.substring(0, start.offset) + + imageText + + content.substring(end.offset); + + const offset = start.offset + imageText.length; + anchorBlock.setCursor(offset, offset, true); + } + copyAsMarkdown() { this.copyType = 'copyAsMarkdown'; document.execCommand('copy'); diff --git a/packages/muya/src/config/index.ts b/packages/muya/src/config/index.ts index f0b319340f..8569bea9e3 100644 --- a/packages/muya/src/config/index.ts +++ b/packages/muya/src/config/index.ts @@ -317,6 +317,7 @@ export const MUYA_DEFAULT_OPTIONS = { frontmatterType: '-', mermaidTheme: 'default', // dark / forest / default vegaTheme: 'latimes', // excel / ggplot2 / quartz / vox / fivethirtyeight / dark / latimes + sequenceTheme: 'hand' as 'hand' | 'simple', // hand / simple hideQuickInsertHint: false, hideLinkPopup: false, autoCheck: false, @@ -387,6 +388,12 @@ export const isWin // http[s] (domain or IPv4 or localhost or IPv6) [port] /not-white-space export const URL_REG = /^http(s)?:\/\/([\w\-.~]+\.[a-z]{2,}|[0-9.]+|localhost|\[[a-f0-9.:]+\])(:\d{1,5})?\/\S+/i; +// A fully-formed base64/percent-encoded image data URL, e.g. +// `data:image/png;base64,iVBORw0KGg...`. Mirrors legacy muyajs `DATA_URL_REG` +// and `utils/image.ts` `getImageSrc`, so a bare `data:image/` prefix is not +// treated as a safe-to-embed source. +export const DATA_URL_REG + = /^data:image\/[\w+-]+(?:;[\w-]+=[\w-]+|;base64)*,[a-zA-Z0-9+/]+={0,2}$/; export const PREVIEW_DOMPURIFY_CONFIG = { // do not forbid `class` because `code` element use class to present language FORBID_ATTR: ['style', 'contenteditable'], diff --git a/packages/muya/src/editor/index.ts b/packages/muya/src/editor/index.ts index e9cebc1c4f..f49b43c71a 100644 --- a/packages/muya/src/editor/index.ts +++ b/packages/muya/src/editor/index.ts @@ -2,7 +2,7 @@ import type { JSONOp, JSONOpComponent, JSONOpList } from 'ot-json1'; import type Content from '../block/base/content'; import type Format from '../block/base/format'; import type { Muya } from '../muya'; -import type { ISelection } from '../selection/types'; +import type { IHistorySelection } from '../selection/types'; import type { TState } from '../state/types'; import type { Nullable } from '../types'; import * as otText from 'ot-text-unicode'; @@ -162,7 +162,7 @@ export class Editor { firstLeafBlock.setCursor(0, 0, needUpdated); } - updateContents(operations: JSONOp, selection: Nullable, source: string) { + updateContents(operations: JSONOp, selection: Nullable, source: string) { const { muya } = this; // ot-json1 no-op (`null`) is forwarded to dispatch — JSONState // short-circuits internally so listeners still see a json-change diff --git a/packages/muya/src/editor/linkMouseEvents.ts b/packages/muya/src/editor/linkMouseEvents.ts index 83ad8af6ae..78f35ed2ac 100644 --- a/packages/muya/src/editor/linkMouseEvents.ts +++ b/packages/muya/src/editor/linkMouseEvents.ts @@ -57,6 +57,13 @@ function findLinkWrapper(target: EventTarget | null): HTMLElement | null { return target.closest(LINK_SELECTOR); } +// True for a Cmd-click (macOS) or Ctrl-click (other platforms). The desktop +// renderer makes the same OS distinction; emitting on either keeps muya +// platform-agnostic and lets the host decide. +function isModifierClick(event: Event): boolean { + return event instanceof MouseEvent && (event.metaKey || event.ctrlKey); +} + function isPopoverTarget(wrapper: HTMLElement): boolean { // HTML `` is always a popover target — no source markers to hide. if (wrapper.classList.contains(CLASS_NAMES.MU_RAW_HTML)) @@ -116,8 +123,39 @@ export function attachLinkMouseHandlers(muya: Muya): void { if (!(event.target instanceof HTMLElement)) return; - if (event.target.closest(ANCHOR_CLICK_SELECTOR)) + // Suppress in-editor navigation for every real anchor variant. Place + // the cursor instead of opening a tab (standard contenteditable + // rich-text pattern). This runs for plain clicks too. + const anchor = event.target.closest(ANCHOR_CLICK_SELECTOR); + if (anchor) event.preventDefault(); + + // Cmd/Ctrl-click a link → ask the host to open it. marktext's + // `clickCtrl.js` dispatched `format-click` with `{ event, formatType: + // 'link', data: { text, href } }`; the desktop renderer gates on the + // modifier itself (`editor.vue` `format-click` handler) and calls + // `FORMAT_LINK_CLICK({ data })`, so the only contract it needs is a + // `data.href`. We gate on the modifier here too so plain clicks keep + // their cursor-placement-only behavior. `getLinkInfo` resolves the + // wrapper that hosts the href even when the IMG/text descendant was + // clicked, and returns a superset (`{ href, raw, text, range }`) of + // the legacy `{ text, href }` payload. + if (!isModifierClick(event)) + return; + + const wrapper = findLinkWrapper(event.target); + if (!wrapper) + return; + + const linkInfo = getLinkInfo(wrapper); + if (!linkInfo || !linkInfo.href) + return; + + eventCenter.emit('format-click', { + event, + formatType: 'link', + data: linkInfo, + }); }; eventCenter.attachDOMEvent(domNode, 'mouseover', overHandler); diff --git a/packages/muya/src/history/index.ts b/packages/muya/src/history/index.ts index b1acd32baf..929555ecd3 100644 --- a/packages/muya/src/history/index.ts +++ b/packages/muya/src/history/index.ts @@ -1,10 +1,11 @@ import type { JSONOpList } from 'ot-json1'; import type { Muya } from '../muya'; -import type { ISelection } from '../selection/types'; +import type { IHistorySelection } from '../selection/types'; import type { TState } from '../state/types'; import type { Nullable } from '../types'; import * as json1 from 'ot-json1'; import { asDoc } from '../state'; +import { deepClone } from '../utils'; interface IOptions { delay: number; @@ -14,7 +15,7 @@ interface IOptions { interface IOperation { operation: JSONOpList; - selection: Nullable; + selection: Nullable; } interface IStack { @@ -22,6 +23,39 @@ interface IStack { redo: IOperation[]; } +// A JSON-serializable view of an ISelection. The live `anchorBlock` / +// `focusBlock` references are dropped — they are an in-memory optimization +// only. `Selection._setCursor` re-resolves the target block from +// `anchorPath` / `focusPath` via `scrollPage.queryBlock(path)` when no block +// instance is present, so a path-only selection restores the caret losslessly. +interface ISerializableSelection { + anchor: IHistorySelection['anchor']; + focus: IHistorySelection['focus']; + anchorPath: IHistorySelection['anchorPath']; + focusPath: IHistorySelection['focusPath']; + isCollapsed: IHistorySelection['isCollapsed']; + isSelectionInSameBlock: IHistorySelection['isSelectionInSameBlock']; + direction: IHistorySelection['direction']; + type: IHistorySelection['type']; +} + +interface ISerializableOperation { + operation: JSONOpList; + selection: Nullable; +} + +// The public, JSON-serializable shape returned by `getHistory` and accepted by +// `setHistory`. Mirrors the private `_stack` plus the bookkeeping pointers +// (`lastRecorded`, `selectionStack`) needed to round-trip the recording state. +export interface ISerializedHistory { + stack: { + undo: ISerializableOperation[]; + redo: ISerializableOperation[]; + }; + lastRecorded: number; + selectionStack: (Nullable)[]; +} + enum HistoryAction { UNDO = 'undo', REDO = 'redo', @@ -36,7 +70,7 @@ const DEFAULT_OPTIONS = { class History { private _lastRecorded: number = 0; private _ignoreChange: boolean = false; - private _selectionStack: (Nullable)[] = []; + private _selectionStack: (Nullable)[] = []; private _stack: IStack = { undo: [], redo: [], @@ -96,6 +130,109 @@ class History { clear() { this._stack = { undo: [], redo: [] }; + this._selectionStack = []; + this._lastRecorded = 0; + } + + /** + * Return a deep, JSON-serializable snapshot of the undo/redo history. + * + * The ot-json1 ops are plain JSON arrays and are deep-cloned as-is. + * Selections drop their live `anchorBlock` / `focusBlock` references and + * keep only the serializable `anchorPath` / `focusPath` + offsets; the + * caret is re-resolved from those paths on restore (see + * `_toSerializableSelection`). The result can be `JSON.stringify`-d, stored + * on a desktop tab, and handed back to `setHistory` to restore the exact + * undo/redo state — `setHistory(getHistory())` followed by `undo()` + * reproduces the prior document state. + */ + getHistory(): ISerializedHistory { + return { + stack: { + undo: this._stack.undo.map(op => this._toSerializableOperation(op)), + redo: this._stack.redo.map(op => this._toSerializableOperation(op)), + }, + lastRecorded: this._lastRecorded, + selectionStack: this._selectionStack.map(sel => + this._toSerializableSelection(sel), + ), + }; + } + + /** + * Restore a snapshot previously produced by `getHistory`. Replaces the + * undo/redo stacks and recording bookkeeping. The restored selections are + * path-only; `Selection.setSelection` / `_setCursor` resolve the live + * block from the path when the op is later applied by `undo` / `redo`. + */ + setHistory(history: ISerializedHistory) { + this._stack = { + undo: history.stack.undo.map(op => this._fromSerializableOperation(op)), + redo: history.stack.redo.map(op => this._fromSerializableOperation(op)), + }; + this._lastRecorded = history.lastRecorded ?? 0; + this._selectionStack = (history.selectionStack ?? []).map(sel => + this._fromSerializableSelection(sel), + ); + } + + private _toSerializableOperation(op: IOperation): ISerializableOperation { + return { + operation: deepClone(op.operation), + selection: this._toSerializableSelection(op.selection), + }; + } + + private _fromSerializableOperation(op: ISerializableOperation): IOperation { + return { + operation: deepClone(op.operation), + selection: this._fromSerializableSelection(op.selection), + }; + } + + // Strip the live block references and keep only plain paths + offsets. + private _toSerializableSelection( + selection: Nullable, + ): Nullable { + if (selection == null) + return selection; + + return { + anchor: deepClone(selection.anchor), + focus: deepClone(selection.focus), + anchorPath: deepClone(selection.anchorPath), + focusPath: deepClone(selection.focusPath), + isCollapsed: selection.isCollapsed, + isSelectionInSameBlock: selection.isSelectionInSameBlock, + direction: selection.direction, + type: selection.type, + }; + } + + // Rebuild a selection without live block references. The block instances + // are intentionally omitted: the only consumers of a restored selection + // are `editor.updateContents` and `selection._setCursor`, both of which + // re-resolve the target block from `anchorPath` / `focusPath` via + // `scrollPage.queryBlock` when no block instance is present. The return + // type is `IHistorySelection`, whose `anchorBlock` / `focusBlock` are + // optional, so the missing block fields are part of the contract rather + // than an unsound cast over fabricated `ContentBlock` instances. + private _fromSerializableSelection( + selection: Nullable, + ): Nullable { + if (selection == null) + return selection; + + return { + anchor: deepClone(selection.anchor), + focus: deepClone(selection.focus), + anchorPath: deepClone(selection.anchorPath), + focusPath: deepClone(selection.focusPath), + isCollapsed: selection.isCollapsed, + isSelectionInSameBlock: selection.isSelectionInSameBlock, + direction: selection.direction, + type: selection.type, + }; } cutoff() { diff --git a/packages/muya/src/index.ts b/packages/muya/src/index.ts index d3e0c3b329..aba40c6f3c 100644 --- a/packages/muya/src/index.ts +++ b/packages/muya/src/index.ts @@ -14,6 +14,8 @@ export { CodeBlockLanguageSelector } from './ui/codeBlockLanguageSelector'; export { EmojiSelector } from './ui/emojiSelector'; export { FootnoteTool } from './ui/footnoteTool'; export { ImageEditTool } from './ui/imageEditTool'; +export { ImagePathPicker } from './ui/imagePicker'; +export type { IImagePathSuggestion } from './ui/imagePicker'; export { ImageResizeBar } from './ui/imageResizeBar'; export { ImageToolBar } from './ui/imageToolbar'; export { InlineFormatToolbar } from './ui/inlineFormatToolbar'; @@ -25,3 +27,7 @@ export { PreviewToolBar } from './ui/previewToolBar'; export { TableColumnToolbar } from './ui/tableColumnToolbar'; export { TableDragBar } from './ui/tableDragBar'; export { TableRowColumMenu } from './ui/tableRowColumMenu'; +export type { IImageInfo } from './utils/image'; +export { getImageInfo } from './utils/image'; +export { escapeHTML, sanitize, unescapeHTML, wordCount } from './utils/index'; +export { generateGithubSlug } from './utils/slug'; diff --git a/packages/muya/src/inlineRenderer/index.ts b/packages/muya/src/inlineRenderer/index.ts index 99f568d05e..6942058f6c 100644 --- a/packages/muya/src/inlineRenderer/index.ts +++ b/packages/muya/src/inlineRenderer/index.ts @@ -35,6 +35,30 @@ class InlineRenderer { return tokenizer(text, { hasBeginRules, labels, options, highlights }); } + /** + * Flush every cached image and force inline images to reload. + * + * The renderer memoises loaded images in `loadImageMap` (keyed by src, + * skipped on the next render once `isSuccess` is true) and resolved URLs + * in `urlMap`. When an image file changes on disk the cached entry would + * otherwise keep the stale bitmap, so clearing both maps and re-rendering + * every content block re-runs `loadImageAsync`, which loads the source + * afresh. Mirrors legacy muyajs `StateRender.invalidateImageCache`. + */ + invalidateImageCache() { + this.renderer.loadImageMap.clear(); + this.renderer.urlMap.clear(); + + const { scrollPage } = this.muya.editor; + if (!scrollPage) + return; + + scrollPage.breadthFirstTraverse((node) => { + if (node.isContent()) + node.update(); + }); + } + patch(block: Format, cursor?: ICursor, highlights: IHighlight[] = []) { this.collectReferenceDefinitions(); const { domNode } = block; diff --git a/packages/muya/src/muya.ts b/packages/muya/src/muya.ts index ead6551e44..369a795899 100644 --- a/packages/muya/src/muya.ts +++ b/packages/muya/src/muya.ts @@ -1,18 +1,29 @@ +import type Content from './block/base/content'; +import type Parent from './block/base/parent'; import type { Listener } from './event/types'; import type { ILocale } from './i18n/types'; +import type { ICursor } from './selection/types'; import type { ITocItem } from './state/getTOC'; -import type { TState } from './state/types'; +import type { IBulletListState, IOrderListState, ITableState, ITaskListState, TState } from './state/types'; import type { IMuyaOptions } from './types'; +import Format from './block/base/format'; +import { ScrollPage } from './block/scrollPage'; +import emptyStates from './config/emptyStates'; import { CLASS_NAMES, + DATA_URL_REG, MUYA_DEFAULT_OPTIONS, + URL_REG, } from './config/index'; import { Editor } from './editor/index'; import EventCenter from './event/index'; import I18n from './i18n/index'; import { getTOC } from './state/getTOC'; +import { isAnyListState, isAtxHeadingState } from './state/types'; +import { replaceBlockByLabel } from './ui/paragraphQuickInsertMenu/config'; import { Ui } from './ui/ui'; +import { deepClone } from './utils'; import './assets/styles/blockSyntax.css'; import './assets/styles/index.css'; import './assets/styles/inlineSyntax.css'; @@ -32,6 +43,36 @@ interface IPlugin { options: Record; } +// Maps the marktext/muyajs paragraph-menu labels the desktop sends through +// `updateParagraph` to the muya `replaceBlockByLabel` vocabulary. +const PARAGRAPH_LABEL_MAP: Record = { + 'paragraph': 'paragraph', + 'hr': 'thematic-break', + 'front-matter': 'frontmatter', + 'table': 'table', + 'mathblock': 'math-block', + 'html': 'html-block', + 'pre': 'code-block', + 'blockquote': 'block-quote', + 'heading 1': 'atx-heading 1', + 'heading 2': 'atx-heading 2', + 'heading 3': 'atx-heading 3', + 'heading 4': 'atx-heading 4', + 'heading 5': 'atx-heading 5', + 'heading 6': 'atx-heading 6', + 'ul-bullet': 'bullet-list', + 'ol-order': 'order-list', + // The desktop command palette emits `ol-bullet` for the ordered-list + // command while the menu emits `ol-order`; accept both. + 'ol-bullet': 'order-list', + 'ul-task': 'task-list', + 'mermaid': 'diagram mermaid', + 'plantuml': 'diagram plantuml', + 'vega-lite': 'diagram vega-lite', + 'flowchart': 'diagram flowchart', + 'sequence': 'diagram sequence', +}; + export class Muya { static plugins: IPlugin[] = []; @@ -141,6 +182,35 @@ export class Muya { this.editor.history.redo(); } + /** + * Return a JSON-serializable snapshot of the undo/redo history. + * + * Used by the desktop shell to persist each tab's editing history across + * tab switches: read it before deactivating a tab, store it, and hand it + * back to `setHistory` when the tab is re-selected. The ot-json1 ops are + * deep-cloned plain JSON; selections are reduced to their serializable + * paths/offsets (live block references are dropped and re-resolved on + * restore). Lossless round-trip: `setHistory(getHistory())` then `undo()` + * reproduces the prior document state. + */ + getHistory() { + return this.editor.history.getHistory(); + } + + /** + * Restore a history snapshot previously produced by `getHistory`. + */ + setHistory(history: ReturnType) { + this.editor.history.setHistory(history); + } + + /** + * Clear the undo/redo history (e.g. after loading a fresh document). + */ + clearHistory() { + this.editor.history.clear(); + } + /** * Search value in current document. * @param {string} value @@ -166,14 +236,651 @@ export class Muya { this.editor.setContent(content, autoFocus); } + /** + * Update editor options at runtime (mirrors marktext muyajs `setOptions`): + * merges `options` into `muya.options`, reflects the container-level ones + * (spellcheck, quick-insert hint), and — when `forceRender` is set — fully + * re-renders the document from its current state so render-affecting + * options (superSubScript, footnote, disableHtml, frontmatterType, + * codeBlockLineNumbers, GitLab compatibility, …) take effect. Unlike + * `setContent`, the undo history is preserved; the cursor is restored by path. + */ + setOptions(options: Partial, forceRender = false) { + Object.assign(this.options, options); + + if ('spellcheckEnabled' in options) + this.domNode.setAttribute('spellcheck', options.spellcheckEnabled ? 'true' : 'false'); + + if ('hideQuickInsertHint' in options) { + this.domNode.classList.toggle( + CLASS_NAMES.MU_SHOW_QUICK_INSERT_HINT, + !options.hideQuickInsertHint, + ); + } + + if (!forceRender) + return; + + const selection = this.editor.selection.getSelection(); + this.editor.scrollPage?.updateState(this.getState()); + // Restore the caret on the rebuilt tree by resolving the block at the + // saved path and setting the cursor on it directly. (Passing only a + // path to setSelection does not work — Selection._setCursor needs a + // concrete block's domNode; a bare queryBlock result is not a Node.) + // Mirrors Editor.updateContents' same-block cursor restore. + if (selection && selection.isSelectionInSameBlock) { + const begin = Math.min(selection.anchor.offset, selection.focus.offset); + const end = Math.max(selection.anchor.offset, selection.focus.offset); + const cursorBlock = this.editor.scrollPage?.queryBlock(selection.anchorPath); + if (cursorBlock && cursorBlock.isContent()) + cursorBlock.setCursor(begin, end, true); + } + } + + /** Update the editor font size / line height (mirrors muyajs `setFont`). */ + setFont({ fontSize, lineHeight }: { fontSize?: IMuyaOptions['fontSize']; lineHeight?: IMuyaOptions['lineHeight'] }) { + if (typeof fontSize === 'number') + this.options.fontSize = fontSize; + if (typeof lineHeight === 'number') + this.options.lineHeight = lineHeight; + } + + /** Update the tab size used for indentation. */ + setTabSize(tabSize: IMuyaOptions['tabSize']) { + this.options.tabSize = tabSize; + } + + /** Update list indentation and re-render so it takes effect. */ + setListIndentation(listIndentation: IMuyaOptions['listIndentation']) { + this.setOptions({ listIndentation }, true); + } + focus() { this.editor.focus(); } + /** + * Toggle focus mode (mirrors marktext muyajs `setFocusMode`). When enabled, + * every top-level block except the one holding the cursor is dimmed via the + * `mu-focus-mode` class on the editor container; the dimming itself lives in + * the stylesheet (`.mu-focus-mode .mu-container > * { opacity }`). + */ + setFocusMode(focusMode: boolean) { + if (focusMode) + this.domNode.classList.add(CLASS_NAMES.MU_FOCUS_MODE); + else + this.domNode.classList.remove(CLASS_NAMES.MU_FOCUS_MODE); + + this.options.focusMode = focusMode; + } + selectAll() { this.editor.selection.selectAll(); } + /** + * Toggle an inline format on the current selection. + * @param type One of strong/em/u/del/inline_code/link/image/inline_math/ + * sub/sup/mark/clear (and html_tag aliases). No-op when the selection is + * not inside a single formattable block. + */ + format(type: string) { + const { selection } = this.editor; + const sel = selection.getSelection(); + if (!sel) + return; + + const { + anchor, + focus, + anchorBlock, + anchorPath, + focusBlock, + focusPath, + isSelectionInSameBlock, + } = sel; + + if (!isSelectionInSameBlock || !(anchorBlock instanceof Format)) + return; + + // Restore the selection before applying the format, mirroring the + // inline format toolbar — the menu/IPC round-trip can drop the live + // DOM selection. + selection.setSelection({ + anchor, + focus, + anchorBlock, + anchorPath, + focusBlock, + focusPath, + }); + + anchorBlock.format(type); + } + + /** + * Replace the word at the current cursor with `replacement`, then place the + * cursor after the replacement. + * + * Mirrors legacy muyajs `_replaceCurrentWordInlineUnsafe`. The desktop spell + * checker calls this when the user picks a suggestion from the misspelled-word + * context menu (Chromium has already selected the whole word). Unsafe: the + * call is a no-op unless the word at the cursor matches `word`. + * + * @param word The expected (misspelled) word at the cursor. + * @param replacement The replacement word. + * @returns True when the replacement was applied. + */ + replaceCurrentWordInlineUnsafe(word: string, replacement: string): boolean { + const block = this.editor.activeContentBlock; + if (!block) + return false; + + return block.replaceCurrentWordInlineUnsafe(word, replacement); + } + + /** + * Return the current selection, or null when the editor has no selection. + */ + getSelection() { + return this.editor.selection.getSelection(); + } + + /** + * Whether the editor (or one of its descendants) currently holds focus. + */ + hasFocus() { + const { activeElement } = document; + + return this.domNode === activeElement || this.domNode.contains(activeElement); + } + + /** + * Blur the editor (mirrors marktext muyajs `blur`). Always hides every + * floating tool and blurs the contenteditable node. + * @param isRemoveAllRange Remove all native selection ranges. + * @param unSelect Clear the selected inline image so its toolbar/resize + * bar do not linger after the editor is blurred. + */ + blur(isRemoveAllRange = false, unSelect = false) { + if (isRemoveAllRange) + document.getSelection()?.removeAllRanges(); + + if (unSelect) + this.editor.selection.selectedImage = null; + + this.editor.activeContentBlock = null; + this.ui.hideAllFloatTools(); + this.domNode.blur(); + } + + /** + * Hide every floating tool/menu (toolbars, pickers, front button, …). + */ + hideAllFloatTools() { + this.ui.hideAllFloatTools(); + } + + /** + * Flush every cached inline image and force them to reload. + * + * The renderer memoises loaded images, so an image whose file changed on + * disk would otherwise keep showing the stale bitmap. Desktop calls this + * after a watched image file changes or on the `mt::invalidate-image-cache` + * IPC; it clears the image caches and re-renders all content blocks so the + * images load afresh. + */ + invalidateImageCache() { + this.editor.inlineRenderer.invalidateImageCache(); + } + + /** + * Copy the current document as Markdown to the clipboard. + */ + copyAsMarkdown() { + this.editor.clipboard.copyAsMarkdown(); + } + + /** + * Copy the current selection as rendered HTML to the clipboard. + */ + copyAsHtml() { + this.editor.clipboard.copyAsHtml(); + } + + /** + * Paste the clipboard content as plain text at the current cursor. + */ + pasteAsPlainText() { + this.editor.clipboard.pasteAsPlainText(); + } + + /** + * The outer-most block at the current cursor — the target for block-level + * operations. Uses the persisted active content block (which survives the + * menu/IPC round-trip), falling back to the selection anchor. + */ + private _outmostBlockAtCursor(): Parent | null { + const content = this.editor.activeContentBlock ?? this.editor.selection.anchorBlock; + + return content?.outMostBlock ?? null; + } + + /** + * Duplicate the block at the current cursor, placing the cursor in the + * copy. No-op when there is no current block. + */ + duplicate() { + const block = this._outmostBlockAtCursor(); + if (!block) + return; + + const state = deepClone(block.getState()); + const dupBlock = ScrollPage.loadBlock(state.name).create(this, state); + block.parent!.insertAfter(dupBlock, block); + dupBlock.lastContentInDescendant()?.setCursor(0, 0, true); + } + + /** + * Insert an empty paragraph relative to the block at the current cursor. + * @param location Insert `before` or `after` the current block (default `after`). + * @param text Initial text of the new paragraph. + */ + insertParagraph(location: 'before' | 'after' = 'after', text = '') { + const block = this._outmostBlockAtCursor(); + if (!block) + return; + + const state = deepClone(emptyStates.paragraph); + state.text = text; + const newBlock = ScrollPage.loadBlock('paragraph').create(this, state); + if (location === 'before') + block.parent!.insertBefore(newBlock, block); + else + block.parent!.insertAfter(newBlock, block); + + newBlock.lastContentInDescendant()?.setCursor(0, 0, true); + } + + /** + * Delete the block at the current cursor, moving the cursor to an adjacent + * block, or to a fresh empty paragraph when it was the only block. + */ + deleteParagraph() { + const block = this._outmostBlockAtCursor(); + if (!block) + return; + + let cursorBlock: Content | null = null; + if (block.prev) { + cursorBlock = block.prev.lastContentInDescendant(); + } + else if (block.next) { + cursorBlock = block.next.firstContentInDescendant(); + } + else { + const newBlock = ScrollPage.loadBlock('paragraph').create( + this, + deepClone(emptyStates.paragraph), + ); + block.parent!.insertAfter(newBlock, block); + cursorBlock = newBlock.lastContentInDescendant(); + } + + block.remove(); + cursorBlock?.setCursor(0, 0, true); + } + + /** + * Insert a GFM table at the current cursor, replacing the block the cursor + * is in (legacy `createTableInFigure`/`createFigure`). The table has `rows` + * rows × `columns` columns with the first row as the header; every cell is + * empty with `align: 'none'`. The cursor lands in the first cell. No-op when + * there is no current block. `rows`/`columns` are coerced to integers and + * clamped to a valid GFM shape (`rows >= 2`, `columns >= 1`) so invalid + * input (e.g. `rows: 0`, non-finite, or fractional values) still yields a + * usable table instead of an invalid state. + */ + createTable({ rows, columns }: { rows: number; columns: number }) { + const block = this._outmostBlockAtCursor(); + if (!block) + return; + + // Coerce and clamp to a valid GFM table shape. A GFM table needs a + // header row plus at least one body row (rows >= 2) and at least one + // column (columns >= 1). Garbage input (NaN/Infinity/floats/negatives) + // is normalised rather than producing an invalid state — `rows = 0` + // would otherwise build a table with no rows and crash `columnCount` + // (which reads `firstChild.firstChild`). + const safeRows = Math.max(2, Number.isFinite(rows) ? Math.floor(rows) : 0); + const safeColumns = Math.max(1, Number.isFinite(columns) ? Math.floor(columns) : 0); + + const makeRow = (): ITableState['children'][number] => ({ + name: 'table.row', + children: Array.from({ length: safeColumns }, () => ({ + name: 'table.cell' as const, + meta: { align: 'none' }, + text: '', + })), + }); + + const state: ITableState = { + name: 'table', + children: Array.from({ length: safeRows }, makeRow), + }; + + const newTable = ScrollPage.loadBlock('table').create(this, state); + block.replaceWith(newTable); + newTable.firstContentInDescendant()?.setCursor(0, 0, true); + } + + /** + * Insert an inline image at the current cursor in the active formattable + * block, mirroring legacy `insertImage`. The `![alt](src)` markdown is + * written through the `Format` block's text setter so it dispatches a JSON + * op (state stays in sync) rather than mutating the DOM directly. No-op when + * there is no active formattable (`Format`) block — e.g. inside a code block + * or with no cursor. + */ + insertImage({ src = '', alt = '' }: { src?: string; alt?: string }) { + const block = this.editor.activeContentBlock ?? this.editor.selection.anchorBlock; + if (!(block instanceof Format)) + return; + + const cursor = block.getCursor(); + if (cursor == null) + return; + + // Derive a sensible alt from the file name when none is provided, + // matching legacy `insertImage`. + if (!alt) { + const match = /[/\\]?([^./\\]+)\.[a-z]+$/i.exec(src); + alt = match?.[1] ?? ''; + } + + // Only percent-encode plain paths; leave full URLs / well-formed data + // URLs as-is. Mirrors legacy `insertImage` / `replaceImage` src + // handling — `DATA_URL_REG` requires the full `data:image/...,` + // shape (the same regex `utils/image.ts` `getImageSrc` uses), so a bare + // `data:image/` prefix is not embedded verbatim and instead falls through + // to the plain-path branch. + let imgUrl: string; + if (URL_REG.test(src)) + imgUrl = encodeURI(src); + else if (DATA_URL_REG.test(src)) + imgUrl = src; + else + imgUrl = src.replace(/ /g, encodeURI(' ')).replace(/#/g, encodeURIComponent('#')); + + const { start, end } = cursor; + const { text } = block; + // When there is a selection, use it as the alt text (legacy behaviour). + const imageAlt = start.offset !== end.offset ? text.substring(start.offset, end.offset) : alt; + const imageText = `![${imageAlt}](${imgUrl})`; + + // The `text` setter diffs against the old value and dispatches a JSON op. + block.text = text.substring(0, start.offset) + imageText + text.substring(end.offset); + // Re-render and place the caret on the alt text (offset of `![`). + block.setCursor(start.offset + 2, start.offset + 2 + imageAlt.length, true); + } + + /** + * Set the cursor programmatically. The desktop passes a cursor like + * `{ anchor, focus, anchorPath, focusPath }` (and may use `{ start, end }` + * / `block` / `path`). Resolves the target block(s) by path on the live tree + * and restores the selection the same way `Editor.updateContents` does — + * `block.setCursor` for the same-block case, `selection.setSelection` with + * resolved block instances for the cross-block case. Passing bare paths to + * `setSelection` does not work (it needs a block's `domNode`), so we always + * resolve and pass the block instance. No-op when the target can't be + * resolved. + */ + setCursor(cursor: ICursor) { + const { scrollPage } = this.editor; + if (!scrollPage) + return; + + // Accept both the `{ anchor, focus, anchorPath, focusPath }` and the + // `{ start, end, path }`/`block` shapes of ICursor. + const anchor = cursor.anchor ?? cursor.start ?? null; + const focus = cursor.focus ?? cursor.end ?? anchor; + const anchorPath = cursor.anchorPath ?? cursor.path; + const focusPath = cursor.focusPath ?? cursor.path ?? anchorPath; + + if (!anchor || !focus) + return; + + // queryBlock mutates its path argument (path.shift()) — pass copies. + const anchorBlock + = cursor.anchorBlock + ?? cursor.block + ?? (anchorPath ? scrollPage.queryBlock([...anchorPath]) : null); + const focusBlock + = cursor.focusBlock + ?? cursor.block + ?? (focusPath ? scrollPage.queryBlock([...focusPath]) : null); + + if (anchorBlock == null || !anchorBlock.isContent()) + return; + + // Same-block, mirror Editor.updateContents' selection-restore. + if (anchorBlock === focusBlock || focusBlock == null) { + const begin = Math.min(anchor.offset, focus.offset); + const last = Math.max(anchor.offset, focus.offset); + anchorBlock.setCursor(begin, last, true); + return; + } + + if (!focusBlock.isContent()) + return; + + this.editor.selection.setSelection({ + anchor, + focus, + anchorBlock, + anchorPath: anchorBlock.path, + focusBlock, + focusPath: focusBlock.path, + }); + } + + /** + * Convert the block at the cursor to another type, mirroring marktext's + * `updateParagraph`. `type` uses the marktext/muyajs paragraph-menu + * vocabulary: `paragraph`, `heading 1`–`heading 6`, `upgrade heading`, + * `degrade heading`, `blockquote`, `pre`, `mathblock`, `html`, `hr`, + * `table`, `front-matter`, `ul-bullet`/`ol-order`/`ul-task`, + * `loose-list-item`, `reset-to-paragraph`, and the diagram types. + */ + updateParagraph(type: string) { + const block = this._outmostBlockAtCursor(); + if (!block) + return; + + if (type === 'upgrade heading' || type === 'degrade heading') { + this._changeHeadingLevel(block, type); + return; + } + + if (type === 'loose-list-item') { + this._toggleLooseList(block); + return; + } + + // `reset-to-paragraph` returns the current block to plain paragraph + // form; structured containers (lists/blockquote) unwrap to preserve + // every child, tables are left untouched (matches legacy). + if (type === 'reset-to-paragraph') { + this._resetToParagraph(block); + return; + } + + const label = PARAGRAPH_LABEL_MAP[type]; + if (!label) + return; + + if (label.endsWith('-list') && isAnyListState(block.getState())) { + // Selecting the active list type toggles the list off (unwrap each + // item back into paragraphs); a different type converts in place, + // preserving every item. + if (block.blockName === label) + this._unwrapToParagraphs(block); + else + this._convertListType(block, label); + + return; + } + + // Legacy `isAllowedTransformation`: hr/table only replace an empty + // block so user content is never silently dropped. + if ( + (label === 'thematic-break' || label === 'table') + && this._blockLeadingText(block).trim() !== '' + ) { + return; + } + + replaceBlockByLabel({ + block, + muya: this, + label, + text: this._blockLeadingText(block), + }); + } + + /** Return the block at the cursor to plain paragraph form. */ + private _resetToParagraph(block: Parent) { + if (block.blockName === 'table') + return; + + if (isAnyListState(block.getState()) || block.blockName === 'block-quote') { + this._unwrapToParagraphs(block); + return; + } + + replaceBlockByLabel({ + block, + muya: this, + label: 'paragraph', + text: this._blockLeadingText(block), + }); + } + + /** + * Unwrap a structured container (list or blockquote) into the top-level + * blocks it contains, preserving every item. + */ + private _unwrapToParagraphs(block: Parent) { + const state = block.getState(); + let inner: TState[] = []; + if (isAnyListState(state)) + inner = state.children.flatMap(li => deepClone(li.children)); + else if (state.name === 'block-quote') + inner = deepClone(state.children); + + if (!inner.length) + return; + + const parent = block.parent!; + let ref: Parent = block; + let firstNew: Parent | null = null; + for (const childState of inner) { + const newBlock = ScrollPage.loadBlock(childState.name).create(this, childState); + parent.insertAfter(newBlock, ref); + ref = newBlock; + firstNew ??= newBlock; + } + + block.remove(); + firstNew?.firstContentInDescendant()?.setCursor(0, 0, true); + } + + /** Leading text of a block, with the atx hash run stripped for headings. */ + private _blockLeadingText(block: Parent): string { + const text = block.firstContentInDescendant()?.text ?? ''; + + return block.blockName === 'atx-heading' + ? text.replace(/^ {0,3}#{1,6}(?:\s+|$)/, '') + : text; + } + + /** Cycle the heading level (marktext upgrade/degrade semantics). */ + private _changeHeadingLevel(block: Parent, type: 'upgrade heading' | 'degrade heading') { + const state = block.getState(); + const level = isAtxHeadingState(state) ? state.meta.level : 0; + let newLevel = level; + + if (type === 'upgrade heading' && level !== 1) + newLevel = level === 0 ? 6 : level - 1; + else if (type === 'degrade heading' && level !== 0) + newLevel = level === 6 ? 0 : level + 1; + + if (newLevel === level) + return; + + replaceBlockByLabel({ + block, + muya: this, + label: newLevel === 0 ? 'paragraph' : `atx-heading ${newLevel}`, + text: this._blockLeadingText(block), + }); + } + + /** Toggle loose/tight on the list at the cursor. */ + private _toggleLooseList(block: Parent) { + const state = block.getState(); + if (!isAnyListState(state)) + return; + + const newState = deepClone(state); + newState.meta.loose = !newState.meta.loose; + const newBlock = ScrollPage.loadBlock(newState.name).create(this, newState); + block.replaceWith(newBlock); + newBlock.firstContentInDescendant()?.setCursor(0, 0, true); + } + + /** Convert an existing list to another list type, preserving items. */ + private _convertListType(block: Parent, label: string) { + const state = block.getState(); + if (!isAnyListState(state) || block.blockName === label) + return; + + const { bulletListMarker, orderListDelimiter } = this.options; + const loose = !!state.meta.loose; + const childContents: TState[][] = state.children.map(li => deepClone(li.children)); + + let newState: IBulletListState | IOrderListState | ITaskListState; + if (label === 'task-list') { + newState = { + name: 'task-list', + meta: { marker: bulletListMarker, loose }, + children: childContents.map(children => ({ + name: 'task-list-item', + meta: { checked: false }, + children, + })), + }; + } + else if (label === 'order-list') { + newState = { + name: 'order-list', + meta: { delimiter: orderListDelimiter, loose, start: 1 }, + children: childContents.map(children => ({ name: 'list-item', children })), + }; + } + else { + newState = { + name: 'bullet-list', + meta: { marker: bulletListMarker, loose }, + children: childContents.map(children => ({ name: 'list-item', children })), + }; + } + + const newBlock = ScrollPage.loadBlock(label).create(this, newState); + block.replaceWith(newBlock); + newBlock.firstContentInDescendant()?.setCursor(0, 0, true); + } + destroy() { this.eventCenter.detachAllDomEvents(); this.eventCenter.unsubscribeAll(); @@ -191,7 +898,7 @@ export class Muya { * [ensureContainerDiv ensure container element is div] */ function getContainer(originContainer: HTMLElement, options: IMuyaOptions) { - const { spellcheckEnabled, hideQuickInsertHint } = options; + const { spellcheckEnabled, hideQuickInsertHint, focusMode } = options; const newContainer = document.createElement('div'); const attrs = originContainer.attributes; // Copy attrs from origin container to new container @@ -202,6 +909,11 @@ function getContainer(originContainer: HTMLElement, options: IMuyaOptions) { if (!hideQuickInsertHint) newContainer.classList.add(CLASS_NAMES.MU_SHOW_QUICK_INSERT_HINT); + // Apply focus mode at construction when initially enabled; `setFocusMode` + // toggles it thereafter. + if (focusMode) + newContainer.classList.add(CLASS_NAMES.MU_FOCUS_MODE); + newContainer.classList.add(CLASS_NAMES.MU_EDITOR); newContainer.setAttribute('contenteditable', 'true'); diff --git a/packages/muya/src/selection/__tests__/selectionChange.spec.ts b/packages/muya/src/selection/__tests__/selectionChange.spec.ts new file mode 100644 index 0000000000..fc2d3d6fed --- /dev/null +++ b/packages/muya/src/selection/__tests__/selectionChange.spec.ts @@ -0,0 +1,88 @@ +// @vitest-environment happy-dom + +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { Muya } from '../../muya'; + +// Coverage for the selection-change payload extras added for the +// muyajs -> @muyajs/core desktop migration: `cursorCoords` (typewriter-mode +// scrolling) and `formats` (active inline formats, for lighting up the +// desktop toolbar). The legacy engine put both on its `selectionChange` +// event; the desktop reads `changes.cursorCoords.y` and the format list. + +const bootedHosts: HTMLElement[] = []; +let originalVersion: string | undefined; +let hadVersion = false; + +beforeEach(() => { + hadVersion = 'MUYA_VERSION' in window; + originalVersion = window.MUYA_VERSION; + window.MUYA_VERSION = 'test'; +}); + +afterEach(() => { + while (bootedHosts.length) { + const host = bootedHosts.pop()!; + host.remove(); + } + if (hadVersion) + window.MUYA_VERSION = originalVersion as string; + else + delete (window as Partial).MUYA_VERSION; +}); + +function bootMuya(markdown: string): Muya { + const host = document.createElement('div'); + document.body.appendChild(host); + const muya = new Muya(host, { markdown } as ConstructorParameters[1]); + muya.init(); + bootedHosts.push(muya.domNode); + return muya; +} + +describe('selection-change payload', () => { + it('includes cursorCoords and a formats array', () => { + const muya = bootMuya('hello world\n'); + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + + let payload: Record | null = null; + muya.on('selection-change', (p: unknown) => { + payload = p as Record; + }); + + muya.editor.selection.setSelection({ + anchor: { offset: 0 }, + focus: { offset: 5 }, + block: first, + path: first.path, + }); + + expect(payload).not.toBeNull(); + // cursorCoords is a DOMRect | null (null under happy-dom, which has no + // real layout) — assert the key is present so the desktop typewriter + // path always receives it. + expect(payload!).toHaveProperty('cursorCoords'); + expect(Array.isArray(payload!.formats)).toBe(true); + }); + + it('reports the active inline format when the cursor is inside bold text', () => { + const muya = bootMuya('**bold**\n'); + const first = muya.editor.scrollPage!.firstContentInDescendant()!; + + let payload: Record | null = null; + muya.on('selection-change', (p: unknown) => { + payload = p as Record; + }); + + // `**bold**` — place the selection inside the bolded word (offsets 3–5). + muya.editor.selection.setSelection({ + anchor: { offset: 3 }, + focus: { offset: 5 }, + block: first, + path: first.path, + }); + + expect(payload).not.toBeNull(); + const formats = payload!.formats as Array<{ type: string }>; + expect(formats.some(f => f.type === 'strong')).toBe(true); + }); +}); diff --git a/packages/muya/src/selection/index.ts b/packages/muya/src/selection/index.ts index 25bf5a2ecc..5f30f046c6 100644 --- a/packages/muya/src/selection/index.ts +++ b/packages/muya/src/selection/index.ts @@ -8,7 +8,7 @@ import type { Muya } from '../muya'; import type { ICursor, INodeOffset, ISelection } from './types'; import { BLOCK_DOM_PROPERTY, CLASS_NAMES } from '../config'; import { isElement, isHTMLElement, isKeyboardEvent, isMouseEvent } from '../utils'; -import { getImageInfo } from '../utils/image'; +import { getImageInfo, getImageSrc } from '../utils/image'; import { compareParagraphsOrder, findContentDOM, @@ -37,7 +37,7 @@ class Selection { }; } - static getCursorCoords() { + static getCursorCoords(preferEnd = false) { const sel = document.getSelection(); let range; let rect = null; @@ -54,8 +54,11 @@ class Selection { : null; } + // For a forward range selection the caret sits at the END, so + // prefer the last client rect; otherwise the first rect is the + // caret (collapsed cursor or backward selection). if (rects?.length) - rect = rects[0]; + rect = preferEnd ? rects[rects.length - 1] : rects[0]; } } @@ -305,6 +308,22 @@ class Selection { selectedImage, } = this; + // Backport of marktext's `selectionChange` payload extras the desktop + // relies on: `cursorCoords` for typewriter-mode scrolling and the + // active inline formats at the cursor for lighting up the toolbar. + // Follow the caret (focus end) for forward selections so typewriter + // scrolling tracks the cursor rather than the selection start. + const cursorCoords = Selection.getCursorCoords(direction === 'forward'); + // Duck-type the Format block — a value import of Format here would + // create a selection -> format circular dependency. + const anchorBlockRef = this.anchorBlock as Format | null; + const formats + = isSelectionInSameBlock + && anchorBlockRef + && typeof anchorBlockRef.getFormatsInRange === 'function' + ? anchorBlockRef.getFormatsInRange().formats + : []; + this.muya.eventCenter.emit('selection-change', { anchor, focus, @@ -317,6 +336,8 @@ class Selection { direction, type, selectedImage, + cursorCoords, + formats, }); } @@ -544,6 +565,28 @@ class Selection { // Handle image click, to select the current image if (isHTMLElement(target) && target.tagName === 'IMG') { + // Cmd/Ctrl-click an image → ask the host to preview it. marktext's + // `clickEvent.js` dispatched `format-click` with `{ event, + // formatType: 'image', data: }`; the desktop renderer + // (`editor.vue` `format-click` handler) gates on the modifier and + // opens a `SimpleImageViewer` with that src string. We resolve the + // src from the token (the same source path the renderer used) + // through `getImageSrc` so relative/file paths become loadable + // URLs, falling back to the rendered 's own `src` attribute. + // The plain-click select/toolbar/transformer path below is left + // untouched. + if (event instanceof MouseEvent && (event.metaKey || event.ctrlKey)) { + const tokenSrc = imageInfo.token.src || imageInfo.token.attrs.src || ''; + const src = getImageSrc(tokenSrc).src || target.getAttribute('src') || ''; + if (src) { + eventCenter.emit('format-click', { + event, + formatType: 'image', + data: src, + }); + } + } + // Handle show image toolbar const rect = imageWrapper .querySelector(`.${CLASS_NAMES.MU_IMAGE_CONTAINER}`) diff --git a/packages/muya/src/selection/types.ts b/packages/muya/src/selection/types.ts index 7f462857ff..377016291b 100644 --- a/packages/muya/src/selection/types.ts +++ b/packages/muya/src/selection/types.ts @@ -36,3 +36,14 @@ export interface ISelection { direction: string; type: string; } + +// An `ISelection` whose live `anchorBlock` / `focusBlock` references are +// optional. The history stacks store selections that may have lost their block +// instances after a serialize/restore round-trip (those references are an +// in-memory optimization re-resolved from `anchorPath` / `focusPath` on apply). +// A full `ISelection` is assignable to this type, so live selections captured +// via `getSelection()` still fit without any cast. +export type IHistorySelection = Omit & { + anchorBlock?: ContentBlock; + focusBlock?: ContentBlock; +}; diff --git a/packages/muya/src/state/__tests__/diagramFlowchartSequence.spec.ts b/packages/muya/src/state/__tests__/diagramFlowchartSequence.spec.ts new file mode 100644 index 0000000000..2445876de4 --- /dev/null +++ b/packages/muya/src/state/__tests__/diagramFlowchartSequence.spec.ts @@ -0,0 +1,109 @@ +import { describe, expect, it } from 'vitest'; +import { MUYA_DEFAULT_OPTIONS } from '../../config'; +import { MarkdownToState } from '../markdownToState'; +import ExportMarkdown from '../stateToMarkdown'; + +// Loose accessor type used only in this spec — every state node the +// MarkdownToState pipeline emits has some subset of these fields, and the +// tests navigate the tree generically without re-implementing the +// discriminated TState union narrowing. +interface IStateLike { + name: string; + text?: string; + meta?: Record & { type?: string; lang?: string }; + children?: IStateLike[]; +} + +function generate(markdown: string): IStateLike[] { + return new MarkdownToState({ + footnote: false, + math: false, + isGitlabCompatibilityEnabled: false, + trimUnnecessaryCodeBlockEmptyLines: false, + frontMatter: false, + }).generate(markdown) as unknown as IStateLike[]; +} + +function toMarkdown(states: IStateLike[]): string { + return new ExportMarkdown({ listIndentation: 1 }).generate( + states as unknown as Parameters[0], + ); +} + +// Parity restoration for flowchart + sequence diagrams. The legacy muyajs +// engine rendered ```flowchart``` (flowchart.js) and ```sequence``` +// (js-sequence-diagrams) fenced blocks as diagram blocks; the TS rewrite +// dropped them. These specs lock the parse + round-trip behaviour so the +// two diagram types stay first-class alongside mermaid / plantuml / +// vega-lite. +describe('diagram blocks — flowchart & sequence parity', () => { + it('parses a ```flowchart``` fence as a diagram block of type flowchart', () => { + const md = `\`\`\`flowchart +st=>start: Start +e=>end: End +st->e +\`\`\` +`; + const states = generate(md); + expect(states.length).toBe(1); + expect(states[0].name).toBe('diagram'); + expect(states[0].meta!.type).toBe('flowchart'); + // flowchart is not vega-lite, so the inner code lang stays yaml. + expect(states[0].meta!.lang).toBe('yaml'); + expect(states[0].text).toContain('st=>start: Start'); + }); + + it('parses a ```sequence``` fence as a diagram block of type sequence', () => { + const md = `\`\`\`sequence +Alice->Bob: Hello Bob +Bob-->Alice: Hi Alice +\`\`\` +`; + const states = generate(md); + expect(states.length).toBe(1); + expect(states[0].name).toBe('diagram'); + expect(states[0].meta!.type).toBe('sequence'); + expect(states[0].meta!.lang).toBe('yaml'); + expect(states[0].text).toContain('Alice->Bob: Hello Bob'); + }); + + it('round-trips a flowchart diagram block back to a ```flowchart``` fence', () => { + const md = `\`\`\`flowchart +st=>start: Start +e=>end: End +st->e +\`\`\` +`; + const out = toMarkdown(generate(md)); + expect(out).toContain('```flowchart'); + expect(out).toContain('st=>start: Start'); + expect(out).toContain('st->e'); + }); + + it('round-trips a sequence diagram block back to a ```sequence``` fence', () => { + const md = `\`\`\`sequence +Alice->Bob: Hello Bob +Bob-->Alice: Hi Alice +\`\`\` +`; + const out = toMarkdown(generate(md)); + expect(out).toContain('```sequence'); + expect(out).toContain('Alice->Bob: Hello Bob'); + expect(out).toContain('Bob-->Alice: Hi Alice'); + }); + + it('still parses mermaid / plantuml / vega-lite (no regression)', () => { + for (const type of ['mermaid', 'plantuml', 'vega-lite'] as const) { + const md = `\`\`\`${type}\nfoo\n\`\`\`\n`; + const states = generate(md); + expect(states[0].name).toBe('diagram'); + expect(states[0].meta!.type).toBe(type); + } + }); +}); + +describe('sequenceTheme option', () => { + it('defaults to `hand` in MUYA_DEFAULT_OPTIONS', () => { + expect(MUYA_DEFAULT_OPTIONS.sequenceTheme).toBe('hand'); + }); +}); diff --git a/packages/muya/src/state/__tests__/nestedMixedLists.spec.ts b/packages/muya/src/state/__tests__/nestedMixedLists.spec.ts new file mode 100644 index 0000000000..28326332ec --- /dev/null +++ b/packages/muya/src/state/__tests__/nestedMixedLists.spec.ts @@ -0,0 +1,123 @@ +// @vitest-environment happy-dom + +import type { TState } from '../types'; +import { describe, expect, it } from 'vitest'; +import { MarkdownToState } from '../markdownToState'; +import StateToMarkdown from '../stateToMarkdown'; + +// Regression coverage ported from marktext#4341 (legacy desktop spec +// `test/unit/specs/markdown-nested-mixed-lists.spec.ts`). A list whose type +// differs from its enclosing list item (a `ul` inside an `ol`, or an `ol` +// inside a `ul`) was being rewritten into a paragraph by the legacy muyajs +// lexer, losing the nested-list structure. +// +// The new @muyajs/core engine parses through marked + the state tree rather +// than the legacy ContentState/ExportMarkdown pair, so the equivalent checks +// are: +// - structural: the state tree from `MarkdownToState` carries a nested +// `bullet-list` / `order-list` under the correct outer `list-item` +// (mirrors the legacy `blocks[...].children.find(type === 'ul')` probe). +// - round-trip: `MarkdownToState` → `StateToMarkdown` reproduces the source +// markdown verbatim (mirrors the legacy importMarkdown → ExportMarkdown +// identity assertion). The legacy spec used a 3-space indent for the +// nested `ul` and 2-space for the nested `ol`; the @muyajs/core serializer +// uses a content-aligned indent (marker width + 1) so the expected output +// is regenerated rather than hard-coded against the legacy indentation. + +function toState(markdown: string): TState[] { + return new MarkdownToState({ + footnote: false, + math: true, + isGitlabCompatibilityEnabled: true, + trimUnnecessaryCodeBlockEmptyLines: false, + frontMatter: true, + }).generate(markdown); +} + +function roundTrip(markdown: string): string { + return new StateToMarkdown({ listIndentation: 1 }).generate(toState(markdown)); +} + +// `children` is only present on container states; leaf states (paragraph, +// table.cell, …) have no `children`. Narrow on the property so we stay +// `any`-free while still tolerating a leaf/undefined input. +function children(state: TState | undefined): TState[] { + return state && 'children' in state ? state.children : []; +} + +// First-child text of a list item (its leading paragraph). Leaf states carry a +// `text` field; container states do not. +function firstText(state: TState | undefined): string | undefined { + const first = children(state)[0]; + return first && 'text' in first ? first.text : undefined; +} + +describe('nested mixed lists (#4341)', () => { + it('preserves a bullet list nested inside an ordered list item (round trip)', () => { + const markdown = `1. Eat a carrot. +2. Find an application: + - New + - Open + - Save +`; + // First pass is the identity, and a second pass is stable. + const once = roundTrip(markdown); + expect(once).toBe(markdown); + expect(roundTrip(once)).toBe(once); + }); + + it('preserves an ordered list nested inside a bullet list item (round trip)', () => { + const markdown = `- Outer bullet +- Container item: + 1. First step + 2. Second step + 3. Third step +`; + const once = roundTrip(markdown); + expect(once).toBe(markdown); + expect(roundTrip(once)).toBe(once); + }); + + it('produces a bullet-list state nested inside the second order-list item (not a paragraph)', () => { + const states = toState(`1. Eat a carrot. +2. Find an application: + - New + - Open + - Save +`); + const ol = states.find(s => s.name === 'order-list'); + expect(ol, 'expected a top-level order-list state').toBeDefined(); + + const secondItem = children(ol)[1]; + expect(secondItem.name).toBe('list-item'); + + const nestedList = children(secondItem).find(c => c.name === 'bullet-list'); + expect(nestedList, 'expected a bullet-list nested inside the second order-list item').toBeDefined(); + // The item is exactly [leading paragraph, nested bullet-list] — the + // nested list did NOT collapse into a paragraph (the #4341 failure mode). + expect(children(secondItem).map(c => c.name)).toEqual(['paragraph', 'bullet-list']); + expect(children(nestedList)).toHaveLength(3); + expect(children(nestedList).map(firstText)).toEqual(['New', 'Open', 'Save']); + }); + + it('produces an order-list state nested inside a bullet-list item (not a paragraph)', () => { + const states = toState(`- Outer bullet +- Container item: + 1. First step + 2. Second step + 3. Third step +`); + const ul = states.find(s => s.name === 'bullet-list'); + expect(ul, 'expected a top-level bullet-list state').toBeDefined(); + + const secondItem = children(ul)[1]; + expect(secondItem.name).toBe('list-item'); + + const nestedList = children(secondItem).find(c => c.name === 'order-list'); + expect(nestedList, 'expected an order-list nested inside the second bullet-list item').toBeDefined(); + // The item is exactly [leading paragraph, nested order-list]. + expect(children(secondItem).map(c => c.name)).toEqual(['paragraph', 'order-list']); + expect(children(nestedList)).toHaveLength(3); + expect(children(nestedList).map(firstText)).toEqual(['First step', 'Second step', 'Third step']); + }); +}); diff --git a/packages/muya/src/state/__tests__/strongCjkFlanking.spec.ts b/packages/muya/src/state/__tests__/strongCjkFlanking.spec.ts new file mode 100644 index 0000000000..2722d920fb --- /dev/null +++ b/packages/muya/src/state/__tests__/strongCjkFlanking.spec.ts @@ -0,0 +1,81 @@ +// @vitest-environment happy-dom + +import { describe, expect, it } from 'vitest'; +import { renderToStaticHTML } from '../renderToStaticHTML'; + +// Regression coverage ported from marktext#4307 (legacy desktop spec +// `test/unit/specs/markdown-strong-cjk.spec.ts`). Strong emphasis (`**…**`) +// whose `**` delimiters sit directly against a CJK character should still be +// recognised, even when the emphasised content begins/ends with a punctuation +// character (a quote, bracket, or paren). +// +// WHY THE CJK CASES FAIL ON @muyajs/core (documented engine gap): +// The new engine tokenises inline markdown with marked@16, which implements +// the CommonMark emphasis "flanking" rule literally. CommonMark classifies +// every character as whitespace, (Unicode) punctuation, or "other"; CJK +// ideographs and Hangul are "other". For a left-flanking `**` run, clause +// (2b) requires the character *before* the run to be whitespace or +// punctuation whenever the character *after* the run is punctuation. In +// `例子例子**"加粗"**例子例子` the char after the opening `**` is `"` +// (punctuation) and the char before it is `子` (a CJK ideograph → "other", +// neither whitespace nor punctuation), so the run is not left-flanking and +// marked emits the literal `**`. The same happens at the closing run. +// +// Legacy marktext shipped its own inline tokenizer (muyajs +// `lib/parser/render`) whose `canOpen/canCloseEmphasis` flanking helpers +// treat CJK characters as punctuation, so `**` adjacent to a CJK char with +// punctuation-bounded inner content opens/closes emphasis. marked has no +// such patch, and fixing it requires either patching the dependency or +// shipping a custom inline-emphasis tokenizer extension — out of scope for a +// tests-only fidelity-verification PR. The CJK cases below assert the +// CORRECT (legacy) behavior and are wrapped in `it.fails`, so: +// - the suite stays green while the gap exists, AND +// - the moment the engine starts recognising these (e.g. a marked upgrade +// or a flanking patch lands) the `it.fails` flips red, forcing this file +// to be promoted to a plain `it`. Fidelity can only go up. +// +// This gap is documented in the PR body for #4307 follow-up. + +function rendersStrong(src: string): boolean { + const html = renderToStaticHTML(src, { sanitize: false }); + return //.test(html); +} + +describe('strong emphasis with CJK boundaries (#4307)', () => { + // Cases that already work on @muyajs/core — they lock in the pre-existing + // behavior so any fix to the CJK gap can't regress them. Each emphasised + // run here is bounded by a CJK ideograph or whitespace on the inner side, + // so the flanking rule is satisfied without the legacy CJK-as-punctuation + // patch. + const sanityCases = [ + 'before **"normal"** after', + 'before**normal**after', + '中文**加粗**中文', + ]; + + for (const src of sanityCases) { + it(`recognises strong in: ${src}`, () => { + expect(rendersStrong(src)).toBe(true); + }); + } + + // CJK-boundary cases. These are the #4307 regression cases the legacy + // marktext tokenizer fixed. @muyajs/core (via marked) does NOT recognise + // them — documented engine gap (see file header). The assertion states the + // CORRECT expected behavior; `it.fails` keeps the suite green until the gap + // is closed, at which point it must be converted to a plain `it`. + const cjkGapCases = [ + '例子例子**"加粗"**例子例子', + '日本語**(強調)**日本語', + '한국어**[강조]**한국어', + // Non-BMP CJK (CJK Ext-B): 𠀀 is U+20000, stored as a surrogate pair. + // The flanking boundary check must read the full code point. + '𠀀𠀁**"加粗"**𠀀𠀁', + ]; + + for (const src of cjkGapCases) { + it.fails(`[GAP #4307] should recognise strong in CJK context: ${src}`, () => { + expect(rendersStrong(src)).toBe(true); + }); + } +}); diff --git a/packages/muya/src/state/markdownToHtml.ts b/packages/muya/src/state/markdownToHtml.ts index b80783222c..c4113dc8fb 100644 --- a/packages/muya/src/state/markdownToHtml.ts +++ b/packages/muya/src/state/markdownToHtml.ts @@ -48,7 +48,7 @@ export class MarkdownToHtml { async renderDiagram() { const selector - = 'code.language-vega-lite, code.language-plantuml'; + = 'code.language-vega-lite, code.language-plantuml, code.language-flowchart, code.language-sequence'; const codes = this._exportContainer!.querySelectorAll(selector); for (const code of codes) { @@ -56,6 +56,10 @@ export class MarkdownToHtml { const functionType = (() => { if (/plantuml/.test(code.className)) return 'plantuml'; + else if (/flowchart/.test(code.className)) + return 'flowchart'; + else if (/sequence/.test(code.className)) + return 'sequence'; else return 'vega-lite'; })(); @@ -75,6 +79,11 @@ export class MarkdownToHtml { theme: 'latimes', // only render light theme }); } + else if (functionType === 'sequence') { + Object.assign(options, { + theme: this.muya?.options.sequenceTheme ?? 'hand', + }); + } try { if (functionType === 'plantuml') { @@ -82,8 +91,14 @@ export class MarkdownToHtml { diagramContainer.innerHTML = ''; diagram.insertImgElement(diagramContainer); } - if (functionType === 'vega-lite') + else if (functionType === 'flowchart' || functionType === 'sequence') { + const diagram = render.parse(rawCode); + diagramContainer.innerHTML = ''; + diagram.drawSVG(diagramContainer, options); + } + else if (functionType === 'vega-lite') { await render(diagramContainer, JSON.parse(rawCode), options); + } } catch { diagramContainer.innerHTML = '< Invalid Diagram >'; diff --git a/packages/muya/src/state/markdownToState.ts b/packages/muya/src/state/markdownToState.ts index 3e4c07e8c3..080c6be930 100644 --- a/packages/muya/src/state/markdownToState.ts +++ b/packages/muya/src/state/markdownToState.ts @@ -159,9 +159,9 @@ export class MarkdownToState { value = value.replace(/\n+$/, '').replace(/^\n+/, ''); } - const diagramMatch = /^(mermaid|vega-lite|plantuml)$/.exec(lang); + const diagramMatch = /^(mermaid|vega-lite|plantuml|flowchart|sequence)$/.exec(lang); if (diagramMatch) { - const diagramType = diagramMatch[1] as 'mermaid' | 'vega-lite' | 'plantuml'; + const diagramType = diagramMatch[1] as 'mermaid' | 'vega-lite' | 'plantuml' | 'flowchart' | 'sequence'; state = { name: 'diagram' as const, text: value, diff --git a/packages/muya/src/state/types.ts b/packages/muya/src/state/types.ts index 0a7723cf67..9e7b41e9f2 100644 --- a/packages/muya/src/state/types.ts +++ b/packages/muya/src/state/types.ts @@ -145,7 +145,7 @@ export interface IFrontmatterState { export interface IDiagramMeta { lang: string; // 'yaml' | 'json'; - type: 'mermaid' | 'plantuml' | 'vega-lite'; + type: 'mermaid' | 'plantuml' | 'vega-lite' | 'flowchart' | 'sequence'; } export interface IDiagramState { diff --git a/packages/muya/src/types.ts b/packages/muya/src/types.ts index aa7368e3a2..d7253d4f52 100644 --- a/packages/muya/src/types.ts +++ b/packages/muya/src/types.ts @@ -18,6 +18,7 @@ export interface IMuyaOptions { frontmatterType: string; // '-' | '+' | ';' | '{'; mermaidTheme: string; vegaTheme: string; + sequenceTheme: 'hand' | 'simple'; hideQuickInsertHint: boolean; hideLinkPopup: boolean; autoCheck: boolean; @@ -36,6 +37,19 @@ export interface IMuyaOptions { }; json?: TState[]; markdown?: string; + /** + * Resolve the OS clipboard to a local file path on paste. + * + * When the user pastes and the system clipboard holds a file (for + * example an image copied from a file manager rather than image bytes), + * the embedder resolves it to an absolute path. If this hook is provided + * and returns a non-empty path with an image extension, muya inserts that + * path as an inline image at the cursor instead of running the default + * text/HTML paste. Return `''` to fall through to the normal paste flow. + * + * Ported from the legacy `@muyajs` `clipboardFilePath` option. + */ + clipboardFilePath?: () => Promise; } export type Nullable = T | null | undefined | void; diff --git a/packages/muya/src/types/index.d.ts b/packages/muya/src/types/index.d.ts index d07b43e076..dbe45e3c4a 100644 --- a/packages/muya/src/types/index.d.ts +++ b/packages/muya/src/types/index.d.ts @@ -7,7 +7,38 @@ declare module '*.bmp'; declare module '*.tiff'; declare module '*.css'; declare module '*.css?inline'; +declare module '*.woff'; +declare module '*.woff2'; declare module 'joplin-turndown-plugin-gfm'; declare module 'prismjs/plugins/keep-markup/prism-keep-markup'; declare module 'prismjs/dependencies'; declare module '@marktext/file-icons'; +declare module 'snapsvg-cjs'; + +declare module 'flowchart.js' { + interface IFlowChartDrawOptions { + [key: string]: unknown; + } + interface IFlowChartInstance { + drawSVG: (container: HTMLElement | string, options?: IFlowChartDrawOptions) => void; + clean: () => void; + } + export function parse(input: string): IFlowChartInstance; + const flowchart: { parse: typeof parse }; + export default flowchart; +} + +declare module '*sequence-diagram-snap' { + interface ISequenceDrawOptions { + theme?: 'hand' | 'simple'; + [key: string]: unknown; + } + interface ISequenceDiagramInstance { + drawSVG: (container: HTMLElement | string, options?: ISequenceDrawOptions) => void; + } + interface ISequenceDiagramConstructor { + parse: (input: string) => ISequenceDiagramInstance; + } + const Diagram: ISequenceDiagramConstructor; + export default Diagram; +} diff --git a/packages/muya/src/ui/imageEditTool/__tests__/imageEditToolAutocomplete.spec.ts b/packages/muya/src/ui/imageEditTool/__tests__/imageEditToolAutocomplete.spec.ts new file mode 100644 index 0000000000..6ba590cee2 --- /dev/null +++ b/packages/muya/src/ui/imageEditTool/__tests__/imageEditToolAutocomplete.spec.ts @@ -0,0 +1,110 @@ +// @vitest-environment happy-dom +import type { Muya } from '../../../muya'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { ImageEditTool } from '..'; +import EventCenter from '../../../event'; + +// Verifies the ImageEditTool ↔ ImagePathPicker wiring: as the user types in +// the image src input, the tool should call the host's `imagePathAutoComplete` +// hook and re-dispatch the result through the `muya-image-picker` event that +// the floating picker subscribes to. +// +// We run BaseFloat for real (so the src input is actually rendered) but mock +// the slice of Muya the tool touches. We do NOT call init() or build a block +// tree — the src input render path only needs i18n + the muya-image-selector +// payload. + +function makeFakeMuya(imagePathAutoComplete?: (src: string) => Promise): { + muya: Muya; + eventCenter: EventCenter; +} { + const eventCenter = new EventCenter(); + const editorDomNode = document.createElement('div'); + const editorWrapper = document.createElement('div'); + editorWrapper.appendChild(editorDomNode); + document.body.appendChild(editorWrapper); + + const muya = { + domNode: editorDomNode, + eventCenter, + i18n: { t: (s: string) => s }, + ui: { shownFloat: new Set() }, + options: { imagePathAutoComplete }, + } as unknown as Muya; + + return { muya, eventCenter }; +} + +function stubReference(): HTMLElement { + const el = document.createElement('span'); + el.getBoundingClientRect = () => + ({ top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0, toJSON: () => '' }) as DOMRect; + document.body.appendChild(el); + return el; +} + +function openTool(eventCenter: EventCenter, src: string) { + eventCenter.emit('muya-image-selector', { + block: { replaceImage: vi.fn() }, + reference: stubReference(), + imageInfo: { imageId: 'id', token: { attrs: { src, alt: '', title: '' } } }, + }); +} + +async function nextTick() { + await new Promise(resolve => setTimeout(resolve, 0)); +} + +describe('imageEditTool — imagePathAutoComplete wiring', () => { + let tool: ImageEditTool; + + beforeEach(() => { + document.body.innerHTML = ''; + }); + + afterEach(() => { + tool?.hide(); + vi.restoreAllMocks(); + }); + + it('calls imagePathAutoComplete with the current src value and dispatches muya-image-picker on keyup', async () => { + const suggestions = [{ text: 'photo.png', iconClass: 'icon-image' }]; + const autocomplete = vi.fn().mockResolvedValue(suggestions); + const { muya, eventCenter } = makeFakeMuya(autocomplete); + tool = new ImageEditTool(muya, { imagePathAutoComplete: autocomplete } as never); + + const pickerEvents: unknown[] = []; + eventCenter.subscribe('muya-image-picker', (payload: unknown) => { + pickerEvents.push(payload); + }); + + openTool(eventCenter, '/some/dir/ph'); + + const input = tool.container!.querySelector('input.src') as HTMLInputElement; + expect(input).not.toBeNull(); + + input.dispatchEvent(new KeyboardEvent('keyup', { key: 'h' })); + await nextTick(); + + expect(autocomplete).toHaveBeenCalledWith('/some/dir/ph'); + expect(pickerEvents.length).toBe(1); + const payload = pickerEvents[0] as { list: unknown[]; reference: HTMLElement }; + expect(payload.list).toEqual(suggestions); + expect(payload.reference).toBe(input); + }); + + it('does not call imagePathAutoComplete when the hook is absent', async () => { + const { muya, eventCenter } = makeFakeMuya(undefined); + tool = new ImageEditTool(muya); + + const pickerSpy = vi.fn(); + eventCenter.subscribe('muya-image-picker', pickerSpy); + + openTool(eventCenter, '/some/dir/ph'); + const input = tool.container!.querySelector('input.src') as HTMLInputElement; + input.dispatchEvent(new KeyboardEvent('keyup', { key: 'h' })); + await nextTick(); + + expect(pickerSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/muya/src/ui/imageEditTool/index.ts b/packages/muya/src/ui/imageEditTool/index.ts index ace8dfcdc0..bed2672fce 100644 --- a/packages/muya/src/ui/imageEditTool/index.ts +++ b/packages/muya/src/ui/imageEditTool/index.ts @@ -2,14 +2,16 @@ import type { VNode } from 'snabbdom'; import type Format from '../../block/base/format'; import type { Muya } from '../../index'; import type { ImageToken } from '../../inlineRenderer/types'; +import type { IImagePathSuggestion } from '../imagePicker'; import type { IBaseOptions } from '../types'; import { EVENT_KEYS, isWin, URL_REG } from '../../config'; import { getUniqueId, isHTMLInputElement, isKeyboardEvent } from '../../utils'; -import { query } from '../../utils/dom'; +import { query } from '../../utils/dom'; import { getImageInfo, getImageSrc } from '../../utils/image'; import { h, patch } from '../../utils/snabbdom'; import BaseFloat from '../baseFloat'; +import { ImagePathPicker } from '../imagePicker'; import './index.css'; /** @@ -28,8 +30,15 @@ interface IState { * Image edit tool options */ type Options = { - /** Custom image path picker function */ + /** Custom image path picker function (one-shot native file dialog) */ imagePathPicker?: () => Promise; + /** + * Local image path autocomplete hook. Given the current src input value, + * returns a list of path suggestions to show in the floating + * {@link ImagePathPicker}. Mirrors the legacy `imagePathAutoComplete` + * option — typically backed by a filesystem directory listing. + */ + imagePathAutoComplete?: (src: string) => Promise; /** Image upload action handler */ imageAction?: (state: IState) => Promise; } & IBaseOptions; @@ -70,6 +79,9 @@ export class ImageEditTool extends BaseFloat { /** The block containing the image */ private _block: Format | null = null; + /** Monotonic counter used to drop out-of-order imagePathAutoComplete responses */ + private _autoCompleteSeq = 0; + /** Current editing state */ private _state: IState = { alt: '', @@ -161,16 +173,131 @@ export class ImageEditTool extends BaseFloat { } /** - * Handle Enter key press to confirm changes + * Locate the floating image-path picker if it is currently open. + * Plugins are registered privately on Muya, so we resolve the instance via + * the shared `ui.shownFloat` registry (the same pattern tableColumnToolbar + * uses to find the format picker). Returns null when the picker plugin is + * not registered or not currently shown. + */ + private _getOpenImagePathPicker(): ImagePathPicker | null { + for (const tool of this.muya.ui.shownFloat) { + if (tool instanceof ImagePathPicker && tool.status) + return tool; + } + return null; + } + + /** + * Handle keydown on the src input. + * When the autocomplete picker is open, arrow keys / Tab / Enter drive the + * picker (navigate + choose) instead of confirming. Otherwise Enter + * confirms the change, mirroring the legacy `srcInputKeyDown` behavior. * @param event - Keyboard event */ - private _handleEnter(event: Event) { + private _handleSrcKeyDown(event: Event) { if (!isKeyboardEvent(event)) return; - event.stopPropagation(); - if (event.key === EVENT_KEYS.Enter) - this._handleConfirm(); + const picker = this._getOpenImagePathPicker(); + if (!picker) { + if (event.key === EVENT_KEYS.Enter) { + event.stopPropagation(); + this._handleConfirm(); + } + return; + } + + switch (event.key) { + case EVENT_KEYS.ArrowUp: + event.preventDefault(); + // Stop the editor's BaseScrollFloat keydown handler (bound on + // muya.domNode) from also stepping the picker — otherwise the + // active item advances twice per keypress. + event.stopPropagation(); + picker.step('previous'); + break; + + case EVENT_KEYS.ArrowDown: + case EVENT_KEYS.Tab: + event.preventDefault(); + event.stopPropagation(); + picker.step('next'); + break; + + case EVENT_KEYS.Enter: + event.preventDefault(); + event.stopPropagation(); + if (picker.activeItem) + picker.selectItem(picker.activeItem); + break; + + default: + break; + } + } + + /** + * Handle keyup on the src input. + * Re-queries the `imagePathAutoComplete` hook (debounced via the browser's + * natural keystroke cadence) and dispatches `muya-image-picker` so the + * floating picker refreshes its suggestions. Navigation keys are ignored so + * they don't re-trigger a fetch while the user is moving through the list. + * @param event - Keyboard event + */ + private async _handleSrcKeyUp(event: Event) { + if (!isKeyboardEvent(event) || !this.options.imagePathAutoComplete) + return; + + const { key } = event; + if ( + key === EVENT_KEYS.ArrowUp + || key === EVENT_KEYS.ArrowDown + || key === EVENT_KEYS.Tab + || (key === EVENT_KEYS.Enter + && !this._state.src.endsWith('/') + && !this._state.src.endsWith('\\')) + ) { + return; + } + + const { eventCenter } = this.muya; + const value = this._state.src; + const reference = this.container + ? query('input.src', this.container) + : null; + + // Write the chosen suggestion back into the src input. The new value is + // the directory portion of the current path plus the chosen basename, + // matching the legacy ImageSelector autocomplete UX. + const cb = (item: IImagePathSuggestion) => { + if (!reference) + return; + + const { text } = item; + // Derive the directory prefix from the CURRENT input value — the + // user may have kept typing after the suggestions were fetched, so + // the value captured on keyup can be stale. + const current = reference.value; + let basePath = ''; + const pathSep = current.match(/(?:\/|\\)[^/\\]*$/); + if (pathSep && pathSep[0]) + basePath = current.substring(0, pathSep.index! + 1); + + const newValue = basePath + text; + const len = newValue.length; + reference.value = newValue; + this._state.src = newValue; + reference.focus(); + reference.setSelectionRange(len, len); + }; + + // Guard against out-of-order resolution: if the user types again before + // a slower earlier request resolves, drop the stale response. + const seq = ++this._autoCompleteSeq; + const list = value ? await this.options.imagePathAutoComplete(value) : []; + if (seq !== this._autoCompleteSeq) + return; + eventCenter.emit('muya-image-picker', { reference, list, cb }); } /** @@ -262,6 +389,17 @@ export class ImageEditTool extends BaseFloat { } } + /** + * Hide the tool and dismiss the autocomplete picker alongside it so a + * confirm/close never leaves a dangling suggestions dropdown. + */ + override hide() { + const picker = this._getOpenImagePathPicker(); + if (picker) + picker.hide(); + super.hide(); + } + /** * Handle click on "more" button to open file picker * Updates the src input with selected path @@ -305,7 +443,8 @@ export class ImageEditTool extends BaseFloat { on: { input: event => this._handleSrcInput(event), paste: event => this._handleSrcInput(event), - keydown: event => this._handleEnter(event), + keydown: event => this._handleSrcKeyDown(event), + keyup: event => this._handleSrcKeyUp(event), }, }); diff --git a/packages/muya/src/ui/imagePicker/__tests__/imagePicker.spec.ts b/packages/muya/src/ui/imagePicker/__tests__/imagePicker.spec.ts new file mode 100644 index 0000000000..a0ad75123d --- /dev/null +++ b/packages/muya/src/ui/imagePicker/__tests__/imagePicker.spec.ts @@ -0,0 +1,130 @@ +// @vitest-environment happy-dom +import type { Muya } from '../../../muya'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { ImagePathPicker } from '..'; +import EventCenter from '../../../event'; + +// Smoke + integration tests for the ImagePathPicker floating autocomplete UI. +// +// The picker is a BaseScrollFloat subclass driven entirely by the +// `muya-image-picker` event: the ImageEditTool fetches suggestions from the +// host's `imagePathAutoComplete` hook and dispatches them here. We mock the +// slice of Muya the picker touches (eventCenter, domNode, i18n, ui) and run +// BaseFloat/BaseScrollFloat for real so the snabbdom render path is exercised +// end-to-end in happy-dom. + +function makeFakeMuya(): { muya: Muya; eventCenter: EventCenter } { + const eventCenter = new EventCenter(); + const editorDomNode = document.createElement('div'); + const editorWrapper = document.createElement('div'); + editorWrapper.appendChild(editorDomNode); + document.body.appendChild(editorWrapper); + + const shownFloat = new Set(); + // Mirror Ui.listen so `status` flips when the float shows/hides. + eventCenter.subscribe('muya-float', (tool: unknown, status: boolean) => { + status ? shownFloat.add(tool) : shownFloat.delete(tool); + }); + + const muya = { + domNode: editorDomNode, + eventCenter, + i18n: { t: (s: string) => s }, + ui: { shownFloat }, + options: {}, + } as unknown as Muya; + + return { muya, eventCenter }; +} + +function stubReference(): HTMLElement { + const input = document.createElement('input'); + // BaseFloat computes position off the reference; happy-dom has no layout, + // so a stubbed rect keeps autoUpdate from throwing. + input.getBoundingClientRect = () => + ({ top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0, toJSON: () => '' }) as DOMRect; + document.body.appendChild(input); + return input; +} + +async function nextTick() { + await new Promise(resolve => setTimeout(resolve, 0)); +} + +describe('imagePathPicker — plugin shape', () => { + it('exposes a stable static pluginName so Muya.use registers it under "imagePathPicker"', () => { + expect(ImagePathPicker.pluginName).toBe('imagePathPicker'); + }); +}); + +describe('imagePathPicker — render on muya-image-picker event', () => { + let muya: Muya; + let eventCenter: EventCenter; + let picker: ImagePathPicker; + + beforeEach(() => { + ({ muya, eventCenter } = makeFakeMuya()); + picker = new ImagePathPicker(muya); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('renders one list item per suggestion and marks the first active', async () => { + const reference = stubReference(); + const list = [ + { text: 'a.png', iconClass: 'icon-image' }, + { text: 'sub', iconClass: 'icon-folder', type: 'directory' }, + ]; + + eventCenter.emit('muya-image-picker', { reference, list, cb: () => {} }); + await nextTick(); + + const items = picker.floatBox!.querySelectorAll('li.item'); + expect(items.length).toBe(2); + expect(items[0].classList.contains('active')).toBe(true); + expect(items[0].querySelector('.text')?.textContent).toBe('a.png'); + // Icon class is rendered when supplied. + expect(items[1].querySelector('.icon-wrapper span.icon-folder')).not.toBeNull(); + expect(picker.status).toBe(true); + }); + + it('hides instead of showing when the suggestion list is empty', async () => { + const reference = stubReference(); + eventCenter.emit('muya-image-picker', { reference, list: [], cb: () => {} }); + await nextTick(); + + expect(picker.status).toBe(false); + }); + + it('invokes the selection callback with the chosen item on click', async () => { + const reference = stubReference(); + const cb = vi.fn(); + const list = [{ text: 'first.png' }, { text: 'second.png' }]; + + eventCenter.emit('muya-image-picker', { reference, list, cb }); + await nextTick(); + + const second = picker.floatBox!.querySelector('[data-index="1"]') as HTMLElement; + second.click(); + + expect(cb).toHaveBeenCalledTimes(1); + expect(cb).toHaveBeenCalledWith(list[1]); + }); + + it('moves the active item with step("next") and selects it via the active item', async () => { + const reference = stubReference(); + const cb = vi.fn(); + const list = [{ text: 'one.png' }, { text: 'two.png' }, { text: 'three.png' }]; + + eventCenter.emit('muya-image-picker', { reference, list, cb }); + await nextTick(); + + picker.step('next'); + expect(picker.activeItem).toBe(list[1]); + + picker.selectItem(picker.activeItem); + expect(cb).toHaveBeenCalledWith(list[1]); + }); +}); diff --git a/packages/muya/src/ui/imagePicker/index.css b/packages/muya/src/ui/imagePicker/index.css new file mode 100644 index 0000000000..9cd40f6684 --- /dev/null +++ b/packages/muya/src/ui/imagePicker/index.css @@ -0,0 +1,64 @@ +.mu-image-picker-wrapper { + z-index: 100000; +} + +.mu-image-picker-wrapper .mu-list-picker { + box-sizing: border-box; + width: 450px; + max-height: 156px; + padding: 8px 0; + overflow-y: auto; + + font-size: 14px; +} + +.mu-image-picker-wrapper .mu-list-picker ul, +.mu-image-picker-wrapper .mu-list-picker li { + margin: 0; + padding: 0; +} + +.mu-image-picker-wrapper .mu-list-picker .item { + display: flex; + align-items: center; + + /* override codeBlockLanguageSelector's global `.mu-list-picker .item` + justify-content: space-between, which would push icon/text apart */ + justify-content: flex-start; + height: 28px; + padding: 0 10px; + + list-style: none; +} + +.mu-image-picker-wrapper .mu-list-picker:hover .active { + background: transparent; +} + +.mu-image-picker-wrapper .mu-list-picker .item:hover, +.mu-image-picker-wrapper .mu-list-picker .item.active { + background-color: var(--float-hover-color); +} + +.mu-image-picker-wrapper .mu-list-picker .item .icon-wrapper { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; +} + +.mu-image-picker-wrapper .mu-list-picker .item .icon-wrapper span::before { + font-size: 14px; +} + +.mu-image-picker-wrapper .mu-list-picker .item .text { + flex: 1; + overflow: hidden; + + color: var(--editor-color); + font-size: 14px; + line-height: 28px; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/packages/muya/src/ui/imagePicker/index.ts b/packages/muya/src/ui/imagePicker/index.ts new file mode 100644 index 0000000000..e983fe89f8 --- /dev/null +++ b/packages/muya/src/ui/imagePicker/index.ts @@ -0,0 +1,134 @@ +import type { VNode } from 'snabbdom'; +import type { Muya } from '../../index'; +import { query } from '../../utils/dom'; + +import { h, patch } from '../../utils/snabbdom'; +import BaseScrollFloat from '../baseScrollFloat'; + +import './index.css'; + +/** + * A single path suggestion produced by the `imagePathAutoComplete` hook. + * `text` is the basename rendered (and written back into the src input); + * `iconClass` selects a font-icon class, `type` distinguishes files from + * directories. Extra keys are tolerated so callers can carry metadata. + */ +export interface IImagePathSuggestion { + text: string; + iconClass?: string; + type?: string; + [key: string]: unknown; +} + +/** Payload of the `muya-image-picker` event the ImageEditTool dispatches. */ +interface IImagePickerEvent { + reference: HTMLElement | null; + list: IImagePathSuggestion[]; + cb: (item: IImagePathSuggestion) => void; +} + +const defaultOptions = { + placement: 'bottom-start' as const, + offsetOptions: { + mainAxis: 0, + crossAxis: 0, + alignmentAxis: 0, + }, + showArrow: false, +}; + +/** + * Floating autocomplete dropdown that suggests local image file paths as the + * user edits an image's `src` in the {@link ImageEditTool}. It is a faithful + * port of the legacy `packages/muyajs/lib/ui/imagePicker` plugin: it listens + * for the `muya-image-picker` event, renders a scrollable filtered list, and + * supports arrow-key navigation plus Enter/click to choose. The chosen path is + * written back through the callback supplied in the event payload. + * + * The list itself is produced by the host application via the + * `imagePathAutoComplete` option on the ImageEditTool — muya only renders the + * result and reports the selection. + */ +export class ImagePathPicker extends BaseScrollFloat { + static pluginName = 'imagePathPicker'; + + private _oldVNode: VNode | null = null; + public override renderArray: IImagePathSuggestion[] = []; + public override activeItem: IImagePathSuggestion | null = null; + + constructor(muya: Muya, options = {}) { + const name = 'mu-list-picker'; + const opts = Object.assign({}, defaultOptions, options); + super(muya, name, opts); + this.floatBox!.classList.add('mu-image-picker-wrapper'); + this.listen(); + } + + override listen() { + super.listen(); + const { eventCenter } = this.muya; + eventCenter.on('muya-image-picker', ({ reference, list, cb }: IImagePickerEvent) => { + if (reference && list.length) { + this.show(reference, cb); + this.renderArray = list; + this.activeItem = list[0]; + this.render(); + } + else { + this.hide(); + } + }); + } + + render() { + const { renderArray, _oldVNode: oldVNode, scrollElement, activeItem } = this; + const children = renderArray.map((item, index) => { + const { text, iconClass } = item; + // Icons are font-icon classes (parity placeholder for the legacy + // inline SVGs — see PR notes). When the host omits `iconClass` we + // simply render the text without an icon. + const iconContent = iconClass + ? [h('div.icon-wrapper', h(`span.${iconClass}`))] + : []; + const textEle = h('div.text', text); + const selector = activeItem === item ? 'li.item.active' : 'li.item'; + + return h( + selector, + { + // Index-based lookup — file names can contain quotes/brackets + // (unsafe in an attribute selector) and duplicate basenames + // would otherwise collide on a text-based attribute. + dataset: { + index: String(index), + }, + on: { + click: () => { + this.selectItem(item); + }, + }, + }, + [...iconContent, textEle], + ); + }); + + const vnode = h('ul', children); + + if (oldVNode) + patch(oldVNode, vnode); + else + patch(scrollElement!, vnode); + + this._oldVNode = vnode; + } + + getItemElement(item: IImagePathSuggestion): HTMLElement | null { + const index = this.renderArray.indexOf(item); + if (index < 0) + return null; + + return query(`[data-index="${index}"]`, this.floatBox!); + } +} + +export default ImagePathPicker; diff --git a/packages/muya/src/ui/paragraphFrontButton/config.ts b/packages/muya/src/ui/paragraphFrontButton/config.ts index f8af8b57a6..7970c74199 100644 --- a/packages/muya/src/ui/paragraphFrontButton/config.ts +++ b/packages/muya/src/ui/paragraphFrontButton/config.ts @@ -5,6 +5,7 @@ import type DiagramBlock from '../../block/extra/diagram'; import bulletListIcon from '../../assets/icons/bullet_list/2.png'; import vegaIcon from '../../assets/icons/chart/2.png'; import codeIcon from '../../assets/icons/code/2.png'; +import flowchartIcon from '../../assets/icons/flowchart/2.png'; import footnoteIcon from '../../assets/icons/footnote/2.png'; import frontMatterIcon from '../../assets/icons/front_matter/2.png'; import header1Icon from '../../assets/icons/heading_1/2.png'; @@ -22,6 +23,7 @@ import orderListIcon from '../../assets/icons/order_list/2.png'; import paragraphIcon from '../../assets/icons/paragraph/2.png'; import plantumlIcon from '../../assets/icons/plantuml/2.png'; import quoteIcon from '../../assets/icons/quote_block/2.png'; +import sequenceIcon from '../../assets/icons/sequence/2.png'; import taskListIcon from '../../assets/icons/todolist/2.png'; const HEADING_ICONS = [ @@ -37,6 +39,8 @@ const DIAGRAM_ICONS = { 'plantuml': plantumlIcon, 'mermaid': mermaidIcon, 'vega-lite': vegaIcon, + 'flowchart': flowchartIcon, + 'sequence': sequenceIcon, }; export function getIcon(block: Parent) { diff --git a/packages/muya/src/ui/paragraphQuickInsertMenu/__tests__/diagramMenuEntries.spec.ts b/packages/muya/src/ui/paragraphQuickInsertMenu/__tests__/diagramMenuEntries.spec.ts new file mode 100644 index 0000000000..f832fd3419 --- /dev/null +++ b/packages/muya/src/ui/paragraphQuickInsertMenu/__tests__/diagramMenuEntries.spec.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { MENU_CONFIG } from '../config'; + +// Lock the quick-insert menu's diagram section to all five supported diagram +// engines. flowchart + sequence were restored for parity with the legacy +// muyajs engine; a future refactor of the menu config shouldn't silently drop +// them. +describe('quick-insert menu — diagram entries', () => { + const diagramSection = MENU_CONFIG.find(section => section.name === 'diagrams'); + + it('has a diagrams section', () => { + expect(diagramSection).toBeDefined(); + }); + + it('exposes flowchart and sequence insert entries', () => { + const labels = diagramSection!.children.map(child => child.label); + expect(labels).toContain('diagram flowchart'); + expect(labels).toContain('diagram sequence'); + }); + + it('keeps the existing mermaid / plantuml / vega-lite entries', () => { + const labels = diagramSection!.children.map(child => child.label); + expect(labels).toContain('diagram mermaid'); + expect(labels).toContain('diagram plantuml'); + expect(labels).toContain('diagram vega-lite'); + }); + + it('gives every diagram entry an icon', () => { + for (const child of diagramSection!.children) + expect(child.icon, `${child.label} should have an icon`).toBeTruthy(); + }); +}); diff --git a/packages/muya/src/ui/paragraphQuickInsertMenu/config.ts b/packages/muya/src/ui/paragraphQuickInsertMenu/config.ts index 143d950546..2945f183ae 100644 --- a/packages/muya/src/ui/paragraphQuickInsertMenu/config.ts +++ b/packages/muya/src/ui/paragraphQuickInsertMenu/config.ts @@ -3,6 +3,7 @@ import type { Muya } from '../../index'; import bulletListIcon from '../../assets/icons/bullet_list/2.png'; import vegaIcon from '../../assets/icons/chart/2.png'; import codeIcon from '../../assets/icons/code/2.png'; +import flowchartIcon from '../../assets/icons/flowchart/2.png'; import frontMatterIcon from '../../assets/icons/front_matter/2.png'; import header1Icon from '../../assets/icons/heading_1/2.png'; import header2Icon from '../../assets/icons/heading_2/2.png'; @@ -19,6 +20,7 @@ import orderListIcon from '../../assets/icons/order_list/2.png'; import paragraphIcon from '../../assets/icons/paragraph/2.png'; import plantumlIcon from '../../assets/icons/plantuml/2.png'; import quoteIcon from '../../assets/icons/quote_block/2.png'; +import sequenceIcon from '../../assets/icons/sequence/2.png'; import todoListIcon from '../../assets/icons/todolist/2.png'; import { ScrollPage } from '../../block/scrollPage'; @@ -326,6 +328,18 @@ export const MENU_CONFIG: IQuickInsertMenuItem[] = [ label: 'diagram plantuml', icon: plantumlIcon, }, + { + title: 'Flowchart', + subTitle: 'By flowchart.js', + label: 'diagram flowchart', + icon: flowchartIcon, + }, + { + title: 'Sequence', + subTitle: 'By js-sequence-diagrams', + label: 'diagram sequence', + icon: sequenceIcon, + }, ], }, ]; @@ -460,11 +474,21 @@ export function replaceBlockByLabel({ block, muya, label, text = '' }: { // fall through case 'diagram mermaid': // fall through - case 'diagram plantuml': { + case 'diagram plantuml': + // fall through + case 'diagram flowchart': + // fall through + case 'diagram sequence': { const diagramState = deepClone(emptyStates.diagram); const [name, type] = label.split(' '); - if (type === 'mermaid' || type === 'plantuml' || type === 'vega-lite') { + if ( + type === 'mermaid' + || type === 'plantuml' + || type === 'vega-lite' + || type === 'flowchart' + || type === 'sequence' + ) { diagramState.meta.type = type; diagramState.meta.lang = type === 'vega-lite' ? 'json' : 'yaml'; } diff --git a/packages/muya/src/utils/__tests__/getCopyTextType.spec.ts b/packages/muya/src/utils/__tests__/getCopyTextType.spec.ts index 8a78f64ade..b03389db9a 100644 --- a/packages/muya/src/utils/__tests__/getCopyTextType.spec.ts +++ b/packages/muya/src/utils/__tests__/getCopyTextType.spec.ts @@ -1,5 +1,5 @@ -import { describe, expect, it } from 'vitest'; -import { getCopyTextType, isStandaloneTableHtml } from '../paste'; +import { describe, expect, it, vi } from 'vitest'; +import { getCopyTextType, isStandaloneTableHtml, resolveClipboardImagePath } from '../paste'; // Regression for marktext commit 067ec485 (#1271). // Some clipboard sources (e.g. Apple Numbers, certain spreadsheet @@ -69,3 +69,53 @@ describe('getCopyTextType — pre-existing classifier behaviour stays put', () = ).toBe('code'); }); }); + +// Ported from the legacy `@muyajs` `clipboardFilePath` paste hook: on paste, +// the embedder may resolve the OS clipboard to a local file path. Only a +// non-empty, image-extension path should short-circuit the normal text/HTML +// paste and be inserted as an inline image. +describe('resolveClipboardImagePath', () => { + it('returns "" when no hook is provided', async () => { + expect(await resolveClipboardImagePath(undefined)).toBe(''); + }); + + it('returns the path when the hook resolves an image file', async () => { + const hook = vi.fn().mockResolvedValue('/tmp/screenshot.png'); + expect(await resolveClipboardImagePath(hook)).toBe('/tmp/screenshot.png'); + expect(hook).toHaveBeenCalledOnce(); + }); + + it('accepts every supported image extension (case-insensitive)', async () => { + for (const path of [ + '/a/b.JPG', + '/a/b.jpeg', + '/a/b.png', + '/a/b.gif', + '/a/b.svg', + '/a/b.webp', + ]) { + expect(await resolveClipboardImagePath(() => Promise.resolve(path))).toBe( + path, + ); + } + }); + + it('tolerates a query string after the extension', async () => { + expect( + await resolveClipboardImagePath(() => Promise.resolve('/a/b.png?x=1')), + ).toBe('/a/b.png?x=1'); + }); + + it('returns "" when the hook resolves an empty string', async () => { + expect(await resolveClipboardImagePath(() => Promise.resolve(''))).toBe(''); + }); + + it('returns "" when the resolved path is not an image', async () => { + expect( + await resolveClipboardImagePath(() => Promise.resolve('/a/b.txt')), + ).toBe(''); + expect( + await resolveClipboardImagePath(() => Promise.resolve('/a/b.pdf')), + ).toBe(''); + }); +}); diff --git a/packages/muya/src/utils/diagram/index.ts b/packages/muya/src/utils/diagram/index.ts index 0631a63f7c..f464814e11 100644 --- a/packages/muya/src/utils/diagram/index.ts +++ b/packages/muya/src/utils/diagram/index.ts @@ -1,7 +1,7 @@ const rendererCache = new Map(); /** * - * @param {string} name the renderer name:plantuml, mermaid, vega-lite + * @param {string} name the renderer name: plantuml, mermaid, vega-lite, flowchart, sequence */ async function loadRenderer(name: string) { if (!rendererCache.has(name)) { @@ -22,6 +22,16 @@ async function loadRenderer(name: string) { rendererCache.set(name, m.default); break; + case 'flowchart': + m = await import('flowchart.js'); + rendererCache.set(name, m.default); + break; + + case 'sequence': + m = await import('./sequence'); + rendererCache.set(name, m.default); + break; + default: throw new Error(`Unknown diagram name ${name}`); } diff --git a/packages/muya/src/utils/diagram/sequence/danielbd.woff b/packages/muya/src/utils/diagram/sequence/danielbd.woff new file mode 100755 index 0000000000..aa01f17b93 Binary files /dev/null and b/packages/muya/src/utils/diagram/sequence/danielbd.woff differ diff --git a/packages/muya/src/utils/diagram/sequence/danielbd.woff2 b/packages/muya/src/utils/diagram/sequence/danielbd.woff2 new file mode 100755 index 0000000000..15ceb49120 Binary files /dev/null and b/packages/muya/src/utils/diagram/sequence/danielbd.woff2 differ diff --git a/packages/muya/src/utils/diagram/sequence/index.ts b/packages/muya/src/utils/diagram/sequence/index.ts new file mode 100644 index 0000000000..66d2edaced --- /dev/null +++ b/packages/muya/src/utils/diagram/sequence/index.ts @@ -0,0 +1,9 @@ +import Diagram from './sequence-diagram-snap'; +import './sequence-diagram.css'; + +// Vendored `js-sequence-diagrams` (bramp, BSD) wired to render via snap.svg. +// The upstream `js-sequence-diagrams` npm package is a dead security-holder +// placeholder, so the legacy muyajs engine vendored the library source. We do +// the same here to keep feature parity. The exported `Diagram` exposes +// `parse(code)` returning an object with `drawSVG(container, { theme })`. +export default Diagram; diff --git a/packages/muya/src/utils/diagram/sequence/sequence-diagram-snap.js b/packages/muya/src/utils/diagram/sequence/sequence-diagram-snap.js new file mode 100644 index 0000000000..bd721fd575 --- /dev/null +++ b/packages/muya/src/utils/diagram/sequence/sequence-diagram-snap.js @@ -0,0 +1,1871 @@ +/** js sequence diagrams 2.0.1 + * https://bramp.github.io/js-sequence-diagrams/ + * (c) 2012-2017 Andrew Brampton (bramp.net) + * @license Simplified BSD license. + */ +import _ from 'underscore' +import Snap from 'snapsvg-cjs' +import WebFont from 'webfontloader' + +function Diagram() { + this.title = undefined + this.actors = [] + this.signals = [] +} +/* + * Return an existing actor with this alias, or creates a new one with alias and name. + */ +Diagram.prototype.getActor = function (alias, name) { + alias = alias.trim() + + var i + var actors = this.actors + for (i in actors) { + if (actors[i].alias == alias) { + return actors[i] + } + } + i = actors.push(new Diagram.Actor(alias, name || alias, actors.length)) + return actors[i - 1] +} + +/* + * Parses the input as either a alias, or a "name as alias", and returns the corresponding actor. + */ +Diagram.prototype.getActorWithAlias = function (input) { + input = input.trim() + + // We are lazy and do some of the parsing in javascript :(. TODO move into the .jison file. + var s = /([\s\S]+) as (\S+)$/im.exec(input) + var alias + var name + if (s) { + name = s[1].trim() + alias = s[2].trim() + } else { + name = alias = input + } + return this.getActor(alias, name) +} + +Diagram.prototype.setTitle = function (title) { + this.title = title +} + +Diagram.prototype.addSignal = function (signal) { + this.signals.push(signal) +} + +Diagram.Actor = function (alias, name, index) { + this.alias = alias + this.name = name + this.index = index +} + +Diagram.Signal = function (actorA, signaltype, actorB, message) { + this.type = 'Signal' + this.actorA = actorA + this.actorB = actorB + this.linetype = signaltype & 3 + this.arrowtype = (signaltype >> 2) & 3 + this.message = message +} + +Diagram.Signal.prototype.isSelf = function () { + return this.actorA.index == this.actorB.index +} + +Diagram.Note = function (actor, placement, message) { + this.type = 'Note' + this.actor = actor + this.placement = placement + this.message = message + + if (this.hasManyActors() && actor[0] == actor[1]) { + throw new Error('Note should be over two different actors') + } +} + +Diagram.Note.prototype.hasManyActors = function () { + return _.isArray(this.actor) +} + +Diagram.unescape = function (s) { + // Turn "\\n" into "\n" + return s + .trim() + .replace(/^"(.*)"$/m, '$1') + .replace(/\\n/gm, '\n') +} + +Diagram.LINETYPE = { + SOLID: 0, + DOTTED: 1 +} + +Diagram.ARROWTYPE = { + FILLED: 0, + OPEN: 1 +} + +Diagram.PLACEMENT = { + LEFTOF: 0, + RIGHTOF: 1, + OVER: 2 +} + +// Some older browsers don't have getPrototypeOf, thus we polyfill it +// https://github.com/bramp/js-sequence-diagrams/issues/57 +// https://github.com/zaach/jison/issues/194 +// Taken from http://ejohn.org/blog/objectgetprototypeof/ +if (typeof Object.getPrototypeOf !== 'function') { + /* jshint -W103 */ + if (typeof 'test'.__proto__ === 'object') { + Object.getPrototypeOf = function (object) { + return object.__proto__ + } + } else { + Object.getPrototypeOf = function (object) { + // May break if the constructor has been tampered with + return object.constructor.prototype + } + } + /* jshint +W103 */ +} + +/** The following is included by preprocessor */ +/* parser generated by jison 0.4.15 */ +/* + Returns a Parser object of the following structure: + Parser: { + yy: {} + } + Parser.prototype: { + yy: {}, + trace: function(), + symbols_: {associative list: name ==> number}, + terminals_: {associative list: number ==> name}, + productions_: [...], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), + table: [...], + defaultActions: {...}, + parseError: function(str, hash), + parse: function(input), + lexer: { + EOF: 1, + parseError: function(str, hash), + setInput: function(input), + input: function(), + unput: function(str), + more: function(), + less: function(n), + pastInput: function(), + upcomingInput: function(), + showPosition: function(), + test_match: function(regex_match_array, rule_index), + next: function(), + lex: function(), + begin: function(condition), + popState: function(), + _currentRules: function(), + topState: function(), + pushState: function(condition), + options: { + ranges: boolean (optional: true ==> token location info will include a .range[] member) + flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) + backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + }, + performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), + rules: [...], + conditions: {associative list: name ==> set}, + } + } + token location info (@$, _$, etc.): { + first_line: n, + last_line: n, + first_column: n, + last_column: n, + range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) + } + the parseError function receives a 'hash' object with these members for lexer and parser errors: { + text: (matched text) + token: (the produced terminal token, if any) + line: (yylineno) + } + while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { + loc: (yylloc) + expected: (string describing the set of expected tokens) + recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + } +*/ +var parser = (function () { + function Parser() { + this.yy = {} + } + var o = function (k, v, o, l) { + for (o = o || {}, l = k.length; l--; o[k[l]] = v); + return o + }, + $V0 = [5, 8, 9, 13, 15, 24], + $V1 = [1, 13], + $V2 = [1, 17], + $V3 = [24, 29, 30], + parser = { + trace: function () {}, + yy: {}, + symbols_: { + error: 2, + start: 3, + document: 4, + EOF: 5, + line: 6, + statement: 7, + NL: 8, + participant: 9, + actor_alias: 10, + signal: 11, + note_statement: 12, + title: 13, + message: 14, + note: 15, + placement: 16, + actor: 17, + over: 18, + actor_pair: 19, + ',': 20, + left_of: 21, + right_of: 22, + signaltype: 23, + ACTOR: 24, + linetype: 25, + arrowtype: 26, + LINE: 27, + DOTLINE: 28, + ARROW: 29, + OPENARROW: 30, + MESSAGE: 31, + $accept: 0, + $end: 1 + }, + terminals_: { + 2: 'error', + 5: 'EOF', + 8: 'NL', + 9: 'participant', + 13: 'title', + 15: 'note', + 18: 'over', + 20: ',', + 21: 'left_of', + 22: 'right_of', + 24: 'ACTOR', + 27: 'LINE', + 28: 'DOTLINE', + 29: 'ARROW', + 30: 'OPENARROW', + 31: 'MESSAGE' + }, + productions_: [ + 0, + [3, 2], + [4, 0], + [4, 2], + [6, 1], + [6, 1], + [7, 2], + [7, 1], + [7, 1], + [7, 2], + [12, 4], + [12, 4], + [19, 1], + [19, 3], + [16, 1], + [16, 1], + [11, 4], + [17, 1], + [10, 1], + [23, 2], + [23, 1], + [25, 1], + [25, 1], + [26, 1], + [26, 1], + [14, 1] + ], + performAction: function (yytext, yyleng, yylineno, yy, yystate, $$, _$) { + /* this == yyval */ + var $0 = $$.length - 1 + switch (yystate) { + case 1: + return yy.parser.yy + + case 4: + break + + case 6: + $$[$0] + break + + case 7: + case 8: + yy.parser.yy.addSignal($$[$0]) + break + + case 9: + yy.parser.yy.setTitle($$[$0]) + break + + case 10: + this.$ = new Diagram.Note($$[$0 - 1], $$[$0 - 2], $$[$0]) + break + + case 11: + this.$ = new Diagram.Note($$[$0 - 1], Diagram.PLACEMENT.OVER, $$[$0]) + break + + case 12: + case 20: + this.$ = $$[$0] + break + + case 13: + this.$ = [$$[$0 - 2], $$[$0]] + break + + case 14: + this.$ = Diagram.PLACEMENT.LEFTOF + break + + case 15: + this.$ = Diagram.PLACEMENT.RIGHTOF + break + + case 16: + this.$ = new Diagram.Signal($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0]) + break + + case 17: + this.$ = yy.parser.yy.getActor(Diagram.unescape($$[$0])) + break + + case 18: + this.$ = yy.parser.yy.getActorWithAlias(Diagram.unescape($$[$0])) + break + + case 19: + this.$ = $$[$0 - 1] | ($$[$0] << 2) + break + + case 21: + this.$ = Diagram.LINETYPE.SOLID + break + + case 22: + this.$ = Diagram.LINETYPE.DOTTED + break + + case 23: + this.$ = Diagram.ARROWTYPE.FILLED + break + + case 24: + this.$ = Diagram.ARROWTYPE.OPEN + break + + case 25: + this.$ = Diagram.unescape($$[$0].substring(1)) + } + }, + table: [ + o($V0, [2, 2], { + 3: 1, + 4: 2 + }), + { + 1: [3] + }, + { + 5: [1, 3], + 6: 4, + 7: 5, + 8: [1, 6], + 9: [1, 7], + 11: 8, + 12: 9, + 13: [1, 10], + 15: [1, 12], + 17: 11, + 24: $V1 + }, + { + 1: [2, 1] + }, + o($V0, [2, 3]), + o($V0, [2, 4]), + o($V0, [2, 5]), + { + 10: 14, + 24: [1, 15] + }, + o($V0, [2, 7]), + o($V0, [2, 8]), + { + 14: 16, + 31: $V2 + }, + { + 23: 18, + 25: 19, + 27: [1, 20], + 28: [1, 21] + }, + { + 16: 22, + 18: [1, 23], + 21: [1, 24], + 22: [1, 25] + }, + o([20, 27, 28, 31], [2, 17]), + o($V0, [2, 6]), + o($V0, [2, 18]), + o($V0, [2, 9]), + o($V0, [2, 25]), + { + 17: 26, + 24: $V1 + }, + { + 24: [2, 20], + 26: 27, + 29: [1, 28], + 30: [1, 29] + }, + o($V3, [2, 21]), + o($V3, [2, 22]), + { + 17: 30, + 24: $V1 + }, + { + 17: 32, + 19: 31, + 24: $V1 + }, + { + 24: [2, 14] + }, + { + 24: [2, 15] + }, + { + 14: 33, + 31: $V2 + }, + { + 24: [2, 19] + }, + { + 24: [2, 23] + }, + { + 24: [2, 24] + }, + { + 14: 34, + 31: $V2 + }, + { + 14: 35, + 31: $V2 + }, + { + 20: [1, 36], + 31: [2, 12] + }, + o($V0, [2, 16]), + o($V0, [2, 10]), + o($V0, [2, 11]), + { + 17: 37, + 24: $V1 + }, + { + 31: [2, 13] + } + ], + defaultActions: { + 3: [2, 1], + 24: [2, 14], + 25: [2, 15], + 27: [2, 19], + 28: [2, 23], + 29: [2, 24], + 37: [2, 13] + }, + parseError: function (str, hash) { + if (!hash.recoverable) throw new Error(str) + this.trace(str) + }, + parse: function (input) { + function lex() { + var token + return ( + (token = lexer.lex() || EOF), + 'number' != typeof token && (token = self.symbols_[token] || token), + token + ) + } + var self = this, + stack = [0], + vstack = [null], + lstack = [], + table = this.table, + yytext = '', + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1, + args = lstack.slice.call(arguments, 1), + lexer = Object.create(this.lexer), + sharedState = { + yy: {} + } + for (var k in this.yy) + Object.prototype.hasOwnProperty.call(this.yy, k) && (sharedState.yy[k] = this.yy[k]) + lexer.setInput(input, sharedState.yy), + (sharedState.yy.lexer = lexer), + (sharedState.yy.parser = this), + 'undefined' == typeof lexer.yylloc && (lexer.yylloc = {}) + var yyloc = lexer.yylloc + lstack.push(yyloc) + var ranges = lexer.options && lexer.options.ranges + 'function' == typeof sharedState.yy.parseError + ? (this.parseError = sharedState.yy.parseError) + : (this.parseError = Object.getPrototypeOf(this).parseError) + for ( + var symbol, preErrorSymbol, state, action, r, p, len, newState, expected, yyval = {}; + ; + + ) { + if ( + ((state = stack[stack.length - 1]), + this.defaultActions[state] + ? (action = this.defaultActions[state]) + : ((null !== symbol && 'undefined' != typeof symbol) || (symbol = lex()), + (action = table[state] && table[state][symbol])), + 'undefined' == typeof action || !action.length || !action[0]) + ) { + var errStr = '' + expected = [] + for (p in table[state]) + this.terminals_[p] && p > TERROR && expected.push("'" + this.terminals_[p] + "'") + ;(errStr = lexer.showPosition + ? 'Parse error on line ' + + (yylineno + 1) + + ':\n' + + lexer.showPosition() + + '\nExpecting ' + + expected.join(', ') + + ", got '" + + (this.terminals_[symbol] || symbol) + + "'" + : 'Parse error on line ' + + (yylineno + 1) + + ': Unexpected ' + + (symbol == EOF ? 'end of input' : "'" + (this.terminals_[symbol] || symbol) + "'")), + this.parseError(errStr, { + text: lexer.match, + token: this.terminals_[symbol] || symbol, + line: lexer.yylineno, + loc: yyloc, + expected: expected + }) + } + if (action[0] instanceof Array && action.length > 1) + throw new Error( + 'Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol + ) + switch (action[0]) { + case 1: + stack.push(symbol), + vstack.push(lexer.yytext), + lstack.push(lexer.yylloc), + stack.push(action[1]), + (symbol = null), + preErrorSymbol + ? ((symbol = preErrorSymbol), (preErrorSymbol = null)) + : ((yyleng = lexer.yyleng), + (yytext = lexer.yytext), + (yylineno = lexer.yylineno), + (yyloc = lexer.yylloc), + recovering > 0 && recovering--) + break + + case 2: + if ( + ((len = this.productions_[action[1]][1]), + (yyval.$ = vstack[vstack.length - len]), + (yyval._$ = { + first_line: lstack[lstack.length - (len || 1)].first_line, + last_line: lstack[lstack.length - 1].last_line, + first_column: lstack[lstack.length - (len || 1)].first_column, + last_column: lstack[lstack.length - 1].last_column + }), + ranges && + (yyval._$.range = [ + lstack[lstack.length - (len || 1)].range[0], + lstack[lstack.length - 1].range[1] + ]), + (r = this.performAction.apply( + yyval, + [yytext, yyleng, yylineno, sharedState.yy, action[1], vstack, lstack].concat(args) + )), + 'undefined' != typeof r) + ) + return r + len && + ((stack = stack.slice(0, -1 * len * 2)), + (vstack = vstack.slice(0, -1 * len)), + (lstack = lstack.slice(0, -1 * len))), + stack.push(this.productions_[action[1]][0]), + vstack.push(yyval.$), + lstack.push(yyval._$), + (newState = table[stack[stack.length - 2]][stack[stack.length - 1]]), + stack.push(newState) + break + + case 3: + return !0 + } + } + return !0 + } + }, + lexer = (function () { + var lexer = { + EOF: 1, + parseError: function (str, hash) { + if (!this.yy.parser) throw new Error(str) + this.yy.parser.parseError(str, hash) + }, + // resets the lexer, sets new input + setInput: function (input, yy) { + return ( + (this.yy = yy || this.yy || {}), + (this._input = input), + (this._more = this._backtrack = this.done = !1), + (this.yylineno = this.yyleng = 0), + (this.yytext = this.matched = this.match = ''), + (this.conditionStack = ['INITIAL']), + (this.yylloc = { + first_line: 1, + first_column: 0, + last_line: 1, + last_column: 0 + }), + this.options.ranges && (this.yylloc.range = [0, 0]), + (this.offset = 0), + this + ) + }, + // consumes and returns one char from the input + input: function () { + var ch = this._input[0] + ;(this.yytext += ch), + this.yyleng++, + this.offset++, + (this.match += ch), + (this.matched += ch) + var lines = ch.match(/(?:\r\n?|\n).*/g) + return ( + lines ? (this.yylineno++, this.yylloc.last_line++) : this.yylloc.last_column++, + this.options.ranges && this.yylloc.range[1]++, + (this._input = this._input.slice(1)), + ch + ) + }, + // unshifts one char (or a string) into the input + unput: function (ch) { + var len = ch.length, + lines = ch.split(/(?:\r\n?|\n)/g) + ;(this._input = ch + this._input), + (this.yytext = this.yytext.substr(0, this.yytext.length - len)), + //this.yyleng -= len; + (this.offset -= len) + var oldLines = this.match.split(/(?:\r\n?|\n)/g) + ;(this.match = this.match.substr(0, this.match.length - 1)), + (this.matched = this.matched.substr(0, this.matched.length - 1)), + lines.length - 1 && (this.yylineno -= lines.length - 1) + var r = this.yylloc.range + return ( + (this.yylloc = { + first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines + ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + + oldLines[oldLines.length - lines.length].length - + lines[0].length + : this.yylloc.first_column - len + }), + this.options.ranges && (this.yylloc.range = [r[0], r[0] + this.yyleng - len]), + (this.yyleng = this.yytext.length), + this + ) + }, + // When called from action, caches matched text and appends it on next action + more: function () { + return (this._more = !0), this + }, + // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. + reject: function () { + return this.options.backtrack_lexer + ? ((this._backtrack = !0), this) + : this.parseError( + 'Lexical error on line ' + + (this.yylineno + 1) + + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + + this.showPosition(), + { + text: '', + token: null, + line: this.yylineno + } + ) + }, + // retain first n characters of the match + less: function (n) { + this.unput(this.match.slice(n)) + }, + // displays already matched input, i.e. for error messages + pastInput: function () { + var past = this.matched.substr(0, this.matched.length - this.match.length) + return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, '') + }, + // displays upcoming input, i.e. for error messages + upcomingInput: function () { + var next = this.match + return ( + next.length < 20 && (next += this._input.substr(0, 20 - next.length)), + (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, '') + ) + }, + // displays the character position where the lexing error occurred, i.e. for error messages + showPosition: function () { + var pre = this.pastInput(), + c = new Array(pre.length + 1).join('-') + return pre + this.upcomingInput() + '\n' + c + '^' + }, + // test the lexed token: return FALSE when not a match, otherwise return token + test_match: function (match, indexed_rule) { + var token, lines, backup + if ( + (this.options.backtrack_lexer && // save context + ((backup = { + yylineno: this.yylineno, + yylloc: { + first_line: this.yylloc.first_line, + last_line: this.last_line, + first_column: this.yylloc.first_column, + last_column: this.yylloc.last_column + }, + yytext: this.yytext, + match: this.match, + matches: this.matches, + matched: this.matched, + yyleng: this.yyleng, + offset: this.offset, + _more: this._more, + _input: this._input, + yy: this.yy, + conditionStack: this.conditionStack.slice(0), + done: this.done + }), + this.options.ranges && (backup.yylloc.range = this.yylloc.range.slice(0))), + (lines = match[0].match(/(?:\r\n?|\n).*/g)), + lines && (this.yylineno += lines.length), + (this.yylloc = { + first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines + ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length + : this.yylloc.last_column + match[0].length + }), + (this.yytext += match[0]), + (this.match += match[0]), + (this.matches = match), + (this.yyleng = this.yytext.length), + this.options.ranges && + (this.yylloc.range = [this.offset, (this.offset += this.yyleng)]), + (this._more = !1), + (this._backtrack = !1), + (this._input = this._input.slice(match[0].length)), + (this.matched += match[0]), + (token = this.performAction.call( + this, + this.yy, + this, + indexed_rule, + this.conditionStack[this.conditionStack.length - 1] + )), + this.done && this._input && (this.done = !1), + token) + ) + return token + if (this._backtrack) { + // recover context + for (var k in backup) this[k] = backup[k] + return !1 + } + return !1 + }, + // return next match in input + next: function () { + if (this.done) return this.EOF + this._input || (this.done = !0) + var token, match, tempMatch, index + this._more || ((this.yytext = ''), (this.match = '')) + for (var rules = this._currentRules(), i = 0; i < rules.length; i++) + if ( + ((tempMatch = this._input.match(this.rules[rules[i]])), + tempMatch && (!match || tempMatch[0].length > match[0].length)) + ) { + if (((match = tempMatch), (index = i), this.options.backtrack_lexer)) { + if (((token = this.test_match(tempMatch, rules[i])), token !== !1)) return token + if (this._backtrack) { + match = !1 + continue + } + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return !1 + } + if (!this.options.flex) break + } + return match + ? ((token = this.test_match(match, rules[index])), token !== !1 && token) + : '' === this._input + ? this.EOF + : this.parseError( + 'Lexical error on line ' + + (this.yylineno + 1) + + '. Unrecognized text.\n' + + this.showPosition(), + { + text: '', + token: null, + line: this.yylineno + } + ) + }, + // return next match that has a token + lex: function () { + var r = this.next() + return r ? r : this.lex() + }, + // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) + begin: function (condition) { + this.conditionStack.push(condition) + }, + // pop the previously active lexer condition state off the condition stack + popState: function () { + var n = this.conditionStack.length - 1 + return n > 0 ? this.conditionStack.pop() : this.conditionStack[0] + }, + // produce the lexer rule set which is active for the currently active lexer condition state + _currentRules: function () { + return this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1] + ? this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules + : this.conditions.INITIAL.rules + }, + // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available + topState: function (n) { + return ( + (n = this.conditionStack.length - 1 - Math.abs(n || 0)), + n >= 0 ? this.conditionStack[n] : 'INITIAL' + ) + }, + // alias for begin(condition) + pushState: function (condition) { + this.begin(condition) + }, + // return the number of states currently on the stack + stateStackSize: function () { + return this.conditionStack.length + }, + options: { + 'case-insensitive': !0 + }, + performAction: function (yy, yy_, $avoiding_name_collisions, YY_START) { + switch ($avoiding_name_collisions) { + case 0: + return 8 + + case 1: + /* skip whitespace */ + break + + case 2: + /* skip comments */ + break + + case 3: + return 9 + + case 4: + return 21 + + case 5: + return 22 + + case 6: + return 18 + + case 7: + return 15 + + case 8: + return 13 + + case 9: + return 20 + + case 10: + return 24 + + case 11: + return 24 + + case 12: + return 28 + + case 13: + return 27 + + case 14: + return 30 + + case 15: + return 29 + + case 16: + return 31 + + case 17: + return 5 + + case 18: + return 'INVALID' + } + }, + rules: [ + /^(?:[\r\n]+)/i, + /^(?:\s+)/i, + /^(?:#[^\r\n]*)/i, + /^(?:participant\b)/i, + /^(?:left of\b)/i, + /^(?:right of\b)/i, + /^(?:over\b)/i, + /^(?:note\b)/i, + /^(?:title\b)/i, + /^(?:,)/i, + /^(?:[^\->:,\r\n"]+)/i, + /^(?:"[^"]+")/i, + /^(?:--)/i, + /^(?:-)/i, + /^(?:>>)/i, + /^(?:>)/i, + /^(?:[^\r\n]+)/i, + /^(?:$)/i, + /^(?:.)/i + ], + conditions: { + INITIAL: { + rules: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + inclusive: !0 + } + } + } + return lexer + })() + return (parser.lexer = lexer), (Parser.prototype = parser), (parser.Parser = Parser), new Parser() +})() + +// CLI bootstrap stripped — this library only runs in the renderer; the +// require('fs')/require('path') path was unreachable in the editor and breaks +// sandboxed bundling. +// jison's UMD `exports.parser/Parser/parse` fallback was also dropped: this +// file is an ESM module (`export default Diagram`), so the CommonJS `exports` +// global is undefined here and Rollup flags it (COMMONJS_VARIABLE_IN_ESM). +// `Diagram.parse` uses the internal `parser` directly, so nothing relies on it. +/** + * jison doesn't have a good exception, so we make one. + * This is brittle as it depends on jison internals + */ +function ParseError(message, hash) { + _.extend(this, hash) + + this.name = 'ParseError' + this.message = message || '' +} +ParseError.prototype = new Error() +Diagram.ParseError = ParseError + +Diagram.parse = function (input) { + // TODO jison v0.4.17 changed their API slightly, so parser is no longer defined: + + // Create the object to track state and deal with errors + parser.yy = new Diagram() + parser.yy.parseError = function (message, hash) { + throw new ParseError(message, hash) + } + + // Parse + var diagram = parser.parse(input) + + // Then clean up the parseError key that a user won't care about + delete diagram.parseError + return diagram +} + +/** js sequence diagrams + * https://bramp.github.io/js-sequence-diagrams/ + * (c) 2012-2017 Andrew Brampton (bramp.net) + * Simplified BSD license. + */ +/*global Diagram, _ */ + +// Following the CSS convention +// Margin is the gap outside the box +// Padding is the gap inside the box +// Each object has x/y/width/height properties +// The x/y should be top left corner +// width/height is with both margin and padding + +// TODO +// Image width is wrong, when there is a note in the right hand col +// Title box could look better +// Note box could look better + +var DIAGRAM_MARGIN = 10 + +var ACTOR_MARGIN = 10 // Margin around a actor +var ACTOR_PADDING = 10 // Padding inside a actor + +var SIGNAL_MARGIN = 5 // Margin around a signal +var SIGNAL_PADDING = 5 // Padding inside a signal + +var NOTE_MARGIN = 10 // Margin around a note +var NOTE_PADDING = 5 // Padding inside a note +var NOTE_OVERLAP = 15 // Overlap when using a "note over A,B" + +var TITLE_MARGIN = 0 +var TITLE_PADDING = 5 + +var SELF_SIGNAL_WIDTH = 20 // How far out a self signal goes + +var PLACEMENT = Diagram.PLACEMENT +var LINETYPE = Diagram.LINETYPE +var ARROWTYPE = Diagram.ARROWTYPE + +var ALIGN_LEFT = 0 +var ALIGN_CENTER = 1 + +function AssertException(message) { + this.message = message +} +AssertException.prototype.toString = function () { + return 'AssertException: ' + this.message +} + +function assert(exp, message) { + if (!exp) { + throw new AssertException(message) + } +} + +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g, '') + } +} + +Diagram.themes = {} +function registerTheme(name, theme) { + Diagram.themes[name] = theme +} + +/****************** + * Drawing extras + ******************/ + +function getCenterX(box) { + return box.x + box.width / 2 +} + +function getCenterY(box) { + return box.y + box.height / 2 +} + +/****************** + * SVG Path extras + ******************/ + +function clamp(x, min, max) { + if (x < min) { + return min + } + if (x > max) { + return max + } + return x +} + +function wobble(x1, y1, x2, y2) { + assert(_.all([x1, x2, y1, y2], _.isFinite), 'x1,x2,y1,y2 must be numeric') + + // Wobble no more than 1/25 of the line length + var factor = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 25 + + // Distance along line where the control points are + // Clamp between 20% and 80% so any arrow heads aren't angled too much + var r1 = clamp(Math.random(), 0.2, 0.8) + var r2 = clamp(Math.random(), 0.2, 0.8) + + var xfactor = Math.random() > 0.5 ? factor : -factor + var yfactor = Math.random() > 0.5 ? factor : -factor + + var p1 = { + x: (x2 - x1) * r1 + x1 + xfactor, + y: (y2 - y1) * r1 + y1 + yfactor + } + + var p2 = { + x: (x2 - x1) * r2 + x1 - xfactor, + y: (y2 - y1) * r2 + y1 - yfactor + } + + return ( + 'C' + + p1.x.toFixed(1) + + ',' + + p1.y.toFixed(1) + // start control point + ' ' + + p2.x.toFixed(1) + + ',' + + p2.y.toFixed(1) + // end control point + ' ' + + x2.toFixed(1) + + ',' + + y2.toFixed(1) + ) // end point +} + +/** + * Draws a wobbly (hand drawn) rect + */ +function handRect(x, y, w, h) { + assert(_.all([x, y, w, h], _.isFinite), 'x, y, w, h must be numeric') + return ( + 'M' + + x + + ',' + + y + + wobble(x, y, x + w, y) + + wobble(x + w, y, x + w, y + h) + + wobble(x + w, y + h, x, y + h) + + wobble(x, y + h, x, y) + ) +} + +/** + * Draws a wobbly (hand drawn) line + */ +function handLine(x1, y1, x2, y2) { + assert(_.all([x1, x2, y1, y2], _.isFinite), 'x1,x2,y1,y2 must be numeric') + return 'M' + x1.toFixed(1) + ',' + y1.toFixed(1) + wobble(x1, y1, x2, y2) +} + +/****************** + * BaseTheme + ******************/ + +var BaseTheme = function (diagram, options) { + this.init(diagram, options) +} + +_.extend(BaseTheme.prototype, { + // Init called while creating the Theme + init: function (diagram, options) { + this.diagram = diagram + + this.actorsHeight_ = 0 + this.signalsHeight_ = 0 + this.title_ = undefined // hack - This should be somewhere better + }, + + setupPaper: function (container) {}, + + draw: function (container) { + this.setupPaper(container) + + this.layout() + + var titleHeight = this.title_ ? this.title_.height : 0 + var y = DIAGRAM_MARGIN + titleHeight + + this.drawTitle() + this.drawActors(y) + this.drawSignals(y + this.actorsHeight_) + }, + + layout: function () { + // Local copies + var diagram = this.diagram + var font = this.font_ + var actors = diagram.actors + var signals = diagram.signals + + diagram.width = 0 // min width + diagram.height = 0 // min height + + // Setup some layout stuff + if (diagram.title) { + var title = (this.title_ = {}) + var bb = this.textBBox(diagram.title, font) + title.textBB = bb + title.message = diagram.title + + title.width = bb.width + (TITLE_PADDING + TITLE_MARGIN) * 2 + title.height = bb.height + (TITLE_PADDING + TITLE_MARGIN) * 2 + title.x = DIAGRAM_MARGIN + title.y = DIAGRAM_MARGIN + + diagram.width += title.width + diagram.height += title.height + } + + _.each( + actors, + function (a) { + var bb = this.textBBox(a.name, font) + a.textBB = bb + + a.x = 0 + a.y = 0 + a.width = bb.width + (ACTOR_PADDING + ACTOR_MARGIN) * 2 + a.height = bb.height + (ACTOR_PADDING + ACTOR_MARGIN) * 2 + + a.distances = [] + a.paddingRight = 0 + this.actorsHeight_ = Math.max(a.height, this.actorsHeight_) + }, + this + ) + + function actorEnsureDistance(a, b, d) { + assert(a < b, 'a must be less than or equal to b') + + if (a < 0) { + // Ensure b has left margin + b = actors[b] + b.x = Math.max(d - b.width / 2, b.x) + } else if (b >= actors.length) { + // Ensure a has right margin + a = actors[a] + a.paddingRight = Math.max(d, a.paddingRight) + } else { + a = actors[a] + a.distances[b] = Math.max(d, a.distances[b] ? a.distances[b] : 0) + } + } + + _.each( + signals, + function (s) { + // Indexes of the left and right actors involved + var a + var b + + var bb = this.textBBox(s.message, font) + + //var bb = t.attr("text", s.message).getBBox(); + s.textBB = bb + s.width = bb.width + s.height = bb.height + + var extraWidth = 0 + + if (s.type == 'Signal') { + s.width += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2 + s.height += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2 + + if (s.isSelf()) { + // TODO Self signals need a min height + a = s.actorA.index + b = a + 1 + s.width += SELF_SIGNAL_WIDTH + } else { + a = Math.min(s.actorA.index, s.actorB.index) + b = Math.max(s.actorA.index, s.actorB.index) + } + } else if (s.type == 'Note') { + s.width += (NOTE_MARGIN + NOTE_PADDING) * 2 + s.height += (NOTE_MARGIN + NOTE_PADDING) * 2 + + // HACK lets include the actor's padding + extraWidth = 2 * ACTOR_MARGIN + + if (s.placement == PLACEMENT.LEFTOF) { + b = s.actor.index + a = b - 1 + } else if (s.placement == PLACEMENT.RIGHTOF) { + a = s.actor.index + b = a + 1 + } else if (s.placement == PLACEMENT.OVER && s.hasManyActors()) { + // Over multiple actors + a = Math.min(s.actor[0].index, s.actor[1].index) + b = Math.max(s.actor[0].index, s.actor[1].index) + + // We don't need our padding, and we want to overlap + extraWidth = -(NOTE_PADDING * 2 + NOTE_OVERLAP * 2) + } else if (s.placement == PLACEMENT.OVER) { + // Over single actor + a = s.actor.index + actorEnsureDistance(a - 1, a, s.width / 2) + actorEnsureDistance(a, a + 1, s.width / 2) + this.signalsHeight_ += s.height + + return // Bail out early + } + } else { + throw new Error('Unhandled signal type:' + s.type) + } + + actorEnsureDistance(a, b, s.width + extraWidth) + this.signalsHeight_ += s.height + }, + this + ) + + // Re-jig the positions + var actorsX = 0 + _.each( + actors, + function (a) { + a.x = Math.max(actorsX, a.x) + + // TODO This only works if we loop in sequence, 0, 1, 2, etc + _.each(a.distances, function (distance, b) { + // lodash (and possibly others) do not like sparse arrays + // so sometimes they return undefined + if (typeof distance == 'undefined') { + return + } + + b = actors[b] + distance = Math.max(distance, a.width / 2, b.width / 2) + b.x = Math.max(b.x, a.x + a.width / 2 + distance - b.width / 2) + }) + + actorsX = a.x + a.width + a.paddingRight + }, + this + ) + + diagram.width = Math.max(actorsX, diagram.width) + + // TODO Refactor a little + diagram.width += 2 * DIAGRAM_MARGIN + diagram.height += 2 * DIAGRAM_MARGIN + 2 * this.actorsHeight_ + this.signalsHeight_ + + return this + }, + + // TODO Instead of one textBBox function, create a function for each element type, e.g + // layout_title, layout_actor, etc that returns it's bounding box + textBBox: function (text, font) {}, + + drawTitle: function () { + var title = this.title_ + if (title) { + this.drawTextBox(title, title.message, TITLE_MARGIN, TITLE_PADDING, this.font_, ALIGN_LEFT) + } + }, + + drawActors: function (offsetY) { + var y = offsetY + _.each( + this.diagram.actors, + function (a) { + // Top box + this.drawActor(a, y, this.actorsHeight_) + + // Bottom box + this.drawActor(a, y + this.actorsHeight_ + this.signalsHeight_, this.actorsHeight_) + + // Veritical line + var aX = getCenterX(a) + this.drawLine( + aX, + y + this.actorsHeight_ - ACTOR_MARGIN, + aX, + y + this.actorsHeight_ + ACTOR_MARGIN + this.signalsHeight_ + ) + }, + this + ) + }, + + drawActor: function (actor, offsetY, height) { + actor.y = offsetY + actor.height = height + this.drawTextBox(actor, actor.name, ACTOR_MARGIN, ACTOR_PADDING, this.font_, ALIGN_CENTER) + }, + + drawSignals: function (offsetY) { + var y = offsetY + _.each( + this.diagram.signals, + function (s) { + // TODO Add debug mode, that draws padding/margin box + if (s.type == 'Signal') { + if (s.isSelf()) { + this.drawSelfSignal(s, y) + } else { + this.drawSignal(s, y) + } + } else if (s.type == 'Note') { + this.drawNote(s, y) + } + + y += s.height + }, + this + ) + }, + + drawSelfSignal: function (signal, offsetY) { + assert(signal.isSelf(), 'signal must be a self signal') + + var textBB = signal.textBB + var aX = getCenterX(signal.actorA) + + var x = aX + SELF_SIGNAL_WIDTH + SIGNAL_PADDING + var y = offsetY + SIGNAL_PADDING + signal.height / 2 + textBB.y + + this.drawText(x, y, signal.message, this.font_, ALIGN_LEFT) + + var y1 = offsetY + SIGNAL_MARGIN + SIGNAL_PADDING + var y2 = y1 + signal.height - 2 * SIGNAL_MARGIN - SIGNAL_PADDING + + // Draw three lines, the last one with a arrow + this.drawLine(aX, y1, aX + SELF_SIGNAL_WIDTH, y1, signal.linetype) + this.drawLine(aX + SELF_SIGNAL_WIDTH, y1, aX + SELF_SIGNAL_WIDTH, y2, signal.linetype) + this.drawLine(aX + SELF_SIGNAL_WIDTH, y2, aX, y2, signal.linetype, signal.arrowtype) + }, + + drawSignal: function (signal, offsetY) { + var aX = getCenterX(signal.actorA) + var bX = getCenterX(signal.actorB) + + // Mid point between actors + var x = (bX - aX) / 2 + aX + var y = offsetY + SIGNAL_MARGIN + 2 * SIGNAL_PADDING + + // Draw the text in the middle of the signal + this.drawText(x, y, signal.message, this.font_, ALIGN_CENTER) + + // Draw the line along the bottom of the signal + y = offsetY + signal.height - SIGNAL_MARGIN - SIGNAL_PADDING + this.drawLine(aX, y, bX, y, signal.linetype, signal.arrowtype) + }, + + drawNote: function (note, offsetY) { + note.y = offsetY + var actorA = note.hasManyActors() ? note.actor[0] : note.actor + var aX = getCenterX(actorA) + switch (note.placement) { + case PLACEMENT.RIGHTOF: + note.x = aX + ACTOR_MARGIN + break + case PLACEMENT.LEFTOF: + note.x = aX - ACTOR_MARGIN - note.width + break + case PLACEMENT.OVER: + if (note.hasManyActors()) { + var bX = getCenterX(note.actor[1]) + var overlap = NOTE_OVERLAP + NOTE_PADDING + note.x = Math.min(aX, bX) - overlap + note.width = Math.max(aX, bX) + overlap - note.x + } else { + note.x = aX - note.width / 2 + } + break + default: + throw new Error('Unhandled note placement: ' + note.placement) + } + return this.drawTextBox(note, note.message, NOTE_MARGIN, NOTE_PADDING, this.font_, ALIGN_LEFT) + }, + + /** + * Draw text surrounded by a box + */ + drawTextBox: function (box, text, margin, padding, font, align) { + var x = box.x + margin + var y = box.y + margin + var w = box.width - 2 * margin + var h = box.height - 2 * margin + + // Draw inner box + this.drawRect(x, y, w, h) + + // Draw text (in the center) + if (align == ALIGN_CENTER) { + x = getCenterX(box) + y = getCenterY(box) + } else { + x += padding + y += padding + } + + return this.drawText(x, y, text, font, align) + } +}) + +/** js sequence diagrams + * https://bramp.github.io/js-sequence-diagrams/ + * (c) 2012-2017 Andrew Brampton (bramp.net) + * Simplified BSD license. + */ +/*global Diagram, Snap, WebFont _ */ +// TODO Move defintion of font onto the , so it can easily be override at each level +if (typeof Snap != 'undefined') { + var xmlns = 'http://www.w3.org/2000/svg' + + var LINE = { + stroke: '#000000', + 'stroke-width': 2, // BUG TODO This gets set as a style, not as a attribute. Look at eve.on("snap.util.attr"... + fill: 'none' + } + + var RECT = { + stroke: '#000000', + 'stroke-width': 2, + fill: '#fff' + } + + var LOADED_FONTS = {} + + /****************** + * SnapTheme + ******************/ + + var SnapTheme = function (diagram, options, resume) { + _.defaults(options, { + 'css-class': 'simple', + 'font-size': 16, + 'font-family': 'Andale Mono, monospace' + }) + + this.init(diagram, options, resume) + } + + _.extend(SnapTheme.prototype, BaseTheme.prototype, { + init: function (diagram, options, resume) { + BaseTheme.prototype.init.call(this, diagram) + + this.paper_ = undefined + this.cssClass_ = options['css-class'] || undefined + this.font_ = { + 'font-size': options['font-size'], + 'font-family': options['font-family'] + } + + var a = (this.arrowTypes_ = {}) + a[ARROWTYPE.FILLED] = 'Block' + a[ARROWTYPE.OPEN] = 'Open' + + var l = (this.lineTypes_ = {}) + l[LINETYPE.SOLID] = '' + l[LINETYPE.DOTTED] = '6,2' + + var that = this + this.waitForFont(function () { + resume(that) + }) + }, + + // Wait for loading of the font + waitForFont: function (callback) { + var fontFamily = this.font_['font-family'] + + if (typeof WebFont == 'undefined') { + throw new Error('WebFont is required (https://github.com/typekit/webfontloader).') + } + + if (LOADED_FONTS[fontFamily]) { + // If already loaded, just return instantly. + callback() + return + } + + WebFont.load({ + custom: { + families: [fontFamily] // TODO replace this with something that reads the css + }, + classes: false, // No need to place classes on the DOM, just use JS Events + active: function () { + LOADED_FONTS[fontFamily] = true + callback() + }, + inactive: function () { + // If we fail to fetch the font, still continue. + LOADED_FONTS[fontFamily] = true + callback() + } + }) + }, + + addDescription: function (svg, description) { + var desc = document.createElementNS(xmlns, 'desc') + desc.appendChild(document.createTextNode(description)) + svg.appendChild(desc) + }, + + setupPaper: function (container) { + // Container must be a SVG element. We assume it's a div, so lets create a SVG and insert + var svg = document.createElementNS(xmlns, 'svg') + container.appendChild(svg) + + this.addDescription(svg, this.diagram.title || '') + + this.paper_ = Snap(svg) + this.paper_.addClass('sequence') + + if (this.cssClass_) { + this.paper_.addClass(this.cssClass_) + } + + this.beginGroup() + + // TODO Perhaps only include the markers if we actually use them. + var a = (this.arrowMarkers_ = {}) + var arrow = this.paper_.path('M 0 0 L 5 2.5 L 0 5 z') + a[ARROWTYPE.FILLED] = arrow.marker(0, 0, 5, 5, 5, 2.5).attr({ id: 'markerArrowBlock' }) + + arrow = this.paper_.path('M 9.6,8 1.92,16 0,13.7 5.76,8 0,2.286 1.92,0 9.6,8 z') + a[ARROWTYPE.OPEN] = arrow + .marker(0, 0, 9.6, 16, 9.6, 8) + .attr({ markerWidth: '4', id: 'markerArrowOpen' }) + }, + + layout: function () { + BaseTheme.prototype.layout.call(this) + this.paper_.attr({ + width: this.diagram.width + 'px', + height: this.diagram.height + 'px' + }) + }, + + textBBox: function (text, font) { + // TODO getBBox will return the bounds with any whitespace/kerning. This makes some of our aligments screwed up + var t = this.createText(text, font) + var bb = t.getBBox() + t.remove() + return bb + }, + + // For each drawn element, push onto the stack, so it can be wrapped in a single outer element + pushToStack: function (element) { + this._stack.push(element) + return element + }, + + // Begin a group of elements + beginGroup: function () { + this._stack = [] + }, + + // Finishes the group, and returns the element + finishGroup: function () { + var g = this.paper_.group.apply(this.paper_, this._stack) + this.beginGroup() // Reset the group + return g + }, + + createText: function (text, font) { + text = _.invoke(text.split('\n'), 'trim') + var t = this.paper_.text(0, 0, text) + t.attr(font || {}) + if (text.length > 1) { + // Every row after the first, set tspan to be 1.2em below the previous line + t.selectAll('tspan:nth-child(n+2)').attr({ + dy: '1.2em', + x: 0 + }) + } + + return t + }, + + drawLine: function (x1, y1, x2, y2, linetype, arrowhead) { + var line = this.paper_.line(x1, y1, x2, y2).attr(LINE) + if (linetype !== undefined) { + line.attr('strokeDasharray', this.lineTypes_[linetype]) + } + if (arrowhead !== undefined) { + line.attr('markerEnd', this.arrowMarkers_[arrowhead]) + } + return this.pushToStack(line) + }, + + drawRect: function (x, y, w, h) { + var rect = this.paper_.rect(x, y, w, h).attr(RECT) + return this.pushToStack(rect) + }, + + /** + * Draws text with a optional white background + * x,y (int) x,y top left point of the text, or the center of the text (depending on align param) + * text (string) text to print + * font (Object) + * align (string) ALIGN_LEFT or ALIGN_CENTER + */ + drawText: function (x, y, text, font, align) { + var t = this.createText(text, font) + var bb = t.getBBox() + + if (align == ALIGN_CENTER) { + x = x - bb.width / 2 + y = y - bb.height / 2 + } + + // Now move the text into place + // `y - bb.y` because text(..) is positioned from the baseline, so this moves it down. + t.attr({ x: x - bb.x, y: y - bb.y }) + t.selectAll('tspan').attr({ x: x }) + + this.pushToStack(t) + return t + }, + + drawTitle: function () { + this.beginGroup() + BaseTheme.prototype.drawTitle.call(this) + return this.finishGroup().addClass('title') + }, + + drawActor: function (actor, offsetY, height) { + this.beginGroup() + BaseTheme.prototype.drawActor.call(this, actor, offsetY, height) + return this.finishGroup().addClass('actor') + }, + + drawSignal: function (signal, offsetY) { + this.beginGroup() + BaseTheme.prototype.drawSignal.call(this, signal, offsetY) + return this.finishGroup().addClass('signal') + }, + + drawSelfSignal: function (signal, offsetY) { + this.beginGroup() + BaseTheme.prototype.drawSelfSignal.call(this, signal, offsetY) + return this.finishGroup().addClass('signal') + }, + + drawNote: function (note, offsetY) { + this.beginGroup() + BaseTheme.prototype.drawNote.call(this, note, offsetY) + return this.finishGroup().addClass('note') + } + }) + + /****************** + * SnapHandTheme + ******************/ + + var SnapHandTheme = function (diagram, options, resume) { + _.defaults(options, { + 'css-class': 'hand', + 'font-size': 16, + 'font-family': 'danielbd' + }) + + this.init(diagram, options, resume) + } + + // Take the standard SnapTheme and make all the lines wobbly + _.extend(SnapHandTheme.prototype, SnapTheme.prototype, { + drawLine: function (x1, y1, x2, y2, linetype, arrowhead) { + var line = this.paper_.path(handLine(x1, y1, x2, y2)).attr(LINE) + if (linetype !== undefined) { + line.attr('strokeDasharray', this.lineTypes_[linetype]) + } + if (arrowhead !== undefined) { + line.attr('markerEnd', this.arrowMarkers_[arrowhead]) + } + return this.pushToStack(line) + }, + + drawRect: function (x, y, w, h) { + var rect = this.paper_.path(handRect(x, y, w, h)).attr(RECT) + return this.pushToStack(rect) + } + }) + + registerTheme('snapSimple', SnapTheme) + registerTheme('snapHand', SnapHandTheme) +} + +/** js sequence diagrams + * https://bramp.github.io/js-sequence-diagrams/ + * (c) 2012-2017 Andrew Brampton (bramp.net) + * Simplified BSD license. + */ +/*global Diagram, _ */ + +if (typeof Raphael == 'undefined' && typeof Snap == 'undefined') { + throw new Error('Raphael or Snap.svg is required to be included.') +} + +if (_.isEmpty(Diagram.themes)) { + // If you are using stock js-sequence-diagrams you should never see this. This only + // happens if you have removed the built in themes. + throw new Error('No themes were registered. Please call registerTheme(...).') +} + +// Set the default hand/simple based on which theme is available. +Diagram.themes.hand = Diagram.themes.snapHand || Diagram.themes.raphaelHand +Diagram.themes.simple = Diagram.themes.snapSimple || Diagram.themes.raphaelSimple + +/* Draws the diagram. Creates a SVG inside the container + * container (HTMLElement|string) DOM element or its ID to draw on + * options (Object) + */ +Diagram.prototype.drawSVG = function (container, options) { + var defaultOptions = { + theme: 'hand' + } + + options = _.defaults(options || {}, defaultOptions) + + if (!(options.theme in Diagram.themes)) { + throw new Error('Unsupported theme: ' + options.theme) + } + + // TODO Write tests for this check + var div = _.isString(container) ? document.getElementById(container) : container + if (div === null || !div.tagName) { + throw new Error('Invalid container: ' + container) + } + + var Theme = Diagram.themes[options.theme] + new Theme(this, options, function (drawing) { + drawing.draw(div) + }) +} // end of drawSVG + +export default Diagram diff --git a/packages/muya/src/utils/diagram/sequence/sequence-diagram.css b/packages/muya/src/utils/diagram/sequence/sequence-diagram.css new file mode 100755 index 0000000000..ea3e66611b --- /dev/null +++ b/packages/muya/src/utils/diagram/sequence/sequence-diagram.css @@ -0,0 +1,12 @@ +/** js sequence diagrams + * https://bramp.github.io/js-sequence-diagrams/ + * (c) 2012-2017 Andrew Brampton (bramp.net) + * Simplified BSD license. + */ +@font-face { + font-family: danielbd; + src: url('danielbd.woff2') format('woff2'), + url('danielbd.woff') format('woff'); + font-weight: normal; + font-style: normal; +} diff --git a/packages/muya/src/utils/marked/getHighlightHtml.ts b/packages/muya/src/utils/marked/getHighlightHtml.ts index ce05e85523..ae7f3707c4 100644 --- a/packages/muya/src/utils/marked/getHighlightHtml.ts +++ b/packages/muya/src/utils/marked/getHighlightHtml.ts @@ -14,6 +14,8 @@ const DIAGRAM_TYPE = [ 'mermaid', 'plantuml', 'vega-lite', + 'flowchart', + 'sequence', ]; function highlight(code: string, lang: string) { diff --git a/packages/muya/src/utils/paste.ts b/packages/muya/src/utils/paste.ts index 2931079e5d..ea3edc9b90 100644 --- a/packages/muya/src/utils/paste.ts +++ b/packages/muya/src/utils/paste.ts @@ -1,4 +1,4 @@ -import { PARAGRAPH_TYPES, PREVIEW_DOMPURIFY_CONFIG } from '../config'; +import { IMAGE_EXT_REG, PARAGRAPH_TYPES, PREVIEW_DOMPURIFY_CONFIG } from '../config'; import { sanitize } from '../utils'; const TIMEOUT = 1500; @@ -126,6 +126,31 @@ export function isStandaloneTableHtml(text: string) { return STANDALONE_TABLE_REG.test(text.trim()); } +/** + * Resolve the `clipboardFilePath` paste hook to a usable inline-image path. + * + * Returns the resolved path only when the hook yields a non-empty string that + * looks like an image file (its extension matches {@link IMAGE_EXT_REG}); + * otherwise returns `''` so the caller falls through to the normal text/HTML + * paste. Ported from the legacy `@muyajs` `pasteImage` guard, which inserted + * the resolved path as an image when it matched the same extension regex. + * + * @param hook the `options.clipboardFilePath` callback, if configured + */ +export async function resolveClipboardImagePath( + hook: (() => Promise) | undefined, +): Promise { + if (typeof hook !== 'function') + return ''; + + const path = await hook(); + + if (typeof path === 'string' && path && IMAGE_EXT_REG.test(path)) + return path; + + return ''; +} + /** * * @param {string} html diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80137411b6..504321f373 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -319,6 +319,9 @@ importers: fast-diff: specifier: ^1.3.0 version: 1.3.0 + flowchart.js: + specifier: ^1.18.0 + version: 1.18.0 fuse.js: specifier: ^7.3.0 version: 7.3.0 @@ -361,9 +364,15 @@ importers: snabbdom-to-html: specifier: ^7.1.0 version: 7.1.0 + snapsvg-cjs: + specifier: ^0.0.6 + version: 0.0.6(eve@0.5.4) turndown: specifier: ^7.2.4 version: 7.2.4 + underscore: + specifier: ^1.13.8 + version: 1.13.8 vega: specifier: ^6.2.0 version: 6.2.0 @@ -373,6 +382,9 @@ importers: vega-lite: specifier: ^6.4.3 version: 6.4.3(vega@6.2.0) + webfontloader: + specifier: ^1.6.28 + version: 1.6.28 devDependencies: '@antfu/eslint-config': specifier: ^9.0.0 @@ -392,6 +404,12 @@ importers: '@types/turndown': specifier: ^5.0.4 version: 5.0.6 + '@types/underscore': + specifier: ^1.13.0 + version: 1.13.0 + '@types/webfontloader': + specifier: ^1.6.38 + version: 1.6.38 '@typescript-eslint/parser': specifier: ^8.59.4 version: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) @@ -416,9 +434,6 @@ importers: stylelint: specifier: ^17.11.1 version: 17.12.0(typescript@6.0.3) - stylelint-config-rational-order: - specifier: ^0.1.2 - version: 0.1.2 stylelint-config-standard: specifier: ^40.0.0 version: 40.0.0(stylelint@17.12.0(typescript@6.0.3)) @@ -2745,10 +2760,6 @@ packages: '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} - '@mrmlnc/readdir-enhanced@2.2.1': - resolution: {integrity: sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==} - engines: {node: '>=4'} - '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -2847,10 +2858,6 @@ packages: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} - '@nodelib/fs.stat@1.1.3': - resolution: {integrity: sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==} - engines: {node: '>= 6'} - '@nodelib/fs.stat@2.0.5': resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} @@ -3747,9 +3754,6 @@ packages: '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/glob@7.2.0': - resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -3780,10 +3784,6 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/minimatch@6.0.0': - resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} - deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. - '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -3825,8 +3825,8 @@ packages: '@types/turndown@5.0.6': resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/underscore@1.13.0': + resolution: {integrity: sha512-L6LBgy1f0EFQZ+7uSA57+n2g/s4Qs5r06Vwrwn0/nuK1de+adz00NWaztRQ30aEqw5qOaWbPI8u2cGQ52lj6VA==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -3834,13 +3834,6 @@ packages: '@types/verror@1.10.11': resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} - '@types/vfile-message@2.0.0': - resolution: {integrity: sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==} - deprecated: This is a stub types definition. vfile-message provides its own type definitions, so you do not need this installed. - - '@types/vfile@3.0.2': - resolution: {integrity: sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==} - '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} @@ -4379,10 +4372,6 @@ packages: resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} engines: {node: '>=4'} - ansi-regex@4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -4440,18 +4429,6 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - arr-diff@4.0.0: - resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} - engines: {node: '>=0.10.0'} - - arr-flatten@1.1.0: - resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} - engines: {node: '>=0.10.0'} - - arr-union@3.1.0: - resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} - engines: {node: '>=0.10.0'} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -4470,18 +4447,6 @@ packages: array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} - array-union@1.0.2: - resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} - engines: {node: '>=0.10.0'} - - array-uniq@1.0.3: - resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} - engines: {node: '>=0.10.0'} - - array-unique@0.3.2: - resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} - engines: {node: '>=0.10.0'} - array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -4506,10 +4471,6 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -4521,10 +4482,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - assign-symbols@1.0.0: - resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} - engines: {node: '>=0.10.0'} - ast-module-types@6.0.2: resolution: {integrity: sha512-6KuK/7nZ/2Qh7sGuVEiwxjCxzTY2Pdb5mTo5z1e6/J8BA0tvjR7G8vQJKrQMTqwmnA3UPEyKIFX4YUS1DO1Hvw==} engines: {node: '>=18'} @@ -4535,10 +4492,6 @@ packages: ast-v8-to-istanbul@1.0.0: resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} - astral-regex@1.0.0: - resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} - engines: {node: '>=4'} - astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -4567,11 +4520,6 @@ packages: atoa@1.0.0: resolution: {integrity: sha512-VVE1H6cc4ai+ZXo/CRWoJiHXrA1qfA31DPnx6D20+kSI547hQN5Greh51LQ1baMRMfxO5K5M4ImMtZbZt2DODQ==} - atob@2.1.2: - resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} - engines: {node: '>= 4.5.0'} - hasBin: true - atom-fs@0.2.1: resolution: {integrity: sha512-H+09ux1pNAPUbJqyrZ7lA/CpNmKqZURrYc23QzDUG29A380EYEnzwW99Do5VlT8SmZtyapOMySFesndgvZZGWg==} engines: {atom: '>=1.13.0'} @@ -4586,10 +4534,6 @@ packages: peerDependencies: postcss: 8.5.15 - autoprefixer@9.8.8: - resolution: {integrity: sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==} - hasBin: true - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -4608,9 +4552,6 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - bail@1.0.5: - resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} - bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -4624,10 +4565,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - base@0.11.2: - resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} - engines: {node: '>=0.10.0'} - baseline-browser-mapping@2.10.29: resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} engines: {node: '>=6.0.0'} @@ -4675,10 +4612,6 @@ packages: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} - braces@2.3.2: - resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} - engines: {node: '>=0.10.0'} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -4723,10 +4656,6 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} - cache-base@1.0.1: - resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} - engines: {node: '>=0.10.0'} - cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} @@ -4750,39 +4679,13 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - - caller-callsite@2.0.0: - resolution: {integrity: sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==} - engines: {node: '>=4'} - - caller-path@2.0.0: - resolution: {integrity: sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==} - engines: {node: '>=4'} - - callsites@2.0.0: - resolution: {integrity: sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==} - engines: {node: '>=4'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase-keys@4.2.0: - resolution: {integrity: sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q==} - engines: {node: '>=4'} - - camelcase@4.1.0: - resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} - engines: {node: '>=4'} - caniuse-lite@1.0.30001792: resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} - ccount@1.1.0: - resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -4809,27 +4712,15 @@ packages: change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - character-entities-html4@1.1.4: - resolution: {integrity: sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - character-entities-legacy@1.1.4: - resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} - character-entities-legacy@3.0.0: resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - character-entities@1.2.4: - resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} - character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - character-reference-invalid@1.1.4: - resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} - chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -4856,10 +4747,6 @@ packages: resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} - class-utils@0.3.6: - resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} - engines: {node: '>=0.10.0'} - clean-regexp@1.0.0: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} @@ -4887,10 +4774,6 @@ packages: resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} engines: {node: '>=20'} - clone-regexp@1.0.1: - resolution: {integrity: sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==} - engines: {node: '>=0.10.0'} - clone-regexp@3.0.0: resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==} engines: {node: '>=12'} @@ -4908,13 +4791,6 @@ packages: codemirror@5.65.21: resolution: {integrity: sha512-6teYk0bA0nR3QP0ihGMoxuKzpl5W80FpnHpBJpgy66NK3cZv5b/d/HY8PnRvfSsCG1MTfr92u2WUl+wT0E40mQ==} - collapse-white-space@1.0.6: - resolution: {integrity: sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==} - - collection-visit@1.0.0: - resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} - engines: {node: '>=0.10.0'} - color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -4997,9 +4873,6 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - component-emitter@1.3.1: - resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -5047,10 +4920,6 @@ packages: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} - copy-descriptor@0.1.1: - resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} - engines: {node: '>=0.10.0'} - core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} @@ -5063,10 +4932,6 @@ packages: cose-base@2.2.0: resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - cosmiconfig@5.2.1: - resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} - engines: {node: '>=4'} - cosmiconfig@9.0.1: resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} engines: {node: '>=14'} @@ -5153,10 +5018,6 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - currently-unhandled@0.4.1: - resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} - engines: {node: '>=0.10.0'} - custom-event@1.0.1: resolution: {integrity: sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==} @@ -5350,14 +5211,6 @@ packages: resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} engines: {node: '>=18'} - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -5379,24 +5232,12 @@ packages: resolution: {integrity: sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -5427,18 +5268,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - define-property@0.2.5: - resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} - engines: {node: '>=0.10.0'} - - define-property@1.0.0: - resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} - engines: {node: '>=0.10.0'} - - define-property@2.0.2: - resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} - engines: {node: '>=0.10.0'} - delaunator@5.1.0: resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} @@ -5526,10 +5355,6 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - dir-glob@2.2.2: - resolution: {integrity: sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==} - engines: {node: '>=4'} - dmg-builder@26.8.1: resolution: {integrity: sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==} @@ -5552,24 +5377,15 @@ packages: dom-plane@1.0.2: resolution: {integrity: sha512-/tR67G6ZGSciXoZLsD706yLxEXvX3mG/OWE8YNYj3A1yU/RAimtPXzklVTu5Y5xoeMoloA/Y+MaNjQm9apgAww==} - dom-serializer@0.2.2: - resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} - dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} dom-set@1.1.1: resolution: {integrity: sha512-sUi2aSvRsK3Ixx++gwX9cnaWk9ZxGVFry8+HnTRVmDimybU5PaiI4wX0o00mVtjFKlQNZLmtGoPTLorYbN0+Rw==} - domelementtype@1.3.1: - resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} - domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - domhandler@2.4.2: - resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} - domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} @@ -5580,9 +5396,6 @@ packages: dompurify@3.4.7: resolution: {integrity: sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==} - domutils@1.7.0: - resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} - domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -5590,10 +5403,6 @@ packages: resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} engines: {node: '>=20'} - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} @@ -5687,9 +5496,6 @@ packages: emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - emoji-regex@7.0.3: - resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -5715,12 +5521,6 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} - entities@1.1.2: - resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} - - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -6209,18 +6009,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - execall@1.0.0: - resolution: {integrity: sha512-/J0Q8CvOvlAdpvhfkD/WnTQ4H1eU0exze2nFGPj/RSC7jpQ0NkKe2r28T5eMkhEEs+fzepMZNy1kVRKNlC04nQ==} - engines: {node: '>=0.10.0'} - execall@3.0.0: resolution: {integrity: sha512-FaJeg2uWc8ADWnAnoDbxhAAr4U/j86ujlojnEpnBHQ4FM2viZKNjjyO2O6AKG6+9usZAnqsI4+1+w6vzx0x4uw==} engines: {node: '>=12'} - expand-brackets@2.1.4: - resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} - engines: {node: '>=0.10.0'} - expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -6243,17 +6035,9 @@ packages: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} - extend-shallow@3.0.2: - resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} - engines: {node: '>=0.10.0'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - extglob@2.0.4: - resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} - engines: {node: '>=0.10.0'} - extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} engines: {node: '>= 10.17.0'} @@ -6269,10 +6053,6 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@2.2.7: - resolution: {integrity: sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==} - engines: {node: '>=4.0.0'} - fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -6337,10 +6117,6 @@ packages: file-entry-cache@11.1.3: resolution: {integrity: sha512-oMbq0PD6VIiIwMF6LIa7MEwd/l9huKwmqRKXqmrkqIZv8CvRbfowL+L0ryAl8h//HfAS0zS+4SbYoRyAoA6BJA==} - file-entry-cache@4.0.0: - resolution: {integrity: sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==} - engines: {node: '>=4'} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -6361,10 +6137,6 @@ packages: engines: {node: '>=18'} hasBin: true - fill-range@4.0.0: - resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} - engines: {node: '>=0.10.0'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -6377,10 +6149,6 @@ packages: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} - find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -6392,10 +6160,6 @@ packages: find-yarn-workspace-root@2.0.0: resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} - flat-cache@2.0.1: - resolution: {integrity: sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==} - engines: {node: '>=4'} - flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -6403,9 +6167,6 @@ packages: flat-cache@6.1.22: resolution: {integrity: sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==} - flatted@2.0.2: - resolution: {integrity: sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==} - flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} @@ -6429,10 +6190,6 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - for-in@1.0.2: - resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} - engines: {node: '>=0.10.0'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -6459,10 +6216,6 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - fragment-cache@0.2.1: - resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} - engines: {node: '>=0.10.0'} - fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -6551,10 +6304,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stdin@6.0.0: - resolution: {integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==} - engines: {node: '>=4'} - get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -6570,10 +6319,6 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - get-value@2.0.6: - resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} - engines: {node: '>=0.10.0'} - github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -6584,9 +6329,6 @@ packages: github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - glob-parent@3.1.0: - resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -6595,9 +6337,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob-to-regexp@0.3.0: - resolution: {integrity: sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==} - glob@12.0.0: resolution: {integrity: sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==} engines: {node: 20 || >=22} @@ -6644,10 +6383,6 @@ packages: resolution: {integrity: sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==} engines: {node: '>=20'} - globby@9.2.0: - resolution: {integrity: sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==} - engines: {node: '>=6'} - globjoin@0.1.4: resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} @@ -6716,22 +6451,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - has-value@0.3.1: - resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} - engines: {node: '>=0.10.0'} - - has-value@1.0.0: - resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} - engines: {node: '>=0.10.0'} - - has-values@0.1.4: - resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} - engines: {node: '>=0.10.0'} - - has-values@1.0.0: - resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} - engines: {node: '>=0.10.0'} - hashery@1.5.1: resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} @@ -6806,10 +6525,6 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html-tags@2.0.0: - resolution: {integrity: sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==} - engines: {node: '>=4'} - html-tags@5.1.0: resolution: {integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==} engines: {node: '>=20.10'} @@ -6820,9 +6535,6 @@ packages: htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} - htmlparser2@3.10.1: - resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} - http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} @@ -6869,10 +6581,6 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -6881,18 +6589,10 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - import-fresh@2.0.0: - resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} - engines: {node: '>=4'} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-lazy@3.1.0: - resolution: {integrity: sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==} - engines: {node: '>=6'} - import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -6908,9 +6608,6 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - indexes-of@1.0.1: - resolution: {integrity: sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -6943,20 +6640,6 @@ packages: resolution: {integrity: sha512-kniTIJmaZYiwa17eTtWIfm0K342seyugl6vuC8DiiyiRAJWAVlLkqGCI0Im0neo0TkXw+pRcKaBPRdcKHnQJ6Q==} engines: {node: '>=0.10.0'} - is-accessor-descriptor@1.0.2: - resolution: {integrity: sha512-AIbwAcazqP3R65dGvqk1V+a+vE5Fg1yu/ZKMOiBWSUIXXiwQkYmXQcVa2O0nh0tSDKDFKxG2mY7dB1Sr4hEP1g==} - engines: {node: '>= 0.4'} - - is-alphabetical@1.0.4: - resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} - - is-alphanumeric@1.0.0: - resolution: {integrity: sha512-ZmRL7++ZkcMOfDuWZuMJyIVLr2keE1o/DeNWh1EmgqGhUcV+9BIVsx0BcSBOHTZqzjs4+dISzr2KAeBEWGgXeA==} - engines: {node: '>=0.10.0'} - - is-alphanumerical@1.0.4: - resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} - is-arguments@1.2.0: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} @@ -6983,13 +6666,6 @@ packages: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - - is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - is-builtin-module@5.0.0: resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} engines: {node: '>=18.20'} @@ -7005,10 +6681,6 @@ packages: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} - is-data-descriptor@1.0.1: - resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} - engines: {node: '>= 0.4'} - is-data-view@1.0.2: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} @@ -7017,21 +6689,6 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} - is-decimal@1.0.4: - resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} - - is-descriptor@0.1.8: - resolution: {integrity: sha512-SceYGWXvdqlWa/OnQ5FQuV+NxvNmMRhMw/w9AHkH71hTzveND4BTYgvp16g+oITK47qbOl/3D0bl0iygehWAWQ==} - engines: {node: '>= 0.4'} - - is-descriptor@1.0.4: - resolution: {integrity: sha512-bv5z95W0dDtLfKwDfkTNxaRxmISBD3eQBKJeVxv2AQ7MjuUnDNG7cIQqvFtMOUYhsILWHhMayWdoGqNqYYYjww==} - engines: {node: '>= 0.4'} - - is-directory@0.3.1: - resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} - engines: {node: '>=0.10.0'} - is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -7041,10 +6698,6 @@ packages: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} - is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -7053,10 +6706,6 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} - is-fullwidth-code-point@2.0.0: - resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} - engines: {node: '>=4'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -7065,17 +6714,10 @@ packages: resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} - is-glob@3.1.0: - resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==} - engines: {node: '>=0.10.0'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-hexadecimal@1.0.4: - resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} - is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -7092,10 +6734,6 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} - is-number@3.0.0: - resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} - engines: {node: '>=0.10.0'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -7104,26 +6742,14 @@ packages: resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} engines: {node: '>=0.10.0'} - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - is-path-inside@4.0.0: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -7158,10 +6784,6 @@ packages: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-supported-regexp-flag@1.0.1: - resolution: {integrity: sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==} - engines: {node: '>=0.10.0'} - is-symbol@1.1.1: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} @@ -7194,23 +6816,10 @@ packages: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} - is-whitespace-character@1.0.4: - resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==} - - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - - is-word-character@1.0.4: - resolution: {integrity: sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==} - is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -7240,14 +6849,6 @@ packages: resolution: {integrity: sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==} engines: {node: '>=6.0'} - isobject@2.1.0: - resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} - engines: {node: '>=0.10.0'} - - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -7325,9 +6926,6 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} - json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -7395,14 +6993,6 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - kind-of@3.2.2: - resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} - engines: {node: '>=0.10.0'} - - kind-of@4.0.0: - resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} - engines: {node: '>=0.10.0'} - kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -7414,9 +7004,6 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - known-css-properties@0.11.0: - resolution: {integrity: sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==} - kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -7436,10 +7023,6 @@ packages: lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} - leven@2.1.0: - resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} - engines: {node: '>=0.10.0'} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -7525,10 +7108,6 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} - loader-utils@3.3.1: resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} engines: {node: '>= 12.13.0'} @@ -7537,10 +7116,6 @@ packages: resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} engines: {node: '>=14'} - locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -7598,9 +7173,6 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - longest-streak@2.0.4: - resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -7608,10 +7180,6 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loud-rejection@1.6.0: - resolution: {integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==} - engines: {node: '>=0.10.0'} - lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -7650,31 +7218,9 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - map-cache@0.2.2: - resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} - engines: {node: '>=0.10.0'} - - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@2.0.0: - resolution: {integrity: sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==} - engines: {node: '>=4'} - - map-visit@1.0.0: - resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} - engines: {node: '>=0.10.0'} - mapped-disposable@1.0.3: resolution: {integrity: sha512-DpYYRSZjNB6tOg8E4BZ+rNsKe4WxoptwM8+JqHi8R0buJgHLmrmAfJtv4lcqqO1esTtxaPx4FXOR54bPoS9qwg==} - markdown-escapes@1.0.4: - resolution: {integrity: sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==} - - markdown-table@1.1.3: - resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==} - markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -7696,15 +7242,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mathml-tag-names@2.1.3: - resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} - mathml-tag-names@4.0.0: resolution: {integrity: sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==} - mdast-util-compact@1.0.4: - resolution: {integrity: sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -7767,10 +7307,6 @@ packages: resolution: {integrity: sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==} engines: {node: '>=20'} - meow@5.0.0: - resolution: {integrity: sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==} - engines: {node: '>=6'} - merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -7875,10 +7411,6 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - micromatch@3.1.10: - resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} - engines: {node: '>=0.10.0'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -7944,10 +7476,6 @@ packages: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} - minimist-options@3.0.2: - resolution: {integrity: sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==} - engines: {node: '>= 4'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -7966,10 +7494,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mixin-deep@1.3.2: - resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} - engines: {node: '>=0.10.0'} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -8005,9 +7529,6 @@ packages: resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} engines: {node: '>=10'} - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -8019,10 +7540,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanomatch@1.2.13: - resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} - engines: {node: '>=0.10.0'} - napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -8136,13 +7653,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - normalize-selector@0.2.0: - resolution: {integrity: sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==} - normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -8160,17 +7670,10 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - num2fraction@1.2.2: - resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-copy@0.1.0: - resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} - engines: {node: '>=0.10.0'} - object-deep-merge@2.0.1: resolution: {integrity: sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==} @@ -8190,10 +7693,6 @@ packages: resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} engines: {node: '>= 10'} - object-visit@1.0.1: - resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} - engines: {node: '>=0.10.0'} - object.assign@4.1.7: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} @@ -8210,10 +7709,6 @@ packages: resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} engines: {node: '>= 0.4'} - object.pick@1.3.0: - resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} - engines: {node: '>=0.10.0'} - object.values@1.2.1: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} @@ -8284,10 +7779,6 @@ packages: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} - p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -8296,10 +7787,6 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} - p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -8308,10 +7795,6 @@ packages: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -8325,9 +7808,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-entities@1.2.2: - resolution: {integrity: sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==} - parse-gitignore@2.0.0: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} @@ -8335,10 +7815,6 @@ packages: parse-imports-exports@0.2.4: resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} - parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} - parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -8366,10 +7842,6 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - pascalcase@0.1.1: - resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} - engines: {node: '>=0.10.0'} - patch-package@8.0.1: resolution: {integrity: sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==} engines: {node: '>=14', npm: '>5'} @@ -8381,13 +7853,6 @@ packages: path-data-parser@0.1.0: resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} - path-dirname@1.0.2: - resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} - - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -8421,10 +7886,6 @@ packages: path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - path-type@3.0.0: - resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} - engines: {node: '>=4'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -8442,9 +7903,6 @@ packages: perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - picocolors@0.2.1: - resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -8456,14 +7914,6 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - - pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} - pinia@3.0.4: resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} peerDependencies: @@ -8517,10 +7967,6 @@ packages: points-on-path@0.2.1: resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} - posix-character-classes@0.1.1: - resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} - engines: {node: '>=0.10.0'} - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -8608,49 +8054,24 @@ packages: peerDependencies: postcss: 8.5.15 - postcss-html@0.36.0: - resolution: {integrity: sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==} - peerDependencies: - postcss: 8.5.15 - postcss-syntax: '>=0.36.0' - postcss-image-set-function@8.0.0: resolution: {integrity: sha512-rEGNkOkNusf4+IuMmfEoIdLuVmvbExGbmG+MIsyV6jR5UaWSoyPcAYHV/PxzVDCmudyF+2Nh/o6Ub2saqUdnuA==} engines: {node: '>=20.19.0'} peerDependencies: postcss: 8.5.15 - postcss-jsx@0.36.4: - resolution: {integrity: sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA==} - peerDependencies: - postcss: 8.5.15 - postcss-syntax: '>=0.36.0' - postcss-lab-function@8.0.4: resolution: {integrity: sha512-dqcJSzVasdELD9xqJ1wfP95uzP57J6zFd80c7S3AWK127H9zwqR9Kbk5ZgyIfN2DiMStI7Vq8E7ablXNeTvpew==} engines: {node: '>=20.19.0'} peerDependencies: postcss: 8.5.15 - postcss-less@3.1.4: - resolution: {integrity: sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==} - engines: {node: '>=6.14.4'} - postcss-logical@9.0.0: resolution: {integrity: sha512-A4LNd9dk3q/juEUA9Gd8ALhBO3TeOeYurnyHLlf2aAToD94VHR8c5Uv7KNmf8YVRhTxvWsyug4c5fKtARzyIRQ==} engines: {node: '>=20.19.0'} peerDependencies: postcss: 8.5.15 - postcss-markdown@0.36.0: - resolution: {integrity: sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==} - peerDependencies: - postcss: 8.5.15 - postcss-syntax: '>=0.36.0' - - postcss-media-query-parser@0.2.3: - resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - postcss-nesting@14.0.0: resolution: {integrity: sha512-YGFOfVrjxYfeGTS5XctP1WCI5hu8Lr9SmntjfRC+iX5hCihEO+QZl9Ra+pkjqkgoVdDKvb2JccpElcowhZtzpw==} engines: {node: '>=20.19.0'} @@ -8697,40 +8118,18 @@ packages: peerDependencies: postcss: 8.5.15 - postcss-reporter@6.0.1: - resolution: {integrity: sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==} - engines: {node: '>=6'} - - postcss-resolve-nested-selector@0.1.6: - resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} - - postcss-safe-parser@4.0.2: - resolution: {integrity: sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==} - engines: {node: '>=6.0.0'} - postcss-safe-parser@7.0.1: resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} engines: {node: '>=18.0'} peerDependencies: postcss: 8.5.15 - postcss-sass@0.3.5: - resolution: {integrity: sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==} - - postcss-scss@2.1.1: - resolution: {integrity: sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==} - engines: {node: '>=6.0.0'} - postcss-selector-not@9.0.0: resolution: {integrity: sha512-xhAtTdHnVU2M/CrpYOPyRUvg3njhVlKmn2GNYXDaRJV9Ygx4d5OkSkc7NINzjUqnbDFtaKXlISOBeyMXU/zyFQ==} engines: {node: '>=20.19.0'} peerDependencies: postcss: 8.5.15 - postcss-selector-parser@3.1.2: - resolution: {integrity: sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==} - engines: {node: '>=8'} - postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} @@ -8740,34 +8139,6 @@ packages: peerDependencies: postcss: 8.5.15 - postcss-sorting@4.1.0: - resolution: {integrity: sha512-r4T2oQd1giURJdHQ/RMb72dKZCuLOdWx2B/XhXN1Y1ZdnwXsKH896Qz6vD4tFy9xSjpKNYhlZoJmWyhH/7JUQw==} - engines: {node: '>=6.14.3'} - - postcss-syntax@0.36.2: - resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==} - peerDependencies: - postcss: 8.5.15 - postcss-html: '*' - postcss-jsx: '*' - postcss-less: '*' - postcss-markdown: '*' - postcss-scss: '*' - peerDependenciesMeta: - postcss-html: - optional: true - postcss-jsx: - optional: true - postcss-less: - optional: true - postcss-markdown: - optional: true - postcss-scss: - optional: true - - postcss-value-parser@3.3.1: - resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} - postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -8871,10 +8242,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@1.1.0: - resolution: {integrity: sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA==} - engines: {node: '>=4'} - quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -8921,14 +8288,6 @@ packages: resolution: {integrity: sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==} deprecated: This package is no longer supported. Please use @npmcli/package-json instead. - read-pkg-up@3.0.0: - resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} - engines: {node: '>=4'} - - read-pkg@3.0.0: - resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} - engines: {node: '>=4'} - readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -8941,10 +8300,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - redent@2.0.0: - resolution: {integrity: sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==} - engines: {node: '>=4'} - refa@0.12.1: resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -8953,10 +8308,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regex-not@1.0.2: - resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} - engines: {node: '>=0.10.0'} - regex-recursion@5.1.1: resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} @@ -9012,33 +8363,12 @@ packages: remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - remark-parse@6.0.3: - resolution: {integrity: sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==} - remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - remark-stringify@6.0.4: - resolution: {integrity: sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==} - - remark@10.0.1: - resolution: {integrity: sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==} - - repeat-element@1.1.4: - resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} - engines: {node: '>=0.10.0'} - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - - replace-ext@1.0.0: - resolution: {integrity: sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA==} - engines: {node: '>= 0.10'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -9071,10 +8401,6 @@ packages: resolution: {integrity: sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==} engines: {node: '>=18'} - resolve-from@3.0.0: - resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} - engines: {node: '>=4'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -9082,10 +8408,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve-url@0.2.1: - resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} - deprecated: https://github.com/lydell/resolve-url#deprecated - resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -9103,10 +8425,6 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - ret@0.1.15: - resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} - engines: {node: '>=0.12'} - retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -9191,9 +8509,6 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} - safe-regex@1.1.0: - resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -9269,10 +8584,6 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -9338,10 +8649,6 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - slice-ansi@2.1.0: - resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} - engines: {node: '>=6'} - slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -9364,18 +8671,6 @@ packages: resolution: {integrity: sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==} engines: {node: '>=12.17.0'} - snapdragon-node@2.1.1: - resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} - engines: {node: '>=0.10.0'} - - snapdragon-util@3.0.1: - resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} - engines: {node: '>=0.10.0'} - - snapdragon@0.8.2: - resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} - engines: {node: '>=0.10.0'} - snapsvg-cjs@0.0.6: resolution: {integrity: sha512-7NNvoGrc3BQvWz5rWK1DsD5/Vni4STswz5B3JrBADboQWcN8OBVGjYVJFPT5JkUXb2iVnEflZANhufEpEcTHXw==} peerDependencies: @@ -9388,21 +8683,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-resolve@0.5.3: - resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated - source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map-url@0.4.1: - resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} - deprecated: See https://github.com/lydell/source-map-url#deprecated - - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -9438,14 +8721,6 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} - specificity@0.4.1: - resolution: {integrity: sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==} - hasBin: true - - split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -9462,13 +8737,6 @@ packages: resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} engines: {node: '>= 6'} - state-toggle@1.0.3: - resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==} - - static-extend@0.1.2: - resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} - engines: {node: '>=0.10.0'} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -9483,10 +8751,6 @@ packages: stream-to-array@2.3.0: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} - string-width@3.1.0: - resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} - engines: {node: '>=6'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -9525,9 +8789,6 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@1.3.2: - resolution: {integrity: sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -9535,10 +8796,6 @@ packages: resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} engines: {node: '>=4'} - strip-ansi@5.2.0: - resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} - engines: {node: '>=6'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -9559,10 +8816,6 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - strip-indent@2.0.0: - resolution: {integrity: sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==} - engines: {node: '>=4'} - strip-indent@4.1.1: resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} engines: {node: '>=12'} @@ -9584,9 +8837,6 @@ packages: stubborn-utils@1.0.2: resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} - style-search@0.1.0: - resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} - styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -9600,9 +8850,6 @@ packages: babel-plugin-macros: optional: true - stylelint-config-rational-order@0.1.2: - resolution: {integrity: sha512-Qo7ZQaihCwTqijfZg4sbdQQHtugOX/B1/fYh018EiDZHW+lkqH9uHOnsDwDPGZrYJuB6CoyI7MZh2ecw2dOkew==} - stylelint-config-recommended@18.0.0: resolution: {integrity: sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==} engines: {node: '>=20.19.0'} @@ -9615,12 +8862,6 @@ packages: peerDependencies: stylelint: ^17.0.0 - stylelint-order@2.2.1: - resolution: {integrity: sha512-019KBV9j8qp1MfBjJuotse6MgaZqGVtXMc91GU9MsS9Feb+jYUvUU3Z8XiClqPdqJZQ0ryXQJGg3U3PcEjXwfg==} - engines: {node: '>=6'} - peerDependencies: - stylelint: ^9.10.1 || ^10.0.0 - stylelint-order@8.1.1: resolution: {integrity: sha512-LqsEB6VggJuu5v10RtkrQsBObcdwBE7GuAOlwfc/LR3VL/w8UqKX2BOLIjhyGt0Gne/njo7gRNGiJAKhfmPMNw==} engines: {node: '>=20.19.0'} @@ -9632,11 +8873,6 @@ packages: engines: {node: '>=20.19.0'} hasBin: true - stylelint@9.10.1: - resolution: {integrity: sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==} - engines: {node: '>=6'} - hasBin: true - stylis@4.4.0: resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} @@ -9645,9 +8881,6 @@ packages: engines: {node: '>=18'} hasBin: true - sugarss@2.0.0: - resolution: {integrity: sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==} - sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -9691,10 +8924,6 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - table@5.4.6: - resolution: {integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==} - engines: {node: '>=6.0.0'} - table@6.9.0: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} @@ -9775,22 +9004,10 @@ packages: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} - to-object-path@0.3.0: - resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} - engines: {node: '>=0.10.0'} - - to-regex-range@2.1.1: - resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} - engines: {node: '>=0.10.0'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - to-regex@3.0.2: - resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} - engines: {node: '>=0.10.0'} - to-valid-identifier@1.0.0: resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} engines: {node: '>=20'} @@ -9825,20 +9042,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - trim-newlines@2.0.0: - resolution: {integrity: sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA==} - engines: {node: '>=4'} - - trim-trailing-lines@1.1.4: - resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==} - - trim@0.0.1: - resolution: {integrity: sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==} - deprecated: Use String.prototype.trim() instead - - trough@1.0.5: - resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} - trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -9979,9 +9182,6 @@ packages: unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - unherit@1.1.3: - resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==} - unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} @@ -9996,49 +9196,21 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unified@7.1.0: - resolution: {integrity: sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==} - - union-value@1.0.1: - resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} - engines: {node: '>=0.10.0'} - - uniq@1.0.1: - resolution: {integrity: sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==} - - unist-util-find-all-after@1.0.5: - resolution: {integrity: sha512-lWgIc3rrTMTlK1Y0hEuL+k+ApzFk78h+lsaa2gHf63Gp5Ww+mt11huDniuaoq1H+XMK2lIIjjPkncxXcDp3QDw==} - - unist-util-is@3.0.0: - resolution: {integrity: sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==} - unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - unist-util-remove-position@1.1.4: - resolution: {integrity: sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==} - unist-util-remove-position@5.0.0: resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} - unist-util-stringify-position@1.1.2: - resolution: {integrity: sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==} - unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - unist-util-visit-parents@2.1.2: - resolution: {integrity: sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==} - unist-util-visit-parents@6.0.2: resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - unist-util-visit@1.4.1: - resolution: {integrity: sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==} - unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} @@ -10091,10 +9263,6 @@ packages: unrs-resolver@1.12.2: resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} - unset-value@1.0.0: - resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} - engines: {node: '>=0.10.0'} - update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -10104,17 +9272,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - urix@0.1.0: - resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} - deprecated: Please see https://github.com/lydell/urix#deprecated - urlpattern-polyfill@10.1.0: resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} - use@3.1.1: - resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} - engines: {node: '>=0.10.0'} - utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} @@ -10257,21 +9417,12 @@ packages: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} engines: {node: '>=0.6.0'} - vfile-location@2.0.6: - resolution: {integrity: sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==} - vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - vfile-message@1.1.1: - resolution: {integrity: sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==} - vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - vfile@3.0.1: - resolution: {integrity: sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==} - vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} @@ -10587,10 +9738,6 @@ packages: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} - write@1.0.3: - resolution: {integrity: sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==} - engines: {node: '>=4'} - ws@8.20.1: resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} @@ -10603,9 +9750,6 @@ packages: utf-8-validate: optional: true - x-is-string@0.1.0: - resolution: {integrity: sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==} - xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -10625,10 +9769,6 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -10652,9 +9792,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yargs-parser@10.1.0: - resolution: {integrity: sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -12838,11 +11975,6 @@ snapshots: '@mixmark-io/domino@2.2.0': {} - '@mrmlnc/readdir-enhanced@2.2.1': - dependencies: - call-me-maybe: 1.0.2 - glob-to-regexp: 0.3.0 - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -12914,8 +12046,6 @@ snapshots: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - '@nodelib/fs.stat@1.1.3': {} - '@nodelib/fs.stat@2.0.5': {} '@nodelib/fs.walk@1.2.8': @@ -13723,11 +12853,6 @@ snapshots: '@types/geojson@7946.0.16': {} - '@types/glob@7.2.0': - dependencies: - '@types/minimatch': 6.0.0 - '@types/node': 22.19.19 - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -13758,10 +12883,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/minimatch@6.0.0': - dependencies: - minimatch: 10.2.5 - '@types/ms@2.1.0': {} '@types/node-fetch@2.6.13': @@ -13810,23 +12931,13 @@ snapshots: '@types/turndown@5.0.6': {} - '@types/unist@2.0.11': {} + '@types/underscore@1.13.0': {} '@types/unist@3.0.3': {} '@types/verror@1.10.11': optional: true - '@types/vfile-message@2.0.0': - dependencies: - vfile-message: 4.0.3 - - '@types/vfile@3.0.2': - dependencies: - '@types/node': 22.19.19 - '@types/unist': 2.0.11 - '@types/vfile-message': 2.0.0 - '@types/web-bluetooth@0.0.21': {} '@types/webfontloader@1.6.38': {} @@ -14571,8 +13682,6 @@ snapshots: ansi-regex@3.0.1: {} - ansi-regex@4.1.1: {} - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -14650,12 +13759,6 @@ snapshots: aria-query@5.3.2: {} - arr-diff@4.0.0: {} - - arr-flatten@1.1.0: {} - - arr-union@3.1.0: {} - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -14678,14 +13781,6 @@ snapshots: array-timsort@1.0.3: {} - array-union@1.0.2: - dependencies: - array-uniq: 1.0.3 - - array-uniq@1.0.3: {} - - array-unique@0.3.2: {} - array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.9 @@ -14737,8 +13832,6 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - arrify@1.0.1: {} - asap@2.0.6: {} assert-plus@1.0.0: @@ -14746,8 +13839,6 @@ snapshots: assertion-error@2.0.1: {} - assign-symbols@1.0.0: {} - ast-module-types@6.0.2: {} ast-types-flow@0.0.8: {} @@ -14758,8 +13849,6 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 - astral-regex@1.0.0: {} - astral-regex@2.0.0: {} async-exit-hook@2.0.1: {} @@ -14776,8 +13865,6 @@ snapshots: atoa@1.0.0: {} - atob@2.1.2: {} - atom-fs@0.2.1: dependencies: mapped-disposable: 1.0.3 @@ -14796,16 +13883,6 @@ snapshots: postcss: 8.5.15 postcss-value-parser: 4.2.0 - autoprefixer@9.8.8: - dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001792 - normalize-range: 0.1.2 - num2fraction: 1.2.2 - picocolors: 0.2.1 - postcss: 8.5.15 - postcss-value-parser: 4.2.0 - available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -14826,8 +13903,6 @@ snapshots: axobject-query@4.1.0: {} - bail@1.0.5: {} - bail@2.0.2: {} balanced-match@1.0.2: {} @@ -14836,16 +13911,6 @@ snapshots: base64-js@1.5.1: {} - base@0.11.2: - dependencies: - cache-base: 1.0.1 - class-utils: 0.3.6 - component-emitter: 1.3.1 - define-property: 1.0.0 - isobject: 3.0.1 - mixin-deep: 1.3.2 - pascalcase: 0.1.1 - baseline-browser-mapping@2.10.29: {} batch-processor@1.0.0: {} @@ -14902,21 +13967,6 @@ snapshots: dependencies: balanced-match: 4.0.4 - braces@2.3.2: - dependencies: - arr-flatten: 1.1.0 - array-unique: 0.3.2 - extend-shallow: 2.0.1 - fill-range: 4.0.0 - isobject: 3.0.1 - repeat-element: 1.1.4 - snapdragon: 0.8.2 - snapdragon-node: 2.1.1 - split-string: 3.1.0 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -14976,18 +14026,6 @@ snapshots: cac@7.0.0: {} - cache-base@1.0.1: - dependencies: - collection-visit: 1.0.0 - component-emitter: 1.3.1 - get-value: 2.0.6 - has-value: 1.0.0 - isobject: 3.0.1 - set-value: 2.0.1 - to-object-path: 0.3.0 - union-value: 1.0.1 - unset-value: 1.0.0 - cacheable-lookup@5.0.4: {} cacheable-request@7.0.4: @@ -15025,32 +14063,10 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - call-me-maybe@1.0.2: {} - - caller-callsite@2.0.0: - dependencies: - callsites: 2.0.0 - - caller-path@2.0.0: - dependencies: - caller-callsite: 2.0.0 - - callsites@2.0.0: {} - callsites@3.1.0: {} - camelcase-keys@4.2.0: - dependencies: - camelcase: 4.1.0 - map-obj: 2.0.0 - quick-lru: 1.1.0 - - camelcase@4.1.0: {} - caniuse-lite@1.0.30001792: {} - ccount@1.1.0: {} - ccount@2.0.1: {} ced@2.0.0: @@ -15074,20 +14090,12 @@ snapshots: change-case@5.4.4: {} - character-entities-html4@1.1.4: {} - character-entities-html4@2.1.0: {} - character-entities-legacy@1.1.4: {} - character-entities-legacy@3.0.0: {} - character-entities@1.2.4: {} - character-entities@2.0.2: {} - character-reference-invalid@1.1.4: {} - chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -15104,13 +14112,6 @@ snapshots: ci-info@4.4.0: {} - class-utils@0.3.6: - dependencies: - arr-union: 3.1.0 - define-property: 0.2.5 - isobject: 3.0.1 - static-extend: 0.1.2 - clean-regexp@1.0.0: dependencies: escape-string-regexp: 1.0.5 @@ -15141,11 +14142,6 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 - clone-regexp@1.0.1: - dependencies: - is-regexp: 1.0.0 - is-supported-regexp-flag: 1.0.1 - clone-regexp@3.0.0: dependencies: is-regexp: 3.1.0 @@ -15170,13 +14166,6 @@ snapshots: codemirror@5.65.21: {} - collapse-white-space@1.0.6: {} - - collection-visit@1.0.0: - dependencies: - map-visit: 1.0.0 - object-visit: 1.0.1 - color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -15233,8 +14222,6 @@ snapshots: compare-versions@6.1.1: {} - component-emitter@1.3.1: {} - concat-map@0.0.1: {} conf@15.1.0: @@ -15276,8 +14263,6 @@ snapshots: dependencies: is-what: 5.5.0 - copy-descriptor@0.1.1: {} - core-js-compat@3.49.0: dependencies: browserslist: 4.28.2 @@ -15293,13 +14278,6 @@ snapshots: dependencies: layout-base: 2.0.1 - cosmiconfig@5.2.1: - dependencies: - import-fresh: 2.0.0 - is-directory: 0.3.1 - js-yaml: 3.14.2 - parse-json: 4.0.0 - cosmiconfig@9.0.1(typescript@6.0.3): dependencies: env-paths: 2.2.1 @@ -15389,10 +14367,6 @@ snapshots: csstype@3.2.3: {} - currently-unhandled@0.4.1: - dependencies: - array-find-index: 1.0.2 - custom-event@1.0.1: {} cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.3): @@ -15620,10 +14594,6 @@ snapshots: dependencies: mimic-function: 5.0.1 - debug@2.6.9: - dependencies: - ms: 2.0.0 - debug@3.2.7: dependencies: ms: 2.1.3 @@ -15634,21 +14604,12 @@ snapshots: debuglog@1.0.1: {} - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - decimal.js@10.6.0: {} decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 - decode-uri-component@0.2.2: {} - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -15696,19 +14657,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - define-property@0.2.5: - dependencies: - is-descriptor: 0.1.8 - - define-property@1.0.0: - dependencies: - is-descriptor: 1.0.4 - - define-property@2.0.2: - dependencies: - is-descriptor: 1.0.4 - isobject: 3.0.1 - delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 @@ -15808,10 +14756,6 @@ snapshots: minimatch: 3.1.5 p-limit: 3.1.0 - dir-glob@2.2.2: - dependencies: - path-type: 3.0.0 - dmg-builder@26.8.1(electron-builder-squirrel-windows@26.8.1): dependencies: app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1) @@ -15856,11 +14800,6 @@ snapshots: dependencies: create-point-cb: 1.2.0 - dom-serializer@0.2.2: - dependencies: - domelementtype: 2.3.0 - entities: 2.2.0 - dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -15873,14 +14812,8 @@ snapshots: is-array: 1.0.1 iselement: 1.1.4 - domelementtype@1.3.1: {} - domelementtype@2.3.0: {} - domhandler@2.4.2: - dependencies: - domelementtype: 1.3.1 - domhandler@5.0.3: dependencies: domelementtype: 2.3.0 @@ -15893,11 +14826,6 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 - domutils@1.7.0: - dependencies: - dom-serializer: 0.2.2 - domelementtype: 1.3.1 - domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -15908,10 +14836,6 @@ snapshots: dependencies: type-fest: 5.6.0 - dot-prop@5.3.0: - dependencies: - is-obj: 2.0.0 - dotenv-expand@11.0.7: dependencies: dotenv: 16.6.1 @@ -16068,8 +14992,6 @@ snapshots: emoji-regex@10.6.0: {} - emoji-regex@7.0.3: {} - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -16092,10 +15014,6 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 - entities@1.1.2: {} - - entities@2.2.0: {} - entities@4.5.0: {} entities@6.0.1: {} @@ -17005,26 +15923,10 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - execall@1.0.0: - dependencies: - clone-regexp: 1.0.1 - execall@3.0.0: dependencies: clone-regexp: 3.0.0 - expand-brackets@2.1.4: - dependencies: - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - posix-character-classes: 0.1.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - expand-template@2.0.3: {} expect-type@1.3.0: {} @@ -17070,26 +15972,8 @@ snapshots: dependencies: is-extendable: 0.1.1 - extend-shallow@3.0.2: - dependencies: - assign-symbols: 1.0.0 - is-extendable: 1.0.1 - extend@3.0.2: {} - extglob@2.0.4: - dependencies: - array-unique: 0.3.2 - define-property: 1.0.0 - expand-brackets: 2.1.4 - extend-shallow: 2.0.1 - fragment-cache: 0.2.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - extract-zip@2.0.1: dependencies: debug: 4.4.3 @@ -17107,17 +15991,6 @@ snapshots: fast-diff@1.3.0: {} - fast-glob@2.2.7: - dependencies: - '@mrmlnc/readdir-enhanced': 2.2.1 - '@nodelib/fs.stat': 1.1.3 - glob-parent: 3.1.0 - is-glob: 4.0.3 - merge2: 1.4.1 - micromatch: 3.1.10 - transitivePeerDependencies: - - supports-color - fast-glob@3.3.1: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -17188,10 +16061,6 @@ snapshots: dependencies: flat-cache: 6.1.22 - file-entry-cache@4.0.0: - dependencies: - flat-cache: 2.0.1 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -17223,13 +16092,6 @@ snapshots: tsconfig-paths: 4.2.0 typescript: 5.9.3 - fill-range@4.0.0: - dependencies: - extend-shallow: 2.0.1 - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range: 2.1.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -17247,10 +16109,6 @@ snapshots: find-up-simple@1.0.1: {} - find-up@2.1.0: - dependencies: - locate-path: 2.0.0 - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -17265,12 +16123,6 @@ snapshots: dependencies: micromatch: 4.0.8 - flat-cache@2.0.1: - dependencies: - flatted: 2.0.2 - rimraf: 2.6.3 - write: 1.0.3 - flat-cache@4.0.1: dependencies: flatted: 3.4.2 @@ -17282,8 +16134,6 @@ snapshots: flatted: 3.4.2 hookified: 1.15.1 - flatted@2.0.2: {} - flatted@3.4.2: {} flowchart.js@1.18.0: @@ -17298,8 +16148,6 @@ snapshots: dependencies: is-callable: 1.2.7 - for-in@1.0.2: {} - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -17326,10 +16174,6 @@ snapshots: fraction.js@5.3.4: {} - fragment-cache@0.2.1: - dependencies: - map-cache: 0.2.2 - fresh@2.0.0: {} fs-constants@1.0.0: {} @@ -17423,8 +16267,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stdin@6.0.0: {} - get-stream@5.2.0: dependencies: pump: 3.0.4 @@ -17441,19 +16283,12 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-value@2.0.6: {} - github-from-package@0.0.0: {} github-markdown-css@5.9.0: {} github-slugger@2.0.0: {} - glob-parent@3.1.0: - dependencies: - is-glob: 3.1.0 - path-dirname: 1.0.2 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -17462,8 +16297,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob-to-regexp@0.3.0: {} - glob@12.0.0: dependencies: foreground-child: 3.3.1 @@ -17529,19 +16362,6 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.4.0 - globby@9.2.0: - dependencies: - '@types/glob': 7.2.0 - array-union: 1.0.2 - dir-glob: 2.2.2 - fast-glob: 2.2.7 - glob: 7.2.3 - ignore: 4.0.6 - pify: 4.0.1 - slash: 2.0.0 - transitivePeerDependencies: - - supports-color - globjoin@0.1.4: {} globrex@0.1.2: {} @@ -17609,25 +16429,6 @@ snapshots: dependencies: has-symbols: 1.1.0 - has-value@0.3.1: - dependencies: - get-value: 2.0.6 - has-values: 0.1.4 - isobject: 2.1.0 - - has-value@1.0.0: - dependencies: - get-value: 2.0.6 - has-values: 1.0.0 - isobject: 3.0.1 - - has-values@0.1.4: {} - - has-values@1.0.0: - dependencies: - is-number: 3.0.0 - kind-of: 4.0.0 - hashery@1.5.1: dependencies: hookified: 1.15.1 @@ -17754,8 +16555,6 @@ snapshots: html-escaper@2.0.2: {} - html-tags@2.0.0: {} - html-tags@5.1.0: {} html-void-elements@3.0.0: {} @@ -17767,15 +16566,6 @@ snapshots: domutils: 3.2.2 entities: 7.0.1 - htmlparser2@3.10.1: - dependencies: - domelementtype: 1.3.1 - domhandler: 2.4.2 - domutils: 1.7.0 - entities: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - http-cache-semantics@4.2.0: {} http-errors@2.0.1: @@ -17834,24 +16624,15 @@ snapshots: ieee754@1.2.1: {} - ignore@4.0.6: {} - ignore@5.3.2: {} ignore@7.0.5: {} - import-fresh@2.0.0: - dependencies: - caller-path: 2.0.0 - resolve-from: 3.0.0 - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-lazy@3.1.0: {} - import-meta-resolve@4.2.0: {} imurmurhash@0.1.4: {} @@ -17860,8 +16641,6 @@ snapshots: indent-string@5.0.0: {} - indexes-of@1.0.1: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -17889,19 +16668,6 @@ snapshots: irregular-plurals@1.4.0: {} - is-accessor-descriptor@1.0.2: - dependencies: - hasown: 2.0.3 - - is-alphabetical@1.0.4: {} - - is-alphanumeric@1.0.0: {} - - is-alphanumerical@1.0.4: - dependencies: - is-alphabetical: 1.0.4 - is-decimal: 1.0.4 - is-arguments@1.2.0: dependencies: call-bound: 1.0.4 @@ -17934,10 +16700,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-buffer@1.1.6: {} - - is-buffer@2.0.5: {} - is-builtin-module@5.0.0: dependencies: builtin-modules: 5.2.0 @@ -17952,10 +16714,6 @@ snapshots: dependencies: hasown: 2.0.3 - is-data-descriptor@1.0.1: - dependencies: - hasown: 2.0.3 - is-data-view@1.0.2: dependencies: call-bound: 1.0.4 @@ -17967,36 +16725,16 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-decimal@1.0.4: {} - - is-descriptor@0.1.8: - dependencies: - is-accessor-descriptor: 1.0.2 - is-data-descriptor: 1.0.1 - - is-descriptor@1.0.4: - dependencies: - is-accessor-descriptor: 1.0.2 - is-data-descriptor: 1.0.1 - - is-directory@0.3.1: {} - is-docker@2.2.1: {} is-extendable@0.1.1: {} - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: dependencies: call-bound: 1.0.4 - is-fullwidth-code-point@2.0.0: {} - is-fullwidth-code-point@3.0.0: {} is-generator-function@1.1.2: @@ -18007,16 +16745,10 @@ snapshots: has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 - is-glob@3.1.0: - dependencies: - is-extglob: 2.1.1 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - is-hexadecimal@1.0.4: {} - is-interactive@1.0.0: {} is-map@2.0.3: {} @@ -18028,26 +16760,14 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-number@3.0.0: - dependencies: - kind-of: 3.2.2 - is-number@7.0.0: {} is-obj@1.0.1: {} - is-obj@2.0.0: {} - is-path-inside@4.0.0: {} - is-plain-obj@1.1.0: {} - is-plain-obj@4.1.0: {} - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - is-potential-custom-element-name@1.0.1: {} is-promise@4.0.0: {} @@ -18076,8 +16796,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-supported-regexp-flag@1.0.1: {} - is-symbol@1.1.1: dependencies: call-bound: 1.0.4 @@ -18105,18 +16823,10 @@ snapshots: is-what@5.5.0: {} - is-whitespace-character@1.0.4: {} - - is-windows@1.0.2: {} - - is-word-character@1.0.4: {} - is-wsl@2.2.0: dependencies: is-docker: 2.2.1 - isarray@1.0.0: {} - isarray@2.0.5: {} isbinaryfile@4.0.10: {} @@ -18133,12 +16843,6 @@ snapshots: iso-639-1@3.1.5: {} - isobject@2.1.0: - dependencies: - isarray: 1.0.0 - - isobject@3.0.1: {} - istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -18231,8 +16935,6 @@ snapshots: json-buffer@3.0.1: {} - json-parse-better-errors@1.0.2: {} - json-parse-even-better-errors@2.3.1: {} json-schema-traverse@0.4.1: {} @@ -18306,14 +17008,6 @@ snapshots: khroma@2.1.0: {} - kind-of@3.2.2: - dependencies: - is-buffer: 1.1.6 - - kind-of@4.0.0: - dependencies: - is-buffer: 1.1.6 - kind-of@6.0.3: {} klaw-sync@6.0.0: @@ -18322,8 +17016,6 @@ snapshots: kleur@4.1.5: {} - known-css-properties@0.11.0: {} - kolorist@1.8.0: {} language-subtag-registry@0.3.23: {} @@ -18338,8 +17030,6 @@ snapshots: lazy-val@1.0.5: {} - leven@2.1.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -18411,13 +17101,6 @@ snapshots: lines-and-columns@1.2.4: {} - load-json-file@4.0.0: - dependencies: - graceful-fs: 4.2.11 - parse-json: 4.0.0 - pify: 3.0.0 - strip-bom: 3.0.0 - loader-utils@3.3.1: {} local-pkg@1.2.1: @@ -18426,11 +17109,6 @@ snapshots: pkg-types: 2.3.1 quansync: 0.2.11 - locate-path@2.0.0: - dependencies: - p-locate: 2.0.0 - path-exists: 3.0.0 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -18476,19 +17154,12 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - longest-streak@2.0.4: {} - longest-streak@3.1.0: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - loud-rejection@1.6.0: - dependencies: - currently-unhandled: 0.4.1 - signal-exit: 3.0.7 - lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} @@ -18536,22 +17207,8 @@ snapshots: dependencies: semver: 7.8.0 - map-cache@0.2.2: {} - - map-obj@1.0.1: {} - - map-obj@2.0.0: {} - - map-visit@1.0.0: - dependencies: - object-visit: 1.0.1 - mapped-disposable@1.0.3: {} - markdown-escapes@1.0.4: {} - - markdown-table@1.1.3: {} - markdown-table@3.0.4: {} marked-highlight@2.2.4(marked@16.4.2): @@ -18567,14 +17224,8 @@ snapshots: math-intrinsics@1.1.0: {} - mathml-tag-names@2.1.3: {} - mathml-tag-names@4.0.0: {} - mdast-util-compact@1.0.4: - dependencies: - unist-util-visit: 1.4.1 - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -18724,18 +17375,6 @@ snapshots: meow@14.1.0: {} - meow@5.0.0: - dependencies: - camelcase-keys: 4.2.0 - decamelize-keys: 1.1.1 - loud-rejection: 1.6.0 - minimist-options: 3.0.2 - normalize-package-data: 2.5.0 - read-pkg-up: 3.0.0 - redent: 2.0.0 - trim-newlines: 2.0.0 - yargs-parser: 10.1.0 - merge-descriptors@2.0.0: {} merge-stream@2.0.0: {} @@ -18974,24 +17613,6 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@3.1.10: - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - braces: 2.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - extglob: 2.0.4 - fragment-cache: 0.2.1 - kind-of: 6.0.3 - nanomatch: 1.2.13 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -19051,11 +17672,6 @@ snapshots: dependencies: brace-expansion: 2.1.0 - minimist-options@3.0.2: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - minimist@1.2.8: {} minipass@4.2.8: {} @@ -19068,11 +17684,6 @@ snapshots: mitt@3.0.1: {} - mixin-deep@1.3.2: - dependencies: - for-in: 1.0.2 - is-extendable: 1.0.1 - mkdirp-classic@0.5.3: {} mkdirp@0.5.6: @@ -19107,30 +17718,12 @@ snapshots: mrmime@1.0.1: {} - ms@2.0.0: {} - ms@2.1.3: {} muggle-string@0.4.1: {} nanoid@3.3.12: {} - nanomatch@1.2.13: - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - fragment-cache: 0.2.1 - is-windows: 1.0.2 - kind-of: 6.0.3 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - napi-build-utils@2.0.0: {} napi-postinstall@0.3.4: {} @@ -19252,10 +17845,6 @@ snapshots: normalize-path@3.0.0: {} - normalize-range@0.1.2: {} - - normalize-selector@0.2.0: {} - normalize-url@6.1.0: {} normalize-wheel-es@1.2.0: {} @@ -19270,16 +17859,8 @@ snapshots: dependencies: boolbase: 1.0.0 - num2fraction@1.2.2: {} - object-assign@4.1.1: {} - object-copy@0.1.0: - dependencies: - copy-descriptor: 0.1.1 - define-property: 0.2.5 - kind-of: 3.2.2 - object-deep-merge@2.0.1: {} object-inspect@1.13.4: {} @@ -19293,10 +17874,6 @@ snapshots: object-treeify@1.1.33: {} - object-visit@1.0.1: - dependencies: - isobject: 3.0.1 - object.assign@4.1.7: dependencies: call-bind: 1.0.9 @@ -19326,10 +17903,6 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 - object.pick@1.3.0: - dependencies: - isobject: 3.0.1 - object.values@1.2.1: dependencies: call-bind: 1.0.9 @@ -19436,10 +18009,6 @@ snapshots: p-cancelable@2.1.1: {} - p-limit@1.3.0: - dependencies: - p-try: 1.0.0 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -19448,10 +18017,6 @@ snapshots: dependencies: yocto-queue: 1.2.2 - p-locate@2.0.0: - dependencies: - p-limit: 1.3.0 - p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -19460,8 +18025,6 @@ snapshots: dependencies: p-limit: 4.0.0 - p-try@1.0.0: {} - package-json-from-dist@1.0.1: {} package-manager-detector@1.6.0: {} @@ -19472,26 +18035,12 @@ snapshots: dependencies: callsites: 3.1.0 - parse-entities@1.2.2: - dependencies: - character-entities: 1.2.4 - character-entities-legacy: 1.1.4 - character-reference-invalid: 1.1.4 - is-alphanumerical: 1.0.4 - is-decimal: 1.0.4 - is-hexadecimal: 1.0.4 - parse-gitignore@2.0.0: {} parse-imports-exports@0.2.4: dependencies: parse-statements: 1.0.11 - parse-json@4.0.0: - dependencies: - error-ex: 1.3.4 - json-parse-better-errors: 1.0.2 - parse-json@5.2.0: dependencies: '@babel/code-frame': 7.29.0 @@ -19519,8 +18068,6 @@ snapshots: parseurl@1.3.3: {} - pascalcase@0.1.1: {} - patch-package@8.0.1: dependencies: '@yarnpkg/lockfile': 1.1.0 @@ -19542,10 +18089,6 @@ snapshots: path-data-parser@0.1.0: {} - path-dirname@1.0.2: {} - - path-exists@3.0.0: {} - path-exists@4.0.0: {} path-expression-matcher@1.5.0: {} @@ -19570,10 +18113,6 @@ snapshots: path-to-regexp@8.4.2: {} - path-type@3.0.0: - dependencies: - pify: 3.0.0 - pathe@2.0.3: {} pe-library@0.4.1: {} @@ -19584,18 +18123,12 @@ snapshots: perfect-debounce@1.0.0: {} - picocolors@0.2.1: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} picomatch@4.0.4: {} - pify@3.0.0: {} - - pify@4.0.1: {} - pinia@3.0.4(typescript@5.9.3)(vue@3.5.34(typescript@5.9.3)): dependencies: '@vue/devtools-api': 7.7.9 @@ -19654,8 +18187,6 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - posix-character-classes@0.1.1: {} - possible-typed-array-names@1.1.0: {} postcss-attribute-case-insensitive@8.0.0(postcss@8.5.15): @@ -19744,26 +18275,12 @@ snapshots: dependencies: postcss: 8.5.15 - postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15): - dependencies: - htmlparser2: 3.10.1 - postcss: 8.5.15 - postcss-syntax: 0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-jsx@0.36.4)(postcss-less@3.1.4)(postcss-markdown@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-scss@2.1.1)(postcss@8.5.15) - postcss-image-set-function@8.0.0(postcss@8.5.15): dependencies: '@csstools/utilities': 3.0.0(postcss@8.5.15) postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-jsx@0.36.4(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15): - dependencies: - '@babel/core': 7.29.0 - postcss: 8.5.15 - postcss-syntax: 0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-jsx@0.36.4)(postcss-less@3.1.4)(postcss-markdown@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-scss@2.1.1)(postcss@8.5.15) - transitivePeerDependencies: - - supports-color - postcss-lab-function@8.0.4(postcss@8.5.15): dependencies: '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) @@ -19773,24 +18290,11 @@ snapshots: '@csstools/utilities': 3.0.0(postcss@8.5.15) postcss: 8.5.15 - postcss-less@3.1.4: - dependencies: - postcss: 8.5.15 - postcss-logical@9.0.0(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-markdown@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15): - dependencies: - postcss: 8.5.15 - postcss-syntax: 0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-jsx@0.36.4)(postcss-less@3.1.4)(postcss-markdown@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-scss@2.1.1)(postcss@8.5.15) - remark: 10.0.1 - unist-util-find-all-after: 1.0.5 - - postcss-media-query-parser@0.2.3: {} - postcss-nesting@14.0.0(postcss@8.5.15): dependencies: '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) @@ -19904,43 +18408,15 @@ snapshots: dependencies: postcss: 8.5.15 - postcss-reporter@6.0.1: - dependencies: - chalk: 2.4.2 - lodash: 4.18.1 - log-symbols: 2.2.0 - postcss: 8.5.15 - - postcss-resolve-nested-selector@0.1.6: {} - - postcss-safe-parser@4.0.2: - dependencies: - postcss: 8.5.15 - postcss-safe-parser@7.0.1(postcss@8.5.15): dependencies: postcss: 8.5.15 - postcss-sass@0.3.5: - dependencies: - gonzales-pe: 4.3.0 - postcss: 8.5.15 - - postcss-scss@2.1.1: - dependencies: - postcss: 8.5.15 - postcss-selector-not@9.0.0(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-selector-parser@3.1.2: - dependencies: - dot-prop: 5.3.0 - indexes-of: 1.0.1 - uniq: 1.0.1 - postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 @@ -19950,23 +18426,6 @@ snapshots: dependencies: postcss: 8.5.15 - postcss-sorting@4.1.0: - dependencies: - lodash: 4.18.1 - postcss: 8.5.15 - - postcss-syntax@0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-jsx@0.36.4)(postcss-less@3.1.4)(postcss-markdown@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-scss@2.1.1)(postcss@8.5.15): - dependencies: - postcss: 8.5.15 - optionalDependencies: - postcss-html: 0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15) - postcss-jsx: 0.36.4(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15) - postcss-less: 3.1.4 - postcss-markdown: 0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15) - postcss-scss: 2.1.1 - - postcss-value-parser@3.3.1: {} - postcss-value-parser@4.2.0: {} postcss-values-parser@6.0.2(postcss@8.5.15): @@ -20090,8 +18549,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-lru@1.1.0: {} - quick-lru@5.1.1: {} quote-unquote@1.0.0: {} @@ -20149,17 +18606,6 @@ snapshots: normalize-package-data: 2.5.0 npm-normalize-package-bin: 1.0.1 - read-pkg-up@3.0.0: - dependencies: - find-up: 2.1.0 - read-pkg: 3.0.0 - - read-pkg@3.0.0: - dependencies: - load-json-file: 4.0.0 - normalize-package-data: 2.5.0 - path-type: 3.0.0 - readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -20175,11 +18621,6 @@ snapshots: readdirp@5.0.0: {} - redent@2.0.0: - dependencies: - indent-string: 3.2.0 - strip-indent: 2.0.0 - refa@0.12.1: dependencies: '@eslint-community/regexpp': 4.12.2 @@ -20195,11 +18636,6 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - regex-not@1.0.2: - dependencies: - extend-shallow: 3.0.2 - safe-regex: 1.1.0 - regex-recursion@5.1.1: dependencies: regex: 5.1.1 @@ -20301,24 +18737,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-parse@6.0.3: - dependencies: - collapse-white-space: 1.0.6 - is-alphabetical: 1.0.4 - is-decimal: 1.0.4 - is-whitespace-character: 1.0.4 - is-word-character: 1.0.4 - markdown-escapes: 1.0.4 - parse-entities: 1.2.2 - repeat-string: 1.6.1 - state-toggle: 1.0.3 - trim: 0.0.1 - trim-trailing-lines: 1.1.4 - unherit: 1.1.3 - unist-util-remove-position: 1.1.4 - vfile-location: 2.0.6 - xtend: 4.0.2 - remark-rehype@11.1.2: dependencies: '@types/hast': 3.0.4 @@ -20333,35 +18751,6 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - remark-stringify@6.0.4: - dependencies: - ccount: 1.1.0 - is-alphanumeric: 1.0.0 - is-decimal: 1.0.4 - is-whitespace-character: 1.0.4 - longest-streak: 2.0.4 - markdown-escapes: 1.0.4 - markdown-table: 1.1.3 - mdast-util-compact: 1.0.4 - parse-entities: 1.2.2 - repeat-string: 1.6.1 - state-toggle: 1.0.3 - stringify-entities: 1.3.2 - unherit: 1.1.3 - xtend: 4.0.2 - - remark@10.0.1: - dependencies: - remark-parse: 6.0.3 - remark-stringify: 6.0.4 - unified: 7.1.0 - - repeat-element@1.1.4: {} - - repeat-string@1.6.1: {} - - replace-ext@1.0.0: {} - require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -20383,14 +18772,10 @@ snapshots: resolve-dependency-path@4.0.1: {} - resolve-from@3.0.0: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} - resolve-url@0.2.1: {} - resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -20416,8 +18801,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - ret@0.1.15: {} - retry@0.12.0: {} reusify@1.1.0: {} @@ -20555,10 +18938,6 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - safe-regex@1.1.0: - dependencies: - ret: 0.1.15 - safer-buffer@2.1.2: {} sanitize-filename@1.6.4: @@ -20652,13 +19031,6 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - set-value@2.0.1: - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 - setprototypeof@1.2.0: {} sharp@0.34.5: @@ -20761,12 +19133,6 @@ snapshots: slash@5.1.0: {} - slice-ansi@2.1.0: - dependencies: - ansi-styles: 3.2.1 - astral-regex: 1.0.0 - is-fullwidth-code-point: 2.0.0 - slice-ansi@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -20797,29 +19163,6 @@ snapshots: snabbdom@3.6.3: {} - snapdragon-node@2.1.1: - dependencies: - define-property: 1.0.0 - isobject: 3.0.1 - snapdragon-util: 3.0.1 - - snapdragon-util@3.0.1: - dependencies: - kind-of: 3.2.2 - - snapdragon@0.8.2: - dependencies: - base: 0.11.2 - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - map-cache: 0.2.2 - source-map: 0.5.7 - source-map-resolve: 0.5.3 - use: 3.1.1 - transitivePeerDependencies: - - supports-color - snapsvg-cjs@0.0.6(eve@0.5.4): dependencies: eve: 0.5.4 @@ -20831,23 +19174,11 @@ snapshots: source-map-js@1.2.1: {} - source-map-resolve@0.5.3: - dependencies: - atob: 2.1.2 - decode-uri-component: 0.2.2 - resolve-url: 0.2.1 - source-map-url: 0.4.1 - urix: 0.1.0 - source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - source-map-url@0.4.1: {} - - source-map@0.5.7: {} - source-map@0.6.1: {} space-separated-tokens@2.0.2: {} @@ -20887,12 +19218,6 @@ snapshots: speakingurl@14.0.1: {} - specificity@0.4.1: {} - - split-string@3.1.0: - dependencies: - extend-shallow: 3.0.2 - sprintf-js@1.0.3: {} sprintf-js@1.1.3: @@ -20904,13 +19229,6 @@ snapshots: stat-mode@1.0.0: {} - state-toggle@1.0.3: {} - - static-extend@0.1.2: - dependencies: - define-property: 0.2.5 - object-copy: 0.1.0 - statuses@2.0.2: {} std-env@4.1.0: {} @@ -20924,12 +19242,6 @@ snapshots: dependencies: any-promise: 1.3.0 - string-width@3.1.0: - dependencies: - emoji-regex: 7.0.3 - is-fullwidth-code-point: 2.0.0 - strip-ansi: 5.2.0 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -21001,13 +19313,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - stringify-entities@1.3.2: - dependencies: - character-entities-html4: 1.1.4 - character-entities-legacy: 1.1.4 - is-alphanumerical: 1.0.4 - is-hexadecimal: 1.0.4 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -21019,10 +19324,6 @@ snapshots: is-obj: 1.0.1 is-regexp: 1.0.0 - strip-ansi@5.2.0: - dependencies: - ansi-regex: 4.1.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -21037,8 +19338,6 @@ snapshots: strip-final-newline@2.0.0: {} - strip-indent@2.0.0: {} - strip-indent@4.1.1: {} strip-json-comments@2.0.1: {} @@ -21053,8 +19352,6 @@ snapshots: stubborn-utils@1.0.2: {} - style-search@0.1.0: {} - styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.6): dependencies: client-only: 0.0.1 @@ -21062,13 +19359,6 @@ snapshots: optionalDependencies: '@babel/core': 7.29.0 - stylelint-config-rational-order@0.1.2: - dependencies: - stylelint: 9.10.1 - stylelint-order: 2.2.1(stylelint@9.10.1) - transitivePeerDependencies: - - supports-color - stylelint-config-recommended@18.0.0(stylelint@17.12.0(typescript@6.0.3)): dependencies: stylelint: 17.12.0(typescript@6.0.3) @@ -21078,13 +19368,6 @@ snapshots: stylelint: 17.12.0(typescript@6.0.3) stylelint-config-recommended: 18.0.0(stylelint@17.12.0(typescript@6.0.3)) - stylelint-order@2.2.1(stylelint@9.10.1): - dependencies: - lodash: 4.18.1 - postcss: 8.5.15 - postcss-sorting: 4.1.0 - stylelint: 9.10.1 - stylelint-order@8.1.1(stylelint@17.12.0(typescript@6.0.3)): dependencies: postcss: 8.5.15 @@ -21132,68 +19415,12 @@ snapshots: - supports-color - typescript - stylelint@9.10.1: - dependencies: - autoprefixer: 9.8.8 - balanced-match: 1.0.2 - chalk: 2.4.2 - cosmiconfig: 5.2.1 - debug: 4.4.3 - execall: 1.0.0 - file-entry-cache: 4.0.0 - get-stdin: 6.0.0 - global-modules: 2.0.0 - globby: 9.2.0 - globjoin: 0.1.4 - html-tags: 2.0.0 - ignore: 5.3.2 - import-lazy: 3.1.0 - imurmurhash: 0.1.4 - known-css-properties: 0.11.0 - leven: 2.1.0 - lodash: 4.18.1 - log-symbols: 2.2.0 - mathml-tag-names: 2.1.3 - meow: 5.0.0 - micromatch: 3.1.10 - normalize-selector: 0.2.0 - pify: 4.0.1 - postcss: 8.5.15 - postcss-html: 0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15) - postcss-jsx: 0.36.4(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15) - postcss-less: 3.1.4 - postcss-markdown: 0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15) - postcss-media-query-parser: 0.2.3 - postcss-reporter: 6.0.1 - postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 4.0.2 - postcss-sass: 0.3.5 - postcss-scss: 2.1.1 - postcss-selector-parser: 3.1.2 - postcss-syntax: 0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-jsx@0.36.4)(postcss-less@3.1.4)(postcss-markdown@0.36.0(postcss-syntax@0.36.2(postcss@8.5.15))(postcss@8.5.15))(postcss-scss@2.1.1)(postcss@8.5.15) - postcss-value-parser: 3.3.1 - resolve-from: 4.0.0 - signal-exit: 3.0.7 - slash: 2.0.0 - specificity: 0.4.1 - string-width: 3.1.0 - style-search: 0.1.0 - sugarss: 2.0.0 - svg-tags: 1.0.0 - table: 5.4.6 - transitivePeerDependencies: - - supports-color - stylis@4.4.0: {} stylus-lookup@6.1.2: dependencies: commander: 12.1.0 - sugarss@2.0.0: - dependencies: - postcss: 8.5.15 - sumchecker@3.0.1: dependencies: debug: 4.4.3 @@ -21239,13 +19466,6 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - table@5.4.6: - dependencies: - ajv: 6.15.0 - lodash: 4.18.1 - slice-ansi: 2.1.0 - string-width: 3.1.0 - table@6.9.0: dependencies: ajv: 8.20.0 @@ -21333,26 +19553,10 @@ snapshots: tmp@0.2.5: {} - to-object-path@0.3.0: - dependencies: - kind-of: 3.2.2 - - to-regex-range@2.1.1: - dependencies: - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - to-regex@3.0.2: - dependencies: - define-property: 2.0.2 - extend-shallow: 3.0.2 - regex-not: 1.0.2 - safe-regex: 1.1.0 - to-valid-identifier@1.0.0: dependencies: '@sindresorhus/base62': 1.0.0 @@ -21382,14 +19586,6 @@ snapshots: trim-lines@3.0.1: {} - trim-newlines@2.0.0: {} - - trim-trailing-lines@1.1.4: {} - - trim@0.0.1: {} - - trough@1.0.5: {} - trough@2.2.0: {} truncate-utf8-bytes@1.0.2: @@ -21545,11 +19741,6 @@ snapshots: dependencies: pathe: 2.0.3 - unherit@1.1.3: - dependencies: - inherits: 2.0.4 - xtend: 4.0.2 - unicorn-magic@0.3.0: {} unicorn-magic@0.4.0: {} @@ -21566,32 +19757,6 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unified@7.1.0: - dependencies: - '@types/unist': 2.0.11 - '@types/vfile': 3.0.2 - bail: 1.0.5 - extend: 3.0.2 - is-plain-obj: 1.1.0 - trough: 1.0.5 - vfile: 3.0.1 - x-is-string: 0.1.0 - - union-value@1.0.1: - dependencies: - arr-union: 3.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - set-value: 2.0.1 - - uniq@1.0.1: {} - - unist-util-find-all-after@1.0.5: - dependencies: - unist-util-is: 3.0.0 - - unist-util-is@3.0.0: {} - unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -21600,34 +19765,20 @@ snapshots: dependencies: '@types/unist': 3.0.3 - unist-util-remove-position@1.1.4: - dependencies: - unist-util-visit: 1.4.1 - unist-util-remove-position@5.0.0: dependencies: '@types/unist': 3.0.3 unist-util-visit: 5.1.0 - unist-util-stringify-position@1.1.2: {} - unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-visit-parents@2.1.2: - dependencies: - unist-util-is: 3.0.0 - unist-util-visit-parents@6.0.2: dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 - unist-util-visit@1.4.1: - dependencies: - unist-util-visit-parents: 2.1.2 - unist-util-visit@5.1.0: dependencies: '@types/unist': 3.0.3 @@ -21693,11 +19844,6 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 - unset-value@1.0.0: - dependencies: - has-value: 0.3.1 - isobject: 3.0.1 - update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -21708,12 +19854,8 @@ snapshots: dependencies: punycode: 2.3.1 - urix@0.1.0: {} - urlpattern-polyfill@10.1.0: {} - use@3.1.1: {} - utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} @@ -22000,29 +20142,16 @@ snapshots: extsprintf: 1.4.1 optional: true - vfile-location@2.0.6: {} - vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 vfile: 6.0.3 - vfile-message@1.1.1: - dependencies: - unist-util-stringify-position: 1.1.2 - vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 - vfile@3.0.1: - dependencies: - is-buffer: 2.0.5 - replace-ext: 1.0.0 - unist-util-stringify-position: 1.1.2 - vfile-message: 1.1.1 - vfile@6.0.3: dependencies: '@types/unist': 3.0.3 @@ -22374,14 +20503,8 @@ snapshots: dependencies: signal-exit: 4.1.0 - write@1.0.3: - dependencies: - mkdirp: 0.5.6 - ws@8.20.1: {} - x-is-string@0.1.0: {} - xml-name-validator@4.0.0: {} xml-name-validator@5.0.0: {} @@ -22392,8 +20515,6 @@ snapshots: xmlchars@2.2.0: {} - xtend@4.0.2: {} - y18n@5.0.8: {} yallist@3.1.1: {} @@ -22409,10 +20530,6 @@ snapshots: yaml@2.9.0: {} - yargs-parser@10.1.0: - dependencies: - camelcase: 4.1.0 - yargs-parser@21.1.1: {} yargs-parser@22.0.0: {}