@janga/norna 0.7.0
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/LICENSE +674 -0
- package/README.md +109 -0
- package/astro.config.mjs +17 -0
- package/bin/norna.mjs +170 -0
- package/docs/README.md +48 -0
- package/docs/command-organization.md +402 -0
- package/docs/commands.md +152 -0
- package/docs/configuration.md +376 -0
- package/docs/content.md +384 -0
- package/docs/engine-development.md +164 -0
- package/docs/getting-started.md +96 -0
- package/docs/images-and-metadata.md +88 -0
- package/docs/local-development.md +61 -0
- package/docs/publishing.md +81 -0
- package/docs/site-examples-structure-note.md +105 -0
- package/docs/site-structure.md +81 -0
- package/fixtures/basic/site/.norna/generated-images.json +1 -0
- package/fixtures/basic/site/config.mjs +59 -0
- package/fixtures/basic/site/content.md +17 -0
- package/fixtures/basic/site/images/work/.gitkeep +1 -0
- package/fixtures/basic/site/public/robots.txt +2 -0
- package/fixtures/basic/site/theme.md +7 -0
- package/package.json +90 -0
- package/scripts/build-site.mjs +16 -0
- package/scripts/check-config.mjs +37 -0
- package/scripts/deploy-site.mjs +389 -0
- package/scripts/dev-local.mjs +313 -0
- package/scripts/doctor.mjs +38 -0
- package/scripts/engine-version.mjs +137 -0
- package/scripts/generate-images.mjs +369 -0
- package/scripts/init-site.mjs +249 -0
- package/scripts/lib/astro-command.mjs +34 -0
- package/scripts/lib/ci-lockfile.mjs +34 -0
- package/scripts/lib/image-dimensions.mjs +78 -0
- package/scripts/lib/presentation.mjs +72 -0
- package/scripts/lib/project-config.mjs +322 -0
- package/scripts/lib/run-command.mjs +21 -0
- package/scripts/lib/site-content.mjs +392 -0
- package/scripts/lib/site-paths.mjs +127 -0
- package/scripts/lib/typography.mjs +166 -0
- package/scripts/release.mjs +77 -0
- package/scripts/show-typography.mjs +210 -0
- package/scripts/sync-content-sections.mjs +610 -0
- package/scripts/sync-site-public.mjs +42 -0
- package/scripts/test-ci-lockfile.mjs +65 -0
- package/scripts/test-content-check.mjs +364 -0
- package/scripts/test-engine-commands.mjs +128 -0
- package/scripts/test-navigation-preview.mjs +108 -0
- package/scripts/test-navigation.mjs +116 -0
- package/scripts/test-package-check.mjs +394 -0
- package/scripts/test-site-public.mjs +85 -0
- package/scripts/test-temporary-visibility.mjs +99 -0
- package/scripts/update-engine.mjs +127 -0
- package/scripts/watch-pages-deploy.mjs +430 -0
- package/src/components/GalleryGrid.astro +221 -0
- package/src/components/SiteNavigation.astro +410 -0
- package/src/components/SitePage.astro +69 -0
- package/src/components/SiteSection.astro +174 -0
- package/src/content.config.ts +171 -0
- package/src/layouts/BaseLayout.astro +90 -0
- package/src/lib/generatedImages.ts +63 -0
- package/src/lib/sectionContent.ts +125 -0
- package/src/lib/sitePages.ts +80 -0
- package/src/lib/sitePublicAssets.ts +39 -0
- package/src/lib/visibility.ts +35 -0
- package/src/pages/[slug].astro +31 -0
- package/src/pages/index.astro +16 -0
- package/src/styles/global.css +872 -0
- package/starters/basic/.github/workflows/deploy.yml +65 -0
- package/starters/basic/README.md +55 -0
- package/starters/basic/package.json +35 -0
- package/starters/basic/site/config.mjs +60 -0
- package/starters/basic/site/content.md +21 -0
- package/starters/basic/site/images/work/.gitkeep +1 -0
- package/starters/basic/site/public/robots.txt +2 -0
- package/starters/basic/site/theme.md +53 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, readdir, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
engineRoot,
|
|
6
|
+
invocationRoot,
|
|
7
|
+
siteDirectoryEnv,
|
|
8
|
+
} from './lib/site-paths.mjs';
|
|
9
|
+
|
|
10
|
+
const packageName = '@janga/norna';
|
|
11
|
+
const cliExecutableName = 'norna';
|
|
12
|
+
|
|
13
|
+
const usage = `
|
|
14
|
+
Usage: norna init <target-dir> [--type pure|embedded] [--site-dir <path>]
|
|
15
|
+
|
|
16
|
+
Creates a new site project or adds a gallery source directory to an existing
|
|
17
|
+
project.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
norna init my-gallery
|
|
21
|
+
norna init my-gallery --type pure
|
|
22
|
+
norna init . --type embedded --site-dir presentation
|
|
23
|
+
`.trim();
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
const parseArgs = (rawArgs) => {
|
|
27
|
+
const options = {
|
|
28
|
+
siteDirectory: process.env[siteDirectoryEnv] ?? 'site',
|
|
29
|
+
targetDirectory: null,
|
|
30
|
+
type: 'pure',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
34
|
+
const arg = rawArgs[index];
|
|
35
|
+
|
|
36
|
+
if (arg === '--type') {
|
|
37
|
+
const value = rawArgs[index + 1];
|
|
38
|
+
if (!value || value.startsWith('-')) {
|
|
39
|
+
throw new Error('--type requires "pure" or "embedded".');
|
|
40
|
+
}
|
|
41
|
+
options.type = value;
|
|
42
|
+
index += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (arg.startsWith('--type=')) {
|
|
47
|
+
options.type = arg.slice('--type='.length);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (arg === '--site-dir') {
|
|
52
|
+
const value = rawArgs[index + 1];
|
|
53
|
+
if (!value || value.startsWith('-')) {
|
|
54
|
+
throw new Error('--site-dir requires a relative path.');
|
|
55
|
+
}
|
|
56
|
+
options.siteDirectory = value;
|
|
57
|
+
index += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (arg.startsWith('--site-dir=')) {
|
|
62
|
+
options.siteDirectory = arg.slice('--site-dir='.length);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (arg.startsWith('-')) {
|
|
67
|
+
throw new Error(`Unknown option: ${arg}\n${usage}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (options.targetDirectory) {
|
|
71
|
+
throw new Error(usage);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
options.targetDirectory = arg;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return options;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const {
|
|
81
|
+
siteDirectory,
|
|
82
|
+
targetDirectory,
|
|
83
|
+
type,
|
|
84
|
+
} = parseArgs(args);
|
|
85
|
+
|
|
86
|
+
if (!targetDirectory) {
|
|
87
|
+
throw new Error(usage);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!['pure', 'embedded'].includes(type)) {
|
|
91
|
+
throw new Error(`Invalid --type "${type}". Use "pure" or "embedded".`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!siteDirectory || path.isAbsolute(siteDirectory) || siteDirectory.split(/[\\/]/).includes('..')) {
|
|
95
|
+
throw new Error('--site-dir must be a non-empty relative path without "..".');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const starterRoot = path.join(engineRoot, 'starters', 'basic');
|
|
99
|
+
const starterSiteRoot = path.join(starterRoot, 'site');
|
|
100
|
+
const targetRoot = path.resolve(invocationRoot, targetDirectory);
|
|
101
|
+
const enginePackageJsonPath = path.join(engineRoot, 'package.json');
|
|
102
|
+
const targetPackageJsonPath = path.join(targetRoot, 'package.json');
|
|
103
|
+
const targetSiteDir = path.join(targetRoot, siteDirectory);
|
|
104
|
+
const isInsideEngineRoot = (candidatePath) => {
|
|
105
|
+
const relativePath = path.relative(engineRoot, candidatePath);
|
|
106
|
+
return relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const readDirectoryEntries = async (directoryPath) => {
|
|
110
|
+
try {
|
|
111
|
+
return await readdir(directoryPath);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error?.code === 'ENOENT') {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const readJsonFile = async (filePath) => JSON.parse(await readFile(filePath, 'utf8'));
|
|
122
|
+
|
|
123
|
+
const siteDirArg = siteDirectory === 'site' ? [] : ['--site-dir', siteDirectory];
|
|
124
|
+
const cliCommand = (command) => [cliExecutableName, ...siteDirArg, command].join(' ');
|
|
125
|
+
const galleryScripts = {
|
|
126
|
+
'norna:dev': cliCommand('dev:local'),
|
|
127
|
+
'norna:dev:lan': cliCommand('dev:lan'),
|
|
128
|
+
'norna:dev:restart': cliCommand('dev:restart'),
|
|
129
|
+
'norna:dev:status': cliCommand('dev:status'),
|
|
130
|
+
'norna:dev:logs': cliCommand('dev:logs'),
|
|
131
|
+
'norna:dev:stop': cliCommand('dev:stop'),
|
|
132
|
+
'norna:check': 'npm run norna:config:check && npm run norna:content:check',
|
|
133
|
+
'norna:config:check': cliCommand('config:check'),
|
|
134
|
+
'norna:content:check': cliCommand('content:check'),
|
|
135
|
+
'norna:sync': cliCommand('content:sync'),
|
|
136
|
+
'norna:typography:presets': cliCommand('typography:presets'),
|
|
137
|
+
'norna:typography:show': cliCommand('typography:show'),
|
|
138
|
+
'norna:public': cliCommand('site:public'),
|
|
139
|
+
'norna:images': cliCommand('images'),
|
|
140
|
+
'norna:build': cliCommand('build'),
|
|
141
|
+
'norna:build:local': cliCommand('build:local'),
|
|
142
|
+
'norna:deploy': cliCommand('deploy'),
|
|
143
|
+
'norna:deploy:commit': cliCommand('deploy:commit'),
|
|
144
|
+
'norna:deploy:watch': cliCommand('deploy:watch'),
|
|
145
|
+
'norna:doctor': cliCommand('doctor'),
|
|
146
|
+
'norna:preview': cliCommand('preview'),
|
|
147
|
+
'norna:engine:update': cliCommand('engine:update'),
|
|
148
|
+
'norna:engine:version': cliCommand('engine:version'),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const addGalleryDependency = (packageJson, version) => {
|
|
152
|
+
packageJson.dependencies ??= {};
|
|
153
|
+
packageJson.dependencies[packageName] ??= version;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const addGalleryScripts = (packageJson, scripts, { includePureAliases }) => {
|
|
157
|
+
packageJson.scripts ??= {};
|
|
158
|
+
const wantedScripts = {
|
|
159
|
+
...(includePureAliases ? { dev: 'npm run norna:dev' } : {}),
|
|
160
|
+
...scripts,
|
|
161
|
+
...(includePureAliases ? { build: 'npm run norna:build' } : {}),
|
|
162
|
+
};
|
|
163
|
+
const conflicts = Object.entries(wantedScripts).filter(([name, value]) => (
|
|
164
|
+
packageJson.scripts[name] !== undefined && packageJson.scripts[name] !== value
|
|
165
|
+
));
|
|
166
|
+
|
|
167
|
+
if (conflicts.length > 0) {
|
|
168
|
+
throw new Error([
|
|
169
|
+
'Refusing to overwrite existing npm scripts:',
|
|
170
|
+
...conflicts.map(([name, value]) => `- ${name}: existing "${packageJson.scripts[name]}", wanted "${value}"`),
|
|
171
|
+
].join('\n'));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Object.assign(packageJson.scripts, wantedScripts);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const enginePackageJson = await readJsonFile(enginePackageJsonPath);
|
|
178
|
+
const existingEntries = await readDirectoryEntries(targetRoot);
|
|
179
|
+
|
|
180
|
+
if (type === 'pure') {
|
|
181
|
+
if (existingEntries && existingEntries.length > 0) {
|
|
182
|
+
const existingPackageJson = existingEntries.includes('package.json')
|
|
183
|
+
? ' The target already contains package.json; use --type embedded to add a gallery to an existing project.'
|
|
184
|
+
: '';
|
|
185
|
+
throw new Error(`Target directory must be empty for --type pure: ${targetRoot}.${existingPackageJson}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await mkdir(path.dirname(targetRoot), { recursive: true });
|
|
189
|
+
await cp(starterRoot, targetRoot, {
|
|
190
|
+
filter: (source) => path.basename(source) !== '.DS_Store',
|
|
191
|
+
recursive: true,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (siteDirectory !== 'site') {
|
|
195
|
+
await mkdir(path.dirname(targetSiteDir), { recursive: true });
|
|
196
|
+
await rename(path.join(targetRoot, 'site'), targetSiteDir);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const targetPackageJson = await readJsonFile(targetPackageJsonPath);
|
|
200
|
+
targetPackageJson.dependencies[packageName] = enginePackageJson.version;
|
|
201
|
+
if (siteDirectory !== 'site') {
|
|
202
|
+
targetPackageJson.scripts = {};
|
|
203
|
+
addGalleryScripts(targetPackageJson, galleryScripts, { includePureAliases: true });
|
|
204
|
+
}
|
|
205
|
+
await writeFile(targetPackageJsonPath, `${JSON.stringify(targetPackageJson, null, 2)}\n`);
|
|
206
|
+
} else {
|
|
207
|
+
if (!existingEntries) {
|
|
208
|
+
throw new Error(`Target directory must exist for --type embedded: ${targetRoot}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const targetPackageJson = await readJsonFile(targetPackageJsonPath).catch((error) => {
|
|
212
|
+
if (error?.code === 'ENOENT') {
|
|
213
|
+
throw new Error(`Embedded setup requires an existing package.json in ${targetRoot}.`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
throw error;
|
|
217
|
+
});
|
|
218
|
+
const existingSiteEntries = await readDirectoryEntries(targetSiteDir);
|
|
219
|
+
|
|
220
|
+
if (existingSiteEntries && existingSiteEntries.length > 0) {
|
|
221
|
+
throw new Error(`Gallery site directory must be empty for --type embedded: ${targetSiteDir}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
addGalleryDependency(targetPackageJson, enginePackageJson.version);
|
|
225
|
+
addGalleryScripts(targetPackageJson, galleryScripts, { includePureAliases: false });
|
|
226
|
+
await mkdir(path.dirname(targetSiteDir), { recursive: true });
|
|
227
|
+
await cp(starterSiteRoot, targetSiteDir, {
|
|
228
|
+
filter: (source) => path.basename(source) !== '.DS_Store',
|
|
229
|
+
recursive: true,
|
|
230
|
+
});
|
|
231
|
+
await writeFile(targetPackageJsonPath, `${JSON.stringify(targetPackageJson, null, 2)}\n`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log(type === 'embedded'
|
|
235
|
+
? `Added norna site directory at ${targetSiteDir}`
|
|
236
|
+
: `Created norna site at ${targetRoot}`);
|
|
237
|
+
if (isInsideEngineRoot(targetRoot)) {
|
|
238
|
+
console.warn('');
|
|
239
|
+
console.warn('Warning: This site was created inside the norna engine repository.');
|
|
240
|
+
console.warn('For normal site projects, create the site next to the engine repository instead, for example as a sibling directory under your Projects folder.');
|
|
241
|
+
console.warn('That keeps site content, npm installs, commits, and releases separate from engine development.');
|
|
242
|
+
}
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log('Next steps:');
|
|
245
|
+
console.log(` cd ${targetRoot}`);
|
|
246
|
+
console.log(type === 'embedded'
|
|
247
|
+
? ' npm install'
|
|
248
|
+
: ' npm install');
|
|
249
|
+
console.log(' npm run norna:dev');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import {
|
|
4
|
+
engineRoot,
|
|
5
|
+
siteProjectRoot,
|
|
6
|
+
} from './site-paths.mjs';
|
|
7
|
+
import { runInherit } from './run-command.mjs';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const astroPackagePath = require.resolve('astro/package.json');
|
|
11
|
+
const astroPackage = require(astroPackagePath);
|
|
12
|
+
|
|
13
|
+
export const astroBinPath = path.join(path.dirname(astroPackagePath), astroPackage.bin.astro);
|
|
14
|
+
export const astroConfigPath = path.relative(siteProjectRoot, path.join(engineRoot, 'astro.config.mjs'))
|
|
15
|
+
.split(path.sep)
|
|
16
|
+
.join('/');
|
|
17
|
+
|
|
18
|
+
export const getAstroArgs = (args) => [
|
|
19
|
+
astroBinPath,
|
|
20
|
+
'--root',
|
|
21
|
+
siteProjectRoot,
|
|
22
|
+
'--config',
|
|
23
|
+
astroConfigPath,
|
|
24
|
+
...args,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const runAstroInherit = (args, options = {}) => runInherit(
|
|
28
|
+
process.execPath,
|
|
29
|
+
getAstroArgs(args),
|
|
30
|
+
{
|
|
31
|
+
cwd: siteProjectRoot,
|
|
32
|
+
...options,
|
|
33
|
+
},
|
|
34
|
+
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { runInherit } from './run-command.mjs';
|
|
2
|
+
|
|
3
|
+
export const ciNpmVersion = '11.16.0';
|
|
4
|
+
|
|
5
|
+
const ciPlatformArgs = [
|
|
6
|
+
'--os=linux',
|
|
7
|
+
'--cpu=x64',
|
|
8
|
+
'--libc=glibc',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const npmCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
12
|
+
|
|
13
|
+
const getNpmArgs = (command, args = []) => [
|
|
14
|
+
'--yes',
|
|
15
|
+
`npm@${ciNpmVersion}`,
|
|
16
|
+
command,
|
|
17
|
+
...args,
|
|
18
|
+
'--ignore-scripts',
|
|
19
|
+
'--no-audit',
|
|
20
|
+
'--no-fund',
|
|
21
|
+
...ciPlatformArgs,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const normalizeCiLockfile = (cwd, options = {}) => runInherit(
|
|
25
|
+
npmCommand,
|
|
26
|
+
getNpmArgs('install', ['--package-lock-only']),
|
|
27
|
+
{ cwd, ...options },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const verifyCiLockfile = (cwd, options = {}) => runInherit(
|
|
31
|
+
npmCommand,
|
|
32
|
+
getNpmArgs('ci', ['--dry-run']),
|
|
33
|
+
{ cwd, ...options },
|
|
34
|
+
);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
const pngSignature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
4
|
+
const jpegStartOfFrameMarkers = new Set([
|
|
5
|
+
0xc0,
|
|
6
|
+
0xc1,
|
|
7
|
+
0xc2,
|
|
8
|
+
0xc3,
|
|
9
|
+
0xc5,
|
|
10
|
+
0xc6,
|
|
11
|
+
0xc7,
|
|
12
|
+
0xc9,
|
|
13
|
+
0xca,
|
|
14
|
+
0xcb,
|
|
15
|
+
0xcd,
|
|
16
|
+
0xce,
|
|
17
|
+
0xcf,
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const isPng = (buffer) => buffer.length >= 24 && buffer.subarray(0, 8).equals(pngSignature);
|
|
21
|
+
|
|
22
|
+
const readPngDimensions = (buffer) => ({
|
|
23
|
+
width: buffer.readUInt32BE(16),
|
|
24
|
+
height: buffer.readUInt32BE(20),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const readJpegDimensions = (buffer) => {
|
|
28
|
+
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let offset = 2;
|
|
33
|
+
|
|
34
|
+
while (offset + 9 < buffer.length) {
|
|
35
|
+
while (buffer[offset] === 0xff) {
|
|
36
|
+
offset += 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const marker = buffer[offset];
|
|
40
|
+
offset += 1;
|
|
41
|
+
|
|
42
|
+
if (marker === 0xd9 || marker === 0xda) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (offset + 2 > buffer.length) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const segmentLength = buffer.readUInt16BE(offset);
|
|
51
|
+
if (segmentLength < 2 || offset + segmentLength > buffer.length) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (jpegStartOfFrameMarkers.has(marker)) {
|
|
56
|
+
if (segmentLength < 7) return null;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
height: buffer.readUInt16BE(offset + 3),
|
|
60
|
+
width: buffer.readUInt16BE(offset + 5),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
offset += segmentLength;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const readImageDimensions = async (filePath) => {
|
|
71
|
+
const buffer = await readFile(filePath);
|
|
72
|
+
|
|
73
|
+
if (isPng(buffer)) {
|
|
74
|
+
return readPngDimensions(buffer);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return readJpegDimensions(buffer);
|
|
78
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultTypography,
|
|
3
|
+
resolveTypographyConfig,
|
|
4
|
+
resolveTypographyOverride,
|
|
5
|
+
} from './typography.mjs';
|
|
6
|
+
|
|
7
|
+
export const fallbackPresentationColors = Object.freeze({
|
|
8
|
+
backgroundColor: 'transparent',
|
|
9
|
+
textColor: 'var(--color-text)',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const normalizePresentation = (presentation) => presentation ?? {};
|
|
13
|
+
|
|
14
|
+
export const resolveThemePresentation = (themePresentation) => {
|
|
15
|
+
const normalizedThemePresentation = normalizePresentation(themePresentation);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
backgroundColor: normalizedThemePresentation.backgroundColor ?? fallbackPresentationColors.backgroundColor,
|
|
19
|
+
textColor: normalizedThemePresentation.textColor ?? fallbackPresentationColors.textColor,
|
|
20
|
+
inlineStyles: normalizedThemePresentation.inlineStyles ?? {},
|
|
21
|
+
typography: resolveTypographyConfig(normalizedThemePresentation.typography ?? defaultTypography),
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const resolvePagePresentation = (themePresentation, pagePresentation) => {
|
|
26
|
+
const resolvedThemePresentation = resolveThemePresentation(themePresentation);
|
|
27
|
+
const normalizedPagePresentation = normalizePresentation(pagePresentation);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
backgroundColor: normalizedPagePresentation.backgroundColor ?? resolvedThemePresentation.backgroundColor,
|
|
31
|
+
textColor: normalizedPagePresentation.textColor ?? resolvedThemePresentation.textColor,
|
|
32
|
+
inlineStyles: resolvedThemePresentation.inlineStyles,
|
|
33
|
+
typography: resolveTypographyOverride(
|
|
34
|
+
resolvedThemePresentation.typography,
|
|
35
|
+
normalizedPagePresentation.typography,
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getColorsFromPresentation = (presentation) => ({
|
|
41
|
+
backgroundColor: presentation.backgroundColor,
|
|
42
|
+
textColor: presentation.textColor,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const resolveColorSource = ({ colors, themePresentation, themeFrameColors, pagePresentation }) => {
|
|
46
|
+
if (!colors || colors === 'theme') {
|
|
47
|
+
return themeFrameColors;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (colors === 'presentation') {
|
|
51
|
+
return getColorsFromPresentation(pagePresentation);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return colors;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const resolveFrameColors = ({ themePresentation, themeFrame, pagePresentation, pageFrame }) => {
|
|
58
|
+
const resolvedThemePresentation = resolveThemePresentation(themePresentation);
|
|
59
|
+
const themeFrameColors = resolveColorSource({
|
|
60
|
+
colors: themeFrame?.colors ?? 'presentation',
|
|
61
|
+
themePresentation: resolvedThemePresentation,
|
|
62
|
+
themeFrameColors: getColorsFromPresentation(resolvedThemePresentation),
|
|
63
|
+
pagePresentation: resolvedThemePresentation,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return resolveColorSource({
|
|
67
|
+
colors: pageFrame?.colors ?? 'theme',
|
|
68
|
+
themePresentation: resolvedThemePresentation,
|
|
69
|
+
themeFrameColors,
|
|
70
|
+
pagePresentation,
|
|
71
|
+
});
|
|
72
|
+
};
|