forked from foundryvtt/pf2e
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve prosemirror dropdown menu (foundryvtt#14690)
- Loading branch information
1 parent
8e57dc2
commit 6018278
Showing
9 changed files
with
223 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
class ProseMirrorMenuPF2e extends foundry.prosemirror.ProseMirrorMenu { | ||
protected override _getDropDownMenus(): Record<string, ProseMirrorDropDownConfig> { | ||
const menus = super._getDropDownMenus(); | ||
const toggleMark = foundry.prosemirror.commands.toggleMark; | ||
const wrapIn = foundry.prosemirror.commands.wrapIn; | ||
|
||
if ("format" in menus) { | ||
menus.format.entries.push({ | ||
action: "pf2e", | ||
title: "PF2e", | ||
children: [ | ||
{ | ||
action: "pf2e-action-glyph", | ||
title: "Icons 1 2 3 F R", | ||
mark: this.schema.marks.span, | ||
attrs: { _preserve: { class: "action-glyph" } }, | ||
priority: 1, | ||
cmd: toggleMark(this.schema.marks.span, { | ||
_preserve: { class: "action-glyph" }, | ||
}), | ||
}, | ||
{ | ||
action: "pf2e-inline-header", | ||
title: "Inline Header", | ||
class: "level4", | ||
node: this.schema.nodes.heading, | ||
attrs: { _preserve: { class: "inline-header" }, level: 4 }, | ||
priority: 1, | ||
cmd: () => { | ||
this._toggleTextBlock(this.schema.nodes.heading, { | ||
attrs: { _preserve: { class: "inline-header" }, level: 4 }, | ||
}); | ||
return true; | ||
}, | ||
}, | ||
{ | ||
action: "pf2e-info-block", | ||
title: "Info Block", | ||
node: this.schema.nodes.section, | ||
attrs: { _preserve: { class: "info" } }, | ||
priority: 1, | ||
cmd: () => { | ||
this._toggleBlock(this.schema.nodes.section, wrapIn, { | ||
attrs: { _preserve: { class: "info" } }, | ||
}); | ||
return true; | ||
}, | ||
}, | ||
{ | ||
action: "pf2e-stat-block", | ||
title: "Stat Block", | ||
node: this.schema.nodes.section, | ||
attrs: { _preserve: { class: "statblock" } }, | ||
priority: 1, | ||
cmd: () => { | ||
this._toggleBlock(this.schema.nodes.section, wrapIn, { | ||
attrs: { _preserve: { class: "statblock" } }, | ||
}); | ||
return true; | ||
}, | ||
}, | ||
{ | ||
action: "pf2e-traits", | ||
title: "Trait", | ||
node: this.schema.nodes.section, | ||
attrs: { _preserve: { class: "traits" } }, | ||
priority: 1, | ||
cmd: () => { | ||
this._toggleBlock(this.schema.nodes.section, wrapIn, { | ||
attrs: { _preserve: { class: "traits" } }, | ||
}); | ||
return true; | ||
}, | ||
}, | ||
{ | ||
action: "pf2e-written-note", | ||
title: "Written Note", | ||
node: this.schema.nodes.paragraph, | ||
attrs: { _preserve: { class: "message" } }, | ||
priority: 1, | ||
cmd: () => { | ||
this._toggleTextBlock(this.schema.nodes.paragraph, { | ||
attrs: { _preserve: { class: "message" } }, | ||
}); | ||
return true; | ||
}, | ||
}, | ||
{ | ||
action: "pf2e-gm-text-block", | ||
title: "GM Text Block", | ||
node: this.schema.nodes.div, | ||
attrs: { _preserve: { "data-visibility": "gm" } }, | ||
priority: 1, | ||
cmd: () => { | ||
this._toggleBlock(this.schema.nodes.div, wrapIn, { | ||
attrs: { _preserve: { "data-visibility": "gm" } }, | ||
}); | ||
return true; | ||
}, | ||
}, | ||
{ | ||
action: "pf2e-gm-text-inline", | ||
title: "GM Text Inline", | ||
mark: this.schema.marks.span, | ||
attrs: { _preserve: { "data-visibility": "gm" } }, | ||
priority: 1, | ||
cmd: toggleMark(this.schema.marks.span, { | ||
_preserve: { "data-visibility": "gm" }, | ||
}), | ||
}, | ||
], | ||
}); | ||
} | ||
return menus; | ||
} | ||
|
||
protected override _isMarkActive(item: ProseMirrorMenuItem): boolean { | ||
if (!item.action.startsWith("pf2e-")) return super._isMarkActive(item); | ||
|
||
// This is the same as the super method except the `attr._preserve` property | ||
// is not removed from marks | ||
const state = this.view.state; | ||
const { from, $from, to, empty } = state.selection; | ||
const markCompare = (mark: ProseMirror.Mark) => { | ||
if (mark.type !== item.mark) return false; | ||
// R.isDeepEqual returns false here so we use the foundry helper | ||
if (item.attrs) return fu.objectsEqual(mark.attrs, item.attrs); | ||
return true; | ||
}; | ||
if (empty) return $from.marks().some(markCompare); | ||
let active = false; | ||
state.doc.nodesBetween(from, to, (node) => { | ||
if (node.marks.some(markCompare)) active = true; | ||
return !active; | ||
}); | ||
return active; | ||
} | ||
|
||
protected override _isNodeActive(item: ProseMirrorMenuItem): boolean { | ||
if (!item.action.startsWith("pf2e-")) return super._isNodeActive(item); | ||
|
||
// Same as the super method except the call to `this.#hasAncestor` | ||
const state = this.view.state; | ||
const { $from, $to, empty } = state.selection; | ||
const sameParent = empty || $from.sameParent($to); | ||
if (!sameParent) return false; | ||
return state.doc.nodeAt($from.pos)?.type === item.node || this.#hasAncestor($from, item.node, item.attrs); | ||
} | ||
|
||
/** A reimplementation of Foundry's `ResolvedPos.prototype.hasAncestor` extension that keeps the | ||
* `attrs._preserve` property when comparing nodes | ||
*/ | ||
#hasAncestor(pos: ProseMirror.ResolvedPos, other?: ProseMirror.NodeType, attrs?: Record<string, unknown>): boolean { | ||
if (!pos.depth || !other) return false; | ||
for (let i = pos.depth; i > 0; i--) { | ||
// Depth 0 is the root document, so we don't need to test that. | ||
const node = pos.node(i); | ||
if (node.type === other) { | ||
if (attrs) return fu.objectsEqual(node.attrs, attrs); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
protected override _onAction(event: MouseEvent): void { | ||
super._onAction(event); | ||
// Return focus to the editor after the command was executed | ||
this.view.focus(); | ||
} | ||
} | ||
|
||
export { ProseMirrorMenuPF2e }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.