Neww notebook.

We have then now launch / launch compatibility.

The new development is scoped in newnotebook and GrafoscopioAbstractNode. t
This commit is contained in:
Santiago Bragagnolo 2020-02-21 10:09:46 +00:00 committed by Offray Luna
parent d57e560554
commit 87dcc43e4d
13 changed files with 2711 additions and 84 deletions

View File

@ -44,7 +44,7 @@ GfUIHelpers class >> addToHelpMenu: aGrafoscopioNotebook [
ifNone: [ ifNone: [
self helpMenu self helpMenu
add: (metadata at: 'shortTitle') add: (metadata at: 'shortTitle')
target: [ GrafoscopioNotebook open: nbFile ] target: [ GrafoscopioNewNotebook open: nbFile ]
selector: #value ] ]. selector: #value ] ].
self updateUI self updateUI
] ]
@ -170,7 +170,7 @@ GfUIHelpers class >> openFromRecentlyUsed [
selection := UIManager default selection := UIManager default
chooseFrom: recentNotebooksReversed title: 'Choose a notebook...'. chooseFrom: recentNotebooksReversed title: 'Choose a notebook...'.
selection > 0 selection > 0
ifTrue: [ GrafoscopioNotebook new openFromFile: (recentNotebooksReversed at: selection)] ifTrue: [ GrafoscopioNewNotebook new openFromFile: (recentNotebooksReversed at: selection)]
ifFalse: [ self inform: 'No notebook selected!' ] ifFalse: [ self inform: 'No notebook selected!' ]
] ]
ifEmpty: [self messageNoRecentDocuments] ifEmpty: [self messageNoRecentDocuments]

View File

