diff --git a/repository/Grafoscopio/GfUIHelpers.class.st b/repository/Grafoscopio/GfUIHelpers.class.st index fdabe45..58355d6 100644 --- a/repository/Grafoscopio/GfUIHelpers.class.st +++ b/repository/Grafoscopio/GfUIHelpers.class.st @@ -44,7 +44,7 @@ GfUIHelpers class >> addToHelpMenu: aGrafoscopioNotebook [ ifNone: [ self helpMenu add: (metadata at: 'shortTitle') - target: [ GrafoscopioNotebook open: nbFile ] + target: [ GrafoscopioNewNotebook open: nbFile ] selector: #value ] ]. self updateUI ] @@ -170,7 +170,7 @@ GfUIHelpers class >> openFromRecentlyUsed [ selection := UIManager default chooseFrom: recentNotebooksReversed title: 'Choose a notebook...'. selection > 0 - ifTrue: [ GrafoscopioNotebook new openFromFile: (recentNotebooksReversed at: selection)] + ifTrue: [ GrafoscopioNewNotebook new openFromFile: (recentNotebooksReversed at: selection)] ifFalse: [ self inform: 'No notebook selected!' ] ] ifEmpty: [self messageNoRecentDocuments] diff --git a/repository/Grafoscopio/GfWorldMenu.class.st b/repository/Grafoscopio/GfWorldMenu.class.st index c99c18d..9cd2c07 100644 --- a/repository/Grafoscopio/GfWorldMenu.class.st +++ b/repository/Grafoscopio/GfWorldMenu.class.st @@ -18,7 +18,7 @@ GfWorldMenu class >> helpMenuOn: aBuilder [ label: 'Manual'; order: 1; parent: #GfHelpAndDocs; - action: [ GrafoscopioNotebook open: GrafoscopioDocs manual ]. + action: [ GrafoscopioNewNotebook open: GrafoscopioDocs manual ]. (aBuilder item: #GfManualPDF) label: 'Manual (PDF)'; order: 2; @@ -28,12 +28,12 @@ GfWorldMenu class >> helpMenuOn: aBuilder [ label: 'Dataviz'; order: 3; parent: #GfHelpAndDocs; - action: [ GrafoscopioNotebook open: DatavizDocs introNotebook ]. + action: [ GrafoscopioNewNotebook open: DatavizDocs introNotebook ]. (aBuilder item: #GfHelpDevNotes) label: 'Devs''s notes'; order: 4; parent: #GfHelpAndDocs; - action: [ GrafoscopioNotebook open: GrafoscopioDocs devNotes ]. + action: [ GrafoscopioNewNotebook open: GrafoscopioDocs devNotes ]. (aBuilder item: #GfHelpAbout) label: 'About Grafoscopio'; order: 5; @@ -42,18 +42,43 @@ GfWorldMenu class >> helpMenuOn: aBuilder [ ] { #category : #'world menu' } -GfWorldMenu class >> launchMenuOn: aBuilder [ +GfWorldMenu class >> launchCompatibilityMenuOn: aBuilder [ (aBuilder item: #'New notebook') label: 'New notebook'; order: 1; - parent: #GfLaunch; + parent: #GfLaunchCompatibility; action: [ GrafoscopioNotebook new openDefault ]. (aBuilder item: #'Notebook from file...') label: 'Notebook from file...'; order: 2; - parent: #GfLaunch; + parent: #GfLaunchCompatibility; action: [ GrafoscopioNotebook new openFromFileSelector ]. + (aBuilder item: #'Notebook from the Internet...') + label: 'Notebook from the Internet...'; + order: 3; + parent: #GfLaunchCompatibility; + action: [ GrafoscopioNotebook new openFromUrlUI ]. + (aBuilder item: #recentNotebooks) + label: 'Recent notebooks...'; + order: 4; + parent: #GfLaunchCompatibility; + action: [ GfUIHelpers openFromRecentlyUsed ] +] + +{ #category : #'world menu' } +GfWorldMenu class >> launchMenuOn: aBuilder [ + + (aBuilder item: #'New notebook') + label: 'New notebook'; + order: 1; + parent: #GfLaunch; + action: [ GrafoscopioNewNotebook new openDefault ]. + (aBuilder item: #'Notebook from file...') + label: 'Notebook from file...'; + order: 2; + parent: #GfLaunch; + action: [ GrafoscopioNewNotebook new openFromFileSelector ]. (aBuilder item: #GfLaunchOpenRecent) label: 'Open recent...'; order: 2; @@ -62,7 +87,7 @@ GfWorldMenu class >> launchMenuOn: aBuilder [ label: 'Notebook from the Internet...'; order: 3; parent: #GfLaunch; - action: [ GrafoscopioNotebook new openFromUrlUI ]. + action: [ GrafoscopioNewNotebook new openFromUrlUI ]. (aBuilder item: #recentNotebooks) label: 'Recent notebooks...'; order: 4; @@ -73,28 +98,36 @@ GfWorldMenu class >> launchMenuOn: aBuilder [ { #category : #'world menu' } GfWorldMenu class >> mainMenuItemsOn: aBuilder [ "I add the main Grafoscopio menu to the Pharo World." + - (aBuilder item: #Grafoscopio) label: 'Grafoscopio'; order: 1; - with: [ - (aBuilder item: #GfLaunch; label: 'Launch') target: self. - (aBuilder item: #GfUpdate; label: 'Update') target: self. - (aBuilder item: #GfHelpAndDocs; label: 'Help & Docs') target: self. ] + with: [ (aBuilder + item: #GfLaunch; + label: 'Launch') target: self. + (aBuilder + item: #GfLaunchCompatibility; + label: 'Launch Compatibility') target: self. + (aBuilder + item: #GfUpdate; + label: 'Update') target: self. + (aBuilder + item: #GfHelpAndDocs; + label: 'Help & Docs') target: self ] ] { #category : #'world menu' } GfWorldMenu class >> openRecentMenu: aBuilder [ - GrafoscopioNotebook recents + GrafoscopioNewNotebook recents do: [ :f | (aBuilder item: #'Open', f basename ) label: 'Open ', f basename; order: 1; parent: #GfLaunchOpenRecent; - action: [ GrafoscopioNotebook open: f ] ] + action: [ GrafoscopioNewNotebook open: f ] ] ] { #category : #'world menu' } diff --git a/repository/Grafoscopio/GrafoscopioAbstractNode.class.st b/repository/Grafoscopio/GrafoscopioAbstractNode.class.st index 18d291c..c4aa9d3 100644 --- a/repository/Grafoscopio/GrafoscopioAbstractNode.class.st +++ b/repository/Grafoscopio/GrafoscopioAbstractNode.class.st @@ -2,20 +2,234 @@ Class { #name : #GrafoscopioAbstractNode, #superclass : #Object, #instVars : [ + 'id', 'header', 'created', 'edited', - 'selected', - 'key', - 'icon', - 'body', - 'tags', - 'children', 'parent', - 'node', - 'nodesInPreorder', - 'links', - 'output' + 'tags' ], #category : #'Grafoscopio-Model' } + +{ #category : #'add/remove nodes' } +GrafoscopioAbstractNode >> addNodeAfterMe: genericNode [ + "Adds a generic node after the given node so they become slibings of the same parent" + + self parent + ifNil: [ self children add: genericNode. + genericNode parent: self ] + ifNotNil: [ self parent children add: genericNode after: self. + genericNode parent: self parent ]. + ^ genericNode +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> addTag: aTag [ + "Tags the recipient node with aTag (string). For the moment we will have only one tag. + In the future we will have several and there will be rules to know how tags interact with + each other" + self tags add: aTag + + +] + +{ #category : #exporting } +GrafoscopioAbstractNode >> asTreeNodePresenter [ + | node | + node := TreeNodePresenter new. + node + hasChildren: [ false ]; + children: [ {} ]; + content: self. + ^ node +] + +{ #category : #'as yet unclassified' } +GrafoscopioAbstractNode >> content [ + self subclassResponsibility +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> created [ + + ^ created +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> created: aTimestamp [ + "I tell when this object was created" + + created := aTimestamp +] + +{ #category : #movement } +GrafoscopioAbstractNode >> demote [ + "I move the current node down in the hierachy, making it a children of its current previous + slibing" + + | collection index predecessor | + collection := self parent children. + index := collection indexOf: self. + (index between: 2 and: collection size) + ifTrue: [ predecessor := collection before: self. + collection remove: self. + predecessor addNode: self] + +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> edited [ + ^ edited +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> edited: aTimestamp [ + "I store the last time when a node was edited. + Because nodes in the notebook have a autosave feature, I'm updated automatically when nodes are + edited from the GUI. + + If I'm in the notebook root (i.e. node's level equals 0) I should store the last time the notebook + was saved on the hard drive." + edited := aTimestamp +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> header [ + "Returns the receiver header" + + ^ header +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> header: anObject [ + "Sets the receivers header" + + header := anObject +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> id [ + ^id +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> id: aChecksum [ + "I'm a unique identifier that changes when node content changes (i.e. header, body, links)." + + id := aChecksum +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> initialize [ + "I create a empty new node" + + super initialize. + self header: 'newHeader'. + id := UUID new. + created := DateAndTime now. + edited := DateAndTime now +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> level [ + "Returns the level of the node. See the setter message for details" + + ^ parent ifNil: [ 0 ] ifNotNil: [ 1 + parent level ] +] + +{ #category : #movement } +GrafoscopioAbstractNode >> moveDown [ + "Moves the current node a place before in the children collection where is located" + + self parent moveDown: self +] + +{ #category : #movement } +GrafoscopioAbstractNode >> moveUp [ + "Moves the current node a place before in the children collection where is located" + + self parent moveUp: self +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> openIn: aNotebook [ + aNotebook header text: self header. + aNotebook body: (aNotebook instantiate: self specModelClass new). + aNotebook body content: self content +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> parent [ + "Returns the parent of the current node" + ^ parent +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> parent: aNode [ + "A parent is a node that has the current node in its children" + aNode ifNil: [ + parent := aNode. + ^self ]. + aNode parent = self ifTrue: [ ^ self ]. + parent := aNode. + (aNode children includes: self) + ifFalse: [ aNode addNode: self ] + +] + +{ #category : #movement } +GrafoscopioAbstractNode >> promote [ + "Moves the current node up in the hierachy, making it a slibing of its current parent" + | collection grandparent | + collection := self parent children. + grandparent := self parent parent. + collection isNotNil & grandparent isNotNil + ifTrue: [ + (grandparent children) add: self after: (self parent). + self parent: grandparent. + collection remove: self.] + + +] + +{ #category : #'as yet unclassified' } +GrafoscopioAbstractNode >> shouldAskBeforeRemove [ + self subclassResponsibility +] + +{ #category : #'as yet unclassified' } +GrafoscopioAbstractNode >> specModelClass [ + ^ GrafoscopioTextModel +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> tagAs: aTag [ + self + error: + 'tags are not used as markers anymore. Use addTag: instead and ensure you are not relying on text/codigo etc.' +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> tags [ + "I returns the receiver tags." + + ^ tags ifNil: [ tags := Set new ] +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> tags: aCollection [ + tags := aCollection +] + +{ #category : #accessing } +GrafoscopioAbstractNode >> title [ + "Returns the receiver header" + + ^ header size > 10 ifTrue: [ (header readStream next: 8) , '...' ] +] + +{ #category : #'as yet unclassified' } +GrafoscopioAbstractNode >> updateEditionTimestamp [ + self edited: DateAndTime now +] diff --git a/repository/Grafoscopio/GrafoscopioCodeNode.class.st b/repository/Grafoscopio/GrafoscopioCodeNode.class.st new file mode 100644 index 0000000..dcecc81 --- /dev/null +++ b/repository/Grafoscopio/GrafoscopioCodeNode.class.st @@ -0,0 +1,34 @@ +Class { + #name : #GrafoscopioCodeNode, + #superclass : #GrafoscopioAbstractNode, + #instVars : [ + 'icon', + 'body' + ], + #category : #'Grafoscopio-Model' +} + +{ #category : #adding } +GrafoscopioCodeNode >> addNode: aNode [ + "Adds the given node to the receivers collection of children, and sets this object as the parent + of the node" + "aNode parent = self ifTrue: [ ^ self ]." + self children add: aNode. + aNode parent: self. + ^aNode +] + +{ #category : #'as yet unclassified' } +GrafoscopioCodeNode >> content [ + ^ body ifNil:[ '' ] +] + +{ #category : #'as yet unclassified' } +GrafoscopioCodeNode >> header [ + ^ super header, ' (Code)' +] + +{ #category : #'as yet unclassified' } +GrafoscopioCodeNode >> shouldAskBeforeRemove [ + ^ self content isNotEmpty +] diff --git a/repository/Grafoscopio/GrafoscopioNewNotebook.class.st b/repository/Grafoscopio/GrafoscopioNewNotebook.class.st new file mode 100644 index 0000000..4d34f36 --- /dev/null +++ b/repository/Grafoscopio/GrafoscopioNewNotebook.class.st @@ -0,0 +1,1292 @@ +" +I am a Grafoscopio Notebook. + +Example: +| testTree nb | +testTree := GrafoscopioNode new becomeDefaultTestTree. +nb := GrafoscopioNotebook new. +nb notebookContent: testTree. +nb openWithSpec +" +Class { + #name : #GrafoscopioNewNotebook, + #superclass : #ComposablePresenter, + #instVars : [ + 'tree', + 'header', + 'body', + 'links', + 'windowMainMenu', + 'workingFile', + 'notebook', + 'debugMessage', + 'imagesList', + 'exporting' + ], + #classInstVars : [ + 'recents' + ], + #category : #'Grafoscopio-UI' +} + +{ #category : #utility } +GrafoscopioNewNotebook class >> SHA1For: aFile is: aSHA1String [ + "I verify that a file has the same signature that the one in a given string, + returning true in that case or false otherwise" + ^ (SHA1 new hashMessage: aFile asFileReference binaryReadStream contents) hex = aSHA1String + +] + +{ #category : #specs } +GrafoscopioNewNotebook class >> defaultSpec [ + + ^ SpecLayout composed + newColumn: [:tcol| + tcol newRow: [ :wrow | wrow add: #windowMainMenu ] height: (self toolbarHeight); + newRow: [:row | + row newColumn: [ :tc | + tc add: #tree + ] width: 300. + row newColumn: [ :bc | + bc newRow: [ :bcr | bcr add: #header ] height: self toolbarHeight. + bc add: #body; add: #links height: self toolbarHeight ]]] +] + +{ #category : #'instance creation' } +GrafoscopioNewNotebook class >> initialize [ + recents := Set new. +] + +{ #category : #'instance creation' } +GrafoscopioNewNotebook class >> kindsOfNode [ + ^ GrafoscopioAbstractNode allSubclasses +] + +{ #category : #'instance creation' } +GrafoscopioNewNotebook class >> newDefault [ + ^ self new. +] + +{ #category : #'instance creation' } +GrafoscopioNewNotebook class >> open: aFileReference [ + self newDefault openFromFile: aFileReference +] + +{ #category : #'instance creation' } +GrafoscopioNewNotebook class >> recents [ + ^ recents +] + +{ #category : #'instance creation' } +GrafoscopioNewNotebook class >> registerRecent: aFileReference [ + recents add: aFileReference +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> addCommandFrom: dictionary into: stream [ + dictionary keysAndValuesDo: [ :k :v | + k = 'thisNotebook' + ifTrue: [ + stream nextPutAll: (GrafoscopioUtils perform: v on: self) ]] +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> addNode [ + | newNode kind | + kind := self chooseKindOfNode. + kind isClass + ifTrue: [ newNode := kind new. + self currentNode addNodeAfterMe: newNode. + self notebookContent: notebook. + self selectedItem: newNode ] +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> askToSaveBeforeClosing [ + + | saveChanges | + + saveChanges := UIManager default + question: 'Do you want to save changes in the notebook before closing?' + title: 'Save changes before closing?'. + saveChanges ifNil: [ ^ self notebook unsavedNodes inspect ]. + ^ saveChanges +] + +{ #category : #operation } +GrafoscopioNewNotebook >> autoSaveBodyOf: aNode [ + | playground bodyContents | + bodyContents := aNode content. + self body class = GrafoscopioTextModel + ifTrue: [ self body body whenTextChanged: [ :arg | + aNode content: arg. + "self body body whenTextIsAccepted: [:bodyText | + self inform: bodyText. + aNode updateEditionTimestamp ]." + bodyContents = arg ifFalse: [ + "self inform: arg." + "aNode updateEditionTimestamp" ]]]. + self body body class = GlamourPresentationModel + ifFalse: [ ^ self ]. + playground := self body body glmPres. + playground + onChangeOfPort: #text + act: [ :x | + aNode content: (x pane port: #entity) value content. + "aNode updateEditionTimestamp." + "self inform: aNode edited" ] + +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> body [ + ^ body +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> body: anObject [ + body := anObject +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> checksumForRootSubtree [ + "I return the checksum (crypto hash) of the workingFile where this notebook is being stored. + I'm useful for data provenance and traceability of derivated files coming from this source + notebook." + self workingFile ifNil: [ ^ self ]. + self workingFile contents = '' ifTrue: [ ^ self ]. + ^ GrafoscopioUtils checksumFor: self workingFile +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> chooseKindOfNode [ + | idx values | + values := self kindsOfNode. + idx := UIManager default + chooseFrom: values + lines: {} + title: 'What kind of node? '. + ^ idx = 0 + ifTrue: [ nil ] + ifFalse: [ values at: idx ] +] + +{ #category : #'as yet unclassified' } +GrafoscopioNewNotebook >> content [ + self shouldBeImplemented. +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> copyNodeToClipboard [ + tree highlightedItem content copyToClipboard. + self notebookContent: notebook. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> createNewExample [ + | node0 node1 | + node0 := GrafoscopioTextNode new + created: DateAndTime now ; + header: 'Arbol principal'; + yourself. + node1 := GrafoscopioTextNode new + created: DateAndTime now; + header: 'Node 1'; + body: ''; + yourself. + node0 addNode: node1. + ^ node0 +] + +{ #category : #operation } +GrafoscopioNewNotebook >> currentNode [ + ^ tree highlightedItem + ifNil: [ notebook children + ifEmpty: [ notebook root ] + ifNotEmpty: [ notebook children first ] ] + ifNotNil: [ :v | v content ] +] + +{ #category : #operation } +GrafoscopioNewNotebook >> currentNodeContent [ + ^ self currentNode content +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> cutNodeToClipboard [ + self copyNodeToClipboard; removeNode. +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> debugMessage [ + ^ debugMessage ifNil: [ self defineDebugMessageUI ] +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> debugMessage: aGrafoscopioNodeSelector [ + "I define a message that can be used for debugging purposes in the current notebook." + debugMessage := aGrafoscopioNodeSelector +] + +{ #category : #operation } +GrafoscopioNewNotebook >> debugWithSelector: aSymbol [ + "I invoke a message to debug in the current node. In the future the debugging scope can be changed to + include different elements instead of the current node." + | currentNode nodeContent | + currentNode := tree highlightedItem. + currentNode ifNil: [ ^ self ]. + nodeContent := currentNode content. + ^ (nodeContent perform: aSymbol asSymbol) inspect +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> defineDebugMessageUI [ + | answer | + answer := UIManager default + request: 'Define debug message to be send to a selected node in this notebook.' + initialAnswer: 'messageNoDebugSelector'. + self debugMessage: answer +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> demoteNode [ + | editedNode | + editedNode := tree highlightedItem content. + editedNode demote. + self notebookContent: notebook. +] + +{ #category : #'as yet unclassified' } +GrafoscopioNewNotebook >> downloadImages [ + "I download all images in a notebook into a local folder that respects relative paths. + So if a image refers to http://mysite.com/uploads/chap1/myimage.png, it will be stored + into: 'uploads/chap1/myimage.png' in the same folder where the notebook is stored. + This is helpful for notebooks conversions that expect to have local images in particular + locations." + + | parentFolder | + parentFolder := self workingFile parent. + self. + ^ self imagesList do: [ :each | | relativePathString link | + link := each contents asUrl. + relativePathString := link directory. + relativePathString ifNotEmpty: [ + GrafoscopioUtils ensureCreateDirectory: relativePathString into: parentFolder ]] +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> ensureNotExporting [ + self isAlreadyExporting + ifTrue: [ ^ self error: ' Already exporting! Please wait ' ] +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportAllSubtreesAsMarkup [ + | toBeExported | + toBeExported := self notebook selectMarkupSubtreesToExport. + toBeExported ifEmpty: [ ^ self ]. + toBeExported do: [ :each | self subtreeAsMarkdownFileFor: each ]. + self inform: toBeExported size asString , ' exported markdown subtrees.' +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportAsHTML [ + "I export the current tree/document to a HTML file, using pandoc external app. + I suppose pandoc is already installed and available in the system." + + | htmlFile | + self markdownFile exists + ifTrue: [ self markdownFile delete ]. + self exportAsMarkdown. + htmlFile := self htmlFile. + htmlFile exists + ifTrue: [ htmlFile delete ]. + self + exportUsing: + {'--standalone'. + self markdownFile fullName. + '--output'. + htmlFile fullName} + output: htmlFile fullName + " + + + Smalltalk platformName = 'Win32' + ifTrue: [WinProcess createProcess: 'pandoc --standalone ', self markdownFile fullName, ' -o ', htmlFile]." +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportAsLaTeX [ + "I export the current tree/document to a LaTeX file, using pandoc external app. + I suppose pandoc is already installed and available in the system." + | texFile | + self markdownFile exists ifTrue: [ self markdownFile delete ]. + self halt. + + "self exportAsMarkdown.""<- This violates the separation of concenrs. Markdown exportation should + be explicit. There is still the issue of how to deal with desynchronization between a notebook + which has unsaved changes as markdown.... TO BE REVIWED!" + texFile := self markdownFile parent fullName,'/', self markdownFile basenameWithoutExtension, '.tex'. + texFile asFileReference exists ifTrue: [ texFile asFileReference delete ]. + "OSProcess command: 'pandoc --standalone ', self markdownFile fullName, ' -o ', texFile." + self inform: ('File exported as: ', String cr, texFile). +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportAsMarkdown [ + "I export the current working tree/document to a markdown file." + workingFile + ifNil: [ self inform: 'File NOT exported. Please save the notebook on hard drive first' ] + ifNotNil: [ self exportNode: (self notebook) asMarkdownIn: (self markdownFile) ] +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportAsPDF [ + "I export the current tree/document to a PDF file, using pandoc and LaTeX external apps. + The latex engine used is xelatex, to minimize errors and warnings related with UTF8 support. + I suppose all them are already installed and defined in the system." + + | pandocCommonCommand | + self ensureNotExporting. + self exportAsMarkdown. + self pdfFile ensureDelete. + pandocCommonCommand := self pandocOptionsComputed , ' ' + , self markdownFile fullName , ' --output ' , self pdfFile fullName. + ^ self + exportUsing: ((' ' split: pandocCommonCommand) reject: #isEmpty) + output: self pdfFile fullName +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportAsSton: aNotebook on: aFileStream [ + aNotebook flatten. + self notebook root updateEditionTimestamp. + (STON writer on: aFileStream) + newLine: String crlf; + prettyPrint: true; + keepNewLines: true; + nextPut: aNotebook children. + +] + +{ #category : #utility } +GrafoscopioNewNotebook >> exportLinkContent [ + Transcript show: (self currentNodeContent asMarkdown) +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportNode: aGrafoscopioNode asMarkdownIn: aFile [ + "I export the current tree/document to a markdown file" + aFile ensureDelete. + aFile + ensureCreateFile; + writeStreamDo: [:stream | + stream + nextPutAll: + ('---', String cr, + 'exportedFrom: ', self checksumForRootSubtree, String cr) withInternetLineEndings. + aGrafoscopioNode metadataAsYamlIn: stream. + stream + nextPutAll: + ('---', String cr, String cr) withInternetLineEndings, + aGrafoscopioNode asMarkdown ]. + self inform: 'Exported as: ', String cr, aFile fullName +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportUsing: arguments [ + self ensureNotExporting. + exporting := (#pandoc command arguments: arguments) future. + exporting + onSuccessDo: [ :val | + exporting := nil. + self + inform: 'File exported as: ' , String cr , self pdfFile fullName ]. + exporting + onFailureDo: [ :e | + exporting := nil. + self + inform: 'Error exporting, ' , self pdfFile fullName , ': ' , e messageText ] +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> exportUsing: arguments output: aName [ + self ensureNotExporting. + exporting := (#pandoc command arguments: arguments) future. + exporting + onSuccessDo: [ :val | + exporting := nil. + self inform: 'File exported as: ' , String cr , aName ]. + exporting + onFailureDo: [ :e | + exporting := nil. + self inform: 'Error exporting, ' , aName , ': ' , e messageText ]. + ^ exporting +] + +{ #category : #api } +GrafoscopioNewNotebook >> extent [ + ^900@500 +] + +{ #category : #'as yet unclassified' } +GrafoscopioNewNotebook >> findAndReplace [ + | currentNode replaceGUI findString replaceString | + currentNode := tree highlightedItem content. + replaceGUI := GrafoscopioReplace new. + replaceGUI openWithSpec. + replaceGUI ok + on: [ ] + do: [ + findString := replaceGUI returnValues at: 'find'. + replaceString := replaceGUI returnValues at: 'replace'. + currentNode find: findString andReplaceWith: replaceString. ] + +] + +{ #category : #testing } +GrafoscopioNewNotebook >> hasAWorkingFileDefined [ + self workingFile ifNil: [ ^ false ] ifNotNil: [ ^ true ] +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> header [ + ^ header +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> header: anObject [ + header := anObject +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> htmlFile [ + ^ (self markdownFile parent fullName , '/' + , self markdownFile basenameWithoutExtension , '.html') + asFileReference +] + +{ #category : #operation } +GrafoscopioNewNotebook >> htmlToMarkdown [ + self currentNodeContent htmlToMarkdown. + self updateBodyFor: self currentNode +] + +{ #category : #operation } +GrafoscopioNewNotebook >> htmlToMarkdownSubtree [ + self currentNodeContent htmlToMarkdownSubtree. + self updateBodyFor: self currentNode +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> imagesList [ + imagesList ifNil: [ ^ #('No images list for this notebook') ]. + ^ imagesList +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> imagesList: anObject [ + self halt. + imagesList := anObject +] + +{ #category : #'as yet unclassified' } +GrafoscopioNewNotebook >> importImages [ + self imagesList: (Pandoc listImagesFrom: self markdownFile). + self inform: 'All notebook images has been imported.', String cr, 'Now you can list and download them.' +] + +{ #category : #operation } +GrafoscopioNewNotebook >> importLinkContent [ + "I see if a node link is an url located at 'http://ws.stfx.eu', wich means that is a shared + workspace, and convert the node body to an interactive playground" + | currentNode nodeContent | + currentNode := tree highlightedItem. + currentNode ifNil: [ ^ self ]. + nodeContent := currentNode content. + nodeContent importPlaygroundLink. + nodeContent importHtmlLink. + self updateBodyFor: currentNode +] + +{ #category : #initialization } +GrafoscopioNewNotebook >> initialize [ + super initialize. + self + notebook: (self createNewExample ); + title: ' New | Grafoscopio notebook'. + self notebookContent: self notebook. + +] + +{ #category : #initialization } +GrafoscopioNewNotebook >> initializePresenter [ + tree + whenHighlightedItemChanged: + [ :item | tree highlightedItem ifNotNil: [ self updateBodyFor: item content ] ]. + tree + whenTreeUpdated: [ :item | item ifNotNil: [ self updateBodyFor: item ] ]. + header + whenTextChanged: [ :arg | + tree highlightedItem content header = arg + ifFalse: [ tree highlightedItem content header: arg. + tree highlightedItem content updateEditionTimestamp. + tree roots: tree roots ] ]. + links + whenTextChanged: [ :arg | + (tree highlightedItem content respondsTo: #link:) + ifTrue: [ tree highlightedItem content link: arg. + tree highlightedItem content updateEditionTimestamp ] ] +] + +{ #category : #initialization } +GrafoscopioNewNotebook >> initializeWidgets [ + windowMainMenu := self topBar. + header := self newTextInput. + header autoAccept: true. + body := self newText. + body class logCr. + body disable. + body text: '<- Select a node'. + body autoAccept: true. + links := self newTextInput. + tree := self newTree. + tree + childrenBlock: [:node | node children]; + displayBlock: [:node | node title ]. + self focusOrder + add: tree; + add: header; + add: body; + add: links. + self askOkToClose: true. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> isAlreadyExporting [ + ^ exporting isNotNil +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> isSaved [ + "I tell if a notebook has been saved in a persistence storage, including last editions." + ^ self hasAWorkingFileDefined and: [self isSavedAfterLastEdition ]. +] + +{ #category : #testing } +GrafoscopioNewNotebook >> isSavedAfterLastEdition [ + ^ self notebook isSavedAfterLastEdition +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> kindsOfNode [ + ^ self class kindsOfNode +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> links [ + ^ links +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> links: anObject [ + links := anObject +] + +{ #category : #'as yet unclassified' } +GrafoscopioNewNotebook >> linksList [ + | currentNode | + currentNode := tree highlightedItem content. + GrafoscopioLinksList new + content: currentNode; + openWithSpec + +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> listImagesUI [ + ListPresenter new + title: 'Images files list'; + items: self imagesList ; + openWithSpec +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> loadFromFile: aFileReference [ + "I load the contents of aFileReference into a GrafoscopioNotebook, without opening it." + (aFileReference basename endsWith: 'ston') ifFalse: [ ^ self ]. + self workingFile: aFileReference. + self notebook: ((STON fromString: self workingFile contents) at: 1) parent. + self title: self workingFile basenameWithIndicator, ' | Grafoscopio notebook'. + self notebookContent: self notebook. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> markdownFile [ + "I define the location of the markdown file where the notebook will be exported" + | markdownFile | + markdownFile := (((workingFile parent) / workingFile basenameWithoutExtension) fullName, '.markdown') asFileReference. + ^ markdownFile +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> markdownFileChecksum [ + self workingFile ifNil: [ ^ self ]. + self workingFile contents = '' ifTrue: [ ^ self ]. + ^ GrafoscopioUtils checksumFor: self markdownFile +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> markdownFileChecksumUpto: anInteger [ + "I cut the markdownFileCheckup upto a given integer. + Type coersion is needed, because this message argument can be read from a string in %metadata nodes. + Maybe the way used by playgrounds to import text as commands can be useful here." + ^ self markdownFileChecksum copyFrom: 1 to: anInteger asInteger. +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> metadata [ + ^ self notebook metadata +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> moveSelectedNodeDown [ + | editedNode | + editedNode := tree selectedItem content. + editedNode moveDown. + self notebookContent: notebook. + tree needRebuild: true. +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> moveSelectedNodeUp [ + | editedNode | + editedNode := tree highlightedItem content. + editedNode moveUp. + self notebookContent: notebook +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> navigateRelativePathFor: aFileString [ + "Given a relative path according to location of the notebook's workingFile, + I navigate to that file if exist and create it, including subdirectories if it does not exist. + If the relative path is located in a subdirectory that shares the route with the notebooks working + file, it must start with the folders name, + without using './' to point the same shared root " + + | finalLocation pathSegments | + aFileString ifEmpty: [ ^ self ]. + aFileString asUrl host ifNotNil: [ ^self ]. + finalLocation := workingFile parent. + pathSegments := aFileString splitOn: '/'. + pathSegments allButLastDo: [ :segment | + (segment = '..') + ifTrue: [ finalLocation := finalLocation parent ] + ifFalse: [ + finalLocation := finalLocation / segment. + finalLocation exists ifFalse: [ finalLocation ensureCreateDirectory ]]]. + finalLocation := finalLocation / (pathSegments last). + finalLocation exists ifFalse: [ finalLocation ensureCreateFile ]. + ^ finalLocation +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> notebook [ + ^ notebook +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> notebook: anObject [ + notebook := anObject +] + +{ #category : #api } +GrafoscopioNewNotebook >> notebookContent: aTree [ + tree + roots: (aTree children collect: [ :gfcNode | gfcNode asTreeNodePresenter ]) +] + +{ #category : #initialization } +GrafoscopioNewNotebook >> notebookSubMenu [ + ^ MenuPresenter new + addGroup: [ :group | + group + addItem: [ :item | + item + name: 'Save'; + icon: (Smalltalk ui icons iconNamed: #smallSave); + shortcut: $s command; + action: [ self saveWorkingNotebook ] ]. + group + addItem: [ :item | + item + name: 'Save as...'; + icon: (Smalltalk ui icons iconNamed: #smallSaveAs); + action: [ self saveToFileUI ] ]. + group + addItem: [ :item | + item + name: 'Import images'; + icon: (Smalltalk ui icons iconNamed: #processBrowser); + action: [ self importImages ] ]. + group + addItem: [ :item | + item + name: 'See images list'; + icon: (Smalltalk ui icons iconNamed: #processBrowser); + action: [ self listImagesUI ] ]. + group + addItem: [ :item | + item + name: 'Download images'; + icon: (Smalltalk ui icons iconNamed: #processBrowser); + action: [ self downloadImages ] ]. + group + addItem: [ :item | + item + name: 'Export as markdown'; + icon: (Smalltalk ui icons iconNamed: #smallSaveAs); + action: [ self exportAsMarkdown ] ]. + group + addItem: [ :item | + item + name: 'Export as html'; + icon: (Smalltalk ui icons iconNamed: #smallWindow); + action: [ self exportAsHTML ] ]. + group + addItem: [ :item | + item + name: 'Export as LaTeX'; + icon: (Smalltalk ui icons iconNamed: #smallPrint); + action: [ self exportAsLaTeX ] ]. + group + addItem: [ :item | + item + name: 'Export as pdf'; + icon: (Smalltalk ui icons iconNamed: #smallPrint); + action: [ self exportAsPDF ] ]. + group + addItem: [ :item | + item + name: 'See html'; + icon: (self iconNamed: #smallInspectIt); + action: [ self seeHtml ] ]. + group + addItem: [ :item | + item + name: 'See pdf'; + icon: (Smalltalk ui icons iconNamed: #smallInspectIt); + action: [ self seePdf ] ]. + group + addItem: [ :item | + item + name: 'Import Article'; + icon: (Smalltalk ui icons iconNamed: #smallInspectIt); + action: [ self importArticle ] ]. + group + addItem: [ :item | + item + name: 'Define debug message...'; + icon: Smalltalk ui icons glamorousBug; + action: [ self defineDebugMessageUI ] ] ] +] + +{ #category : #'event handling' } +GrafoscopioNewNotebook >> okToChange [ + + self isSaved ifTrue: [ ^ true ] ifFalse: [ ^ self askToSaveBeforeClosing ] +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> openDefault [ +"I open a new default notebook" + + ^ self class new openWithSpec. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> openFromFile: aFileReference [ + self class registerRecent: aFileReference. + self loadFromFile: aFileReference. + ^ self openWithSpec. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> openFromFileSelector [ + + | file nb | + file := UIManager default + chooseExistingFileReference:'Choose a file' + extensions: #('ston') + path: FileLocator documents. + file ifNil: [ + self inform: 'No file selected'. + ^ self ]. + self workingFile: file. + nb := self class new. + nb openFromFile: self workingFile. + GfUIHelpers updateRecentNotebooksWith: workingFile +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> openFromUrl: url [ +"Opens a tree from a file named aFileName" + + | fileName sanitized | + sanitized := GrafoscopioUtils sanitize: url. + fileName := sanitized segments last. + GrafoscopioUtils + downloadingFrom: sanitized + withMessage: 'Downloading document...' + into: FileLocator temp. + self class new openFromFile: (FileLocator temp / fileName) +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> openFromUrlUI [ + "This method generates the UI for the openFromUrl: method, it asks for a URL from the user" + + | fileUrl | + "GrafoscopioBrowser configureSettings." + fileUrl := UIManager default + textEntry: 'Enter the URL' + title: 'Open notebook from URL'. + fileUrl isNil ifTrue: [ ^nil ]. + self class new openFromUrl: fileUrl +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> pandocOptions [ + ^ self notebook pandocOptions +] + +{ #category : #utilities } +GrafoscopioNewNotebook >> pandocOptionsComputed [ + "I convert the pandoc options array into a single line that can be used with the pandoc command." + + | result | + result := '' writeStream. + self pandocOptions ifNil: [ ^ '' ]. + self pandocOptions + do: [ :option | + option isDictionary + ifTrue: [ + self addCommandFrom: option into: result ] + ifFalse: [ + result + nextPutAll: option] ]. + ^ result contents +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> pasteNodeFromClipboard [ + tree highlightedItem content pasteFromClipboard. + self notebookContent: notebook. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> pdfFile [ + "I define the location of the markdown file where the notebook will be exported" + | pdfFile | + pdfFile := (self markdownFile parent fullName,'/', self markdownFile basenameWithoutExtension, '.pdf') asFileReference. + ^ pdfFile. + +] + +{ #category : #initialization } +GrafoscopioNewNotebook >> projectSubMenu [ + ^ MenuModel new + addGroup: [ :group | + group + addItem: [ :item | + item + name: 'Activate remote repository...'; + icon: Smalltalk ui icons smallPushpinIcon; + action: [ self inform: 'To be implemented ...' ] ]. + group + addItem: [ :item | + item + name: 'Activate local repository...'; + icon: Smalltalk ui icons homeIcon; + action: [ self inform: 'To be implemented ...' ] ]. + group + addItem: [ :item | + item + name: 'Add file...'; + icon: Smalltalk ui icons newerPackagesAvailableIcon; + action: [ self inform: 'To be implemented ...' ] ]. + group + addItem: [ :item | + item + name: 'Delete file...'; + icon: Smalltalk ui icons packageDeleteIcon; + action: [ self inform: 'To be implemented ...' ] ]. + group + addItem: [ :item | + item + name: 'Commit to repository'; + icon: Smalltalk ui icons smallScreenshotIcon; + action: [ self inform: 'To be implemented ...' ] ]. + group + addItem: [ :item | + item + name: 'Credentials'; + icon: (self iconNamed: #userIcon); + action: [ self inform: 'To be implemented ...' ] ] ] +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> promoteNode [ + | editedNote | + editedNote := tree selectedItem content. + editedNote promote. + self notebookContent: notebook +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> removeNode [ + | contentToDelete parentContent newSelectedContent children | + tree selectedItem + ifNil: [ ^ self inform: 'No node available or properly selected ' ]. + contentToDelete := tree selectedItem content. + contentToDelete shouldAskBeforeRemove + ifTrue: [ (UIManager default + questionWithoutCancel: + 'The selected node has children and / or content. This change so far cannot be undone. Are you sure you want to proceed? ' + title: 'Remove node') + ifFalse: [ ^ self ] ]. + parentContent := contentToDelete parent. + children := parentContent children. + children size > 1 + ifTrue: [ children last = contentToDelete + ifTrue: [ newSelectedContent := children at: children size - 1 ] ] + ifFalse: [ newSelectedContent := parentContent ]. + contentToDelete parent removeNode: contentToDelete. + self notebookContent: notebook. + self resetSelectedItem +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> resetSelectedItem [ + tree selectedIndex: (tree selectedIndex min: notebook children size). + tree highlightedItem: tree selectedItem. + tree updatePresenter. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> saveToFile: aFileReference [ + "I save the current tree/document to a file and update storage timestamp." + + aFileReference ifNil: [ self inform: 'No file selected for saving. Save NOT done.'. ^ self ]. + workingFile := aFileReference. + self workingFile ensureDelete. + self workingFile writeStreamDo: [:stream | + self exportAsSton: self notebook on: stream ]. + self title: self workingFile basenameWithIndicator, ' | Grafoscopio notebook'. + self inform: ('File saved at: ', String cr, self workingFile fullName). + GfUIHelpers updateRecentNotebooksWith: aFileReference. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> saveToFileUI [ + + | file | + + file := UIManager default + chooseForSaveFileReference: 'Save notebook to file as...' + extensions: #('ston') + path: (workingFile ifNotNil: [ workingFile parent ] ifNil: [ FileLocator documents ] ). + file + ifNil: [ self inform: 'Saving to file cancelled'. ^ self ] + ifNotNil:[self saveToFile: file]. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> saveWorkingNotebook [ + "Saves the current tree to the user predefined file location used when he/she opened it." + self workingFile + ifNil: [ self saveToFileUI ] + ifNotNil: [ self saveToFile: workingFile ]. + self notebook root updateEditionTimestamp. + GfUIHelpers updateRecentNotebooksWith: workingFile + + +] + +{ #category : #inspecting } +GrafoscopioNewNotebook >> seeHtml [ + self pdfFile exists + ifTrue: [ (#open command argument: self htmlFile fullName) schedule ] +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> seePdf [ + self exportAsPDF + onSuccessDo: [ :v | (#open command argument: self pdfFile fullName) schedule ] +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> selectedItem: anItem [ + tree selectedItem: (tree roots detect: [ : p | p content = anItem ]). + tree highlightedItem: tree selectedItem. +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> subtreeAsMarkdown [ + | currentNode | + currentNode := tree highlightedItem content. + self inform: ('Exported as: ', String cr, (self subtreeAsMarkdownFileFor: currentNode) fullName ) +] + +{ #category : #persistence } +GrafoscopioNewNotebook >> subtreeAsMarkdownFileFor: aNode [ + | exportedFile | + aNode links ifEmpty: [ ^ self ]. + exportedFile:= self navigateRelativePathFor: aNode links last. + exportedFile class = self class ifTrue: [ ^ self ]. + self exportNode: aNode asMarkdownIn: exportedFile. + ^ exportedFile +] + +{ #category : #'editing nodes' } +GrafoscopioNewNotebook >> toggleCodeNode [ + | currentNode | + currentNode := tree highlightedItem. + currentNode content toggleCodeText. + self updateBodyFor: currentNode. +] + +{ #category : #initialization } +GrafoscopioNewNotebook >> topBar [ + ^ MenuPresenter new + addGroup: [ :group | + group + addItem: [ :item | + item + name: 'Notebook'; + icon: (self iconNamed: #smallObjects); + subMenu: self notebookSubMenu ]. + group + addItem: [ :item | + item + name: 'Project'; + icon: (self iconNamed: #catalog); + subMenu: self projectSubMenu ] ]; + addGroup: [ :group | + group + addItem: [ :item | + item + name: nil; + description: 'Save notebook'; + icon: (self iconNamed: #glamorousSave); + action: [ self saveWorkingNotebook ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Export all Markdown subtrees'; + icon: (self iconNamed: #glamorousMore); + action: [ self exportAllSubtreesAsMarkup ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Cut'; + icon: (self iconNamed: #smallCut); + action: [ self cutNodeToClipboard ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Copy'; + icon: (self iconNamed: #smallCopy); + action: [ self copyNodeToClipboard ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Paste'; + icon: (self iconNamed: #smallPaste); + action: [ self pasteNodeFromClipboard ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Find & Replace'; + icon: (self iconNamed: #smallFind); + action: [ self findAndReplace ] ] ]; + addGroup: [ :group | + group + addItem: [ :item | + item + name: nil; + description: 'Add node'; + icon: MendaIcons new plusIcon; + action: [ self addNode ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Delete node'; + icon: MendaIcons new minusIcon; + action: [ self removeNode ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Move node up'; + icon: MendaIcons new arrowUpIcon; + action: [ self moveSelectedNodeUp ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Move node down'; + icon: MendaIcons new arrowDownIcon; + action: [ self moveSelectedNodeDown ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Move node left'; + icon: MendaIcons new arrowLeftIcon; + action: [ self promoteNode ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Move node right'; + icon: MendaIcons new arrowRightIcon; + action: [ self demoteNode ] ] ]; + addGroup: [ :group | + group + addItem: [ :item | + item + name: nil; + description: 'Toggle: code <--> text'; + icon: MendaIcons new smalltalkCodeIcon; + action: [ self toggleCodeNode ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'List node links'; + icon: (self iconNamed: #tinyMenu); + action: [ self linksList ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Visit link'; + icon: (self iconNamed: #glamorousRight); + action: [ self visitNodeLink ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Import link content'; + icon: (self iconNamed: #glamorousOpenFromUrl); + action: [ self importLinkContent ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Export link content'; + icon: (self iconNamed: #glamorousSaveToUrl); + action: [ self exportLinkContent ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'HTML to Markdown'; + icon: (self iconNamed: #smallProfile); + action: [ self htmlToMarkdown ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'HTML to Markdown subtree'; + icon: (self iconNamed: #hierarchy); + action: [ self htmlToMarkdownSubtree ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Tag as...'; + icon: MendaIcons new tagAddIcon; + action: [ self inform: 'To be implemented...' ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Untag ....'; + icon: MendaIcons new tagMinusIcon; + action: [ self inform: 'To be implemented...' ] ]. + group + addItem: [ :item | + item + name: nil; + description: 'Edit tags...'; + icon: FontAwesomeIcons new tagsIcon; + action: [ self inform: 'To be implemented...' ] ] ]; + addGroup: [ :debug | + debug + addItem: [ :item | + item + name: nil; + description: 'Debug'; + icon: + (self iconNamed: #glamorousBug); + action: [ self debugWithSelector: self debugMessage ] ] ] +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> tree [ + ^ tree +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> tree: anObject [ + tree := anObject +] + +{ #category : #operation } +GrafoscopioNewNotebook >> updateBodyFor: aNode [ + self needRebuild: false. + tree needRebuild: false. + body needRebuild: true. + aNode openIn: self. + self buildWithSpecLayout: self class defaultSpec +] + +{ #category : #operation } +GrafoscopioNewNotebook >> visitNodeLink [ + + tree highlightedItem content visitLastLink. +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> windowMainMenu [ + ^ windowMainMenu +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> windowMainMenu: anObject [ + windowMainMenu := anObject +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> workingFile [ + ^ workingFile +] + +{ #category : #accessing } +GrafoscopioNewNotebook >> workingFile: aFileReference [ + workingFile := aFileReference. +] + +{ #category : #'as yet unclassified' } +GrafoscopioNewNotebook >> wrapBodyLines [ + self currentNodeContent wrapBodyLines. + self updateBodyFor: self currentNode +] diff --git a/repository/Grafoscopio/GrafoscopioNode.class.st b/repository/Grafoscopio/GrafoscopioNode.class.st index 8bc38b7..9cfe8d7 100644 --- a/repository/Grafoscopio/GrafoscopioNode.class.st +++ b/repository/Grafoscopio/GrafoscopioNode.class.st @@ -12,10 +12,11 @@ Class { #name : #GrafoscopioNode, #superclass : #Object, #instVars : [ - 'id', 'header', + 'headers', 'created', 'edited', + 'selected', 'key', 'icon', 'body', @@ -23,7 +24,10 @@ Class { 'children', 'parent', 'node', - 'links' + 'level', + 'nodesInPreorder', + 'links', + 'output' ], #classInstVars : [ 'clipboard' @@ -851,8 +855,7 @@ GrafoscopioNode >> moveDown [ GrafoscopioNode >> moveDown: aNode [ | index | "Moves the current node a place before in the children collection where is located" - index := (children indexOf: aNode) max: 1 . - + index := children indexOf: aNode. children swap: index with: (index + 1 min: children size) ] diff --git a/repository/Grafoscopio/GrafoscopioNodeTest.class.st b/repository/Grafoscopio/GrafoscopioNodeTest.class.st index d285964..06a9b22 100644 --- a/repository/Grafoscopio/GrafoscopioNodeTest.class.st +++ b/repository/Grafoscopio/GrafoscopioNodeTest.class.st @@ -19,28 +19,28 @@ GrafoscopioNodeTest >> dummyHtml [ { #category : #tests } GrafoscopioNodeTest >> newTestTree [ | node0 node1 node2 node3 node4 | - node0 := GrafoscopioNode new - created: DateAndTime now printString; + node0 := GrafoscopioTextNode new + created: DateAndTime now; header: 'Arbol principal'. - node1 := GrafoscopioNode new - created: DateAndTime now printString; + node1 := GrafoscopioTextNode new + created: DateAndTime now; header: 'Markup'; body: 'I am just a node with markup'; tagAs: 'text'; links: 'temp.md'. - node2 := GrafoscopioNode new - created: DateAndTime now printString; + node2 := GrafoscopioTextNode new + created: DateAndTime now ; header: '%output Code'; tagAs: 'código'; body: '(ConfigurationOfGrafoscopio>>#version14:) sourceCode'. - node3 := GrafoscopioNode new - created: DateAndTime now printString; + node3 := GrafoscopioTextNode new + created: DateAndTime now ; header: '%invisible'; tagAs: 'text'; body: 'Just testing'. node1 addNode: node3. - node4 := GrafoscopioNode new - created: DateAndTime now printString; + node4 := GrafoscopioTextNode new + created: DateAndTime now ; header: 'Something'; tagAs: 'text'; body: '

else

'. @@ -54,7 +54,7 @@ GrafoscopioNodeTest >> newTestTree [ GrafoscopioNodeTest >> testAddingChildren [ | tree nnode orig | tree := self newTestTree. - nnode := GrafoscopioNode new. + nnode := GrafoscopioTextNode new. orig := tree children size. tree addNode: nnode. self assert: tree children size equals: orig + 1. @@ -64,9 +64,9 @@ GrafoscopioNodeTest >> testAddingChildren [ { #category : #tests } GrafoscopioNodeTest >> testDemoteNode [ | tree child1 child2 | - tree := GrafoscopioNode new. - child1 := GrafoscopioNode new. - child2 := GrafoscopioNode new. + tree := GrafoscopioTextNode new. + child1 := GrafoscopioTextNode new. + child2 := GrafoscopioTextNode new. tree addNode: child1; addNode: child2. @@ -79,7 +79,7 @@ GrafoscopioNodeTest >> testDemoteNode [ { #category : #tests } GrafoscopioNodeTest >> testFindAndReplace [ | tree | - tree := GrafoscopioNode new. + tree := GrafoscopioTextNode new. tree body: 'I''m only a test node.'. tree find: 'only' andReplaceWith: 'JUST'. self assert: (tree body findString: 'JUST') > 0. @@ -100,15 +100,15 @@ GrafoscopioNodeTest >> testHasMarkdownSubtreesToExport [ { #category : #tests } GrafoscopioNodeTest >> testInitializeIsOk [ - self shouldnt: [ GrafoscopioNode new ] raise: Error + self shouldnt: [ GrafoscopioTextNode new ] raise: Error ] { #category : #tests } GrafoscopioNodeTest >> testPromoteNode [ | tree child1 child2 | - tree := GrafoscopioNode new. - child1 := GrafoscopioNode new. - child2 := GrafoscopioNode new. + tree := GrafoscopioTextNode new. + child1 := GrafoscopioTextNode new. + child2 := GrafoscopioTextNode new. tree addNode: child1. child1 addNode: child2. child2 promote. @@ -132,7 +132,7 @@ GrafoscopioNodeTest >> testRemoveLeadingLineNumbersSized [ 9var nodes = tree.nodes(treeData); 10var links = tree.links(nodes); 11 '. - testNode := GrafoscopioNode new + testNode := GrafoscopioTextNode new body: copiedCode. testNode removeLeadingLineNumbersSized: 3. self assert: testNode body equals: ' @@ -165,7 +165,7 @@ GrafoscopioNodeTest >> testSanitizedLink [ | node link | link := 'docutopia://hackbo:hackbot'. - node := GrafoscopioNode new links: link. + node := GrafoscopioTextNode new links: link. self assert: (node sanitizeDefaultLink = 'https://docutopia.tupale.co/hackbo:hackbot') equals: true ] diff --git a/repository/Grafoscopio/GrafoscopioNotebook.class.st b/repository/Grafoscopio/GrafoscopioNotebook.class.st index 9a41ab0..b77fbaa 100644 --- a/repository/Grafoscopio/GrafoscopioNotebook.class.st +++ b/repository/Grafoscopio/GrafoscopioNotebook.class.st @@ -87,10 +87,8 @@ GrafoscopioNotebook >> addCommandFrom: dictionary into: stream [ { #category : #'editing nodes' } GrafoscopioNotebook >> addNode [ - | newNode | - newNode := self currentNode addNodeAfterMe. + self currentNode addNodeAfterMe. self notebookContent: notebook. - self selectedItem: newNode. ] { #category : #persistence } @@ -627,8 +625,7 @@ GrafoscopioNotebook >> moveSelectedNodeDown [ | editedNode | editedNode := tree selectedItem content. editedNode moveDown. - self notebookContent: notebook. - tree needRebuild: true. + self notebookContent: notebook ] { #category : #'editing nodes' } @@ -676,7 +673,8 @@ GrafoscopioNotebook >> notebook: anObject [ { #category : #api } GrafoscopioNotebook >> notebookContent: aTree [ tree - roots: (aTree children collect: [ :gfcNode | gfcNode asTreeNodePresenter ]) + roots: (aTree children collect: [ :gfcNode | gfcNode asTreeNodePresenter ]). + tree selectedIndex: (tree selectedIndex min: aTree children size) ] { #category : #initialization } @@ -922,12 +920,6 @@ GrafoscopioNotebook >> removeNode [ tree selectedItem ifNil: [ ^ self inform: 'No node available or properly selected ' ]. contentToDelete := tree selectedItem content. - (contentToDelete body isNotEmpty or: [ contentToDelete hasChildren ]) - ifTrue: [ (UIManager default - questionWithoutCancel: - 'The selected node has children and / or content. This change so far cannot be undone. Are you sure you want to proceed? ' - title: 'Remove node') - ifFalse: [ ^ self ] ]. parentContent := contentToDelete parent. children := parentContent children. children size > 1 @@ -935,15 +927,7 @@ GrafoscopioNotebook >> removeNode [ ifTrue: [ newSelectedContent := children at: children size - 1 ] ] ifFalse: [ newSelectedContent := parentContent ]. contentToDelete parent removeNode: contentToDelete. - self notebookContent: notebook. - self resetSelectedItem. -] - -{ #category : #'editing nodes' } -GrafoscopioNotebook >> resetSelectedItem [ - tree selectedIndex: (tree selectedIndex min: notebook children size). - tree highlightedItem: tree selectedItem. - tree updatePresenter. + self notebookContent: notebook ] { #category : #persistence } @@ -998,12 +982,6 @@ GrafoscopioNotebook >> seePdf [ onSuccessDo: [ :v | (#open command argument: self pdfFile fullName) schedule ] ] -{ #category : #'editing nodes' } -GrafoscopioNotebook >> selectedItem: anItem [ - tree selectedItem: (tree roots detect: [ : p | p content = anItem ]). - tree highlightedItem: tree selectedItem. -] - { #category : #persistence } GrafoscopioNotebook >> subtreeAsMarkdown [ | currentNode | diff --git a/repository/Grafoscopio/GrafoscopioTextModel.class.st b/repository/Grafoscopio/GrafoscopioTextModel.class.st index 17076ce..6bcd3f8 100644 --- a/repository/Grafoscopio/GrafoscopioTextModel.class.st +++ b/repository/Grafoscopio/GrafoscopioTextModel.class.st @@ -36,6 +36,6 @@ GrafoscopioTextModel >> content: aGrafoscopioNodeContent [ GrafoscopioTextModel >> initializeWidgets [ body := self newText. - body beForGrafoscopio. + body beForText. body autoAccept: true. ] diff --git a/repository/Grafoscopio/GrafoscopioTextNode.class.st b/repository/Grafoscopio/GrafoscopioTextNode.class.st new file mode 100644 index 0000000..849357c --- /dev/null +++ b/repository/Grafoscopio/GrafoscopioTextNode.class.st @@ -0,0 +1,995 @@ +" +An UbakyeNode is and administrator of all node operations in a tree. + +Instance Variables + node: + +node + - xxxxx + +" +Class { + #name : #GrafoscopioTextNode, + #superclass : #GrafoscopioAbstractNode, + #instVars : [ + 'key', + 'icon', + 'body', + 'children', + 'links' + ], + #classInstVars : [ + 'clipboard' + ], + #category : #'Grafoscopio-Model' +} + +{ #category : #utility } +GrafoscopioTextNode class >> cleanTreeRootReferences [ + + | ref | + clipboard ifNil: [ ^ self ]. + clipboard children ifNil: [ ^ self ]. + clipboard preorderTraversal allButFirstDo: [ :n | + ref := n. + n level - 1 timesRepeat: [ ref := ref parent ]. + ref parent = clipboard parent ifTrue: [ ref parent: nil ]]. + clipboard parent: nil. +] + +{ #category : #accessing } +GrafoscopioTextNode class >> clipboard [ + ^ clipboard +] + +{ #category : #accessing } +GrafoscopioTextNode class >> clipboard: anObject [ + clipboard := anObject +] + +{ #category : #utility } +GrafoscopioTextNode class >> contentProviders [ + "I list the domains of certain providers that are treated specially, because they + store and offer content like Smalltalk playgrounds or source code, that can be used + in particular ways while importing or exporting content in a node." + + ^ Dictionary new + at: 'playgrounds' put: #('ws.stfx.eu'); + at: 'fossil' put: #('mutabit.com/repos.fossil'); + at: 'etherpads' put: #('pad.tupale.co' ); + yourself. +] + +{ #category : #utility } +GrafoscopioTextNode class >> specialWords [ + "I return a list of word that were used in the first versions of Grafoscopio to mark node + headers to indicate special ways to handle them and their node contents. + Previous versions of first notebooks stored in Grafoscopio using this convention should be + migrated to newer versions where tags are used for the same function with simpler code" + + ^ #('%config' '%abstract' '%invisible' '%idea' '%footnote' '%metadata' '%output' '%embed' '%item'). +] + +{ #category : #operation } +GrafoscopioTextNode >> addLink: anUrl [ + "anUrl is a string" + + (self links includes: anUrl) + ifFalse: [ self links add: anUrl ] + +] + +{ #category : #adding } +GrafoscopioTextNode >> addNode: aNode [ + "Adds the given node to the receivers collection of children, and sets this object as the parent + of the node" + "aNode parent = self ifTrue: [ ^ self ]." + self children add: aNode. + aNode parent: self. + ^aNode +] + +{ #category : #accessing } +GrafoscopioTextNode >> ancestors [ + "I return a collection of all the nodes wich are ancestors of the receiver node" + | currentNode ancestors | + + currentNode := self. + ancestors := OrderedCollection new. + [ currentNode parent notNil and: [ currentNode level > 0 ] ] + whileTrue: [ + ancestors add: currentNode parent. + currentNode := currentNode parent]. + ancestors := ancestors reversed. + ^ ancestors +] + +{ #category : #accessing } +GrafoscopioTextNode >> ancestorsAll [ + "I return a collection of all the nodes wich are ancestors of the receiver node" + | currentNode ancestors | + + currentNode := self. + ancestors := OrderedCollection new. + [ currentNode parent notNil and: [ currentNode level > 0 ] ] + whileTrue: [ + ancestors add: currentNode parent. + currentNode := currentNode parent]. + ancestors := ancestors reversed. + ^ ancestors +] + +{ #category : #accessing } +GrafoscopioTextNode >> ancestorsHeaders [ + "Returns the headers of all the ancestors of the node. + Maybe this and 'headers' should be integrated, so both act on a collection of children instead of + having two separate methods" + | currentNode ancestors | + + currentNode := self. + ancestors := OrderedCollection new. + (self level - 1) + timesRepeat: [ + ancestors add: currentNode parent. + currentNode := currentNode parent.]. + ancestors := ancestors reversed. + ^ ancestors collect: [:ancestor | ancestor header ] + + +] + +{ #category : #exporting } +GrafoscopioTextNode >> asMarkdown [ + "I export children of the current node as pandoc markdown, using special nodes + accoding to tags. + Early version... tags processing should be vastly improved" + | markdownOutput | + + markdownOutput := '' writeStream. + "self metadataAsYamlIn: markdownOutput." + (self preorderTraversal) do: [ :eachNode | + (eachNode level > 0) + ifTrue: [(eachNode hasAncestorTaggedAs: 'invisible') + | (eachNode tags includes: 'invisible') + ifFalse: [ + markdownOutput nextPutAll: (eachNode markdownContent) ]]]. + ^ markdownOutput contents +] + +{ #category : #exporting } +GrafoscopioTextNode >> asSton [ + "Exports current tree as STON format" + | stonOutput | + + stonOutput := '' writeStream. + stonOutput nextPutAll: (STON toStringPretty: self "flatten"). + ^stonOutput contents + +] + +{ #category : #exporting } +GrafoscopioTextNode >> asStonFromRoot [ + "Exports current tree as STON format" + | stonOutput | + + stonOutput := '' writeStream. + self flatten. + stonOutput nextPutAll: (STON toStringPretty: self children). + ^stonOutput contents + +] + +{ #category : #accessing } +GrafoscopioTextNode >> asTreeNodePresenter [ + | node | + node := TreeNodePresenter new. + node + hasChildren: [ self children isNotEmpty ]; + children: [ self children collect: [ :subNode | subNode asTreeNodePresenter ] ]; + content: self. + ^ node +] + +{ #category : #accessing } +GrafoscopioTextNode >> body [ + "Returns the receivers body" + + ^ body + +] + +{ #category : #accessing } +GrafoscopioTextNode >> body: anObject [ + body := anObject +] + +{ #category : #exporting } +GrafoscopioTextNode >> bodyAsCode [ + "I return the node body with proper decorators added to show them as raw code" + | codeBody | + codeBody := '' writeStream. + codeBody + nextPutAll: '~~~{.numberLines}'; lf; + nextPutAll: (self body contents asString withInternetLineEndings); lf; + nextPutAll: '~~~'; lf; lf. + ^ codeBody contents +] + +{ #category : #exporting } +GrafoscopioTextNode >> bodyAsMarkdownInto: aStream [ + "I export the header as markdown using the level inside the tree to determine hierarchy + and replacing all line endings to make them Internet friendly". + self embeddedNodes ifNotNil: [ aStream nextPutAll: (self embedNodes contents asString withInternetLineEndings); crlf; crlf]. +] + +{ #category : #'add/remove nodes' } +GrafoscopioTextNode >> buildPreorderCollection: aCollection [ + "Stores the current node in a collection and recursively stores its children" + + aCollection add: self. + (self children isNotEmpty) & ((self header findString: '#invisible')=1) not + ifTrue: [ (self children) do: [ :eachNode | eachNode buildPreorderCollection: aCollection]]. + + +] + +{ #category : #operation } +GrafoscopioTextNode >> checksum [ + "I return the SHA1SUM of the current node. + I'm used to test changes on the node contents, without including changes in the children." + | nodeCopy | + nodeCopy := self surfaceCopy. + ^ self checksumFor: nodeCopy asSton. +] + +{ #category : #utility } +GrafoscopioTextNode >> checksumFor: aText [ + "I return the SHA1SUM of the current tree. I'm used to test changes on the contents + and for traceability of how the document tree is converted to other formats, as markdown." + ^ (SHA1 new hashMessage: aText) hex +] + +{ #category : #operation } +GrafoscopioTextNode >> checksumForRootSubtree [ + "I return the SHA1SUM of the current tree. I'm used to test changes on the contents + and for traceability of how the document tree is converted to other formats, as markdown." + ^ self checksumFor: self root flatten asStonFromRoot. + "^ (SHA1 new hashMessage: self root flatten asStonFromRoot) hex" +] + +{ #category : #accessing } +GrafoscopioTextNode >> children [ + "Returns the receivers list of children" + + ^ children ifNil: [ children := OrderedCollection new ] +] + +{ #category : #accessing } +GrafoscopioTextNode >> children: aCollection [ + "Sets the receivers children" + + aCollection do: [:currentNode | currentNode parent: self ]. + children := aCollection. +] + +{ #category : #'as yet unclassified' } +GrafoscopioTextNode >> content [ + ^ body +] + +{ #category : #'as yet unclassified' } +GrafoscopioTextNode >> content: aContent [ + self body: aContent +] + +{ #category : #'add/remove nodes' } +GrafoscopioTextNode >> copyToClipboard [ + self class clipboard: self subtreeCopy. + + +] + +{ #category : #operation } +GrafoscopioTextNode >> currentLink [ + "TODO: This method should not only select sanitized links, but also provide ways to detect wich link + is selected from the list. For the moment, is only the last one, but probably links needs to be heavily + refactored to support this kind of operations and a better UI." + ^ self sanitizeDefaultLink +] + +{ #category : #'as yet unclassified' } +GrafoscopioTextNode >> detectSelectionIndex [ + "I tell which is the index of the current selected node or return the first childre + (indexed at 1) if is not found." + + | root | + root := self root. + root preorderTraversal allButFirst doWithIndex: [ :currentNode :index | + currentNode isSelected ifTrue: [ ^ index ] ]. + ^ 1. +] + +{ #category : #'custom markup' } +GrafoscopioTextNode >> embedAll [ + "This is just a previous part of the messy markDownContent. The %embed-all keyword should be revaluated. + By default a node embeds all its children. Any non-embedable content should be under a %invisible node" + "(temporalBody includesSubstring: '%embed-all') + ifFalse: [ ] + ifTrue: [ + self embeddedNodes do: [ :each | + temporalBody := temporalBody copyReplaceAll: '%embed-all' with: (each body, (String with: Character cr), + '%embed-all')]. + temporalBody := temporalBody copyReplaceAll: '%embed-all' with: '']" + +] + +{ #category : #'custom markup' } +GrafoscopioTextNode >> embedNodes [ + "I find any ocurrence of '%embed a node header' in the body of a node and replace it + by the children which have such header. + Using embedded nodes is useful to change the order in which children appear into parents body, + while exporting" + | temporalBody | + temporalBody := self body. + self embeddedNodes ifNotNil: [ self embeddedNodes do: [ :each | + (each isTaggedAs: 'código') + ifFalse: [temporalBody := temporalBody copyReplaceAll: (each header) with: each body] + ifTrue: [temporalBody := temporalBody copyReplaceAll: (each header) with: each bodyAsCode]]]. + ^ temporalBody +] + +{ #category : #'custom markup' } +GrafoscopioTextNode >> embeddedNodes [ + ^ self children select: [:each | each headerStartsWith: '%embed'] +] + +{ #category : #exporting } +GrafoscopioTextNode >> exportCodeBlockTo: aStream [ + "I convert the content of a node taged as 'código' (code) as pandoc markdown and put it + into aStream." + aStream nextPutAll: ('~~~{.numberLines}'); lf. + aStream nextPutAll: (self body contents asString withInternetLineEndings); lf. + aStream nextPutAll: ('~~~'); lf;lf. + ^aStream contents +] + +{ #category : #exporting } +GrafoscopioTextNode >> exportCodeNodeTo: aStream [ + "I convert the content of a node taged as 'código' (code) as pandoc markdown + and put it into aStream." + ((self headerStartsWith: '%output') or: [ self headerStartsWith: '%metadata' ]) + ifTrue: [ self exportCodeOutputTo: aStream ] + ifFalse: [ self exportCodeBlockTo: aStream ] +] + +{ #category : #exporting } +GrafoscopioTextNode >> exportCodeOutputTo: aStream [ + "I convert the output of a node taged as 'código' (code) as pandoc markdown and + put it into aStream." + (self headerStartsWith: '%metadata') ifTrue: [ ^ self ]. + aStream nextPutAll: ('~~~{.numberLines}'); lf. + aStream nextPutAll: (self output asString withInternetLineEndings); lf. + aStream nextPutAll: ('~~~'); lf;lf. + ^aStream contents +] + +{ #category : #exporting } +GrafoscopioTextNode >> exportLaTeXCodeBlockTo: aStream [ + "I convert the content of a node taged as 'código' (code) as pandoc markdown and put it + into aStream. + The code block is decorated with LaTeX commands for proper syntax highlighting using pygments. + Pdf exportation requires the installation of pygments and minted package for latex" + aStream nextPutAll: ('\begin{minted}{smalltalk}'); lf. + aStream nextPutAll: (self body contents asString withInternetLineEndings); lf. + aStream nextPutAll: '\end{minted}';lf;lf. + ^aStream contents +] + +{ #category : #exporting } +GrafoscopioTextNode >> exportPreambleTo: aStream [ + "comment stating purpose of message" + | configDict | + (self header = '%config') + ifTrue: [ + configDict := STON fromString: (self body). + aStream nextPutAll: 'title: ', (configDict at: 'title'); lf. + aStream nextPutAll: 'author: ', ((configDict at: 'author') at: 'given'), ' ', ((configDict at: 'author') at: 'family'); lf. + aStream nextPutAll: 'bibliography: ', (configDict at: 'bibliography'); lf. + aStream nextPutAll: 'abstract: ', '|'; lf; nextPutAll: (configDict at: 'abstract'); lf] +] + +{ #category : #utility } +GrafoscopioTextNode >> find: aString andReplaceWith: anotherString [ + anotherString ifNil: [ ^ self ]. + self body: ((self body) copyReplaceAll: aString with: anotherString) +] + +{ #category : #exporting } +GrafoscopioTextNode >> flatten [ + "I traverse the tree looking for node bodies containing 'Text' objects and transform them to + their string content, so space is saved and storage format is DVCS friendly while serializing + them to STON" + + (self preorderTraversal) do: [ :eachNode | + (eachNode body class = Text) + ifTrue: [eachNode body: (eachNode body asString)]] +] + +{ #category : #exporting } +GrafoscopioTextNode >> footnoteAsMarkdownInto: aStream [ + "I export a node with %footnode in its header for valid Pandoc's markdown + and replace all line endings to make them Internet friendly. + Maybe I should include the condition about my own header, instead of leaving it to markdownCotent..." + aStream nextPutAll: ('[^',(self header copyReplaceAll: '%footnote ' with: ''),']: ' ); lf. + self body contents asString withInternetLineEndings + linesDo: [ :line | aStream nextPutAll: ' ', line; lf ]. + aStream nextPutAll: String lf. + +] + +{ #category : #exporting } +GrafoscopioTextNode >> hasAncestorHeaderWith: aSpecialWord [ + "Looks if the receptor node has an ancestor with a header with 'aSpecialWord' as the only or the first word" + + ^ (self ancestorsHeaders includes: aSpecialWord) | ((self ancestorsHeaders collect: [:eachHeader | (eachHeader findTokens: $ ) at: 1 ]) includes: aSpecialWord) + + +] + +{ #category : #exporting } +GrafoscopioTextNode >> hasAncestorTaggedAs: aSpecialWord [ + "Looks if the receptor node has an ancestor with a header with 'aSpecialWord' in its tags" + + self ancestors detect: [:eachAncestor | eachAncestor tags includes: aSpecialWord ] + ifFound: [^true ] + ifNone: [^false ]. +] + +{ #category : #accessing } +GrafoscopioTextNode >> hasChildren [ + (self children size > 0) + ifTrue: [ ^true ] + ifFalse: [ ^false ] +] + +{ #category : #exporting } +GrafoscopioTextNode >> headerAsMarkdownInto: aStream [ + "I export the header as markdown using the level inside the tree to determine hierarchy + and replacing all line endings to make them Internet friendly" + self level timesRepeat: [ aStream nextPutAll: '#' ]. + aStream nextPutAll: ' '. + aStream nextPutAll: (self header copyReplaceTokens: #cr with: #lf); crlf; crlf. +] + +{ #category : #'custom markup' } +GrafoscopioTextNode >> headerStartsWith: aString [ + ^ (self header findString: aString) = 1 +] + +{ #category : #accessing } +GrafoscopioTextNode >> headers [ + "I returns the headers of the receiver children" + ^ self children collect: [:currentNode | currentNode header ] +] + +{ #category : #operation } +GrafoscopioTextNode >> htmlToMarkdown [ + "I convert the node body from HTML format to Pandoc's Markdown." + | htmlFile | + (self isTaggedAs: 'código' ) ifTrue: [ ^self ]. + ((self headerStartsWith: '%invisible') "or:[self hasAncestorHeaderWith: '%invisible']") + ifTrue: [ ^self ]. + htmlFile := FileLocator temp asFileReference / 'body.html'. + htmlFile ensureCreateFile. + htmlFile writeStreamDo: [:out | out nextPutAll: self body ]. + Smalltalk platformName = 'unix' + ifTrue: [ self body: (Pandoc htmlToMarkdown: htmlFile) ]. + Smalltalk platformName = 'Win32' + ifTrue: [ self shouldBeImplemented ]. + htmlFile ensureDelete. +] + +{ #category : #operation } +GrafoscopioTextNode >> htmlToMarkdownSubtree [ + "I convert self and childern nodes body from HTML format to Pandoc's Markdown." + self preorderTraversal do: [ :each | each htmlToMarkdown ] +] + +{ #category : #accessing } +GrafoscopioTextNode >> icon [ + "Returns the receivers icon" + + ^icon +] + +{ #category : #accessing } +GrafoscopioTextNode >> icon: aSymbol [ + "Sets the receivers icon" + + icon := aSymbol +] + +{ #category : #importing } +GrafoscopioTextNode >> importHtmlLink [ + "I take the last link and import its contents in node body. " + | selectedLink downloadedContent | + selectedLink := self currentLink. + selectedLink asUrl host = 'ws.stfx.eu' ifTrue: [ ^ self ]. + selectedLink asUrl host = 'docutopia.tupale.co' + ifTrue: [ self inform: 'Docutopia importing still not supported.'. + ^ self ]. + downloadedContent := (GrafoscopioUtils + downloadingFrom: selectedLink + withMessage: 'Downloading node contents...' + into: FileLocator temp). + self uploadBodyFrom: downloadedContent filteredFor: selectedLink. +] + +{ #category : #importing } +GrafoscopioTextNode >> importPlaygroundLink [ + "I take the last link and import its contents in node body. + Last links should be hosted in http://zn.stfx.eu/" + self currentLink asUrl host = 'ws.stfx.eu' ifFalse: [ ^ self ]. + self + body: (ZnClient new get: self currentLink); + tagAs: 'código'. +] + +{ #category : #initialization } +GrafoscopioTextNode >> initialize [ + "I create a empty new node" + + super initialize. + self body: '' +] + +{ #category : #accessing } +GrafoscopioTextNode >> isEmpty [ + body ifNil: [ ^ true ] ifNotNil: [ ^ false ] +] + +{ #category : #operation } +GrafoscopioTextNode >> isSavedAfterLastEdition [ + | root | + root := self root. + root edited ifNil: [ ^ false ]. + ^ self unsavedNodes isEmpty. + "self unsavedNodes isEmpty ifFalse: [ ^ self unsavedNodes inspect ]" +] + +{ #category : #testing } +GrafoscopioTextNode >> isSelected [ + self selected ifNil: [ ^ false ]. + ^ self selected. +] + +{ #category : #operation } +GrafoscopioTextNode >> isTaggedAs: aString [ + ^ self tags includes: aString +] + +{ #category : #exporting } +GrafoscopioTextNode >> itemAsMarkdownInto: aStream [ + "I export a node with %item in its header as valid Pandoc's markdown + and replace all line endings to make them Internet friendly. + Maybe I should include the condition about my own header, instead of leaving it to markdownContent..." + + | lines | + lines := self body contents asString withInternetLineEndings lines. + lines ifEmpty: [ ^ self ]. + aStream + nextPutAll: ' - '; + nextPutAll: lines first; + lf. + lines + allButFirstDo: [ :line | + aStream + nextPutAll: ' '; + nextPutAll: line; + lf ]. + aStream nextPutAll: String lf +] + +{ #category : #accessing } +GrafoscopioTextNode >> key [ + "Returns a unique key identifying the receiver in the help system" + + ^key +] + +{ #category : #accessing } +GrafoscopioTextNode >> key: aUniqueKey [ + "Sets a unique key identifying the receiver in the help system" + + key := aUniqueKey +] + +{ #category : #accessing } +GrafoscopioTextNode >> lastLink [ + self links ifNil: [ ^ '' ]. + self links ifEmpty: [ ^ '' ]. + ^ self links last +] + +{ #category : #accessing } +GrafoscopioTextNode >> lastNetLink [ + ^ self links detect: [ :l | l asZnUrl ] +] + +{ #category : #accessing } +GrafoscopioTextNode >> links [ + "I model local or remote links that are associated to a particular node." + ^ links ifNil: [ ^ links := OrderedCollection new ] +] + +{ #category : #accessing } +GrafoscopioTextNode >> links: anObject [ + self links add: anObject +] + +{ #category : #operation } +GrafoscopioTextNode >> linksToMarkupFile [ + "I detect if the links contains any reference to a file ending in '.md' or '.markdown'" + self links + ifNotNil: [ + self links + detect: [:l | (l endsWithAnyOf: #('.md' '.markdown' '.md.html'))] + ifFound: [ ^ true ] + ifNone: [^ false]]. + ^ false + +] + +{ #category : #operation } +GrafoscopioTextNode >> localFilesLinks [ + "I collect all the links that point to the local file system. Because is supposed that + links contains only references to remote URL or local files, anything that is not a URL is + treated as a loca file link." + ^ self links collect: [ :l | l asZnUrl host isNil ] +] + +{ #category : #exporting } +GrafoscopioTextNode >> margin [ + "I define the same margin of the page used for PDF exportations" + + ^'2 cm' +] + +{ #category : #exporting } +GrafoscopioTextNode >> margins [ + "I define each individual margin of the page used for PDF exportations" + + | margins | + margins := Dictionary new + add: 'top' -> '3 cm'; + add: 'bottom' -> '3 cm'; + add: 'left' -> '2 cm'; + add: 'right' -> '2 cm'; + yourself. + ^ margins +] + +{ #category : #exporting } +GrafoscopioTextNode >> markdownContent [ + "I extract the markdown of a node using body as content, header as title and level as + hierarchical level of the title. + If special nodes types are present, that use %keywords in its header or body I convert them + into proper markup" + | markdownStream | + markdownStream := '' writeStream. + (self class specialWords includes: self header) not + & (self class specialWords includes: ((self header findTokens: $ ) at: 1)) not + & (self isTaggedAs: 'código') not + & (self hasAncestorHeaderWith: '%invisible') not + ifTrue: [ + self headerAsMarkdownInto: markdownStream. + self bodyAsMarkdownInto: markdownStream ]. + (self headerStartsWith: '%idea') + ifTrue: [ self bodyAsMarkdownInto: markdownStream ]. + (self headerStartsWith: '%item') + ifTrue: [ self itemAsMarkdownInto: markdownStream ]. + (self headerStartsWith: '%footnote') + ifTrue: [ self footnoteAsMarkdownInto: markdownStream ]. + ((self isTaggedAs: 'código') + and: [(self hasAncestorHeaderWith: '%invisible') not + & (self headerStartsWith: '%embed') not ]) + ifTrue: [ self exportCodeNodeTo: markdownStream ]. + ^ markdownStream contents +] + +{ #category : #operation } +GrafoscopioTextNode >> metadata [ + | mnode | + mnode := self root preorderTraversal + detect: [ :n | n headerStartsWith: '%metadata' ] + ifNone: [ ^ nil ]. + ^ mnode output. + +] + +{ #category : #exporting } +GrafoscopioTextNode >> metadataAsYamlIn: markdownStream [ + "I convert the first '%metadata' node into a YAML preamble contents to be used by Pandoc + exportation." + self metadata + ifNil: [ markdownStream nextPutAll: String crlf. ] + ifNotNil: [ + self metadata + keysAndValuesDo: [ :k :v | + k = 'pandocOptions' + ifTrue: [ + markdownStream + nextPutAll: + (k, ': ', self pandocOptionsPrettyYaml) ] + ifFalse: [ + markdownStream + nextPutAll: + (k , ': ' , v asString) withInternetLineEndings; + lf] ]]. + markdownStream + nextPutAll: String cr, String cr. +] + +{ #category : #movement } +GrafoscopioTextNode >> moveDown: aNode [ + | index | + "Moves the current node a place before in the children collection where is located" + index := (children indexOf: aNode) max: 1 . + + children swap: index with: (index + 1 min: children size) +] + +{ #category : #movement } +GrafoscopioTextNode >> moveUp: aNode [ + | index | + "Moves the current node a place before in the children collection where is located" + index := children indexOf: aNode. + children swap: index with: (index - 1 max: 1) +] + +{ #category : #accessing } +GrafoscopioTextNode >> openIn: aNotebook [ + super openIn: aNotebook. + aNotebook autoSaveBodyOf: self. + aNotebook links text: self lastLink +] + +{ #category : #accessing } +GrafoscopioTextNode >> output [ + (self isTaggedAs: 'código') + ifFalse: [ ^ self ]. + self body ifNil: [ ^ nil ]. + ^ OpalCompiler new + source: self body; + evaluate +] + +{ #category : #utility } +GrafoscopioTextNode >> pandocOptions [ + self metadata ifNil: [ ^ nil ]. + self metadata at: 'pandocOptions' ifAbsent: [ ^ '' ]. + ^ self metadata at: 'pandocOptions' +] + +{ #category : #utility } +GrafoscopioTextNode >> pandocOptionsPrettyYaml [ + "I convert pandoc options, if present into an indented Yaml block." + | yamlOutput pretyOutput | + pretyOutput := STON toStringPretty: self pandocOptions. + yamlOutput := '' writeStream. + yamlOutput + nextPutAll: + '|'; + lf. + pretyOutput linesDo: [ :line | + yamlOutput + nextPutAll: + ' ', line; + lf ]. + ^ yamlOutput contents +] + +{ #category : #'add/remove nodes' } +GrafoscopioTextNode >> pasteFromClipboard [ + | clipchild | + self class clipboard + ifNotNil: [ + clipchild := self class clipboard. + self addNode: clipchild. + clipchild ] + ifNil: [ self inform: 'Cache is emtpy. Pleas cut/copy a node before pasting' ] + +] + +{ #category : #operation } +GrafoscopioTextNode >> preorderTraversal [ + | nodesInPreorder | + nodesInPreorder := OrderedCollection new. + self buildPreorderCollection: nodesInPreorder. + ^ nodesInPreorder +] + +{ #category : #exporting } +GrafoscopioTextNode >> publish [ + | publishedUrl | + (self confirm: 'Publish playground content to the cloud?') + ifFalse: [ ^ self ]. + self content ifEmpty: [ + self inform: 'Nothing was published because the playground is empty'. + ^ self ]. + Clipboard clipboardText: (publishedUrl := (GTUrlProvider new post: self content) asString). + self inform: publishedUrl , ' was published and the url was copied to clipboard' +] + +{ #category : #'add/remove nodes' } +GrafoscopioTextNode >> removeLastNode [ + "Adds the given node to the receivers collection of children, and sets this object as the parent + of the node" + + self children removeLast. + +] + +{ #category : #utility } +GrafoscopioTextNode >> removeLeadingLineNumbersSized: anInteger [ + | cleanBody | + cleanBody := ''. + self body lines do: [ :line | | cleanLine | + line size >= anInteger + ifTrue: [ cleanLine := line copyFrom: anInteger to: line size. ] + ifFalse: [ cleanLine := '' ]. + cleanBody := cleanBody, cleanLine, String cr ]. + self body: cleanBody asString. +] + +{ #category : #'add/remove nodes' } +GrafoscopioTextNode >> removeNode: aNode [ + (self children includes: aNode) + ifTrue: [ self children remove: aNode ] + ifFalse: [ self inform: 'The node doesn''t belong to this node children' ] + + +] + +{ #category : #utility } +GrafoscopioTextNode >> replaceAccentedHTMLChars [ + self body: (self body copyReplaceAll: 'í' with: 'í' ) +] + +{ #category : #accessing } +GrafoscopioTextNode >> root [ + "I return the root node of the Grafoscopio tree, i.e the common ancestor of all other nodes" + self level = 0 + ifFalse: [ ^ self ancestors first ]. + ^ self +] + +{ #category : #operation } +GrafoscopioTextNode >> sanitizeDefaultLink [ + | defaultLink sanitized protocol | + defaultLink := self lastLink. + protocol := 'docutopia://'. + sanitized := (defaultLink beginsWith: protocol) + ifTrue: [ defaultLink + copyReplaceAll: protocol + with: 'https://docutopia.tupale.co/' ] + ifFalse: [ defaultLink ]. + ^ sanitized +] + +{ #category : #accessing } +GrafoscopioTextNode >> saveContent: anObject [ + "Sets the receivers body to the given object" + + body := anObject +] + +{ #category : #operation } +GrafoscopioTextNode >> selectMarkupSubtreesToExport [ + ^ (self root preorderTraversal) select: [ :each | each linksToMarkupFile ]. +] + +{ #category : #'as yet unclassified' } +GrafoscopioTextNode >> shouldAskBeforeRemove [ + ^ self body isNotEmpty or: [ self hasChildren ] +] + +{ #category : #accessing } +GrafoscopioTextNode >> specModelClass [ + + (self isTaggedAs: 'código') ifTrue: [^GrafoscopioCodeModel]. + (self isTaggedAs: 'johan') ifTrue:[^GrafoscopioButtonModel]. + "por defecto" + ^ GrafoscopioTextModel +] + +{ #category : #operation } +GrafoscopioTextNode >> subtreeCopy [ + "I return the same node if its subtree only contains the receiver, or a copy of the receivers + subtree, in other cases." + | linearSubtree linearSubtreeCopy | + linearSubtree := self preorderTraversal. + linearSubtreeCopy := OrderedCollection new. + linearSubtree do: [ :cn | linearSubtreeCopy add: cn surfaceCopy ]. + linearSubtreeCopy allButFirst doWithIndex: [ :n :i | | parentPos | + parentPos := linearSubtree indexOf: (linearSubtree at: i+1) parent. + n parent: (linearSubtreeCopy at: parentPos) ]. + ^ linearSubtreeCopy at: 1. +] + +{ #category : #operation } +GrafoscopioTextNode >> surfaceCopy [ + "I copy the most relevant values of the receiver. I'm useful to avoid copying references + to the rest of the container tree, which could end in copying the whole tree." + | newNode | + newNode := self class new. + newNode + header: self header; + body: self body; + tags: self tags. + self links ifNotEmpty: [ newNode links addAll: self links ]. + ^ newNode. + + +] + +{ #category : #operation } +GrafoscopioTextNode >> toggleCodeText [ + "Some tags are exclusionary. + For example a node can not be tagged as text and as 'code' (código) simultaneosly. + In that case, I replace the ocurrence of one tag by the other to warranty that both are not + in the same node." + (self isTaggedAs: 'text') + ifTrue: [ ^ self tags replaceAll: 'text' with: 'código']. + (self isTaggedAs: 'código') + ifTrue: [ ^ self tags replaceAll: 'código' with: 'text' ]. +] + +{ #category : #operation } +GrafoscopioTextNode >> unsavedNodes [ + "I collect all nodes that have changed after the last saving" + | lastSavedOn root unsavedNodes | + root := self root. + lastSavedOn := root edited asDateAndTime. + unsavedNodes := root preorderTraversal select: [ :currentNode | + currentNode edited isNotNil and: [currentNode edited asDateAndTime > lastSavedOn] ]. + ^ unsavedNodes. + +] + +{ #category : #importing } +GrafoscopioTextNode >> uploadBodyFrom: fileLocator filteredFor: selectedLink [ + self body: fileLocator contents +] + +{ #category : #operation } +GrafoscopioTextNode >> visitLastLink [ + self lastLink = '' + ifTrue: [ self inform: 'This node has no associated links to visit'. ^ self ]. + [WebBrowser openOn: self lastLink] fork +] + +{ #category : #'as yet unclassified' } +GrafoscopioTextNode >> wrapBodyLines [ + "I convert the node body from HTML format to Pandoc's Markdown." + + self wrapBodyLines: 80 +] + +{ #category : #'as yet unclassified' } +GrafoscopioTextNode >> wrapBodyLines: aWidth [ + "I convert the node body from HTML format to Pandoc's Markdown." + + | bodyReader chunck | + (self isTaggedAs: 'código') + ifTrue: [ ^ self ]. + bodyReader := body readStream. + body := String + streamContents: [ :bodyWriter | + [ bodyReader atEnd ] + whileFalse: [ + chunck := bodyReader next: aWidth - 1. + bodyWriter nextPutAll: chunck. + bodyReader atEnd + ifFalse: [ bodyWriter nextPut: Character lf ] ] ] +] diff --git a/repository/Grafoscopio/GrafoscopioUrlCachedNode.class.st b/repository/Grafoscopio/GrafoscopioUrlCachedNode.class.st new file mode 100644 index 0000000..913050d --- /dev/null +++ b/repository/Grafoscopio/GrafoscopioUrlCachedNode.class.st @@ -0,0 +1,22 @@ +Class { + #name : #GrafoscopioUrlCachedNode, + #superclass : #GrafoscopioUrlNode, + #instVars : [ + 'content' + ], + #category : #'Grafoscopio-Model' +} + +{ #category : #accessing } +GrafoscopioUrlCachedNode >> content [ + ^ content isEmptyOrNil + ifTrue: [ content := super content ] + ifFalse: [ content ] +] + +{ #category : #accessing } +GrafoscopioUrlCachedNode >> content: aContent [ + self + error: + 'You are not supposed to change the content of url node from outside. The content comes only from the fetch.' +] diff --git a/repository/Grafoscopio/GrafoscopioUrlNode.class.st b/repository/Grafoscopio/GrafoscopioUrlNode.class.st new file mode 100644 index 0000000..0dd1a73 --- /dev/null +++ b/repository/Grafoscopio/GrafoscopioUrlNode.class.st @@ -0,0 +1,56 @@ +Class { + #name : #GrafoscopioUrlNode, + #superclass : #GrafoscopioAbstractNode, + #instVars : [ + 'link' + ], + #category : #'Grafoscopio-Model' +} + +{ #category : #'as yet unclassified' } +GrafoscopioUrlNode >> content [ + ^ (self url + ifNil: [ ' Invalid url ' ] + ifNotNil: [ :url | self fetchContent: url ]) +] + +{ #category : #'as yet unclassified' } +GrafoscopioUrlNode >> fetchContent: anUrl [ + ^ (ZnEasy get: anUrl) entity contents +] + +{ #category : #'as yet unclassified' } +GrafoscopioUrlNode >> getUrl [ + | url | + url := UIManager default + request: 'Please insert a url ' + initialAnswer: 'http://' + title: 'URL Node'. + url ifNil: [ ^ nil ]. + url := url asZnUrl. + (url host isEmptyOrNil + or: [ url scheme isEmptyOrNil or: [ url authority isEmptyOrNil ] ]) + ifTrue: [ ^ nil ]. + ^ url +] + +{ #category : #accessing } +GrafoscopioUrlNode >> link: aZnUrl [ + link := aZnUrl +] + +{ #category : #'as yet unclassified' } +GrafoscopioUrlNode >> openIn: aNotebook [ + super openIn: aNotebook. + aNotebook links text: (link ifNil: 'Invalid url') +] + +{ #category : #'as yet unclassified' } +GrafoscopioUrlNode >> shouldAskBeforeRemove [ + ^ false +] + +{ #category : #'as yet unclassified' } +GrafoscopioUrlNode >> url [ + ^ link ifNil: [ link := self getUrl ] +] diff --git a/repository/Grafoscopio/ManifestGrafoscopio.class.st b/repository/Grafoscopio/ManifestGrafoscopio.class.st index eb8aeb2..9207d50 100644 --- a/repository/Grafoscopio/ManifestGrafoscopio.class.st +++ b/repository/Grafoscopio/ManifestGrafoscopio.class.st @@ -9,7 +9,7 @@ Class { { #category : #'code-critics' } ManifestGrafoscopio class >> ruleRBAssignmentInIfTrueRuleV1FalsePositive [ - ^ #(#(#(#RGMethodDefinition #(#GrafoscopioNode #embedNodes #false)) #'2017-02-16T20:07:02.600781-05:00') #(#(#RGMethodDefinition #(#GrafoscopioNotebook #navigateRelativePathFor: #false)) #'2017-03-28T22:30:53.541042-05:00') ) + ^ #(#(#(#RGMethodDefinition #(#GrafoscopioTextNode #embedNodes #false)) #'2017-02-16T20:07:02.600781-05:00') #(#(#RGMethodDefinition #(#GrafoscopioNewNotebook #navigateRelativePathFor: #false)) #'2017-03-28T22:30:53.541042-05:00') ) ] { #category : #'code-critics' } @@ -24,7 +24,7 @@ ManifestGrafoscopio class >> ruleRBBooleanPrecedenceRuleV1FalsePositive [ { #category : #'code-critics' } ManifestGrafoscopio class >> ruleRBCascadedNextPutAllsRuleV1FalsePositive [ - ^ #(#(#(#RGMethodDefinition #(#GrafoscopioNode #metadataAsYamlIn: #false)) #'2017-04-20T19:01:59.286212-05:00') ) + ^ #(#(#(#RGMethodDefinition #(#GrafoscopioTextNode #metadataAsYamlIn: #false)) #'2017-04-20T19:01:59.286212-05:00') ) ] { #category : #'code-critics' } @@ -39,12 +39,12 @@ ManifestGrafoscopio class >> ruleRBEqualsTrueRuleV1FalsePositive [ { #category : #'code-critics' } ManifestGrafoscopio class >> ruleRBLongMethodsRuleV1FalsePositive [ - ^ #(#(#(#RGMethodDefinition #(#GrafoscopioNotebook #topBar #false)) #'2016-12-17T18:51:33.99062-05:00') #(#(#RGMethodDefinition #(#GrafoscopioNotebook #notebookSubMenu #false)) #'2017-02-02T11:43:53.106456-05:00') ) + ^ #(#(#(#RGMethodDefinition #(#GrafoscopioNewNotebook #topBar #false)) #'2016-12-17T18:51:33.99062-05:00') #(#(#RGMethodDefinition #(#GrafoscopioNewNotebook #notebookSubMenu #false)) #'2017-02-02T11:43:53.106456-05:00') ) ] { #category : #'code-critics' } ManifestGrafoscopio class >> ruleRBSentNotImplementedRuleV1FalsePositive [ - ^ #(#(#(#RGMetaclassDefinition #(#'GrafoscopioGUI class' #GfUIHelpers)) #'2015-12-23T10:38:16.706667-05:00') #(#(#RGClassDefinition #(#GfUIHelpers)) #'2016-01-06T18:53:45.844051-05:00') #(#(#RGMethodDefinition #(#GrafoscopioNotebook #topBar #false)) #'2016-12-17T18:51:40.617924-05:00') ) + ^ #(#(#(#RGMetaclassDefinition #(#'GrafoscopioGUI class' #GfUIHelpers)) #'2015-12-23T10:38:16.706667-05:00') #(#(#RGClassDefinition #(#GfUIHelpers)) #'2016-01-06T18:53:45.844051-05:00') #(#(#RGMethodDefinition #(#GrafoscopioNewNotebook #topBar #false)) #'2016-12-17T18:51:40.617924-05:00') ) ] { #category : #'code-critics' } @@ -59,5 +59,5 @@ ManifestGrafoscopio class >> ruleRBStringConcatenationRuleV1FalsePositive [ { #category : #'code-critics' } ManifestGrafoscopio class >> ruleSmTMethodTestedRuleV1FalsePositive [ - ^ #(#(#(#RGClassDefinition #(#GrafoscopioNode)) #'2017-10-31T19:59:03.294735-05:00') ) + ^ #(#(#(#RGClassDefinition #(#GrafoscopioTextNode)) #'2017-10-31T19:59:03.294735-05:00') ) ]