Compare commits

..

201 Commits

Author SHA1 Message Date
60294a40b9 Sanitizing YAML dictionaries. 2024-12-15 12:49:12 -05:00
6825d9f242 Removing unnecessary method. 2024-12-14 17:10:23 -05:00
738697bc63 Using other YAML prepackaged parsers. 2024-12-14 17:07:55 -05:00
d822926612 Moving functionality to improve modularity. 2024-12-13 19:06:23 -05:00
af064db451 If YAML metadata is absent, then is not shown in the text view. 2024-11-21 17:54:29 -05:00
f85ed1803b Pushing up methods. 2024-11-21 17:23:45 -05:00
92760a54a9 Minor renaming and tagging while splitting the document. 2024-11-17 12:19:39 -05:00
e73cbbb72a Improving Markdown with metadata file exportation. 2024-11-16 18:03:04 -05:00
b89b3755f7 URL in metadata needs to be commented to please Markdeep's parser. 2024-11-12 18:52:08 -05:00
d2959856b9 Preserving HedgeDoc importation metadata when converted to Markdeep. 2024-11-12 18:03:32 -05:00
ca3550a3f4 Adding default edit page time. 2024-11-04 14:26:13 -05:00
d470dd533c Fixing Markdown importation for simple cases. 2024-11-04 06:33:55 -05:00
babbd48934 Improving splitting of admonition snippets. 2024-11-04 03:28:49 -05:00
5ccff44fc9 Improving splitting of admonition strings. 2024-11-03 22:38:31 -05:00
33f3a9a0c2 Markdown crude semantic splits when converting to LePage. 2024-11-03 15:06:58 -05:00
96903d8627 Implementing Markdown crude semantic splits. 2024-11-03 14:56:57 -05:00
4beca346be Renaming and modularizing. 2024-11-03 08:59:48 -05:00
37d6000702 Implementing page "semantic splitters". 2024-11-02 18:12:46 -05:00
0ce6b89b6c Implementing page "semantic splitters". 2024-11-02 17:38:12 -05:00
629a9e32ca Alphanumeric counter facility. 2024-10-31 11:25:06 -05:00
e26334425f Merge bb7bab403e 2024-10-22 13:43:02 -05:00
58ec9eed76 Fixing metadata retention between HedgeDoc and Lepiter. 2024-10-22 13:35:24 -05:00
bb7bab403e File metadata extraction. 2024-10-14 13:01:27 -05:00
cea7ce5e6c Merge pull request 'Reclasifying.' (!6) from unsync into master
Reviewed-on: #6
2024-10-14 15:35:47 +00:00
65fb92964f Reclasifying. 2024-10-14 10:34:35 -05:00
cccf81e89b Merge branch 'master' of https://code.sustrato.red/Offray/MiniDocs 2024-09-25 17:40:32 -05:00
fa6303b762 Creating short titles, when titles are too long. 2024-09-25 17:17:34 -05:00
02403b5ae3 Migrating Pandoc conversors. 2024-09-24 13:27:51 -05:00
e4d0880fea Migrating Pandoc conversors. 2024-09-24 13:26:26 -05:00
ee80105f44 Migrating Pandoc conversors. 2024-09-24 13:24:16 -05:00
ecb4321551 Debugging YAML metadata detection. 2024-09-24 13:17:59 -05:00
56ef3869ca Improving setters. 2024-09-24 12:23:39 -05:00
8fe49106bb Debugging page importation. 2024-08-21 12:10:03 -05:00
ff9fbb92f9 Debugging page importation. 2024-08-21 11:54:25 -05:00
a6e49448fa Debugging page importation via snippet. 2024-08-21 11:26:24 -05:00
45c4762201 Debugging importation. 2024-08-21 11:19:03 -05:00
8634047be9 Debuggin picture importation code. 2024-08-21 11:12:28 -05:00
f3929ceece Improving importation code. 2024-08-21 11:02:21 -05:00
b4d6940ec6 Fixing importing LePicture bug caused by new announcers behavior. 2024-08-21 09:06:39 -05:00
7bbf399ae1 Improving page importation code. 2024-08-21 08:33:34 -05:00
fb64d5c1ae Improving management of empty YAML metadata. 2024-08-02 12:35:21 -05:00
554fb9000e Starting interface with Taskwarrior. It may become its own repository later. 2024-07-28 10:57:59 -05:00
47eaafaf5c Fixing baseline. 2024-07-23 01:18:10 +00:00
78900cc64d Solving sync conflicts. 2024-07-22 19:34:46 -05:00
78f1b1474f Improving exportation. 2024-07-22 19:24:56 -05:00
94fdf3052f Modularizing and cleaning to improve upcoming support for tagged snippets. 2024-07-18 17:08:46 -05:00
6072fd8971 Retaking taging as a way to improve document export/import. 2024-07-18 16:26:32 -05:00
6e58c5631d Fixing Lepiter Building Blocs. 2024-07-17 14:45:22 -05:00
8cadf004dd Fixing typo in baseline. testing for Lepiter building blocs dependency. 2024-07-17 14:40:17 -05:00
2bc0b44fa2 Modifying baseline for testing with Lepiter Building Blocs dependency. 2024-07-17 14:37:15 -05:00
882f33859c Web preview for pages not located in the default Lepiter folder. 2024-06-20 12:15:31 -05:00
968bfff3bb Ignoring importation errors, temporarily. 2024-06-20 11:10:56 -05:00
f67a24c94c Ignoring importation errors, temporarily. 2024-06-20 10:40:37 -05:00
3c01731fdc Debuging new importation errors derived, probably, from coder updating strategies. 2024-06-20 10:32:17 -05:00
1b66316d03 More modular code. Pending! Redundancies should be revised. 2024-06-20 10:12:05 -05:00
62a5c398d1 Adding characters to accented correction. 2024-06-19 18:33:07 -05:00
23335a7727 Debugging metadata export/import. 2024-06-11 16:35:29 -05:00
3202717cea Debugging metadata exportation. 2024-06-11 16:12:05 -05:00
27a28ce543 Debugging metadata exportation. 2024-06-11 16:09:11 -05:00
47aabb1d4f Enriching object metadata during exportation. 2024-06-11 15:43:23 -05:00
eeaf28127b Adding Markdeep export to GraphiQL snippets. 2024-05-25 20:56:50 -05:00
141942ce3e Merge c9b214f633 2024-05-21 11:37:09 -05:00
c9b214f633 Creating extension for ZnConstants. 2024-05-21 11:37:31 -05:00
59b8387728 Extending exportation to other formats. 2024-05-21 11:36:46 -05:00
6433da508a Unique ids for booleans in values and improved visualization. 2024-05-11 12:19:34 -05:00
5cf1d7bcc6 Unique ids for nils and booleans in arrays. 2024-05-11 11:51:20 -05:00
28a3d22911 Utilities for visualizing dictionaries. 2024-05-11 11:19:25 -05:00
da459b9d26 Debugging LogSeq pages. 2024-05-03 17:20:52 -05:00
31e27acdff Implementing FileLocator aliases, so data narratives can use them instead of fixed locations. 2024-05-02 16:00:57 -05:00
Offray
f592e29eef Moving NanoID to ExoRepo. 2024-04-30 16:51:32 -05:00
Offray
de61e736fa Moving YQ to ExoRepo and adding Windows Scoop path. 2024-04-30 16:24:27 -05:00
a22005da27 Starting multiOS support for binary files. 2024-04-29 17:28:20 -05:00
5e4db00352 Fixing email String importation/preview. 2024-04-28 14:43:07 -05:00
ef982eb6a1 Nim's package manager, Nimble, moved from MiniDocs to ExoRepo. 2024-04-27 03:59:21 -05:00
089eb8c2eb Fixing image link importation for descriptions with links inside. 2024-04-26 08:48:53 -05:00
5ec6ea6377 Arreglando manualmente problema en el baseline. 2024-04-25 12:00:19 -05:00
93fc8ffe1c Resyncing after detachemed. 2024-04-25 08:49:23 -05:00
7dc0bdaac0 Debugging image links with links in the description. 2024-04-22 14:59:56 -05:00
0c45ccb39e Debugging image links with links in the description. 2024-04-22 14:01:59 -05:00
573c929845 Improving LeSnippet to LePage conversion. 2024-04-16 14:18:41 -05:00
13f9b8050e Fixing repeated medatada when exporting to Markdeep. 2024-04-13 11:49:04 -05:00
53910fa840 Recovering metadata from children for updating notebook metadata. 2024-04-13 10:00:12 -05:00
bf0ea4b46d Recovering headers. 2024-04-12 20:27:21 -05:00
977922d7a3 Enabling importation of Markdeep pages without page metadata. 2024-04-11 13:06:21 -05:00
51e84c2404 Markdown can be exported with or without metadata wrappers. 2024-04-09 16:03:47 -05:00
8fb6373a83 Refactoring, so functionality can be called without the GUI. 2024-04-08 12:46:42 -05:00
9ae7a6ec62 Adding Nim and NanoID support to Windows platform. 2024-04-04 17:35:25 -05:00
7979007091 Merge a264070d5c 2024-04-03 17:27:09 -05:00
a264070d5c Merge 3a2d096025 2024-04-03 17:10:18 -05:00
642712cdfd Adding yq support to Windows platform. 2024-04-03 17:10:01 -05:00
34d77ecedd Default Markdown and webView for DataFrames. 2024-04-03 10:13:19 -05:00
3a2d096025 Debugging baseline, comment on LepiterBuildingBlocs. 2024-04-02 23:51:36 +00:00
7cd3f30216 Debugging baseline, fixing typo. 2024-04-02 23:42:34 +00:00
5c897886e0 Debugging baseline, error handler for LepiterBuildingBlocs. 2024-04-02 23:32:41 +00:00
2037af37a3 Debugging baseline, adding dataframe. 2024-04-02 23:21:15 +00:00
82405165e1 Debugging baseline. 2024-04-02 23:11:47 +00:00
b8262d00ca Debugging baseline. 2024-04-02 22:36:49 +00:00
6832d9024d Default Markdown and webView for DataFrames. 2024-04-01 13:17:59 -05:00
da4c9bf9c4 Recovering from unexplainable detached repository head. 2024-03-30 10:17:53 -05:00
2ca24978bc Arreglando manualmente problema en el baseline. 2024-03-30 10:11:42 -05:00
bba3daebfa Quitando archivo en conflicto antes del merge. 2024-03-30 10:07:07 -05:00
f1f9a7b30c Fixing baseline. 2024-03-30 10:04:13 -05:00
02bc11d6a7 Restoring baseline. 2024-03-30 09:54:18 -05:00
48c1707fbe MiniDocs unsync without reason. 2024-03-30 09:47:06 -05:00
0c5ede8498 DataFrame as a dependency. Improved dependencies reading, as they are getting longer. 2024-03-29 10:38:27 -05:00
87fba41704 Merge b2a4dc1839 2024-03-29 10:33:11 -05:00
0305a68aca DataFrame as a dependency. 2024-03-29 10:28:57 -05:00
b2a4dc1839 Debugging baseline. 2024-03-29 00:01:24 -05:00
e1c4f4fb52 Merge f0bca02f73 2024-03-28 22:21:47 -05:00
428936c1a7 Improving DataFrame views. DataFrame needs to be added in the dependencies. 2024-03-28 22:19:06 -05:00
f0bca02f73 Fixing Baseline. 2024-03-28 17:08:04 -05:00
536d2a9326 HedgeDoc convert/import as LePage promotes Markdown headers. 2024-03-26 21:54:46 -05:00
60939fe2b8 Improving HedgeDoc(s) convert/import as LePage(s). 2024-03-26 19:50:40 -05:00
72f3a633ae Improving HedgeDoc(s) convert/import as LePage(s). 2024-03-26 14:52:18 -05:00
4399531c8d Debugging MiniDocs loading. LepiterBuildingBlocs seems the culprit, instead of PetitParser islands. Recovering their loading. 2024-03-26 14:20:30 -05:00
aec4b58e23 Improving HedgeDoc page importation. 2024-03-26 12:53:55 -05:00
e7f2910a51 Better default for authors. authorsString needs to be refactored. 2024-03-26 07:08:44 -05:00
b4f2564f67 Debugging title underline. 2024-03-26 06:55:48 -05:00
cd47d22480 Cleaning Markdeep metadata exportation. 2024-03-25 18:42:24 -05:00
50a8595aa7 Title is not part of metadata anymore, only for Markdeep export. 2024-03-25 18:34:29 -05:00
5aeac92772 Title is not part of metadata anymore (it should be only for Markdeep export). 2024-03-25 18:29:19 -05:00
27a20e83b0 Improving HedgeDoc objects reading from web links. 2024-03-25 16:23:14 -05:00
80afb07c77 Improving Markdown objects reading from files. 2024-03-25 11:44:16 -05:00
0185c741a1 Moving basic YAML metadata capabilities to the String class. 2024-03-25 11:16:53 -05:00
b7fdbb731c Merge a51583e5fc 2024-03-23 09:36:32 -05:00
494bfc76a7 Regression for Tiddlers exportation in friendly format corrected. 2024-03-23 09:36:14 -05:00
a51583e5fc Promoting document title. 2024-03-22 15:42:10 -05:00
e22eb1221d Improving Markdown importation. 2024-03-22 15:17:39 -05:00
655ee2e935 Merge f2b41dd546 2024-03-20 17:41:37 -05:00
b518ec0c7c Creating Lepiter UI for hedgedoc url import and improving hedgedoc url test. 2024-03-20 15:31:13 -05:00
f2b41dd546 Associating PubPubContents to their respective files. 2024-03-20 11:03:46 -05:00
6c4fc47900 Adding Teapot and Temple dependencies. 2024-03-19 19:38:49 -05:00
172e72c1f6 Logseq started. 2024-03-19 19:32:03 -05:00
31bdc6dbd2 Adding Temple and full PetitParser 2024-03-19 12:09:28 -05:00
1a380405a3 Improving table of contents management. 2024-03-19 07:27:38 -05:00
d876485db9 Renaming and improved YAML metadata reading. 2024-03-16 12:19:31 -05:00
ac8fa682e3 Markdeep importer is not called on Lepiter exported files. 2024-03-15 13:39:40 -05:00
9744a8c6b0 Markdown YAML metadata and default options. 2024-03-10 19:29:19 -05:00
4c661e0b6d Improving Markdown exportation from Markdeep files. 2024-03-10 15:57:05 -05:00
05269ac364 Optimistic reading of a Markdeep file as a live object done. 2024-03-10 11:21:53 -05:00
8143633b42 Merge c162b9b4bc 2024-03-10 10:23:14 -05:00
ac41b1721f Finishing optimistic extraction of Markdeep file contents as a live object. 2024-03-10 10:23:00 -05:00
c162b9b4bc Improving populating metadata from a given Markdeep file. 2024-03-09 12:05:25 -05:00
4199ed0a6c Improving Markdeep files reading and Markdown document tree views. 2024-03-04 11:15:44 -05:00
004b286835 Starting importation of Markdeep objects from files. 2024-03-03 18:48:00 -05:00
06ccc84c29 Improving document tree views. 2024-02-08 15:59:07 -05:00
2a2a4bb5f1 Snippets Markdeep importers debugged. 2024-02-08 08:30:17 -05:00
fe57ba9497 Debugging of STON pretty print started. 2024-02-04 19:24:08 -05:00
3699111416 Moving functionality from GrafoscopioUtils. 2024-01-03 14:35:46 -05:00
14160ee4ca Defining defaultColor for annotations. 2024-01-03 08:01:43 -05:00
b09f2310ef Adding configuration files to pages for granular control of exportation. 2024-01-02 13:17:06 -05:00
979facfd86 Adding configuration files to pages for granular control of exportation. 2024-01-02 10:41:24 -05:00
8983f25b3a Default location for exported file when convering to Markdeep. 2024-01-02 10:26:54 -05:00
8222d8c7d2 Improving exportation and preparing config.ston file usage. 2024-01-02 09:53:14 -05:00
9fc938407a Default blue color for all custom annotations, before testing customized colors. 2024-01-01 21:00:16 -05:00
349c314318 Starting support for custom annotations. 2024-01-01 09:06:40 -05:00
efd11cc44c Merge ef855a9ae5 2023-12-30 20:46:25 -05:00
41779dfdde Fixing image metadata. 2023-12-30 20:45:30 -05:00
ef855a9ae5 Improved Markdown exportation for better Pandoc conversions between formats. 2023-12-27 19:55:26 -05:00
486773af63 Improving metadata on images. 2023-12-24 08:08:00 -05:00
cc99feebac Moving Pandoc class from GrafoscopioUtils. 2023-12-10 12:44:45 -05:00
8b424f4f03 Cleaning mixed line jumps. 2023-12-09 17:46:51 -05:00
1d9cb57477 Fixing image metadata exportation. 2023-11-14 11:22:04 -05:00
ebc093c6e8 Fossil again as a dependency of MiniDocs. 2023-11-05 10:45:01 -05:00
0987d4520f Fixing metadata sanitization/exportation. 2023-11-03 10:25:46 -05:00
bb75994dd0 Adding remote locations to increase documents traceability. 2023-11-01 21:11:10 -05:00
95a457e31e New functionality for retaking Indie Web with Brea, now as Lepiter/MiniDocs notebooks. 2023-11-01 20:04:44 -05:00
1a2f5a3a4a Improving metadata sanitization. 2023-11-01 09:52:12 -05:00
1e97ae3489 Improving metadata sanitization. 2023-11-01 09:25:49 -05:00
f52e027edd Modularizing the code under proper responsable classes. 2023-10-31 16:02:18 -05:00
372c6a6a55 Document tree metadata now from current state of the snippet. 2023-10-31 15:48:51 -05:00
5c4f6ab55b LePictureSnippets from Markdeep: importing from plain content strings (not yet tested with multiline captions). 2023-10-31 14:28:05 -05:00
eda63bfc3a Implementing readability of LePictureSnippets from Markdeep. 2023-10-31 14:03:39 -05:00
056dad228a Improved Markdeep exporter for snippets metadata readability. 2023-10-31 13:13:31 -05:00
9163d0b145 Improved Markdeep exporter for LePictureSnippets. 2023-10-31 13:04:47 -05:00
55e786f01c For the moment we need the previous method while we fix Socialmentrica loading. 2023-10-28 19:49:22 -05:00
6143da2db9 Fixing errors' log view. 2023-10-28 19:13:02 -05:00
253a5df692 Minimal working example of nesting page contents importation. 2023-10-28 13:28:50 -05:00
55b304581c Nested pages content importation. 2023-10-26 12:42:32 -05:00
92a686de96 Copy of nested docs working thanks to botwhytho's LepiterBuiddingBlocs. 2023-10-25 06:50:15 -05:00
6a3c0332b3 Fixing historical traceability for migrated documents with nil values. 2023-10-20 18:37:51 -05:00
60cfd01f0d Preparing improvements on parenthood detection for the document tree. 2023-10-20 13:54:43 -05:00
9361094bf6 Finishing Grafoscopio notebooks importation with fix for original creation/modification nil data in nodes. 2023-10-20 13:23:28 -05:00
a84ead51e1 More modular code from Lepiter snippets conversion to/from ordered dictionaries. 2023-10-20 12:59:17 -05:00
01a68d562c Importating legacy Grafoscopio notebooks: dealing with nil creation/modification timestamps. 2023-10-20 09:22:06 -05:00
362659b584 Improving Lepiter pages importation fom legacy Grafoscopio notebooks. 2023-10-19 13:14:10 -05:00
8488531726 Prototyping copying pages. Snippets' order is preserved, but not (yet) hierarchy. 2023-10-12 17:01:26 -05:00
0a802799b2 Relocating default server port to address firewall prohibition in classrooms with Windows machines. 2023-10-09 06:43:51 -05:00
dff10a6705 Implementing minimal functionality for machine training. Should be moved to Polyglot once the repository reactivates. 2023-09-23 11:50:46 -05:00
fbe8f8d1c3 Fixing keyboard combinations to keep ergonomy. 2023-08-21 09:17:32 -05:00
90fa52653f Addressing emtpy captios for images. 2023-07-30 12:44:56 -05:00
d011f9c1c1 Redefining dependencies. 2023-07-26 10:08:53 -05:00
4f849d2e36 Improving Markdeep multiline images. 2023-07-09 22:26:09 -05:00
7a7dfa648d Improving title printing. 2023-07-03 22:33:24 -05:00
f5006572e8 Improving Markdown to Markdeep conversion. 2023-07-03 21:55:57 -05:00
e3f3a62078 Debugging Markdeep conversors. 2023-05-23 08:08:35 -05:00
e7411c2075 Markdeep front page generation. 2023-05-22 13:18:03 -05:00
4b6853d920 Extracting from populated table of contents. Debuging web preview. 2023-05-22 06:58:33 -05:00
356ee754bf Merge pull request 'gt-crashes/2' (!3) from gt-crashes/2 into master
Reviewed-on: #3
2023-05-22 01:59:35 +00:00
4944923069 Merge pull request 'New PubPubContent object.' (!2) from gt-crashes/2 into master
Reviewed-on: #2
2023-05-22 01:39:33 +00:00
146d762f5f Fixing STON wiki exportation. 2023-05-19 10:43:15 -05:00
58 changed files with 2653 additions and 629 deletions

