mirror of
https://github.com/searxng/searxng.git
synced 2025-09-02 11:33:48 +00:00
[enh] theme/simple: custom router
Lay the foundation for loading scripts granularly depending on the endpoint it's on. Remove vendor specific prefixes as there are now managed by browserslist and LightningCSS. Enabled quite a few rules in Biome that don't come in recommended to better catch issues and improve consistency. Related: - https://github.com/searxng/searxng/pull/5073#discussion_r2256037965 - https://github.com/searxng/searxng/pull/5073#discussion_r2256057100
This commit is contained in:
parent
adc4361eb9
commit
60bd8b90f0
28 changed files with 1109 additions and 1039 deletions
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.1.3/schema.json",
|
||||
"files": {
|
||||
"includes": ["**", "!dist/**", "!node_modules/**"],
|
||||
"ignoreUnknown": true
|
||||
|
@ -28,7 +28,116 @@
|
|||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
"recommended": true,
|
||||
"complexity": {
|
||||
"noForEach": "error",
|
||||
"useSimplifiedLogicExpression": "error"
|
||||
},
|
||||
"correctness": {
|
||||
"noUndeclaredVariables": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"checkTypes": true
|
||||
}
|
||||
},
|
||||
"useImportExtensions": "error"
|
||||
},
|
||||
"nursery": {
|
||||
"noAwaitInLoop": "warn",
|
||||
"noBitwiseOperators": "warn",
|
||||
"noConstantBinaryExpression": "warn",
|
||||
"noGlobalDirnameFilename": "warn",
|
||||
"noImplicitCoercion": "warn",
|
||||
"noMisusedPromises": "warn",
|
||||
"noUnassignedVariables": "warn",
|
||||
"noUselessBackrefInRegex": "warn",
|
||||
"noUselessEscapeInString": "warn",
|
||||
"noUselessUndefined": "warn",
|
||||
"useAdjacentGetterSetter": "warn",
|
||||
"useConsistentObjectDefinition": {
|
||||
"level": "warn",
|
||||
"options": {
|
||||
"syntax": "explicit"
|
||||
}
|
||||
},
|
||||
"useConsistentResponse": "warn",
|
||||
"useExhaustiveSwitchCases": "warn",
|
||||
"useExplicitType": "warn",
|
||||
"useIndexOf": "warn",
|
||||
"useIterableCallbackReturn": "warn",
|
||||
"useJsonImportAttribute": "warn",
|
||||
"useNumericSeparators": "warn",
|
||||
"useObjectSpread": "warn",
|
||||
"useParseIntRadix": "warn",
|
||||
"useReadonlyClassProperties": "warn",
|
||||
"useSingleJsDocAsterisk": "warn",
|
||||
"useUnifiedTypeSignature": "warn"
|
||||
},
|
||||
"performance": {
|
||||
"noBarrelFile": "error",
|
||||
"noDelete": "error",
|
||||
"noNamespaceImport": "error",
|
||||
"noReExportAll": "error",
|
||||
"useTopLevelRegex": "error"
|
||||
},
|
||||
"style": {
|
||||
"noCommonJs": "error",
|
||||
"noEnum": "error",
|
||||
"noInferrableTypes": "error",
|
||||
"noNamespace": "error",
|
||||
"noNegationElse": "error",
|
||||
"noNestedTernary": "error",
|
||||
"noParameterAssign": "error",
|
||||
"noParameterProperties": "error",
|
||||
"noRestrictedTypes": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"types": {
|
||||
"Element": {
|
||||
"message": "Element is too generic",
|
||||
"use": "HTMLElement"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"noSubstr": "error",
|
||||
"noUnusedTemplateLiteral": "error",
|
||||
"noUselessElse": "error",
|
||||
"noYodaExpression": "error",
|
||||
"useAsConstAssertion": "error",
|
||||
"useAtIndex": "error",
|
||||
"useCollapsedElseIf": "error",
|
||||
"useCollapsedIf": "error",
|
||||
"useConsistentArrayType": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"syntax": "shorthand"
|
||||
}
|
||||
},
|
||||
"useConsistentBuiltinInstantiation": "error",
|
||||
"useConsistentMemberAccessibility": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"accessibility": "explicit"
|
||||
}
|
||||
},
|
||||
"useDefaultSwitchClause": "error",
|
||||
"useExplicitLengthCheck": "error",
|
||||
"useForOf": "error",
|
||||
"useNumberNamespace": "error",
|
||||
"useShorthandAssign": "error",
|
||||
"useSingleVarDeclarator": "error",
|
||||
"useThrowNewError": "error",
|
||||
"useThrowOnlyError": "error",
|
||||
"useTrimStartEnd": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noAlert": "error",
|
||||
"noEmptyBlockStatements": "error",
|
||||
"noEvolvingTypes": "error",
|
||||
"noVar": "error",
|
||||
"useNumberToFixedDigitsArgument": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
|
|
344
client/simple/package-lock.json
generated
344
client/simple/package-lock.json
generated
|
@ -9,27 +9,27 @@
|
|||
"version": "0.0.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"ionicons": "~8.0.13",
|
||||
"ionicons": "~8.0.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"ol": "~10.6.1",
|
||||
"ol": "~10.6.0",
|
||||
"swiped-events": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.1.2",
|
||||
"@types/node": "~24.0.15",
|
||||
"browserslist": "~4.25.1",
|
||||
"browserslist-to-esbuild": "~2.1.1",
|
||||
"edge.js": "~6.2.1",
|
||||
"@biomejs/biome": "2.1.3",
|
||||
"@types/node": "~24.2.0",
|
||||
"browserslist": "~4.25.0",
|
||||
"browserslist-to-esbuild": "~2.1.0",
|
||||
"edge.js": "~6.2.0",
|
||||
"less": "~4.4.0",
|
||||
"lightningcss": "~1.30.1",
|
||||
"sharp": "~0.34.3",
|
||||
"lightningcss": "~1.30.0",
|
||||
"sharp": "~0.34.0",
|
||||
"sort-package-json": "~3.4.0",
|
||||
"stylelint": "~16.22.0",
|
||||
"stylelint-config-standard-less": "~3.0.1",
|
||||
"stylelint-prettier": "~5.0.3",
|
||||
"stylelint": "~16.23.0",
|
||||
"stylelint-config-standard-less": "~3.0.0",
|
||||
"stylelint-prettier": "~5.0.0",
|
||||
"svgo": "~4.0.0",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "npm:rolldown-vite@~7.0.9",
|
||||
"typescript": "~5.9.0",
|
||||
"vite": "npm:rolldown-vite@7.0.12",
|
||||
"vite-bundle-analyzer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
|
@ -59,9 +59,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.1.2.tgz",
|
||||
"integrity": "sha512-yq8ZZuKuBVDgAS76LWCfFKHSYIAgqkxVB3mGVVpOe2vSkUTs7xG46zXZeNPRNVjiJuw0SZ3+J2rXiYx0RUpfGg==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.1.3.tgz",
|
||||
"integrity": "sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -75,20 +75,20 @@
|
|||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "2.1.2",
|
||||
"@biomejs/cli-darwin-x64": "2.1.2",
|
||||
"@biomejs/cli-linux-arm64": "2.1.2",
|
||||
"@biomejs/cli-linux-arm64-musl": "2.1.2",
|
||||
"@biomejs/cli-linux-x64": "2.1.2",
|
||||
"@biomejs/cli-linux-x64-musl": "2.1.2",
|
||||
"@biomejs/cli-win32-arm64": "2.1.2",
|
||||
"@biomejs/cli-win32-x64": "2.1.2"
|
||||
"@biomejs/cli-darwin-arm64": "2.1.3",
|
||||
"@biomejs/cli-darwin-x64": "2.1.3",
|
||||
"@biomejs/cli-linux-arm64": "2.1.3",
|
||||
"@biomejs/cli-linux-arm64-musl": "2.1.3",
|
||||
"@biomejs/cli-linux-x64": "2.1.3",
|
||||
"@biomejs/cli-linux-x64-musl": "2.1.3",
|
||||
"@biomejs/cli-win32-arm64": "2.1.3",
|
||||
"@biomejs/cli-win32-x64": "2.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.1.2.tgz",
|
||||
"integrity": "sha512-leFAks64PEIjc7MY/cLjE8u5OcfBKkcDB0szxsWUB4aDfemBep1WVKt0qrEyqZBOW8LPHzrFMyDl3FhuuA0E7g==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.1.3.tgz",
|
||||
"integrity": "sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -103,9 +103,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.1.2.tgz",
|
||||
"integrity": "sha512-Nmmv7wRX5Nj7lGmz0FjnWdflJg4zii8Ivruas6PBKzw5SJX/q+Zh2RfnO+bBnuKLXpj8kiI2x2X12otpH6a32A==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.1.3.tgz",
|
||||
"integrity": "sha512-Q/4OTw8P9No9QeowyxswcWdm0n2MsdCwWcc5NcKQQvzwPjwuPdf8dpPPf4r+x0RWKBtl1FLiAUtJvBlri6DnYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -120,9 +120,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.1.2.tgz",
|
||||
"integrity": "sha512-NWNy2Diocav61HZiv2enTQykbPP/KrA/baS7JsLSojC7Xxh2nl9IczuvE5UID7+ksRy2e7yH7klm/WkA72G1dw==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.1.3.tgz",
|
||||
"integrity": "sha512-2hS6LgylRqMFmAZCOFwYrf77QMdUwJp49oe8PX/O8+P2yKZMSpyQTf3Eo5ewnsMFUEmYbPOskafdV1ds1MZMJA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -137,9 +137,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.1.2.tgz",
|
||||
"integrity": "sha512-qgHvafhjH7Oca114FdOScmIKf1DlXT1LqbOrrbR30kQDLFPEOpBG0uzx6MhmsrmhGiCFCr2obDamu+czk+X0HQ==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.1.3.tgz",
|
||||
"integrity": "sha512-KXouFSBnoxAWZYDQrnNRzZBbt5s9UJkIm40hdvSL9mBxSSoxRFQJbtg1hP3aa8A2SnXyQHxQfpiVeJlczZt76w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -154,9 +154,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.1.2.tgz",
|
||||
"integrity": "sha512-Km/UYeVowygTjpX6sGBzlizjakLoMQkxWbruVZSNE6osuSI63i4uCeIL+6q2AJlD3dxoiBJX70dn1enjQnQqwA==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.1.3.tgz",
|
||||
"integrity": "sha512-NxlSCBhLvQtWGagEztfAZ4WcE1AkMTntZV65ZvR+J9jp06+EtOYEBPQndA70ZGhHbEDG57bR6uNvqkd1WrEYVA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -171,9 +171,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.1.2.tgz",
|
||||
"integrity": "sha512-xlB3mU14ZUa3wzLtXfmk2IMOGL+S0aHFhSix/nssWS/2XlD27q+S6f0dlQ8WOCbYoXcuz8BCM7rCn2lxdTrlQA==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.1.3.tgz",
|
||||
"integrity": "sha512-KaLAxnROouzIWtl6a0Y88r/4hW5oDUJTIqQorOTVQITaKQsKjZX4XCUmHIhdEk8zMnaiLZzRTAwk1yIAl+mIew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -188,9 +188,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.1.2.tgz",
|
||||
"integrity": "sha512-G8KWZli5ASOXA3yUQgx+M4pZRv3ND16h77UsdunUL17uYpcL/UC7RkWTdkfvMQvogVsAuz5JUcBDjgZHXxlKoA==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.1.3.tgz",
|
||||
"integrity": "sha512-V9CUZCtWH4u0YwyCYbQ3W5F4ZGPWp2C2TYcsiWFNNyRfmOW1j/TY/jAurl33SaRjgZPO5UUhGyr9m6BN9t84NQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -205,9 +205,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.1.2.tgz",
|
||||
"integrity": "sha512-9zajnk59PMpjBkty3bK2IrjUsUHvqe9HWwyAWQBjGLE7MIBjbX2vwv1XPEhmO2RRuGoTkVx3WCanHrjAytICLA==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.1.3.tgz",
|
||||
"integrity": "sha512-dxy599q6lgp8ANPpR8sDMscwdp9oOumEsVXuVCVT9N2vAho8uYXlCz53JhxX6LtJOXaE73qzgkGQ7QqvFlMC0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -814,15 +814,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.1.tgz",
|
||||
"integrity": "sha512-KVlQ/jgywZpixGCKMNwxStmmbYEMyokZpCf2YuIChhfJA2uqfAKNEM8INz7zzTo55iEXfBhIIs3VqYyqzDLj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.4.3",
|
||||
"@emnapi/runtime": "^1.4.3",
|
||||
"@emnapi/core": "^1.4.5",
|
||||
"@emnapi/runtime": "^1.4.5",
|
||||
"@tybys/wasm-util": "^0.10.0"
|
||||
}
|
||||
},
|
||||
|
@ -865,9 +865,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@oxc-project/runtime": {
|
||||
"version": "0.77.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.77.0.tgz",
|
||||
"integrity": "sha512-cMbHs/DaomWSjxeJ79G10GA5hzJW9A7CZ+/cO+KuPZ7Trf3Rr07qSLauC4Ns8ba4DKVDjd8VSC9nVLpw6jpoGQ==",
|
||||
"version": "0.78.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.78.0.tgz",
|
||||
"integrity": "sha512-jOU7sDFMyq5ShGJC21UobalVzqcdtWGfySVp8ELvKoVLzMpLHb4kv1bs9VKxaP8XC7Z9hlAXwEKVhCTN+j21aQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -875,9 +875,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.77.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.77.0.tgz",
|
||||
"integrity": "sha512-iUQj185VvCPnSba+ltUV5tVDrPX6LeZVtQywnnoGbe4oJ1VKvDKisjGkD/AvVtdm98b/BdsVS35IlJV1m2mBBA==",
|
||||
"version": "0.78.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.78.0.tgz",
|
||||
"integrity": "sha512-8FvExh0WRWN1FoSTjah1xa9RlavZcJQ8/yxRbZ7ElmSa2Ij5f5Em7MvRbSthE6FbwC6Wh8iAw0Gpna7QdoqLGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -960,9 +960,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-IJL3efUJmvb5MfTEi7bGK4jq3ZFAzVbSy+vmul0DcdrglUd81Tfyy7Zzq2oM0tUgmACG32d8Jz/ykbpbf+3C5A==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-4j7QBitb/WMT1fzdJo7BsFvVNaFR5WCQPdf/RPDHEsgQIYwBaHaL47KTZxncGFQDD1UAKN3XScJ0k7LAsZfsvg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -974,9 +974,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-TXTiuHbtnHfb0c44vNfWfIyEFJ0BFUf63ip9Z4mj8T2zRcZXQYVger4OuAxnwGNGBgDyHo1VaNBG+Vxn2VrpqQ==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-4vWFTe1o5LXeitI2lW8qMGRxxwrH/LhKd2HDLa/QPhdxohvdnfKyDZWN96XUhDyje2bHFCFyhMs3ak2lg2mJFA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -988,9 +988,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-Jpjflgvbolh+fAaaEajPJQCOpZMawYMbNVzuZp3nidX1B7kMAP7NEKp9CWzthoL2Y8RfD7OApN6bx4+vFurTaw==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-MxrfodqImbsDFFFU/8LxyFPZjt7s4ht8g2Zb76EmIQ+xlmit46L9IzvWiuMpEaSJ5WbnjO7fCDWwakMGyJJ+Dw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -1002,9 +1002,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-07ZNlXIunyS1jCTnene7aokkzCZNBUnmnJWu4Nz5X5XQvVHJNjsDhPFJTlNmneSDzA3vGkRNwdECKXiDTH/CqA==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-c/TQXcATKoO8qE1bCjCOkymZTu7yVUAxBSNLp42Q97XHCb0Cu9v6MjZpB6c7Hq9NQ9NzW44uglak9D/r77JeDw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -1016,9 +1016,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-z74ah00oyKnTUtaIbg34TaIU1PYM8tGE1bK6aUs8OLZ9sWW4g3Xo5A0nit2zyeanmYFvrAUxnt3Bpk+mTZCtlg==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-Vxci4xylM11zVqvrmezAaRjGBDyOlMRtlt7TDgxaBmSYLuiokXbZpD8aoSuOyjUAeN0/tmWItkxNGQza8UWGNQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -1030,9 +1030,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-b9oKl/M5OIyAcosS73BmjOZOjvcONV97t2SnKpgwfDX/mjQO3dBgTYyvHMFA6hfhIDW1+2XVQR/k5uzBULFhoA==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-iEBEdSs25Ol0lXyVNs763f7YPAIP0t1EAjoXME81oJ94DesJslaLTj71Rn1shoMDVA+dfkYA286w5uYnOs9ZNA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -1044,9 +1044,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-RmaNSkVmAH8u/r5Q+v4O0zL4HY8pLrvlM5wBoBrb/QHDQgksGKBqhecpg1ERER0Q7gMh/GJUz6JiiD55Q+9UOA==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-Ny684Sn1X8c+gGLuDlxkOuwiEE3C7eEOqp1/YVBzQB4HO7U/b4n7alvHvShboOEY5DP1fFUjq6Z+sBLYlCIZbQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -1058,9 +1058,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-ohos": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-ohos/-/binding-linux-arm64-ohos-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-gq78fI/g0cp1UKFMk53kP/oZAgYOXbaqdadVMuCJc0CoSkDJcpO2YIasRs/QYlE91QWfcHD5RZl9zbf4ksTS/w==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-ohos/-/binding-linux-arm64-ohos-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-6moyULHDPKwt5RDEV72EqYw5n+s46AerTwtEBau5wCsZd1wuHS1L9z6wqhKISXAFTK9sneN0TEjvYKo+sgbbiA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -1072,9 +1072,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-yS/GreJ6BT44dHu1WLigc50S8jZA+pDzzsf8tqRptUTwi5YW7dX3NqcDlc/lXsZqu57aKynLljgClYAm90LEKw==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-p0yoPdoGg5Ow2YZKKB5Ypbn58i7u4XFk3PvMkriFnEcgtVk40c5u7miaX7jH0JdzahyXVBJ/KT5yEpJrzQn8yg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -1086,9 +1086,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-6FV9To1sXewGHY4NaCPeOE5p5o1qfuAjj+m75WVIPw9HEJVsQoC5QiTL5wWVNqSMch4X0eWnQ6WsQolU6sGMIA==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-sM/KhCrsT0YdHX10mFSr0cvbfk1+btG6ftepAfqhbcDfhi0s65J4dTOxGmklJnJL9i1LXZ8WA3N4wmnqsfoK8Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -1100,9 +1100,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-VcxdhF0PQda9krFJHw4DqUkdAsHWYs/Uz/Kr/zhU8zMFDzmK6OdUgl9emGj9wTzXAEHYkAMDhk+OJBRJvp424g==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-i3kD5OWs8PQP0V+JW3TFyCLuyjuNzrB45em0g84Jc+gvnDsGVlzVjMNPo7txE/yT8CfE90HC/lDs3ry9FvaUyw==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
|
@ -1110,16 +1110,16 @@
|
|||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@napi-rs/wasm-runtime": "^0.2.12"
|
||||
"@napi-rs/wasm-runtime": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-3bXSARqSf8jLHrQ1/tw9pX1GwIR9jA6OEsqTgdC0DdpoZ+34sbJXE9Nse3dQ0foGLKBkh4PqDv/rm2Thu9oVBw==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-q7mrYln30V35VrCqnBVQQvNPQm8Om9HC59I3kMYiOWogvJobzSPyO+HA1MP363+Qgwe39I2I1nqBKPOtWZ33AQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -1131,9 +1131,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-ia32-msvc": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-xPGcKb+W8NIWAf5KApsUIrhiKH5NImTarICge5jQ2m0BBxD31crio4OXy/eYVq5CZkqkqszLQz2fWZcWNmbzlQ==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-nUqGBt39XTpbBEREEnyKofdP3uz+SN/x2884BH+N3B2NjSUrP6NXwzltM35C0wKK42hX/nthRrwSgj715m99Jw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
@ -1145,9 +1145,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-3y1G8ARpXBAcz4RJM5nzMU6isS/gXZl8SuX8lS2piFOnQMiOp6ajeelnciD+EgG4ej793zvNvr+WZtdnao2yrw==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-lbnvUwAXIVWSXAeZrCa4b1KvV/DW0rBnMHuX0T7I6ey1IsXZ90J37dEgt3j48Ex1Cw1E+5H7VDNP2gyOX8iu3w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -1159,9 +1159,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -1270,9 +1270,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.1.tgz",
|
||||
"integrity": "sha512-LRZN1c4X+9/7kR+zG74SrcZ6XxKlilDTkDXajw3ioeDdVlJEvW5wU8Wn3BcAAnk7fjrgLZVN7ickgeuG7u0AKg==",
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
|
@ -1311,13 +1311,13 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz",
|
||||
"integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==",
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
||||
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.8.0"
|
||||
"undici-types": "~7.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pluralize": {
|
||||
|
@ -1526,14 +1526,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cacheable": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.2.tgz",
|
||||
"integrity": "sha512-hMkETCRV4hwBAvjQY1/xGw15tlPj+7cM4d5HOlYJJFftLQVRCboVX+mT6AJ6eL0fsqUhSUwDiF+pgfTR2r2Hxg==",
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.3.tgz",
|
||||
"integrity": "sha512-M6p10iJ/VT0wT7TLIGUnm958oVrU2cUK8pQAVU21Zu7h8rbk/PeRtRWrvHJBql97Bhzk3g1N6+2VKC+Rjxna9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hookified": "^1.10.0",
|
||||
"keyv": "^5.3.4"
|
||||
"keyv": "^5.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
|
@ -1547,9 +1547,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001727",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
||||
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
|
||||
"version": "1.0.30001731",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
|
||||
"integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1986,9 +1986,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.187",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz",
|
||||
"integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==",
|
||||
"version": "1.5.197",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.197.tgz",
|
||||
"integrity": "sha512-m1xWB3g7vJ6asIFz+2pBUbq3uGmfmln1M9SSvBe4QIFWYrRHylP73zL/3nMjDmwz8V+1xAXQDfBd6+HPW0WvDQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
@ -2145,13 +2145,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.1.tgz",
|
||||
"integrity": "sha512-zcmsHjg2B2zjuBgjdnB+9q0+cWcgWfykIcsDkWDB4GTPtl1eXUA+gTI6sO0u01AqK3cliHryTU55/b2Ow1hfZg==",
|
||||
"version": "10.1.3",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.3.tgz",
|
||||
"integrity": "sha512-D+w75Ub8T55yor7fPgN06rkCAUbAYw2vpxJmmjv/GDAcvCnv9g7IvHhIZoxzRZThrXPFI2maeY24pPbtyYU7Lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"flat-cache": "^6.1.10"
|
||||
"flat-cache": "^6.1.12"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
|
@ -2168,13 +2168,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "6.1.11",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.11.tgz",
|
||||
"integrity": "sha512-zfOAns94mp7bHG/vCn9Ru2eDCmIxVQ5dELUHKjHfDEOJmHNzE+uGa6208kfkgmtym4a0FFjEuFksCXFacbVhSg==",
|
||||
"version": "6.1.12",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.12.tgz",
|
||||
"integrity": "sha512-U+HqqpZPPXP5d24bWuRzjGqVqUcw64k4nZAbruniDwdRg0H10tvN7H6ku1tjhA4rg5B9GS3siEvwO2qjJJ6f8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cacheable": "^1.10.1",
|
||||
"cacheable": "^1.10.3",
|
||||
"flatted": "^3.3.3",
|
||||
"hookified": "^1.10.0"
|
||||
}
|
||||
|
@ -2355,9 +2355,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hookified": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.10.0.tgz",
|
||||
"integrity": "sha512-dJw0492Iddsj56U1JsSTm9E/0B/29a1AuoSLRAte8vQg/kaTGF3IgjEWT8c8yG4cC10+HisE1x5QAwR0Xwc+DA==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.11.0.tgz",
|
||||
"integrity": "sha512-aDdIN3GyU5I6wextPplYdfmWCo+aLmjjVbntmX6HLD5RCi/xKsivYEBhnRD+d9224zFf008ZpLMPlWF0ZodYZw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -2594,9 +2594,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.4.0.tgz",
|
||||
"integrity": "sha512-TMckyVjEoacG5IteUpUrOBsFORtheqziVyyY2dLUwg1jwTb8u48LX4TgmtogkNl9Y9unaEJ1luj10fGyjMGFOQ==",
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.0.tgz",
|
||||
"integrity": "sha512-QG7qR2tijh1ftOvClut4YKKg1iW6cx3GZsKoGyJPxHkGWK9oJhG9P3j5deP0QQOGDowBMVQFaP+Vm4NpGYvmIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -3445,35 +3445,35 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-aYiJmzKoUHoaaEZLRegYVfZkXW7gzdgSbq+u5cXQ6iXc/y8tnQ3zGffQo44Pr1lTKeLluw3bDIDUCx/NAzqKeA==",
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-H/LmDTUPlm65hWOTjXvd1k0qrGinNi8LrG3JsHVm6Oit7STg0upBmgoG5PZUHbAnGTHr0MLoLyzjmH261lIqSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/runtime": "=0.77.0",
|
||||
"@oxc-project/types": "=0.77.0",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.27",
|
||||
"@oxc-project/runtime": "=0.78.0",
|
||||
"@oxc-project/types": "=0.78.0",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.30",
|
||||
"ansis": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0-beta.27",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-beta.27",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-beta.27",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-beta.27",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.27",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.27",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-beta.27",
|
||||
"@rolldown/binding-linux-arm64-ohos": "1.0.0-beta.27",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.27",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-beta.27",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-beta.27",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.27",
|
||||
"@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.27",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-beta.27"
|
||||
"@rolldown/binding-android-arm64": "1.0.0-beta.30",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-beta.30",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-beta.30",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-beta.30",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.30",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.30",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-beta.30",
|
||||
"@rolldown/binding-linux-arm64-ohos": "1.0.0-beta.30",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.30",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-beta.30",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-beta.30",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.30",
|
||||
"@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.30",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-beta.30"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
|
@ -3778,9 +3778,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/stylelint": {
|
||||
"version": "16.22.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.22.0.tgz",
|
||||
"integrity": "sha512-SVEMTdjKNV4ollUrIY9ordZ36zHv2/PHzPjfPMau370MlL2VYXeLgSNMMiEbLGRO8RmD2R8/BVUeF2DfnfkC0w==",
|
||||
"version": "16.23.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.23.0.tgz",
|
||||
"integrity": "sha512-69T5aS2LUY306ekt1Q1oaSPwz/jaG9HjyMix3UMrai1iEbuOafBe2Dh8xlyczrxFAy89qcKyZWWtc42XLx3Bbw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3807,7 +3807,7 @@
|
|||
"debug": "^4.4.1",
|
||||
"fast-glob": "^3.3.3",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
"file-entry-cache": "^10.1.1",
|
||||
"file-entry-cache": "^10.1.3",
|
||||
"global-modules": "^2.0.0",
|
||||
"globby": "^11.1.0",
|
||||
"globjoin": "^0.1.4",
|
||||
|
@ -4110,9 +4110,9 @@
|
|||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -4124,9 +4124,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -4170,17 +4170,17 @@
|
|||
},
|
||||
"node_modules/vite": {
|
||||
"name": "rolldown-vite",
|
||||
"version": "7.0.9",
|
||||
"resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.0.9.tgz",
|
||||
"integrity": "sha512-RxVP6CY9CNCEM9UecdytqeADxOGSjgkfSE/eI986sM7I3/F09lQ9UfQo3y6W10ICBppKsEHe71NbCX/tirYDFg==",
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.0.12.tgz",
|
||||
"integrity": "sha512-Gr40FRnE98FwPJcMwcJgBwP6U7Qxw/VEtDsFdFjvGUTdgI/tTmF7z7dbVo/ajItM54G+Zo9w5BIrUmat6MbuWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.6",
|
||||
"lightningcss": "^1.30.1",
|
||||
"picomatch": "^4.0.2",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rolldown": "1.0.0-beta.27",
|
||||
"rolldown": "1.0.0-beta.30",
|
||||
"tinyglobby": "^0.2.14"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
@ -25,27 +25,27 @@
|
|||
"not dead"
|
||||
],
|
||||
"dependencies": {
|
||||
"ionicons": "~8.0.13",
|
||||
"ionicons": "~8.0.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"ol": "~10.6.1",
|
||||
"ol": "~10.6.0",
|
||||
"swiped-events": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.1.2",
|
||||
"@types/node": "~24.0.15",
|
||||
"browserslist": "~4.25.1",
|
||||
"browserslist-to-esbuild": "~2.1.1",
|
||||
"edge.js": "~6.2.1",
|
||||
"@biomejs/biome": "2.1.3",
|
||||
"@types/node": "~24.2.0",
|
||||
"browserslist": "~4.25.0",
|
||||
"browserslist-to-esbuild": "~2.1.0",
|
||||
"edge.js": "~6.2.0",
|
||||
"less": "~4.4.0",
|
||||
"lightningcss": "~1.30.1",
|
||||
"sharp": "~0.34.3",
|
||||
"lightningcss": "~1.30.0",
|
||||
"sharp": "~0.34.0",
|
||||
"sort-package-json": "~3.4.0",
|
||||
"stylelint": "~16.22.0",
|
||||
"stylelint-config-standard-less": "~3.0.1",
|
||||
"stylelint-prettier": "~5.0.3",
|
||||
"stylelint": "~16.23.0",
|
||||
"stylelint-config-standard-less": "~3.0.0",
|
||||
"stylelint-prettier": "~5.0.0",
|
||||
"svgo": "~4.0.0",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "npm:rolldown-vite@~7.0.9",
|
||||
"typescript": "~5.9.0",
|
||||
"vite": "npm:rolldown-vite@7.0.12",
|
||||
"vite-bundle-analyzer": "~1.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./00_toolkit.ts";
|
||||
import "./infinite_scroll.ts";
|
||||
import "./keyboard.ts";
|
||||
import "./mapresult.ts";
|
||||
import "./preferences.ts";
|
||||
import "./results.ts";
|
||||
import "./search.ts";
|
||||
import "./router.ts";
|
||||
import "./toolkit.ts";
|
||||
import "./listener.ts";
|
5
client/simple/src/js/core/listener.ts
Normal file
5
client/simple/src/js/core/listener.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { listen } from "./toolkit.ts";
|
||||
|
||||
listen("click", ".close", function (this: HTMLElement) {
|
||||
(this.parentNode as HTMLElement)?.classList.add("invisible");
|
||||
});
|
38
client/simple/src/js/core/router.ts
Normal file
38
client/simple/src/js/core/router.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { Endpoints, endpoint, ready, settings } from "./toolkit.ts";
|
||||
|
||||
ready(
|
||||
() => {
|
||||
import("../main/keyboard.ts");
|
||||
import("../main/search.ts");
|
||||
|
||||
if (settings.autocomplete) {
|
||||
import("../main/autocomplete.ts");
|
||||
}
|
||||
},
|
||||
{ on: [endpoint === Endpoints.index] }
|
||||
);
|
||||
|
||||
ready(
|
||||
() => {
|
||||
import("../main/keyboard.ts");
|
||||
import("../main/mapresult.ts");
|
||||
import("../main/results.ts");
|
||||
import("../main/search.ts");
|
||||
|
||||
if (settings.infinite_scroll) {
|
||||
import("../main/infinite_scroll.ts");
|
||||
}
|
||||
|
||||
if (settings.autocomplete) {
|
||||
import("../main/autocomplete.ts");
|
||||
}
|
||||
},
|
||||
{ on: [endpoint === Endpoints.results] }
|
||||
);
|
||||
|
||||
ready(
|
||||
() => {
|
||||
import("../main/preferences.ts");
|
||||
},
|
||||
{ on: [endpoint === Endpoints.preferences] }
|
||||
);
|
140
client/simple/src/js/core/toolkit.ts
Normal file
140
client/simple/src/js/core/toolkit.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
import type { KeyBindingLayout } from "../main/keyboard.ts";
|
||||
|
||||
// synced with searx/webapp.py get_client_settings
|
||||
type Settings = {
|
||||
advanced_search?: boolean;
|
||||
autocomplete?: string;
|
||||
autocomplete_min?: number;
|
||||
doi_resolver?: string;
|
||||
favicon_resolver?: string;
|
||||
hotkeys?: KeyBindingLayout;
|
||||
infinite_scroll?: boolean;
|
||||
method?: "GET" | "POST";
|
||||
query_in_title?: boolean;
|
||||
results_on_new_tab?: boolean;
|
||||
safesearch?: 0 | 1 | 2;
|
||||
search_on_category_select?: boolean;
|
||||
theme?: string;
|
||||
theme_static_path?: string;
|
||||
translations?: Record<string, string>;
|
||||
url_formatting?: "pretty" | "full" | "host";
|
||||
};
|
||||
|
||||
type HTTPOptions = {
|
||||
body?: BodyInit;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
type ReadyOptions = {
|
||||
// all values must be truthy for the callback to be executed
|
||||
on?: (boolean | undefined)[];
|
||||
};
|
||||
|
||||
type AssertElement = (element?: HTMLElement | null) => asserts element is HTMLElement;
|
||||
|
||||
export type EndpointsKeys = keyof typeof Endpoints;
|
||||
|
||||
export const Endpoints = {
|
||||
index: "index",
|
||||
results: "results",
|
||||
preferences: "preferences",
|
||||
unknown: "unknown"
|
||||
} as const;
|
||||
|
||||
export const mutable = {
|
||||
closeDetail: undefined as (() => void) | undefined,
|
||||
scrollPageToSelected: undefined as (() => void) | undefined,
|
||||
selectImage: undefined as ((resultElement: HTMLElement) => void) | undefined,
|
||||
selectNext: undefined as ((openDetailView?: boolean) => void) | undefined,
|
||||
selectPrevious: undefined as ((openDetailView?: boolean) => void) | undefined
|
||||
};
|
||||
|
||||
const getEndpoint = (): EndpointsKeys => {
|
||||
const metaEndpoint = document.querySelector('meta[name="endpoint"]')?.getAttribute("content");
|
||||
|
||||
if (metaEndpoint && metaEndpoint in Endpoints) {
|
||||
return metaEndpoint as EndpointsKeys;
|
||||
}
|
||||
|
||||
return Endpoints.unknown;
|
||||
};
|
||||
|
||||
const getSettings = (): Settings => {
|
||||
const settings = document.querySelector("script[client_settings]")?.getAttribute("client_settings");
|
||||
if (!settings) return {};
|
||||
|
||||
try {
|
||||
return JSON.parse(atob(settings));
|
||||
} catch (error) {
|
||||
console.error("Failed to load client_settings:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const assertElement: AssertElement = (element?: HTMLElement | null): asserts element is HTMLElement => {
|
||||
if (!element) {
|
||||
throw new Error("Bad assertion: DOM element not found");
|
||||
}
|
||||
};
|
||||
|
||||
export const http = async (method: string, url: string | URL, options?: HTTPOptions): Promise<Response> => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), options?.timeout ?? 30_000);
|
||||
|
||||
const res = await fetch(url, {
|
||||
body: options?.body,
|
||||
method: method,
|
||||
signal: controller.signal
|
||||
}).finally(() => clearTimeout(timeoutId));
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const listen = <K extends keyof DocumentEventMap, E extends HTMLElement>(
|
||||
type: string | K,
|
||||
target: string | Document | E,
|
||||
listener: (this: E, event: DocumentEventMap[K]) => void | Promise<void>,
|
||||
options?: AddEventListenerOptions
|
||||
): void => {
|
||||
if (typeof target !== "string") {
|
||||
target.addEventListener(type, listener as EventListener, options);
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
type,
|
||||
(event: Event) => {
|
||||
for (const node of event.composedPath()) {
|
||||
if (node instanceof HTMLElement && node.matches(target)) {
|
||||
try {
|
||||
listener.call(node as E, event as DocumentEventMap[K]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
export const ready = (callback: () => void, options?: ReadyOptions): void => {
|
||||
for (const condition of options?.on ?? []) {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState !== "loading") {
|
||||
callback();
|
||||
} else {
|
||||
listen("DOMContentLoaded", document, callback, { once: true });
|
||||
}
|
||||
};
|
||||
|
||||
export const endpoint: EndpointsKeys = getEndpoint();
|
||||
export const settings: Settings = getSettings();
|
|
@ -1,118 +0,0 @@
|
|||
import type { KeyBindingLayout } from "./keyboard.ts";
|
||||
|
||||
type Settings = {
|
||||
theme_static_path?: string;
|
||||
method?: string;
|
||||
hotkeys?: KeyBindingLayout;
|
||||
infinite_scroll?: boolean;
|
||||
autocomplete?: boolean;
|
||||
autocomplete_min?: number;
|
||||
search_on_category_select?: boolean;
|
||||
translations?: Record<string, string>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
type ReadyOptions = {
|
||||
// all values must be truthy for the callback to be executed
|
||||
on?: (boolean | undefined)[];
|
||||
};
|
||||
|
||||
const getEndpoint = (): string => {
|
||||
const endpointClass = Array.from(document.body.classList).find((className) => className.endsWith("_endpoint"));
|
||||
return endpointClass?.split("_")[0] ?? "";
|
||||
};
|
||||
|
||||
const getSettings = (): Settings => {
|
||||
const settings = document.querySelector("script[client_settings]")?.getAttribute("client_settings");
|
||||
if (!settings) return {};
|
||||
|
||||
try {
|
||||
return JSON.parse(atob(settings));
|
||||
} catch (error) {
|
||||
console.error("Failed to load client_settings:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
type AssertElement = (element?: Element | null) => asserts element is Element;
|
||||
export const assertElement: AssertElement = (element?: Element | null): asserts element is Element => {
|
||||
if (!element) {
|
||||
throw new Error("Bad assertion: DOM element not found");
|
||||
}
|
||||
};
|
||||
|
||||
export const searxng = {
|
||||
// dynamic functions
|
||||
closeDetail: undefined as (() => void) | undefined,
|
||||
scrollPageToSelected: undefined as (() => void) | undefined,
|
||||
selectImage: undefined as ((resultElement: Element) => void) | undefined,
|
||||
selectNext: undefined as ((openDetailView?: boolean) => void) | undefined,
|
||||
selectPrevious: undefined as ((openDetailView?: boolean) => void) | undefined,
|
||||
|
||||
endpoint: getEndpoint(),
|
||||
|
||||
http: async (method: string, url: string | URL, data?: BodyInit): Promise<Response> => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||
|
||||
const res = await fetch(url, {
|
||||
body: data,
|
||||
method,
|
||||
signal: controller.signal
|
||||
}).finally(() => clearTimeout(timeoutId));
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
listen: <K extends keyof DocumentEventMap, E extends Element>(
|
||||
type: string | K,
|
||||
target: string | Document | E,
|
||||
listener: (this: E, event: DocumentEventMap[K]) => void,
|
||||
options?: AddEventListenerOptions
|
||||
): void => {
|
||||
if (typeof target !== "string") {
|
||||
target.addEventListener(type, listener as EventListener, options);
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
type,
|
||||
(event: Event) => {
|
||||
for (const node of event.composedPath()) {
|
||||
if (node instanceof Element && node.matches(target)) {
|
||||
try {
|
||||
listener.call(node as E, event as DocumentEventMap[K]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
ready: (callback: () => void, options?: ReadyOptions): void => {
|
||||
for (const condition of options?.on ?? []) {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState !== "loading") {
|
||||
callback();
|
||||
} else {
|
||||
searxng.listen("DOMContentLoaded", document, callback, { once: true });
|
||||
}
|
||||
},
|
||||
|
||||
settings: getSettings()
|
||||
};
|
||||
|
||||
searxng.listen("click", ".close", function (this: Element) {
|
||||
(this.parentNode as Element)?.classList.add("invisible");
|
||||
});
|
129
client/simple/src/js/main/autocomplete.ts
Normal file
129
client/simple/src/js/main/autocomplete.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
import { assertElement, http, listen, settings } from "../core/toolkit.ts";
|
||||
|
||||
const fetchResults = async (qInput: HTMLInputElement, query: string): Promise<void> => {
|
||||
try {
|
||||
let res: Response;
|
||||
|
||||
if (settings.method === "GET") {
|
||||
res = await http("GET", `./autocompleter?q=${query}`);
|
||||
} else {
|
||||
res = await http("POST", "./autocompleter", { body: new URLSearchParams({ q: query }) });
|
||||
}
|
||||
|
||||
const results = await res.json();
|
||||
|
||||
const autocomplete = document.querySelector<HTMLElement>(".autocomplete");
|
||||
assertElement(autocomplete);
|
||||
|
||||
const autocompleteList = document.querySelector<HTMLUListElement>(".autocomplete ul");
|
||||
assertElement(autocompleteList);
|
||||
|
||||
autocomplete.classList.add("open");
|
||||
autocompleteList.replaceChildren();
|
||||
|
||||
// show an error message that no result was found
|
||||
if (results?.[1]?.length === 0) {
|
||||
const noItemFoundMessage = Object.assign(document.createElement("li"), {
|
||||
className: "no-item-found",
|
||||
textContent: settings.translations?.no_item_found ?? "No results found"
|
||||
});
|
||||
autocompleteList.append(noItemFoundMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const fragment = new DocumentFragment();
|
||||
|
||||
for (const result of results[1]) {
|
||||
const li = Object.assign(document.createElement("li"), { textContent: result });
|
||||
|
||||
listen("mousedown", li, () => {
|
||||
qInput.value = result;
|
||||
|
||||
const form = document.querySelector<HTMLFormElement>("#search");
|
||||
form?.submit();
|
||||
|
||||
autocomplete.classList.remove("open");
|
||||
});
|
||||
|
||||
fragment.append(li);
|
||||
}
|
||||
|
||||
autocompleteList.append(fragment);
|
||||
} catch (error) {
|
||||
console.error("Error fetching autocomplete results:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
assertElement(qInput);
|
||||
|
||||
let timeoutId: number;
|
||||
|
||||
listen("input", qInput, () => {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
const query = qInput.value;
|
||||
const minLength = settings.autocomplete_min ?? 2;
|
||||
|
||||
if (query.length < minLength) return;
|
||||
|
||||
timeoutId = window.setTimeout(async () => {
|
||||
if (query === qInput.value) {
|
||||
await fetchResults(qInput, query);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
const autocomplete: HTMLElement | null = document.querySelector<HTMLElement>(".autocomplete");
|
||||
const autocompleteList: HTMLUListElement | null = document.querySelector<HTMLUListElement>(".autocomplete ul");
|
||||
if (autocompleteList) {
|
||||
listen("keyup", qInput, (event: KeyboardEvent) => {
|
||||
const listItems = [...autocompleteList.children] as HTMLElement[];
|
||||
|
||||
const currentIndex = listItems.findIndex((item) => item.classList.contains("active"));
|
||||
let newCurrentIndex = -1;
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowUp": {
|
||||
const currentItem = listItems[currentIndex];
|
||||
if (currentItem && currentIndex >= 0) {
|
||||
currentItem.classList.remove("active");
|
||||
}
|
||||
// we need to add listItems.length to the index calculation here because the JavaScript modulos
|
||||
// operator doesn't work with negative numbers
|
||||
newCurrentIndex = (currentIndex - 1 + listItems.length) % listItems.length;
|
||||
break;
|
||||
}
|
||||
case "ArrowDown": {
|
||||
const currentItem = listItems[currentIndex];
|
||||
if (currentItem && currentIndex >= 0) {
|
||||
currentItem.classList.remove("active");
|
||||
}
|
||||
newCurrentIndex = (currentIndex + 1) % listItems.length;
|
||||
break;
|
||||
}
|
||||
case "Tab":
|
||||
case "Enter":
|
||||
if (autocomplete) {
|
||||
autocomplete.classList.remove("open");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (newCurrentIndex !== -1) {
|
||||
const selectedItem = listItems[newCurrentIndex];
|
||||
if (selectedItem) {
|
||||
selectedItem.classList.add("active");
|
||||
|
||||
if (!selectedItem.classList.contains("no-item-found")) {
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
if (qInput) {
|
||||
qInput.value = selectedItem.textContent ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { assertElement, searxng } from "./00_toolkit";
|
||||
import { assertElement, http, settings } from "../core/toolkit.ts";
|
||||
|
||||
const newLoadSpinner = (): HTMLDivElement => {
|
||||
return Object.assign(document.createElement("div"), {
|
||||
|
@ -13,12 +13,9 @@ const loadNextPage = async (onlyImages: boolean, callback: () => void): Promise<
|
|||
const form = document.querySelector<HTMLFormElement>("#pagination form.next_page");
|
||||
assertElement(form);
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
const action = searchForm.getAttribute("action");
|
||||
if (!action) {
|
||||
console.error("Form action not found");
|
||||
return;
|
||||
throw new Error("Form action not defined");
|
||||
}
|
||||
|
||||
const paginationElement = document.querySelector<HTMLElement>("#pagination");
|
||||
|
@ -27,7 +24,7 @@ const loadNextPage = async (onlyImages: boolean, callback: () => void): Promise<
|
|||
paginationElement.replaceChildren(newLoadSpinner());
|
||||
|
||||
try {
|
||||
const res = await searxng.http("POST", action, formData);
|
||||
const res = await http("POST", action, { body: new FormData(form) });
|
||||
const nextPage = await res.text();
|
||||
if (!nextPage) return;
|
||||
|
||||
|
@ -39,8 +36,7 @@ const loadNextPage = async (onlyImages: boolean, callback: () => void): Promise<
|
|||
|
||||
const urlsElement = document.querySelector<HTMLElement>("#urls");
|
||||
if (!urlsElement) {
|
||||
console.error("URLs element not found");
|
||||
return;
|
||||
throw new Error("URLs element not found");
|
||||
}
|
||||
|
||||
if (articleList.length > 0 && !onlyImages) {
|
||||
|
@ -59,7 +55,7 @@ const loadNextPage = async (onlyImages: boolean, callback: () => void): Promise<
|
|||
console.error("Error loading next page:", error);
|
||||
|
||||
const errorElement = Object.assign(document.createElement("div"), {
|
||||
textContent: searxng.settings.translations?.error_loading_next_page ?? "Error loading next page",
|
||||
textContent: settings.translations?.error_loading_next_page ?? "Error loading next page",
|
||||
className: "dialog-error"
|
||||
});
|
||||
errorElement.setAttribute("role", "alert");
|
||||
|
@ -67,42 +63,36 @@ const loadNextPage = async (onlyImages: boolean, callback: () => void): Promise<
|
|||
}
|
||||
};
|
||||
|
||||
searxng.ready(
|
||||
() => {
|
||||
const resultsElement = document.getElementById("results");
|
||||
if (!resultsElement) {
|
||||
console.error("Results element not found");
|
||||
return;
|
||||
}
|
||||
const resultsElement: HTMLElement | null = document.getElementById("results");
|
||||
if (!resultsElement) {
|
||||
throw new Error("Results element not found");
|
||||
}
|
||||
|
||||
const onlyImages = resultsElement.classList.contains("only_template_images");
|
||||
const observedSelector = "article.result:last-child";
|
||||
const onlyImages: boolean = resultsElement.classList.contains("only_template_images");
|
||||
const observedSelector = "article.result:last-child";
|
||||
|
||||
const intersectionObserveOptions: IntersectionObserverInit = {
|
||||
rootMargin: "320px"
|
||||
};
|
||||
const intersectionObserveOptions: IntersectionObserverInit = {
|
||||
rootMargin: "320px"
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(async (entries: IntersectionObserverEntry[]) => {
|
||||
const [paginationEntry] = entries;
|
||||
const observer: IntersectionObserver = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
|
||||
const [paginationEntry] = entries;
|
||||
|
||||
if (paginationEntry?.isIntersecting) {
|
||||
observer.unobserve(paginationEntry.target);
|
||||
if (paginationEntry?.isIntersecting) {
|
||||
observer.unobserve(paginationEntry.target);
|
||||
|
||||
await loadNextPage(onlyImages, () => {
|
||||
const nextObservedElement = document.querySelector<HTMLElement>(observedSelector);
|
||||
if (nextObservedElement) {
|
||||
observer.observe(nextObservedElement);
|
||||
}
|
||||
});
|
||||
loadNextPage(onlyImages, () => {
|
||||
const nextObservedElement = document.querySelector<HTMLElement>(observedSelector);
|
||||
if (nextObservedElement) {
|
||||
observer.observe(nextObservedElement);
|
||||
}
|
||||
}, intersectionObserveOptions);
|
||||
|
||||
const initialObservedElement = document.querySelector<HTMLElement>(observedSelector);
|
||||
if (initialObservedElement) {
|
||||
observer.observe(initialObservedElement);
|
||||
}
|
||||
},
|
||||
{
|
||||
on: [searxng.endpoint === "results", searxng.settings.infinite_scroll]
|
||||
}).then(() => {
|
||||
// wait until promise is resolved
|
||||
});
|
||||
}
|
||||
);
|
||||
}, intersectionObserveOptions);
|
||||
|
||||
const initialObservedElement: HTMLElement | null = document.querySelector<HTMLElement>(observedSelector);
|
||||
if (initialObservedElement) {
|
||||
observer.observe(initialObservedElement);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { assertElement, searxng } from "./00_toolkit.ts";
|
||||
import { assertElement, listen, mutable, settings } from "../core/toolkit.ts";
|
||||
|
||||
export type KeyBindingLayout = "default" | "vim";
|
||||
|
||||
|
@ -9,11 +9,13 @@ type KeyBinding = {
|
|||
cat: string;
|
||||
};
|
||||
|
||||
type HighlightResultElement = "down" | "up" | "visible" | "bottom" | "top";
|
||||
|
||||
/* common base for layouts */
|
||||
const baseKeyBinding: Record<string, KeyBinding> = {
|
||||
Escape: {
|
||||
key: "ESC",
|
||||
fun: (event) => removeFocus(event),
|
||||
fun: (event: KeyboardEvent) => removeFocus(event),
|
||||
des: "remove focus from the focused input",
|
||||
cat: "Control"
|
||||
},
|
||||
|
@ -145,12 +147,12 @@ const keyBindingLayouts: Record<KeyBindingLayout, Record<string, KeyBinding>> =
|
|||
}
|
||||
};
|
||||
|
||||
const keyBindings =
|
||||
searxng.settings.hotkeys && searxng.settings.hotkeys in keyBindingLayouts
|
||||
? keyBindingLayouts[searxng.settings.hotkeys]
|
||||
const keyBindings: Record<string, KeyBinding> =
|
||||
settings.hotkeys && settings.hotkeys in keyBindingLayouts
|
||||
? keyBindingLayouts[settings.hotkeys]
|
||||
: keyBindingLayouts.default;
|
||||
|
||||
const isElementInDetail = (element?: Element): boolean => {
|
||||
const isElementInDetail = (element?: HTMLElement): boolean => {
|
||||
const ancestor = element?.closest(".detail, .result");
|
||||
return ancestor?.classList.contains("detail") ?? false;
|
||||
};
|
||||
|
@ -159,12 +161,12 @@ const getResultElement = (element?: HTMLElement): HTMLElement | undefined => {
|
|||
return element?.closest(".result") ?? undefined;
|
||||
};
|
||||
|
||||
const isImageResult = (resultElement?: Element): boolean => {
|
||||
const isImageResult = (resultElement?: HTMLElement): boolean => {
|
||||
return resultElement?.classList.contains("result-images") ?? false;
|
||||
};
|
||||
|
||||
const highlightResult =
|
||||
(which: string | HTMLElement) =>
|
||||
(which: HighlightResultElement | HTMLElement) =>
|
||||
(noScroll?: boolean, keepFocus?: boolean): void => {
|
||||
let effectiveWhich = which;
|
||||
let current = document.querySelector<HTMLElement>(".result[data-vim-selected]");
|
||||
|
@ -210,7 +212,7 @@ const highlightResult =
|
|||
next = results[results.indexOf(current) - 1] || current;
|
||||
break;
|
||||
case "bottom":
|
||||
next = results[results.length - 1];
|
||||
next = results.at(-1);
|
||||
break;
|
||||
// biome-ignore lint/complexity/noUselessSwitchCase: fallthrough is intended
|
||||
case "top":
|
||||
|
@ -229,7 +231,7 @@ const highlightResult =
|
|||
}
|
||||
|
||||
if (!noScroll) {
|
||||
scrollPageToSelected();
|
||||
mutable.scrollPageToSelected?.();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -245,7 +247,7 @@ const removeFocus = (event: KeyboardEvent): void => {
|
|||
if (document.activeElement && (tagName === "input" || tagName === "select" || tagName === "textarea")) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
} else {
|
||||
searxng.closeDetail?.();
|
||||
mutable.closeDetail?.();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -256,23 +258,23 @@ const pageButtonClick = (css_selector: string): void => {
|
|||
}
|
||||
};
|
||||
|
||||
const GoToNextPage = () => {
|
||||
const GoToNextPage = (): void => {
|
||||
pageButtonClick('nav#pagination .next_page button[type="submit"]');
|
||||
};
|
||||
|
||||
const GoToPreviousPage = () => {
|
||||
const GoToPreviousPage = (): void => {
|
||||
pageButtonClick('nav#pagination .previous_page button[type="submit"]');
|
||||
};
|
||||
|
||||
const scrollPageToSelected = (): void => {
|
||||
mutable.scrollPageToSelected = (): void => {
|
||||
const sel = document.querySelector<HTMLElement>(".result[data-vim-selected]");
|
||||
if (!sel) return;
|
||||
|
||||
const wtop = document.documentElement.scrollTop || document.body.scrollTop,
|
||||
height = document.documentElement.clientHeight,
|
||||
etop = sel.offsetTop,
|
||||
ebot = etop + sel.clientHeight,
|
||||
offset = 120;
|
||||
const wtop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const height = document.documentElement.clientHeight;
|
||||
const etop = sel.offsetTop;
|
||||
const ebot = etop + sel.clientHeight;
|
||||
const offset = 120;
|
||||
|
||||
// first element ?
|
||||
if (!sel.previousElementSibling && ebot < height) {
|
||||
|
@ -297,7 +299,7 @@ const scrollPage = (amount: number): void => {
|
|||
highlightResult("visible")();
|
||||
};
|
||||
|
||||
const scrollPageTo = (position: number, nav: string): void => {
|
||||
const scrollPageTo = (position: number, nav: HighlightResultElement): void => {
|
||||
window.scrollTo(0, position);
|
||||
highlightResult(nav)();
|
||||
};
|
||||
|
@ -385,7 +387,10 @@ const initHelpContent = (divElement: HTMLElement, keyBindings: typeof baseKeyBin
|
|||
|
||||
const toggleHelp = (keyBindings: typeof baseKeyBinding): void => {
|
||||
let helpPanel = document.querySelector<HTMLElement>("#vim-hotkeys-help");
|
||||
if (!helpPanel) {
|
||||
if (helpPanel) {
|
||||
// toggle hidden
|
||||
helpPanel.classList.toggle("invisible");
|
||||
} else {
|
||||
// first call
|
||||
helpPanel = Object.assign(document.createElement("div"), {
|
||||
id: "vim-hotkeys-help",
|
||||
|
@ -396,9 +401,6 @@ const toggleHelp = (keyBindings: typeof baseKeyBinding): void => {
|
|||
if (body) {
|
||||
body.appendChild(helpPanel);
|
||||
}
|
||||
} else {
|
||||
// toggle hidden
|
||||
helpPanel.classList.toggle("invisible");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -412,56 +414,53 @@ const copyURLToClipboard = async (): Promise<void> => {
|
|||
}
|
||||
};
|
||||
|
||||
searxng.ready(() => {
|
||||
searxng.listen("click", ".result", function (this: HTMLElement, event: Event) {
|
||||
if (!isElementInDetail(event.target as HTMLElement)) {
|
||||
highlightResult(this)(true, true);
|
||||
listen("click", ".result", function (this: HTMLElement, event: PointerEvent) {
|
||||
if (!isElementInDetail(event.target as HTMLElement)) {
|
||||
highlightResult(this)(true, true);
|
||||
|
||||
const resultElement = getResultElement(event.target as HTMLElement);
|
||||
|
||||
if (resultElement && isImageResult(resultElement)) {
|
||||
event.preventDefault();
|
||||
mutable.selectImage?.(resultElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME: Focus might also trigger Pointer event ^^^
|
||||
listen(
|
||||
"focus",
|
||||
".result a",
|
||||
(event: FocusEvent) => {
|
||||
if (!isElementInDetail(event.target as HTMLElement)) {
|
||||
const resultElement = getResultElement(event.target as HTMLElement);
|
||||
|
||||
if (resultElement && !resultElement.hasAttribute("data-vim-selected")) {
|
||||
highlightResult(resultElement)(true);
|
||||
}
|
||||
|
||||
if (resultElement && isImageResult(resultElement)) {
|
||||
event.preventDefault();
|
||||
searxng.selectImage?.(resultElement);
|
||||
mutable.selectImage?.(resultElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
{ capture: true }
|
||||
);
|
||||
|
||||
searxng.listen(
|
||||
"focus",
|
||||
".result a",
|
||||
(event: Event) => {
|
||||
if (!isElementInDetail(event.target as HTMLElement)) {
|
||||
const resultElement = getResultElement(event.target as HTMLElement);
|
||||
listen("keydown", document, (event: KeyboardEvent) => {
|
||||
// check for modifiers so we don't break browser's hotkeys
|
||||
if (Object.hasOwn(keyBindings, event.key) && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) {
|
||||
const tagName = (event.target as HTMLElement)?.tagName?.toLowerCase();
|
||||
|
||||
if (resultElement && !resultElement.getAttribute("data-vim-selected")) {
|
||||
highlightResult(resultElement)(true);
|
||||
}
|
||||
|
||||
if (resultElement && isImageResult(resultElement)) {
|
||||
searxng.selectImage?.(resultElement);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ capture: true }
|
||||
);
|
||||
|
||||
searxng.listen("keydown", document, (event: KeyboardEvent) => {
|
||||
// check for modifiers so we don't break browser's hotkeys
|
||||
if (Object.hasOwn(keyBindings, event.key) && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) {
|
||||
const tagName = (event.target as Element)?.tagName?.toLowerCase();
|
||||
|
||||
if (event.key === "Escape") {
|
||||
keyBindings[event.key]?.fun(event);
|
||||
} else {
|
||||
if (event.target === document.body || tagName === "a" || tagName === "button") {
|
||||
event.preventDefault();
|
||||
keyBindings[event.key]?.fun(event);
|
||||
}
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
keyBindings[event.key]?.fun(event);
|
||||
} else if (event.target === document.body || tagName === "a" || tagName === "button") {
|
||||
event.preventDefault();
|
||||
keyBindings[event.key]?.fun(event);
|
||||
}
|
||||
});
|
||||
|
||||
searxng.scrollPageToSelected = scrollPageToSelected;
|
||||
searxng.selectNext = highlightResult("down");
|
||||
searxng.selectPrevious = highlightResult("up");
|
||||
}
|
||||
});
|
||||
|
||||
mutable.selectNext = highlightResult("down");
|
||||
mutable.selectPrevious = highlightResult("up");
|
||||
|
|
|
@ -1,89 +1,84 @@
|
|||
import { searxng } from "./00_toolkit.ts";
|
||||
import { listen } from "../core/toolkit.ts";
|
||||
|
||||
searxng.ready(
|
||||
() => {
|
||||
searxng.listen("click", ".searxng_init_map", async function (this: HTMLElement, event: Event) {
|
||||
event.preventDefault();
|
||||
this.classList.remove("searxng_init_map");
|
||||
listen("click", ".searxng_init_map", async function (this: HTMLElement, event: Event) {
|
||||
event.preventDefault();
|
||||
this.classList.remove("searxng_init_map");
|
||||
|
||||
const {
|
||||
View,
|
||||
OlMap,
|
||||
TileLayer,
|
||||
VectorLayer,
|
||||
OSM,
|
||||
VectorSource,
|
||||
Style,
|
||||
Stroke,
|
||||
Fill,
|
||||
Circle,
|
||||
fromLonLat,
|
||||
GeoJSON,
|
||||
Feature,
|
||||
Point
|
||||
} = await import("../pkg/ol.ts");
|
||||
import("ol/ol.css");
|
||||
const {
|
||||
View,
|
||||
OlMap,
|
||||
TileLayer,
|
||||
VectorLayer,
|
||||
OSM,
|
||||
VectorSource,
|
||||
Style,
|
||||
Stroke,
|
||||
Fill,
|
||||
Circle,
|
||||
fromLonLat,
|
||||
GeoJSON,
|
||||
Feature,
|
||||
Point
|
||||
} = await import("../pkg/ol.ts");
|
||||
import("ol/ol.css");
|
||||
|
||||
const { leafletTarget: target, mapLon, mapLat, mapGeojson } = this.dataset;
|
||||
const { leafletTarget: target, mapLon, mapLat, mapGeojson } = this.dataset;
|
||||
|
||||
const lon = parseFloat(mapLon || "0");
|
||||
const lat = parseFloat(mapLat || "0");
|
||||
const view = new View({ maxZoom: 16, enableRotation: false });
|
||||
const map = new OlMap({
|
||||
target,
|
||||
layers: [new TileLayer({ source: new OSM({ maxZoom: 16 }) })],
|
||||
view
|
||||
const lon = Number.parseFloat(mapLon || "0");
|
||||
const lat = Number.parseFloat(mapLat || "0");
|
||||
const view = new View({ maxZoom: 16, enableRotation: false });
|
||||
const map = new OlMap({
|
||||
target: target,
|
||||
layers: [new TileLayer({ source: new OSM({ maxZoom: 16 }) })],
|
||||
view: view
|
||||
});
|
||||
|
||||
try {
|
||||
const markerSource = new VectorSource({
|
||||
features: [
|
||||
new Feature({
|
||||
geometry: new Point(fromLonLat([lon, lat]))
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const markerLayer = new VectorLayer({
|
||||
source: markerSource,
|
||||
style: new Style({
|
||||
image: new Circle({
|
||||
radius: 6,
|
||||
fill: new Fill({ color: "#3050ff" })
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
map.addLayer(markerLayer);
|
||||
} catch (error) {
|
||||
console.error("Failed to create marker layer:", error);
|
||||
}
|
||||
|
||||
if (mapGeojson) {
|
||||
try {
|
||||
const geoSource = new VectorSource({
|
||||
features: new GeoJSON().readFeatures(JSON.parse(mapGeojson), {
|
||||
dataProjection: "EPSG:4326",
|
||||
featureProjection: "EPSG:3857"
|
||||
})
|
||||
});
|
||||
|
||||
try {
|
||||
const markerSource = new VectorSource({
|
||||
features: [
|
||||
new Feature({
|
||||
geometry: new Point(fromLonLat([lon, lat]))
|
||||
})
|
||||
]
|
||||
});
|
||||
const geoLayer = new VectorLayer({
|
||||
source: geoSource,
|
||||
style: new Style({
|
||||
stroke: new Stroke({ color: "#3050ff", width: 2 }),
|
||||
fill: new Fill({ color: "#3050ff33" })
|
||||
})
|
||||
});
|
||||
|
||||
const markerLayer = new VectorLayer({
|
||||
source: markerSource,
|
||||
style: new Style({
|
||||
image: new Circle({
|
||||
radius: 6,
|
||||
fill: new Fill({ color: "#3050ff" })
|
||||
})
|
||||
})
|
||||
});
|
||||
map.addLayer(geoLayer);
|
||||
|
||||
map.addLayer(markerLayer);
|
||||
} catch (error) {
|
||||
console.error("Failed to create marker layer:", error);
|
||||
}
|
||||
|
||||
if (mapGeojson) {
|
||||
try {
|
||||
const geoSource = new VectorSource({
|
||||
features: new GeoJSON().readFeatures(JSON.parse(mapGeojson), {
|
||||
dataProjection: "EPSG:4326",
|
||||
featureProjection: "EPSG:3857"
|
||||
})
|
||||
});
|
||||
|
||||
const geoLayer = new VectorLayer({
|
||||
source: geoSource,
|
||||
style: new Style({
|
||||
stroke: new Stroke({ color: "#3050ff", width: 2 }),
|
||||
fill: new Fill({ color: "#3050ff33" })
|
||||
})
|
||||
});
|
||||
|
||||
map.addLayer(geoLayer);
|
||||
|
||||
view.fit(geoSource.getExtent(), { padding: [20, 20, 20, 20] });
|
||||
} catch (error) {
|
||||
console.error("Failed to create GeoJSON layer:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
{ on: [searxng.endpoint === "results"] }
|
||||
);
|
||||
view.fit(geoSource.getExtent(), { padding: [20, 20, 20, 20] });
|
||||
} catch (error) {
|
||||
console.error("Failed to create GeoJSON layer:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { searxng } from "./00_toolkit.ts";
|
||||
import { http, listen, settings } from "../core/toolkit.ts";
|
||||
|
||||
let engineDescriptions: Record<string, [string, string]> | undefined;
|
||||
|
||||
const loadEngineDescriptions = async (): Promise<void> => {
|
||||
let engineDescriptions: Record<string, [string, string]> | null = null;
|
||||
if (engineDescriptions) return;
|
||||
try {
|
||||
const res = await searxng.http("GET", "engine_descriptions.json");
|
||||
const res = await http("GET", "engine_descriptions.json");
|
||||
engineDescriptions = await res.json();
|
||||
} catch (error) {
|
||||
console.error("Error fetching engineDescriptions:", error);
|
||||
|
@ -12,7 +14,7 @@ const loadEngineDescriptions = async (): Promise<void> => {
|
|||
|
||||
for (const [engine_name, [description, source]] of Object.entries(engineDescriptions)) {
|
||||
const elements = document.querySelectorAll<HTMLElement>(`[data-engine-name="${engine_name}"] .engine-description`);
|
||||
const sourceText = ` (<i>${searxng.settings.translations?.Source}: ${source}</i>)`;
|
||||
const sourceText = ` (<i>${settings.translations?.Source}: ${source}</i>)`;
|
||||
|
||||
for (const element of elements) {
|
||||
element.innerHTML = description + sourceText;
|
||||
|
@ -29,43 +31,38 @@ const toggleEngines = (enable: boolean, engineToggles: NodeListOf<HTMLInputEleme
|
|||
}
|
||||
};
|
||||
|
||||
searxng.ready(
|
||||
() => {
|
||||
const engineElements = document.querySelectorAll<HTMLElement>("[data-engine-name]");
|
||||
for (const engineElement of engineElements) {
|
||||
searxng.listen("mouseenter", engineElement, loadEngineDescriptions);
|
||||
}
|
||||
const engineElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>("[data-engine-name]");
|
||||
for (const engineElement of engineElements) {
|
||||
listen("mouseenter", engineElement, loadEngineDescriptions);
|
||||
}
|
||||
|
||||
const engineToggles = document.querySelectorAll<HTMLInputElement>(
|
||||
"tbody input[type=checkbox][class~=checkbox-onoff]"
|
||||
);
|
||||
|
||||
const enableAllEngines = document.querySelectorAll<HTMLElement>(".enable-all-engines");
|
||||
for (const engine of enableAllEngines) {
|
||||
searxng.listen("click", engine, () => toggleEngines(true, engineToggles));
|
||||
}
|
||||
|
||||
const disableAllEngines = document.querySelectorAll<HTMLElement>(".disable-all-engines");
|
||||
for (const engine of disableAllEngines) {
|
||||
searxng.listen("click", engine, () => toggleEngines(false, engineToggles));
|
||||
}
|
||||
|
||||
const copyHashButton = document.querySelector<HTMLElement>("#copy-hash");
|
||||
if (copyHashButton) {
|
||||
searxng.listen("click", copyHashButton, async (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { copiedText, hash } = copyHashButton.dataset;
|
||||
if (!copiedText || !hash) return;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(hash);
|
||||
copyHashButton.innerText = copiedText;
|
||||
} catch (error) {
|
||||
console.error("Failed to copy hash:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ on: [searxng.endpoint === "preferences"] }
|
||||
const engineToggles: NodeListOf<HTMLInputElement> = document.querySelectorAll<HTMLInputElement>(
|
||||
"tbody input[type=checkbox][class~=checkbox-onoff]"
|
||||
);
|
||||
|
||||
const enableAllEngines: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(".enable-all-engines");
|
||||
for (const engine of enableAllEngines) {
|
||||
listen("click", engine, () => toggleEngines(true, engineToggles));
|
||||
}
|
||||
|
||||
const disableAllEngines: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(".disable-all-engines");
|
||||
for (const engine of disableAllEngines) {
|
||||
listen("click", engine, () => toggleEngines(false, engineToggles));
|
||||
}
|
||||
|
||||
const copyHashButton: HTMLElement | null = document.querySelector<HTMLElement>("#copy-hash");
|
||||
if (copyHashButton) {
|
||||
listen("click", copyHashButton, async (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { copiedText, hash } = copyHashButton.dataset;
|
||||
if (!(copiedText && hash)) return;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(hash);
|
||||
copyHashButton.innerText = copiedText;
|
||||
} catch (error) {
|
||||
console.error("Failed to copy hash:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,181 +1,175 @@
|
|||
import "../../../node_modules/swiped-events/src/swiped-events.js";
|
||||
import { assertElement, searxng } from "./00_toolkit.ts";
|
||||
import { assertElement, listen, mutable, settings } from "../core/toolkit.ts";
|
||||
|
||||
const loadImage = (imgSrc: string, onSuccess: () => void): void => {
|
||||
// singleton image object, which is used for all loading processes of a detailed image
|
||||
const imgLoader = new Image();
|
||||
let imgTimeoutID: number;
|
||||
|
||||
// set handlers in the on-properties
|
||||
imgLoader.onload = () => {
|
||||
onSuccess();
|
||||
};
|
||||
const imageLoader = (resultElement: HTMLElement): void => {
|
||||
if (imgTimeoutID) clearTimeout(imgTimeoutID);
|
||||
|
||||
imgLoader.src = imgSrc;
|
||||
const imgElement = resultElement.querySelector<HTMLImageElement>(".result-images-source img");
|
||||
if (!imgElement) return;
|
||||
|
||||
// use thumbnail until full image loads
|
||||
const thumbnail = resultElement.querySelector<HTMLImageElement>(".image_thumbnail");
|
||||
if (thumbnail) {
|
||||
if (thumbnail.src === `${settings.theme_static_path}/img/img_load_error.svg`) return;
|
||||
|
||||
imgElement.onerror = (): void => {
|
||||
imgElement.src = thumbnail.src;
|
||||
};
|
||||
|
||||
imgElement.src = thumbnail.src;
|
||||
}
|
||||
|
||||
const imgSource = imgElement.getAttribute("data-src");
|
||||
if (!imgSource) return;
|
||||
|
||||
// unsafe nodejs specific, cast to https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout#return_value
|
||||
// https://github.com/searxng/searxng/pull/5073#discussion_r2265767231
|
||||
imgTimeoutID = setTimeout(() => {
|
||||
imgElement.src = imgSource;
|
||||
imgElement.removeAttribute("data-src");
|
||||
}, 1000) as unknown as number;
|
||||
};
|
||||
|
||||
searxng.ready(
|
||||
const imageThumbnails: NodeListOf<HTMLImageElement> =
|
||||
document.querySelectorAll<HTMLImageElement>("#urls img.image_thumbnail");
|
||||
for (const thumbnail of imageThumbnails) {
|
||||
if (thumbnail.complete && thumbnail.naturalWidth === 0) {
|
||||
thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
|
||||
}
|
||||
|
||||
thumbnail.onerror = (): void => {
|
||||
thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
|
||||
};
|
||||
}
|
||||
|
||||
const copyUrlButton: HTMLButtonElement | null =
|
||||
document.querySelector<HTMLButtonElement>("#search_url button#copy_url");
|
||||
copyUrlButton?.style.setProperty("display", "block");
|
||||
|
||||
mutable.selectImage = (resultElement: HTMLElement): void => {
|
||||
// add a class that can be evaluated in the CSS and indicates that the
|
||||
// detail view is open
|
||||
const resultsElement = document.getElementById("results");
|
||||
resultsElement?.classList.add("image-detail-open");
|
||||
|
||||
// add a hash to the browser history so that pressing back doesn't return
|
||||
// to the previous page this allows us to dismiss the image details on
|
||||
// pressing the back button on mobile devices
|
||||
window.location.hash = "#image-viewer";
|
||||
|
||||
mutable.scrollPageToSelected?.();
|
||||
|
||||
// if there is no element given by the caller, stop here
|
||||
if (!resultElement) return;
|
||||
|
||||
imageLoader(resultElement);
|
||||
};
|
||||
|
||||
mutable.closeDetail = (): void => {
|
||||
const resultsElement = document.getElementById("results");
|
||||
resultsElement?.classList.remove("image-detail-open");
|
||||
|
||||
// remove #image-viewer hash from url by navigating back
|
||||
if (window.location.hash === "#image-viewer") {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
mutable.scrollPageToSelected?.();
|
||||
};
|
||||
|
||||
listen("click", ".btn-collapse", function (this: HTMLElement) {
|
||||
const btnLabelCollapsed = this.getAttribute("data-btn-text-collapsed");
|
||||
const btnLabelNotCollapsed = this.getAttribute("data-btn-text-not-collapsed");
|
||||
const target = this.getAttribute("data-target");
|
||||
|
||||
if (!(target && btnLabelCollapsed && btnLabelNotCollapsed)) return;
|
||||
|
||||
const targetElement = document.querySelector<HTMLElement>(target);
|
||||
assertElement(targetElement);
|
||||
|
||||
const isCollapsed = this.classList.contains("collapsed");
|
||||
const newLabel = isCollapsed ? btnLabelNotCollapsed : btnLabelCollapsed;
|
||||
const oldLabel = isCollapsed ? btnLabelCollapsed : btnLabelNotCollapsed;
|
||||
|
||||
this.innerHTML = this.innerHTML.replace(oldLabel, newLabel);
|
||||
this.classList.toggle("collapsed");
|
||||
|
||||
targetElement.classList.toggle("invisible");
|
||||
});
|
||||
|
||||
listen("click", ".media-loader", function (this: HTMLElement) {
|
||||
const target = this.getAttribute("data-target");
|
||||
if (!target) return;
|
||||
|
||||
const iframeLoad = document.querySelector<HTMLIFrameElement>(`${target} > iframe`);
|
||||
assertElement(iframeLoad);
|
||||
|
||||
const srctest = iframeLoad.getAttribute("src");
|
||||
if (!srctest) {
|
||||
const dataSrc = iframeLoad.getAttribute("data-src");
|
||||
if (dataSrc) {
|
||||
iframeLoad.setAttribute("src", dataSrc);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
listen("click", "#copy_url", async function (this: HTMLElement) {
|
||||
const target = this.parentElement?.querySelector<HTMLPreElement>("pre");
|
||||
assertElement(target);
|
||||
|
||||
await navigator.clipboard.writeText(target.innerText);
|
||||
const copiedText = this.dataset.copiedText;
|
||||
if (copiedText) {
|
||||
this.innerText = copiedText;
|
||||
}
|
||||
});
|
||||
|
||||
listen("click", ".result-detail-close", (event: Event) => {
|
||||
event.preventDefault();
|
||||
mutable.closeDetail?.();
|
||||
});
|
||||
|
||||
listen("click", ".result-detail-previous", (event: Event) => {
|
||||
event.preventDefault();
|
||||
mutable.selectPrevious?.(false);
|
||||
});
|
||||
|
||||
listen("click", ".result-detail-next", (event: Event) => {
|
||||
event.preventDefault();
|
||||
mutable.selectNext?.(false);
|
||||
});
|
||||
|
||||
// listen for the back button to be pressed and dismiss the image details when called
|
||||
window.addEventListener("hashchange", () => {
|
||||
if (window.location.hash !== "#image-viewer") {
|
||||
mutable.closeDetail?.();
|
||||
}
|
||||
});
|
||||
|
||||
const swipeHorizontal: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(".swipe-horizontal");
|
||||
for (const element of swipeHorizontal) {
|
||||
listen("swiped-left", element, () => {
|
||||
mutable.selectNext?.(false);
|
||||
});
|
||||
|
||||
listen("swiped-right", element, () => {
|
||||
mutable.selectPrevious?.(false);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"scroll",
|
||||
() => {
|
||||
const imageThumbnails = document.querySelectorAll<HTMLImageElement>("#urls img.image_thumbnail");
|
||||
for (const thumbnail of imageThumbnails) {
|
||||
if (thumbnail.complete && thumbnail.naturalWidth === 0) {
|
||||
thumbnail.src = `${searxng.settings.theme_static_path}/img/img_load_error.svg`;
|
||||
}
|
||||
const backToTopElement = document.getElementById("backToTop");
|
||||
const resultsElement = document.getElementById("results");
|
||||
|
||||
thumbnail.onerror = () => {
|
||||
thumbnail.src = `${searxng.settings.theme_static_path}/img/img_load_error.svg`;
|
||||
};
|
||||
if (backToTopElement && resultsElement) {
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const isScrolling = scrollTop >= 100;
|
||||
resultsElement.classList.toggle("scrolling", isScrolling);
|
||||
}
|
||||
|
||||
const copyUrlButton = document.querySelector<HTMLButtonElement>("#search_url button#copy_url");
|
||||
copyUrlButton?.style.setProperty("display", "block");
|
||||
|
||||
searxng.listen("click", ".btn-collapse", function (this: HTMLElement) {
|
||||
const btnLabelCollapsed = this.getAttribute("data-btn-text-collapsed");
|
||||
const btnLabelNotCollapsed = this.getAttribute("data-btn-text-not-collapsed");
|
||||
const target = this.getAttribute("data-target");
|
||||
|
||||
if (!target || !btnLabelCollapsed || !btnLabelNotCollapsed) return;
|
||||
|
||||
const targetElement = document.querySelector<HTMLElement>(target);
|
||||
assertElement(targetElement);
|
||||
|
||||
const isCollapsed = this.classList.contains("collapsed");
|
||||
const newLabel = isCollapsed ? btnLabelNotCollapsed : btnLabelCollapsed;
|
||||
const oldLabel = isCollapsed ? btnLabelCollapsed : btnLabelNotCollapsed;
|
||||
|
||||
this.innerHTML = this.innerHTML.replace(oldLabel, newLabel);
|
||||
this.classList.toggle("collapsed");
|
||||
|
||||
targetElement.classList.toggle("invisible");
|
||||
});
|
||||
|
||||
searxng.listen("click", ".media-loader", function (this: HTMLElement) {
|
||||
const target = this.getAttribute("data-target");
|
||||
if (!target) return;
|
||||
|
||||
const iframeLoad = document.querySelector<HTMLIFrameElement>(`${target} > iframe`);
|
||||
assertElement(iframeLoad);
|
||||
|
||||
const srctest = iframeLoad.getAttribute("src");
|
||||
if (!srctest) {
|
||||
const dataSrc = iframeLoad.getAttribute("data-src");
|
||||
if (dataSrc) {
|
||||
iframeLoad.setAttribute("src", dataSrc);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searxng.listen("click", "#copy_url", async function (this: HTMLElement) {
|
||||
const target = this.parentElement?.querySelector<HTMLPreElement>("pre");
|
||||
assertElement(target);
|
||||
|
||||
await navigator.clipboard.writeText(target.innerText);
|
||||
const copiedText = this.dataset.copiedText;
|
||||
if (copiedText) {
|
||||
this.innerText = copiedText;
|
||||
}
|
||||
});
|
||||
|
||||
searxng.selectImage = (resultElement: Element): void => {
|
||||
// add a class that can be evaluated in the CSS and indicates that the
|
||||
// detail view is open
|
||||
const resultsElement = document.getElementById("results");
|
||||
resultsElement?.classList.add("image-detail-open");
|
||||
|
||||
// add a hash to the browser history so that pressing back doesn't return
|
||||
// to the previous page this allows us to dismiss the image details on
|
||||
// pressing the back button on mobile devices
|
||||
window.location.hash = "#image-viewer";
|
||||
|
||||
searxng.scrollPageToSelected?.();
|
||||
|
||||
// if there is no element given by the caller, stop here
|
||||
if (!resultElement) return;
|
||||
|
||||
// find image element, if there is none, stop here
|
||||
const img = resultElement.querySelector<HTMLImageElement>(".result-images-source img");
|
||||
if (!img) return;
|
||||
|
||||
// <img src="" data-src="http://example.org/image.jpg">
|
||||
const src = img.getAttribute("data-src");
|
||||
if (!src) return;
|
||||
|
||||
// use thumbnail until full image loads
|
||||
const thumbnail = resultElement.querySelector<HTMLImageElement>(".image_thumbnail");
|
||||
if (thumbnail) {
|
||||
img.src = thumbnail.src;
|
||||
}
|
||||
|
||||
// load full size image
|
||||
loadImage(src, () => {
|
||||
img.src = src;
|
||||
img.onerror = () => {
|
||||
img.src = `${searxng.settings.theme_static_path}/img/img_load_error.svg`;
|
||||
};
|
||||
|
||||
img.removeAttribute("data-src");
|
||||
});
|
||||
};
|
||||
|
||||
searxng.closeDetail = (): void => {
|
||||
const resultsElement = document.getElementById("results");
|
||||
resultsElement?.classList.remove("image-detail-open");
|
||||
|
||||
// remove #image-viewer hash from url by navigating back
|
||||
if (window.location.hash === "#image-viewer") {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
searxng.scrollPageToSelected?.();
|
||||
};
|
||||
|
||||
searxng.listen("click", ".result-detail-close", (event: Event) => {
|
||||
event.preventDefault();
|
||||
searxng.closeDetail?.();
|
||||
});
|
||||
|
||||
searxng.listen("click", ".result-detail-previous", (event: Event) => {
|
||||
event.preventDefault();
|
||||
searxng.selectPrevious?.(false);
|
||||
});
|
||||
|
||||
searxng.listen("click", ".result-detail-next", (event: Event) => {
|
||||
event.preventDefault();
|
||||
searxng.selectNext?.(false);
|
||||
});
|
||||
|
||||
// listen for the back button to be pressed and dismiss the image details when called
|
||||
window.addEventListener("hashchange", () => {
|
||||
if (window.location.hash !== "#image-viewer") {
|
||||
searxng.closeDetail?.();
|
||||
}
|
||||
});
|
||||
|
||||
const swipeHorizontal = document.querySelectorAll<HTMLElement>(".swipe-horizontal");
|
||||
for (const element of swipeHorizontal) {
|
||||
searxng.listen("swiped-left", element, () => {
|
||||
searxng.selectNext?.(false);
|
||||
});
|
||||
|
||||
searxng.listen("swiped-right", element, () => {
|
||||
searxng.selectPrevious?.(false);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"scroll",
|
||||
() => {
|
||||
const backToTopElement = document.getElementById("backToTop");
|
||||
const resultsElement = document.getElementById("results");
|
||||
|
||||
if (backToTopElement && resultsElement) {
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const isScrolling = scrollTop >= 100;
|
||||
resultsElement.classList.toggle("scrolling", isScrolling);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
{ on: [searxng.endpoint === "results"] }
|
||||
true
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { assertElement, searxng } from "./00_toolkit.ts";
|
||||
import { assertElement, listen, settings } from "../core/toolkit.ts";
|
||||
|
||||
const submitIfQuery = (qInput: HTMLInputElement): void => {
|
||||
if (qInput.value.length > 0) {
|
||||
|
@ -17,217 +17,88 @@ const createClearButton = (qInput: HTMLInputElement): void => {
|
|||
|
||||
updateClearButton(qInput, cs);
|
||||
|
||||
searxng.listen("click", cs, (event: MouseEvent) => {
|
||||
listen("click", cs, (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
qInput.value = "";
|
||||
qInput.focus();
|
||||
updateClearButton(qInput, cs);
|
||||
});
|
||||
|
||||
searxng.listen("input", qInput, () => updateClearButton(qInput, cs), { passive: true });
|
||||
listen("input", qInput, () => updateClearButton(qInput, cs), { passive: true });
|
||||
};
|
||||
|
||||
const fetchResults = async (qInput: HTMLInputElement, query: string): Promise<void> => {
|
||||
try {
|
||||
let res: Response;
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
assertElement(qInput);
|
||||
|
||||
if (searxng.settings.method === "GET") {
|
||||
res = await searxng.http("GET", `./autocompleter?q=${query}`);
|
||||
} else {
|
||||
res = await searxng.http("POST", "./autocompleter", new URLSearchParams({ q: query }));
|
||||
}
|
||||
const isMobile: boolean = window.matchMedia("(max-width: 50em)").matches;
|
||||
const isResultsPage: boolean = document.querySelector("main")?.id === "main_results";
|
||||
|
||||
const results = await res.json();
|
||||
// focus search input on large screens
|
||||
if (!(isMobile || isResultsPage)) {
|
||||
qInput.focus();
|
||||
}
|
||||
|
||||
const autocomplete = document.querySelector<HTMLElement>(".autocomplete");
|
||||
assertElement(autocomplete);
|
||||
createClearButton(qInput);
|
||||
|
||||
const autocompleteList = document.querySelector<HTMLUListElement>(".autocomplete ul");
|
||||
assertElement(autocompleteList);
|
||||
// Additionally to searching when selecting a new category, we also
|
||||
// automatically start a new search request when the user changes a search
|
||||
// filter (safesearch, time range or language) (this requires JavaScript
|
||||
// though)
|
||||
if (
|
||||
settings.search_on_category_select &&
|
||||
// If .search_filters is undefined (invisible) we are on the homepage and
|
||||
// hence don't have to set any listeners
|
||||
document.querySelector(".search_filters")
|
||||
) {
|
||||
const safesearchElement = document.getElementById("safesearch");
|
||||
if (safesearchElement) {
|
||||
listen("change", safesearchElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
autocomplete.classList.add("open");
|
||||
autocompleteList.replaceChildren();
|
||||
const timeRangeElement = document.getElementById("time_range");
|
||||
if (timeRangeElement) {
|
||||
listen("change", timeRangeElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
// show an error message that no result was found
|
||||
if (!results?.[1]?.length) {
|
||||
const noItemFoundMessage = Object.assign(document.createElement("li"), {
|
||||
className: "no-item-found",
|
||||
textContent: searxng.settings.translations?.no_item_found ?? "No results found"
|
||||
});
|
||||
autocompleteList.append(noItemFoundMessage);
|
||||
const languageElement = document.getElementById("language");
|
||||
if (languageElement) {
|
||||
listen("change", languageElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
}
|
||||
|
||||
const categoryButtons: HTMLButtonElement[] = [
|
||||
...document.querySelectorAll<HTMLButtonElement>("button.category_button")
|
||||
];
|
||||
for (const button of categoryButtons) {
|
||||
listen("click", button, (event: MouseEvent) => {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
button.classList.toggle("selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const fragment = new DocumentFragment();
|
||||
|
||||
for (const result of results[1]) {
|
||||
const li = Object.assign(document.createElement("li"), { textContent: result });
|
||||
|
||||
searxng.listen("mousedown", li, () => {
|
||||
qInput.value = result;
|
||||
|
||||
const form = document.querySelector<HTMLFormElement>("#search");
|
||||
form?.submit();
|
||||
|
||||
autocomplete.classList.remove("open");
|
||||
});
|
||||
|
||||
fragment.append(li);
|
||||
// deselect all other categories
|
||||
for (const categoryButton of categoryButtons) {
|
||||
categoryButton.classList.toggle("selected", categoryButton === button);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
autocompleteList.append(fragment);
|
||||
} catch (error) {
|
||||
console.error("Error fetching autocomplete results:", error);
|
||||
const form: HTMLFormElement | null = document.querySelector<HTMLFormElement>("#search");
|
||||
assertElement(form);
|
||||
|
||||
// override form submit action to update the actually selected categories
|
||||
listen("submit", form, (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const categoryValuesInput = document.querySelector<HTMLInputElement>("#selected-categories");
|
||||
if (categoryValuesInput) {
|
||||
const categoryValues = categoryButtons
|
||||
.filter((button) => button.classList.contains("selected"))
|
||||
.map((button) => button.name.replace("category_", ""));
|
||||
|
||||
categoryValuesInput.value = categoryValues.join(",");
|
||||
}
|
||||
};
|
||||
|
||||
searxng.ready(
|
||||
() => {
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
assertElement(qInput);
|
||||
|
||||
const isMobile = window.matchMedia("(max-width: 50em)").matches;
|
||||
const isResultsPage = document.querySelector("main")?.id === "main_results";
|
||||
|
||||
// focus search input on large screens
|
||||
if (!isMobile && !isResultsPage) {
|
||||
qInput.focus();
|
||||
}
|
||||
|
||||
createClearButton(qInput);
|
||||
|
||||
// autocompleter
|
||||
if (searxng.settings.autocomplete) {
|
||||
let timeoutId: number;
|
||||
|
||||
searxng.listen("input", qInput, () => {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
const query = qInput.value;
|
||||
const minLength = searxng.settings.autocomplete_min ?? 2;
|
||||
|
||||
if (query.length < minLength) return;
|
||||
|
||||
timeoutId = window.setTimeout(async () => {
|
||||
if (query === qInput.value) {
|
||||
await fetchResults(qInput, query);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
const autocomplete = document.querySelector<HTMLElement>(".autocomplete");
|
||||
const autocompleteList = document.querySelector<HTMLUListElement>(".autocomplete ul");
|
||||
if (autocompleteList) {
|
||||
searxng.listen("keyup", qInput, (event: KeyboardEvent) => {
|
||||
const listItems = [...autocompleteList.children] as HTMLElement[];
|
||||
|
||||
const currentIndex = listItems.findIndex((item) => item.classList.contains("active"));
|
||||
let newCurrentIndex = -1;
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowUp": {
|
||||
const currentItem = listItems[currentIndex];
|
||||
if (currentItem && currentIndex >= 0) {
|
||||
currentItem.classList.remove("active");
|
||||
}
|
||||
// we need to add listItems.length to the index calculation here because the JavaScript modulos
|
||||
// operator doesn't work with negative numbers
|
||||
newCurrentIndex = (currentIndex - 1 + listItems.length) % listItems.length;
|
||||
break;
|
||||
}
|
||||
case "ArrowDown": {
|
||||
const currentItem = listItems[currentIndex];
|
||||
if (currentItem && currentIndex >= 0) {
|
||||
currentItem.classList.remove("active");
|
||||
}
|
||||
newCurrentIndex = (currentIndex + 1) % listItems.length;
|
||||
break;
|
||||
}
|
||||
case "Tab":
|
||||
case "Enter":
|
||||
if (autocomplete) {
|
||||
autocomplete.classList.remove("open");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (newCurrentIndex !== -1) {
|
||||
const selectedItem = listItems[newCurrentIndex];
|
||||
if (selectedItem) {
|
||||
selectedItem.classList.add("active");
|
||||
|
||||
if (!selectedItem.classList.contains("no-item-found")) {
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
if (qInput) {
|
||||
qInput.value = selectedItem.textContent ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Additionally to searching when selecting a new category, we also
|
||||
// automatically start a new search request when the user changes a search
|
||||
// filter (safesearch, time range or language) (this requires JavaScript
|
||||
// though)
|
||||
if (
|
||||
searxng.settings.search_on_category_select &&
|
||||
// If .search_filters is undefined (invisible) we are on the homepage and
|
||||
// hence don't have to set any listeners
|
||||
document.querySelector(".search_filters")
|
||||
) {
|
||||
const safesearchElement = document.getElementById("safesearch");
|
||||
if (safesearchElement) {
|
||||
searxng.listen("change", safesearchElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
const timeRangeElement = document.getElementById("time_range");
|
||||
if (timeRangeElement) {
|
||||
searxng.listen("change", timeRangeElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
const languageElement = document.getElementById("language");
|
||||
if (languageElement) {
|
||||
searxng.listen("change", languageElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
}
|
||||
|
||||
const categoryButtons = [...document.querySelectorAll<HTMLButtonElement>("button.category_button")];
|
||||
for (const button of categoryButtons) {
|
||||
searxng.listen("click", button, (event: MouseEvent) => {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
button.classList.toggle("selected");
|
||||
return;
|
||||
}
|
||||
|
||||
// deselect all other categories
|
||||
for (const categoryButton of categoryButtons) {
|
||||
categoryButton.classList.toggle("selected", categoryButton === button);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = document.querySelector<HTMLFormElement>("#search");
|
||||
assertElement(form);
|
||||
|
||||
// override form submit action to update the actually selected categories
|
||||
searxng.listen("submit", form, (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const categoryValuesInput = document.querySelector<HTMLInputElement>("#selected-categories");
|
||||
if (categoryValuesInput) {
|
||||
const categoryValues = categoryButtons
|
||||
.filter((button) => button.classList.contains("selected"))
|
||||
.map((button) => button.name.replace("category_", ""));
|
||||
|
||||
categoryValuesInput.value = categoryValues.join(",");
|
||||
}
|
||||
|
||||
form.submit();
|
||||
});
|
||||
},
|
||||
{ on: [searxng.endpoint === "index" || searxng.endpoint === "results"] }
|
||||
);
|
||||
form.submit();
|
||||
});
|
||||
|
|
|
@ -18,11 +18,7 @@
|
|||
cursor: default;
|
||||
|
||||
&::selection {
|
||||
background: transparent; /* WebKit/Blink Browsers */
|
||||
}
|
||||
|
||||
&::-moz-selection {
|
||||
background: transparent; /* Gecko Browsers */
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
margin-right: 8px;
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
// Mixins
|
||||
.text-size-adjust (@property: 100%) {
|
||||
-webkit-text-size-adjust: @property;
|
||||
-ms-text-size-adjust: @property;
|
||||
-moz-text-size-adjust: @property;
|
||||
text-size-adjust: @property;
|
||||
}
|
||||
|
||||
|
@ -22,7 +19,6 @@
|
|||
|
||||
// disable user selection
|
||||
.disable-user-select () {
|
||||
-webkit-touch-callout: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
@import (inline) "../../node_modules/normalize.css/normalize.css";
|
||||
@import "definitions.less";
|
||||
|
||||
.text-size-adjust (@property: 100%) {
|
||||
-webkit-text-size-adjust: @property;
|
||||
-ms-text-size-adjust: @property;
|
||||
-moz-text-size-adjust: @property;
|
||||
text-size-adjust: @property;
|
||||
}
|
||||
@import "mixins.less";
|
||||
|
||||
// Reset padding and margin
|
||||
html,
|
||||
|
|
|
@ -196,11 +196,6 @@ html.no-js #clear_search.hide_if_nojs {
|
|||
.ltr-rounded-left-corners(0.8rem);
|
||||
}
|
||||
|
||||
#q::-ms-clear,
|
||||
#q::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#send_search {
|
||||
.ltr-rounded-right-corners(0.8rem);
|
||||
|
||||
|
@ -271,7 +266,6 @@ html.no-js #clear_search.hide_if_nojs {
|
|||
width: 100%;
|
||||
.ltr-text-align-left();
|
||||
overflow: scroll hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -374,11 +368,6 @@ html.no-js #clear_search.hide_if_nojs {
|
|||
|
||||
#categories {
|
||||
.disable-user-select;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#categories_container {
|
||||
|
|
|
@ -129,13 +129,7 @@
|
|||
}
|
||||
|
||||
// select HTML element
|
||||
@supports (
|
||||
(background-position-x: 100%) and
|
||||
(
|
||||
(appearance: none) or (-webkit-appearance: none) or
|
||||
(-moz-appearance: none)
|
||||
)
|
||||
) {
|
||||
@supports ((background-position-x: 100%) and ((appearance: none))) {
|
||||
select {
|
||||
border-width: 0 0 0 2rem;
|
||||
background-position-x: -2rem;
|
||||
|
|
|
@ -156,9 +156,7 @@ div.selectable_url {
|
|||
|
||||
td {
|
||||
padding: 0 1em 0 0;
|
||||
padding-top: 0;
|
||||
.ltr-padding-right(1rem);
|
||||
padding-bottom: 0;
|
||||
.ltr-padding-left(0);
|
||||
}
|
||||
|
||||
|
@ -307,7 +305,7 @@ html body .tabs > input:checked {
|
|||
}
|
||||
|
||||
~ label {
|
||||
position: inherited;
|
||||
position: inherit;
|
||||
background: inherit;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-weight: normal;
|
||||
|
@ -347,17 +345,9 @@ select {
|
|||
}
|
||||
}
|
||||
|
||||
@supports (
|
||||
(background-position-x: 100%) and
|
||||
(
|
||||
(appearance: none) or (-webkit-appearance: none) or
|
||||
(-moz-appearance: none)
|
||||
)
|
||||
) {
|
||||
@supports ((background-position-x: 100%) and ((appearance: none))) {
|
||||
select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border-width: 0 2rem 0 0;
|
||||
border-color: transparent;
|
||||
background: data-uri("image/svg+xml;charset=UTF-8", @select-light-svg-path)
|
||||
|
@ -400,8 +390,6 @@ select {
|
|||
|
||||
/* -- checkbox-onoff -- */
|
||||
input.checkbox-onoff[type="checkbox"] {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
@ -475,8 +463,6 @@ input.checkbox-onoff.reversed-checkbox[type="checkbox"] {
|
|||
/* -- checkbox -- */
|
||||
@supports (transform: rotate(-45deg)) {
|
||||
input[type="checkbox"]:not(.checkbox-onoff) {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
|
||||
width: 20px;
|
||||
|
@ -549,33 +535,16 @@ input.checkbox-onoff.reversed-checkbox[type="checkbox"] {
|
|||
border-right: 0.5em solid var(--color-toolkit-loader-border);
|
||||
border-bottom: 0.5em solid var(--color-toolkit-loader-border);
|
||||
border-left: 0.5em solid var(--color-toolkit-loader-borderleft);
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation: load8 1.2s infinite linear;
|
||||
animation: load8 1.2s infinite linear;
|
||||
}
|
||||
|
||||
@-webkit-keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
@ -606,9 +575,6 @@ td:hover .engine-tooltip,
|
|||
margin: 0;
|
||||
padding: 0 0.125rem 0 4rem;
|
||||
width: 100%;
|
||||
width: -moz-available;
|
||||
width: -webkit-fill-available;
|
||||
width: fill;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
|
|
|
@ -8,7 +8,7 @@ import type { Config as SvgoConfig } from "svgo";
|
|||
import { type IconSet, type JinjaMacro, jinja_svg_sets } from "./tools/jinja_svg_catalog.ts";
|
||||
|
||||
const HERE = `${dirname(argv[1] || "")}/`;
|
||||
const dest = resolve(HERE, "../../searx/templates/simple/icons.html");
|
||||
const dest: string = resolve(HERE, "../../searx/templates/simple/icons.html");
|
||||
|
||||
const searxng_jinja_macros: JinjaMacro[] = [
|
||||
{ name: "icon", class: "sxng-icon-set" },
|
||||
|
|
|
@ -17,24 +17,24 @@ export type Src2Dest = {
|
|||
*
|
||||
* @param items - Array of SVG files (src: SVG, dest:PNG) to convert.
|
||||
*/
|
||||
export const svg2png = async (items: Src2Dest[]) => {
|
||||
export const svg2png = (items: Src2Dest[]): void => {
|
||||
for (const item of items) {
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(item.dest), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(item.dest), { recursive: true });
|
||||
|
||||
const info = await sharp(item.src)
|
||||
.png({
|
||||
force: true,
|
||||
compressionLevel: 9,
|
||||
palette: true
|
||||
})
|
||||
.toFile(item.dest);
|
||||
|
||||
console.log(`[svg2png] created ${item.dest} -- bytes: ${info.size}, w:${info.width}px, h:${info.height}px`);
|
||||
} catch (err) {
|
||||
console.error(`ERROR: ${item.dest} -- ${err}`);
|
||||
throw err;
|
||||
}
|
||||
sharp(item.src)
|
||||
.png({
|
||||
force: true,
|
||||
compressionLevel: 9,
|
||||
palette: true
|
||||
})
|
||||
.toFile(item.dest)
|
||||
.then((info) => {
|
||||
console.log(`[svg2png] created ${item.dest} -- bytes: ${info.size}, w:${info.width}px, h:${info.height}px`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`ERROR: ${item.dest} -- ${error}`);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -44,7 +44,7 @@ export const svg2png = async (items: Src2Dest[]) => {
|
|||
* @param items - Array of SVG files (src:SVG, dest:SVG) to optimize.
|
||||
* @param svgo_opts - Options passed to svgo.
|
||||
*/
|
||||
export const svg2svg = (items: Src2Dest[], svgo_opts: Config) => {
|
||||
export const svg2svg = (items: Src2Dest[], svgo_opts: Config): void => {
|
||||
for (const item of items) {
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(item.dest), { recursive: true });
|
||||
|
@ -54,9 +54,9 @@ export const svg2svg = (items: Src2Dest[], svgo_opts: Config) => {
|
|||
|
||||
fs.writeFileSync(item.dest, opt.data);
|
||||
console.log(`[svg2svg] optimized: ${item.dest} -- src: ${item.src}`);
|
||||
} catch (err) {
|
||||
console.error(`ERROR: optimize src: ${item.src} -- ${err}`);
|
||||
throw err;
|
||||
} catch (error) {
|
||||
console.error(`ERROR: optimize src: ${item.src} -- ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import fs from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Edge } from "edge.js";
|
||||
import { type Config as SvgoConfig, optimize as svgo } from "svgo";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const __jinja_class_placeholder__ = "__jinja_class_placeholder__";
|
||||
|
||||
// A set of icons
|
||||
|
@ -43,9 +41,9 @@ export type JinjaMacro = {
|
|||
* @param macros - Jinja macros to create.
|
||||
* @param items - Array of SVG items.
|
||||
*/
|
||||
export const jinja_svg_catalog = (dest: string, macros: JinjaMacro[], items: IconSVG[]) => {
|
||||
export const jinja_svg_catalog = (dest: string, macros: JinjaMacro[], items: IconSVG[]): void => {
|
||||
const svg_catalog: Record<string, string> = {};
|
||||
const edge_template = resolve(__dirname, "jinja_svg_catalog.html.edge");
|
||||
const edge_template = resolve(import.meta.dirname, "jinja_svg_catalog.html.edge");
|
||||
|
||||
for (const item of items) {
|
||||
// JSON.stringify & JSON.parse are used to create a deep copy of the item.svgo_opts object
|
||||
|
@ -63,15 +61,13 @@ export const jinja_svg_catalog = (dest: string, macros: JinjaMacro[], items: Ico
|
|||
const opt = svgo(raw, svgo_opts);
|
||||
|
||||
svg_catalog[item.name] = opt.data;
|
||||
} catch (err) {
|
||||
console.error(`ERROR: jinja_svg_catalog processing ${item.name} src: ${item.src} -- ${err}`);
|
||||
throw err;
|
||||
} catch (error) {
|
||||
console.error(`ERROR: jinja_svg_catalog processing ${item.name} src: ${item.src} -- ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
fs.mkdir(dirname(dest), { recursive: true }, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
fs.mkdirSync(dirname(dest), { recursive: true });
|
||||
|
||||
const ctx = {
|
||||
svg_catalog: svg_catalog,
|
||||
|
@ -97,7 +93,7 @@ export const jinja_svg_catalog = (dest: string, macros: JinjaMacro[], items: Ico
|
|||
* @param macros - Jinja macros to create.
|
||||
* @param sets - Array of SVG sets.
|
||||
*/
|
||||
export const jinja_svg_sets = (dest: string, macros: JinjaMacro[], sets: IconSet[]) => {
|
||||
export const jinja_svg_sets = (dest: string, macros: JinjaMacro[], sets: IconSet[]): void => {
|
||||
const items: IconSVG[] = [];
|
||||
const all: string[] = [];
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ export const plg_svg2png = (items: Src2Dest[]): Plugin => {
|
|||
return {
|
||||
name: "searxng-simple-svg2png",
|
||||
apply: "build",
|
||||
async writeBundle() {
|
||||
await svg2png(items);
|
||||
writeBundle: () => {
|
||||
svg2png(items);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -36,7 +36,7 @@ export const plg_svg2svg = (items: Src2Dest[], svgo_opts: Config): Plugin => {
|
|||
return {
|
||||
name: "searxng-simple-svg2svg",
|
||||
apply: "build",
|
||||
writeBundle() {
|
||||
writeBundle: () => {
|
||||
svg2svg(items, svgo_opts);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,11 +6,12 @@ import { resolve } from "node:path";
|
|||
import { constants as zlibConstants } from "node:zlib";
|
||||
import browserslistToEsbuild from "browserslist-to-esbuild";
|
||||
import { browserslistToTargets } from "lightningcss";
|
||||
import type { PreRenderedAsset } from "rolldown";
|
||||
import type { Config } from "svgo";
|
||||
import type { UserConfig } from "vite";
|
||||
import analyzer from "vite-bundle-analyzer";
|
||||
import manifest from "./package.json";
|
||||
import { plg_svg2png, plg_svg2svg } from "./tools/plg";
|
||||
import manifest from "./package.json" with { type: "json" };
|
||||
import { plg_svg2png, plg_svg2svg } from "./tools/plg.ts";
|
||||
|
||||
const ROOT = "../../"; // root of the git repository
|
||||
|
||||
|
@ -20,7 +21,7 @@ const PATH = {
|
|||
modules: "node_modules/",
|
||||
src: "src/",
|
||||
templates: resolve(ROOT, "searx/templates/simple/")
|
||||
};
|
||||
} as const;
|
||||
|
||||
const svg2svg_opts: Config = {
|
||||
plugins: [{ name: "preset-default" }, "sortAttrs", "convertStyleToAttrs"]
|
||||
|
@ -49,37 +50,37 @@ export default {
|
|||
rollupOptions: {
|
||||
input: {
|
||||
// build CSS files
|
||||
"searxng-ltr.min.css": `${PATH.src}/less/style-ltr.less`,
|
||||
"searxng-rtl.min.css": `${PATH.src}/less/style-rtl.less`,
|
||||
"rss.min.css": `${PATH.src}/less/rss.less`,
|
||||
"searxng-ltr.css": `${PATH.src}/less/style-ltr.less`,
|
||||
"searxng-rtl.css": `${PATH.src}/less/style-rtl.less`,
|
||||
"rss.css": `${PATH.src}/less/rss.less`,
|
||||
|
||||
// build script files
|
||||
"searxng.min": `${PATH.src}/js/main/index.ts`,
|
||||
"searxng.core": `${PATH.src}/js/core/index.ts`,
|
||||
|
||||
// ol
|
||||
"ol.min": `${PATH.src}/js/pkg/ol.ts`,
|
||||
"ol.min.css": `${PATH.modules}/ol/ol.css`
|
||||
ol: `${PATH.src}/js/pkg/ol.ts`,
|
||||
"ol.css": `${PATH.modules}/ol/ol.css`
|
||||
},
|
||||
|
||||
// file naming conventions / pathnames are relative to outDir (PATH.dist)
|
||||
output: {
|
||||
entryFileNames: "js/[name].js",
|
||||
chunkFileNames: "js/[name].js",
|
||||
assetFileNames: ({ names }) => {
|
||||
entryFileNames: "js/[name].min.js",
|
||||
chunkFileNames: "js/[name].min.js",
|
||||
assetFileNames: ({ names }: PreRenderedAsset): string => {
|
||||
const [name] = names;
|
||||
|
||||
const extension = name?.split(".").pop();
|
||||
switch (extension) {
|
||||
case "css":
|
||||
return `css/[name][extname]`;
|
||||
return "css/[name].min[extname]";
|
||||
case "js":
|
||||
return `js/[name][extname]`;
|
||||
return "js/[name].min[extname]";
|
||||
case "png":
|
||||
case "svg":
|
||||
return `img/[name][extname]`;
|
||||
return "img/[name][extname]";
|
||||
default:
|
||||
console.warn("Unknown asset:", name);
|
||||
return `[name][extname]`;
|
||||
return "[name][extname]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<html class="no-js theme-{{ preferences.get_value('simple_style') or 'auto' }} center-alignment-{{ preferences.get_value('center_alignment') and 'yes' or 'no' }}" lang="{{ locale_rfc5646 }}" {% if rtl %} dir="rtl"{% endif %}>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="endpoint" content="{{ endpoint }}">
|
||||
<meta name="description" content="SearXNG — a privacy-respecting, open metasearch engine">
|
||||
<meta name="keywords" content="SearXNG, search, search engine, metasearch, meta search">
|
||||
<meta name="generator" content="searxng/{{ searx_version }}">
|
||||
|
@ -82,6 +83,6 @@
|
|||
{% endfor %}
|
||||
</p>
|
||||
</footer>
|
||||
<script type="module" src="{{ url_for('static', filename='js/searxng.min.js') }}" client_settings="{{ client_settings }}"></script>
|
||||
<script type="module" src="{{ url_for('static', filename='js/searxng.core.min.js') }}" client_settings="{{ client_settings }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -358,7 +358,7 @@ def get_client_settings():
|
|||
'favicon_resolver': req_pref.get_value('favicon_resolver'),
|
||||
'advanced_search': req_pref.get_value('advanced_search'),
|
||||
'query_in_title': req_pref.get_value('query_in_title'),
|
||||
'safesearch': str(req_pref.get_value('safesearch')),
|
||||
'safesearch': req_pref.get_value('safesearch'),
|
||||
'theme': req_pref.get_value('theme'),
|
||||
'doi_resolver': get_doi_resolver(),
|
||||
}
|
||||
|
@ -368,15 +368,7 @@ def render(template_name: str, **kwargs):
|
|||
# values from the preferences
|
||||
# pylint: disable=too-many-statements
|
||||
client_settings = get_client_settings()
|
||||
kwargs['client_settings'] = str(
|
||||
base64.b64encode(
|
||||
bytes(
|
||||
json.dumps(client_settings),
|
||||
encoding='utf-8',
|
||||
)
|
||||
),
|
||||
encoding='utf-8',
|
||||
)
|
||||
kwargs['client_settings'] = base64.b64encode(json.dumps(client_settings).encode('utf-8')).decode('utf-8')
|
||||
kwargs['preferences'] = sxng_request.preferences
|
||||
kwargs.update(client_settings)
|
||||
|
||||
|
|
Loading…
Reference in a new issue