@flourish/sdk 3.19.0 → 4.1.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/README.md +37 -0
- package/RELEASE_NOTES.md +16 -0
- package/bin/flourish.js +3 -0
- package/lib/cmd/help.js +1 -1
- package/lib/cmd/login.js +34 -1
- package/lib/sdk.js +2 -1
- package/lib/validate_config.js +7 -4
- package/package.json +10 -10
- package/server/columns.js +1 -1
- package/server/comms_js.js +4 -5
- package/server/data.js +6 -8
- package/server/index.js +1 -1
- package/server/index_html.js +64 -78
- package/server/json.js +1 -1
- package/server/views/index.html +18 -7
- package/site/embedded.js +1 -1
- package/site/images/icon-scrolly.png +0 -0
- package/site/images/icon-slides.png +0 -0
- package/site/script.js +2 -2
- package/site/sdk.css +2 -2
- package/test/.eslintrc.json +3 -0
- package/test/lib/sdk.js +7 -6
- package/test/lib/validate_config.js +4 -4
- package/utils/state.js +1 -1
- package/README.md~ +0 -473
- package/RELEASE_NOTES.md~ +0 -372
- package/lib/sdk.js~ +0 -540
- package/lib/validate_config.js~ +0 -437
- package/package-lock.json~ +0 -2669
- package/package.json~ +0 -53
- package/server/index.js~ +0 -540
- package/test/lib/validate_config.js~ +0 -1006
package/README.md
CHANGED
|
@@ -168,6 +168,22 @@ document.body.style.fontFamily = state.body_font.url;
|
|
|
168
168
|
#### `hidden`
|
|
169
169
|
This type is for documenting state properties which should not be editable from the settings panel in the visualisation editor. Any property with a `hidden` type will not be editable within the visualisation editor interface.
|
|
170
170
|
|
|
171
|
+
#### Text editor settings
|
|
172
|
+
For `html` type settings, text editing functionality will automatically be added, such as making text bold or italic, adding a link, or referring to column headers from the data. You can add the `editor` property to override which editor functionalities to show. It accepts an object like this:
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
- property: popup_text
|
|
176
|
+
name: Popup text
|
|
177
|
+
type: html
|
|
178
|
+
editor: # An object specifying the different text editor functions (omitting `editor` will only load style, url and flourish_embed settings)
|
|
179
|
+
style: true # bold/italic/title buttons
|
|
180
|
+
url: true # "Add a link" button
|
|
181
|
+
flourish_embed: true # Add flourish iframe
|
|
182
|
+
bindings: [ data.name, data.values ] # Adds buttons for column headers from dataset. Use `true` to add all data bindings
|
|
183
|
+
bindings_custom: # A list of custom bindings that don't appear in the columns (e.g. {{VALUE}} for dynamic values)
|
|
184
|
+
- ["Current value", "VALUE"] # The first item in array is display label, the second item is what is added to the text
|
|
185
|
+
```
|
|
186
|
+
|
|
171
187
|
#### Conditional settings
|
|
172
188
|
Sometimes you might want to simplify the user experience for Flourish users by hiding some settings depending on whether they are needed or not. You can use the `show_if` and `hide_if` properties to control whether or not a setting should be displayed based on another setting’s value.
|
|
173
189
|
|
|
@@ -471,3 +487,24 @@ Called whenever the user changes a data table or setting in the visualisation ed
|
|
|
471
487
|
An object into which Flourish will put the data from user-editable data tables. Usually your code will initialise `data` as an empty object `{}`, and read from it in the `draw()` and `update()` functions.
|
|
472
488
|
|
|
473
489
|
Each property is a `dataset`: an array containing an object for each row in the relevant data table. The structure of each `dataset` is defined in the [data bindings of the `template.yml`](#data-bindings), and the data is loaded from the tables in the [`data/`](#data) directory.
|
|
490
|
+
|
|
491
|
+
### `.screenshot(opts, takeScreenshot)`
|
|
492
|
+
Called whenever the user has clicked "Download image" in the visualisation editor. This function gets called just before the image is downloaded and allows you to prepare the visualisation to be downloaded as an image. This is useful for example for disabling a template's animation, or converting HTML elements to SVG elements for SVG export.
|
|
493
|
+
|
|
494
|
+
The method has 2 arguments:
|
|
495
|
+
- `opts` – an object with information about the image to be downloaded
|
|
496
|
+
```js
|
|
497
|
+
{
|
|
498
|
+
download: true
|
|
499
|
+
filename: ""
|
|
500
|
+
format: "png" // can be png, svg or jpeg
|
|
501
|
+
height: "575"
|
|
502
|
+
scale: 1
|
|
503
|
+
width: "796"
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
- `takeScreenshot` – the Flourish screenshot function. This can be called after you've made amendments to your visualisation. Returns a promise which gets fired when the screenshot is finished, the callback takes two arguments for when the screenshot has succeeded or failed respectively, which can be used to undo any changes you made for the screenshot.
|
|
507
|
+
|
|
508
|
+
```js
|
|
509
|
+
takeScreenshot().then(onSuccess, onFail)
|
|
510
|
+
```
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
# 4.1.0
|
|
2
|
+
* Allow SDK CLI token based authentication.
|
|
3
|
+
|
|
4
|
+
# 4.0.0
|
|
5
|
+
* This is purely a Node.js compatibility break; it’s a major version change, because the SDK is
|
|
6
|
+
no longer compatible with old versions of Node.js: it now requires at least version 13.2.
|
|
7
|
+
There are no functional changes in this release.
|
|
8
|
+
|
|
9
|
+
# 3.20.1
|
|
10
|
+
* Use version 1.7.3 of the shell-quote package, to avoid a security issue that doesn’t
|
|
11
|
+
really affect us in this context, but could trigger spurious security warnings
|
|
12
|
+
(https://snyk.io/test/npm/shell-quote/1.6.1)
|
|
13
|
+
|
|
14
|
+
# 3.20.0
|
|
15
|
+
* Add the `html` setting type, which gives you a rich text editor for a settings value
|
|
16
|
+
|
|
1
17
|
# 3.19.0
|
|
2
18
|
* Add the `is_premium` top level property in template.yml to indicate that this is a premium template
|
|
3
19
|
|
package/bin/flourish.js
CHANGED
package/lib/cmd/help.js
CHANGED
|
@@ -28,7 +28,7 @@ Commands:
|
|
|
28
28
|
flourish [-h|--help|help] [topic]
|
|
29
29
|
flourish history [--full] template_id
|
|
30
30
|
flourish list [--full] [template id]
|
|
31
|
-
flourish login [email_address]
|
|
31
|
+
flourish login ( [email_address] | --token [token] )
|
|
32
32
|
flourish logout
|
|
33
33
|
flourish new directory_name
|
|
34
34
|
flourish publish [template_directory]
|
package/lib/cmd/login.js
CHANGED
|
@@ -34,6 +34,19 @@ exports.command = function login(args) {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function getToken() {
|
|
38
|
+
return new Promise(function(resolve, reject) {
|
|
39
|
+
if (args.token) {
|
|
40
|
+
resolve(args.token);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
read({ prompt: "SDK Token: ", silent: true }, function(error, token) {
|
|
44
|
+
if (error) return reject(error);
|
|
45
|
+
resolve(token);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
function login(email, password) {
|
|
38
51
|
return sdk.request(args, "user/login", { email: email, password: password })
|
|
39
52
|
.then((response) => {
|
|
@@ -47,6 +60,24 @@ exports.command = function login(args) {
|
|
|
47
60
|
.catch((error) => log.die("Failed to save SDK token", error.message));
|
|
48
61
|
});
|
|
49
62
|
}
|
|
63
|
+
async function loginWithToken(sdk_token) {
|
|
64
|
+
try {
|
|
65
|
+
await sdk.setSdkToken(args, sdk_token);
|
|
66
|
+
const user_info = await sdk.request(args, "user/whoami", {});
|
|
67
|
+
return user_info;
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
log.die("Failed to save SDK token", e.message);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if ("token" in args) {
|
|
74
|
+
getToken().then((token) => {
|
|
75
|
+
loginWithToken(token).then(user_info => {
|
|
76
|
+
log.victory("Logged in as " + user_info.email);
|
|
77
|
+
}).catch(error => log.die("Failed to save SDK token", error.message));
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
50
81
|
|
|
51
82
|
getEmail()
|
|
52
83
|
.then((email) => Promise.all([email, getPassword()]))
|
|
@@ -55,7 +86,9 @@ exports.command = function login(args) {
|
|
|
55
86
|
};
|
|
56
87
|
|
|
57
88
|
exports.help = `
|
|
58
|
-
|
|
89
|
+
Usage:
|
|
90
|
+
flourish login [email]
|
|
91
|
+
flourish login --token [token]
|
|
59
92
|
|
|
60
93
|
Log in to Flourish. You will be prompted for a password.
|
|
61
94
|
|
package/lib/sdk.js
CHANGED
|
@@ -21,6 +21,7 @@ const sdk_tokens_file = path.join(process.env.HOME || process.env.USERPROFILE, "
|
|
|
21
21
|
const YAML_DUMP_OPTS = { flowLevel: 4 };
|
|
22
22
|
const SDK_VERSION = require("../package.json").version;
|
|
23
23
|
const SDK_MAJOR_VERSION = semver.parse(SDK_VERSION)[0];
|
|
24
|
+
const SDK_MAJOR_VERSION_COMPAT = 3; // Flourish templates built for SDK version 3 are compatible with the current version
|
|
24
25
|
|
|
25
26
|
function getSdkToken(server_opts) {
|
|
26
27
|
return new Promise(function(resolve, reject) {
|
|
@@ -525,7 +526,7 @@ function checkTemplateVersion(template_dir) {
|
|
|
525
526
|
if (!template_sdk_version) {
|
|
526
527
|
throw new Error("Template does not specify an sdk_version");
|
|
527
528
|
}
|
|
528
|
-
if (template_sdk_version <
|
|
529
|
+
if (template_sdk_version < SDK_MAJOR_VERSION_COMPAT) {
|
|
529
530
|
throw new Error("This template was built for an older version of Flourish. Try running 'flourish upgrade'");
|
|
530
531
|
}
|
|
531
532
|
if (template_sdk_version > SDK_MAJOR_VERSION) {
|
package/lib/validate_config.js
CHANGED
|
@@ -175,7 +175,7 @@ function validateImport(template_directory, setting) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
const VALID_SETTING_TYPES = new Set(["number", "string", "text", "code", "boolean", "color", "colors", "url", "font", "hidden"]);
|
|
178
|
+
const VALID_SETTING_TYPES = new Set(["number", "string", "text", "html", "code", "boolean", "color", "colors", "url", "font", "hidden"]);
|
|
179
179
|
function validateSetting(template_directory, conditional_settings, setting) {
|
|
180
180
|
if (typeof setting !== "object" || Array.isArray(setting)) {
|
|
181
181
|
throw new Error("template.yml: setting must be a mapping");
|
|
@@ -228,9 +228,12 @@ function validateSetting(template_directory, conditional_settings, setting) {
|
|
|
228
228
|
throw new Error(`template.yml setting “${property}” has unsupported width property: must be “full”, “half”, “quarter” or “three quarters”`);
|
|
229
229
|
}
|
|
230
230
|
if ("size" in setting) {
|
|
231
|
-
const can_set_size = setting.type == "code" || setting.type == "text" || (setting.type == "string" && setting.style == "buttons");
|
|
232
|
-
if (!can_set_size) throw new Error(`template.yml setting “${property}” has a “size” property; this requires type “text” or “
|
|
233
|
-
else if (setting.size !== "large") throw new Error(`template.yml setting “${property}” has unsupported size property: must be “large”`);
|
|
231
|
+
const can_set_size = setting.type == "code" || setting.type == "text" || setting.type == "html" || (setting.type == "string" && setting.style == "buttons");
|
|
232
|
+
if (!can_set_size) throw new Error(`template.yml setting “${property}” has a “size” property; this requires type “text”, “code” or “html”, or type “string” with ”style: buttons”`);
|
|
233
|
+
else if (setting.size !== "large" && setting.size !== "small") throw new Error(`template.yml setting “${property}” has unsupported size property: must be “large” or “small”`);
|
|
234
|
+
}
|
|
235
|
+
if ("editor" in setting && setting.type != "html") {
|
|
236
|
+
throw new Error(`template.yml setting “${property}” has an “editor” field, but is not of type “html”`);
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flourish/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "The Flourish SDK",
|
|
5
5
|
"module": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -16,42 +16,42 @@
|
|
|
16
16
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
17
17
|
"repository": "kiln/flourish-sdk",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@flourish/interpreter": "^
|
|
19
|
+
"@flourish/interpreter": "^8.0.0",
|
|
20
20
|
"@flourish/semver": "^1.0.1",
|
|
21
21
|
"@flourish/transform-data": "^2.1.0",
|
|
22
22
|
"@handlebars/allow-prototype-access": "^1.0.3",
|
|
23
23
|
"@rollup/plugin-commonjs": "^17.1.0",
|
|
24
24
|
"archiver": "^5.0.2",
|
|
25
|
-
"chokidar": "^3.
|
|
25
|
+
"chokidar": "^3.5.3",
|
|
26
26
|
"cross-spawn": "^7.0.3",
|
|
27
27
|
"d3-dsv": "^2.0.0",
|
|
28
28
|
"express": "^4.17.1",
|
|
29
29
|
"form-data": "^4.0.0",
|
|
30
30
|
"handlebars": "^4.7.6",
|
|
31
31
|
"js-yaml": "^3.14.0",
|
|
32
|
-
"minimist": "^1.2.
|
|
32
|
+
"minimist": "^1.2.6",
|
|
33
33
|
"ncp": "^2.0.0",
|
|
34
34
|
"node-fetch": "^2.6.7",
|
|
35
|
-
"parse5": "^
|
|
35
|
+
"parse5": "^7.1.2",
|
|
36
36
|
"picocolors": "^1.0.0",
|
|
37
37
|
"read": "^1.0.7",
|
|
38
38
|
"resolve": "^1.18.1",
|
|
39
39
|
"rewrite-links": "^1.1.0",
|
|
40
40
|
"rollup-plugin-terser": "^7.0.2",
|
|
41
|
-
"shell-quote": "^1.7.
|
|
41
|
+
"shell-quote": "^1.7.3",
|
|
42
42
|
"tmp": "^0.2.1",
|
|
43
43
|
"ws": "^7.4.6"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@rollup/plugin-node-resolve": "^9.0.0",
|
|
47
47
|
"d3-request": "^1.0.6",
|
|
48
|
-
"mocha": "^
|
|
49
|
-
"npm-audit-resolver": "^
|
|
48
|
+
"mocha": "^10.0.0",
|
|
49
|
+
"npm-audit-resolver": "^3.0.0-7",
|
|
50
50
|
"rollup": "^2.32.1",
|
|
51
51
|
"sinon": "^9.2.0",
|
|
52
|
-
"tempy": "^
|
|
52
|
+
"tempy": "^3.0.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
|
-
"node": ">=
|
|
55
|
+
"node": ">=13.2"
|
|
56
56
|
}
|
|
57
57
|
}
|
package/server/columns.js
CHANGED
package/server/comms_js.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
const BEFORE = `
|
|
2
3
|
window.addEventListener("message", function(event) {
|
|
3
4
|
`;
|
|
4
|
-
|
|
5
5
|
const CHECK_ORIGIN = `
|
|
6
6
|
var a = document.createElement("a");
|
|
7
7
|
a.href = event.origin;
|
|
@@ -12,8 +12,9 @@ const CHECK_ORIGIN = `
|
|
|
12
12
|
|| (a.hostname.match(/\\.flourish\\.rocks$/) && window.location.hostname.match(/\\.flourish\\.rocks$/))
|
|
13
13
|
|| (a.hostname.match(/\\.flourish\\.studio$/) && window.location.hostname.match(/\\.flourish\\.studio$/))
|
|
14
14
|
|| (a.hostname == "app.flourish.studio" && window.location.hostname == "flourish-user-templates.com")
|
|
15
|
+
|| (a.hostname == "flourish-user-preview.com" && window.location.hostname == "flourish-user-templates.com")
|
|
15
16
|
|| (${"" /* Cope with previously-published stories, that are still on the old domain,
|
|
16
|
-
|
|
17
|
+
that have been republished (hence rerendered to use the new template URLs) */}
|
|
17
18
|
(a.hostname == "public.flourish.studio" && window.location.hostname == "flo.uri.sh")
|
|
18
19
|
|| (a.hostname == "public.flourish.rocks" && window.location.hostname == "staging-flo.uri.sh")
|
|
19
20
|
|| (a.hostname == "public.dev.flourish.rocks" && window.location.hostname == "dev-flo.uri.sh")
|
|
@@ -21,7 +22,6 @@ const CHECK_ORIGIN = `
|
|
|
21
22
|
|
|
22
23
|
if (!origin_okay) return;
|
|
23
24
|
`;
|
|
24
|
-
|
|
25
25
|
const AFTER = `
|
|
26
26
|
var message = event.data;
|
|
27
27
|
var port = event.ports[0];
|
|
@@ -122,7 +122,6 @@ const AFTER = `
|
|
|
122
122
|
}
|
|
123
123
|
}, false);
|
|
124
124
|
`;
|
|
125
|
-
|
|
126
125
|
const VALIDATE = `
|
|
127
126
|
if (template && template.draw && template.draw.length != 0) {
|
|
128
127
|
console.warn("The draw() function should be declared with no parameters");
|
|
@@ -131,7 +130,7 @@ if (template && template.update && template.update.length != 0) {
|
|
|
131
130
|
console.warn("The update() function should be declared with no parameters");
|
|
132
131
|
}
|
|
133
132
|
`;
|
|
134
|
-
|
|
135
133
|
exports.withOriginCheck = BEFORE + CHECK_ORIGIN + AFTER;
|
|
136
134
|
exports.withoutOriginCheck = BEFORE + AFTER;
|
|
137
135
|
exports.validate = VALIDATE;
|
|
136
|
+
//# sourceMappingURL=comms_js.js.map
|
package/server/data.js
CHANGED
|
@@ -6,11 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
8
8
|
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
|
-
|
|
13
|
-
var createInterpreter__default = /*#__PURE__*/_interopDefaultLegacy(createInterpreter);
|
|
9
|
+
var interpreter$1 = require('@flourish/interpreter');
|
|
14
10
|
|
|
15
11
|
// Polyfills for IE11 and Edge
|
|
16
12
|
|
|
@@ -61,7 +57,7 @@ function extractData(data_binding, data_by_id, column_types_by_id, template_data
|
|
|
61
57
|
|
|
62
58
|
function getInterpreter(data_table_id, column_index) {
|
|
63
59
|
const { type_id } = getInterpretationIds(data_table_id, column_index);
|
|
64
|
-
if (type_id) return
|
|
60
|
+
if (type_id) return interpreter$1.createInterpreter.getInterpretation(type_id);
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
for (var data_table_id in column_types_by_id) {
|
|
@@ -143,6 +139,7 @@ function extractData(data_binding, data_by_id, column_types_by_id, template_data
|
|
|
143
139
|
function parse(b, column_index, string_value) {
|
|
144
140
|
if (!b.template_data_binding.data_type) return string_value;
|
|
145
141
|
var interpreter = getInterpreter(b.data_table_id, column_index);
|
|
142
|
+
if (interpreter && interpreter.type == "number") string_value = stripCommonFixes(string_value);
|
|
146
143
|
var result = interpreter ? interpreter.parse(string_value) : string_value;
|
|
147
144
|
|
|
148
145
|
// We require our marshalled data to be JSON-serialisable,
|
|
@@ -246,7 +243,7 @@ function trimWhitespace(data) {
|
|
|
246
243
|
|
|
247
244
|
|
|
248
245
|
var ERROR_STRINGS = ["#DIV/0", "#N/A", "#NAME?", "#NULL!", "#NUM!", "#REF!", "#VALUE!", "#ERROR!"];
|
|
249
|
-
var interpreter =
|
|
246
|
+
var interpreter = interpreter$1.createInterpreter().nMax(Infinity).nFailingValues(8).failureFraction(0.1);
|
|
250
247
|
|
|
251
248
|
|
|
252
249
|
function stripCommonFixes(str) {
|
|
@@ -257,7 +254,7 @@ function stripCommonFixes(str) {
|
|
|
257
254
|
|
|
258
255
|
function transposeNestedArray(nested_array) {
|
|
259
256
|
var n_inner = nested_array.length;
|
|
260
|
-
var n_outer = nested_array[0].length;
|
|
257
|
+
var n_outer = n_inner > 0 ? nested_array[0].length : 0;
|
|
261
258
|
var transposed_array = [];
|
|
262
259
|
|
|
263
260
|
for (var i = 0; i < n_outer; i++) {
|
|
@@ -299,6 +296,7 @@ exports.getRandomSeededSample = getRandomSeededSample;
|
|
|
299
296
|
exports.getSlicedData = getSlicedData;
|
|
300
297
|
exports.interpretColumn = interpretColumn;
|
|
301
298
|
exports.mulberry32 = mulberry32;
|
|
299
|
+
exports.stripCommonFixes = stripCommonFixes;
|
|
302
300
|
exports.transposeNestedArray = transposeNestedArray;
|
|
303
301
|
exports.trimTrailingEmptyRows = trimTrailingEmptyRows;
|
|
304
302
|
exports.trimWhitespace = trimWhitespace;
|
package/server/index.js
CHANGED
|
@@ -25,7 +25,7 @@ const crypto = require("crypto"),
|
|
|
25
25
|
const { allowInsecurePrototypeAccess } = require("@handlebars/allow-prototype-access");
|
|
26
26
|
const handlebars = allowInsecurePrototypeAccess(require("handlebars"));
|
|
27
27
|
|
|
28
|
-
const TA = require("parse5
|
|
28
|
+
const { defaultTreeAdapter: TA } = require("parse5");
|
|
29
29
|
|
|
30
30
|
// Generate a static prefix randomly
|
|
31
31
|
//
|
package/server/index_html.js
CHANGED
|
@@ -1,100 +1,86 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
2
|
const URL = require("url");
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
RewriteLinks = require("rewrite-links");
|
|
7
|
-
|
|
8
|
-
const TA = require("parse5/lib/tree-adapters/default.js");
|
|
9
|
-
|
|
3
|
+
const parse5 = require("parse5"), RewriteLinks = require("rewrite-links");
|
|
4
|
+
const { defaultTreeAdapter: TA } = require("parse5");
|
|
10
5
|
function findChild(node, nodeName, ok_if_not_found) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
for (const child of TA.getChildNodes(node)) {
|
|
7
|
+
if (child.nodeName == nodeName)
|
|
8
|
+
return child;
|
|
9
|
+
}
|
|
10
|
+
if (ok_if_not_found)
|
|
11
|
+
return null;
|
|
12
|
+
throw new Error("Node not found: " + nodeName);
|
|
16
13
|
}
|
|
17
|
-
|
|
18
14
|
function findHtmlNode(document) {
|
|
19
|
-
|
|
15
|
+
return findChild(document, "html");
|
|
20
16
|
}
|
|
21
|
-
|
|
22
17
|
function findHead(document) {
|
|
23
|
-
|
|
18
|
+
return findChild(findHtmlNode(document), "head");
|
|
24
19
|
}
|
|
25
|
-
|
|
26
20
|
function findBody(document) {
|
|
27
|
-
|
|
21
|
+
return findChild(findHtmlNode(document), "body");
|
|
28
22
|
}
|
|
29
|
-
|
|
30
23
|
function replaceTitle(document, title) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
TA.insertText(title_node, title);
|
|
24
|
+
const head = findHead(document);
|
|
25
|
+
let title_node = findChild(head, "title", true);
|
|
26
|
+
if (title_node) {
|
|
27
|
+
for (const child of TA.getChildNodes(title_node)) {
|
|
28
|
+
TA.detachNode(child);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
title_node = TA.createElement("title", head.namespaceURI, []);
|
|
33
|
+
TA.appendChild(head, title_node);
|
|
34
|
+
}
|
|
35
|
+
TA.insertText(title_node, title);
|
|
45
36
|
}
|
|
46
|
-
|
|
47
37
|
function appendFragmentToBody(document, fragment) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
const body = findBody(document);
|
|
39
|
+
for (const child of TA.getChildNodes(fragment)) {
|
|
40
|
+
TA.appendChild(body, child);
|
|
41
|
+
}
|
|
52
42
|
}
|
|
53
|
-
|
|
54
43
|
function insertOembedLink(document, oembed_url) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
const head = findHead(document);
|
|
45
|
+
const link_node = TA.createElement("link", head.namespaceURI, [
|
|
46
|
+
{ name: "rel", value: "alternate" },
|
|
47
|
+
{ name: "type", value: "application/json+oembed" },
|
|
48
|
+
{ name: "href", value: oembed_url }
|
|
49
|
+
]);
|
|
50
|
+
TA.appendChild(head, link_node);
|
|
62
51
|
}
|
|
63
|
-
|
|
64
52
|
function insertCanonicalLink(document, canonical_url) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
53
|
+
const head = findHead(document);
|
|
54
|
+
const link_node = TA.createElement("link", head.namespaceURI, [
|
|
55
|
+
{ name: "rel", value: "canonical" },
|
|
56
|
+
{ name: "href", value: canonical_url }
|
|
57
|
+
]);
|
|
58
|
+
TA.appendChild(head, link_node);
|
|
71
59
|
}
|
|
72
|
-
|
|
73
60
|
function rewriteLinks(document, static_prefix) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
61
|
+
if (!static_prefix.endsWith("/"))
|
|
62
|
+
static_prefix += "/";
|
|
63
|
+
const rewriter = new RewriteLinks(function (url) {
|
|
64
|
+
// We don’t want to rewrite URLs that are just fragment identifiers
|
|
65
|
+
if (url.startsWith("#"))
|
|
66
|
+
return url;
|
|
67
|
+
// ... or relative self-links
|
|
68
|
+
if (url == "" || url == ".")
|
|
69
|
+
return url;
|
|
70
|
+
return URL.resolve(static_prefix, url); // eslint-disable-line node/no-deprecated-api
|
|
71
|
+
});
|
|
72
|
+
return rewriter.rewriteDocument(document);
|
|
86
73
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.then(parse5.serialize.bind(parse5));
|
|
74
|
+
async function render(template_text, params) {
|
|
75
|
+
const document = parse5.parse(template_text), script_fragment = params.parsed_script || parse5.parseFragment(params.script);
|
|
76
|
+
replaceTitle(document, params.title);
|
|
77
|
+
if (params.canonical_url)
|
|
78
|
+
insertCanonicalLink(document, params.canonical_url);
|
|
79
|
+
if (params.oembed_url)
|
|
80
|
+
insertOembedLink(document, params.oembed_url);
|
|
81
|
+
appendFragmentToBody(document, script_fragment);
|
|
82
|
+
const rewritten_document = await rewriteLinks(document, params.static);
|
|
83
|
+
return parse5.serialize(rewritten_document);
|
|
98
84
|
}
|
|
99
|
-
|
|
100
85
|
exports.render = render;
|
|
86
|
+
//# sourceMappingURL=index_html.js.map
|
package/server/json.js
CHANGED
package/server/views/index.html
CHANGED
|
@@ -3,13 +3,25 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
<script type="text/javascript">
|
|
8
|
+
function OptanonWrapper() {
|
|
9
|
+
OneTrust.OnConsentChanged(() => {
|
|
10
|
+
document.cookie = `OptanonAlertBoxClosed=${new Date().toISOString()};`
|
|
11
|
+
+ "SameSite=None;"
|
|
12
|
+
+ "Secure;"
|
|
13
|
+
+ `expires=${new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString()};`;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<title>Flourish | Data Visualization & Storytelling</title>
|
|
7
19
|
|
|
8
20
|
<script>if ("ontouchstart" in window) { document.documentElement.classList.add("is-touch"); }</script>
|
|
9
21
|
|
|
10
22
|
|
|
11
23
|
<link rel="stylesheet" type="text/css" href="/sdk.css">
|
|
12
|
-
<script src="/script.js" charset="utf-8"></script>
|
|
24
|
+
<script data-ot-ignore src="/script.js" charset="utf-8"></script>
|
|
13
25
|
</head>
|
|
14
26
|
<body class="sdk">
|
|
15
27
|
<div class="row header no-select" style="padding-left:10px">
|
|
@@ -17,10 +29,9 @@
|
|
|
17
29
|
</div>
|
|
18
30
|
<div class="row editor-bar">
|
|
19
31
|
<div id="preview-menu" class="row-menu left no-select">
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
</a>
|
|
32
|
+
<a id="full-preview" href="preview" target="_blank" class="menu-item clickable popup" data-popup-head="Full preview in new window" aria-label="Full preview in new window">
|
|
33
|
+
<i class="fa fa-expand"></i>
|
|
34
|
+
</a>
|
|
24
35
|
<div id="editor-previews">
|
|
25
36
|
<span id="editor-auto" data-target="auto" class="menu-item preview-mode clickable selected popup" data-popup-head="Preview using available width">
|
|
26
37
|
<i class="fa fa-tv"></i>
|
|
@@ -55,7 +66,7 @@
|
|
|
55
66
|
<p class="empty-label"></p>
|
|
56
67
|
<p class="empty-details"></p>
|
|
57
68
|
</div>
|
|
58
|
-
<iframe id="preview" sandbox="allow-same-origin allow-scripts allow-downloads" src="about:blank" data-testid="visualisation-iframe" aria-label="
|
|
69
|
+
<iframe id="preview" sandbox="allow-same-origin allow-scripts allow-downloads" src="about:blank" data-testid="visualisation-iframe" aria-label="Visualization preview"></iframe>
|
|
59
70
|
<div id="resize-overlay"></div>
|
|
60
71
|
<div id="resize-handle-container">
|
|
61
72
|
<div id="resize-handle"></div>
|