@knpkv/confluence-to-markdown 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +45 -10
  3. package/dist/ConfluenceAuth.d.ts.map +1 -1
  4. package/dist/ConfluenceAuth.js +12 -22
  5. package/dist/ConfluenceAuth.js.map +1 -1
  6. package/dist/ConfluenceClient.d.ts +13 -3
  7. package/dist/ConfluenceClient.d.ts.map +1 -1
  8. package/dist/ConfluenceClient.js +34 -70
  9. package/dist/ConfluenceClient.js.map +1 -1
  10. package/dist/ConfluenceError.d.ts +12 -12
  11. package/dist/GitError.d.ts +5 -5
  12. package/dist/GitService.d.ts.map +1 -1
  13. package/dist/GitService.js +0 -3
  14. package/dist/GitService.js.map +1 -1
  15. package/dist/SchemaConverterError.d.ts +3 -3
  16. package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts +23 -0
  17. package/dist/parsers/preprocessing/ConfluencePreprocessing.d.ts.map +1 -0
  18. package/dist/parsers/preprocessing/ConfluencePreprocessing.js +323 -0
  19. package/dist/parsers/preprocessing/ConfluencePreprocessing.js.map +1 -0
  20. package/dist/parsers/preprocessing/index.d.ts +7 -0
  21. package/dist/parsers/preprocessing/index.d.ts.map +1 -0
  22. package/dist/parsers/preprocessing/index.js +7 -0
  23. package/dist/parsers/preprocessing/index.js.map +1 -0
  24. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +29 -0
  25. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -1
  26. package/dist/schemas/preprocessing/ConfluencePreprocessor.js +3 -5
  27. package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -1
  28. package/package.json +35 -26
  29. package/src/AdfPlaceholders.ts +266 -0
  30. package/src/AdfSchemaValidator.ts +67 -0
  31. package/src/AdfWalker.ts +511 -0
  32. package/src/AtlaskitTransformers.ts +72 -0
  33. package/src/ConfluenceClient.ts +4 -4
  34. package/src/ConfluenceError.ts +65 -3
  35. package/src/MarkdownConverter.ts +106 -139
  36. package/src/Schemas.ts +4 -4
  37. package/src/SyncEngine.ts +130 -83
  38. package/src/atlaskit-adf-schema.d.ts +3 -0
  39. package/src/commands/clone.ts +8 -1
  40. package/src/commands/layers.ts +11 -4
  41. package/src/index.ts +3 -18
  42. package/test/AdfPlaceholders.test.ts +295 -0
  43. package/test/AdfSchemaValidator.test.ts +34 -0
  44. package/test/AdfWalker.test.ts +530 -0
  45. package/test/AtlaskitTransformers.test.ts +25 -0
  46. package/test/MarkdownConverter.test.ts +120 -105
  47. package/test/RoundTrip.test.ts +266 -0
  48. package/LICENSE +0 -21
  49. package/src/SchemaConverterError.ts +0 -108
  50. package/src/ast/BlockNode.ts +0 -425
  51. package/src/ast/Document.ts +0 -90
  52. package/src/ast/InlineNode.ts +0 -323
  53. package/src/ast/MacroNode.ts +0 -245
  54. package/src/ast/index.ts +0 -83
  55. package/src/parsers/ConfluenceParser.ts +0 -950
  56. package/src/parsers/MarkdownParser.ts +0 -1198
  57. package/src/parsers/index.ts +0 -8
  58. package/src/schemas/ConfluenceSchema.ts +0 -56
  59. package/src/schemas/ConversionSchema.ts +0 -318
  60. package/src/schemas/MarkdownSchema.ts +0 -56
  61. package/src/schemas/hast/HastFromHtml.ts +0 -153
  62. package/src/schemas/hast/HastSchema.ts +0 -274
  63. package/src/schemas/hast/index.ts +0 -35
  64. package/src/schemas/index.ts +0 -20
  65. package/src/schemas/mdast/MdastFromMarkdown.ts +0 -118
  66. package/src/schemas/mdast/MdastSchema.ts +0 -566
  67. package/src/schemas/mdast/index.ts +0 -59
  68. package/src/schemas/mdast/mdastToString.ts +0 -102
  69. package/src/schemas/nodes/block/BlockSchema.ts +0 -773
  70. package/src/schemas/nodes/block/index.ts +0 -13
  71. package/src/schemas/nodes/index.ts +0 -20
  72. package/src/schemas/nodes/inline/InlineSchema.ts +0 -523
  73. package/src/schemas/nodes/inline/index.ts +0 -14
  74. package/src/schemas/nodes/macro/MacroSchema.ts +0 -226
  75. package/src/schemas/nodes/macro/index.ts +0 -6
  76. package/src/schemas/preprocessing/ConfluencePreprocessor.ts +0 -446
  77. package/src/schemas/preprocessing/index.ts +0 -8
  78. package/src/serializers/ConfluenceSerializer.ts +0 -717
  79. package/src/serializers/MarkdownSerializer.ts +0 -493
  80. package/src/serializers/index.ts +0 -8
  81. package/test/ast/BlockNode.test.ts +0 -265
  82. package/test/ast/Document.test.ts +0 -126
  83. package/test/ast/InlineNode.test.ts +0 -161
  84. package/test/fixtures/integration-test.html.fixture +0 -103
  85. package/test/fixtures/integration-test.md.expected +0 -257
  86. package/test/parsers/ConfluenceParser.test.ts +0 -283
  87. package/test/schemas/ConfluencePreprocessor.test.ts +0 -180
  88. package/test/schemas/ConversionSchema.test.ts +0 -159
  89. package/test/schemas/HastSchema.test.ts +0 -138
  90. package/test/schemas/MdastSchema.test.ts +0 -145
  91. package/test/schemas/nodes/block/BlockSchema.test.ts +0 -173
  92. package/test/schemas/nodes/inline/InlineSchema.test.ts +0 -198
  93. package/test/schemas/nodes/macro/MacroSchema.test.ts +0 -142
