Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit bdba677
Author: 老J <[email protected]>
Date:   Wed Jul 31 13:27:04 2024 +0800

    v1.4.0

commit c7e656b
Author: 老J <[email protected]>
Date:   Wed Jul 31 13:24:39 2024 +0800

    Catch the error when using the items

commit 9c18d82
Author: 老J <[email protected]>
Date:   Thu Jul 25 14:25:27 2024 +0800

    Fix error on Safari: Failed to load resource: The operation couldn’t be completed. (WebKitBlobResource error 1.)

commit 69838c2
Author: 老J <[email protected]>
Date:   Tue Jul 23 19:48:32 2024 +0800

    Empty history.

commit fe3ead9
Author: 老J <[email protected]>
Date:   Sun Jul 21 17:48:12 2024 +0800

    Set the history item. to file link area.

commit d95e654
Author: 老J <[email protected]>
Date:   Sun Jul 21 11:11:36 2024 +0800

    Write history items to clipboard.

commit 1b0d054
Author: 老J <[email protected]>
Date:   Fri Jul 19 10:32:09 2024 +0800

    Pin/unpin history items.

commit 07ee495
Author: 老J <[email protected]>
Date:   Thu Jul 18 19:53:34 2024 +0800

    Delete item from history IndexedDB

commit 592d72e
Author: 老J <[email protected]>
Date:   Thu Jul 18 08:57:28 2024 +0800

    Replace the group buttons with the ellipsis button.

commit d72f1fe
Author: 老J <[email protected]>
Date:   Wed Jul 17 18:42:17 2024 +0800

    Display history items from IndexedDB

commit 9eb3cf2
Author: 老J <[email protected]>
Date:   Tue Jul 16 20:10:22 2024 +0800

    Add history item to IndexedDB

commit 698831f
Author: 老J <[email protected]>
Date:   Fri Jul 12 15:55:54 2024 +0800

    Put clipboard data into indexedDB.

commit 05cb4f5
Author: 老J <[email protected]>
Date:   Tue Jul 9 17:24:11 2024 +0800

    Made some adjustments to the page.

commit 5510b8d
Author: 老J <[email protected]>
Date:   Tue Jul 9 16:27:27 2024 +0800

    Add static pages of clipboard history feature.
  • Loading branch information
llaoj committed Jul 31, 2024
1 parent 74d6417 commit c6174ba
Show file tree
Hide file tree
Showing 16 changed files with 640 additions and 210 deletions.
2 changes: 2 additions & 0 deletions frontend/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import SyncClipboard from "@/components/sync-clipboard";
import Notice from "@/components/notice";
import Footer from "@/components/footer";
import { NextIntlClientProvider, useMessages } from "next-intl";
import moment from "moment/min/moment-with-locales";

