elm-pages 2.1.6 → 2.1.10

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.
Files changed (75) hide show
  1. package/generator/review/elm.json +34 -0
  2. package/generator/review/src/ReviewConfig.elm +10 -0
  3. package/generator/src/basepath-middleware.js +15 -9
  4. package/generator/src/build.js +100 -6
  5. package/generator/src/cli.js +13 -9
  6. package/generator/src/compile-elm.js +43 -0
  7. package/generator/src/dev-server.js +63 -11
  8. package/generator/src/error-formatter.js +62 -9
  9. package/generator/src/generate-template-module-connector.js +17 -4
  10. package/generator/src/init.js +4 -0
  11. package/generator/src/pre-render-html.js +19 -12
  12. package/generator/src/render-worker.js +0 -1
  13. package/generator/src/render.js +1 -2
  14. package/generator/src/seo-renderer.js +21 -2
  15. package/generator/static-code/hmr.js +43 -6
  16. package/generator/template/elm-tooling.json +9 -0
  17. package/generator/template/package.json +5 -1
  18. package/package.json +16 -9
  19. package/src/ApiRoute.elm +178 -0
  20. package/src/AriaLiveAnnouncer.elm +36 -0
  21. package/src/BuildError.elm +60 -0
  22. package/src/DataSource/File.elm +288 -0
  23. package/src/DataSource/Glob.elm +1050 -0
  24. package/src/DataSource/Http.elm +467 -0
  25. package/src/DataSource/Internal/Glob.elm +74 -0
  26. package/src/DataSource/Port.elm +87 -0
  27. package/src/DataSource/ServerRequest.elm +60 -0
  28. package/src/DataSource.elm +801 -0
  29. package/src/Head/Seo.elm +516 -0
  30. package/src/Head/Twitter.elm +109 -0
  31. package/src/Head.elm +452 -0
  32. package/src/HtmlPrinter.elm +27 -0
  33. package/src/Internal/ApiRoute.elm +89 -0
  34. package/src/Internal/OptimizedDecoder.elm +18 -0
  35. package/src/KeepOrDiscard.elm +6 -0
  36. package/src/OptimizedDecoder/Pipeline.elm +335 -0
  37. package/src/OptimizedDecoder.elm +818 -0
  38. package/src/Pages/ContentCache.elm +248 -0
  39. package/src/Pages/Flags.elm +26 -0
  40. package/src/Pages/Http.elm +10 -0
  41. package/src/Pages/Internal/ApplicationType.elm +6 -0
  42. package/src/Pages/Internal/NotFoundReason.elm +256 -0
  43. package/src/Pages/Internal/Platform/Cli.elm +1015 -0
  44. package/src/Pages/Internal/Platform/Effect.elm +14 -0
  45. package/src/Pages/Internal/Platform/StaticResponses.elm +540 -0
  46. package/src/Pages/Internal/Platform/ToJsPayload.elm +138 -0
  47. package/src/Pages/Internal/Platform.elm +745 -0
  48. package/src/Pages/Internal/RoutePattern.elm +122 -0
  49. package/src/Pages/Internal/Router.elm +116 -0
  50. package/src/Pages/Internal/StaticHttpBody.elm +54 -0
  51. package/src/Pages/Internal/String.elm +39 -0
  52. package/src/Pages/Manifest/Category.elm +240 -0
  53. package/src/Pages/Manifest.elm +412 -0
  54. package/src/Pages/PageUrl.elm +38 -0
  55. package/src/Pages/ProgramConfig.elm +73 -0
  56. package/src/Pages/Review/NoContractViolations.elm +397 -0
  57. package/src/Pages/Secrets.elm +83 -0
  58. package/src/Pages/SiteConfig.elm +13 -0
  59. package/src/Pages/StaticHttp/Request.elm +42 -0
  60. package/src/Pages/StaticHttpRequest.elm +320 -0
  61. package/src/Pages/Url.elm +60 -0
  62. package/src/Path.elm +96 -0
  63. package/src/QueryParams.elm +216 -0
  64. package/src/RenderRequest.elm +163 -0
  65. package/src/RequestsAndPending.elm +20 -0
  66. package/src/Secrets.elm +111 -0
  67. package/src/SecretsDict.elm +45 -0
  68. package/src/StructuredData.elm +236 -0
  69. package/src/TerminalText.elm +242 -0
  70. package/src/Test/Html/Internal/ElmHtml/Constants.elm +53 -0
  71. package/src/Test/Html/Internal/ElmHtml/Helpers.elm +17 -0
  72. package/src/Test/Html/Internal/ElmHtml/InternalTypes.elm +529 -0
  73. package/src/Test/Html/Internal/ElmHtml/Markdown.elm +56 -0
  74. package/src/Test/Html/Internal/ElmHtml/ToString.elm +197 -0
  75. 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 {string} contentJsonString
