-
-
Notifications
You must be signed in to change notification settings - Fork 191
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
281 additions
and
19 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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
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,28 @@ | ||
import { EditorView } from 'prosemirror-view'; | ||
|
||
interface TooltipPosition { | ||
bottom: number; | ||
left: number; | ||
} | ||
|
||
export const calculateBubblePos = (view: EditorView, toolTipEl: HTMLElement): TooltipPosition => { | ||
const { state: { selection } } = view; | ||
const { from, to } = selection; | ||
|
||
// These are in screen coordinates | ||
const start = view.coordsAtPos(from); | ||
const end = view.coordsAtPos(to); | ||
|
||
// The box in which the tooltip is positioned, to use as base | ||
const parent = toolTipEl.offsetParent; | ||
const box = parent.getBoundingClientRect(); | ||
|
||
// Find a center-ish x position from the selection endpoints (when | ||
// crossing lines, end may be more to the left) | ||
const left = Math.max((start.left + end.left) / 2, start.left + 3); | ||
|
||
return { | ||
left: left - box.left, | ||
bottom: box.bottom - start.top | ||
}; | ||
}; |
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,16 @@ | ||
import { EditorState } from 'prosemirror-state'; | ||
import { Mark } from 'prosemirror-model'; | ||
|
||
export const getSelectionMarks = (state: EditorState): Mark[] => { | ||
let marks: Mark[] = []; | ||
|
||
const { selection: { from, to } } = state; | ||
|
||
state.doc.nodesBetween(from, to, node => { | ||
marks = [...marks, ...node.marks]; | ||
}); | ||
|
||
return marks; | ||
}; | ||
|
||
export default getSelectionMarks; |
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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './isMarkActive'; | ||
export * from './isNodeActive'; | ||
export * from './isListItem'; | ||
export * from './getSelectionMarks'; |
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 |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export { default as placeholder } from './placeholder'; | ||
export { default as menu } from './menu'; | ||
export { default as link } from './link'; |
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,136 @@ | ||
import { EditorView } from 'prosemirror-view'; | ||
import { Plugin, PluginKey } from 'prosemirror-state'; | ||
import { Mark } from 'prosemirror-model'; | ||
|
||
import { calculateBubblePos } from '../helpers/bubblePosition'; | ||
import { isMarkActive, getSelectionMarks } from '../helpers'; | ||
|
||
class FloatingOptionsView { | ||
bubbleEL: HTMLElement; | ||
|
||
constructor(view: EditorView) { | ||
this.render(view); | ||
this.update(view); | ||
} | ||
|
||
render(view: EditorView): void { | ||
this.bubbleEL = document.createElement('div'); | ||
this.bubbleEL.className = 'NgxEditor__FloatingBubble'; | ||
view.dom.parentNode.appendChild(this.bubbleEL); | ||
} | ||
|
||
setDomPosition(view: EditorView): void { | ||
// Otherwise, reposition it and update its content | ||
this.bubbleEL.style.display = ''; | ||
|
||
const { bottom, left } = calculateBubblePos(view, this.bubbleEL); | ||
this.bubbleEL.style.left = left + 'px'; | ||
this.bubbleEL.style.bottom = bottom + 'px'; | ||
} | ||
|
||
createLinkNode(item: Mark, removeCB: (e: MouseEvent) => void): DocumentFragment { | ||
const el = document.createDocumentFragment(); | ||
|
||
const link = document.createElement('a'); | ||
link.href = item.attrs.href; | ||
link.target = '_blank'; | ||
link.innerText = item.attrs.href; | ||
link.title = item.attrs.href; | ||
|
||
const commands = document.createElement('div'); | ||
commands.classList.add('commands'); | ||
|
||
const editOpt = document.createElement('button'); | ||
editOpt.classList.add('command'); | ||
editOpt.textContent = 'Edit'; | ||
|
||
const removeOpt = document.createElement('button'); | ||
removeOpt.classList.add('command'); | ||
removeOpt.textContent = 'Remove'; | ||
|
||
removeOpt.onclick = removeCB; | ||
|
||
// commands.appendChild(editOpt); | ||
commands.appendChild(removeOpt); | ||
|
||
el.appendChild(link); | ||
el.appendChild(commands); | ||
|
||
return el; | ||
} | ||
|
||
clearBubbleContent(): void { | ||
this.bubbleEL.textContent = ''; | ||
} | ||
|
||
hideBubble(): void { | ||
this.bubbleEL.style.display = 'none'; | ||
} | ||
|
||
update(view: EditorView): void { | ||
const { state, dispatch } = view; | ||
const { selection, schema, doc, tr } = state; | ||
|
||
if (!schema.marks.link) { | ||
return; | ||
} | ||
|
||
const hasFocus = view.hasFocus(); | ||
const isActive = isMarkActive(state, schema.marks.link); | ||
const linkMarks: Mark[] = getSelectionMarks(state).filter(mark => mark.type === schema.marks.link); | ||
|
||
|
||
// hide for selection and show only for clicks | ||
if (!isActive || linkMarks.length !== 1 || !hasFocus) { | ||
this.hideBubble(); | ||
return; | ||
} | ||
|
||
const { $head: { pos } } = selection; | ||
const [linkItem] = linkMarks; | ||
|
||
this.clearBubbleContent(); | ||
|
||
const removeCB = (e: MouseEvent) => { | ||
e.preventDefault(); | ||
|
||
const $pos = doc.resolve(pos); | ||
const linkStart = pos - $pos.textOffset; | ||
const linkEnd = linkStart + $pos.parent.child($pos.index()).nodeSize; | ||
|
||
tr.removeMark(linkStart, linkEnd); | ||
|
||
dispatch(tr); | ||
view.focus(); | ||
}; | ||
|
||
const el = this.createLinkNode(linkItem, removeCB); | ||
this.bubbleEL.appendChild(el); | ||
|
||
// update dom position | ||
this.setDomPosition(view); | ||
} | ||
|
||
destroy(): void { | ||
this.bubbleEL.remove(); | ||
} | ||
} | ||
|
||
function linkPlugin(): Plugin { | ||
return new Plugin({ | ||
key: new PluginKey('link'), | ||
view(editorView: EditorView): FloatingOptionsView { | ||
return new FloatingOptionsView(editorView); | ||
}, | ||
props: { | ||
handleDOMEvents: { | ||
blur(view): boolean { | ||
view.dispatch(view.state.tr.setMeta('LINK_PLUGIN_EDITOR_BLUR', true)); | ||
return false; | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
|
||
export default linkPlugin; |
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.