elm-pages 2.1.7 → 2.1.11
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/generator/review/elm.json +34 -0
- package/generator/review/src/ReviewConfig.elm +10 -0
- package/generator/src/basepath-middleware.js +15 -9
- package/generator/src/build.js +77 -4
- package/generator/src/cli.js +13 -9
- package/generator/src/compile-elm.js +43 -0
- package/generator/src/dev-server.js +63 -11
- package/generator/src/error-formatter.js +62 -9
- package/generator/src/generate-template-module-connector.js +17 -4
- package/generator/src/init.js +4 -0
- package/generator/src/pre-render-html.js +19 -12
- package/generator/src/render-worker.js +0 -1
- package/generator/src/render.js +1 -2
- package/generator/src/seo-renderer.js +21 -2
- package/generator/static-code/hmr.js +43 -6
- package/generator/template/elm.json +13 -5
- package/generator/template/package.json +3 -2
- package/package.json +14 -8
- package/src/ApiRoute.elm +178 -0
- package/src/AriaLiveAnnouncer.elm +36 -0
- package/src/BuildError.elm +60 -0
- package/src/DataSource/File.elm +288 -0
- package/src/DataSource/Glob.elm +1050 -0
- package/src/DataSource/Http.elm +467 -0
- package/src/DataSource/Internal/Glob.elm +74 -0
- package/src/DataSource/Port.elm +87 -0
- package/src/DataSource/ServerRequest.elm +60 -0
- package/src/DataSource.elm +801 -0
- package/src/Head/Seo.elm +516 -0
- package/src/Head/Twitter.elm +109 -0
- package/src/Head.elm +452 -0
- package/src/HtmlPrinter.elm +27 -0
- package/src/Internal/ApiRoute.elm +89 -0
- package/src/Internal/OptimizedDecoder.elm +18 -0
- package/src/KeepOrDiscard.elm +6 -0
- package/src/OptimizedDecoder/Pipeline.elm +335 -0
- package/src/OptimizedDecoder.elm +818 -0
- package/src/Pages/ContentCache.elm +248 -0
- package/src/Pages/Flags.elm +26 -0
- package/src/Pages/Http.elm +10 -0
- package/src/Pages/Internal/ApplicationType.elm +6 -0
- package/src/Pages/Internal/NotFoundReason.elm +256 -0
- package/src/Pages/Internal/Platform/Cli.elm +1015 -0
- package/src/Pages/Internal/Platform/Effect.elm +14 -0
- package/src/Pages/Internal/Platform/StaticResponses.elm +540 -0
- package/src/Pages/Internal/Platform/ToJsPayload.elm +138 -0
- package/src/Pages/Internal/Platform.elm +745 -0
- package/src/Pages/Internal/RoutePattern.elm +122 -0
- package/src/Pages/Internal/Router.elm +116 -0
- package/src/Pages/Internal/StaticHttpBody.elm +54 -0
- package/src/Pages/Internal/String.elm +39 -0
- package/src/Pages/Manifest/Category.elm +240 -0
- package/src/Pages/Manifest.elm +412 -0
- package/src/Pages/PageUrl.elm +38 -0
- package/src/Pages/ProgramConfig.elm +73 -0
- package/src/Pages/Review/NoContractViolations.elm +397 -0
- package/src/Pages/Secrets.elm +83 -0
- package/src/Pages/SiteConfig.elm +13 -0
- package/src/Pages/StaticHttp/Request.elm +42 -0
- package/src/Pages/StaticHttpRequest.elm +320 -0
- package/src/Pages/Url.elm +60 -0
- package/src/Path.elm +96 -0
- package/src/QueryParams.elm +216 -0
- package/src/RenderRequest.elm +163 -0
- package/src/RequestsAndPending.elm +20 -0
- package/src/Secrets.elm +111 -0
- package/src/SecretsDict.elm +45 -0
- package/src/StructuredData.elm +236 -0
- package/src/TerminalText.elm +242 -0
- package/src/Test/Html/Internal/ElmHtml/Constants.elm +53 -0
- package/src/Test/Html/Internal/ElmHtml/Helpers.elm +17 -0
- package/src/Test/Html/Internal/ElmHtml/InternalTypes.elm +529 -0
- package/src/Test/Html/Internal/ElmHtml/Markdown.elm +56 -0
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +197 -0
- package/src/Test/Internal/KernelConstants.elm +34 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const cliVersion = require("../../package.json").version;
|
|
2
2
|
const seo = require("./seo-renderer.js");
|
|
3
3
|
const elmPagesJsMinified = require("./elm-pages-js-minified.js");
|
|
4
|
-
const path = require("path");
|
|
4
|
+
const path = require("path").posix;
|
|
5
|
+
const jsesc = require("jsesc");
|
|
5
6
|
|
|
6
7
|
/** @typedef { { head: any[]; errors: any[]; contentJson: any[]; html: string; route: string; title: string; } } Arg */
|
|
7
8
|
/** @typedef { { tag : 'PageProgress'; args : Arg[] } } PageProgress */
|
|
@@ -10,11 +11,11 @@ module.exports =
|
|
|
10
11
|
/**
|
|
11
12
|
* @param {string} basePath
|
|
12
13
|
* @param {Arg} fromElm
|
|
13
|
-
* @param {
|
|
14
|
+
* @param {unknown} contentJson
|
|
14
15
|
* @param {boolean} devServer
|
|
15
16
|
* @returns {string}
|
|
16
17
|
*/
|
|
17
|
-
function wrapHtml(basePath, fromElm,
|
|
18
|
+
function wrapHtml(basePath, fromElm, contentJson, devServer) {
|
|
18
19
|
const devServerOnly = (/** @type {string} */ devServerOnlyString) =>
|
|
19
20
|
devServer ? devServerOnlyString : "";
|
|
20
21
|
const seoData = seo.gather(fromElm.head);
|
|
@@ -28,9 +29,9 @@ module.exports =
|
|
|
28
29
|
<link rel="modulepreload" href="${path.join(basePath, "index.js")}">
|
|
29
30
|
${devServerOnly(
|
|
30
31
|
/* html */ `<script defer="defer" src="${path.join(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
basePath,
|
|
33
|
+
"hmr.js"
|
|
34
|
+
)}" type="text/javascript"></script>`
|
|
34
35
|
)}
|
|
35
36
|
<script defer="defer" src="${path.join(
|
|
36
37
|
basePath,
|
|
@@ -44,13 +45,19 @@ ${elmPagesJsMinified}
|
|
|
44
45
|
</script>
|
|
45
46
|
<title>${fromElm.title}</title>
|
|
46
47
|
<meta name="generator" content="elm-pages v${cliVersion}">
|
|
47
|
-
<link rel="manifest" href="
|
|
48
|
+
<link rel="manifest" href="${path.join(basePath, "manifest.json")}">
|
|
48
49
|
<meta name="mobile-web-app-capable" content="yes">
|
|
49
50
|
<meta name="theme-color" content="#ffffff">
|
|
50
51
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
51
52
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
52
53
|
${seoData.headTags}
|
|
53
|
-
<script id="__ELM_PAGES_DATA__" type="application/json">${
|
|
54
|
+
<script id="__ELM_PAGES_DATA__" type="application/json">${jsesc(
|
|
55
|
+
contentJson,
|
|
56
|
+
{
|
|
57
|
+
isScriptContext: true,
|
|
58
|
+
json: true,
|
|
59
|
+
}
|
|
60
|
+
)}</script>
|
|
54
61
|
</head>
|
|
55
62
|
<body>
|
|
56
63
|
<div data-url="" display="none"></div>
|
|
@@ -74,10 +81,10 @@ function pathToRoot(cleanedRoute) {
|
|
|
74
81
|
return cleanedRoute === ""
|
|
75
82
|
? cleanedRoute
|
|
76
83
|
: cleanedRoute
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
.split("/")
|
|
85
|
+
.map((_) => "..")
|
|
86
|
+
.join("/")
|
|
87
|
+
.replace(/\.$/, "./");
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
function devServerStyleTag() {
|
package/generator/src/render.js
CHANGED
|
@@ -182,12 +182,11 @@ async function outputString(
|
|
|
182
182
|
contentJson["is404"] = args.is404;
|
|
183
183
|
contentJson["path"] = args.route;
|
|
184
184
|
const normalizedRoute = args.route.replace(/index$/, "");
|
|
185
|
-
const contentJsonString = JSON.stringify(contentJson);
|
|
186
185
|
|
|
187
186
|
return {
|
|
188
187
|
is404: args.is404,
|
|
189
188
|
route: normalizedRoute,
|
|
190
|
-
htmlString: preRenderHtml(basePath, args,
|
|
189
|
+
htmlString: preRenderHtml(basePath, args, contentJson, isDevServer),
|
|
191
190
|
contentJson: args.contentJson,
|
|
192
191
|
kind: "html",
|
|
193
192
|
};
|
|
@@ -60,7 +60,7 @@ function toString(/** @type { SeoTag[] } */ tags) {
|
|
|
60
60
|
/** @typedef {{ name: string; attributes: string[][]; type: 'head' }} HeadTag */
|
|
61
61
|
function appendTag(/** @type {HeadTag} */ tagDetails) {
|
|
62
62
|
const tagsString = tagDetails.attributes.map(([name, value]) => {
|
|
63
|
-
return
|
|
63
|
+
return pairToAttribute([name, value]);
|
|
64
64
|
});
|
|
65
65
|
return ` <${tagDetails.name} ${tagsString.join(" ")} />`;
|
|
66
66
|
}
|
|
@@ -77,5 +77,24 @@ ${JSON.stringify(tagDetails.contents)}
|
|
|
77
77
|
* @returns string
|
|
78
78
|
*/
|
|
79
79
|
function pairToAttribute([name, value]) {
|
|
80
|
-
return `${name}="${value}"`;
|
|
80
|
+
return `${name}="${quoteattr(value)}"`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function quoteattr(s, preserveCR) {
|
|
84
|
+
preserveCR = preserveCR ? " " : "\n";
|
|
85
|
+
return (
|
|
86
|
+
("" + s) /* Forces the conversion to string. */
|
|
87
|
+
.replace(/&/g, "&") /* This MUST be the 1st replacement. */
|
|
88
|
+
.replace(/'/g, "'") /* The 4 other predefined entities, required. */
|
|
89
|
+
.replace(/"/g, """)
|
|
90
|
+
.replace(/</g, "<")
|
|
91
|
+
.replace(/>/g, ">")
|
|
92
|
+
/*
|
|
93
|
+
You may add other replacements here for HTML only
|
|
94
|
+
(but it's not necessary).
|
|
95
|
+
Or for XML, only if the named entities are defined in its DTD.
|
|
96
|
+
*/
|
|
97
|
+
.replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */
|
|
98
|
+
.replace(/[\r\n]/g, preserveCR)
|
|
99
|
+
);
|
|
81
100
|
}
|
|
@@ -204,7 +204,7 @@ function colorConverter(color) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
const addNewLine = (str) => str + "\n";
|
|
207
|
-
const styleColor = (str = "WHITE") => `color: ${colorConverter(str)};`;
|
|
207
|
+
const styleColor = (str = "WHITE") => `color: ${colorConverter(str) || str};`;
|
|
208
208
|
const styleUnderline = `text-decoration: underline;`;
|
|
209
209
|
const styleBold = `text-decoration: bold;`;
|
|
210
210
|
const parseStyle = ({ underline, color, bold }) =>
|
|
@@ -255,13 +255,23 @@ const parseConsoleErrors = (path) =>
|
|
|
255
255
|
/**
|
|
256
256
|
* @param {{ title: string; message: Message[]}} info
|
|
257
257
|
* */
|
|
258
|
-
(info) =>
|
|
259
|
-
|
|
258
|
+
(info) => {
|
|
259
|
+
if (info.rule) {
|
|
260
|
+
return joinMessage(
|
|
261
|
+
info.formatted.reduce(consoleMsg, {
|
|
262
|
+
error: [consoleHeader(info.rule, path)],
|
|
263
|
+
style: [styleColor("blue")],
|
|
264
|
+
})
|
|
265
|
+
);
|
|
266
|
+
} else {
|
|
267
|
+
return joinMessage(
|
|
260
268
|
info.message.reduce(consoleMsg, {
|
|
261
269
|
error: [consoleHeader(info.title, path)],
|
|
262
270
|
style: [styleColor("blue")],
|
|
263
271
|
})
|
|
264
272
|
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
265
275
|
|
|
266
276
|
/**
|
|
267
277
|
* @param {RootObject} error
|
|
@@ -274,6 +284,12 @@ const restoreColorConsole = (error) => {
|
|
|
274
284
|
acc.concat(problems.map(parseConsoleErrors(path))),
|
|
275
285
|
[]
|
|
276
286
|
);
|
|
287
|
+
} else if (error.type === 'review-errors' && error.errors) {
|
|
288
|
+
return error.errors.reduce(
|
|
289
|
+
(acc, { errors, path }) =>
|
|
290
|
+
acc.concat(errors.map(parseConsoleErrors(path))),
|
|
291
|
+
[]
|
|
292
|
+
);
|
|
277
293
|
} else if (error.type === 'error') {
|
|
278
294
|
return parseConsoleErrors(error.path)(error)
|
|
279
295
|
} else {
|
|
@@ -298,8 +314,14 @@ const htmlMsg = (acc, msg) =>
|
|
|
298
314
|
typeof msg === "string" ? { color: "WHITE" } : msg
|
|
299
315
|
)}">${htmlSanitize(typeof msg === "string" ? msg : msg.string)}</span>`;
|
|
300
316
|
|
|
301
|
-
const parseHtmlErrors = (path) => (
|
|
302
|
-
|
|
317
|
+
const parseHtmlErrors = (path) => (info) => {
|
|
318
|
+
if (info.rule) {
|
|
319
|
+
return info.formatted.reduce(htmlMsg, htmlHeader(info.rule, path));
|
|
320
|
+
} else {
|
|
321
|
+
|
|
322
|
+
return info.message.reduce(htmlMsg, htmlHeader(info.title, path));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
303
325
|
|
|
304
326
|
const restoreColorHtml =
|
|
305
327
|
/**
|
|
@@ -312,6 +334,12 @@ const restoreColorHtml =
|
|
|
312
334
|
acc.concat(problems.map(parseHtmlErrors(path))),
|
|
313
335
|
[]
|
|
314
336
|
);
|
|
337
|
+
} else if (error.type === 'review-errors') {
|
|
338
|
+
return error.errors.reduce(
|
|
339
|
+
(acc, { errors, path }) =>
|
|
340
|
+
acc.concat(errors.map(parseHtmlErrors(path))),
|
|
341
|
+
[]
|
|
342
|
+
);
|
|
315
343
|
} else if (error.type === 'error') {
|
|
316
344
|
return parseHtmlErrors(error.path)(error);
|
|
317
345
|
} else {
|
|
@@ -627,4 +655,13 @@ function hideCompiling(velocity) {
|
|
|
627
655
|
|
|
628
656
|
/** @typedef { { title: string; region: Region; message: Message[]; } } Problem */
|
|
629
657
|
/** @typedef {string | {underline: boolean; color: string?; string: string}} Message */
|
|
630
|
-
/** @typedef { { path: string; name: string; problems: Problem[]; } } Error_ */
|
|
658
|
+
/** @typedef { { path: string; name: string; problems: Problem[]; } } Error_ */
|
|
659
|
+
|
|
660
|
+
/** @typedef { { type: "review-errors"; errors: IFileError[]; } } IElmReviewError */
|
|
661
|
+
|
|
662
|
+
/** @typedef { { path: string; errors: IError[]; } } IFileError */
|
|
663
|
+
|
|
664
|
+
/** @typedef { { rule: string; ruleLink: string; message: string; details: string[]; region: IRegion; fix?: { range: IRegion; string: string; }[]; } } IError */
|
|
665
|
+
|
|
666
|
+
/** @typedef { { start: IPosition; end: IPosition; } } IRegion */
|
|
667
|
+
/** @typedef { { line: number; column: number; } } IPosition */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"elm-version": "0.19.1",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"direct": {
|
|
10
|
-
"dillonkearns/elm-pages": "
|
|
10
|
+
"dillonkearns/elm-pages": "9.0.0",
|
|
11
11
|
"elm/browser": "1.0.2",
|
|
12
12
|
"elm/core": "1.0.5",
|
|
13
13
|
"elm/html": "1.0.0",
|
|
@@ -15,26 +15,34 @@
|
|
|
15
15
|
"elm/url": "1.0.0"
|
|
16
16
|
},
|
|
17
17
|
"indirect": {
|
|
18
|
-
"MartinSStewart/elm-serialize": "1.2.
|
|
18
|
+
"MartinSStewart/elm-serialize": "1.2.6",
|
|
19
19
|
"avh4/elm-color": "1.0.0",
|
|
20
|
-
"bburdette/toop": "1.0
|
|
20
|
+
"bburdette/toop": "1.2.0",
|
|
21
21
|
"danfishgold/base64-bytes": "1.1.0",
|
|
22
22
|
"danyx23/elm-mimetype": "4.0.1",
|
|
23
23
|
"dillonkearns/elm-bcp47-language-tag": "1.0.1",
|
|
24
24
|
"elm/bytes": "1.0.8",
|
|
25
25
|
"elm/file": "1.0.5",
|
|
26
26
|
"elm/http": "2.0.0",
|
|
27
|
+
"elm/parser": "1.1.0",
|
|
28
|
+
"elm/project-metadata-utils": "1.0.2",
|
|
27
29
|
"elm/random": "1.0.0",
|
|
28
30
|
"elm/regex": "1.0.0",
|
|
29
31
|
"elm/time": "1.0.0",
|
|
30
32
|
"elm/virtual-dom": "1.0.2",
|
|
31
33
|
"elm-community/dict-extra": "2.4.0",
|
|
32
|
-
"elm-community/list-extra": "8.
|
|
34
|
+
"elm-community/list-extra": "8.5.1",
|
|
35
|
+
"elm-explorations/test": "1.2.2",
|
|
33
36
|
"fredcy/elm-parseint": "2.0.1",
|
|
34
|
-
"
|
|
37
|
+
"jfmengels/elm-review": "2.6.1",
|
|
38
|
+
"mgold/elm-nonempty-list": "4.2.0",
|
|
35
39
|
"miniBill/elm-codec": "2.0.0",
|
|
40
|
+
"miniBill/elm-unicode": "1.0.2",
|
|
36
41
|
"noahzgordon/elm-color-extra": "1.0.2",
|
|
37
42
|
"robinheghan/murmur3": "1.0.0",
|
|
43
|
+
"rtfeldman/elm-hex": "1.0.0",
|
|
44
|
+
"stil4m/elm-syntax": "7.2.8",
|
|
45
|
+
"stil4m/structured-writer": "1.0.3",
|
|
38
46
|
"tripokey/elm-fuzzy": "5.2.1",
|
|
39
47
|
"vito/elm-ansi": "10.0.1",
|
|
40
48
|
"zwilias/json-decode-exploration": "6.0.0"
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elm-pages",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.11",
|
|
4
4
|
"homepage": "https://elm-pages.com",
|
|
5
5
|
"moduleResolution": "node",
|
|
6
6
|
"description": "Type-safe static sites, written in pure elm with your own custom elm-markup syntax.",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"start": "cd examples/end-to-end && npm start",
|
|
10
|
-
"test": "elm-verify-examples --run-tests && elm-test && (cd generator && mocha) && (cd examples/routing && npm i && npm run build -- --debug && elm-test)",
|
|
10
|
+
"test": "npx elmi-to-json --version && elm-verify-examples --run-tests && elm-test && (cd generator && mocha) && (cd examples/routing && npm i && npm run build -- --debug && elm-test) && npm run test:snapshot",
|
|
11
|
+
"test:snapshot": "(cd examples/escaping && npm install && npm test && cd ../..) && (cd examples/base-path && npm install && npm test && cd ../..)",
|
|
11
12
|
"cypress": "npm start & cypress run --spec cypress/integration/elm-pages-dev.spec.js",
|
|
12
13
|
"review": "elm-review"
|
|
13
14
|
},
|
|
@@ -23,39 +24,44 @@
|
|
|
23
24
|
"license": "BSD-3-Clause",
|
|
24
25
|
"dependencies": {
|
|
25
26
|
"chokidar": "3.5.2",
|
|
26
|
-
"commander": "8.
|
|
27
|
+
"commander": "^8.1.0",
|
|
27
28
|
"connect": "^3.7.0",
|
|
28
29
|
"cross-spawn": "7.0.3",
|
|
30
|
+
"devcert": "^1.2.0",
|
|
29
31
|
"elm-doc-preview": "^5.0.5",
|
|
30
32
|
"elm-hot": "^1.1.6",
|
|
31
33
|
"fs-extra": "^10.0.0",
|
|
32
34
|
"globby": "11.0.4",
|
|
33
35
|
"gray-matter": "^4.0.3",
|
|
36
|
+
"jsesc": "^3.0.2",
|
|
34
37
|
"kleur": "^4.1.4",
|
|
35
38
|
"micromatch": "^4.0.4",
|
|
36
39
|
"object-hash": "^2.2.0",
|
|
37
40
|
"serve-static": "^1.14.1",
|
|
38
|
-
"terser": "5.7.
|
|
39
|
-
"undici": "4.
|
|
41
|
+
"terser": "^5.7.2",
|
|
42
|
+
"undici": "^4.4.7",
|
|
40
43
|
"which": "^2.0.2"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@types/cross-spawn": "^6.0.2",
|
|
44
47
|
"@types/fs-extra": "9.0.12",
|
|
45
|
-
"@types/micromatch": "^4.0.
|
|
48
|
+
"@types/micromatch": "^4.0.2",
|
|
46
49
|
"@types/node": "12.20.12",
|
|
47
50
|
"@types/serve-static": "1.13.10",
|
|
48
|
-
"cypress": "^8.
|
|
51
|
+
"cypress": "^8.3.0",
|
|
49
52
|
"elm-optimize-level-2": "^0.1.5",
|
|
50
53
|
"elm-review": "^2.5.3",
|
|
51
54
|
"elm-test": "^0.19.1-revision7",
|
|
52
55
|
"elm-tooling": "^1.3.0",
|
|
53
56
|
"elm-verify-examples": "^5.0.0",
|
|
54
|
-
"
|
|
57
|
+
"elmi-to-json": "^1.2.0",
|
|
58
|
+
"mocha": "^9.1.0",
|
|
55
59
|
"typescript": "4.3.5"
|
|
56
60
|
},
|
|
57
61
|
"files": [
|
|
58
62
|
"generator/src/",
|
|
63
|
+
"generator/review/",
|
|
64
|
+
"src/",
|
|
59
65
|
"generator/template/",
|
|
60
66
|
"generator/static-code/"
|
|
61
67
|
],
|
package/src/ApiRoute.elm
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
module ApiRoute exposing (ApiRoute, ApiRouteBuilder, Response, buildTimeRoutes, capture, int, literal, single, slash, succeed, getBuildTimeRoutes)
|
|
2
|
+
|
|
3
|
+
{-| ApiRoute's are defined in `src/Api.elm` and are a way to generate files, like RSS feeds, sitemaps, or any text-based file that you output with an Elm function! You get access
|
|
4
|
+
to a DataSource so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in
|
|
5
|
+
the DataSource for your ApiRoutes, and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.
|
|
6
|
+
|
|
7
|
+
In a future release, ApiRoutes may be able to run at request-time in a serverless function, allowing you to use pure Elm code to create dynamic APIs, and even pulling in data from
|
|
8
|
+
DataSources dynamically.
|
|
9
|
+
|
|
10
|
+
@docs ApiRoute, ApiRouteBuilder, Response, buildTimeRoutes, capture, int, literal, single, slash, succeed, getBuildTimeRoutes
|
|
11
|
+
|
|
12
|
+
-}
|
|
13
|
+
|
|
14
|
+
import DataSource exposing (DataSource)
|
|
15
|
+
import Internal.ApiRoute exposing (ApiRoute(..), ApiRouteBuilder(..))
|
|
16
|
+
import Regex
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
{-| -}
|
|
20
|
+
type alias ApiRoute response =
|
|
21
|
+
Internal.ApiRoute.ApiRoute response
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
{-| -}
|
|
25
|
+
single : ApiRouteBuilder (DataSource Response) (List String) -> ApiRoute Response
|
|
26
|
+
single handler =
|
|
27
|
+
handler
|
|
28
|
+
|> buildTimeRoutes (\constructor -> DataSource.succeed [ constructor ])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
{-| -}
|
|
32
|
+
buildTimeRoutes : (constructor -> DataSource (List (List String))) -> ApiRouteBuilder (DataSource Response) constructor -> ApiRoute Response
|
|
33
|
+
buildTimeRoutes buildUrls ((ApiRouteBuilder pattern _ toString constructor) as fullHandler) =
|
|
34
|
+
let
|
|
35
|
+
buildTimeRoutes__ : DataSource (List String)
|
|
36
|
+
buildTimeRoutes__ =
|
|
37
|
+
buildUrls (constructor [])
|
|
38
|
+
|> DataSource.map (List.map toString)
|
|
39
|
+
|
|
40
|
+
preBuiltMatches : DataSource (List (List String))
|
|
41
|
+
preBuiltMatches =
|
|
42
|
+
buildUrls (constructor [])
|
|
43
|
+
in
|
|
44
|
+
ApiRoute
|
|
45
|
+
{ regex = Regex.fromString ("^" ++ pattern ++ "$") |> Maybe.withDefault Regex.never
|
|
46
|
+
, matchesToResponse =
|
|
47
|
+
\path ->
|
|
48
|
+
let
|
|
49
|
+
matches : List String
|
|
50
|
+
matches =
|
|
51
|
+
Internal.ApiRoute.pathToMatches path fullHandler
|
|
52
|
+
|
|
53
|
+
routeFound : DataSource Bool
|
|
54
|
+
routeFound =
|
|
55
|
+
preBuiltMatches
|
|
56
|
+
|> DataSource.map (List.member matches)
|
|
57
|
+
in
|
|
58
|
+
routeFound
|
|
59
|
+
|> DataSource.andThen
|
|
60
|
+
(\found ->
|
|
61
|
+
if found then
|
|
62
|
+
Internal.ApiRoute.tryMatch path fullHandler
|
|
63
|
+
|> Maybe.map (DataSource.map Just)
|
|
64
|
+
|> Maybe.withDefault (DataSource.succeed Nothing)
|
|
65
|
+
|
|
66
|
+
else
|
|
67
|
+
DataSource.succeed Nothing
|
|
68
|
+
)
|
|
69
|
+
, buildTimeRoutes = buildTimeRoutes__
|
|
70
|
+
, handleRoute =
|
|
71
|
+
\path ->
|
|
72
|
+
let
|
|
73
|
+
matches : List String
|
|
74
|
+
matches =
|
|
75
|
+
Internal.ApiRoute.pathToMatches path fullHandler
|
|
76
|
+
in
|
|
77
|
+
preBuiltMatches
|
|
78
|
+
|> DataSource.map (List.member matches)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
{-| -}
|
|
83
|
+
type alias ApiRouteBuilder a constructor =
|
|
84
|
+
Internal.ApiRoute.ApiRouteBuilder a constructor
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
{-| -}
|
|
88
|
+
type alias Response =
|
|
89
|
+
{ body : String }
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
{-| -}
|
|
93
|
+
succeed : a -> ApiRouteBuilder a (List String)
|
|
94
|
+
succeed a =
|
|
95
|
+
ApiRouteBuilder "" (\_ -> a) (\_ -> "") (\list -> list)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
{-| -}
|
|
99
|
+
literal : String -> ApiRouteBuilder a constructor -> ApiRouteBuilder a constructor
|
|
100
|
+
literal segment (ApiRouteBuilder pattern handler toString constructor) =
|
|
101
|
+
ApiRouteBuilder (pattern ++ segment) handler (\values -> toString values ++ segment) constructor
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
{-| -}
|
|
105
|
+
slash : ApiRouteBuilder a constructor -> ApiRouteBuilder a constructor
|
|
106
|
+
slash (ApiRouteBuilder pattern handler toString constructor) =
|
|
107
|
+
ApiRouteBuilder (pattern ++ "/") handler (\arg -> toString arg ++ "/") constructor
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
{-| -}
|
|
111
|
+
capture :
|
|
112
|
+
ApiRouteBuilder (String -> a) constructor
|
|
113
|
+
-> ApiRouteBuilder a (String -> constructor)
|
|
114
|
+
capture (ApiRouteBuilder pattern previousHandler toString constructor) =
|
|
115
|
+
ApiRouteBuilder
|
|
116
|
+
(pattern ++ "(.*)")
|
|
117
|
+
(\matches ->
|
|
118
|
+
case matches of
|
|
119
|
+
first :: rest ->
|
|
120
|
+
previousHandler rest first
|
|
121
|
+
|
|
122
|
+
_ ->
|
|
123
|
+
previousHandler [] "Error"
|
|
124
|
+
)
|
|
125
|
+
(\s ->
|
|
126
|
+
case s of
|
|
127
|
+
first :: rest ->
|
|
128
|
+
toString rest ++ first
|
|
129
|
+
|
|
130
|
+
_ ->
|
|
131
|
+
""
|
|
132
|
+
)
|
|
133
|
+
(\matches ->
|
|
134
|
+
\string ->
|
|
135
|
+
constructor (string :: matches)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
{-| -}
|
|
140
|
+
int :
|
|
141
|
+
ApiRouteBuilder (Int -> a) constructor
|
|
142
|
+
-> ApiRouteBuilder a (Int -> constructor)
|
|
143
|
+
int (ApiRouteBuilder pattern previousHandler toString constructor) =
|
|
144
|
+
ApiRouteBuilder
|
|
145
|
+
(pattern ++ "(\\d+)")
|
|
146
|
+
(\matches ->
|
|
147
|
+
case matches of
|
|
148
|
+
first :: rest ->
|
|
149
|
+
previousHandler rest (String.toInt first |> Maybe.withDefault -1)
|
|
150
|
+
|
|
151
|
+
_ ->
|
|
152
|
+
previousHandler [] -1
|
|
153
|
+
)
|
|
154
|
+
(\s ->
|
|
155
|
+
case s of
|
|
156
|
+
first :: rest ->
|
|
157
|
+
toString rest ++ first
|
|
158
|
+
|
|
159
|
+
_ ->
|
|
160
|
+
""
|
|
161
|
+
)
|
|
162
|
+
(\matches ->
|
|
163
|
+
\string ->
|
|
164
|
+
constructor (String.fromInt string :: matches)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
{-| For internal use by generated code. Not so useful in user-land.
|
|
169
|
+
-}
|
|
170
|
+
getBuildTimeRoutes : ApiRoute response -> DataSource (List String)
|
|
171
|
+
getBuildTimeRoutes (ApiRoute handler) =
|
|
172
|
+
handler.buildTimeRoutes
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
--captureRest : ApiRouteBuilder (List String -> a) b -> ApiRouteBuilder a b
|
|
177
|
+
--captureRest previousHandler =
|
|
178
|
+
-- Debug.todo ""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module AriaLiveAnnouncer exposing (view)
|
|
2
|
+
|
|
3
|
+
import Html exposing (Html)
|
|
4
|
+
import Html.Attributes as Attr
|
|
5
|
+
import Html.Lazy
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
{-| This ensures that page changes are announced with screen readers.
|
|
9
|
+
|
|
10
|
+
Inspired by <https://www.gatsbyjs.com/blog/2020-02-10-accessible-client-side-routing-improvements/>.
|
|
11
|
+
|
|
12
|
+
-}
|
|
13
|
+
view : String -> Html msg
|
|
14
|
+
view title =
|
|
15
|
+
Html.Lazy.lazy mainView title
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
mainView : String -> Html msg
|
|
19
|
+
mainView title =
|
|
20
|
+
Html.div
|
|
21
|
+
[ Attr.id "elm-pages-announcer"
|
|
22
|
+
, Attr.attribute "aria-live" "assertive"
|
|
23
|
+
, Attr.attribute "aria-atomic" "true"
|
|
24
|
+
|
|
25
|
+
--, Attr.attribute "ref" reference
|
|
26
|
+
, Attr.style "position" "absolute"
|
|
27
|
+
, Attr.style "top" "0"
|
|
28
|
+
, Attr.style "width" "1px"
|
|
29
|
+
, Attr.style "height" "1px"
|
|
30
|
+
, Attr.style "padding" "0"
|
|
31
|
+
, Attr.style "overflow" "hidden"
|
|
32
|
+
, Attr.style "clip" "rect(0, 0, 0, 0)"
|
|
33
|
+
, Attr.style "whiteSpace" "nowrap"
|
|
34
|
+
, Attr.style "border" "0"
|
|
35
|
+
]
|
|
36
|
+
[ Html.text <| "Navigated to " ++ title ]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module BuildError exposing (BuildError, encode, errorToString, errorsToString)
|
|
2
|
+
|
|
3
|
+
import Json.Encode as Encode
|
|
4
|
+
import TerminalText as Terminal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
type alias BuildError =
|
|
8
|
+
{ title : String
|
|
9
|
+
, path : String
|
|
10
|
+
, message : List Terminal.Text
|
|
11
|
+
, fatal : Bool
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
errorsToString : List BuildError -> String
|
|
16
|
+
errorsToString errors =
|
|
17
|
+
errors
|
|
18
|
+
|> List.map errorToString
|
|
19
|
+
|> String.join "\n\n"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
errorToString : BuildError -> String
|
|
23
|
+
errorToString error =
|
|
24
|
+
banner error.title
|
|
25
|
+
++ error.message
|
|
26
|
+
|> Terminal.toString
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
banner : String -> List Terminal.Text
|
|
30
|
+
banner title =
|
|
31
|
+
[ Terminal.cyan <|
|
|
32
|
+
("-- " ++ String.toUpper title ++ " ----------------------------------------------------- elm-pages")
|
|
33
|
+
, Terminal.text "\n\n"
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
encode : List BuildError -> Encode.Value
|
|
38
|
+
encode buildErrors =
|
|
39
|
+
Encode.object
|
|
40
|
+
[ ( "type", Encode.string "compile-errors" )
|
|
41
|
+
, ( "errors"
|
|
42
|
+
, Encode.list
|
|
43
|
+
(\buildError ->
|
|
44
|
+
Encode.object
|
|
45
|
+
[ ( "path", Encode.string buildError.path )
|
|
46
|
+
, ( "name", Encode.string buildError.title )
|
|
47
|
+
, ( "problems", Encode.list (messagesEncoder buildError.title) [ buildError.message ] )
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
buildErrors
|
|
51
|
+
)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
messagesEncoder : String -> List Terminal.Text -> Encode.Value
|
|
56
|
+
messagesEncoder title messages =
|
|
57
|
+
Encode.object
|
|
58
|
+
[ ( "title", Encode.string title )
|
|
59
|
+
, ( "message", Encode.list Terminal.encoder messages )
|
|
60
|
+
]
|