TiddlyWikiPharo/repository/TiddlyWiki/Tiddler.class.st

565 lines
13 KiB
Smalltalk

"
I model a Tiddler object in [TiddlyWiki](https://tiddlywiki.com/).
I implement the standard fields as described in the standard documentation at: <https://tiddlywiki.com/#TiddlerFields>
"
Class {
#name : #Tiddler,
#superclass : #Object,
#instVars : [
'title',
'text',
'modified',
'created',
'creator',
'tags',
'type',
'list',
'caption',
'modifier',
'wiki',
'customFields',
'bag',
'revision'
],
#category : #'TiddlyWiki-Model'
}
{ #category : #'instance creation' }
Tiddler class >> nowLocal [
^ ((ZTimestampFormat fromString: '20010203160506700')
format: (ZTimestamp fromString: Time nowLocal asDateAndTime asString)) copyFrom: 1 to: 17
]
{ #category : #accessing }
Tiddler >> asDictionary [
| response |
response := Dictionary new
at: 'title' put: self title;
at: 'text' put: self text;
at: 'created' put: self created;
at: 'tags' put: self tags;
at: 'type' put: self type;
at: 'creator' put: self creator;
at: 'modifier' put: self modifier;
at: 'modified' put: self modified;
at: 'bag' put: self bag;
at: 'revision' put: self revision;
yourself.
self customFields ifNotEmpty: [
self customFields keysAndValuesDo: [:k :v |
response at: k put: v
]
].
^ response
]
{ #category : #converting }
Tiddler >> asJson [
^ STONJSON toStringPretty: { self asDictionary }
]
{ #category : #converting }
Tiddler >> asJsonString [
^ STONJSON toStringPretty: self asDictionary
]
{ #category : #accessing }
Tiddler >> asJsonTempFile [
^ MarkupFile exportAsFileOn: FileLocator temp / self title, 'json' containing:self asJson
]
{ #category : #accessing }
Tiddler >> asStonStringPretty [
| output temp |
temp := self copy.
temp wiki: nil.
output := '' writeStream.
(STON writer on: output)
newLine: String crlf;
prettyPrint: true;
keepNewLines: true;
nextPut: temp.
^ output contents
]
{ #category : #accessing }
Tiddler >> bag [
^ bag
]
{ #category : #accessing }
Tiddler >> bag: aString [
bag := aString
]
{ #category : #accessing }
Tiddler >> caption [
^ caption
]
{ #category : #accessing }
Tiddler >> caption: anObject [
caption := anObject
]
{ #category : #accessing }
Tiddler >> created [
^ created ifNil: [ created := self class nowLocal ]
]
{ #category : #accessing }
Tiddler >> created: anObject [
created := anObject
]
{ #category : #accessing }
Tiddler >> createdAsTWFormat [
(self created beginsWith: 'NaN') ifTrue: [ self created: self class nowLocal].
^ ((ZTimestampFormat fromString: '20010203160506700')
format: (ZTimestamp fromString: self created)) copyFrom: 1 to: 17
]
{ #category : #accessing }
Tiddler >> createdReversableEncoded [
"I encode the tiddler creation date with miliseconds precision in a shorter reversable way
(from 17 characters to 10).
But when tiddlers are created with the same exact date (for example programmatically)
I produce the same encoding (because of reversability).
I recommend to use nanoID instead to get unique visible different identifiers "
| output longDate |
longDate := self createdAsTWFormat.
output := WriteStream on: ''.
1 to: 14 by: 2 do: [ :i |
output nextPutAll: (longDate copyFrom: i to: i +1) greaseInteger asCharacterDigit greaseString
].
output nextPutAll: (longDate copyFrom: 15 to: 17).
^ output contents
]
{ #category : #accessing }
Tiddler >> creator [
^ creator
]
{ #category : #accessing }
Tiddler >> creator: anObject [
creator := anObject
]
{ #category : #accessing }
Tiddler >> customFields [
^ customFields ifNil: [ customFields := Dictionary new]
]
{ #category : #accessing }
Tiddler >> deleteUid [
self customFields deleteKey: 'uid'.
]
{ #category : #accessing }
Tiddler >> exportJSONFile [
| jsonFile folder |
folder := self wiki folder.
jsonFile := folder / 'tiddlers' / ((self fileName removeSuffix: '.ston'), '.json').
^ MarkupFile exportAsFileOn: jsonFile containing:self asJson
]
{ #category : #accessing }
Tiddler >> exportSTONFile [
^ self exportSTONFileInto: 'tiddlers'
]
{ #category : #accessing }
Tiddler >> exportSTONFileInto: subfolder [
| stonFile |
stonFile := self wiki folder / subfolder / self fileName.
^ MarkupFile exportAsFileOn: stonFile containing: self asStonStringPretty
]
{ #category : #accessing }
Tiddler >> exportSTONFileOptimized [
| exporter wikiFolder tiddlersFolder |
wikiFolder := self wiki folder.
exporter := wikiFolder / 'scripts' / 'stringAsFileInto'.
exporter exists ifFalse: [ self installTiddlerExporter ].
tiddlersFolder := wikiFolder / 'tiddlers'.
tiddlersFolder exists ifFalse: [ tiddlersFolder ensureCreateDirectory ].
OSSUnixSubprocess new
command: exporter fullName;
arguments: { self asStonStringPretty . self fileName };
workingDirectory: tiddlersFolder fullName;
runAndWaitOnExitDo: [ :process :outString | ^ tiddlersFolder / self fileName ]
]
{ #category : #accessing }
Tiddler >> exportWithTemplate: aTemplate [
^ aTemplate asMustacheTemplate value: self asDictionary
]
{ #category : #accessing }
Tiddler >> fileName [
| dashedTitle sanitized |
dashedTitle := '-' join: (self title substrings collect: [ :each | each ]).
sanitized := dashedTitle copyWithoutAll: #($¿ $? $! $/).
^ sanitized , '--', (self uid copyFrom: 1 to: 12), '.ston'.
]
{ #category : #accessing }
Tiddler >> fromDictionary: aDictionary [
| customKeys |
self
title: (aDictionary at: 'title');
text: (aDictionary at: 'text' ifAbsentPut: [ nil ]);
tags: (aDictionary at: 'tags' ifAbsentPut: [ nil ]);
created: (aDictionary at: 'created' ifAbsentPut: [ self class nowLocal ]);
creator: (aDictionary at: 'creator' ifAbsentPut: [ nil ]);
modified: (aDictionary at: 'modified' ifAbsentPut: [ nil ]);
modifier: (aDictionary at: 'modifier' ifAbsentPut: [ nil ]);
type: (aDictionary at: 'type' ifAbsentPut: [ nil ]);
caption: (aDictionary at: 'caption' ifAbsentPut: [ nil ]);
bag: (aDictionary at: 'bag' ifAbsentPut: [ nil ]);
list: (aDictionary at: 'list' ifAbsentPut: [ nil ]);
revision: (aDictionary at: 'revision' ifAbsentPut: [ nil ]).
customKeys := aDictionary keys
copyWithoutAll: (self class instanceVariables collect: [ :each | each name ]).
"(customKeys includes: 'uid') ifFalse: [ self uidGenerator ]."
customKeys do: [:key | | valueTemp |
valueTemp := aDictionary at: key.
valueTemp class = Array
ifTrue: [ self customFields at: key put: (self tiddlersListFrom: valueTemp) ]
ifFalse: [ self customFields at: key put: valueTemp ].
valueTemp class
].
]
{ #category : #'instance creation' }
Tiddler >> fromMarkdownParsedItems: aCollection [
| outputStream |
outputStream := '' writeStream.
aCollection children do: [ :each |
each children
ifEmpty: [ self itemContentsStringFor: each into: outputStream ]
ifNotEmpty: [
each children do: [ :child |
self itemContentsStringFor: child into: outputStream ] ]
]
]
{ #category : #accessing }
Tiddler >> gtTextFor: aView [
<gtView>
^ aView textEditor
title: 'Text';
text: [ text ]
]
{ #category : #testing }
Tiddler >> hasUID [
^ self customFields includesKey: 'uid'
]
{ #category : #accessing }
Tiddler >> importFedWikiPage: pageViewUrlString [
| pageTitle pageViewUrl pageData |
pageViewUrl := pageViewUrlString asZnUrl.
pageTitle := pageViewUrl segments second.
pageData := (pageViewUrl scheme, '://', pageViewUrl host, '/', pageTitle, '.json') asZnUrl.
^ STONJSON fromString: pageData retrieveContents
]
{ #category : #accessing }
Tiddler >> installTiddlerExporter [
| folder |
folder := (self wiki folder).
folder := (folder / 'scripts') ensureCreateDirectory.
ZnClient new
url: 'https://mutabit.com/repos.fossil/mutabit/uv/wiki/scripts/stringAsFileInto';
downloadTo: folder / 'stringAsFileInto'.
ZnClient new
url: 'https://mutabit.com/repos.fossil/mutabit/doc/trunk/wiki/scripts/stringAsFileInto.nim';
downloadTo: folder / 'stringAsFileInto.nim'.
OSSUnixSubprocess new
command: 'chmod';
arguments: { '+x' . (folder / 'stringAsFileInto') fullName };
workingDirectory: folder fullName;
redirectStdout;
redirectStderr;
runAndWaitOnExitDo: [ :process :outString | ^ outString ]
]
{ #category : #testing }
Tiddler >> isImage [
^ self type ifNil: [ ^ false ];
beginsWith: 'image/'
]
{ #category : #testing }
Tiddler >> isJavascript [
^ self type = 'application/javascript'
]
{ #category : #testing }
Tiddler >> isMarkdown [
^ self type = 'text/x-markdown'
]
{ #category : #testing }
Tiddler >> isNilType [
^ self type = nil
]
{ #category : #testing }
Tiddler >> isPDF [
^ self type = 'application/pdf'
]
{ #category : #testing }
Tiddler >> isShadow [
^ self title beginsWith: '$:/'
]
{ #category : #testing }
Tiddler >> isTW5Type [
^ self type = 'text/vnd.tiddlywiki'
]
{ #category : #testing }
Tiddler >> isTextPlain [
^ self type = 'text/plain'
]
{ #category : #testing }
Tiddler >> isXTiddlerDictionary [
^ self type = 'application/x-tiddler-dictionary'
]
{ #category : #utilities }
Tiddler >> itemContentsStringFor: item into: stream [
stream
nextPutAll: item text;
nextPut: Character cr;
nextPut: Character cr
]
{ #category : #accessing }
Tiddler >> linkedTiddlers [
"At the begining we are going to introduce 'pureTiddlers' as thos included in the wiki which are not linked
via aliases. Future versions of this method sould included internal aliased tiddlers."
| pureTiddlersTitles |
self rawLinks ifNil: [ ^nil ].
pureTiddlersTitles := self rawLinks difference: self rawAliasedLinks.
^ self wiki tiddlers select: [:tiddler | pureTiddlersTitles includes: tiddler title ].
]
{ #category : #accessing }
Tiddler >> list [
^ list
]
{ #category : #accessing }
Tiddler >> list: anObject [
list := anObject
]
{ #category : #accessing }
Tiddler >> listedTiddlers [
"I export all tiddlers in the list field as an alphabetic collection.
Future versions should preserve the order in the list.
Notice that while '#list' only gives the titles of the listed tiddlers,
I return them as proper tiddler objects."
| remainList remainListArray listedTiddlers |
self list ifNil: [^ nil ].
remainList := self list copy.
self manualLinksList do: [:manualLink |
remainList := remainList copyReplaceAll: manualLink with: ''
].
remainListArray := (remainList copyReplaceAll: '[[]]' with: '') withBlanksCondensed splitOn: Character space.
listedTiddlers := self manualLinksList, remainListArray.
^ self wiki tiddlers select: [:tiddler | listedTiddlers includes: tiddler title ].
]
{ #category : #accessing }
Tiddler >> manualLinksList [
self list ifNil: [^ nil].
^ WikiTextGrammar new linkSea star parse: self list.
]
{ #category : #utilities }
Tiddler >> markdownLinksAsWikiText [
"I'm useful to convert _internal_ links between formats, as is a common pattern
found when migrating content from Markdown to TiddlyWiki's WikiText.
I DON'T work on external links. A better regex could be used for that.
See:
- https://davidwells.io/snippets/regex-match-markdown-links
- http://blog.michaelperrin.fr/2019/02/04/advanced-regular-expressions/"
| markdownLinks |
markdownLinks := (self text splitOn: Character space) select: [:each | each matchesRegex: '\[(.+)\)'].
markdownLinks ifEmpty: [^ self].
^ markdownLinks
]
{ #category : #accessing }
Tiddler >> modified [
^ modified
]
{ #category : #accessing }
Tiddler >> modified: anObject [
modified := anObject
]
{ #category : #accessing }
Tiddler >> modifier [
^ modifier
]
{ #category : #accessing }
Tiddler >> modifier: anObject [
modifier := anObject
]
{ #category : #accessing }
Tiddler >> printOn: aStream [
super printOn: aStream.
aStream
nextPutAll: '( ', self title, ' )'
]
{ #category : #accessing }
Tiddler >> rawAliasedLinks [
^ self rawLinks select: [ :each | each includesSubstring: '|' ]
]
{ #category : #accessing }
Tiddler >> rawLinks [
self text ifNil: [ ^ Set new ].
^ (WikiTextGrammar new linkSea star parse: self text) asSet
]
{ #category : #accessing }
Tiddler >> revision [
^ revision
]
{ #category : #accessing }
Tiddler >> revision: aNumberString [
revision := aNumberString
]
{ #category : #accessing }
Tiddler >> tags [
^ tags
]
{ #category : #accessing }
Tiddler >> tags: anObject [
tags := anObject
]
{ #category : #accessing }
Tiddler >> text [
^ text
]
{ #category : #accessing }
Tiddler >> text: anObject [
text := anObject
]
{ #category : #accessing }
Tiddler >> tiddlersListFrom: anArray [
| output |
output := '' writeStream.
anArray doWithIndex: [:each :i |
output nextPutAll: '[[', each asString, ']]'.
i = anArray size ifFalse: [ output nextPutAll: Character space asString ].
].
^ output contents.
]
{ #category : #accessing }
Tiddler >> title [
^ title
]
{ #category : #accessing }
Tiddler >> title: anObject [
title := anObject
]
{ #category : #accessing }
Tiddler >> type [
^ type
]
{ #category : #accessing }
Tiddler >> type: anObject [
type := anObject
]
{ #category : #accessing }
Tiddler >> uid [
^ self customFields at: 'uid' ifAbsentPut: [ self uidGenerator ].
]
{ #category : #accessing }
Tiddler >> uidGenerator [
^ self customFields at: 'uid' put: NanoID generate.
]
{ #category : #accessing }
Tiddler >> wiki [
^ wiki
]
{ #category : #accessing }
Tiddler >> wiki: aTiddlyWiki [
wiki := aTiddlyWiki
]