Compare commits
No commits in common. "1943c829f14ab880aa703e3b7e4d08612e4415a8" and "e66c6f49db804a35ab665c927c6c58f7a1118854" have entirely different histories.
1943c829f1
...
e66c6f49db
14 changed files with 58 additions and 356 deletions
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { currentRoute } from "./lib/router";
|
import { currentRoute } from "./lib/routes";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let onClose: () => void;
|
|
||||||
|
|
||||||
function click(event: Event) {
|
|
||||||
if (event.target !== this) return;
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
function keydown(event: KeyboardEvent) {
|
|
||||||
if (event.key === "Escape") onClose();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="modal"
|
|
||||||
aria-labelledby="modal-title"
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
on:click={click}
|
|
||||||
on:keydown={keydown}
|
|
||||||
>
|
|
||||||
<div class="backdrop" />
|
|
||||||
|
|
||||||
<div class="content-alignment" on:click={click} on:keydown={keydown}>
|
|
||||||
<div class="content">
|
|
||||||
<h3 id="modal-title">
|
|
||||||
<slot name="title" />
|
|
||||||
</h3>
|
|
||||||
<slot />
|
|
||||||
<!--<div
|
|
||||||
class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"
|
|
||||||
>
|
|
||||||
<!--
|
|
||||||
Modal panel, show/hide based on modal state.
|
|
||||||
|
|
||||||
Entering: "ease-out duration-300"
|
|
||||||
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
To: "opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
Leaving: "ease-in duration-200"
|
|
||||||
From: "opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
||||||
--!>
|
|
||||||
<div
|
|
||||||
class="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
|
|
||||||
>
|
|
||||||
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
||||||
<div class="sm:flex sm:items-start">
|
|
||||||
<div
|
|
||||||
class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="h-6 w-6 text-red-600"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
|
||||||
<h3
|
|
||||||
class="text-base font-semibold leading-6 text-gray-900"
|
|
||||||
id="modal-title"
|
|
||||||
>
|
|
||||||
Deactivate account
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2">
|
|
||||||
<p class="text-sm text-gray-500">
|
|
||||||
Are you sure you want to deactivate your account? All of your
|
|
||||||
data will be permanently removed. This action cannot be
|
|
||||||
undone.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
|
|
||||||
>Deactivate</button
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
|
|
||||||
>Cancel</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.modal {
|
|
||||||
position: relative;
|
|
||||||
z-index: 169;
|
|
||||||
}
|
|
||||||
|
|
||||||
.backdrop {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: #6b7280;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-alignment {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100vh;
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 269;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
overflow-y: none;
|
|
||||||
background: white;
|
|
||||||
padding: 16px 20px;
|
|
||||||
border-radius: 16px;
|
|
||||||
min-width: 50%;
|
|
||||||
min-height: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,67 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { Readable } from "svelte/store";
|
|
||||||
import { yDocToProsemirrorJSON } from "y-prosemirror";
|
|
||||||
import { Node } from "prosemirror-model";
|
|
||||||
import type { Doc } from "yjs";
|
|
||||||
import { schema } from "../editor/schema";
|
|
||||||
import { makeYdocStore } from "../lib/makeYdocStore";
|
|
||||||
import { lastUpdated } from "../lib/lastUpdated";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
type Entry = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
title?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getDocs(ydoc: Doc): Entry[] {
|
|
||||||
let docs: Entry[] = [];
|
|
||||||
for (const name of ydoc.share.keys()) {
|
|
||||||
if (name.startsWith("page/")) {
|
|
||||||
const json = yDocToProsemirrorJSON(ydoc, name);
|
|
||||||
const node = Node.fromJSON(schema, json);
|
|
||||||
let titleNode: null | Node = null;
|
|
||||||
node.descendants((node) => {
|
|
||||||
if (titleNode) return false;
|
|
||||||
if (node.type.name === "heading" && node.attrs.level === 1) {
|
|
||||||
titleNode = node;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
docs.push({
|
|
||||||
title: titleNode?.textContent,
|
|
||||||
id: name.replace(/^page\//, ""),
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return docs;
|
|
||||||
}
|
|
||||||
|
|
||||||
let filter: string = "";
|
|
||||||
|
|
||||||
export let onChoose: (id: string) => void;
|
|
||||||
export let ydoc: Doc;
|
|
||||||
$: entries = deriveEntries(ydoc);
|
|
||||||
$: lastU = lastUpdated(ydoc);
|
|
||||||
$: sortedEntries = $entries
|
|
||||||
.sort((a, b) => +$lastU.get(b.name) - +$lastU.get(a.name))
|
|
||||||
// TODO: FTS
|
|
||||||
.filter((x) => (x.title ?? x.id).includes(filter));
|
|
||||||
// $: console.debug($lastU);
|
|
||||||
|
|
||||||
const deriveEntries = makeYdocStore((_, __, ydoc) => getDocs(ydoc));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<input type="text" bind:value={filter} placeholder="Buscar..." autofocus />
|
|
||||||
<ul>
|
|
||||||
<button type="button" on:click={() => onChoose(nanoid())}>Página nueva</button
|
|
||||||
>
|
|
||||||
{#each sortedEntries as entry}
|
|
||||||
<li>
|
|
||||||
<button type="button" on:click={() => onChoose(entry.id)}
|
|
||||||
>{entry.title ?? entry.id}</button
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
|
@ -21,13 +21,9 @@
|
||||||
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";
|
import Button from "./bubblemenu/Button.svelte";
|
||||||
import Modal from "../components/Modal.svelte";
|
|
||||||
import PagePicker from "../components/PagePicker.svelte";
|
|
||||||
import type { WorldY } from "../lib/doc";
|
|
||||||
|
|
||||||
export let view: EditorView;
|
export let view: EditorView;
|
||||||
export let state: EditorState;
|
export let state: EditorState;
|
||||||
export let worldY: WorldY;
|
|
||||||
|
|
||||||
let changingProp:
|
let changingProp:
|
||||||
| false
|
| false
|
||||||
|
@ -87,17 +83,11 @@
|
||||||
runCommand(updateMark(view.state.schema.marks.link, { href: url }));
|
runCommand(updateMark(view.state.schema.marks.link, { href: url }));
|
||||||
}
|
}
|
||||||
|
|
||||||
let makingInternalLink = false;
|
function createInternalLink() {
|
||||||
function startMakingInternalLink() {
|
const pageId = nanoid();
|
||||||
if (markIsActive(state, view.state.schema.marks.internal_link)) {
|
runCommand(
|
||||||
runCommand(removeMark(view.state.schema.marks.internal_link));
|
updateMark(view.state.schema.marks.internal_link, { id: pageId })
|
||||||
} else {
|
);
|
||||||
makingInternalLink = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function makeInternalLink(id: string) {
|
|
||||||
runCommand(updateMark(view.state.schema.marks.internal_link, { id }));
|
|
||||||
makingInternalLink = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const svgStyle = "width: 100%; height: 100%";
|
const svgStyle = "width: 100%; height: 100%";
|
||||||
|
@ -127,13 +117,6 @@ transform: scale(${1 / viewport.scale});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if makingInternalLink}
|
|
||||||
<Modal onClose={() => (makingInternalLink = false)}>
|
|
||||||
<svelte:fragment slot="title">Elegir página</svelte:fragment>
|
|
||||||
<PagePicker ydoc={worldY.ydoc} onChoose={makeInternalLink} />
|
|
||||||
</Modal>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="bubble" hidden={state.selection.empty} style={barStyle}>
|
<div class="bubble" hidden={state.selection.empty} style={barStyle}>
|
||||||
{#if changingProp === false}
|
{#if changingProp === false}
|
||||||
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strong}
|
<SimpleMarkItem {view} {state} type={view.state.schema.marks.strong}
|
||||||
|
@ -154,8 +137,7 @@ transform: scale(${1 / viewport.scale});
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
active={markIsActive(state, view.state.schema.marks.internal_link)}
|
active={markIsActive(state, view.state.schema.marks.internal_link)}
|
||||||
onClick={startMakingInternalLink}
|
onClick={createInternalLink}><InternalLinkIcon style={svgStyle} /></Button
|
||||||
><InternalLinkIcon style={svgStyle} /></Button
|
|
||||||
>
|
>
|
||||||
{:else if changingProp.type === "link"}
|
{:else if changingProp.type === "link"}
|
||||||
<input
|
<input
|
||||||
|
@ -181,7 +163,6 @@ transform: scale(${1 / viewport.scale});
|
||||||
/* https://wicg.github.io/visual-viewport/examples/fixed-to-keyboard.html */
|
/* https://wicg.github.io/visual-viewport/examples/fixed-to-keyboard.html */
|
||||||
transform-origin: left bottom;
|
transform-origin: left bottom;
|
||||||
|
|
||||||
background: white;
|
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
<!-- 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}
|
{#if view}
|
||||||
<BubbleMenu {view} {worldY} state={updatedState} />
|
<BubbleMenu {view} state={updatedState} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Schema, type Attrs } from "prosemirror-model";
|
import { Schema, type Attrs } from "prosemirror-model";
|
||||||
import { parse } from "regexparam";
|
import { parse, inject } from "regexparam";
|
||||||
import { routes } from "../lib/routes";
|
import { routes } from "../lib/routes";
|
||||||
|
|
||||||
const hex = (x: string) => ("0" + parseInt(x).toString(16)).slice(-2);
|
const hex = (x: string) => ("0" + parseInt(x).toString(16)).slice(-2);
|
||||||
|
|
|
@ -31,10 +31,9 @@ export function getWorldY(world: WorldIdentifier): WorldY {
|
||||||
password: world.password,
|
password: world.password,
|
||||||
signaling: [
|
signaling: [
|
||||||
"wss://webrtc-signaling.schreiben.nulo.ar",
|
"wss://webrtc-signaling.schreiben.nulo.ar",
|
||||||
"wss://y-webrtc-eu.fly.dev",
|
"wss://signaling.yjs.dev",
|
||||||
// "wss://signaling.yjs.dev",
|
"wss://y-webrtc-signaling-eu.herokuapp.com",
|
||||||
// "wss://y-webrtc-signaling-eu.herokuapp.com",
|
"wss://y-webrtc-signaling-us.herokuapp.com",
|
||||||
// "wss://y-webrtc-signaling-us.herokuapp.com",
|
|
||||||
],
|
],
|
||||||
peerOpts: {
|
peerOpts: {
|
||||||
config: {
|
config: {
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import type { AbstractType, Doc } from "yjs";
|
|
||||||
import { makeYdocStore } from "./makeYdocStore.js";
|
|
||||||
|
|
||||||
// XXX: si hay problemas de perf, mirar acá..
|
|
||||||
// después de implementar esto me di cuenta que simplemente puedo fijarme en
|
|
||||||
// cuales fueron las últimas páginas a las que se entró, y probablemente sería más útil.
|
|
||||||
|
|
||||||
export function lastUpdated(ydoc: Doc) {
|
|
||||||
let map: Map<string, Date> = new Map();
|
|
||||||
let observers: Set<{ y: AbstractType<any>; observer: () => void }> =
|
|
||||||
new Set();
|
|
||||||
return makeYdocStore(
|
|
||||||
(_, __, ydoc) => {
|
|
||||||
for (const name of ydoc.share.keys()) {
|
|
||||||
if (name.startsWith("page/")) {
|
|
||||||
if (!map.get(name)) {
|
|
||||||
map.set(name, new Date());
|
|
||||||
const y = ydoc.getXmlFragment(name);
|
|
||||||
const observer = () => map.set(name, new Date());
|
|
||||||
observers.add({ y, observer });
|
|
||||||
y.observeDeep(observer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
},
|
|
||||||
() => observers.forEach(({ y, observer }) => y.unobserveDeep(observer))
|
|
||||||
)(ydoc);
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import type { Readable } from "svelte/store";
|
|
||||||
import type { Doc, Transaction } from "yjs";
|
|
||||||
|
|
||||||
export function makeYdocStore<T>(
|
|
||||||
handler: (update: Uint8Array, origin: any, ydoc: Doc, tr: Transaction) => T,
|
|
||||||
unhandler?: () => void
|
|
||||||
) {
|
|
||||||
return (ydoc: Doc): Readable<T> => {
|
|
||||||
// thanks https://github.com/relm-us/svelt-yjs/blob/main/src/types/array.ts
|
|
||||||
return {
|
|
||||||
subscribe: (run) => {
|
|
||||||
function updateHandler(
|
|
||||||
update: Uint8Array,
|
|
||||||
origin: any,
|
|
||||||
ydoc: Doc,
|
|
||||||
tr: Transaction
|
|
||||||
) {
|
|
||||||
run(handler(update, origin, ydoc, tr));
|
|
||||||
}
|
|
||||||
ydoc.on("update", updateHandler);
|
|
||||||
updateHandler(null, null, ydoc, null);
|
|
||||||
return () => {
|
|
||||||
if (unhandler) unhandler();
|
|
||||||
ydoc.off("update", updateHandler);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
import navaid from "navaid";
|
|
||||||
import { writable } from "svelte/store";
|
|
||||||
|
|
||||||
import ChooseWorld from "../views/ChooseWorld.svelte";
|
|
||||||
import CreateWorld from "../views/CreateWorld.svelte";
|
|
||||||
import JoinWorld from "../views/JoinWorld.svelte";
|
|
||||||
import NotFound from "../views/NotFound.svelte";
|
|
||||||
import Page from "../views/Page.svelte";
|
|
||||||
import ShareWorld from "../views/ShareWorld.svelte";
|
|
||||||
import { routes } from "./routes";
|
|
||||||
|
|
||||||
export let currentRoute = writable<{
|
|
||||||
// XXX: in lack of a better type for Svelte components
|
|
||||||
component: any;
|
|
||||||
params?: Record<string, string>;
|
|
||||||
}>({ component: ChooseWorld });
|
|
||||||
|
|
||||||
export let router = navaid("/", () =>
|
|
||||||
currentRoute.set({ component: NotFound })
|
|
||||||
);
|
|
||||||
router.on(routes.ChooseWorld, () =>
|
|
||||||
currentRoute.set({ component: ChooseWorld })
|
|
||||||
);
|
|
||||||
router.on(routes.CreateWorld, () =>
|
|
||||||
currentRoute.set({ component: CreateWorld })
|
|
||||||
);
|
|
||||||
router.on(routes.ShareWorld, (params) =>
|
|
||||||
currentRoute.set({ component: ShareWorld, params })
|
|
||||||
);
|
|
||||||
router.on(routes.JoinWorld, (params) =>
|
|
||||||
currentRoute.set({ component: JoinWorld, params })
|
|
||||||
);
|
|
||||||
router.on(routes.Page, (params) =>
|
|
||||||
currentRoute.set({ component: Page, params })
|
|
||||||
);
|
|
||||||
|
|
||||||
router.listen();
|
|
|
@ -1,3 +1,22 @@
|
||||||
|
import navaid from "navaid";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
import ChooseWorld from "../views/ChooseWorld.svelte";
|
||||||
|
import CreateWorld from "../views/CreateWorld.svelte";
|
||||||
|
import JoinWorld from "../views/JoinWorld.svelte";
|
||||||
|
import NotFound from "../views/NotFound.svelte";
|
||||||
|
import Page from "../views/Page.svelte";
|
||||||
|
import ShareWorld from "../views/ShareWorld.svelte";
|
||||||
|
|
||||||
|
export let router = navaid("/", () =>
|
||||||
|
currentRoute.set({ component: NotFound })
|
||||||
|
);
|
||||||
|
export let currentRoute = writable<{
|
||||||
|
// XXX: in lack of a better type for Svelte components
|
||||||
|
component: any;
|
||||||
|
params?: Record<string, string>;
|
||||||
|
}>({ component: ChooseWorld });
|
||||||
|
|
||||||
export const routes = {
|
export const routes = {
|
||||||
ChooseWorld: "/",
|
ChooseWorld: "/",
|
||||||
CreateWorld: "/create",
|
CreateWorld: "/create",
|
||||||
|
@ -5,3 +24,21 @@ export const routes = {
|
||||||
JoinWorld: "/w/:worldId/join", // password as hash
|
JoinWorld: "/w/:worldId/join", // password as hash
|
||||||
Page: "/w/:worldId/:pageId",
|
Page: "/w/:worldId/:pageId",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
router.on(routes.ChooseWorld, () =>
|
||||||
|
currentRoute.set({ component: ChooseWorld })
|
||||||
|
);
|
||||||
|
router.on(routes.CreateWorld, () =>
|
||||||
|
currentRoute.set({ component: CreateWorld })
|
||||||
|
);
|
||||||
|
router.on(routes.ShareWorld, (params) =>
|
||||||
|
currentRoute.set({ component: ShareWorld, params })
|
||||||
|
);
|
||||||
|
router.on(routes.JoinWorld, (params) =>
|
||||||
|
currentRoute.set({ component: JoinWorld, params })
|
||||||
|
);
|
||||||
|
router.on(routes.Page, (params) =>
|
||||||
|
currentRoute.set({ component: Page, params })
|
||||||
|
);
|
||||||
|
|
||||||
|
router.listen();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { generateNewWorld } from "../lib/doc";
|
import { generateNewWorld } from "../lib/doc";
|
||||||
import { routes } from "../lib/routes";
|
import { router, routes } from "../lib/routes";
|
||||||
import { router } from "../lib/router";
|
|
||||||
import { writeWorlds } from "../lib/worldStorage";
|
import { writeWorlds } from "../lib/worldStorage";
|
||||||
|
|
||||||
function crear(
|
function crear(
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { routes } from "../lib/routes";
|
import { router, routes } from "../lib/routes";
|
||||||
import { router } from "../lib/router";
|
|
||||||
import { inject } from "regexparam";
|
import { inject } from "regexparam";
|
||||||
import { writeWorlds } from "../lib/worldStorage";
|
import { writeWorlds } from "../lib/worldStorage";
|
||||||
|
|
||||||
|
@ -8,11 +7,7 @@
|
||||||
|
|
||||||
async function addWorld() {
|
async function addWorld() {
|
||||||
const password = location.hash.slice(1);
|
const password = location.hash.slice(1);
|
||||||
await writeWorlds((worlds) => [
|
await writeWorlds((worlds) => [...worlds, { room: worldId, password }]);
|
||||||
// reemplazar mundo en vez de agregar nuevo, por si se agregó antes con la contraseña incorrecta
|
|
||||||
...worlds.filter(({ room }) => room !== worldId),
|
|
||||||
{ room: worldId, password },
|
|
||||||
]);
|
|
||||||
router.route(inject(routes.Page, { worldId, pageId: "index" }));
|
router.route(inject(routes.Page, { worldId, pageId: "index" }));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -19,19 +19,10 @@
|
||||||
throw new Error("No conozco ese mundo.");
|
throw new Error("No conozco ese mundo.");
|
||||||
}
|
}
|
||||||
const worldY = getWorldY(worldIdentifier);
|
const worldY = getWorldY(worldIdentifier);
|
||||||
|
|
||||||
return { worldY, doc: getWorldPage(worldY.ydoc, pageId) };
|
return { worldY, doc: getWorldPage(worldY.ydoc, pageId) };
|
||||||
}
|
}
|
||||||
|
|
||||||
let state: "loading" | { worldY: WorldY; doc: XmlFragment } | { error: any };
|
$: docPromise = loadDoc(worldId, pageId);
|
||||||
$: {
|
|
||||||
state = "loading";
|
|
||||||
loadDoc(worldId, pageId)
|
|
||||||
.then((doc) => {
|
|
||||||
state = doc;
|
|
||||||
})
|
|
||||||
.catch((error) => (state = { error }));
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
|
@ -47,13 +38,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</nav>
|
</nav>
|
||||||
|
{#await docPromise then doc}
|
||||||
{#if state === "loading"}Cargando...{:else if "doc" in state}
|
<Editor doc={doc.doc} worldY={doc.worldY} />
|
||||||
<Editor doc={state.doc} worldY={state.worldY} />
|
{:catch error}
|
||||||
{:else if "error" in state}
|
{error}
|
||||||
{state.error}
|
|
||||||
<a href={routes.ChooseWorld}>Volver al inicio</a>
|
<a href={routes.ChooseWorld}>Volver al inicio</a>
|
||||||
{/if}
|
{/await}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
nav a {
|
nav a {
|
||||||
|
|
Loading…
Reference in a new issue