mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-21 14:09:34 +01:00
mobile: clip dynamic websites
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -21,6 +21,12 @@
|
|||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100% !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
height: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|||||||
@@ -167,10 +167,9 @@ export const Editor = ({ onChange, onLoad }) => {
|
|||||||
}}
|
}}
|
||||||
nestedScrollEnabled
|
nestedScrollEnabled
|
||||||
javaScriptEnabled={true}
|
javaScriptEnabled={true}
|
||||||
focusable={true}
|
|
||||||
setSupportMultipleWindows={false}
|
setSupportMultipleWindows={false}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
scrollEnabled={false}
|
scrollEnabled={Platform.OS === "ios"}
|
||||||
keyboardDisplayRequiresUserAction={false}
|
keyboardDisplayRequiresUserAction={false}
|
||||||
cacheMode="LOAD_DEFAULT"
|
cacheMode="LOAD_DEFAULT"
|
||||||
cacheEnabled={true}
|
cacheEnabled={true}
|
||||||
|
|||||||
237
apps/mobile/share/fetch-webview.js
Normal file
237
apps/mobile/share/fetch-webview.js
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { createRef, useImperativeHandle, useRef, useState } from "react";
|
||||||
|
import WebView from "react-native-webview";
|
||||||
|
import { Config } from "./store";
|
||||||
|
import RNFetchBlob from "react-native-blob-util";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
|
export const fetchHandle = createRef();
|
||||||
|
export const HtmlLoadingWebViewAgent = React.memo(
|
||||||
|
() => {
|
||||||
|
const [source, setSource] = useState(null);
|
||||||
|
const [clipper, setClipper] = useState(null);
|
||||||
|
const loadHandler = useRef();
|
||||||
|
const htmlHandler = useRef();
|
||||||
|
const webview = useRef();
|
||||||
|
useImperativeHandle(
|
||||||
|
fetchHandle,
|
||||||
|
() => ({
|
||||||
|
processUrl: (url) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setSource(url);
|
||||||
|
console.log("processing...", url);
|
||||||
|
let resolved = false;
|
||||||
|
htmlHandler.current = (html) => {
|
||||||
|
if (resolved) return;
|
||||||
|
resolved = true;
|
||||||
|
setSource(null);
|
||||||
|
resolve(html);
|
||||||
|
};
|
||||||
|
loadHandler.current = (result) => {
|
||||||
|
if (resolved) return;
|
||||||
|
if (!result) {
|
||||||
|
resolved = true;
|
||||||
|
setSource(null);
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("loaded event fired");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const clipperPath =
|
||||||
|
Platform.OS === "ios"
|
||||||
|
? RNFetchBlob.fs.dirs.MainBundleDir +
|
||||||
|
"/extension.bundle/clipper.bundle.js"
|
||||||
|
: RNFetchBlob.fs.asset("clipper.bundle.js");
|
||||||
|
RNFetchBlob.fs.readFile(clipperPath, "utf8").then((clipper) => {
|
||||||
|
setClipper(clipper);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return !source || !clipper ? null : (
|
||||||
|
<WebView
|
||||||
|
ref={webview}
|
||||||
|
onLoad={() => {
|
||||||
|
console.log("Webview is loaded");
|
||||||
|
loadHandler.current?.(true);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
position: "absolute",
|
||||||
|
opacity: 0,
|
||||||
|
zIndex: -1
|
||||||
|
}}
|
||||||
|
pointerEvents="none"
|
||||||
|
onMessage={(event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.nativeEvent.data);
|
||||||
|
if (data && data.type === "html") {
|
||||||
|
console.log("message recieved page loaded");
|
||||||
|
htmlHandler.current?.(data.value);
|
||||||
|
} else {
|
||||||
|
if (data.type === "error") {
|
||||||
|
htmlHandler.current?.(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error handling webview message", e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
injectedJavaScript={`
|
||||||
|
${clipper}
|
||||||
|
window.onload = () => {
|
||||||
|
function postMessage(type, value) {
|
||||||
|
if (window.ReactNativeWebView) {
|
||||||
|
window.ReactNativeWebView.postMessage(
|
||||||
|
JSON.stringify({
|
||||||
|
type: type,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.Clipper.clipArticle(document, {
|
||||||
|
images: true,
|
||||||
|
corsProxy: false
|
||||||
|
}).then(result => {
|
||||||
|
postMessage("html", result);
|
||||||
|
}).catch(e => {
|
||||||
|
postMessage("error");
|
||||||
|
})
|
||||||
|
|
||||||
|
};`}
|
||||||
|
onError={() => {
|
||||||
|
console.log("Error loading page");
|
||||||
|
loadHandler.current?.();
|
||||||
|
}}
|
||||||
|
source={{
|
||||||
|
uri: source
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
);
|
||||||
|
HtmlLoadingWebViewAgent.displayName = "HtmlLoadingWebViewAgent";
|
||||||
|
|
||||||
|
const old = `
|
||||||
|
window.onload = () => {
|
||||||
|
// Function to convert relative URLs to absolute URLs
|
||||||
|
function fixRelativeUrls(baseUrl, elements, attribute) {
|
||||||
|
elements.forEach((element) => {
|
||||||
|
const relativeUrl = element.getAttribute(attribute);
|
||||||
|
if (relativeUrl) {
|
||||||
|
const absoluteUrl = new URL(relativeUrl, baseUrl).href;
|
||||||
|
element.setAttribute(attribute, absoluteUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to remove unnecessary attributes from elements
|
||||||
|
function removeUnnecessaryAttributes(elements) {
|
||||||
|
elements.forEach((element) => {
|
||||||
|
// Remove unnecessary attributes
|
||||||
|
const unnecessaryAttributes = ["class", "id", "style", "data-*"];
|
||||||
|
unnecessaryAttributes.forEach((attr) => element.removeAttribute(attr));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to exclude specific tags
|
||||||
|
function excludeTags(elements) {
|
||||||
|
elements.forEach((element) => {
|
||||||
|
element.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the HTML content and modify it
|
||||||
|
function extractAndModifyHtml() {
|
||||||
|
const baseUrl = window.location.href;
|
||||||
|
const htmlContent = document.documentElement.outerHTML;
|
||||||
|
|
||||||
|
// Exclude specific tags (e.g., styles, scripts, and others)
|
||||||
|
const tagsToExclude = [
|
||||||
|
"style",
|
||||||
|
"script",
|
||||||
|
"head",
|
||||||
|
"button",
|
||||||
|
"select",
|
||||||
|
"form",
|
||||||
|
"link",
|
||||||
|
"canvas",
|
||||||
|
"nav",
|
||||||
|
"svg",
|
||||||
|
"audio",
|
||||||
|
"video",
|
||||||
|
"iframe",
|
||||||
|
"object",
|
||||||
|
"input",
|
||||||
|
"textarea",
|
||||||
|
"footer",
|
||||||
|
"dialog"
|
||||||
|
];
|
||||||
|
const elementsToExclude = tagsToExclude
|
||||||
|
.map((tagName) => [...document.querySelectorAll(tagName)])
|
||||||
|
.flat();
|
||||||
|
excludeTags(elementsToExclude);
|
||||||
|
|
||||||
|
// Select the remaining elements after excluding specific tags
|
||||||
|
const remainingElements = [...document.querySelectorAll("*")];
|
||||||
|
|
||||||
|
// Remove unnecessary attributes from the remaining elements
|
||||||
|
removeUnnecessaryAttributes(remainingElements);
|
||||||
|
|
||||||
|
// Convert relative URLs to absolute URLs in links, images, and other attributes
|
||||||
|
const elementsToFixUrls = [
|
||||||
|
...document.querySelectorAll(
|
||||||
|
"a[href], img[src], link[href], script[src], iframe[src], form[action], object[data]"
|
||||||
|
)
|
||||||
|
];
|
||||||
|
fixRelativeUrls(baseUrl, elementsToFixUrls, "href");
|
||||||
|
fixRelativeUrls(baseUrl, elementsToFixUrls, "src");
|
||||||
|
fixRelativeUrls(baseUrl, elementsToFixUrls, "action");
|
||||||
|
fixRelativeUrls(baseUrl, elementsToFixUrls, "data");
|
||||||
|
|
||||||
|
return document.documentElement.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function postMessage(type, value) {
|
||||||
|
if (window.ReactNativeWebView) {
|
||||||
|
window.ReactNativeWebView.postMessage(
|
||||||
|
JSON.stringify({
|
||||||
|
type: type,
|
||||||
|
value: value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = extractAndModifyHtml();
|
||||||
|
postMessage("html", html);
|
||||||
|
};
|
||||||
|
`;
|
||||||
@@ -56,15 +56,15 @@ import { Editor } from "./editor";
|
|||||||
import { Search } from "./search";
|
import { Search } from "./search";
|
||||||
import { initDatabase, useShareStore } from "./store";
|
import { initDatabase, useShareStore } from "./store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import { HtmlLoadingWebViewAgent, fetchHandle } from "./fetch-webview";
|
||||||
|
|
||||||
const getLinkPreview = (url) => {
|
const getLinkPreview = (url) => {
|
||||||
return getPreviewData(url, 5000);
|
return getPreviewData(url, 5000);
|
||||||
};
|
};
|
||||||
async function sanitizeHtml(site) {
|
async function sanitizeHtml(site) {
|
||||||
try {
|
try {
|
||||||
let html = await fetch(site);
|
let html = await fetchHandle.current?.processUrl(site);
|
||||||
html = await html.text();
|
return html;
|
||||||
return sanitize(html, site);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -82,93 +82,6 @@ function makeHtmlFromPlainText(text) {
|
|||||||
.replace(/(?:\r\n|\r|\n)/g, "</p><p>")}</p>`;
|
.replace(/(?:\r\n|\r|\n)/g, "</p><p>")}</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBaseUrl(site) {
|
|
||||||
var url = site.split("/").slice(0, 3).join("/");
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrapTablesWithDiv(document) {
|
|
||||||
const tables = document.getElementsByTagName("table");
|
|
||||||
for (let table of tables) {
|
|
||||||
table.setAttribute("contenteditable", "true");
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.setAttribute("contenteditable", "false");
|
|
||||||
div.innerHTML = table.outerHTML;
|
|
||||||
div.classList.add("table-container");
|
|
||||||
table.replaceWith(div);
|
|
||||||
}
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
let elementBlacklist = [
|
|
||||||
"script",
|
|
||||||
"button",
|
|
||||||
"input",
|
|
||||||
"textarea",
|
|
||||||
"style",
|
|
||||||
"form",
|
|
||||||
"link",
|
|
||||||
"head",
|
|
||||||
"nav",
|
|
||||||
"iframe",
|
|
||||||
"canvas",
|
|
||||||
"select",
|
|
||||||
"dialog",
|
|
||||||
"footer"
|
|
||||||
];
|
|
||||||
|
|
||||||
function removeInvalidElements(document) {
|
|
||||||
let elements = document.querySelectorAll(elementBlacklist.join(","));
|
|
||||||
for (let element of elements) {
|
|
||||||
element.remove();
|
|
||||||
}
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceSrcWithAbsoluteUrls(document, baseUrl) {
|
|
||||||
let images = document.querySelectorAll("img");
|
|
||||||
|
|
||||||
for (var i = 0; i < images.length; i++) {
|
|
||||||
let img = images[i];
|
|
||||||
let url = getBaseUrl(baseUrl);
|
|
||||||
let src = img.getAttribute("src");
|
|
||||||
if (src.startsWith("/")) {
|
|
||||||
if (src.startsWith("//")) {
|
|
||||||
src = src.replace("//", "https://");
|
|
||||||
} else {
|
|
||||||
src = url + src;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (src.startsWith("data:")) {
|
|
||||||
img.remove();
|
|
||||||
} else {
|
|
||||||
img.setAttribute("src", src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixCodeBlocks(document) {
|
|
||||||
let elements = document.querySelectorAll("code,pre");
|
|
||||||
|
|
||||||
for (let element of elements) {
|
|
||||||
element.classList.add(".hljs");
|
|
||||||
}
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitize(html, baseUrl) {
|
|
||||||
let parser = parseHTML(html);
|
|
||||||
parser = wrapTablesWithDiv(parser);
|
|
||||||
parser = removeInvalidElements(parser);
|
|
||||||
parser = replaceSrcWithAbsoluteUrls(parser, baseUrl);
|
|
||||||
parser = fixCodeBlocks(parser);
|
|
||||||
let htmlString = parser.body.outerHTML;
|
|
||||||
htmlString = htmlString + `<hr>${makeHtmlFromUrl(baseUrl)}`;
|
|
||||||
return htmlString;
|
|
||||||
}
|
|
||||||
|
|
||||||
let defaultNote = {
|
let defaultNote = {
|
||||||
title: null,
|
title: null,
|
||||||
id: null,
|
id: null,
|
||||||
@@ -210,6 +123,7 @@ const ShareView = ({ quicknote = false }) => {
|
|||||||
const [mode, setMode] = useState(1);
|
const [mode, setMode] = useState(1);
|
||||||
const keyboardHeight = useRef(0);
|
const keyboardHeight = useRef(0);
|
||||||
const { width, height } = useWindowDimensions();
|
const { width, height } = useWindowDimensions();
|
||||||
|
const [loadingPage, setLoadingPage] = useState(false);
|
||||||
const insets =
|
const insets =
|
||||||
Platform.OS === "android"
|
Platform.OS === "android"
|
||||||
? { top: StatusBar.currentHeight }
|
? { top: StatusBar.currentHeight }
|
||||||
@@ -260,6 +174,10 @@ const ShareView = ({ quicknote = false }) => {
|
|||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
if (noteContent.current) {
|
||||||
|
onLoad();
|
||||||
|
return;
|
||||||
|
}
|
||||||
defaultNote.content.data = null;
|
defaultNote.content.data = null;
|
||||||
setNote({ ...defaultNote });
|
setNote({ ...defaultNote });
|
||||||
const data = await ShareExtension.data();
|
const data = await ShareExtension.data();
|
||||||
@@ -310,6 +228,7 @@ const ShareView = ({ quicknote = false }) => {
|
|||||||
}, [onLoad]);
|
}, [onLoad]);
|
||||||
|
|
||||||
const onLoad = useCallback(() => {
|
const onLoad = useCallback(() => {
|
||||||
|
console.log("sending event...");
|
||||||
eSendEvent(eOnLoadNote + "shareEditor", {
|
eSendEvent(eOnLoadNote + "shareEditor", {
|
||||||
id: null,
|
id: null,
|
||||||
content: {
|
content: {
|
||||||
@@ -397,11 +316,13 @@ const ShareView = ({ quicknote = false }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (m === 2) {
|
if (m === 2) {
|
||||||
|
setLoadingPage(true);
|
||||||
let html = await sanitizeHtml(rawData.value);
|
let html = await sanitizeHtml(rawData.value);
|
||||||
|
noteContent.current = html;
|
||||||
|
setLoadingPage(false);
|
||||||
|
onLoad();
|
||||||
setNote((note) => {
|
setNote((note) => {
|
||||||
note.content.data = html;
|
note.content.data = html;
|
||||||
noteContent.current = html;
|
|
||||||
onLoad();
|
|
||||||
return { ...note };
|
return { ...note };
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -423,6 +344,7 @@ const ShareView = ({ quicknote = false }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onLoadEditor = useCallback(() => {
|
const onLoadEditor = useCallback(() => {
|
||||||
|
console.log("ON LOAD");
|
||||||
Storage.write("shareExtensionOpened", "opened");
|
Storage.write("shareExtensionOpened", "opened");
|
||||||
loadData();
|
loadData();
|
||||||
}, [loadData]);
|
}, [loadData]);
|
||||||
@@ -450,6 +372,8 @@ const ShareView = ({ quicknote = false }) => {
|
|||||||
justifyContent: quicknote ? "flex-start" : "flex-end"
|
justifyContent: quicknote ? "flex-start" : "flex-end"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<HtmlLoadingWebViewAgent />
|
||||||
|
|
||||||
{quicknote && !searchMode ? (
|
{quicknote && !searchMode ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -716,16 +640,27 @@ const ShareView = ({ quicknote = false }) => {
|
|||||||
<SafeAreaProvider
|
<SafeAreaProvider
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingTop: 10
|
paddingTop: 10,
|
||||||
|
justifyContent: loadingPage ? "center" : undefined,
|
||||||
|
alignItems: loadingPage ? "center" : undefined
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!loadingExtension && (
|
{!loadingExtension && !loadingPage ? (
|
||||||
<Editor
|
<Editor
|
||||||
onLoad={onLoadEditor}
|
onLoad={onLoadEditor}
|
||||||
onChange={(html) => {
|
onChange={(html) => {
|
||||||
noteContent.current = html;
|
noteContent.current = html;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{loadingPage ? (
|
||||||
|
<>
|
||||||
|
<ActivityIndicator />
|
||||||
|
<Text>Preparing web clip...</Text>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -90,3 +90,7 @@ export const useShareStore = create((set) => ({
|
|||||||
set({ selectedTags });
|
set({ selectedTags });
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const Config = {
|
||||||
|
corsProxy: appSettings?.corsProxy
|
||||||
|
};
|
||||||
|
|||||||
2279
packages/clipper/package-lock.json
generated
2279
packages/clipper/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,9 @@
|
|||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.27.1",
|
"@playwright/test": "^1.27.1",
|
||||||
"slugify": "^1.6.5"
|
"slugify": "^1.6.5",
|
||||||
|
"webpack": "^5.88.2",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@@ -21,7 +23,7 @@
|
|||||||
"url": "git+https://github.com/streetwriters/notesnook.git"
|
"url": "git+https://github.com/streetwriters/notesnook.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc && yarn webpack -c webpack.config.js",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
|
|||||||
27
packages/clipper/src/index.global.ts
Normal file
27
packages/clipper/src/index.global.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import { clipArticle } from "./index";
|
||||||
|
|
||||||
|
declare module global {
|
||||||
|
var Clipper: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.Clipper = {
|
||||||
|
clipArticle
|
||||||
|
};
|
||||||
@@ -44,16 +44,10 @@ const inlineOptions: InlineOptions = {
|
|||||||
|
|
||||||
async function clipPage(
|
async function clipPage(
|
||||||
document: Document,
|
document: Document,
|
||||||
withStyles: boolean,
|
|
||||||
onlyVisible: boolean,
|
onlyVisible: boolean,
|
||||||
config?: Config
|
config?: Config
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const { body, head } = await getPage(
|
const { body, head } = await getPage(document, config, onlyVisible);
|
||||||
document,
|
|
||||||
withStyles,
|
|
||||||
config,
|
|
||||||
onlyVisible
|
|
||||||
);
|
|
||||||
if (!body || !head) return null;
|
if (!body || !head) return null;
|
||||||
const result = toDocument(head, body).documentElement.outerHTML;
|
const result = toDocument(head, body).documentElement.outerHTML;
|
||||||
return `<!doctype html>\n${result}`;
|
return `<!doctype html>\n${result}`;
|
||||||
@@ -61,10 +55,9 @@ async function clipPage(
|
|||||||
|
|
||||||
async function clipArticle(
|
async function clipArticle(
|
||||||
doc: Document,
|
doc: Document,
|
||||||
withStyles: boolean,
|
|
||||||
config?: Config
|
config?: Config
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const { body, head } = await getPage(doc, withStyles, config);
|
const { body, head } = await getPage(doc, config);
|
||||||
if (!body || !head) return null;
|
if (!body || !head) return null;
|
||||||
const newDoc = toDocument(head, body);
|
const newDoc = toDocument(head, body);
|
||||||
|
|
||||||
@@ -454,7 +447,6 @@ function cleanup() {
|
|||||||
|
|
||||||
async function getPage(
|
async function getPage(
|
||||||
document: Document,
|
document: Document,
|
||||||
styles: boolean,
|
|
||||||
config?: Config,
|
config?: Config,
|
||||||
onlyVisible = false
|
onlyVisible = false
|
||||||
) {
|
) {
|
||||||
@@ -463,10 +455,10 @@ async function getPage(
|
|||||||
fetchOptions: resolveFetchOptions(config),
|
fetchOptions: resolveFetchOptions(config),
|
||||||
inlineOptions: {
|
inlineOptions: {
|
||||||
fonts: false,
|
fonts: false,
|
||||||
images: styles,
|
images: config?.images,
|
||||||
stylesheets: styles
|
stylesheets: config?.styles
|
||||||
},
|
},
|
||||||
styles,
|
styles: config?.styles,
|
||||||
filter: (node) => {
|
filter: (node) => {
|
||||||
return !onlyVisible || isElementInViewport(node);
|
return !onlyVisible || isElementInViewport(node);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,4 +52,6 @@ export type Options = {
|
|||||||
|
|
||||||
export type Config = {
|
export type Config = {
|
||||||
corsProxy?: string;
|
corsProxy?: string;
|
||||||
|
images?: boolean;
|
||||||
|
styles?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
28
packages/clipper/webpack.config.js
Normal file
28
packages/clipper/webpack.config.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
module.exports = {
|
||||||
|
entry: ["./dist/index.global.js"],
|
||||||
|
mode: "production",
|
||||||
|
output: {
|
||||||
|
filename: "clipper.bundle.js",
|
||||||
|
path: path.resolve(__dirname, "../../apps/mobile/native/ios")
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user