firefly-compiler 0.4.78 → 0.4.79

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 (133) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +153 -153
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +257 -257
  6. package/compiler/Compiler.ff +227 -227
  7. package/compiler/Dependencies.ff +187 -187
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/JsEmitter.ff +946 -946
  10. package/compiler/LspHook.ff +202 -202
  11. package/compiler/ModuleCache.ff +178 -178
  12. package/compiler/Workspace.ff +88 -88
  13. package/core/.firefly/include/package-lock.json +564 -394
  14. package/core/.firefly/include/package.json +5 -5
  15. package/core/.firefly/include/prepare.sh +1 -1
  16. package/core/.firefly/package.ff +2 -2
  17. package/core/Array.ff +265 -265
  18. package/core/Atomic.ff +64 -64
  19. package/core/Box.ff +7 -7
  20. package/core/BrowserSystem.ff +40 -40
  21. package/core/BuildSystem.ff +148 -148
  22. package/core/Crypto.ff +96 -96
  23. package/core/Equal.ff +36 -36
  24. package/core/HttpClient.ff +148 -148
  25. package/core/JsSystem.ff +69 -69
  26. package/core/Json.ff +434 -434
  27. package/core/List.ff +486 -486
  28. package/core/Lock.ff +144 -144
  29. package/core/NodeSystem.ff +216 -216
  30. package/core/Ordering.ff +161 -161
  31. package/core/Path.ff +401 -401
  32. package/core/Random.ff +134 -134
  33. package/core/RbMap.ff +216 -216
  34. package/core/Show.ff +43 -43
  35. package/core/SourceLocation.ff +68 -68
  36. package/core/Task.ff +141 -141
  37. package/experimental/benchmarks/ListGrab.ff +23 -23
  38. package/experimental/benchmarks/ListGrab.java +55 -55
  39. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  40. package/experimental/benchmarks/Pyrotek45.java +64 -64
  41. package/experimental/bidirectional/Bidi.ff +88 -88
  42. package/experimental/random/Index.ff +53 -53
  43. package/experimental/random/Process.ff +120 -120
  44. package/experimental/random/Scrape.ff +51 -51
  45. package/experimental/random/Symbols.ff +73 -73
  46. package/experimental/random/Tensor.ff +52 -52
  47. package/experimental/random/Units.ff +36 -36
  48. package/experimental/s3/S3TestAuthorizationHeader.ff +39 -38
  49. package/experimental/s3/S3TestPut.ff +16 -15
  50. package/experimental/tests/TestJson.ff +26 -26
  51. package/firefly.sh +0 -0
  52. package/fireflysite/.firefly/package.ff +4 -4
  53. package/fireflysite/CommunityOverview.ff +20 -20
  54. package/fireflysite/CountingButtonDemo.ff +58 -58
  55. package/fireflysite/DocumentParser.ff +218 -0
  56. package/fireflysite/ExamplesOverview.ff +40 -31
  57. package/fireflysite/FrontPage.ff +360 -0
  58. package/fireflysite/Guide.ff +411 -308
  59. package/fireflysite/GuideAll.ff +21 -0
  60. package/fireflysite/GuideBaseTypes.ff +168 -0
  61. package/fireflysite/GuideControlFlow.ff +212 -0
  62. package/fireflysite/GuideIntroduction.ff +52 -69
  63. package/fireflysite/Main.ff +137 -92
  64. package/fireflysite/MatchingPasswordsDemo.ff +82 -86
  65. package/fireflysite/PackagesOverview.ff +49 -49
  66. package/fireflysite/PostgresqlDemo.ff +34 -0
  67. package/fireflysite/Styles.ff +495 -306
  68. package/fireflysite/assets/NotoSansMono-Regular.ttf +0 -0
  69. package/fireflysite/assets/NunitoSans-VariableFont_YTLC,opsz,wdth,wght.ttf +0 -0
  70. package/fireflysite/assets/autocomplete-small.png +0 -0
  71. package/fireflysite/assets/autocomplete.png +0 -0
  72. package/fireflysite/assets/edit-time-error.png +0 -0
  73. package/fireflysite/assets/firefly-logo-yellow.png +0 -0
  74. package/fireflysite/assets/markdown/ControlFlow.md +136 -0
  75. package/fireflysite/assets/markdown/Example.md +78 -0
  76. package/lsp/.firefly/package.ff +1 -1
  77. package/lsp/CompletionHandler.ff +828 -828
  78. package/lsp/Handler.ff +714 -714
  79. package/lsp/HoverHandler.ff +79 -79
  80. package/lsp/LanguageServer.ff +272 -272
  81. package/lsp/SignatureHelpHandler.ff +55 -55
  82. package/lsp/SymbolHandler.ff +181 -181
  83. package/lsp/TestReferences.ff +17 -17
  84. package/lsp/TestReferencesCase.ff +7 -7
  85. package/lsp/stderr.txt +1 -1
  86. package/lsp/stdout.txt +34 -34
  87. package/lux/.firefly/package.ff +1 -1
  88. package/lux/Css.ff +648 -648
  89. package/lux/CssTest.ff +48 -48
  90. package/lux/Lux.ff +487 -487
  91. package/lux/LuxEvent.ff +116 -116
  92. package/lux/Main.ff +123 -123
  93. package/lux/Main2.ff +143 -143
  94. package/output/js/ff/compiler/Builder.mjs +43 -43
  95. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  96. package/output/js/ff/core/Array.mjs +59 -59
  97. package/output/js/ff/core/Atomic.mjs +36 -36
  98. package/output/js/ff/core/BrowserSystem.mjs +11 -11
  99. package/output/js/ff/core/BuildSystem.mjs +30 -30
  100. package/output/js/ff/core/Crypto.mjs +40 -40
  101. package/output/js/ff/core/HttpClient.mjs +56 -56
  102. package/output/js/ff/core/Json.mjs +147 -147
  103. package/output/js/ff/core/List.mjs +50 -50
  104. package/output/js/ff/core/Lock.mjs +97 -97
  105. package/output/js/ff/core/NodeSystem.mjs +83 -83
  106. package/output/js/ff/core/Ordering.mjs +8 -8
  107. package/output/js/ff/core/Path.mjs +231 -231
  108. package/output/js/ff/core/Random.mjs +56 -56
  109. package/output/js/ff/core/Task.mjs +31 -31
  110. package/package.json +1 -1
  111. package/rpc/.firefly/package.ff +1 -1
  112. package/rpc/Rpc.ff +70 -70
  113. package/s3/.firefly/package.ff +1 -1
  114. package/s3/S3.ff +94 -94
  115. package/unsafejs/UnsafeJs.ff +19 -19
  116. package/vscode/LICENSE.txt +21 -21
  117. package/vscode/Prepublish.ff +15 -15
  118. package/vscode/README.md +16 -16
  119. package/vscode/client/package.json +22 -22
  120. package/vscode/client/src/extension.ts +104 -104
  121. package/vscode/icons/firefly-icon.svg +10 -10
  122. package/vscode/language-configuration.json +61 -61
  123. package/vscode/package-lock.json +3623 -3623
  124. package/vscode/package.json +15 -1
  125. package/vscode/snippets.json +241 -241
  126. package/vscode/syntaxes/firefly-markdown-injection.json +45 -0
  127. package/webserver/.firefly/include/package-lock.json +22 -16
  128. package/webserver/.firefly/include/package.json +5 -5
  129. package/webserver/.firefly/package.ff +2 -2
  130. package/webserver/WebServer.ff +685 -685
  131. package/websocket/.firefly/package.ff +1 -1
  132. package/websocket/WebSocket.ff +131 -131
  133. /package/fireflysite/{firefly-logo-notext.png → assets/firefly-logo-notext.png} +0 -0
