tweak and edit more stuff
This commit is contained in:
parent
47d5c2fee2
commit
1ae824fa4b
9 changed files with 409 additions and 149 deletions
|
@ -15,12 +15,16 @@
|
|||
"@tanstack/virtual-core": "^3.0.1",
|
||||
"@tiptap/core": "^2.1.13",
|
||||
"@tiptap/extension-document": "^2.1.13",
|
||||
"@tiptap/extension-dropcursor": "^2.1.13",
|
||||
"@tiptap/extension-gapcursor": "^2.1.13",
|
||||
"@tiptap/extension-hard-break": "^2.1.13",
|
||||
"@tiptap/extension-history": "^2.1.13",
|
||||
"@tiptap/extension-mention": "^2.1.13",
|
||||
"@tiptap/extension-paragraph": "^2.1.13",
|
||||
"@tiptap/extension-placeholder": "^2.1.13",
|
||||
"@tiptap/extension-text": "^2.1.13",
|
||||
"@tiptap/pm": "^2.1.13",
|
||||
"@tiptap/starter-kit": "^2.1.13",
|
||||
"@tiptap/suggestion": "^2.1.13",
|
||||
"i18next": "^23.7.8",
|
||||
"marked": "^11.0.1",
|
||||
|
|
158
pnpm-lock.yaml
158
pnpm-lock.yaml
|
@ -26,6 +26,15 @@ dependencies:
|
|||
'@tiptap/extension-document':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-dropcursor':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-gapcursor':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-hard-break':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-history':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
|
@ -44,6 +53,9 @@ dependencies:
|
|||
'@tiptap/pm':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13
|
||||
'@tiptap/starter-kit':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13(@tiptap/pm@2.1.13)
|
||||
'@tiptap/suggestion':
|
||||
specifier: ^2.1.13
|
||||
version: 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
|
@ -831,6 +843,22 @@ packages:
|
|||
'@tiptap/pm': 2.1.13
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-blockquote@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-oe6wSQACmODugoP9XH3Ouffjy4BsOBWfTC+dETHNCG6ZED6ShHN3CB9Vr7EwwRgmm2WLaKAjMO1sVumwH+Z1rg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-bold@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-6cHsQTh/rUiG4jkbJer3vk7g60I5tBwEBSGpdxmEHh83RsvevD8+n92PjA24hYYte5RNlATB011E1wu8PVhSvw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-bubble-menu@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-Hm7e1GX3AI6lfaUmr6WqsS9MMyXIzCkhh+VQi6K8jj4Q4s8kY4KPoAyD/c3v9pZ/dieUtm2TfqrOCkbHzsJQBg==}
|
||||
peerDependencies:
|
||||
|
@ -842,6 +870,32 @@ packages:
|
|||
tippy.js: 6.3.7
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-bullet-list@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-NkWlQ5bLPUlcROj6G/d4oqAxMf3j3wfndGOPp0z8OoXJtVbVoXl/aMSlLbVgE6n8r6CS8MYxKhXNxrb7Ll2foA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-code-block@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-E3tweNExPOV+t1ODKX0MDVsS0aeHGWc1ECt+uyp6XwzsN0bdF2A5+pttQqM7sTcMnQkVACGFbn9wDeLRRcfyQg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
'@tiptap/pm': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
'@tiptap/pm': 2.1.13
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-code@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-f5fLYlSgliVVa44vd7lQGvo49+peC+Z2H0Fn84TKNCH7tkNZzouoJsHYn0/enLaQ9Sq+24YPfqulfiwlxyiT8w==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-document@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-wLwiTWsVmZTGIE5duTcHRmW4ulVxNW4nmgfpk95+mPn1iKyNGtrVhGWleLhBlTj+DWXDtcfNWZgqZkZNzhkqYQ==}
|
||||
peerDependencies:
|
||||
|
@ -850,6 +904,16 @@ packages:
|
|||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-dropcursor@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-NAyJi4BJxH7vl/2LNS1X0ndwFKjEtX+cRgshXCnMyh7qNpIRW6Plczapc/W1OiMncOEhZJfpZfkRSfwG01FWFg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
'@tiptap/pm': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
'@tiptap/pm': 2.1.13
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-floating-menu@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-9Oz7pk1Nts2+EyY+rYfnREGbLzQ5UFazAvRhF6zAJdvyuDmAYm0Jp6s0GoTrpV0/dJEISoFaNpPdMJOb9EBNRw==}
|
||||
peerDependencies:
|
||||
|
@ -861,6 +925,32 @@ packages:
|
|||
tippy.js: 6.3.7
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-gapcursor@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-Cl5apsoTcyPPCgE3ThufxQxZ1wyqqh+9uxUN9VF9AbeTkid6oPZvKXwaILf6AFnkSy+SuKrb9kZD2iaezxpzXw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
'@tiptap/pm': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
'@tiptap/pm': 2.1.13
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-hard-break@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-TGkMzMQayuKg+vN4du0x1ahEItBLcCT1jdWeRsjdM8gHfzbPLdo4PQhVsvm1I0xaZmbJZelhnVsUwRZcIu1WNA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-heading@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-PEmc19QLmlVUTiHWoF0hpgNTNPNU0nlaFmMKskzO+cx5Df4xvHmv/UqoIwp7/UFbPMkfVJT1ozQU7oD1IWn9Hg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-history@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-1ouitThGTBUObqw250aDwGLMNESBH5PRXIGybsCFO1bktdmWtEw7m72WY41EuX2BH8iKJpcYPerl3HfY1vmCNw==}
|
||||
peerDependencies:
|
||||
|
@ -871,6 +961,32 @@ packages:
|
|||
'@tiptap/pm': 2.1.13
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-horizontal-rule@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-7OgjgNqZXvBejgULNdMSma2M1nzv4bbZG+FT5XMFZmEOxR9IB1x/RzChjPdeicff2ZK2sfhMBc4Y9femF5XkUg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
'@tiptap/pm': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
'@tiptap/pm': 2.1.13
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-italic@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-HyDJfuDn5hzwGKZiANcvgz6wcum6bEgb4wmJnfej8XanTMJatNVv63TVxCJ10dSc9KGpPVcIkg6W8/joNXIEbw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-list-item@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-6e8iiCWXOiJTl1XOwVW2tc0YG18h70HUtEHFCx2m5HspOGFKsFEaSS3qYxOheM9HxlmQeDt8mTtqftRjEFRxPQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-mention@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)(@tiptap/suggestion@2.1.13):
|
||||
resolution: {integrity: sha512-OYqaucyBiCN/CmDYjpOVX74RJcIEKmAqiZxUi8Gfaq7ryEO5a8Gk93nK+8uZ0onaqHE+mHpoLFFbcAFbOPgkUQ==}
|
||||
peerDependencies:
|
||||
|
@ -883,6 +999,14 @@ packages:
|
|||
'@tiptap/suggestion': 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-ordered-list@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-UO4ZAL5Vrr1WwER5VjgmeNIWHpqy9cnIRo1En07gZ0OWTjs1eITPcu+4TCn1ZG6DhoFvAQzE5DTxxdhIotg+qw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-paragraph@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-cEoZBJrsQn69FPpUMePXG/ltGXtqKISgypj70PEHXt5meKDjpmMVSY4/8cXvFYEYsI9GvIwyAK0OrfAHiSoROA==}
|
||||
peerDependencies:
|
||||
|
@ -901,6 +1025,14 @@ packages:
|
|||
'@tiptap/pm': 2.1.13
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-strike@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-VN6zlaCNCbyJUCDyBFxavw19XmQ4LkCh8n20M8huNqW77lDGXA2A7UcWLHaNBpqAijBRu9mWI8l4Bftyf2fcAw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.0.0
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
dev: false
|
||||
|
||||
/@tiptap/extension-text@2.1.13(@tiptap/core@2.1.13):
|
||||
resolution: {integrity: sha512-zzsTTvu5U67a8WjImi6DrmpX2Q/onLSaj+LRWPh36A1Pz2WaxW5asZgaS+xWCnR+UrozlCALWa01r7uv69jq0w==}
|
||||
peerDependencies:
|
||||
|
@ -932,6 +1064,32 @@ packages:
|
|||
prosemirror-view: 1.32.6
|
||||
dev: false
|
||||
|
||||
/@tiptap/starter-kit@2.1.13(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-ph/mUR/OwPtPkZ5rNHINxubpABn8fHnvJSdhXFrY/q6SKoaO11NZXgegRaiG4aL7O6Sz4LsZVw6Sm0Ae+GJmrg==}
|
||||
dependencies:
|
||||
'@tiptap/core': 2.1.13(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-blockquote': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-bold': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-bullet-list': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-code': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-code-block': 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-document': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-dropcursor': 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-gapcursor': 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-hard-break': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-heading': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-history': 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-horizontal-rule': 2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13)
|
||||
'@tiptap/extension-italic': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-list-item': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-ordered-list': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-paragraph': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-strike': 2.1.13(@tiptap/core@2.1.13)
|
||||
'@tiptap/extension-text': 2.1.13(@tiptap/core@2.1.13)
|
||||
transitivePeerDependencies:
|
||||
- '@tiptap/pm'
|
||||
dev: false
|
||||
|
||||
/@tiptap/suggestion@2.1.13(@tiptap/core@2.1.13)(@tiptap/pm@2.1.13):
|
||||
resolution: {integrity: sha512-Y05TsiXTFAJ5SrfoV+21MAxig5UNbY0AVa03lQlh/yicTRPpIc6hgZzblB0uxDSYoj6+kaHE4MIZvPvhUD8BJQ==}
|
||||
peerDependencies:
|
||||
|
|
53
src/App.scss
53
src/App.scss
|
@ -7,7 +7,7 @@
|
|||
"nav-spaces nav-rooms main sidebar"
|
||||
"status status main sidebar";
|
||||
grid-template-columns: 64px 256px 1fr auto;
|
||||
grid-template-rows: 64px 1fr 72px;
|
||||
grid-template-rows: 48px 1fr 72px;
|
||||
}
|
||||
|
||||
#header {
|
||||
|
@ -95,7 +95,11 @@
|
|||
padding: 4px 8px;
|
||||
|
||||
&:hover {
|
||||
background: #ffffff33;
|
||||
background: #ffffff11;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: #ffffff22;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,11 +343,21 @@
|
|||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
scrollbar-color: var(--background-1) var(--background-3);
|
||||
overflow-anchor: none;
|
||||
// overflow-anchor: none;
|
||||
|
||||
& > .spacer {
|
||||
margin-top: auto;
|
||||
margin-bottom: 32px;
|
||||
min-height: 32px;
|
||||
padding: 0 4px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
justify-content: end;
|
||||
|
||||
& > button {
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .spacer-bottom {
|
||||
|
@ -354,6 +368,7 @@
|
|||
position: sticky;
|
||||
top: -1px;
|
||||
z-index: 999;
|
||||
visibility: hidden;
|
||||
|
||||
& > * {
|
||||
padding: 1px 8px 0;
|
||||
|
@ -366,8 +381,25 @@
|
|||
|
||||
& > .real {
|
||||
position: absolute;
|
||||
transition: all .2s;
|
||||
border-bottom: solid var(--background-3) 0;
|
||||
display: flex;
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
& > button {
|
||||
display: none;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
font-weight: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&.setup > .real {
|
||||
transition: all .2s;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&.stuck > .real {
|
||||
|
@ -375,6 +407,17 @@
|
|||
border-bottom: solid var(--background-3) 1px;
|
||||
font-size: 1.2rem;
|
||||
padding: 8px;
|
||||
|
||||
& > div {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
& > button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
src/App.tsx
13
src/App.tsx
|
@ -1,4 +1,4 @@
|
|||
import { Match, Show, Switch, VoidProps, createSignal, onCleanup } from "solid-js";
|
||||
import { Match, Show, Switch, VoidProps, createSelector, createSignal, onCleanup } from "solid-js";
|
||||
import "./App.scss";
|
||||
import { Client, Room } from "sdk";
|
||||
import { Home, RoomView } from "./Main";
|
||||
|
@ -31,7 +31,7 @@ function App(props: VoidProps<{ client: Client }>) {
|
|||
Object.assign(globalThis, { client: props.client });
|
||||
|
||||
props.client.lists.subscribe("rooms", {
|
||||
ranges: [[0, 120]],
|
||||
ranges: [[0, 99999]],
|
||||
required_state: [["m.room.name", ""], ["m.room.topic", ""]],
|
||||
// timeline_limit: 3,
|
||||
} as any);
|
||||
|
@ -41,11 +41,12 @@ function App(props: VoidProps<{ client: Client }>) {
|
|||
});
|
||||
|
||||
const thread = () => globals.roomState.focusedThread();
|
||||
|
||||
const isSelected = createSelector(() => globals.globalState.focusedRoom());
|
||||
|
||||
return (
|
||||
<>
|
||||
<header id="header">
|
||||
</header>
|
||||
<header id="header"></header>
|
||||
<main id="main">
|
||||
<Show when={globals.globalState.focusedRoom()} fallback={<Home client={props.client} />}>
|
||||
<RoomView room={globals.globalState.focusedRoom()!} />
|
||||
|
@ -53,8 +54,8 @@ function App(props: VoidProps<{ client: Client }>) {
|
|||
</main>
|
||||
<nav id="nav-rooms">
|
||||
<ul>
|
||||
<li onClick={() => change({ type: "focusRoom", room: null })}>home</li>
|
||||
{rooms().rooms.map(i => <li onClick={() => change({ type: "focusRoom", room: i })}>{i.getState("m.room.name")?.content.name || "unnamed"}</li>)}
|
||||
<li classList={{ selected: isSelected(null) }} onClick={() => change({ type: "focusRoom", room: null })}>home</li>
|
||||
{rooms().rooms.map(i => <li classList={{ selected: isSelected(i) }} onClick={() => change({ type: "focusRoom", room: i })}>{i.getState("m.room.name")?.content.name || "unnamed"}</li>)}
|
||||
</ul>
|
||||
</nav>
|
||||
<nav id="nav-spaces"></nav>
|
||||
|
|
113
src/Editor.tsx
113
src/Editor.tsx
|
@ -1,114 +1,3 @@
|
|||
import { createEditor, EditorContent } from "tiptap-solid";
|
||||
import Document from "@tiptap/extension-document";
|
||||
import Paragraph from "@tiptap/extension-paragraph";
|
||||
import Text from "@tiptap/extension-text";
|
||||
import History from "@tiptap/extension-history";
|
||||
// import Mention from "@tiptap/extension-mention";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import "./Editor.scss";
|
||||
// import { Accessor, createEffect, createSignal, VoidProps } from "solid-js";
|
||||
// import { Node } from "@tiptap/core";
|
||||
import { Extension, Mark, markInputRule, wrappingInputRule } from "@tiptap/core";
|
||||
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||
import { marked } from "marked";
|
||||
|
||||
// function Suggestions(props: VoidProps<{ suggestions: Accessor<Array<string>> }>) {
|
||||
// createEffect(() => console.log(props.suggestions()));
|
||||
// return (
|
||||
// <ul>
|
||||
// {props.suggestions().map(i => <li>{i}</li>)}
|
||||
// </ul>
|
||||
// )
|
||||
// }
|
||||
|
||||
const Markdown = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
"Mod-b": () => {
|
||||
const { state } = this.editor.view;
|
||||
const { selection } = state;
|
||||
const { from, to } = selection;
|
||||
this.editor.chain()
|
||||
.insertContentAt({ from: to, to }, "**", { updateSelection: true })
|
||||
.insertContentAt({ from, to: from }, "**", { updateSelection: true })
|
||||
.setTextSelection({ from: from + 2, to: to + 2 })
|
||||
.focus()
|
||||
.run();
|
||||
return true;
|
||||
},
|
||||
}
|
||||
},
|
||||
addProseMirrorPlugins() {
|
||||
return [new Plugin({
|
||||
key: new PluginKey("markdown"),
|
||||
props: {
|
||||
decorations(state) {
|
||||
const decorations = [];
|
||||
let pos = 0;
|
||||
|
||||
function walk(token) {
|
||||
if (token.type === "em") {
|
||||
decorations.push(Decoration.inline(pos, pos + 2, { class: "syn" }));
|
||||
decorations.push(Decoration.inline(pos + 2, pos + token.raw.length, { style: "font-style:italic" }));
|
||||
decorations.push(Decoration.inline(pos + token.raw.length, pos + token.raw.length + 1, { class: "syn" }));
|
||||
pos += token.raw.length;
|
||||
} else if (token.type === "strong") {
|
||||
decorations.push(Decoration.inline(pos + 1, pos + 3, { class: "syn" }));
|
||||
decorations.push(Decoration.inline(pos + 3, pos + token.raw.length - 1, { style: "font-weight:bold" }));
|
||||
decorations.push(Decoration.inline(pos + token.raw.length - 1, pos + token.raw.length + 1, { class: "syn" }));
|
||||
pos += token.raw.length;
|
||||
} else if (token.type === "codespan") {
|
||||
decorations.push(Decoration.inline(pos, pos + 2, { class: "syn" }));
|
||||
decorations.push(Decoration.inline(pos + 2, pos + token.raw.length, { nodeName: "code" }));
|
||||
decorations.push(Decoration.inline(pos + token.raw.length, pos + token.raw.length + 1, { class: "syn" }));
|
||||
pos += token.raw.length;
|
||||
} else if (token.type === "text") {
|
||||
pos += token.raw.length;
|
||||
} else {
|
||||
for (const t of token.tokens || []) {
|
||||
walk(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(state.doc.textContent)
|
||||
// console.log(marked.lexer(state.doc.textContent));
|
||||
for (const token of marked.lexer(state.doc.textContent)) walk(token);
|
||||
return DecorationSet.create(state.doc, decorations);
|
||||
}
|
||||
}
|
||||
})]
|
||||
}
|
||||
});
|
||||
|
||||
export function Editor() {
|
||||
let editorEl;
|
||||
const editor = createEditor({
|
||||
content: "hello world *asdf*",
|
||||
autofocus: true,
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
History,
|
||||
Placeholder.configure({
|
||||
placeholder: "write something nice...",
|
||||
}),
|
||||
Markdown,
|
||||
]
|
||||
});
|
||||
function doThing() {
|
||||
const ed = editor()!;
|
||||
// ed.chain().focus().insertContent("hello world!").run();
|
||||
ed.state.selection.$from
|
||||
ed.state.selection.$to
|
||||
ed.chain().focus().insertContentAt(0, "content").run();
|
||||
}
|
||||
return (
|
||||
<div class="editor" ref={editorEl}>
|
||||
<button onClick={doThing}>do thing</button>
|
||||
<EditorContent editor={editor()} />
|
||||
</div>
|
||||
);
|
||||
return <textarea />;
|
||||
}
|
||||
|
|
10
src/Main.tsx
10
src/Main.tsx
|
@ -34,11 +34,19 @@ function Threads(props: VoidProps<{ room: Room }>) {
|
|||
);
|
||||
}
|
||||
|
||||
const ROOM_TYPE_CHAT = "jw.chat";
|
||||
const ROOM_TYPE_SPACE = "m.space";
|
||||
|
||||
export function Home(props: VoidProps<{ client: Client }>) {
|
||||
function handleSubmit(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
const name = (e.target as HTMLFormElement).elements.namedItem("name")! as HTMLInputElement;
|
||||
props.client.rooms.create({ initialState: [{ type: "m.room.name", content: { name: name.value }, stateKey: ""}] });
|
||||
props.client.rooms.create({
|
||||
creationContent: {
|
||||
type: ROOM_TYPE_CHAT,
|
||||
},
|
||||
initialState: [{ type: "m.room.name", content: { name: name.value }, stateKey: ""}]
|
||||
});
|
||||
name.value = "";
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import { Text, Time } from "./Atoms";
|
|||
export function ThreadsItem(props: VoidProps<{ thread: Thread }>) {
|
||||
const [globals, change] = useGlobals();
|
||||
|
||||
const THREAD_PREVIEW_COUNT = 10;
|
||||
const [preview, { mutate }] = createResource(props.thread, async (thread) => {
|
||||
return thread.room.timelines.forThread(thread, "start");
|
||||
return thread.timelines.fetch("start");
|
||||
}, {
|
||||
storage: (init) => createSignal(init, { equals: false }),
|
||||
});
|
||||
|
@ -21,7 +22,7 @@ export function ThreadsItem(props: VoidProps<{ thread: Thread }>) {
|
|||
onCleanup(() => props.thread.room.off("timeline", refresh));
|
||||
|
||||
const [thread, setThread] = createSignal(props.thread, { equals: false });
|
||||
const remaining = () => preview() && (thread().messageCount - Math.min(preview()!.getEvents().length, 10));
|
||||
const remaining = () => preview() && (thread().messageCount - Math.min(preview()!.getEvents().length, THREAD_PREVIEW_COUNT ));
|
||||
|
||||
const willOpenThread = (target: "default" | "latest") => (e: MouseEvent) => {
|
||||
// TODO: target
|
||||
|
@ -48,7 +49,7 @@ export function ThreadsItem(props: VoidProps<{ thread: Thread }>) {
|
|||
</header>
|
||||
<div class="preview">
|
||||
<Show when={!preview.loading}>
|
||||
{preview()?.getEvents().filter(ev => ev.type === "m.message").slice(0, 10).map((ev, idx) => <Message event={ev} title={idx === 0} />)}
|
||||
{preview()?.getEvents().filter(ev => ev.type === "m.message").slice(0, THREAD_PREVIEW_COUNT).map((ev, idx) => <Message event={ev} title={idx === 0} />)}
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={remaining() && false}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { For, ParentProps, Show, VoidProps, createEffect, createResource, createSignal, onCleanup, onMount } from "solid-js";
|
||||
import "./App.scss";
|
||||
import { Client, Room, Thread, Timeline } from "sdk";
|
||||
import { Client, Room, Thread } from "sdk";
|
||||
import { Message, TextBlock, ThreadsItem } from "./Room";
|
||||
// import { computePosition, shift, offset, autoUpdate } from "@floating-ui/dom";
|
||||
// import { Portal } from "solid-js/web";
|
||||
|
@ -8,6 +8,7 @@ import { Time } from "./Atoms";
|
|||
import { ThreadTimeline } from "sdk/dist/src/timeline";
|
||||
import { debounce } from "@solid-primitives/scheduled";
|
||||
import { Editor } from "./Editor";
|
||||
import { useGlobals } from "./Context";
|
||||
// import { createVirtualizer } from "@tanstack/solid-virtual";
|
||||
|
||||
export function ThreadView(props: VoidProps<{ thread: Thread }>) {
|
||||
|
@ -25,9 +26,13 @@ export function ThreadView(props: VoidProps<{ thread: Thread }>) {
|
|||
}
|
||||
}
|
||||
|
||||
const [timeline, { mutate }] = createResource(() => props.thread, async (thread) => {
|
||||
return thread.room.timelines.forThread(thread, "end");
|
||||
}, {
|
||||
// updates when the thread changes
|
||||
const [timelineReal] = createResource(() => props.thread, async (thread) => {
|
||||
return thread.timelines.fetch("end", 50);
|
||||
});
|
||||
|
||||
// updates every time there's new events
|
||||
const [timeline, { mutate }] = createResource(() => timelineReal(), (t) => t, {
|
||||
storage: (init) => createSignal(init, { equals: false }),
|
||||
});
|
||||
|
||||
|
@ -43,15 +48,14 @@ export function ThreadView(props: VoidProps<{ thread: Thread }>) {
|
|||
|
||||
let oldTimeline: ThreadTimeline | undefined;
|
||||
createEffect(() => {
|
||||
oldTimeline?.off("timelineUpdate", refresh);
|
||||
oldTimeline?.off("timelineAppend", refresh);
|
||||
timeline()?.on("timelineUpdate", refresh);
|
||||
timeline()?.on("timelineAppend", refresh);
|
||||
oldTimeline = timeline();
|
||||
setIsAtBeginning(timeline()?.isAtBeginning || false);
|
||||
}, timeline);
|
||||
timelineReal()?.on("timelineAppend", refresh);
|
||||
oldTimeline = timelineReal();
|
||||
setIsAtBeginning(timelineReal()?.isAtBeginning || false);
|
||||
scrollEl.scrollBy(0, 999999);
|
||||
// paginate();
|
||||
}, timelineReal);
|
||||
onCleanup(() => {
|
||||
oldTimeline?.off("timelineUpdate", refresh);
|
||||
oldTimeline?.off("timelineAppend", refresh);
|
||||
});
|
||||
|
||||
|
@ -63,19 +67,21 @@ export function ThreadView(props: VoidProps<{ thread: Thread }>) {
|
|||
|
||||
if (scrollEl.scrollTop < PAGINATE_MARGIN && !isPaginating() && !isAtBeginning()) {
|
||||
const scrollRefEl = scrollEl.querySelector(".message") as HTMLElement | undefined;
|
||||
const oldOffsetTop = (scrollRefEl?.offsetTop || 0);
|
||||
setIsPaginating(true);
|
||||
await timeline()?.paginate("b", 10);
|
||||
const newOffsetTop = scrollRefEl?.offsetTop || 0;
|
||||
await timeline()?.paginate("b");
|
||||
const oldOffsetTop = scrollRefEl?.offsetTop || 0;
|
||||
refresh();
|
||||
const newOffsetTop = scrollRefEl?.offsetTop || 0;
|
||||
console.log("scroll diff ", scrollRefEl, newOffsetTop, oldOffsetTop);
|
||||
console.log("element moved by px", newOffsetTop - oldOffsetTop);
|
||||
console.log("isAutoscrolling?", isAutoscrolling);
|
||||
scrollEl.scrollBy(0, newOffsetTop - oldOffsetTop);
|
||||
// scrollEl.scrollTo(0, oldOffsetTop);
|
||||
setIsPaginating(false);
|
||||
if (timeline()?.isAtBeginning) setIsAtBeginning(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = debounce(paginate, 100);
|
||||
const handleScroll = () => paginate();
|
||||
const events = () => timeline()?.getEvents().filter(ev => ev.type === "m.message");
|
||||
// const eventsLength = () => events()?.length || 0;
|
||||
|
||||
|
@ -119,26 +125,32 @@ export function ThreadView(props: VoidProps<{ thread: Thread }>) {
|
|||
function ThreadInfo(props: VoidProps<{ thread: Thread, showHeader: boolean }>) {
|
||||
const event = () => props.thread.baseEvent;
|
||||
let headerEl: HTMLHeadingElement;
|
||||
const [_globals, change] = useGlobals();
|
||||
const [setup, setSetup] = createSignal(false);
|
||||
const [stuck, setStuck] = createSignal(false);
|
||||
|
||||
// FIXME: this feels hacky, and has a flash of "unstuck" style + 1px offsets occasionally
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver(([e]) => setStuck(!e.isIntersecting), {
|
||||
const observer = new IntersectionObserver(([e]) => {
|
||||
setStuck(!e.isIntersecting);
|
||||
setTimeout(() => setSetup(true), 10);
|
||||
}, {
|
||||
threshold: 1,
|
||||
root: headerEl.parentElement,
|
||||
});
|
||||
|
||||
|
||||
observer.observe(headerEl);
|
||||
onCleanup(() => observer.unobserve(headerEl));
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.showHeader && <div class="spacer"></div>}
|
||||
<div class="thread-title" ref={headerEl!} classList={{ stuck: stuck() || !props.showHeader }}>
|
||||
{props.showHeader && <div class="spacer">
|
||||
<button onClick={() => change({ type: "focusThread", thread: null })}>close thread</button>
|
||||
</div>}
|
||||
<div class="thread-title" ref={headerEl!} classList={{ stuck: stuck() || !props.showHeader, setup: setup() }}>
|
||||
<h1 class="real">
|
||||
<TextBlock text={event().content.title} fallback="Untitled thread" />
|
||||
<button onClick={() => change({ type: "focusThread", thread: null })}>close thread</button>
|
||||
</h1>
|
||||
<h1 class="fake" aria-hidden="true">
|
||||
<TextBlock text={event().content.title} fallback="Untitled thread" />
|
||||
|
|
144
src/_Editor.tsx
Normal file
144
src/_Editor.tsx
Normal file
|
@ -0,0 +1,144 @@
|
|||
import { createEditor, EditorContent } from "tiptap-solid";
|
||||
import Document from "@tiptap/extension-document";
|
||||
import Paragraph from "@tiptap/extension-paragraph";
|
||||
import Text from "@tiptap/extension-text";
|
||||
import History from "@tiptap/extension-history";
|
||||
// import Mention from "@tiptap/extension-mention";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import "./Editor.scss";
|
||||
// import { Accessor, createEffect, createSignal, VoidProps } from "solid-js";
|
||||
import { Extension, Mark, markInputRule, wrappingInputRule } from "@tiptap/core";
|
||||
import * as tiptap from "@tiptap/core";
|
||||
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
||||
import { Node } from "@tiptap/pm/model";
|
||||
import { Marked, marked, Token } from "marked";
|
||||
import { Dropcursor } from "@tiptap/extension-dropcursor";
|
||||
import { Gapcursor } from "@tiptap/extension-gapcursor";
|
||||
import { HardBreak } from "@tiptap/extension-hard-break";
|
||||
|
||||
// function Suggestions(props: VoidProps<{ suggestions: Accessor<Array<string>> }>) {
|
||||
// createEffect(() => console.log(props.suggestions()));
|
||||
// return (
|
||||
// <ul>
|
||||
// {props.suggestions().map(i => <li>{i}</li>)}
|
||||
// </ul>
|
||||
// )
|
||||
// }
|
||||
|
||||
const Markdown = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
"Mod-b": () => {
|
||||
const { state } = this.editor.view;
|
||||
const { selection } = state;
|
||||
const { from, to } = selection;
|
||||
this.editor.chain()
|
||||
.insertContentAt({ from: to, to }, "**", { updateSelection: true })
|
||||
.insertContentAt({ from, to: from }, "**", { updateSelection: true })
|
||||
.setTextSelection({ from: from + 2, to: to + 2 })
|
||||
.focus()
|
||||
.run();
|
||||
return true;
|
||||
},
|
||||
"Shift-Enter": () => {
|
||||
const { state } = this.editor.view;
|
||||
const { selection } = state;
|
||||
const { from, to } = selection;
|
||||
const { paragraph } = state.schema.nodes;
|
||||
console.log(paragraph)
|
||||
// this.editor.chain().deleteSelection().unsetCode(from, paragraph.create())
|
||||
this.editor.chain().setNode("paragraph").run();
|
||||
console.log("s+enter");
|
||||
return false;
|
||||
},
|
||||
"Enter": () => {
|
||||
console.log("enter");
|
||||
return true;
|
||||
},
|
||||
}
|
||||
},
|
||||
addProseMirrorPlugins() {
|
||||
return [new Plugin({
|
||||
key: new PluginKey("markdown"),
|
||||
props: {
|
||||
decorations(state) {
|
||||
const decorations: Array<Decoration> = [];
|
||||
console.log(state.doc, state.doc.textContent)
|
||||
|
||||
state.doc.descendants((node, nodePos) => {
|
||||
if (!node.isText) return;
|
||||
|
||||
let pos = nodePos - 1;
|
||||
const walk = (ast: Token) => {
|
||||
switch (ast.type) {
|
||||
case "em": {
|
||||
const end = pos + ast.raw.length + 1;
|
||||
decorations.push(Decoration.inline(pos, pos + 2, { class: "syn" }));
|
||||
decorations.push(Decoration.inline(pos + 2, end - 1, { style: "font-style: italic" }));
|
||||
decorations.push(Decoration.inline(end - 1, end, { class: "syn" }));
|
||||
break;
|
||||
}
|
||||
case "strong": {
|
||||
const end = pos + ast.raw.length + 1;
|
||||
decorations.push(Decoration.inline(pos, pos + 3, { class: "syn" }));
|
||||
decorations.push(Decoration.inline(pos + 3, end - 2, { style: "font-weight: bold" }));
|
||||
decorations.push(Decoration.inline(end - 2, end, { class: "syn" }));
|
||||
break;
|
||||
}
|
||||
case "codespan": {
|
||||
const end = pos + ast.raw.length + 1;
|
||||
decorations.push(Decoration.inline(pos, pos + 2, { class: "syn" }));
|
||||
decorations.push(Decoration.inline(pos + 2, end - 1, { style: "font-style: italic" }));
|
||||
decorations.push(Decoration.inline(end - 1, end, { class: "syn" }));
|
||||
break;
|
||||
}
|
||||
case "code": {
|
||||
// console.log(ast)
|
||||
// const end = pos + ast.raw.length + 1;
|
||||
// decorations.push(Decoration.inline(pos, pos + 2, { class: "syn" }));
|
||||
// decorations.push(Decoration.inline(pos + 2, end - 1, { style: "font-style: italic" }));
|
||||
// decorations.push(Decoration.inline(end - 1, end, { class: "syn" }));
|
||||
break;
|
||||
}
|
||||
case "paragraph": ast.tokens?.forEach(walk); return;
|
||||
// default: console.log(ast);
|
||||
}
|
||||
pos += ast.raw.length;
|
||||
};
|
||||
// console.log(node, marked.lexer(node.text!))
|
||||
marked.lexer(node.text!).forEach(walk);
|
||||
});
|
||||
|
||||
return DecorationSet.create(state.doc, decorations);
|
||||
}
|
||||
}
|
||||
})]
|
||||
}
|
||||
});
|
||||
|
||||
export function Editor() {
|
||||
let editorEl;
|
||||
const editor = createEditor({
|
||||
content: "hello world *asdf*",
|
||||
autofocus: true,
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
History,
|
||||
Gapcursor,
|
||||
Dropcursor,
|
||||
HardBreak,
|
||||
Placeholder.configure({
|
||||
placeholder: "write something nice...",
|
||||
}),
|
||||
Markdown,
|
||||
]
|
||||
});
|
||||
return (
|
||||
<div class="editor" ref={editorEl}>
|
||||
<EditorContent editor={editor()} />
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue