Compare commits
No commits in common. "6368c6b54f2a59a4a4e3b0cab5f04aea8e31b46c" and "cc361f107b38ff42baf518a024048a9b81096ba7" have entirely different histories.
6368c6b54f
...
cc361f107b
8 changed files with 272 additions and 127 deletions
|
@ -1,14 +1,43 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import Editor from "./editor/Editor.svelte";
|
||||||
|
|
||||||
|
import { getWorldPage, getWorldY } from "./lib/doc";
|
||||||
import { currentRoute } from "./lib/routes";
|
import { currentRoute } from "./lib/routes";
|
||||||
|
|
||||||
|
let worldDescriptor: string = "";
|
||||||
|
let fileId: string = nanoid();
|
||||||
|
$: world = worldDescriptor.includes(":")
|
||||||
|
? {
|
||||||
|
room: worldDescriptor.split(":")[0],
|
||||||
|
password: worldDescriptor.split(":")[1],
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
$: worldY = world && getWorldY(world);
|
||||||
|
$: doc = worldY && getWorldPage(worldY.ydoc, fileId);
|
||||||
|
|
||||||
|
function generateWorld() {
|
||||||
|
worldDescriptor = `${nanoid()}:${nanoid()}`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<svelte:component this={$currentRoute.component} {...$currentRoute.params} />
|
<svelte:component this={$currentRoute.component} {...$currentRoute.params} />
|
||||||
|
<!-- <button on:click={generateWorld}>generar mundo</button>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={worldDescriptor}
|
||||||
|
placeholder="mundo descriptor"
|
||||||
|
/>
|
||||||
|
<input type="text" bind:value={fileId} placeholder="world" />
|
||||||
|
|
||||||
|
{#if doc}<Editor {doc} />{/if} -->
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
main {
|
main {
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body {
|
||||||
#app,
|
|
||||||
main {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount, tick } from "svelte";
|
import { onMount, onDestroy, tick } from "svelte";
|
||||||
import type { EditorView } from "prosemirror-view";
|
import type { EditorView } from "prosemirror-view";
|
||||||
import { EditorState, TextSelection } from "prosemirror-state";
|
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 BoldIcon from "bootstrap-icons/icons/type-bold.svg";
|
||||||
import ItalicIcon from "bootstrap-icons/icons/type-italic.svg";
|
import ItalicIcon from "bootstrap-icons/icons/type-italic.svg";
|
||||||
|
@ -16,15 +19,47 @@
|
||||||
updateMark,
|
updateMark,
|
||||||
removeMark,
|
removeMark,
|
||||||
markIsActive,
|
markIsActive,
|
||||||
|
commandListener,
|
||||||
getFirstMarkInSelection,
|
getFirstMarkInSelection,
|
||||||
} from "./ps-utils";
|
} from "./ps-utils";
|
||||||
|
import { refreshCoords as _refreshCoords } from "./bubblemenu/coords";
|
||||||
import SimpleMarkItem from "./bubblemenu/SimpleMarkItem.svelte";
|
import SimpleMarkItem from "./bubblemenu/SimpleMarkItem.svelte";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import Button from "./bubblemenu/Button.svelte";
|
|
||||||
|
|
||||||
export let view: EditorView;
|
export let view: EditorView;
|
||||||
export let state: EditorState;
|
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:
|
let changingProp:
|
||||||
| false
|
| false
|
||||||
| { type: "link"; url: string }
|
| { type: "link"; url: string }
|
||||||
|
@ -50,7 +85,8 @@
|
||||||
command(state, view.dispatch);
|
command(state, view.dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startEditingLink() {
|
function startEditingLink(event: Event) {
|
||||||
|
const { to, from } = state.selection;
|
||||||
const match = getFirstMarkInSelection(state, view.state.schema.marks.link);
|
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
|
// si no hay un link en la selección, empezar a editar uno sin ningún enlace
|
||||||
|
@ -91,33 +127,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const svgStyle = "width: 100%; height: 100%";
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="bubble" hidden={state.selection.empty} style={barStyle}>
|
<div
|
||||||
|
bind:this={bubbleEl}
|
||||||
|
class="bubble"
|
||||||
|
hidden={state.selection.empty}
|
||||||
|
style="left: {left}px; bottom: {bottom}px"
|
||||||
|
>
|
||||||
{#if changingProp === false}
|
{#if changingProp === false}
|
||||||
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strong}
|
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strong}
|
||||||
><BoldIcon style={svgStyle} /></SimpleMarkItem
|
><BoldIcon style={svgStyle} /></SimpleMarkItem
|
||||||
|
@ -131,13 +148,17 @@ transform: scale(${1 / viewport.scale});
|
||||||
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strikethrough}
|
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strikethrough}
|
||||||
><StrikethroughIcon style={svgStyle} /></SimpleMarkItem
|
><StrikethroughIcon style={svgStyle} /></SimpleMarkItem
|
||||||
>
|
>
|
||||||
<Button
|
<button
|
||||||
active={markIsActive(state, view.state.schema.marks.link)}
|
type="button"
|
||||||
onClick={startEditingLink}><LinkIcon style={svgStyle} /></Button
|
class:active={markIsActive(state, view.state.schema.marks.link)}
|
||||||
|
on:mousedown|preventDefault={startEditingLink}
|
||||||
|
><LinkIcon style={svgStyle} /></button
|
||||||
>
|
>
|
||||||
<Button
|
<button
|
||||||
active={markIsActive(state, view.state.schema.marks.internal_link)}
|
type="button"
|
||||||
onClick={createInternalLink}><InternalLinkIcon style={svgStyle} /></Button
|
class:active={markIsActive(state, view.state.schema.marks.internal_link)}
|
||||||
|
on:mousedown|preventDefault={createInternalLink}
|
||||||
|
><InternalLinkIcon style={svgStyle} /></button
|
||||||
>
|
>
|
||||||
{:else if changingProp.type === "link"}
|
{:else if changingProp.type === "link"}
|
||||||
<input
|
<input
|
||||||
|
@ -147,40 +168,11 @@ transform: scale(${1 / viewport.scale});
|
||||||
on:change|preventDefault={onChangeLink}
|
on:change|preventDefault={onChangeLink}
|
||||||
value={changingProp.url}
|
value={changingProp.url}
|
||||||
/>
|
/>
|
||||||
<Button title="Borrar enlace" onClick={removeLink}
|
<button
|
||||||
><CloseIcon style={svgStyle} /></Button
|
type="button"
|
||||||
|
title="Borrar enlace"
|
||||||
|
on:mousedown|preventDefault={removeLink}
|
||||||
|
><CloseIcon style={svgStyle} /></button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.bubble {
|
|
||||||
display: flex;
|
|
||||||
position: fixed;
|
|
||||||
left: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
padding: 0rem;
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
transition: opacity 0.2s, visibility 0.2s;
|
|
||||||
}
|
|
||||||
.bubble[hidden] {
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble input {
|
|
||||||
appearance: none;
|
|
||||||
background: none;
|
|
||||||
color: inherit;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.25em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -67,19 +67,9 @@
|
||||||
|
|
||||||
<div class="editor">
|
<div class="editor">
|
||||||
{#if view}
|
{#if view}
|
||||||
|
<BubbleMenu {view} state={updatedState} />
|
||||||
<MenuBar {view} state={updatedState} />
|
<MenuBar {view} state={updatedState} />
|
||||||
{/if}
|
{/if}
|
||||||
<!-- this element gets replaced with the editor itself when mounted -->
|
<!-- this element gets replaced with the editor itself when mounted -->
|
||||||
<div bind:this={wrapperEl} />
|
<div bind:this={wrapperEl} />
|
||||||
{#if view}
|
|
||||||
<BubbleMenu {view} state={updatedState} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.editor,
|
|
||||||
div,
|
|
||||||
:global(.ProseMirror) {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let active: boolean = false;
|
|
||||||
export let onClick: (event: Event) => void;
|
|
||||||
export let title: string = "";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
{title}
|
|
||||||
class:active
|
|
||||||
on:mousedown|preventDefault={onClick}
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
button {
|
|
||||||
appearance: none;
|
|
||||||
background: none;
|
|
||||||
color: inherit;
|
|
||||||
border: none;
|
|
||||||
border-radius: 2px;
|
|
||||||
padding: 0.3em;
|
|
||||||
margin: 0.2em;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
font-size: 1em;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
/* https://stackoverflow.com/a/64553121 */
|
|
||||||
@media (hover: hover) and (pointer: fine) {
|
|
||||||
button:hover {
|
|
||||||
background: #eee;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button.active {
|
|
||||||
background: #ddd;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -5,7 +5,6 @@
|
||||||
import { toggleMark } from "prosemirror-commands";
|
import { toggleMark } from "prosemirror-commands";
|
||||||
|
|
||||||
import { commandListener, markIsActive } from "../ps-utils";
|
import { commandListener, markIsActive } from "../ps-utils";
|
||||||
import Button from "./Button.svelte";
|
|
||||||
|
|
||||||
export let view: EditorView;
|
export let view: EditorView;
|
||||||
export let state: EditorState;
|
export let state: EditorState;
|
||||||
|
@ -16,10 +15,10 @@
|
||||||
$: listener = commandListener(view, toggleMark(type));
|
$: listener = commandListener(view, toggleMark(type));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button active={isActive} onClick={listener}>
|
<button type="button" class:active={isActive} on:mousedown={listener}>
|
||||||
{#if small}
|
{#if small}
|
||||||
<small><slot /></small>
|
<small><slot /></small>
|
||||||
{:else}
|
{:else}
|
||||||
<slot />
|
<slot />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</button>
|
||||||
|
|
104
src/editor/bubblemenu/coords.ts
Normal file
104
src/editor/bubblemenu/coords.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
|
@ -23,6 +23,11 @@
|
||||||
border-bottom: 1px solid #bbb;
|
border-bottom: 1px solid #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor .menubar .separator {
|
||||||
|
border-right: 2px solid #bbb;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.editor .menubar button {
|
.editor .menubar button {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -47,6 +52,76 @@
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor .bubble {
|
||||||
|
display: flex !important;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 420;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
transition: opacity 0.2s, visibility 0.2s;
|
||||||
|
}
|
||||||
|
.editor .bubble[hidden] {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor .bubble input {
|
||||||
|
appearance: none;
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor .bubble .separator {
|
||||||
|
border-right: 1px solid #777;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor .bubble button {
|
||||||
|
appearance: none;
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0.3em;
|
||||||
|
margin: 0.2em;
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.editor .bubble button:hover {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
.editor .bubble button.active {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor .bubble .color-button {
|
||||||
|
padding: 0.4em;
|
||||||
|
}
|
||||||
|
.editor .bubble .color {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor .bubble p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
Loading…
Reference in a new issue