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

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. 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
`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. `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 ClipArea = "full-page" | "visible" | "selection" | "article";
export type ClipMode = "simplified" | "screenshot" | "complete"; export type ClipMode = "bookmark" | "simplified" | "screenshot" | "complete";
export type User = { export type User = {
email?: string; email?: string;

View File

@@ -57,6 +57,7 @@ export const Icons = {
visible: mdiViewDayOutline, visible: mdiViewDayOutline,
selection: mdiCursorDefaultClickOutline, selection: mdiCursorDefaultClickOutline,
bookmark: mdiBookmarkOutline,
simplified: mdiTextBoxOutline, simplified: mdiTextBoxOutline,
screenshot: mdiFitToScreenOutline, screenshot: mdiFitToScreenOutline,
complete: mdiViewDashboardOutline, 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 }[] = const clipModes: { name: string; id: ClipMode; icon: string; pro?: boolean }[] =
[ [
{
name: "Bookmark",
id: "bookmark",
icon: Icons.bookmark
},
{ {
name: "Simplified", name: "Simplified",
id: "simplified", id: "simplified",
@@ -166,7 +171,7 @@ export function Main() {
}, [settings]); }, [settings]);
async function startClip() { async function startClip() {
if (!clipArea || !clipMode) return; if (!clipArea || !clipMode || clipMode === "bookmark") return;
try { try {
setError(undefined); setError(undefined);
@@ -268,7 +273,11 @@ export function Main() {
setClipperState(ClipperState.Idle); setClipperState(ClipperState.Idle);
setClipArea(item.id); setClipArea(item.id);
}} }}
disabled={isClipping || clipperState === ClipperState.Clipped} disabled={
isClipping ||
clipperState === ClipperState.Clipped ||
clipMode === "bookmark"
}
sx={{ sx={{
display: "flex", display: "flex",
borderRadius: "default", borderRadius: "default",
@@ -342,7 +351,10 @@ export function Main() {
</Button> </Button>
))} ))}
{clipData && clipData.data && !isClipping && ( {clipMode !== "bookmark" &&
clipData &&
clipData.data &&
!isClipping && (
<Flex sx={{ gap: 1, justifyContent: "space-between" }}> <Flex sx={{ gap: 1, justifyContent: "space-between" }}>
<Text <Text
variant="body" variant="body"
@@ -454,14 +466,24 @@ export function Main() {
disabled={isClipping} disabled={isClipping}
onClick={async () => { onClick={async () => {
if ( if (
clipperState === ClipperState.Idle || clipMode !== "bookmark" &&
clipperState === ClipperState.Error (clipperState === ClipperState.Idle ||
clipperState === ClipperState.Error)
) { ) {
await startClip(); await startClip();
return; 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); const notesnook = await connectApi(false);
if (!notesnook) { if (!notesnook) {
@@ -476,7 +498,7 @@ export function Main() {
note, note,
refs, refs,
pageTitle: pageTitle.current, pageTitle: pageTitle.current,
...clipData ...data
}); });
setClipData(undefined); setClipData(undefined);
@@ -492,7 +514,9 @@ export function Main() {
window.close(); window.close();
}} }}
> >
{clipperButtonLabelMap[clipperState]} {clipMode === "bookmark"
? "Save bookmark"
: clipperButtonLabelMap[clipperState]}
</Button> </Button>
<Flex <Flex
@@ -567,3 +591,11 @@ export async function clip(
settings 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;
}