@@ -1,257 +0,0 @@
1
- <!--cf:layout-start-->
2
-
3
- <!--cf:section:0;fixed-width;default;;1-->
4
-
5
- <!--cf:cell:0;0-->
6
-
7
- | | |
8
- | --- | --- |
9
- | Owner | <!--cf:user:557058:76c20100-e45a-495d-9cb2-4d7c809e6a9b--> |
10
- | On this page | <!--cf:toc:;--> |
11
-
12
- Normal text
13
-
14
- ## Headings
15
-
16
- # Heading 1
17
-
18
- ## Heading 2
19
-
20
- ### Heading 3
21
-
22
- #### Heading 4
23
-
24
- ##### Heading 5
25
-
26
- ###### Heading 6
27
-
28
- ## Text Formatting
29
-
30
- ### Basic Styles
31
-
32
- **Bold**
33
-
34
- *Italic*
35
-
36
- <u>Inderline</u>
37
-
38
- ~~Strikethrough~~
39
-
40
- `Code`
41
-
42
- <sub>Subscript</sub>
43
-
44
- <sup>Superscript</sup>
45
-
46
- ### Quotes
47
-
48
- > Quote
49
-
50
- ### Alignment
51
-
52
- <p style="text-align: center;">Align center</p>
53
-
54
- <p style="text-align: right;">Align right</p>
55
-
56
- Default
57
-
58
- ## Colors
59
-
60
- ### Text Colors
61
-
62
- <span style="color: rgb(151,160,175);">Gray</span>
63
-
64
- <span style="color: rgb(255,255,255);">White</span>
65
-
66
- <span style="color: rgb(7,71,166);">Bold blue</span>
67
-
68
- <span style="color: rgb(76,154,255);">Blue</span>
69
-
70
- <span style="color: rgb(179,212,255);">Subtle blue</span>
71
-
72
- <span style="color: rgb(0,141,166);">Bold teal</span>
73
-
74
- <span style="color: rgb(0,184,217);">Teal</span>
75
-
76
- <span style="color: rgb(179,245,255);">Subtle blue</span>
77
-
78
- <span style="color: rgb(0,102,68);">Bold green</span>
79
-
80
- <span style="color: rgb(54,179,126);">Green</span>
81
-
82
- <span style="color: rgb(171,245,209);">Subtle green</span>
83
-
84
- <span style="color: rgb(255,153,31);">Bold orange</span>
85
-
86
- <span style="color: rgb(255,196,0);">Yellow</span>
87
-
88
- <span style="color: rgb(255,240,179);">Subtle yellow</span>
89
-
90
- <span style="color: rgb(191,38,0);">Bold red</span>
91
-
92
- <span style="color: rgb(255,86,48);">Red</span>
93
-
94
- <span style="color: rgb(255,189,173);">Subtle red</span>
95
-
96
- <span style="color: rgb(64,50,148);">Bold purple</span>
97
-
98
- <span style="color: rgb(101,84,192);">Purple</span>
99
-
100
- <span style="color: rgb(234,230,255);">Subtle purple</span>
101
-
102
- ### Highlights
103
-
104
- <span style="background-color: rgb(220,223,228);">Gray highlight</span>
105
-
106
- <span style="background-color: rgb(198,237,251);">Teal highlight</span>
107
-
108
- <span style="background-color: rgb(211,241,167);">Lime highlight</span>
109
-
110
- <span style="background-color: rgb(254,222,200);">Yellow highlight</span>
111
-
112
- <span style="background-color: rgb(253,208,236);">Magenta highlight</span>
113
-
114
- <span style="background-color: rgb(223,216,253);">Purple highlight</span>
115
-
116
- ## Lists
117
-
118
- ### Bullet Lists
119
-
120
- - Bullet list 1
121
- <ul local-id="bed75a55-f8b9-45fb-9346-7531dd9ea338"><li local-id="30852041-2521-40ed-acbb-1a8469be3c30"><p local-id="5b0cccdf-3a87-4b32-b618-910a94529992">Bullet list 1.1</p><ul local-id="453de478-a2d7-4b2f-9b93-91732877c960"><li local-id="cc32bd38-0927-42f2-b2bc-8f480ea97762"><p local-id="308bc2ca-c9b5-4734-a0ff-dea8e319184d">Bullet list 1.1.1</p></li></ul></li></ul>
122
- - Bullet list 2
123
-
124
- ### Numbered Lists
125
-
126
- 1. Numbered list 1
127
- <ol start="1" local-id="d40d0651-9647-4012-aa44-22dea36dca0f"><li local-id="16340895-0a02-4369-a4f2-7fe3f7c56112"><p local-id="d5ad263d-afe0-4ca3-8299-929fbc1c7cd0">Numbered list a</p><ol start="1" local-id="897771c4-1ff1-4b72-832b-c8d6cc79f57b"><li local-id="25120a69-d5ff-41e0-9192-35e036d752f0"><p local-id="6bd35901-285f-4f58-872f-51e4152e296b">Numbered list i</p></li></ol></li></ol>
128
- 2. Numbered list 2
129
-
130
- ### Indentation
131
-
132
- <p style="margin-left: 30px;">Indent tab 1</p>
133
-
134
- <p style="margin-left: 60px;">Indent tab 2</p>
135
-
136
- <p style="margin-left: 90px;">Indent tab 3</p>
137
-
138
- ### Action Items
139
-
140
- <!--cf:tasklist:11|bce1bc39-6cba-44f9-b1b3-6adf0634475a|incomplete|Action%20Item%20unchecked;12|49c6a48e-cb41-4d98-ba45-64c1ecf96702|complete|Action%20item%20checked-->
141
-
142
- ## Links & Media
143
-
144
- ### Links
145
-
146
- [External link](http://example.com)
147
-
148
- <!--cf:link:Confluence%20link-->
149
-
150
- ### Images
151
-
152
- <!--cf:image:f=Atlassian_Confluence_2017_logo.svg|a=Atlassian_Confluence_2017_logo.svg|al=center|w=238-->
153
-
154
- ### User Mentions
155
-
156
- <!--cf:user:557058:76c20100-e45a-495d-9cb2-4d7c809e6a9b-->
157
-
158
- ### Emoticons
159
-
160
- <!--cf:emoticon:%3Agrinning%3A|1f600|%F0%9F%98%80--> <!--cf:emoticon:%3Asmiley%3A|1f603|%F0%9F%98%83--> <!--cf:emoticon:%3Asmile%3A|1f604|%F0%9F%98%84--> <!--cf:emoticon:%3Agrin%3A|1f601|%F0%9F%98%81--> <!--cf:emoticon:%3Alaughing%3A|1f606|%F0%9F%98%86--> <!--cf:emoticon:%3Asweat_smile%3A|1f605|%F0%9F%98%85--> <!--cf:emoticon:%3Ajoy%3A|1f602|%F0%9F%98%82--> <!--cf:emoticon:%3Arofl%3A|1f923|%F0%9F%A4%A3-->
161
-
162
- ## Tables
163
-
164
- | **Header 1** | **Header 2** | **Header 3** |
165
- | --- | --- | --- |
166
- | Cell 1.1 | Cell 2.1 | Cell 3.1 |
167
- | Cell 1.2 | Cell 2.2 | Cell 3.2 |
168
-
169
- <!--cf:section-end:0-->
170
-
171
- <!--cf:section:1;two_equal;wide;760;2-->
172
-
173
- <!--cf:cell:1;0-->
174
-
175
- Column 1
176
-
177
- <!--cf:cell:1;1-->
178
-
179
- Column 2
180
-
181
- <!--cf:section-end:1-->
182
-
183
- <!--cf:section:2;fixed-width;default;;1-->
184
-
185
- <!--cf:cell:2;0-->
186
-
187
- ### Code Blocks
188
-
189
- ```typescript
190
- const typescript = {};
191
- ```
192
-
193
- ```json
194
- {
195
- "json": true
196
- }
197
- ```
198
-
199
- ### Decisions
200
-
201
- <!--cf:decision:7c3012f5-3ad3-47e1-89cc-cd87267668d3;DECIDED;Decision-->
202
-
203
- ---
204
-
205
- ### Expand
206
-
207
- <!--cf:expand:Expand%20title:Expand%20content-->
208
-
209
- ### Dates
210
-
211
- <!--cf:date:2026-01-01-->
212
-
213
- ### Status
214
-
215
- <!--cf:status:Gray;--> <!--cf:status:;Blue--> <!--cf:status:;Green--> <!--cf:status:;Yellow--> <!--cf:status:;Red--> <!--cf:status:;Purple-->
216
-
217
- ### Smart Links
218
-
219
- <!--cf:smartlink:https%3A%2F%2Fknpkv-team.atlassian.net%2Fissues%2F%3Fjql%3Dproject%2520in%2520(LEARNJIRA)%2520ORDER%2520BY%2520created%2520DESC;block;%7B%22id%22%3A%22d8b75300-dfda-4519-b6cd-e49abbd50401%22%2C%22parameters%22%3A%7B%22cloudId%22%3A%2285fe2d49-d86f-4afa-be53-e7ec408a4d5e%22%2C%22jql%22%3A%22project%20in%20(LEARNJIRA)%20ORDER%20BY%20created%20DESC%22%7D%2C%22views%22%3A%5B%7B%22type%22%3A%22table%22%2C%22properties%22%3A%7B%22columns%22%3A%5B%7B%22key%22%3A%22issuetype%22%7D%2C%7B%22key%22%3A%22key%22%7D%2C%7B%22key%22%3A%22summary%22%7D%2C%7B%22key%22%3A%22assignee%22%7D%2C%7B%22key%22%3A%22priority%22%7D%2C%7B%22key%22%3A%22status%22%7D%2C%7B%22key%22%3A%22updated%22%7D%5D%7D%7D%5D%7D-->
220
-
221
- <!--cf:smartlink:https%3A%2F%2Fknpkv-team.atlassian.net%2Fwiki%2Fsearch%3Ftext%3DGetting%2Bstarted;block;%7B%22id%22%3A%22768fc736-3af4-4a8f-b27e-203602bff8ca%22%2C%22parameters%22%3A%7B%22cloudId%22%3A%2285fe2d49-d86f-4afa-be53-e7ec408a4d5e%22%2C%22searchString%22%3A%22Getting%20started%22%7D%2C%22views%22%3A%5B%7B%22type%22%3A%22table%22%2C%22properties%22%3A%7B%22columns%22%3A%5B%7B%22key%22%3A%22type%22%7D%2C%7B%22key%22%3A%22title%22%7D%2C%7B%22key%22%3A%22space%22%2C%22width%22%3A243%7D%2C%7B%22key%22%3A%22description%22%7D%2C%7B%22key%22%3A%22ownedBy%22%7D%2C%7B%22key%22%3A%22updatedAt%22%7D%5D%7D%7D%5D%7D-->
222
-
223
- ### Panels
224
-
225
- :::info
226
- Info panel
227
- :::
228
-
229
- :::note
230
- Note panel
231
- :::
232
-
233
- :::warning
234
- Error panel
235
- :::
236
-
237
- :::panel
238
- Custom panel
239
- :::
240
-
241
- :::tip
242
- Success panel
243
- :::
244
-
245
- :::note
246
- Warning panel
247
- :::
248
-
249
- ### Other
250
-
251
- Hello 3
252
-
253
- <!--cf:section-end:2-->
254
-
255
- <!--cf:layout-end-->
256
-
257
- <!--cf:raw:PGFjOmxheW91dD48YWM6bGF5b3V0LXNlY3Rpb24gYWM6dHlwZT0iZml4ZWQtd2lkdGgiIGFjOmJyZWFrb3V0LW1vZGU9ImRlZmF1bHQiPjxhYzpsYXlvdXQtY2VsbD48dGFibGU+PHRoZWFkPjx0cj48dGggLz48dGggLz48L3RyPjwvdGhlYWQ+PHRib2R5Pjx0cj48dGQ+T3duZXI8L3RkPjx0ZD48YWM6bGluaz48cmk6dXNlciByaTphY2NvdW50LWlkPSI1NTcwNTg6NzZjMjAxMDAtZTQ1YS00OTVkLTljYjItNGQ3YzgwOWU2YTliIiAvPjwvYWM6bGluaz48L3RkPjwvdHI+PHRyPjx0ZD5PbiB0aGlzIHBhZ2U8L3RkPjx0ZD48YWM6c3RydWN0dXJlZC1tYWNybyBhYzpuYW1lPSJ0b2MiIGFjOnNjaGVtYS12ZXJzaW9uPSIxIiBhYzptYWNyby1pZD0iZDQ1MmJhZDQtNzAwZi00OTczLWFlMDYtNTc0Nzc2YTE0Njg3IiAvPjwvdGQ+PC90cj48L3Rib2R5PjwvdGFibGU+CjxwPk5vcm1hbCB0ZXh0PC9wPgo8aDI+SGVhZGluZ3M8L2gyPgo8aDE+SGVhZGluZyAxPC9oMT4KPGgyPkhlYWRpbmcgMjwvaDI+CjxoMz5IZWFkaW5nIDM8L2gzPgo8aDQ+SGVhZGluZyA0PC9oND4KPGg1PkhlYWRpbmcgNTwvaDU+CjxoNj5IZWFkaW5nIDY8L2g2Pgo8aDI+VGV4dCBGb3JtYXR0aW5nPC9oMj4KPGgzPkJhc2ljIFN0eWxlczwvaDM+CjxwPjxzdHJvbmc+Qm9sZDwvc3Ryb25nPjwvcD4KPHA+PGVtPkl0YWxpYzwvZW0+PC9wPgo8cD48dT5JbmRlcmxpbmU8L3U+PC9wPgo8cD48ZGVsPlN0cmlrZXRocm91Z2g8L2RlbD48L3A+CjxwPjxjb2RlPkNvZGU8L2NvZGU+PC9wPgo8cD48c3ViPlN1YnNjcmlwdDwvc3ViPjwvcD4KPHA+PHN1cD5TdXBlcnNjcmlwdDwvc3VwPjwvcD4KPGgzPlF1b3RlczwvaDM+CjxibG9ja3F1b3RlPjxwPlF1b3RlPC9wPjwvYmxvY2txdW90ZT4KPGgzPkFsaWdubWVudDwvaDM+CjxwIHN0eWxlPSJ0ZXh0LWFsaWduOiBjZW50ZXI7Ij5BbGlnbiBjZW50ZXI8L3A+CjxwIHN0eWxlPSJ0ZXh0LWFsaWduOiByaWdodDsiPkFsaWduIHJpZ2h0PC9wPgo8cD5EZWZhdWx0PC9wPgo8aDI+Q29sb3JzPC9oMj4KPGgzPlRleHQgQ29sb3JzPC9oMz4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMTUxLDE2MCwxNzUpOyI+R3JheTwvc3Bhbj48L3A+CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjogcmdiKDI1NSwyNTUsMjU1KTsiPldoaXRlPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoNyw3MSwxNjYpOyI+Qm9sZCBibHVlPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoNzYsMTU0LDI1NSk7Ij5CbHVlPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMTc5LDIxMiwyNTUpOyI+U3VidGxlIGJsdWU8L3NwYW4+PC9wPgo8cD48c3BhbiBzdHlsZT0iY29sb3I6IHJnYigwLDE0MSwxNjYpOyI+Qm9sZCB0ZWFsPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMCwxODQsMjE3KTsiPlRlYWw8L3NwYW4+PC9wPgo8cD48c3BhbiBzdHlsZT0iY29sb3I6IHJnYigxNzksMjQ1LDI1NSk7Ij5TdWJ0bGUgYmx1ZTwvc3Bhbj48L3A+CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjogcmdiKDAsMTAyLDY4KTsiPkJvbGQgZ3JlZW48L3NwYW4+PC9wPgo8cD48c3BhbiBzdHlsZT0iY29sb3I6IHJnYig1NCwxNzksMTI2KTsiPkdyZWVuPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMTcxLDI0NSwyMDkpOyI+U3VidGxlIGdyZWVuPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMjU1LDE1MywzMSk7Ij5Cb2xkIG9yYW5nZTwvc3Bhbj48L3A+CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjogcmdiKDI1NSwxOTYsMCk7Ij5ZZWxsb3c8L3NwYW4+PC9wPgo8cD48c3BhbiBzdHlsZT0iY29sb3I6IHJnYigyNTUsMjQwLDE3OSk7Ij5TdWJ0bGUgeWVsbG93PC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMTkxLDM4LDApOyI+Qm9sZCByZWQ8L3NwYW4+PC9wPgo8cD48c3BhbiBzdHlsZT0iY29sb3I6IHJnYigyNTUsODYsNDgpOyI+UmVkPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMjU1LDE4OSwxNzMpOyI+U3VidGxlIHJlZDwvc3Bhbj48L3A+CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjogcmdiKDY0LDUwLDE0OCk7Ij5Cb2xkIHB1cnBsZTwvc3Bhbj48L3A+CjxwPjxzcGFuIHN0eWxlPSJjb2xvcjogcmdiKDEwMSw4NCwxOTIpOyI+UHVycGxlPC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImNvbG9yOiByZ2IoMjM0LDIzMCwyNTUpOyI+U3VidGxlIHB1cnBsZTwvc3Bhbj48L3A+CjxoMz5IaWdobGlnaHRzPC9oMz4KPHA+PHNwYW4gc3R5bGU9ImJhY2tncm91bmQtY29sb3I6IHJnYigyMjAsMjIzLDIyOCk7Ij5HcmF5IGhpZ2hsaWdodDwvc3Bhbj48L3A+CjxwPjxzcGFuIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMTk4LDIzNywyNTEpOyI+VGVhbCBoaWdobGlnaHQ8L3NwYW4+PC9wPgo8cD48c3BhbiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjogcmdiKDIxMSwyNDEsMTY3KTsiPkxpbWUgaGlnaGxpZ2h0PC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImJhY2tncm91bmQtY29sb3I6IHJnYigyNTQsMjIyLDIwMCk7Ij5ZZWxsb3cgaGlnaGxpZ2h0PC9zcGFuPjwvcD4KPHA+PHNwYW4gc3R5bGU9ImJhY2tncm91bmQtY29sb3I6IHJnYigyNTMsMjA4LDIzNik7Ij5NYWdlbnRhIGhpZ2hsaWdodDwvc3Bhbj48L3A+CjxwPjxzcGFuIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjIzLDIxNiwyNTMpOyI+UHVycGxlIGhpZ2hsaWdodDwvc3Bhbj48L3A+CjxoMj5MaXN0czwvaDI+CjxoMz5CdWxsZXQgTGlzdHM8L2gzPgo8dWw+PGxpPjxwPkJ1bGxldCBsaXN0IDE8L3A+PHVsIGxvY2FsLWlkPSJiZWQ3NWE1NS1mOGI5LTQ1ZmItOTM0Ni03NTMxZGQ5ZWEzMzgiPjxsaSBsb2NhbC1pZD0iMzA4NTIwNDEtMjUyMS00MGVkLWFjYmItMWE4NDY5YmUzYzMwIj48cCBsb2NhbC1pZD0iNWIwY2NjZGYtM2E4Ny00YjMyLWI2MTgtOTEwYTk0NTI5OTkyIj5CdWxsZXQgbGlzdCAxLjE8L3A+PHVsIGxvY2FsLWlkPSI0NTNkZTQ3OC1hMmQ3LTRiMmYtOWI5My05MTczMjg3N2M5NjAiPjxsaSBsb2NhbC1pZD0iY2MzMmJkMzgtMDkyNy00MmYyLWIyYmMtOGY0ODBlYTk3NzYyIj48cCBsb2NhbC1pZD0iMzA4YmMyY2EtYzliNS00NzM0LWEwZmYtZGVhOGUzMTkxODRkIj5CdWxsZXQgbGlzdCAxLjEuMTwvcD48L2xpPjwvdWw+PC9saT48L3VsPjwvbGk+PGxpPjxwPkJ1bGxldCBsaXN0IDI8L3A+PC9saT48L3VsPgo8aDM+TnVtYmVyZWQgTGlzdHM8L2gzPgo8b2w+PGxpPjxwPk51bWJlcmVkIGxpc3QgMTwvcD48b2wgc3RhcnQ9IjEiIGxvY2FsLWlkPSJkNDBkMDY1MS05NjQ3LTQwMTItYWE0NC0yMmRlYTM2ZGNhMGYiPjxsaSBsb2NhbC1pZD0iMTYzNDA4OTUtMGEwMi00MzY5LWE0ZjItN2ZlM2Y3YzU2MTEyIj48cCBsb2NhbC1pZD0iZDVhZDI2M2QtYWZlMC00Y2EzLTgyOTktOTI5ZmJjMWM3Y2QwIj5OdW1iZXJlZCBsaXN0IGE8L3A+PG9sIHN0YXJ0PSIxIiBsb2NhbC1pZD0iODk3NzcxYzQtMWZmMS00YjcyLTgzMmItYzhkNmNjNzlmNTdiIj48bGkgbG9jYWwtaWQ9IjI1MTIwYTY5LWQ1ZmYtNDFlMC05MTkyLTM1ZTAzNmQ3NTJmMCI+PHAgbG9jYWwtaWQ9IjZiZDM1OTAxLTI4NWYtNGY1OC04NzJmLTUxZTQxNTJlMjk2YiI+TnVtYmVyZWQgbGlzdCBpPC9wPjwvbGk+PC9vbD48L2xpPjwvb2w+PC9saT48bGk+PHA+TnVtYmVyZWQgbGlzdCAyPC9wPjwvbGk+PC9vbD4KPGgzPkluZGVudGF0aW9uPC9oMz4KPHAgc3R5bGU9Im1hcmdpbi1sZWZ0OiAzMC4wcHg7Ij5JbmRlbnQgdGFiIDE8L3A+CjxwIHN0eWxlPSJtYXJnaW4tbGVmdDogNjAuMHB4OyI+SW5kZW50IHRhYiAyPC9wPgo8cCBzdHlsZT0ibWFyZ2luLWxlZnQ6IDkwLjBweDsiPkluZGVudCB0YWIgMzwvcD4KPGgzPkFjdGlvbiBJdGVtczwvaDM+CjxhYzp0YXNrLWxpc3Q+CjxhYzp0YXNrPjxhYzp0YXNrLWlkPjQzPC9hYzp0YXNrLWlkPjxhYzp0YXNrLXV1aWQ+Yjk0ZTUzMzQtNzRkYy00Yjk1LWJkNmYtYTAzMGMxZGY3MDY4PC9hYzp0YXNrLXV1aWQ+PGFjOnRhc2stc3RhdHVzPmluY29tcGxldGU8L2FjOnRhc2stc3RhdHVzPjxhYzp0YXNrLWJvZHk+PHNwYW4gY2xhc3M9InBsYWNlaG9sZGVyLWlubGluZS10YXNrcyI+QWN0aW9uIEl0ZW0gdW5jaGVja2VkPC9zcGFuPjwvYWM6dGFzay1ib2R5PjwvYWM6dGFzaz4KPGFjOnRhc2s+PGFjOnRhc2staWQ+NDQ8L2FjOnRhc2staWQ+PGFjOnRhc2stdXVpZD5mN2YzNDBlMS03MmNmLTQ2NWItYTE3YS00NDA0OGY1M2I0ZjU8L2FjOnRhc2stdXVpZD48YWM6dGFzay1zdGF0dXM+Y29tcGxldGU8L2FjOnRhc2stc3RhdHVzPjxhYzp0YXNrLWJvZHk+PHNwYW4gY2xhc3M9InBsYWNlaG9sZGVyLWlubGluZS10YXNrcyI+QWN0aW9uIGl0ZW0gY2hlY2tlZDwvc3Bhbj48L2FjOnRhc2stYm9keT48L2FjOnRhc2s+CjwvYWM6dGFzay1saXN0Pgo8aDI+TGlua3MgJmFtcDsgTWVkaWE8L2gyPgo8aDM+TGlua3M8L2gzPgo8cD48YSBocmVmPSJodHRwOi8vZXhhbXBsZS5jb20iPkxpbms8L2E+PC9wPgo8aDM+SW1hZ2VzPC9oMz4KPGFjOmltYWdlIGFjOmFsaWduPSJjZW50ZXIiIGFjOndpZHRoPSIyNTAiIGFjOmFsdD0iQXRsYXNzaWFuX0NvbmZsdWVuY2VfMjAxN19sb2dvLnN2ZyI+PHJpOmF0dGFjaG1lbnQgcmk6ZmlsZW5hbWU9IkF0bGFzc2lhbl9Db25mbHVlbmNlXzIwMTdfbG9nby5zdmciIHJpOnZlcnNpb24tYXQtc2F2ZT0iMSIgLz48L2FjOmltYWdlPgo8aDM+VXNlciBNZW50aW9uczwvaDM+CjxwPjxhYzpsaW5rPjxyaTp1c2VyIHJpOmFjY291bnQtaWQ9IjU1NzA1ODo3NmMyMDEwMC1lNDVhLTQ5NWQtOWNiMi00ZDdjODA5ZTZhOWIiIC8+PC9hYzpsaW5rPjwvcD4KPGgzPkVtb3RpY29uczwvaDM+CjxwPjxhYzplbW90aWNvbiBhYzplbW9qaS1zaG9ydG5hbWU9IjpncmlubmluZzoiIGFjOmVtb2ppLWlkPSIxZjYwMCIgYWM6ZW1vamktZmFsbGJhY2s9IvCfmIAiIC8+IDxhYzplbW90aWNvbiBhYzplbW9qaS1zaG9ydG5hbWU9IjpzbWlsZXk6IiBhYzplbW9qaS1pZD0iMWY2MDMiIGFjOmVtb2ppLWZhbGxiYWNrPSLwn5iDIiAvPiA8YWM6ZW1vdGljb24gYWM6ZW1vamktc2hvcnRuYW1lPSI6c21pbGU6IiBhYzplbW9qaS1pZD0iMWY2MDQiIGFjOmVtb2ppLWZhbGxiYWNrPSLwn5iEIiAvPiA8YWM6ZW1vdGljb24gYWM6ZW1vamktc2hvcnRuYW1lPSI6Z3JpbjoiIGFjOmVtb2ppLWlkPSIxZjYwMSIgYWM6ZW1vamktZmFsbGJhY2s9IvCfmIEiIC8+IDxhYzplbW90aWNvbiBhYzplbW9qaS1zaG9ydG5hbWU9IjpsYXVnaGluZzoiIGFjOmVtb2ppLWlkPSIxZjYwNiIgYWM6ZW1vamktZmFsbGJhY2s9IvCfmIYiIC8+IDxhYzplbW90aWNvbiBhYzplbW9qaS1zaG9ydG5hbWU9Ijpzd2VhdF9zbWlsZToiIGFjOmVtb2ppLWlkPSIxZjYwNSIgYWM6ZW1vamktZmFsbGJhY2s9IvCfmIUiIC8+IDxhYzplbW90aWNvbiBhYzplbW9qaS1zaG9ydG5hbWU9Ijpqb3k6IiBhYzplbW9qaS1pZD0iMWY2MDIiIGFjOmVtb2ppLWZhbGxiYWNrPSLwn5iCIiAvPiA8YWM6ZW1vdGljb24gYWM6ZW1vamktc2hvcnRuYW1lPSI6cm9mbDoiIGFjOmVtb2ppLWlkPSIxZjkyMyIgYWM6ZW1vamktZmFsbGJhY2s9IvCfpKMiIC8+PC9wPgo8aDI+VGFibGVzPC9oMj4KPHRhYmxlPjx0aGVhZD48dHI+PHRoPjxzdHJvbmc+SGVhZGVyIDE8L3N0cm9uZz48L3RoPjx0aD48c3Ryb25nPkhlYWRlciAyPC9zdHJvbmc+PC90aD48dGg+PHN0cm9uZz5IZWFkZXIgMzwvc3Ryb25nPjwvdGg+PC90cj48L3RoZWFkPjx0Ym9keT48dHI+PHRkPkNlbGwgMS4xPC90ZD48dGQ+Q2VsbCAyLjE8L3RkPjx0ZD5DZWxsIDMuMTwvdGQ+PC90cj48dHI+PHRkPkNlbGwgMS4yPC90ZD48dGQ+Q2VsbCAyLjI8L3RkPjx0ZD5DZWxsIDMuMjwvdGQ+PC90cj48L3Rib2R5PjwvdGFibGU+PC9hYzpsYXlvdXQtY2VsbD48L2FjOmxheW91dC1zZWN0aW9uPjxhYzpsYXlvdXQtc2VjdGlvbiBhYzp0eXBlPSJ0d29fZXF1YWwiIGFjOmJyZWFrb3V0LW1vZGU9IndpZGUiIGFjOmJyZWFrb3V0LXdpZHRoPSI3NjAiPjxhYzpsYXlvdXQtY2VsbD48cD5Db2x1bW4gMTwvcD48L2FjOmxheW91dC1jZWxsPjxhYzpsYXlvdXQtY2VsbD48cD5Db2x1bW4gMjwvcD48L2FjOmxheW91dC1jZWxsPjwvYWM6bGF5b3V0LXNlY3Rpb24+PGFjOmxheW91dC1zZWN0aW9uIGFjOnR5cGU9ImZpeGVkLXdpZHRoIiBhYzpicmVha291dC1tb2RlPSJkZWZhdWx0Ij48YWM6bGF5b3V0LWNlbGw+PGgzPkNvZGUgQmxvY2tzPC9oMz4KPGFjOnN0cnVjdHVyZWQtbWFjcm8gYWM6bmFtZT0iY29kZSIgYWM6c2NoZW1hLXZlcnNpb249IjEiIGFjOm1hY3JvLWlkPSI4NGRlN2UwMy1lM2IwLTQ4ODQtYWE3Yi0zNGNiZmNlNmMxYzEiPjxhYzpwYXJhbWV0ZXIgYWM6bmFtZT0ibGFuZ3VhZ2UiPnR5cGVzY3JpcHQ8L2FjOnBhcmFtZXRlcj48YWM6cGxhaW4tdGV4dC1ib2R5PjwhW0NEQVRBW2NvbnN0IHR5cGVzY3JpcHQgPSB7fTtdXT48L2FjOnBsYWluLXRleHQtYm9keT48L2FjOnN0cnVjdHVyZWQtbWFjcm8+CjxhYzpzdHJ1Y3R1cmVkLW1hY3JvIGFjOm5hbWU9ImNvZGUiIGFjOnNjaGVtYS12ZXJzaW9uPSIxIiBhYzptYWNyby1pZD0iMGQ2MGZlNTYtYzkzYy00ZjZiLTk3OWItMzQzMmM3OWRiOWJhIj48YWM6cGFyYW1ldGVyIGFjOm5hbWU9Imxhbmd1YWdlIj5qc29uPC9hYzpwYXJhbWV0ZXI+PGFjOnBsYWluLXRleHQtYm9keT48IVtDREFUQVt7CiAgImpzb24iOiB0cnVlCn1dXT48L2FjOnBsYWluLXRleHQtYm9keT48L2FjOnN0cnVjdHVyZWQtbWFjcm8+CjxoMz5EZWNpc2lvbnM8L2gzPgo8YWM6YWRmLWV4dGVuc2lvbj48YWM6YWRmLW5vZGUgdHlwZT0iZGVjaXNpb24tbGlzdCI+PGFjOmFkZi1ub2RlIHR5cGU9ImRlY2lzaW9uLWl0ZW0iPjxhYzphZGYtYXR0cmlidXRlIGtleT0ibG9jYWwtaWQiPjdjMzAxMmY1LTNhZDMtNDdlMS04OWNjLWNkODcyNjc2NjhkMzwvYWM6YWRmLWF0dHJpYnV0ZT48YWM6YWRmLWF0dHJpYnV0ZSBrZXk9InN0YXRlIj5ERUNJREVEPC9hYzphZGYtYXR0cmlidXRlPjxhYzphZGYtY29udGVudD5EZWNpc2lvbjwvYWM6YWRmLWNvbnRlbnQ+PC9hYzphZGYtbm9kZT48L2FjOmFkZi1ub2RlPjxhYzphZGYtZmFsbGJhY2s+PHVsIGNsYXNzPSJkZWNpc2lvbi1saXN0Ij48bGk+RGVjaXNpb248L2xpPjwvdWw+PC9hYzphZGYtZmFsbGJhY2s+PC9hYzphZGYtZXh0ZW5zaW9uPgo8aHIgLz4KPGgzPkV4cGFuZDwvaDM+CjxhYzpzdHJ1Y3R1cmVkLW1hY3JvIGFjOm5hbWU9ImV4cGFuZCIgYWM6c2NoZW1hLXZlcnNpb249IjEiIGFjOm1hY3JvLWlkPSJhMWM1NmMyZC1kOGExLTRiODMtOGRiYy1jMTI4M2E0MDFjYjgiPjxhYzpwYXJhbWV0ZXIgYWM6bmFtZT0idGl0bGUiPkV4cGFuZCB0aXRsZTwvYWM6cGFyYW1ldGVyPjxhYzpyaWNoLXRleHQtYm9keT48cD5FeHBhbmQgY29udGVudDwvcD48L2FjOnJpY2gtdGV4dC1ib2R5PjwvYWM6c3RydWN0dXJlZC1tYWNybz4KPGgzPkRhdGVzPC9oMz4KPHA+PHRpbWUgZGF0ZXRpbWU9IjIwMjYtMDEtMDEiIC8+PC9wPgo8aDM+U3RhdHVzPC9oMz4KPHA+PGFjOnN0cnVjdHVyZWQtbWFjcm8gYWM6bmFtZT0ic3RhdHVzIiBhYzpzY2hlbWEtdmVyc2lvbj0iMSIgYWM6bWFjcm8taWQ9ImJhMGE0Mjk3LTlmZjktNGQ0NC1iZjY5LTM0Njk2NjM2MzZmNyI+PGFjOnBhcmFtZXRlciBhYzpuYW1lPSJ0aXRsZSI+R3JheTwvYWM6cGFyYW1ldGVyPjwvYWM6c3RydWN0dXJlZC1tYWNybz4gPGFjOnN0cnVjdHVyZWQtbWFjcm8gYWM6bmFtZT0ic3RhdHVzIiBhYzpzY2hlbWEtdmVyc2lvbj0iMSIgYWM6bWFjcm8taWQ9ImYyMjBlZTBjLTczNWEtNDYxMy1iYmI2LTY0YjVmOTk1MjQ3MiI+PGFjOnBhcmFtZXRlciBhYzpuYW1lPSJjb2xvdXIiPkJsdWU8L2FjOnBhcmFtZXRlcj48L2FjOnN0cnVjdHVyZWQtbWFjcm8+IDxhYzpzdHJ1Y3R1cmVkLW1hY3JvIGFjOm5hbWU9InN0YXR1cyIgYWM6c2NoZW1hLXZlcnNpb249IjEiIGFjOm1hY3JvLWlkPSI3MTIzNGZkNS01NDQyLTQ1NTctYTZkZS1lZDc2MjkyM2Q5OWQiPjxhYzpwYXJhbWV0ZXIgYWM6bmFtZT0iY29sb3VyIj5HcmVlbjwvYWM6cGFyYW1ldGVyPjwvYWM6c3RydWN0dXJlZC1tYWNybz4gPGFjOnN0cnVjdHVyZWQtbWFjcm8gYWM6bmFtZT0ic3RhdHVzIiBhYzpzY2hlbWEtdmVyc2lvbj0iMSIgYWM6bWFjcm8taWQ9IjcyYWMzMDA3LTkyNjQtNDY4ZS1iMGYyLTg1ODQ2NWU1ZTljZCI+PGFjOnBhcmFtZXRlciBhYzpuYW1lPSJjb2xvdXIiPlllbGxvdzwvYWM6cGFyYW1ldGVyPjwvYWM6c3RydWN0dXJlZC1tYWNybz4gPGFjOnN0cnVjdHVyZWQtbWFjcm8gYWM6bmFtZT0ic3RhdHVzIiBhYzpzY2hlbWEtdmVyc2lvbj0iMSIgYWM6bWFjcm8taWQ9IjI3ZmFmYjU5LTQwZDktNDRiOS05NzlhLTdiODIwY2NjMWJhNiI+PGFjOnBhcmFtZXRlciBhYzpuYW1lPSJjb2xvdXIiPlJlZDwvYWM6cGFyYW1ldGVyPjwvYWM6c3RydWN0dXJlZC1tYWNybz4gPGFjOnN0cnVjdHVyZWQtbWFjcm8gYWM6bmFtZT0ic3RhdHVzIiBhYzpzY2hlbWEtdmVyc2lvbj0iMSIgYWM6bWFjcm8taWQ9ImNjMzc2NDE5LTFhYTAtNGZjMy05Mzc4LTRhOWUwZWZjNWE0ZSI+PGFjOnBhcmFtZXRlciBhYzpuYW1lPSJjb2xvdXIiPlB1cnBsZTwvYWM6cGFyYW1ldGVyPjwvYWM6c3RydWN0dXJlZC1tYWNybz48L3A+CjxoMz5TbWFydCBMaW5rczwvaDM+CjxwPjxhIGhyZWY9Imh0dHBzOi8va25wa3YtdGVhbS5hdGxhc3NpYW4ubmV0L2lzc3Vlcy8/anFsPXByb2plY3QlMjBpbiUyMChMRUFSTkpJUkEpJTIwT1JERVIlMjBCWSUyMGNyZWF0ZWQlMjBERVNDIiBkYXRhLWNhcmQtYXBwZWFyYW5jZT0iYmxvY2siIGRhdGEtZGF0YXNvdXJjZT0ieyZxdW90O2lkJnF1b3Q7OiZxdW90O2Q4Yjc1MzAwLWRmZGEtNDUxOS1iNmNkLWU0OWFiYmQ1MDQwMSZxdW90OywmcXVvdDtwYXJhbWV0ZXJzJnF1b3Q7OnsmcXVvdDtjbG91ZElkJnF1b3Q7OiZxdW90Ozg1ZmUyZDQ5LWQ4NmYtNGFmYS1iZTUzLWU3ZWM0MDhhNGQ1ZSZxdW90OywmcXVvdDtqcWwmcXVvdDs6JnF1b3Q7cHJvamVjdCBpbiAoTEVBUk5KSVJBKSBPUkRFUiBCWSBjcmVhdGVkIERFU0MmcXVvdDt9LCZxdW90O3ZpZXdzJnF1b3Q7Olt7JnF1b3Q7dHlwZSZxdW90OzomcXVvdDt0YWJsZSZxdW90OywmcXVvdDtwcm9wZXJ0aWVzJnF1b3Q7OnsmcXVvdDtjb2x1bW5zJnF1b3Q7Olt7JnF1b3Q7a2V5JnF1b3Q7OiZxdW90O2lzc3VldHlwZSZxdW90O30seyZxdW90O2tleSZxdW90OzomcXVvdDtrZXkmcXVvdDt9LHsmcXVvdDtrZXkmcXVvdDs6JnF1b3Q7c3VtbWFyeSZxdW90O30seyZxdW90O2tleSZxdW90OzomcXVvdDthc3NpZ25lZSZxdW90O30seyZxdW90O2tleSZxdW90OzomcXVvdDtwcmlvcml0eSZxdW90O30seyZxdW90O2tleSZxdW90OzomcXVvdDtzdGF0dXMmcXVvdDt9LHsmcXVvdDtrZXkmcXVvdDs6JnF1b3Q7dXBkYXRlZCZxdW90O31dfX1dfSI+aHR0cHM6Ly9rbnBrdi10ZWFtLmF0bGFzc2lhbi5uZXQvaXNzdWVzLz9qcWw9cHJvamVjdCUyMGluJTIwKExFQVJOSklSQSklMjBPUkRFUiUyMEJZJTIwY3JlYXRlZCUyMERFU0M8L2E+PC9wPgo8cD48YSBocmVmPSJodHRwczovL2tucGt2LXRlYW0uYXRsYXNzaWFuLm5ldC93aWtpL3NlYXJjaD90ZXh0PUdldHRpbmcrc3RhcnRlZCIgZGF0YS1jYXJkLWFwcGVhcmFuY2U9ImJsb2NrIiBkYXRhLWRhdGFzb3VyY2U9InsmcXVvdDtpZCZxdW90OzomcXVvdDs3NjhmYzczNi0zYWY0LTRhOGYtYjI3ZS0yMDM2MDJiZmY4Y2EmcXVvdDssJnF1b3Q7cGFyYW1ldGVycyZxdW90Ozp7JnF1b3Q7Y2xvdWRJZCZxdW90OzomcXVvdDs4NWZlMmQ0OS1kODZmLTRhZmEtYmU1My1lN2VjNDA4YTRkNWUmcXVvdDssJnF1b3Q7c2VhcmNoU3RyaW5nJnF1b3Q7OiZxdW90O0dldHRpbmcgc3RhcnRlZCZxdW90O30sJnF1b3Q7dmlld3MmcXVvdDs6W3smcXVvdDt0eXBlJnF1b3Q7OiZxdW90O3RhYmxlJnF1b3Q7LCZxdW90O3Byb3BlcnRpZXMmcXVvdDs6eyZxdW90O2NvbHVtbnMmcXVvdDs6W3smcXVvdDtrZXkmcXVvdDs6JnF1b3Q7dHlwZSZxdW90O30seyZxdW90O2tleSZxdW90OzomcXVvdDt0aXRsZSZxdW90O30seyZxdW90O2tleSZxdW90OzomcXVvdDtzcGFjZSZxdW90OywmcXVvdDt3aWR0aCZxdW90OzoyNDN9LHsmcXVvdDtrZXkmcXVvdDs6JnF1b3Q7ZGVzY3JpcHRpb24mcXVvdDt9LHsmcXVvdDtrZXkmcXVvdDs6JnF1b3Q7b3duZWRCeSZxdW90O30seyZxdW90O2tleSZxdW90OzomcXVvdDt1cGRhdGVkQXQmcXVvdDt9XX19XX0iPmh0dHBzOi8va25wa3YtdGVhbS5hdGxhc3NpYW4ubmV0L3dpa2kvc2VhcmNoP3RleHQ9R2V0dGluZytzdGFydGVkPC9hPjwvcD4KPGgzPlBhbmVsczwvaDM+CjxhYzpzdHJ1Y3R1cmVkLW1hY3JvIGFjOm5hbWU9ImluZm8iIGFjOnNjaGVtYS12ZXJzaW9uPSIxIiBhYzptYWNyby1pZD0iNWRjY2M3ZjEtZWRlMi00M2U2LTkyNmMtOWMzNmZjNDE0NGNlIj48YWM6cmljaC10ZXh0LWJvZHk+PHA+SW5mbyBwYW5lbDwvcD48L2FjOnJpY2gtdGV4dC1ib2R5PjwvYWM6c3RydWN0dXJlZC1tYWNybz4KPGFjOnN0cnVjdHVyZWQtbWFjcm8gYWM6bmFtZT0ibm90ZSIgYWM6c2NoZW1hLXZlcnNpb249IjEiIGFjOm1hY3JvLWlkPSI1ODg0ZGE5Yi1kNzM4LTQ1YjMtOTgyMS1lOGRjODMyZTY2MDEiPjxhYzpyaWNoLXRleHQtYm9keT48cD5Ob3RlIHBhbmVsPC9wPjwvYWM6cmljaC10ZXh0LWJvZHk+PC9hYzpzdHJ1Y3R1cmVkLW1hY3JvPgo8YWM6c3RydWN0dXJlZC1tYWNybyBhYzpuYW1lPSJ3YXJuaW5nIiBhYzpzY2hlbWEtdmVyc2lvbj0iMSIgYWM6bWFjcm8taWQ9ImMxYzI4MzI5LTIyOGMtNDJiNy04NDU2LWYyNTMwZjU2ZjJjMCI+PGFjOnJpY2gtdGV4dC1ib2R5PjxwPkVycm9yIHBhbmVsPC9wPjwvYWM6cmljaC10ZXh0LWJvZHk+PC9hYzpzdHJ1Y3R1cmVkLW1hY3JvPgo8YWM6c3RydWN0dXJlZC1tYWNybyBhYzpuYW1lPSJwYW5lbCIgYWM6c2NoZW1hLXZlcnNpb249IjEiIGFjOm1hY3JvLWlkPSJmZTQ4OWY2Ny1mZTY5LTRhNTItYmQyNi05Y2MyM2VlYTgxNWMiPjxhYzpyaWNoLXRleHQtYm9keT48cD5DdXN0b20gcGFuZWw8L3A+PC9hYzpyaWNoLXRleHQtYm9keT48L2FjOnN0cnVjdHVyZWQtbWFjcm8+CjxhYzpzdHJ1Y3R1cmVkLW1hY3JvIGFjOm5hbWU9InRpcCIgYWM6c2NoZW1hLXZlcnNpb249IjEiIGFjOm1hY3JvLWlkPSI3N2FkN2RjZS1kNDg5LTQyYTEtOTk1ZC0wYTFkYjNhOWVmODMiPjxhYzpyaWNoLXRleHQtYm9keT48cD5TdWNjZXNzIHBhbmVsPC9wPjwvYWM6cmljaC10ZXh0LWJvZHk+PC9hYzpzdHJ1Y3R1cmVkLW1hY3JvPgo8YWM6c3RydWN0dXJlZC1tYWNybyBhYzpuYW1lPSJub3RlIiBhYzpzY2hlbWEtdmVyc2lvbj0iMSIgYWM6bWFjcm8taWQ9ImU1YzYzNTJlLWNmNmUtNDdjYi1hNDIzLWE5MTI4MzhkY2VjNCI+PGFjOnJpY2gtdGV4dC1ib2R5PjxwPldhcm5pbmcgcGFuZWw8L3A+PC9hYzpyaWNoLXRleHQtYm9keT48L2FjOnN0cnVjdHVyZWQtbWFjcm8+CjxoMz5PdGhlcjwvaDM+CjxwPkhlbGxvIDM8L3A+PC9hYzpsYXlvdXQtY2VsbD48L2FjOmxheW91dC1zZWN0aW9uPjwvYWM6bGF5b3V0Pg==-->
@@ -1,283 +0,0 @@
1
- import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"
2
- import * as NodePath from "@effect/platform-node/NodePath"
3
- import * as FileSystem from "@effect/platform/FileSystem"
4
- import * as Path from "@effect/platform/Path"
5
- import { describe, expect, it } from "@effect/vitest"
6
- import * as Effect from "effect/Effect"
7
- import * as Layer from "effect/Layer"
8
- import { parseConfluenceHtml } from "../../src/parsers/ConfluenceParser.js"
9
- import { parseMarkdown } from "../../src/parsers/MarkdownParser.js"
10
- import { serializeToConfluence } from "../../src/serializers/ConfluenceSerializer.js"
11
- import { serializeToMarkdown } from "../../src/serializers/MarkdownSerializer.js"
12
-
13
- const PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)
14
-
15
- const getFixturesDir = Effect.gen(function*() {
16
- const path = yield* Path.Path
17
- return path.join(import.meta.dirname, "../fixtures")
18
- })
19
-
20
- const readFixture = (filename: string) =>
21
- Effect.gen(function*() {
22
- const fs = yield* FileSystem.FileSystem
23
- const path = yield* Path.Path
24
- const fixturesDir = yield* getFixturesDir
25
- return yield* fs.readFileString(path.join(fixturesDir, filename))
26
- }).pipe(Effect.provide(PlatformLive))
27
-
28
- describe("ConfluenceParser", () => {
29
- describe("parseConfluenceHtml", () => {
30
- it.effect("parses basic headings", () =>
31
- Effect.gen(function*() {
32
- const html = "<h1>Title</h1><h2>Subtitle</h2>"
33
- const doc = yield* parseConfluenceHtml(html)
34
- expect(doc.children.length).toBe(2)
35
- expect(doc.children[0]?._tag).toBe("Heading")
36
- }))
37
-
38
- it.effect("parses paragraphs", () =>
39
- Effect.gen(function*() {
40
- const html = "<p>Hello world</p>"
41
- const doc = yield* parseConfluenceHtml(html)
42
- expect(doc.children.length).toBe(1)
43
- expect(doc.children[0]?._tag).toBe("Paragraph")
44
- }))
45
-
46
- it.effect("parses task lists", () =>
47
- Effect.gen(function*() {
48
- const html = `<ac:task-list ac:task-list-id="test">
49
- <ac:task>
50
- <ac:task-id>1</ac:task-id>
51
- <ac:task-uuid>uuid-1</ac:task-uuid>
52
- <ac:task-status>incomplete</ac:task-status>
53
- <ac:task-body><span>Task 1</span></ac:task-body>
54
- </ac:task>
55
- <ac:task>
56
- <ac:task-id>2</ac:task-id>
57
- <ac:task-uuid>uuid-2</ac:task-uuid>
58
- <ac:task-status>complete</ac:task-status>
59
- <ac:task-body><span>Task 2</span></ac:task-body>
60
- </ac:task>
61
- </ac:task-list>`
62
- const doc = yield* parseConfluenceHtml(html)
63
- expect(doc.children.length).toBe(1)
64
- const taskList = doc.children[0]
65
- expect(taskList?._tag).toBe("TaskList")
66
- if (taskList?._tag === "TaskList") {
67
- expect(taskList.children.length).toBe(2)
68
- expect(taskList.children[0]?.status).toBe("incomplete")
69
- expect(taskList.children[0]?.body[0]?._tag).toBe("Text")
70
- expect(taskList.children[1]?.status).toBe("complete")
71
- }
72
- }))
73
-
74
- it.effect("parses images with attachments", () =>
75
- Effect.gen(function*() {
76
- const html = `<ac:image ac:align="center" ac:width="250" ac:alt="logo">
77
- <ri:attachment ri:filename="logo.svg"/>
78
- </ac:image>`
79
- const doc = yield* parseConfluenceHtml(html)
80
- expect(doc.children.length).toBe(1)
81
- const image = doc.children[0]
82
- expect(image?._tag).toBe("Image")
83
- if (image?._tag === "Image") {
84
- expect(image.attachment?.filename).toBe("logo.svg")
85
- expect(image.align).toBe("center")
86
- expect(image.width).toBe(250)
87
- expect(image.alt).toBe("logo")
88
- }
89
- }))
90
-
91
- it.effect("parses emoticons", () =>
92
- Effect.gen(function*() {
93
- const html =
94
- `<p><ac:emoticon ac:emoji-shortname=":grinning:" ac:emoji-id="1f600" ac:emoji-fallback="smile"/></p>`
95
- const doc = yield* parseConfluenceHtml(html)
96
- expect(doc.children.length).toBe(1)
97
- const para = doc.children[0]
98
- expect(para?._tag).toBe("Paragraph")
99
- if (para?._tag === "Paragraph") {
100
- const emoticon = para.children[0]
101
- expect(emoticon?._tag).toBe("Emoticon")
102
- if (emoticon?._tag === "Emoticon") {
103
- expect(emoticon.shortname).toBe(":grinning:")
104
- expect(emoticon.emojiId).toBe("1f600")
105
- expect(emoticon.fallback).toBe("smile")
106
- }
107
- }
108
- }))
109
-
110
- it.effect("parses user mentions", () =>
111
- Effect.gen(function*() {
112
- const html = `<p><ac:link><ri:user ri:account-id="557058:abc123"/></ac:link></p>`
113
- const doc = yield* parseConfluenceHtml(html)
114
- expect(doc.children.length).toBe(1)
115
- const para = doc.children[0]
116
- if (para?._tag === "Paragraph") {
117
- const mention = para.children[0]
118
- expect(mention?._tag).toBe("UserMention")
119
- if (mention?._tag === "UserMention") {
120
- expect(mention.accountId).toBe("557058:abc123")
121
- }
122
- }
123
- }))
124
-
125
- it.effect("parses colored text", () =>
126
- Effect.gen(function*() {
127
- const html = `<p><span style="color: rgb(255,0,0);">Red text</span></p>`
128
- const doc = yield* parseConfluenceHtml(html)
129
- const para = doc.children[0]
130
- if (para?._tag === "Paragraph") {
131
- const colored = para.children[0]
132
- expect(colored?._tag).toBe("ColoredText")
133
- if (colored?._tag === "ColoredText") {
134
- expect(colored.color).toBe("rgb(255,0,0)")
135
- }
136
- }
137
- }))
138
-
139
- it.effect("parses highlighted text", () =>
140
- Effect.gen(function*() {
141
- const html = `<p><span style="background-color: rgb(255,255,0);">Highlighted</span></p>`
142
- const doc = yield* parseConfluenceHtml(html)
143
- const para = doc.children[0]
144
- if (para?._tag === "Paragraph") {
145
- const highlight = para.children[0]
146
- expect(highlight?._tag).toBe("Highlight")
147
- if (highlight?._tag === "Highlight") {
148
- expect(highlight.backgroundColor).toBe("rgb(255,255,0)")
149
- }
150
- }
151
- }))
152
-
153
- it.effect("parses paragraph with alignment", () =>
154
- Effect.gen(function*() {
155
- const html = `<p style="text-align: center;">Centered</p>`
156
- const doc = yield* parseConfluenceHtml(html)
157
- const para = doc.children[0]
158
- expect(para?._tag).toBe("Paragraph")
159
- if (para?._tag === "Paragraph") {
160
- expect(para.alignment).toBe("center")
161
- }
162
- }))
163
-
164
- it.effect("parses paragraph with indent", () =>
165
- Effect.gen(function*() {
166
- const html = `<p style="margin-left: 30px;">Indented</p>`
167
- const doc = yield* parseConfluenceHtml(html)
168
- const para = doc.children[0]
169
- expect(para?._tag === "Paragraph").toBe(true)
170
- if (para?._tag === "Paragraph") {
171
- expect(para.indent).toBe(30)
172
- }
173
- }))
174
-
175
- it.effect("parses underline, subscript, superscript", () =>
176
- Effect.gen(function*() {
177
- const html = `<p><u>Underline</u> <sub>Sub</sub> <sup>Sup</sup></p>`
178
- const doc = yield* parseConfluenceHtml(html)
179
- const para = doc.children[0]
180
- if (para?._tag === "Paragraph") {
181
- const [u, , sub, , sup] = para.children
182
- expect(u?._tag).toBe("Underline")
183
- expect(sub?._tag).toBe("Subscript")
184
- expect(sup?._tag).toBe("Superscript")
185
- }
186
- }))
187
-
188
- it.effect("parses tables with proper cell content", () =>
189
- Effect.gen(function*() {
190
- const html = `<table>
191
- <thead><tr><th><p>Header 1</p></th><th><p>Header 2</p></th></tr></thead>
192
- <tbody><tr><td><p>Cell 1</p></td><td><p>Cell 2</p></td></tr></tbody>
193
- </table>`
194
- const doc = yield* parseConfluenceHtml(html)
195
- const table = doc.children[0]
196
- expect(table?._tag).toBe("Table")
197
- if (table?._tag === "Table") {
198
- // Header should have 2 cells
199
- expect(table.header?.cells.length).toBe(2)
200
- // First header cell should have text "Header 1"
201
- const headerCell = table.header?.cells[0]
202
- if (headerCell) {
203
- expect(headerCell.children[0]?._tag).toBe("Text")
204
- if (headerCell.children[0]?._tag === "Text") {
205
- expect(headerCell.children[0].value).toBe("Header 1")
206
- }
207
- }
208
- // Body rows
209
- expect(table.rows.length).toBe(1)
210
- const bodyCell = table.rows[0]?.cells[0]
211
- if (bodyCell) {
212
- expect(bodyCell.children[0]?._tag).toBe("Text")
213
- if (bodyCell.children[0]?._tag === "Text") {
214
- expect(bodyCell.children[0].value).toBe("Cell 1")
215
- }
216
- }
217
- }
218
- }))
219
- })
220
-
221
- describe("integration test fixture", () => {
222
- it.effect("parses and serializes integration test fixture", () =>
223
- Effect.gen(function*() {
224
- const html = yield* readFixture("integration-test.html.fixture")
225
- const doc = yield* parseConfluenceHtml(html)
226
-
227
- // Doc should have children - layout markers plus actual content
228
- expect(doc.children.length).toBeGreaterThan(0)
229
-
230
- // Serialize to markdown - content should be readable (not URL-encoded)
231
- const markdown = yield* serializeToMarkdown(doc)
232
- // Layout markers should be present as comments
233
- expect(markdown).toContain("cf:layout-start")
234
- expect(markdown).toContain("cf:section:")
235
- // Content should be readable markdown, not URL-encoded
236
- expect(markdown).toContain("# Heading 1")
237
- expect(markdown).toContain("## Heading 2")
238
-
239
- // Serialize to confluence - should reconstruct layout structure
240
- const confluenceHtml = yield* serializeToConfluence(doc)
241
- expect(confluenceHtml).toContain("<ac:layout>")
242
- expect(confluenceHtml).toContain("<ac:layout-section")
243
- expect(confluenceHtml).toContain("<ac:layout-cell>")
244
- // Note: ac:task-list may have attributes, so search for the tag name
245
- expect(confluenceHtml).toContain("ac:task-list")
246
- expect(confluenceHtml).toContain("<ac:task-status>incomplete</ac:task-status>")
247
- expect(confluenceHtml).toContain("<ac:task-status>complete</ac:task-status>")
248
- expect(confluenceHtml).toContain("ac:emoticon")
249
- expect(confluenceHtml).toContain("ri:account-id")
250
- }))
251
- })
252
-
253
- describe("roundtrip HTML -> MD -> HTML", () => {
254
- it.effect("roundtrips integration-test.html with 1-to-1 preservation", () =>
255
- Effect.gen(function*() {
256
- const originalHtml = yield* readFixture("integration-test.html.fixture")
257
- const doc1 = yield* parseConfluenceHtml(originalHtml)
258
- const md = yield* serializeToMarkdown(doc1)
259
- const doc2 = yield* parseMarkdown(md)
260
- const finalHtml = yield* serializeToConfluence(doc2)
261
-
262
- // 1-to-1 roundtrip: finalHtml should EXACTLY match originalHtml
263
- expect(finalHtml).toBe(originalHtml)
264
- }))
265
-
266
- it.effect("roundtrips TOC macro", () =>
267
- Effect.gen(function*() {
268
- const html =
269
- `<ac:structured-macro ac:name="toc"><ac:parameter ac:name="minLevel">2</ac:parameter></ac:structured-macro>`
270
- const doc1 = yield* parseConfluenceHtml(html)
271
- expect(doc1.children[0]?._tag).toBe("TocMacro")
272
-
273
- const md = yield* serializeToMarkdown(doc1)
274
- expect(md).toContain("[[toc]]")
275
-
276
- const doc2 = yield* parseMarkdown(md)
277
- expect(doc2.children[0]?._tag).toBe("TocMacro")
278
-
279
- const html2 = yield* serializeToConfluence(doc2)
280
- expect(html2).toContain("<ac:structured-macro ac:name=\"toc\">")
281
- }))
282
- })
283
- })