refactor: clipper screenshot (#2325)

This commit is contained in:
Aman Harwara
2023-05-05 20:12:44 +05:30
committed by GitHub
parent 313ce08c15
commit 2b845e814e
8 changed files with 65 additions and 24 deletions

View File

@@ -32,7 +32,6 @@
"webpack": "*" "webpack": "*"
}, },
"dependencies": { "dependencies": {
"@mozilla/readability": "^0.4.2", "@mozilla/readability": "^0.4.2"
"html-to-image": "^1.11.11"
} }
} }

View File

@@ -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',
})
} }
}) })

View File

@@ -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

View File

@@ -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"
}, },

View File

@@ -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

View File

@@ -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}>

View File

@@ -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"