From 5b548cccab64a2c5ff8112064bc02b03da53e37b Mon Sep 17 00:00:00 2001 From: Jonathan Staab Date: Fri, 14 Apr 2023 11:09:27 -0500 Subject: [PATCH] Switch from custom data to lists --- CHANGELOG.md | 8 +++ ROADMAP.md | 13 ++-- src/agent/cmd.ts | 5 +- src/agent/db.ts | 2 +- src/agent/sync.ts | 17 +++-- src/agent/user.ts | 31 ++++---- src/app/Modal.svelte | 18 ++--- src/app/shared/FeedSummary.svelte | 11 --- src/app/shared/ListSummary.svelte | 24 +++++++ src/app/shared/NoteReply.svelte | 2 +- src/app/shared/PersonActions.svelte | 6 +- src/app/shared/RelayActions.svelte | 6 +- src/app/shared/TopicActions.svelte | 6 +- src/app/state.ts | 6 +- src/app/views/FeedEdit.svelte | 99 -------------------------- src/app/views/FeedList.svelte | 52 -------------- src/app/views/FeedSelect.svelte | 45 ------------ src/app/views/Feeds.svelte | 106 ++++++++++++---------------- src/app/views/ListEdit.svelte | 89 +++++++++++++++++++++++ src/app/views/ListList.svelte | 57 +++++++++++++++ src/app/views/ListSelect.svelte | 43 +++++++++++ src/app/views/PersonFeed.svelte | 20 +++++- src/partials/Chip.svelte | 2 +- src/partials/MultiSelect.svelte | 6 +- src/partials/state.ts | 22 ++++-- src/util/nostr.ts | 7 +- src/util/types.ts | 8 --- 27 files changed, 367 insertions(+), 344 deletions(-) delete mode 100644 src/app/shared/FeedSummary.svelte create mode 100644 src/app/shared/ListSummary.svelte delete mode 100644 src/app/views/FeedEdit.svelte delete mode 100644 src/app/views/FeedList.svelte delete mode 100644 src/app/views/FeedSelect.svelte create mode 100644 src/app/views/ListEdit.svelte create mode 100644 src/app/views/ListList.svelte create mode 100644 src/app/views/ListSelect.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index e64dc892..d8396d38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.2.24 + +- [x] Replace localforage with loki.js for storage +- [x] Fix a bunch of bugs in content parsing +- [x] Add lists/custom feeds +- [x] Refactor component hiararchy +- [x] Re-work how modals stack + ## 0.2.23 - [x] Fix modal scroll position for nested modals diff --git a/ROADMAP.md b/ROADMAP.md index 0022dbfa..16f1f3d2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,11 +5,14 @@ - [x] Add topic search, keep cache of topics - [x] Ability to create custom feeds - [x] Bookmark icon opens "create feed" dialog with form pre-filled - - [ ] Use lists instead of custom app data - - [ ] Public/private toggle - - [ ] Add person to feed button (maybe lists make more sense for this?) - - [ ] Add 30078 to personKinds (except we'll have to get more involved) + - [ ] Replace some modals instead of pushing + - [ ] Test anonymous with lists + - [ ] Test hardcoded relay, currently you get asked to pick a relay if not logged in - [ ] Claim relays bounty +- [ ] Fix notifications +- [ ] Queue context requests to avoid having too many concurrent subscriptions +- [ ] Advanced search + - Select timeframe, authors, p tags, t tags - [ ] Some lnurls aren't working npub1y3k2nheva29y9ej8a22e07epuxrn04rvgy28wvs54y57j7vsxxuq0gvp4j - [ ] Global search modal that searches within current feed - [ ] Fix force relays on login: http://localhost:5173/messages/npub1l66wvfm7dxhd6wmvpukpjpyhvwtlxzu0qqajqxjfpr4rlfa8hl5qlkfr3q @@ -64,7 +67,7 @@ # UI/Features - [ ] Remember message/chat status -- [ ] Linkify topics +- [ ] Allow sharing of lists/following other people's lists - [ ] Add suggestion list for topics on compose - [ ] Badges link to https://badges.page/p/97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322 - [ ] Add QR code that pre-fills follows and relays for a new user diff --git a/src/agent/cmd.ts b/src/agent/cmd.ts index a813e6a2..08524720 100644 --- a/src/agent/cmd.ts +++ b/src/agent/cmd.ts @@ -35,8 +35,7 @@ const setPetnames = petnames => new PublishableEvent(3, {tags: petnames}) const setMutes = mutes => new PublishableEvent(10000, {tags: mutes}) -const setFeeds = feeds => - new PublishableEvent(30078, {content: JSON.stringify(feeds), tags: [["d", "coracle/feeds"]]}) +const createList = list => new PublishableEvent(30001, {tags: list}) const createRoom = room => new PublishableEvent(40, {content: JSON.stringify(pick(roomAttrs, room))}) @@ -202,7 +201,7 @@ export default { setRelays, setPetnames, setMutes, - setFeeds, + createList, createRoom, updateRoom, createChatMessage, diff --git a/src/agent/db.ts b/src/agent/db.ts index dd18d140..98875f32 100644 --- a/src/agent/db.ts +++ b/src/agent/db.ts @@ -210,7 +210,7 @@ export const people = new Table("people", "pubkey", { }) export const userEvents = new Table("userEvents", "id", {max: 2000, sort: sortByCreatedAt}) -export const notifications = new Table("notifications", "id") +export const notifications = new Table("notifications", "id", {sort: sortByCreatedAt}) export const contacts = new Table("contacts", "pubkey") export const rooms = new Table("rooms", "id") export const relays = new Table("relays", "url") diff --git a/src/agent/sync.ts b/src/agent/sync.ts index f719a1cb..0e172285 100644 --- a/src/agent/sync.ts +++ b/src/agent/sync.ts @@ -1,4 +1,4 @@ -import {uniq, nth, objOf, pick, identity} from "ramda" +import {uniq, prop, reject, nth, uniqBy, objOf, pick, identity} from "ramda" import {nip05} from "nostr-tools" import {noop, ensurePlural, chunk} from "hurdak/lib/hurdak" import { @@ -222,11 +222,16 @@ addHandler( ) addHandler( - 30078, - profileHandler("feeds", (e, p) => { - if (Tags.from(e).type("d").values().first() === "coracle/feeds") { - return tryJson(() => JSON.parse(e.content)) - } + 30001, + profileHandler("lists", (e, p) => uniqBy(prop("id"), p.lists.concat(e))) +) + +addHandler( + 5, + profileHandler("lists", (e, p) => { + const ids = new Set(Tags.from(e).type("e").values().all()) + + return reject(e => ids.has(e.id), p.lists) }) ) diff --git a/src/agent/user.ts b/src/agent/user.ts index 9e62763e..df742b59 100644 --- a/src/agent/user.ts +++ b/src/agent/user.ts @@ -1,4 +1,4 @@ -import type {CustomFeed, Relay} from "src/util/types" +import type {Relay, MyEvent} from "src/util/types" import type {Readable} from "svelte/store" import { slice, @@ -37,14 +37,14 @@ const profile = synced("agent/user/profile", { petnames: [], relays: [], mutes: [], - feeds: [], + lists: [], }) const settings = derived(profile, prop("settings")) const petnames = derived(profile, prop("petnames")) const relays = derived(profile, prop("relays")) as Readable> const mutes = derived(profile, prop("mutes")) as Readable> -const feeds = derived(profile, prop("feeds")) as Readable> +const lists = derived(profile, prop("lists")) as Readable> const canPublish = derived( [keys.pubkey, relays], @@ -166,23 +166,20 @@ export default { return this.updateMutes(reject(t => t[1] === pubkey)) }, - // Feeds + // Lists - feeds, - getFeeds: () => profileCopy.feeds, - updateFeeds(f) { - const $feeds = f(profileCopy.feeds) + lists, + getLists: () => profileCopy.lists, + async putList(id, name, params, relays) { + const tags = [["d", name]].concat(params).concat(relays) - profile.update(assoc("feeds", $feeds)) - - if (keys.canSign()) { - return cmd.setFeeds($feeds).publish(profileCopy.relays) + if (id) { + await cmd.deleteEvent([id]).publish(profileCopy.relays) } + + await cmd.createList(tags).publish(profileCopy.relays) }, - addFeed(feed) { - return this.updateFeeds($feeds => $feeds.concat(feed)) - }, - removeFeed(id) { - return this.updateFeeds($feeds => reject(whereEq({id}), $feeds)) + removeList(id) { + return cmd.deleteEvent([id]).publish(profileCopy.relays) }, } diff --git a/src/app/Modal.svelte b/src/app/Modal.svelte index 6106fb56..728c4c9e 100644 --- a/src/app/Modal.svelte +++ b/src/app/Modal.svelte @@ -17,9 +17,9 @@ import PersonProfileInfo from "src/app/views/PersonProfileInfo.svelte" import PersonShare from "src/app/views/PersonShare.svelte" import TopicFeed from "src/app/views/TopicFeed.svelte" - import FeedList from "src/app/views/FeedList.svelte" - import FeedSelect from "src/app/views/FeedSelect.svelte" - import FeedEdit from "src/app/views/FeedEdit.svelte" + import ListList from "src/app/views/ListList.svelte" + import ListSelect from "src/app/views/ListSelect.svelte" + import ListEdit from "src/app/views/ListEdit.svelte" import RelayAdd from "src/app/views/RelayAdd.svelte" const {stack} = modal @@ -64,12 +64,12 @@ {#key m.topic} {/key} - {:else if m.type === "feed/list"} - - {:else if m.type === "feed/select"} - - {:else if m.type === "feed/edit"} - + {:else if m.type === "list/list"} + + {:else if m.type === "list/select"} + + {:else if m.type === "list/edit"} + {:else if m.type === "message"}
{m.message}
diff --git a/src/app/shared/FeedSummary.svelte b/src/app/shared/FeedSummary.svelte deleted file mode 100644 index 8ffe9b41..00000000 --- a/src/app/shared/FeedSummary.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - -

