diff --git a/@webwriter/app-desktop/package.json b/@webwriter/app-desktop/package.json index 447479af..72e8280f 100644 --- a/@webwriter/app-desktop/package.json +++ b/@webwriter/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@webwriter/app-desktop", - "version": "0.8.2", + "version": "0.8.3", "description": "Windows/Mac/Linux version of WebWriter", "private": true, "author": "Frederic Salmen ", diff --git a/@webwriter/app-desktop/src-tauri/Cargo.lock b/@webwriter/app-desktop/src-tauri/Cargo.lock index 6488119d..d3b1b9bd 100644 --- a/@webwriter/app-desktop/src-tauri/Cargo.lock +++ b/@webwriter/app-desktop/src-tauri/Cargo.lock @@ -3590,7 +3590,7 @@ dependencies = [ [[package]] name = "webwriter" -version = "0.8.2" +version = "0.8.3" dependencies = [ "font-loader", "serde", diff --git a/@webwriter/app-desktop/src-tauri/Cargo.toml b/@webwriter/app-desktop/src-tauri/Cargo.toml index e1a25495..2150d363 100644 --- a/@webwriter/app-desktop/src-tauri/Cargo.toml +++ b/@webwriter/app-desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webwriter" -version = "0.8.2" +version = "0.8.3" description = "Windows/Mac/Linux version of WebWriter" authors = ["Frederic Salmen "] license = "UNLICENSED" diff --git a/@webwriter/core/localization/generated/de.ts b/@webwriter/core/localization/generated/de.ts index 28dd966d..41bfd34e 100644 --- a/@webwriter/core/localization/generated/de.ts +++ b/@webwriter/core/localization/generated/de.ts @@ -413,5 +413,7 @@ 'spaceKey': `Leertaste`, 'tabKey': `⭾ Tab`, 'upKey': `↑ Hoch`, +'s3b3ea038c5d3bb06': `Division`, +'se6cdad455e2d8c02': `Insert a division`, }; \ No newline at end of file diff --git a/@webwriter/core/model/environment/tauri.ts b/@webwriter/core/model/environment/tauri.ts index 7a34303f..ef0170bf 100644 --- a/@webwriter/core/model/environment/tauri.ts +++ b/@webwriter/core/model/environment/tauri.ts @@ -173,11 +173,19 @@ export async function search(text: string, params?: {size?: number, from?: numbe export async function pm(command: string, commandArgs: string[] = [], json=true, cwd?: string) { const cmdArgs = [command, ...(json ? ["--json"]: []), ...commandArgs] const opts = cwd? {cwd}: {} + console.info(`[TAURI] ${cwd? cwd: await appDir()}> bin/yarn ${[...cmdArgs, "--mutex file"].join(" ")}`) const output = await Command.sidecar("bin/yarn", [...cmdArgs, "--mutex file"], opts).execute() if(output.stderr) { - const err = output.stderr.split("\n").map((e: any) => JSON.parse(e)) - const errors = err.filter((e: any) => e.type === "error") - const warnings = err.filter((e: any) => e.type === "warning") + const err = output.stderr.split("\n").map((e: any) => { + try { + console.error(JSON.parse(e)) + } + catch(err) { + console.error(e) + } + }) + const errors = err.filter((e: any) => e?.type === "error") + const warnings = err.filter((e: any) => e?.type === "warning") warnings.forEach((w: any) => console.warn(w.data)) if(err?.some((e: any) => e?.type === "error")) { throw err diff --git a/@webwriter/core/model/schemas/packageschema/index.ts b/@webwriter/core/model/schemas/packageschema/index.ts index ed719b37..46e51e1c 100644 --- a/@webwriter/core/model/schemas/packageschema/index.ts +++ b/@webwriter/core/model/schemas/packageschema/index.ts @@ -386,7 +386,7 @@ const PackageObjectSchema = z.object({ imports: z.record(z.string().startsWith("#"), z.record(z.string())).optional(), editingConfig: EditingConfig.optional(), installed: z.boolean().optional().default(false), - outdated: z.boolean().optional().default(false), + latest: SemVer.schema.optional(), imported: z.boolean().optional().default(false), watching: z.boolean().optional().default(false), reloadCount: z.number().optional().default(0), diff --git a/@webwriter/core/model/schemas/resourceschema/editingstyles.css b/@webwriter/core/model/schemas/resourceschema/editingstyles.css index 15341ac5..09f4aa66 100644 --- a/@webwriter/core/model/schemas/resourceschema/editingstyles.css +++ b/@webwriter/core/model/schemas/resourceschema/editingstyles.css @@ -59,6 +59,10 @@ html::-webkit-scrollbar-button:vertical:single-button:increment:disabled { border-color: var(--sl-color-gray-400) transparent transparent transparent; } +* { + outline: none; +} + a:not([href]) { text-decoration: underline; color: #0000EE; @@ -145,25 +149,43 @@ figure.ProseMirror-selectednode::before { -webkit-user-select: none !important; } -.ww-widget[editable]::after { +.ProseMirror > :not(.ProseMirror-widget) { + position: relative; +} + +.ProseMirror > :not(.ProseMirror-widget)::after { content: ""; position: absolute; - right: -14px; + right: -18px; top: 0; - width: 6px; + border-right: 6px solid transparent; background: none; height: 100%; - border-radius: 4px; + cursor: pointer; + width: 20px; } -.ww-widget[editable]:hover::after { - background: var(--sl-color-primary-300); +.ProseMirror *:not([data-ww-inline]):hover { + outline: 1px dotted var(--sl-color-gray-400); + outline-offset: 2px; +} +/* +.ProseMirror > *:hover::after { + border-color: var(--sl-color-gray-100); } -.ww-widget[editable][data-ww-selected]::after { - background: var(--sl-color-primary-400); +.ProseMirror > *:is([data-ww-selected], :has([data-ww-selected]))::after { + border-color: var(--sl-color-gray-200); +} + +.ProseMirror > *:is([data-ww-selected]:hover, :has([data-ww-selected]:hover))::after { + border-color: var(--sl-color-gray-300); } +.ww-widget[editable][data-ww-deleting]::after { + border-color: var(--sl-color-danger-400); +}*/ + .ww-widget[editable][data-ww-deleting]::before { content: ""; position: absolute; @@ -177,10 +199,6 @@ figure.ProseMirror-selectednode::before { height: 100%; } -.ww-widget[editable][data-ww-deleting]::after { - background: var(--sl-color-danger-400); -} - .ww-widget#ww_preview { position: relative; display: block; diff --git a/@webwriter/core/model/schemas/resourceschema/htmlelementspec.ts b/@webwriter/core/model/schemas/resourceschema/htmlelementspec.ts index 270ade2b..39b47d54 100644 --- a/@webwriter/core/model/schemas/resourceschema/htmlelementspec.ts +++ b/@webwriter/core/model/schemas/resourceschema/htmlelementspec.ts @@ -173,7 +173,7 @@ export function toAttributes(node: Node | Attrs, extraAttrs?: Attrs) { const attrSpec: (k: string) => AttributeSpec & {private?: boolean} | undefined = (k: string) => complex? (node.type?.spec?.attrs ?? {})[k]: {} for (const [k, v] of Object.entries(attrs)) { const spec = attrSpec(k) - if(k !== "data" && v !== undefined && (spec?.default !== v) && !spec?.private) { + if(k !== "data" && v && (spec?.default !== v) && !spec?.private) { outputAttrs[k] = Array.isArray(v)? v.join(" "): v } if(k === "data") { diff --git a/@webwriter/core/model/schemas/resourceschema/plugins/base.ts b/@webwriter/core/model/schemas/resourceschema/plugins/base.ts index 117439d8..9a2005ff 100644 --- a/@webwriter/core/model/schemas/resourceschema/plugins/base.ts +++ b/@webwriter/core/model/schemas/resourceschema/plugins/base.ts @@ -100,13 +100,18 @@ export const basePlugin = () => ({ content: "text | phrasing*", whitespace: "pre" }), + div: HTMLElementSpec({ + tag: "div", + group: "flow palpable", + content: "flow*" + }), pre: HTMLElementSpec({ tag: "pre", group: "flow palpable", content: "text | phrasing*", whitespace: "pre" }), - unknownElement: { + /*unknownElement: { attrs: { tagName: {}, otherAttrs: {default: {}} @@ -133,7 +138,7 @@ export const basePlugin = () => ({ "data-ww-editing": CustomElementName.safeParse(node.attrs.tagName).success? "unknowncustom": "unknownbuiltin", "data-ww-tagname": node.attrs.tagName }] - }, + },*/ }, topNode: "explorable", keymap: baseKeymap, diff --git a/@webwriter/core/model/schemas/resourceschema/plugins/modal.ts b/@webwriter/core/model/schemas/resourceschema/plugins/modal.ts index 7576e491..34dc2850 100644 --- a/@webwriter/core/model/schemas/resourceschema/plugins/modal.ts +++ b/@webwriter/core/model/schemas/resourceschema/plugins/modal.ts @@ -1,25 +1,96 @@ +import { TextSelection, Command, EditorState, NodeSelection, AllSelection } from "prosemirror-state"; import { SchemaPlugin } from "."; +import { range } from "../../../../utility"; import { HTMLElementSpec } from "../htmlelementspec"; +import {Node, ResolvedPos, NodeType, Attrs, NodeRange, Fragment} from "prosemirror-model" +import {canSplit, liftTarget} from "prosemirror-transform" -export const modalPlugin = () => ({ - dialog: HTMLElementSpec({ - tag: "dialog", - group: "flow sectioningroot", - content: "flow*", - attrs: { - open: {default: undefined} - } - }), - details: HTMLElementSpec({ - tag: "details", - group: "flow sectioningroot interactive palpable", - content: `summary flow*`, - attrs: { - open: {default: undefined} +export function getNodePath($pos: ResolvedPos, excludeRoot=true) { + return range($pos.depth + 1).map(k => $pos.node(k)).slice(excludeRoot? 1: 0) +} + +export function getPosPath($pos: ResolvedPos, excludeRoot=true) { + return range($pos.depth + 1).map(k => $pos.before(k + 1)).slice(excludeRoot? 1: 0) +} + +export function findAncestor($pos: ResolvedPos, predicate: (value: [number, Node], index: number, array: [number, Node][]) => boolean) { + const nodePath = getNodePath($pos) + const posPath = getPosPath($pos) + return posPath.map((pos, i) => [pos, nodePath[i]] as [number, Node]).find(predicate) ?? [] +} + +export function ancestorOfType($pos: ResolvedPos, type: string) { + return findAncestor($pos, ([_, node]) => node?.type.name === type) +} + +function selectDetailsContent(split=false): Command { + return (state, dispatch, view) => { + const {selection, doc} = state + const pType = state.schema.nodes["p"]! + const [detailsPos, details] = ancestorOfType(selection.$from, "details") + const [summaryPos, summary] = ancestorOfType(selection.$from, "summary") + const nodePath = getNodePath(selection.$from) + if(details && summary && split) { + + let tr = state.tr.setNodeAttribute(detailsPos! - 1, "open", true) + tr = tr.deleteSelection() + tr = tr.split(tr.selection.from, undefined, [{type: pType}]) + dispatch && dispatch(tr) + return true } - }), - summary: HTMLElementSpec({ - tag: "summary", - content: "heading" - }) + return false + } +} + +export const modalPlugin = () => ({ + nodes: { + dialog: HTMLElementSpec({ + tag: "dialog", + group: "flow sectioningroot", + content: "flow*", + attrs: { + open: {default: undefined} + } + }), + details: HTMLElementSpec({ + tag: "details", + group: "flow sectioningroot interactive palpable", + content: `summary flow*`, + attrs: { + open: {default: undefined} + } + }), + summary: HTMLElementSpec({ + tag: "summary", + content: "phrasing*" // simplified until phrase editing is fixed + }) + }, + keymap: { + "Enter": selectDetailsContent(true), + "ArrowDown": selectDetailsContent(), + "Backspace": (state, dispatch, view) => { + const {selection, doc} = state + const [summaryPos, summary] = ancestorOfType(selection.$from, "summary") + if(summary && selection.from === summaryPos) { + + } + /* + console.log(getNodePath(selection.$from), getPosPath(selection.$from)) + const [detailsPos, details] = ancestorOfType(selection.$from, "details") + const [summaryPos, summary] = detailsPos? [ + detailsPos + 1, + details?.child(0).type.name === "summary"? details?.child(0): undefined + ]: [] + const open = details?.attrs.open + console.log(details, summaryPos, selection.from) + if(details && summaryPos !== undefined && selection.from === summaryPos) { + console.log("backspace handled") + let tr = state.tr.setSelection(TextSelection.create(doc, selection.from - 2)) + + dispatch && dispatch(tr) + return true + }*/ + return false + }, + } } as SchemaPlugin) \ No newline at end of file diff --git a/@webwriter/core/model/schemas/resourceschema/plugins/phrasing.ts b/@webwriter/core/model/schemas/resourceschema/plugins/phrasing.ts index d73cbb0f..61f0aca5 100644 --- a/@webwriter/core/model/schemas/resourceschema/plugins/phrasing.ts +++ b/@webwriter/core/model/schemas/resourceschema/plugins/phrasing.ts @@ -44,10 +44,6 @@ export function toggleOrUpdateMark(mark: string, attrs: any = {}) { export const phrasingPlugin = () => ({ nodes: { - text: { - group: "phrasing", - inline: true - }, br: HTMLElementSpec({ tag: "br", group: "phrasing", @@ -58,6 +54,10 @@ export const phrasingPlugin = () => ({ group: "phrasing", inline: true }), + text: { + group: "phrasing", + inline: true + }, _phrase: HTMLElementSpec({ tag: "span", content: "text? | phrasing*", diff --git a/@webwriter/core/model/schemas/resourceschema/plugins/widget.ts b/@webwriter/core/model/schemas/resourceschema/plugins/widget.ts index 8967f705..5713a876 100644 --- a/@webwriter/core/model/schemas/resourceschema/plugins/widget.ts +++ b/@webwriter/core/model/schemas/resourceschema/plugins/widget.ts @@ -60,10 +60,7 @@ export function packageWidgetNodeSpec(pkg: Package): NodeSpec { const mediaTypes = getMediaTypesOfContent(pkg.editingConfig?.content) const mediaNodeNames = mediaTypes .map(t => t.slice(1).replaceAll("/", "__").replaceAll("-", "_")) - const maybeMediaParseRules = mediaTypes.length > 0? [ - - ]: [] - + const maybeMediaParseRules = mediaTypes.length > 0? []: [] return { group: "flow widget", widget: true, @@ -83,6 +80,7 @@ export function packageWidgetNodeSpec(pkg: Package): NodeSpec { ...styleAttrs }, parseDOM : [{tag: unscopePackageName(pkg.name), getAttrs: (dom: HTMLElement ) => { + console.log(dom) return { id: dom.getAttribute("id"), editable: dom.getAttribute("editable") ?? false, diff --git a/@webwriter/core/model/schemas/resourceschema/themes/base.css b/@webwriter/core/model/schemas/resourceschema/themes/base.css index 79cd03c2..0c0ae5c0 100644 --- a/@webwriter/core/model/schemas/resourceschema/themes/base.css +++ b/@webwriter/core/model/schemas/resourceschema/themes/base.css @@ -78,4 +78,13 @@ blockquote { padding: 0.4rem 0.8rem; border-left: 10px solid #ccc; color: var(--text-light); +} + +details { + border-left: 2px solid black; + padding-left: 1ch; +} + +details p { + margin: 0; } \ No newline at end of file diff --git a/@webwriter/core/model/stores/packagestore.ts b/@webwriter/core/model/stores/packagestore.ts index 3f499d40..47fb9877 100644 --- a/@webwriter/core/model/stores/packagestore.ts +++ b/@webwriter/core/model/stores/packagestore.ts @@ -241,7 +241,7 @@ export class PackageStore { } get outdatedPkgs() { - return this.packages.filter(pkg => pkg.outdated) + return this.packages.filter(pkg => pkg.latest) } get availableWidgetTypes() { @@ -261,9 +261,13 @@ export class PackageStore { await this.pm("add", [...PackageStore.defaultArgs, ...this.corePackages], true, appDir) // await Promise.all(H5P_REPOSITORIES.map(this.installH5Package)) } - const packages = await this.fetchInstalled() - const importable = (await this.writeBundle(packages, {editMode: true, force: packages.some(pkg => pkg.localPath)}))?.packages - await this.import(importable ?? [], {bundlename: "bundle", editMode: true}) + await this.loadAll() + } + + async loadAll() { + await this.fetchInstalled(true) + await this.writeBundle(this.installed, {editMode: true, force: this.installed.some(pkg => pkg.localPath)}) + await this.import(this.installed.filter(pkg => !pkg.importError), {bundlename: "bundle", editMode: true}) } async testImportable(packages: Package[], setImportError=true) { @@ -340,14 +344,19 @@ export class PackageStore { const installed = await this.fetchInstalled(false) const available = await this.fetchAvailable(from) const installedPkgNames = installed.map(pkg => pkg.name) - const outdated = installed - .filter(({name, version}) => { - const availVersion = available.find(pkg => pkg.name === name)?.version - return availVersion && String(availVersion) !== String(version) + const installedWithLatest = installed + .map((pkg) => { + const availVersion = available.find(p => p.name === pkg.name)?.version + if(availVersion && String(availVersion) !== String(pkg.version)) { + return pkg.extend({latest: availVersion}) + } + else { + return pkg + } }) this.packages = [ ...available.filter(pkg => !installedPkgNames.includes(pkg.name)), - ...installed.map(pkg => pkg.extend({outdated: outdated.includes(pkg)})) + ...installedWithLatest ] return this.packages } @@ -441,6 +450,7 @@ export class PackageStore { if(packages.length === 0) { return } + console.log("import", PackageStore.computeBundleID(packages, editMode)) const appDir = await this.Path.appDir() const bundleFilename = PackageStore.computeBundleHash(packages, bundlename, editMode) const bundlePathJS = await this.Path.join(appDir, bundleFilename + ".js") diff --git a/@webwriter/core/package.json b/@webwriter/core/package.json index f6138bb1..a74d28a8 100644 --- a/@webwriter/core/package.json +++ b/@webwriter/core/package.json @@ -1,6 +1,6 @@ { "name": "@webwriter/core", - "version": "0.8.2", + "version": "0.8.3", "description": "Core functionality of WebWriter", "main": "index.html", "private": true, @@ -41,6 +41,7 @@ "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.4", "prosemirror-transform": "^1.8.0", + "prosemirror-utils": "^1.2.1-0", "prosemirror-view": "^1.32.0", "redefine-custom-elements": "^0.1.2", "sortablejs": "^1.15.0", diff --git a/@webwriter/core/view/configurator/keymapmanager.ts b/@webwriter/core/view/configurator/keymapmanager.ts index 9233aad9..1e7eeef2 100644 --- a/@webwriter/core/view/configurator/keymapmanager.ts +++ b/@webwriter/core/view/configurator/keymapmanager.ts @@ -289,7 +289,6 @@ export class KeymapManager extends LitElement { render() { const groupedEntries = groupBy(Object.entries(this.commands), ([key, entry]) => entry.category) - console.log(Object.keys(groupedEntries)) return html`
${Object.keys(groupedEntries).map(key => html`

${this.categoryLabels[key] ?? key}

diff --git a/@webwriter/core/view/configurator/packagemanager.ts b/@webwriter/core/view/configurator/packagemanager.ts index 4303bbad..e059c310 100644 --- a/@webwriter/core/view/configurator/packagemanager.ts +++ b/@webwriter/core/view/configurator/packagemanager.ts @@ -4,7 +4,7 @@ import {classMap} from "lit/directives/class-map.js" import { localized, msg, str } from "@lit/localize" import { SlBadge, SlDialog } from "@shoelace-style/shoelace" -import { Package } from "../../model" +import { Package, SemVer } from "../../model" import { shortenBytes } from "../../utility" import { ifDefined } from "lit/directives/if-defined.js" import { StoreController } from "../../viewmodel" @@ -72,8 +72,8 @@ export class PackageManager extends LitElement { new CustomEvent("ww-remove-package", {composed: true, bubbles: true, detail: {args: [pkg]}}) ) - emitUpgradePackage = (pkg: string) => this.dispatchEvent( - new CustomEvent("ww-upgrade-package", {composed: true, bubbles: true, detail: {args: [pkg]}}) + emitUpgradePackage = (pkg: string, latest: SemVer) => this.dispatchEvent( + new CustomEvent("ww-upgrade-package", {composed: true, bubbles: true, detail: {args: [`${pkg}@${latest}`]}}) ) emitRefresh = () => this.dispatchEvent( @@ -222,6 +222,10 @@ export class PackageManager extends LitElement { color: darkgray; } + .package-latest { + color: var(--sl-color-amber-700); + } + .spinner-container { display: none; align-items: center; @@ -508,8 +512,8 @@ export class PackageManager extends LitElement { ` } - packageListItem = (pkg: Package, i: number) => { - const {name, author, version, description, keywords, installed, outdated, importError, localPath, watching, jsSize, cssSize} = pkg + packageListItem = (pkg: Package) => { + const {name, author, version, description, keywords, installed, latest, importError, localPath, watching, jsSize, cssSize} = pkg const {emitAddPackage, emitRemovePackage, emitUpgradePackage, emitToggleWatch, emitOpenPackageCode, emitEditPackage} = this const adding = this.adding.includes(name) const removing = this.removing.includes(name) @@ -519,7 +523,7 @@ export class PackageManager extends LitElement { const unimportable = !!importError const available = !installed // @ts-ignore - return html` + return html` e.stopPropagation()} style="cursor: help">
@@ -529,7 +533,12 @@ export class PackageManager extends LitElement { ${name} ${author} - ${version} + + ${version} + ${latest? html` + 🠖${String(latest)} + `: null} +
${keywords?.filter(kw => kw !== "webwriter-widget" && kw !== "official").map(kw => html` ${kw} @@ -547,7 +556,7 @@ export class PackageManager extends LitElement { emitToggleWatch(name)} icon=${`bolt${watching? "-off": ""}`}> `: html` - emitUpgradePackage(name)} outline slot="footer" ?loading=${upgrading} ?disabled=${removing || adding || !outdated} > + emitUpgradePackage(name, latest)} outline slot="footer" ?loading=${upgrading} ?disabled=${removing || adding || !latest} > ${msg("Update")} `} @@ -733,7 +742,7 @@ export class PackageManager extends LitElement {
${length === 0 && !this.loading ? html`${emptyText}` - : packages.map(this.packageListItem) + : packages.map(pkg => this.packageListItem(pkg)) }
@@ -764,7 +773,7 @@ export class PackageManager extends LitElement { { key: "outdated" as const, label: msg("Outdated"), - packages: this.packages.filter(pkg => pkg.outdated), + packages: this.packages.filter(pkg => pkg.latest), emptyText: msg("No packages outdated") }, { diff --git a/@webwriter/core/view/editor/debugoverlay.ts b/@webwriter/core/view/editor/debugoverlay.ts new file mode 100644 index 00000000..bc619b80 --- /dev/null +++ b/@webwriter/core/view/editor/debugoverlay.ts @@ -0,0 +1,81 @@ +import { LitElement, TemplateResult, css, html } from "lit" +import { customElement, property, query } from "lit/decorators.js" +import { styleMap } from "lit/directives/style-map.js" + +import { camelCaseToSpacedCase, prettifyPackageName, unscopePackageName } from "../../utility" +import { Mark, ResolvedPos } from "prosemirror-model" +import { classMap } from "lit/directives/class-map.js" +import { localized, msg } from "@lit/localize" +import { Command } from "../../viewmodel" +import { spreadProps } from "@open-wc/lit-helpers" +import "../elements/stylepickers" + +import { ifDefined } from "lit/directives/if-defined.js" +import { App } from ".." +import { EditorState, Selection } from "prosemirror-state" +import { getNodePath } from "../../model" + +@localized() +@customElement("ww-debugoverlay") +export class DebugOverlay extends LitElement { + + @property({attribute: false}) + app: App + + @property({type: Object, attribute: false}) + editorState: EditorState + + @property({type: Object, attribute: false}) + activeElement: HTMLElement | null + + static styles = css` + :host { + position: fixed; + bottom: 5px; + right: 5px; + padding: 1rem; + z-index: 2147483647; + color: red; + font-family: monospace; + font-weight: bold; + } + + sub + sup, + sup + sub { + position: relative; + left: -1ch; + } + ` + static pathString($pos: ResolvedPos) { + if($pos.pos === 0) { + return "" + } + const pathString = getNodePath($pos).map(n => n.type.name).join("/") + const indexString = $pos.index($pos.depth - 1) + const offsetString = $pos.parentOffset + return html`${pathString}${indexString}${offsetString}` + } + + static selectionTypeString(selection: Selection) { + const type = selection.toJSON().type + return html`${type !== "text"? "$" + type.toUpperCase(): ""}` + } + + static selectionPosString(selection: Selection) { + const {anchor, head, empty, from, to} = selection + return html`${anchor <= head? "|": ""}${empty? from: `${from}:${to}`}${head < anchor? "|": ""}` + } + + static posMarks($pos: ResolvedPos) { + return $pos.marks().map(mark => "." + mark.type.name) + } + + render() { + return html` + ${DebugOverlay.selectionPosString(this.editorState.selection)} + ${DebugOverlay.selectionTypeString(this.editorState.selection)} + ${DebugOverlay.pathString(this.editorState.selection.$anchor)} + ${DebugOverlay.posMarks(this.editorState.selection.$anchor)} + ` + } +} \ No newline at end of file diff --git a/@webwriter/core/view/editor/editor.ts b/@webwriter/core/view/editor/editor.ts index 199c879d..c777e38c 100644 --- a/@webwriter/core/view/editor/editor.ts +++ b/@webwriter/core/view/editor/editor.ts @@ -7,7 +7,7 @@ import { Node, Mark} from "prosemirror-model" import { localized, msg, str } from "@lit/localize" import { MediaType, Package, createWidget } from "../../model" -import { FigureView, WidgetView } from "." +import { FigureView, WidgetView, nodeViews } from "." import { DocumentHeader } from "./documentheader" import { DocumentFooter } from "./documentfooter" @@ -232,23 +232,27 @@ export class ExplorableEditor extends LitElement { private cachedNodeViews: Record private get nodeViews() { + const {nodes} = this.editorState.schema const cached = this.cachedNodeViews const cachedKeys = Object.keys(cached ?? {}) - const widgetKeys = Object.entries(this.editorState.schema.nodes) - .filter(([k, v]) => v.spec["widget"]) + const widgetKeys = Object.entries(nodes) + .filter(([_, v]) => v.spec["widget"]) .map(([k, _]) => k) - const mediaKeys = Object.entries(this.editorState.schema.nodes) - .filter(([k, v]) => v.spec["media"]) - .map(([k, _]) => k) - if(sameMembers([...widgetKeys, ...mediaKeys], cachedKeys)) { + const nodeKeys = Object.keys(nodeViews) + //const mediaKeys = Object.entries(this.editorState.schema.nodes) + //.filter(([k, v]) => v.spec["media"]) + //.map(([k, _]) => k) + if(sameMembers([...widgetKeys, ...nodeKeys], cachedKeys)) { return cached } else { const widgetViewEntries = widgetKeys .map(key => [key, (node: Node, view: EditorViewController, getPos: () => number) => new WidgetView(node, view, getPos)]) - const mediaViewEntries = mediaKeys - .map(key => [key, (node: Node, view: EditorViewController, getPos: () => number) => new FigureView(node, view, getPos)]) - this.cachedNodeViews = {...Object.fromEntries([...widgetViewEntries, ...mediaViewEntries])} + const nodeViewEntries = Object.entries(nodeViews).map(([k, V]) => [ + k, + (node: Node, view: EditorViewController, getPos: () => number) => new V(node, view, getPos) + ]) + this.cachedNodeViews = {...Object.fromEntries([...widgetViewEntries, ...nodeViewEntries])} return this.cachedNodeViews } } @@ -378,345 +382,6 @@ export class ExplorableEditor extends LitElement { ` } - static editingStyles = css` - - html { - background: var(--sl-color-gray-100); - overflow-y: scroll; - overflow-x: hidden; - height: 100%; - --sl-color-danger-300: #fca5a5; - --sl-color-primary-400: #38bdf8; - } - - html::-webkit-scrollbar { - width: 16px; - } - - html::-webkit-scrollbar-thumb { - background-color: #b0b0b0; - background-clip: padding-box; - border-bottom: 6px solid transparent; - border-top: 6px solid transparent; - } - - html::-webkit-scrollbar-track { - background-color: transparent; - } - /* Buttons */ - html::-webkit-scrollbar-button:single-button { - background-color: transparent; - display: block; - border-style: solid; - height: 16px; - width: 16px; - padding: 2px; - } - /* Up */ - html::-webkit-scrollbar-button:single-button:vertical:decrement { - border-width: 0 8px 8px 8px; - border-color: transparent transparent var(--sl-color-gray-600) transparent; - } - - html::-webkit-scrollbar-button:single-button:vertical:decrement:hover { - border-color: transparent transparent var(--sl-color-gray-800) transparent; - } - - html::-webkit-scrollbar-button:single-button:vertical:decrement:disabled { - border-color: transparent transparent var(--sl-color-gray-400) transparent; - } - - /* Down */ - html::-webkit-scrollbar-button:single-button:vertical:increment { - border-width: 8px 8px 0 8px; - border-color: var(--sl-color-gray-600) transparent transparent transparent; - } - - html::-webkit-scrollbar-button:vertical:single-button:increment:hover { - border-color: var(--sl-color-gray-800) transparent transparent transparent; - } - - html::-webkit-scrollbar-button:vertical:single-button:increment:disabled { - border-color: var(--sl-color-gray-400) transparent transparent transparent; - } - - a:not([href]) { - text-decoration: underline; - color: #0000EE; - } - - body { - display: block; - margin: 0; - max-width: 840px; - } - - audio, video, picture, picture > img, embed { - width: 100%; - } - - embed { - aspect-ratio: 1/1.4142; - } - - figure:has(.ProseMirror-selectednode) { - position: relative; - } - - figure:has(.ProseMirror-selectednode)::before { - position: absolute; - content: ""; - left: 0; - top: 0; - height: 100%; - width: 100%; - background: var(--sl-color-primary-400); - opacity: 0.5; - z-index: 1; - } - - figcaption { - text-align: center; - font-size: 0.875rem; - margin-top: 0.125rem; - } - - figure > script { - display: block; - font-family: monospace; - border: 2px solid darkgray; - border-radius: 0.25rem; - font-size: 0.875rem; - padding: 0.5rem; - white-space: pre; - overflow: scroll; - height: 300px; - resize: vertical; - } - - .slot-content { - cursor: text; - } - - - .ProseMirror { - outline: none; - display: block; - white-space: normal !important; - font-family: Arial, sans-serif; - font-size: 14pt; - background: white; - border: 1px solid rgba(0, 0, 0, 0.1); - padding: 19px; - min-height: 100%; - box-sizing: border-box; - } - - .ww-widget { - --ww-action-opacity: 1; - position: relative !important; - display: block !important; - user-select: none !important; - -webkit-user-select: none !important; - } - - .ww-widget[editable]::before { - content: ""; - position: absolute; - right: -20px; - top: 0; - left: 0px; - background: transparent; - height: calc(100% + 5px); - width: calc(100% + 20px); - } - - .ww-widget[editable]::after { - content: ""; - position: absolute; - right: -14px; - top: 0; - width: 6px; - background: none; - height: 100%; - border-radius: 4px; - } - - .ww-widget[editable]:hover::after { - background: var(--sl-color-primary-300); - } - - .ww-widget[editable][data-ww-selected]::after { - background: var(--sl-color-primary-400); - } - - .ww-widget[editable][data-ww-deleting]::before { - background: var(--sl-color-danger-400); - opacity: 0.25; - z-index: 1; - width: 100%; - height: 100%; - } - - .ww-widget[editable][data-ww-deleting]::after { - background: var(--sl-color-danger-400); - } - - .ww-widget#ww_preview { - position: relative; - display: block; - user-select: none; - -webkit-user-select: none; - outline: 4px dashed var(--sl-color-primary-400); - } - - - main:not(:focus-within) .ww-widget[data-ww-selected] { - outline-color: lightgray; - } - - .ww-widget:focus-within { - --ww-action-opacity: 1; - } - - .ww-widget:not(:focus-within) { - cursor: pointer; - } - - .ww-widget::part(action) { - opacity: var(--ww-action-opacity); - } - - :host([previewing]) ww-widget-toolbox { - display: none; - } - - .ProseMirror::before { - color: darkgray; - position: absolute; - content: '⠀'; - pointer-events: none; - user-select: none; - -webkit-user-select: none; - } - - [data-empty] { - position: relative; - } - - :is(h1, h2, h3, h4, h5, h6)[data-empty]::before { - content: attr(data-placeholder); - position: absolute; - top: 0; - left: 2px; - color: var(--sl-color-gray-400); - pointer-events: none; - user-select: none; - -webkit-user-select: none; - } - - .ProseMirror > p { - margin: 0; - position: relative; - z-index: 1; - line-height: 2; - } - - .ProseMirror table { - margin: 0; - } - - .ProseMirror th, - .ProseMirror td { - min-width: 1em; - border: 1px solid var(--sl-color-gray-600); - padding: 3px 5px; - transition: width 0.1s; - } - - .ProseMirror .tableWrapper { - margin: 1em 0; - } - - .ProseMirror th { - font-weight: bold; - text-align: left; - } - - table .ProseMirror-gapcursor { - border: 2px dashed var(--sl-color-primary-600); - display: table-cell; - min-width: 1em; - padding: 3px 5px; - vertical-align: top; - box-sizing: border-box; - position: relative; - height: 100%; - } - - table .ProseMirror-gapcursor::after { - border-top: none; - border-left: 1px solid black; - width: 2px; - height: 1em; - position: static; - } - - .ProseMirror ::selection { - background-color: var(--sl-color-primary-400); - color: var(--sl-color-gray-100); - } - - blockquote { - background: #f9f9f9; - border-left: 10px solid #ccc; - margin: 1.5em 10px; - padding: 0.5em 10px; - } - - @media only screen and (min-width: 1071px) { - - .ww-widget::part(action) { - position: absolute; - height: calc(100% - 40px); - width: calc(min(100vw - 840px - 40px, 800px)); - left: 100%; - top: 0px; - padding-left: 30px; - padding-top: 40px; - user-select: none; - -webkit-user-select: none; - } - - .ww-widget:not(:focus-within) { - --ww-action-opacity: 0; - } - } - - @media print { - html { - overflow: visible; - background: none; - border: none; - } - - body { - background: none; - border: none; - } - - .ProseMirror { - background: none; - border: none; - } - - .ProseMirror[data-empty]::before { - display: none; - } - } - - ` - loadingSpinnerTemplate = () => html`
@@ -771,31 +436,35 @@ export class ExplorableEditor extends LitElement { } decorations = (state: EditorState) => { - if(!this.previewing) { - const {from, to} = state.selection - const decorations = [] as Decoration[] - state.doc.forEach((node, k, i) => { - if(node.type.spec["widget"]) { - decorations.push(Decoration.node(k, k + 1, { - editable: "true", - ...this.deletingWidget?.id === node.attrs.id? {"data-ww-deleting": ""}: {}, - ...from <= k && k <= to? {"data-ww-selected": ""}: {} - })) - } - else if(node.type.spec.group?.split(" ").includes("heading")) { - const cmd = this.app.commands.containerCommands.find(cmd => cmd.id === node.type.name) - decorations.push(Decoration.node(k, state.doc.resolve(k + 1).after(1), { + const {from, to, $from} = state.selection + const decorations = [] as Decoration[] + state.doc.descendants((node, pos, parent, index) => { + const name = node.type.name + const selectionEndsInNode = pos <= to && to <= pos + node.nodeSize + const selectionStartsInNode = pos <= from && from <= pos + node.nodeSize + const selectionWrapsNode = from <= pos && pos + node.nodeSize <= to + if((selectionStartsInNode || selectionEndsInNode || selectionWrapsNode) && name !== "text") { + decorations.push(Decoration.node(pos, pos + node.nodeSize, {"data-ww-selected": ""})) + } + if(node.isInline || name === "_phrase") { + decorations.push(Decoration.node(pos, pos + node.nodeSize, {"data-ww-inline": ""})) + } + if(node.attrs.id && this.deletingWidget?.id === node.attrs.id) { + decorations.push(Decoration.node(pos, pos + node.nodeSize, {"data-ww-deleting": ""})) + } + if(node.type.spec.group?.split(" ").includes("heading")) { + const cmd = this.app.commands.containerCommands.find(cmd => cmd.id === name) + decorations.push(Decoration.node( + pos, + pos + node.nodeSize, + { "data-placeholder": cmd?.label, - ...(node.textContent.trim() === ""? {"data-empty": ""}: {}), - ...from <= k && k <= to? {"data-ww-selected": ""}: {} - })) - } - }) - return DecorationSet.create(state.doc, decorations) - } - else { - return DecorationSet.create(state.doc, []) - } + ...(node.textContent.trim() === ""? {"data-empty": ""}: {}) + } + )) + } + }) + return DecorationSet.create(state.doc, decorations) } handleUpdate = () => { @@ -813,18 +482,9 @@ export class ExplorableEditor extends LitElement { return null } if(selection instanceof TextSelection || selection instanceof AllSelection) { - const pos = this.selection.from - let node = this.pmEditor?.domAtPos(pos, 0)?.node - let docNode = this.pmEditor?.domAtPos(0, 0)?.node - while(node?.parentElement && node.parentElement !== docNode) { - node = node.parentElement - } - /* - let offset = this.pmEditor?.domAtPos(pos).offset - if(node instanceof Text) { - node = this.pmEditor?.domAtPos(pos - offset).node - }*/ - return node as HTMLElement + const node = this.pmEditor.domAtPos(this.selection.anchor, 0)?.node + return node?.nodeType === window.Node.TEXT_NODE? node.parentElement: node as HTMLElement + } else if(selection instanceof NodeSelection) { const node = this.pmEditor?.nodeDOM(selection.anchor) @@ -919,7 +579,13 @@ export class ExplorableEditor extends LitElement { middleware: [] }) this.toolboxX = roundByDPR(docWidth + 10) - this.toolboxY = roundByDPR(Math.max(Math.min(iframeOffsetY + y, docHeight - this.toolbox.clientHeight + iframeOffsetY), iframeOffsetY))/*roundByDPR( + this.toolboxY = roundByDPR(Math.max( + Math.min( + y, + docHeight - this.toolbox.clientHeight + iframeOffsetY + ), + iframeOffsetY + ))/*roundByDPR( Math.min(Math.max(selectionY, 0), yMax) )*/ } @@ -1108,7 +774,6 @@ export class ExplorableEditor extends LitElement { handleDropOrPaste = (ev: DragEvent | ClipboardEvent) => { const DragEvent = this.pmEditor.window.DragEvent const data = ev instanceof DragEvent? ev.dataTransfer: ev.clipboardData - console.log(data?.getData("text/html")) if((data?.files?.length ?? 0) > 0) { const files = [...(data?.files as any)].filter(file => file) as File[] let elements = [] as Element[] @@ -1135,7 +800,6 @@ export class ExplorableEditor extends LitElement { get contentStyle() { return [ - ExplorableEditor.editingStyles.cssText, this.bundleCSS ] } @@ -1165,7 +829,10 @@ export class ExplorableEditor extends LitElement { .contentScript=${this.contentScript} .contentStyle=${this.contentStyle} .shouldBeEditable=${this.shouldBeEditable} - .handleDOMEvents=${this.handleDOMEvents}> + .handleDOMEvents=${this.handleDOMEvents} + .transformPastedHTML=${(html: string) => { + return html.replaceAll(/style=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g, "") + }}> ` } @@ -1297,6 +964,7 @@ export class ExplorableEditor extends LitElement { ${this.CoreEditor()} ${this.Toolbox()} ${this.Palette()} + ` } diff --git a/@webwriter/core/view/editor/index.ts b/@webwriter/core/view/editor/index.ts index af09f288..089b6726 100644 --- a/@webwriter/core/view/editor/index.ts +++ b/@webwriter/core/view/editor/index.ts @@ -6,4 +6,5 @@ export * from "./prosemirroreditor" export * from "./toolbox" export * from "./nodeviews" export * from "./metaeditor" -export * from "./attributesdialog" \ No newline at end of file +export * from "./attributesdialog" +export * from "./debugoverlay" \ No newline at end of file diff --git a/@webwriter/core/view/editor/nodeviews.ts b/@webwriter/core/view/editor/nodeviews.ts index b9b67468..285f5686 100644 --- a/@webwriter/core/view/editor/nodeviews.ts +++ b/@webwriter/core/view/editor/nodeviews.ts @@ -1,4 +1,4 @@ -import { Decoration, DecorationSource, NodeView } from "prosemirror-view" +import { Decoration, DecorationSource, NodeView, NodeViewConstructor } from "prosemirror-view" import { NodeSelection } from "prosemirror-state" import { DOMSerializer, Node } from "prosemirror-model" import {html, render} from "lit" @@ -20,7 +20,7 @@ export class WidgetView implements NodeView { this.view = view const existingDom = view.dom.querySelector(`#${node.attrs.id}`) const newDom = this.createDOM(!!existingDom) - this.dom = existingDom as HTMLElement ?? newDom + this.dom = this.contentDOM = existingDom as HTMLElement ?? newDom if(existingDom) { const oldNames = this.dom.getAttributeNames() const newNames = newDom.getAttributeNames() @@ -28,6 +28,7 @@ export class WidgetView implements NodeView { toRemove.forEach(k => this.dom.removeAttribute(k)) newNames.forEach(k => this.dom.setAttribute(k, newDom.getAttribute(k)!)) } + this.dom.toggleAttribute("editable", true) } getPos() { @@ -155,7 +156,7 @@ export class FigureView implements NodeView { this.node = node this.view = view this.getPos = getPos - this.dom = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement + this.dom = this.contentDOM = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement const observer = new MutationObserver(() => { selectParentNode(this.view.state, this.view.dispatch, this.view) }) @@ -176,7 +177,7 @@ export class AudioView implements NodeView { this.node = node this.view = view this.getPos = getPos - this.dom = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement + this.dom = this.contentDOM = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement const observer = new MutationObserver(mutations => { if(mutations.some(m => m.attributeName === "src") && !node.attrs.src) { this.dom.setAttribute("src", AudioView.emptyDataURL) @@ -199,7 +200,7 @@ export class VideoView implements NodeView { this.node = node this.view = view this.getPos = getPos - this.dom = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement + this.dom = this.contentDOM = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement const observer = new MutationObserver(mutations => { if(mutations.some(m => m.attributeName === "src") && !node.attrs.src) { this.dom.setAttribute("src", AudioView.emptyDataURL) @@ -220,14 +221,38 @@ export class UnknownElementView implements NodeView { this.node = node this.view = view this.getPos = getPos - this.dom = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement + this.dom = this.contentDOM = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLElement } } +export class DetailsView implements NodeView { + node: Node + view: EditorViewController + getPos: () => number + dom: HTMLDetailsElement + contentDOM?: HTMLElement + + constructor(node: Node, view: EditorViewController, getPos: () => number) { + this.node = node + this.view = view + this.getPos = getPos + this.dom = this.contentDOM = DOMSerializer.fromSchema(this.node.type.schema).serializeNode(this.node) as HTMLDetailsElement + this.dom.addEventListener("click", e => { + const el = e.target as HTMLElement + if(el.tagName === "SUMMARY") { + this.view.dispatch(this.view.state.tr.setNodeAttribute(this.getPos(), "open", !this.node.attrs.open)) + } + }) + } + +} + + export const nodeViews = { "_widget": WidgetView, "_unknownElement": UnknownElementView, "audio": AudioView, - "video": VideoView + "video": VideoView, + "details": DetailsView } \ No newline at end of file diff --git a/@webwriter/core/view/editor/toolbox.ts b/@webwriter/core/view/editor/toolbox.ts index 72263311..76276b4e 100644 --- a/@webwriter/core/view/editor/toolbox.ts +++ b/@webwriter/core/view/editor/toolbox.ts @@ -90,9 +90,9 @@ export class Toolbox extends LitElement { -webkit-user-select: none; background: var(--sl-color-gray-100); display: flex; - flex-direction: row; + flex-direction: column; flex-wrap: wrap; - gap: 32px; + gap: 8px; align-items: center; user-select: none; -webkit-user-select: none; @@ -490,7 +490,7 @@ export class Toolbox extends LitElement { ` } - MarkCommands = () => this.app.commands.markCommands.filter(cmd => !cmd.tags?.includes("inline") && !cmd.tags?.includes("advanced")).map(v => { + MarkCommands = () => this.app.commands.markCommands.filter(v => !v.tags?.includes("advanced") || v.active).map(v => { const classes = { "inline-commands": true, "applied": Boolean(v.active), @@ -583,7 +583,6 @@ export class Toolbox extends LitElement { ${this.LayoutCommands(el)}
- ${this.InlineToolbox()}
${this.Pickers(el)} @@ -661,12 +660,13 @@ export class Toolbox extends LitElement { } } - get activeElementAncestors() { + get activeElementPath() { let el = this.activeElement - const ancestors = [el] as HTMLElement[] - while(el?.parentElement) { - if(!(["BODY", "HTML"].includes(el.parentElement.tagName))) { - ancestors.unshift(el.parentElement) + const ancestors = [] as HTMLElement[] + while(el) { + const tagsToExclude = ["html", "body", ...this.app.commands.markCommands.map(cmd => cmd.id)].map(k => k.toUpperCase()) + if(!(tagsToExclude.includes(el.tagName))) { + ancestors.unshift(el) } el = el.parentElement } @@ -676,14 +676,17 @@ export class Toolbox extends LitElement { render() { if (this.activeElement && this.isActiveElementWidget) { const name = prettifyPackageName(this.activeElement.tagName.toLowerCase()) - return html` + return html`
this.emitClickName(this.activeElement ?? undefined)} title=${this.activeElement.id}>${name} - ` +
` } else if(this.activeElement) { - return this.activeElementAncestors.map(this.BlockToolbox) + return html` + ${this.activeElementPath.map(this.BlockToolbox)} + ${this.InlineToolbox()} + ` } else { return null diff --git a/@webwriter/core/view/index.ts b/@webwriter/core/view/index.ts index 42df2df4..4b62694c 100644 --- a/@webwriter/core/view/index.ts +++ b/@webwriter/core/view/index.ts @@ -256,7 +256,7 @@ export class App extends ViewModelMixin(LitElement) Content = () => { const {changed, set, setHead, url, editorState} = this.store.document const {packages, availableWidgetTypes, bundleCode, bundleCSS, bundleID} = this.store.packages - const {locale, showTextPlaceholder, showWidgetPreview} = this.store.ui + const {locale, showTextPlaceholder} = this.store.ui const {open} = this.environment.api.Shell const {documentCommands, commands: {setDocAttrs, editHead}} = this.commands const head = html` `: null @@ -335,17 +334,16 @@ export class App extends ViewModelMixin(LitElement) } render() { - const initializing = !this.store || this.store.packages.initializing - if(!initializing) { + if(!this.initializing) { this.Notification() this.localization.setLocale(this.store.ui.locale) } return html` - ${initializing? this.Placeholder(): [ + ${this.initializing? this.Placeholder(): [ this.HeaderLeft(), this.HeaderRight(), this.Content(), diff --git a/@webwriter/core/viewmodel/commandcontroller.ts b/@webwriter/core/viewmodel/commandcontroller.ts index fafde8dc..d7613eb7 100644 --- a/@webwriter/core/viewmodel/commandcontroller.ts +++ b/@webwriter/core/viewmodel/commandcontroller.ts @@ -301,7 +301,8 @@ class NodeCommand extends Command } run(options?: any, e?: Event) { const {exec, editorState} = this.host.activeEditor ?? {exec: () => {}} - return super.run(options, e, (host, attrs) => exec(wrapSelection(editorState?.schema.nodes[this.id]!, attrs))) + console.log(editorState) + return super.run(options, e, (host, attrs) => exec(wrapSelection(this.id, attrs))) } get active() { return this.spec.active? this.spec.active(this.host): !!this.host.store.document.activeNodeMap[this.id] @@ -377,11 +378,11 @@ export class CommandController implements ReactiveController { } @Memoize() get markCommands() { - return this.queryCommands({tags: ["mark"]}).filter(cmd => !cmd.tags?.includes("advanced")) + return this.queryCommands({tags: ["mark"]}) } - + @Memoize() get nodeCommands() { - return this.queryCommands({tags: ["node"]}).filter(cmd => !cmd.tags?.includes("advanced")) + return this.queryCommands({tags: ["node"]}) } @Memoize() get groupedNodeCommands() { @@ -777,13 +778,19 @@ export class CommandController implements ReactiveController { group: "textblock", tags: ["node", "container"] }), + div: new NodeCommand(this.host, { + id: "div", + label: () => msg("Division"), + icon: "square", + description: () => msg("Insert a division"), + tags: ["node", "container", "advanced"] + }), pre: new NodeCommand(this.host, { id: "pre", label: () => msg("Preformatted Text"), icon: "code-dots", description: () => msg("Insert a preformatted text block"), - group: "textblock", - tags: ["node", "container"] + tags: ["node", "container", "advanced"] }), h1: new NodeCommand(this.host, { id: "h1", @@ -1080,7 +1087,7 @@ export class CommandController implements ReactiveController { icon: "circle-chevron-right", description: () => msg("Insert details"), group: "details", - tags: ["node", "container", "advanced"] + tags: ["node", "container"] }), summary: new NodeCommand(this.host, { id: "summary", diff --git a/@webwriter/core/viewmodel/index.ts b/@webwriter/core/viewmodel/index.ts index 1861c995..a6131951 100644 --- a/@webwriter/core/viewmodel/index.ts +++ b/@webwriter/core/viewmodel/index.ts @@ -9,7 +9,7 @@ export * from "./environmentcontroller" export * from "./iconcontroller" import {StoreController, EnvironmentController, CommandController, LocalizationController, NotificationController, SettingsController, IconController} from "." -import { RootStore } from "../model" +import { PackageStore, RootStore } from "../model" import { msg } from "@lit/localize" import { WINDOW_OPTIONS } from "./commandcontroller" @@ -26,9 +26,11 @@ export const ViewModelMixin = (cls: LitElementConstructor, isSettings=false) => icons: IconController initialized: Promise + initializing: boolean = false async connectedCallback() { this.initialized = new Promise(async resolve => { + this.initializing = true super.connectedCallback() this.environment = new EnvironmentController(this) await this.environment.apiReady @@ -48,7 +50,8 @@ export const ViewModelMixin = (cls: LitElementConstructor, isSettings=false) => } const {join, appDir} = this.environment.api.Path const packageJsonPath = await join(await appDir(), "package.json") - this.environment.api.watch(packageJsonPath, () => this.store.packages.fetchInstalled(true)) + this.environment.api.watch(packageJsonPath, () => this.store.packages.loadAll()) + this.initializing = false resolve(undefined) }) } diff --git a/package-lock.json b/package-lock.json index c8892130..f5425641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ ], "dependencies": { "lit": "^2.6.1", + "prosemirror-utils": "^1.2.1-0", "semver": "^7.3.8" }, "devDependencies": { @@ -96,6 +97,7 @@ "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.3.4", "prosemirror-transform": "^1.8.0", + "prosemirror-utils": "^1.2.1-0", "prosemirror-view": "^1.32.0", "redefine-custom-elements": "^0.1.2", "sortablejs": "^1.15.0", @@ -141,7 +143,7 @@ "license": "UNLICENSED" }, "@webwriter/lit": { - "version": "0.6.0", + "version": "0.7.1", "license": "MIT", "dependencies": { "@open-wc/scoped-elements": "^2.2.0", @@ -456,9 +458,9 @@ "version": "0.4.1", "license": "MIT", "dependencies": { - "@shoelace-style/shoelace": "^2.5.1", - "@webwriter/lit": "^0.4.1", - "lit": "^2.3.1" + "@shoelace-style/shoelace": "^2.10.0", + "@webwriter/lit": "^0.4.2", + "lit": "^2.8.0" } }, "@webwriter/ww-textarea/node_modules/@webwriter/lit": { @@ -2787,11 +2789,11 @@ } }, "node_modules/@ctrl/tinycolor": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz", - "integrity": "sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.0.2.tgz", + "integrity": "sha512-fKQinXE9pJ83J1n+C3rDl2xNLJwfoYNvXLRy5cYZA9hBJJw2q+sbb/AOSNKmLxnTWyNTmy4994dueSwP4opi5g==", "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/@emmetio/abbreviation": { @@ -3148,18 +3150,27 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz", - "integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } }, "node_modules/@floating-ui/dom": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.7.tgz", - "integrity": "sha512-DyqylONj1ZaBnzj+uBnVfzdjjCkFCL2aA9ESHLyUOGSqb03RpbLMImP1ekIQXYs4KLk9jAjJfZAU8hXfWSahEg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", "dependencies": { - "@floating-ui/core": "^1.2.6" + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" } }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -4342,14 +4353,6 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@lit-labs/react": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-2.0.2.tgz", - "integrity": "sha512-bRr0uVaLzVMv/r6bNui91rwsY3UuOYDef5wCvfABQ3ipVtArz8TbH2k4Cn8UgLG/9IXO7IhAYt5xwMHaljxCAg==", - "peerDependencies": { - "@types/react": "17 || 18" - } - }, "node_modules/@lit-labs/ssr": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@lit-labs/ssr/-/ssr-3.1.2.tgz", @@ -4463,6 +4466,14 @@ "node": ">=12" } }, + "node_modules/@lit/react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.0.tgz", + "integrity": "sha512-uTuU6vpxtZvCWxcu3GNosckP2JpFWZpMKjhwQ42Bzu/OU9kjStJspA04o7RadecQfx0YiFIImX3qek15BXhaWQ==", + "peerDependencies": { + "@types/react": "17 || 18" + } + }, "node_modules/@lit/reactive-element": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.1.tgz", @@ -6472,22 +6483,22 @@ } }, "node_modules/@shoelace-style/localize": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.1.1.tgz", - "integrity": "sha512-NkM/hj3Js6yXCU9WxhsyxRUdyqUUUl/BSvIluUMptQteUWGOJaoyP1iMbOMqO544DYMzBfnoCw66ZHkGuTdKgA==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.1.2.tgz", + "integrity": "sha512-Hf45HeO+vdQblabpyZOTxJ4ZeZsmIUYXXPmoYrrR4OJ5OKxL+bhMz5mK8JXgl7HsoEowfz7+e248UGi861de9Q==" }, "node_modules/@shoelace-style/shoelace": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@shoelace-style/shoelace/-/shoelace-2.8.0.tgz", - "integrity": "sha512-WigVUaW+MptefOW4zUlZaq+zn0n2hP+I4/mztoeJii5u3ex1CGexfQGcdw2gx6d7P7LAa6/NW0TlgAELzJQnCA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@shoelace-style/shoelace/-/shoelace-2.10.0.tgz", + "integrity": "sha512-bS9S2yqFZE+P4CqOa3vP1kMgSzVLKi1aTx9VYEW47YoR+TZuMkwgcPsJlAHb4Dw+MmaBqGO9XLhSIfq9pZpCug==", "dependencies": { - "@ctrl/tinycolor": "^3.5.0", - "@floating-ui/dom": "^1.2.1", - "@lit-labs/react": "^2.0.1", + "@ctrl/tinycolor": "^4.0.2", + "@floating-ui/dom": "^1.5.3", + "@lit/react": "^1.0.0", "@shoelace-style/animations": "^1.1.0", - "@shoelace-style/localize": "^3.1.1", + "@shoelace-style/localize": "^3.1.2", "composed-offset-position": "^0.0.4", - "lit": "^2.7.5", + "lit": "^3.0.0", "qr-creator": "^1.0.0" }, "engines": { @@ -6498,6 +6509,42 @@ "url": "https://github.com/sponsors/claviska" } }, + "node_modules/@shoelace-style/shoelace/node_modules/@lit/reactive-element": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.0.tgz", + "integrity": "sha512-wn+2+uDcs62ROBmVAwssO4x5xue/uKD3MGGZOXL2sMxReTRIT0JXKyMXeu7gh0aJ4IJNEIG/3aOnUaQvM7BMzQ==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.1.2-pre.0" + } + }, + "node_modules/@shoelace-style/shoelace/node_modules/lit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.0.0.tgz", + "integrity": "sha512-nQ0teRzU1Kdj++VdmttS2WvIen8M79wChJ6guRKIIym2M3Ansg3Adj9O6yuQh2IpjxiUXlNuS81WKlQ4iL3BmA==", + "dependencies": { + "@lit/reactive-element": "^2.0.0", + "lit-element": "^4.0.0", + "lit-html": "^3.0.0" + } + }, + "node_modules/@shoelace-style/shoelace/node_modules/lit-element": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.0.tgz", + "integrity": "sha512-N6+f7XgusURHl69DUZU6sTBGlIN+9Ixfs3ykkNDfgfTkDYGGOWwHAYBhDqVswnFGyWgQYR2KiSpu4J76Kccs/A==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.1.2-pre.0", + "@lit/reactive-element": "^2.0.0", + "lit-html": "^3.0.0" + } + }, + "node_modules/@shoelace-style/shoelace/node_modules/lit-html": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.0.0.tgz", + "integrity": "sha512-DNJIE8dNY0dQF2Gs0sdMNUppMQT2/CvV4OVnSdg7BXAsGqkVwsE5bqQ04POfkYH5dBIuGnJYdFz5fYYyNnOxiA==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/@sigstore/protobuf-specs": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz", @@ -7163,15 +7210,15 @@ "dev": true }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "version": "15.7.9", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", + "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==", "peer": true }, "node_modules/@types/react": { - "version": "18.2.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", - "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "version": "18.2.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.29.tgz", + "integrity": "sha512-Z+ZrIRocWtdD70j45izShRwDuiB4JZqDegqMFW/I8aG5DxxLKOzVNoq62UIO82v9bdgi+DO1jvsb9sTEZUSm+Q==", "peer": true, "dependencies": { "@types/prop-types": "*", @@ -7193,9 +7240,9 @@ } }, "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", + "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==", "peer": true }, "node_modules/@types/semver": { @@ -24489,9 +24536,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.19.0.tgz", - "integrity": "sha512-/CvFGJnwc41EJSfDkQLly1cAJJJmBpZwwUJtwZPTjY2RqZJfM8HVbCreOY/jti8wTRbVyjagcylyGoeJH/g/3w==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.19.3.tgz", + "integrity": "sha512-tgSnwN7BS7/UM0sSARcW+IQryx2vODKX4MI7xpqY2X+iaepJdKBPc7I4aACIsDV/LTaTjt12Z56MhDr9LsyuZQ==", "dependencies": { "orderedmap": "^2.0.0" } @@ -24552,6 +24599,15 @@ "prosemirror-model": "^1.0.0" } }, + "node_modules/prosemirror-utils": { + "version": "1.2.1-0", + "resolved": "https://registry.npmjs.org/prosemirror-utils/-/prosemirror-utils-1.2.1-0.tgz", + "integrity": "sha512-YJNjxSAFhV+w7/nKfLU4SJ0sYEHDJuaIvIxp6V1DgrVFSBBksnvHZTGPVmKGYEnbTqr0kG8HXqopNKPoQtT+3A==", + "peerDependencies": { + "prosemirror-model": "^1.19.2", + "prosemirror-state": "^1.4.3" + } + }, "node_modules/prosemirror-view": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.32.0.tgz", diff --git a/package.json b/package.json index c58a671a..d84a469b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webwriter", - "version": "0.8.2", + "version": "0.8.3", "description": "Web-based authoring tool for explorables", "scripts": { "lerna": "lerna", @@ -70,6 +70,7 @@ }, "dependencies": { "lit": "^2.6.1", + "prosemirror-utils": "^1.2.1-0", "semver": "^7.3.8" } }