ElyMark: the page hands every link click to the app (M-05)
Preview clicks and Cmd/Ctrl clicks in the editor go through one click handler; it stops the browser and sends the link mark's own address, since Milkdown blanks unsafe hrefs in the DOM. Reviews run at effort xhigh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fa5c0b7a75
commit
c5f614d095
12 files changed
+160
-18
No files matched your search
@@ -43,6 +43,9 @@ function flush() {
|
||||
pending = null
|
||||
}
|
||||
|
||||
/** A link the user opens; the app decides. */
|
||||
const linked = (href) => tell({ kind: 'link', generation, href })
|
||||
|
||||
/** The shown editor's headings, for the app's outline. */
|
||||
function outline() {
|
||||
if (current) tell({ kind: 'outline', generation, items: current.outline() })
|
||||
@@ -83,7 +86,7 @@ async function build(text, mode) {
|
||||
const edited = (write) => changed(() => front + write())
|
||||
let made
|
||||
try {
|
||||
made = await visual(place, body, mode === 'visual', texts, edited, edge)
|
||||
made = await visual(place, body, mode === 'visual', texts, edited, edge, linked)
|
||||
} catch (error) {
|
||||
// A broken editor falls back to source and says so.
|
||||
console.error(`ely: the visual editor failed: ${error.name}`)
|
||||
|
||||
@@ -39,7 +39,7 @@ import { search } from 'prosemirror-search'
|
||||
import { style } from './style.js'
|
||||
|
||||
/** An editor over `markdown`; null when blocks cannot map. */
|
||||
export async function visual(root, markdown, editable, texts, changed, edge) {
|
||||
export async function visual(root, markdown, editable, texts, changed, edge, linked) {
|
||||
const owner = diagrams()
|
||||
let write = () => ({ text: markdown, spans: [] })
|
||||
// Setting up raw blocks is no edit.
|
||||
@@ -71,6 +71,29 @@ export async function visual(root, markdown, editable, texts, changed, edge) {
|
||||
},
|
||||
}),
|
||||
)
|
||||
// Preview: a click opens. Editor: Cmd (Ctrl elsewhere).
|
||||
const mac = navigator.platform.startsWith('Mac')
|
||||
const links = $prose(
|
||||
() =>
|
||||
new Plugin({
|
||||
props: {
|
||||
handleDOMEvents: {
|
||||
click: (view, event) => {
|
||||
const anchor = event.target.closest?.('a')
|
||||
if (!anchor || !view.dom.contains(anchor)) return false
|
||||
event.preventDefault()
|
||||
if (editable && !(mac ? event.metaKey : event.ctrlKey)) return true
|
||||
// The DOM href is sanitized; the mark is not.
|
||||
const node = view.state.doc.nodeAt(view.posAtDOM(anchor, 0))
|
||||
const mark = node?.marks.find((mark) => mark.type.name === 'link')
|
||||
if (!mark) throw new Error('ely: a link without its mark')
|
||||
linked(mark.attrs.href)
|
||||
return true
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
// Past their history, undo and redo reach the page.
|
||||
const ends = $prose(() =>
|
||||
keymap({
|
||||
@@ -103,6 +126,7 @@ export async function visual(root, markdown, editable, texts, changed, edge) {
|
||||
.use(watch)
|
||||
.use(current)
|
||||
.use(ends)
|
||||
.use(links)
|
||||
.use($prose(() => search()))
|
||||
.create()
|
||||
const editor = await made.catch((error) => {
|
||||
|
||||
Reference in new issue
Block a user