From ecee0b6236b0cd8bfda063a8dd288a49dfc82ece Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sat, 7 Jan 2023 22:08:29 +0800
Subject: [PATCH 01/21] chore: refactor build process
Signed-off-by: harytfw
---
Makefile | 55 +++++++------------
scripts/gen_manifest.js | 11 +---
rollup.config.js => scripts/rollup.config.js | 40 +++++++++-----
.../test_bootstrap.js | 5 +-
scripts/utils.js | 17 ++++++
5 files changed, 67 insertions(+), 61 deletions(-)
rename rollup.config.js => scripts/rollup.config.js (63%)
rename test/bootstrap.mjs => scripts/test_bootstrap.js (90%)
create mode 100644 scripts/utils.js
diff --git a/Makefile b/Makefile
index 77069be..bb97034 100644
--- a/Makefile
+++ b/Makefile
@@ -1,46 +1,41 @@
export BUILD_VERSION ?= 2.1.4
-export SRC ?= $(shell realpath ./src)
export BUILD_DIR ?= $(shell realpath ./build)
-export BROWSER_LOCATION ?= $(shell which firefox-developer-edition)
-export NODE_MODULES ?= $(shell realpath ./node_modules)
+export SRC ?= $(shell realpath ./src)
export BUILD_PROFILE ?= debug
-export BUILD_COMMIT_ID ?= $(shell git rev-parse --short HEAD)
-export BUILD_DATE ?= $(shell date --rfc-3339=seconds)
-export BUILD_NODE_VERSION ?= $(shell node --version)
-export BUILD_ROLLUP_VERSION ?= $(shell npx rollup --version)
-export BUILD_OS ?= $(shell node -e "console.log(require('os').platform())")
export BUILD_WEBSOCKET_SERVER =
-
-export TARGET_BROWSER = firefox
-export TARGET_DIST = $(BUILD_DIR)/$(TARGET_BROWSER)/dist
-
export ENTRY_POINTS = background content_scripts options components
+export TARGET_BROWSER = firefox
+export TARGET_DIST = $(BUILD_DIR)/firefox/dist
.PHONY: ext-firefox
ext-firefox: TARGET_BROWSER = firefox
-ext-firefox: setup-dist assets compile lint package
+ext-firefox: TARGET_DIST = $(BUILD_DIR)/firefox/dist
+ext-firefox: setup-dist assets compile lint
+ cd $(TARGET_DIST) && zip -r $(BUILD_DIR)/artifacts/glitterdrag-pro-$(BUILD_VERSION)-firefox.zip .
.PHONY: ext-chromium
-ext-chromium: TARGET_BROWSER = chromium
-ext-chromium: setup-dist assets compile package
+ext-test: TARGET_BROWSER = chromium
+ext-chromium: TARGET_DIST = $(BUILD_DIR)/chromium/dist
+ext-chromium: setup-dist assets compile
+ cd $(TARGET_DIST) && zip -r $(BUILD_DIR)/artifacts/glitterdrag-pro-$(BUILD_VERSION)-chromium.zip .
.PHONY: test
test: TARGET_BROWSER = firefox-test
test: ENTRY_POINTS += test
+test: TARGET_DIST = $(BUILD_DIR)/test/dist
test: BUILD_WEBSOCKET_SERVER = ws://localhost:8000
test: setup-dist assets mocha-assets compile
- @node test/bootstrap.mjs
+ @node scripts/test_bootstrap.js
.PHONY: open-mocha
-open-mocha: TARGET_BROWSER = firefox-test
+open-mocha: TARGET_DIST = $(BUILD_DIR)/firefox/dist
open-mocha: ENTRY_POINTS += test
open-mocha: setup-dist assets mocha-assets compile
web-ext run -v \
-s $(TARGET_DIST) \
- -f "$(BROWSER_LOCATION)"
+ -f "$(shell which firefox-developer-edition)"
.PHONY: setup-dist
-setup-dist: TARGET_DIST = $(BUILD_DIR)/$(TARGET_BROWSER)/dist
setup-dist:
$(shell [ ! -d $(TARGET_DIST) ] && mkdir -p $(TARGET_DIST)/)
@@ -50,7 +45,7 @@ lint:
.PHONY: compile
compile:
- @npx rollup -c;
+ @pnpm exec rollup -c "./scripts/rollup.config.js"
.PHONY: manifest
manifest:
@@ -63,29 +58,19 @@ assets: manifest
cp -f $(SRC)/options/options.html $(TARGET_DIST)/options/options.html
@mkdir -p $(TARGET_DIST)/res && \
- cp -r -f $(NODE_MODULES)/simpledotcss/simple.min.css $(TARGET_DIST)/res/simple.min.css
+ cp -r -f node_modules/simpledotcss/simple.min.css $(TARGET_DIST)/res/simple.min.css
@cp -r -f $(SRC)/icon/ $(SRC)/_locales/ $(TARGET_DIST)/
.PHONY: mocha-assets
mocha-assets:
@mkdir -p $(TARGET_DIST)/test && \
- cp $(NODE_MODULES)/mocha/mocha.js \
- $(NODE_MODULES)/mocha/mocha.css \
- $(NODE_MODULES)/chai/chai.js \
+ cp node_modules/mocha/mocha.js \
+ node_modules/mocha/mocha.css \
+ node_modules/chai/chai.js \
$(SRC)/test/mocha.html \
$(TARGET_DIST)/test
-.PHONY: package
-package:
- @mkdir -p $(BUILD_DIR)/artifacts
- @cd $(TARGET_DIST) && zip -r $(BUILD_DIR)/artifacts/glitterdrag-pro-$(BUILD_VERSION)-$(TARGET_BROWSER).zip .
-
.PHONY: clean
clean:
- @rm -rf $(BUILD_DIR)
-
-
-start-server: port ?= 8000
-start-server:
- @python3 -m http.server $(port)
+ @rm -rf $(BUILD_DIR)
\ No newline at end of file
diff --git a/scripts/gen_manifest.js b/scripts/gen_manifest.js
index 6c9600d..4b74b7b 100644
--- a/scripts/gen_manifest.js
+++ b/scripts/gen_manifest.js
@@ -1,13 +1,4 @@
-
-function mustEnv(key) {
- const value = process.env[key]
- if (!value || value.length === 0) {
- console.error("require environment: " + key)
- process.exit(1)
- }
- return value
-}
-
+import { mustEnv } from "./utils.js"
const version = mustEnv("BUILD_VERSION")
const targetBrowser = mustEnv("TARGET_BROWSER")
diff --git a/rollup.config.js b/scripts/rollup.config.js
similarity index 63%
rename from rollup.config.js
rename to scripts/rollup.config.js
index 36787da..3da5734 100644
--- a/rollup.config.js
+++ b/scripts/rollup.config.js
@@ -1,3 +1,5 @@
+import { mustEnv, readStdout } from "./utils.js"
+
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
@@ -5,23 +7,32 @@ import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';
import autoPreprocess from 'svelte-preprocess';
import commonjs from '@rollup/plugin-commonjs';
-import pathLib from 'path'
+import pathLib from 'node:path'
+import os from "node:os"
-function mustEnv(name) {
- const value = process.env[name]
- if (!value) {
- console.error("require env: " + name)
- process.exit(1)
- }
- return value
-}
+const BUILD_COMMIT_ID = readStdout("git rev-parse --short HEAD")
+const BUILD_DATE = readStdout("date --rfc-3339=seconds")
+const BUILD_NODE_VERSION = readStdout("node --version")
+const BUILD_ROLLUP_VERSION = readStdout("npx rollup --version")
+const BUILD_OS = os.platform()
+const BUILD_PROFILE = mustEnv("BUILD_PROFILE")
+const BUILD_WEBSOCKET_SERVER = mustEnv("BUILD_WEBSOCKET_SERVER")
-const isProd = mustEnv("BUILD_PROFILE").toLowerCase() === 'prod';
+const isProd = BUILD_PROFILE.toLowerCase() === 'prod';
+const sourceMap = !isProd
const src = mustEnv("SRC")
const dist = mustEnv("TARGET_DIST")
const entryPoints = mustEnv("ENTRY_POINTS").split(" ")
-const safeEnvVar = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[0].startsWith("BUILD")))
+const safeEnvVar = {
+ BUILD_COMMIT_ID,
+ BUILD_DATE,
+ BUILD_NODE_VERSION,
+ BUILD_ROLLUP_VERSION,
+ BUILD_OS,
+ BUILD_PROFILE,
+ BUILD_WEBSOCKET_SERVER,
+}
const useCustomElement = ["components"]
@@ -38,7 +49,7 @@ function getPlugins(entrypoint) {
}),
replace({
__ENV: JSON.stringify(safeEnvVar),
- __BUILD_PROFILE: JSON.stringify(process.env.BUILD_PROFILE),
+ __BUILD_PROFILE: JSON.stringify(BUILD_PROFILE),
preventAssignment: true
}),
svelte({
@@ -58,15 +69,16 @@ function getPlugins(entrypoint) {
return plugins
}
+console.log("sourcemap: ", sourceMap)
+
const output = []
-console.log("sourcemap: ", !isProd)
for (const e of entryPoints) {
output.push({
external: ["chai", "mocha", "Mocha"],
input: pathLib.join(src, e, "main.ts"),
output: {
- sourcemap: !isProd,
+ sourcemap: sourceMap,
globals: {
"chai": "chai",
"mocha": "mocha",
diff --git a/test/bootstrap.mjs b/scripts/test_bootstrap.js
similarity index 90%
rename from test/bootstrap.mjs
rename to scripts/test_bootstrap.js
index ade0448..ffd3c86 100644
--- a/test/bootstrap.mjs
+++ b/scripts/test_bootstrap.js
@@ -1,16 +1,17 @@
import { WebSocketServer } from 'ws';
import { spawn } from 'node:child_process';
+import webExt from 'web-ext'
const TEST_TIMEOUT_MS = 1000 * 60 * 5
-const { BROWSER_LOCATION, TARGET_DIST, BUILD_WEBSOCKET_SERVER } = process.env
+const { TARGET_DIST, BUILD_WEBSOCKET_SERVER } = process.env
async function runBrowser(signal) {
return new Promise((resolve, reject) => {
- const proc = spawn("web-ext", ["run", "-f", BROWSER_LOCATION, "-s", TARGET_DIST], {
+ const proc = spawn("pnpm", ["exec", "web-ext", "run", "-f", "firefox-developer-edition" , "-s", TARGET_DIST], {
stdio: ["ignore", process.stdout, process.stderr],
signal: signal
})
diff --git a/scripts/utils.js b/scripts/utils.js
new file mode 100644
index 0000000..6cc58d3
--- /dev/null
+++ b/scripts/utils.js
@@ -0,0 +1,17 @@
+
+import { execSync } from 'node:child_process'
+
+export function mustEnv(key) {
+ const value = process.env[key]
+ if (!value || value.length === 0) {
+ console.error("require environment: " + key)
+ process.exit(1)
+ }
+ return value
+}
+
+
+export function readStdout(command) {
+ const result = execSync(command)
+ return result.toString("utf8")
+}
From e3a626ebd624f5390ce17a6176e995313b5f8063 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sat, 7 Jan 2023 22:31:03 +0800
Subject: [PATCH 02/21] chore: fix build
Signed-off-by: harytfw
---
Makefile | 2 ++
scripts/rollup.config.js | 2 +-
scripts/utils.js | 5 ++++-
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index bb97034..cf1a5ff 100644
--- a/Makefile
+++ b/Makefile
@@ -7,6 +7,8 @@ export ENTRY_POINTS = background content_scripts options components
export TARGET_BROWSER = firefox
export TARGET_DIST = $(BUILD_DIR)/firefox/dist
+$(shell mkdir -p $(BUILD_DIR)/artifacts/)
+
.PHONY: ext-firefox
ext-firefox: TARGET_BROWSER = firefox
ext-firefox: TARGET_DIST = $(BUILD_DIR)/firefox/dist
diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js
index 3da5734..560c9b8 100644
--- a/scripts/rollup.config.js
+++ b/scripts/rollup.config.js
@@ -16,7 +16,7 @@ const BUILD_NODE_VERSION = readStdout("node --version")
const BUILD_ROLLUP_VERSION = readStdout("npx rollup --version")
const BUILD_OS = os.platform()
const BUILD_PROFILE = mustEnv("BUILD_PROFILE")
-const BUILD_WEBSOCKET_SERVER = mustEnv("BUILD_WEBSOCKET_SERVER")
+const BUILD_WEBSOCKET_SERVER = mustEnv("BUILD_WEBSOCKET_SERVER", "")
const isProd = BUILD_PROFILE.toLowerCase() === 'prod';
const sourceMap = !isProd
diff --git a/scripts/utils.js b/scripts/utils.js
index 6cc58d3..ec7cab8 100644
--- a/scripts/utils.js
+++ b/scripts/utils.js
@@ -1,9 +1,12 @@
import { execSync } from 'node:child_process'
-export function mustEnv(key) {
+export function mustEnv(key, default_value) {
const value = process.env[key]
if (!value || value.length === 0) {
+ if (typeof default_value !== "undefined") {
+ return default_value
+ }
console.error("require environment: " + key)
process.exit(1)
}
From 1b46221ba26abd0cf25bbe217701053c0cd1f73d Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sat, 7 Jan 2023 22:32:55 +0800
Subject: [PATCH 03/21] fix: fix check defaultPrevented at dragstart event
Signed-off-by: harytfw
---
src/content_scripts/drag.ts | 27 +++++++++++++++------------
src/content_scripts/main.ts | 2 +-
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/src/content_scripts/drag.ts b/src/content_scripts/drag.ts
index 8b5b6b8..8b89ccb 100644
--- a/src/content_scripts/drag.ts
+++ b/src/content_scripts/drag.ts
@@ -46,15 +46,15 @@ export class DragController {
return
}
for (const n of ["dragstart", "dragover", "dragenter", 'dragleave', "drop", "dragend"]) {
- this.eventSource.addEventListener(n, this.handler, true)
- this.eventSource.addEventListener(n, this.handler, false)
+ this.eventSource.addEventListener(n, this.handler, {capture: true})
+ this.eventSource.addEventListener(n, this.handler, {capture: false})
}
}
stop() {
for (const n of ["dragstart", "dragover", "dragenter", 'dragleave', "drop", "dragend"]) {
- this.eventSource.removeEventListener(n, this.handler, true);
- this.eventSource.removeEventListener(n, this.handler, false);
+ this.eventSource.removeEventListener(n, this.handler, {capture: true});
+ this.eventSource.removeEventListener(n, this.handler, {capture: false});
}
}
@@ -169,14 +169,17 @@ export class DragController {
}
private checkDragStart(event: DragEvent) {
- if (event.eventPhase !== EventPhase.capturing) {
- return
- }
- this.sourceTarget = this.findSourceTarget(event)
- if (this.sourceTarget != null) {
- this.initFramePosition()
- const op = this.makeOp(OpType.start, this.sourceTarget, event)
- this.c.applyOp(op)
+ if (event.eventPhase === EventPhase.bubbling) {
+ if (!event.defaultPrevented) {
+ this.sourceTarget = this.findSourceTarget(event)
+ if (this.sourceTarget != null) {
+ this.initFramePosition()
+ const op = this.makeOp(OpType.start, this.sourceTarget, event)
+ this.c.applyOp(op)
+ }
+ } else {
+ log.V("host page cancel dragtart event")
+ }
}
}
diff --git a/src/content_scripts/main.ts b/src/content_scripts/main.ts
index 69f7634..ab88605 100644
--- a/src/content_scripts/main.ts
+++ b/src/content_scripts/main.ts
@@ -67,7 +67,7 @@ async function setup() {
const opExecutor = new OpExecutor()
opExecutor.reset()
- controller = new DragController(document.documentElement, opExecutor);
+ controller = new DragController(window, opExecutor);
onDocumentLoaded(async () => {
await browser.runtime.sendMessage(buildRuntimeMessage(RuntimeMessageName.contextScriptLoaded, null))
From 9ebe3e4c3b57a4c3a40944c3f22066d5cc33f131 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sun, 8 Jan 2023 12:47:07 +0800
Subject: [PATCH 04/21] docs: add limitation
Signed-off-by: harytfw
---
README.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/README.md b/README.md
index 62777b2..5c88faa 100644
--- a/README.md
+++ b/README.md
@@ -25,3 +25,12 @@ make ext-chromium
```
After the build process completed, `./build/firefox/dist` contains compiled result, `./build/firefox/artifacts` contains package file.
+
+## Limitation
+
+The extension has limited support at these situation:
+
+- Can not work on privilege pages or restricted pages, like , ,
+- Can not work when dragging any selection from page A and dropping at page B, and page A and page B have different origin. (For example, [host page](www.example.com) is A, it embeds [youtube video](https://www.youtube.com/embed/-88qGXDmh3E) as B)
+- When host page explicitly uses drag-and-drop features, the extension will stop working temporarily
+- Unable show icon of menu on when [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources) of host page prohibits *data:* URLs
From 70c3b5bb4e0db6ba9d5e3d75fb5e32cc1c876b34 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sun, 8 Jan 2023 13:25:47 +0800
Subject: [PATCH 05/21] feat: support other protocol uri
Signed-off-by: harytfw
---
src/utils/url.ts | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/src/utils/url.ts b/src/utils/url.ts
index 3dc3f84..4446585 100644
--- a/src/utils/url.ts
+++ b/src/utils/url.ts
@@ -28,18 +28,18 @@ function matchRe(s: string, pat: RegExp): boolean {
return r && r.length > 0
}
-function isTopLevelDomain(url: URL): boolean {
- return parse(url.hostname).isIcann
-}
-
const log = rootLog.subLogger(LogLevel.VVV, "urlFixer")
const urlLRU = new TinyLRU()
export class URLFixer {
private protocol = "https://"
+ private specialProtocols: Set = new Set()
- constructor() {
+ constructor(extraProtocols?: string[]) {
+ if (Array.isArray(extraProtocols)) {
+ this.specialProtocols = new Set(extraProtocols)
+ }
}
fix(urlLike?: string): URL | null {
@@ -52,7 +52,13 @@ export class URLFixer {
return res
}
- internalFix(urlLike?: string): URL | null {
+ private isTopLevelDomain(url: URL): boolean {
+ if (parse(url.hostname).isIcann) {
+ return true
+ }
+ }
+
+ private internalFix(urlLike?: string): URL | null {
log.VVV("try fix url: ", urlLike)
@@ -97,7 +103,12 @@ export class URLFixer {
const url = this.tryBuildURL(urlLike)
if (url) {
- if (isTopLevelDomain(url)) {
+ if (this.isTopLevelDomain(url)) {
+ log.VVV(urlLike, "is top level domain")
+ return url
+ }
+ if (this.specialProtocols.has(url.protocol)) {
+ log.VVV(urlLike, "has special protocol", url.protocol)
return url
}
return null
From 24ed7e6603c664eea101364ce9ef72249084f885 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sun, 8 Jan 2023 13:26:26 +0800
Subject: [PATCH 06/21] test: fix usage of assert
Signed-off-by: harytfw
---
src/context/context.test.ts | 17 ++++++++---------
src/utils/test_helper.ts | 12 ++++--------
src/utils/url.test.ts | 30 ++++++++++++++++++++++--------
src/utils/var_substitute.test.ts | 6 +++---
4 files changed, 37 insertions(+), 28 deletions(-)
diff --git a/src/context/context.test.ts b/src/context/context.test.ts
index 85c2be9..c8b914e 100644
--- a/src/context/context.test.ts
+++ b/src/context/context.test.ts
@@ -1,21 +1,20 @@
import { blankExecuteContext } from "./test_helper"
import { ContextType } from "../config/config"
-import { assertEqual } from "../utils/test_helper"
import { primaryContextType } from "./utils"
-
+import { assert } from 'chai'
describe("test context", () => {
- it("primary type",async () => {
- let ctx = await blankExecuteContext()
+ it("primary type", async () => {
+ let ctx = await blankExecuteContext()
ctx.data.selection = "hello"
- assertEqual(primaryContextType(ctx), ContextType.selection)
+ assert.deepEqual(primaryContextType(ctx), ContextType.selection)
- ctx = await blankExecuteContext()
+ ctx = await blankExecuteContext()
ctx.data.link = "http://example.com/"
- assertEqual(primaryContextType(ctx), ContextType.link)
+ assert.deepEqual(primaryContextType(ctx), ContextType.link)
- ctx = await blankExecuteContext()
+ ctx = await blankExecuteContext()
ctx.data.imageSource = "http://example.com/a.jpg"
- assertEqual(primaryContextType(ctx), ContextType.image)
+ assert.deepEqual(primaryContextType(ctx), ContextType.image)
})
})
\ No newline at end of file
diff --git a/src/utils/test_helper.ts b/src/utils/test_helper.ts
index 091cbb8..64906c3 100644
--- a/src/utils/test_helper.ts
+++ b/src/utils/test_helper.ts
@@ -1,21 +1,17 @@
import browser from 'webextension-polyfill';
-import isEqual from "lodash-es/isEqual"
+import { assert } from 'chai'
export function assertEqual(expected: any, actual: any, ...message: any[]) {
- return assertOk(isEqual(actual, expected), ...message)
+ return assert.deepEqual(actual, expected, ...message)
}
export function assertOk(value: any, ...message: any[]) {
- if (!value) {
- throw new Error(message.join(" "))
- }
+ return assert.ok(value, ...message)
}
export function assertFail(value: any, ...message: any[]) {
- if (!!value) {
- throw new Error(message.join(" "))
- }
+ return assert.notOk(value, ...message)
}
export async function closeTab(tabIds: number | number[], delayMs?: number) {
diff --git a/src/utils/url.test.ts b/src/utils/url.test.ts
index d288ae2..42d5d7b 100644
--- a/src/utils/url.test.ts
+++ b/src/utils/url.test.ts
@@ -1,22 +1,23 @@
import { URLFixer } from './url'
-import { assertEqual, assertFail, assertOk } from './test_helper';
import { rootLog } from './log';
+import { assert } from 'chai'
describe('test url fixer', () => {
- const fixer = new URLFixer()
it('good url', () => {
+ const fixer = new URLFixer()
const urls = [
"http://example.com/",
"http://example.org/",
"http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/win-64"
]
for (const c of urls) {
- assertEqual(fixer.fix(c).toString(), c)
+ assert.deepEqual(fixer.fix(c).toString(), c)
}
})
it('bad url', () => {
+ const fixer = new URLFixer()
const urls = [
"12345",
@@ -24,35 +25,38 @@ describe('test url fixer', () => {
]
for (const c of urls) {
- assertFail(fixer.fix(c), c)
+ assert.notOk(fixer.fix(c), c)
}
});
it('valid ipv4', () => {
+ const fixer = new URLFixer()
const urls = [
"8.8.8.8",
"192.168.1.1",
"127.0.0.1"
]
for (const c of urls) {
- assertOk(fixer.fix(c), c)
+ assert.ok(fixer.fix(c), c)
}
});
it('valid ipv6', () => {
+ const fixer = new URLFixer()
const urls = [
"2001:db8:0:0:1:0:0:1",
"2001:db8::1:0:0:1",
"::1"
]
for (const c of urls) {
- assertFail(fixer.fix(c), "not support plain ipv6: ", c)
- assertOk(fixer.fix("[" + c + "]"), "ipv6 url: ", c)
+ assert.notOk(fixer.fix(c), "not support plain ipv6: " + c)
+ assert.ok(fixer.fix("[" + c + "]"), "ipv6 url: " + c)
}
});
it('fix url', () => {
+ const fixer = new URLFixer()
const urls = [
[
"www.example.com/a",
@@ -79,7 +83,17 @@ describe('test url fixer', () => {
for (const c of urls) {
const fixed = fixer.fix(c[0]).toString()
rootLog.VVV("origin: ", c[0], "fixed:", fixed, " expected: ", c[1], " same: ", fixed == c[1])
- assertEqual(fixed, c[1], "expected: ", c[1], "but got:", fixed)
+ assert.deepEqual(fixed, c[1])
}
});
+ it('special protocol url', function () {
+ const fixer = new URLFixer(["magnet:", "mailto:"])
+ const urls = [
+ "magnet:?xt=urn:btih:000000000000000000000000000&dn",
+ "mailto:someone@example.com"
+ ]
+ for (const url of urls) {
+ assert.ok(fixer.fix(url))
+ }
+ })
});
\ No newline at end of file
diff --git a/src/utils/var_substitute.test.ts b/src/utils/var_substitute.test.ts
index e0439a3..d0ccb5c 100644
--- a/src/utils/var_substitute.test.ts
+++ b/src/utils/var_substitute.test.ts
@@ -1,6 +1,6 @@
import { assertEqual } from "./test_helper"
import { VarSubstituteTemplate } from "./var_substitute"
-
+import { assert } from 'chai'
describe('test variable substitute', () => {
@@ -20,7 +20,7 @@ describe('test variable substitute', () => {
for (const c of cases) {
const template = new VarSubstituteTemplate(c)
const result = template.substitute(m)
- assertEqual(result, c, "expected: ", c, ", actually: ", result)
+ assert.deepEqual(result, c)
}
})
@@ -52,7 +52,7 @@ describe('test variable substitute', () => {
for (const [s, expected] of cases) {
const template = new VarSubstituteTemplate(s)
const result = template.substitute(m)
- assertEqual(result, expected, "expected: ", expected, ", actually: ", result)
+ assert.deepEqual(result, expected)
}
})
})
\ No newline at end of file
From f3ed0fc1e85782ce1fd34f45dc1c3796ef8c265b Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sun, 8 Jan 2023 14:12:57 +0800
Subject: [PATCH 07/21] feat: add configuration for protocol of smartURL
Signed-off-by: harytfw
---
src/config/config.ts | 13 +++++++++++--
src/content_scripts/op_source.ts | 16 ++++++++++++++--
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/src/config/config.ts b/src/config/config.ts
index 7d44da3..5dc9484 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -107,13 +107,21 @@ export interface PlainLogConfig {
}
export class SmartURLConfig {
- cfg: KVRecord
- constructor(cfg: KVRecord) {
+ cfg: PlainSmartURLConfig
+
+ constructor(cfg: PlainSmartURLConfig) {
this.cfg = cfg
}
+
+ get protocols(): string[] {
+ return defaultTo(this.cfg.protocols, [])
+ }
}
+export interface PlainSmartURLConfig {
+ protocols?: string[]
+}
export class ModeConfig {
@@ -653,6 +661,7 @@ export interface PlainConfiguration {
assets?: PlainAsset[],
scripts?: PlainScript[],
compatibility?: PlainCompatibilityRule[],
+ smartURL?: PlainSmartURLConfig,
}
export class BroadcastEventTarget {
diff --git a/src/content_scripts/op_source.ts b/src/content_scripts/op_source.ts
index 99eb16d..050d900 100644
--- a/src/content_scripts/op_source.ts
+++ b/src/content_scripts/op_source.ts
@@ -1,8 +1,15 @@
-import { ContextType } from "../config/config"
+import { configBroadcast, ContextType, type ReadonlyConfiguration } from "../config/config"
import type { KVRecord } from "../types"
import { URLFixer } from "../utils/url"
import type { OpSummary } from "./op"
+
+let config: ReadonlyConfiguration | null = null
+
+configBroadcast.addListener((cfg) => {
+ config = cfg
+})
+
export class OpSource {
private _isContentEditable: boolean = false
private _isDraggable: boolean = false
@@ -72,7 +79,12 @@ export class OpSource {
}
if (os.type === ContextType.selection) {
- const url = new URLFixer().fix(os.text)
+ let protocols = []
+ if (config) {
+ protocols = Array.from(config.smartURL.protocols)
+ }
+ const fixer = new URLFixer(protocols)
+ const url = fixer.fix(os.text)
if (url) {
os._link = url.toString()
os._linkText = os.text
From 79ea12a601f3f62cd45b630ad41265af3e58f05e Mon Sep 17 00:00:00 2001
From: harytfw
Date: Sun, 8 Jan 2023 14:30:07 +0800
Subject: [PATCH 08/21] refactor: replace KVRecord with concrete type
Signed-off-by: harytfw
---
src/background/executor.test.ts | 4 +-
src/config/config.ts | 183 ++++++++++++++------------------
2 files changed, 84 insertions(+), 103 deletions(-)
diff --git a/src/background/executor.test.ts b/src/background/executor.test.ts
index 2e07e20..e84b3b6 100644
--- a/src/background/executor.test.ts
+++ b/src/background/executor.test.ts
@@ -29,14 +29,14 @@ describe("test executor", async () => {
})
it("copy text", async () => {
- const ctx = await blankExecuteContext(new ActionConfig({ "command": "copy" }))
+ const ctx = await blankExecuteContext(new ActionConfig({ command: CommandKind.copy }))
ctx.data.selection = "copy demo"
await executor.copyHandler(ctx)
})
it("copy image", async () => {
- const ctx = await blankExecuteContext(new ActionConfig({ "command": "copy" }))
+ const ctx = await blankExecuteContext(new ActionConfig({ command: CommandKind.copy }))
ctx.data.imageSource = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA8UlEQVQYlY3QoaqDYACG4dd5ysDgQBwz+QdZWFBYsTnwHryek3cVZi1isngHljVBUNAi/Ay3MIaWnXhY88tP+HiVqqo+rNhmDQL4aZoGx3FI05RpmhBCcDweKYoCXdeJooh5nlEty/rd7/ecTifatsV1XWzbZpomwjDkfr+TJAnK9Xr9qKpKEAR0XUdVVXieh2EY7Ha7/4/b7ZbX6wWAEAJN0+j7/gsBbN7vN5qmAfB8PjFNk3EcGYbhGw7DgOM4PB4Pbrcb5/OZy+VCWZZIKQFYloWN7/scDgfyPKeua6SU1HXNsixkWYaUkjiOUdYG/wONbmSLuq2dcQAAAABJRU5ErkJggg=="
await executor.copyHandler(ctx)
diff --git a/src/config/config.ts b/src/config/config.ts
index 5dc9484..7bd4a7a 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -91,19 +91,19 @@ export enum Feature {
export class LogConfig {
- private cfg: KVRecord
+ private cfg: PlainLogConfig
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainLogConfig) {
this.cfg = cfg
}
get level(): LogLevel {
- return defaultTo(this.cfg['level'], LogLevel.S)
+ return defaultTo(this.cfg.level, LogLevel.S)
}
}
export interface PlainLogConfig {
- level: number
+ level?: number
}
export class SmartURLConfig {
@@ -125,66 +125,56 @@ export interface PlainSmartURLConfig {
export class ModeConfig {
- private cfg: KVRecord
+ private cfg: PlainModeConfig
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainModeConfig) {
this.cfg = cfg
}
get link(): OperationMode {
- return defaultTo(this.cfg['link'], OperationMode.normal)
+ return defaultTo(this.cfg.link, OperationMode.normal)
}
get selection(): OperationMode {
- return defaultTo(this.cfg['selection'], OperationMode.normal)
+ return defaultTo(this.cfg.selection, OperationMode.normal)
}
get image(): OperationMode {
- return defaultTo(this.cfg['image'], OperationMode.normal)
+ return defaultTo(this.cfg.image, OperationMode.normal)
}
}
export interface PlainModeConfig {
- link?: string
- selection?: string
- image?: string
+ link?: OperationMode
+ selection?: OperationMode
+ image?: OperationMode
}
export class CommonConfig {
- private cfg: KVRecord
+ private cfg: PlainCommonConfig
private _mode: ModeConfig
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainCommonConfig) {
this.cfg = cfg
- this._mode = new ModeConfig(defaultTo(this.cfg['mode'], {}))
+ this._mode = new ModeConfig(defaultTo(this.cfg.mode, {}))
}
get mode(): ModeConfig {
return this._mode
}
- get allow(): string[] {
- return defaultTo(this.cfg['allow'], [])
- }
-
- get disallow(): string[] {
- return defaultTo(this.cfg['disallow'], [])
- }
-
get minDistance(): number {
- return defaultTo(this.cfg['minDistance'], 0)
+ return defaultTo(this.cfg.minDistance, 0)
}
get maxDistance(): number {
- return defaultTo(this.cfg['maxDistance'], 0)
+ return defaultTo(this.cfg.maxDistance, 0)
}
}
export interface PlainCommonConfig {
mode?: PlainModeConfig
- allowHost?: string[]
- disallowHost?: string[]
minDistance?: number
maxDistance?: number
}
@@ -195,9 +185,9 @@ export enum MenuLayout {
}
export class MenuConfig {
- cfg: KVRecord
+ cfg: PlainMenuConfig
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainMenuConfig) {
this.cfg = cfg
}
@@ -207,19 +197,11 @@ export interface PlainMenuConfig {
}
-export class ListModeConfig {
- cfg: KVRecord
-
- constructor(cfg: KVRecord) {
- this.cfg = cfg
- }
-}
-
export class Script {
- private cfg: KVRecord
+ private cfg: PlainScript
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainScript) {
this.cfg = cfg
}
@@ -231,15 +213,15 @@ export class Script {
}
get id() {
- return defaultTo(this.cfg['id'], "")
+ return defaultTo(this.cfg.id, "")
}
get text() {
- return defaultTo(this.cfg['text'], "")
+ return defaultTo(this.cfg.text, "")
}
get name() {
- return defaultTo(this.cfg['name'], this.id)
+ return defaultTo(this.cfg.name, this.id)
}
}
@@ -256,14 +238,14 @@ export class CommandRequest {
private _url: string
private _query: KVRecord
private _protocol: string
- private cfg: KVRecord
+ private cfg: PlainCommandRequest
constructor(cfg: PlainCommandRequest) {
this.cfg = cloneDeep(cfg)
- const urlObj = new URL(cfg['url'])
+ const urlObj = new URL(cfg.url)
- this._query = defaultTo(cfg['query'], {})
+ this._query = defaultTo(cfg.query, {})
for (const [k, v] of urlObj.searchParams) {
this._query[k] = cloneDeep(v)
}
@@ -289,11 +271,11 @@ export class CommandRequest {
}
get id() {
- return defaultTo(this.cfg['id'], '')
+ return defaultTo(this.cfg.id, '')
}
get name() {
- return defaultTo(this.cfg["name"], this.id)
+ return defaultTo(this.cfg.name, this.id)
}
get method() {
@@ -331,41 +313,41 @@ export const enum AssetType {
}
export class Asset {
- private cfg: KVRecord
+ private cfg: PlainAsset
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainAsset) {
this.cfg = cfg
}
get id(): string {
- return defaultTo(this.cfg["id"], "")
+ return defaultTo(this.cfg.id, "")
}
get name(): string {
- return defaultTo(this.cfg["name"], this.id)
+ return defaultTo(this.cfg.name, this.id)
}
get type(): AssetType {
- return defaultTo(this.cfg["type"], AssetType.html)
+ return defaultTo(this.cfg.type, AssetType.html)
}
get data(): string {
- return defaultTo(this.cfg["data"], "")
+ return defaultTo(this.cfg.data, "")
}
}
export interface PlainAsset {
id?: string
name?: string
- type?: string
+ type?: AssetType
data?: string
}
export class CommandConfig {
- private cfg: KVRecord
+ private cfg: PlainCommandConfig
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainCommandConfig) {
this.cfg = cfg
}
@@ -383,53 +365,53 @@ export class CommandConfig {
}
get activeTab(): boolean {
- return defaultTo(this.cfg['activeTab'], true)
+ return defaultTo(this.cfg.activeTab, true)
}
get tabPosition(): TabPosition {
- return defaultTo(this.cfg['tabPosition'], TabPosition.next)
+ return defaultTo(this.cfg.tabPosition, TabPosition.next)
}
get requestId(): string {
- return defaultTo(this.cfg['requestId'], "")
+ return defaultTo(this.cfg.requestId, "")
}
get scriptId(): string {
- return defaultTo(this.cfg['scriptId'], "")
+ return defaultTo(this.cfg.scriptId, "")
}
get directory(): string {
- return defaultTo(this.cfg['directory'], "")
+ return defaultTo(this.cfg.directory, "")
}
get showSaveAsDialog(): boolean {
- return defaultTo(this.cfg['showSaveAsDialog'], false)
+ return defaultTo(this.cfg.showSaveAsDialog, false)
}
get preferDataTypes(): ContextDataType[] {
- return defaultTo(this.cfg['preferDataTypes'], [])
+ return defaultTo(this.cfg.preferDataTypes, [])
}
get container(): string {
- return defaultTo(this.cfg['container'], "")
+ return defaultTo(this.cfg.container, "")
}
}
export interface PlainCommandConfig {
activeTab?: boolean
- tabPosition?: string
+ tabPosition?: TabPosition
requestId?: string
scriptId?: string
directory?: string
showSaveAsDialog?: boolean
- preferDataTypes?: string[]
+ preferDataTypes?: ContextDataType[]
container?: string
}
export class ConditionConfig {
- private cfg: KVRecord
+ private cfg: PlainConditionConfig
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainConditionConfig) {
this.cfg = cfg
}
@@ -442,39 +424,39 @@ export class ConditionConfig {
}
get modes(): OperationMode[] {
- return defaultTo(this.cfg['modes'], [])
+ return defaultTo(this.cfg.modes, [])
}
get contextTypes(): ContextType[] {
- return defaultTo(this.cfg['contextTypes'], [])
+ return defaultTo(this.cfg.contextTypes, [])
}
get directions(): Direction[] {
- return defaultTo(this.cfg['directions'], [])
+ return defaultTo(this.cfg.directions, [])
}
get extra(): ContextType[] {
- return defaultTo(this.cfg['extra'], [])
+ return defaultTo(this.cfg.extra, [])
}
}
export interface PlainConditionConfig {
- modes?: string[]
- contextTypes?: string[],
- directions?: string[],
- extra?: string[]
+ modes?: OperationMode[]
+ contextTypes?: ContextType[],
+ directions?: Direction[],
+ extra?: ContextType[]
}
export class ActionConfig {
- private cfg: KVRecord
+ private cfg: PlainActionConfig
private _conditionConfig: ConditionConfig
private _config: CommandConfig
- constructor(cfg: KVRecord) {
+ constructor(cfg: PlainActionConfig) {
this.cfg = cfg
- this._conditionConfig = new ConditionConfig(defaultTo(cfg['condition'], {}))
- this._config = new CommandConfig(defaultTo(cfg['config'], {}))
+ this._conditionConfig = new ConditionConfig(defaultTo(cfg.condition, {}))
+ this._config = new CommandConfig(defaultTo(cfg.config, {}))
}
toPlainObject(): PlainActionConfig {
@@ -490,15 +472,15 @@ export class ActionConfig {
}
get id(): string {
- return defaultTo(this.cfg['id'], "")
+ return defaultTo(this.cfg.id, "")
}
get name(): string {
- return defaultTo(this.cfg['name'], this.id)
+ return defaultTo(this.cfg.name, this.id)
}
get prompt(): string {
- return defaultTo(this.cfg['prompt'], "")
+ return defaultTo(this.cfg.prompt, "")
}
get condition(): Readonly {
@@ -506,7 +488,7 @@ export class ActionConfig {
}
get command(): CommandKind {
- return defaultTo(this.cfg['command'], CommandKind.invalid)
+ return defaultTo(this.cfg.command, CommandKind.invalid)
}
get config(): CommandConfig {
@@ -514,11 +496,11 @@ export class ActionConfig {
}
get icon(): string {
- return defaultTo(this.cfg['icon'], "")
+ return defaultTo(this.cfg.icon, "")
}
get iconAssetId(): string {
- return defaultTo(this.cfg['iconAssetId'], "")
+ return defaultTo(this.cfg.iconAssetId, "")
}
}
@@ -526,7 +508,7 @@ export interface PlainActionConfig {
id?: string
name?: string
condition?: PlainConditionConfig
- command?: string
+ command?: CommandKind
config?: PlainCommandConfig
icon?: string
iconAssetId?: string
@@ -544,13 +526,11 @@ export interface PlainCompatibilityRule { host?: string, regexp?: string, status
export class CompatibilityRule {
- private cfg: KVRecord
private _host: string
private _regexp: string
private _status: CompatibilityStatus
constructor(cfg: PlainCompatibilityRule) {
- this.cfg = cfg
this._host = defaultTo(cfg.host, "")
this._regexp = defaultTo(cfg.regexp, "")
this._status = defaultTo(cfg.status, CompatibilityStatus.enable)
@@ -590,19 +570,19 @@ export class Configuration {
private _scripts: Script[]
private _compatibility: CompatibilityRule[]
- constructor(data?: KVRecord) {
- data = defaultTo(data, {})
+ constructor(cfg?: PlainConfiguration) {
+ cfg = defaultTo(cfg, {})
- this._log = new LogConfig(defaultTo(data['log'], {}))
- this._smartURL = new SmartURLConfig(defaultTo(data['smartURL'], {}))
- this._common = new CommonConfig(defaultTo(data['common'], {}))
- this._menu = new MenuConfig(defaultTo(data['grid'], {}))
- this._actions = defaultTo(data['actions'], []).map((c: KVRecord) => new ActionConfig(c))
- this._features = new Set(defaultTo(data['features'], []))
- this._requests = defaultTo(data['requests'], []).map((r: KVRecord) => new CommandRequest(r))
- this._assets = defaultTo(data['assets'], []).map((r: KVRecord) => new Asset(r))
- this._scripts = defaultTo(data['scripts'], []).map((r: KVRecord) => new Script(r))
- this._compatibility = defaultTo(data["compatibility"], []).map((r: KVRecord) => new CompatibilityRule(r))
+ this._log = new LogConfig(defaultTo(cfg.log, {}))
+ this._smartURL = new SmartURLConfig(defaultTo(cfg.smartURL, {}))
+ this._common = new CommonConfig(defaultTo(cfg.common, {}))
+ this._menu = new MenuConfig(defaultTo(cfg.menu, {}))
+ this._actions = defaultTo(cfg.actions, []).map((c: PlainActionConfig) => new ActionConfig(c))
+ this._requests = defaultTo(cfg.requests, []).map((r: PlainCommandRequest) => new CommandRequest(r))
+ this._assets = defaultTo(cfg.assets, []).map((r: PlainAsset) => new Asset(r))
+ this._scripts = defaultTo(cfg.scripts, []).map((r: PlainScript) => new Script(r))
+ this._compatibility = defaultTo(cfg.compatibility, []).map((r: PlainCompatibilityRule) => new CompatibilityRule(r))
+ this._features = new Set(defaultTo(cfg.features as Feature[], []))
}
get features(): Set {
@@ -662,6 +642,7 @@ export interface PlainConfiguration {
scripts?: PlainScript[],
compatibility?: PlainCompatibilityRule[],
smartURL?: PlainSmartURLConfig,
+ features?: string[]
}
export class BroadcastEventTarget {
From 55ee756af717a6998b5bed85ce6c64ec9478d8a2 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Tue, 10 Jan 2023 14:30:07 +0800
Subject: [PATCH 09/21] chore: refactor build cli
Signed-off-by: harytfw
---
Makefile | 90 +++-------
package.json | 8 +-
pnpm-lock.yaml | 83 +++++----
scripts/build_rollup.mjs | 82 +++++++++
scripts/cli.mjs | 295 ++++++++++++++++++++++++++++++++
scripts/gen_manifest.js | 119 -------------
scripts/gen_manifest.mjs | 124 ++++++++++++++
scripts/rollup.config.js | 94 ----------
scripts/test_bootstrap.js | 95 ----------
scripts/{utils.js => utils.mjs} | 4 +
src/build_info.ts | 10 +-
src/global.d.ts | 12 +-
src/test/mocha_init.ts | 2 +-
13 files changed, 598 insertions(+), 420 deletions(-)
create mode 100644 scripts/build_rollup.mjs
create mode 100644 scripts/cli.mjs
delete mode 100644 scripts/gen_manifest.js
create mode 100644 scripts/gen_manifest.mjs
delete mode 100644 scripts/rollup.config.js
delete mode 100644 scripts/test_bootstrap.js
rename scripts/{utils.js => utils.mjs} (85%)
diff --git a/Makefile b/Makefile
index cf1a5ff..40d6f81 100644
--- a/Makefile
+++ b/Makefile
@@ -1,78 +1,38 @@
export BUILD_VERSION ?= 2.1.4
export BUILD_DIR ?= $(shell realpath ./build)
-export SRC ?= $(shell realpath ./src)
-export BUILD_PROFILE ?= debug
-export BUILD_WEBSOCKET_SERVER =
-export ENTRY_POINTS = background content_scripts options components
-export TARGET_BROWSER = firefox
-export TARGET_DIST = $(BUILD_DIR)/firefox/dist
+export BUILD_SRC ?= $(shell realpath ./src)
-$(shell mkdir -p $(BUILD_DIR)/artifacts/)
+cli = node ./scripts/cli.mjs
+.PHONY: build
+build: TARGET ?= firefox
+build:
+ $(cli) build --target $(TARGET)
+
+.PHONY: build-watch
+build-watch: TARGET ?= firefox
+build-watch:
+ $(cli) build --target $(TARGET) --watch
.PHONY: ext-firefox
-ext-firefox: TARGET_BROWSER = firefox
-ext-firefox: TARGET_DIST = $(BUILD_DIR)/firefox/dist
-ext-firefox: setup-dist assets compile lint
- cd $(TARGET_DIST) && zip -r $(BUILD_DIR)/artifacts/glitterdrag-pro-$(BUILD_VERSION)-firefox.zip .
+ext-firefox: BUILD_PROFILE ?= debug
+ext-firefox:
+ $(cli) build --target firefox --lint --profile $(BUILD_PROFILE)
.PHONY: ext-chromium
-ext-test: TARGET_BROWSER = chromium
-ext-chromium: TARGET_DIST = $(BUILD_DIR)/chromium/dist
-ext-chromium: setup-dist assets compile
- cd $(TARGET_DIST) && zip -r $(BUILD_DIR)/artifacts/glitterdrag-pro-$(BUILD_VERSION)-chromium.zip .
+ext-chromium: BUILD_PROFILE ?= debug
+ext-chromium:
+ $(cli) build --target chromium --profile $(BUILD_PROFILE)
.PHONY: test
-test: TARGET_BROWSER = firefox-test
-test: ENTRY_POINTS += test
-test: TARGET_DIST = $(BUILD_DIR)/test/dist
-test: BUILD_WEBSOCKET_SERVER = ws://localhost:8000
-test: setup-dist assets mocha-assets compile
- @node scripts/test_bootstrap.js
-
-.PHONY: open-mocha
-open-mocha: TARGET_DIST = $(BUILD_DIR)/firefox/dist
-open-mocha: ENTRY_POINTS += test
-open-mocha: setup-dist assets mocha-assets compile
- web-ext run -v \
- -s $(TARGET_DIST) \
- -f "$(shell which firefox-developer-edition)"
-
-.PHONY: setup-dist
-setup-dist:
- $(shell [ ! -d $(TARGET_DIST) ] && mkdir -p $(TARGET_DIST)/)
-
-.PHONY: lint
-lint:
- @pnpm exec web-ext lint -s $(TARGET_DIST)
-
-.PHONY: compile
-compile:
- @pnpm exec rollup -c "./scripts/rollup.config.js"
-
-.PHONY: manifest
-manifest:
- @node scripts/gen_manifest.js > $(TARGET_DIST)/manifest.json
-
-.PHONY: assets
-assets: manifest
-
- @mkdir -p $(TARGET_DIST)/options && \
- cp -f $(SRC)/options/options.html $(TARGET_DIST)/options/options.html
-
- @mkdir -p $(TARGET_DIST)/res && \
- cp -r -f node_modules/simpledotcss/simple.min.css $(TARGET_DIST)/res/simple.min.css
-
- @cp -r -f $(SRC)/icon/ $(SRC)/_locales/ $(TARGET_DIST)/
+test: TARGET ?= firefox
+test:
+ $(cli) test --target $(TARGET)
-.PHONY: mocha-assets
-mocha-assets:
- @mkdir -p $(TARGET_DIST)/test && \
- cp node_modules/mocha/mocha.js \
- node_modules/mocha/mocha.css \
- node_modules/chai/chai.js \
- $(SRC)/test/mocha.html \
- $(TARGET_DIST)/test
+.PHONY: watch
+watch: TARGET ?= firefox
+watch:
+ $(cli) watch
.PHONY: clean
clean:
- @rm -rf $(BUILD_DIR)
\ No newline at end of file
+ $(cli) clean
\ No newline at end of file
diff --git a/package.json b/package.json
index a999ad2..2fe137b 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.1",
+ "@rollup/plugin-terser": "^0.3.0",
"@rollup/plugin-typescript": "^9.0.2",
"@tsconfig/svelte": "^3.0.0",
"@types/chai": "^4.3.4",
@@ -13,14 +14,16 @@
"@types/node": "^18.11.9",
"@types/uuid": "^8.3.4",
"@types/webextension-polyfill": "^0.9.1",
+ "@types/ws": "^8.5.4",
"chai": "^4.3.7",
+ "commander": "^9.5.0",
+ "fs-extra": "^11.1.0",
"lodash": "^4.17.21",
"lodash-core": "^4.17.19",
"lodash-es": "^4.17.21",
"mocha": "^10.1.0",
"rollup": "^3.4.0",
"rollup-plugin-svelte": "^7.1.0",
- "rollup-plugin-terser": "^7.0.2",
"simpledotcss": "^2.1.1",
"svelte": "^3.53.1",
"svelte-preprocess": "^4.10.7",
@@ -30,7 +33,8 @@
"uuid": "^9.0.0",
"web-ext": "7.4.0",
"webextension-polyfill": "^0.10.0",
- "ws": "^8.11.0"
+ "ws": "^8.11.0",
+ "zip-dir": "^2.0.0"
},
"license": "MIT"
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b9f8755..d24aa67 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,6 +4,7 @@ specifiers:
'@rollup/plugin-commonjs': ^23.0.2
'@rollup/plugin-node-resolve': ^15.0.1
'@rollup/plugin-replace': ^5.0.1
+ '@rollup/plugin-terser': ^0.3.0
'@rollup/plugin-typescript': ^9.0.2
'@tsconfig/svelte': ^3.0.0
'@types/chai': ^4.3.4
@@ -12,14 +13,16 @@ specifiers:
'@types/node': ^18.11.9
'@types/uuid': ^8.3.4
'@types/webextension-polyfill': ^0.9.1
+ '@types/ws': ^8.5.4
chai: ^4.3.7
+ commander: ^9.5.0
+ fs-extra: ^11.1.0
lodash: ^4.17.21
lodash-core: ^4.17.19
lodash-es: ^4.17.21
mocha: ^10.1.0
rollup: ^3.4.0
rollup-plugin-svelte: ^7.1.0
- rollup-plugin-terser: ^7.0.2
simpledotcss: ^2.1.1
svelte: ^3.53.1
svelte-preprocess: ^4.10.7
@@ -30,11 +33,13 @@ specifiers:
web-ext: 7.4.0
webextension-polyfill: ^0.10.0
ws: ^8.11.0
+ zip-dir: ^2.0.0
devDependencies:
'@rollup/plugin-commonjs': 23.0.2_rollup@3.4.0
'@rollup/plugin-node-resolve': 15.0.1_rollup@3.4.0
'@rollup/plugin-replace': 5.0.1_rollup@3.4.0
+ '@rollup/plugin-terser': 0.3.0_rollup@3.4.0
'@rollup/plugin-typescript': 9.0.2_vmrh7niimjcuan47h4hgsgyxjy
'@tsconfig/svelte': 3.0.0
'@types/chai': 4.3.4
@@ -43,14 +48,16 @@ devDependencies:
'@types/node': 18.11.9
'@types/uuid': 8.3.4
'@types/webextension-polyfill': 0.9.1
+ '@types/ws': 8.5.4
chai: 4.3.7
+ commander: 9.5.0
+ fs-extra: 11.1.0
lodash: 4.17.21
lodash-core: 4.17.19
lodash-es: 4.17.21
mocha: 10.1.0
rollup: 3.4.0
rollup-plugin-svelte: 7.1.0_rollup@3.4.0+svelte@3.53.1
- rollup-plugin-terser: 7.0.2_rollup@3.4.0
simpledotcss: 2.1.1
svelte: 3.53.1
svelte-preprocess: 4.10.7_7dvewpees4iyn2tkw2qzal77a4
@@ -61,6 +68,7 @@ devDependencies:
web-ext: 7.4.0
webextension-polyfill: 0.10.0
ws: 8.11.0
+ zip-dir: 2.0.0
packages:
@@ -110,7 +118,7 @@ packages:
'@devicefarmer/adbkit-logcat': 2.1.2
'@devicefarmer/adbkit-monkey': 1.2.1
bluebird: 3.7.2
- commander: 9.4.1
+ commander: 9.5.0
debug: 4.3.4
node-forge: 1.3.1
split: 1.0.1
@@ -282,6 +290,21 @@ packages:
rollup: 3.4.0
dev: true
+ /@rollup/plugin-terser/0.3.0_rollup@3.4.0:
+ resolution: {integrity: sha512-mYTkNW9KjOscS/3QWU5LfOKsR3/fAAVDaqcAe2TZ7ng6pN46f+C7FOZbITuIW/neA+PhcjoKl7yMyB3XcmA4gw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^2.x || ^3.x
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ dependencies:
+ rollup: 3.4.0
+ serialize-javascript: 6.0.0
+ smob: 0.0.6
+ terser: 5.16.1
+ dev: true
+
/@rollup/plugin-typescript/9.0.2_vmrh7niimjcuan47h4hgsgyxjy:
resolution: {integrity: sha512-/sS93vmHUMjzDUsl5scNQr1mUlNE1QjBBvOhmRwJCH8k2RRhDIm3c977B3wdu3t3Ap17W6dDeXP3hj1P1Un1bA==}
engines: {node: '>=14.0.0'}
@@ -389,6 +412,12 @@ packages:
resolution: {integrity: sha512-6aNzPIhqKlAV9t06nwSH3/veAceYE2dS2RVFZI8V1+UXHqsFNB6cRwxNmheiBvEGRc45E/gyZNzH0xAYIC27KA==}
dev: true
+ /@types/ws/8.5.4:
+ resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
+ dependencies:
+ '@types/node': 18.11.9
+ dev: true
+
/@types/yauzl/2.10.0:
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
dependencies:
@@ -937,8 +966,8 @@ packages:
graceful-readlink: 1.0.1
dev: true
- /commander/9.4.1:
- resolution: {integrity: sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==}
+ /commander/9.5.0:
+ resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
dev: true
@@ -1586,6 +1615,15 @@ packages:
universalify: 2.0.0
dev: true
+ /fs-extra/11.1.0:
+ resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==}
+ engines: {node: '>=14.14'}
+ dependencies:
+ graceful-fs: 4.2.10
+ jsonfile: 6.1.0
+ universalify: 2.0.0
+ dev: true
+
/fs-extra/9.0.1:
resolution: {integrity: sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==}
engines: {node: '>=10'}
@@ -2063,15 +2101,6 @@ packages:
resolution: {integrity: sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=}
dev: true
- /jest-worker/26.6.2:
- resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
- engines: {node: '>= 10.13.0'}
- dependencies:
- '@types/node': 18.11.9
- merge-stream: 2.0.0
- supports-color: 7.2.0
- dev: true
-
/jose/4.11.1:
resolution: {integrity: sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==}
dev: true
@@ -3052,18 +3081,6 @@ packages:
svelte: 3.53.1
dev: true
- /rollup-plugin-terser/7.0.2_rollup@3.4.0:
- resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
- peerDependencies:
- rollup: ^2.0.0
- dependencies:
- '@babel/code-frame': 7.18.6
- jest-worker: 26.6.2
- rollup: 3.4.0
- serialize-javascript: 4.0.0
- terser: 5.16.0
- dev: true
-
/rollup-pluginutils/2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
dependencies:
@@ -3140,12 +3157,6 @@ packages:
lru-cache: 6.0.0
dev: true
- /serialize-javascript/4.0.0:
- resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==}
- dependencies:
- randombytes: 2.1.0
- dev: true
-
/serialize-javascript/6.0.0:
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
dependencies:
@@ -3207,6 +3218,10 @@ packages:
resolution: {integrity: sha512-BhqPaXVd8yGiZucZzn8rRV+1q19iVUvkv4mVXvtc4KMuf8H7/FrreTqzDcTfOl8X44DftHdn/laMkAKtX/itcw==}
dev: true
+ /smob/0.0.6:
+ resolution: {integrity: sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==}
+ dev: true
+
/sonic-boom/3.2.0:
resolution: {integrity: sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==}
dependencies:
@@ -3461,8 +3476,8 @@ packages:
engines: {node: '>= 8'}
dev: true
- /terser/5.16.0:
- resolution: {integrity: sha512-KjTV81QKStSfwbNiwlBXfcgMcOloyuRdb62/iLFPGBcVNF4EXjhdYBhYHmbJpiBrVxZhDvltE11j+LBQUxEEJg==}
+ /terser/5.16.1:
+ resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==}
engines: {node: '>=10'}
hasBin: true
dependencies:
diff --git a/scripts/build_rollup.mjs b/scripts/build_rollup.mjs
new file mode 100644
index 0000000..361c2b4
--- /dev/null
+++ b/scripts/build_rollup.mjs
@@ -0,0 +1,82 @@
+import { readStdout } from "./utils.mjs";
+
+import commonjs from '@rollup/plugin-commonjs';
+import resolve from '@rollup/plugin-node-resolve';
+import replace from '@rollup/plugin-replace';
+import terser from '@rollup/plugin-terser';
+import typescript from '@rollup/plugin-typescript';
+import os from "node:os";
+import pathLib from 'node:path';
+import svelte from 'rollup-plugin-svelte';
+import autoPreprocess from 'svelte-preprocess';
+
+
+const useCustomElement = ["components"]
+
+export default function (opts) {
+ const { profile, websocketServer, src, dist, entryPoints, target } = opts
+
+ const isProd = profile.toLowerCase() === 'prod';
+ const sourceMap = !isProd
+
+ const safeEnvVar = {
+ commitId: readStdout("git rev-parse --short HEAD"),
+ date: readStdout("date --rfc-3339=seconds"),
+ nodeVersion: readStdout("node --version"),
+ rollupVersion: readStdout("npx rollup --version"),
+ os: os.platform(),
+ profile,
+ websocketServer,
+ target,
+ }
+
+ function getPlugins(entrypoint) {
+ return [
+ commonjs(),
+ replace({
+ values: {
+ // avoid "Function constructor" warning
+ "Function('return this')()": "globalThis",
+ },
+ delimiters: ["", ""],
+ preventAssignment: true
+ }),
+ replace({
+ __ENV: JSON.stringify(safeEnvVar),
+ __BUILD_PROFILE: JSON.stringify(profile),
+ preventAssignment: true
+ }),
+ svelte({
+ preprocess: autoPreprocess(),
+ compilerOptions: {
+ customElement: useCustomElement.includes(entrypoint)
+ }
+ }),
+ typescript({ sourceMap: false }),
+ resolve({ browser: true }),
+ isProd && terser(),
+ ]
+ }
+
+ const output = []
+
+ for (const e of entryPoints) {
+ output.push({
+ external: ["chai", "mocha", "Mocha"],
+ input: pathLib.join(src, e, "main.ts"),
+ output: {
+ sourcemap: sourceMap,
+ globals: {
+ "chai": "chai",
+ "mocha": "mocha",
+ "Mocha": "Mocha"
+ },
+ file: pathLib.join(dist, e, "main.js"),
+ format: 'iife'
+ },
+ plugins: getPlugins(e)
+ })
+ }
+
+ return output
+}
\ No newline at end of file
diff --git a/scripts/cli.mjs b/scripts/cli.mjs
new file mode 100644
index 0000000..a486844
--- /dev/null
+++ b/scripts/cli.mjs
@@ -0,0 +1,295 @@
+import { program } from 'commander'
+import fs from 'fs-extra'
+import { spawn } from 'node:child_process'
+import pathLib from 'node:path'
+import * as rollup from 'rollup'
+import webExt from 'web-ext'
+import { WebSocketServer } from 'ws'
+import zipDir from 'zip-dir'
+import buildRollupConfig from './build_rollup.mjs'
+import genManifest from './gen_manifest.mjs'
+import { isTestTarget, mustEnv } from './utils.mjs'
+
+const BUILD_DIR = mustEnv("BUILD_DIR", "./build")
+const BUILD_SRC = mustEnv("BUILD_SRC", "./src")
+const BUILD_VERSION = mustEnv("BUILD_VERSION", "2.1.0")
+
+const FIREFOX = "firefox-developer-edition"
+function copyAssets(destDir) {
+ const assets = [
+ {
+ src: pathLib.join(BUILD_SRC, "options/options.html"),
+ dest: pathLib.join(destDir, "options/options.html")
+ },
+ {
+ src: pathLib.join(BUILD_SRC, "/icon"),
+ dest: pathLib.join(destDir, "/icon")
+ },
+ {
+ src: pathLib.join(BUILD_SRC, "/_locales"),
+ dest: pathLib.join(destDir, "/_locales")
+ },
+
+ {
+ src: "node_modules/simpledotcss/simple.min.css",
+ dest: pathLib.join(destDir, "res/simple.min.css")
+ },
+ ]
+ for (const entry of assets) {
+ fs.copySync(entry.src, entry.dest)
+ }
+}
+
+function copyTestAssets(dest) {
+ dest = pathLib.join(dest, "/test")
+ const assets = [
+ {
+ src: pathLib.join("node_modules/mocha/mocha.js"),
+ dest: pathLib.join(dest, "mocha.js")
+ },
+ {
+ src: pathLib.join("node_modules/mocha/mocha.css"),
+ dest: pathLib.join(dest, "mocha.css")
+ },
+ {
+ src: pathLib.join("node_modules/chai/chai.js"),
+ dest: pathLib.join(dest, "chai.js")
+ },
+ {
+ src: pathLib.join(BUILD_SRC, "/test/mocha.html"),
+ dest: pathLib.join(dest, "mocha.html")
+ }
+ ]
+ for (const entry of assets) {
+ fs.copySync(entry.src, entry.dest)
+ }
+}
+
+function buildDist(target) {
+ return pathLib.join(BUILD_DIR, target, "dist")
+}
+
+function buildManifest(dist, version, target) {
+ const manifest = genManifest({
+ version: version,
+ target: target,
+ })
+ fs.writeFileSync(pathLib.join(dist, "manifest.json"), JSON.stringify(manifest, null, " "))
+}
+
+async function buildWithRollup(config) {
+ for (const optionsObj of config) {
+ try {
+ const bundle = await rollup.rollup(optionsObj);
+ await bundle.write(optionsObj.output);
+ if (bundle) {
+ // closes the bundle
+ await bundle.close();
+ }
+ } catch (err) {
+ console.error(err)
+ }
+ }
+}
+
+function validateTarget(target) {
+ if (!["firefox", "chromium", "firefox-test"].includes(target)) {
+ throw new Error("not support build target: " + target)
+ }
+}
+
+const defaultEntryPoints = ["background", "content_scripts", "options", "components"]
+
+program.command('build')
+ .description('Build extension')
+ .option('-t, --target ', "The browser target to build", "firefox")
+ .option('--profile ', "The profile of compilation", "debug")
+ .option('--artifacts ', "The directory to store artifacts", "artifacts")
+ .option('--lint', "Use web-ext the validate extension source", false)
+ .option('--watch', "Watch source file change", false)
+ .option('--websocket-server ', "The address of websocket server for capture event of unit test", "ws://localhost:8000")
+ .action(async (args, options) => {
+ validateTarget(args.target)
+ const dist = pathLib.join(BUILD_DIR, args.target, "dist")
+ copyAssets(dist)
+ buildManifest(dist, BUILD_VERSION, args.target)
+
+ const entryPoints = [...defaultEntryPoints]
+ if (isTestTarget(args.target)) {
+ entryPoints.push("test")
+ copyTestAssets()
+ }
+ console.debug("args:", args)
+ console.debug("entryPoints: ", entryPoints)
+
+ const config = buildRollupConfig({
+ profile: args.profile,
+ src: BUILD_SRC,
+ dist: dist,
+ entryPoints: entryPoints,
+ watch: args.watch,
+ target: args.target,
+ websocketServer: args.websocketServer,
+ })
+ const artifacts = pathLib.join(
+ BUILD_DIR,
+ args.artifacts
+ )
+ fs.ensureDirSync(artifacts)
+
+ if (args.watch) {
+ const watcher = rollup.watch(config)
+ watcher.on('event', (event) => {
+ if (event.code === "BUNDLE_END") {
+ console.log("code: ", event.code, "input:", event.input, "output:", event.output)
+ }
+ const { result } = event;
+ if (result) {
+ result.close();
+ }
+ });
+ } else {
+ await buildWithRollup(config)
+
+ if (args.lint) {
+ webExt.cmd.lint({ sourceDir: dist })
+ }
+ await zipDir(
+ dist,
+ {
+ saveTo: pathLib.join(
+ artifacts,
+ `glitterdrag-pro-${BUILD_VERSION}-${args.target}.zip`
+ )
+ }
+ );
+ }
+
+ });
+
+program.command('test')
+ .description('Start Firefox browser and run unit tests')
+ .option('-t, --target ', "The target to build", "firefox-test")
+ .option('-s, --websocket-server', "The address of websocket server for capture event of unit test", "ws://localhost:8000")
+ .action(async (args) => {
+ validateTarget(args.target)
+ const dist = buildDist(args.target)
+
+ async function runBrowser(signal) {
+
+ return new Promise((resolve, reject) => {
+
+ const proc = spawn("pnpm", ["exec", "web-ext", "run", "-f", FIREFOX, "-s", dist], {
+ stdio: ["ignore", process.stdout, process.stderr],
+ signal: signal
+ })
+
+ proc.on("error", (err) => {
+ console.error(err)
+ resolve()
+ })
+
+ proc.on("close", (code) => {
+ if (code === 0) {
+ resolve()
+ } else {
+ reject("code: " + code)
+ }
+ })
+ })
+ }
+
+
+ async function waitTestComplete() {
+ const url = new URL(args.websocketServer)
+ let port = undefined
+ if (url.port.length > 0) {
+ port = Number.parseInt(url.port)
+ }
+ const wss = new WebSocketServer({ host: url.hostname, port, });
+
+ return new Promise((resolve) => {
+ wss.on('connection', (ws) => {
+ ws.on('message', (data) => {
+ const [type, payload] = JSON.parse(data)
+ switch (type) {
+ case "start": {
+ ws.send("ok")
+ break
+ }
+ case "pass": {
+ ws.send("ok")
+ console.info("pass: %s", payload.fullTitle)
+ break
+ }
+ case "fail": {
+ ws.send("ok")
+ console.error("fail: %s", payload.fullTitle)
+ break
+ }
+ case "end": {
+ ws.close()
+ wss.close()
+ resolve(payload)
+ break
+ }
+ default: {
+ throw new Error("unknown event type: ", type)
+ }
+ }
+ });
+ ws.send("ok")
+ });
+ })
+ }
+
+ const controller = new AbortController()
+ const resultPromise = waitTestComplete()
+ const browser = runBrowser(controller.signal)
+
+ const result = await resultPromise
+ await browser
+ console.log("tests: %d, passes: %d, failures: %d", result.tests, result.passes, result.failures)
+ if (result.failure > 0) {
+ process.exit(1)
+ } else {
+ process.exit(0)
+ }
+ });
+
+program.command('watch')
+ .description('detect dist directory change and reload extension')
+ .option('-t, --target', "The target to build", "firefox")
+ .action((args) => {
+
+ validateTarget(args.target)
+
+ const dist = buildDist(args.target)
+
+ return new Promise((resolve, reject) => {
+ const proc = spawn("pnpm", ["exec", "web-ext", "run", "-f", FIREFOX, "-s", dist], {
+ stdio: ["ignore", process.stdout, process.stderr],
+ })
+
+ proc.on("error", (err) => {
+ console.error(err)
+ resolve()
+ })
+
+ proc.on("close", (code) => {
+ if (code === 0) {
+ resolve()
+ } else {
+ reject("code: " + code)
+ }
+ })
+ })
+ });
+
+program.command('clean')
+ .description('clean build directory')
+ .action(() => {
+ fs.removeSync(BUILD_DIR)
+ });
+
+await program.parseAsync();
\ No newline at end of file
diff --git a/scripts/gen_manifest.js b/scripts/gen_manifest.js
deleted file mode 100644
index 4b74b7b..0000000
--- a/scripts/gen_manifest.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { mustEnv } from "./utils.js"
-
-const version = mustEnv("BUILD_VERSION")
-const targetBrowser = mustEnv("TARGET_BROWSER")
-const isFirefox = targetBrowser.startsWith("firefox")
-const isChromium = targetBrowser.startsWith("chromium")
-const isTest = targetBrowser.endsWith("test")
-
-function contentSecurityPolicy() {
- if (isFirefox && isTest) {
- // override CSP to allow mocha test connect to a insecure websocket
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1797086
- return {
- "extension_pages": "script-src 'self'"
- }
- }
- return undefined
-}
-
-function manifestPermissions() {
- const permissions = [
- "activeTab",
- "storage",
- "tabs",
- "clipboardWrite",
- "downloads",
- "search",
- "scripting",
- "contextMenus",
- "alarms"
- ]
-
- if (isFirefox) {
- permissions.push(...[
- "contextualIdentities",
- "cookies"
- ])
- }
-
- return permissions
-}
-
-function manifestBackground() {
- if (isChromium) {
- return {
- "service_worker": "background/main.js"
- }
- }
- return {
- "scripts": [
- "background/main.js"
- ]
- }
-}
-
-function manifestVersion() {
- return version
-}
-
-function browserSpecificSetting() {
- if (isFirefox) {
- return {
- "gecko": {
- "id": "glitterdragpro@harytfw",
- "strict_min_version": "106.0"
- }
- }
- }
- return undefined
-}
-
-const manifest = {
- "manifest_version": 3,
- "name": "__MSG_extensionName__",
- "description": "__MSG_extensionDescription__",
- "version": manifestVersion(),
- "homepage_url": "https://github.com/harytfw/GlitterDrag",
- "icons": {
- "128": "/icon/drag.png"
- },
- "author": "harytfw",
- "permissions": manifestPermissions(),
- "host_permissions": [
- "*://*/*"
- ],
- "background": manifestBackground(),
- "content_scripts": [
- {
- "run_at": "document_end",
- "all_frames": true,
- "matches": [
- "*://*/*"
- ],
- "js": [
- "content_scripts/main.js"
- ]
- }
- ],
- "options_ui": {
- "page": "options/options.html",
- "browser_style": true,
- "open_in_tab": true
- },
- "web_accessible_resources": [
- {
- "resources": [
- "components/main.js"
- ],
- "matches": [
- "*://*/*"
- ]
- }
- ],
- "default_locale": "en",
- "browser_specific_settings": browserSpecificSetting(),
- "content_security_policy": contentSecurityPolicy()
-}
-
-console.log(JSON.stringify(manifest, null, " "))
\ No newline at end of file
diff --git a/scripts/gen_manifest.mjs b/scripts/gen_manifest.mjs
new file mode 100644
index 0000000..a9c9a56
--- /dev/null
+++ b/scripts/gen_manifest.mjs
@@ -0,0 +1,124 @@
+import assert from 'node:assert/strict'
+export default function (opt) {
+
+ const { version, target } = opt
+
+ {
+ assert.ok(typeof version === 'string')
+ assert.ok(version.length > 0)
+
+ assert.ok(typeof target === 'string')
+ assert.ok(target.length > 0)
+ }
+
+ const isFirefox = target.startsWith("firefox")
+ const isChromium = target.startsWith("chromium")
+ const isTest = target.endsWith("test")
+
+ function contentSecurityPolicy(opt) {
+ if (isFirefox && isTest) {
+ // override CSP to allow mocha test connect to a insecure websocket
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1797086
+ return {
+ "extension_pages": "script-src 'self'"
+ }
+ }
+ return undefined
+ }
+
+ function manifestPermissions() {
+ const permissions = [
+ "activeTab",
+ "storage",
+ "tabs",
+ "clipboardWrite",
+ "downloads",
+ "search",
+ "scripting",
+ "contextMenus",
+ "alarms"
+ ]
+
+ if (isFirefox) {
+ permissions.push(...[
+ "contextualIdentities",
+ "cookies"
+ ])
+ }
+
+ return permissions
+ }
+
+ function manifestBackground() {
+ if (isChromium) {
+ return {
+ "service_worker": "background/main.js"
+ }
+ }
+ return {
+ "scripts": [
+ "background/main.js"
+ ]
+ }
+ }
+
+ function browserSpecificSetting() {
+ if (isFirefox) {
+ return {
+ "gecko": {
+ "id": "glitterdragpro@harytfw",
+ "strict_min_version": "106.0"
+ }
+ }
+ }
+ return undefined
+ }
+
+ const manifest = {
+ "manifest_version": 3,
+ "name": "__MSG_extensionName__",
+ "description": "__MSG_extensionDescription__",
+ "version": version,
+ "homepage_url": "https://github.com/harytfw/GlitterDrag",
+ "icons": {
+ "128": "/icon/drag.png"
+ },
+ "author": "harytfw",
+ "permissions": manifestPermissions(),
+ "host_permissions": [
+ "*://*/*"
+ ],
+ "background": manifestBackground(),
+ "content_scripts": [
+ {
+ "run_at": "document_end",
+ "all_frames": true,
+ "matches": [
+ "*://*/*"
+ ],
+ "js": [
+ "content_scripts/main.js"
+ ]
+ }
+ ],
+ "options_ui": {
+ "page": "options/options.html",
+ "browser_style": true,
+ "open_in_tab": true
+ },
+ "web_accessible_resources": [
+ {
+ "resources": [
+ "components/main.js"
+ ],
+ "matches": [
+ "*://*/*"
+ ]
+ }
+ ],
+ "default_locale": "en",
+ "browser_specific_settings": browserSpecificSetting(),
+ "content_security_policy": contentSecurityPolicy()
+ }
+ return manifest
+}
diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js
deleted file mode 100644
index 560c9b8..0000000
--- a/scripts/rollup.config.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import { mustEnv, readStdout } from "./utils.js"
-
-import svelte from 'rollup-plugin-svelte';
-import resolve from '@rollup/plugin-node-resolve';
-import typescript from '@rollup/plugin-typescript';
-import replace from '@rollup/plugin-replace';
-import { terser } from 'rollup-plugin-terser';
-import autoPreprocess from 'svelte-preprocess';
-import commonjs from '@rollup/plugin-commonjs';
-import pathLib from 'node:path'
-import os from "node:os"
-
-const BUILD_COMMIT_ID = readStdout("git rev-parse --short HEAD")
-const BUILD_DATE = readStdout("date --rfc-3339=seconds")
-const BUILD_NODE_VERSION = readStdout("node --version")
-const BUILD_ROLLUP_VERSION = readStdout("npx rollup --version")
-const BUILD_OS = os.platform()
-const BUILD_PROFILE = mustEnv("BUILD_PROFILE")
-const BUILD_WEBSOCKET_SERVER = mustEnv("BUILD_WEBSOCKET_SERVER", "")
-
-const isProd = BUILD_PROFILE.toLowerCase() === 'prod';
-const sourceMap = !isProd
-const src = mustEnv("SRC")
-const dist = mustEnv("TARGET_DIST")
-const entryPoints = mustEnv("ENTRY_POINTS").split(" ")
-
-const safeEnvVar = {
- BUILD_COMMIT_ID,
- BUILD_DATE,
- BUILD_NODE_VERSION,
- BUILD_ROLLUP_VERSION,
- BUILD_OS,
- BUILD_PROFILE,
- BUILD_WEBSOCKET_SERVER,
-}
-
-const useCustomElement = ["components"]
-
-function getPlugins(entrypoint) {
- const plugins = [
- commonjs(),
- replace({
- values: {
- // avoid "Function constructor" warning
- "Function('return this')()": "globalThis",
- },
- delimiters: ["", ""],
- preventAssignment: true
- }),
- replace({
- __ENV: JSON.stringify(safeEnvVar),
- __BUILD_PROFILE: JSON.stringify(BUILD_PROFILE),
- preventAssignment: true
- }),
- svelte({
- preprocess: autoPreprocess(),
- compilerOptions: {
- customElement: useCustomElement.includes(entrypoint)
- }
- }),
- typescript({ sourceMap: false }),
- resolve({ browser: true }),
- ]
-
- if (isProd) {
- plugins.push(terser())
- }
-
- return plugins
-}
-
-console.log("sourcemap: ", sourceMap)
-
-const output = []
-
-for (const e of entryPoints) {
- output.push({
- external: ["chai", "mocha", "Mocha"],
- input: pathLib.join(src, e, "main.ts"),
- output: {
- sourcemap: sourceMap,
- globals: {
- "chai": "chai",
- "mocha": "mocha",
- "Mocha": "Mocha"
- },
- file: pathLib.join(dist, e, "main.js"),
- format: 'iife'
- },
- plugins: getPlugins(e)
- })
-}
-
-export default output
diff --git a/scripts/test_bootstrap.js b/scripts/test_bootstrap.js
deleted file mode 100644
index ffd3c86..0000000
--- a/scripts/test_bootstrap.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { WebSocketServer } from 'ws';
-import { spawn } from 'node:child_process';
-import webExt from 'web-ext'
-
-
-const TEST_TIMEOUT_MS = 1000 * 60 * 5
-
-const { TARGET_DIST, BUILD_WEBSOCKET_SERVER } = process.env
-
-async function runBrowser(signal) {
-
- return new Promise((resolve, reject) => {
-
- const proc = spawn("pnpm", ["exec", "web-ext", "run", "-f", "firefox-developer-edition" , "-s", TARGET_DIST], {
- stdio: ["ignore", process.stdout, process.stderr],
- signal: signal
- })
-
- proc.on("error", (err) => {
- console.error(err)
- resolve()
- })
-
- proc.on("close", (code) => {
- if (code === 0) {
- resolve()
- } else {
- reject("code: " + code)
- }
- })
- })
-}
-
-async function waitTestComplete() {
- const url = new URL(BUILD_WEBSOCKET_SERVER)
- let port = undefined
- if (url.port.length > 0) {
- port = Number.parseInt(url.port)
- }
- const wss = new WebSocketServer({ host: url.hostname, port, });
-
- return new Promise((resolve) => {
- wss.on('connection', (ws) => {
- ws.on('message', (data) => {
- const [type, payload] = JSON.parse(data)
- switch (type) {
- case "start": {
- ws.send("ok")
- break
- }
- case "pass": {
- ws.send("ok")
- console.info("pass: %s", payload.fullTitle)
- break
- }
- case "fail": {
- ws.send("ok")
- console.error("fail: %s", payload.fullTitle)
- break
- }
- case "end": {
- ws.close()
- wss.close()
- resolve(payload)
- break
- }
- default: {
- throw new Error("unknown event type: ", type)
- }
- }
- });
- ws.send("ok")
- });
- })
-}
-
-async function main() {
- const controller = new AbortController()
-
- const browser = runBrowser(controller.signal)
-
- const summary = await waitTestComplete()
-
- await browser
-
- console.log("tests: %d, passes: %d, failures: %d", summary.tests, summary.passes, summary.failures)
- // console.log(summary)
- if (summary.failure > 0) {
- process.exit(1)
- } else {
- process.exit(0)
- }
-}
-
-main()
\ No newline at end of file
diff --git a/scripts/utils.js b/scripts/utils.mjs
similarity index 85%
rename from scripts/utils.js
rename to scripts/utils.mjs
index ec7cab8..caa21af 100644
--- a/scripts/utils.js
+++ b/scripts/utils.mjs
@@ -18,3 +18,7 @@ export function readStdout(command) {
const result = execSync(command)
return result.toString("utf8")
}
+
+export function isTestTarget(target) {
+ return target.endsWith("test")
+}
diff --git a/src/build_info.ts b/src/build_info.ts
index 368d511..fb0e4f1 100644
--- a/src/build_info.ts
+++ b/src/build_info.ts
@@ -1,12 +1,4 @@
const env = __ENV
-export default {
- commitId: env["BUILD_COMMIT_ID"],
- date: env["BUILD_DATE"],
- nodeVersion: env["BUILD_NODE_VERSION"],
- rollupVersion: env["BUILD_ROLLUP_VERSION"],
- os: env["BUILD_OS"],
- websocketServer: env["BUILD_WEBSOCKET_SERVER"],
- profile: env["BUILD_PROFILE"],
-} as const
+export default env
diff --git a/src/global.d.ts b/src/global.d.ts
index 2f1a245..676b83f 100644
--- a/src/global.d.ts
+++ b/src/global.d.ts
@@ -1,8 +1,18 @@
import { Mocha } from 'mocha'
+type EnvVariable = {
+ commitId: string
+ date: string
+ nodeVersion: string
+ rollupVersion: string
+ os: string
+ websocketServer: string
+ profile: string
+ target: string
+}
export declare global {
- export const __ENV: Record
+ export const __ENV: EnvVariable
export const __BUILD_PROFILE: 'prod' | 'debug'
export const chrome: any
export const Mocha: Mocha
diff --git a/src/test/mocha_init.ts b/src/test/mocha_init.ts
index 45988f9..f9511ff 100644
--- a/src/test/mocha_init.ts
+++ b/src/test/mocha_init.ts
@@ -67,7 +67,7 @@ async function closeInstance() {
}
export let ws = null
-if (buildInfo.websocketServer) {
+if (buildInfo.target.endsWith("test") && buildInfo.websocketServer) {
ws = new WebSocket(buildInfo.websocketServer)
ws.addEventListener("open", (event) => {
console.log("open", event)
From 1165bdac9b58365bdc848f3f1ad5210c4cff35dd Mon Sep 17 00:00:00 2001
From: harytfw
Date: Tue, 10 Jan 2023 14:31:27 +0800
Subject: [PATCH 10/21] refactor: clean code about menu
Signed-off-by: harytfw
---
src/components/menu/menu.svelte | 29 +++++-------
src/components/menu/menu.ts | 31 +++++++------
src/components/menu/menu_builder.test.ts | 5 ++-
src/components/menu/menu_builder.ts | 56 +++++++++++++-----------
src/components/types.ts | 3 ++
5 files changed, 66 insertions(+), 58 deletions(-)
diff --git a/src/components/menu/menu.svelte b/src/components/menu/menu.svelte
index 9f75599..eb44c77 100644
--- a/src/components/menu/menu.svelte
+++ b/src/components/menu/menu.svelte
@@ -3,11 +3,7 @@
@@ -99,14 +95,13 @@
id="container"
on:dragover={dragover}
on:dragleave={dragleave}
- width={box()[0]}
- height={box()[1]}
+ style="width: {box[0]}px; height: {box[1]}px;"
bind:this={container}
>
diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts
index cb3d3d6..cc2b727 100644
--- a/src/components/menu/menu.ts
+++ b/src/components/menu/menu.ts
@@ -8,35 +8,27 @@ import { LogLevel } from '../../config/config';
const log = rootLog.subLogger(LogLevel.VVV, "menu")
const tag = "glitterdrag-menu"
-function computeStyle(x: number, y: number, width: number, height: number) {
- const style = `position: absolute;
- left: ${x}px;
- top: ${y}px;
- z-index: 2147483647;
- width: ${width}px;
- height: ${height}px;
- user-select: none;
- `
- return style
-}
class MenuImpl extends MessageTarget implements MenuInterface {
+
elem: MenuElement & HTMLElement
constructor() {
super(ProxyEventType.Menu)
customElements.define(tag, MenuElement as any);
this.elem = document.createElement(tag) as HTMLElement & MenuElement
- this.elem.setAttribute("style", computeStyle(0, 0, 0, 0));
+ this.elem.setAttribute("style", this.computeStyle(0, 0, 0, 0));
}
show(opts: ShowMenuOptions) {
log.V("show menu: ", opts)
- const [width, height] = this.elem.box()
+ const [width, height] = this.elem.box
+
const x = opts.position.x - width / 2
const y = opts.position.y - height / 2
- this.elem.setAttribute("style", computeStyle(x, y, width, height));
+
+ this.elem.setAttribute("style", this.computeStyle(x, y, width, height));
this.elem.show(opts)
!this.elem.parentElement && document.body.append(this.elem)
}
@@ -50,6 +42,17 @@ class MenuImpl extends MessageTarget implements MenuInterface {
this.elem.remove()
}
+ computeStyle(x: number, y: number, width: number, height: number) {
+ const style = `position: absolute;
+ left: ${x}px;
+ top: ${y}px;
+ z-index: 2147483647;
+ width: ${width}px;
+ height: ${height}px;
+ user-select: none;
+ `
+ return style
+ }
}
export const menuImpl = new MenuImpl()
\ No newline at end of file
diff --git a/src/components/menu/menu_builder.test.ts b/src/components/menu/menu_builder.test.ts
index 119c4d8..d86af46 100644
--- a/src/components/menu/menu_builder.test.ts
+++ b/src/components/menu/menu_builder.test.ts
@@ -56,7 +56,10 @@ describe("menu builder", () => {
dividerLineLength: 10,
iconOffset: 10,
iconSize: 10,
- textOffset: 10
+ textOffset: 10,
+ fontSize: 4,
+ width: 100,
+ height: 100
})
assert.equal(svg.querySelectorAll("g").length, 2)
})
diff --git a/src/components/menu/menu_builder.ts b/src/components/menu/menu_builder.ts
index aa27ed0..731cfa7 100644
--- a/src/components/menu/menu_builder.ts
+++ b/src/components/menu/menu_builder.ts
@@ -17,31 +17,6 @@ const colorDef = `
id="stop14618" />
`;
-const styleContent = `
- g .sector-fill {
- fill: #fff0;
- stroke: none;
- }
-
- g:hover .sector-fill {
- fill: url("#sector-color");
- }
-
- g.hover .sector-fill {
- fill: url("#sector-color");
- }
-
- g.hover .sector-fill text {
- fill: white;
- }
-
- g text {
- font-size: 4px;
- fill: white;
- text-shadow: #000 0px 0 4px;
- font-weight: bold;
- }
-`;
export function polar2cartesian(
r: number,
@@ -80,7 +55,7 @@ export async function toDataURL(text: string, type: string): Promise {
export async function rebuildMenu(svg: SVGSVGElement, opt: MenuOptions) {
- const [width, height] = [200, 200];
+ const { width, height } = opt
const center = [width / 2, height / 2];
const arcRadius = opt.dividerLineLength + opt.circleRadius;
const angleUnit = 360 / opt.items.length;
@@ -96,6 +71,35 @@ export async function rebuildMenu(svg: SVGSVGElement, opt: MenuOptions) {
svg.dataset["iconOffset"] = `${opt.iconOffset}`;
svg.dataset["iconSize"] = `${opt.iconSize}`;
svg.dataset["textOffset"] = `${opt.textOffset}`;
+ svg.dataset["fontSize"] = `${opt.fontSize}`;
+
+
+ const styleContent = `
+ g .sector-fill {
+ fill: #fff0;
+ stroke: none;
+ }
+
+ g:hover .sector-fill {
+ fill: url("#sector-color");
+ }
+
+ g.hover .sector-fill {
+ fill: url("#sector-color");
+ }
+
+ g.hover .sector-fill text {
+ fill: white;
+ }
+
+ g text {
+ font-size: ${opt.fontSize}px;
+ fill: white;
+ text-shadow: #000 0px 0 4px;
+ font-weight: bold;
+ }
+`;
+
{
const defs = document.createElementNS(SVG_NAMESPACE, "defs");
diff --git a/src/components/types.ts b/src/components/types.ts
index 92b574b..e78c96c 100644
--- a/src/components/types.ts
+++ b/src/components/types.ts
@@ -75,4 +75,7 @@ export interface MenuOptions {
iconOffset: number;
iconSize: number;
textOffset: number;
+ fontSize: number;
+ width: number;
+ height: number;
}
From 66aa94385deffd0e5354b2e87aedf55946c83ca5 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Wed, 18 Jan 2023 18:25:58 +0800
Subject: [PATCH 11/21] refactor: clean code about cli.mjs
Signed-off-by: harytfw
---
scripts/cli.mjs | 23 ++++++++++-------------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/scripts/cli.mjs b/scripts/cli.mjs
index a486844..ddc05ed 100644
--- a/scripts/cli.mjs
+++ b/scripts/cli.mjs
@@ -15,6 +15,7 @@ const BUILD_SRC = mustEnv("BUILD_SRC", "./src")
const BUILD_VERSION = mustEnv("BUILD_VERSION", "2.1.0")
const FIREFOX = "firefox-developer-edition"
+
function copyAssets(destDir) {
const assets = [
{
@@ -79,23 +80,19 @@ function buildManifest(dist, version, target) {
async function buildWithRollup(config) {
for (const optionsObj of config) {
- try {
- const bundle = await rollup.rollup(optionsObj);
- await bundle.write(optionsObj.output);
- if (bundle) {
- // closes the bundle
- await bundle.close();
- }
- } catch (err) {
- console.error(err)
+ const bundle = await rollup.rollup(optionsObj);
+ await bundle.write(optionsObj.output);
+ if (bundle) {
+ await bundle.close();
}
}
}
function validateTarget(target) {
- if (!["firefox", "chromium", "firefox-test"].includes(target)) {
- throw new Error("not support build target: " + target)
+ if (["firefox", "chromium", "firefox-test"].includes(target)) {
+ return
}
+ throw new Error("not support build target: " + target)
}
const defaultEntryPoints = ["background", "content_scripts", "options", "components"]
@@ -105,7 +102,7 @@ program.command('build')
.option('-t, --target ', "The browser target to build", "firefox")
.option('--profile ', "The profile of compilation", "debug")
.option('--artifacts ', "The directory to store artifacts", "artifacts")
- .option('--lint', "Use web-ext the validate extension source", false)
+ .option('--lint', "Use web-ext validates extension source", false)
.option('--watch', "Watch source file change", false)
.option('--websocket-server ', "The address of websocket server for capture event of unit test", "ws://localhost:8000")
.action(async (args, options) => {
@@ -263,7 +260,7 @@ program.command('watch')
.action((args) => {
validateTarget(args.target)
-
+
const dist = buildDist(args.target)
return new Promise((resolve, reject) => {
From 26a48df37217fa0b42698e014aec65cd15d17ef8 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Wed, 18 Jan 2023 18:32:32 +0800
Subject: [PATCH 12/21] test: clean code about unit test
Signed-off-by: harytfw
---
Makefile | 2 +-
src/background/executor.test.ts | 2 +-
src/background/search.test.ts | 2 +-
src/background/utils.test.ts | 16 ++++++++--------
src/config/config.test.ts | 9 +++------
src/content_scripts/utils.test.ts | 20 ++++++++++----------
src/utils/test.ts | 8 ++++++++
src/utils/test_helper.ts | 22 ----------------------
src/utils/var_substitute.test.ts | 1 -
9 files changed, 32 insertions(+), 50 deletions(-)
create mode 100644 src/utils/test.ts
delete mode 100644 src/utils/test_helper.ts
diff --git a/Makefile b/Makefile
index 40d6f81..3fb7c19 100644
--- a/Makefile
+++ b/Makefile
@@ -24,7 +24,7 @@ ext-chromium:
$(cli) build --target chromium --profile $(BUILD_PROFILE)
.PHONY: test
-test: TARGET ?= firefox
+test: TARGET ?= firefox-test
test:
$(cli) test --target $(TARGET)
diff --git a/src/background/executor.test.ts b/src/background/executor.test.ts
index e84b3b6..88f0d30 100644
--- a/src/background/executor.test.ts
+++ b/src/background/executor.test.ts
@@ -1,7 +1,7 @@
import browser, { tabs } from 'webextension-polyfill';
import { ActionConfig, CommandKind } from "../config/config"
-import { closeTab } from '../utils/test_helper';
+import { closeTab } from '../utils/test';
import { Executor } from "./executor"
import { blankExecuteContext } from "../context/test_helper"
diff --git a/src/background/search.test.ts b/src/background/search.test.ts
index ceb6ac8..bbf17ee 100644
--- a/src/background/search.test.ts
+++ b/src/background/search.test.ts
@@ -1,5 +1,5 @@
import browser from 'webextension-polyfill'
-import { closeTab } from '../utils/test_helper'
+import { closeTab } from '../utils/test'
import { searchText } from './search'
describe("test browser search", () => {
diff --git a/src/background/utils.test.ts b/src/background/utils.test.ts
index 47fe91a..d0d874f 100644
--- a/src/background/utils.test.ts
+++ b/src/background/utils.test.ts
@@ -1,5 +1,5 @@
import browser from 'webextension-polyfill'
-import { assertOk } from "../utils/test_helper"
+import { assert } from "chai"
import { blankExecuteContext } from "../context/test_helper"
import { bufferToObjectURL, buildDownloadableURL, generatedDownloadFileName } from "./utils"
@@ -9,44 +9,44 @@ describe("background utils", () => {
it("text", async () => {
const ctx = await blankExecuteContext()
ctx.data.selection = "hello world"
- assertOk(generatedDownloadFileName(ctx, await buildDownloadableURL(ctx)))
+ assert.ok(generatedDownloadFileName(ctx, await buildDownloadableURL(ctx)))
})
it("image", async () => {
const ctx = await blankExecuteContext()
ctx.data.imageSource = "http://example.com/a.jpg"
- assertOk(generatedDownloadFileName(ctx, await buildDownloadableURL(ctx)))
+ assert.ok(generatedDownloadFileName(ctx, await buildDownloadableURL(ctx)))
})
it("link", async () => {
const ctx = await blankExecuteContext()
ctx.data.link = "http://example.com/"
- assertOk(generatedDownloadFileName(ctx, await buildDownloadableURL(ctx)))
+ assert.ok(generatedDownloadFileName(ctx, await buildDownloadableURL(ctx)))
})
})
it("buffer to object url", () => {
- assertOk(bufferToObjectURL(new ArrayBuffer(10)))
+ assert.ok(bufferToObjectURL(new ArrayBuffer(10)))
})
it("build url for text", async () => {
let ctx = await blankExecuteContext()
ctx.data.selection = "hello"
- assertOk(buildDownloadableURL(ctx))
+ assert.ok(buildDownloadableURL(ctx))
})
it("build url for image", async () => {
let ctx = await blankExecuteContext()
ctx.data.imageSource = browser.runtime.getURL("icon/drag.png")
- assertOk(buildDownloadableURL(ctx))
+ assert.ok(buildDownloadableURL(ctx))
})
it("build url for link", async () => {
let ctx = await blankExecuteContext()
ctx.data.link = "http://www.example.com"
- assertOk(buildDownloadableURL(ctx))
+ assert.ok(buildDownloadableURL(ctx))
})
})
\ No newline at end of file
diff --git a/src/config/config.test.ts b/src/config/config.test.ts
index fc514db..a9ce249 100644
--- a/src/config/config.test.ts
+++ b/src/config/config.test.ts
@@ -1,9 +1,6 @@
-import { assertOk } from "../utils/test_helper"
+import { assert } from "chai"
import { ActionConfig, BroadcastEventTarget, CommandRequest, configBroadcast as configBroadcast, Configuration, type ReadonlyConfiguration } from "./config"
-import chai from "chai"
-const assert = chai.assert
-
describe("test configuration", () => {
it("empty config", () => {
new Configuration()
@@ -11,14 +8,14 @@ describe("test configuration", () => {
it("action config", () => {
const action = new ActionConfig({})
- assertOk(action.toPlainObject())
+ assert.ok(action.toPlainObject())
})
it("request config", () => {
const req = new CommandRequest({
"url": "http://example.com"
})
- assertOk(req.toPlainObject())
+ assert.ok(req.toPlainObject())
})
it("broadcast", () => {
diff --git a/src/content_scripts/utils.test.ts b/src/content_scripts/utils.test.ts
index e617d59..cfb725a 100644
--- a/src/content_scripts/utils.test.ts
+++ b/src/content_scripts/utils.test.ts
@@ -1,4 +1,4 @@
-import { assertOk } from "../utils/test_helper"
+import { assert } from "chai"
import { getAngle, TinyLRU } from "./utils"
describe("content script utils", () => {
@@ -27,28 +27,28 @@ describe("content script utils", () => {
]
for (const tt of testCases) {
let a = getAngle(tt.args[0], tt.args[1])
- assertOk(tt.angle === a, tt.angle, " != ", a)
+ assert.ok(tt.angle === a)
}
})
it('tiny lru', () => {
const lru = new TinyLRU()
- assertOk(lru.get(1) === undefined)
- assertOk(lru.get(2) === undefined)
- assertOk(lru.get(undefined) === undefined)
+ assert.ok(lru.get(1) === undefined)
+ assert.ok(lru.get(2) === undefined)
+ assert.ok(lru.get(undefined) === undefined)
lru.put(1, 1)
lru.put(2, 2)
- assertOk(lru.get(1) === 1)
- assertOk(lru.get(2) === 2)
+ assert.ok(lru.get(1) === 1)
+ assert.ok(lru.get(2) === 2)
- assertOk(lru.get(0) === undefined)
- assertOk(lru.size() === 2)
+ assert.ok(lru.get(0) === undefined)
+ assert.ok(lru.size() === 2)
lru.clear()
- assertOk(lru.size() === 0)
+ assert.ok(lru.size() === 0)
})
})
\ No newline at end of file
diff --git a/src/utils/test.ts b/src/utils/test.ts
new file mode 100644
index 0000000..9a00a4f
--- /dev/null
+++ b/src/utils/test.ts
@@ -0,0 +1,8 @@
+import browser from 'webextension-polyfill';
+
+export async function closeTab(tabIds: number | number[], delayMs?: number) {
+ if (typeof delayMs === 'number') {
+ await new Promise(r => setTimeout(r, delayMs))
+ }
+ return browser.tabs.remove(tabIds)
+}
\ No newline at end of file
diff --git a/src/utils/test_helper.ts b/src/utils/test_helper.ts
deleted file mode 100644
index 64906c3..0000000
--- a/src/utils/test_helper.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-
-import browser from 'webextension-polyfill';
-import { assert } from 'chai'
-
-export function assertEqual(expected: any, actual: any, ...message: any[]) {
- return assert.deepEqual(actual, expected, ...message)
-}
-
-export function assertOk(value: any, ...message: any[]) {
- return assert.ok(value, ...message)
-}
-
-export function assertFail(value: any, ...message: any[]) {
- return assert.notOk(value, ...message)
-}
-
-export async function closeTab(tabIds: number | number[], delayMs?: number) {
- if (typeof delayMs === 'number') {
- await new Promise(r => setTimeout(r, delayMs))
- }
- return browser.tabs.remove(tabIds)
-}
\ No newline at end of file
diff --git a/src/utils/var_substitute.test.ts b/src/utils/var_substitute.test.ts
index d0ccb5c..f292d5c 100644
--- a/src/utils/var_substitute.test.ts
+++ b/src/utils/var_substitute.test.ts
@@ -1,4 +1,3 @@
-import { assertEqual } from "./test_helper"
import { VarSubstituteTemplate } from "./var_substitute"
import { assert } from 'chai'
From 595845daf035fc22efc100bc83a352c99c8e0537 Mon Sep 17 00:00:00 2001
From: harytfw
Date: Wed, 18 Jan 2023 19:43:07 +0800
Subject: [PATCH 13/21] chore: fix archive file was not generated when enable
lint flag
Signed-off-by: harytfw
---
scripts/cli.mjs | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/scripts/cli.mjs b/scripts/cli.mjs
index ddc05ed..e7f425e 100644
--- a/scripts/cli.mjs
+++ b/scripts/cli.mjs
@@ -147,10 +147,6 @@ program.command('build')
});
} else {
await buildWithRollup(config)
-
- if (args.lint) {
- webExt.cmd.lint({ sourceDir: dist })
- }
await zipDir(
dist,
{
@@ -161,7 +157,10 @@ program.command('build')
}
);
}
-
+ if (args.lint) {
+ // linter will terminate program with proper exit code
+ await webExt.cmd.lint({ sourceDir: dist }, { shouldExitProgram: true })
+ }
});
program.command('test')
From 62374fc0a9dd6a3d0d213083713defd5e2ef07fa Mon Sep 17 00:00:00 2001
From: harytfw
Date: Thu, 19 Jan 2023 11:38:33 +0800
Subject: [PATCH 14/21] feat: add feature tab to manage features
Signed-off-by: harytfw
---
src/config/config.ts | 1 +
src/options/common.ts | 12 +++++--
src/options/feature.svelte | 53 +++++++++++++++++++++++++++
src/options/nav.svelte | 73 ++++++++++++++++++++++++++++++--------
src/options/options.svelte | 3 ++
src/options/store.ts | 1 +
6 files changed, 127 insertions(+), 16 deletions(-)
create mode 100644 src/options/feature.svelte
diff --git a/src/config/config.ts b/src/config/config.ts
index 7bd4a7a..fb7d1ea 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -84,6 +84,7 @@ export type ContextData = {
};
export enum Feature {
+ featureTab = "featureTab",
middleButtonSelector = "middleButtonSelector",
retainComponent = "retainComponent",
auxClose = "auxClose",
diff --git a/src/options/common.ts b/src/options/common.ts
index 4f41775..95889b0 100644
--- a/src/options/common.ts
+++ b/src/options/common.ts
@@ -1,6 +1,5 @@
-import { CommandKind, Direction, OperationMode, TabPosition, ContextType, ContextDataType, CompatibilityStatus } from "../config/config";
+import { CommandKind, Direction, OperationMode, TabPosition, ContextType, ContextDataType, CompatibilityStatus, Feature } from "../config/config";
import { LocaleMessageHelper } from "../locale";
-import { titleCase } from "./utils";
const localeHelper = new LocaleMessageHelper()
@@ -14,6 +13,7 @@ export enum Tab {
common = "common",
configEditor = "configEditor",
compatibility = "compatibility",
+ feature = "feature",
}
@@ -100,4 +100,12 @@ export const compatibilityStatusOptions: Readonly> = Object.values(Feature)
+ .map(s => {
+ return {
+ label: s as string,
+ value: s,
+ }
})
\ No newline at end of file
diff --git a/src/options/feature.svelte b/src/options/feature.svelte
new file mode 100644
index 0000000..b0568b0
--- /dev/null
+++ b/src/options/feature.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
diff --git a/src/options/nav.svelte b/src/options/nav.svelte
index d913f3b..662a4ca 100644
--- a/src/options/nav.svelte
+++ b/src/options/nav.svelte
@@ -1,21 +1,23 @@
-
-
+