- {feed.topics ? quantify(feed.topics.length, "topic") : ""} - {feed.authors ? quantify(feed.authors.length, "author") : ""} - {feed.relays ? quantify(feed.relays.length, "relay") : ""} -

diff --git a/src/app/shared/ListSummary.svelte b/src/app/shared/ListSummary.svelte new file mode 100644 index 00000000..e2f32b73 --- /dev/null +++ b/src/app/shared/ListSummary.svelte @@ -0,0 +1,24 @@ + + +

+ {summary} + {relays.length > 0 ? `on ${quantify(relays.length, "relay")}` : ""} +

diff --git a/src/app/shared/NoteReply.svelte b/src/app/shared/NoteReply.svelte index a725497a..ccd0d4bc 100644 --- a/src/app/shared/NoteReply.svelte +++ b/src/app/shared/NoteReply.svelte @@ -99,7 +99,7 @@ class="note-reply relative z-10 my-2 flex flex-col gap-1" bind:this={container} on:click|stopPropagation> -
+
-
- - diff --git a/src/app/views/FeedList.svelte b/src/app/views/FeedList.svelte deleted file mode 100644 index d4c2a2e1..00000000 --- a/src/app/views/FeedList.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - - -
- Custom Feeds - - Feed - -
-

- You custom feeds are listed below. You can create new custom feeds by handing using the "add - feed" button, or by clicking the icon that appears throughout - Coracle. -

