mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-11-04 18:40:20 +00:00
fix(webapp): migrate to Tailwind CSS 4 to remove vulnerable braces dependency
This commit is contained in:
1 parent
293f5ce196
commit
4c241aa64b
15 files changed
+551
-1102
No files matched your search
@@ -18,6 +18,9 @@ updates:
|
|||||||
- dependency-name: "tailwindcss"
|
- dependency-name: "tailwindcss"
|
||||||
update-types:
|
update-types:
|
||||||
- "version-update:semver-major"
|
- "version-update:semver-major"
|
||||||
|
- dependency-name: "@tailwindcss/*"
|
||||||
|
update-types:
|
||||||
|
- "version-update:semver-major"
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
|||||||
@@ -98,6 +98,14 @@ Password hints are reminders, not recovery secrets. They must never contain the
|
|||||||
master password, recovery codes, API keys, or anything that directly unlocks the
|
master password, recovery codes, API keys, or anything that directly unlocks the
|
||||||
vault.
|
vault.
|
||||||
|
|
||||||
|
### Web Vault Styles
|
||||||
|
|
||||||
|
The web vault uses Tailwind CSS 4 through `@tailwindcss/vite`. Theme aliases,
|
||||||
|
dark-mode variants, and source paths are defined in `webapp/src/tailwind.css`,
|
||||||
|
which also imports `styles.css` so component `@apply` rules share the same theme.
|
||||||
|
Keep runtime light/dark design tokens in `webapp/src/styles/tokens.css`.
|
||||||
|
Tailwind 4's browser baseline is Safari 16.4+, Chrome 111+, and Firefox 128+.
|
||||||
|
|
||||||
### i18n
|
### i18n
|
||||||
|
|
||||||
Locale files are complete standalone bundles. When adding or changing user-facing
|
Locale files are complete standalone bundles. When adding or changing user-facing
|
||||||
|
|||||||
Generated
+425
-1036
File diff suppressed because it is too large.
Load diff
+3
-5
@@ -57,17 +57,15 @@
|
|||||||
"ws": "8.21.0",
|
"ws": "8.21.0",
|
||||||
"sharp": "0.35.4",
|
"sharp": "0.35.4",
|
||||||
"browserslist": "4.28.7",
|
"browserslist": "4.28.7",
|
||||||
"baseline-browser-mapping": "2.11.0",
|
"baseline-browser-mapping": "2.11.0"
|
||||||
"postcss-selector-parser": "6.1.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^4.20260630.1",
|
"@cloudflare/workers-types": "^4.20260630.1",
|
||||||
"@preact/preset-vite": "^2.10.5",
|
"@preact/preset-vite": "^2.10.5",
|
||||||
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"@types/node": "^26.0.1",
|
"@types/node": "^26.0.1",
|
||||||
"autoprefixer": "^10.5.2",
|
|
||||||
"opencc-js": "^1.3.2",
|
"opencc-js": "^1.3.2",
|
||||||
"postcss": "^8.5.23",
|
"tailwindcss": "^4.3.3",
|
||||||
"tailwindcss": "^3.4.19",
|
|
||||||
"tsx": "^4.22.4",
|
"tsx": "^4.22.4",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.1.3",
|
"vite": "^8.1.3",
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: ['./webapp/index.html', './webapp/src/**/*.{ts,tsx}'],
|
|
||||||
darkMode: ['class', '[data-theme="dark"]'],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
canvas: 'var(--bg-accent)',
|
|
||||||
panel: 'var(--panel)',
|
|
||||||
'panel-soft': 'var(--panel-soft)',
|
|
||||||
'panel-muted': 'var(--panel-muted)',
|
|
||||||
line: 'var(--line)',
|
|
||||||
'line-soft': 'var(--line-soft)',
|
|
||||||
ink: 'var(--text)',
|
|
||||||
muted: 'var(--muted)',
|
|
||||||
'muted-strong': 'var(--muted-strong)',
|
|
||||||
brand: 'var(--primary)',
|
|
||||||
'brand-hover': 'var(--primary-hover)',
|
|
||||||
'brand-strong': 'var(--primary-strong)',
|
|
||||||
danger: 'var(--danger)',
|
|
||||||
},
|
|
||||||
boxShadow: {
|
|
||||||
soft: 'var(--shadow-sm)',
|
|
||||||
panel: 'var(--shadow-md)',
|
|
||||||
elevated: 'var(--shadow-lg)',
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
sans: ['Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', 'sans-serif'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
@@ -4,7 +4,6 @@ import App from './App';
|
|||||||
import { initI18n } from './lib/i18n';
|
import { initI18n } from './lib/i18n';
|
||||||
import { registerNodeWardenServiceWorker } from './lib/pwa';
|
import { registerNodeWardenServiceWorker } from './lib/pwa';
|
||||||
import './tailwind.css';
|
import './tailwind.css';
|
||||||
import './styles.css';
|
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
|
|||||||
@@ -353,7 +353,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.not-found-logo {
|
.not-found-logo {
|
||||||
@apply h-14 w-14 flex-shrink-0 object-contain;
|
@apply h-14 w-14 shrink-0 object-contain;
|
||||||
filter: drop-shadow(0 8px 18px rgba(43, 102, 217, 0.22));
|
filter: drop-shadow(0 8px 18px rgba(43, 102, 217, 0.22));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.standalone-brand-logo {
|
.standalone-brand-logo {
|
||||||
@apply h-14 w-[70px] flex-shrink-0 object-contain;
|
@apply h-14 w-[70px] shrink-0 object-contain;
|
||||||
filter: drop-shadow(0 8px 18px rgba(43, 102, 217, 0.22));
|
filter: drop-shadow(0 8px 18px rgba(43, 102, 217, 0.22));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
@apply h-12 w-full rounded-xl border px-3.5 py-2.5 outline-none;
|
@apply h-12 w-full rounded-xl border px-3.5 py-2.5 outline-hidden;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border-color: rgba(74, 103, 150, 0.34);
|
border-color: rgba(74, 103, 150, 0.34);
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82);
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82);
|
||||||
|
|||||||
@@ -802,15 +802,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.yubikey-manage-dialog-body {
|
.yubikey-manage-dialog-body {
|
||||||
@apply mt-3 space-y-4;
|
@apply mt-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yubikey-manage-dialog-body > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-plain-steps {
|
.settings-plain-steps {
|
||||||
@apply m-0 space-y-1 border-b pb-3 pl-5;
|
@apply m-0 border-b pb-3 pl-5;
|
||||||
border-color: var(--line);
|
border-color: var(--line);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-plain-steps > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.yubikey-input-row {
|
.yubikey-input-row {
|
||||||
@apply grid items-center gap-2;
|
@apply grid items-center gap-2;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
@@ -825,8 +835,9 @@
|
|||||||
@apply h-9 w-9 p-0;
|
@apply h-9 w-9 p-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-checkbox-block {
|
.settings-checkbox-block > :not([hidden]) ~ :not([hidden]) {
|
||||||
@apply space-y-2;
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-inline {
|
.checkbox-inline {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topbar-actions > .network-status-badge {
|
.topbar-actions > .network-status-badge {
|
||||||
@apply inline-flex h-9 w-9 min-w-9 justify-center gap-0 rounded-xl p-0 text-[0];
|
@apply inline-flex h-9 w-9 min-w-9 justify-center gap-0 rounded-xl p-0 text-[0px];
|
||||||
background: color-mix(in srgb, var(--panel) 88%, transparent);
|
background: color-mix(in srgb, var(--panel) 88%, transparent);
|
||||||
border-color: var(--line);
|
border-color: var(--line);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
|
|
||||||
.mobile-sidebar-toggle,
|
.mobile-sidebar-toggle,
|
||||||
.mobile-lock-btn {
|
.mobile-lock-btn {
|
||||||
@apply inline-flex h-9 w-9 min-w-9 justify-center gap-0 rounded-xl p-0 text-[0];
|
@apply inline-flex h-9 w-9 min-w-9 justify-center gap-0 rounded-xl p-0 text-[0px];
|
||||||
background: color-mix(in srgb, var(--panel) 88%, transparent);
|
background: color-mix(in srgb, var(--panel) 88%, transparent);
|
||||||
border-color: var(--line);
|
border-color: var(--line);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -451,7 +451,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mobile-fab-trigger {
|
.mobile-fab-trigger {
|
||||||
@apply h-9 w-9 gap-0 rounded-full p-0 text-[0];
|
@apply h-9 w-9 gap-0 rounded-full p-0 text-[0px];
|
||||||
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.28);
|
box-shadow: 0 14px 30px rgba(37, 99, 235, 0.28);
|
||||||
transition: transform 180ms var(--ease-spring), box-shadow var(--dur-fast) var(--ease-out-soft);
|
transition: transform 180ms var(--ease-spring), box-shadow var(--dur-fast) var(--ease-out-soft);
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+15
-4
@@ -31,7 +31,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-content {
|
.skeleton-content {
|
||||||
@apply min-w-0 flex-1 space-y-2;
|
@apply min-w-0 flex-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-content > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-line {
|
.skeleton-line {
|
||||||
@@ -63,13 +68,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-page {
|
.skeleton-page {
|
||||||
@apply h-full space-y-4 p-4;
|
@apply h-full p-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-page > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-header {
|
.skeleton-header {
|
||||||
@apply mb-6;
|
@apply mb-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-body {
|
.skeleton-body > :not([hidden]) ~ :not([hidden]) {
|
||||||
@apply space-y-3;
|
margin-top: 0.75rem;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
@apply h-12 w-full rounded-xl border px-3.5 py-2.5 text-base text-ink outline-none transition;
|
@apply h-12 w-full rounded-xl border px-3.5 py-2.5 text-base text-ink outline-hidden transition;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border-color: rgba(74, 103, 150, 0.34);
|
border-color: rgba(74, 103, 150, 0.34);
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82);
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82);
|
||||||
@@ -416,7 +416,7 @@ select.input.duplicate-mode-toolbar-select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.desktop-create-trigger {
|
.desktop-create-trigger {
|
||||||
@apply h-[34px] w-[34px] min-w-[34px] gap-0 rounded-[10px] p-0 text-[0];
|
@apply h-[34px] w-[34px] min-w-[34px] gap-0 rounded-[10px] p-0 text-[0px];
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-create-trigger .btn-icon {
|
.desktop-create-trigger .btn-icon {
|
||||||
@@ -586,7 +586,7 @@ select.input.duplicate-mode-toolbar-select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-brand-icon {
|
.card-brand-icon {
|
||||||
@apply inline-grid h-6 w-9 shrink-0 place-items-center rounded border text-[7px] font-black uppercase leading-none;
|
@apply inline-grid h-6 w-9 shrink-0 place-items-center rounded-sm border text-[7px] font-black uppercase leading-none;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
color: #1e3a8a;
|
color: #1e3a8a;
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f4f7fb 100%);
|
background: linear-gradient(180deg, #ffffff 0%, #f4f7fb 100%);
|
||||||
|
|||||||
+71
-3
@@ -1,3 +1,71 @@
|
|||||||
@tailwind base;
|
@import 'tailwindcss' source(none);
|
||||||
@tailwind components;
|
@import './styles.css';
|
||||||
@tailwind utilities;
|
|
||||||
|
@source '../index.html';
|
||||||
|
@source './**/*.{ts,tsx}';
|
||||||
|
|
||||||
|
@custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));
|
||||||
|
|
||||||
|
/* Inline aliases keep utilities tied to the active light/dark design tokens. */
|
||||||
|
@theme inline {
|
||||||
|
--color-canvas: var(--bg-accent);
|
||||||
|
--color-panel: var(--panel);
|
||||||
|
--color-panel-soft: var(--panel-soft);
|
||||||
|
--color-panel-muted: var(--panel-muted);
|
||||||
|
--color-line: var(--line);
|
||||||
|
--color-line-soft: var(--line-soft);
|
||||||
|
--color-ink: var(--text);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-strong: var(--muted-strong);
|
||||||
|
--color-brand: var(--primary);
|
||||||
|
--color-brand-hover: var(--primary-hover);
|
||||||
|
--color-brand-strong: var(--primary-strong);
|
||||||
|
--color-danger: var(--danger);
|
||||||
|
|
||||||
|
--shadow-soft: var(--shadow-sm);
|
||||||
|
--shadow-panel: var(--shadow-md);
|
||||||
|
--shadow-elevated: var(--shadow-lg);
|
||||||
|
|
||||||
|
--font-sans: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
|
||||||
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||||
|
|
||||||
|
/* Preserve utility radii independently of tokens.css's --radius-* names. */
|
||||||
|
--radius-xs: 0.125rem;
|
||||||
|
--radius-sm: 0.25rem;
|
||||||
|
--radius-md: 0.375rem;
|
||||||
|
--radius-lg: 0.5rem;
|
||||||
|
--radius-xl: 0.75rem;
|
||||||
|
--radius-2xl: 1rem;
|
||||||
|
--radius-3xl: 1.5rem;
|
||||||
|
|
||||||
|
/* Keep line heights fixed when component styles override the font size. */
|
||||||
|
--text-xs--line-height: 1rem;
|
||||||
|
--text-sm--line-height: 1.25rem;
|
||||||
|
--text-base--line-height: 1.5rem;
|
||||||
|
--text-lg--line-height: 1.75rem;
|
||||||
|
--text-xl--line-height: 1.75rem;
|
||||||
|
--text-2xl--line-height: 2rem;
|
||||||
|
--text-3xl--line-height: 2.25rem;
|
||||||
|
--text-4xl--line-height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Retain the v3 defaults where components do not set their own values. */
|
||||||
|
@layer base {
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after,
|
||||||
|
::backdrop,
|
||||||
|
::file-selector-button {
|
||||||
|
border-color: var(--color-gray-200, currentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
color: var(--color-gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not(:disabled),
|
||||||
|
[role='button']:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import preact from '@preact/preset-vite';
|
import preact from '@preact/preset-vite';
|
||||||
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import { defineConfig, type Plugin } from 'vite';
|
import { defineConfig, type Plugin } from 'vite';
|
||||||
|
|
||||||
const rootDir = fileURLToPath(new URL('.', import.meta.url));
|
const rootDir = fileURLToPath(new URL('.', import.meta.url));
|
||||||
@@ -263,7 +264,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
root: rootDir,
|
root: rootDir,
|
||||||
plugins: [preact(), searchIndexPolicyPlugin(isDemo), pwaServiceWorkerPlugin(isDemo)],
|
plugins: [preact(), tailwindcss(), searchIndexPolicyPlugin(isDemo), pwaServiceWorkerPlugin(isDemo)],
|
||||||
define: {
|
define: {
|
||||||
__NODEWARDEN_DEMO__: JSON.stringify(isDemo),
|
__NODEWARDEN_DEMO__: JSON.stringify(isDemo),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user