View File

@ -11,20 +11,29 @@ BaselineOfMiniDocs >> baseline: spec [
for: #common for: #common
do: [ do: [
"Dependencies" "Dependencies"
self setUpTeapot: spec.
self setUpPetitParser: spec.
self setUpLepiterBuildingBlocs: spec. "working in v1.0.993"
spec spec
baseline: 'Mustache' with: [ spec repository: 'github://noha/mustache' ]; baseline: 'Mustache' with: [ spec repository: 'github://noha/mustache' ];
baseline: 'Temple' with: [ spec repository: 'github://astares/Pharo-Temple/src' ];
baseline: 'Tealight' with: [ spec repository: 'github://astares/Tealight:main/src' ]; baseline: 'Tealight' with: [ spec repository: 'github://astares/Tealight:main/src' ];
baseline: 'PetitParser' with: [ baseline: 'DataFrame' with: [ spec repository: 'github://PolyMathOrg/DataFrame/src' ].
spec
loads: #('Minimal' 'Core' 'Tests' 'Islands');
repository: 'github://moosetechnology/PetitParser:v3.x.x/src'
].
"self fossil: spec." "self fossil: spec."
self xmlParserHTML: spec.
"Packages" "Packages"
spec spec
package: 'PetitMarkdown' with: [ spec requires: #('PetitParser')]; package: 'PetitMarkdown' with: [ spec requires: #('PetitParser')];
package: 'MiniDocs' with: [ spec requires: #('Mustache' 'Tealight' 'PetitMarkdown')]. package: 'MiniDocs'
with: [ spec requires: #(
'Mustache' 'Temple' "Templating"
'Teapot' 'Tealight' "Web server"
'PetitMarkdown' 'PetitParser' "Parsers"
'DataFrame' "Tabular data"
'LepiterBuildingBlocs' "Lepiter utilities")].
.
"Groups" "Groups"
@ -37,7 +46,7 @@ BaselineOfMiniDocs >> baseline: spec [
BaselineOfMiniDocs >> fossil: spec [ BaselineOfMiniDocs >> fossil: spec [
| repo | | repo |
repo := ExoRepo new repo := ExoRepo new
repository: 'https://code.tupale.co/Offray/Fossil'. repository: 'https://code.sustrato.red/Offray/Fossil'.
repo load. repo load.
spec baseline: 'Fossil' with: [ spec repository: 'gitlocal://', repo local fullName ] spec baseline: 'Fossil' with: [ spec repository: 'gitlocal://', repo local fullName ]
] ]
@ -48,14 +57,40 @@ BaselineOfMiniDocs >> semanticVersion [
] ]
{ #category : #accessing } { #category : #accessing }
BaselineOfMiniDocs >> xmlParserHTML: spec [ BaselineOfMiniDocs >> setUpLepiterBuildingBlocs: spec [
Metacello new spec
baseline: 'XMLParserHTML'; baseline: 'LepiterBuildingBlocs'
repository: 'github://pharo-contributions/XML-XMLParserHTML/src'; with: [spec repository: 'github://botwhytho/LepiterBuildingBlocs:main/src']
onConflict: [ :ex | ex useLoaded ]; ]
onUpgrade: [ :ex | ex useLoaded ];
onDowngrade: [ :ex | ex useLoaded ]; { #category : #accessing }
onWarningLog; BaselineOfMiniDocs >> setUpPetitParser: spec [
load. spec
spec baseline: 'XMLParserHTML' with: [spec repository: 'github://pharo-contributions/XML-XMLParserHTML/src'] baseline: 'PetitParser'
with: [ spec
repository: 'github://moosetechnology/PetitParser:v3.x.x/src';
loads: #('Minimal' 'Core' 'Tests' 'Islands')];
import: 'PetitParser'
]
{ #category : #accessing }
BaselineOfMiniDocs >> setUpTeapot: spec [
spec
baseline: 'Teapot'
with: [ spec
repository: 'github://zeroflag/Teapot/source';
loads: #('ALL') ];
import: 'Teapot'
]
{ #category : #accessing }
BaselineOfMiniDocs >> xmlParserHTML: spec [
spec
baseline: 'XMLParserHTML'
with: [ spec
repository: 'github://ruidajo/XML-XMLParserHTML/src';
loads: #('ALL')];
import: 'XMLParserHTML'
] ]

View File

@ -0,0 +1,18 @@
"
I model a possible bridge between TaskWarrior and MiniDocs. (starting DRAFT).
"
Class {
#name : #AcroReport,
#superclass : #Object,
#category : #MiniDocs
}
{ #category : #accessing }
AcroReport class >> project: projectName [
| jsonReport |
jsonReport := (GtSubprocessWithInMemoryOutput new
shellCommand: 'task project:', projectName , ' export';
runAndWait;
stdout).
^ STONJSON fromString: jsonReport
]

View File

@ -0,0 +1,57 @@
Class {
#name : #AlphanumCounter,
#superclass : #Object,
#instVars : [
'letters',
'digits',
'currentLetter',
'currentDigit'
],
#category : #MiniDocs
}
{ #category : #accessing }
AlphanumCounter >> current [
^ self currentLetter asString, self currentDigit asString
]
{ #category : #accessing }
AlphanumCounter >> currentDigit [
^ currentDigit ifNil: [ currentDigit := self digits first ]
]
{ #category : #accessing }
AlphanumCounter >> currentLetter [
^ currentLetter ifNil: [ currentLetter := self letters first ]
]
{ #category : #accessing }
AlphanumCounter >> currentLetterIndex [
^ self letters detectIndex: [:n | n = self currentLetter]
]
{ #category : #accessing }
AlphanumCounter >> digits [
^ digits ifNil: [ digits := 1 to: 9 ]
]
{ #category : #accessing }
AlphanumCounter >> digits: aNumbersArray [
digits := aNumbersArray
]
{ #category : #accessing }
AlphanumCounter >> increase [
(self currentDigit < self digits last)
ifTrue: [ currentDigit := currentDigit + 1 ]
ifFalse: [
currentLetter := self letters at: (self currentLetterIndex + 1).
currentDigit := self digits first
]
]
{ #category : #accessing }
AlphanumCounter >> letters [
^ letters ifNil: [ letters := $A to: $Z ]
]

View File

@ -0,0 +1,45 @@
Extension { #name : #Array }
{ #category : #'*MiniDocs' }
Array >> bagOfWordsFor: sentenceArray [
"An utility machine training little algorithm.
Inspired by https://youtu.be/8qwowmiXANQ?t=1144.
This should be moved probably to [Polyglot](https://github.com/pharo-ai/Polyglot),
but the repository is pretty innactive (with commits 2 or more years old and no reponse to issues).
Meanwhile, it will be in MiniDocs.
Given the sentence := #('hello' 'how' 'are' 'you')
and the testVocabulary := #('hi' 'hello' 'I' 'you' 'bye' 'thank' 'you')
then
testVocabulary bagOfWordsFor: sentence.
Should give: #(0 1 0 1 0 0 0)
"
| bagOfWords |
bagOfWords := Array new: self size.
bagOfWords doWithIndex: [:each :i | bagOfWords at: i put: 0 ].
sentenceArray do: [:token | |index|
index := self indexOf: token.
index > 0
ifTrue: [bagOfWords at: index put: 1]
].
^ bagOfWords
]
{ #category : #'*MiniDocs' }
Array >> replaceWithUniqueNilsAndBooleans [
| response |
(self includesAny: #(true false nil))
ifFalse: [ response := self ]
ifTrue: [ | newItem |
response := OrderedCollection new.
self do: [:item |
(item isBoolean or: [ item isNil ])
ifTrue: [ newItem := item asString, '-', (NanoID generate copyFrom: 1 to: 3) ]
ifFalse: [ newItem := item ].
response add: newItem.
].
].
^ response
]

View File

@ -0,0 +1,12 @@
Extension { #name : #ByteString }
{ #category : #'*MiniDocs' }
ByteString >> asHTMLComment [
^ '<!-- ', self , ' -->'
]
{ #category : #'*MiniDocs' }
ByteString >> email [
"Quick fix for importing Lepiter pages that have a plain ByteString field as email."
^ self
]

View File

@ -0,0 +1,46 @@
Extension { #name : #DataFrame }
{ #category : #'*MiniDocs' }
DataFrame >> asMarkdown [
| response |
response := '' writeStream.
self columnNames do: [ :name | response nextPutAll: '| ' , name , ' ' ].
response
nextPutAll: '|';
cr.
self columns size timesRepeat: [ response nextPutAll: '|---' ].
response
nextPutAll: '|';
cr.
self asArrayOfRows
do: [ :row |
row do: [ :cell | response nextPutAll: '| ' , cell asString , ' ' ].
response
nextPutAll: '|';
cr ].
^ response contents accentedCharactersCorrection withInternetLineEndings.
]
{ #category : #'*MiniDocs' }
DataFrame >> viewDataFor: aView [
<gtView>
| columnedList |
self numberOfRows >= 1 ifFalse: [ ^ aView empty ].
columnedList := aView columnedList
title: 'Data';
items: [ self transposed columns ];
priority: 40.
self columnNames
withIndexDo: [:aName :anIndex |
columnedList
column: aName
text: [:anItem | anItem at: anIndex ]
].
^ columnedList
]
{ #category : #'*MiniDocs' }
DataFrame >> webView [
^ Pandoc convertString: self asMarkdown from: 'markdown' to: 'html'
]

View File

@ -0,0 +1,6 @@
Extension { #name : #Dictionary }
{ #category : #'*MiniDocs' }
Dictionary >> treeView [
^ self asOrderedDictionary treeView
]

View File

@ -0,0 +1,53 @@
Extension { #name : #FileLocator }
{ #category : #'*MiniDocs' }
FileLocator class >> aliases [
| fileAliases |
fileAliases := self fileAliases.
fileAliases exists
ifFalse: [ | initialConfig |
initialConfig := Dictionary new.
fileAliases ensureCreateFile.
MarkupFile exportAsFileOn: fileAliases containing: initialConfig
].
^ STON fromString: fileAliases contents
]
{ #category : #'*MiniDocs' }
FileLocator class >> atAlias: aString put: aFolderOrFile [
| updatedAliases |
updatedAliases:= self aliases
at: aString put: aFolderOrFile;
yourself.
MarkupFile exportAsFileOn: self fileAliases containing: updatedAliases.
^ updatedAliases
]
{ #category : #'*MiniDocs' }
FileLocator >> extractMetadata [
"I package the functionality from [[How to extract meta information using ExifTool]],
from the GToolkit Book.
I depend on the external tool ExifTool."
| process variablesList |
process := GtSubprocessWithInMemoryOutput new
command: 'exiftool';
arguments: { self fullName}.
process errorBlock: [ :proc | ^ self error: 'Failed to run exiftool' ].
process runAndWait.
variablesList := process stdout lines collect: [ :currentLine |
| separatorIndex name value |
separatorIndex := currentLine indexOf: $:.
name := (currentLine copyFrom: 1 to: separatorIndex - 1) trimBoth.
value := (currentLine
copyFrom: separatorIndex + 1
to: currentLine size) trimBoth.
name -> value
].
^ variablesList asOrderedDictionary
]
{ #category : #'*MiniDocs' }
FileLocator class >> fileAliases [
^ MiniDocs appFolder / 'fileAliases.ston'
]

View File

@ -27,6 +27,14 @@ GrafoscopioNode class >> fromFile: aFileReference [
^ (STON fromString: aFileReference contents) first parent ^ (STON fromString: aFileReference contents) first parent
] ]
{ #category : #accessing }
GrafoscopioNode class >> fromLink: aStonLink [
| notebook |
notebook := (STON fromString: aStonLink asUrl retrieveContents utf8Decoded) first parent.
notebook addRemoteLocation: aStonLink.
^ notebook
]
{ #category : #accessing } { #category : #accessing }
GrafoscopioNode >> addRemoteLocation: anURL [ GrafoscopioNode >> addRemoteLocation: anURL [
self remoteLocations add: anURL self remoteLocations add: anURL
@ -53,10 +61,11 @@ GrafoscopioNode >> asLePage [
self root populateTimestamps. self root populateTimestamps.
page := LePage new page := LePage new
initializeTitle: 'Grafoscopio Notebook (imported)'. initializeTitle: 'Grafoscopio Notebook (imported)'.
self nodesInPreorder allButFirst do: [:node | self nodesInPreorder allButFirst
page addSnippet: node asSnippet ]. do: [:node | page addSnippet: node asSnippet ].
page latestEditTime: self root latestEditionDate. page latestEditTime: self root latestEditionDate.
page createTime: self root earliestCreationDate. page createTime: self root earliestCreationDate.
page optionAt: 'remoteLocations' put: self remoteLocations.
^ page. ^ page.
] ]

View File

@ -0,0 +1,72 @@
Extension { #name : #GtGQLSnippet }
{ #category : #'*MiniDocs' }
GtGQLSnippet >> asMarkdeep [
| output |
output := WriteStream on: ''.
(self metadata)
at: 'operation' put: self operation;
at: 'input' put: self input;
at: 'context' put: self context;
yourself.
output
nextPutAll: self metadataDiv;
nextPutAll: self markdeepCustomOpener;
nextPutAll: self asMarkdownString;
nextPut: Character lf;
nextPutAll: self markdeepCustomCloser;
nextPut: Character lf;
nextPutAll: '</div>';
nextPut: Character lf;
nextPut: Character lf.
^ output contents withInternetLineEndings
]
{ #category : #'*MiniDocs' }
GtGQLSnippet >> markdeepCustomCloser [
^ self markdeepCustomOpener
]
{ #category : #'*MiniDocs' }
GtGQLSnippet >> markdeepCustomOpener [
^ '* * *'
]
{ #category : #'*MiniDocs' }
GtGQLSnippet >> metadataDiv [
"PENDING: Shared among several snippets. Should be abstracted further?"
| output |
output := WriteStream on: ''.
output
nextPutAll: '<div st-class="' , self class greaseString , '"';
nextPut: Character lf;
nextPutAll: ' st-data="' , (STON toStringPretty: self metadata) , '">';
nextPut: Character lf.
^ output contents withInternetLineEndings.
]
{ #category : #'*MiniDocs' }
GtGQLSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uid asString36;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
GtGQLSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -16,12 +16,52 @@ Class {
#category : #'MiniDocs-Core' #category : #'MiniDocs-Core'
} }
{ #category : #accessing }
HedgeDoc class >> fromLink: aUrl [
^ self new fromLink: aUrl
]
{ #category : #'as yet unclassified' } { #category : #'as yet unclassified' }
HedgeDoc class >> newDefault [ HedgeDoc class >> newDefault [
^ self new ^ self new
defaultServer. defaultServer.
] ]
{ #category : #accessing }
HedgeDoc >> asLePage [
| newPage sanitizedMarkdown |
sanitizedMarkdown := self bodyWithoutTitleHeader promoteMarkdownHeaders.
newPage := LePage new
initializeTitle: self title.
sanitizedMarkdown := sanitizedMarkdown markdownSplitted.
sanitizedMarkdown class = OrderedCollection ifTrue: [
sanitizedMarkdown do: [:lines | | snippet |
snippet := LeTextSnippet new
string: lines asStringWithCr;
uid: LeUID new.
newPage
addSnippet: snippet;
yourself
]
].
sanitizedMarkdown class = ByteString ifTrue: [ | snippet |
snippet := LeTextSnippet new
string: sanitizedMarkdown;
uid: LeUID new.
newPage
addSnippet: snippet;
yourself
].
newPage
incomingLinks;
splitAdmonitionSnippets.
newPage editTime: DateAndTime now.
newPage options
at: 'HedgeDoc' at: 'yamlFrontmatter' put: self metadata;
at: 'HedgeDoc' at: 'url' put: self url asString asHTMLComment.
^ newPage
]
{ #category : #accessing } { #category : #accessing }
HedgeDoc >> asMarkdeep [ HedgeDoc >> asMarkdeep [
^ Markdeep new ^ Markdeep new
@ -40,9 +80,18 @@ HedgeDoc >> asMarkdownTiddler [
created: Tiddler nowLocal. created: Tiddler nowLocal.
] ]
{ #category : #accessing }
HedgeDoc >> bodyWithoutTitleHeader [
| headerIndex |
headerIndex := self body lines
detectIndex: [ :line | line includesSubstring: self headerAsTitle ]
ifNone: [ ^ self body].
^ (self body lines copyWithoutIndex: headerIndex) asStringWithCr
]
{ #category : #accessing } { #category : #accessing }
HedgeDoc >> contents [ HedgeDoc >> contents [
^ body ^ super contents
] ]
{ #category : #accessing } { #category : #accessing }
@ -55,6 +104,12 @@ HedgeDoc >> defaultServer [
self server: 'https://docutopia.tupale.co'. self server: 'https://docutopia.tupale.co'.
] ]
{ #category : #accessing }
HedgeDoc >> fromLink: aString [
self url: aString.
self retrieveContents
]
{ #category : #'as yet unclassified' } { #category : #'as yet unclassified' }
HedgeDoc >> htmlUrl [ HedgeDoc >> htmlUrl [
| link | | link |
@ -81,7 +136,7 @@ HedgeDoc >> pad: anObject [
{ #category : #accessing } { #category : #accessing }
HedgeDoc >> retrieveContents [ HedgeDoc >> retrieveContents [
self url ifNil: [ ^ self ]. self url ifNil: [ ^ self ].
self contents: (self url addPathSegment: 'download') retrieveContents. self fromString: (self url addPathSegment: 'download') retrieveContents.
^ self. ^ self.
] ]
@ -117,11 +172,18 @@ HedgeDoc >> server: aUrlString [
{ #category : #accessing } { #category : #accessing }
HedgeDoc >> url [ HedgeDoc >> url [
^ url asUrl url ifNotNil: [ ^ url asUrl ]
] ]
{ #category : #accessing } { #category : #accessing }
HedgeDoc >> url: anObject [ HedgeDoc >> url: anObject [
| tempUrl html |
tempUrl := anObject asZnUrl.
html := XMLHTMLParser parse: tempUrl retrieveContents.
(html xpath: '//head/meta[@name="application-name"][@content = "HedgeDoc - Ideas grow better together"]') isEmpty
ifTrue: [ self inform: 'Not a hedgedoc url'.
url := nil ].
server := tempUrl host.
url := anObject url := anObject
] ]

View File

@ -8,16 +8,16 @@ Class {
} }
{ #category : #accessing } { #category : #accessing }
HedgeDocGrammar >> start [ HedgeDocGrammar >> metadataAsYAML [
| any | "I parse the header of the hedgedoc document for YAML metadata."
any := #any asPParser. ^ '---' asPParser token, #any asPParser starLazy token, '---' asPParser token
^ (self yamlMetadata / any starLazy), youtubeEmbeddedLink
] ]
{ #category : #accessing } { #category : #accessing }
HedgeDocGrammar >> yamlMetadata [ HedgeDocGrammar >> start [
"I parse the header of the hedgedoc document for YAML metadata." | any |
^ '---' asPParser token, #any asPParser starLazy token, '---' asPParser token any := #any asPParser.
^ (self metadataAsYAML / any starLazy), youtubeEmbeddedLink
] ]
{ #category : #accessing } { #category : #accessing }

View File

@ -0,0 +1,26 @@
Extension { #name : #LeChangesSnippet }
{ #category : #'*MiniDocs' }
LeChangesSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeChangesSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -0,0 +1,21 @@
Extension { #name : #LeCodeSnippet }
{ #category : #'*MiniDocs' }
LeCodeSnippet >> metadataUpdate [
| surrogate |
self parent
ifNil: [ surrogate := nil]
ifNotNil: [
self parent isString
ifTrue: [ surrogate := self parent]
ifFalse: [ surrogate := self parent uidString ]
].
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: surrogate;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: self createEmail asString withoutXMLTagDelimiters;
at: 'modifier' put: self editEmail asString withoutXMLTagDelimiters;
yourself
]

View File

@ -1,28 +1,17 @@
Extension { #name : #LeDatabase } Extension { #name : #LeDatabase }
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeDatabase >> addPageFromMarkdeep: markdeepDocTree withRemote: externalDocLocation [ LeDatabase >> addPage2FromMarkdeep: markdeepDocTree withRemote: externalDocLocation [
| remoteMetadata divSnippets snippets page | | newPage |
divSnippets := (markdeepDocTree xpath: '//div[@st-class]') asOrderedCollection "^ { snippets . page }"
collect: [ :xmlElement | xmlElement postCopy ]. "Rebulding partial subtrees"
snippets := divSnippets "Adding unrooted subtrees to the page"
collect: [ :xmlElement | "^ newPage"
(xmlElement attributes at: 'st-class') = 'LeTextSnippet' newPage := self
ifTrue: [ LeTextSnippet new contentFrom: xmlElement ] rebuildPageFromMarkdeep: markdeepDocTree
ifFalse: [ (xmlElement attributes at: 'st-class') = 'LePharoSnippet' withRemote: externalDocLocation.
ifTrue: [ LePharoSnippet new contentFrom: xmlElement ] ] ]. newPage
remoteMetadata := Markdeep new metadataFromXML: markdeepDocTree. childrenDo: [ :snippet |
page := LePage new
title: (remoteMetadata at: 'title');
basicUid: (UUID fromString36: (remoteMetadata at: 'id'));
createTime: (LeTime new time: (remoteMetadata at: 'created') asDateAndTime);
editTime: (LeTime new time: (remoteMetadata at: 'modified') asDateAndTime);
latestEditTime: (LeTime new time: (remoteMetadata at: 'modified') asDateAndTime);
createEmail: (remoteMetadata at: 'creator');
editEmail: (remoteMetadata at: 'modifier').
snippets do: [ :snippet | page addSnippet: snippet ].
page children
do: [ :snippet |
(self hasBlockUID: snippet uid) (self hasBlockUID: snippet uid)
ifTrue: [ | existingPage | ifTrue: [ | existingPage |
existingPage := self pages existingPage := self pages
@ -31,6 +20,56 @@ LeDatabase >> addPageFromMarkdeep: markdeepDocTree withRemote: externalDocLocati
^ self ] ^ self ]
ifFalse: [ snippet database: self. ifFalse: [ snippet database: self.
self registerSnippet: snippet ] ]. self registerSnippet: snippet ] ].
self addPage: newPage.
^ newPage
]
{ #category : #'*MiniDocs' }
LeDatabase >> addPageCopy: aLePage [
| pageTitle timestamp shortID page |
timestamp := DateAndTime now asString.
pageTitle := 'Copy of ', aLePage title.
page := aLePage duplicatePageWithNewName: pageTitle, timestamp.
shortID := '(id: ', (page uid asString copyFrom: 1 to: 8), ')'.
page title: (page title copyReplaceAll: timestamp with: shortID).
^ page
]
{ #category : #'*MiniDocs' }
LeDatabase >> addPageFromMarkdeep: markdeepDocTree withRemote: externalDocLocation [
| remoteMetadata divSnippets dataSnippets page |
divSnippets := (markdeepDocTree xpath: '//div[@st-class]') asOrderedCollection
collect: [ :xmlElement | xmlElement postCopy ].
remoteMetadata := Markdeep new metadataFromXML: markdeepDocTree.
"Ensuring remote metadata has consistent data"
remoteMetadata at: 'origin' put: externalDocLocation.
remoteMetadata at: 'title' ifAbsentPut: [ markdeepDocTree detectMarkdeepTitle ].
remoteMetadata at: 'id' ifAbsentPut: [UUID new asString36].
remoteMetadata at: 'created' ifAbsentPut: [ DateAndTime now] .
remoteMetadata at: 'creator' ifAbsentPut: [ 'unknown' ].
remoteMetadata at: 'modified' ifAbsentPut: [ DateAndTime now].
remoteMetadata at: 'modifier' ifAbsentPut: [ 'unknown' ].
dataSnippets := self sanitizeMarkdeepSnippets: divSnippets withMetadata: remoteMetadata.
page := LePage new.
page fromDictionary: remoteMetadata.
dataSnippets do: [:each | | snippet|
snippet := each asLepiterSnippet.
page addSnippet: snippet.
].
page children
do: [ :snippet |
(self hasBlockUID: snippet uid)
ifTrue: [ | existingPage |
existingPage := self pages
detect: [ :pageTemp | pageTemp includesSnippetUid: snippet uid ]
ifFound: [
self importErrorForLocal: existingPage withRemote: externalDocLocation.
^ self
]
ifNone: [ snippet database: self ].
]
ifFalse: [ snippet database: self ]
].
self addPage: page. self addPage: page.
^ page ^ page
] ]
@ -42,7 +81,7 @@ LeDatabase >> addPageFromMarkdeepUrl: aString [
page page
ifNotNil: [ :arg | ifNotNil: [ :arg |
self importErrorForLocal: page withRemote: aString. self importErrorForLocal: page withRemote: aString.
^ self ]. ^ self errorCardFor: page uidString ].
^ self addPageFromMarkdeep: (self docTreeForLink: aString) withRemote: aString ^ self addPageFromMarkdeep: (self docTreeForLink: aString) withRemote: aString
] ]
@ -62,10 +101,10 @@ LeDatabase >> docTreeForLink: aString [
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeDatabase >> errorCardFor: error [ LeDatabase >> errorCardFor: errorKey [
| keepButton overwriteButton loadCopyButton errorMessageUI localPage |
| keepButton overwriteButton backupButton errorMessageUI localPage errorKey |
errorKey := error keys first.
localPage := self pageWithID: errorKey. localPage := self pageWithID: errorKey.
keepButton := BrButton new keepButton := BrButton new
aptitude: BrGlamorousButtonWithIconAndLabelAptitude; aptitude: BrGlamorousButtonWithIconAndLabelAptitude;
@ -82,22 +121,28 @@ LeDatabase >> errorCardFor: error [
icon: BrGlamorousVectorIcons edit; icon: BrGlamorousVectorIcons edit;
action: [ :aButton | action: [ :aButton |
self removePage: localPage. self removePage: localPage.
aButton phlow spawnObject: (self addPageFromMarkdeepUrl: (error at: errorKey at: 'remote')). aButton phlow spawnObject: (self addPageFromMarkdeepUrl: (self errors at: errorKey at: 'remote')).
self errors removeKey: errorKey self errors removeKey: errorKey
]; ];
margin: (BlInsets left: 10). margin: (BlInsets left: 10).
backupButton := BrButton new loadCopyButton := BrButton new
aptitude: BrGlamorousButtonWithIconAndLabelAptitude; aptitude: BrGlamorousButtonWithIconAndLabelAptitude;
label: 'Backup local page'; label: 'Load remote page as a copy';
icon: BrGlamorousVectorIcons changes; icon: BrGlamorousVectorIcons changes;
action: [ :aButton | ]; action: [ :aButton | self ];
margin: (BlInsets left: 10). margin: (BlInsets left: 10).
errorMessageUI := BrEditor new errorMessageUI := BrEditor new
aptitude: BrGlamorousRegularEditorAptitude new ; aptitude: BrGlamorousRegularEditorAptitude new ;
text: (error at: errorKey at: 'message'); text: (self errors at: errorKey at: 'message');
vFitContent. vFitContent.
^ { errorMessageUI. keepButton. overwriteButton. backupButton } ^ BrHorizontalPane new
matchParent;
alignCenter;
addChild:errorMessageUI;
addChild: keepButton;
addChild: overwriteButton;
addChild: loadCopyButton
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -124,6 +169,34 @@ LeDatabase >> gtViewErrorDetailsOn: aView [
]. ].
] ]
{ #category : #'*MiniDocs' }
LeDatabase >> gtViewErrorDetailsOn: aView withKey: erroKey [
<gtView>
^ aView explicit
title: 'Errors beta' translated;
priority: 5;
stencil: [ | container |
container := BlElement new
layout: BlFlowLayout new;
constraintsDo: [ :c |
c vertical fitContent.
c horizontal matchParent ];
padding: (BlInsets all: 10).
container
addChildren: (self errorCardFor: erroKey)
].
]
{ #category : #'*MiniDocs' }
LeDatabase >> importDocumentFrom: aURL [
| doc |
"Using file extension in URL as a cheap (non-robuts) way of detecting the kind of document.
Better file type detection should be implemented in the future."
(aURL endsWith: '.md.html') ifTrue: [ ^ self addPageFromMarkdeepUrl: aURL ].
doc := HedgeDoc fromLink: aURL asString.
^ self addPage: doc asLePage
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeDatabase >> importErrorForLocal: page withRemote: externalDocLocation [ LeDatabase >> importErrorForLocal: page withRemote: externalDocLocation [
@ -154,7 +227,8 @@ LeDatabase >> importErrorForLocal: page withRemote: externalDocLocation [
at: 'remote' put: externalDocLocation; at: 'remote' put: externalDocLocation;
at: 'message' put: message ; at: 'message' put: message ;
yourself. yourself.
self errors at: id put: error self errors at: id put: error.
^ self errors at: id.
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -162,3 +236,77 @@ LeDatabase >> options [
^ options ^ options
] ]
{ #category : #'*MiniDocs' }
LeDatabase >> previewSanitizedPageFromMarkdeep: markdeepDocTree withRemote: externalDocLocation [
| remoteMetadata divSnippets divSnippetsSanitized |
divSnippets := (markdeepDocTree xpath: '//div[@st-class]') asOrderedCollection
collect: [ :xmlElement | xmlElement postCopy ].
remoteMetadata := Markdeep new metadataFromXML: markdeepDocTree.
remoteMetadata at: 'origin' put: externalDocLocation.
divSnippetsSanitized := self sanitizeMarkdeepSnippets: divSnippets withMetadata: remoteMetadata.
^ { divSnippets . divSnippetsSanitized . remoteMetadata }
]
{ #category : #'*MiniDocs' }
LeDatabase >> rebuildPageFromMarkdeep: markdeepDocTree withRemote: externalDocLocation [
| newPage snippets divSnippets remoteMetadata dataSnippets |
divSnippets := (markdeepDocTree xpath: '//div[@st-class]') asOrderedCollection
collect: [ :xmlElement | xmlElement postCopy ].
remoteMetadata := Markdeep new metadataFromXML: markdeepDocTree.
remoteMetadata at: 'origin' put: externalDocLocation.
dataSnippets := self
sanitizeMarkdeepSnippets: divSnippets
withMetadata: remoteMetadata.
snippets := dataSnippets collect: [ :each | each asLepiterSnippet ].
newPage := LePage new
title: (remoteMetadata at: 'title');
basicUid: (UUID fromString36: (remoteMetadata at: 'id'));
createTime: (LeTime new time: (remoteMetadata at: 'created') asDateAndTime);
editTime: (LeTime new time: (remoteMetadata at: 'modified') asDateAndTime);
latestEditTime: (LeTime new time: (remoteMetadata at: 'modified') asDateAndTime);
createEmail: (remoteMetadata at: 'creator');
editEmail: (remoteMetadata at: 'modifier'). "^ { snippets . page }" "Rebulding partial subtrees"
snippets
do: [ :currentSnippet |
| parentSnippet |
parentSnippet := snippets
detect: [ :item | item uid asString = currentSnippet parent ]
ifNone: [ parentSnippet := 'unrooted' ].
currentSnippet parent: parentSnippet.
parentSnippet class = ByteString
ifFalse: [ parentSnippet children addChild: currentSnippet ] ]. "Adding unrooted subtrees to the page"
"^ { unrooted . newPage }."
snippets
select: [ :each | each parent = 'unrooted' ]
thenDo: [ :unrooted | newPage addSnippet: unrooted ].
^ newPage
]
{ #category : #'*MiniDocs' }
LeDatabase >> sanitizeMarkdeepSnippets: divSnippets withMetadata: remoteMetadata [
^ divSnippets collectWithIndex: [:markdeepDiv :i | | snippetData creationTime modificationTime timestampWarning |
snippetData := markdeepDiv asSnippetDictionary.
creationTime := snippetData at: 'created'.
modificationTime := snippetData at: 'modified'.
timestampWarning := [:timestamp |
'Modified timestamps: ', timestamp ,' date and time was replaced instead of nil value. See "origin" metadata for more historical traceability information.'
].
(creationTime = 'nil' and: [ modificationTime ~= 'nil' ])
ifTrue: [
snippetData redefineTimestampsBefore: modificationTime.
snippetData addErrata: (timestampWarning value: 'creation').
snippetData at: 'origin' put: (remoteMetadata at: 'origin').
].
(creationTime = 'nil' and: [ modificationTime = 'nil' ])
ifTrue: [ | timeDiff |
timeDiff := divSnippets size - i. "Suggesting that last snippets were modified after the first ones."
modificationTime := (remoteMetadata at: 'created') asDateAndTime - timeDiff seconds.
snippetData redefineTimestampsBefore: modificationTime.
snippetData addErrata: (timestampWarning value: 'creation').
snippetData addErrata: (timestampWarning value: 'modification').
snippetData at: 'origin' put: (remoteMetadata at: 'origin').
].
snippetData.
]
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LeDockerSnippet }
{ #category : #'*MiniDocs' }
LeDockerSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeDockerSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -5,3 +5,28 @@ LeExampleSnippet >> asMarkdeep [
^ (WriteStream on: '') contents ^ (WriteStream on: '') contents
] ]
{ #category : #'*MiniDocs' }
LeExampleSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeExampleSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LeGitHubSnippet }
{ #category : #'*MiniDocs' }
LeGitHubSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeGitHubSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -0,0 +1,8 @@
Extension { #name : #LeHeaderNode }
{ #category : #'*MiniDocs' }
LeHeaderNode >> headerFullName [
^ self topParent completeSource
copyFrom: self startPosition
to: self stopPosition
]

View File

@ -0,0 +1,56 @@
Extension { #name : #LeHomeDatabaseHeaderElement }
{ #category : #'*MiniDocs' }
LeHomeDatabaseHeaderElement >> importMinidocsButtonElement [
^ self userData at: 'importMinidocsButtonElement' ifAbsentPut: [ self newImportMiniDocsButton]
]
{ #category : #'*MiniDocs' }
LeHomeDatabaseHeaderElement >> initialize [
super initialize.
self initializeEditableTitleElement.
self initializeButtons.
self addChild: self toolbarElement as: #toolbar.
self toolbarElement
addItem: self editableTitleElement;
addItem: self newAddNewPageButton;
addItem: self removeButtonElement;
addItem: self importButtonElement;
addItem: self exportButtonElement;
addItem: self importMinidocsButtonElement.
self addAptitude: (BrLayoutResizerAptitude new
hInherit;
vAnyToFitContent;
hInherit: self toolbarElement;
vAnyToFitContent: self toolbarElement).
]
{ #category : #'*MiniDocs' }
LeHomeDatabaseHeaderElement >> initializeButtons [
self initializeRemoveButton.
self initializeImportButton.
self initializeExportButton.
self initializeMiniDocsImportButton.
]
{ #category : #'*MiniDocs' }
LeHomeDatabaseHeaderElement >> initializeMiniDocsImportButton [
self userData at: 'importMinidocsButtonElement' put: self newImportMiniDocsButton.
]
{ #category : #'*MiniDocs' }
LeHomeDatabaseHeaderElement >> newImportMiniDocsButton [
^ LeMiniDocsImport new
tooltip: 'Import document from link';
contentExtent: 200 @ 30
]
{ #category : #'*MiniDocs' }
LeHomeDatabaseHeaderElement >> updateToolbarButtons [
self updateRemoveButtonElement.
self exportButtonElement database: self database.
self importButtonElement database: self database.
self importMinidocsButtonElement database: self database.
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LeJenkinsSnippet }
{ #category : #'*MiniDocs' }
LeJenkinsSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeJenkinsSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -0,0 +1,89 @@
Class {
#name : #LeMiniDocsImport,
#superclass : #BrButton,
#instVars : [
'contentExtent',
'database'
],
#category : #'MiniDocs-UI'
}
{ #category : #accessing }
LeMiniDocsImport >> contentExtent [
^ contentExtent
]
{ #category : #accessing }
LeMiniDocsImport >> contentExtent: aPoint [
self
assert: [ aPoint isNotNil ]
description: [ 'Extent must be non-nil' ].
contentExtent := aPoint
]
{ #category : #accessing }
LeMiniDocsImport >> createDropdownExpandedHandleButton [
^ BrButton new
icon: BrGlamorousVectorIcons downwards;
label: self tooltip;
aptitude: BrGlamorousButtonWithIconAndLabelAptitude
]
{ #category : #accessing }
LeMiniDocsImport >> createURLeditable [
| base editable |
base := BlElement new
background: (Color white);
size: 200 @ 30;
margin: (BlInsets all: 10);
yourself.
editable := BrEditableLabel new
aptitude: BrGlamorousEditableLabelAptitude new glamorousRegularFontAndSize;
text: 'Document link';
switchToEditor.
editable when: BrEditorAcceptWish do: [ :aWish |
self importDocumentFrom: aWish text asString.
].
base addChild: editable.
^ base
]
{ #category : #accessing }
LeMiniDocsImport >> database [
^ database
]
{ #category : #accessing }
LeMiniDocsImport >> database: aLeDatabase [
database := aLeDatabase
]
{ #category : #accessing }
LeMiniDocsImport >> importDocumentFrom: aURL [
^ self database importDocumentFrom: aURL.
]
{ #category : #accessing }
LeMiniDocsImport >> initialize [
super initialize.
self
icon: BrGlamorousVectorIcons downwards;
label: 'Add MiniDocs';
aptitude: BrGlamorousButtonWithIconAndLabelAptitude.
self addAptitude: (BrGlamorousWithDropdownAptitude
handle: [ self createDropdownExpandedHandleButton ]
content: [ self createURLeditable ]).
self aptitude - BrGlamorousButtonExteriorAptitude.
]
{ #category : #accessing }
LeMiniDocsImport >> tooltip [
^ self label
]
{ #category : #accessing }
LeMiniDocsImport >> tooltip: aString [
self label: aString
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LeMockedSnippet }
{ #category : #'*MiniDocs' }
LeMockedSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeMockedSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -3,7 +3,7 @@ Extension { #name : #LePage }
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> asHtmlFile [ LePage >> asHtmlFile [
self asMarkdownFile. self asMarkdownFileWithMetadataWrappers.
self defaultPandocTemplate exists self defaultPandocTemplate exists
ifFalse: [ MarkupFile installTemplate: 'https://mutabit.com/repos.fossil/mutabit/doc/trunk/plantillas/Pandoc/clean-menu-mod.html' into: self defaultPandocTemplate parent ]. ifFalse: [ MarkupFile installTemplate: 'https://mutabit.com/repos.fossil/mutabit/doc/trunk/plantillas/Pandoc/clean-menu-mod.html' into: self defaultPandocTemplate parent ].
@ -21,29 +21,23 @@ LePage >> asHtmlFile [
LePage >> asMarkdeep [ LePage >> asMarkdeep [
| bodyStream markdeep | | bodyStream markdeep |
bodyStream := '' writeStream. bodyStream := '' writeStream.
self preorderTraversal do: [:snippet | bodyStream nextPutAll: self notebookMetadataSnippet asMarkdeep.
bodyStream nextPutAll: snippet asMarkdeep self preorderTraversal
]. do: [ :snippet | bodyStream nextPutAll: snippet asMarkdeep ].
markdeep := Markdeep new markdeep := Markdeep new
title: self title; title: self title;
body: bodyStream contents; body: bodyStream contents;
metadata: self metadata;
file: self storage / self markdeepFileName;
navTop: self navTop. navTop: self navTop.
self metadata keysAndValuesDo: [:k :v | self metadata
k = 'lang' at: 'authors'
ifTrue: [ ifPresent: [ :author | markdeep metadata at: 'authors' put: author ].
markdeep head self metadata
add: '<meta lang="', v,'">'; at: 'version'
yourself. ifPresent: [ :version | markdeep metadata at: 'version' put: version ].
] markdeep head: nil.
ifFalse: [ ^ markdeep
markdeep head
add: '<meta name="', k, '" content="', v,'">';
yourself.
]
].
self metadata at: 'authors' ifPresent: [:author | markdeep metadata at: 'authors' put: author ].
self metadata at: 'version' ifPresent: [:version | markdeep metadata at: 'version' put: version ].
^ markdeep.
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -54,27 +48,49 @@ LePage >> asMarkdeepFile [
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> asMarkdown [ LePage >> asMarkdown [
"PENDING: to debug the output."
| bodyStream markdown | | bodyStream markdown |
bodyStream := '' writeStream. bodyStream := '' writeStream.
bodyStream bodyStream
nextPutAll: '---'; nextPutAll: '# ', self title; cr; cr.
nextPutAll: String lf.
self metadata keysAndValuesDo: [ :k :v |
bodyStream
nextPutAll: k , ': "' , v, '"';
nextPutAll: String lf ].
bodyStream nextPutAll: '---' , String lf , String lf.
self preorderTraversal self preorderTraversal
do: [ :snippet | bodyStream nextPutAll: snippet asMarkdown ]. do: [ :snippet | bodyStream nextPutAll: snippet asMarkdown ].
markdown := Markdown new contents: bodyStream contents. markdown := Markdown new
contents: bodyStream contents promoteMarkdownHeaders;
metadata: (self metadata at: 'original' ifAbsentPut: Dictionary new).
^ markdown ^ markdown
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> asMarkdownFile [ LePage >> asMarkdownFileWithMetadataWrappers [
| folder | | folder |
folder := self storage. folder := self storage.
^ MarkupFile exportAsFileOn: folder / self markdownFileName containing: self asMarkdown contents ^ MarkupFile exportAsFileOn: folder / self markdownFileName containing: self asMarkdownWithMetadataWrappers contents
]
{ #category : #'*MiniDocs' }
LePage >> asMarkdownWithMetadataWrappers [
| bodyStream markdown |
bodyStream := '' writeStream.
"bodyStream
nextPut: Character lf;
nextPutAll: '# ', self title; cr; cr."
self preorderTraversal
do: [ :snippet | bodyStream nextPutAll: snippet asMarkdownWithMetadataWrappers ].
markdown := Markdown new
contents: bodyStream contents promoteMarkdownHeaders;
title: self title;
metadata: self metadata.
^ markdown
]
{ #category : #'*MiniDocs' }
LePage >> config [
| configFile |
configFile := self storage / 'config.ston'.
configFile exists
ifTrue: [^ STON fromString: configFile contents ]
ifFalse: [ ^ nil ]
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -85,18 +101,52 @@ LePage >> defaultPandocTemplate [
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> detectParentSnippetWithUid: uidString [ LePage >> detectParentSnippetWithUid: uidString [
"Answer a boolean indicating whether the supplied uid is present" uidString = self uid asString36 ifTrue: [ ^ self ].
^ self preorderTraversal detect: [ :snippet | snippet uidString = uidString ]
]
^ self preorderTraversal detect: [ :snippet | snippet uidString = uidString ] ifNone: [ ^ self ] { #category : #'*MiniDocs' }
LePage >> exportMetadataToHead: markdeep [
self metadata
keysAndValuesDo: [ :k :v |
k = 'lang'
ifTrue: [ markdeep head
add: '<meta lang="' , v , '">';
yourself ]
ifFalse: [ markdeep head
add: '<meta name="' , k , '" content="' , v , '">';
yourself ] ]
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> exportedFileName [ LePage >> exportedFileName [
| sanitized | | sanitized titleWords shortTitle |
sanitized := self title asDashedLowercase romanizeAccents copyWithoutAll: #($/ $:). titleWords := self title splitOn: Character space.
(titleWords size > 11)
ifTrue: [
titleWords := titleWords copyFrom: 1 to: 3.
shortTitle := titleWords joinUsing: Character space.
]
ifFalse: [shortTitle := self title].
sanitized := shortTitle asDashedLowercase romanizeAccents copyWithoutAll: #($/ $: $🢒 $,).
^ sanitized , '--' , (self uidString copyFrom: 1 to: 5) ^ sanitized , '--' , (self uidString copyFrom: 1 to: 5)
] ]
{ #category : #'*MiniDocs' }
LePage >> fromDictionary: aDictionary [
self
title: (aDictionary at: 'title');
basicUid: (UUID fromString36: (aDictionary at: 'id'));
createTime: (LeTime new
time: (aDictionary at: 'created') asDateAndTime);
editTime: (LeTime new
time: (aDictionary at: 'modified') asDateAndTime);
latestEditTime: (LeTime new
time: (aDictionary at: 'modified') asDateAndTime);
createEmail: (aDictionary at: 'creator');
editEmail: (aDictionary at: 'modifier').
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> fromMarkdeepUrl: aString [ LePage >> fromMarkdeepUrl: aString [
| docTree pageMetadata | | docTree pageMetadata |
@ -129,8 +179,11 @@ LePage >> latestEditTime: aLeTime [
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> localHostAddress [ LePage >> localHostAddress [
| localUrl route | | localUrl route |
MiniDocsServer teapot server isRunning ifFalse: [ MiniDocsServer start ]. MiniDocsServer teapot server isRunning ifFalse: [ MiniDocsServer restart ].
route := MiniDocsServer teapot staticRouter prefix joinUsing: '/'. route := self storage path segments joinUsing: '/'.
MiniDocsServer teapot
serveStatic: ('/', route, '/', self markdeepFileName)
from: self storage / self markdeepFileName.
localUrl := MiniDocsServer teapot server localUrl asString. localUrl := MiniDocsServer teapot server localUrl asString.
^ localUrl, route, '/', self markdeepFileName ^ localUrl, route, '/', self markdeepFileName
] ]
@ -149,17 +202,17 @@ LePage >> markdownFileName [
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> metadata [ LePage >> metadata [
^ self options at: 'metadata' ifAbsentPut: [ self metadataInit] ^ self metadataUpdate
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> metadataInit [ LePage >> metadataUpdate [
^ OrderedDictionary new ^ OrderedDictionary new
at: 'id' put: self uidString; at: 'id' put: self uidString;
at: 'title' put: self contentAsString; at: 'title' put: self contentAsString;
at: 'created' put: self createTime greaseString; at: 'created' put: self createTime greaseString;
at: 'modified' put: self latestEditTime greaseString; at: 'modified' put: self getLatestEditTime greaseString;
at: 'creator' put: self createEmail greaseString; at: 'creator' put: self createEmail greaseString;
at: 'modifier' put: self editEmail greaseString; at: 'modifier' put: self editEmail greaseString;
yourself yourself
@ -174,6 +227,30 @@ LePage >> navTop [
ifTrue: [ ^ topNavFile contents ] ifTrue: [ ^ topNavFile contents ]
] ]
{ #category : #'*MiniDocs' }
LePage >> notebookMetadataSnippet [
| response |
response := LeTextSnippet new fromString: '<!-- See this snippet source code for this notebook''s metadata -->'.
response parent: self.
self optionAt: 'HedgeDoc' ifAbsent: [ ^ response ].
(response extra)
at: 'HedgeDoc' put: (self optionAt: 'HedgeDoc').
^ response
]
{ #category : #'*MiniDocs' }
LePage >> olderChild [
"I provide the last edited child node.
I'm useful to recalculate the age of a notebook."
| response|
response := self preorderTraversal first.
self preorderTraversal do: [:current |
current editTime >= response editTime
ifTrue: [ response := current ]
].
^ response
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> options [ LePage >> options [
^ options ^ options
@ -191,6 +268,11 @@ LePage >> removeSnippetsMetadata [
ifTrue: [ snippet options removeKey: 'metadata' ] ] ifTrue: [ snippet options removeKey: 'metadata' ] ]
] ]
{ #category : #'*MiniDocs' }
LePage >> sanitizeMetadata [
self allChildrenDepthFirst do: [:snippet | snippet sanitizeMetadata ]
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> sharedVariablesBindings [ LePage >> sharedVariablesBindings [
| codeSnippets shared | | codeSnippets shared |
@ -215,6 +297,25 @@ LePage >> sharedVariablesBindings [
^ shared asDictionary ^ shared asDictionary
] ]
{ #category : #'*MiniDocs' }
LePage >> splitAdmonitionSnippets [
"I'm used to clean after importing from HedgeDoc to ensure that a snippet contains only admonitions and extra content is put in a new cell."
| admonitionSnippets |
admonitionSnippets := self children select: [:node | node string startsWithMarkdownAdmonition ].
admonitionSnippets ifEmpty: [ ^ self ].
admonitionSnippets do: [:node | | nodeContent |
node ifNotNil: [
nodeContent := node string.
nodeContent startsWithMarkdownAdmonition
ifTrue: [ | snippetCommand |
snippetCommand := node splitSnippetCommandAtPosition: nodeContent admonitionEndingPosition.
snippetCommand execute.
node tagWith: (nodeContent lines first trimBoth withoutPrefix: ':::')
]
]
]
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> storage [ LePage >> storage [
| current | | current |
@ -224,6 +325,15 @@ LePage >> storage [
^ self optionAt: 'storage' ^ self optionAt: 'storage'
] ]
{ #category : #'*MiniDocs' }
LePage >> uiAddCopyButtonFor: anAction [
<lePageAction>
^ anAction button
tooltip: 'Export Page';
icon: BrGlamorousVectorIcons changes;
action: [:aButton | aButton phlow spawnObject: (self page database addPageCopy: self page) ]
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePage >> uiDefineFolderFor: anAction [ LePage >> uiDefineFolderFor: anAction [
<lePageAction> <lePageAction>
@ -259,3 +369,16 @@ LePage >> uiRefreshWebPreviewButtonFor: anAction [
"TODO: If Chrome/Chromium are not installed, I should execute:" "TODO: If Chrome/Chromium are not installed, I should execute:"
"WebBrowser openOn: self page localHostAddress" ] "WebBrowser openOn: self page localHostAddress" ]
] ]
{ #category : #'*MiniDocs' }
LePage >> youngerChild [
"I provide the first create child node.
I'm useful to recalculate the age of a notebook."
| response|
response := self preorderTraversal first.
self preorderTraversal do: [:current |
current createTime <= response createTime
ifTrue: [ response := current ]
].
^ response
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LePharoRewriteSnippet }
{ #category : #'*MiniDocs' }
LePharoRewriteSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LePharoRewriteSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -9,21 +9,26 @@ LePharoSnippet >> contentAsStringCustomized [
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePharoSnippet >> contentFrom: markdeepDiv [ LePharoSnippet >> fromDictionary: anOrderedDictionary [
self
uid: (LeUID new uidString: (anOrderedDictionary at: 'id'));
parent: (anOrderedDictionary at: 'parent');
createTime: (LeTime new time: ((anOrderedDictionary at: 'created')asDateAndTime));
editTime: (LeTime new time: ((anOrderedDictionary at: 'modified') asDateAndTime));
editEmail: (anOrderedDictionary at: 'modifier');
createEmail: (anOrderedDictionary at: 'creator').
]
| sanitizedStringText metadata joinedText | { #category : #'*MiniDocs' }
metadata := STON fromString: (markdeepDiv attributes at: 'st-data'). LePharoSnippet >> fromMarkdeep: markdeepDiv [
sanitizedStringText := markdeepDiv contentString lines.
sanitizedStringText := sanitizedStringText copyFrom: 4 to: sanitizedStringText size -2. ^ markdeepDiv asSnippetDictionary asLepiterSnippet
joinedText := '' writeStream. ]
sanitizedStringText do: [ :line | joinedText nextPutAll: line; nextPut: Character lf ].
self code: joinedText contents allButLast; { #category : #'*MiniDocs' }
uid: (LeUID new uidString: (metadata at: 'id')); LePharoSnippet >> fromString: aString [
parent: (metadata at: 'parent');
createTime: (LeTime new time: ((metadata at: 'created')asDateAndTime)); [ self coder forSource: aString ] onErrorDo: [ ]
editTime: (LeTime new time: ((metadata at: 'modified') asDateAndTime));
editEmail: (metadata at: 'modifier');
createEmail: (metadata at: 'creator')
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }

View File

@ -2,11 +2,22 @@ Extension { #name : #LePictureSnippet }
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePictureSnippet >> asMarkdeep [ LePictureSnippet >> asMarkdeep [
| output | | output curatedCaption captionLines |
captionLines := self caption lines.
(captionLines size <= 1)
ifTrue: [ curatedCaption := caption ]
ifFalse: [
curatedCaption := WriteStream on: ''.
curatedCaption nextPutAll: captionLines first.
captionLines allButFirstDo: [:line |
curatedCaption nextPutAll: ' ', line.
curatedCaption := curatedCaption contents.
]
].
output := WriteStream on: ''. output := WriteStream on: ''.
output output
nextPutAll: self metadataDiv; nextPutAll: self metadataDiv;
nextPutAll: '![ ', self caption ,' ](', self urlString, ')'; nextPutAll: '![ ', curatedCaption ,' ](', self urlString, ')';
nextPut: Character lf; nextPut: Character lf;
nextPutAll: '</div>'; nextPutAll: '</div>';
nextPut: Character lf; nextPut: Character lf;
@ -14,9 +25,60 @@ LePictureSnippet >> asMarkdeep [
^ output contents ^ output contents
] ]
{ #category : #'*MiniDocs' }
LePictureSnippet >> asMarkdownWithMetadataWrappers [
^ self asMarkdeep
]
{ #category : #'*MiniDocs' }
LePictureSnippet >> contentFrom: markdeepDiv [
| caption width |
caption := markdeepDiv contentString.
width := (markdeepDiv // 'img' @ 'width') stringValue.
self
optionAt: 'caption' put: caption;
optionAt: 'width' put: width.
self urlString: (markdeepDiv // 'img' @ 'src') stringValue.
]
{ #category : #'*MiniDocs' }
LePictureSnippet >> fromDictionary: anOrderedDictionary [
| sanitizedUrl|
sanitizedUrl := (anOrderedDictionary at: 'url').
sanitizedUrl := sanitizedUrl copyFrom: 5 to: sanitizedUrl size - 3.
self
uid: (LeUID new uidString: (anOrderedDictionary at: 'id'));
parent: (anOrderedDictionary at: 'parent');
createTime: (LeTime new time: ((anOrderedDictionary at: 'created')asDateAndTime));
editTime: (LeTime new time: ((anOrderedDictionary at: 'modified') asDateAndTime));
editEmail: (anOrderedDictionary at: 'modifier');
createEmail: (anOrderedDictionary at: 'creator');
urlString: sanitizedUrl;
caption: (anOrderedDictionary at: 'content') first
]
{ #category : #'*MiniDocs' }
LePictureSnippet >> fromMarkdeep: markdeepDiv [
^ markdeepDiv asSnippetDictionary asLepiterSnippet
]
{ #category : #'*MiniDocs' }
LePictureSnippet >> fromString: aStringArray [
"aStringArray should contain as first element the sanitized string and
as second the full original image Link string, which may contains links in the description."
| args urlTemp |
args := aStringArray second splitOn: ']('.
urlTemp := args last.
urlTemp := urlTemp copyFrom: 1 to: urlTemp size - 1.
self caption: aStringArray first.
self urlString: urlTemp.
^ self
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LePictureSnippet >> metadata [ LePictureSnippet >> metadata [
^ self optionAt: 'metadata' ifAbsentPut: [ self metadataInit ] ^ self metadataInit
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -26,8 +88,9 @@ LePictureSnippet >> metadataDiv [
output output
nextPutAll: '<div st-class="' , self class greaseString , '"'; nextPutAll: '<div st-class="' , self class greaseString , '"';
nextPut: Character lf; nextPut: Character lf;
nextPutAll: ' st-data="' , (STON toString: self metadata) , '">'. nextPutAll: ' st-data="' , (STON toStringPretty: self metadata) , '">';
^ output. nextPut: Character lf.
^ output contents withInternetLineEndings.
] ]
@ -40,9 +103,36 @@ LePictureSnippet >> metadataInit [
^ OrderedDictionary new ^ OrderedDictionary new
at: 'id' put: self uidString; at: 'id' put: self uidString;
at: 'parent' put: surrogate; at: 'parent' put: surrogate;
at: 'url' put: '<!--',self contentAsString, '-->';
at: 'created' put: self createTime asString; at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString; at: 'modified' put: self latestEditTime asString;
at: 'creator' put: self createEmail asString; at: 'creator' put: self createEmail asString withoutXMLTagDelimiters;
at: 'modifier' put: self editEmail asString; at: 'modifier' put: self editEmail asString withoutXMLTagDelimiters;
yourself yourself
] ]
{ #category : #'*MiniDocs' }
LePictureSnippet >> metadataUpdate [
| surrogate |
self parent
ifNil: [ surrogate := nil]
ifNotNil: [ surrogate := self parent uidString ].
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: surrogate;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: self createEmail asString withoutXMLTagDelimiters;
at: 'modifier' put: self editEmail asString withoutXMLTagDelimiters;
yourself
]
{ #category : #'*MiniDocs' }
LePictureSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LeSmaCCRewriteSnippet }
{ #category : #'*MiniDocs' }
LeSmaCCRewriteSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeSmaCCRewriteSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -7,10 +7,26 @@ LeSnippet class >> fromMetaMarkdeep: div [
metadata := STON fromString:(div xpath: '@st-data') stringValue. metadata := STON fromString:(div xpath: '@st-data') stringValue.
snippet := className asClass new. snippet := className asClass new.
snippet injectMetadataFrom: metadata. snippet injectMetadataFrom: metadata.
snippet contentFrom: div. snippet fromMarkdeep: div.
^ snippet. ^ snippet.
] ]
{ #category : #'*MiniDocs' }
LeSnippet >> metadata [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
self optionAt: 'metadata' ifAbsentPut: [ OrderedDictionary new ].
^ (self optionAt: 'metadata')
at: 'id' put: self uidString;
at: 'parent' put: self parent uid asString36;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeSnippet >> moveToPageTitled: pageName [ LeSnippet >> moveToPageTitled: pageName [
| db origin destination thisSnippet | | db origin destination thisSnippet |

View File

@ -1,5 +1,25 @@
Extension { #name : #LeTextCoderSnippetElement } Extension { #name : #LeTextCoderSnippetElement }
{ #category : #'*MiniDocs' }
LeTextCoderSnippetElement >> asLePage [
| currentSnippet newPage |
currentSnippet := self snippet.
newPage := LePage new.
newPage
title: (currentSnippet text asString trimLeft: [:char | char = $# ]) trim.
self page database
addPage: newPage.
currentSnippet allChildrenBreadthFirstDo: [:child |
child moveToPageTitled: newPage title.
].
^ newPage
]
{ #category : #'*MiniDocs' }
LeTextCoderSnippetElement >> asSnippetViewModel [
^ self snippetContent
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextCoderSnippetElement >> moveToPageTitled: pageName [ LeTextCoderSnippetElement >> moveToPageTitled: pageName [
| db origin destination | | db origin destination |

View File

@ -2,9 +2,14 @@ Extension { #name : #LeTextSnippet }
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextSnippet >> asLePage [ LeTextSnippet >> asLePage [
| page | | page title currentSnippet |
title := self contentAsString markdownHeaders associations first value.
title := (title trimBoth: [:char | char = $# ]) trimmed.
page := LePage new page := LePage new
initializeTitle: self contentAsString. initializeTitle: title.
currentSnippet := LeTextSnippet new
string: self contentAsString.
page addSnippet: currentSnippet.
self database addPage: page. self database addPage: page.
self childrenDo: [:child | self childrenDo: [:child |
child moveToPageTitled: page title child moveToPageTitled: page title
@ -14,39 +19,34 @@ LeTextSnippet >> asLePage [
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextSnippet >> contentFrom: markdeepDiv [ LeTextSnippet >> fromDictionary: anOrderedDictionary [
self
uid: (LeUID new uidString: (anOrderedDictionary at: 'id'));
parent: (anOrderedDictionary at: 'parent');
createTime: (LeTime new time: ((anOrderedDictionary at: 'created')asDateAndTime));
editTime: (LeTime new time: ((anOrderedDictionary at: 'modified') asDateAndTime));
editEmail: (anOrderedDictionary at: 'modifier');
createEmail: (anOrderedDictionary at: 'creator')
]
| sanitizedStringText metadata | { #category : #'*MiniDocs' }
metadata := STON fromString: (markdeepDiv attributes at: 'st-data'). LeTextSnippet >> fromMarkdeep: markdeepDiv [
sanitizedStringText := markdeepDiv contentString.
sanitizedStringText := sanitizedStringText allButFirst. ^ markdeepDiv asSnippetDictionary asLepiterSnippet
sanitizedStringText := sanitizedStringText allButLast. ]
self string: sanitizedStringText;
uid: (LeUID new uidString: (metadata at: 'id')); { #category : #'*MiniDocs' }
parent: (metadata at: 'parent'); LeTextSnippet >> fromString: aString [
createTime: (LeTime new time: ((metadata at: 'created')asDateAndTime));
editTime: (LeTime new time: ((metadata at: 'modified') asDateAndTime)); self
editEmail: (metadata at: 'modifier'); string: aString;
createEmail: (metadata at: 'creator') uid: LeUID new.
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextSnippet >> metadata [ LeTextSnippet >> metadata [
^ self optionAt: 'metadata' ifAbsentPut: [ self metadataInit ] ^ self metadataUpdate
]
{ #category : #'*MiniDocs' }
LeTextSnippet >> metadataInit [
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parentId;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: self createEmail asString;
at: 'modifier' put: self editEmail asString;
yourself
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -57,11 +57,23 @@ LeTextSnippet >> options [
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextSnippet >> parentId [ LeTextSnippet >> parentId [
self parent ifNil: [ ^ self ]. self parent ifNil: [ ^ self ].
(self parent isString) ifTrue: [^ self parent].
^ self parent uidString. ^ self parent uidString.
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextSnippet >> taggedWith: aString [ LeTextSnippet >> tagWith: aString [
self metadata at: 'tags' ifPresent: [ (self metadata at: 'tags') add: aString; yourself ] ifAbsentPut: [ Set new ]. self tags add: aString.
^ self metadata at: 'tags' ]
{ #category : #'*MiniDocs' }
LeTextSnippet >> withFollowingSnippets [
"I'm the same method implemented for PharoSnippets,
but present also here as a way to improve moving prose snippets from pages.
"
| snippets stop start |
snippets := self parent children asArray.
start := snippets indexOf: self.
stop := snippets size.
^ snippets copyFrom: start to: stop
] ]

View File

@ -2,45 +2,62 @@ Extension { #name : #LeTextualSnippet }
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextualSnippet >> asMarkdeep [ LeTextualSnippet >> asMarkdeep [
"Inspired by Alpine.js and Assembler CSS 'x-' properties, we are going to use
'st-' properties as a way to extend divs metadata regarding its contents."
| output | | output |
output := WriteStream on: ''. output := WriteStream on: ''.
output output
nextPutAll: '<div st-class="' , self class greaseString , '"'; nextPutAll: self metadataDiv;
nextPut: Character lf;
nextPutAll: ' st-data="' , (STON toString: self metadata) , '">';
nextPut: Character lf;
nextPutAll: self markdeepCustomOpener; nextPutAll: self markdeepCustomOpener;
nextPutAll: self contentAsString; nextPutAll: self contentAsStringAnnotated;
nextPut: Character lf; nextPut: Character lf;
nextPutAll: self markdeepCustomCloser; nextPutAll: self markdeepCustomCloser;
nextPutAll: '</div>'; nextPutAll: '</div>';
nextPut: Character lf; nextPut: Character lf;
nextPut: Character lf. nextPut: Character lf.
^ output contents ^ output contents withInternetLineEndings
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextualSnippet >> asMarkdown [ LeTextualSnippet >> asMarkdown [
| output |
output := '' writeStream.
output
nextPutAll: self contentAsStringCustomized; lf.
^ output contents
]
{ #category : #'*MiniDocs' }
LeTextualSnippet >> asMarkdownWithMetadataWrappers [
"Inspired by Alpine.js and Assembler CSS 'x-' properties, we are going to use "Inspired by Alpine.js and Assembler CSS 'x-' properties, we are going to use
'st-' properties as a way to extend divs metadata regarding its contents." 'st-' properties as a way to extend divs metadata regarding its contents."
| output | | output |
output := '' writeStream. output := '' writeStream.
output output
nextPutAll: '<div st-class="', self class asString, '"'; lf; nextPutAll: self metadataDiv;
nextPutAll: ' st-data="', (STON toString: self metadata), '">'; lf;
nextPutAll: self markdownCustomOpener; nextPutAll: self markdownCustomOpener;
nextPutAll: self contentAsStringCustomized; lf; nextPutAll: self contentAsStringCustomized; lf;
nextPutAll: self markdownCustomCloser; nextPutAll: self markdownCustomCloser;
nextPutAll: '</div>'; lf; lf. nextPutAll: '</div>'; lf; lf.
^ output contents ^ output contents withInternetLineEndings
]
{ #category : #'*MiniDocs' }
LeTextualSnippet >> contentAsStringAnnotated [
self ast ifNotNil: [ ^ self processSnippetAnnotations ].
^ self contentAsString
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextualSnippet >> contentAsStringCustomized [ LeTextualSnippet >> contentAsStringCustomized [
^ self contentAsString (self contentAsString beginsWith: '#')
ifTrue: [ ^ '#', self contentAsString ]
ifFalse: [ ^ self contentAsString ]
]
{ #category : #'*MiniDocs' }
LeTextualSnippet >> extra [
^ self optionAt: 'extra' ifAbsentPut: [ Dictionary new ]
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -66,26 +83,84 @@ LeTextualSnippet >> markdownCustomOpener [
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextualSnippet >> metadata [ LeTextualSnippet >> metadata [
^ self optionAt: 'metadata' ifAbsentPut: [ self metadataInit ] ^ self metadataUpdate
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextualSnippet >> metadataInit [ LeTextualSnippet >> metadataDiv [
| surrogate | "Inspired by Alpine.js and Assembler CSS 'x-' properties, we are going to use
self parent 'st-' properties as a way to extend divs metadata regarding its contents."
ifNil: [ surrogate := nil] "PENDING: this is repeated in several snippets. Can be abstracted up in a common object of the class hierarchy?"
ifNotNil: [ surrogate := self parent uidString ]. | output |
output := WriteStream on: ''.
output
nextPutAll: '<div st-class="' , self class greaseString , '"';
nextPut: Character lf;
nextPutAll: ' st-data="' , (STON toStringPretty: self metadata) , '">';
nextPut: Character lf.
^ output contents withInternetLineEndings.
]
{ #category : #'*MiniDocs' }
LeTextualSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new ^ OrderedDictionary new
at: 'id' put: self uidString; at: 'id' put: self uidString;
at: 'parent' put: surrogate; at: 'parent' put: (self parent ifNotNil: [self parent uidString ]);
at: 'created' put: self createTime asString; at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString; at: 'modified' put: self latestEditTime asString;
at: 'creator' put: self createEmail asString; at: 'creator' put: createEmailSanitized;
at: 'modifier' put: self editEmail asString; at: 'modifier' put: editEmailSanitized;
at: 'extra' put: self extra;
yourself yourself
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeTextualSnippet >> tags [ LeTextualSnippet >> processSnippetAnnotations [
^ self metadata at: 'tags' ifAbsentPut: [ Set new ] | exported substitutions annotations pageConfig |
annotations := self ast parts
select: [ :each | each className includesSubstring: 'AnnotationNode' ].
annotations ifEmpty: [ ^ self contentAsString ].
substitutions := OrderedDictionary new.
pageConfig := self page config.
annotations
do: [ :each |
| key type value color |
key := each source.
type := (key splitOn: ':') first copyWithoutAll: '{{'.
value := key copyFrom: type size + 4 to: key size - 2.
pageConfig
ifNil: [ color := 'default' ]
ifNotNil: [ | colors |
colors := pageConfig at: 'annotationColors' ifAbsent: [ nil ].
colors
ifNotNil: [ color := colors
at: type
ifAbsent: [ colors at: 'defaultColor' ifAbsentPut: [ 'default' ] ] ] ].
substitutions
at: key
put: '<span st-class="' , type , '" style="color:' , color , '">' , value , '</span>' ].
exported := self contentAsString.
substitutions
keysAndValuesDo: [ :k :v | exported := exported copyReplaceAll: k with: v ].
^ exported
]
{ #category : #'*MiniDocs' }
LeTextualSnippet >> sanitizeMetadata [
self options ifNil: [^ self ].
self options removeKey: 'metadata' ifAbsent: [^ self ].
self metadata keysAndValuesDo: [:k :v |
(v asString includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v asString copyWithoutXMLDelimiters)
]
]
]
{ #category : #'*MiniDocs' }
LeTextualSnippet >> tags [
^ self extra at: 'tags' ifAbsentPut: [ Set new ]
] ]

View File

@ -0,0 +1,21 @@
Extension { #name : #LeUnknownSnippet }
{ #category : #'*MiniDocs' }
LeUnknownSnippet >> metadataUpdate [
| surrogate |
self parent
ifNil: [ surrogate := nil]
ifNotNil: [
self parent isString
ifTrue: [ surrogate := self parent]
ifFalse: [ surrogate := self parent uidString ]
].
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: surrogate;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: self createEmail asString;
at: 'modifier' put: self editEmail asString;
yourself
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LeWardleyMapSnippet }
{ #category : #'*MiniDocs' }
LeWardleyMapSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeWardleyMapSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -0,0 +1,26 @@
Extension { #name : #LeWordSnippet }
{ #category : #'*MiniDocs' }
LeWordSnippet >> metadataUpdate [
| createEmailSanitized editEmailSanitized |
createEmailSanitized := self createEmail asString withoutXMLTagDelimiters.
editEmailSanitized := self editEmail asString withoutXMLTagDelimiters.
^ OrderedDictionary new
at: 'id' put: self uidString;
at: 'parent' put: self parent uuid;
at: 'created' put: self createTime asString;
at: 'modified' put: self latestEditTime asString;
at: 'creator' put: createEmailSanitized;
at: 'modifier' put: editEmailSanitized;
yourself
]
{ #category : #'*MiniDocs' }
LeWordSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -16,7 +16,7 @@ LeYoutubeReferenceSnippet >> asMarkdeep [
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeYoutubeReferenceSnippet >> metadata [ LeYoutubeReferenceSnippet >> metadata [
^ self optionAt: 'metadata' ifAbsentPut: [ self metadataInit ] ^ self optionAt: 'metadata' ifAbsentPut: [ self metadataUpdate ]
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
@ -26,12 +26,12 @@ LeYoutubeReferenceSnippet >> metadataDiv [
output output
nextPutAll: '<div st-class="' , self class greaseString , '"'; nextPutAll: '<div st-class="' , self class greaseString , '"';
nextPut: Character lf; nextPut: Character lf;
nextPutAll: ' st-data="' , (STON toString: self metadata) , '">'. nextPutAll: ' st-data="' , (STON toStringPretty: self metadata) , '">'.
^ output. ^ output contents withInternetLineEndings.
] ]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
LeYoutubeReferenceSnippet >> metadataInit [ LeYoutubeReferenceSnippet >> metadataUpdate [
| surrogate | | surrogate |
self parent self parent
ifNil: [ surrogate := nil] ifNil: [ surrogate := nil]
@ -45,3 +45,13 @@ LeYoutubeReferenceSnippet >> metadataInit [
at: 'modifier' put: self editEmail asString; at: 'modifier' put: self editEmail asString;
yourself yourself
] ]
{ #category : #'*MiniDocs' }
LeYoutubeReferenceSnippet >> sanitizeMetadata [
self metadata keysAndValuesDo: [:k :v |
(v includesAny: #($< $>))
ifTrue: [
self metadata at: k put: (v copyWithoutAll: #($< $>))
]
]
]

View File

@ -0,0 +1,33 @@
Class {
#name : #Logseq,
#superclass : #Object,
#instVars : [
'folder'
],
#category : #'MiniDocs-Model'
}
{ #category : #accessing }
Logseq >> assets [
^ self folder / 'assets'
]
{ #category : #accessing }
Logseq >> folder [
^ folder
]
{ #category : #accessing }
Logseq >> folder: aFolder [
folder := aFolder
]
{ #category : #accessing }
Logseq >> journals [
self folder / 'journals'
]
{ #category : #accessing }
Logseq >> pages [
^self folder/ 'pages'
]

View File

@ -5,7 +5,6 @@ Class {
#name : #Markdeep, #name : #Markdeep,
#superclass : #Markdown, #superclass : #Markdown,
#instVars : [ #instVars : [
'title',
'comments', 'comments',
'tail', 'tail',
'language', 'language',
@ -30,16 +29,25 @@ Markdeep class >> fromPubPubTOC: orderedDictionary folder: folder index: ordina
^ self new fromMarkdownFile: testFile. ^ self new fromMarkdownFile: testFile.
] ]
{ #category : #accessing }
Markdeep >> asMarkdownWithMetadataWrappers [
^ Markdown new
metadata: self metadata;
body: self body;
file: self markdownFile
]
{ #category : #'instance creation' } { #category : #'instance creation' }
Markdeep >> authors [ Markdeep >> authors [
self metadata at: 'authors' ifPresent: [:k | ^ '**', k, '**' ]. self metadata at: 'authors' ifAbsentPut: [ Dictionary new ].
^ ''. "self metadata at: 'authors' ifNotEmpty: [:k | ^ '**', k, '**' ]
" ^ ''.
] ]
{ #category : #'instance creation' } { #category : #'instance creation' }
Markdeep >> authorsString [ Markdeep >> authorsString [
self authors self authors
ifNil: [ ^ '' ] ifNotNil: [ ^ ' ', self authors ] ifEmpty: [ ^ '' ] ifNotEmpty: [ ^ ' ', self authors ]
] ]
{ #category : #accessing } { #category : #accessing }
@ -57,10 +65,15 @@ Markdeep >> bodyReplaceAll: original with: replacement [
self body: (self body copyReplaceAll: original with: replacement) self body: (self body copyReplaceAll: original with: replacement)
] ]
{ #category : #accessing }
Markdeep >> cleanMetadata [
metadata := nil
]
{ #category : #accessing } { #category : #accessing }
Markdeep >> commentPubPubDelimiters [ Markdeep >> commentPubPubDelimiters [
| commented openners | | commented openners |
openners := #('::: {.pub-body-component}' '::: {.editor .Prosemirror}' '::: {.pub-notes}'). openners := #('::: {.pub-body-component}' '::: pub-body-component' '::: {.editor .Prosemirror}' '::: {.pub-notes}').
commented := self body. commented := self body.
openners do: [:openner | openners do: [:openner |
commented := commented copyReplaceAll: openner with: '<!--@div-open ', openner, '-->' commented := commented copyReplaceAll: openner with: '<!--@div-open ', openner, '-->'
@ -113,7 +126,9 @@ Markdeep >> commentsSupport [
{ #category : #accessing } { #category : #accessing }
Markdeep >> config [ Markdeep >> config [
| configFile |
configFile := self folder / 'config.ston'.
configFile exists ifTrue: [ ^ config := STON fromString: configFile contents ].
^ config ifNil: [ config := Dictionary new] ^ config ifNil: [ config := Dictionary new]
] ]
@ -156,6 +171,68 @@ Markdeep >> converPubPubFootnoteBetween: footnote and: nextFootnote in: footnote
^ response contents ^ response contents
] ]
{ #category : #accessing }
Markdeep >> extractTitleFrom: docTree [
| tempTitle |
tempTitle := ((docTree children
detect: [ :node | node className = 'PPCMIndentedCode' ]) children
detect: [ :subnode | subnode text trimmed beginsWith: '**' ]) text trimmed.
self title: (tempTitle copyFrom: 3 to: tempTitle size - 2).
^ tempTitle
]
{ #category : #accessing }
Markdeep >> extractYamlMetadataFrom: documentTree [
| yamlComment response |
yamlComment := documentTree children
detect: [:node | node className = 'PPCMHtmlBlock' and: [node text trimmed beginsWith: '<!--@yaml']]
ifNone: [ ^ nil ].
response := '' writeStream.
yamlComment children allButFirst allButLast do: [:each |
response nextPutAll: each text; cr
].
^ {YAML2JSON fromString: response contents . yamlComment }
]
{ #category : #accessing }
Markdeep >> file: aFileReference [
file := aFileReference.
self fillInContentsFrom: aFileReference
]
{ #category : #accessing }
Markdeep >> fillInContentsFrom: aFileReference [
| docTree docTreeChildren headTree bodyStartLine bodyEndLine contentLines rawMetadata |
aFileReference exists ifFalse: [ ^ self ].
docTree := (Markdown new contents: aFileReference contents) documentTree.
docTreeChildren := docTree children.
headTree := docTreeChildren
detect: [ :node |
node className = 'PPCMParagraph'
and: [ (node children detect: [ :subnode | subnode text = '<head>' ]) isNotNil ] ]
ifNone: [ ^self ].
headTree children allButFirst allButLast
do: [ :node | node className = 'PPCMHtml' ifTrue: [ self head add: node text ] ].
self head: self head asSet asOrderedCollection.
rawMetadata := (self extractYamlMetadataFrom: docTree).
rawMetadata ifNotNil: [self metadata: rawMetadata first].
self title ifNil: [
self title: (self metadata at: 'title' ifAbsent: [self extractTitleFrom: docTree]).
self title: (self title trimBoth: [ :char | char = $" ]).
self metadata at: 'title' put: self title].
contentLines := self file contents lines.
bodyStartLine := (contentLines
detectIndex: [ :line | line includesSubstring: '<!--@yaml' ] ifNone: [ ^ self ]) + rawMetadata second children size.
bodyEndLine := contentLines detectIndex: [:line | line includesSubstring: '<!-- Markdeep'] ifNone: [ 0 ].
self body: (contentLines copyFrom: bodyStartLine to: bodyEndLine - 1 ) asStringWithCr.
^ self .
]
{ #category : #accessing }
Markdeep >> folder [
^ self file parent
]
{ #category : #utilities } { #category : #utilities }
Markdeep >> fontAwesomeHeader [ Markdeep >> fontAwesomeHeader [
"I enable the font awesome support in the document header" "I enable the font awesome support in the document header"
@ -167,6 +244,7 @@ Markdeep >> fontAwesomeHeader [
Markdeep >> fromMarkdownFile: aFileReference [ Markdeep >> fromMarkdownFile: aFileReference [
"I create a Markdeep document from a given Markdown file." "I create a Markdeep document from a given Markdown file."
self processMarkdownFor: aFileReference. self processMarkdownFor: aFileReference.
self file: aFileReference, 'html'.
^ self. ^ self.
] ]
@ -191,8 +269,11 @@ Markdeep >> gtTextFor: aView [
{ #category : #accessing } { #category : #accessing }
Markdeep >> head [ Markdeep >> head [
^ head ifNil: [ head := OrderedCollection new.
head add: self fontAwesomeHeader; yourself ] ^ head ifNil: [
head := OrderedCollection new.
head add: self fontAwesomeHeader; yourself.
].
] ]
{ #category : #accessing } { #category : #accessing }
@ -213,6 +294,11 @@ Markdeep >> headContents [
nextPutAll: line; nextPutAll: line;
nextPut: Character lf nextPut: Character lf
]. ].
self metadata keysAndValuesDo: [:k :v |
k = 'lang'
ifTrue: [ stream nextPutAll: ' <meta lang="', v,'">'; cr. ]
ifFalse: [ stream nextPutAll: ' <meta name="', k, '" content="', v,'">'; cr. ]
].
stream stream
nextPutAll: '</head>'; nextPutAll: '</head>';
nextPut: Character lf. nextPut: Character lf.
@ -248,12 +334,15 @@ Markdeep >> markdownFile [
{ #category : #accessing } { #category : #accessing }
Markdeep >> markdownFile: aFileReference [ Markdeep >> markdownFile: aFileReference [
"Where the Mardown file associated with me is stored. Used for sync. and import/export purposes." "Where the Mardown file associated with me is stored. Used for sync. and import/export purposes."
self config at: 'markdownFile' put: aFileReference self file: aFileReference, 'html'
] ]
{ #category : #'instance creation' } { #category : #'instance creation' }
Markdeep >> metadata [ Markdeep >> metadata [
^ metadata ifNil: [ metadata := OrderedDictionary new ] metadata ifNil: [^ metadata := OrderedDictionary new ].
(metadata isNil and: [ self file contents isNil ])
ifTrue: [ metadata := OrderedDictionary new ].
^ metadata
] ]
{ #category : #accessing } { #category : #accessing }
@ -294,20 +383,13 @@ Markdeep >> options [
] ]
] ]
{ #category : #printing }
Markdeep >> printOn: aStream [
super printOn: aStream.
aStream
nextPutAll: '( ', self title accentedCharactersCorrection, ' )'
]
{ #category : #'instance creation' } { #category : #'instance creation' }
Markdeep >> processMarkdownFor: aFileReference [ Markdeep >> processMarkdownFor: aFileReference [
"comment stating purpose of message" "comment stating purpose of message"
| markdownContent | | markdownContent |
self file: aFileReference, 'html'. self file: aFileReference, 'html'.
markdownContent := Markdown fromFile: aFileReference. markdownContent := Markdown fromFile: aFileReference.
self metadata: markdownContent yamlMetadata. self metadata: markdownContent metadataAsYAML.
self body: (markdownContent commentYAMLMetadata contents). self body: (markdownContent commentYAMLMetadata contents).
] ]
@ -351,7 +433,8 @@ Markdeep >> pubPubFootnotesLinesRangeFor: contentSection [
| beginningLine endingLine | | beginningLine endingLine |
beginningLine := contentSection lines size + 1. beginningLine := contentSection lines size + 1.
contentSection lines doWithIndex: [:line :i | contentSection lines doWithIndex: [:line :i |
(line includesSubstring: '::: {.pub-notes}') ifTrue: [ beginningLine := i ]. ((line includesSubstring: '::: {.pub-notes}') or: [line includesSubstring: '::: pub-notes'])
ifTrue: [ beginningLine := i ].
(i > beginningLine and: [ line beginsWith: ':::' ]) (i > beginningLine and: [ line beginsWith: ':::' ])
ifTrue: [ ifTrue: [
endingLine := i. endingLine := i.
@ -465,7 +548,8 @@ please visit the HTML version or download the PDF.
{ #category : #accessing } { #category : #accessing }
Markdeep >> removeCCByLicenseDiv [ Markdeep >> removeCCByLicenseDiv [
| licenseDiv| | licenseDiv|
licenseDiv := '<div> licenseDiv := '
<div>
**License:** [Creative Commons Attribution 4.0 International License **License:** [Creative Commons Attribution 4.0 International License
(CC-BY 4.0)](https://creativecommons.org/licenses/by/4.0/) (CC-BY 4.0)](https://creativecommons.org/licenses/by/4.0/)
@ -565,7 +649,7 @@ Markdeep >> tail: anObject [
{ #category : #accessing } { #category : #accessing }
Markdeep >> title [ Markdeep >> title [
^ title ifNil: [ title := self metadata at: 'title' ifAbsent: [ '' ] ] ^ title
] ]
{ #category : #accessing } { #category : #accessing }

View File

@ -9,7 +9,8 @@ Class {
#superclass : #MarkupFile, #superclass : #MarkupFile,
#instVars : [ #instVars : [
'metadata', 'metadata',
'body' 'body',
'title'
], ],
#category : #'MiniDocs-Core' #category : #'MiniDocs-Core'
} }
@ -19,20 +20,22 @@ Markdown class >> fromFile: aFileReference [
^ self new fromFile: aFileReference ^ self new fromFile: aFileReference
] ]
{ #category : #utilities }
Markdown class >> yamlMetadataDelimiter [
^ '---'
]
{ #category : #accessing } { #category : #accessing }
Markdown >> asMarkdeep [ Markdown >> asMarkdeep [
^ Markdeep new ^ Markdeep new
body: self body; body: self body;
markdownFile: self file;
commentYAMLMetadata commentYAMLMetadata
] ]
{ #category : #accessing }
Markdown >> asMarkdownTiddler [
^ Tiddler new
title: self title;
text: self contents;
type: 'text/x-markdown';
created: Tiddler nowLocal.
]
{ #category : #accessing } { #category : #accessing }
Markdown >> body [ Markdown >> body [
^ body ^ body
@ -46,7 +49,7 @@ Markdown >> body: aString [
{ #category : #operation } { #category : #operation }
Markdown >> commentYAMLMetadata [ Markdown >> commentYAMLMetadata [
| newContents | | newContents |
self detectYAMLMetadata ifFalse: [ ^ self ]. self contents detectYAMLMetadata ifFalse: [ ^ self ].
newContents := '' writeStream. newContents := '' writeStream.
newContents nextPutAll: '<!--@yaml'; lf. newContents nextPutAll: '<!--@yaml'; lf.
newContents nextPutAll: self yamlMetadataString. newContents nextPutAll: self yamlMetadataString.
@ -63,48 +66,27 @@ Markdown >> containsYAMLMetadataClosing [
{ #category : #accessing } { #category : #accessing }
Markdown >> contents [ Markdown >> contents [
^ body | response metadataString |
response := WriteStream on: ''.
metadataString := self metadataAsYAML
ifEmpty: [ '' ]
ifNotEmpty: [ '---', String cr, self metadataAsYAML, String cr, '---', String cr ].
response
nextPutAll: metadataString;
nextPutAll: (self body ifNil: [ '' ]).
^ response contents withInternetLineEndings
] ]
{ #category : #accessing } { #category : #accessing }
Markdown >> contents: anObject [ Markdown >> contents: aString [
body := anObject body := aString
]
{ #category : #accessing }
Markdown >> contentsWithoutYAMLMetadata [
| newContents |
self detectYAMLMetadata ifFalse: [ ^ self ].
newContents := '' writeStream.
(self lines copyFrom: self yamlMetadataClosingLineNumber + 2 to: self lines size) do: [ :line |
newContents nextPutAll: line; crlf ].
^ newContents contents.
]
{ #category : #operation }
Markdown >> deleteYAMLMetadata [
| newContents |
self detectYAMLMetadata ifFalse: [ ^ self ].
newContents := '' writeStream.
(self lines copyFrom: self yamlMetadataClosingLineNumber + 1 to: self lines size) do: [ :line |
newContents nextPutAll: line; lf;lf ].
^ newContents contents.
]
{ #category : #utilities }
Markdown >> detectYAMLMetadata [
| lines |
lines := self lines.
^ self startsWithYAMLMetadataDelimiter
and: [ lines allButFirst
detect: [ :currentLine | currentLine beginsWith: self class yamlMetadataDelimiter ]
ifFound: [ ^ true ] ifNone: [ ^ false ] ]
] ]
{ #category : #accessing } { #category : #accessing }
Markdown >> documentTree [ Markdown >> documentTree [
| parser| | parser|
parser := PPCommonMarkBlockParser new parse: self contents. self contents ifNil: [^ nil].
parser := PPCommonMarkBlockParser new parse: self body.
^ parser accept: CMBlockVisitor new ^ parser accept: CMBlockVisitor new
] ]
@ -121,7 +103,12 @@ Markdown >> exportAsFileOn: aFileReference [
aFileReference ensureDelete. aFileReference ensureDelete.
aFileReference exists ifFalse: [ aFileReference ensureCreateFile ]. aFileReference exists ifFalse: [ aFileReference ensureCreateFile ].
aFileReference writeStreamDo: [ :stream | aFileReference writeStreamDo: [ :stream |
stream nextPutAll: self contents ]. stream nextPutAll: self contents withInternetLineEndings ].
]
{ #category : #accessing }
Markdown >> exportAsHTML [
^ Pandoc markdownToHtml: self file
] ]
{ #category : #operation } { #category : #operation }
@ -155,7 +142,7 @@ Markdown >> exportMetadataAsYaml [
{ #category : #accessing } { #category : #accessing }
Markdown >> file [ Markdown >> file [
^ file ifNil: [ file := FileLocator temp / (NanoID generate asLowercase, '.md') ] ^ file ifNil: [ file := FileLocator temp / 'temporalMarkdeep.md' ]
] ]
{ #category : #accessing } { #category : #accessing }
@ -166,15 +153,24 @@ Markdown >> file: aFileReference [
{ #category : #'instance creation' } { #category : #'instance creation' }
Markdown >> fromFile: aFileReference [ Markdown >> fromFile: aFileReference [
self contents: aFileReference contents. self fromString: aFileReference contents.
self file: aFileReference. self file: aFileReference.
] ]
{ #category : #'instance creation' } { #category : #'instance creation' }
Markdown >> fromString: markdownString [ Markdown >> fromString: markdownString [
self contents: markdownString. | yamlMetadataRaw bodyTemp |
self populateMetadata. yamlMetadataRaw := (YamlHeaderParser parse: markdownString).
self contents: self contentsWithoutYAMLMetadata bodyTemp := '' writeStream.
(yamlMetadataRaw removeKey: 'body') do: [:paragraph |
bodyTemp nextPutAll: paragraph; cr; cr
].
self body: bodyTemp contents withInternetLineEndings.
(yamlMetadataRaw sanitizeMultilineValuesWith: markdownString)
ifNotNil: [
self metadata
ifEmpty: [ self metadata: yamlMetadataRaw ]
ifNotEmpty: [ self metadata at: 'hedgeDoc' put: yamlMetadataRaw ]].
] ]
{ #category : #accessing } { #category : #accessing }
@ -185,8 +181,17 @@ Markdown >> gtTextFor: aView [
text: [ self contents ] text: [ self contents ]
] ]
{ #category : #accessing }
Markdown >> headerAsTitle [
| headerNode |
headerNode := self documentTree children
detect: [ :node | node className = 'PPCMHeader' and: [ node level = 1 ] ] ifNone: [ ^ 'Untitled' ].
^ headerNode text
]
{ #category : #utilities } { #category : #utilities }
Markdown >> lines [ Markdown >> lines [
self file ifNotNil: [^ self file contents lines ].
^ self contents lines. ^ self contents lines.
] ]
@ -195,6 +200,7 @@ Markdown >> metadata [
^ metadata ifNil: [ metadata := Dictionary new]. ^ metadata ifNil: [ metadata := Dictionary new].
] ]
{ #category : #accessing } { #category : #accessing }
@ -203,6 +209,12 @@ Markdown >> metadata: rawMeta [
metadata := rawMeta metadata := rawMeta
] ]
{ #category : #accessing }
Markdown >> metadataAsYAML [
self metadata isEmptyOrNil ifTrue: [ ^ '' ].
^ (YQ jsonToYaml: self metadata) accentedCharactersCorrection
]
{ #category : #persistence } { #category : #persistence }
Markdown >> notifyExportAsFileOn: aFileReference [ Markdown >> notifyExportAsFileOn: aFileReference [
self exportAsFileOn: aFileReference. self exportAsFileOn: aFileReference.
@ -211,62 +223,25 @@ Markdown >> notifyExportAsFileOn: aFileReference [
] ]
{ #category : #accessing } { #category : #accessing }
Markdown >> populateMetadata [ Markdown >> options [
| rawMeta | ^ self metadata at: 'options' ifAbsentPut: [ self defaultOptions]
rawMeta := MiniDocs yamlToJson: self yamlMetadataString.
rawMeta associationsDo: [ :assoc |
assoc value = 'false' ifTrue: [ assoc value: false ].
assoc value = 'true' ifTrue: [ assoc value: true ] ].
self metadata: rawMeta
] ]
{ #category : #accessing } { #category : #accessing }
Markdown >> printOn: aStream [ Markdown >> printOn: aStream [
| response |
super printOn: aStream. super printOn: aStream.
response := self title ifNil: [ 'Untitled' ].
aStream aStream
nextPutAll: '( ', (self metadata at: 'title' ifAbsent: ['untitled']), ' )' nextPutAll: '( ', response , ' )'
]
{ #category : #utilities }
Markdown >> startsWithYAMLMetadataDelimiter [
^ self lines first beginsWith: self class yamlMetadataDelimiter
] ]
{ #category : #accessing } { #category : #accessing }
Markdown >> yamlMetadata [ Markdown >> title [
^ MiniDocs yamlToJson: self yamlMetadataString ^ title ifNil: [ title:= self headerAsTitle ]
] ]
{ #category : #utilities } { #category : #accessing }
Markdown >> yamlMetadataClosingLineNumber [ Markdown >> title: aString [
"I return the line where the closing of the YAML metadata occurs or 0 if no closing is found." title := aString
self startsWithYAMLMetadataDelimiter ifFalse: [ ^ self ].
self lines allButFirst doWithIndex: [ :currentLine :i |
(currentLine beginsWith: self class yamlMetadataDelimiter) ifTrue: [ ^ i + 1 ]]
]
{ #category : #operation }
Markdown >> yamlMetadataString [
| output yamlLines |
self detectYAMLMetadata ifFalse: [ ^ nil ].
yamlLines := self lines copyFrom: 2 to: self yamlMetadataClosingLineNumber - 1.
output := '' writeStream.
yamlLines do: [ :line |
output
nextPutAll: line;
nextPut: Character lf. ].
^ output contents
]
{ #category : #utilities }
Markdown >> yamlMetadataStringWithDelimiters [
| output |
self yamlMetadataString ifNil: [ ^ nil ].
output := String new writeStream.
output nextPutAll: self class yamlMetadataDelimiter; cr.
output nextPutAll: self yamlMetadataString.
output nextPutAll: self class yamlMetadataDelimiter; cr.
^ output contents.
] ]

View File

@ -11,13 +11,16 @@ Class {
} }
{ #category : #persistence } { #category : #persistence }
MarkupFile class >> exportAsFileOn: aFileReferenceOrFileName containing: text [ MarkupFile class >> exportAsFileOn: aFileReferenceOrFileName containing: anObject [
| file | | file preprocessed |
file := aFileReferenceOrFileName asFileReference. file := aFileReferenceOrFileName asFileReference.
file ensureDelete. file ensureDelete.
file exists ifFalse: [ file ensureCreateFile ]. file exists ifFalse: [ file ensureCreateFile ].
(#('String' 'ByteString' 'WideString') includes: anObject className )
ifTrue: [ preprocessed := anObject ]
ifFalse: [preprocessed := STON toStringPretty: anObject ].
file writeStreamDo: [ :stream | file writeStreamDo: [ :stream |
stream nextPutAll: text withUnixLineEndings]. stream nextPutAll: preprocessed ].
self inform: 'Exported as: ', String cr, file fullName. self inform: 'Exported as: ', String cr, file fullName.
^ file ^ file
] ]

View File

@ -10,7 +10,7 @@ Class {
{ #category : #accessing } { #category : #accessing }
MiniDocs class >> altKeys [ MiniDocs class >> altKeys [
^ BlAlternativeCombination new ^ BlAlternativeCombination new
key: (BlSingleKeyCombination key:BlKeyboardKey altLeft) combination: (BlSingleKeyCombination key:BlKeyboardKey altLeft)
or: (BlSingleKeyCombination key:BlKeyboardKey altRight) or: (BlSingleKeyCombination key:BlKeyboardKey altRight)
] ]
@ -34,15 +34,17 @@ MiniDocs class >> altShiftRightCombo [
{ #category : #accessing } { #category : #accessing }
MiniDocs class >> appFolder [ MiniDocs class >> appFolder [
| tempFolder userDataFolder | | tempFolder |
userDataFolder := Smalltalk os isWindows tempFolder := ExoRepo userDataFolder / 'Mutabit' / 'MiniDocs'.
ifTrue: [ FileLocator home / 'AppData' / 'Local' ]
ifFalse: [ FileLocator userData ].
tempFolder := userDataFolder / 'Mutabit' / 'MiniDocs'.
tempFolder exists ifFalse: [ tempFolder ensureCreateDirectory ]. tempFolder exists ifFalse: [ tempFolder ensureCreateDirectory ].
^ tempFolder ^ tempFolder
] ]
{ #category : #accessing }
MiniDocs class >> exportAsSton: anObject on: aFileReference [
MarkupFile exportAsFileOn: aFileReference containing: (STON toStringPretty: anObject) withInternetLineEndings
]
{ #category : #accessing } { #category : #accessing }
MiniDocs class >> importGrafoscopioFile: aFileReference [ MiniDocs class >> importGrafoscopioFile: aFileReference [
@ -87,7 +89,7 @@ MiniDocs class >> keyboardShortcutsRemapping [
{ #category : #accessing } { #category : #accessing }
MiniDocs class >> shiftKeys [ MiniDocs class >> shiftKeys [
^ BlAlternativeCombination new ^ BlAlternativeCombination new
key: (BlSingleKeyCombination key:BlKeyboardKey shiftLeft) combination: (BlSingleKeyCombination key:BlKeyboardKey shiftLeft)
or: (BlSingleKeyCombination key:BlKeyboardKey shiftRight) or: (BlSingleKeyCombination key:BlKeyboardKey shiftRight)
] ]
@ -95,6 +97,7 @@ MiniDocs class >> shiftKeys [
MiniDocs class >> yamlToJson: yamlString [ MiniDocs class >> yamlToJson: yamlString [
"This method uses a external binary written in Nim, as the native Pharo parser for YAML, written in PetitParser, "This method uses a external binary written in Nim, as the native Pharo parser for YAML, written in PetitParser,
was less robust and unable to parse correctly the same strings as the external one." was less robust and unable to parse correctly the same strings as the external one."
yamlString ifNil: [ ^ Dictionary new ].
self yamlToJsonBinary exists ifFalse: [ self installYamlToJson ]. self yamlToJsonBinary exists ifFalse: [ self installYamlToJson ].
OSSUnixSubprocess new OSSUnixSubprocess new
@ -102,7 +105,7 @@ MiniDocs class >> yamlToJson: yamlString [
arguments: {yamlString}; arguments: {yamlString};
redirectStdout; redirectStdout;
runAndWaitOnExitDo: [ :process :outString | runAndWaitOnExitDo: [ :process :outString |
^ (STONJSON fromString: outString allButFirst) first ^ (STONJSON fromString: outString allButFirst accentedCharactersCorrection) first
] ]
] ]

View File

@ -39,7 +39,7 @@ MiniDocsServer class >> restart [
{ #category : #accessing } { #category : #accessing }
MiniDocsServer class >> singleton [ MiniDocsServer class >> singleton [
^ singleton ^ singleton ifNil: [ singleton := MiniDocsServer teapot ]
] ]
{ #category : #accessing } { #category : #accessing }

View File

@ -1,55 +0,0 @@
"
I'm run an implementation of the [Nano ID](https://github.com/ai/nanoid) tiny, secure URL-friendly unique string ID generator via its [Nim implementation](https://github.com/icyphox/nanoid.nim).
The Nim script has hard coded:
* a [base 58 encoding](https://medium.com/concerning-pharo/understanding-base58-encoding-23e673e37ff6) alphabet to avoid similar looking letter and the use of non-alphanumeric characters.
* a 12 characters length output, which gives [a pretty low probability collision](https://zelark.github.io/nano-id-cc/) for the previous alphabet:
~616 years needed, in order to have a 1% probability of at least one collision at a speed of 1000 IDs per hour.
This is more than enough for our unique IDs applications, mostly in the documentation context,
which consists of hand crafted and/or programmatically produced notes ,
for example in data narratives, book(lets) and TiddlyWiki tiddlers of tens or hundreds of notes at most,
unevenly produced between hours, days and/or weeks..
The `External` tag is related on its dependency on other programming languages and frameworks,
though the dependency should be loaded by just loading a small binary with no dependencies.
"
Class {
#name : #NanoID,
#superclass : #Object,
#category : #'MiniDocs-External'
}
{ #category : #accessing }
NanoID class >> binaryFile [
^ MiniDocs appFolder / self scriptSourceCode basenameWithoutExtension
]
{ #category : #accessing }
NanoID class >> generate [
self binaryFile exists ifFalse: [ NanoID install].
OSSUnixSubprocess new
command: self binaryFile fullName;
redirectStdout;
redirectStdout;
runAndWaitOnExitDo: [ :process :outString | ^ outString copyWithoutAll: (Character lf asString) ]
]
{ #category : #accessing }
NanoID class >> install [
"For the moment, only Gnu/Linux and Mac are supported.
IMPORTANT: Nimble, Nim's package manager should be installed, as this process doesn't verify its proper installation."
self binaryFile exists ifTrue: [ ^ MiniDocs appFolder ].
Nimble install: 'nanoid'.
OSSUnixSubprocess new
command: 'nim';
arguments: {'c'. self scriptSourceCode fullName};
runAndWaitOnExitDo: [ :process :outString |
(self scriptSourceCode parent / (self scriptSourceCode) basenameWithoutExtension) moveToPageTitled: MiniDocs appFolder asFileReference.
^ MiniDocs appFolder ]
]
{ #category : #accessing }
NanoID class >> scriptSourceCode [
^ FileLocator image parent / 'pharo-local/iceberg/Offray/MiniDocs/src/nanoIdGen.nim'
]

View File

@ -1,82 +0,0 @@
"
I'm a helper class modelling the common uses of the Nim's [Nimble package manager](https://github.com/nim-lang/nimble).
This was evolved in the context of the [Grafoscopio](mutabit.com/grafoscopio/en.html) community exploration and prototyping of interactive documentation.
"
Class {
#name : #Nimble,
#superclass : #Object,
#category : #'MiniDocs-External'
}
{ #category : #accessing }
Nimble class >> detect: packageName [
^ self installed
detect: [ :dependency | dependency beginsWith: packageName ]
ifFound: [ ^ true ]
ifNone: [ ^ false ]
]
{ #category : #accessing }
Nimble class >> install: packageName [
(self detect: packageName) ifTrue: [ ^ self ].
self installPackagesList.
OSSUnixSubprocess new
command: 'nimble';
arguments: {'install'.
packageName};
redirectStdout;
redirectStderr;
runAndWaitOnExitDo: [ :process :outString :errString |
process isSuccess
ifTrue: [ Transcript show: 'Command exited correctly with output: ', outString. ]
ifFalse: [
^ 'Command exit with error status: ', process exitStatusInterpreter printString, String cr,
'Stderr contents: ', errString.
]
]
]
{ #category : #accessing }
Nimble class >> installPackagesList [
(FileLocator home / '.nimble' / 'packages_official.json') exists
ifTrue: [ ^ self ].
OSSUnixSubprocess new
command: 'nimble';
arguments: #('refresh');
redirectStdout;
runAndWaitOnExitDo: [ :process :outString | ^ outString ]
]
{ #category : #accessing }
Nimble class >> installed [
Smalltalk os isWindows
ifTrue: [ | process |
process := GtExternalProcessBuilder new
command: 'nimble.exe';
args: #('list' '--installed');
output.
^ process stdout lines ].
OSSUnixSubprocess new
command: 'nimble';
arguments: #('list' '--installed');
redirectStdout;
redirectStderr;
runAndWaitOnExitDo: [ :process :outString :errString |
process isSuccess
ifTrue: [ ^ outString lines ];
ifFalse: [ ^ nil ]
]
]
{ #category : #accessing }
Nimble class >> version [
OSSUnixSubprocess new
command: 'nimble';
arguments: #('--version');
redirectStdout;
runAndWaitOnExitDo: [ :process :outString | ^ outString ]
]

View File

@ -0,0 +1,100 @@
Extension { #name : #OrderedDictionary }
{ #category : #'*MiniDocs' }
OrderedDictionary >> addErrata: noteString [
self errata add: noteString
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> asLepiterSnippet [
| response |
self at: 'className' ifAbsent: [ ^ nil ].
response := (self at: 'className') asClass new.
[ response fromDictionary: self ] onErrorDo: [ ].
[ response fromString: (self at: 'content') ] onErrorDo: [ ].
self at: 'origin' ifPresent: [ response metadata at: 'origin' put: (self at: 'origin') ].
self at: 'errata' ifPresent: [ response metadata at: 'errata' put: (self at: 'errata') ].
^ response
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> asYAML [
^ (YQ jsonToYaml: self) accentedCharactersCorrection.
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> errata [
^ self at: 'errata' ifAbsentPut: [ OrderedCollection new]
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> redefineTimestampsBefore: dateAndTime [
self at: 'modified' put: dateAndTime asDateAndTime.
self at: 'created' put: dateAndTime asDateAndTime - 1 second.
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> replaceNilsWith: aCharacter [
self associationsDo: [:each |
each value ifNil: [self at: each key put: aCharacter].
each value isDictionary ifTrue: [each value replaceNilsWith: aCharacter].
each value isArray ifTrue: [ | newArray|
newArray := (each value asDataSeries replaceNilsWith: aCharacter) asArray.
self at: each key put: newArray
]
]
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> replaceWithUniqueNilsAndBooleansStartingAt: anInteger [
| totalNils shortUID |
totalNils := self flattened asDataSeries countNils.
shortUID := [NanoID generate copyFrom: 1 to: 3].
self associations doWithIndex: [:assoc :i | | subIndex |
subIndex := anInteger asString, '-', i asString.
assoc value
ifNil: [ self at: assoc key put: 'nil-', subIndex ].
assoc value isBoolean
ifTrue: [ self at: assoc key put: assoc value asString, '-', subIndex ].
assoc value isDictionary ifTrue: [assoc replaceWithUniqueNilsAndBooleansStartingAt: i].
assoc value isArray
ifTrue: [ self at: assoc key put: (assoc value replaceWithUniqueNilsAndBooleans)]
]
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> sanitizeMultilineValuesWith: aString [
| toSanitize response |
toSanitize := OrderedCollection new.
response := OrderedCollection new.
self keysAndValuesDo: [:k :v |
(v isString and: [v lines size > 1])
ifTrue: [
aString lines
detect: [:line | line includesSubstring: k ]
ifFound: [:line | | sanitized|
sanitized := (line withoutPrefix: k, ':'), String cr,
v indentedWithExtraSpaces: 4.
self at: k put: sanitized ]
]
].
]
{ #category : #'*MiniDocs' }
OrderedDictionary >> treeView [
| view |
view := GtMondrian new.
view nodes
stencil: [ :x |
BlElement new
border: (BlBorder paint: Color black);
geometry: BlEllipseGeometry new;
layout: (BlLinearLayout new alignCenter);
addChild: (BlTextElement text: (x asRopedText fontSize: 10)) ];
with: (self flatCollectAsSet: #yourself) , self keys.
view edges
stencil: [ :x | BlLineElement new border: (BlBorder paint: (Color blue alpha: 0.5) width: 4) ];
connect: self associations from: #key toAll: #value.
view layout tree.
^ view
]

View File

@ -0,0 +1,162 @@
"
I model the interaction between Pandoc and Grafoscopio.
"
Class {
#name : #Pandoc,
#superclass : #Object,
#classInstVars : [
'executable'
],
#category : #'MiniDocs-Core'
}
{ #category : #'*MiniDocs' }
Pandoc class >> convertString: aString from: inputFormat to: outputFormat [
OSSUnixSubprocess new
shellCommand: 'echo "', aString , '" | pandoc -f ', inputFormat,' -t ', outputFormat;
redirectStdout;
runAndWaitOnExitDo: [ :command :outString |
^ outString
].
]
{ #category : #'as yet unclassified' }
Pandoc class >> downloadLuaFilters [
self luaFilters do: [ :filter | | filterUrl |
filterUrl := filter asUrl.
(FileLocator temp asFileReference / (filterUrl segments last)) exists
ifFalse: [
ZnClient new
url: filterUrl;
downloadTo: FileLocator temp ] ]
]
{ #category : #accessing }
Pandoc class >> executable [
^ executable ifNil: [ self executableLocation ]
]
{ #category : #accessing }
Pandoc class >> executable: aFileReference [
executable := aFileReference
]
{ #category : #accessing }
Pandoc class >> executableLocation [
| location |
location := '/usr/bin/pandoc'.
location asFileReference exists
ifTrue: [ ^ location ]
ifFalse: [ self definePandocExecutable ]
]
{ #category : #utility }
Pandoc class >> extractImagesInUnixFor: aFileReference withFilter: aLuaFilter [
"I use Pandoc Lua scripting capabilities to extract al images links in aFileReference"
OSSUnixSubprocess new
command: 'pandoc';
arguments: {aFileReference fullName . '--lua-filter=',aLuaFilter fullName };
redirectStdout;
redirectStderr;
runAndWaitOnExitDo: [ :process :outString :errString |
process isSuccess
ifTrue: [
^ ((Soup fromString: outString) findAllTags: 'td') collect: [ :each | each next ] ]
ifFalse: [
"OSSUnixProcessExitStatus has a nice #printOn: "
Transcript show: 'Command exit with error status: ', process exitStatusInterpreter printString; cr.
Transcript show: 'Stderr contents: ', errString.
]
]
]
{ #category : #accessing }
Pandoc class >> htmlStringToMarkdown: aString [
OSSUnixSubprocess new
shellCommand: 'echo "', aString , '" | pandoc -f markdown -t html';
redirectStdout;
runAndWaitOnExitDo: [ :command :outString |
^ outString
].
]
{ #category : #converters }
Pandoc class >> htmlToMarkdown: inputFile [
| outputFile |
outputFile := FileLocator temp / 'body.md'.
outputFile ensureDelete.
outputFile ensureCreateFile.
OSSUnixSubprocess new
command: 'pandoc';
arguments: {'-f'. 'html'. '-t'. 'markdown'. '--atx-headers'. inputFile fullName.
'--output'. outputFile fullName };
redirectStdout;
redirectStderr;
runAndWaitOnExitDo: [ :process :outString :errString |
process isSuccess
ifTrue: [ ^ outputFile contents ]
ifFalse: [ ^inputFile contents ]
]
]
{ #category : #'as yet unclassified' }
Pandoc class >> listImagesFrom: aFileReference [
"I provide a list of all images contained in aFile."
| filter commandString outputString |
filter := FileLocator temp asFileReference / 'image-links.lua'.
filter exists
ifFalse: [ self downloadLuaFilters ].
commandString := 'pandoc ' , aFileReference fullName
, ' --lua-filter=' , filter fullName.
^ self extractImagesInUnixFor: aFileReference withFilter: filter
]
{ #category : #utility }
Pandoc class >> luaFilters [
"I define the location of set of scripts, that allows to change the default behaviour of Pandoc
and/or the processing of supported markup languages.
For more information about Lua filters see:
https://pandoc.org/lua-filters.html
"
| filters |
filters := OrderedCollection new.
filters
add: 'http://mutabit.com/repos.fossil/dataweek/doc/tip/Artefactos/Scripts/image-links.lua'.
^ filters
]
{ #category : #converters }
Pandoc class >> markdownToHtml: inputFile [
(Smalltalk os isUnix or: [ Smalltalk os isMacOS ]) ifTrue: [ ^ self markdownToHtmlOnUnix: inputFile ].
Smalltalk os isWindows ifTrue: [ ^ self markdownToHtmlOnWindows: inputFile ].
]
{ #category : #converters }
Pandoc class >> markdownToHtmlOnUnix: inputFile [
| outputFile |
outputFile := inputFile parent / (inputFile basenameWithoutExtension , '.html').
outputFile ensureDelete.
outputFile ensureCreateFile.
GtSubprocessWithInMemoryOutput new
shellCommand: 'pandoc -f markdown+startnum+task_lists --standalone -t html ', inputFile fullName, ' --output ', outputFile fullName;
runAndWait;
stdout.
^ outputFile.
]
{ #category : #converters }
Pandoc class >> markdownToHtmlOnWindows: inputFile [
"ToDo: This command still doesn't receive any arguments."
^ (LibC resultOfCommand: 'pandoc ', inputFile fullName) correctAccentedCharacters.
]

View File

@ -0,0 +1,11 @@
Extension { #name : #Pandoc }
{ #category : #'*MiniDocs' }
Pandoc class >> convertString: aString from: inputFormat to: outputFormat [
OSSUnixSubprocess new
shellCommand: 'echo "', aString , '" | pandoc -f ', inputFormat,' -t ', outputFormat;
redirectStdout;
runAndWaitOnExitDo: [ :command :outString |
^ outString
].
]

View File

@ -1,139 +0,0 @@
Class {
#name : #PubPub,
#superclass : #Object,
#instVars : [
'address',
'tableOfContents',
'titles',
'folder',
'currentLanguage',
'languages'
],
#category : #'MiniDocs-Model'
}
{ #category : #accessing }
PubPub >> addTableOfContents: anOrderedDictionary [
self tableOfContents
at: (self currentLanguage) put: anOrderedDictionary;
yourself
]
{ #category : #accessing }
PubPub >> addTitle: aString [
self titles
at: (self currentLanguage) put: aString
]
{ #category : #accessing }
PubPub >> address [
^ address
]
{ #category : #accessing }
PubPub >> address: anUrl [
address := anUrl
]
{ #category : #accessing }
PubPub >> currentLanguage [
^ currentLanguage
]
{ #category : #accessing }
PubPub >> currentLanguage: twoLettersInISO639_1 [
currentLanguage := twoLettersInISO639_1
]
{ #category : #accessing }
PubPub >> defaultTitle [
^ self titles associations first value
]
{ #category : #accessing }
PubPub >> downloadContents [
| workingDirectory |
workingDirectory := self folder / self currentLanguage / 'book'.
self tableOfContents keysAndValuesDo: [ :name :chapterAddress | |currentFileName|
currentFileName := name, '--', chapterAddress, '.md'.
(workingDirectory / currentFileName) asFileReference ensureDelete.
(workingDirectory / 'markdown') asFileReference ensureDelete.
ZnClient new
get: self address, 'pub/', chapterAddress, '/download/markdown';
downloadTo: workingDirectory .
workingDirectory / 'markdown' renameTo: currentFileName
].
^ workingDirectory
]
{ #category : #accessing }
PubPub >> exportMarkdeepFiles [
| markdownFiles markdeepDocs |
markdownFiles := self languageFolder allChildren select: [:file |
file basename endsWith: '.md'
].
markdeepDocs := markdownFiles collect: [:file |
(Markdeep fromMarkdownFile:file)
].
markdeepDocs do: [:each |
each fromPubPubToMarkdeep exportAsFile
].
^ self languageFolder
]
{ #category : #accessing }
PubPub >> extractAllContentsRaw [
^ self frontPage xpath: '//div[@class="layout-pubs-block"]'
]
{ #category : #accessing }
PubPub >> extractRawTableOfContents [
^ self extractAllContentsRaw first xpath: '//div[contains(concat(" ",normalize-space(@class)," "), " pub-preview-component ")]'
]
{ #category : #accessing }
PubPub >> extractTableOfContents [
^ self extractRawTableOfContents collect: [:each |
PubPubContent fromXML: each
]
]
{ #category : #accessing }
PubPub >> folder [
^ folder ensureCreateDirectory
]
{ #category : #accessing }
PubPub >> folder: localDirectory [
folder := localDirectory
]
{ #category : #accessing }
PubPub >> frontPage [
"This should scrap contents of the book's front-page and translate them into Markdeep,
according to our templates."
^ (XMLHTMLParser on: (self address asUrl retrieveContents)) parseDocument
]
{ #category : #accessing }
PubPub >> languageFolder [
^ self folder / self currentLanguage
]
{ #category : #accessing }
PubPub >> printOn: aStream [
super printOn: aStream.
aStream
nextPutAll: '(',self defaultTitle, ' | ', self address, ' )'
]
{ #category : #accessing }
PubPub >> tableOfContents [
tableOfContents ifNil: [^ tableOfContents := Dictionary new].
^ tableOfContents at: self currentLanguage
]
{ #category : #accessing }
PubPub >> titles [
^ titles ifNil: [titles := OrderedDictionary new]
]

View File

@ -5,7 +5,9 @@ Class {
'title', 'title',
'language', 'language',
'url', 'url',
'thumbnail' 'thumbnail',
'work',
'contents'
], ],
#category : #'MiniDocs-Model' #category : #'MiniDocs-Model'
} }
@ -15,6 +17,40 @@ PubPubContent class >> fromXML: anXMLElement [
^ self new fromXML: anXMLElement ^ self new fromXML: anXMLElement
] ]
{ #category : #accessing }
PubPubContent >> asMarkdeepFrontPageElement [
| response anchorName anchorLink markdeepFile |
response := '' writeStream.
anchorName := '[', self title,']'.
markdeepFile := './book/', self shortName,'--',self id,'.md.html'.
anchorLink := '(', markdeepFile,')'.
response
nextPutAll: '<big>', anchorName, anchorLink,'</big><br><br>';
nextPutAll: String lf.
self thumbnail ifNotNil: [ |image|
image := '
<img
src=', self thumbnail,
' width="55%"
style="width: 400px; height: 220px; object-fit: cover;"
/>'.
response nextPutAll: '<a href="',markdeepFile,'">', image, '</a>'
].
response
nextPutAll: String lf, String lf.
^ response contents
]
{ #category : #accessing }
PubPubContent >> contents: anObject [
contents := anObject
]
{ #category : #accessing }
PubPubContent >> fileName [
^ self shortName,'--', self id, '.md'
]
{ #category : #accessing } { #category : #accessing }
PubPubContent >> fromXML: aXMLElement [ PubPubContent >> fromXML: aXMLElement [
| image anchor| | image anchor|
@ -37,6 +73,23 @@ PubPubContent >> id [
^ (self url splitOn: $/) last ^ (self url splitOn: $/) last
] ]
{ #category : #'as yet unclassified' }
PubPubContent >> language: aString [
language := aString
]
{ #category : #accessing }
PubPubContent >> next [
^ self nextInstance
]
{ #category : #accessing }
PubPubContent >> previous [
| index |
index := self work tableOfContents detectIndex: [:pubContent | pubContent = self ] ifNone: [ ^ nil ].
^ self work tableOfContents at: index - 1.
]
{ #category : #accessing } { #category : #accessing }
PubPubContent >> printOn: aStream [ PubPubContent >> printOn: aStream [
super printOn: aStream. super printOn: aStream.
@ -54,6 +107,11 @@ PubPubContent >> shortName [
^ sanitized ^ sanitized
] ]
{ #category : #accessing }
PubPubContent >> thumbnail [
^ thumbnail
]
{ #category : #accessing } { #category : #accessing }
PubPubContent >> thumbnail: anURL [ PubPubContent >> thumbnail: anURL [
thumbnail := anURL thumbnail := anURL
@ -78,3 +136,13 @@ PubPubContent >> url [
PubPubContent >> url: anObject [ PubPubContent >> url: anObject [
url := anObject url := anObject
] ]
{ #category : #accessing }
PubPubContent >> work [
^ work
]
{ #category : #accessing }
PubPubContent >> work: aPubPubWork [
work := aPubPubWork
]

View File

@ -11,7 +11,7 @@ Class {
'footnoteLabel', 'footnoteLabel',
'footnoteContent' 'footnoteContent'
], ],
#category : #MiniDocs #category : #'MiniDocs-Model'
} }
{ #category : #accessing } { #category : #accessing }

View File

@ -0,0 +1,240 @@
Class {
#name : #PubPubWork,
#superclass : #Object,
#instVars : [
'address',
'tableOfContents',
'titles',
'folder',
'currentLanguage',
'languages'
],
#category : #'MiniDocs-Model'
}
{ #category : #accessing }
PubPubWork >> addTableOfContents: anOrderedDictionary [
self tableOfContents
at: (self currentLanguage) put: anOrderedDictionary;
yourself
]
{ #category : #accessing }
PubPubWork >> addTitle: aString [
self titles
at: (self currentLanguage) put: aString
]
{ #category : #accessing }
PubPubWork >> address [
^ address
]
{ #category : #accessing }
PubPubWork >> address: anUrl [
address := anUrl
]
{ #category : #accessing }
PubPubWork >> bookishFolder [
^ { 'en' -> 'book'.
'es' -> 'libro'} asDictionary
]
{ #category : #accessing }
PubPubWork >> currentLanguage [
^ currentLanguage
]
{ #category : #accessing }
PubPubWork >> currentLanguage: twoLettersInISO639_1 [
currentLanguage := twoLettersInISO639_1
]
{ #category : #accessing }
PubPubWork >> defaultOptions [
^ { 'sourceCodeLink' -> true .
'commentsProvider' -> 'Hypothesis' } asDictionary
]
{ #category : #accessing }
PubPubWork >> defaultTitle [
^ self titles associations first value
]
{ #category : #accessing }
PubPubWork >> downloadContents [
| workingDirectory |
workingDirectory := self workingDirectory.
self tableOfContentsDictionary
keysAndValuesDo: [ :name :chapterAddress |
| currentFileName |
currentFileName := name , '--' , chapterAddress , '.md'.
(workingDirectory / currentFileName) asFileReference ensureDelete.
(workingDirectory / 'markdown') asFileReference ensureDelete.
ZnClient new
get: self address , 'pub/' , chapterAddress , '/download/markdown';
downloadTo: workingDirectory.
workingDirectory / 'markdown' renameTo: currentFileName ].
^ workingDirectory
]
{ #category : #accessing }
PubPubWork >> downloadContents2 [
| workingDirectory |
workingDirectory := self folder / self currentLanguage / 'book'.
self tableOfContentsDictionary keysAndValuesDo: [ :name :chapterAddress | |currentFileName|
currentFileName := name, '--', chapterAddress, '.md'.
(workingDirectory / currentFileName) asFileReference ensureDelete.
(workingDirectory / 'markdown') asFileReference ensureDelete.
ZnClient new
get: self address, 'pub/', chapterAddress, '/download/markdown';
downloadTo: workingDirectory .
workingDirectory / 'markdown' renameTo: currentFileName
].
^ workingDirectory
]
{ #category : #accessing }
PubPubWork >> exportToHTML [
self markdownFiles
do: [ :file | | doc |
doc := Markdown new fromFile: file.
doc exportAsHTML ].
^ self markdownFiles first parent
]
{ #category : #accessing }
PubPubWork >> exportToMarkdeep [
| markdeepDocs |
markdeepDocs := self markdownFiles
collect: [ :file | Markdeep fromMarkdownFile: file ].
markdeepDocs do: [ :each | each fromPubPubToMarkdeep exportAsFile ].
^ self languageFolder
]
{ #category : #accessing }
PubPubWork >> extractAllContentsRaw [
^ self frontPage xpath: '//div[@class="layout-pubs-block"]'
]
{ #category : #accessing }
PubPubWork >> extractRawTableOfContents [
^ self extractAllContentsRaw first xpath: '//div[contains(concat(" ",normalize-space(@class)," "), " pub-preview-component ")]'
]
{ #category : #accessing }
PubPubWork >> folder [
^ folder ensureCreateDirectory
]
{ #category : #accessing }
PubPubWork >> folder: localDirectory [
folder := localDirectory
]
{ #category : #accessing }
PubPubWork >> frontPage [
"This should scrap contents of the book's front-page and translate them into Markdeep,
according to our templates."
^ (XMLHTMLParser on: (self address asUrl retrieveContents)) parseDocument
]
{ #category : #accessing }
PubPubWork >> languageFolder [
^ self folder / self currentLanguage
]
{ #category : #accessing }
PubPubWork >> markdeepFrontPage [
| frontPage markdeepIndex |
frontPage := Markdeep new.
frontPage
title: self defaultTitle;
file: self languageFolder / 'frontPage.md.html'.
markdeepIndex := '' writeStream.
self tableOfContents do: [:pubPubContent|
markdeepIndex
nextPutAll: pubPubContent asMarkdeepFrontPageElement
].
frontPage body: markdeepIndex contents.
^ frontPage
]
{ #category : #accessing }
PubPubWork >> markdownFiles [
^ self languageFolder allChildren
select: [ :file | file basename endsWith: '.md' ]
]
{ #category : #accessing }
PubPubWork >> populateContents [
self tableOfContents isEmptyOrNil
ifTrue: [ self populateTableOfContents ].
self workingDirectory children ifEmpty: [self downloadContents].
self tableOfContents do: [:pubPubContent | | contentFile|
contentFile := self workingDirectory / pubPubContent fileName.
contentFile exists
ifTrue: [ pubPubContent contents: (Markdown new fromFile: contentFile) ]
]
]
{ #category : #accessing }
PubPubWork >> populateTableOfContents [
| contentsCollection |
contentsCollection := self extractRawTableOfContents collect: [:each |
(PubPubContent fromXML: each)
language: self currentLanguage;
work: self
].
self addTableOfContents: contentsCollection asOrderedCollection
]
{ #category : #accessing }
PubPubWork >> printOn: aStream [
super printOn: aStream.
aStream
nextPutAll: '(',self defaultTitle, ' | ', self address, ' )'
]
{ #category : #accessing }
PubPubWork >> tableOfContents [
tableOfContents ifNil: [ ^ tableOfContents := Dictionary new].
^ tableOfContents at: self currentLanguage
]
{ #category : #accessing }
PubPubWork >> tableOfContents: anObject [
tableOfContents := anObject
]
{ #category : #accessing }
PubPubWork >> tableOfContentsDictionary [
| response |
response := OrderedDictionary new.
self tableOfContents do: [:content |
response
at: content shortName put: content id
].
^ response
]
{ #category : #accessing }
PubPubWork >> titles [
^ titles ifNil: [titles := OrderedDictionary new]
]
{ #category : #accessing }
PubPubWork >> viewContentsFor: aView [
<gtView>
^ aView list
title: 'Contents';
priority: 10;
items: [ self tableOfContents ]
]
{ #category : #accessing }
PubPubWork >> workingDirectory [
^ self folder / self currentLanguage / (self bookishFolder at: self currentLanguage)
]

View File

@ -4,7 +4,7 @@ Extension { #name : #String }
String >> accentedCharactersCorrection [ String >> accentedCharactersCorrection [
| modified corrections | | modified corrections |
corrections := { corrections := {
'ó' -> 'ó' . º' -> 'ú' . 'ñ' -> 'ñ' . 'ó' -> 'ó' . “' -> 'Ó' . º' -> 'ú' . 'ñ' -> 'ñ' . 'Ñ' -> 'Ñ' .
'í' -> 'í' . 'á' -> 'á' . 'é' -> 'é' . '’' -> $' asString} asDictionary. 'í' -> 'í' . 'á' -> 'á' . 'é' -> 'é' . '’' -> $' asString} asDictionary.
modified := self copy. modified := self copy.
corrections keysAndValuesDo: [ :k :v | corrections keysAndValuesDo: [ :k :v |
@ -13,6 +13,41 @@ String >> accentedCharactersCorrection [
^ modified ^ modified
] ]
{ #category : #'*MiniDocs' }
String >> admonitionBorderLines [
| response |
response := OrderedDictionary new.
self lines doWithIndex: [:line :index |
(self admonitionBorders includes: line trimBoth)
ifTrue: [ response at: index put: line trimBoth ]
].
^ response
]
{ #category : #'*MiniDocs' }
String >> admonitionBorders [
"For the moment I only work with the admonition starting border
as adding the closing one would imply to redo the #markdownSplitted
method implementing a proper parser, which, ATM is overkill."
| response |
response := #('info' 'success' 'warning' 'danger') collect: [ :each | ':::', each ].
^ response "copyWith: ':::'"
]
{ #category : #'*MiniDocs' }
String >> admonitionEndingPosition [
| response |
response := 0.
self startsWithMarkdownAdmonition ifFalse: [ ^ response ].
self lines do: [:line |
response > 0 ifTrue: [ response := response + 1 ].
(line trimBoth = ':::')
ifFalse: [ response := response + line size ]
ifTrue: [ ^ response := response + line size. ]
].
^ response
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
String >> asDashedLowercase [ String >> asDashedLowercase [
"I convert phrases like 'This is a phrase' into 'this-is-a-phrase'." "I convert phrases like 'This is a phrase' into 'this-is-a-phrase'."
@ -20,6 +55,119 @@ String >> asDashedLowercase [
^ '-' join: (self substrings collect: [:each | each asLowercase ]) ^ '-' join: (self substrings collect: [:each | each asLowercase ])
] ]
{ #category : #'*MiniDocs' }
String >> asInteger [
"Return the integer present in the receiver, or nil. In case of float, returns the integer part."
"'1' asInteger >>> 1"
"'-1' asInteger >>> -1"
"'10' asInteger >>> 10"
"'a' asInteger >>> nil"
"'1.234' asInteger >>> 1"
^ (self copyWithoutAll: '_') asSignedInteger
]
{ #category : #'*MiniDocs' }
String >> contentsWithoutYAMLMetadata [
| newContents |
self detectYAMLMetadata ifFalse: [ ^ self ].
newContents := '' writeStream.
(self lines copyFrom: self yamlMetadataClosingLineNumber + 2 to: self lines size) do: [ :line |
newContents nextPutAll: line; cr ].
^ newContents contents.
]
{ #category : #'*MiniDocs' }
String >> deleteYAMLMetadata [
| newContents |
self detectYAMLMetadata ifFalse: [ ^ self ].
newContents := '' writeStream.
(self lines copyFrom: self yamlMetadataClosingLineNumber + 1 to: self lines size) do: [ :line |
newContents nextPutAll: line; lf;lf ].
^ newContents contents.
]
{ #category : #'*MiniDocs' }
String >> demoteMarkdownHeaders [
| response |
response := self contents lines.
self markdownHeaders associations allButFirstDo: [ :assoc |
response at: assoc key put: '#', assoc value ].
^ response asStringWithCr withInternetLineEndings
]
{ #category : #'*MiniDocs' }
String >> detectYAMLMetadata [
| lines |
lines := self lines.
^ self startsWithYAMLMetadataDelimiter
and: [ lines allButFirst
detect: [ :currentLine | currentLine beginsWith: self class yamlMetadataDelimiter ]
ifFound: [ ^ true ] ifNone: [ ^ false ] ]
]
{ #category : #'*MiniDocs' }
String >> indentedWithExtraSpaces: spaceNumber [
| response indent |
response := '' writeStream.
indent := String new.
spaceNumber timesRepeat: [ indent := indent, ' ' ].
self lines do: [:line | response nextPutAll: indent, line, String lf ].
^ response contents
]
{ #category : #'*MiniDocs' }
String >> markdownHeaders [
| response headers |
headers := (LeTextSnippet string: self contents) ast // #LeHeaderNode collect: [ :each | each headerFullName asString ].
response := OrderedDictionary new.
self lines doWithIndex: [:line :index |
(line beginsWithAnyOf: headers)
ifTrue: [ response at: index put: line ]
].
^ response
]
{ #category : #'*MiniDocs' }
String >> markdownSplitLines [
"I'm useful for conversions between the HedgeDoc Markdown variant and Lepiter page snippets.
I provide broad places to where semantic breaks should be located in a page,
depending on headers or admonitions to create pages snippets with similar divisions.
Further page splits should be provided manually by the document author."
| response |
response := OrderedDictionary new.
response := response
addAll: self markdownHeaders;
addAll: self admonitionBorderLines;
yourself.
^ (response associations sorted: [ :x :y | x key < y key ]) asOrderedDictionary
]
{ #category : #'*MiniDocs' }
String >> markdownSplitted [
| response lastPart |
self markdownSplitLines ifEmpty: [ ^ self ].
response := OrderedCollection new.
self markdownSplitLines keys allButLast doWithIndex: [:key :index | | nextLine part |
nextLine := (self markdownSplitLines keys at: index + 1) - 1.
part := self lines copyFrom: key to: nextLine.
response add: part.
].
lastPart := self lines
copyFrom: self markdownSplitLines keys last
to: self lines size.
response add: lastPart.
^ response
]
{ #category : #'*MiniDocs' }
String >> promoteMarkdownHeaders [
| response |
response := self contents lines.
self markdownHeaders associationsDo: [ :assoc |
response at: assoc key put: assoc value allButFirst ].
^ response asStringWithCr withInternetLineEndings
]
{ #category : #'*MiniDocs' } { #category : #'*MiniDocs' }
String >> romanizeAccents [ String >> romanizeAccents [
| modified corrections | | modified corrections |
@ -32,3 +180,61 @@ String >> romanizeAccents [
]. ].
^ modified ^ modified
] ]
{ #category : #'*MiniDocs' }
String >> startsWithMarkdownAdmonition [
self lines ifEmpty: [ ^ false ].
^ self admonitionBorders includes: self lines first trimBoth
]
{ #category : #'*MiniDocs' }
String >> startsWithYAMLMetadataDelimiter [
self lines ifEmpty: [^false].
^ self lines first beginsWith: self class yamlMetadataDelimiter
]
{ #category : #'*MiniDocs' }
String >> withoutXMLTagDelimiters [
^ self copyWithoutAll: #($< $>)
]
{ #category : #'*MiniDocs' }
String >> yamlMetadataClosingLineNumber [
"I return the line where the closing of the YAML metadata occurs or 0 if no closing is found."
self startsWithYAMLMetadataDelimiter ifFalse: [ ^ self ].
self lines allButFirst doWithIndex: [ :currentLine :i |
(currentLine beginsWith: self class yamlMetadataDelimiter) ifTrue: [ ^ i + 1 ]]
]
{ #category : #'*MiniDocs' }
String class >> yamlMetadataDelimiter [
^ '---'
]
{ #category : #'*MiniDocs' }
String >> yamlMetadataString [
| output yamlLines |
self detectYAMLMetadata ifFalse: [ ^nil ].
self lines ifEmpty: [ ^nil ].
yamlLines := self lines copyFrom: 2 to: self yamlMetadataClosingLineNumber - 1.
output := '' writeStream.
yamlLines do: [ :line |
output
nextPutAll: line;
nextPut: Character lf. ].
^ output contents
]
{ #category : #'*MiniDocs' }
String >> yamlMetadataStringWithDelimiters [
| output |
self yamlMetadataString ifNil: [ ^ nil ].
output := String new writeStream.
output nextPutAll: self class yamlMetadataDelimiter; cr.
output nextPutAll: self yamlMetadataString.
output nextPutAll: self class yamlMetadataDelimiter; cr.
^ output contents.
]

View File

@ -0,0 +1,9 @@
Extension { #name : #XMLDocument }
{ #category : #'*MiniDocs' }
XMLDocument >> detectMarkdeepTitle [
| titleLine |
titleLine := (self nodesCollect: [:node | node contentString ]) first lines
detect: [:line | line includesSubstring: ' **'] ifNone: ['Untitled'].
^ titleLine trimmed trimBoth: [:char | char = $* ]
]

View File

@ -0,0 +1,53 @@
Extension { #name : #XMLElement }
{ #category : #'*MiniDocs' }
XMLElement >> asSnippetDictionary [
| response |
response := STON fromString: (self attributes at: 'st-data').
response at: 'className' put: (self attributes at: 'st-class').
response at: 'content' put: self sanitizedContent.
^ response
]
{ #category : #'*MiniDocs' }
XMLElement >> extractMarkdownImageLinkData [
| linkParserNodes sanitizedText linkParser |
linkParser := (PPCommonMarkBlockParser parse: (self contentString trimBoth: [:each | each = Character lf]) allButFirst)
accept: CMBlockVisitor new.
linkParserNodes := linkParser children first children.
linkParserNodes size = 1
ifTrue: [ sanitizedText := linkParserNodes first label text ]
ifFalse: [ sanitizedText := '' writeStream.
linkParserNodes allButLast
do: [ :each |
each className = 'PPCMText'
ifTrue: [ sanitizedText nextPutAll: each text allButFirst ].
each className = 'PPCMLink'
ifTrue: [ sanitizedText nextPutAll: each printString ] ].
sanitizedText := sanitizedText contents ].
^ {sanitizedText . self contentString }
]
{ #category : #'*MiniDocs' }
XMLElement >> sanitizedContent [
| className sanitizedText |
className := self attributes at: 'st-class'.
className = 'LeTextSnippet'
ifTrue: [ sanitizedText := self contentString.
sanitizedText := sanitizedText allButFirst.
sanitizedText := sanitizedText allButLast ].
className = 'LePharoSnippet'
ifTrue: [ | joinedText |
sanitizedText := self contentString lines.
sanitizedText := sanitizedText copyFrom: 4 to: sanitizedText size - 2.
joinedText := '' writeStream.
sanitizedText
do: [ :line |
joinedText
nextPutAll: line;
nextPut: Character lf ].
sanitizedText := joinedText contents allButLast ].
className = 'LePictureSnippet'
ifTrue: [ sanitizedText := self extractMarkdownImageLinkData ].
^ sanitizedText
]

View File

@ -1,29 +0,0 @@
"
The `External` tag is related on its dependency on other programming languages and frameworks,
though the dependency should be loaded by just loading a small binary with no dependencies.
"
Class {
#name : #YQ,
#superclass : #Object,
#category : #'MiniDocs-External'
}
{ #category : #accessing }
YQ class >> binaryDownloadLinkFor: operativeSystem on: processor [
| binaryName binaryDownloadData |
binaryName := 'yq_', operativeSystem , '_', processor.
binaryDownloadData := ((self lastReleaseData at: 'assets')
select: [:each | (each at: 'name') beginsWith: binaryName ]) first.
^ binaryDownloadData at: 'browser_download_url'
]
{ #category : #accessing }
YQ class >> install [
^ self lastReleaseData
]
{ #category : #accessing }
YQ class >> lastReleaseData [
^ (STONJSON
fromString: 'https://api.github.com/repos/mikefarah/yq/releases' asUrl retrieveContents) first
]

View File

@ -0,0 +1,10 @@
Extension { #name : #ZnConstants }
{ #category : #'*MiniDocs' }
ZnConstants class >> maximumLineLength [
"Return the maximum line length to accept.
Used by ZnLineReader and thus for reading request/status lines as well as headers.
This helps to protect us from malicious content."
^ 5096 "8192"
]

View File

@ -23,3 +23,8 @@ PPCMBlockQuote >> initialize [
PPCMBlockQuote >> isBlockLevel [ PPCMBlockQuote >> isBlockLevel [
^ true ^ true
] ]
{ #category : #accessing }
PPCMBlockQuote >> viewBody [
^ (self className ,' ', self text) asRopedText.
]

View File

@ -39,9 +39,8 @@ PPCMLink >> printOn: aStream [
super initialize. super initialize.
^ aStream ^ aStream
nextPutAll: nextPutAll:
self label text, '[',self label text,']',
' -> ', '(',self destination,')'
self destination
] ]
{ #category : #accessing } { #category : #accessing }