Extension { #name : #LeDatabase } { #category : #'*MiniDocs' } LeDatabase >> addPage2FromMarkdeep: markdeepDocTree withRemote: externalDocLocation [ | newPage | "^ { snippets . page }" "Rebulding partial subtrees" "Adding unrooted subtrees to the page" "^ newPage" newPage := self rebuildPageFromMarkdeep: markdeepDocTree withRemote: externalDocLocation. newPage childrenDo: [ :snippet | (self hasBlockUID: snippet uid) ifTrue: [ | existingPage | existingPage := self pages detect: [ :pageTemp | pageTemp includesSnippetUid: snippet uid ]. self importErrorForLocal: existingPage withRemote: externalDocLocation. ^ self ] ifFalse: [ snippet database: self. 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 snippets page | 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 ]. page := LePage new. page title: (remoteMetadata at: 'title' ifAbsent: [ page detectMarkdeepTitleFrom: markdeepDocTree ]); basicUid: (UUID fromString36: (remoteMetadata at: 'id' ifAbsent: [UUID new asString36])); createTime: (LeTime new time: (remoteMetadata at: 'created' ifAbsent: [ DateAndTime now]) asDateAndTime); editTime: (LeTime new time: (remoteMetadata at: 'modified' ifAbsent: [ DateAndTime now]) asDateAndTime); latestEditTime: (LeTime new time: (remoteMetadata at: 'modified' ifAbsent: [ DateAndTime now]) asDateAndTime); createEmail: (remoteMetadata at: 'creator' ifAbsent: [ 'unknown' ]); editEmail: (remoteMetadata at: 'modifier' ifAbsent: [ 'unknown' ]). snippets do: [ :snippet | "| currentParent |" page addSnippet: snippet. "currentParent := page detectParentSnippetWithUid: (snippet metadata at: 'parent'). snippet parent: currentParent." ]. page children do: [ :snippet | (self hasBlockUID: snippet uid) ifTrue: [ | existingPage | existingPage := self pages detect: [ :pageTemp | pageTemp includesSnippetUid: snippet uid ]. self importErrorForLocal: existingPage withRemote: externalDocLocation. ^ self ] ifFalse: [ snippet database: self. self registerSnippet: snippet ] ]. self addPage: page. ^ page ] { #category : #'*MiniDocs' } LeDatabase >> addPageFromMarkdeepUrl: aString [ | page | page := self detectLocalPageForRemote: aString. page ifNotNil: [ :arg | self importErrorForLocal: page withRemote: aString. ^ self errorCardFor: page uidString ]. ^ self addPageFromMarkdeep: (self docTreeForLink: aString) withRemote: aString ] { #category : #'*MiniDocs' } LeDatabase >> detectLocalPageForRemote: markdeepDocUrl [ | markdeepHelper id remoteMetadata docTree | markdeepHelper := Markdeep new. docTree := self docTreeForLink: markdeepDocUrl. remoteMetadata := markdeepHelper metadataFromXML: docTree. id := remoteMetadata at: 'id' ifAbsent: [ nil ]. ^ self pageWithID: id ifAbsent: [ ^ nil ]. ] { #category : #'*MiniDocs' } LeDatabase >> docTreeForLink: aString [ ^ (XMLHTMLParser on: aString asUrl retrieveContents) parseDocument ] { #category : #'*MiniDocs' } LeDatabase >> errorCardFor: errorKey [ | keepButton overwriteButton loadCopyButton errorMessageUI localPage | localPage := self pageWithID: errorKey. keepButton := BrButton new aptitude: BrGlamorousButtonWithIconAndLabelAptitude; label: 'Keep existing local page'; icon: BrGlamorousVectorIcons cancel; margin: (BlInsets left: 10); action: [ :aButton | aButton phlow spawnObject: localPage. self errors removeKey: errorKey ]. overwriteButton := BrButton new aptitude: BrGlamorousButtonWithIconAndLabelAptitude; label: 'Overwrite with remote page'; icon: BrGlamorousVectorIcons edit; action: [ :aButton | self removePage: localPage. aButton phlow spawnObject: (self addPageFromMarkdeepUrl: (self errors at: errorKey at: 'remote')). self errors removeKey: errorKey ]; margin: (BlInsets left: 10). loadCopyButton := BrButton new aptitude: BrGlamorousButtonWithIconAndLabelAptitude; label: 'Load remote page as a copy'; icon: BrGlamorousVectorIcons changes; action: [ :aButton | self ]; margin: (BlInsets left: 10). errorMessageUI := BrEditor new aptitude: BrGlamorousRegularEditorAptitude new ; text: (self errors at: errorKey at: 'message'); vFitContent. ^ BrHorizontalPane new matchParent; alignCenter; addChild:errorMessageUI; addChild: keepButton; addChild: overwriteButton; addChild: loadCopyButton ] { #category : #'*MiniDocs' } LeDatabase >> errors [ ^ self optionAt: 'errors' ifAbsentPut: [ Dictionary new ] ] { #category : #'*MiniDocs' } LeDatabase >> gtViewErrorDetailsOn: aView [ ^ aView explicit title: 'Errors' 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: self errors) ]. ] { #category : #'*MiniDocs' } LeDatabase >> gtViewErrorDetailsOn: aView withKey: erroKey [ ^ 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' } LeDatabase >> importErrorForLocal: page withRemote: externalDocLocation [ | message id error | id := page uidString. message := String streamContents: [ :stream | stream nextPutAll: 'IMPORTATION ERROR: A page with '; nextPut: Character lf; nextPutAll: ' id: ' , id; nextPut: Character lf; nextPutAll: ' title: ' , page contentAsString; nextPut: Character lf; nextPut: Character lf; nextPutAll: 'already exists in this database and includes overlapping contents'; nextPut: Character lf; nextPutAll: 'with the page you are trying to import from: '; nextPut: Character lf; nextPutAll: externalDocLocation; nextPut: Character lf; nextPut: Character lf; nextPutAll: 'Please choose one of the following options to addres the issue: ' ]. error := Dictionary new at: 'remote' put: externalDocLocation; at: 'message' put: message ; yourself. self errors at: id put: error. ^ self errors at: id. ] { #category : #'*MiniDocs' } LeDatabase >> 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. ] ]