- {#each $feeds as feed (feed.name)} -
- removeFeed(feed)} /> -
-
- {feed.name} - -
- editFeed(feed)}>Edit -
-
- {:else} -

You don't have any custom feeds yet.

- {/each} -
diff --git a/src/app/views/FeedSelect.svelte b/src/app/views/FeedSelect.svelte deleted file mode 100644 index ad433478..00000000 --- a/src/app/views/FeedSelect.svelte +++ /dev/null @@ -1,45 +0,0 @@ - - - -
- Select a Feed - selectFeed({})}> - Feed - -
-

- Select a feed to modify. The selected {label} will be added to it as an additional filter. -

- {#each $feeds as feed (feed.name)} - selectFeed(feed)}> - {feed.name} - - - {:else} -

You don't have any custom feeds yet.

- {/each} -
diff --git a/src/app/views/Feeds.svelte b/src/app/views/Feeds.svelte index 12aa70c6..7eb16e23 100644 --- a/src/app/views/Feeds.svelte +++ b/src/app/views/Feeds.svelte @@ -1,7 +1,7 @@ + +
+ + {values.id ? "Edit" : "Add"} list +
+
+ Name + +

+ Lists are identified by their name, so this has to be unique. +

+
+
+ Topics and People + +
+ {#if item[0] === "p"} + {displayPerson(getPersonWithFallback(item[1]))} + {:else} + #{item[1]} + {/if} +
+
+

Type "@" to look for people, and "#" to look for topics.

+
+
+ Relays + +
+ {displayRelay({url: item[1]})} +
+
+

+ Select which relays to limit this list to. If you leave this blank, your default relays + will be used. +

+
+ +
+
+
diff --git a/src/app/views/ListList.svelte b/src/app/views/ListList.svelte new file mode 100644 index 00000000..55ae5d5a --- /dev/null +++ b/src/app/views/ListList.svelte @@ -0,0 +1,57 @@ + + + +
+ Your Lists + + List + +
+

+ Lists allow you to group people and topics to create custom feeds. You can create new lists by + handing using the " List" button above, or by clicking the + icon that appears throughout Coracle. +

+ {#each $lists as e (e.id)} + {@const meta = Tags.from(e).asMeta()} +
+ removeList(e)} /> +
+
+ {meta.d} + +
+ editList(e)}>Edit +
+
+ {:else} +

You don't have any lists yet.

+ {/each} +
diff --git a/src/app/views/ListSelect.svelte b/src/app/views/ListSelect.svelte new file mode 100644 index 00000000..312cfa1e --- /dev/null +++ b/src/app/views/ListSelect.svelte @@ -0,0 +1,43 @@ + + + +
+ Select a List + selectlist({})}> + List + +
+

