From cf377a8e64f5060e46cd5ee2140f6fab8a91ffc5 Mon Sep 17 00:00:00 2001 From: terwer Date: Fri, 3 Mar 2023 00:14:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20#1=20=E4=BB=A3=E7=A0=81=E5=9D=97?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96-=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E8=83=8C=E6=99=AF=E8=87=AA=E5=8A=A8=E9=80=8F=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 + public/lib/vendor/translucify/index.js | 36 +++ public/lib/vendor/translucify/translucify.js | 250 +++++++++++++++++++ script/cjs.py | 2 - script/esm.py | 2 + src/apps/zhi/Lifecycle.ts | 13 +- src/apps/zhi/zhi.ts | 6 +- src/utils/otherlib/loadOtherlib.js | 41 ++- theme.js | 25 +- typings/custom.d.ts | 14 +- 10 files changed, 373 insertions(+), 23 deletions(-) create mode 100644 public/lib/vendor/translucify/index.js create mode 100644 public/lib/vendor/translucify/translucify.js diff --git a/README.md b/README.md index 543b1f5b..61be1596 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - 字体样式美化,以 `落霞孤鹜` 和 `Times New Roman` 为主 - 代码块美化,类似 `Mac` 窗口风格 + - 文档图片背景自动透明 - 整合热门挂件以及其他小工具,提供统一的入口 - 天生支持插件系统,插件系统由社区开发者提供支持 @@ -62,6 +63,12 @@ npm publish --dry-run # 发布到仓库 npm publish ``` +## 挂载的window对象 + +```bash +# translucify +window.translucify($('img')); +``` ## 项目结构 diff --git a/public/lib/vendor/translucify/index.js b/public/lib/vendor/translucify/index.js new file mode 100644 index 00000000..c84eb7e0 --- /dev/null +++ b/public/lib/vendor/translucify/index.js @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, Terwer . All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Terwer designates this + * particular file as subject to the "Classpath" exception as provided + * by Terwer in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com + * or visit www.terwer.space if you need additional information or have any + * questions. + */ + +const initTranslucify = () => { + return [ + "/appearance/themes/zhi/dist-cjs/lib/vendor/translucify/translucify.js", + ] +} + +const translucify = { + initTranslucify, +} + +module.exports = translucify diff --git a/public/lib/vendor/translucify/translucify.js b/public/lib/vendor/translucify/translucify.js new file mode 100644 index 00000000..39ec813e --- /dev/null +++ b/public/lib/vendor/translucify/translucify.js @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2023, Terwer . All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Terwer designates this + * particular file as subject to the "Classpath" exception as provided + * by Terwer in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com + * or visit www.terwer.space if you need additional information or have any + * questions. + */ + +/** + * Translucify + * jim.yang@thisplace.com + * 20/4/2015 + */ + +;(function () { + var DEFAULT_TOLERANCE_VALUE = 0.05 + + // Tolerance threshold for flood fill. + var _toleranceValue = DEFAULT_TOLERANCE_VALUE + + /** + * @param {HTMLImageElement} image + * @returns {boolean} + */ + function isImageLoaded(image) { + if ( + image.nodeType === 1 && + image.tagName.toLowerCase() === "img" && + image.src !== "" + ) { + return ( + image.complete || + image.readyState === 4 || + image.naturalWidth + image.naturalHeight > 0 + ) + } else { + return false + } + } + + /** + * Generates a set of , which is untainted by Cross-Origin image data. + * @param {HTMLImageElement} image + * @returns {{canvas: HTMLCanvasElement, imageCORS: HTMLImageElement}} + */ + function getCanvasAndCORSImage(image) { + /* + Get CORS image without triggering security exceptions + which occur when accessing pixel data even on an image + with response header 'Access-Control-Allow-Origin: *' + */ + var imageCORS = new Image() + imageCORS.crossOrigin = "use-credentials" + + var canvas = document.createElement("canvas") + + var w = image.naturalWidth + var h = image.naturalHeight + + canvas.width = w + canvas.height = h + + return { + canvas: canvas, + imageCORS: imageCORS, + } + } + + /** + * @param {HTMLImageElement} image + */ + function modifyImagePixels(image) { + var created = getCanvasAndCORSImage(image) + created.imageCORS.onload = function () { + applyFloodFill(image, created.imageCORS, created.canvas) + } + created.imageCORS.src = image.src + + // Apply filter immediately if imageCORS is loaded from cache + // and doesn't fire the load event. + if (isImageLoaded(created.imageCORS)) { + applyFloodFill(image, created.imageCORS, created.canvas) + } + } + + /** + * @param {number} x + * @param {number} y + * @param {CanvasRenderingContext2D} context + * @param {number} tolerance + */ + function floodFill(x, y, context, tolerance) { + var pixelStack = [[x, y]] + var width = context.canvas.width + var height = context.canvas.height + var pixelPos = (y * width + x) * 4 + var imageData = context.getImageData(0, 0, width, height) + + var rMax = imageData.data[pixelPos] * (1 + tolerance) + var gMax = imageData.data[pixelPos + 1] * (1 + tolerance) + var bMax = imageData.data[pixelPos + 2] * (1 + tolerance) + + var rMin = imageData.data[pixelPos] * (1 - tolerance) + var gMin = imageData.data[pixelPos + 1] * (1 - tolerance) + var bMin = imageData.data[pixelPos + 2] * (1 - tolerance) + + /** + * Returns true if within tolerance bounds of flood-fill origin. + * @param pixelIndex + * @returns {boolean} + */ + function matchTolerance(pixelIndex) { + var r = imageData.data[pixelIndex] + var g = imageData.data[pixelIndex + 1] + var b = imageData.data[pixelIndex + 2] + + return ( + r >= rMin && + r <= rMax && + g >= gMin && + g <= gMax && + b >= bMin && + b <= bMax + ) + } + + while (pixelStack.length) { + var newPos = pixelStack.pop() + x = newPos[0] + y = newPos[1] + pixelPos = (y * width + x) * 4 + while (y-- >= 0 && matchTolerance(pixelPos)) { + pixelPos -= width * 4 + } + pixelPos += width * 4 + ++y + var reachLeft = false + var reachRight = false + while (y++ < height - 1 && matchTolerance(pixelPos)) { + imageData.data[pixelPos] = 0 + imageData.data[pixelPos + 1] = 0 + imageData.data[pixelPos + 2] = 0 + imageData.data[pixelPos + 3] = 0 + + if (x > 0) { + if (matchTolerance(pixelPos - 4)) { + if (!reachLeft) { + pixelStack.push([x - 1, y]) + reachLeft = true + } + } else if (reachLeft) { + reachLeft = false + } + } + if (x < width - 1) { + if (matchTolerance(pixelPos + 4)) { + if (!reachRight) { + pixelStack.push([x + 1, y]) + reachRight = true + } + } else if (matchTolerance(pixelPos + 4 - width * 4)) { + if (!reachLeft) { + pixelStack.push([x + 1, y - 1]) + reachLeft = true + } + } else if (reachRight) { + reachRight = false + } + } + pixelPos += width * 4 + } + } + context.putImageData(imageData, 0, 0) + } + + /** + * Flood-fill from (0,0). + * @param {HTMLImageElement} originalImage + * @param {HTMLImageElement} imageCORS + * @param {HTMLCanvasElement} canvas + */ + function applyFloodFill(originalImage, imageCORS, canvas) { + var ctx = canvas.getContext("2d") + ctx.drawImage(imageCORS, 0, 0) + floodFill(0, 0, ctx, _toleranceValue) + if (originalImage.parentNode) { + originalImage.parentNode.insertBefore(canvas, originalImage) + originalImage.parentNode.removeChild(originalImage) + } + } + + function translucifyOneFloodFill(image) { + if (isImageLoaded(image)) { + modifyImagePixels(image) + } else { + image.onload = function () { + modifyImagePixels(image) + } + } + } + + /** + * Translucifies one HTMLImageElement or a set of them via NodeList. + * Specifies which modifier to use. + * @param {HTMLImageElement | NodeList | jQuery} queryResult + * @param {number} [tolerance] + */ + function translucifyAll(queryResult, tolerance) { + if (typeof tolerance !== "undefined") { + _toleranceValue = tolerance + } else { + _toleranceValue = DEFAULT_TOLERANCE_VALUE + } + + if (queryResult instanceof HTMLImageElement) { + // passed in directly + translucifyOneFloodFill(queryResult) + } else if (queryResult instanceof NodeList) { + // document.querySelectorAll support + Array.prototype.slice.call(queryResult).forEach(translucifyOneFloodFill) + } else { + // jQuery object support + if (queryResult && queryResult.toArray) { + queryResult.toArray().forEach(translucifyOneFloodFill) + } + } + } + window.translucify = translucifyAll + + setTimeout(function () { + window.translucify(document.querySelectorAll("img")) + }, 1000) +})() diff --git a/script/cjs.py b/script/cjs.py index 2c595495..a0187440 100644 --- a/script/cjs.py +++ b/script/cjs.py @@ -31,8 +31,6 @@ scriptutils.rm_folder("./dist-cjs") os.system("tsc && vite build -c vite.cjs.config.ts --outDir dist-cjs") - scriptutils.rm_folder("./dist-cjs/fonts") - scriptutils.rm_folder("./dist-cjs/lib") scriptutils.rm_file("./dist-cjs/vite.svg") print("cjs构建完成.") diff --git a/script/esm.py b/script/esm.py index 796665b9..b3583c40 100644 --- a/script/esm.py +++ b/script/esm.py @@ -30,4 +30,6 @@ scriptutils.rm_folder("./dist") os.system("tsc && vite build") + scriptutils.rm_folder("./dist/lib") + print("esm构建完成.") \ No newline at end of file diff --git a/src/apps/zhi/Lifecycle.ts b/src/apps/zhi/Lifecycle.ts index cdd4f739..f4957be8 100644 --- a/src/apps/zhi/Lifecycle.ts +++ b/src/apps/zhi/Lifecycle.ts @@ -43,10 +43,12 @@ class Lifecycle { const pluginSystemImports = this.loadPluginSystem() const widgetsImports = this.loadWidgets() + const vendorImports = this.loadVendors() this._dynamicImports = allImports .concat(pluginSystemImports) .concat(widgetsImports) + .concat(vendorImports) } /** @@ -64,7 +66,16 @@ class Lifecycle { * @private */ private loadWidgets(): string[] { - return loadOtherlib.loadPostPublisherScript() + return loadOtherlib.loadWidgetsScript() + } + + /** + * 加载第三方库 + * + * @private + */ + private loadVendors(): string[] { + return loadOtherlib.loadVendorsScript() } } diff --git a/src/apps/zhi/zhi.ts b/src/apps/zhi/zhi.ts index 5875be72..ec47d807 100644 --- a/src/apps/zhi/zhi.ts +++ b/src/apps/zhi/zhi.ts @@ -26,6 +26,7 @@ import strUtil from "~/src/utils/strUtil" import { version } from "~/package.json" import ThemeFromEnum from "~/src/utils/enums/themeFromEnum" +import { Bootstrap } from "~/src/apps/zhi/bootstrap" /** * 主题入口 @@ -36,9 +37,8 @@ import ThemeFromEnum from "~/src/utils/enums/themeFromEnum" class Zhi { public async main(args: string[], callback: Function) { this.hello(ThemeFromEnum.ThemeFrom_Siyuan) - // const dynamicImports = await Bootstrap.start() - // callback(dynamicImports) - callback([]) + const dynamicImports = await Bootstrap.start() + callback(dynamicImports) } public hello(from: string): void { diff --git a/src/utils/otherlib/loadOtherlib.js b/src/utils/otherlib/loadOtherlib.js index 464b2295..c2622a86 100644 --- a/src/utils/otherlib/loadOtherlib.js +++ b/src/utils/otherlib/loadOtherlib.js @@ -50,7 +50,7 @@ export const getSiyuanConfDir = () => { * @since 0.0.1 */ export const getZhiDir = () => { - return `${getSiyuanConfDir()}/appearance/themes/zhi/dist` + return `${getSiyuanConfDir()}/appearance/themes/zhi/dist-cjs` } /** @@ -75,7 +75,6 @@ export const getOtherlibDir = () => { const requireOtherlib = (entryName, libfile, alias) => { const path = window.require("path") const libpath = path.join(getOtherlibDir(), libfile) - console.log(entryName + " 将要从以下位置引入 " + alias, libpath) return window.require(libpath) } @@ -111,9 +110,45 @@ const loadPostPublisherScript = () => { return postPublisher.initPostPublisher() } +/** + * 加载图片透明库 + * + * @author terwer + * @since 0.0.1 + */ +const loadTranslucifyScript = () => { + const translucify = requireOtherlib( + "zhi", + "vendor/translucify/index.js", + "图片透明类库" + ) + return translucify.initTranslucify() +} + +/** + * 加载挂件 + * + * @author terwer + * @since 0.0.1 + */ +const loadWidgetsScript = () => { + return loadPostPublisherScript() +} + +/** + * 加载挂件 + * + * @author terwer + * @since 0.0.1 + */ +const loadVendorsScript = () => { + return loadTranslucifyScript() +} + const loadOtherlib = { loadPluginSystemScript, - loadPostPublisherScript + loadWidgetsScript, + loadVendorsScript, } export default loadOtherlib diff --git a/theme.js b/theme.js index c6c3de46..fb4426c7 100644 --- a/theme.js +++ b/theme.js @@ -57,26 +57,27 @@ const getRealPath = (libpath) => { * @since 0.0.1 */ const safeImport = async (libpath) => { - const fs = window.require("fs") - const realpath = getRealPath(libpath) + const fs = window.require("fs") + const realpath = getRealPath(libpath) - try { - if (!fs.existsSync(realpath)) { - console.warn("依赖库不存在,请排查。依赖库路径=>", realpath) - return - } - await import(libpath) - } catch (e) { - console.error("依赖库加载失败,请排查。依赖库路径=>", realpath) - console.error(e) + try { + if (!fs.existsSync(realpath)) { + console.warn("依赖库不存在,请排查。依赖库路径=>", realpath) + return } + console.log("将要从以下位置引入依赖=>", libpath) + await import(libpath) + } catch (e) { + console.error("依赖库加载失败,请排查。依赖库路径=>", realpath) + console.error(e) } +} ;(async () => { const zhiLibpath = getRealPath("/appearance/themes/zhi/dist-cjs/zhi.js") const zhi = window.require(zhiLibpath) // 主流程加载 - await zhi.main([], async function(dynamicImports) { + await zhi.main([], async function (dynamicImports) { for (const item of dynamicImports) { await safeImport(item) } diff --git a/typings/custom.d.ts b/typings/custom.d.ts index f9700e1e..44b02e99 100644 --- a/typings/custom.d.ts +++ b/typings/custom.d.ts @@ -35,12 +35,22 @@ declare module "~/src/utils/otherlib/loadOtherlib" { export function loadPluginSystemScript(): string[] /** - * 加载发布挂件脚本 + * 加载挂件脚本 * * 此脚本在Electron环境执行,非Electron环境无法使用 * * @author terwer * @since 0.0.1 */ - export function loadPostPublisherScript(): string[] + export function loadWidgetsScript(): string[] + + /** + * 加载第三方库脚本 + * + * 此脚本在Electron环境执行,非Electron环境无法使用 + * + * @author terwer + * @since 0.0.1 + */ + export function loadVendorsScript(): string[] }