Merge pull request #7294 from 01zulfi/webclipper/bookmark

webclipper: add bookmark option
This commit is contained in:
Abdullah Atta
2025-10-27 15:19:07 +05:00
committed by GitHub
5 changed files with 110 additions and 63 deletions

View File

@@ -73,7 +73,11 @@ export class WebExtensionServer implements Server {
async saveClip(clip: Clip) {
let clipContent = "";
if (clip.mode === "simplified" || clip.mode === "screenshot") {
if (
clip.mode === "simplified" ||
clip.mode === "screenshot" ||
clip.mode === "bookmark"
) {
clipContent += clip.data;
} else {
const clippedFile = new File(
@@ -105,11 +109,17 @@ export class WebExtensionServer implements Server {
if (isCipher(content)) return;
content += clipContent;
content += h("div", [
h("hr"),
h("p", ["Clipped from ", h("a", [clip.title], { href: clip.url })]),
h("p", [`Date clipped: ${getFormattedDate(Date.now())}`])
]).innerHTML;
content +=
clip.mode === "bookmark"
? h("div", [
h("p", [`Date bookmarked: ${getFormattedDate(Date.now())}`]),
h("hr")
]).innerHTML
: h("div", [
h("hr"),
h("p", ["Clipped from ", h("a", [clip.title], { href: clip.url })]),
h("p", [`Date clipped: ${getFormattedDate(Date.now())}`])
]).innerHTML;
const id = await db.notes.add({
id: note?.id,

View File

@@ -73,6 +73,10 @@ The `Selected nodes` mode allows you to select exactly which nodes you want to c
The clipping mode controls how the final clip should look.
### Bookmark
`Bookmark` mode saves only the URL of the page along with the title. It is best suited to save pages for later reading/reference.
### Simplified
`Simplified` mode doesn't include any styles. It is best suited for long-form content such as articles & blogs. All clips in `Simplified` mode are saved directly as is i.e. they do not appear as web clip embeds in the Notesnook editor.

View File

@@ -21,7 +21,7 @@ import { ThemeDefinition } from "@notesnook/theme";
export type ClipArea = "full-page" | "visible" | "selection" | "article";
export type ClipMode = "simplified" | "screenshot" | "complete";
export type ClipMode = "bookmark" | "simplified" | "screenshot" | "complete";
export type User = {
email?: string;

View File

@@ -57,6 +57,7 @@ export const Icons = {
visible: mdiViewDayOutline,
selection: mdiCursorDefaultClickOutline,
bookmark: mdiBookmarkOutline,
simplified: mdiTextBoxOutline,
screenshot: mdiFitToScreenOutline,
complete: mdiViewDashboardOutline,

View File

@@ -68,6 +68,11 @@ const clipAreas: { name: string; id: ClipArea; icon: string }[] = [
const clipModes: { name: string; id: ClipMode; icon: string; pro?: boolean }[] =
[
{
name: "Bookmark",
id: "bookmark",
icon: Icons.bookmark
},
{
name: "Simplified",
id: "simplified",
@@ -166,7 +171,7 @@ export function Main() {
}, [settings]);
async function startClip() {
if (!clipArea || !clipMode) return;
if (!clipArea || !clipMode || clipMode === "bookmark") return;
try {
setError(undefined);
@@ -268,7 +273,11 @@ export function Main() {
setClipperState(ClipperState.Idle);
setClipArea(item.id);
}}
disabled={isClipping || clipperState === ClipperState.Clipped}
disabled={
isClipping ||
clipperState === ClipperState.Clipped ||
clipMode === "bookmark"
}
sx={{
display: "flex",
borderRadius: "default",
@@ -342,55 +351,58 @@ export function Main() {
</Button>
))}
{clipData && clipData.data && !isClipping && (
<Flex sx={{ gap: 1, justifyContent: "space-between" }}>
<Text
variant="body"
sx={{
flex: 1,
mt: 1,
bg: "shade",
color: "accent",
p: 1,
border: "1px solid var(--accent)",
borderRadius: "default",
cursor: "pointer",
":hover": {
filter: "brightness(80%)"
}
}}
onClick={async () => {
const winUrl = URL.createObjectURL(
new Blob(["\ufeff", clipData.data], { type: "text/html" })
);
await browser.windows.create({
url: winUrl
});
}}
>
Clip done. Click here to preview.
</Text>
<Text
variant="body"
sx={{
mt: 1,
bg: "background-secondary",
p: 1,
borderRadius: "default",
cursor: "pointer",
":hover": {
filter: "brightness(80%)"
}
}}
onClick={async () => {
setClipData(undefined);
setClipperState(ClipperState.Idle);
}}
>
Discard
</Text>
</Flex>
)}
{clipMode !== "bookmark" &&
clipData &&
clipData.data &&
!isClipping && (
<Flex sx={{ gap: 1, justifyContent: "space-between" }}>
<Text
variant="body"
sx={{
flex: 1,
mt: 1,
bg: "shade",
color: "accent",
p: 1,
border: "1px solid var(--accent)",
borderRadius: "default",
cursor: "pointer",
":hover": {
filter: "brightness(80%)"
}
}}
onClick={async () => {
const winUrl = URL.createObjectURL(
new Blob(["\ufeff", clipData.data], { type: "text/html" })
);
await browser.windows.create({
url: winUrl
});
}}
>
Clip done. Click here to preview.
</Text>
<Text
variant="body"
sx={{
mt: 1,
bg: "background-secondary",
p: 1,
borderRadius: "default",
cursor: "pointer",
":hover": {
filter: "brightness(80%)"
}
}}
onClick={async () => {
setClipData(undefined);
setClipperState(ClipperState.Idle);
}}
>
Discard
</Text>
</Flex>
)}
{error && (
<Text
@@ -454,14 +466,24 @@ export function Main() {
disabled={isClipping}
onClick={async () => {
if (
clipperState === ClipperState.Idle ||
clipperState === ClipperState.Error
clipMode !== "bookmark" &&
(clipperState === ClipperState.Idle ||
clipperState === ClipperState.Error)
) {
await startClip();
return;
}
if (!clipData || !title || !clipArea || !clipMode || !url) return;
if (!title || !clipArea || !clipMode || !url) return;
const data =
clipMode === "bookmark"
? {
data: createBookmark(url, title)
}
: clipData;
if (!data) return;
const notesnook = await connectApi(false);
if (!notesnook) {
@@ -476,7 +498,7 @@ export function Main() {
note,
refs,
pageTitle: pageTitle.current,
...clipData
...data
});
setClipData(undefined);
@@ -492,7 +514,9 @@ export function Main() {
window.close();
}}
>
{clipperButtonLabelMap[clipperState]}
{clipMode === "bookmark"
? "Save bookmark"
: clipperButtonLabelMap[clipperState]}
</Button>
<Flex
@@ -567,3 +591,11 @@ export async function clip(
settings
});
}
function createBookmark(url: string, title: string) {
const a = document.createElement("a");
a.setAttribute("href", url);
a.setAttribute("title", title);
a.innerText = title;
return a.outerHTML;
}