@aptoma/folio-tools 1.0.1
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/build-and-run.js +54 -0
- package/build.js +13 -0
- package/dev.js +16 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/src/types.d.ts +122 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +3 -0
- package/dist/src/utils.d.ts +14 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +84 -0
- package/package.json +48 -0
- package/server/dredition.js +53 -0
- package/server/grid-overlay.js +139 -0
- package/server/index.js +75 -0
- package/server/preview.js +68 -0
- package/server/products.js +36 -0
- package/test-vm.js +10 -0
package/build-and-run.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const esbuild = require('esbuild');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {execFileSync, spawn} = require('child_process');
|
|
6
|
+
const pkg = require(path.join(process.cwd(), 'package.json'));
|
|
7
|
+
|
|
8
|
+
let child = null;
|
|
9
|
+
|
|
10
|
+
function killChild(signal = 'SIGTERM') {
|
|
11
|
+
if (!child) return;
|
|
12
|
+
try {
|
|
13
|
+
child.kill(signal);
|
|
14
|
+
} catch (_) {
|
|
15
|
+
// ignore
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function startServer() {
|
|
20
|
+
killChild('SIGTERM');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
execFileSync(process.execPath, [path.join(__dirname, 'test-vm.js')], {stdio: 'inherit'});
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
child = spawn(process.execPath, [path.join(__dirname, 'server/index.js')], {stdio: 'inherit', shell: false});
|
|
29
|
+
child.on('exit', (code, signal) => {
|
|
30
|
+
child = null;
|
|
31
|
+
if (signal) process.exit(0);
|
|
32
|
+
process.exit(code ?? 0);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
process.on('SIGINT', () => {
|
|
37
|
+
killChild('SIGINT');
|
|
38
|
+
process.exit(0);
|
|
39
|
+
});
|
|
40
|
+
process.on('SIGTERM', () => {
|
|
41
|
+
killChild('SIGTERM');
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
process.on('exit', () => {
|
|
45
|
+
killChild('SIGTERM');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
esbuild.build({
|
|
49
|
+
entryPoints: ['src/index.ts'],
|
|
50
|
+
outfile: pkg.main,
|
|
51
|
+
bundle: true,
|
|
52
|
+
platform: 'node',
|
|
53
|
+
format: 'cjs'
|
|
54
|
+
}).then(startServer).catch(() => process.exit(1));
|
package/build.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const esbuild = require('esbuild');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const pkg = require(path.join(process.cwd(), 'package.json'));
|
|
6
|
+
|
|
7
|
+
esbuild.build({
|
|
8
|
+
entryPoints: ['src/index.ts'],
|
|
9
|
+
outfile: pkg.main,
|
|
10
|
+
bundle: true,
|
|
11
|
+
platform: 'node',
|
|
12
|
+
format: 'cjs'
|
|
13
|
+
}).catch(() => process.exit(1));
|
package/dev.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Starts nodemon with the folio build-and-run script.
|
|
4
|
+
// nodemon.json in the calling project controls watch paths and extensions.
|
|
5
|
+
const {spawn} = require('child_process');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const child = spawn(
|
|
9
|
+
require.resolve('nodemon/bin/nodemon'),
|
|
10
|
+
[path.join(__dirname, 'build-and-run.js')],
|
|
11
|
+
{stdio: 'inherit', shell: false}
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
15
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
16
|
+
child.on('exit', (code, signal) => process.exit(signal ? 0 : (code ?? 0)));
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./src/types"), exports);
|
|
18
|
+
__exportStar(require("./src/utils"), exports);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export interface Dimension {
|
|
2
|
+
height: number;
|
|
3
|
+
width: number;
|
|
4
|
+
unit: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RawMargin {
|
|
7
|
+
top: number;
|
|
8
|
+
bottom: number;
|
|
9
|
+
inside: number;
|
|
10
|
+
outside: number;
|
|
11
|
+
unit: string;
|
|
12
|
+
}
|
|
13
|
+
export interface Column {
|
|
14
|
+
number: number;
|
|
15
|
+
gap: number;
|
|
16
|
+
unit: string;
|
|
17
|
+
}
|
|
18
|
+
export interface Baseline {
|
|
19
|
+
height: number;
|
|
20
|
+
offsetTop: number;
|
|
21
|
+
unit: string;
|
|
22
|
+
}
|
|
23
|
+
export interface Edition {
|
|
24
|
+
_id: string;
|
|
25
|
+
publishDate: string;
|
|
26
|
+
dimension: Dimension;
|
|
27
|
+
margin: RawMargin;
|
|
28
|
+
baseline: Baseline;
|
|
29
|
+
column: Column;
|
|
30
|
+
}
|
|
31
|
+
export interface PageProperties {
|
|
32
|
+
section?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface Page {
|
|
36
|
+
_id: string;
|
|
37
|
+
number: number;
|
|
38
|
+
displayNumber: number;
|
|
39
|
+
withoutFolio?: boolean;
|
|
40
|
+
properties?: PageProperties;
|
|
41
|
+
baseline?: Partial<Baseline>;
|
|
42
|
+
column?: Partial<Column>;
|
|
43
|
+
margin?: Partial<RawMargin>;
|
|
44
|
+
}
|
|
45
|
+
export interface EditionObject {
|
|
46
|
+
_id: string;
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
height: number;
|
|
50
|
+
width: number;
|
|
51
|
+
pages: string[];
|
|
52
|
+
itemId?: string;
|
|
53
|
+
divider?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
export interface Item {
|
|
56
|
+
_id: string;
|
|
57
|
+
article?: Record<string, unknown>;
|
|
58
|
+
pdf?: Record<string, unknown>;
|
|
59
|
+
ad?: Record<string, unknown>;
|
|
60
|
+
html?: Record<string, unknown>;
|
|
61
|
+
frontpage?: Record<string, unknown>;
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
export interface Payload {
|
|
65
|
+
pageId: string;
|
|
66
|
+
data: {
|
|
67
|
+
edition: Edition;
|
|
68
|
+
pages: Page[];
|
|
69
|
+
objects: EditionObject[];
|
|
70
|
+
items: Item[];
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export interface ComputedMargin {
|
|
74
|
+
top: number;
|
|
75
|
+
bottom: number;
|
|
76
|
+
inside: number;
|
|
77
|
+
outside: number;
|
|
78
|
+
left: number;
|
|
79
|
+
right: number;
|
|
80
|
+
unit: 'mm';
|
|
81
|
+
}
|
|
82
|
+
export interface PagePosition {
|
|
83
|
+
top: string;
|
|
84
|
+
left: string;
|
|
85
|
+
width: string;
|
|
86
|
+
height: string;
|
|
87
|
+
}
|
|
88
|
+
export interface PageElement extends Record<string, unknown> {
|
|
89
|
+
_id: string;
|
|
90
|
+
index: number;
|
|
91
|
+
x: number;
|
|
92
|
+
y: number;
|
|
93
|
+
height: number;
|
|
94
|
+
width: number;
|
|
95
|
+
pages: string[];
|
|
96
|
+
itemId?: string;
|
|
97
|
+
divider?: Record<string, unknown>;
|
|
98
|
+
type?: string;
|
|
99
|
+
pagePosition: PagePosition;
|
|
100
|
+
}
|
|
101
|
+
export interface PreparedPage {
|
|
102
|
+
number: number;
|
|
103
|
+
side: 'left' | 'right';
|
|
104
|
+
dimensions: Dimension;
|
|
105
|
+
margin: ComputedMargin;
|
|
106
|
+
column: Column;
|
|
107
|
+
baseline: Baseline;
|
|
108
|
+
date: string;
|
|
109
|
+
withoutFolio?: boolean;
|
|
110
|
+
properties: PageProperties;
|
|
111
|
+
elements: PageElement[];
|
|
112
|
+
section: string;
|
|
113
|
+
}
|
|
114
|
+
export interface Layer {
|
|
115
|
+
name: string;
|
|
116
|
+
order: string;
|
|
117
|
+
html: string;
|
|
118
|
+
}
|
|
119
|
+
export interface Http {
|
|
120
|
+
request(opts: unknown, retries?: number): Promise<Response>;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Baseline, Column, ComputedMargin, Dimension, EditionObject, Item, PageElement, PreparedPage, RawMargin } from './types';
|
|
2
|
+
export declare function getGridCssProps({ column, baseline, dimensions, margin }: {
|
|
3
|
+
column: Column;
|
|
4
|
+
baseline: Baseline;
|
|
5
|
+
dimensions: Dimension;
|
|
6
|
+
margin: ComputedMargin;
|
|
7
|
+
}): string[];
|
|
8
|
+
export declare function getMarginsInMm(edition: {
|
|
9
|
+
margin: RawMargin;
|
|
10
|
+
}, page: {
|
|
11
|
+
margin?: Partial<RawMargin>;
|
|
12
|
+
}): ComputedMargin;
|
|
13
|
+
export declare function convertObjectsToPageElements(objects: EditionObject[], items: Item[], preparedPage: PreparedPage): PageElement[];
|
|
14
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAE/H,wBAAgB,eAAe,CAAC,EAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAC,EAAE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,SAAS,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACvB,GAAG,MAAM,EAAE,CAqBX;AAMD,wBAAgB,cAAc,CAAC,OAAO,EAAE;IAAC,MAAM,EAAE,SAAS,CAAA;CAAC,EAAE,IAAI,EAAE;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;CAAC,GAAG,cAAc,CAYhH;AAMD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,YAAY,GAAG,WAAW,EAAE,CAQ/H"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getGridCssProps = getGridCssProps;
|
|
4
|
+
exports.getMarginsInMm = getMarginsInMm;
|
|
5
|
+
exports.convertObjectsToPageElements = convertObjectsToPageElements;
|
|
6
|
+
function getGridCssProps({ column, baseline, dimensions, margin }) {
|
|
7
|
+
if (!column || !baseline)
|
|
8
|
+
return [];
|
|
9
|
+
const contentWidth = dimensions.width - margin.left - margin.right;
|
|
10
|
+
const colCount = column.number;
|
|
11
|
+
const gap = column.gap;
|
|
12
|
+
const colWidth = (contentWidth - gap * (colCount - 1)) / colCount;
|
|
13
|
+
const props = [`--grid-column-count: ${colCount}`];
|
|
14
|
+
for (let i = 1; i <= colCount; i++) {
|
|
15
|
+
const left = margin.left + (i - 1) * (colWidth + gap);
|
|
16
|
+
const right = left + colWidth;
|
|
17
|
+
props.push(`--grid-col-${i}-left: ${round(left)}mm`);
|
|
18
|
+
props.push(`--grid-col-${i}-right: ${round(right)}mm`);
|
|
19
|
+
}
|
|
20
|
+
const toMm = baseline.unit === 'pt' ? (n) => n * 0.3528 : (n) => n;
|
|
21
|
+
props.push(`--grid-baseline-first: ${round(toMm(baseline.offsetTop))}mm`);
|
|
22
|
+
props.push(`--grid-baseline-increment: ${round(toMm(baseline.height))}mm`);
|
|
23
|
+
return props;
|
|
24
|
+
}
|
|
25
|
+
function round(n) {
|
|
26
|
+
return Math.round(n * 1000) / 1000;
|
|
27
|
+
}
|
|
28
|
+
function getMarginsInMm(edition, page) {
|
|
29
|
+
const toMm = edition.margin.unit === 'pt' ? convertPt2Mm : (n) => n;
|
|
30
|
+
const m = Object.assign({}, edition.margin, page.margin);
|
|
31
|
+
return {
|
|
32
|
+
top: toMm(m.top),
|
|
33
|
+
bottom: toMm(m.bottom),
|
|
34
|
+
inside: toMm(m.inside),
|
|
35
|
+
outside: toMm(m.outside),
|
|
36
|
+
left: 0,
|
|
37
|
+
right: 0,
|
|
38
|
+
unit: 'mm'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function convertPt2Mm(value) {
|
|
42
|
+
return value * 0.3528;
|
|
43
|
+
}
|
|
44
|
+
function convertObjectsToPageElements(objects, items, preparedPage) {
|
|
45
|
+
return objects.reduce((acc, object, index) => {
|
|
46
|
+
const element = createPageElement(object, items, index + 1);
|
|
47
|
+
assignPagePosition(element, preparedPage);
|
|
48
|
+
acc.push(element);
|
|
49
|
+
return acc;
|
|
50
|
+
}, []);
|
|
51
|
+
}
|
|
52
|
+
function createPageElement(object, items, index) {
|
|
53
|
+
const element = {
|
|
54
|
+
...object,
|
|
55
|
+
index,
|
|
56
|
+
pagePosition: { top: '', left: '', width: '', height: '' }
|
|
57
|
+
};
|
|
58
|
+
if (object.divider) {
|
|
59
|
+
element.type = 'divider';
|
|
60
|
+
return element;
|
|
61
|
+
}
|
|
62
|
+
const item = items.find((item) => item._id === object.itemId);
|
|
63
|
+
if (item) {
|
|
64
|
+
Object.assign(element, item);
|
|
65
|
+
element.type = getItemType(item);
|
|
66
|
+
}
|
|
67
|
+
return element;
|
|
68
|
+
}
|
|
69
|
+
function getItemType(item) {
|
|
70
|
+
const types = ['article', 'pdf', 'ad', 'html', 'frontpage'];
|
|
71
|
+
return types.find((type) => item[type]);
|
|
72
|
+
}
|
|
73
|
+
function assignPagePosition(element, preparedPage) {
|
|
74
|
+
if (preparedPage.side === 'right' && element.pages.length > 1) {
|
|
75
|
+
const articleArea = preparedPage.dimensions.width - preparedPage.margin.inside - preparedPage.margin.outside;
|
|
76
|
+
element.x = element.x - articleArea - 2 * preparedPage.margin.inside;
|
|
77
|
+
}
|
|
78
|
+
element.pagePosition = {
|
|
79
|
+
top: element.y + 'mm',
|
|
80
|
+
left: element.x + 'mm',
|
|
81
|
+
width: element.width + 'mm',
|
|
82
|
+
height: element.height + 'mm'
|
|
83
|
+
};
|
|
84
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aptoma/folio-tools",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Build tooling and dev server for Aptoma DrEdition print folio renderers",
|
|
5
|
+
"license": "Unlicensed",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/",
|
|
10
|
+
"server/",
|
|
11
|
+
"build.js",
|
|
12
|
+
"build-and-run.js",
|
|
13
|
+
"dev.js",
|
|
14
|
+
"test-vm.js"
|
|
15
|
+
],
|
|
16
|
+
"bin": {
|
|
17
|
+
"folio-build": "./build.js",
|
|
18
|
+
"folio-dev": "./dev.js",
|
|
19
|
+
"folio-test-vm": "./test-vm.js"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/aptoma/folio-tools.git"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"prepublishOnly": "tsc",
|
|
28
|
+
"prerelease-check": "tsc --noEmit",
|
|
29
|
+
"release": "npm run prerelease-check && release-it -n -i patch",
|
|
30
|
+
"release:minor": "npm run prerelease-check && release-it -n -i minor",
|
|
31
|
+
"release:major": "npm run prerelease-check && release-it -n -i major"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@hapi/hapi": "^20.2.1",
|
|
35
|
+
"@hapi/inert": "^6.0.5",
|
|
36
|
+
"esbuild": "^0.27.3",
|
|
37
|
+
"joi": "^17.6.0",
|
|
38
|
+
"node-fetch": "^2.6.7",
|
|
39
|
+
"nodemon": "^2.0.15",
|
|
40
|
+
"vm2": "^3.10.5"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.3.0",
|
|
44
|
+
"auto-changelog": "^2.5.0",
|
|
45
|
+
"release-it": "^19.0.5",
|
|
46
|
+
"typescript": "^5.9.3"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
|
|
7
|
+
const config = require(path.join(process.cwd(), 'config/config'));
|
|
8
|
+
|
|
9
|
+
const httpsAgent = new https.Agent({rejectUnauthorized: false});
|
|
10
|
+
|
|
11
|
+
function apiFetch(url, extraHeaders = {}) {
|
|
12
|
+
return fetch(url, {
|
|
13
|
+
headers: {
|
|
14
|
+
Authorization: `apikey ${config.dredition.apikey}`,
|
|
15
|
+
...extraHeaders
|
|
16
|
+
},
|
|
17
|
+
agent: httpsAgent
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const editionCache = {};
|
|
22
|
+
|
|
23
|
+
async function getEditionById(editionId) {
|
|
24
|
+
if (!editionCache[editionId] || isExpired(editionCache[editionId])) {
|
|
25
|
+
const url = `${config.dredition.url}/print-editions/${editionId}`;
|
|
26
|
+
const response = await apiFetch(url, {Accept: 'application/vnd.dredition.v11+json'});
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const {data} = await response.json();
|
|
33
|
+
editionCache[editionId] = {data, lastUpdated: Date.now()};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return editionCache[editionId].data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isExpired({lastUpdated}) {
|
|
40
|
+
return lastUpdated < Date.now() - 15000;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function getPageFolioData(editionId, pageId) {
|
|
44
|
+
const url = `${config.dredition.url}/print-editions/${editionId}/pages/${pageId}/folio-data`;
|
|
45
|
+
return apiFetch(url, {Accept: 'application/vnd.dredition.v11+json'});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function getProductList() {
|
|
49
|
+
const url = `${config.dredition.url}/print-editions?page[number]=1&page[size]=20&fields[editions]=id,title,updatedAt&fields[products]=id,title&filter[isTemplate]=0&sort=-updatedAt`;
|
|
50
|
+
return apiFetch(url);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {getEditionById, getPageFolioData, getProductList};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class FgGridOverlay extends HTMLElement {
|
|
4
|
+
connectedCallback() {
|
|
5
|
+
if (this.shadowRoot) return; // guard against re-connection after DOM move
|
|
6
|
+
|
|
7
|
+
const st = getComputedStyle(document.documentElement);
|
|
8
|
+
const colCount = parseInt(st.getPropertyValue('--grid-column-count'), 10) || 0;
|
|
9
|
+
if (!colCount) return;
|
|
10
|
+
|
|
11
|
+
// Position relative to .page — move self there if needed
|
|
12
|
+
const page = document.querySelector('.page');
|
|
13
|
+
if (page && this.parentElement !== page) {
|
|
14
|
+
page.appendChild(this); // triggers connectedCallback again; guard above prevents double-init
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const shadow = this.attachShadow({mode: 'open'});
|
|
19
|
+
shadow.innerHTML = this._template(st, colCount);
|
|
20
|
+
|
|
21
|
+
const root = shadow;
|
|
22
|
+
function wire(cbId, layerId) {
|
|
23
|
+
const cb = root.getElementById(cbId);
|
|
24
|
+
const layer = root.getElementById(layerId);
|
|
25
|
+
if (localStorage.getItem(cbId) === 'true') {
|
|
26
|
+
cb.checked = true;
|
|
27
|
+
layer.style.display = 'block';
|
|
28
|
+
}
|
|
29
|
+
cb.addEventListener('change', function () {
|
|
30
|
+
layer.style.display = this.checked ? 'block' : 'none';
|
|
31
|
+
localStorage.setItem(cbId, this.checked);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
wire('fg-cb-margins', 'fg-margins');
|
|
35
|
+
wire('fg-cb-columns', 'fg-columns');
|
|
36
|
+
wire('fg-cb-baseline', 'fg-baseline');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_template(st, colCount) {
|
|
40
|
+
const columnLines = this._buildColumnLines(st, colCount);
|
|
41
|
+
return `
|
|
42
|
+
<style>
|
|
43
|
+
:host {
|
|
44
|
+
position: absolute;
|
|
45
|
+
top: 0;
|
|
46
|
+
left: 0;
|
|
47
|
+
width: 100%;
|
|
48
|
+
height: 100%;
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
z-index: 400;
|
|
51
|
+
}
|
|
52
|
+
#fg-margins {
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: var(--margin-top);
|
|
55
|
+
left: var(--margin-left);
|
|
56
|
+
right: var(--margin-right);
|
|
57
|
+
bottom: var(--margin-bottom);
|
|
58
|
+
border: 1px dashed rgba(224, 64, 251, 0.7);
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
#fg-columns {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: 0;
|
|
65
|
+
left: 0;
|
|
66
|
+
right: 0;
|
|
67
|
+
bottom: 0;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
display: none;
|
|
70
|
+
}
|
|
71
|
+
.col-line {
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: var(--margin-top);
|
|
74
|
+
bottom: var(--margin-bottom);
|
|
75
|
+
width: 1px;
|
|
76
|
+
background: rgba(77, 182, 172, 0.5);
|
|
77
|
+
}
|
|
78
|
+
#fg-baseline {
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: calc(var(--margin-top) + var(--grid-baseline-first));
|
|
81
|
+
left: var(--margin-left);
|
|
82
|
+
right: var(--margin-right);
|
|
83
|
+
bottom: var(--margin-bottom);
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
display: none;
|
|
86
|
+
background-image: repeating-linear-gradient(
|
|
87
|
+
to bottom,
|
|
88
|
+
rgba(3, 155, 229, 0.5) 0,
|
|
89
|
+
rgba(3, 155, 229, 0.5) 1px,
|
|
90
|
+
transparent 1px,
|
|
91
|
+
transparent var(--grid-baseline-increment)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
#fg-controls {
|
|
95
|
+
position: fixed;
|
|
96
|
+
bottom: 8px;
|
|
97
|
+
right: 8px;
|
|
98
|
+
z-index: 999;
|
|
99
|
+
font: 400 11px/20px sans-serif;
|
|
100
|
+
background: rgba(255, 255, 255, 0.92);
|
|
101
|
+
color: #111;
|
|
102
|
+
padding: 4px 10px;
|
|
103
|
+
border-radius: 3px;
|
|
104
|
+
display: flex;
|
|
105
|
+
gap: 10px;
|
|
106
|
+
align-items: center;
|
|
107
|
+
pointer-events: auto;
|
|
108
|
+
}
|
|
109
|
+
#fg-controls label {
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
display: flex;
|
|
112
|
+
gap: 4px;
|
|
113
|
+
align-items: center;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
116
|
+
<div id="fg-margins"></div>
|
|
117
|
+
<div id="fg-columns">${columnLines}</div>
|
|
118
|
+
<div id="fg-baseline"></div>
|
|
119
|
+
<div id="fg-controls">
|
|
120
|
+
<label><input type="checkbox" id="fg-cb-margins"> <span style="color:#e040fb">Margins</span></label>
|
|
121
|
+
<label><input type="checkbox" id="fg-cb-columns"> <span style="color:#4db6ac">Columns</span></label>
|
|
122
|
+
<label><input type="checkbox" id="fg-cb-baseline"> <span style="color:#039be5">Baseline</span></label>
|
|
123
|
+
</div>`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_buildColumnLines(st, colCount) {
|
|
127
|
+
const lines = [];
|
|
128
|
+
for (let i = 1; i <= colCount; i++) {
|
|
129
|
+
for (const side of ['left', 'right']) {
|
|
130
|
+
const x = st.getPropertyValue(`--grid-col-${i}-${side}`).trim();
|
|
131
|
+
lines.push(`<div class="col-line" style="left:${x}"></div>`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return lines.join('\n');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
customElements.define('fg-grid-overlay', FgGridOverlay);
|
|
139
|
+
document.body.appendChild(document.createElement('fg-grid-overlay'));
|
package/server/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const Hapi = require('@hapi/hapi');
|
|
5
|
+
const Joi = require('joi');
|
|
6
|
+
|
|
7
|
+
const config = require(path.join(process.cwd(), 'config/config'));
|
|
8
|
+
const {preview} = require('./preview');
|
|
9
|
+
const {products} = require('./products');
|
|
10
|
+
|
|
11
|
+
const server = Hapi.server(config.server);
|
|
12
|
+
|
|
13
|
+
start().catch((err) => {
|
|
14
|
+
console.error(err.stack);
|
|
15
|
+
server.stop({timeout: 5000}, () => {
|
|
16
|
+
console.log('Server stopped');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function start() {
|
|
22
|
+
await server.register([require('@hapi/inert')]);
|
|
23
|
+
|
|
24
|
+
server.route({
|
|
25
|
+
method: 'GET',
|
|
26
|
+
path: '/',
|
|
27
|
+
handler: products
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
server.route({
|
|
31
|
+
method: 'GET',
|
|
32
|
+
path: '/preview',
|
|
33
|
+
handler: preview,
|
|
34
|
+
options: {
|
|
35
|
+
validate: {
|
|
36
|
+
query: Joi.object({
|
|
37
|
+
editionId: Joi.string(),
|
|
38
|
+
pageId: Joi.string().default(''),
|
|
39
|
+
layer: Joi.number().default(0)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
server.route({
|
|
46
|
+
method: 'GET',
|
|
47
|
+
path: '/stylesheets/{param*}',
|
|
48
|
+
handler: {
|
|
49
|
+
directory: {
|
|
50
|
+
path: path.join(process.cwd(), 'stylesheets')
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
server.route({
|
|
56
|
+
method: 'GET',
|
|
57
|
+
path: '/files/{param*}',
|
|
58
|
+
handler: {
|
|
59
|
+
directory: {
|
|
60
|
+
path: path.join(process.cwd(), 'files')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
server.route({
|
|
66
|
+
method: 'GET',
|
|
67
|
+
path: '/dev/grid-overlay.js',
|
|
68
|
+
handler: {
|
|
69
|
+
file: path.join(__dirname, 'grid-overlay.js')
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await server.start();
|
|
74
|
+
console.log('Folio server running at:', server.info.uri);
|
|
75
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const config = require(path.join(process.cwd(), 'config/config'));
|
|
6
|
+
const pkg = require(path.join(process.cwd(), 'package.json'));
|
|
7
|
+
const renderer = require(path.join(process.cwd(), pkg.main));
|
|
8
|
+
const {getEditionById, getPageFolioData} = require('./dredition');
|
|
9
|
+
|
|
10
|
+
const GRID_OVERLAY = `<script src="/dev/grid-overlay.js"></script>`;
|
|
11
|
+
|
|
12
|
+
const PAGE_NAV_SCRIPT = `<script>
|
|
13
|
+
document.addEventListener('keydown', function (e) {
|
|
14
|
+
if (e.key !== 'j' && e.key !== 'k') return;
|
|
15
|
+
var links = Array.from(document.querySelectorAll('.page-nav a'));
|
|
16
|
+
var currentPageId = new URLSearchParams(location.search).get('pageId');
|
|
17
|
+
var current = links.findIndex(function (a) {
|
|
18
|
+
return new URL(a.href).searchParams.get('pageId') === currentPageId;
|
|
19
|
+
});
|
|
20
|
+
var target = current + (e.key === 'j' ? 1 : -1);
|
|
21
|
+
if (target >= 0 && target < links.length) location.href = links[target].href;
|
|
22
|
+
});
|
|
23
|
+
</script>`;
|
|
24
|
+
|
|
25
|
+
async function getPageLinks(editionId) {
|
|
26
|
+
const data = await getEditionById(editionId);
|
|
27
|
+
if (!data) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return data.pageOrder.map((pageId, index) => {
|
|
31
|
+
return `<a href="/preview?editionId=${editionId}&pageId=${pageId}">${index + 1}</a>`;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function getFirstPageId(editionId) {
|
|
36
|
+
const data = await getEditionById(editionId);
|
|
37
|
+
if (!data) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return data.pageOrder[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function preview(req) {
|
|
44
|
+
const editionId = req.query.editionId;
|
|
45
|
+
const pageId = req.query.pageId || (await getFirstPageId(editionId));
|
|
46
|
+
const pages = await getPageLinks(editionId);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const response = await getPageFolioData(editionId, pageId);
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
data.help = `Check api url, used: ${config.dredition.url}`;
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const layers = await renderer.render(data, `http://localhost:${config.server.port}`, null);
|
|
58
|
+
return layers[req.query.layer].html.replace(
|
|
59
|
+
'</body>',
|
|
60
|
+
`${GRID_OVERLAY}<div class="page-nav">${pages.join('\n')}</div>${PAGE_NAV_SCRIPT}</body>`
|
|
61
|
+
);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.log(err);
|
|
64
|
+
return err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {preview};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {getProductList} = require('./dredition');
|
|
4
|
+
|
|
5
|
+
async function products() {
|
|
6
|
+
try {
|
|
7
|
+
const response = await getProductList();
|
|
8
|
+
const result = await response.json();
|
|
9
|
+
|
|
10
|
+
if (result.errors) {
|
|
11
|
+
return result.errors;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const productsWithEditions = result.included.products.map((product) => {
|
|
15
|
+
product.editions = result.data.filter((edition) => edition.product === product._id);
|
|
16
|
+
return product;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return `
|
|
20
|
+
<html><body style="font: 400 12pt/16pt sans-serif">${productsWithEditions.reduce((html, product) => {
|
|
21
|
+
return (
|
|
22
|
+
html +
|
|
23
|
+
`<h2>${product.title}</h2>` +
|
|
24
|
+
product.editions.reduce((html, edition) => {
|
|
25
|
+
return html + `<a href="/preview?editionId=${edition._id}">${edition.title}</a><br/>`;
|
|
26
|
+
}, '')
|
|
27
|
+
);
|
|
28
|
+
}, '')}</body></html>
|
|
29
|
+
`;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.log(err);
|
|
32
|
+
return err;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {products};
|
package/test-vm.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const {NodeVM, VMScript} = require('vm2');
|
|
5
|
+
const vm = new NodeVM({
|
|
6
|
+
sandbox: {self: null}
|
|
7
|
+
});
|
|
8
|
+
const file = require('fs').readFileSync('./files/index.js').toString();
|
|
9
|
+
const script = new VMScript(file);
|
|
10
|
+
vm.run(script);
|