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.
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 +77 -4
  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.json +13 -5
  17. package/generator/template/package.json +3 -2
  18. package/package.json +14 -8
  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
@@ -0,0 +1,516 @@
1
+ module Head.Seo exposing (Common, Image, article, audioPlayer, book, profile, song, summary, summaryLarge, videoPlayer, website)
2
+
3
+ {-| <https://ogp.me/#>
4
+ <https://developers.facebook.com/docs/sharing/opengraph>
5
+
6
+ This module encapsulates some of the best practices for SEO for your site.
7
+
8
+ `elm-pages` will pre-render each of the static pages (in your `content` directory) so that
9
+ web crawlers can efficiently and accurately process it. The functions in this module are for use
10
+ with the `head` function that you pass to your Pages config (`Pages.application`).
11
+
12
+ import Date
13
+ import Head
14
+ import Head.Seo as Seo
15
+
16
+
17
+ -- justinmimbs/date package
18
+ type alias ArticleMetadata =
19
+ { title : String
20
+ , description : String
21
+ , published : Date
22
+ , author : Data.Author.Author
23
+ }
24
+
25
+ head : ArticleMetadata -> List Head.Tag
26
+ head articleMetadata =
27
+ Seo.summaryLarge
28
+ { canonicalUrlOverride = Nothing
29
+ , siteName = "elm-pages"
30
+ , image =
31
+ { url = Pages.images.icon
32
+ , alt = articleMetadata.description
33
+ , dimensions = Nothing
34
+ , mimeType = Nothing
35
+ }
36
+ , description = articleMetadata.description
37
+ , locale = Nothing
38
+ , title = articleMetadata.title
39
+ }
40
+ |> Seo.article
41
+ { tags = []
42
+ , section = Nothing
43
+ , publishedTime = Just (Date.toIsoString articleMetadata.published)
44
+ , modifiedTime = Nothing
45
+ , expirationTime = Nothing
46
+ }
47
+
48
+ @docs Common, Image, article, audioPlayer, book, profile, song, summary, summaryLarge, videoPlayer, website
49
+
50
+ -}
51
+
52
+ import Head
53
+ import Head.Twitter as Twitter
54
+ import Pages.Url
55
+
56
+
57
+ {-| Will be displayed as a large card in twitter
58
+ See: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary-card-with-large-image>
59
+
60
+ The options will also be used to build up the appropriate OpenGraph `<meta>` tags.
61
+
62
+ Note: You cannot include audio or video tags with summaries.
63
+ If you want one of those, use `audioPlayer` or `videoPlayer`
64
+
65
+ -}
66
+ summaryLarge :
67
+ { canonicalUrlOverride : Maybe String
68
+ , siteName : String
69
+ , image : Image
70
+ , description : String
71
+ , title : String
72
+ , locale : Maybe Locale
73
+ }
74
+ -> Common
75
+ summaryLarge config =
76
+ buildSummary config Twitter.Large
77
+
78
+
79
+ {-| Will be displayed as a large card in twitter
80
+ See: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary>
81
+
82
+ The options will also be used to build up the appropriate OpenGraph `<meta>` tags.
83
+
84
+ Note: You cannot include audio or video tags with summaries.
85
+ If you want one of those, use `audioPlayer` or `videoPlayer`
86
+
87
+ -}
88
+ summary :
89
+ { canonicalUrlOverride : Maybe String
90
+ , siteName : String
91
+ , image : Image
92
+ , description : String
93
+ , title : String
94
+ , locale : Maybe Locale
95
+ }
96
+ -> Common
97
+ summary config =
98
+ buildSummary config Twitter.Regular
99
+
100
+
101
+ {-| Will be displayed as a Player card in twitter
102
+ See: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>
103
+
104
+ OpenGraph audio will also be included.
105
+ The options will also be used to build up the appropriate OpenGraph `<meta>` tags.
106
+
107
+ -}
108
+ audioPlayer :
109
+ { canonicalUrlOverride : Maybe String
110
+ , siteName : String
111
+ , image : Image
112
+ , description : String
113
+ , title : String
114
+ , audio : Audio
115
+ , locale : Maybe Locale
116
+ }
117
+ -> Common
118
+ audioPlayer { title, image, canonicalUrlOverride, description, siteName, audio, locale } =
119
+ { title = title
120
+ , image = image
121
+ , canonicalUrlOverride = canonicalUrlOverride
122
+ , description = description
123
+ , siteName = siteName
124
+ , audio = Just audio
125
+ , video = Nothing
126
+ , locale = locale
127
+ , alternateLocales = [] -- TODO remove hardcoding
128
+ , twitterCard =
129
+ Twitter.Player
130
+ { title = title
131
+ , description = Just description
132
+ , siteUser = ""
133
+ , image = { url = image.url, alt = image.alt }
134
+ , player = audio.url
135
+
136
+ -- TODO what should I do here? These are requried by Twitter...
137
+ -- probably require them for both (strictest common requirement)
138
+ , width = 0
139
+ , height = 0
140
+ }
141
+ }
142
+
143
+
144
+ {-| Will be displayed as a Player card in twitter
145
+ See: <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card>
146
+
147
+ OpenGraph video will also be included.
148
+ The options will also be used to build up the appropriate OpenGraph `<meta>` tags.
149
+
150
+ -}
151
+ videoPlayer :
152
+ { canonicalUrlOverride : Maybe String
153
+ , siteName : String
154
+ , image : Image
155
+ , description : String
156
+ , title : String
157
+ , video : Video
158
+ , locale : Maybe Locale
159
+ }
160
+ -> Common
161
+ videoPlayer { title, image, canonicalUrlOverride, description, siteName, video, locale } =
162
+ { title = title
163
+ , image = image
164
+ , canonicalUrlOverride = canonicalUrlOverride
165
+ , description = description
166
+ , siteName = siteName
167
+ , audio = Nothing
168
+ , video = Just video
169
+ , locale = locale
170
+ , alternateLocales = [] -- TODO remove hardcoding
171
+ , twitterCard =
172
+ Twitter.Player
173
+ { title = title
174
+ , description = Just description
175
+ , siteUser = ""
176
+ , image = { url = image.url, alt = image.alt }
177
+ , player = video.url
178
+
179
+ -- TODO what should I do here? These are requried by Twitter...
180
+ -- probably require them for both (strictest common requirement)
181
+ , width = 0
182
+ , height = 0
183
+ }
184
+ }
185
+
186
+
187
+ buildSummary :
188
+ { canonicalUrlOverride : Maybe String
189
+ , siteName : String
190
+ , image : Image
191
+ , description : String
192
+ , title : String
193
+ , locale : Maybe Locale
194
+ }
195
+ -> Twitter.SummarySize
196
+ -> Common
197
+ buildSummary { title, image, canonicalUrlOverride, description, siteName, locale } summarySize =
198
+ { title = title
199
+ , image = image
200
+ , canonicalUrlOverride = canonicalUrlOverride
201
+ , description = description
202
+ , siteName = siteName
203
+ , audio = Nothing
204
+ , video = Nothing
205
+ , locale = locale
206
+ , alternateLocales = [] -- TODO remove hardcoding
207
+ , twitterCard =
208
+ Twitter.Summary
209
+ { title = title
210
+ , description = Just description
211
+ , siteUser = Nothing -- TODO remove hardcoding
212
+ , image = Just { url = image.url, alt = image.alt }
213
+ , size = summarySize
214
+ }
215
+ }
216
+
217
+
218
+
219
+ -- TODO add constructor Twitter app-card
220
+
221
+
222
+ {-| <https://ogp.me/#type_website>
223
+ -}
224
+ website :
225
+ Common
226
+ -> List Head.Tag
227
+ website common =
228
+ Website |> Content common |> tags
229
+
230
+
231
+ {-| See <https://ogp.me/#type_article>
232
+ -}
233
+ article :
234
+ { tags : List String
235
+ , section : Maybe String
236
+ , publishedTime : Maybe Iso8601DateTime
237
+ , modifiedTime : Maybe Iso8601DateTime
238
+ , expirationTime : Maybe Iso8601DateTime
239
+ }
240
+ -> Common
241
+ -> List Head.Tag
242
+ article details common =
243
+ Article details |> Content common |> tags
244
+
245
+
246
+ {-| See <https://ogp.me/#type_book>
247
+ -}
248
+ book :
249
+ Common
250
+ ->
251
+ { tags : List String
252
+ , isbn : Maybe String
253
+ , releaseDate : Maybe Iso8601DateTime
254
+ }
255
+ -> List Head.Tag
256
+ book common details =
257
+ Book details |> Content common |> tags
258
+
259
+
260
+ {-| See <https://ogp.me/#type_profile>
261
+ -}
262
+ profile :
263
+ { firstName : String
264
+ , lastName : String
265
+ , username : Maybe String
266
+ }
267
+ -> Common
268
+ -> List Head.Tag
269
+ profile details common =
270
+ Profile details |> Content common |> tags
271
+
272
+
273
+ {-| See <https://ogp.me/#type_music.song>
274
+ -}
275
+ song :
276
+ Common
277
+ ->
278
+ { duration : Maybe Int
279
+ , album : Maybe Int
280
+ , disc : Maybe Int
281
+ , track : Maybe Int
282
+ }
283
+ -> List Head.Tag
284
+ song common details =
285
+ Song details |> Content common |> tags
286
+
287
+
288
+ {-| These fields apply to any type in the og object types
289
+ See <https://ogp.me/#metadata> and <https://ogp.me/#optional>
290
+
291
+ Skipping this for now, if there's a use case I can add it in:
292
+
293
+ - og:determiner - The word that appears before this object's title in a sentence. An enum of (a, an, the, "", auto). If auto is chosen, the consumer of your data should chose between "a" or "an". Default is "" (blank).
294
+
295
+ -}
296
+ type alias Common =
297
+ { title : String
298
+ , image : Image
299
+ , canonicalUrlOverride : Maybe String
300
+ , description : String
301
+ , siteName : String
302
+ , audio : Maybe Audio
303
+ , video : Maybe Video
304
+ , locale : Maybe Locale
305
+ , alternateLocales : List Locale
306
+ , twitterCard : Twitter.TwitterCard
307
+ }
308
+
309
+
310
+ tagsForCommon : Common -> List ( String, Maybe Head.AttributeValue )
311
+ tagsForCommon common =
312
+ tagsForImage common.image
313
+ ++ (common.audio |> Maybe.map tagsForAudio |> Maybe.withDefault [])
314
+ ++ (common.video |> Maybe.map tagsForVideo |> Maybe.withDefault [])
315
+ ++ [ ( "og:title", Just (Head.raw common.title) )
316
+ , ( "og:url", common.canonicalUrlOverride |> Maybe.map Head.raw |> Maybe.withDefault Head.currentPageFullUrl |> Just )
317
+ , ( "og:description", Just (Head.raw common.description) )
318
+ , ( "og:site_name", Just (Head.raw common.siteName) )
319
+ , ( "og:locale", common.locale |> Maybe.map Head.raw )
320
+ ]
321
+ ++ (common.alternateLocales
322
+ |> List.map
323
+ (\alternateLocale ->
324
+ ( "og:locale:alternate", alternateLocale |> Head.raw |> Just )
325
+ )
326
+ )
327
+ ++ Twitter.rawTags common.twitterCard
328
+
329
+
330
+ {-| See the audio section in <https://ogp.me/#structured>
331
+ Example:
332
+
333
+ { url = "https://example.com/sound.mp3"
334
+ mimeType = Just "audio/mpeg"
335
+ }
336
+
337
+ -}
338
+ type alias Audio =
339
+ { url : String
340
+ , mimeType : Maybe MimeType
341
+ }
342
+
343
+
344
+ tagsForAudio : Audio -> List ( String, Maybe Head.AttributeValue )
345
+ tagsForAudio audio =
346
+ [ ( "og:audio", audio.url |> Head.raw |> Just )
347
+ , ( "og:audio:secure_url", audio.url |> Head.raw |> Just )
348
+ , ( "og:audio:type", audio.mimeType |> Maybe.map Head.raw )
349
+ ]
350
+
351
+
352
+ type alias Locale =
353
+ -- TODO make this more type-safe
354
+ String
355
+
356
+
357
+ type Content
358
+ = Content Common ContentDetails
359
+
360
+
361
+ type ContentDetails
362
+ = Website
363
+ | Article
364
+ { tags : List String
365
+ , section : Maybe String
366
+ , publishedTime : Maybe Iso8601DateTime
367
+ , modifiedTime : Maybe Iso8601DateTime
368
+ , expirationTime : Maybe Iso8601DateTime
369
+ }
370
+ | Book
371
+ { tags : List String
372
+ , isbn : Maybe String
373
+ , releaseDate : Maybe Iso8601DateTime
374
+ }
375
+ | Song
376
+ {-
377
+
378
+ TODO
379
+ music:album - music.album array - The album this song is from.
380
+ music:musician - profile array - The musician that made this song.
381
+ -}
382
+ { duration : Maybe Int
383
+ , album : Maybe Int
384
+ , disc : Maybe Int
385
+ , track : Maybe Int
386
+ }
387
+ | Profile
388
+ { firstName : String
389
+ , lastName : String
390
+ , username : Maybe String
391
+ }
392
+
393
+
394
+ {-| <https://en.wikipedia.org/wiki/ISO_8601>
395
+ -}
396
+ type alias Iso8601DateTime =
397
+ -- TODO should be more type-safe here
398
+ String
399
+
400
+
401
+ {-| <https://en.wikipedia.org/wiki/Media_type>
402
+ -}
403
+ type alias MimeType =
404
+ -- TODO should be more type-safe here
405
+ String
406
+
407
+
408
+ {-| See <https://ogp.me/#structured>
409
+ -}
410
+ type alias Image =
411
+ { url : Pages.Url.Url
412
+ , alt : String
413
+ , dimensions : Maybe { width : Int, height : Int }
414
+ , mimeType : Maybe MimeType
415
+ }
416
+
417
+
418
+ tagsForImage : Image -> List ( String, Maybe Head.AttributeValue )
419
+ tagsForImage image =
420
+ [ ( "og:image", Just (Head.urlAttribute image.url) )
421
+ , ( "og:image:secure_url", Just (Head.urlAttribute image.url) )
422
+ , ( "og:image:alt", image.alt |> Head.raw |> Just )
423
+ , ( "og:image:width", image.dimensions |> Maybe.map .width |> Maybe.map String.fromInt |> Maybe.map Head.raw )
424
+ , ( "og:image:height", image.dimensions |> Maybe.map .height |> Maybe.map String.fromInt |> Maybe.map Head.raw )
425
+ ]
426
+
427
+
428
+ {-| See <https://ogp.me/#structured>
429
+ -}
430
+ type alias Video =
431
+ { url : String
432
+ , mimeType : Maybe MimeType
433
+ , dimensions : Maybe { width : Int, height : Int }
434
+ }
435
+
436
+
437
+ tagsForVideo : Video -> List ( String, Maybe Head.AttributeValue )
438
+ tagsForVideo video =
439
+ [ ( "og:video", video.url |> Head.raw |> Just )
440
+ , ( "og:video:secure_url", video.url |> Head.raw |> Just )
441
+ , ( "og:video:width", video.dimensions |> Maybe.map .width |> Maybe.map String.fromInt |> Maybe.map Head.raw )
442
+ , ( "og:video:height", video.dimensions |> Maybe.map .height |> Maybe.map String.fromInt |> Maybe.map Head.raw )
443
+ ]
444
+
445
+
446
+ tags : Content -> List Head.Tag
447
+ tags (Content common details) =
448
+ tagsForCommon common
449
+ ++ (case details of
450
+ Website ->
451
+ [ ( "og:type", "website" |> Head.raw |> Just )
452
+ ]
453
+
454
+ Article articleDetails ->
455
+ {-
456
+ TODO
457
+ - article:author - profile array - Writers of the article.
458
+ -}
459
+ [ ( "og:type", "article" |> Head.raw |> Just )
460
+ , ( "article:section", articleDetails.section |> Maybe.map Head.raw )
461
+ , ( "article:published_time", articleDetails.publishedTime |> Maybe.map Head.raw )
462
+ , ( "article:modified_time", articleDetails.modifiedTime |> Maybe.map Head.raw )
463
+ , ( "article:expiration_time", articleDetails.expirationTime |> Maybe.map Head.raw )
464
+ ]
465
+ ++ List.map
466
+ (\tag -> ( "article:tag", tag |> Head.raw |> Just ))
467
+ articleDetails.tags
468
+
469
+ Book bookDetails ->
470
+ [ ( "og:type", "book" |> Head.raw |> Just )
471
+ , ( "og:isbn", bookDetails.isbn |> Maybe.map Head.raw )
472
+ , ( "og:release_date", bookDetails.releaseDate |> Maybe.map Head.raw )
473
+ ]
474
+ ++ List.map
475
+ (\tag -> ( "book:tag", tag |> Head.raw |> Just ))
476
+ bookDetails.tags
477
+
478
+ Song songDetails ->
479
+ [ ( "og:type", "music.song" |> Head.raw |> Just )
480
+ , ( "music:duration", songDetails.duration |> Maybe.map String.fromInt |> Maybe.map Head.raw )
481
+ , ( "music:album:disc", songDetails.disc |> Maybe.map String.fromInt |> Maybe.map Head.raw )
482
+ , ( "music:album:track", songDetails.track |> Maybe.map String.fromInt |> Maybe.map Head.raw )
483
+ ]
484
+
485
+ Profile profileDetails ->
486
+ [ ( "og:type", "profile" |> Head.raw |> Just )
487
+ , ( "profile:first_name", profileDetails.firstName |> Head.raw |> Just )
488
+ , ( "profile:last_name", profileDetails.lastName |> Head.raw |> Just )
489
+ , ( "profile:username", profileDetails.username |> Maybe.map Head.raw )
490
+ ]
491
+ )
492
+ |> List.filterMap
493
+ (\( name, maybeContent ) ->
494
+ maybeContent
495
+ |> Maybe.map (\metaContent -> Head.metaProperty name metaContent)
496
+ )
497
+ |> List.append
498
+ [ Head.canonicalLink common.canonicalUrlOverride
499
+ , Head.metaName "description" (Head.raw common.description)
500
+ ]
501
+
502
+
503
+
504
+ {-
505
+ TODO remaining types:
506
+
507
+
508
+ - music.album
509
+ - music.playlist
510
+ - music.radio_station
511
+ - video.movie
512
+ - video.episode
513
+ - video.tv_show
514
+ - video.other
515
+
516
+ -}
@@ -0,0 +1,109 @@
1
+ module Head.Twitter exposing (SummarySize(..), TwitterCard(..), rawTags)
2
+
3
+ import Head
4
+ import Pages.Url
5
+
6
+
7
+ type SummarySize
8
+ = Regular
9
+ | Large
10
+
11
+
12
+ type alias Image =
13
+ { url : Pages.Url.Url
14
+ , alt : String
15
+ }
16
+
17
+
18
+ type TwitterCard
19
+ = Summary
20
+ { title : String
21
+ , description : Maybe String
22
+ , siteUser : Maybe String
23
+ , image : Maybe Image
24
+ , size : SummarySize
25
+ }
26
+ | App
27
+ { title : String
28
+ , description : Maybe String
29
+ , siteUser : String
30
+ , image : Maybe Image
31
+ , appIdIphone : Maybe Int
32
+ , appIdIpad : Maybe Int
33
+ , appIdGooglePlay : Maybe String
34
+ , appUrlIphone : Maybe String
35
+ , appUrlIpad : Maybe String
36
+ , appUrlGooglePlay : Maybe String
37
+ , appCountry : Maybe String
38
+ , appNameIphone : Maybe String
39
+ , appNameIpad : Maybe String
40
+ , appNameGooglePlay : Maybe String
41
+ }
42
+ -- https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card
43
+ | Player
44
+ { title : String
45
+ , description : Maybe String
46
+ , siteUser : String
47
+ , image : Image
48
+ , player : String
49
+ , width : Int
50
+ , height : Int
51
+ }
52
+
53
+
54
+ rawTags : TwitterCard -> List ( String, Maybe Head.AttributeValue )
55
+ rawTags card =
56
+ ( "twitter:card", cardValue card |> Head.raw |> Just )
57
+ :: (case card of
58
+ Summary details ->
59
+ [ ( "twitter:title", details.title |> Head.raw |> Just )
60
+ , ( "twitter:site", details.siteUser |> Maybe.map Head.raw )
61
+ , ( "twitter:description", details.description |> Maybe.map Head.raw )
62
+ , ( "twitter:image", details.image |> Maybe.map .url |> Maybe.map Head.urlAttribute )
63
+ , ( "twitter:image:alt", details.image |> Maybe.map .alt |> Maybe.map Head.raw )
64
+ ]
65
+
66
+ App details ->
67
+ [ ( "twitter:title", details.title |> Head.raw |> Just )
68
+ , ( "twitter:site", details.siteUser |> Head.raw |> Just )
69
+ , ( "twitter:description", details.description |> Maybe.map Head.raw )
70
+ , ( "twitter:image", details.image |> Maybe.map .url |> Maybe.map Head.urlAttribute )
71
+ , ( "twitter:image:alt", details.image |> Maybe.map .alt |> Maybe.map Head.raw )
72
+ , ( "twitter:app:name:iphone", details.appNameIphone |> Maybe.map Head.raw )
73
+ , ( "twitter:app:name:ipad", details.appNameIpad |> Maybe.map Head.raw )
74
+ , ( "twitter:app:name:googleplay", details.appNameGooglePlay |> Maybe.map Head.raw )
75
+ , ( "twitter:app:id:iphone", details.appIdIphone |> Maybe.map String.fromInt |> Maybe.map Head.raw )
76
+ , ( "twitter:app:id:ipad", details.appIdIpad |> Maybe.map String.fromInt |> Maybe.map Head.raw )
77
+ , ( "twitter:app:id:googleplay", details.appIdGooglePlay |> Maybe.map Head.raw )
78
+ , ( "twitter:app:url:iphone", details.appUrlIphone |> Maybe.map Head.raw )
79
+ , ( "twitter:app:url:ipad", details.appUrlIpad |> Maybe.map Head.raw )
80
+ , ( "twitter:app:url:googleplay", details.appUrlGooglePlay |> Maybe.map Head.raw )
81
+ , ( "twitter:app:country", details.appCountry |> Maybe.map Head.raw )
82
+ ]
83
+
84
+ Player details ->
85
+ [ ( "twitter:title", details.title |> Head.raw |> Just )
86
+ , ( "twitter:site", details.siteUser |> Head.raw |> Just )
87
+ , ( "twitter:description", details.description |> Maybe.map Head.raw )
88
+ , ( "twitter:image", Just (Head.urlAttribute details.image.url) )
89
+ , ( "twitter:image:alt", details.image.alt |> Head.raw |> Just )
90
+ ]
91
+ )
92
+
93
+
94
+ cardValue : TwitterCard -> String
95
+ cardValue card =
96
+ case card of
97
+ Summary details ->
98
+ case details.size of
99
+ Regular ->
100
+ "summary"
101
+
102
+ Large ->
103
+ "summary_large_image"
104
+
105
+ App _ ->
106
+ "app"
107
+
108
+ Player _ ->
109
+ "player"