export default function Home({
params: { locale },
}: {
params: { locale: string };
}) {
moment.locale(locale == "zh" ? "zh-cn" : "en");
const messages = useMessages();
return (
<div className="min-h-screen flex flex-col items-center justify-between mx-auto max-w-5xl">
Expand Down
29 changes: 29 additions & 0 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,32 @@ body {
padding-right: 0.3rem;
margin-left: -1.75rem;
}

.logbox ::-webkit-scrollbar-track {
background: var(--fallback-n, oklch(var(--n)));
}

.logbox ::-webkit-scrollbar-thumb {
background: var(--fallback-b3, oklch(var(--b3)));
}

.logbox ::-webkit-scrollbar-thumb:hover {
background: var(--fallback-b1, oklch(var(--b1)));
}

::-webkit-scrollbar {
width: 5px;
}

::-webkit-scrollbar-track {
background: var(--fallback-b2, oklch(var(--b2)));
}

::-webkit-scrollbar-thumb {
background: var(--fallback-b3, oklch(var(--b3)));
border-radius: 5px;
}

::-webkit-scrollbar-thumb:hover {
background: var(--fallback-bc, oklch(var(--bc)));
}
190 changes: 190 additions & 0 deletions frontend/components/history-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { useTranslations } from "next-intl";
import {
EllipsisHorizontalIcon,
LockClosedIcon,
} from "@heroicons/react/24/solid";
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import { HistoryItemEntity } from "@/models/history";
import moment from "moment/min/moment-with-locales";
import { db } from "@/models/db";
import { Log, LogLevel } from "@/lib/log";
import { clipboardWriteBlob, clipboardWriteBlobPromise } from "@/lib/clipboard";
import { browserName } from "react-device-detect";
import { FileInfo } from "@/lib/clipboard";

export default function HistoryItem({
item,
addLog,
updateFileLink,
}: {
item: HistoryItemEntity;
addLog: (log: Log) => void;
updateFileLink: (fileInfo: FileInfo) => void;
}) {
const t = useTranslations("SyncClipboard");
const ulRef = useRef<HTMLUListElement>(null);

const [text, setText] = useState<string>("");
useEffect(() => {
setText("");
if (item.type == "text") {
const parseText = async () => {
if (item.dataArrayBuffer) {
setText(
await new Blob([item.dataArrayBuffer], {
type: item.dataType,
}).text(),
);
}
};
parseText();
}
}, [item]);

return (
<tr>
<td className="w-4 p-2 opacity-50">
{item.pin == "true" ? (
<LockClosedIcon className="w-4 h-4" />
) : (
<span className="text-sm">{item.index}</span>
)}
</td>
<td className="h-14 p-2 w-auto">
{item.type == "text" && (
<p className="line-clamp-1 opacity-70 break-all">{text}</p>
)}
{item.type == "screenshot" && (
<div className="relative h-full">
<Image
className="object-left object-contain"
src={
item.dataArrayBuffer
? (window.URL || window.webkitURL).createObjectURL(
new Blob([item.dataArrayBuffer], { type: item.dataType }),
)
: ""
}
alt="user's screenshot"
fill
/>
</div>
)}
{item.type == "file" && (
<p className="line-clamp-1 opacity-70 break-all">
{"[" + t("file") + "]" + item.fileName}
</p>
)}
</td>
<td className="w-min pr-1 whitespace-pre p-2 text-xs text-nowrap break-keep opacity-50 text-right">
{moment(item.createdAt).fromNow()}
</td>
<td className="w-9 px-0">
<div className="dropdown dropdown-end">
<div tabIndex={0} role="button" className="btn btn-ghost btn-xs">
<EllipsisHorizontalIcon className="h-4 w-4" />
</div>
<ul
tabIndex={0}
className="dropdown-content menu bg-base-100 rounded-box z-[1] w-28 p-2 shadow-lg border border-base-300"
ref={ulRef}
>
<li>
<a
className="hover:bg-base-300 opacity-70"
onClick={async () => {
try {
ulRef.current && ulRef.current.blur();
if (item.type == "file") {
updateFileLink({
fileName: item.fileName ?? "",
fileURL: (
window.URL || window.webkitURL
).createObjectURL(item.data),
});
addLog({
message: t("logs.autoDownload"),
level: LogLevel.Success,
});
return;
}

if (item.type == "text" || item.type == "screenshot") {
if (browserName.includes("Safari")) {
await clipboardWriteBlobPromise(item.data);
addLog({
message: t("logs.writeSuccess"),
level: LogLevel.Success,
});
return;
}

await clipboardWriteBlob(item.data);
addLog({
message: t("logs.writeSuccess"),
level: LogLevel.Success,
});
return;
}
} catch (err) {
addLog({ message: String(err), level: LogLevel.Error });
}
}}
>
{t("history.use")}
</a>
</li>
{item.pin == "true" && (
<li>
<a
className="hover:bg-base-300 opacity-70"
onClick={() => {
db.history.update(item, { pin: "false" });
}}
>
Unpin
</a>
</li>
)}
{item.pin == "false" && (
<li>
<a
className="hover:bg-base-300 opacity-70"
onClick={async () => {
const count = await db.history
.where("pin")
.equals("true")
.count();
if (count >= 10) {
ulRef.current && ulRef.current.blur();
addLog({
message: t("logs.pinLimit"),
level: LogLevel.Warn,
});
return;
}
db.history.update(item, { pin: "true" });
}}
>
Pin
</a>
</li>
)}
<li>
<a
className="hover:bg-base-300 opacity-70"
onClick={() => {
item.createdAt && db.history.delete(item.createdAt);
ulRef.current && ulRef.current.blur();
}}
>
{t("history.delete")}
</a>
</li>
</ul>
</div>
</td>
</tr>
);
}
58 changes: 58 additions & 0 deletions frontend/components/history.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useTranslations } from "next-intl";
import HistoryItem from "./history-item";
import { useLiveQuery } from "dexie-react-hooks";
import { db } from "@/models/db";
import { Log } from "@/lib/log";
import { FileInfo } from "@/lib/clipboard";

export default function History({
addLog,
updateFileLink,
}: {
addLog: (log: Log) => void;
updateFileLink: (fileInfo: FileInfo) => void;
}) {
const t = useTranslations("SyncClipboard.history");
const items = useLiveQuery(() => db.history.reverse().toArray());
if (!items) return null;

return (
<div className="pb-4">
<div className="pb-2 text-base font-bold">{t("title")}</div>
<div className="pb-2 text-sm opacity-70">{t("subTitle")}</div>
<div className="bg-base-100 rounded-box mb-2 px-2 border border-base-300">
<table className="table">
<tbody>
{items.length == 0 && (
<tr>
<td className="w-4 p-2 opacity-50 text-center">{t("empty")}</td>
</tr>
)}
{items.map(
(item) =>
item.pin == "true" && (
<HistoryItem
key={item.createdAt}
item={item}
addLog={addLog}
updateFileLink={updateFileLink}
/>
),
)}
{items.map(
(item) =>
item.pin == "false" && (
<HistoryItem
key={item.createdAt}
item={item}
addLog={addLog}
updateFileLink={updateFileLink}
/>
),
)}
</tbody>
</table>
</div>
</div>
);
}
8 changes: 4 additions & 4 deletions frontend/components/log-box.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Log, Level } from "@/lib/log";
import { Log, LogLevel } from "@/lib/log";
import { useRef, useEffect } from "react";

export default function LogBox({ logs }: { logs: Log[] }) {
Expand All @@ -13,13 +13,13 @@ export default function LogBox({ logs }: { logs: Log[] }) {
const listItems = logs.map((log, index) => {
let level;
switch (log.level) {
case Level.Warn:
case LogLevel.Warn:
level = "text-warning";
break;
case Level.Success:
case LogLevel.Success:
level = "text-green-600";
break;
case Level.Error:
case LogLevel.Error:
level = "text-rose-600";
break;
default:
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/quick-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function QuickInput({
/>
<div className="flex justify-between items-center text-sm select-none opacity-70 peer-checked/showTextarea:pb-2 cursor-pointer">
<div>
<span className="font-bold">{t("quickInput.title")}:</span>
<span className="font-bold">{t("quickInput.title")}</span>
<span>{" " + t("quickInput.subTitle")}</span>
{isDesktop && (
<span className="ml-1 text-xs opacity-50">
Expand Down
Loading

0 comments on commit c6174ba

Please sign in to comment.