14
+ * @param {unknown} contentJson
14
15
  * @param {boolean} devServer
15
16
  * @returns {string}
16
17
  */
17
- function wrapHtml(basePath, fromElm, contentJsonString, devServer) {
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
- basePath,
32
- "hmr.js"
33
- )}" type="text/javascript"></script>`
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="/manifest.json">
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">${contentJsonString}</script>
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
- .split("/")
78
- .map((_) => "..")
79
- .join("/")
80
- .replace(/\.$/, "./");
84
+ .split("/")
85
+ .map((_) => "..")
86
+ .join("/")
87
+ .replace(/\.$/, "./");
81
88
  }
82
89
 
83
90
  function devServerStyleTag() {
@@ -76,7 +76,6 @@ async function outputString(
76
76
  `dist/${normalizedRoute}/content.json`,
77
77
  contentJsonString
78
78
  );
79
- // parentPort.postMessage({ tag: "done" });
80
79
  parentPort.postMessage({ tag: "done" });
81
80
  break;
82
81
  }
@@ -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, contentJsonString, isDevServer),
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 `${name}="${value}"`;
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 ? "&#13;" : "\n";
85
+ return (
86
+ ("" + s) /* Forces the conversion to string. */
87
+ .replace(/&/g, "&amp;") /* This MUST be the 1st replacement. */
88
+ .replace(/'/g, "&apos;") /* The 4 other predefined entities, required. */
89
+ .replace(/"/g, "&quot;")
90
+ .replace(/</g, "&lt;")
91
+ .replace(/>/g, "&gt;")
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
- joinMessage(
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) => ({ title, message }) =>
302
- message.reduce(htmlMsg, htmlHeader(title, path));
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 */
@@ -0,0 +1,9 @@
1
+ {
2
+ "entrypoints": [
3
+ "./src/Main.elm"
4
+ ],
5
+ "tools": {
6
+ "elm": "0.19.1",
7
+ "elm-format": "0.8.5"
8
+ }
9
+ }
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "elm-pages-app",
3
3
  "scripts": {
4
+ "postinstall": "elm-tooling install",
4
5
  "start": "elm-pages dev",
5
6
  "build": "elm-pages build"
6
7
  },
7
8
  "devDependencies": {
8
- "elm-pages": "^2.1.6"
9
+ "elm-optimize-level-2": "^0.1.5",
10
+ "elm-pages": "2.1.9",
11
+ "elm-review": "^2.5.5",
12
+ "elm-tooling": "^1.4.0"
9
13
  }
10
14
  }
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "elm-pages",
3
- "version": "2.1.6",
3
+ "version": "2.1.10",
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,38 +24,44 @@
23
24
  "license": "BSD-3-Clause",
24
25
  "dependencies": {
25
26
  "chokidar": "3.5.2",
26
- "commander": "8.0.0",
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
- "elm-optimize-level-2": "^0.1.5",
32
33
  "fs-extra": "^10.0.0",
33
34
  "globby": "11.0.4",
34
35
  "gray-matter": "^4.0.3",
36
+ "jsesc": "^3.0.2",
35
37
  "kleur": "^4.1.4",
36
38
  "micromatch": "^4.0.4",
37
39
  "object-hash": "^2.2.0",
38
40
  "serve-static": "^1.14.1",
39
- "terser": "5.7.1",
40
- "undici": "4.2.1"
41
+ "terser": "^5.7.2",
42
+ "undici": "^4.4.7",
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.1",
48
+ "@types/micromatch": "^4.0.2",
46
49
  "@types/node": "12.20.12",
47
50
  "@types/serve-static": "1.13.10",
48
- "cypress": "^8.0.0",
51
+ "cypress": "^8.3.0",
52
+ "elm-optimize-level-2": "^0.1.5",
49
53
  "elm-review": "^2.5.3",
50
54
  "elm-test": "^0.19.1-revision7",
51
55
  "elm-tooling": "^1.3.0",
52
56
  "elm-verify-examples": "^5.0.0",
53
- "mocha": "^8.4.0",
57
+ "elmi-to-json": "^1.2.0",
58
+ "mocha": "^9.1.0",
54
59
  "typescript": "4.3.5"
55
60
  },
56
61
  "files": [
57
62
  "generator/src/",
63
+ "generator/review/",
64
+ "src/",
58
65
  "generator/template/",
59
66
  "generator/static-code/"
60
67
  ],
@@ -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
+ ]