@@ -1,308 +1,411 @@
1
- import Lux from ff:lux
2
- import Css from ff:lux
3
- import Tokenizer from ff:compiler
4
- import Token from ff:compiler
5
- import Styles
6
-
7
- data Guide(
8
- documents: List[Document]
9
- )
10
-
11
- data Document(
12
- sections: List[Section]
13
- )
14
-
15
- data Section(
16
- heading: String
17
- blocks: List[Block]
18
- )
19
-
20
- data Block {
21
- Paragraph(inlines: List[Inline])
22
- Bullets(items: List[List[Inline]])
23
- CodeBlock(code: String, firefly: Bool = False)
24
- Image(url: String)
25
- Video(url: String)
26
- LuxDemo(demo: String)
27
- }
28
-
29
- data Inline {
30
- Text(text: String)
31
- Bold(text: String)
32
- Italic(text: String)
33
- Code(code: String, firefly: Bool = False)
34
- Link(text: String, url: String)
35
- Anchor(heading: String, title: Option[String] = None)
36
- }
37
-
38
- capability Demo(
39
- name: String
40
- render: Lux => Unit
41
- )
42
-
43
- render(lux: Lux, prefix: String, kebab: String, guide: Guide, demos: List[Demo]) {
44
- let document = guide.documents.find {
45
- _.sections.first().any {kebabCase(_.heading) == kebab}
46
- }.else {guide.documents.grabFirst()}
47
- lux.add("div") {
48
- lux.cssClass(Styles.pageCss)
49
- renderTopbar(lux, prefix)
50
- lux.add("div") {
51
- lux.cssClass(Styles.guideCss)
52
- renderSidebar(lux, prefix, guide, document.sections.first().map {_.heading}.else {""})
53
- lux.add("main") {
54
- lux.cssClass(Styles.guideMainCss)
55
- renderDocument(lux, document, demos)
56
- }
57
- }
58
- }
59
- }
60
-
61
- renderSoleDocument(lux: Lux, prefix: String, document: Document, demos: List[Demo]) {
62
- lux.add("div") {
63
- lux.cssClass(Styles.pageCss)
64
- renderTopbar(lux, prefix)
65
- lux.add("main") {
66
- lux.cssClass(Styles.guideSoleDocumentCss)
67
- renderDocument(lux, document, demos)
68
- }
69
- }
70
- }
71
-
72
- renderTopbar(lux: Lux, prefix: String) {
73
- lux.add("header") {
74
- lux.add("nav") {
75
- lux.cssClass(Styles.topbarCss)
76
- lux.add("a") {
77
- lux.cssClass(Styles.whiteLinkCss)
78
- lux.cssClass(Styles.topbarFireflyCss)
79
- if(prefix == "/") {lux.set("aria-current", "page")}
80
- lux.set("href", "/")
81
- lux.text("Firefly")
82
- }
83
- lux.add("a") {
84
- lux.cssClass(Styles.whiteLinkCss)
85
- if(prefix == "/guide/") {lux.set("aria-current", "page")}
86
- lux.set("href", "/guide/")
87
- lux.text("Guide")
88
- }
89
- lux.add("a") {
90
- lux.cssClass(Styles.whiteLinkCss)
91
- if(prefix == "/examples/") {lux.set("aria-current", "page")}
92
- lux.set("href", "/examples/")
93
- lux.text("Examples")
94
- }
95
- lux.add("a") {
96
- lux.cssClass(Styles.whiteLinkCss)
97
- if(prefix == "/packages/") {lux.set("aria-current", "page")}
98
- lux.set("href", "/packages/")
99
- lux.text("Packages")
100
- }
101
- lux.add("a") {
102
- lux.cssClass(Styles.whiteLinkCss)
103
- if(prefix == "/community/") {lux.set("aria-current", "page")}
104
- lux.set("href", "/community/")
105
- lux.text("Community")
106
- }
107
- }
108
- }
109
- }
110
-
111
- renderSidebar(lux: Lux, prefix: String, guide: Guide, selectedHeading: String) {
112
- lux.add("nav") {
113
- lux.cssClass(Styles.guideSidebarCss)
114
- lux.add("form") {
115
- lux.set("role", "search")
116
- lux.add("input") {
117
- lux.set("aria-label", "Search")
118
- lux.cssClass(Styles.searchInputCss)
119
- lux.set("placeholder", "Search...")
120
- }
121
- }
122
- lux.add("ul") {
123
- lux.cssClass(Styles.guideSidebarUlCss)
124
- mutable first = True
125
- guide.documents.each {document =>
126
- lux.add("li") {
127
- lux.cssClass(Styles.guideSidebarLiCss)
128
- lux.add("a") {
129
- let heading = document.sections.grabFirst().heading
130
- lux.cssClass(Styles.whiteLinkCss)
131
- if(heading == selectedHeading) {lux.set("aria-current", "page")}
132
- lux.set("href", if(first) {prefix} else {prefix + kebabCase(heading)})
133
- lux.text(heading)
134
- }
135
- }
136
- first = False
137
- }
138
- }
139
- }
140
- }
141
-
142
- renderDocument(lux: Lux, document: Document, demos: List[Demo]) {
143
- lux.add("article") {
144
- lux.cssClass(Styles.guideDocumentCss)
145
- document.sections.pairs().each {| Pair(index, section) =>
146
- renderSection(lux, index == 0, section, demos)
147
- }
148
- }
149
- }
150
-
151
- renderSection(lux: Lux, first: Bool, section: Section, demos: List[Demo]) {
152
- lux.add(if(first) {"h1"} else {"h2"}) {
153
- lux.cssClass(if(first) {Styles.guideH1Css} else {Styles.guideH2Css})
154
- lux.text(section.heading)
155
- }
156
- section.blocks.each {renderBlock(lux, _, demos)}
157
- }
158
-
159
- renderBlock(lux: Lux, block: Block, demos: List[Demo]) {
160
- block.{
161
- | Paragraph(inlines) => renderParagraph(lux, inlines)
162
- | CodeBlock(code, firefly) => renderCodeBlock(lux, code, firefly)
163
- | Bullets(items) => renderBullets(lux, items)
164
- | Image(url) => renderImage(lux, url)
165
- | Video(url) => renderVideo(lux, url)
166
- | LuxDemo(name) => renderLuxDemo(lux, name, demos)
167
- }
168
- }
169
-
170
- renderParagraph(lux: Lux, inlines: List[Inline]) {
171
- lux.add("p") {
172
- renderInlines(lux, inlines)
173
- }
174
- }
175
-
176
- renderCodeBlock(lux: Lux, code: String, firefly: Bool) {
177
- let lines = code.lines().dropWhile {_.all {_ == ' '}}.reverse().dropWhile {_.all {_ == ' '}}.reverse()
178
- let indentation = lines.first().map {_.takeWhile {_ == ' '}.size()}.else {0}
179
- lux.add("pre") {
180
- lux.cssClass(Styles.guideCodeBlockCss)
181
- lux.add("code") {
182
- lux.cssClass(Styles.guideCodeCss)
183
- let unindentedCode = lines.map {_.dropFirst(indentation)}.join("\n")
184
- if(firefly) {
185
- renderHighlightedCode(lux, unindentedCode)
186
- } else {
187
- lux.text(unindentedCode)
188
- }
189
- }
190
- }
191
- }
192
-
193
- renderBullets(lux: Lux, items: List[List[Inline]]) {
194
- lux.add("ul") {
195
- items.each {inlines =>
196
- lux.add("li") {
197
- renderInlines(lux, inlines)
198
- }
199
- }
200
- }
201
- }
202
-
203
- renderImage(lux: Lux, url: String) {
204
- lux.add("img") {
205
- lux.set("src", url)
206
- lux.css(Css.maxWidth("100%"))
207
- }
208
- }
209
-
210
- renderVideo(lux: Lux, url: String) {
211
- lux.add("video") {
212
- lux.set("src", url)
213
- lux.css(Css.maxWidth("100%"))
214
- }
215
- }
216
-
217
- renderLuxDemo(lux: Lux, name: String, demos: List[Demo]) {
218
- demos.find {_.name == name}.map {_.render(lux)}.else {lux.text("Demo not found")}
219
- }
220
-
221
- renderInlines(lux: Lux, inlines: List[Inline]) {
222
- inlines.separate([Text(" ")]).each {renderInline(lux, _)}
223
- }
224
-
225
- renderInline(lux: Lux, inline: Inline) {
226
- inline.{
227
- | Anchor(heading, title) =>
228
- lux.add("a") {
229
- let url = title.map {"/guide/" + kebabCase(_)}.else {""} + "#" + kebabCase(heading)
230
- lux.cssClass(Styles.guideLinkCss)
231
- lux.set("href", url)
232
- lux.text(title.map {_ + ": "}.else {""} + heading)
233
- }
234
- | Bold(text) =>
235
- lux.add("b") {
236
- lux.text(text)
237
- }
238
- | Code(code, firefly) =>
239
- lux.add("code") {
240
- lux.cssClass(Styles.guideCodeCss)
241
- if(firefly) {
242
- renderHighlightedCode(lux, code)
243
- } else {
244
- lux.text(code)
245
- }
246
- }
247
- | Italic(text) =>
248
- lux.add("i") {
249
- lux.text(text)
250
- }
251
- | Link(text, url) =>
252
- lux.add("a") {
253
- lux.cssClass(Styles.guideLinkCss)
254
- lux.set("href", url)
255
- lux.text(text)
256
- }
257
- | Text(text) =>
258
- lux.text(text)
259
- }
260
- }
261
-
262
- renderHighlightedCode(lux: Lux, code: String) {
263
- let tokens = Tokenizer.tokenize("<example>", code, None, False)
264
- mutable offset = 0
265
- mutable index = 0
266
- tokens.each {token =>
267
- if(token.startOffset > offset) {
268
- lux.span {
269
- lux.cssClass(Styles.codeCommentCss)
270
- lux.text(code.slice(offset, token.startOffset))
271
- }
272
- }
273
- if(token.kind != LEnd) {
274
- let css = token.kind.{
275
- | LChar => Styles.codeStringCss
276
- | LFloat => Styles.codeNumberCss
277
- | LInt => Styles.codeNumberCss
278
- | LKeyword => Styles.codeKeywordCss
279
- | LNamespace => Styles.codeTypeCss
280
- | LString => Styles.codeStringCss
281
- | LUpper => Styles.codeTypeCss
282
- | LWildcard => Styles.codeVariableCss
283
- | LLower {tokens.get(index - 1).any {t =>
284
- t.kind == LBracketRight || t.kind == LDot
285
- }} => Styles.codeCallCss
286
- | LLower {tokens.grab(index + 1).kind == LBracketLeft} => Styles.codeCallCss
287
- | LLower => Styles.codeVariableCss
288
- | _ => Styles.codeOtherCss
289
- }
290
- lux.span {
291
- lux.cssClass(css)
292
- lux.text(code.slice(token.startOffset, token.stopOffset))
293
- }
294
- offset = token.stopOffset
295
- }
296
- index += 1
297
- }
298
- }
299
-
300
- kebabCase(text: String): String {
301
- mutable result = text.trim().lower().filter {c => c.isAsciiLetterOrDigit() || c == ' '}
302
- doWhile {
303
- let before = result
304
- result = result.replace(" ", "")
305
- result != before
306
- }
307
- result.replace(" ", "-")
308
- }
1
+ import Lux from ff:lux
2
+ import LuxEvent from ff:lux
3
+ import Css from ff:lux
4
+ import Tokenizer from ff:compiler
5
+ import Token from ff:compiler
6
+ import Styles
7
+ import DocumentParser
8
+
9
+ data Guide(
10
+ prefix: String
11
+ documents: List[Document]
12
+ )
13
+
14
+ data Document {
15
+ ReadyDocument(
16
+ sections: List[Section]
17
+ )
18
+ UnfetchedDocument(
19
+ header: String
20
+ )
21
+ }
22
+
23
+
24
+ data Section(heading: String) {
25
+ Section(
26
+ blocks: List[Block]
27
+ )
28
+ SplitSection(
29
+ first: Block
30
+ second: Block
31
+ )
32
+ }
33
+
34
+ data Block {
35
+ Paragraph(inlines: List[Inline])
36
+ Bullets(items: List[List[Inline]])
37
+ CodeBlock(code: String, firefly: Bool = False)
38
+ Image(url: String)
39
+ Video(url: String)
40
+ LuxDemo(demo: String)
41
+ }
42
+
43
+ data Inline {
44
+ Text(text: String)
45
+ Bold(text: String)
46
+ Italic(text: String)
47
+ Code(code: String, firefly: Bool = False)
48
+ Link(text: String, url: String)
49
+ Anchor(heading: String, title: Option[String] = None)
50
+ }
51
+
52
+ extend self: Guide {
53
+ heading(): String {
54
+ self.documents.first().map {_.heading()}.else {""}
55
+ }
56
+ title(): String {
57
+ if(self.heading() == "Firefly") {self.heading()} else {"Firefly " + self.heading()}
58
+ }
59
+ }
60
+
61
+ extend self: Document {
62
+ heading(): String {
63
+ self.{
64
+ | ReadyDocument(sections) =>
65
+ sections.first().map {_.heading}.else {""}
66
+ | UnfetchedDocument(header) => header
67
+ }
68
+ }
69
+ title(guide: Guide): String {
70
+ if(guide.documents.first().any {_.heading() == self.heading()}) {
71
+ guide.title()
72
+ } else {
73
+ self.heading() + " · " + guide.title()
74
+ }
75
+ }
76
+ path(guide: Guide): String {
77
+ if(guide.documents.first().any {_.heading() == self.heading()}) {
78
+ guide.prefix
79
+ } else {
80
+ guide.prefix + kebabCase(self.heading())
81
+ }
82
+ }
83
+ }
84
+
85
+ capability Demo(
86
+ name: String
87
+ render: Lux => Unit
88
+ )
89
+
90
+ data GuideDocument(
91
+ guide: Guide
92
+ document: Document
93
+ )
94
+
95
+ render(lux: Lux, browser: BrowserSystem, prefix: String, kebab: String, guides: List[Guide], demos: List[Demo]) {
96
+ let guide = guides.find {_.prefix == prefix}.else {guides.grabFirst()}
97
+ let document = guide.documents.find {d =>
98
+ kebabCase(d.heading()) == kebab
99
+ }.else {guide.documents.grabFirst()}
100
+ browser.js().global().get("document").set("title", document.title(guide))
101
+ let guideDocuments = guides.flatMap {guide => guide.documents.map {document =>
102
+ GuideDocument(guide, document)
103
+ }}
104
+ let nextDocument = guideDocuments.dropWhile {n =>
105
+ n.guide.prefix != guide.prefix || n.document.heading() != document.heading()
106
+ }.dropFirst().first()
107
+ lux.add("div") {
108
+ lux.useState(False): menu, setMenu =>
109
+ lux.cssClass(Styles.pageCss)
110
+ lux.add("div") {
111
+ lux.cssClass(Styles.guideCss)
112
+ lux.add("main") {
113
+ lux.cssClass(Styles.guideMainCss)
114
+ renderDocument(lux, browser.httpClient(), document, demos, nextDocument)
115
+ }
116
+ lux.add("button") {
117
+ lux.set("aria-label", "Toggle the menu")
118
+ lux.cssClass(Styles.guideSidebarButtonCss)
119
+ if(menu) {lux.cssClass(Styles.guideSidebarButtonOpenCss)}
120
+ lux.onClick {event =>
121
+ event.preventDefault()
122
+ setMenu(True)
123
+ }
124
+ }
125
+ lux.add("div") {
126
+ lux.cssClass(Styles.guideSidebarBackdropCss)
127
+ if(menu) {lux.cssClass(Styles.guideSidebarBackdropOpenCss)}
128
+ lux.onClick {event =>
129
+ event.preventDefault()
130
+ setMenu(False)
131
+ }
132
+ }
133
+ renderSidebar(lux, guides, guide, document.heading(), menu)
134
+ }
135
+ }
136
+ }
137
+
138
+ renderSidebar(lux: Lux, guides: List[Guide], selectedGuide: Guide, selectedHeading: String, menu: Bool) {
139
+ lux.add("nav") {
140
+ lux.cssClass(Styles.guideSidebarCss)
141
+ if(menu) {lux.cssClass(Styles.guideSidebarOpenCss)}
142
+ lux.add("a") {
143
+ lux.set("href", "/front/")
144
+ lux.cssClass(Styles.guideSidebarLogoCss)
145
+ lux.add("img") {
146
+ lux.set("src", "/assets/firefly-logo-yellow.png")
147
+ }
148
+ lux.div {
149
+ lux.text("Firefly")
150
+ }
151
+ }
152
+ lux.add("form") {
153
+ lux.set("role", "search")
154
+ lux.add("input") {
155
+ lux.set("aria-label", "Search")
156
+ lux.cssClass(Styles.searchInputCss)
157
+ lux.set("placeholder", "Search...")
158
+ }
159
+ }
160
+ lux.add("ul") {
161
+ lux.cssClass(Styles.guideSidebarUl1Css)
162
+ guides.dropFirst().each {guide =>
163
+ lux.add("li") {
164
+ lux.cssClass(Styles.guideSidebarLi1Css)
165
+ lux.add("a") {
166
+ lux.cssClass(Styles.whiteLinkCss)
167
+ let heading = guide.documents.grabFirst().heading()
168
+ if(selectedGuide.prefix == guide.prefix && heading == selectedHeading) {
169
+ lux.set("aria-current", "page")
170
+ }
171
+ lux.set("href", guide.prefix)
172
+ lux.text(heading)
173
+ }
174
+ if(guide.documents.size() > 1):
175
+ lux.add("ul") {
176
+ lux.cssClass(Styles.guideSidebarUl2Css)
177
+ guide.documents.dropFirst().each {document =>
178
+ lux.add("li") {
179
+ lux.cssClass(Styles.guideSidebarLi2Css)
180
+ lux.add("a") {
181
+ lux.cssClass(Styles.whiteLinkCss)
182
+ let heading = document.heading()
183
+ if(selectedGuide.prefix == guide.prefix && heading == selectedHeading) {
184
+ lux.set("aria-current", "page")
185
+ }
186
+ lux.set("href", guide.prefix + kebabCase(heading))
187
+ lux.text(heading)
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ renderDocument(lux: Lux, http: HttpClient, document: Document, demos: List[Demo], nextDocument: Option[GuideDocument]) {
199
+ lux.add("article") {
200
+ lux.cssClass(Styles.guideDocumentCss)
201
+ document.{
202
+ | ReadyDocument(sections) =>
203
+ renderSections(lux, sections, demos)
204
+ | UnfetchedDocument(header) =>
205
+ lux.div {
206
+ lux.useSuspense {lux.div {lux.text("Loading document...")}}: lux =>
207
+ let asset = "/assets/markdown/" + document.heading().replace(" ", "") + ".md"
208
+ let markdown = http.get(asset, []) {_.readText()}
209
+ let parser = DocumentParser(asset, markdown.split('\n'))
210
+ let sections = parser.parseDocument()
211
+ renderSections(lux, sections, demos)
212
+ }
213
+ }
214
+ nextDocument.each {next =>
215
+ lux.div {
216
+ lux.cssClass(Styles.guideNextButtonCss)
217
+ lux.add("a") {
218
+ lux.cssClass(Styles.guideButtonCss)
219
+ lux.set("href", next.document.path(next.guide))
220
+ lux.text("Next: " + next.document.heading())
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ renderSections(lux: Lux, sections: List[Section], demos: List[Demo]) {
228
+ sections.pairs().each {| Pair(index, section) =>
229
+ renderSection(lux, index == 0, section, demos)
230
+ }
231
+ }
232
+
233
+ renderSection(lux: Lux, firstSection: Bool, section: Section, demos: List[Demo]) {
234
+ section.{
235
+ | Section(heading, blocks) =>
236
+ lux.add(if(firstSection) {"h1"} else {"h2"}) {
237
+ lux.cssClass(if(firstSection) {Styles.guideH1Css} else {Styles.guideH2Css})
238
+ lux.text(heading)
239
+ }
240
+ blocks.each {renderBlock(lux, _, demos)}
241
+ | SplitSection(heading, first, second) =>
242
+ lux.add("div") {
243
+ lux.cssClass(Styles.guideSplitCss)
244
+ lux.add("div") {
245
+ lux.add("h2") {
246
+ lux.cssClass(Styles.guideSplitHeadingCss)
247
+ lux.text(section.heading)
248
+ }
249
+ renderBlock(lux, first, demos)
250
+ }
251
+ lux.add("div") {
252
+ renderBlock(lux, second, demos)
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ renderBlock(lux: Lux, block: Block, demos: List[Demo]) {
259
+ block.{
260
+ | Paragraph(inlines) => renderParagraph(lux, inlines)
261
+ | CodeBlock(code, firefly) => renderCodeBlock(lux, code, firefly)
262
+ | Bullets(items) => renderBullets(lux, items)
263
+ | Image(url) => renderImage(lux, url)
264
+ | Video(url) => renderVideo(lux, url)
265
+ | LuxDemo(name) => renderLuxDemo(lux, name, demos)
266
+ }
267
+ }
268
+
269
+ renderParagraph(lux: Lux, inlines: List[Inline]) {
270
+ lux.add("p") {
271
+ renderInlines(lux, inlines)
272
+ }
273
+ }
274
+
275
+ renderCodeBlock(lux: Lux, code: String, firefly: Bool) {
276
+ let lines = code.lines().dropWhile {_.all {_ == ' '}}.reverse().dropWhile {_.all {_ == ' '}}.reverse()
277
+ let indentation = lines.first().map {_.takeWhile {_ == ' '}.size()}.else {0}
278
+ lux.add("pre") {
279
+ lux.cssClass(Styles.guideCodeBlockCss)
280
+ lux.add("code") {
281
+ lux.cssClass(Styles.guideCodeCss)
282
+ let unindentedCode = lines.map {_.dropFirst(indentation)}.join("\n")
283
+ if(firefly) {
284
+ renderHighlightedCode(lux, unindentedCode)
285
+ } else {
286
+ lux.text(unindentedCode)
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ renderBullets(lux: Lux, items: List[List[Inline]]) {
293
+ lux.add("ul") {
294
+ items.each {inlines =>
295
+ lux.add("li") {
296
+ renderInlines(lux, inlines)
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ renderImage(lux: Lux, url: String) {
303
+ lux.add("img") {
304
+ lux.set("src", url)
305
+ lux.css(Css.maxWidth("100%"))
306
+ lux.css(Css.borderRadius("8px"))
307
+ }
308
+ }
309
+
310
+ renderVideo(lux: Lux, url: String) {
311
+ lux.add("video") {
312
+ lux.set("src", url)
313
+ lux.css(Css.maxWidth("100%"))
314
+ }
315
+ }
316
+
317
+ renderLuxDemo(lux: Lux, name: String, demos: List[Demo]) {
318
+ demos.find {_.name == name}.map {_.render(lux)}.else {lux.text("Demo not found")}
319
+ }
320
+
321
+ renderInlines(lux: Lux, inlines: List[Inline]) {
322
+ inlines.separate([Text(" ")]).each {renderInline(lux, _)}
323
+ }
324
+
325
+ renderInline(lux: Lux, inline: Inline) {
326
+ inline.{
327
+ | Anchor(heading, title) =>
328
+ lux.add("a") {
329
+ let url = title.map {"/guide/" + kebabCase(_)}.else {""} + "#" + kebabCase(heading)
330
+ lux.cssClass(Styles.guideLinkCss)
331
+ lux.set("href", url)
332
+ lux.text(title.map {_ + ": "}.else {""} + heading)
333
+ }
334
+ | Bold(text) =>
335
+ lux.add("b") {
336
+ lux.text(text)
337
+ }
338
+ | Code(code, firefly) =>
339
+ lux.add("code") {
340
+ lux.cssClass(Styles.guideCodeCss)
341
+ if(firefly) {
342
+ renderHighlightedCode(lux, code)
343
+ } else {
344
+ lux.text(code)
345
+ }
346
+ }
347
+ | Italic(text) =>
348
+ lux.add("i") {
349
+ lux.text(text)
350
+ }
351
+ | Link(text, url) =>
352
+ lux.add("a") {
353
+ lux.cssClass(Styles.guideLinkCss)
354
+ lux.set("href", url)
355
+ lux.text(text)
356
+ }
357
+ | Text(text) =>
358
+ lux.text(text)
359
+ }
360
+ }
361
+
362
+ renderHighlightedCode(lux: Lux, code: String) {
363
+ try {
364
+ Tokenizer.tokenize("<example>", code + "\n", None, False)
365
+ }.toOption().map {tokens =>
366
+ mutable offset = 0
367
+ mutable index = 0
368
+ tokens.each {token =>
369
+ if(token.startOffset > offset) {
370
+ lux.span {
371
+ lux.cssClass(Styles.codeCommentCss)
372
+ lux.text(code.slice(offset, token.startOffset))
373
+ }
374
+ }
375
+ if(token.kind != LEnd) {
376
+ let css = token.kind.{
377
+ | LChar => Styles.codeStringCss
378
+ | LFloat => Styles.codeNumberCss
379
+ | LInt => Styles.codeNumberCss
380
+ | LKeyword => Styles.codeKeywordCss
381
+ | LNamespace => Styles.codeTypeCss
382
+ | LString => Styles.codeStringCss
383
+ | LUpper => Styles.codeTypeCss
384
+ | LWildcard => Styles.codeVariableCss
385
+ | LLower {tokens.get(index - 1).any {t =>
386
+ t.kind == LBracketRight || t.kind == LDot
387
+ }} => Styles.codeCallCss
388
+ | LLower {tokens.grab(index + 1).kind == LBracketLeft} => Styles.codeCallCss
389
+ | LLower => Styles.codeVariableCss
390
+ | _ => Styles.codeOtherCss
391
+ }
392
+ lux.span {
393
+ lux.cssClass(css)
394
+ lux.text(code.slice(token.startOffset, token.stopOffset))
395
+ }
396
+ offset = token.stopOffset
397
+ }
398
+ index += 1
399
+ }
400
+ }.else {lux.text(code)}
401
+ }
402
+
403
+ kebabCase(text: String): String {
404
+ mutable result = text.trim().lower().filter {c => c.isAsciiLetterOrDigit() || c == ' '}
405
+ doWhile {
406
+ let before = result
407
+ result = result.replace(" ", "")
408
+ result != before
409
+ }
410
+ result.replace(" ", "-")
411
+ }