From 4011771b64e3deae02b6d6ef067b7749abcea6ea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 24 Jan 2026 13:58:23 +0200 Subject: [PATCH] chore(web-clipper): port the remaining files to TypeScript --- apps/web-clipper/build.js | 1 - apps/web-clipper/build.ts | 1 + .../background/{index.js => index.ts} | 93 +++++++++---------- apps/web-clipper/entrypoints/popup/index.html | 1 - apps/web-clipper/{utils.js => utils.ts} | 14 +-- 5 files changed, 51 insertions(+), 59 deletions(-) delete mode 100644 apps/web-clipper/build.js create mode 100644 apps/web-clipper/build.ts rename apps/web-clipper/entrypoints/background/{index.js => index.ts} (83%) rename apps/web-clipper/{utils.js => utils.ts} (75%) diff --git a/apps/web-clipper/build.js b/apps/web-clipper/build.js deleted file mode 100644 index 3826b25246..0000000000 --- a/apps/web-clipper/build.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { buildDate:"2022-10-29T15:25:37+02:00", buildRevision: "c9c10a90aa9b94efdf150b0b2fd57f9df5bf2d0a" }; diff --git a/apps/web-clipper/build.ts b/apps/web-clipper/build.ts new file mode 100644 index 0000000000..2f5056d477 --- /dev/null +++ b/apps/web-clipper/build.ts @@ -0,0 +1 @@ +export default { buildDate:"2022-10-29T15:25:37+02:00", buildRevision: "c9c10a90aa9b94efdf150b0b2fd57f9df5bf2d0a" }; diff --git a/apps/web-clipper/entrypoints/background/index.js b/apps/web-clipper/entrypoints/background/index.ts similarity index 83% rename from apps/web-clipper/entrypoints/background/index.js rename to apps/web-clipper/entrypoints/background/index.ts index e8d3c6d038..c36aa48994 100644 --- a/apps/web-clipper/entrypoints/background/index.js +++ b/apps/web-clipper/entrypoints/background/index.ts @@ -1,11 +1,13 @@ import { randomString } from "../../utils"; import TriliumServerFacade, { isDevEnv } from "./trilium_server_facade"; +type Rect = { x: number, y: number, width: number, height: number }; + export default defineBackground(() => { const triliumServerFacade = new TriliumServerFacade(); // Keyboard shortcuts - chrome.commands.onCommand.addListener(async function (command) { + browser.commands.onCommand.addListener(async (command) => { if (command == "saveSelection") { await saveSelection(); } else if (command == "saveWholePage") { @@ -21,8 +23,8 @@ export default defineBackground(() => { } }); - function cropImage(newArea, dataUrl) { - return new Promise((resolve, reject) => { + function cropImage(newArea: Rect, dataUrl: string) { + return new Promise((resolve) => { const img = new Image(); img.onload = function () { @@ -31,8 +33,7 @@ export default defineBackground(() => { canvas.height = newArea.height; const ctx = canvas.getContext('2d'); - - ctx.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); + ctx?.drawImage(img, newArea.x, newArea.y, newArea.width, newArea.height, 0, 0, newArea.width, newArea.height); resolve(canvas.toDataURL()); }; @@ -41,17 +42,17 @@ export default defineBackground(() => { }); } - async function takeCroppedScreenshot(cropRect) { + async function takeCroppedScreenshot(cropRect: Rect) { const activeTab = await getActiveTab(); const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio; - const newArea = Object.assign({}, cropRect); + const newArea: Rect = Object.assign({}, cropRect); newArea.x *= zoom; newArea.y *= zoom; newArea.width *= zoom; newArea.height *= zoom; - const dataUrl = await browser.tabs.captureVisibleTab(null, { format: 'png' }); + const dataUrl = await browser.tabs.captureVisibleTab({ format: 'png' }); return await cropImage(newArea, dataUrl); } @@ -61,7 +62,7 @@ export default defineBackground(() => { // workaround to save the whole page is to scroll & stitch // example in https://github.com/mrcoles/full-page-screen-capture-chrome-extension // see page.js and popup.js - return await browser.tabs.captureVisibleTab(null, { format: 'png' }); + return await browser.tabs.captureVisibleTab({ format: 'png' }); } browser.runtime.onInstalled.addListener(() => { @@ -134,52 +135,47 @@ export default defineBackground(() => { async function sendMessageToActiveTab(message) { const activeTab = await getActiveTab(); - if (!activeTab) { + if (!activeTab?.id) { throw new Error("No active tab."); } - try { - return await browser.tabs.sendMessage(activeTab.id, message); - } - catch (e) { - throw e; - } + return await browser.tabs.sendMessage(activeTab.id, message); } - function toast(message, noteId = null, tabIds = null) { + function toast(message: string, noteId: string | null = null, tabIds: number[] | null = null) { sendMessageToActiveTab({ name: 'toast', - message: message, - noteId: noteId, - tabIds: tabIds + message, + noteId, + tabIds }); } - function blob2base64(blob) { - return new Promise(resolve => { + function blob2base64(blob: Blob) { + return new Promise(resolve => { const reader = new FileReader(); reader.onloadend = function() { - resolve(reader.result); + resolve(reader.result as string | null); }; reader.readAsDataURL(blob); }); } - async function fetchImage(url) { + async function fetchImage(url: string) { const resp = await fetch(url); const blob = await resp.blob(); return await blob2base64(blob); } - async function postProcessImage(image) { + async function postProcessImage(image: { src: string, dataUrl?: string | null }) { if (image.src.startsWith("data:image/")) { image.dataUrl = image.src; - image.src = "inline." + image.src.substr(11, 3); // this should extract file type - png/jpg + image.src = `inline.${ image.src.substr(11, 3)}`; // this should extract file type - png/jpg } else { try { - image.dataUrl = await fetchImage(image.src, image); + image.dataUrl = await fetchImage(image.src); } catch (e) { console.log(`Cannot fetch image from ${image.src}`); @@ -187,7 +183,7 @@ export default defineBackground(() => { } } - async function postProcessImages(resp) { + async function postProcessImages(resp: { images?: { src: string, dataUrl?: string }[] }) { if (resp.images) { for (const image of resp.images) { await postProcessImage(image); @@ -212,7 +208,7 @@ export default defineBackground(() => { async function getImagePayloadFromSrc(src, pageUrl) { const image = { imageId: randomString(20), - src: src + src }; await postProcessImage(image); @@ -223,7 +219,7 @@ export default defineBackground(() => { title: activeTab.title, content: ``, images: [image], - pageUrl: pageUrl + pageUrl }; } @@ -291,8 +287,8 @@ export default defineBackground(() => { } const resp = await triliumServerFacade.callService('POST', 'notes', { - title: title, - content: content, + title, + content, clipType: 'note', pageUrl: activeTab.url }); @@ -309,27 +305,27 @@ export default defineBackground(() => { async function getTabsPayload(tabs) { let content = ''; const domainsCount = tabs.map(tab => tab.url) .reduce((acc, url) => { - const hostname = new URL(url).hostname - return acc.set(hostname, (acc.get(hostname) || 0) + 1) + const hostname = new URL(url).hostname; + return acc.set(hostname, (acc.get(hostname) || 0) + 1); }, new Map()); let topDomains = [...domainsCount] - .sort((a, b) => {return b[1]-a[1]}) + .sort((a, b) => {return b[1]-a[1];}) .slice(0,3) .map(domain=>domain[0]) - .join(', ') + .join(', '); - if (tabs.length > 3) { topDomains += '...' } + if (tabs.length > 3) { topDomains += '...'; } return { title: `${tabs.length} browser tabs: ${topDomains}`, - content: content, + content, clipType: 'tabs' }; } @@ -340,17 +336,13 @@ export default defineBackground(() => { const payload = await getTabsPayload(tabs); const resp = await triliumServerFacade.callService('POST', 'notes', payload); + if (!resp) return; - if (!resp) { - return; - } - - const tabIds = tabs.map(tab=>{return tab.id}); - + const tabIds = tabs.map(tab => tab.id!).filter(id => id !== undefined) as number[]; toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds); } - browser.contextMenus.onClicked.addListener(async function(info, tab) { + browser.contextMenus.onClicked.addListener(async (info, tab) => { if (info.menuItemId === 'trilium-save-selection') { await saveSelection(); } @@ -365,8 +357,9 @@ export default defineBackground(() => { } else if (info.menuItemId === 'trilium-save-link') { const link = document.createElement("a"); + if (!info.linkUrl) return; link.href = info.linkUrl; - // linkText might be available only in firefox + // linkText is not supported in Chrome/Edge; fallback to selected text or URL link.appendChild(document.createTextNode(info.linkText || info.linkUrl)); const activeTab = await getActiveTab(); @@ -395,7 +388,7 @@ export default defineBackground(() => { console.log("Received", request); if (request.name === 'openNoteInTrilium') { - const resp = await triliumServerFacade.callService('POST', 'open/' + request.noteId); + const resp = await triliumServerFacade.callService('POST', `open/${ request.noteId}`); if (!resp) { return; @@ -406,7 +399,7 @@ export default defineBackground(() => { const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl"); if (triliumServerUrl) { - const noteUrl = triliumServerUrl + '/#' + request.noteId; + const noteUrl = `${triliumServerUrl }/#${ request.noteId}`; console.log("Opening new tab in browser", noteUrl); @@ -420,7 +413,7 @@ export default defineBackground(() => { } } else if (request.name === 'closeTabs') { - return await browser.tabs.remove(request.tabIds) + return await browser.tabs.remove(request.tabIds); } else if (request.name === 'save-cropped-screenshot') { const activeTab = await getActiveTab(); diff --git a/apps/web-clipper/entrypoints/popup/index.html b/apps/web-clipper/entrypoints/popup/index.html index a1051659f3..ee4b13871b 100644 --- a/apps/web-clipper/entrypoints/popup/index.html +++ b/apps/web-clipper/entrypoints/popup/index.html @@ -49,7 +49,6 @@ - diff --git a/apps/web-clipper/utils.js b/apps/web-clipper/utils.ts similarity index 75% rename from apps/web-clipper/utils.js rename to apps/web-clipper/utils.ts index a69a00dbab..5c1b3efd6a 100644 --- a/apps/web-clipper/utils.js +++ b/apps/web-clipper/utils.ts @@ -1,4 +1,4 @@ -export function randomString(len) { +export function randomString(len: number) { let text = ""; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -13,9 +13,9 @@ export function getBaseUrl() { let output = getPageLocationOrigin() + location.pathname; if (output[output.length - 1] !== '/') { - output = output.split('/'); - output.pop(); - output = output.join('/'); + const outputArr = output.split('/'); + outputArr.pop(); + output = outputArr.join('/'); } return output; @@ -27,14 +27,14 @@ export function getPageLocationOrigin() { return location.protocol === 'file:' ? 'file://' : location.origin; } -export function createLink(clickAction, text, color = "lightskyblue") { +export function createLink(clickAction: object, text: string, color = "lightskyblue") { const link = document.createElement('a'); link.href = "javascript:"; link.style.color = color; link.appendChild(document.createTextNode(text)); link.addEventListener("click", () => { - browser.runtime.sendMessage(null, clickAction) + browser.runtime.sendMessage(null, clickAction); }); - return link + return link; }