mover bubblemenu pa bajo
This commit is contained in:
parent
7668c4a805
commit
baca793e0a
6 changed files with 59 additions and 165 deletions
|
@ -38,6 +38,5 @@
|
|||
main {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
body,
|
||||
#app,
|
||||
main {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy, tick } from "svelte";
|
||||
import { onDestroy, onMount, tick } from "svelte";
|
||||
import type { EditorView } from "prosemirror-view";
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import type { Node as ProsemirrorNode } from "prosemirror-model";
|
||||
import { toggleMark } from "prosemirror-commands";
|
||||
import type { Mark } from "prosemirror-model";
|
||||
|
||||
import BoldIcon from "bootstrap-icons/icons/type-bold.svg";
|
||||
import ItalicIcon from "bootstrap-icons/icons/type-italic.svg";
|
||||
|
@ -19,10 +16,8 @@
|
|||
updateMark,
|
||||
removeMark,
|
||||
markIsActive,
|
||||
commandListener,
|
||||
getFirstMarkInSelection,
|
||||
} from "./ps-utils";
|
||||
import { refreshCoords as _refreshCoords } from "./bubblemenu/coords";
|
||||
import SimpleMarkItem from "./bubblemenu/SimpleMarkItem.svelte";
|
||||
import { nanoid } from "nanoid";
|
||||
import Button from "./bubblemenu/Button.svelte";
|
||||
|
@ -30,37 +25,6 @@
|
|||
export let view: EditorView;
|
||||
export let state: EditorState;
|
||||
|
||||
// == Posicionamiento ==
|
||||
let bubbleEl: HTMLElement = null;
|
||||
let left: number = 0;
|
||||
let bottom: number = 0;
|
||||
|
||||
function refreshCoords() {
|
||||
const coords = _refreshCoords(view, bubbleEl);
|
||||
left = coords.left;
|
||||
bottom = coords.bottom;
|
||||
}
|
||||
|
||||
$: {
|
||||
// refrescar cuando cambia state
|
||||
view;
|
||||
state;
|
||||
if (bubbleEl) refreshCoords();
|
||||
}
|
||||
|
||||
let resizeObserver = new ResizeObserver(() => {
|
||||
if (bubbleEl) refreshCoords();
|
||||
});
|
||||
$: {
|
||||
view;
|
||||
resizeObserver.disconnect();
|
||||
if (bubbleEl) {
|
||||
resizeObserver.observe(bubbleEl.parentElement);
|
||||
}
|
||||
}
|
||||
onDestroy(() => resizeObserver.disconnect());
|
||||
// == /Posicionamiento ==
|
||||
|
||||
let changingProp:
|
||||
| false
|
||||
| { type: "link"; url: string }
|
||||
|
@ -86,8 +50,7 @@
|
|||
command(state, view.dispatch);
|
||||
}
|
||||
|
||||
function startEditingLink(event: Event) {
|
||||
const { to, from } = state.selection;
|
||||
function startEditingLink() {
|
||||
const match = getFirstMarkInSelection(state, view.state.schema.marks.link);
|
||||
|
||||
// si no hay un link en la selección, empezar a editar uno sin ningún enlace
|
||||
|
@ -128,14 +91,33 @@
|
|||
}
|
||||
|
||||
const svgStyle = "width: 100%; height: 100%";
|
||||
|
||||
/* https://wicg.github.io/visual-viewport/examples/fixed-to-keyboard.html */
|
||||
let barStyle = "";
|
||||
function updateBar() {
|
||||
const viewport = window.visualViewport;
|
||||
// Since the bar is position: fixed we need to offset it by the
|
||||
// visual viewport's offset from the layout viewport origin.
|
||||
const offsetY = window.innerHeight - viewport.height - viewport.offsetTop;
|
||||
|
||||
barStyle = `
|
||||
left: ${viewport.offsetLeft}px;
|
||||
bottom: ${offsetY}px;
|
||||
transform: scale(${1 / viewport.scale});
|
||||
`;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
window.visualViewport.addEventListener("resize", updateBar);
|
||||
window.visualViewport.addEventListener("scroll", updateBar);
|
||||
});
|
||||
onDestroy(() => {
|
||||
window.visualViewport.removeEventListener("resize", updateBar);
|
||||
window.visualViewport.removeEventListener("scroll", updateBar);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={bubbleEl}
|
||||
class="bubble"
|
||||
hidden={state.selection.empty}
|
||||
style="left: {left}px; bottom: {bottom}px"
|
||||
>
|
||||
<div class="bubble" hidden={state.selection.empty} style={barStyle}>
|
||||
{#if changingProp === false}
|
||||
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strong}
|
||||
><BoldIcon style={svgStyle} /></SimpleMarkItem
|
||||
|
@ -173,15 +155,16 @@
|
|||
|
||||
<style>
|
||||
.bubble {
|
||||
display: flex !important;
|
||||
position: absolute;
|
||||
z-index: 420;
|
||||
transform: translateX(-50%);
|
||||
background: black;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
padding: 0rem;
|
||||
margin-bottom: 0.5rem;
|
||||
/* https://wicg.github.io/visual-viewport/examples/fixed-to-keyboard.html */
|
||||
transform-origin: left bottom;
|
||||
|
||||
border-top: 1px solid #ccc;
|
||||
width: 100%;
|
||||
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
|
|
|
@ -67,9 +67,19 @@
|
|||
|
||||
<div class="editor">
|
||||
{#if view}
|
||||
<BubbleMenu {view} state={updatedState} />
|
||||
<MenuBar {view} state={updatedState} />
|
||||
{/if}
|
||||
<!-- this element gets replaced with the editor itself when mounted -->
|
||||
<div bind:this={wrapperEl} />
|
||||
{#if view}
|
||||
<BubbleMenu {view} state={updatedState} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.editor,
|
||||
div,
|
||||
:global(.ProseMirror) {
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,17 +22,20 @@
|
|||
border-radius: 2px;
|
||||
padding: 0.3em;
|
||||
margin: 0.2em;
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 1em;
|
||||
line-height: 1;
|
||||
|
||||
transition: background 0.2s;
|
||||
}
|
||||
button:hover {
|
||||
background: #333;
|
||||
/* https://stackoverflow.com/a/64553121 */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
button:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
button.active {
|
||||
background: #555;
|
||||
background: #ddd;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
import type { EditorView } from "prosemirror-view";
|
||||
|
||||
function textRange(
|
||||
node: Node,
|
||||
from: number = 0,
|
||||
to: number | null = null
|
||||
): Range {
|
||||
const range = document.createRange();
|
||||
range.setEnd(node, to == null ? node.nodeValue.length : to);
|
||||
range.setStart(node, Math.max(from, 0));
|
||||
return range;
|
||||
}
|
||||
|
||||
function singleRect(object: Element | Range, bias: number) {
|
||||
const rects = object.getClientRects();
|
||||
return !rects.length
|
||||
? object.getBoundingClientRect()
|
||||
: rects[bias < 0 ? 0 : rects.length - 1];
|
||||
}
|
||||
|
||||
interface Coords {
|
||||
top: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
right: number;
|
||||
}
|
||||
|
||||
function coordsAtPos(
|
||||
view: EditorView,
|
||||
pos: number,
|
||||
end: boolean = false
|
||||
): Coords {
|
||||
const { node, offset } = (view as any).docView.domFromPos(pos);
|
||||
let side: "left" | "right";
|
||||
let rect: DOMRect;
|
||||
if (node.nodeType === 3) {
|
||||
if (end && offset < node.nodeValue.length) {
|
||||
rect = singleRect(textRange(node, offset - 1, offset), -1);
|
||||
side = "right";
|
||||
} else if (offset < node.nodeValue.length) {
|
||||
rect = singleRect(textRange(node, offset, offset + 1), -1);
|
||||
side = "left";
|
||||
}
|
||||
} else if (node.firstChild) {
|
||||
if (offset < node.childNodes.length) {
|
||||
const child = node.childNodes[offset];
|
||||
rect = singleRect(child.nodeType === 3 ? textRange(child) : child, -1);
|
||||
side = "left";
|
||||
}
|
||||
if ((!rect || rect.top === rect.bottom) && offset) {
|
||||
const child = node.childNodes[offset - 1];
|
||||
rect = singleRect(child.nodeType === 3 ? textRange(child) : child, 1);
|
||||
side = "right";
|
||||
}
|
||||
} else {
|
||||
rect = node.getBoundingClientRect();
|
||||
side = "left";
|
||||
}
|
||||
const x = rect[side];
|
||||
|
||||
return {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
left: x,
|
||||
right: x,
|
||||
};
|
||||
}
|
||||
|
||||
export function refreshCoords(view: EditorView, bubbleEl: HTMLElement) {
|
||||
// Brutally stolen from https://github.com/ueberdosis/tiptap/blob/d2cf88fd166092d6df079cb47fe2a55520fadf80/packages/tiptap/src/Plugins/MenuBubble.js
|
||||
const { from, to } = view.state.selection;
|
||||
|
||||
// These are in screen coordinates
|
||||
// We can't use EditorView.coordsAtPos here because it can't handle linebreaks correctly
|
||||
// See: https://github.com/ProseMirror/prosemirror-view/pull/47
|
||||
const start = coordsAtPos(view, from);
|
||||
const end = coordsAtPos(view, to, true);
|
||||
|
||||
// The box in which the tooltip is positioned, to use as base
|
||||
const parent = bubbleEl.offsetParent;
|
||||
|
||||
if (!parent) {
|
||||
console.error(
|
||||
"Me parece que te falto importar el CSS. `import '@suttyweb/editor/dist/style.css';`"
|
||||
);
|
||||
// TODO: i18n
|
||||
throw new Error(
|
||||
"¡El editor tuvo un error! Contactar a lxs desarrolladorxs."
|
||||
);
|
||||
}
|
||||
|
||||
const box = parent.getBoundingClientRect();
|
||||
const el = bubbleEl.getBoundingClientRect();
|
||||
|
||||
const _left = Math.max((start.left + end.left) / 2 - box.left, el.width / 2);
|
||||
|
||||
return {
|
||||
left: Math.round(
|
||||
_left + el.width / 2 > box.width ? box.width - el.width / 2 : _left
|
||||
),
|
||||
bottom: Math.round(box.bottom - start.top),
|
||||
top: Math.round(end.bottom - box.top),
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue