Forking of boards, better routing, lots of other stuff

This commit is contained in:
@s.roertgen 2025-04-15 15:11:21 +02:00
parent c4bde3e147
commit 08ad305cc2
7 changed files with 165 additions and 64 deletions

View file

@ -4,10 +4,10 @@
import { dndzone } from 'svelte-dnd-action';
import AddColumnModal from './AddColumnModal.svelte';
import AddCardModal from './AddCardModal.svelte';
import { cardsForColumn, columnsForBoard, selectedColumn, currentBoard } from '$lib/db';
import { publishBoard, publishCards } from '$lib/ndk';
import { cardsForColumn, columnsForBoard, selectedColumn, currentBoard, user } from '$lib/db';
import { eventTitle, publishBoard, publishCards } from '$lib/ndk';
export let boardId;
export let boardAddress;
let addColumnModal;
let addCardModal;
@ -23,7 +23,6 @@
col.items = cardsForColumn(col.id);
return col;
});
$: console.log('items', items);
const flipDurationMs = 200;
@ -62,8 +61,12 @@
}
</script>
<button class="btn" on:click={openColumnModal}>Add column</button>
<div class="flex flex-row items-center justify-between">
<h1 class="ml-5 text-lg">{eventTitle($currentBoard)}</h1>
{#if $user && $user.pubkey === $currentBoard.pubkey}
<button class="btn mr-5" on:click={openColumnModal}>Add column</button>
{/if}
</div>
<section
class="board flex flex-nowrap"
use:dndzone={{ items, flipDurationMs, type: 'columns' }}
@ -72,17 +75,25 @@
>
{#each items as column (column.id)}
<div class="column" animate:flip={{ duration: flipDurationMs }}>
<div class="column-title">{column.dTag}</div>
<button class="btn" on:click={() => deleteColumn(column.id)}>Delete Column</button>
<div>
<button
class="btn"
on:click={() => {
// set selected columns
$selectedColumn = column.dTag;
openCardModal();
}}>Add Card</button
>
<div class="flex">
<div class="column-title">{column.dTag}</div>
{#if $user && $user.pubkey === $currentBoard.pubkey}
<button class="btn btn-error ml-auto mr-0" on:click={() => deleteColumn(column.id)}
>🗑️</button
>
{/if}
</div>
<div class="flex">
{#if $user && $user.pubkey === $currentBoard.pubkey}
<button
class="btn mx-auto"
on:click={() => {
// set selected columns
$selectedColumn = column.dTag;
openCardModal();
}}>Add Card</button
>
{/if}
</div>
<div
class="column-content"

View file

@ -1,16 +1,28 @@
<script>
import { setBoard } from '$lib/db';
import { eventTitle } from '$lib/ndk';
import { setBoard, user, userBoards } from '$lib/db';
import { eventTitle, forkBoard, deleteBoard } from '$lib/ndk';
export let board;
</script>
<div class="card w-96 bg-base-100 shadow-xl">
<div class="card w-96 flex-none bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">{eventTitle(board)}</h2>
<div class="card-actions justify-end">
<a onclick={() => setBoard(board)} href={`/board/${board.dTag}`} class="btn btn-primary"
>Open Board</a
{#if $user && board.pubkey !== $user?.pubkey}
{#if $userBoards.map((e) => e.dTag).includes(board.dTag)}
<p class="text-info">Already forked</p>
{:else}
<button onclick={() => forkBoard(board)} class="btn btn-success">Fork</button>
{/if}
{/if}
<a
onclick={() => setBoard(board)}
href={`/board/${board.tagAddress()}`}
class="btn btn-primary">Open Board</a
>
{#if $user && $userBoards.map((e) => e.dTag).includes(board.dTag)}
<button onclick={() => deleteBoard(board)} class="btn btn-error">🗑️</button>
{/if}
</div>
</div>
</div>

View file

@ -1,5 +1,5 @@
<script>
import { db } from '$lib/db';
import { db, user } from '$lib/db';
import { login } from '$lib/ndk';
</script>
@ -9,21 +9,18 @@
</div>
<div class="flex-none gap-2">
<div class="form-control">
<input type="text" placeholder="Search" class="input input-bordered w-24 md:w-auto" />
<!-- <input type="text" placeholder="Search" class="input input-bordered w-24 md:w-auto" /> -->
</div>
{#if $db.user}
{#if $user}
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar">
<div tabindex="0" role="button" class="avatar btn btn-circle btn-ghost">
<div class="w-10 rounded-full">
<img
alt="Tailwind CSS Navbar component"
src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp"
/>
<img alt="" src={`https://robohash.org/${$user.pubkey}`} />
</div>
</div>
<ul
tabindex="0"
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow"
class="menu dropdown-content menu-sm z-[1] mt-3 w-52 rounded-box bg-base-100 p-2 shadow"
>
<li>
<a class="justify-between">
@ -40,7 +37,7 @@
<button class="btn">Login</button>
<ul
tabindex="0"
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow"
class="menu dropdown-content menu-sm z-[1] mt-3 w-52 rounded-box bg-base-100 p-2 shadow"
>
<li><a onclick={() => login('browser-extension')}>Browser Extension</a></li>
</ul>

View file

@ -3,28 +3,39 @@ import NDK from '@nostr-dev-kit/ndk';
import { columnsDsFromBoard } from './ndk';
export const events = writable([]);
export const boards = derived(events, ($events) => {
const allBoards = $events.filter((e) => e.kind === 30043);
const deduped = deduplicateKeepMostRecent(allBoards);
return deduped;
});
export const currentBoardId = writable('');
export const currentBoard = derived(currentBoardId, ($currentBoardId) => {
return get(events).find((e) => e.dTag === $currentBoardId);
export const currentBoardAddress = writable('');
export const currentBoard = derived(currentBoardAddress, ($currentBoardAddress) => {
console.log('looking for current board');
const board = get(events).find((e) => e.tagAddress() === $currentBoardAddress);
console.log(board);
return board;
});
export const columns = derived(events, ($events) => {
const allColumns = $events.filter((e) => e.kind === 30044);
const deduped = deduplicateKeepMostRecent(allColumns);
return deduped;
const sorted = deduped.sort((a, b) => {
return b.created_at - a.created_at;
});
return sorted;
});
export const columnsForBoard = derived(
[events, boards, columns, currentBoardId],
([$events, $boards, $columns, $currentBoardId]) => {
let currentBoard = $boards.find((e) => e.dTag === $currentBoardId);
[events, boards, columns, currentBoardAddress],
([$events, $boards, $columns, $currentBoardAddress]) => {
let currentBoard = $boards.find((e) => e.tagAddress() === $currentBoardAddress);
if (currentBoard === undefined) {
return [];
}
let boardColumnIds = columnsDsFromBoard(currentBoard);
const cols = boardColumnIds.map((d) => {
return $columns.find((c) => c.tagAddress() === d);
});
if (cols.includes(undefined)) return [];
return cols;
}
);
@ -49,6 +60,13 @@ export const cardsForColumn = (columnId) => {
}
};
export const user = writable(null);
export const userBoards = derived([user, boards], ([$user, $boards]) => {
const newUserBoards = $boards.filter((b) => b.pubkey === $user.pubkey);
return newUserBoards;
});
export const db = writable({
user: null,
ndk: await initNDK(),
@ -60,6 +78,20 @@ async function initNDK() {
explicitRelayUrls: ['ws://localhost:10547']
});
await ndk.connect();
const sub = ndk.subscribe({ kinds: [30043, 30044, 30045] }); // listen for boards, columns indexes
sub.on('event', async (event) => {
events.update((events) => [...events, event]);
});
const subDelete = ndk.subscribe({ kinds: [5] });
subDelete.on('event', async (event) => {
const toBeDeletedDs = event.getMatchingTags('a').map((e) => e[1].split(':')[2]);
events.update((events) => {
return events.filter((e) => !toBeDeletedDs.includes(e.dTag));
});
});
return ndk;
}

View file

@ -1,6 +1,13 @@
import NDK, { NDKNip07Signer, NDKEvent } from '@nostr-dev-kit/ndk';
import { get } from 'svelte/store';
import { db, events, currentBoardId, currentBoard, selectedColumn } from '$lib/db';
import {
db,
events,
currentBoardAddress,
currentBoard,
selectedColumn,
user as userStore
} from '$lib/db';
export async function login(method) {
const nip07signer = new NDKNip07Signer();
@ -11,6 +18,8 @@ export async function login(method) {
case 'browser-extension': {
console.log('login with extension');
const user = await nip07signer.user();
userStore.set(user);
console.log('user', user);
db.update((db) => {
return { ...db, ndk, user };
});
@ -28,17 +37,21 @@ export function addBoard(board) {
export async function addColumn(column) {
const ndk = get(db).ndk;
const user = get(userStore);
const columnEvent = new NDKEvent(ndk, { kind: 30044, content: 'Column Event' });
columnEvent.tags = [['title', column.title]];
await columnEvent.publish();
// add column to Board
const existingBoard = await ndk.fetchEvent({
kinds: [30043],
authors: [columnEvent.pubkey],
'#d': [get(currentBoardId)]
});
existingBoard.tags.push(['a', `30044:${columnEvent.pubkey}:${columnEvent.dTag}`]);
const boardD = get(currentBoard).dTag;
console.log('board dtag', boardD);
// const existingBoard = await ndk.fetchEvent({
// kinds: [30043],
// authors: [user.pubkey],
// '#d': [get(currentBoard).dTag]
// });
const existingBoard = new NDKEvent(ndk, get(currentBoard));
existingBoard.tags.push(['a', `30044:${user.pubkey}:${columnEvent.dTag}`]);
existingBoard.publishReplaceable();
}
@ -49,7 +62,7 @@ export async function addCard(card) {
const columnEvent = await ndk.fetchEvent({
kinds: [30044],
authors: [cardEvent.pubkey],
// authors: [cardEvent.pubkey],
'#d': [get(selectedColumn)]
});
columnEvent.tags.push(['a', `${cardEvent.kind}:${cardEvent.pubkey}:${cardEvent.dTag}`]);
@ -126,8 +139,8 @@ export async function publishBoard(board) {
const ndk = get(db).ndk;
const existingBoard = await ndk.fetchEvent({
kinds: [30043],
authors: [board.pubkey],
'#d': [get(currentBoardId)]
// authors: [board.pubkey],
'#d': [get(currentBoardAddress)]
});
const columnIds = board.items.map((e) => e.dTag);
let tags = existingBoard.tags.filter((t) => t[0] !== 'a');
@ -142,7 +155,7 @@ export async function publishCards(column) {
const ndk = get(db).ndk;
const existingColumn = await ndk.fetchEvent({
kinds: [30044],
authors: [column.pubkey],
// authors: [column.pubkey],
ids: [column.id]
});
const cardIds = column.items.map((e) => e.dTag);
@ -153,3 +166,18 @@ export async function publishCards(column) {
existingColumn.tags = tags;
existingColumn.publishReplaceable();
}
export async function forkBoard(board) {
const ndk = get(db).ndk;
const userBoard = new NDKEvent(ndk, {
kind: board.kind,
content: board.content,
tags: board.tags
});
userBoard.publish();
}
export async function deleteBoard(board) {
const ndk = get(db).ndk;
const deletionEvent = await new NDKEvent(ndk, board).delete('user said so', true);
}

View file

@ -1,6 +1,6 @@
<script>
import { onMount } from 'svelte';
import { db, boards } from '$lib/db';
import { db, boards, user, userBoards } from '$lib/db';
import { getBoards } from '$lib/ndk';
import AddBoardModal from '$lib/components/AddBoardModal.svelte';
import BoardCard from '$lib/components/BoardCard.svelte';
@ -16,14 +16,35 @@
});
</script>
<button onclick={openModal}>Add Board</button>
<div class="flex w-full">
<button class="btn ml-auto mr-2" disabled={!$user} onclick={openModal}>Add Board</button>
</div>
<AddBoardModal bind:modalRef={addBoardModal} />
<h1>Boards</h1>
{#if $boards.length > 0}
<div class="flex flex-wrap gap-2">
{#each $boards as board (board.id)}
<BoardCard {board} />
{/each}
</div>
{/if}
<div class="flex w-full flex-col gap-2">
{#if $user}
<h1 class="mx-auto text-lg">Your Boards</h1>
<div class="grid grid-flow-col grid-rows-2 gap-2 overflow-x-auto">
{#key $userBoards}
{#each $userBoards as board (board.id)}
<BoardCard {board} />
{/each}
{/key}
</div>
{/if}
{#if $boards.length > 0}
<h1 class="mx-auto text-lg">Alle Boards</h1>
<div class="flex flex-wrap justify-start gap-2">
{#if $user}
{#each $boards.filter((e) => e.pubkey !== $user.pubkey) as board (board.id)}
<BoardCard {board} />
{/each}
{:else}
{#each $boards as board (board.id)}
<BoardCard {board} />
{/each}
{/if}
</div>
{/if}
</div>

View file

@ -1,10 +1,10 @@
<script>
import Board from '$lib/components/Board.svelte';
import { currentBoardId } from '$lib/db.js';
import { currentBoardAddress } from '$lib/db.js';
export let data;
console.log('data.id', data.id);
$currentBoardId = data.id;
console.log(data.id);
$currentBoardAddress = data.id;
</script>
<Board boardId={data.id} />
<Board boardAddress={data.id} />