mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
9 Commits
fix_releas
...
feat/clipp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19e10b8239 | ||
|
|
693704c870 | ||
|
|
b099286406 | ||
|
|
48d9087180 | ||
|
|
c0cb4610b2 | ||
|
|
ee802b327f | ||
|
|
e082ace03e | ||
|
|
fc35a41b9a | ||
|
|
763ce73eab |
File diff suppressed because one or more lines are too long
@@ -21,6 +21,12 @@
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
background-color: transparent !important;
|
||||
height: unset !important;
|
||||
}
|
||||
|
||||
.editor {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
diff --git a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilFS.java b/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilFS.java
|
||||
index c75347f..76d9b9e 100644
|
||||
--- a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilFS.java
|
||||
+++ b/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilFS.java
|
||||
@@ -257,9 +257,9 @@ class ReactNativeBlobUtilFS {
|
||||
if (resolved != null && resolved.startsWith(ReactNativeBlobUtilConst.FILE_PREFIX_BUNDLE_ASSET)) {
|
||||
String assetName = path.replace(ReactNativeBlobUtilConst.FILE_PREFIX_BUNDLE_ASSET, "");
|
||||
// This fails should an asset file be >2GB
|
||||
- length = (int) ReactNativeBlobUtilImpl.RCTContext.getAssets().openFd(assetName).getLength();
|
||||
- bytes = new byte[length];
|
||||
InputStream in = ReactNativeBlobUtilImpl.RCTContext.getAssets().open(assetName);
|
||||
+ length = in.available();
|
||||
+ bytes = new byte[length];
|
||||
bytesRead = in.read(bytes, 0, length);
|
||||
in.close();
|
||||
}
|
||||
diff --git a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java b/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java
|
||||
index 9aee829..0ecc59b 100644
|
||||
--- a/node_modules/react-native-blob-util/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java
|
||||
@@ -109,7 +125,7 @@ index 97e5263..640aaea 100644
|
||||
}
|
||||
|
||||
diff --git a/node_modules/react-native-blob-util/index.js b/node_modules/react-native-blob-util/index.js
|
||||
index ecaddf9..40a5c37 100644
|
||||
index ecaddf9..70d6ba5 100644
|
||||
--- a/node_modules/react-native-blob-util/index.js
|
||||
+++ b/node_modules/react-native-blob-util/index.js
|
||||
@@ -14,6 +14,7 @@ import ios from './ios';
|
||||
|
||||
@@ -167,10 +167,9 @@ export const Editor = ({ onChange, onLoad }) => {
|
||||
}}
|
||||
nestedScrollEnabled
|
||||
javaScriptEnabled={true}
|
||||
focusable={true}
|
||||
setSupportMultipleWindows={false}
|
||||
overScrollMode="never"
|
||||
scrollEnabled={false}
|
||||
scrollEnabled={Platform.OS === "ios"}
|
||||
keyboardDisplayRequiresUserAction={false}
|
||||
cacheMode="LOAD_DEFAULT"
|
||||
cacheEnabled={true}
|
||||
|
||||
181
apps/mobile/share/fetch-webview.js
Normal file
181
apps/mobile/share/fetch-webview.js
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
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, {
|
||||
createRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import WebView from "react-native-webview";
|
||||
import { Config } from "./store";
|
||||
import { db } from "../app/common/database";
|
||||
import { SUBSCRIPTION_STATUS } from "../app/utils/constants";
|
||||
|
||||
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();
|
||||
const premium = useRef(false);
|
||||
const corsProxy = Config.corsProxy;
|
||||
|
||||
useImperativeHandle(
|
||||
fetchHandle,
|
||||
() => ({
|
||||
processUrl: (url) => {
|
||||
return new Promise((resolve) => {
|
||||
setSource(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;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const user = await db.user.getUser();
|
||||
const subscriptionStatus =
|
||||
user?.subscription?.type || SUBSCRIPTION_STATUS.BASIC;
|
||||
premium.current =
|
||||
user && subscriptionStatus !== SUBSCRIPTION_STATUS.BASIC;
|
||||
|
||||
const clipperPath =
|
||||
Platform.OS === "ios"
|
||||
? RNFetchBlob.fs.dirs.MainBundleDir +
|
||||
"/extension.bundle/clipper.bundle.js"
|
||||
: "bundle-assets://clipper.bundle.js";
|
||||
|
||||
RNFetchBlob.fs
|
||||
.readFile(clipperPath, "utf8")
|
||||
.then((clipper) => {
|
||||
setClipper(clipper);
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return !source || !clipper ? null : (
|
||||
<WebView
|
||||
ref={webview}
|
||||
onLoad={() => {
|
||||
loadHandler.current?.(true);
|
||||
}}
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: -1
|
||||
}}
|
||||
useSharedProcessPool={false}
|
||||
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") {
|
||||
console.log("error", data.value);
|
||||
htmlHandler.current?.(null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error handling webview message", e);
|
||||
}
|
||||
}}
|
||||
injectedJavaScriptBeforeContentLoaded={script(clipper, premium.current)}
|
||||
onError={() => {
|
||||
console.log("Error loading page");
|
||||
loadHandler.current?.();
|
||||
}}
|
||||
source={{
|
||||
uri: source
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
() => true
|
||||
);
|
||||
|
||||
const script = (clipper, pro) => `
|
||||
${clipper}
|
||||
|
||||
function postMessage(type, value) {
|
||||
if (window.ReactNativeWebView) {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
JSON.stringify({
|
||||
type: type,
|
||||
value: value
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(() => {
|
||||
try {
|
||||
const loadFn = () => {
|
||||
if (!globalThis.Clipper.clipPage) {
|
||||
postMessage("error", globalThis.Clipper.clipPage);
|
||||
} else {
|
||||
globalThis.Clipper.clipPage(document,false, {
|
||||
images: ${pro},
|
||||
inlineImages: false,
|
||||
styles: false,
|
||||
corsProxy: undefined
|
||||
}).then(result => {
|
||||
postMessage("html", result);
|
||||
}).catch(e => {
|
||||
postMessage("error");
|
||||
});
|
||||
}
|
||||
};
|
||||
window.addEventListener("load",loadFn, false);
|
||||
} catch(e) {
|
||||
postMessage("error", e.message);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
`;
|
||||
|
||||
HtmlLoadingWebViewAgent.displayName = "HtmlLoadingWebViewAgent";
|
||||
@@ -56,15 +56,15 @@ import { Editor } from "./editor";
|
||||
import { Search } from "./search";
|
||||
import { initDatabase, useShareStore } from "./store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { HtmlLoadingWebViewAgent, fetchHandle } from "./fetch-webview";
|
||||
|
||||
const getLinkPreview = (url) => {
|
||||
return getPreviewData(url, 5000);
|
||||
};
|
||||
async function sanitizeHtml(site) {
|
||||
try {
|
||||
let html = await fetch(site);
|
||||
html = await html.text();
|
||||
return sanitize(html, site);
|
||||
let html = await fetchHandle.current?.processUrl(site);
|
||||
return html;
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
@@ -82,93 +82,6 @@ function makeHtmlFromPlainText(text) {
|
||||
.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 = {
|
||||
title: null,
|
||||
id: null,
|
||||
@@ -210,6 +123,7 @@ const ShareView = ({ quicknote = false }) => {
|
||||
const [mode, setMode] = useState(1);
|
||||
const keyboardHeight = useRef(0);
|
||||
const { width, height } = useWindowDimensions();
|
||||
const [loadingPage, setLoadingPage] = useState(false);
|
||||
const insets =
|
||||
Platform.OS === "android"
|
||||
? { top: StatusBar.currentHeight }
|
||||
@@ -260,6 +174,10 @@ const ShareView = ({ quicknote = false }) => {
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
if (noteContent.current) {
|
||||
onLoad();
|
||||
return;
|
||||
}
|
||||
defaultNote.content.data = null;
|
||||
setNote({ ...defaultNote });
|
||||
const data = await ShareExtension.data();
|
||||
@@ -397,14 +315,19 @@ const ShareView = ({ quicknote = false }) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (m === 2) {
|
||||
let html = await sanitizeHtml(rawData.value);
|
||||
setNote((note) => {
|
||||
note.content.data = html;
|
||||
setLoadingPage(true);
|
||||
setTimeout(async () => {
|
||||
let html = await sanitizeHtml(rawData.value);
|
||||
noteContent.current = html;
|
||||
setLoadingPage(false);
|
||||
onLoad();
|
||||
return { ...note };
|
||||
});
|
||||
setNote((note) => {
|
||||
note.content.data = html;
|
||||
return { ...note };
|
||||
});
|
||||
}, 300);
|
||||
} else {
|
||||
setLoadingPage(false);
|
||||
let html = isURL(rawData.value)
|
||||
? makeHtmlFromUrl(rawData.value)
|
||||
: makeHtmlFromPlainText(rawData.value);
|
||||
@@ -450,6 +373,8 @@ const ShareView = ({ quicknote = false }) => {
|
||||
justifyContent: quicknote ? "flex-start" : "flex-end"
|
||||
}}
|
||||
>
|
||||
{loadingPage ? <HtmlLoadingWebViewAgent /> : null}
|
||||
|
||||
{quicknote && !searchMode ? (
|
||||
<View
|
||||
style={{
|
||||
@@ -716,16 +641,27 @@ const ShareView = ({ quicknote = false }) => {
|
||||
<SafeAreaProvider
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingTop: 10
|
||||
paddingTop: 10,
|
||||
justifyContent: loadingPage ? "center" : undefined,
|
||||
alignItems: loadingPage ? "center" : undefined
|
||||
}}
|
||||
>
|
||||
{!loadingExtension && (
|
||||
{!loadingExtension && !loadingPage ? (
|
||||
<Editor
|
||||
onLoad={onLoadEditor}
|
||||
onChange={(html) => {
|
||||
noteContent.current = html;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{loadingPage ? (
|
||||
<>
|
||||
<ActivityIndicator color={colors.primary.accent} />
|
||||
<Text>Preparing web clip...</Text>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</SafeAreaProvider>
|
||||
</View>
|
||||
|
||||
@@ -90,3 +90,7 @@ export const useShareStore = create((set) => ({
|
||||
set({ selectedTags });
|
||||
}
|
||||
}));
|
||||
|
||||
export const Config = {
|
||||
corsProxy: appSettings?.corsProxy
|
||||
};
|
||||
|
||||
@@ -77,18 +77,22 @@ function clip(message: ClipMessage) {
|
||||
const isScreenshot = message.mode === "screenshot";
|
||||
const withStyles = message.mode === "complete" || isScreenshot;
|
||||
|
||||
if (config) {
|
||||
config.styles = withStyles;
|
||||
}
|
||||
|
||||
if (isScreenshot && message.area === "full-page") {
|
||||
return clipScreenshot(document.body, "jpeg", config);
|
||||
} else if (message.area === "full-page") {
|
||||
return clipPage(document, withStyles, false, config);
|
||||
return clipPage(document, false, config);
|
||||
} else if (message.area === "selection") {
|
||||
enterNodeSelectionMode(document, config).then((result) =>
|
||||
browser.runtime.sendMessage({ type: "manual", data: result })
|
||||
);
|
||||
} else if (message.area === "article") {
|
||||
return clipArticle(document, withStyles, config);
|
||||
return clipArticle(document, config);
|
||||
} else if (message.area === "visible") {
|
||||
return clipPage(document, withStyles, true, config);
|
||||
return clipPage(document, true, config);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
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",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.27.1",
|
||||
"slugify": "^1.6.5"
|
||||
"slugify": "^1.6.5",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -21,7 +23,7 @@
|
||||
"url": "git+https://github.com/streetwriters/notesnook.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "tsc && yarn webpack -c webpack.config.js",
|
||||
"test": "playwright test",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
|
||||
@@ -112,13 +112,14 @@ type CloneProps = {
|
||||
pseudoElement: string
|
||||
) => CSSStyleDeclaration | undefined;
|
||||
fetchOptions?: FetchOptions;
|
||||
images?: boolean;
|
||||
};
|
||||
|
||||
export async function cloneNode(node: HTMLElement, options: CloneProps) {
|
||||
const { root, filter } = options;
|
||||
if (!root && filter && !filter(node)) return null;
|
||||
|
||||
let clone = await makeNodeCopy(node, options.fetchOptions);
|
||||
let clone = await makeNodeCopy(node, options);
|
||||
|
||||
if (!clone) return null;
|
||||
clone = await cloneChildren(node, clone, options);
|
||||
@@ -127,10 +128,22 @@ export async function cloneNode(node: HTMLElement, options: CloneProps) {
|
||||
return processed;
|
||||
}
|
||||
|
||||
function makeNodeCopy(original: HTMLElement, options?: FetchOptions) {
|
||||
function makeNodeCopy(original: HTMLElement, options?: CloneProps) {
|
||||
try {
|
||||
if (original instanceof HTMLCanvasElement)
|
||||
return createImage(original.toDataURL(), options);
|
||||
if (original instanceof HTMLCanvasElement && options?.images)
|
||||
return createImage(original.toDataURL(), options?.fetchOptions);
|
||||
|
||||
if (!options?.images && original instanceof HTMLImageElement) return null;
|
||||
|
||||
if (
|
||||
!options?.styles &&
|
||||
(original instanceof HTMLButtonElement ||
|
||||
original instanceof HTMLFormElement ||
|
||||
original instanceof HTMLSelectElement ||
|
||||
original instanceof HTMLInputElement ||
|
||||
original instanceof HTMLTextAreaElement)
|
||||
)
|
||||
return null;
|
||||
|
||||
if (original.nodeType === Node.COMMENT_NODE) return null;
|
||||
|
||||
@@ -215,12 +228,25 @@ function processClone(
|
||||
copyStyle(original, clone, options);
|
||||
clonePseudoElements(original, clone, options);
|
||||
}
|
||||
|
||||
fixRelativeUrl(clone);
|
||||
copyUserInput(original, clone);
|
||||
fixSvg(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
function fixRelativeUrl(node: HTMLElement) {
|
||||
const attributes = ["href", "src"];
|
||||
const baseUrl = window.location.href;
|
||||
for (const attribute of attributes) {
|
||||
const url = node.getAttribute(attribute);
|
||||
const relativeUrl = url?.startsWith("http") ? undefined : url;
|
||||
if (relativeUrl) {
|
||||
const absoluteUrl = new URL(relativeUrl, baseUrl).href;
|
||||
node.setAttribute(attribute, absoluteUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyFont(source: CSSStyleDeclaration, target: CSSStyleDeclaration) {
|
||||
target.font = source.font;
|
||||
target.fontFamily = source.fontFamily;
|
||||
|
||||
@@ -30,7 +30,8 @@ const defaultOptions: Options = {
|
||||
};
|
||||
|
||||
async function getInlinedNode(node: HTMLElement, options: Options) {
|
||||
const { fonts, images, stylesheets } = options.inlineOptions || {};
|
||||
const { fonts, images, stylesheets, inlineImages } =
|
||||
options.inlineOptions || {};
|
||||
|
||||
if (stylesheets) await inlineStylesheets(options.fetchOptions);
|
||||
|
||||
@@ -45,14 +46,15 @@ async function getInlinedNode(node: HTMLElement, options: Options) {
|
||||
vector: !options.raster,
|
||||
fetchOptions: options.fetchOptions,
|
||||
getElementStyles: styleCache?.get,
|
||||
getPseudoElementStyles: styleCache?.getPseudo
|
||||
getPseudoElementStyles: styleCache?.getPseudo,
|
||||
images: images
|
||||
});
|
||||
|
||||
if (!clone || clone instanceof Text) return;
|
||||
|
||||
if (fonts) clone = await embedFonts(clone, options.fetchOptions);
|
||||
|
||||
if (images) await inlineAllImages(clone, options.fetchOptions);
|
||||
if (inlineImages) await inlineAllImages(clone, options.fetchOptions);
|
||||
|
||||
finalize(clone);
|
||||
return clone;
|
||||
|
||||
28
packages/clipper/src/index.global.ts
Normal file
28
packages/clipper/src/index.global.ts
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/>.
|
||||
*/
|
||||
import { clipArticle, clipPage } from "./index";
|
||||
|
||||
declare module global {
|
||||
var Clipper: any;
|
||||
}
|
||||
|
||||
global.Clipper = {
|
||||
clipArticle,
|
||||
clipPage
|
||||
};
|
||||
@@ -44,16 +44,10 @@ const inlineOptions: InlineOptions = {
|
||||
|
||||
async function clipPage(
|
||||
document: Document,
|
||||
withStyles: boolean,
|
||||
onlyVisible: boolean,
|
||||
config?: Config
|
||||
): Promise<string | null> {
|
||||
const { body, head } = await getPage(
|
||||
document,
|
||||
withStyles,
|
||||
config,
|
||||
onlyVisible
|
||||
);
|
||||
const { body, head } = await getPage(document, config, onlyVisible);
|
||||
if (!body || !head) return null;
|
||||
const result = toDocument(head, body).documentElement.outerHTML;
|
||||
return `<!doctype html>\n${result}`;
|
||||
@@ -61,10 +55,9 @@ async function clipPage(
|
||||
|
||||
async function clipArticle(
|
||||
doc: Document,
|
||||
withStyles: boolean,
|
||||
config?: Config
|
||||
): Promise<string | null> {
|
||||
const { body, head } = await getPage(doc, withStyles, config);
|
||||
const { body, head } = await getPage(doc, config);
|
||||
if (!body || !head) return null;
|
||||
const newDoc = toDocument(head, body);
|
||||
|
||||
@@ -454,7 +447,6 @@ function cleanup() {
|
||||
|
||||
async function getPage(
|
||||
document: Document,
|
||||
styles: boolean,
|
||||
config?: Config,
|
||||
onlyVisible = false
|
||||
) {
|
||||
@@ -463,10 +455,11 @@ async function getPage(
|
||||
fetchOptions: resolveFetchOptions(config),
|
||||
inlineOptions: {
|
||||
fonts: false,
|
||||
images: styles,
|
||||
stylesheets: styles
|
||||
inlineImages: config?.inlineImages,
|
||||
images: config?.images,
|
||||
stylesheets: config?.styles
|
||||
},
|
||||
styles,
|
||||
styles: config?.styles,
|
||||
filter: (node) => {
|
||||
return !onlyVisible || isElementInViewport(node);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export type InlineOptions = {
|
||||
stylesheets?: boolean;
|
||||
fonts?: boolean;
|
||||
images?: boolean;
|
||||
inlineImages?: boolean;
|
||||
};
|
||||
|
||||
export type Options = {
|
||||
@@ -52,4 +53,7 @@ export type Options = {
|
||||
|
||||
export type Config = {
|
||||
corsProxy?: string;
|
||||
images?: boolean;
|
||||
inlineImages?: boolean;
|
||||
styles?: boolean;
|
||||
};
|
||||
|
||||
31
packages/clipper/webpack.config.js
Normal file
31
packages/clipper/webpack.config.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
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/extension.bundle"
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -69,13 +69,14 @@ export function ImageComponent(
|
||||
const [source, setSource] = useState<string>();
|
||||
const downloadOptions = useToolbarStore((store) => store.downloadOptions);
|
||||
const isReadonly = !editor.current?.isEditable;
|
||||
|
||||
const hasOrSrc = hash || src;
|
||||
useEffect(
|
||||
() => {
|
||||
(async () => {
|
||||
if (!src && !dataurl && !IMAGE_SOURCE_CACHE[hash]) return;
|
||||
if (!src && !dataurl && !IMAGE_SOURCE_CACHE[hasOrSrc]) return;
|
||||
try {
|
||||
if (IMAGE_SOURCE_CACHE[hash]) setSource(IMAGE_SOURCE_CACHE[hash]);
|
||||
if (IMAGE_SOURCE_CACHE[hasOrSrc])
|
||||
setSource(IMAGE_SOURCE_CACHE[hasOrSrc]);
|
||||
else if (dataurl) setSource(await toBlobURL(dataurl));
|
||||
else if (isDataUrl(src)) setSource(await toBlobURL(src));
|
||||
else if (canParse(src)) {
|
||||
@@ -101,7 +102,7 @@ export function ImageComponent(
|
||||
[src, dataurl, imageRef, downloadOptions]
|
||||
);
|
||||
|
||||
if (source && hash) IMAGE_SOURCE_CACHE[hash] = source;
|
||||
if (source && hasOrSrc) IMAGE_SOURCE_CACHE[hasOrSrc] = source;
|
||||
const relativeHeight = aspectRatio
|
||||
? editor.view.dom.clientWidth / aspectRatio
|
||||
: undefined;
|
||||
|
||||
@@ -318,6 +318,12 @@ img.ProseMirror-separator {
|
||||
margin-inline: 5px;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.ProseMirror a > span.image-view-content-wrap {
|
||||
display: block;
|
||||
margin-inline: 5px;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user