cambiar forma de navegación mucho
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
230c6a8732
commit
dc773b14ff
17 changed files with 219 additions and 291 deletions
|
@ -48,6 +48,7 @@
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"navaid": "^1.2.0",
|
"navaid": "^1.2.0",
|
||||||
|
"prosemirror-inputrules": "^1.2.1",
|
||||||
"regexparam": "^2.0.1",
|
"regexparam": "^2.0.1",
|
||||||
"y-indexeddb": "^9.0.10",
|
"y-indexeddb": "^9.0.10",
|
||||||
"y-prosemirror": "^1.2.1",
|
"y-prosemirror": "^1.2.1",
|
||||||
|
|
|
@ -23,6 +23,9 @@ dependencies:
|
||||||
navaid:
|
navaid:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
|
prosemirror-inputrules:
|
||||||
|
specifier: ^1.2.1
|
||||||
|
version: 1.2.1
|
||||||
regexparam:
|
regexparam:
|
||||||
specifier: ^2.0.1
|
specifier: ^2.0.1
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
|
@ -1317,6 +1320,13 @@ packages:
|
||||||
rope-sequence: 1.3.3
|
rope-sequence: 1.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/prosemirror-inputrules@1.2.1:
|
||||||
|
resolution: {integrity: sha512-3LrWJX1+ULRh5SZvbIQlwZafOXqp1XuV21MGBu/i5xsztd+9VD15x6OtN6mdqSFI7/8Y77gYUbQ6vwwJ4mr6QQ==}
|
||||||
|
dependencies:
|
||||||
|
prosemirror-state: 1.4.2
|
||||||
|
prosemirror-transform: 1.7.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/prosemirror-keymap@1.2.1:
|
/prosemirror-keymap@1.2.1:
|
||||||
resolution: {integrity: sha512-kVK6WGC+83LZwuSJnuCb9PsADQnFZllt94qPP3Rx/vLcOUV65+IbBeH2nS5cFggPyEVJhGkGrgYFRrG250WhHQ==}
|
resolution: {integrity: sha512-kVK6WGC+83LZwuSJnuCb9PsADQnFZllt94qPP3Rx/vLcOUV65+IbBeH2nS5cFggPyEVJhGkGrgYFRrG250WhHQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
41
src/components/BottomFloatingBar.svelte
Normal file
41
src/components/BottomFloatingBar.svelte
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
class="transform-origin- fixed bottom-0 left-0 z-40 flex w-full flex-col"
|
||||||
|
style={barStyle}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* https://wicg.github.io/visual-viewport/examples/fixed-to-keyboard.html */
|
||||||
|
.transform-origin- {
|
||||||
|
transform-origin: left bottom;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CloseIcon from "eva-icons/fill/svg/close.svg";
|
import CloseIcon from "eva-icons/fill/svg/close.svg";
|
||||||
|
import bodyScroll from "../lib/bodyScroll";
|
||||||
|
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
|
||||||
|
@ -10,6 +11,8 @@
|
||||||
function keydown(event: KeyboardEvent) {
|
function keydown(event: KeyboardEvent) {
|
||||||
if (event.key === "Escape") onClose();
|
if (event.key === "Escape") onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: $bodyScroll;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -20,10 +23,16 @@
|
||||||
on:click={click}
|
on:click={click}
|
||||||
on:keydown={keydown}
|
on:keydown={keydown}
|
||||||
>
|
>
|
||||||
<div class="backdrop" />
|
<div class="fixed inset-0 bg-neutral-100/70 dark:bg-neutral-950/70" />
|
||||||
|
|
||||||
<div class="content-alignment" on:click={click} on:keydown={keydown}>
|
<div
|
||||||
<div class="content shadow-xl">
|
class="fixed inset-0 z-[269] flex h-screen items-center justify-center overflow-y-auto"
|
||||||
|
on:click={click}
|
||||||
|
on:keydown={keydown}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-full w-full overflow-y-auto rounded-2xl bg-neutral-100 px-5 py-4 shadow-xl dark:bg-neutral-800 sm:h-auto sm:w-auto"
|
||||||
|
>
|
||||||
<div class="mb-3 flex items-center justify-between">
|
<div class="mb-3 flex items-center justify-between">
|
||||||
<h3 class="text-2xl" id="modal-title">
|
<h3 class="text-2xl" id="modal-title">
|
||||||
<slot name="title" />
|
<slot name="title" />
|
||||||
|
@ -109,29 +118,4 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 169;
|
z-index: 169;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backdrop {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: var(--background);
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-alignment {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 269;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
overflow-y: auto;
|
|
||||||
background: var(--background);
|
|
||||||
padding: 16px 20px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount, tick } from "svelte";
|
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { EditorView } from "prosemirror-view";
|
import type { EditorView } from "prosemirror-view";
|
||||||
import type { EditorState } from "prosemirror-state";
|
import type { EditorState } from "prosemirror-state";
|
||||||
|
@ -10,6 +9,8 @@
|
||||||
import StrikethroughIcon from "bootstrap-icons/icons/type-strikethrough.svg";
|
import StrikethroughIcon from "bootstrap-icons/icons/type-strikethrough.svg";
|
||||||
import LinkIcon from "eva-icons/outline/svg/external-link-outline.svg";
|
import LinkIcon from "eva-icons/outline/svg/external-link-outline.svg";
|
||||||
import InternalLinkIcon from "eva-icons/outline/svg/menu-arrow-outline.svg";
|
import InternalLinkIcon from "eva-icons/outline/svg/menu-arrow-outline.svg";
|
||||||
|
import H2Icon from "bootstrap-icons/icons/type-h2.svg";
|
||||||
|
import H3Icon from "bootstrap-icons/icons/type-h3.svg";
|
||||||
|
|
||||||
import type { Command } from "./ps-utils";
|
import type { Command } from "./ps-utils";
|
||||||
import {
|
import {
|
||||||
|
@ -23,35 +24,16 @@
|
||||||
import Button from "./bubblemenu/Button.svelte";
|
import Button from "./bubblemenu/Button.svelte";
|
||||||
import Modal from "../components/Modal.svelte";
|
import Modal from "../components/Modal.svelte";
|
||||||
import PagePicker from "../components/PagePicker.svelte";
|
import PagePicker from "../components/PagePicker.svelte";
|
||||||
|
import BottomFloatingBar from "../components/BottomFloatingBar.svelte";
|
||||||
import type { WorldY } from "../lib/doc";
|
import type { WorldY } from "../lib/doc";
|
||||||
import Linking from "./menubar/Linking.svelte";
|
import Linking from "./bubblemenu/Linking.svelte";
|
||||||
|
import HeadingButton from "./bubblemenu/HeadingButton.svelte";
|
||||||
|
|
||||||
export let view: EditorView;
|
export let view: EditorView;
|
||||||
export let state: EditorState;
|
export let state: EditorState;
|
||||||
export let worldY: WorldY;
|
export let worldY: WorldY;
|
||||||
export let editingLink: Writable<false | "new" | "selection">;
|
export let editingLink: Writable<false | "new" | "selection">;
|
||||||
|
|
||||||
let changingProp:
|
|
||||||
| false
|
|
||||||
| { type: "link"; url: string }
|
|
||||||
| { type: "mark" }
|
|
||||||
| { type: "mark-custom"; color: string } = false;
|
|
||||||
|
|
||||||
let linkInputEl: HTMLElement;
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (state.selection.empty) {
|
|
||||||
changingProp = false;
|
|
||||||
}
|
|
||||||
if (changingProp && changingProp.type === "link") {
|
|
||||||
tick().then(() => {
|
|
||||||
linkInputEl.focus();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
view.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCommand(command: Command) {
|
function runCommand(command: Command) {
|
||||||
command(state, view.dispatch);
|
command(state, view.dispatch);
|
||||||
}
|
}
|
||||||
|
@ -86,30 +68,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
|
|
||||||
{#if makingInternalLink}
|
{#if makingInternalLink}
|
||||||
|
@ -119,55 +77,47 @@ transform: scale(${1 / viewport.scale});
|
||||||
</Modal>
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="floating z-40" style={barStyle}>
|
<BottomFloatingBar>
|
||||||
<Linking {state} />
|
<Linking {state} />
|
||||||
<div class="bubble" hidden={state.selection.empty}>
|
<div class="bubble flex items-center" hidden={state.selection.empty}>
|
||||||
{#if changingProp === false}
|
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strong}>
|
||||||
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strong}
|
<BoldIcon style={svgStyle} />
|
||||||
><BoldIcon style={svgStyle} /></SimpleMarkItem
|
</SimpleMarkItem>
|
||||||
>
|
<SimpleMarkItem {view} {state} type={view.state.schema.marks.em}>
|
||||||
<SimpleMarkItem {view} {state} type={view.state.schema.marks.em}
|
<ItalicIcon style={svgStyle} />
|
||||||
><ItalicIcon style={svgStyle} /></SimpleMarkItem
|
</SimpleMarkItem>
|
||||||
>
|
<SimpleMarkItem {view} {state} type={view.state.schema.marks.underline}>
|
||||||
<SimpleMarkItem {view} {state} type={view.state.schema.marks.underline}
|
<UnderlineIcon style={svgStyle} />
|
||||||
><UnderlineIcon style={svgStyle} /></SimpleMarkItem
|
</SimpleMarkItem>
|
||||||
>
|
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strikethrough}>
|
||||||
<SimpleMarkItem
|
<StrikethroughIcon style={svgStyle} />
|
||||||
{view}
|
</SimpleMarkItem>
|
||||||
{state}
|
<Button
|
||||||
type={view.state.schema.marks.strikethrough}
|
active={markIsActive(state, view.state.schema.marks.link)}
|
||||||
><StrikethroughIcon style={svgStyle} /></SimpleMarkItem
|
onClick={startEditingLink}
|
||||||
>
|
>
|
||||||
<Button
|
<LinkIcon style={svgStyle} />
|
||||||
active={markIsActive(state, view.state.schema.marks.link)}
|
</Button>
|
||||||
onClick={startEditingLink}><LinkIcon style={svgStyle} /></Button
|
<Button
|
||||||
>
|
active={markIsActive(state, view.state.schema.marks.internal_link)}
|
||||||
<Button
|
onClick={startMakingInternalLink}
|
||||||
active={markIsActive(state, view.state.schema.marks.internal_link)}
|
>
|
||||||
onClick={startMakingInternalLink}
|
<InternalLinkIcon style={svgStyle} />
|
||||||
><InternalLinkIcon style={svgStyle} /></Button
|
</Button>
|
||||||
>
|
|
||||||
{/if}
|
<span class="mx-2 block h-6 border-r border-neutral-400" />
|
||||||
|
|
||||||
|
<HeadingButton {view} {state} level={2}>
|
||||||
|
<H2Icon style={svgStyle} />
|
||||||
|
</HeadingButton>
|
||||||
|
<HeadingButton {view} {state} level={3}>
|
||||||
|
<H3Icon style={svgStyle} />
|
||||||
|
</HeadingButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</BottomFloatingBar>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.floating {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: fixed;
|
|
||||||
left: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
padding: 0rem;
|
|
||||||
/* https://wicg.github.io/visual-viewport/examples/fixed-to-keyboard.html */
|
|
||||||
transform-origin: left bottom;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble {
|
.bubble {
|
||||||
display: flex;
|
|
||||||
|
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border-top: 1px solid var(--accent-bg);
|
border-top: 1px solid var(--accent-bg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { dropCursor } from "prosemirror-dropcursor";
|
import { dropCursor } from "prosemirror-dropcursor";
|
||||||
import { gapCursor } from "prosemirror-gapcursor";
|
import { gapCursor } from "prosemirror-gapcursor";
|
||||||
import { history } from "prosemirror-history";
|
import { history } from "prosemirror-history";
|
||||||
|
import { inputRules, wrappingInputRule } from "prosemirror-inputrules";
|
||||||
import type { XmlFragment } from "yjs";
|
import type { XmlFragment } from "yjs";
|
||||||
import { ySyncPlugin } from "y-prosemirror";
|
import { ySyncPlugin } from "y-prosemirror";
|
||||||
|
|
||||||
|
@ -12,7 +13,6 @@
|
||||||
|
|
||||||
import { schema } from "./schema";
|
import { schema } from "./schema";
|
||||||
import BubbleMenu from "./BubbleMenu.svelte";
|
import BubbleMenu from "./BubbleMenu.svelte";
|
||||||
import MenuBar from "./MenuBar.svelte";
|
|
||||||
// import { placeholderPlugin } from "./upload";
|
// import { placeholderPlugin } from "./upload";
|
||||||
import { baseKeymap } from "./keymap";
|
import { baseKeymap } from "./keymap";
|
||||||
import type { WorldY } from "../lib/doc";
|
import type { WorldY } from "../lib/doc";
|
||||||
|
@ -56,6 +56,14 @@
|
||||||
// yCursorPlugin(doc.webrtcProvider.awareness),
|
// yCursorPlugin(doc.webrtcProvider.awareness),
|
||||||
// yUndoPlugin(),
|
// yUndoPlugin(),
|
||||||
keymap(baseKeymap),
|
keymap(baseKeymap),
|
||||||
|
inputRules({
|
||||||
|
rules: [
|
||||||
|
// https://github.com/ueberdosis/tiptap/blob/6f218be6e439603c85559c6d77ec93a205003bf5/packages/extension-bullet-list/src/bullet-list.ts#L24C27-L24C43
|
||||||
|
wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
|
||||||
|
wrappingInputRule(/^\s*([0-9]\.)\s$/, schema.nodes.ordered_list),
|
||||||
|
wrappingInputRule(/^>\s$/, schema.nodes.blockquote),
|
||||||
|
],
|
||||||
|
}),
|
||||||
// placeholderPlugin,
|
// placeholderPlugin,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -73,7 +81,6 @@
|
||||||
|
|
||||||
<div class="editor min-h-screen">
|
<div class="editor min-h-screen">
|
||||||
{#if view}
|
{#if view}
|
||||||
<MenuBar {view} state={updatedState} />
|
|
||||||
<EditLinkMenu state={updatedState} {view} {editingLink} />
|
<EditLinkMenu state={updatedState} {view} {editingLink} />
|
||||||
<LinkTooltip state={updatedState} {view} {editingLink} />
|
<LinkTooltip state={updatedState} {view} {editingLink} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { EditorState } from "prosemirror-state";
|
|
||||||
import type { EditorView } from "prosemirror-view";
|
|
||||||
|
|
||||||
import BlockSelect from "./menubar/BlockSelect.svelte";
|
|
||||||
// import UploadItem from "./menubar/UploadItem.svelte";
|
|
||||||
import ListItem from "./menubar/ListItem.svelte";
|
|
||||||
import BlockQuoteItem from "./menubar/BlockQuoteItem.svelte";
|
|
||||||
import { ListKind } from "./utils";
|
|
||||||
|
|
||||||
export let view: EditorView;
|
|
||||||
export let state: EditorState;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="sticky top-0 z-50 flex items-center border-b border-neutral-200/60 bg-white px-4 py-2 dark:border-neutral-700 dark:bg-neutral-800"
|
|
||||||
>
|
|
||||||
<BlockSelect {view} {state} />
|
|
||||||
<!-- <UploadItem {view} {state} /> -->
|
|
||||||
<ListItem {view} {state} kind={ListKind.Unordered} />
|
|
||||||
<ListItem {view} {state} kind={ListKind.Ordered} />
|
|
||||||
<BlockQuoteItem {view} {state} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="postcss">
|
|
||||||
div :global(button) {
|
|
||||||
@apply m-1 appearance-none rounded p-2 leading-none transition-colors hover:bg-neutral-600/20 dark:hover:bg-neutral-600/40;
|
|
||||||
}
|
|
||||||
div :global(button.active) {
|
|
||||||
@apply bg-neutral-300 dark:bg-neutral-700;
|
|
||||||
}
|
|
||||||
div :global(button svg) {
|
|
||||||
@apply h-6 w-6;
|
|
||||||
}
|
|
||||||
</style>
|
|
29
src/editor/bubblemenu/HeadingButton.svelte
Normal file
29
src/editor/bubblemenu/HeadingButton.svelte
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { EditorState } from "prosemirror-state";
|
||||||
|
import type { EditorView } from "prosemirror-view";
|
||||||
|
|
||||||
|
import { commandListener } from "../ps-utils";
|
||||||
|
import Button from "./Button.svelte";
|
||||||
|
import { chainCommands, setBlockType } from "prosemirror-commands";
|
||||||
|
|
||||||
|
export let view: EditorView;
|
||||||
|
export let state: EditorState;
|
||||||
|
export let level: number;
|
||||||
|
|
||||||
|
$: type = state.schema.nodes.heading;
|
||||||
|
$: paragraphType = state.schema.nodes.paragraph;
|
||||||
|
|
||||||
|
$: isActive =
|
||||||
|
state.selection.to <= state.selection.$from.end() &&
|
||||||
|
state.selection.$from.parent.type === type &&
|
||||||
|
state.selection.$from.parent.attrs.level === level;
|
||||||
|
|
||||||
|
$: listener = commandListener(
|
||||||
|
view,
|
||||||
|
chainCommands(setBlockType(type, { level }), setBlockType(paragraphType)),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button active={isActive} onClick={listener}>
|
||||||
|
<slot />
|
||||||
|
</Button>
|
|
@ -15,7 +15,7 @@ export function floatingUi(
|
||||||
options?: Partial<ComputePositionConfig>,
|
options?: Partial<ComputePositionConfig>,
|
||||||
): Readable<Style> {
|
): Readable<Style> {
|
||||||
return {
|
return {
|
||||||
subscribe(run, invalidate) {
|
subscribe(run) {
|
||||||
return autoUpdate(refEl, tooltipEl, () => {
|
return autoUpdate(refEl, tooltipEl, () => {
|
||||||
computePosition(refEl, tooltipEl, options).then(({ x, y }) => {
|
computePosition(refEl, tooltipEl, options).then(({ x, y }) => {
|
||||||
run(`left: ${x}px; top: ${y}px`);
|
run(`left: ${x}px; top: ${y}px`);
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { wrapIn } from "prosemirror-commands";
|
|
||||||
|
|
||||||
import type { EditorState } from "prosemirror-state";
|
|
||||||
import type { EditorView } from "prosemirror-view";
|
|
||||||
|
|
||||||
import BlockquoteIcon from "bootstrap-icons/icons/blockquote-left.svg";
|
|
||||||
|
|
||||||
import { commandListener, nodeIsActiveFn } from "../ps-utils";
|
|
||||||
|
|
||||||
export let view: EditorView;
|
|
||||||
export let state: EditorState;
|
|
||||||
|
|
||||||
const type = state.schema.nodes.blockquote;
|
|
||||||
|
|
||||||
$: isActive = nodeIsActiveFn(type, null, true);
|
|
||||||
$: command = wrapIn(type);
|
|
||||||
$: isPossible = command(state);
|
|
||||||
$: actionListener = commandListener(view, command);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class:active={isActive(state)}
|
|
||||||
on:mousedown={actionListener}
|
|
||||||
disabled={!isPossible}><BlockquoteIcon /></button
|
|
||||||
>
|
|
|
@ -1,49 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { EditorState } from "prosemirror-state";
|
|
||||||
import type { EditorView } from "prosemirror-view";
|
|
||||||
import { setBlockType } from "prosemirror-commands";
|
|
||||||
|
|
||||||
export let view: EditorView;
|
|
||||||
export let state: EditorState;
|
|
||||||
|
|
||||||
const paragraphType = state.schema.nodes.paragraph;
|
|
||||||
const headingType = state.schema.nodes.heading;
|
|
||||||
|
|
||||||
$: currentValue =
|
|
||||||
state.selection.to <= state.selection.$from.end() &&
|
|
||||||
(state.selection.$from.parent.type == headingType
|
|
||||||
? `heading:${state.selection.$from.parent.attrs.level}`
|
|
||||||
: state.selection.$from.parent.type == paragraphType
|
|
||||||
? "paragraph"
|
|
||||||
: null);
|
|
||||||
|
|
||||||
const onChange = (
|
|
||||||
event: Event & { currentTarget: EventTarget & HTMLSelectElement }
|
|
||||||
) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const [type, param] = event.currentTarget.value.split(":");
|
|
||||||
if (type === "paragraph") {
|
|
||||||
setBlockType(paragraphType, {
|
|
||||||
align: state.selection.$from.parent.attrs.align,
|
|
||||||
})(state, view.dispatch);
|
|
||||||
} else if (type === "heading") {
|
|
||||||
setBlockType(headingType, {
|
|
||||||
level: parseInt(param),
|
|
||||||
align: state.selection.$from.parent.attrs.align,
|
|
||||||
})(state, view.dispatch);
|
|
||||||
} else {
|
|
||||||
console.error(`¡type no es heading ni paragraph! Es`, type);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<select value={currentValue} on:change={onChange} class="relative flex items-center justify-between py-3 pl-3 pr-10 text-left bg-white dark:bg-neutral-800 border rounded-md shadow-sm cursor-default border-neutral-200/70 dark:border-neutral-700/70 text-sm">
|
|
||||||
<option value="paragraph">Párrafo</option>
|
|
||||||
<option value="heading:1">Titulo grande</option>
|
|
||||||
<option value="heading:2">Titulo mediano</option>
|
|
||||||
<option value="heading:3">Subtitulo</option>
|
|
||||||
<option value="heading:4">Subsubtitulo</option>
|
|
||||||
<option value="heading:5">Subsubsubtitulo</option>
|
|
||||||
<option value="heading:6">Subsubsubsubtitulo</option>
|
|
||||||
</select>
|
|
|
@ -1,44 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { chainCommands } from "prosemirror-commands";
|
|
||||||
|
|
||||||
import { wrapInList } from "prosemirror-schema-list";
|
|
||||||
import { liftListItem } from "prosemirror-schema-list";
|
|
||||||
import type { EditorState } from "prosemirror-state";
|
|
||||||
import type { EditorView } from "prosemirror-view";
|
|
||||||
|
|
||||||
import UlIcon from "bootstrap-icons/icons/list-ul.svg";
|
|
||||||
import OlIcon from "bootstrap-icons/icons/list-ol.svg";
|
|
||||||
|
|
||||||
import { ListKind } from "../utils";
|
|
||||||
import { commandListener, nodeIsActiveFn } from "../ps-utils";
|
|
||||||
|
|
||||||
export let view: EditorView;
|
|
||||||
export let state: EditorState;
|
|
||||||
export let kind: ListKind;
|
|
||||||
|
|
||||||
$: type =
|
|
||||||
kind === ListKind.Unordered
|
|
||||||
? state.schema.nodes.bullet_list
|
|
||||||
: kind === ListKind.Ordered
|
|
||||||
? state.schema.nodes.ordered_list
|
|
||||||
: (null as never);
|
|
||||||
const listItemType = state.schema.nodes.list_item;
|
|
||||||
$: iconComponent =
|
|
||||||
kind === ListKind.Unordered
|
|
||||||
? UlIcon
|
|
||||||
: kind === ListKind.Ordered
|
|
||||||
? OlIcon
|
|
||||||
: (console.error("adsfadsf"), UlIcon);
|
|
||||||
|
|
||||||
$: isActive = nodeIsActiveFn(type, null, true);
|
|
||||||
$: command = chainCommands(liftListItem(listItemType), wrapInList(type));
|
|
||||||
$: isPossible = command(state);
|
|
||||||
$: actionListener = commandListener(view, command);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class:active={isActive(state)}
|
|
||||||
on:mousedown={actionListener}
|
|
||||||
disabled={!isPossible}><svelte:component this={iconComponent} /></button
|
|
||||||
>
|
|
24
src/lib/bodyScroll.ts
Normal file
24
src/lib/bodyScroll.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import type { Readable } from "svelte/store";
|
||||||
|
|
||||||
|
// weird way to stop scroll in the body
|
||||||
|
// when the store is mounted, scroll stops in the body
|
||||||
|
class BodyScroll implements Readable<boolean> {
|
||||||
|
num: number = 0;
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
if (this.num > 0) document.body.style.overflow = "hidden";
|
||||||
|
else document.body.style.overflow = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe() {
|
||||||
|
this.num++;
|
||||||
|
this.refresh();
|
||||||
|
return () => {
|
||||||
|
this.num--;
|
||||||
|
this.refresh();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bodyScroll = new BodyScroll();
|
||||||
|
export default bodyScroll;
|
|
@ -1,11 +1,6 @@
|
||||||
import {
|
import { derived, type Readable } from "svelte/store";
|
||||||
derived,
|
|
||||||
type Readable,
|
|
||||||
type Subscriber,
|
|
||||||
type Unsubscriber,
|
|
||||||
} from "svelte/store";
|
|
||||||
import type { Doc, Transaction, XmlFragment } from "yjs";
|
import type { Doc, Transaction, XmlFragment } from "yjs";
|
||||||
import { loadWorlds, worldsStore } from "./worldStorage";
|
import { worldsStore } from "./worldStorage";
|
||||||
import { getWorldPage, getWorldY } from "./doc";
|
import { getWorldPage, getWorldY } from "./doc";
|
||||||
|
|
||||||
export function makeYdocStore<T>(
|
export function makeYdocStore<T>(
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { XmlFragment } from "yjs";
|
import type { XmlFragment } from "yjs";
|
||||||
import { inject } from "regexparam";
|
import { inject } from "regexparam";
|
||||||
|
import ArrowBackIcon from "eva-icons/fill/svg/arrow-back.svg";
|
||||||
import Editor from "../editor/Editor.svelte";
|
import Editor from "../editor/Editor.svelte";
|
||||||
|
import Breadcrumbs from "./Page/Breadcrumbs.svelte";
|
||||||
import { getWorldPage, getWorldY, type WorldY } from "../lib/doc";
|
import { getWorldPage, getWorldY, type WorldY } from "../lib/doc";
|
||||||
import { routes } from "../lib/routes";
|
import { routes } from "../lib/routes";
|
||||||
import { loadWorlds } from "../lib/worldStorage";
|
import { loadWorlds } from "../lib/worldStorage";
|
||||||
import Breadcrumbs from "./Page/Breadcrumbs.svelte";
|
|
||||||
|
|
||||||
export let worldId: string;
|
export let worldId: string;
|
||||||
export let pageId: string;
|
export let pageId: string;
|
||||||
|
@ -33,9 +34,13 @@
|
||||||
})
|
})
|
||||||
.catch((error) => (state = { error }));
|
.catch((error) => (state = { error }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pop() {
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="px-4 py-2">
|
<div class="px-4">
|
||||||
<details>
|
<details>
|
||||||
<summary>Opciones</summary>
|
<summary>Opciones</summary>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -47,7 +52,13 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
</div>
|
||||||
|
<nav
|
||||||
|
class="sticky top-0 z-10 flex h-10 items-stretch gap-4 bg-white text-neutral-700 shadow dark:bg-neutral-800 dark:text-neutral-200"
|
||||||
|
>
|
||||||
|
<button title="Ir a la página anterior" on:click={pop}>
|
||||||
|
<ArrowBackIcon class="w-10 shrink-0 fill-current pl-2" />
|
||||||
|
</button>
|
||||||
<Breadcrumbs {pageId} {worldId} />
|
<Breadcrumbs {pageId} {worldId} />
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import ChevronRight from "eva-icons/fill/svg/chevron-right.svg";
|
import ChevronRight from "eva-icons/fill/svg/chevron-right.svg";
|
||||||
|
import ArrowDown from "eva-icons/fill/svg/arrow-down.svg";
|
||||||
import { inject } from "regexparam";
|
import { inject } from "regexparam";
|
||||||
import breadcrumbs from "../../lib/breadcrumbs";
|
import breadcrumbs from "../../lib/breadcrumbs";
|
||||||
import { pageStore } from "../../lib/makeYdocStore";
|
import { pageStore } from "../../lib/makeYdocStore";
|
||||||
import { derived } from "svelte/store";
|
import { derived } from "svelte/store";
|
||||||
import { getTitle } from "../../lib/getTitle";
|
import { getTitle } from "../../lib/getTitle";
|
||||||
import { routes } from "../../lib/routes";
|
import { routes } from "../../lib/routes";
|
||||||
import type { HTMLOlAttributes } from "svelte/elements";
|
import Modal from "../../components/Modal.svelte";
|
||||||
|
|
||||||
export let worldId: string;
|
export let worldId: string;
|
||||||
export let pageId: string;
|
export let pageId: string;
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
).subscribe(set);
|
).subscribe(set);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
$: currentTitle = $crumbsTitles[$crumbsTitles.length - 1];
|
||||||
|
|
||||||
let breadcrumbsEl: HTMLDivElement;
|
let breadcrumbsEl: HTMLDivElement;
|
||||||
let breadcrumbsListEl: HTMLOListElement;
|
let breadcrumbsListEl: HTMLOListElement;
|
||||||
|
@ -49,22 +51,51 @@
|
||||||
resizeObserver.observe(breadcrumbsEl);
|
resizeObserver.observe(breadcrumbsEl);
|
||||||
return () => resizeObserver.disconnect();
|
return () => resizeObserver.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let breadcrumbsModalOpen = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="flex items-center overflow-hidden text-lg sm:hidden"
|
||||||
|
on:click={() => (breadcrumbsModalOpen = true)}
|
||||||
|
>
|
||||||
|
<span class="overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
|
>{currentTitle}</span
|
||||||
|
>
|
||||||
|
<ArrowDown class="h-6 w-6 flex-shrink-0 fill-current" /></button
|
||||||
|
>
|
||||||
|
|
||||||
|
{#if breadcrumbsModalOpen}
|
||||||
|
<Modal onClose={() => (breadcrumbsModalOpen = false)}>
|
||||||
|
<ol class="h-full w-full">
|
||||||
|
{#each $pageBreadcrumbs as crumb, index}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={inject(routes.Page, { worldId, pageId: crumb })}
|
||||||
|
class="flex items-center text-ellipsis whitespace-nowrap p-4"
|
||||||
|
class:active-breadcrumb={crumb === pageId}
|
||||||
|
>{$crumbsTitles[index] || crumb}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- https://devdojo.com/pines/docs/breadcrumbs -->
|
<!-- https://devdojo.com/pines/docs/breadcrumbs -->
|
||||||
<div
|
<div
|
||||||
class="flex justify-between overflow-x-auto rounded-md border border-neutral-200/60 px-3.5 py-1 dark:border-neutral-700"
|
class="hidden justify-between overflow-x-hidden leading-none sm:flex"
|
||||||
bind:this={breadcrumbsEl}
|
bind:this={breadcrumbsEl}
|
||||||
>
|
>
|
||||||
<ol
|
<ol
|
||||||
class="inline-flex items-center space-x-1 text-xs text-neutral-500 dark:text-neutral-300 sm:mb-0 [&_.active-breadcrumb]:font-medium [&_.active-breadcrumb]:text-neutral-600 dark:[&_.active-breadcrumb]:text-neutral-200"
|
class="mb-0 inline-flex items-center space-x-1 text-ellipsis text-sm text-neutral-500 dark:text-neutral-300 [&_.active-breadcrumb]:font-medium [&_.active-breadcrumb]:text-neutral-600 dark:[&_.active-breadcrumb]:text-neutral-200"
|
||||||
bind:this={breadcrumbsListEl}
|
bind:this={breadcrumbsListEl}
|
||||||
>
|
>
|
||||||
{#each $pageBreadcrumbs as crumb, index}
|
{#each $pageBreadcrumbs as crumb, index}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href={inject(routes.Page, { worldId, pageId: crumb })}
|
href={inject(routes.Page, { worldId, pageId: crumb })}
|
||||||
class="font-norma inline-flex items-center text-ellipsis whitespace-nowrap py-1 focus:outline-none"
|
class="inline-flex items-center text-ellipsis whitespace-nowrap py-1 font-normal focus:outline-none"
|
||||||
class:active-breadcrumb={crumb === pageId}
|
class:active-breadcrumb={crumb === pageId}
|
||||||
>{$crumbsTitles[index] || crumb}</a
|
>{$crumbsTitles[index] || crumb}</a
|
||||||
>
|
>
|
||||||
|
|
Loading…
Reference in a new issue