mirror of
https://github.com/edufeed-org/educards.git
synced 2025-12-07 23:34:34 +00:00
Forking of boards, better routing, lots of other stuff
This commit is contained in:
parent
c4bde3e147
commit
08ad305cc2
7 changed files with 165 additions and 64 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue