refactor: clipper screenshot (#2325)
This commit is contained in:
Binary file not shown.
@@ -32,7 +32,6 @@
|
|||||||
"webpack": "*"
|
"webpack": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mozilla/readability": "^0.4.2",
|
"@mozilla/readability": "^0.4.2"
|
||||||
"html-to-image": "^1.11.11"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { runtime, action, browserAction, windows, storage } from 'webextension-polyfill'
|
import { runtime, action, browserAction, windows, storage, tabs } from 'webextension-polyfill'
|
||||||
import { ClipPayload, RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
import { ClipPayload, RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
||||||
|
|
||||||
const isFirefox = navigator.userAgent.indexOf('Firefox/') !== -1
|
const isFirefox = navigator.userAgent.indexOf('Firefox/') !== -1
|
||||||
@@ -22,11 +22,15 @@ const openPopupAndClipSelection = async (payload: ClipPayload) => {
|
|||||||
void openPopup()
|
void openPopup()
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.onMessage.addListener((message: RuntimeMessage) => {
|
runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
||||||
if (message.type === RuntimeMessageTypes.OpenPopupWithSelection) {
|
if (message.type === RuntimeMessageTypes.OpenPopupWithSelection) {
|
||||||
if (!message.payload) {
|
if (!message.payload) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void openPopupAndClipSelection(message.payload)
|
void openPopupAndClipSelection(message.payload)
|
||||||
|
} else if (message.type === RuntimeMessageTypes.CaptureVisibleTab) {
|
||||||
|
return await tabs.captureVisibleTab(undefined, {
|
||||||
|
format: 'png',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { runtime } from 'webextension-polyfill'
|
import { runtime } from 'webextension-polyfill'
|
||||||
import { Readability } from '@mozilla/readability'
|
import { Readability } from '@mozilla/readability'
|
||||||
import { RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
import { RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
||||||
import { toPng } from 'html-to-image'
|
|
||||||
|
|
||||||
let isSelectingNodeForClipping = false
|
let isSelectingNodeForClipping = false
|
||||||
let isScreenshotMode = false
|
let isScreenshotMode = false
|
||||||
@@ -47,7 +46,9 @@ runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
|||||||
}
|
}
|
||||||
case RuntimeMessageTypes.GetFullPage: {
|
case RuntimeMessageTypes.GetFullPage: {
|
||||||
if (isScreenshotMode) {
|
if (isScreenshotMode) {
|
||||||
const content = await toPng(document.body)
|
const content = await runtime.sendMessage({
|
||||||
|
type: RuntimeMessageTypes.CaptureVisibleTab,
|
||||||
|
})
|
||||||
return { title: document.title, content: content, url: window.location.href, isScreenshot: true }
|
return { title: document.title, content: content, url: window.location.href, isScreenshot: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +102,55 @@ const disableNodeSelection = () => {
|
|||||||
nodeOverlayElement.style.visibility = 'hidden'
|
nodeOverlayElement.style.visibility = 'hidden'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imageFromBase64 = (base64: string) => {
|
||||||
|
return new Promise<HTMLImageElement>((resolve, reject) => {
|
||||||
|
const image = new Image()
|
||||||
|
image.onload = () => {
|
||||||
|
resolve(image)
|
||||||
|
}
|
||||||
|
image.onerror = reject
|
||||||
|
image.src = base64
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const toPng = async (element: HTMLElement) => {
|
||||||
|
const visibleTab: string = await runtime.sendMessage({
|
||||||
|
type: RuntimeMessageTypes.CaptureVisibleTab,
|
||||||
|
})
|
||||||
|
|
||||||
|
const image = await imageFromBase64(visibleTab)
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
|
||||||
|
canvas.width = image.width
|
||||||
|
canvas.height = image.height
|
||||||
|
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Could not get canvas context')
|
||||||
|
}
|
||||||
|
|
||||||
|
context.drawImage(image, 0, 0)
|
||||||
|
|
||||||
|
const elementRect = element.getBoundingClientRect()
|
||||||
|
|
||||||
|
const x = elementRect.x
|
||||||
|
const y = elementRect.y
|
||||||
|
|
||||||
|
const width = elementRect.width
|
||||||
|
const height = elementRect.height
|
||||||
|
|
||||||
|
const imageData = context.getImageData(x, y, width, height)
|
||||||
|
|
||||||
|
canvas.width = width
|
||||||
|
canvas.height = height
|
||||||
|
|
||||||
|
context.putImageData(imageData, 0, 0)
|
||||||
|
|
||||||
|
return canvas.toDataURL('image/png')
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('click', async (event) => {
|
window.addEventListener('click', async (event) => {
|
||||||
if (!isSelectingNodeForClipping) {
|
if (!isSelectingNodeForClipping) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Standard Notes Clipper",
|
"name": "Standard Notes Clipper",
|
||||||
"description": "Web clipper for Standard Notes",
|
"description": "Web clipper for Standard Notes",
|
||||||
"permissions": ["activeTab", "storage"],
|
"permissions": ["activeTab", "storage", "<all_urls>"],
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_popup": "popup/index.html?route=extension"
|
"default_popup": "popup/index.html?route=extension"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const RuntimeMessageTypes = {
|
|||||||
OpenPopupWithSelection: 'open-popup-with-selection',
|
OpenPopupWithSelection: 'open-popup-with-selection',
|
||||||
StartNodeSelection: 'start-node-selection',
|
StartNodeSelection: 'start-node-selection',
|
||||||
ToggleScreenshotMode: 'toggle-screenshot-mode',
|
ToggleScreenshotMode: 'toggle-screenshot-mode',
|
||||||
|
CaptureVisibleTab: 'capture-visible-tab',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type RuntimeMessageType = typeof RuntimeMessageTypes[keyof typeof RuntimeMessageTypes]
|
export type RuntimeMessageType = typeof RuntimeMessageTypes[keyof typeof RuntimeMessageTypes]
|
||||||
@@ -24,6 +25,7 @@ export type RuntimeMessageReturnTypes = {
|
|||||||
[RuntimeMessageTypes.GetSelection]: ClipPayload
|
[RuntimeMessageTypes.GetSelection]: ClipPayload
|
||||||
[RuntimeMessageTypes.HasSelection]: boolean
|
[RuntimeMessageTypes.HasSelection]: boolean
|
||||||
[RuntimeMessageTypes.GetFullPage]: ClipPayload
|
[RuntimeMessageTypes.GetFullPage]: ClipPayload
|
||||||
|
[RuntimeMessageTypes.CaptureVisibleTab]: string
|
||||||
[RuntimeMessageTypes.OpenPopupWithSelection]: void
|
[RuntimeMessageTypes.OpenPopupWithSelection]: void
|
||||||
[RuntimeMessageTypes.StartNodeSelection]: void
|
[RuntimeMessageTypes.StartNodeSelection]: void
|
||||||
[RuntimeMessageTypes.ToggleScreenshotMode]: void
|
[RuntimeMessageTypes.ToggleScreenshotMode]: void
|
||||||
|
|||||||
@@ -340,15 +340,9 @@ const ClipperView = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setClipPayload(payload)
|
setClipPayload(payload)
|
||||||
if (isScreenshotMode) {
|
|
||||||
addToast({
|
|
||||||
type: ToastType.Regular,
|
|
||||||
message: 'Capturing full page...',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Clip full page
|
{isScreenshotMode ? 'Capture visible' : 'Clip full page'}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={isScreenshotMode}
|
disabled={isScreenshotMode}
|
||||||
@@ -380,7 +374,7 @@ const ClipperView = ({
|
|||||||
window.close()
|
window.close()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Select elements to clip
|
Select elements to {isScreenshotMode ? 'capture' : 'clip'}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuSwitchButtonItem
|
<MenuSwitchButtonItem
|
||||||
checked={isScreenshotMode}
|
checked={isScreenshotMode}
|
||||||
@@ -393,7 +387,7 @@ const ClipperView = ({
|
|||||||
</MenuSwitchButtonItem>
|
</MenuSwitchButtonItem>
|
||||||
<div className="border-t border-border px-3 py-3 text-base text-foreground">
|
<div className="border-t border-border px-3 py-3 text-base text-foreground">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="font-medium">Default tag:</div>
|
<div className="font-medium">Default tag</div>
|
||||||
{defaultTag && (
|
{defaultTag && (
|
||||||
<StyledTooltip label="Remove default tag" gutter={2}>
|
<StyledTooltip label="Remove default tag" gutter={2}>
|
||||||
<button className="rounded-full p-1 hover:bg-contrast hover:text-info" onClick={unselectTag}>
|
<button className="rounded-full p-1 hover:bg-contrast hover:text-info" onClick={unselectTag}>
|
||||||
|
|||||||
@@ -4709,7 +4709,6 @@ __metadata:
|
|||||||
copy-webpack-plugin: 11.0.0
|
copy-webpack-plugin: 11.0.0
|
||||||
eslint: ^8.29.0
|
eslint: ^8.29.0
|
||||||
eslint-config-prettier: ^8.5.0
|
eslint-config-prettier: ^8.5.0
|
||||||
html-to-image: ^1.11.11
|
|
||||||
ts-loader: ^9.4.2
|
ts-loader: ^9.4.2
|
||||||
typescript: "*"
|
typescript: "*"
|
||||||
web-ext: ^7.5.0
|
web-ext: ^7.5.0
|
||||||
@@ -13713,13 +13712,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"html-to-image@npm:^1.11.11":
|
|
||||||
version: 1.11.11
|
|
||||||
resolution: "html-to-image@npm:1.11.11"
|
|
||||||
checksum: b453beca72a697bf06fae4945e5460d1d9b1751e8569a0d721dda9485df1dde093938cc9bd9172b8df5fc23133a53a4d619777b3d22f7211cd8a67e3197ab4e8
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"htmlparser2@npm:^8.0.1":
|
"htmlparser2@npm:^8.0.1":
|
||||||
version: 8.0.1
|
version: 8.0.1
|
||||||
resolution: "htmlparser2@npm:8.0.1"
|
resolution: "htmlparser2@npm:8.0.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user