jskelet 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +136 -136
- package/CHANGELOG.md +620 -596
- package/LICENSE +21 -21
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -309
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +661 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1443 -1423
- package/docs/07-yapilandirma.md +12 -6
- package/docs/08-build.md +429 -428
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +338 -338
- package/docs/12-panel-ve-oturum.md +478 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -328
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +669 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1453 -1431
- package/docs/en/07-configuration.md +1219 -1214
- package/docs/en/08-build.md +447 -446
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +340 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +488 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +17 -1
- package/src/config/index.js +13 -0
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +230 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -462
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -0
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1122
- package/src/server/image-optimizer.js +500 -407
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -66
- package/src/server/logs/pipeline.js +165 -158
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -100
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +356 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1058
- package/src/server/redis.js +588 -569
- package/src/server/render.js +4 -4
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +15 -1
- package/types/config/index.d.ts +8 -0
- package/types/server/cache-blob.d.ts +13 -0
- package/types/server/data-cache.d.ts +9 -0
- package/types/server/disk-cache.d.ts +36 -0
- package/types/server/html-cache.d.ts +26 -3
- package/types/server/logs/file-sink.d.ts +16 -5
- package/types/server/redis.d.ts +2 -1
package/src/version.mjs
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Framework sürümü. `package.json`'dan okunur; paket bir kez yayınlandıktan
|
|
3
|
-
* sonra sürümü iki yerde tutmak kaçınılmaz olarak birbirinden ayrılıyor.
|
|
4
|
-
*/
|
|
5
|
-
import fs from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { FRAMEWORK_ROOT } from "./config/index.js";
|
|
8
|
-
|
|
9
|
-
const manifest = (() => {
|
|
10
|
-
try {
|
|
11
|
-
const file = path.join(FRAMEWORK_ROOT, "package.json");
|
|
12
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
13
|
-
} catch {
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
})();
|
|
17
|
-
|
|
18
|
-
/** @type {string} */
|
|
19
|
-
export const FRAMEWORK_VERSION = manifest.version ?? "0.0.0";
|
|
20
|
-
|
|
21
|
-
/** Paket adı: sürüm kontrolü hangi kayıt defteri girdisine bakacağını buradan bilir. */
|
|
22
|
-
export const FRAMEWORK_PACKAGE = manifest.name ?? "jskelet";
|
|
23
|
-
|
|
24
|
-
/** Künye alanları: yönetim panelinin footer'ı bunları basıyor. */
|
|
25
|
-
export const FRAMEWORK_LICENSE = manifest.license ?? "MIT";
|
|
26
|
-
|
|
27
|
-
/** @type {string} */
|
|
28
|
-
export const FRAMEWORK_NODE_RANGE = manifest.engines?.node ?? "";
|
|
29
|
-
|
|
30
|
-
/** @type {string} */
|
|
31
|
-
export const FRAMEWORK_HOMEPAGE = manifest.homepage ?? "";
|
|
1
|
+
/**
|
|
2
|
+
* Framework sürümü. `package.json`'dan okunur; paket bir kez yayınlandıktan
|
|
3
|
+
* sonra sürümü iki yerde tutmak kaçınılmaz olarak birbirinden ayrılıyor.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { FRAMEWORK_ROOT } from "./config/index.js";
|
|
8
|
+
|
|
9
|
+
const manifest = (() => {
|
|
10
|
+
try {
|
|
11
|
+
const file = path.join(FRAMEWORK_ROOT, "package.json");
|
|
12
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
|
|
18
|
+
/** @type {string} */
|
|
19
|
+
export const FRAMEWORK_VERSION = manifest.version ?? "0.0.0";
|
|
20
|
+
|
|
21
|
+
/** Paket adı: sürüm kontrolü hangi kayıt defteri girdisine bakacağını buradan bilir. */
|
|
22
|
+
export const FRAMEWORK_PACKAGE = manifest.name ?? "jskelet";
|
|
23
|
+
|
|
24
|
+
/** Künye alanları: yönetim panelinin footer'ı bunları basıyor. */
|
|
25
|
+
export const FRAMEWORK_LICENSE = manifest.license ?? "MIT";
|
|
26
|
+
|
|
27
|
+
/** @type {string} */
|
|
28
|
+
export const FRAMEWORK_NODE_RANGE = manifest.engines?.node ?? "";
|
|
29
|
+
|
|
30
|
+
/** @type {string} */
|
|
31
|
+
export const FRAMEWORK_HOMEPAGE = manifest.homepage ?? "";
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `views/components/**` (ve feature/shared köklerindeki eşleri) altındaki
|
|
3
|
-
* tüm modülleri yükler ve named export'larını tek bir nesnede birleştirir.
|
|
4
|
-
*
|
|
5
|
-
* Elle bakılan bir barrel dosyası yok: yeni bir bileşen eklemek için dosyayı
|
|
6
|
-
* oluşturmak yeterli.
|
|
7
|
-
*/
|
|
8
|
-
import fs from "node:fs";
|
|
9
|
-
import path from "node:path";
|
|
10
|
-
import { pathToFileURL } from "node:url";
|
|
11
|
-
|
|
12
|
-
/** Bileşen olmayan altyapı dosyaları. */
|
|
13
|
-
const SKIP_FILES = new Set(["loader.js", "index.js"]);
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Barrel önce, en düşük öncelikle yüklenir: tek amacı `lib/` yeniden
|
|
17
|
-
* ihraçlarını şablon local'i yapmak. Bileşenlerin kendi dosyaları sonradan
|
|
18
|
-
* gelip sessizce üzerine yazar.
|
|
19
|
-
*/
|
|
20
|
-
const BARREL = "index.js";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {string} dir
|
|
24
|
-
* @param {string[]} [out]
|
|
25
|
-
* @returns {string[]}
|
|
26
|
-
*/
|
|
27
|
-
function collect(dir, out = []) {
|
|
28
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
29
|
-
const full = path.join(dir, entry.name);
|
|
30
|
-
|
|
31
|
-
if (entry.isDirectory()) {
|
|
32
|
-
collect(full, out);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!entry.name.endsWith(".js")) continue;
|
|
37
|
-
if (SKIP_FILES.has(entry.name)) continue;
|
|
38
|
-
|
|
39
|
-
out.push(full);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return out;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Tek bir `components/` dizinini yükler.
|
|
47
|
-
*
|
|
48
|
-
* @param {string} dir
|
|
49
|
-
* @param {Record<string, unknown>} components
|
|
50
|
-
* @param {Map<string, string>} origin
|
|
51
|
-
* @returns {Promise<void>}
|
|
52
|
-
*/
|
|
53
|
-
async function loadDir(dir, components, origin) {
|
|
54
|
-
if (!dir || !fs.existsSync(dir)) return;
|
|
55
|
-
|
|
56
|
-
const barrel = path.join(dir, BARREL);
|
|
57
|
-
const files = [
|
|
58
|
-
...(fs.existsSync(barrel) ? [barrel] : []),
|
|
59
|
-
...collect(dir).sort(),
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
for (const file of files) {
|
|
63
|
-
const isBarrel = path.basename(file) === BARREL;
|
|
64
|
-
// Kimlik mutlak yol — çoklu components kökünde göreli ad çakışmasın.
|
|
65
|
-
const fileId = isBarrel ? BARREL : file.split(path.sep).join("/");
|
|
66
|
-
const module = await import(pathToFileURL(file).href);
|
|
67
|
-
|
|
68
|
-
for (const [name, value] of Object.entries(module)) {
|
|
69
|
-
if (name === "default") continue;
|
|
70
|
-
|
|
71
|
-
const previous = origin.get(name);
|
|
72
|
-
// Barrel üzerine yazmak bilinçli; iki gerçek bileşen dosyası çakışması hata.
|
|
73
|
-
if (previous && previous !== BARREL && previous !== fileId) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
`[components] '${name}' is defined twice: ${previous} and ${fileId}`,
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
components[name] = value;
|
|
80
|
-
origin.set(name, fileId);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* @param {string | string[]} dirs Tek dizin veya çoklu kök (feature/shared).
|
|
87
|
-
* @returns {Promise<Record<string, unknown>>}
|
|
88
|
-
*/
|
|
89
|
-
export async function loadComponents(dirs) {
|
|
90
|
-
/** @type {Record<string, unknown>} */
|
|
91
|
-
const components = {};
|
|
92
|
-
/** @type {Map<string, string>} */
|
|
93
|
-
const origin = new Map();
|
|
94
|
-
|
|
95
|
-
const list = Array.isArray(dirs) ? dirs : [dirs];
|
|
96
|
-
for (const dir of list) {
|
|
97
|
-
await loadDir(dir, components, origin);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return components;
|
|
101
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `views/components/**` (ve feature/shared köklerindeki eşleri) altındaki
|
|
3
|
+
* tüm modülleri yükler ve named export'larını tek bir nesnede birleştirir.
|
|
4
|
+
*
|
|
5
|
+
* Elle bakılan bir barrel dosyası yok: yeni bir bileşen eklemek için dosyayı
|
|
6
|
+
* oluşturmak yeterli.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
11
|
+
|
|
12
|
+
/** Bileşen olmayan altyapı dosyaları. */
|
|
13
|
+
const SKIP_FILES = new Set(["loader.js", "index.js"]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Barrel önce, en düşük öncelikle yüklenir: tek amacı `lib/` yeniden
|
|
17
|
+
* ihraçlarını şablon local'i yapmak. Bileşenlerin kendi dosyaları sonradan
|
|
18
|
+
* gelip sessizce üzerine yazar.
|
|
19
|
+
*/
|
|
20
|
+
const BARREL = "index.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} dir
|
|
24
|
+
* @param {string[]} [out]
|
|
25
|
+
* @returns {string[]}
|
|
26
|
+
*/
|
|
27
|
+
function collect(dir, out = []) {
|
|
28
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
29
|
+
const full = path.join(dir, entry.name);
|
|
30
|
+
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
collect(full, out);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!entry.name.endsWith(".js")) continue;
|
|
37
|
+
if (SKIP_FILES.has(entry.name)) continue;
|
|
38
|
+
|
|
39
|
+
out.push(full);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tek bir `components/` dizinini yükler.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} dir
|
|
49
|
+
* @param {Record<string, unknown>} components
|
|
50
|
+
* @param {Map<string, string>} origin
|
|
51
|
+
* @returns {Promise<void>}
|
|
52
|
+
*/
|
|
53
|
+
async function loadDir(dir, components, origin) {
|
|
54
|
+
if (!dir || !fs.existsSync(dir)) return;
|
|
55
|
+
|
|
56
|
+
const barrel = path.join(dir, BARREL);
|
|
57
|
+
const files = [
|
|
58
|
+
...(fs.existsSync(barrel) ? [barrel] : []),
|
|
59
|
+
...collect(dir).sort(),
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
const isBarrel = path.basename(file) === BARREL;
|
|
64
|
+
// Kimlik mutlak yol — çoklu components kökünde göreli ad çakışmasın.
|
|
65
|
+
const fileId = isBarrel ? BARREL : file.split(path.sep).join("/");
|
|
66
|
+
const module = await import(pathToFileURL(file).href);
|
|
67
|
+
|
|
68
|
+
for (const [name, value] of Object.entries(module)) {
|
|
69
|
+
if (name === "default") continue;
|
|
70
|
+
|
|
71
|
+
const previous = origin.get(name);
|
|
72
|
+
// Barrel üzerine yazmak bilinçli; iki gerçek bileşen dosyası çakışması hata.
|
|
73
|
+
if (previous && previous !== BARREL && previous !== fileId) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`[components] '${name}' is defined twice: ${previous} and ${fileId}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
components[name] = value;
|
|
80
|
+
origin.set(name, fileId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string | string[]} dirs Tek dizin veya çoklu kök (feature/shared).
|
|
87
|
+
* @returns {Promise<Record<string, unknown>>}
|
|
88
|
+
*/
|
|
89
|
+
export async function loadComponents(dirs) {
|
|
90
|
+
/** @type {Record<string, unknown>} */
|
|
91
|
+
const components = {};
|
|
92
|
+
/** @type {Map<string, string>} */
|
|
93
|
+
const origin = new Map();
|
|
94
|
+
|
|
95
|
+
const list = Array.isArray(dirs) ? dirs : [dirs];
|
|
96
|
+
for (const dir of list) {
|
|
97
|
+
await loadDir(dir, components, origin);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return components;
|
|
101
|
+
}
|
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
/** HTML üretimi için ortak yardımcılar (React'in kaçış davranışının karşılığı). */
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
|
-
|
|
4
|
-
const ESCAPE_MAP = {
|
|
5
|
-
"&": "&",
|
|
6
|
-
"<": "<",
|
|
7
|
-
">": ">",
|
|
8
|
-
'"': """,
|
|
9
|
-
"'": "'",
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Metin içeriği ve attribute değerleri için kaçış.
|
|
14
|
-
* @param {unknown} value
|
|
15
|
-
* @returns {string}
|
|
16
|
-
*/
|
|
17
|
-
export function esc(value) {
|
|
18
|
-
if (value == null || value === false) return "";
|
|
19
|
-
return String(value).replace(/[&<>"']/g, (char) => ESCAPE_MAP[char]);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* `<script type="application/ld+json">` gövdesi için güvenli JSON.
|
|
24
|
-
* `</script`, `<!--` ve U+2028/2029 kaçırılır.
|
|
25
|
-
* @param {unknown} value
|
|
26
|
-
* @returns {string}
|
|
27
|
-
*/
|
|
28
|
-
export function jsonScript(value) {
|
|
29
|
-
return JSON.stringify(value)
|
|
30
|
-
.replace(/</g, "\\u003c")
|
|
31
|
-
.replace(/>/g, "\\u003e")
|
|
32
|
-
.replace(/&/g, "\\u0026")
|
|
33
|
-
.replace(/\u2028/g, "\\u2028")
|
|
34
|
-
.replace(/\u2029/g, "\\u2029");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Attribute nesnesini string'e çevirir. `false`/`null`/`undefined` atlanır,
|
|
39
|
-
* `true` boolean attribute olarak yazılır.
|
|
40
|
-
* @param {Record<string, unknown>} attrs
|
|
41
|
-
* @returns {string}
|
|
42
|
-
*/
|
|
43
|
-
export function attrs(attrs) {
|
|
44
|
-
const parts = [];
|
|
45
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
46
|
-
if (value == null || value === false) continue;
|
|
47
|
-
if (value === true) {
|
|
48
|
-
parts.push(key);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
parts.push(`${key}="${esc(value)}"`);
|
|
52
|
-
}
|
|
53
|
-
return parts.length ? ` ${parts.join(" ")}` : "";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* `clsx` karşılığı — koşullu sınıf birleştirme, çakışma çözümü yok.
|
|
58
|
-
* @param {...unknown} inputs
|
|
59
|
-
* @returns {string}
|
|
60
|
-
*/
|
|
61
|
-
export function cx(...inputs) {
|
|
62
|
-
const out = [];
|
|
63
|
-
|
|
64
|
-
for (const input of inputs) {
|
|
65
|
-
if (!input) continue;
|
|
66
|
-
|
|
67
|
-
if (typeof input === "string" || typeof input === "number") {
|
|
68
|
-
out.push(String(input));
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (Array.isArray(input)) {
|
|
73
|
-
const nested = cx(...input);
|
|
74
|
-
if (nested) out.push(nested);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (typeof input === "object") {
|
|
79
|
-
for (const [key, active] of Object.entries(input)) {
|
|
80
|
-
if (active) out.push(key);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return out.join(" ");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* `lib/ui/cn.js` ile aynı davranış: birleştir, sonra Tailwind çakışmalarını çöz.
|
|
90
|
-
*
|
|
91
|
-
* `tailwind-merge` çalışma zamanı bağımlılığı olarak korunur çünkü sınıf
|
|
92
|
-
* hesabı **yalnızca sunucuda** yapılır — client bundle'a hiç girmez, dolayısıyla
|
|
93
|
-
* sayfa ağırlığına etkisi yoktur. Elle yazılmış bir grup tablosu ise
|
|
94
|
-
* `border-2` + `border-transparent` gibi genişlik/renk çiftlerini birbirine
|
|
95
|
-
* karıştırıp sınıf düşürdüğü için görsel regresyon üretiyordu.
|
|
96
|
-
*
|
|
97
|
-
* @param {...unknown} inputs
|
|
98
|
-
* @returns {string}
|
|
99
|
-
*/
|
|
100
|
-
export function cn(...inputs) {
|
|
101
|
-
return twMerge(cx(...inputs));
|
|
102
|
-
}
|
|
1
|
+
/** HTML üretimi için ortak yardımcılar (React'in kaçış davranışının karşılığı). */
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
const ESCAPE_MAP = {
|
|
5
|
+
"&": "&",
|
|
6
|
+
"<": "<",
|
|
7
|
+
">": ">",
|
|
8
|
+
'"': """,
|
|
9
|
+
"'": "'",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Metin içeriği ve attribute değerleri için kaçış.
|
|
14
|
+
* @param {unknown} value
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function esc(value) {
|
|
18
|
+
if (value == null || value === false) return "";
|
|
19
|
+
return String(value).replace(/[&<>"']/g, (char) => ESCAPE_MAP[char]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `<script type="application/ld+json">` gövdesi için güvenli JSON.
|
|
24
|
+
* `</script`, `<!--` ve U+2028/2029 kaçırılır.
|
|
25
|
+
* @param {unknown} value
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function jsonScript(value) {
|
|
29
|
+
return JSON.stringify(value)
|
|
30
|
+
.replace(/</g, "\\u003c")
|
|
31
|
+
.replace(/>/g, "\\u003e")
|
|
32
|
+
.replace(/&/g, "\\u0026")
|
|
33
|
+
.replace(/\u2028/g, "\\u2028")
|
|
34
|
+
.replace(/\u2029/g, "\\u2029");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Attribute nesnesini string'e çevirir. `false`/`null`/`undefined` atlanır,
|
|
39
|
+
* `true` boolean attribute olarak yazılır.
|
|
40
|
+
* @param {Record<string, unknown>} attrs
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
export function attrs(attrs) {
|
|
44
|
+
const parts = [];
|
|
45
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
46
|
+
if (value == null || value === false) continue;
|
|
47
|
+
if (value === true) {
|
|
48
|
+
parts.push(key);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
parts.push(`${key}="${esc(value)}"`);
|
|
52
|
+
}
|
|
53
|
+
return parts.length ? ` ${parts.join(" ")}` : "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `clsx` karşılığı — koşullu sınıf birleştirme, çakışma çözümü yok.
|
|
58
|
+
* @param {...unknown} inputs
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
export function cx(...inputs) {
|
|
62
|
+
const out = [];
|
|
63
|
+
|
|
64
|
+
for (const input of inputs) {
|
|
65
|
+
if (!input) continue;
|
|
66
|
+
|
|
67
|
+
if (typeof input === "string" || typeof input === "number") {
|
|
68
|
+
out.push(String(input));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Array.isArray(input)) {
|
|
73
|
+
const nested = cx(...input);
|
|
74
|
+
if (nested) out.push(nested);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof input === "object") {
|
|
79
|
+
for (const [key, active] of Object.entries(input)) {
|
|
80
|
+
if (active) out.push(key);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return out.join(" ");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* `lib/ui/cn.js` ile aynı davranış: birleştir, sonra Tailwind çakışmalarını çöz.
|
|
90
|
+
*
|
|
91
|
+
* `tailwind-merge` çalışma zamanı bağımlılığı olarak korunur çünkü sınıf
|
|
92
|
+
* hesabı **yalnızca sunucuda** yapılır — client bundle'a hiç girmez, dolayısıyla
|
|
93
|
+
* sayfa ağırlığına etkisi yoktur. Elle yazılmış bir grup tablosu ise
|
|
94
|
+
* `border-2` + `border-transparent` gibi genişlik/renk çiftlerini birbirine
|
|
95
|
+
* karıştırıp sınıf düşürdüğü için görsel regresyon üretiyordu.
|
|
96
|
+
*
|
|
97
|
+
* @param {...unknown} inputs
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
export function cn(...inputs) {
|
|
101
|
+
return twMerge(cx(...inputs));
|
|
102
|
+
}
|