mirror of
https://github.com/zadam/trilium.git
synced 2026-02-03 20:34:24 +01:00
chore(web-clipper): make entrypoints actually run
This commit is contained in:
parent
e4d319c7a1
commit
cb8b968637
3
apps/web-clipper/.gitignore
vendored
3
apps/web-clipper/.gitignore
vendored
@ -1 +1,2 @@
|
||||
dist/
|
||||
.output
|
||||
.wxt
|
||||
@ -1 +0,0 @@
|
||||
{}
|
||||
@ -1,451 +1,453 @@
|
||||
// Keyboard shortcuts
|
||||
chrome.commands.onCommand.addListener(async function (command) {
|
||||
if (command == "saveSelection") {
|
||||
await saveSelection();
|
||||
} else if (command == "saveWholePage") {
|
||||
await saveWholePage();
|
||||
} else if (command == "saveTabs") {
|
||||
await saveTabs();
|
||||
} else if (command == "saveCroppedScreenshot") {
|
||||
export default defineBackground(() => {
|
||||
// Keyboard shortcuts
|
||||
chrome.commands.onCommand.addListener(async function (command) {
|
||||
if (command == "saveSelection") {
|
||||
await saveSelection();
|
||||
} else if (command == "saveWholePage") {
|
||||
await saveWholePage();
|
||||
} else if (command == "saveTabs") {
|
||||
await saveTabs();
|
||||
} else if (command == "saveCroppedScreenshot") {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
await saveCroppedScreenshot(activeTab.url);
|
||||
} else {
|
||||
console.log("Unrecognized command", command);
|
||||
}
|
||||
});
|
||||
|
||||
function cropImage(newArea, dataUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
|
||||
img.onload = function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = newArea.width;
|
||||
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);
|
||||
|
||||
resolve(canvas.toDataURL());
|
||||
};
|
||||
|
||||
img.src = dataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
async function takeCroppedScreenshot(cropRect) {
|
||||
const activeTab = await getActiveTab();
|
||||
const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio;
|
||||
|
||||
const newArea = Object.assign({}, cropRect);
|
||||
newArea.x *= zoom;
|
||||
newArea.y *= zoom;
|
||||
newArea.width *= zoom;
|
||||
newArea.height *= zoom;
|
||||
|
||||
const dataUrl = await browser.tabs.captureVisibleTab(null, { format: 'png' });
|
||||
|
||||
return await cropImage(newArea, dataUrl);
|
||||
}
|
||||
|
||||
async function takeWholeScreenshot() {
|
||||
// this saves only visible portion of the page
|
||||
// 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' });
|
||||
}
|
||||
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
if (isDevEnv()) {
|
||||
browser.browserAction.setIcon({
|
||||
path: 'icons/32-dev.png',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-selection",
|
||||
title: "Save selection to Trilium",
|
||||
contexts: ["selection"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-cropped-screenshot",
|
||||
title: "Clip screenshot to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-cropped-screenshot",
|
||||
title: "Crop screen shot to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-whole-screenshot",
|
||||
title: "Save whole screen shot to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-page",
|
||||
title: "Save whole page to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-link",
|
||||
title: "Save link to Trilium",
|
||||
contexts: ["link"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-image",
|
||||
title: "Save image to Trilium",
|
||||
contexts: ["image"]
|
||||
});
|
||||
|
||||
async function getActiveTab() {
|
||||
const tabs = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
});
|
||||
|
||||
return tabs[0];
|
||||
}
|
||||
|
||||
async function getWindowTabs() {
|
||||
const tabs = await browser.tabs.query({
|
||||
currentWindow: true
|
||||
});
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
async function sendMessageToActiveTab(message) {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
await saveCroppedScreenshot(activeTab.url);
|
||||
} else {
|
||||
console.log("Unrecognized command", command);
|
||||
if (!activeTab) {
|
||||
throw new Error("No active tab.");
|
||||
}
|
||||
|
||||
try {
|
||||
return await browser.tabs.sendMessage(activeTab.id, message);
|
||||
}
|
||||
catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function cropImage(newArea, dataUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
|
||||
img.onload = function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = newArea.width;
|
||||
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);
|
||||
|
||||
resolve(canvas.toDataURL());
|
||||
};
|
||||
|
||||
img.src = dataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
async function takeCroppedScreenshot(cropRect) {
|
||||
const activeTab = await getActiveTab();
|
||||
const zoom = await browser.tabs.getZoom(activeTab.id) * window.devicePixelRatio;
|
||||
|
||||
const newArea = Object.assign({}, cropRect);
|
||||
newArea.x *= zoom;
|
||||
newArea.y *= zoom;
|
||||
newArea.width *= zoom;
|
||||
newArea.height *= zoom;
|
||||
|
||||
const dataUrl = await browser.tabs.captureVisibleTab(null, { format: 'png' });
|
||||
|
||||
return await cropImage(newArea, dataUrl);
|
||||
}
|
||||
|
||||
async function takeWholeScreenshot() {
|
||||
// this saves only visible portion of the page
|
||||
// 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' });
|
||||
}
|
||||
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
if (isDevEnv()) {
|
||||
browser.browserAction.setIcon({
|
||||
path: 'icons/32-dev.png',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-selection",
|
||||
title: "Save selection to Trilium",
|
||||
contexts: ["selection"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-cropped-screenshot",
|
||||
title: "Clip screenshot to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-cropped-screenshot",
|
||||
title: "Crop screen shot to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-whole-screenshot",
|
||||
title: "Save whole screen shot to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-page",
|
||||
title: "Save whole page to Trilium",
|
||||
contexts: ["page"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-link",
|
||||
title: "Save link to Trilium",
|
||||
contexts: ["link"]
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: "trilium-save-image",
|
||||
title: "Save image to Trilium",
|
||||
contexts: ["image"]
|
||||
});
|
||||
|
||||
async function getActiveTab() {
|
||||
const tabs = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
});
|
||||
|
||||
return tabs[0];
|
||||
}
|
||||
|
||||
async function getWindowTabs() {
|
||||
const tabs = await browser.tabs.query({
|
||||
currentWindow: true
|
||||
});
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
async function sendMessageToActiveTab(message) {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
if (!activeTab) {
|
||||
throw new Error("No active tab.");
|
||||
}
|
||||
|
||||
try {
|
||||
return await browser.tabs.sendMessage(activeTab.id, message);
|
||||
}
|
||||
catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function toast(message, noteId = null, tabIds = null) {
|
||||
sendMessageToActiveTab({
|
||||
name: 'toast',
|
||||
message: message,
|
||||
noteId: noteId,
|
||||
tabIds: tabIds
|
||||
});
|
||||
}
|
||||
|
||||
function blob2base64(blob) {
|
||||
return new Promise(resolve => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchImage(url) {
|
||||
const resp = await fetch(url);
|
||||
const blob = await resp.blob();
|
||||
|
||||
return await blob2base64(blob);
|
||||
}
|
||||
|
||||
async function postProcessImage(image) {
|
||||
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
|
||||
}
|
||||
else {
|
||||
try {
|
||||
image.dataUrl = await fetchImage(image.src, image);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(`Cannot fetch image from ${image.src}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function postProcessImages(resp) {
|
||||
if (resp.images) {
|
||||
for (const image of resp.images) {
|
||||
await postProcessImage(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSelection() {
|
||||
const payload = await sendMessageToActiveTab({name: 'trilium-save-selection'});
|
||||
|
||||
await postProcessImages(payload);
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'clippings', payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Selection has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function getImagePayloadFromSrc(src, pageUrl) {
|
||||
const image = {
|
||||
imageId: randomString(20),
|
||||
src: src
|
||||
};
|
||||
|
||||
await postProcessImage(image);
|
||||
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
return {
|
||||
title: activeTab.title,
|
||||
content: `<img src="${image.imageId}">`,
|
||||
images: [image],
|
||||
pageUrl: pageUrl
|
||||
};
|
||||
}
|
||||
|
||||
async function saveCroppedScreenshot(pageUrl) {
|
||||
const cropRect = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'});
|
||||
|
||||
const src = await takeCroppedScreenshot(cropRect);
|
||||
|
||||
const payload = await getImagePayloadFromSrc(src, pageUrl);
|
||||
|
||||
const resp = await triliumServerFacade.callService("POST", "clippings", payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Screenshot has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveWholeScreenshot(pageUrl) {
|
||||
const src = await takeWholeScreenshot();
|
||||
|
||||
const payload = await getImagePayloadFromSrc(src, pageUrl);
|
||||
|
||||
const resp = await triliumServerFacade.callService("POST", "clippings", payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Screenshot has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveImage(srcUrl, pageUrl) {
|
||||
const payload = await getImagePayloadFromSrc(srcUrl, pageUrl);
|
||||
|
||||
const resp = await triliumServerFacade.callService("POST", "clippings", payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Image has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveWholePage() {
|
||||
const payload = await sendMessageToActiveTab({name: 'trilium-save-page'});
|
||||
|
||||
await postProcessImages(payload);
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'notes', payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Page has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveLinkWithNote(title, content) {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
if (!title.trim()) {
|
||||
title = activeTab.title;
|
||||
}
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'notes', {
|
||||
title: title,
|
||||
content: content,
|
||||
clipType: 'note',
|
||||
pageUrl: activeTab.url
|
||||
});
|
||||
|
||||
if (!resp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
toast("Link with note has been saved to Trilium.", resp.noteId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getTabsPayload(tabs) {
|
||||
let content = '<ul>';
|
||||
tabs.forEach(tab => {
|
||||
content += `<li><a href="${tab.url}">${tab.title}</a></li>`
|
||||
});
|
||||
content += '</ul>';
|
||||
|
||||
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)
|
||||
}, new Map());
|
||||
|
||||
let topDomains = [...domainsCount]
|
||||
.sort((a, b) => {return b[1]-a[1]})
|
||||
.slice(0,3)
|
||||
.map(domain=>domain[0])
|
||||
.join(', ')
|
||||
|
||||
if (tabs.length > 3) { topDomains += '...' }
|
||||
|
||||
return {
|
||||
title: `${tabs.length} browser tabs: ${topDomains}`,
|
||||
content: content,
|
||||
clipType: 'tabs'
|
||||
};
|
||||
}
|
||||
|
||||
async function saveTabs() {
|
||||
const tabs = await getWindowTabs();
|
||||
|
||||
const payload = await getTabsPayload(tabs);
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'notes', payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabIds = tabs.map(tab=>{return tab.id});
|
||||
|
||||
toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds);
|
||||
}
|
||||
|
||||
browser.contextMenus.onClicked.addListener(async function(info, tab) {
|
||||
if (info.menuItemId === 'trilium-save-selection') {
|
||||
await saveSelection();
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-cropped-screenshot') {
|
||||
await saveCroppedScreenshot(info.pageUrl);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-whole-screenshot') {
|
||||
await saveWholeScreenshot(info.pageUrl);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-image') {
|
||||
await saveImage(info.srcUrl, info.pageUrl);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-link') {
|
||||
const link = document.createElement("a");
|
||||
link.href = info.linkUrl;
|
||||
// linkText might be available only in firefox
|
||||
link.appendChild(document.createTextNode(info.linkText || info.linkUrl));
|
||||
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'clippings', {
|
||||
title: activeTab.title,
|
||||
content: link.outerHTML,
|
||||
pageUrl: info.pageUrl
|
||||
});
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Link has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-page') {
|
||||
await saveWholePage();
|
||||
}
|
||||
else {
|
||||
console.log("Unrecognized menuItemId", info.menuItemId);
|
||||
}
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener(async request => {
|
||||
console.log("Received", request);
|
||||
|
||||
if (request.name === 'openNoteInTrilium') {
|
||||
const resp = await triliumServerFacade.callService('POST', 'open/' + request.noteId);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// desktop app is not available so we need to open in browser
|
||||
if (resp.result === 'open-in-browser') {
|
||||
const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl");
|
||||
|
||||
if (triliumServerUrl) {
|
||||
const noteUrl = triliumServerUrl + '/#' + request.noteId;
|
||||
|
||||
console.log("Opening new tab in browser", noteUrl);
|
||||
|
||||
browser.tabs.create({
|
||||
url: noteUrl
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.error("triliumServerUrl not found in local storage.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (request.name === 'closeTabs') {
|
||||
return await browser.tabs.remove(request.tabIds)
|
||||
}
|
||||
else if (request.name === 'load-script') {
|
||||
return await browser.tabs.executeScript({file: request.file});
|
||||
}
|
||||
else if (request.name === 'save-cropped-screenshot') {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
return await saveCroppedScreenshot(activeTab.url);
|
||||
}
|
||||
else if (request.name === 'save-whole-screenshot') {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
return await saveWholeScreenshot(activeTab.url);
|
||||
}
|
||||
else if (request.name === 'save-whole-page') {
|
||||
return await saveWholePage();
|
||||
}
|
||||
else if (request.name === 'save-link-with-note') {
|
||||
return await saveLinkWithNote(request.title, request.content);
|
||||
}
|
||||
else if (request.name === 'save-tabs') {
|
||||
return await saveTabs();
|
||||
}
|
||||
else if (request.name === 'trigger-trilium-search') {
|
||||
triliumServerFacade.triggerSearchForTrilium();
|
||||
}
|
||||
else if (request.name === 'send-trilium-search-status') {
|
||||
triliumServerFacade.sendTriliumSearchStatusToPopup();
|
||||
}
|
||||
else if (request.name === 'trigger-trilium-search-note-url') {
|
||||
const activeTab = await getActiveTab();
|
||||
triliumServerFacade.triggerSearchNoteByUrl(activeTab.url);
|
||||
}
|
||||
|
||||
function toast(message, noteId = null, tabIds = null) {
|
||||
sendMessageToActiveTab({
|
||||
name: 'toast',
|
||||
message: message,
|
||||
noteId: noteId,
|
||||
tabIds: tabIds
|
||||
});
|
||||
}
|
||||
|
||||
function blob2base64(blob) {
|
||||
return new Promise(resolve => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchImage(url) {
|
||||
const resp = await fetch(url);
|
||||
const blob = await resp.blob();
|
||||
|
||||
return await blob2base64(blob);
|
||||
}
|
||||
|
||||
async function postProcessImage(image) {
|
||||
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
|
||||
}
|
||||
else {
|
||||
try {
|
||||
image.dataUrl = await fetchImage(image.src, image);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(`Cannot fetch image from ${image.src}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function postProcessImages(resp) {
|
||||
if (resp.images) {
|
||||
for (const image of resp.images) {
|
||||
await postProcessImage(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSelection() {
|
||||
const payload = await sendMessageToActiveTab({name: 'trilium-save-selection'});
|
||||
|
||||
await postProcessImages(payload);
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'clippings', payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Selection has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function getImagePayloadFromSrc(src, pageUrl) {
|
||||
const image = {
|
||||
imageId: randomString(20),
|
||||
src: src
|
||||
};
|
||||
|
||||
await postProcessImage(image);
|
||||
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
return {
|
||||
title: activeTab.title,
|
||||
content: `<img src="${image.imageId}">`,
|
||||
images: [image],
|
||||
pageUrl: pageUrl
|
||||
};
|
||||
}
|
||||
|
||||
async function saveCroppedScreenshot(pageUrl) {
|
||||
const cropRect = await sendMessageToActiveTab({name: 'trilium-get-rectangle-for-screenshot'});
|
||||
|
||||
const src = await takeCroppedScreenshot(cropRect);
|
||||
|
||||
const payload = await getImagePayloadFromSrc(src, pageUrl);
|
||||
|
||||
const resp = await triliumServerFacade.callService("POST", "clippings", payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Screenshot has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveWholeScreenshot(pageUrl) {
|
||||
const src = await takeWholeScreenshot();
|
||||
|
||||
const payload = await getImagePayloadFromSrc(src, pageUrl);
|
||||
|
||||
const resp = await triliumServerFacade.callService("POST", "clippings", payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Screenshot has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveImage(srcUrl, pageUrl) {
|
||||
const payload = await getImagePayloadFromSrc(srcUrl, pageUrl);
|
||||
|
||||
const resp = await triliumServerFacade.callService("POST", "clippings", payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Image has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveWholePage() {
|
||||
const payload = await sendMessageToActiveTab({name: 'trilium-save-page'});
|
||||
|
||||
await postProcessImages(payload);
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'notes', payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Page has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
|
||||
async function saveLinkWithNote(title, content) {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
if (!title.trim()) {
|
||||
title = activeTab.title;
|
||||
}
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'notes', {
|
||||
title: title,
|
||||
content: content,
|
||||
clipType: 'note',
|
||||
pageUrl: activeTab.url
|
||||
});
|
||||
|
||||
if (!resp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
toast("Link with note has been saved to Trilium.", resp.noteId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getTabsPayload(tabs) {
|
||||
let content = '<ul>';
|
||||
tabs.forEach(tab => {
|
||||
content += `<li><a href="${tab.url}">${tab.title}</a></li>`
|
||||
});
|
||||
content += '</ul>';
|
||||
|
||||
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)
|
||||
}, new Map());
|
||||
|
||||
let topDomains = [...domainsCount]
|
||||
.sort((a, b) => {return b[1]-a[1]})
|
||||
.slice(0,3)
|
||||
.map(domain=>domain[0])
|
||||
.join(', ')
|
||||
|
||||
if (tabs.length > 3) { topDomains += '...' }
|
||||
|
||||
return {
|
||||
title: `${tabs.length} browser tabs: ${topDomains}`,
|
||||
content: content,
|
||||
clipType: 'tabs'
|
||||
};
|
||||
}
|
||||
|
||||
async function saveTabs() {
|
||||
const tabs = await getWindowTabs();
|
||||
|
||||
const payload = await getTabsPayload(tabs);
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'notes', payload);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabIds = tabs.map(tab=>{return tab.id});
|
||||
|
||||
toast(`${tabs.length} links have been saved to Trilium.`, resp.noteId, tabIds);
|
||||
}
|
||||
|
||||
browser.contextMenus.onClicked.addListener(async function(info, tab) {
|
||||
if (info.menuItemId === 'trilium-save-selection') {
|
||||
await saveSelection();
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-cropped-screenshot') {
|
||||
await saveCroppedScreenshot(info.pageUrl);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-whole-screenshot') {
|
||||
await saveWholeScreenshot(info.pageUrl);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-image') {
|
||||
await saveImage(info.srcUrl, info.pageUrl);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-link') {
|
||||
const link = document.createElement("a");
|
||||
link.href = info.linkUrl;
|
||||
// linkText might be available only in firefox
|
||||
link.appendChild(document.createTextNode(info.linkText || info.linkUrl));
|
||||
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
const resp = await triliumServerFacade.callService('POST', 'clippings', {
|
||||
title: activeTab.title,
|
||||
content: link.outerHTML,
|
||||
pageUrl: info.pageUrl
|
||||
});
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast("Link has been saved to Trilium.", resp.noteId);
|
||||
}
|
||||
else if (info.menuItemId === 'trilium-save-page') {
|
||||
await saveWholePage();
|
||||
}
|
||||
else {
|
||||
console.log("Unrecognized menuItemId", info.menuItemId);
|
||||
}
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener(async request => {
|
||||
console.log("Received", request);
|
||||
|
||||
if (request.name === 'openNoteInTrilium') {
|
||||
const resp = await triliumServerFacade.callService('POST', 'open/' + request.noteId);
|
||||
|
||||
if (!resp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// desktop app is not available so we need to open in browser
|
||||
if (resp.result === 'open-in-browser') {
|
||||
const {triliumServerUrl} = await browser.storage.sync.get("triliumServerUrl");
|
||||
|
||||
if (triliumServerUrl) {
|
||||
const noteUrl = triliumServerUrl + '/#' + request.noteId;
|
||||
|
||||
console.log("Opening new tab in browser", noteUrl);
|
||||
|
||||
browser.tabs.create({
|
||||
url: noteUrl
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.error("triliumServerUrl not found in local storage.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (request.name === 'closeTabs') {
|
||||
return await browser.tabs.remove(request.tabIds)
|
||||
}
|
||||
else if (request.name === 'load-script') {
|
||||
return await browser.tabs.executeScript({file: request.file});
|
||||
}
|
||||
else if (request.name === 'save-cropped-screenshot') {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
return await saveCroppedScreenshot(activeTab.url);
|
||||
}
|
||||
else if (request.name === 'save-whole-screenshot') {
|
||||
const activeTab = await getActiveTab();
|
||||
|
||||
return await saveWholeScreenshot(activeTab.url);
|
||||
}
|
||||
else if (request.name === 'save-whole-page') {
|
||||
return await saveWholePage();
|
||||
}
|
||||
else if (request.name === 'save-link-with-note') {
|
||||
return await saveLinkWithNote(request.title, request.content);
|
||||
}
|
||||
else if (request.name === 'save-tabs') {
|
||||
return await saveTabs();
|
||||
}
|
||||
else if (request.name === 'trigger-trilium-search') {
|
||||
triliumServerFacade.triggerSearchForTrilium();
|
||||
}
|
||||
else if (request.name === 'send-trilium-search-status') {
|
||||
triliumServerFacade.sendTriliumSearchStatusToPopup();
|
||||
}
|
||||
else if (request.name === 'trigger-trilium-search-note-url') {
|
||||
const activeTab = await getActiveTab();
|
||||
triliumServerFacade.triggerSearchNoteByUrl(activeTab.url);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,351 +1,358 @@
|
||||
function absoluteUrl(url) {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const protocol = url.toLowerCase().split(':')[0];
|
||||
if (['http', 'https', 'file'].indexOf(protocol) >= 0) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (url.indexOf('//') === 0) {
|
||||
return location.protocol + url;
|
||||
} else if (url[0] === '/') {
|
||||
return location.protocol + '//' + location.host + url;
|
||||
} else {
|
||||
return getBaseUrl() + '/' + url;
|
||||
}
|
||||
}
|
||||
|
||||
function pageTitle() {
|
||||
const titleElements = document.getElementsByTagName("title");
|
||||
|
||||
return titleElements.length ? titleElements[0].text.trim() : document.title.trim();
|
||||
}
|
||||
|
||||
function getReadableDocument() {
|
||||
// Readability directly change the passed document, so clone to preserve the original web page.
|
||||
const documentCopy = document.cloneNode(true);
|
||||
const readability = new Readability(documentCopy, {
|
||||
serializer: el => el // so that .content is returned as DOM element instead of HTML
|
||||
});
|
||||
|
||||
const article = readability.parse();
|
||||
|
||||
if (!article) {
|
||||
throw new Error('Could not parse HTML document with Readability');
|
||||
}
|
||||
|
||||
return {
|
||||
title: article.title,
|
||||
body: article.content,
|
||||
}
|
||||
}
|
||||
|
||||
function getDocumentDates() {
|
||||
var dates = {
|
||||
publishedDate: null,
|
||||
modifiedDate: null,
|
||||
};
|
||||
|
||||
const articlePublishedTime = document.querySelector("meta[property='article:published_time']");
|
||||
if (articlePublishedTime && articlePublishedTime.getAttribute('content')) {
|
||||
dates.publishedDate = new Date(articlePublishedTime.getAttribute('content'));
|
||||
}
|
||||
|
||||
const articleModifiedTime = document.querySelector("meta[property='article:modified_time']");
|
||||
if (articleModifiedTime && articleModifiedTime.getAttribute('content')) {
|
||||
dates.modifiedDate = new Date(articleModifiedTime.getAttribute('content'));
|
||||
}
|
||||
|
||||
// TODO: if we didn't get dates from meta, then try to get them from JSON-LD
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
function getRectangleArea() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.opacity = '0.6';
|
||||
overlay.style.background = 'black';
|
||||
overlay.style.width = '100%';
|
||||
overlay.style.height = '100%';
|
||||
overlay.style.zIndex = 99999999;
|
||||
overlay.style.top = 0;
|
||||
overlay.style.left = 0;
|
||||
overlay.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const messageComp = document.createElement('div');
|
||||
|
||||
const messageCompWidth = 300;
|
||||
messageComp.setAttribute("tabindex", "0"); // so that it can be focused
|
||||
messageComp.style.position = 'fixed';
|
||||
messageComp.style.opacity = '0.95';
|
||||
messageComp.style.fontSize = '14px';
|
||||
messageComp.style.width = messageCompWidth + 'px';
|
||||
messageComp.style.maxWidth = messageCompWidth + 'px';
|
||||
messageComp.style.border = '1px solid black';
|
||||
messageComp.style.background = 'white';
|
||||
messageComp.style.color = 'black';
|
||||
messageComp.style.top = '10px';
|
||||
messageComp.style.textAlign = 'center';
|
||||
messageComp.style.padding = '10px';
|
||||
messageComp.style.left = Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) + 'px';
|
||||
messageComp.style.zIndex = overlay.style.zIndex + 1;
|
||||
|
||||
messageComp.textContent = 'Drag and release to capture a screenshot';
|
||||
|
||||
document.body.appendChild(messageComp);
|
||||
|
||||
const selection = document.createElement('div');
|
||||
selection.style.opacity = '0.5';
|
||||
selection.style.border = '1px solid red';
|
||||
selection.style.background = 'white';
|
||||
selection.style.border = '2px solid black';
|
||||
selection.style.zIndex = overlay.style.zIndex - 1;
|
||||
selection.style.top = 0;
|
||||
selection.style.left = 0;
|
||||
selection.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(selection);
|
||||
|
||||
messageComp.focus(); // we listen on keypresses on this element to cancel on escape
|
||||
|
||||
let isDragging = false;
|
||||
let draggingStartPos = null;
|
||||
let selectionArea = {};
|
||||
|
||||
function updateSelection() {
|
||||
selection.style.left = selectionArea.x + 'px';
|
||||
selection.style.top = selectionArea.y + 'px';
|
||||
selection.style.width = selectionArea.width + 'px';
|
||||
selection.style.height = selectionArea.height + 'px';
|
||||
}
|
||||
|
||||
function setSelectionSizeFromMouse(event) {
|
||||
if (event.clientX < draggingStartPos.x) {
|
||||
selectionArea.x = event.clientX;
|
||||
}
|
||||
|
||||
if (event.clientY < draggingStartPos.y) {
|
||||
selectionArea.y = event.clientY;
|
||||
}
|
||||
|
||||
selectionArea.width = Math.max(1, Math.abs(event.clientX - draggingStartPos.x));
|
||||
selectionArea.height = Math.max(1, Math.abs(event.clientY - draggingStartPos.y));
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function selection_mouseDown(event) {
|
||||
selectionArea = {x: event.clientX, y: event.clientY, width: 0, height: 0};
|
||||
draggingStartPos = {x: event.clientX, y: event.clientY};
|
||||
isDragging = true;
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function selection_mouseMove(event) {
|
||||
if (!isDragging) return;
|
||||
setSelectionSizeFromMouse(event);
|
||||
}
|
||||
|
||||
function removeOverlay() {
|
||||
isDragging = false;
|
||||
|
||||
overlay.removeEventListener('mousedown', selection_mouseDown);
|
||||
overlay.removeEventListener('mousemove', selection_mouseMove);
|
||||
overlay.removeEventListener('mouseup', selection_mouseUp);
|
||||
|
||||
document.body.removeChild(overlay);
|
||||
document.body.removeChild(selection);
|
||||
document.body.removeChild(messageComp);
|
||||
}
|
||||
|
||||
function selection_mouseUp(event) {
|
||||
setSelectionSizeFromMouse(event);
|
||||
|
||||
removeOverlay();
|
||||
|
||||
console.info('selectionArea:', selectionArea);
|
||||
|
||||
if (!selectionArea || !selectionArea.width || !selectionArea.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to wait a bit before taking the screenshot to make sure
|
||||
// the overlays have been removed and don't appear in the
|
||||
// screenshot. 10ms is not enough.
|
||||
setTimeout(() => resolve(selectionArea), 100);
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
if (event.key === "Escape") {
|
||||
removeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
overlay.addEventListener('mousedown', selection_mouseDown);
|
||||
overlay.addEventListener('mousemove', selection_mouseMove);
|
||||
overlay.addEventListener('mouseup', selection_mouseUp);
|
||||
overlay.addEventListener('mouseup', selection_mouseUp);
|
||||
messageComp.addEventListener('keydown', cancel);
|
||||
});
|
||||
}
|
||||
|
||||
function makeLinksAbsolute(container) {
|
||||
for (const link of container.getElementsByTagName('a')) {
|
||||
if (link.href) {
|
||||
link.href = absoluteUrl(link.href);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getImages(container) {
|
||||
const images = [];
|
||||
|
||||
for (const img of container.getElementsByTagName('img')) {
|
||||
if (!img.src) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingImage = images.find(image => image.src === img.src);
|
||||
|
||||
if (existingImage) {
|
||||
img.src = existingImage.imageId;
|
||||
}
|
||||
else {
|
||||
const imageId = randomString(20);
|
||||
|
||||
images.push({
|
||||
imageId: imageId,
|
||||
src: img.src
|
||||
});
|
||||
|
||||
img.src = imageId;
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
function createLink(clickAction, text, 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)
|
||||
});
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
async function prepareMessageResponse(message) {
|
||||
console.info('Message: ' + message.name);
|
||||
|
||||
if (message.name === "toast") {
|
||||
let messageText;
|
||||
|
||||
if (message.noteId) {
|
||||
messageText = document.createElement('p');
|
||||
messageText.setAttribute("style", "padding: 0; margin: 0; font-size: larger;")
|
||||
messageText.appendChild(document.createTextNode(message.message + " "));
|
||||
messageText.appendChild(createLink(
|
||||
{name: 'openNoteInTrilium', noteId: message.noteId},
|
||||
"Open in Trilium."
|
||||
));
|
||||
|
||||
// only after saving tabs
|
||||
if (message.tabIds) {
|
||||
messageText.appendChild(document.createElement("br"));
|
||||
messageText.appendChild(createLink(
|
||||
{name: 'closeTabs', tabIds: message.tabIds},
|
||||
"Close saved tabs.",
|
||||
"tomato"
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
messageText = message.message;
|
||||
}
|
||||
|
||||
await requireLib('/lib/toast.js');
|
||||
|
||||
showToast(messageText, {
|
||||
settings: {
|
||||
duration: 7000
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (message.name === "trilium-save-selection") {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
||||
for (let i = 0; i < selection.rangeCount; i++) {
|
||||
const range = selection.getRangeAt(i);
|
||||
|
||||
container.appendChild(range.cloneContents());
|
||||
}
|
||||
|
||||
makeLinksAbsolute(container);
|
||||
|
||||
const images = getImages(container);
|
||||
|
||||
return {
|
||||
title: pageTitle(),
|
||||
content: container.innerHTML,
|
||||
images: images,
|
||||
pageUrl: getPageLocationOrigin() + location.pathname + location.search + location.hash
|
||||
};
|
||||
|
||||
}
|
||||
else if (message.name === 'trilium-get-rectangle-for-screenshot') {
|
||||
return getRectangleArea();
|
||||
}
|
||||
else if (message.name === "trilium-save-page") {
|
||||
await requireLib("/lib/JSDOMParser.js");
|
||||
await requireLib("/lib/Readability.js");
|
||||
await requireLib("/lib/Readability-readerable.js");
|
||||
|
||||
const {title, body} = getReadableDocument();
|
||||
|
||||
makeLinksAbsolute(body);
|
||||
|
||||
const images = getImages(body);
|
||||
|
||||
var labels = {};
|
||||
const dates = getDocumentDates();
|
||||
if (dates.publishedDate) {
|
||||
labels['publishedDate'] = dates.publishedDate.toISOString().substring(0, 10);
|
||||
}
|
||||
if (dates.modifiedDate) {
|
||||
labels['modifiedDate'] = dates.publishedDate.toISOString().substring(0, 10);
|
||||
}
|
||||
|
||||
return {
|
||||
title: title,
|
||||
content: body.innerHTML,
|
||||
images: images,
|
||||
pageUrl: getPageLocationOrigin() + location.pathname + location.search,
|
||||
clipType: 'page',
|
||||
labels: labels
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new Error('Unknown command: ' + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
browser.runtime.onMessage.addListener(prepareMessageResponse);
|
||||
|
||||
const loadedLibs = [];
|
||||
|
||||
async function requireLib(libPath) {
|
||||
if (!loadedLibs.includes(libPath)) {
|
||||
loadedLibs.push(libPath);
|
||||
|
||||
await browser.runtime.sendMessage({name: 'load-script', file: libPath});
|
||||
}
|
||||
}
|
||||
export default defineContentScript({
|
||||
matches: [
|
||||
"<all_urls>"
|
||||
],
|
||||
main: () => {
|
||||
function absoluteUrl(url) {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const protocol = url.toLowerCase().split(':')[0];
|
||||
if (['http', 'https', 'file'].indexOf(protocol) >= 0) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (url.indexOf('//') === 0) {
|
||||
return location.protocol + url;
|
||||
} else if (url[0] === '/') {
|
||||
return location.protocol + '//' + location.host + url;
|
||||
} else {
|
||||
return getBaseUrl() + '/' + url;
|
||||
}
|
||||
}
|
||||
|
||||
function pageTitle() {
|
||||
const titleElements = document.getElementsByTagName("title");
|
||||
|
||||
return titleElements.length ? titleElements[0].text.trim() : document.title.trim();
|
||||
}
|
||||
|
||||
function getReadableDocument() {
|
||||
// Readability directly change the passed document, so clone to preserve the original web page.
|
||||
const documentCopy = document.cloneNode(true);
|
||||
const readability = new Readability(documentCopy, {
|
||||
serializer: el => el // so that .content is returned as DOM element instead of HTML
|
||||
});
|
||||
|
||||
const article = readability.parse();
|
||||
|
||||
if (!article) {
|
||||
throw new Error('Could not parse HTML document with Readability');
|
||||
}
|
||||
|
||||
return {
|
||||
title: article.title,
|
||||
body: article.content,
|
||||
}
|
||||
}
|
||||
|
||||
function getDocumentDates() {
|
||||
var dates = {
|
||||
publishedDate: null,
|
||||
modifiedDate: null,
|
||||
};
|
||||
|
||||
const articlePublishedTime = document.querySelector("meta[property='article:published_time']");
|
||||
if (articlePublishedTime && articlePublishedTime.getAttribute('content')) {
|
||||
dates.publishedDate = new Date(articlePublishedTime.getAttribute('content'));
|
||||
}
|
||||
|
||||
const articleModifiedTime = document.querySelector("meta[property='article:modified_time']");
|
||||
if (articleModifiedTime && articleModifiedTime.getAttribute('content')) {
|
||||
dates.modifiedDate = new Date(articleModifiedTime.getAttribute('content'));
|
||||
}
|
||||
|
||||
// TODO: if we didn't get dates from meta, then try to get them from JSON-LD
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
function getRectangleArea() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.opacity = '0.6';
|
||||
overlay.style.background = 'black';
|
||||
overlay.style.width = '100%';
|
||||
overlay.style.height = '100%';
|
||||
overlay.style.zIndex = 99999999;
|
||||
overlay.style.top = 0;
|
||||
overlay.style.left = 0;
|
||||
overlay.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const messageComp = document.createElement('div');
|
||||
|
||||
const messageCompWidth = 300;
|
||||
messageComp.setAttribute("tabindex", "0"); // so that it can be focused
|
||||
messageComp.style.position = 'fixed';
|
||||
messageComp.style.opacity = '0.95';
|
||||
messageComp.style.fontSize = '14px';
|
||||
messageComp.style.width = messageCompWidth + 'px';
|
||||
messageComp.style.maxWidth = messageCompWidth + 'px';
|
||||
messageComp.style.border = '1px solid black';
|
||||
messageComp.style.background = 'white';
|
||||
messageComp.style.color = 'black';
|
||||
messageComp.style.top = '10px';
|
||||
messageComp.style.textAlign = 'center';
|
||||
messageComp.style.padding = '10px';
|
||||
messageComp.style.left = Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) + 'px';
|
||||
messageComp.style.zIndex = overlay.style.zIndex + 1;
|
||||
|
||||
messageComp.textContent = 'Drag and release to capture a screenshot';
|
||||
|
||||
document.body.appendChild(messageComp);
|
||||
|
||||
const selection = document.createElement('div');
|
||||
selection.style.opacity = '0.5';
|
||||
selection.style.border = '1px solid red';
|
||||
selection.style.background = 'white';
|
||||
selection.style.border = '2px solid black';
|
||||
selection.style.zIndex = overlay.style.zIndex - 1;
|
||||
selection.style.top = 0;
|
||||
selection.style.left = 0;
|
||||
selection.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(selection);
|
||||
|
||||
messageComp.focus(); // we listen on keypresses on this element to cancel on escape
|
||||
|
||||
let isDragging = false;
|
||||
let draggingStartPos = null;
|
||||
let selectionArea = {};
|
||||
|
||||
function updateSelection() {
|
||||
selection.style.left = selectionArea.x + 'px';
|
||||
selection.style.top = selectionArea.y + 'px';
|
||||
selection.style.width = selectionArea.width + 'px';
|
||||
selection.style.height = selectionArea.height + 'px';
|
||||
}
|
||||
|
||||
function setSelectionSizeFromMouse(event) {
|
||||
if (event.clientX < draggingStartPos.x) {
|
||||
selectionArea.x = event.clientX;
|
||||
}
|
||||
|
||||
if (event.clientY < draggingStartPos.y) {
|
||||
selectionArea.y = event.clientY;
|
||||
}
|
||||
|
||||
selectionArea.width = Math.max(1, Math.abs(event.clientX - draggingStartPos.x));
|
||||
selectionArea.height = Math.max(1, Math.abs(event.clientY - draggingStartPos.y));
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function selection_mouseDown(event) {
|
||||
selectionArea = {x: event.clientX, y: event.clientY, width: 0, height: 0};
|
||||
draggingStartPos = {x: event.clientX, y: event.clientY};
|
||||
isDragging = true;
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function selection_mouseMove(event) {
|
||||
if (!isDragging) return;
|
||||
setSelectionSizeFromMouse(event);
|
||||
}
|
||||
|
||||
function removeOverlay() {
|
||||
isDragging = false;
|
||||
|
||||
overlay.removeEventListener('mousedown', selection_mouseDown);
|
||||
overlay.removeEventListener('mousemove', selection_mouseMove);
|
||||
overlay.removeEventListener('mouseup', selection_mouseUp);
|
||||
|
||||
document.body.removeChild(overlay);
|
||||
document.body.removeChild(selection);
|
||||
document.body.removeChild(messageComp);
|
||||
}
|
||||
|
||||
function selection_mouseUp(event) {
|
||||
setSelectionSizeFromMouse(event);
|
||||
|
||||
removeOverlay();
|
||||
|
||||
console.info('selectionArea:', selectionArea);
|
||||
|
||||
if (!selectionArea || !selectionArea.width || !selectionArea.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to wait a bit before taking the screenshot to make sure
|
||||
// the overlays have been removed and don't appear in the
|
||||
// screenshot. 10ms is not enough.
|
||||
setTimeout(() => resolve(selectionArea), 100);
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
if (event.key === "Escape") {
|
||||
removeOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
overlay.addEventListener('mousedown', selection_mouseDown);
|
||||
overlay.addEventListener('mousemove', selection_mouseMove);
|
||||
overlay.addEventListener('mouseup', selection_mouseUp);
|
||||
overlay.addEventListener('mouseup', selection_mouseUp);
|
||||
messageComp.addEventListener('keydown', cancel);
|
||||
});
|
||||
}
|
||||
|
||||
function makeLinksAbsolute(container) {
|
||||
for (const link of container.getElementsByTagName('a')) {
|
||||
if (link.href) {
|
||||
link.href = absoluteUrl(link.href);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getImages(container) {
|
||||
const images = [];
|
||||
|
||||
for (const img of container.getElementsByTagName('img')) {
|
||||
if (!img.src) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingImage = images.find(image => image.src === img.src);
|
||||
|
||||
if (existingImage) {
|
||||
img.src = existingImage.imageId;
|
||||
}
|
||||
else {
|
||||
const imageId = randomString(20);
|
||||
|
||||
images.push({
|
||||
imageId: imageId,
|
||||
src: img.src
|
||||
});
|
||||
|
||||
img.src = imageId;
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
function createLink(clickAction, text, 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)
|
||||
});
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
async function prepareMessageResponse(message) {
|
||||
console.info('Message: ' + message.name);
|
||||
|
||||
if (message.name === "toast") {
|
||||
let messageText;
|
||||
|
||||
if (message.noteId) {
|
||||
messageText = document.createElement('p');
|
||||
messageText.setAttribute("style", "padding: 0; margin: 0; font-size: larger;")
|
||||
messageText.appendChild(document.createTextNode(message.message + " "));
|
||||
messageText.appendChild(createLink(
|
||||
{name: 'openNoteInTrilium', noteId: message.noteId},
|
||||
"Open in Trilium."
|
||||
));
|
||||
|
||||
// only after saving tabs
|
||||
if (message.tabIds) {
|
||||
messageText.appendChild(document.createElement("br"));
|
||||
messageText.appendChild(createLink(
|
||||
{name: 'closeTabs', tabIds: message.tabIds},
|
||||
"Close saved tabs.",
|
||||
"tomato"
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
messageText = message.message;
|
||||
}
|
||||
|
||||
await requireLib('/lib/toast.js');
|
||||
|
||||
showToast(messageText, {
|
||||
settings: {
|
||||
duration: 7000
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (message.name === "trilium-save-selection") {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
||||
for (let i = 0; i < selection.rangeCount; i++) {
|
||||
const range = selection.getRangeAt(i);
|
||||
|
||||
container.appendChild(range.cloneContents());
|
||||
}
|
||||
|
||||
makeLinksAbsolute(container);
|
||||
|
||||
const images = getImages(container);
|
||||
|
||||
return {
|
||||
title: pageTitle(),
|
||||
content: container.innerHTML,
|
||||
images: images,
|
||||
pageUrl: getPageLocationOrigin() + location.pathname + location.search + location.hash
|
||||
};
|
||||
|
||||
}
|
||||
else if (message.name === 'trilium-get-rectangle-for-screenshot') {
|
||||
return getRectangleArea();
|
||||
}
|
||||
else if (message.name === "trilium-save-page") {
|
||||
await requireLib("/lib/JSDOMParser.js");
|
||||
await requireLib("/lib/Readability.js");
|
||||
await requireLib("/lib/Readability-readerable.js");
|
||||
|
||||
const {title, body} = getReadableDocument();
|
||||
|
||||
makeLinksAbsolute(body);
|
||||
|
||||
const images = getImages(body);
|
||||
|
||||
var labels = {};
|
||||
const dates = getDocumentDates();
|
||||
if (dates.publishedDate) {
|
||||
labels['publishedDate'] = dates.publishedDate.toISOString().substring(0, 10);
|
||||
}
|
||||
if (dates.modifiedDate) {
|
||||
labels['modifiedDate'] = dates.publishedDate.toISOString().substring(0, 10);
|
||||
}
|
||||
|
||||
return {
|
||||
title: title,
|
||||
content: body.innerHTML,
|
||||
images: images,
|
||||
pageUrl: getPageLocationOrigin() + location.pathname + location.search,
|
||||
clipType: 'page',
|
||||
labels: labels
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new Error('Unknown command: ' + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
const loadedLibs = [];
|
||||
|
||||
async function requireLib(libPath) {
|
||||
if (!loadedLibs.includes(libPath)) {
|
||||
loadedLibs.push(libPath);
|
||||
|
||||
await browser.runtime.sendMessage({name: 'load-script', file: libPath});
|
||||
}
|
||||
}
|
||||
|
||||
browser.runtime.onMessage.addListener(prepareMessageResponse);
|
||||
}
|
||||
});
|
||||
|
||||
@ -26,9 +26,6 @@
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"lib/browser-polyfill.js",
|
||||
"utils.js"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user