diff --git a/electron.vite.config.mts b/electron.vite.config.mts index 5db531e4..cbe9b479 100644 --- a/electron.vite.config.mts +++ b/electron.vite.config.mts @@ -1,4 +1,4 @@ -import { resolve, dirname, join } from 'node:path'; +import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { UserConfig } from 'vite'; diff --git a/package.json b/package.json index 4ec2217c..aa3ef599 100644 --- a/package.json +++ b/package.json @@ -231,7 +231,8 @@ }, "patchedDependencies": { "vudio@2.1.1": "patches/vudio@2.1.1.patch", - "@malept/flatpak-bundler": "patches/@malept__flatpak-bundler.patch" + "@malept/flatpak-bundler@0.4.0": "patches/@malept__flatpak-bundler@0.4.0.patch", + "kuromoji@0.1.2": "patches/kuromoji@0.1.2.patch" }, "neverBuiltDependencies": [] }, @@ -268,9 +269,11 @@ "electron-store": "10.0.1", "electron-unhandled": "4.0.1", "electron-updater": "6.3.9", + "es-hangul": "2.2.4", "fast-average-color": "9.5.0", "fast-equals": "5.2.2", "filenamify": "6.0.0", + "hanja": "1.1.4", "happy-dom": "17.4.4", "hono": "4.7.5", "howler": "2.2.4", @@ -279,9 +282,15 @@ "jimp": "1.6.0", "keyboardevent-from-electron-accelerator": "2.0.0", "keyboardevents-areequal": "0.2.2", + "kuromoji": "0.1.2", + "kuroshiro": "1.2.0", + "kuroshiro-analyzer-kuromoji": "1.1.0", + "lazy-var": "2.2.2", "node-html-parser": "7.0.1", "node-id3": "0.2.8", "peerjs": "1.5.4", + "pinyin": "4.0.0-alpha.2", + "segmentit": "2.0.3", "semver": "7.7.1", "serve": "14.2.4", "simple-youtube-age-restriction-bypass": "github:organization/Simple-YouTube-Age-Restriction-Bypass#v2.5.9", @@ -297,6 +306,7 @@ }, "devDependencies": { "@eslint/js": "9.22.0", + "@malept/flatpak-bundler": "0.4.0", "@playwright/test": "1.51.1", "@stylistic/eslint-plugin-js": "4.2.0", "@total-typescript/ts-reset": "0.6.1", diff --git a/patches/@malept__flatpak-bundler.patch b/patches/@malept__flatpak-bundler@0.4.0.patch similarity index 100% rename from patches/@malept__flatpak-bundler.patch rename to patches/@malept__flatpak-bundler@0.4.0.patch diff --git a/patches/eslint-plugin-import@2.29.1.patch b/patches/eslint-plugin-import@2.29.1.patch deleted file mode 100644 index 512d6597..00000000 --- a/patches/eslint-plugin-import@2.29.1.patch +++ /dev/null @@ -1,161 +0,0 @@ -diff --git a/lib/importDeclaration.js b/lib/importDeclaration.js -index afb4de779034cfea080825a5f4320661c48bee32..f10b0a11a39577fbd42569e6b0e768255c1ef276 100644 ---- a/lib/importDeclaration.js -+++ b/lib/importDeclaration.js -@@ -1,5 +1,5 @@ --"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports["default"] = importDeclaration;function importDeclaration(context) { -- var ancestors = context.getAncestors(); -+"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports["default"] = importDeclaration;function importDeclaration(context, node) { -+ var ancestors = context.getSourceCode().getAncestors(node); - return ancestors[ancestors.length - 1]; - } - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9pbXBvcnREZWNsYXJhdGlvbi5qcyJdLCJuYW1lcyI6WyJpbXBvcnREZWNsYXJhdGlvbiIsImNvbnRleHQiLCJhbmNlc3RvcnMiLCJnZXRBbmNlc3RvcnMiLCJsZW5ndGgiXSwibWFwcGluZ3MiOiJnR0FBd0JBLGlCLENBQVQsU0FBU0EsaUJBQVQsQ0FBMkJDLE9BQTNCLEVBQW9DO0FBQ2pELE1BQU1DLFlBQVlELFFBQVFFLFlBQVIsRUFBbEI7QUFDQSxTQUFPRCxVQUFVQSxVQUFVRSxNQUFWLEdBQW1CLENBQTdCLENBQVA7QUFDRCIsImZpbGUiOiJpbXBvcnREZWNsYXJhdGlvbi5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGltcG9ydERlY2xhcmF0aW9uKGNvbnRleHQpIHtcbiAgY29uc3QgYW5jZXN0b3JzID0gY29udGV4dC5nZXRBbmNlc3RvcnMoKTtcbiAgcmV0dXJuIGFuY2VzdG9yc1thbmNlc3RvcnMubGVuZ3RoIC0gMV07XG59XG4iXX0= -\ No newline at end of file -diff --git a/lib/rules/first.js b/lib/rules/first.js -index a77168660cf32c8c3e96f3ff4b8240a36d7de3a6..c0e00d75f9989916057fef3999eeee8d21820292 100644 ---- a/lib/rules/first.js -+++ b/lib/rules/first.js -@@ -66,7 +66,7 @@ module.exports = { - } - } - if (nonImportCount > 0) {var _iteratorNormalCompletion = true;var _didIteratorError = false;var _iteratorError = undefined;try { -- for (var _iterator = context.getDeclaredVariables(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var variable = _step.value; -+ for (var _iterator = sourceCode.getDeclaredVariables(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {var variable = _step.value; - if (!shouldSort) {break;} - var references = variable.references; - if (references.length) {var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try { -diff --git a/lib/rules/namespace.js b/lib/rules/namespace.js -index 574d89a60d15c7e0e712956ea6a3ad2d0eac7f08..82e7cb3cff4246592d762cce86323f2b72de92e4 100644 ---- a/lib/rules/namespace.js -+++ b/lib/rules/namespace.js -@@ -86,7 +86,7 @@ module.exports = { - - // same as above, but does not add names to local map - ExportNamespaceSpecifier: function () {function ExportNamespaceSpecifier(namespace) { -- var declaration = (0, _importDeclaration2['default'])(context); -+ var declaration = (0, _importDeclaration2['default'])(context, namespace); - - var imports = _ExportMap2['default'].get(declaration.source.value, context); - if (imports == null) {return null;} -diff --git a/lib/rules/newline-after-import.js b/lib/rules/newline-after-import.js -index 6cc15686464a17803a0b976c35b99627cdbfabee..520eec6d9a375527ab72c459960fe4416c046c17 100644 ---- a/lib/rules/newline-after-import.js -+++ b/lib/rules/newline-after-import.js -@@ -194,7 +194,7 @@ module.exports = { - }return CallExpression;}(), - 'Program:exit': function () {function ProgramExit() { - log('exit processing for', context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename()); -- var scopeBody = getScopeBody(context.getScope()); -+ var scopeBody = getScopeBody(context.getSourceCode().getScope(node)); - log('got scope:', scopeBody); - - requireCalls.forEach(function (node, index) { -diff --git a/lib/rules/no-amd.js b/lib/rules/no-amd.js -index 7ac108bf812ca4f78bfa6fe5ae8b9cf38e2ff497..346c3105dc70f72c4d76fcc6b96b946d1d4ec6d5 100644 ---- a/lib/rules/no-amd.js -+++ b/lib/rules/no-amd.js -@@ -23,7 +23,7 @@ module.exports = { - create: function () {function create(context) { - return { - CallExpression: function () {function CallExpression(node) { -- if (context.getScope().type !== 'module') {return;} -+ if (context.getSourceCode().getScope(node).type !== 'module') {return;} - - if (node.callee.type !== 'Identifier') {return;} - if (node.callee.name !== 'require' && node.callee.name !== 'define') {return;} -diff --git a/lib/rules/no-commonjs.js b/lib/rules/no-commonjs.js -index befeff0026d61d3ac1e6bbcea29f5c471dc1d353..e91c5ed34e968d5867e884ea800e166cda345aef 100644 ---- a/lib/rules/no-commonjs.js -+++ b/lib/rules/no-commonjs.js -@@ -107,7 +107,7 @@ module.exports = { - - // exports. - if (node.object.name === 'exports') { -- var isInScope = context.getScope(). -+ var isInScope = context.getSourceCode().getScope(node). - variables. - some(function (variable) {return variable.name === 'exports';}); - if (!isInScope) { -@@ -117,7 +117,7 @@ module.exports = { - - }return MemberExpression;}(), - CallExpression: function () {function CallExpression(call) { -- if (!validateScope(context.getScope())) {return;} -+ if (!validateScope(context.getSourceCode().getScope(call))) {return;} - - if (call.callee.type !== 'Identifier') {return;} - if (call.callee.name !== 'require') {return;} -diff --git a/lib/rules/no-mutable-exports.js b/lib/rules/no-mutable-exports.js -index 40bd1b4cfa95d41732bb13bba0ed1969a91cc7ff..8a25abfbfadb299204b36a6cbf283259bcc2e790 100644 ---- a/lib/rules/no-mutable-exports.js -+++ b/lib/rules/no-mutable-exports.js -@@ -32,7 +32,7 @@ module.exports = { - } - - function handleExportDefault(node) { -- var scope = context.getScope(); -+ var scope = context.getSourceCode().getScope(node); - - if (node.declaration.name) { - checkDeclarationsInScope(scope, node.declaration.name); -@@ -40,7 +40,7 @@ module.exports = { - } - - function handleExportNamed(node) { -- var scope = context.getScope(); -+ var scope = context.getSourceCode().getScope(node); - - if (node.declaration) { - checkDeclaration(node.declaration); -diff --git a/lib/rules/no-named-as-default-member.js b/lib/rules/no-named-as-default-member.js -index 0c15051e027ad7d1d45f1b51c20be1c000b0af01..5b3d6ba415511b7f9f83a52e1acfebe5a1045a7b 100644 ---- a/lib/rules/no-named-as-default-member.js -+++ b/lib/rules/no-named-as-default-member.js -@@ -35,7 +35,7 @@ module.exports = { - - return { - ImportDefaultSpecifier: function () {function ImportDefaultSpecifier(node) { -- var declaration = (0, _importDeclaration2['default'])(context); -+ var declaration = (0, _importDeclaration2['default'])(context, node); - var exportMap = _ExportMap2['default'].get(declaration.source.value, context); - if (exportMap == null) {return;} - -diff --git a/lib/rules/no-named-as-default.js b/lib/rules/no-named-as-default.js -index 63378a33a1c7da004c57a524cec1a1cddf23e210..c81b1f93b11628676158b79f1c4015911943cc7d 100644 ---- a/lib/rules/no-named-as-default.js -+++ b/lib/rules/no-named-as-default.js -@@ -18,7 +18,7 @@ module.exports = { - // #566: default is a valid specifier - if (defaultSpecifier[nameKey].name === 'default') {return;} - -- var declaration = (0, _importDeclaration2['default'])(context); -+ var declaration = (0, _importDeclaration2['default'])(context, defaultSpecifier); - - var imports = _ExportMap2['default'].get(declaration.source.value, context); - if (imports == null) {return;} -diff --git a/lib/rules/no-namespace.js b/lib/rules/no-namespace.js -index 2b0c783adea788101b779b17f977bbcb582cfd3f..a7f7b202ac7c4a342febef2a993586c4cc84fc7a 100644 ---- a/lib/rules/no-namespace.js -+++ b/lib/rules/no-namespace.js -@@ -43,7 +43,7 @@ var _docsUrl = require('../docsUrl');var _docsUrl2 = _interopRequireDefault(_doc - return; - } - -- var scopeVariables = context.getScope().variables; -+ var scopeVariables = context.getSourceCode().getScope(node).variables; - var namespaceVariable = scopeVariables.find(function (variable) {return variable.defs[0].node === node;}); - var namespaceReferences = namespaceVariable.references; - var namespaceIdentifiers = namespaceReferences.map(function (reference) {return reference.identifier;}); -diff --git a/package.json b/package.json -index 5c0af48543483a21791fa23a4a583071d3551772..5deeac3d0accc3878ef0fc93dfb52a8ca7c46e84 100644 ---- a/package.json -+++ b/package.json -@@ -72,7 +72,7 @@ - "chai": "^4.3.10", - "cross-env": "^4.0.0", - "escope": "^3.6.0", -- "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", -+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", - "eslint-doc-generator": "^1.6.1", - "eslint-import-resolver-node": "file:./resolvers/node", - "eslint-import-resolver-typescript": "^1.0.2 || ^1.1.1", diff --git a/patches/kuromoji@0.1.2.patch b/patches/kuromoji@0.1.2.patch new file mode 100644 index 00000000..91b4af37 --- /dev/null +++ b/patches/kuromoji@0.1.2.patch @@ -0,0 +1,580 @@ +diff --git a/build/kuromoji.js b/build/kuromoji.js +index f0f4ae9183ff8965fda64a2042f29936f76506d1..8912a754d184742d2768854c7bba83d66f9ff95f 100644 +--- a/build/kuromoji.js ++++ b/build/kuromoji.js +@@ -1,5 +1,5 @@ +-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.kuromoji = f()}})(function(){var define,module,exports;return (function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1 && value % 1 == 0 && value < length); ++ (type == 'number' || ++ (type != 'symbol' && reIsUint.test(value))) && ++ (value > -1 && value % 1 == 0 && value < length); + } + + /** `Object#toString` result references. */ +@@ -755,6 +758,14 @@ var freeProcess = moduleExports$1 && freeGlobal.process; + /** Used to access faster Node.js helpers. */ + var nodeUtil = (function() { + try { ++ // Use `util.types` for Node.js 10+. ++ var types = freeModule$1 && freeModule$1.require && freeModule$1.require('util').types; ++ ++ if (types) { ++ return types; ++ } ++ ++ // Legacy `process.binding('util')` for Node.js < 10. + return freeProcess && freeProcess.binding && freeProcess.binding('util'); + } catch (e) {} + }()); +@@ -939,6 +950,9 @@ function createObjectIterator(obj) { + var len = okeys.length; + return function next() { + var key = okeys[++i]; ++ if (key === '__proto__') { ++ return next(); ++ } + return i < len ? {value: obj[key], key: key} : null; + }; + } +@@ -970,6 +984,7 @@ function _eachOfLimit(limit) { + var nextElem = iterator(obj); + var done = false; + var running = 0; ++ var looping = false; + + function iterateeCallback(err, value) { + running -= 1; +@@ -981,12 +996,13 @@ function _eachOfLimit(limit) { + done = true; + return callback(null); + } +- else { ++ else if (!looping) { + replenish(); + } + } + + function replenish () { ++ looping = true; + while (running < limit && !done) { + var elem = nextElem(); + if (elem === null) { +@@ -999,6 +1015,7 @@ function _eachOfLimit(limit) { + running += 1; + iteratee(elem.value, elem.key, onlyOnce(iterateeCallback)); + } ++ looping = false; + } + + replenish(); +@@ -3819,7 +3836,7 @@ function memoize(fn, hasher) { + + /** + * Calls `callback` on a later loop around the event loop. In Node.js this just +- * calls `process.nextTicl`. In the browser it will use `setImmediate` if ++ * calls `process.nextTick`. In the browser it will use `setImmediate` if + * available, otherwise `setTimeout(callback, 0)`, which means other higher + * priority events may precede the execution of `callback`. + * +@@ -5596,8 +5613,8 @@ Object.defineProperty(exports, '__esModule', { value: true }); + + }))); + +-}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +-},{"_process":4}],2:[function(require,module,exports){ ++}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate) ++},{"_process":3,"timers":4}],2:[function(require,module,exports){ + // Copyright (c) 2014 Takuya Asano All Rights Reserved. + + (function () { +@@ -6391,234 +6408,6 @@ Object.defineProperty(exports, '__esModule', { value: true }); + })(); + + },{}],3:[function(require,module,exports){ +-(function (process){ +-// Copyright Joyent, Inc. and other Node contributors. +-// +-// Permission is hereby granted, free of charge, to any person obtaining a +-// copy of this software and associated documentation files (the +-// "Software"), to deal in the Software without restriction, including +-// without limitation the rights to use, copy, modify, merge, publish, +-// distribute, sublicense, and/or sell copies of the Software, and to permit +-// persons to whom the Software is furnished to do so, subject to the +-// following conditions: +-// +-// The above copyright notice and this permission notice shall be included +-// in all copies or substantial portions of the Software. +-// +-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +-// USE OR OTHER DEALINGS IN THE SOFTWARE. +- +-// resolves . and .. elements in a path array with directory names there +-// must be no slashes, empty elements, or device names (c:\) in the array +-// (so also no leading and trailing slashes - it does not distinguish +-// relative and absolute paths) +-function normalizeArray(parts, allowAboveRoot) { +- // if the path tries to go above the root, `up` ends up > 0 +- var up = 0; +- for (var i = parts.length - 1; i >= 0; i--) { +- var last = parts[i]; +- if (last === '.') { +- parts.splice(i, 1); +- } else if (last === '..') { +- parts.splice(i, 1); +- up++; +- } else if (up) { +- parts.splice(i, 1); +- up--; +- } +- } +- +- // if the path is allowed to go above the root, restore leading ..s +- if (allowAboveRoot) { +- for (; up--; up) { +- parts.unshift('..'); +- } +- } +- +- return parts; +-} +- +-// Split a filename into [root, dir, basename, ext], unix version +-// 'root' is just a slash, or nothing. +-var splitPathRe = +- /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +-var splitPath = function(filename) { +- return splitPathRe.exec(filename).slice(1); +-}; +- +-// path.resolve([from ...], to) +-// posix version +-exports.resolve = function() { +- var resolvedPath = '', +- resolvedAbsolute = false; +- +- for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { +- var path = (i >= 0) ? arguments[i] : process.cwd(); +- +- // Skip empty and invalid entries +- if (typeof path !== 'string') { +- throw new TypeError('Arguments to path.resolve must be strings'); +- } else if (!path) { +- continue; +- } +- +- resolvedPath = path + '/' + resolvedPath; +- resolvedAbsolute = path.charAt(0) === '/'; +- } +- +- // At this point the path should be resolved to a full absolute path, but +- // handle relative paths to be safe (might happen when process.cwd() fails) +- +- // Normalize the path +- resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { +- return !!p; +- }), !resolvedAbsolute).join('/'); +- +- return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +-}; +- +-// path.normalize(path) +-// posix version +-exports.normalize = function(path) { +- var isAbsolute = exports.isAbsolute(path), +- trailingSlash = substr(path, -1) === '/'; +- +- // Normalize the path +- path = normalizeArray(filter(path.split('/'), function(p) { +- return !!p; +- }), !isAbsolute).join('/'); +- +- if (!path && !isAbsolute) { +- path = '.'; +- } +- if (path && trailingSlash) { +- path += '/'; +- } +- +- return (isAbsolute ? '/' : '') + path; +-}; +- +-// posix version +-exports.isAbsolute = function(path) { +- return path.charAt(0) === '/'; +-}; +- +-// posix version +-exports.join = function() { +- var paths = Array.prototype.slice.call(arguments, 0); +- return exports.normalize(filter(paths, function(p, index) { +- if (typeof p !== 'string') { +- throw new TypeError('Arguments to path.join must be strings'); +- } +- return p; +- }).join('/')); +-}; +- +- +-// path.relative(from, to) +-// posix version +-exports.relative = function(from, to) { +- from = exports.resolve(from).substr(1); +- to = exports.resolve(to).substr(1); +- +- function trim(arr) { +- var start = 0; +- for (; start < arr.length; start++) { +- if (arr[start] !== '') break; +- } +- +- var end = arr.length - 1; +- for (; end >= 0; end--) { +- if (arr[end] !== '') break; +- } +- +- if (start > end) return []; +- return arr.slice(start, end - start + 1); +- } +- +- var fromParts = trim(from.split('/')); +- var toParts = trim(to.split('/')); +- +- var length = Math.min(fromParts.length, toParts.length); +- var samePartsLength = length; +- for (var i = 0; i < length; i++) { +- if (fromParts[i] !== toParts[i]) { +- samePartsLength = i; +- break; +- } +- } +- +- var outputParts = []; +- for (var i = samePartsLength; i < fromParts.length; i++) { +- outputParts.push('..'); +- } +- +- outputParts = outputParts.concat(toParts.slice(samePartsLength)); +- +- return outputParts.join('/'); +-}; +- +-exports.sep = '/'; +-exports.delimiter = ':'; +- +-exports.dirname = function(path) { +- var result = splitPath(path), +- root = result[0], +- dir = result[1]; +- +- if (!root && !dir) { +- // No dirname whatsoever +- return '.'; +- } +- +- if (dir) { +- // It has a dirname, strip trailing slash +- dir = dir.substr(0, dir.length - 1); +- } +- +- return root + dir; +-}; +- +- +-exports.basename = function(path, ext) { +- var f = splitPath(path)[2]; +- // TODO: make this comparison case-insensitive on windows? +- if (ext && f.substr(-1 * ext.length) === ext) { +- f = f.substr(0, f.length - ext.length); +- } +- return f; +-}; +- +- +-exports.extname = function(path) { +- return splitPath(path)[3]; +-}; +- +-function filter (xs, f) { +- if (xs.filter) return xs.filter(f); +- var res = []; +- for (var i = 0; i < xs.length; i++) { +- if (f(xs[i], i, xs)) res.push(xs[i]); +- } +- return res; +-} +- +-// String.prototype.substr - negative index don't work in IE8 +-var substr = 'ab'.substr(-1) === 'b' +- ? function (str, start, len) { return str.substr(start, len) } +- : function (str, start, len) { +- if (start < 0) start = str.length + start; +- return str.substr(start, len); +- } +-; +- +-}).call(this,require('_process')) +-},{"_process":4}],4:[function(require,module,exports){ + // shim for using process in browser + var process = module.exports = {}; + +@@ -6804,7 +6593,86 @@ process.chdir = function (dir) { + }; + process.umask = function() { return 0; }; + +-},{}],5:[function(require,module,exports){ ++},{}],4:[function(require,module,exports){ ++(function (setImmediate,clearImmediate){(function (){ ++var nextTick = require('process/browser.js').nextTick; ++var apply = Function.prototype.apply; ++var slice = Array.prototype.slice; ++var immediateIds = {}; ++var nextImmediateId = 0; ++ ++// DOM APIs, for completeness ++ ++exports.setTimeout = function() { ++ return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); ++}; ++exports.setInterval = function() { ++ return new Timeout(apply.call(setInterval, window, arguments), clearInterval); ++}; ++exports.clearTimeout = ++exports.clearInterval = function(timeout) { timeout.close(); }; ++ ++function Timeout(id, clearFn) { ++ this._id = id; ++ this._clearFn = clearFn; ++} ++Timeout.prototype.unref = Timeout.prototype.ref = function() {}; ++Timeout.prototype.close = function() { ++ this._clearFn.call(window, this._id); ++}; ++ ++// Does not start the time, just sets up the members needed. ++exports.enroll = function(item, msecs) { ++ clearTimeout(item._idleTimeoutId); ++ item._idleTimeout = msecs; ++}; ++ ++exports.unenroll = function(item) { ++ clearTimeout(item._idleTimeoutId); ++ item._idleTimeout = -1; ++}; ++ ++exports._unrefActive = exports.active = function(item) { ++ clearTimeout(item._idleTimeoutId); ++ ++ var msecs = item._idleTimeout; ++ if (msecs >= 0) { ++ item._idleTimeoutId = setTimeout(function onTimeout() { ++ if (item._onTimeout) ++ item._onTimeout(); ++ }, msecs); ++ } ++}; ++ ++// That's not how node.js implements it but the exposed api is the same. ++exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { ++ var id = nextImmediateId++; ++ var args = arguments.length < 2 ? false : slice.call(arguments, 1); ++ ++ immediateIds[id] = true; ++ ++ nextTick(function onNextTick() { ++ if (immediateIds[id]) { ++ // fn.call() is faster so we optimize for the common use-case ++ // @see http://jsperf.com/call-apply-segu ++ if (args) { ++ fn.apply(null, args); ++ } else { ++ fn.call(null); ++ } ++ // Prevent ids from leaking ++ exports.clearImmediate(id); ++ } ++ }); ++ ++ return id; ++}; ++ ++exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { ++ delete immediateIds[id]; ++}; ++}).call(this)}).call(this,require("timers").setImmediate,require("timers").clearImmediate) ++},{"process/browser.js":3,"timers":4}],5:[function(require,module,exports){ + /** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function n(e){throw e;}var p=void 0,aa=this;function t(e,b){var d=e.split("."),c=aa;!(d[0]in c)&&c.execScript&&c.execScript("var "+d[0]);for(var a;d.length&&(a=d.shift());)!d.length&&b!==p?c[a]=b:c=c[a]?c[a]:c[a]={}};var x="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;new (x?Uint8Array:Array)(256);var y;for(y=0;256>y;++y)for(var A=y,ba=7,A=A>>>1;A;A>>>=1)--ba;function B(e,b,d){var c,a="number"===typeof b?b:b=0,f="number"===typeof d?d:e.length;c=-1;for(a=f&7;a--;++b)c=c>>>8^C[(c^e[b])&255];for(a=f>>3;a--;b+=8)c=c>>>8^C[(c^e[b])&255],c=c>>>8^C[(c^e[b+1])&255],c=c>>>8^C[(c^e[b+2])&255],c=c>>>8^C[(c^e[b+3])&255],c=c>>>8^C[(c^e[b+4])&255],c=c>>>8^C[(c^e[b+5])&255],c=c>>>8^C[(c^e[b+6])&255],c=c>>>8^C[(c^e[b+7])&255];return(c^4294967295)>>>0} + var D=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759, + 2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977, +@@ -6984,7 +6852,7 @@ module.exports = Tokenizer; + "use strict"; + + var Tokenizer = require("./Tokenizer"); +-var DictionaryLoader = require("./loader/NodeDictionaryLoader"); ++var BrowserDictionaryLoader = require("./loader/BrowserDictionaryLoader"); + + /** + * TokenizerBuilder create Tokenizer instance. +@@ -7005,7 +6873,7 @@ function TokenizerBuilder(option) { + * @param {TokenizerBuilder~onLoad} callback Callback function + */ + TokenizerBuilder.prototype.build = function (callback) { +- var loader = new DictionaryLoader(this.dic_path); ++ var loader = new BrowserDictionaryLoader(this.dic_path); + loader.load(function (err, dic) { + callback(err, new Tokenizer(dic)); + }); +@@ -7020,7 +6888,7 @@ TokenizerBuilder.prototype.build = function (callback) { + + module.exports = TokenizerBuilder; + +-},{"./Tokenizer":6,"./loader/NodeDictionaryLoader":19}],8:[function(require,module,exports){ ++},{"./Tokenizer":6,"./loader/BrowserDictionaryLoader":19}],8:[function(require,module,exports){ + /* + * Copyright 2014 Takuya Asano + * Copyright 2010-2014 Atilika Inc. and contributors +@@ -8163,7 +8031,6 @@ module.exports = BrowserDictionaryLoader; + + "use strict"; + +-var path = require("path"); + var async = require("async"); + var DynamicDictionaries = require("../dict/DynamicDictionaries"); + +@@ -8194,7 +8061,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + // Trie + function (callback) { + async.map([ "base.dat.gz", "check.dat.gz" ], function (filename, _callback) { +- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) { ++ loadArrayBuffer(dic_path + filename, function (err, buffer) { + if(err) { + return _callback(err); + } +@@ -8214,7 +8081,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + // Token info dictionaries + function (callback) { + async.map([ "tid.dat.gz", "tid_pos.dat.gz", "tid_map.dat.gz" ], function (filename, _callback) { +- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) { ++ loadArrayBuffer(dic_path + filename, function (err, buffer) { + if(err) { + return _callback(err); + } +@@ -8234,7 +8101,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + }, + // Connection cost matrix + function (callback) { +- loadArrayBuffer(path.join(dic_path, "cc.dat.gz"), function (err, buffer) { ++ loadArrayBuffer(dic_path + "cc.dat.gz", function (err, buffer) { + if(err) { + return callback(err); + } +@@ -8246,7 +8113,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + // Unknown dictionaries + function (callback) { + async.map([ "unk.dat.gz", "unk_pos.dat.gz", "unk_map.dat.gz", "unk_char.dat.gz", "unk_compat.dat.gz", "unk_invoke.dat.gz" ], function (filename, _callback) { +- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) { ++ loadArrayBuffer(dic_path + filename, function (err, buffer) { + if(err) { + return _callback(err); + } +@@ -8282,7 +8149,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + + module.exports = DictionaryLoader; + +-},{"../dict/DynamicDictionaries":11,"async":1,"path":3}],21:[function(require,module,exports){ ++},{"../dict/DynamicDictionaries":11,"async":1}],21:[function(require,module,exports){ + /* + * Copyright 2014 Takuya Asano + * Copyright 2010-2014 Atilika Inc. and contributors +diff --git a/src/TokenizerBuilder.js b/src/TokenizerBuilder.js +index 9ef5c6a2efc63e8b12735a8a9f1cb08d6c52c20c..98881e9fd731047c3fca848a71ede7e381e74f51 100644 +--- a/src/TokenizerBuilder.js ++++ b/src/TokenizerBuilder.js +@@ -18,7 +18,7 @@ + "use strict"; + + var Tokenizer = require("./Tokenizer"); +-var DictionaryLoader = require("./loader/NodeDictionaryLoader"); ++var BrowserDictionaryLoader = require("./loader/BrowserDictionaryLoader"); + + /** + * TokenizerBuilder create Tokenizer instance. +@@ -39,7 +39,7 @@ function TokenizerBuilder(option) { + * @param {TokenizerBuilder~onLoad} callback Callback function + */ + TokenizerBuilder.prototype.build = function (callback) { +- var loader = new DictionaryLoader(this.dic_path); ++ var loader = new BrowserDictionaryLoader(this.dic_path); + loader.load(function (err, dic) { + callback(err, new Tokenizer(dic)); + }); +diff --git a/src/loader/DictionaryLoader.js b/src/loader/DictionaryLoader.js +index 5f88c0b7f9a786dd8c072a7b84ae86a6f31412cb..3d6f8a67e16d251b3e4ba4dbbbc947679c364382 100644 +--- a/src/loader/DictionaryLoader.js ++++ b/src/loader/DictionaryLoader.js +@@ -17,7 +17,6 @@ + + "use strict"; + +-var path = require("path"); + var async = require("async"); + var DynamicDictionaries = require("../dict/DynamicDictionaries"); + +@@ -48,7 +47,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + // Trie + function (callback) { + async.map([ "base.dat.gz", "check.dat.gz" ], function (filename, _callback) { +- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) { ++ loadArrayBuffer(dic_path + filename, function (err, buffer) { + if(err) { + return _callback(err); + } +@@ -68,7 +67,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + // Token info dictionaries + function (callback) { + async.map([ "tid.dat.gz", "tid_pos.dat.gz", "tid_map.dat.gz" ], function (filename, _callback) { +- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) { ++ loadArrayBuffer(dic_path + filename, function (err, buffer) { + if(err) { + return _callback(err); + } +@@ -88,7 +87,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + }, + // Connection cost matrix + function (callback) { +- loadArrayBuffer(path.join(dic_path, "cc.dat.gz"), function (err, buffer) { ++ loadArrayBuffer(dic_path + "cc.dat.gz", function (err, buffer) { + if(err) { + return callback(err); + } +@@ -100,7 +99,7 @@ DictionaryLoader.prototype.load = function (load_callback) { + // Unknown dictionaries + function (callback) { + async.map([ "unk.dat.gz", "unk_pos.dat.gz", "unk_map.dat.gz", "unk_char.dat.gz", "unk_compat.dat.gz", "unk_invoke.dat.gz" ], function (filename, _callback) { +- loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) { ++ loadArrayBuffer(dic_path + filename, function (err, buffer) { + if(err) { + return _callback(err); + } +diff --git a/src/loader/NodeDictionaryLoader.js b/src/loader/NodeDictionaryLoader.js +deleted file mode 100644 +index 26eb79249121efe39bd5ae77c17e1caa197fb4ce..0000000000000000000000000000000000000000 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82f81d1d..19fd8afd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,12 @@ overrides: '@babel/runtime': 7.26.10 patchedDependencies: - '@malept/flatpak-bundler': + '@malept/flatpak-bundler@0.4.0': hash: c787371eeb2af011ea934e8818a0dad6d7dcb2df31bbb1686babc7231af0183c - path: patches/@malept__flatpak-bundler.patch + path: patches/@malept__flatpak-bundler@0.4.0.patch + kuromoji@0.1.2: + hash: e4a08f477026373a427a51d9bedd268ee22a173850ede3750de32af5d241c28c + path: patches/kuromoji@0.1.2.patch vudio@2.1.1: hash: 0e06c2ed11c02bdc490c209fa80070e98517c2735c641f5738b6e15d7dc1959d path: patches/vudio@2.1.1.patch @@ -120,6 +123,9 @@ importers: electron-updater: specifier: 6.3.9 version: 6.3.9 + es-hangul: + specifier: 2.2.4 + version: 2.2.4 fast-average-color: specifier: 9.5.0 version: 9.5.0 @@ -129,6 +135,9 @@ importers: filenamify: specifier: 6.0.0 version: 6.0.0 + hanja: + specifier: 1.1.4 + version: 1.1.4 happy-dom: specifier: 17.4.4 version: 17.4.4 @@ -153,6 +162,18 @@ importers: keyboardevents-areequal: specifier: 0.2.2 version: 0.2.2 + kuromoji: + specifier: 0.1.2 + version: 0.1.2(patch_hash=e4a08f477026373a427a51d9bedd268ee22a173850ede3750de32af5d241c28c) + kuroshiro: + specifier: 1.2.0 + version: 1.2.0 + kuroshiro-analyzer-kuromoji: + specifier: 1.1.0 + version: 1.1.0 + lazy-var: + specifier: 2.2.2 + version: 2.2.2 node-html-parser: specifier: 7.0.1 version: 7.0.1 @@ -162,6 +183,12 @@ importers: peerjs: specifier: 1.5.4 version: 1.5.4 + pinyin: + specifier: 4.0.0-alpha.2 + version: 4.0.0-alpha.2(segmentit@2.0.3) + segmentit: + specifier: 2.0.3 + version: 2.0.3 semver: specifier: 7.7.1 version: 7.7.1 @@ -202,6 +229,9 @@ importers: '@eslint/js': specifier: 9.22.0 version: 9.22.0 + '@malept/flatpak-bundler': + specifier: 0.4.0 + version: 0.4.0(patch_hash=c787371eeb2af011ea934e8818a0dad6d7dcb2df31bbb1686babc7231af0183c) '@playwright/test': specifier: 1.51.1 version: 1.51.1 @@ -1383,6 +1413,9 @@ packages: '@types/node@22.13.5': resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==} + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} @@ -1669,6 +1702,9 @@ packages: async-mutex@0.5.0: resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} + async@2.6.4: + resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -1695,6 +1731,13 @@ packages: peerDependencies: '@babel/core': ^7.20.12 + babel-plugin-macros@2.8.0: + resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} + + babel-plugin-preval@4.0.0: + resolution: {integrity: sha512-fZI/4cYneinlj2k/FsXw0/lTWSC5KKoepUueS1g25Gb5vx3GrRyaVwxWCshYqx11GEU4mZnbbFhee8vpquFS2w==} + engines: {node: '>=8', npm: '>=6'} + babel-preset-solid@1.9.5: resolution: {integrity: sha512-85I3osODJ1LvZbv8wFozROV1vXq32BubqHXAGu73A//TRs3NLI1OFP83AQBUTSQHwgZQmARjHlJciym3we+V+w==} peerDependencies: @@ -1927,6 +1970,10 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@1.1.1: + resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==} + engines: {node: '>= 0.6.x'} + commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} @@ -1970,6 +2017,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cosmiconfig@6.0.0: + resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} + engines: {node: '>=8'} + crc@3.8.0: resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} @@ -2159,6 +2210,9 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + doublearray@0.0.2: + resolution: {integrity: sha512-aw55FtZzT6AmiamEj2kvmR6BuFqvYgKZUkfQ7teqVRNqD5UE0rw8IeW/3gieHNKQ5sPuDKlljWEn4bzv5+1bHw==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2281,6 +2335,9 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} @@ -2299,6 +2356,9 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + es-hangul@2.2.4: + resolution: {integrity: sha512-P0zZh+dADhefLo/KEYzTs2d29H+w99c9X6aUeVIoMxTBE1Z8H4/a4e4ZN3u8rmRD+3uOEhNfWXH/NX5dF4aixw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -2738,6 +2798,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + hanja@1.1.4: + resolution: {integrity: sha512-O8K0+9jyibUlmLd/WAXCQ+8XGersUm9PloQuEXFPRfiUztFHRz4WaELXij0iejtl8AN85FxWQYSNmAuoXLircA==} + happy-dom@17.4.4: resolution: {integrity: sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==} engines: {node: '>=18.0.0'} @@ -2903,6 +2966,9 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -3127,6 +3193,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -3166,12 +3235,28 @@ packages: keyboardevents-areequal@0.2.2: resolution: {integrity: sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==} + keypress@0.1.0: + resolution: {integrity: sha512-x0yf9PL/nx9Nw9oLL8ZVErFAk85/lslwEP7Vz7s5SI1ODXZIgit3C5qyWjw4DxOuO/3Hb4866SQh28a1V1d+WA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kuromoji@0.1.2: + resolution: {integrity: sha512-V0dUf+C2LpcPEXhoHLMAop/bOht16Dyr+mDiIE39yX3vqau7p80De/koFqpiTcL1zzdZlc3xuHZ8u5gjYRfFaQ==} + + kuroshiro-analyzer-kuromoji@1.1.0: + resolution: {integrity: sha512-BSJFhpsQdPwfFLfjKxfLA9iL+/PC6LCR9vgwgb5Jc7jZwk9ilX8SAV6CwhAQZY611tiuhbB52ONYKDO8hgY1bA==} + + kuroshiro@1.2.0: + resolution: {integrity: sha512-yBGCK9oDOY3LGZ/KXaN9m7ADcAuSczOR2FoMRYwHLUlis3/o/uxdMVROAjENFO0NQJgALhIdWxI/vIBVrMCk9w==} + engines: {node: '>=6.5.0'} + lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + lazy-var@2.2.2: + resolution: {integrity: sha512-158qpmga63PQS6ZmnmrASQ/I+L7ACqczyR6vt61UHBlcVVMddPQIKgG8i63QRgLh98FLawo8qXkJS0gAJi39gQ==} + leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -3185,6 +3270,9 @@ packages: lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -3598,6 +3686,10 @@ packages: parse-bmfont-xml@1.1.6: resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parse5@7.2.1: resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} @@ -3636,6 +3728,10 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + path-type@6.0.0: resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} engines: {node: '>=18'} @@ -3682,6 +3778,22 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + pinyin@4.0.0-alpha.2: + resolution: {integrity: sha512-SED2wWr1X0QwH6rXIDgg20zS1mAk0AVMO8eM3KomUlOYzC8mNMWZnspZWhhI0M8MBIbF2xwa+5r30jTSjAqNsg==} + engines: {install-node: ^18.0.0} + hasBin: true + peerDependencies: + '@node-rs/jieba': ^1.6.0 + nodejieba: 2.5.2 + segmentit: ^2.0.3 + peerDependenciesMeta: + '@node-rs/jieba': + optional: true + nodejieba: + optional: true + segmentit: + optional: true + pixelmatch@5.3.0: resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} hasBin: true @@ -3734,6 +3846,9 @@ packages: engines: {node: '>=14'} hasBin: true + preval.macro@4.0.0: + resolution: {integrity: sha512-sJJnE71X+MPr64CVD2AurmUj4JEDqbudYbStav3L9Xjcqm4AR0ymMm6sugw1mUmfI/7gw4JWA4JXo/k6w34crw==} + proc-log@2.0.1: resolution: {integrity: sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -3936,6 +4051,9 @@ packages: sdp@3.2.0: resolution: {integrity: sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==} + segmentit@2.0.3: + resolution: {integrity: sha512-7mn2XL3OdTUQ+AhHz7SbgyxLTaQRzTWQNVwiK+UlTO8aePGbSwvKUzTwE4238+OUY9MoR6ksAg35zl8sfTunQQ==} + selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -4620,6 +4738,10 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yaml@2.7.0: resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} engines: {node: '>= 14'} @@ -4643,6 +4765,9 @@ packages: youtubei.js@13.3.0: resolution: {integrity: sha512-tbl7rxltpgKoSsmfGUe9JqWUAzv6HFLqrOn0N85EbTn5DLt24EXrjClnXdxyr3PBARMJ3LC4vbll100a0ABsYw==} + zlibjs@0.3.1: + resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==} + zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} @@ -5732,6 +5857,8 @@ snapshots: dependencies: undici-types: 6.20.0 + '@types/parse-json@4.0.2': {} + '@types/plist@3.0.5': dependencies: '@types/node': 22.13.5 @@ -6075,6 +6202,10 @@ snapshots: dependencies: tslib: 2.8.1 + async@2.6.4: + dependencies: + lodash: 4.17.21 + async@3.2.6: {} asynckit@0.4.0: {} @@ -6102,6 +6233,18 @@ snapshots: parse5: 7.2.1 validate-html-nesting: 1.2.2 + babel-plugin-macros@2.8.0: + dependencies: + '@babel/runtime': 7.26.10 + cosmiconfig: 6.0.0 + resolve: 1.22.10 + + babel-plugin-preval@4.0.0: + dependencies: + '@babel/runtime': 7.26.10 + babel-plugin-macros: 2.8.0 + require-from-string: 2.0.2 + babel-preset-solid@1.9.5(@babel/core@7.26.9): dependencies: '@babel/core': 7.26.9 @@ -6391,6 +6534,10 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@1.1.1: + dependencies: + keypress: 0.1.0 + commander@5.1.0: {} commander@9.5.0: @@ -6442,6 +6589,14 @@ snapshots: core-util-is@1.0.3: {} + cosmiconfig@6.0.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + crc@3.8.0: dependencies: buffer: 5.7.1 @@ -6659,6 +6814,8 @@ snapshots: dotenv@16.4.7: {} + doublearray@0.0.2: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6833,6 +6990,10 @@ snapshots: err-code@2.0.3: {} + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + error-stack-parser-es@1.0.5: {} es-abstract@1.23.9: @@ -6905,6 +7066,8 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.1.0 + es-hangul@2.2.4: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -7480,6 +7643,8 @@ snapshots: graphemer@1.4.0: {} + hanja@1.1.4: {} + happy-dom@17.4.4: dependencies: webidl-conversions: 7.0.0 @@ -7650,6 +7815,8 @@ snapshots: call-bound: 1.0.3 get-intrinsic: 1.3.0 + is-arrayish@0.2.1: {} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -7873,6 +8040,8 @@ snapshots: json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -7911,12 +8080,30 @@ snapshots: keyboardevents-areequal@0.2.2: {} + keypress@0.1.0: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + kuromoji@0.1.2(patch_hash=e4a08f477026373a427a51d9bedd268ee22a173850ede3750de32af5d241c28c): + dependencies: + async: 2.6.4 + doublearray: 0.0.2 + zlibjs: 0.3.1 + + kuroshiro-analyzer-kuromoji@1.1.0: + dependencies: + kuromoji: 0.1.2(patch_hash=e4a08f477026373a427a51d9bedd268ee22a173850ede3750de32af5d241c28c) + + kuroshiro@1.2.0: + dependencies: + '@babel/runtime': 7.26.10 + lazy-val@1.0.5: {} + lazy-var@2.2.2: {} + leac@0.6.0: {} levn@0.4.1: @@ -7930,6 +8117,8 @@ snapshots: dependencies: immediate: 3.0.6 + lines-and-columns@1.2.4: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -8347,6 +8536,13 @@ snapshots: xml-parse-from-string: 1.0.1 xml2js: 0.6.2 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parse5@7.2.1: dependencies: entities: 4.5.0 @@ -8380,6 +8576,8 @@ snapshots: path-to-regexp@3.3.0: {} + path-type@4.0.0: {} + path-type@6.0.0: {} pathe@2.0.3: {} @@ -8413,6 +8611,12 @@ snapshots: picomatch@4.0.2: {} + pinyin@4.0.0-alpha.2(segmentit@2.0.3): + dependencies: + commander: 1.1.1 + optionalDependencies: + segmentit: 2.0.3 + pixelmatch@5.3.0: dependencies: pngjs: 6.0.0 @@ -8456,6 +8660,10 @@ snapshots: prettier@3.5.2: {} + preval.macro@4.0.0: + dependencies: + babel-plugin-preval: 4.0.0 + proc-log@2.0.1: {} proc-log@5.0.0: {} @@ -8696,6 +8904,10 @@ snapshots: sdp@3.2.0: {} + segmentit@2.0.3: + dependencies: + preval.macro: 4.0.0 + selderee@0.11.0: dependencies: parseley: 0.12.1 @@ -9418,6 +9630,8 @@ snapshots: yallist@5.0.0: {} + yaml@1.10.2: {} + yaml@2.7.0: {} yargs-parser@21.1.1: {} @@ -9446,4 +9660,6 @@ snapshots: tslib: 2.8.1 undici: 5.28.5 + zlibjs@0.3.1: {} + zod@3.24.2: {} diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index e24614d4..824a681e 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -756,6 +756,10 @@ "label": "Make the lyrics perfectly synced", "tooltip": "Calculate to the milisecond the display of the next line (can have a small impact on performance)" }, + "romanization": { + "label": "Romanize lyrics", + "tooltip": "If the lyrics are in a different language, try to display a latin version." + }, "show-lyrics-even-if-inexact": { "label": "Show lyrics even if inexact", "tooltip": "If the song is not found, the plugin tries again with a different search query.\nThe result from the second attempt may not be exact." diff --git a/src/index.ts b/src/index.ts index 6abed0be..53d30604 100644 --- a/src/index.ts +++ b/src/index.ts @@ -825,7 +825,7 @@ app.whenReady().then(async () => { // Optimized for Mac OS X if (is.macOS() && !config.get('options.appVisible')) { - app.dock.hide(); + app.dock?.hide(); } let forceQuit = false; diff --git a/src/plugins/synced-lyrics/index.ts b/src/plugins/synced-lyrics/index.ts index f1e99952..1dbc2d55 100644 --- a/src/plugins/synced-lyrics/index.ts +++ b/src/plugins/synced-lyrics/index.ts @@ -20,7 +20,8 @@ export default createPlugin({ showTimeCodes: false, defaultTextString: '♪', lineEffect: 'fancy', - } satisfies SyncedLyricsPluginConfig, + romanization: true, + } satisfies SyncedLyricsPluginConfig as SyncedLyricsPluginConfig, menu, renderer, diff --git a/src/plugins/synced-lyrics/menu.ts b/src/plugins/synced-lyrics/menu.ts index 4532bac2..2a1adcb3 100644 --- a/src/plugins/synced-lyrics/menu.ts +++ b/src/plugins/synced-lyrics/menu.ts @@ -1,7 +1,6 @@ -import { MenuItemConstructorOptions } from 'electron'; - import { t } from '@/i18n'; +import type { MenuItemConstructorOptions } from 'electron'; import type { MenuContext } from '@/types/contexts'; import type { SyncedLyricsPluginConfig } from './types'; @@ -136,6 +135,17 @@ export const menu = async ( }, ], }, + { + label: t('plugins.synced-lyrics.menu.romanization.label'), + toolTip: t('plugins.synced-lyrics.menu.romanization.tooltip'), + type: 'checkbox', + checked: config.romanization, + click(item) { + ctx.setConfig({ + romanization: item.checked, + }); + }, + }, { label: t('plugins.synced-lyrics.menu.show-time-codes.label'), toolTip: t('plugins.synced-lyrics.menu.show-time-codes.tooltip'), diff --git a/src/plugins/synced-lyrics/providers/LyricsGenius.ts b/src/plugins/synced-lyrics/providers/LyricsGenius.ts index 519e6f94..56063d96 100644 --- a/src/plugins/synced-lyrics/providers/LyricsGenius.ts +++ b/src/plugins/synced-lyrics/providers/LyricsGenius.ts @@ -30,15 +30,16 @@ export class LyricsGenius implements LyricProvider { title: titleA, primary_artist: { name: artistA }, }, - }, - { + }, { result: { title: titleB, primary_artist: { name: artistB }, }, }) => { - const pointsA = (titleA === title ? 1 : 0) + (artistA.includes(artist) ? 1 : 0); - const pointsB = (titleB === title ? 1 : 0) + (artistB.includes(artist) ? 1 : 0); + const pointsA = (titleA === title ? 1 : 0) + + (artistA.includes(artist) ? 1 : 0); + const pointsB = (titleB === title ? 1 : 0) + + (artistB.includes(artist) ? 1 : 0); return pointsB - pointsA; }, @@ -51,14 +52,21 @@ export class LyricsGenius implements LyricProvider { const { result: { path } } = closestHit; - const html = await fetch(`${this.baseUrl}${path}`).then((res) => res.text()); + const html = await fetch(`${this.baseUrl}${path}`).then((res) => + res.text() + ); const doc = this.domParser.parseFromString(html, 'text/html'); - const preloadedStateScript = Array.prototype.find.call(doc.querySelectorAll('script'), (script: HTMLScriptElement) => { - return script.textContent?.includes('window.__PRELOADED_STATE__'); - }) as HTMLScriptElement; + const preloadedStateScript = Array.prototype.find.call( + doc.querySelectorAll('script'), + (script: HTMLScriptElement) => { + return script.textContent?.includes('window.__PRELOADED_STATE__'); + }, + ) as HTMLScriptElement; - const preloadedState = preloadedStateScript.textContent?.match(preloadedStateRegex)?.[1]?.replace(/\\"/g, '"'); + const preloadedState = preloadedStateScript.textContent?.match( + preloadedStateRegex, + )?.[1]?.replace(/\\"/g, '"'); const lyricsHtml = preloadedState?.match(preloadHtmlRegex)?.[1] ?.replace(/\\\//g, '/') @@ -67,12 +75,19 @@ export class LyricsGenius implements LyricProvider { ?.replace(/\\'/g, "'") ?.replace(/\\"/g, '"'); - if (!lyricsHtml) throw new Error('Failed to extract lyrics from preloaded state.'); + const hasUnreleasedPlaceholder = preloadedState && + /lyricsPlaceholderReason.{1,5}unreleased/.test(preloadedState); + if (!lyricsHtml) { + if (hasUnreleasedPlaceholder) return null; + throw new Error('Failed to extract lyrics from preloaded state.'); + } const lyricsDoc = this.domParser.parseFromString(lyricsHtml, 'text/html'); const lyrics = lyricsDoc.body.innerText; - if (lyrics.trim().toLowerCase().replace(/[[\]]/g, '') === 'instrumental') return null; + if (lyrics.trim().toLowerCase().replace(/[[\]]/g, '') === 'instrumental') { + return null; + } return { title: closestHit.result.title, diff --git a/src/plugins/synced-lyrics/renderer/components/LyricsContainer.tsx b/src/plugins/synced-lyrics/renderer/components/LyricsContainer.tsx index 6d0e244d..c8b2dc0d 100644 --- a/src/plugins/synced-lyrics/renderer/components/LyricsContainer.tsx +++ b/src/plugins/synced-lyrics/renderer/components/LyricsContainer.tsx @@ -1,4 +1,4 @@ -import { createSignal, For, Match, Show, Switch } from 'solid-js'; +import { createEffect, createSignal, For, Match, Show, Switch } from 'solid-js'; import { SyncedLine } from './SyncedLine'; @@ -6,6 +6,7 @@ import { ErrorDisplay } from './ErrorDisplay'; import { LoadingKaomoji } from './LoadingKaomoji'; import { PlainLyrics } from './PlainLyrics'; +import { hasJapaneseInString, hasKoreanInString } from '../utils'; import { currentLyrics, lyricsStore } from '../../providers'; export const [debugInfo, setDebugInfo] = createSignal(); @@ -13,6 +14,20 @@ export const [currentTime, setCurrentTime] = createSignal(-1); // prettier-ignore export const LyricsContainer = () => { + const [hasJapanese, setHasJapanese] = createSignal(false); + const [hasKorean, setHasKorean] = createSignal(false); + + createEffect(() => { + const data = currentLyrics()?.data; + if (data) { + setHasKorean(hasKoreanInString(data)); + setHasJapanese(hasJapaneseInString(data)); + } else { + setHasKorean(false); + setHasJapanese(false); + } + }); + return (
@@ -42,12 +57,12 @@ export const LyricsContainer = () => { - {(item) => } + {(item) => } - +
diff --git a/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx b/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx index c93326fd..9284450f 100644 --- a/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx +++ b/src/plugins/synced-lyrics/renderer/components/PlainLyrics.tsx @@ -1,28 +1,91 @@ -import { createMemo, For } from 'solid-js'; +import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'; + +import { + canonicalize, + romanizeChinese, + romanizeHangul, + romanizeJapanese, + romanizeJapaneseOrHangul, + simplifyUnicode, +} from '../utils'; +import { config } from '../renderer'; interface PlainLyricsProps { lyrics: string; + hasJapanese: boolean; + hasKorean: boolean; } export const PlainLyrics = (props: PlainLyricsProps) => { - const lines = createMemo(() => props.lyrics.split('\n')); + const lines = props.lyrics.split('\n').filter((line) => line.trim()); + const [romanizedLines, setRomanizedLines] = createSignal< + Record + >({}); + + const combinedLines = createMemo(() => { + const out = []; + + for (let i = 0; i < lines.length; i++) { + out.push([lines[i], romanizedLines()[i]]); + } + + return out; + }); + + createEffect(async () => { + if (!config()?.romanization) return; + + for (let i = 0; i < lines.length; i++) { + let romanized: string; + + if (props.hasJapanese) { + if (props.hasKorean) + romanized = await romanizeJapaneseOrHangul(lines[i]); + else romanized = await romanizeJapanese(lines[i]); + } else if (props.hasKorean) romanized = romanizeHangul(lines[i]); + else romanized = romanizeChinese(lines[i]); + + setRomanizedLines((prev) => ({ + ...prev, + [i]: canonicalize(romanized), + })); + } + }); return (
- - {(line) => { - if (line.trim() === '') { - return
; - } else { - return ( + + {([line, romanized]) => { + return ( +
- ); - } + + + +
+ ); }}
diff --git a/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx b/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx index 2805a6b1..abca8fc0 100644 --- a/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx +++ b/src/plugins/synced-lyrics/renderer/components/SyncedLine.tsx @@ -1,22 +1,33 @@ -import { createEffect, createMemo, For } from 'solid-js'; +import { createEffect, createMemo, For, Show, createSignal } from 'solid-js'; import { currentTime } from './LyricsContainer'; import { config } from '../renderer'; import { _ytAPI } from '..'; +import { + canonicalize, + romanizeChinese, + romanizeHangul, + romanizeJapanese, + romanizeJapaneseOrHangul, + simplifyUnicode, +} from '../utils'; + import type { LineLyrics } from '../../types'; interface SyncedLineProps { line: LineLyrics; + hasJapanese: boolean; + hasKorean: boolean; } -export const SyncedLine = ({ line }: SyncedLineProps) => { +export const SyncedLine = (props: SyncedLineProps) => { const status = createMemo(() => { const current = currentTime(); - if (line.timeInMs >= current) return 'upcoming'; - if (current - line.timeInMs >= line.duration) return 'previous'; + if (props.line.timeInMs >= current) return 'upcoming'; + if (current - props.line.timeInMs >= props.line.duration) return 'previous'; return 'current'; }); @@ -28,8 +39,28 @@ export const SyncedLine = ({ line }: SyncedLineProps) => { }); const text = createMemo(() => { - if (line.text.trim()) return line.text; - return config()?.defaultTextString ?? ''; + if (!props.line.text.trim()) { + return config()?.defaultTextString ?? ''; + } + + return props.line.text; + }); + + const [romanization, setRomanization] = createSignal(''); + + createEffect(async () => { + if (!config()?.romanization) return; + + const input = canonicalize(text()); + + let result: string; + if (props.hasJapanese) { + if (props.hasKorean) result = await romanizeJapaneseOrHangul(input); + else result = await romanizeJapanese(input); + } else if (props.hasKorean) result = romanizeHangul(input); + else result = romanizeChinese(input); + + setRomanization(canonicalize(result)); }); if (!text()) { @@ -47,35 +78,79 @@ export const SyncedLine = ({ line }: SyncedLineProps) => { ref={ref} class={`synced-line ${status()}`} onClick={() => { - _ytAPI?.seekTo(line.timeInMs / 1000); + _ytAPI?.seekTo(props.line.timeInMs / 1000); }} > -
+
- - {(word, index) => { - return ( - - - +
{ + // TODO: Investigate the animation, even though the duration is properly set, all lines have the same animation duration + div.style.setProperty( + '--lyrics-duration', + `${props.line.duration / 1000}s`, + 'important', ); }} - + style={{ 'display': 'flex', 'flex-direction': 'column' }} + > + + + {(word, index) => { + return ( + + + + ); + }} + + + + + + + {(word, index) => { + return ( + + + + ); + }} + + + +
); diff --git a/src/plugins/synced-lyrics/renderer/utils.tsx b/src/plugins/synced-lyrics/renderer/utils.tsx index e51f976e..85875cd3 100644 --- a/src/plugins/synced-lyrics/renderer/utils.tsx +++ b/src/plugins/synced-lyrics/renderer/utils.tsx @@ -1,8 +1,20 @@ import { render } from 'solid-js/web'; +import KuromojiAnalyzer from 'kuroshiro-analyzer-kuromoji'; +import Kuroshiro from 'kuroshiro'; + +import { romanize as esHangulRomanize } from 'es-hangul'; +import hanja from 'hanja'; + +import pinyin from 'pinyin/esm/pinyin'; + +import { lazy } from 'lazy-var'; + import { waitForElement } from '@/utils/wait-for-element'; import { LyricsRenderer, setIsVisible } from './renderer'; +import type { LyricResult } from '@/plugins/synced-lyrics/types'; + export const selectors = { head: '#tabsContent > .tab-header:nth-of-type(2)', body: { @@ -33,3 +45,148 @@ export const tabStates: Record void> = { setIsVisible(false); }, }; + +export const canonicalize = (text: string) => { + return ( + text + // `hi there` => `hi there` + .replaceAll(/\s+/g, ' ') + + // `( a )` => `(a)` + .replaceAll(/([([]) ([^ ])/g, (_, symbol, a) => `${symbol}${a}`) + .replaceAll(/([^ ]) ([)\]])/g, (_, a, symbol) => `${a}${symbol}`) + + // `can ' t` => `can't` + .replaceAll( + /([Ii]) (') ([^ ])|(n) (') (t)(?= |$)|(t) (') (s)|([^ ]) (') (re)|([^ ]) (') (ve)|([^ ]) (-) ([^ ])/g, + (m, ...groups) => { + for (let i = 0; i < groups.length; i += 3) { + if (groups[i]) { + return groups.slice(i, i + 3).join(''); + } + } + + return m; + }, + ) + // `Stayin ' still` => `Stayin' still` + .replaceAll(/in ' ([^ ])/g, (_, char) => `in' ${char}`) + .replaceAll("in ',", "in',") + + .replaceAll(", ' cause", ", 'cause") + + // `hi , there` => `hi, there` + .replaceAll(/([^ ]) ([.,!?])/g, (_, a, symbol) => `${a}${symbol}`) + + // `hi " there "` => `hi "there"` + .replaceAll( + /"([^"]+)"/g, + (_, content) => + `"${typeof content === 'string' ? content.trim() : content}"`, + ) + .trim() + ); +}; + +export const simplifyUnicode = (text?: string) => + text + ? text + .replaceAll(/\u0020|\u00A0|[\u2000-\u200A]|\u202F|\u205F|\u3000/g, ' ') + .trim() + : text; + +// Japanese Shinjitai +const shinjitai = [ + 20055, 20081, 20120, 20124, 20175, 26469, 20341, 20206, 20253, 20605, 20385, + 20537, 20816, 20001, 20869, 23500, 28092, 20956, 21104, 21091, 21092, 21172, + 21234, 21169, 21223, 21306, 24059, 21363, 21442, 21782, 21336, 22107, 21427, + 22065, 22287, 22269, 22258, 20870, 22259, 22243, 37326, 23597, 22679, 22549, + 22311, 22593, 22730, 22732, 22766, 22769, 23551, 22885, 22888, 23330, 23398, + 23517, 23455, 20889, 23515, 23453, 23558, 23554, 23550, 23626, 23631, 23646, + 23792, 23777, 23798, 23731, 24012, 24035, 24111, 24182, 24259, 24195, 24193, + 24382, 24357, 24367, 24452, 24467, 24500, 24499, 24658, 24693, 24746, 24745, + 24910, 24808, 24540, 25040, 24651, 25126, 25135, 25144, 25147, 25173, 25244, + 25309, 25375, 25407, 25522, 25531, 25594, 25436, 25246, 25731, 25285, 25312, + 25369, 25313, 25666, 25785, 21454, 21177, 21465, 21189, 25968, 26029, 26179, + 26217, 26172, 26278, 26241, 26365, 20250, 26465, 26719, 26628, 27097, 27010, + 27005, 27004, 26530, 27096, 27178, 26727, 26908, 26716, 27177, 27431, 27475, + 27497, 27508, 24112, 27531, 27579, 27572, 27598, 27671, 28169, 28057, 27972, + 27973, 28167, 28179, 28201, 28382, 28288, 28300, 28508, 28171, 27810, 28287, + 28168, 27996, 27818, 28381, 28716, 28286, 28948, 28783, 28988, 21942, 28809, + 20105, 28858, 29344, 29366, 29421, 22888, 29420, 29471, 29539, 29486, 24321, + 29942, 30011, 24403, 30067, 30185, 30196, 30330, 26479, 30423, 23613, 30495, + 30740, 30741, 30783, 31192, 31108, 31109, 31036, 31074, 31095, 31216, 31282, + 38964, 31298, 31311, 31331, 31363, 20006, 31883, 31992, 32076, 32209, 32210, + 32257, 30476, 32294, 32207, 32333, 32260, 32117, 32331, 32153, 32154, 32330, + 27424, 32566, 22768, 32884, 31899, 33075, 32966, 33235, 21488, 19982, 26087, + 33398, 33624, 33550, 33804, 19975, 33931, 22290, 34219, 34101, 33464, 34220, + 33446, 20966, 34394, 21495, 34509, 34411, 34635, 34453, 34542, 34907, 35013, + 35090, 35226, 35239, 35251, 35302, 35617, 35388, 35379, 35465, 35501, 22793, + 35698, 35715, 35914, 33398, 20104, 24336, 22770, 38972, 36059, 36341, 36527, + 36605, 36620, 36578, 24321, 36766, 24321, 36965, 36883, 36933, 36794, 37070, + 37111, 37204, 21307, 37284, 37271, 37304, 37320, 37682, 37549, 37676, 37806, + 37444, 37619, 37489, 38306, 38501, 38543, 38522, 38560, 21452, 38609, 35207, + 38666, 38745, 39003, 38997, 32763, 20313, 39173, 39366, 39442, 39366, 39443, + 39365, 39620, 20307, 39658, 38360, 40335, 40206, 40568, 22633, 40614, 40633, + 40634, 40644, 40658, 40665, 28857, 20826, 25993, 25998, 27503, 40802, 31452, + 20096, +].map((codePoint) => String.fromCodePoint(codePoint)); +const shinjitaiRegex = new RegExp(`[${shinjitai.join('')}]`); + +const kuroshiro = lazy(async () => { + const _kuroshiro = new Kuroshiro(); + await _kuroshiro.init( + new KuromojiAnalyzer({ + dictPath: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/', + }), + ); + return _kuroshiro; +}); + +const hasJapanese = (lines: string[]) => + lines.some( + (line) => Kuroshiro.Util.hasKana(line) || shinjitaiRegex.test(line), + ); + +// tests for Hangul characters, sufficient for our use case +const hasKorean = (lines: string[]) => + lines.some((line) => /[ㄱ-ㅎㅏ-ㅣ가-힣]+/.test(line)); + +export const hasJapaneseInString = (lyric: LyricResult) => { + if (!lyric || (!lyric.lines && !lyric.lyrics)) return false; + const lines = Array.isArray(lyric.lines) + ? lyric.lines.map(({ text }) => text) + : lyric.lyrics!.split('\n'); + return hasJapanese(lines); +}; + +export const hasKoreanInString = (lyric: LyricResult) => { + if (!lyric || (!lyric.lines && !lyric.lyrics)) return false; + + const lines = Array.isArray(lyric.lines) + ? lyric.lines.map(({ text }) => text) + : lyric.lyrics!.split('\n'); + + return hasKorean(lines); +}; + +export const romanizeJapanese = async (line: string) => + (await kuroshiro.get()).convert(line, { + to: 'romaji', + mode: 'spaced', + }); + +export const romanizeHangul = (line: string) => + esHangulRomanize(hanja.translate(line, 'SUBSTITUTION')); + +export const romanizeJapaneseOrHangul = async (line: string) => + romanizeHangul(await romanizeJapanese(line)); + +export const romanizeChinese = (line: string) => + pinyin(line, { + heteronym: true, + segment: true, + group: true, + }) + .flat() + .join(' '); diff --git a/src/plugins/synced-lyrics/style.css b/src/plugins/synced-lyrics/style.css index 525e4a26..79e8cf12 100644 --- a/src/plugins/synced-lyrics/style.css +++ b/src/plugins/synced-lyrics/style.css @@ -8,6 +8,17 @@ display: block !important; } +/* Hide the scrollbar in the lyrics-tab */ +#tab-renderer[page-type='MUSIC_PAGE_TYPE_TRACK_LYRICS'] { + scrollbar-width: none; +} + +@property --lyrics-duration { + syntax: '