@ -18,7 +18,7 @@ GfWorldMenu class >> helpMenuOn: aBuilder [
label: 'Manual'; label: 'Manual';
order: 1; order: 1;
parent: #GfHelpAndDocs; parent: #GfHelpAndDocs;
action: [ GrafoscopioNotebook open: GrafoscopioDocs manual ]. action: [ GrafoscopioNewNotebook open: GrafoscopioDocs manual ].
(aBuilder item: #GfManualPDF) (aBuilder item: #GfManualPDF)
label: 'Manual (PDF)'; label: 'Manual (PDF)';
order: 2; order: 2;
@ -28,12 +28,12 @@ GfWorldMenu class >> helpMenuOn: aBuilder [
label: 'Dataviz'; label: 'Dataviz';
order: 3; order: 3;
parent: #GfHelpAndDocs; parent: #GfHelpAndDocs;
action: [ GrafoscopioNotebook open: DatavizDocs introNotebook ]. action: [ GrafoscopioNewNotebook open: DatavizDocs introNotebook ].
(aBuilder item: #GfHelpDevNotes) (aBuilder item: #GfHelpDevNotes)
label: 'Devs''s notes'; label: 'Devs''s notes';
order: 4; order: 4;
parent: #GfHelpAndDocs; parent: #GfHelpAndDocs;
action: [ GrafoscopioNotebook open: GrafoscopioDocs devNotes ]. action: [ GrafoscopioNewNotebook open: GrafoscopioDocs devNotes ].
(aBuilder item: #GfHelpAbout) (aBuilder item: #GfHelpAbout)
label: 'About Grafoscopio'; label: 'About Grafoscopio';
order: 5; order: 5;
@ -41,6 +41,31 @@ GfWorldMenu class >> helpMenuOn: aBuilder [
action: [ GfUIHelpers messageAbout ]. action: [ GfUIHelpers messageAbout ].
] ]
{ #category : #'world menu' }
GfWorldMenu class >> launchCompatibilityMenuOn: aBuilder [
<worldMenu>
(aBuilder item: #'New notebook')
label: 'New notebook';
order: 1;
parent: #GfLaunchCompatibility;
action: [ GrafoscopioNotebook new openDefault ].
(aBuilder item: #'Notebook from file...')
label: 'Notebook from file...';
order: 2;
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' } { #category : #'world menu' }
GfWorldMenu class >> launchMenuOn: aBuilder [ GfWorldMenu class >> launchMenuOn: aBuilder [
<worldMenu> <worldMenu>
@ -48,12 +73,12 @@ GfWorldMenu class >> launchMenuOn: aBuilder [
label: 'New notebook'; label: 'New notebook';
order: 1; order: 1;
parent: #GfLaunch; parent: #GfLaunch;
action: [ GrafoscopioNotebook new openDefault ]. action: [ GrafoscopioNewNotebook new openDefault ].
(aBuilder item: #'Notebook from file...') (aBuilder item: #'Notebook from file...')
label: 'Notebook from file...'; label: 'Notebook from file...';
order: 2; order: 2;
parent: #GfLaunch; parent: #GfLaunch;
action: [ GrafoscopioNotebook new openFromFileSelector ]. action: [ GrafoscopioNewNotebook new openFromFileSelector ].
(aBuilder item: #GfLaunchOpenRecent) (aBuilder item: #GfLaunchOpenRecent)
label: 'Open recent...'; label: 'Open recent...';
order: 2; order: 2;
@ -62,7 +87,7 @@ GfWorldMenu class >> launchMenuOn: aBuilder [
label: 'Notebook from the Internet...'; label: 'Notebook from the Internet...';
order: 3; order: 3;
parent: #GfLaunch; parent: #GfLaunch;
action: [ GrafoscopioNotebook new openFromUrlUI ]. action: [ GrafoscopioNewNotebook new openFromUrlUI ].
(aBuilder item: #recentNotebooks) (aBuilder item: #recentNotebooks)
label: 'Recent notebooks...'; label: 'Recent notebooks...';
order: 4; order: 4;
@ -73,28 +98,36 @@ GfWorldMenu class >> launchMenuOn: aBuilder [
{ #category : #'world menu' } { #category : #'world menu' }
GfWorldMenu class >> mainMenuItemsOn: aBuilder [ GfWorldMenu class >> mainMenuItemsOn: aBuilder [
"I add the main Grafoscopio menu to the Pharo World." "I add the main Grafoscopio menu to the Pharo World."
<worldMenu>
<worldMenu>
(aBuilder item: #Grafoscopio) (aBuilder item: #Grafoscopio)
label: 'Grafoscopio'; label: 'Grafoscopio';
order: 1; order: 1;
with: [ with: [ (aBuilder
(aBuilder item: #GfLaunch; label: 'Launch') target: self. item: #GfLaunch;
(aBuilder item: #GfUpdate; label: 'Update') target: self. label: 'Launch') target: self.
(aBuilder item: #GfHelpAndDocs; label: 'Help & Docs') 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' } { #category : #'world menu' }
GfWorldMenu class >> openRecentMenu: aBuilder [ GfWorldMenu class >> openRecentMenu: aBuilder [
<worldMenu> <worldMenu>
GrafoscopioNotebook recents GrafoscopioNewNotebook recents
do: [ :f | do: [ :f |
(aBuilder item: #'Open', f basename ) (aBuilder item: #'Open', f basename )
label: 'Open ', f basename; label: 'Open ', f basename;
order: 1; order: 1;
parent: #GfLaunchOpenRecent; parent: #GfLaunchOpenRecent;
action: [ GrafoscopioNotebook open: f ] ] action: [ GrafoscopioNewNotebook open: f ] ]
] ]
{ #category : #'world menu' } { #category : #'world menu' }

View File

@ -2,20 +2,234 @@ Class {
#name : #GrafoscopioAbstractNode, #name : #GrafoscopioAbstractNode,
#superclass : #Object, #superclass : #Object,
#instVars : [ #instVars : [
'id',
'header', 'header',
'created', 'created',
'edited', 'edited',
'selected',
'key',
'icon',
'body',
'tags',
'children',
'parent', 'parent',
'node', 'tags'
'nodesInPreorder',
'links',
'output'
], ],
#category : #'Grafoscopio-Model' #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
]

View File

@ -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
]

File diff suppressed because it is too large Load Diff

View File

@ -12,10 +12,11 @@ Class {
#name : #GrafoscopioNode, #name : #GrafoscopioNode,
#superclass : #Object, #superclass : #Object,
#instVars : [ #instVars : [
'id',
'header', 'header',
'headers',
'created', 'created',
'edited', 'edited',
'selected',
'key', 'key',
'icon', 'icon',
'body', 'body',
@ -23,7 +24,10 @@ Class {
'children', 'children',
'parent', 'parent',
'node', 'node',
'links' 'level',
'nodesInPreorder',
'links',
'output'
], ],
#classInstVars : [ #classInstVars : [
'clipboard' 'clipboard'
@ -851,8 +855,7 @@ GrafoscopioNode >> moveDown [
GrafoscopioNode >> moveDown: aNode [ GrafoscopioNode >> moveDown: aNode [
| index | | index |
"Moves the current node a place before in the children collection where is located" "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) children swap: index with: (index + 1 min: children size)
] ]

View File

@ -19,28 +19,28 @@ GrafoscopioNodeTest >> dummyHtml [
{ #category : #tests } { #category : #tests }
GrafoscopioNodeTest >> newTestTree [ GrafoscopioNodeTest >> newTestTree [
| node0 node1 node2 node3 node4 | | node0 node1 node2 node3 node4 |
node0 := GrafoscopioNode new node0 := GrafoscopioTextNode new
created: DateAndTime now printString; created: DateAndTime now;
header: 'Arbol principal'. header: 'Arbol principal'.
node1 := GrafoscopioNode new node1 := GrafoscopioTextNode new
created: DateAndTime now printString; created: DateAndTime now;
header: 'Markup'; header: 'Markup';
body: 'I am <b>just a node with markup</b>'; body: 'I am <b>just a node with markup</b>';
tagAs: 'text'; tagAs: 'text';
links: 'temp.md'. links: 'temp.md'.
node2 := GrafoscopioNode new node2 := GrafoscopioTextNode new
created: DateAndTime now printString; created: DateAndTime now ;
header: '%output Code'; header: '%output Code';
tagAs: 'código'; tagAs: 'código';
body: '(ConfigurationOfGrafoscopio>>#version14:) sourceCode'. body: '(ConfigurationOfGrafoscopio>>#version14:) sourceCode'.
node3 := GrafoscopioNode new node3 := GrafoscopioTextNode new
created: DateAndTime now printString; created: DateAndTime now ;
header: '%invisible'; header: '%invisible';
tagAs: 'text'; tagAs: 'text';
body: '<i>Just testing</i>'. body: '<i>Just testing</i>'.
node1 addNode: node3. node1 addNode: node3.
node4 := GrafoscopioNode new node4 := GrafoscopioTextNode new
created: DateAndTime now printString; created: DateAndTime now ;
header: 'Something'; header: 'Something';
tagAs: 'text'; tagAs: 'text';
body: '<h1>else</h1>'. body: '<h1>else</h1>'.
@ -54,7 +54,7 @@ GrafoscopioNodeTest >> newTestTree [
GrafoscopioNodeTest >> testAddingChildren [ GrafoscopioNodeTest >> testAddingChildren [
| tree nnode orig | | tree nnode orig |
tree := self newTestTree. tree := self newTestTree.
nnode := GrafoscopioNode new. nnode := GrafoscopioTextNode new.
orig := tree children size. orig := tree children size.
tree addNode: nnode. tree addNode: nnode.
self assert: tree children size equals: orig + 1. self assert: tree children size equals: orig + 1.
@ -64,9 +64,9 @@ GrafoscopioNodeTest >> testAddingChildren [
{ #category : #tests } { #category : #tests }
GrafoscopioNodeTest >> testDemoteNode [ GrafoscopioNodeTest >> testDemoteNode [
| tree child1 child2 | | tree child1 child2 |
tree := GrafoscopioNode new. tree := GrafoscopioTextNode new.
child1 := GrafoscopioNode new. child1 := GrafoscopioTextNode new.
child2 := GrafoscopioNode new. child2 := GrafoscopioTextNode new.
tree tree
addNode: child1; addNode: child1;
addNode: child2. addNode: child2.
@ -79,7 +79,7 @@ GrafoscopioNodeTest >> testDemoteNode [
{ #category : #tests } { #category : #tests }
GrafoscopioNodeTest >> testFindAndReplace [ GrafoscopioNodeTest >> testFindAndReplace [
| tree | | tree |
tree := GrafoscopioNode new. tree := GrafoscopioTextNode new.
tree body: 'I''m only a test node.'. tree body: 'I''m only a test node.'.
tree find: 'only' andReplaceWith: 'JUST'. tree find: 'only' andReplaceWith: 'JUST'.
self assert: (tree body findString: 'JUST') > 0. self assert: (tree body findString: 'JUST') > 0.
@ -100,15 +100,15 @@ GrafoscopioNodeTest >> testHasMarkdownSubtreesToExport [
{ #category : #tests } { #category : #tests }
GrafoscopioNodeTest >> testInitializeIsOk [ GrafoscopioNodeTest >> testInitializeIsOk [
self shouldnt: [ GrafoscopioNode new ] raise: Error self shouldnt: [ GrafoscopioTextNode new ] raise: Error
] ]
{ #category : #tests } { #category : #tests }
GrafoscopioNodeTest >> testPromoteNode [ GrafoscopioNodeTest >> testPromoteNode [
| tree child1 child2 | | tree child1 child2 |
tree := GrafoscopioNode new. tree := GrafoscopioTextNode new.
child1 := GrafoscopioNode new. child1 := GrafoscopioTextNode new.
child2 := GrafoscopioNode new. child2 := GrafoscopioTextNode new.
tree addNode: child1. tree addNode: child1.
child1 addNode: child2. child1 addNode: child2.
child2 promote. child2 promote.
@ -132,7 +132,7 @@ GrafoscopioNodeTest >> testRemoveLeadingLineNumbersSized [
9var nodes = tree.nodes(treeData); 9var nodes = tree.nodes(treeData);
10var links = tree.links(nodes); 10var links = tree.links(nodes);
11 '. 11 '.
testNode := GrafoscopioNode new testNode := GrafoscopioTextNode new
body: copiedCode. body: copiedCode.
testNode removeLeadingLineNumbersSized: 3. testNode removeLeadingLineNumbersSized: 3.
self assert: testNode body equals: ' self assert: testNode body equals: '
@ -165,7 +165,7 @@ GrafoscopioNodeTest >> testSanitizedLink [
| node link | | node link |
link := 'docutopia://hackbo:hackbot'. 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 self assert: (node sanitizeDefaultLink = 'https://docutopia.tupale.co/hackbo:hackbot') equals: true
] ]

View File

@ -87,10 +87,8 @@ GrafoscopioNotebook >> addCommandFrom: dictionary into: stream [
{ #category : #'editing nodes' } { #category : #'editing nodes' }
GrafoscopioNotebook >> addNode [ GrafoscopioNotebook >> addNode [
| newNode | self currentNode addNodeAfterMe.
newNode := self currentNode addNodeAfterMe.
self notebookContent: notebook. self notebookContent: notebook.
self selectedItem: newNode.
] ]
{ #category : #persistence } { #category : #persistence }
@ -627,8 +625,7 @@ GrafoscopioNotebook >> moveSelectedNodeDown [
| editedNode | | editedNode |
editedNode := tree selectedItem content. editedNode := tree selectedItem content.
editedNode moveDown. editedNode moveDown.
self notebookContent: notebook. self notebookContent: notebook
tree needRebuild: true.
] ]
{ #category : #'editing nodes' } { #category : #'editing nodes' }
@ -676,7 +673,8 @@ GrafoscopioNotebook >> notebook: anObject [
{ #category : #api } { #category : #api }
GrafoscopioNotebook >> notebookContent: aTree [ GrafoscopioNotebook >> notebookContent: aTree [
tree 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 } { #category : #initialization }
@ -922,12 +920,6 @@ GrafoscopioNotebook >> removeNode [
tree selectedItem tree selectedItem
ifNil: [ ^ self inform: 'No node available or properly selected ' ]. ifNil: [ ^ self inform: 'No node available or properly selected ' ].
contentToDelete := tree selectedItem content. 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. parentContent := contentToDelete parent.
children := parentContent children. children := parentContent children.
children size > 1 children size > 1
@ -935,15 +927,7 @@ GrafoscopioNotebook >> removeNode [
ifTrue: [ newSelectedContent := children at: children size - 1 ] ] ifTrue: [ newSelectedContent := children at: children size - 1 ] ]
ifFalse: [ newSelectedContent := parentContent ]. ifFalse: [ newSelectedContent := parentContent ].
contentToDelete parent removeNode: contentToDelete. contentToDelete parent removeNode: contentToDelete.
self notebookContent: notebook. self notebookContent: notebook
self resetSelectedItem.
]
{ #category : #'editing nodes' }
GrafoscopioNotebook >> resetSelectedItem [
tree selectedIndex: (tree selectedIndex min: notebook children size).
tree highlightedItem: tree selectedItem.
tree updatePresenter.
] ]
{ #category : #persistence } { #category : #persistence }
@ -998,12 +982,6 @@ GrafoscopioNotebook >> seePdf [
onSuccessDo: [ :v | (#open command argument: self pdfFile fullName) schedule ] 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 } { #category : #persistence }
GrafoscopioNotebook >> subtreeAsMarkdown [ GrafoscopioNotebook >> subtreeAsMarkdown [
| currentNode | | currentNode |

View File

@ -36,6 +36,6 @@ GrafoscopioTextModel >> content: aGrafoscopioNodeContent [
GrafoscopioTextModel >> initializeWidgets [ GrafoscopioTextModel >> initializeWidgets [
body := self newText. body := self newText.
body beForGrafoscopio. body beForText.
body autoAccept: true. body autoAccept: true.
] ]

View File

@ -0,0 +1,995 @@
"
An UbakyeNode is and administrator of all node operations in a tree.
Instance Variables
node: <Object>
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: '&iacute;' 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 ] ] ]
]

View File

@ -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.'
]

View File

@ -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 ]
]

View File

@ -9,7 +9,7 @@ Class {
{ #category : #'code-critics' } { #category : #'code-critics' }
ManifestGrafoscopio class >> ruleRBAssignmentInIfTrueRuleV1FalsePositive [ 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' } { #category : #'code-critics' }
@ -24,7 +24,7 @@ ManifestGrafoscopio class >> ruleRBBooleanPrecedenceRuleV1FalsePositive [
{ #category : #'code-critics' } { #category : #'code-critics' }
ManifestGrafoscopio class >> ruleRBCascadedNextPutAllsRuleV1FalsePositive [ 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' } { #category : #'code-critics' }
@ -39,12 +39,12 @@ ManifestGrafoscopio class >> ruleRBEqualsTrueRuleV1FalsePositive [
{ #category : #'code-critics' } { #category : #'code-critics' }
ManifestGrafoscopio class >> ruleRBLongMethodsRuleV1FalsePositive [ 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' } { #category : #'code-critics' }
ManifestGrafoscopio class >> ruleRBSentNotImplementedRuleV1FalsePositive [ 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' } { #category : #'code-critics' }
@ -59,5 +59,5 @@ ManifestGrafoscopio class >> ruleRBStringConcatenationRuleV1FalsePositive [
{ #category : #'code-critics' } { #category : #'code-critics' }
ManifestGrafoscopio class >> ruleSmTMethodTestedRuleV1FalsePositive [ ManifestGrafoscopio class >> ruleSmTMethodTestedRuleV1FalsePositive [
^ #(#(#(#RGClassDefinition #(#GrafoscopioNode)) #'2017-10-31T19:59:03.294735-05:00') ) ^ #(#(#(#RGClassDefinition #(#GrafoscopioTextNode)) #'2017-10-31T19:59:03.294735-05:00') )
] ]