+ Select a list to modify. The selected {label} will be added to it as an additional filter. +

+ {#each $lists as e (e.id)} + {@const meta = Tags.from(e).asMeta()} + selectlist(e)}> + {meta.d} + + + {:else} +

You don't have any custom lists yet.

+ {/each} +
diff --git a/src/app/views/PersonFeed.svelte b/src/app/views/PersonFeed.svelte index b0382c33..a4e0b143 100644 --- a/src/app/views/PersonFeed.svelte +++ b/src/app/views/PersonFeed.svelte @@ -1,6 +1,8 @@ +
+ -
+
diff --git a/src/partials/Chip.svelte b/src/partials/Chip.svelte index c4cec2eb..0ddab427 100644 --- a/src/partials/Chip.svelte +++ b/src/partials/Chip.svelte @@ -11,7 +11,7 @@
-
diff --git a/src/partials/MultiSelect.svelte b/src/partials/MultiSelect.svelte index 559d72d5..1c5744a8 100644 --- a/src/partials/MultiSelect.svelte +++ b/src/partials/MultiSelect.svelte @@ -20,7 +20,7 @@ } const select = item => { - value = value.concat(item) + value = value.concat([item]) term = "" } @@ -45,10 +45,6 @@ } } - if (!term && event.key === "Backspace") { - value = value.slice(0, -1) - } - if (suggestions?.get() && event.code === "ArrowUp") { event.preventDefault() suggestions.prev() diff --git a/src/partials/state.ts b/src/partials/state.ts index 7f59c9f4..f21a43fa 100644 --- a/src/partials/state.ts +++ b/src/partials/state.ts @@ -48,22 +48,30 @@ export const openModals = writable(0) export const modal = { stack: new WritableList([]) as WritableList, - sync: $stack => { + sync: ($stack, opts) => { const hash = $stack.length > 0 ? `#m=${$stack.length}` : "" - navigate(window.location.pathname + hash) + navigate(window.location.pathname + hash, opts) return $stack }, - push: data => modal.stack.update($stack => modal.sync($stack.concat(data))), - pop: () => modal.stack.update($stack => modal.sync($stack.slice(0, -1))), - clear: async () => { + push(data) { + modal.stack.update($stack => modal.sync($stack.concat(data))) + }, + async pop() { + modal.stack.update($stack => modal.sync($stack.slice(0, -1))) + await sleep(100) + }, + async replace(data) { + await modal.pop() + modal.push(data) + }, + async clear() { const stackSize = (get(modal.stack) as any).length // Reverse history so the back button doesn't bring our modal back up for (let i = 0; i < stackSize; i++) { - history.back() - await sleep(100) + await modal.pop() } }, } diff --git a/src/util/nostr.ts b/src/util/nostr.ts index 9021c7da..b7a91295 100644 --- a/src/util/nostr.ts +++ b/src/util/nostr.ts @@ -40,6 +40,9 @@ export class Tags { asMeta() { return fromPairs(this.tags) } + getMeta(k) { + return this.type(k).values().first() + } values() { return new Tags(this.tags.map(t => t[1])) } @@ -47,7 +50,9 @@ export class Tags { return new Tags(this.tags.filter(f)) } type(type) { - return new Tags(this.tags.filter(t => t[0] === type)) + const types = ensurePlural(type) + + return new Tags(this.tags.filter(t => types.includes(t[0]))) } equals(value) { return new Tags(this.tags.filter(t => t[1] === value)) diff --git a/src/util/types.ts b/src/util/types.ts index 648d99f5..575ba009 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -37,11 +37,3 @@ export type Room = { about?: string picture?: string } - -export type CustomFeed = { - id: string - name: string - authors?: "follows" | "network" | Array - topics?: Array - relays?: Array -}