Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4ccd7f1
chore: release v0.4.1-hotfix.3
goniszewski Sep 23, 2024
69192b3
fix(data-migration): early return if no categories have parents (#128)
goniszewski Sep 23, 2024
e9237a4
Merge branch 'main' into develop
goniszewski Sep 27, 2024
cd3fe87
Closes #130 (#131)
goniszewski Sep 27, 2024
141ea90
fix(database): use dynamic path for SQLite database file
goniszewski Sep 27, 2024
c6370e3
docs(readme): use single README file for latest/preview version
goniszewski Sep 27, 2024
c76c544
feat(ci): add manual deployment workflow and adjust tag conditions
goniszewski Sep 27, 2024
12e1f39
refactor(workflow): simplify manual-deploy GitHub Action
goniszewski Sep 27, 2024
1b054a0
fix(metadata): handle multiple image URLs in mainImageUrl field
goniszewski Sep 30, 2024
6eac509
fix: auth error handling (#144)
Sparkenstein Oct 31, 2024
c842b99
refactor(api): migrate Swagger UI to external documentation and enhan…
goniszewski Nov 6, 2024
11c2fda
chore: release v0.4.3
goniszewski Nov 6, 2024
c68232a
chore: resolve conflicts
goniszewski Nov 6, 2024
b39fda2
fix(docker): resolve issue with data directory permissions (#150)
goniszewski Nov 14, 2024
5e31e77
Fix issue #153: creation/update of root categories (#157)
gpoussel Dec 9, 2024
97965c2
Fix issue #145: bookmarks without images (#156)
gpoussel Dec 9, 2024
89fa893
chore: release v0.4.4
goniszewski Dec 9, 2024
42f777f
Merge branch 'main' into develop
goniszewski Dec 9, 2024
051cec7
Feat: import bookmarks (#139)
goniszewski Jan 16, 2025
461fc9d
chore: release v0.5.0-pre.1
goniszewski Jan 16, 2025
b5d338f
Resolves #133 (#162)
goniszewski Jan 30, 2025
8e3b761
chore: release v0.5.0-pre.2
goniszewski Jan 30, 2025
0b43e01
fix(build): resolve issue with lodash causing build fail
goniszewski Jan 30, 2025
f7b61be
fix(Dockerfile): remove grimoire user causing problems, limit layers …
goniszewski Feb 5, 2025
dfd53b0
fix(Dockerfile): ensure all packages are in sync
goniszewski Feb 5, 2025
c6dd371
chore: release v0.5.0 (#167)
goniszewski Feb 21, 2025
b80be8a
Merge branch 'main' into develop
goniszewski Feb 21, 2025
0f88018
chore: force version (#169)
goniszewski Feb 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
FROM oven/bun AS base
FROM oven/bun AS builder
LABEL maintainer="Grimoire Developers <[email protected]>"
LABEL description="Bookmark manager for the wizards"
LABEL org.opencontainers.image.source="https://github.com/goniszewski/grimoire"

RUN apt-get update && apt-get install -y python3 python3-pip wget build-essential && \
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
xz-utils python3 python3-pip wget build-essential && \
dpkg --configure -a && \
rm -rf /var/lib/apt/lists/* && \
bun i -g svelte-kit@latest
mkdir -p /etc/s6-overlay/s6-rc.d/grimoire /etc/s6-overlay/s6-rc.d/user/contents.d

RUN mkdir -p /app/data

ARG S6_OVERLAY_VERSION=3.1.6.2
ARG TARGETARCH=x86_64
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${TARGETARCH}.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \
tar -C / -Jxpf /tmp/s6-overlay-${TARGETARCH}.tar.xz && \
rm /tmp/s6-overlay-*xz

COPY docker/etc/s6-overlay /etc/s6-overlay/
RUN chmod +x /etc/s6-overlay/s6-rc.d/grimoire/run

ENV S6_KEEP_ENV=1 \
S6_SERVICES_GRACETIME=15000 \
S6_KILL_GRACETIME=10000 \
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
S6_SYNC_DISKS=1

RUN bun i -g svelte-kit@latest

RUN adduser --disabled-password --gecos '' grimoire
RUN mkdir -p /app/data && chown -R grimoire:grimoire /app/data && chmod 766 /app/data
WORKDIR /app

FROM base AS dependencies
FROM builder AS dependencies
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
RUN bun install --frozen-lockfile --production
RUN bun install --frozen-lockfile && \
bun install --frozen-lockfile --production

FROM base AS build
FROM builder AS build
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
RUN bun run svelte-kit sync
Expand All @@ -29,7 +54,8 @@ ENV NODE_ENV=production \
NODE_OPTIONS="--max-old-space-size=4096"
RUN bun --bun run build

FROM base AS release
FROM builder AS release

COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=build /app/build ./build
COPY --from=build /app/migrations ./migrations
Expand Down
Binary file modified bun.lockb
Binary file not shown.
9 changes: 9 additions & 0 deletions docker/etc/s6-overlay/s6-rc.d/grimoire/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/command/with-contenv bash

cd /app

# Run migrations first
/usr/local/bin/bun run run-migrations

# Start the application
exec /usr/local/bin/bun ./build/index.js
1 change: 1 addition & 0 deletions docker/etc/s6-overlay/s6-rc.d/grimoire/type
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
longrun
1 change: 1 addition & 0 deletions docker/etc/s6-overlay/s6-rc.d/user/contents.d/grimoire
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "grimoire",
"version": "0.4.4",
"version": "0.5.0",
"description": "Bookmark manager for the wizards 🧙",
"author": "Robert Goniszewski <[email protected]>",
"main": "./build/index.js",
Expand Down Expand Up @@ -78,20 +78,20 @@
"@tabler/icons-svelte": "^3.16.0",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/html-to-text": "^9.0.4",
"@types/lodash": "^4.17.7",
"@types/sanitize-html": "^2.13.0",
"adm-zip": "^0.5.16",
"chalk": "^5.3.0",
"daisyui": "^4.12.10",
"dotenv": "^16.4.5",
"drizzle-orm": "^0.33.0",
"es-toolkit": "^1.31.0",
"eslint-plugin-drizzle": "^0.2.3",
"express": "^4.21.0",
"fuse.js": "^7.0.0",
"html-to-text": "^9.0.5",
"joi": "^17.13.3",
"lodash": "^4.17.21",
"lucia": "^3.2.0",
"node-parse-bookmarks": "^1.0.3",
"sanitize-html": "^2.13.0",
"svelte-french-toast": "^1.2.0",
"svelte-select": "^5.8.3",
Expand Down
1 change: 1 addition & 0 deletions run-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

trap 'echo "Received SIGINT or SIGTERM. Exiting..." >&2; exit 1' SIGINT SIGTERM

bun --bun run run-migrations
bun --bun run dev

kill -- -$$
9 changes: 4 additions & 5 deletions src/lib/components/AddBookmarkForm/AddBookmarkForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { addBookmarkToSearchIndex } from '$lib/utils/search';
import { showToast } from '$lib/utils/show-toast';

import { IconInfoCircle } from '@tabler/icons-svelte';
import _ from 'lodash';
import { debounce } from 'es-toolkit';
import { onDestroy } from 'svelte';
import Select from 'svelte-select';
import { writable, type Writable } from 'svelte/store';
Expand Down Expand Up @@ -112,7 +112,7 @@ let error = '';
let warning = '';
const loading = writable(false);

const onGetMetadata = _.debounce(
const onGetMetadata = debounce(
async (event: Event) => {
const target = event.target as HTMLButtonElement;
const url = target.value;
Expand Down Expand Up @@ -188,9 +188,8 @@ const onGetMetadata = _.debounce(
},
1000,
{
leading: false,
trailing: true,
maxWait: 1000
signal: AbortSignal.timeout(5000),
edges: ['trailing']
}
);
</script>
Expand Down
60 changes: 60 additions & 0 deletions src/lib/components/BulkList/BulkList.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script lang="ts">
import { importBookmarkStore } from '$lib/stores/import-bookmarks.store';
import { type Readable } from 'svelte/store';
import BulkListItem from '../BulkListItem/BulkListItem.svelte';
import type { BookmarkEdit } from '$lib/types/Bookmark.type';

export let itemList: Readable<BookmarkEdit[]>;
export let isLoading: boolean;

const selectAllItems = ({ target }: Event) => {
if (target instanceof HTMLInputElement) {
importBookmarkStore.setSelectStatusForAll(target.checked);
}
};
</script>

<div class="flex flex-col gap-2">
<div class="max-h-[calc(100vh-16rem)] overflow-x-auto">
<table class="table table-pin-rows table-pin-cols table-xs">
<!-- head -->
<thead>
<tr>
<th>
<label>
<input type="checkbox" class="checkbox" on:change={selectAllItems} />
</label>
</th>
<th>URL</th>
<th>Title</th>
<th>Category</th>
<th></th>
</tr>
</thead>
<tbody>
{#each $itemList as item (item.id)}
<BulkListItem
id={item.id}
icon={item.icon}
url={item.url}
title={item.title}
category={item.category}
selected={item.selected}
isLoading={isLoading}
metadataFetched={!!item.domain}
metadata={item} />
{/each}
</tbody>
<!-- foot -->
<tfoot>
<tr>
<th></th>
<th>URL</th>
<th>Title</th>
<th>Category</th>
<th></th>
</tr>
</tfoot>
</table>
</div>
</div>
117 changes: 117 additions & 0 deletions src/lib/components/BulkListItem/BulkListItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script lang="ts">
import { editBookmarkStore } from '$lib/stores/edit-bookmark.store';
import { importBookmarkStore } from '$lib/stores/import-bookmarks.store';
import { createSlug } from '$lib/utils/create-slug';
import {
IconCircleDashedCheck,
IconExclamationCircle,
IconPhotoX,
IconStopwatch
} from '@tabler/icons-svelte';

export let id: number;
export let selected = false;
export let icon: string | null;
export let url: string;
export let title: string;
export let category: {
id?: number;
name: string;
};
export let isLoading: boolean;
export let metadataFetched: boolean;
export let metadata: any;

let urlObj = new URL(url);

const onEditItem = () => {
editBookmarkStore.set({
...metadata,
category: {
id: category.id,
name: category.name
}
});
};

const onRemoveItem = () => {
importBookmarkStore.removeItem(id);
};
</script>

<tr>
<th>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={selected}
on:change={() => importBookmarkStore.toggleSelectionForItem(id)} />
</label>
</th>
<td>
<div class="flex flex-col gap-1">
<div class="flex items-center gap-3">
<div class="avatar">
<div class="mask mask-squircle h-12 w-12">
{#if icon}
<img src={icon} alt="Icon" />
{:else}
<IconPhotoX class="m-2 h-8 w-8 opacity-80" />
{/if}
</div>
</div>
<div class="max-w-lg">
<div class="tooltip" data-tip={url}>
<a href={url} target="_blank" class="font-bold">
{urlObj.pathname !== '/'
? `${urlObj.hostname}/.../${urlObj.pathname.slice(-5)}`
: urlObj.hostname}
</a>
</div>
<div class="flex items-center gap-1 text-sm tracking-tight text-secondary">
{new URL(url).hostname.replace(/^www\./, '')}
{#if metadataFetched}
<div class="tooltip" data-tip="Metadata fetched">
<IconCircleDashedCheck class="h-4 w-4 text-success" />
</div>
{:else if isLoading}
<div class="tooltip" data-tip="Loading metadata">
<IconStopwatch class="h-4 w-4 text-warning" />
</div>
{:else}
<div class="tooltip" data-tip="Failed to fetch metadata">
<IconExclamationCircle class="h-4 w-4 text-error" />
</div>
{/if}
</div>
</div>
</div>
<div class="ml-2 flex gap-1 text-sm">
{#if metadata?.bookmarkTags?.length}
<span class="font-sans text-xs">Tags: </span>
{/if}
{#each metadata?.bookmarkTags || [] as tag (tag.value)}
<a href={`/tags/${createSlug(tag.value)}`} class="link font-sans text-xs">{tag.value}</a>
{/each}
</div>
</div>
</td>
<td class="max-w-xs">
<div class="tooltip" data-tip={title}>
<span title={title} class="line-clamp-2">{title}</span>
</div>
</td>
<td
><a
class="link hover:link-secondary"
href={`/categories/${createSlug(category.name)}`}
target="_blank">{category.name}</a
></td>
<th>
<button class="btn btn-ghost btn-xs text-secondary" on:click|preventDefault={onEditItem}
>edit</button>
<button class="btn btn-ghost btn-xs text-error" on:click|preventDefault={onRemoveItem}
>remove</button>
</th>
</tr>
Loading
Loading