Going to a even more aggresive refactor :-o

This commit is contained in:
Santiago Bragagnolo 2020-03-24 16:03:15 +00:00 committed by Offray Luna
parent 6a92669efd
commit b61a9e83de
24 changed files with 835 additions and 2565 deletions

View File

@ -44,7 +44,7 @@ GfUIHelpers class >> addToHelpMenu: aGrafoscopioNotebook [
ifNone: [
self helpMenu
add: (metadata at: 'shortTitle')
target: [ GrafoscopioNewNotebook open: nbFile ]
target: [ GrafoscopioNotebook 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: [ GrafoscopioNewNotebook new openFromFile: (recentNotebooksReversed at: selection)]
ifTrue: [ GrafoscopioNotebook new openFromFile: (recentNotebooksReversed at: selection)]
ifFalse: [ self inform: 'No notebook selected!' ]
]
ifEmpty: [self messageNoRecentDocuments]

View File

@ -1,13 +1,15 @@
"
Just an abstract node.
"
Class {
#name : #GrafoscopioAbstractNode,
#superclass : #Object,
#instVars : [
'id',
'header',
'created',
'edited',
'parent',
'tags'
'tags',
'order',
'name'
],
#classInstVars : [
'clipboard'
@ -15,67 +17,24 @@ Class {
#category : 'Grafoscopio-Model'
}
{ #category : #utility }
GrafoscopioAbstractNode 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 }
GrafoscopioAbstractNode class >> clipboard [
^ clipboard
]
{ #category : #accessing }
GrafoscopioAbstractNode class >> clipboard: anObject [
clipboard := anObject
]
{ #category : #utility }
GrafoscopioAbstractNode 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 : #testing }
GrafoscopioAbstractNode class >> isAbstract [
^ self = GrafoscopioAbstractNode
]
{ #category : #utility }
GrafoscopioAbstractNode 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 : #testing }
GrafoscopioAbstractNode class >> isLeaf [
^ false
]
{ #category : #'add/remove nodes' }
GrafoscopioAbstractNode >> addNodeAfterMe: genericNode [
"Adds a generic node after the given node so they become slibings of the same parent"
{ #category : #testing }
GrafoscopioAbstractNode class >> showInMenu [
^ false
]
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 >> addChild: aBlock ofClass: aClass [
self subclassResponsibility
]
{ #category : #accessing }
@ -88,11 +47,6 @@ GrafoscopioAbstractNode >> addTag: aTag [
]
{ #category : #'as yet unclassified' }
GrafoscopioAbstractNode >> content [
self subclassResponsibility
]
{ #category : #accessing }
GrafoscopioAbstractNode >> created [
@ -106,21 +60,6 @@ GrafoscopioAbstractNode >> created: aTimestamp [
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
@ -137,129 +76,27 @@ GrafoscopioAbstractNode >> edited: aTimestamp [
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 >> instantiateBody [
| body |
body := self specModelClass new.
body content: self content.
^ body
]
{ #category : #accessing }
GrafoscopioAbstractNode >> isLeaf [
^ true
^ self class isLeaf
]
{ #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
GrafoscopioAbstractNode >> name [
^ name
]
{ #category : #accessing }
GrafoscopioAbstractNode >> openIn: aNotebook [
aNotebook header text: self header.
aNotebook
body:
(self instantiateBody
owner: aNotebook;
yourself)
]
{ #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 [
^ GrafoscopioNewTextModel
GrafoscopioAbstractNode >> name: aName [
name := aName
]
{ #category : #accessing }
@ -281,14 +118,7 @@ GrafoscopioAbstractNode >> tags: aCollection [
tags := aCollection
]
{ #category : #accessing }
GrafoscopioAbstractNode >> title [
"Returns the receiver header"
^ header size > 30 ifTrue: [ (header readStream next: 28) , '...' ] ifFalse: [ header ]
]
{ #category : #'as yet unclassified' }
GrafoscopioAbstractNode >> updateEditionTimestamp [
GrafoscopioAbstractNode >> updateStamp [
self edited: DateAndTime now
]

View File

@ -0,0 +1,54 @@
"
Branch node. this kind of node has no content
"
Class {
#name : #GrafoscopioBranchNode,
#superclass : #GrafoscopioLeafNode,
#instVars : [
'children'
],
#category : 'Grafoscopio-Model'
}
{ #category : #'instance creation' }
GrafoscopioBranchNode class >> isLeaf [
^ false
]
{ #category : #accessing }
GrafoscopioBranchNode >> acceptVisitor: aGrafoscopioVisitor [
aGrafoscopioVisitor visitBranchNode: self.
]
{ #category : #accessing }
GrafoscopioBranchNode >> acceptsChildsOfClass: aClass [
^ {GrafoscopioBranchNode.
GrafoscopioUnitNode} includes: aClass
]
{ #category : #accessing }
GrafoscopioBranchNode >> addChild: aBlock ofClass: aClass [
(self acceptsChildsOfClass: aClass)
ifTrue: [ self children add: aBlock value ]
]
{ #category : #accessing }
GrafoscopioBranchNode >> children [
"Returns the receivers list of children"
^ children ifNil: [ children := OrderedCollection new ]
]
{ #category : #accessing }
GrafoscopioBranchNode >> children: aCollection [
"Sets the receivers children"
aCollection do: [:currentNode | currentNode parent: self ].
children := aCollection.
]
{ #category : #accessing }
GrafoscopioBranchNode >> isLeaf [
^ false
]

View File

@ -1,62 +1,24 @@
"
This kind of a leafNodes holds code text.
"
Class {
#name : #GrafoscopioCodeNode,
#superclass : #GrafoscopioTrunkNode,
#instVars : [
'icon',
'body'
],
#superclass : #GrafoscopioTextNode,
#category : 'Grafoscopio-Model'
}
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode class >> icon [
^ self iconNamed:#objects
]
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode class >> nameForSelection [
^ 'New Code Node'
]
{ #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 : #accessing }
GrafoscopioCodeNode >> acceptVisitor: aGrafoscopioVisitor [
aGrafoscopioVisitor visitCodeNode: self.
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode >> body [
^ body
]
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode >> body: aBody [
body := aBody
]
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode >> content [
^ body ifNil:[ '' ]
]
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode >> header [
^ super header, ' (Code)'
]
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode >> instantiateBody [
| widget |
widget := super instantiateBody.
widget body whenTextChangedDo: [ :arg | self body: arg ].
^ widget
]
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode >> shouldAskBeforeRemove [
^ self content isNotEmpty
]
{ #category : #'as yet unclassified' }
GrafoscopioCodeNode >> specModelClass [
^ GrafoscopioNewCodeModel
]

View File

@ -0,0 +1,76 @@
Class {
#name : #GrafoscopioDocumentEditionPerspective,
#superclass : #GrafoscopioPerspective,
#instVars : [
'tree',
'document'
],
#category : 'Grafoscopio-New-UI'
}
{ #category : #accessing }
GrafoscopioDocumentEditionPerspective class >> defaultSpec [
^ SpBoxLayout newVertical
add: #toolbar height: self toolbarHeight;
add:
(SpBoxLayout newHorizontal
add: #tree width: 100;
add: #viewport;
yourself) yourself
]
{ #category : #accessing }
GrafoscopioDocumentEditionPerspective class >> icon [
^ self iconNamed: #merge
]
{ #category : #'as yet unclassified' }
GrafoscopioDocumentEditionPerspective >> addNewNodeOfClass: aClass [
(tree selectedItem ifNil: [ document ])
addChild: [ self instantiateNode: aClass ]
ofClass: aClass.
self needRebuild: false.
self buildWithSpec
]
{ #category : #initialization }
GrafoscopioDocumentEditionPerspective >> createDefaultViewportVisitor [
^ GrafoscopioViewportVisitor new
]
{ #category : #initialization }
GrafoscopioDocumentEditionPerspective >> createViewport [
^ self createDefaultViewportVisitor createViewportFor: document into: self
]
{ #category : #initialization }
GrafoscopioDocumentEditionPerspective >> initializeWidgets [
super initializeWidgets.
tree := self newTreeTable.
tree
addColumn: (SpStringTableColumn evaluated: #name);
children: [ :node |
node isLeaf
ifTrue: [ {} ]
ifFalse: [ node children ] ]
]
{ #category : #'as yet unclassified' }
GrafoscopioDocumentEditionPerspective >> instantiateNode: aClass [
aClass class = GrafoscopioUnitNode species
ifTrue: [ | name |
name := UIManager default
request: 'Unit name'
initialAnswer: 'New unit'.
^ aClass new
name: name;
yourself ].
^ aClass new
" self error: 'Unexpected class'"
]
{ #category : #initialization }
GrafoscopioDocumentEditionPerspective >> updateModel: aModel [
document := aModel document.
tree roots: aModel document children.
]

View File

@ -0,0 +1,15 @@
Class {
#name : #GrafoscopioExportPerspective,
#superclass : #GrafoscopioPerspective,
#category : 'Grafoscopio-New-UI'
}
{ #category : #accessing }
GrafoscopioExportPerspective class >> icon [
^ self iconNamed: #export
]
{ #category : #initialization }
GrafoscopioExportPerspective >> createViewport [
^ self newLabel
]

View File

@ -0,0 +1,53 @@
"
Leaf node. Any content node is leaf
"
Class {
#name : #GrafoscopioLeafNode,
#superclass : #GrafoscopioAbstractNode,
#instVars : [
'parent'
],
#category : 'Grafoscopio-Model'
}
{ #category : #testing }
GrafoscopioLeafNode class >> isLeaf [
^ true
]
{ #category : #accessing }
GrafoscopioLeafNode >> acceptVisitor: aGrafoscopioVisitor [
aGrafoscopioVisitor visitLeafNode: self.
]
{ #category : #accessing }
GrafoscopioLeafNode >> addChild: aBlock ofClass: aClass [
self error: 'Leaf nodes are abstract. '
]
{ #category : #accessing }
GrafoscopioLeafNode >> level [
"Returns the level of the node. See the setter message for details"
^ parent ifNil: [ 0 ] ifNotNil: [ 1 + parent level ]
]
{ #category : #accessing }
GrafoscopioLeafNode >> parent [
"Returns the parent of the current node"
^ parent
]
{ #category : #accessing }
GrafoscopioLeafNode >> 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 ]
]

View File

@ -29,32 +29,6 @@ GrafoscopioNewCodeModel >> content: aGrafoscopioNodeContent [
body text: aGrafoscopioNodeContent
]
{ #category : #'as yet unclassified' }
GrafoscopioNewCodeModel >> extractHtmlImages [
"comment stating purpose of message"
|imgSoup imgHost imgList src|
imgList := Set new.
imgSoup := Soup fromString: self body.
(imgSoup findAllTags: 'img') do: [ :each|
src := (each attributeAt: 'src') asUrl.
(src host) ifNil: [src host: self links last asUrl removeLastPathSegment].
imgList add: src.
"imgList add: (each attributeAt: 'src') asUrl."
"OSProcess waitForCommand: 'wget ', (each attributeAt: 'src')."
"imgHost := self links last removeLastPathSegment."
"imgPath:= ((each attributeAt: 'src') asUrl). "
"ZnEasy getJpeg: (imgHost , imgPath) asUrl."
"OSProcess waitForCommand: ('mkdir ', imgPath)."
"Transcript show: ' wget ', imgPath , '/',(each attributeAt: 'src'). "
].
^imgList .
]
{ #category : #initialization }
GrafoscopioNewCodeModel >> initializeWidgets [

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ Class {
{ #category : #specs }
GrafoscopioNewTextModel class >> defaultSpec [
^ SpBoxLayout newVertical
add: #body;
add: #body height: 300;
yourself
]

View File

@ -0,0 +1,179 @@
Class {
#name : #GrafoscopioPerspective,
#superclass : #SpPresenter,
#instVars : [
'toolbar',
'viewport'
],
#category : 'Grafoscopio-New-UI'
}
{ #category : #'as yet unclassified' }
GrafoscopioPerspective class >> defaultPerspective [
^ GrafoscopioDocumentEditionPerspective
]
{ #category : #'as yet unclassified' }
GrafoscopioPerspective class >> defaultSpec [
^ SpBoxLayout newVertical
add: #toolbar height: self toolbarHeight;
add: #viewport;
yourself
]
{ #category : #'as yet unclassified' }
GrafoscopioPerspective class >> isAbstract [
^ self = GrafoscopioPerspective
]
{ #category : #'as yet unclassified' }
GrafoscopioPerspective class >> perspectives [
^ self allSubclasses select: [ : c | c isAbstract not ]
]
{ #category : #initialization }
GrafoscopioPerspective >> aboutToBeUninstalledFrom: aTreeNotebook [
]
{ #category : #initialization }
GrafoscopioPerspective >> addItemTo: aGroup [
aGroup
addItem: [ :item |
item
name: 'Dynamic';
icon: (self iconNamed: #delete);
action: [ aGroup menuItems remove: item.
self needRebuild: false.
self buildWithSpec ] ].
self needRebuild: false.
self buildWithSpec
]
{ #category : #initialization }
GrafoscopioPerspective >> addingMenu [
| menu |
menu := self newMenu.
GrafoscopioAbstractNode allSubclasses
select: [ :c | c showInMenu ]
thenDo: [ :n |
menu
addItem: [ :item |
item
name: n nameForSelection;
icon: n icon;
action: [ self addNewNodeOfClass: n ] ] ].
^ menu
]
{ #category : #initialization }
GrafoscopioPerspective >> createToolbar [
| aMenu |
aMenu := self newMenuBar
addGroup: [ :group |
group
addItem: [ :item |
item
name: 'File';
icon: (self iconNamed: #openIcon);
subMenu: self subMenu ].
group
addItem: [ :item |
item
name: nil;
description: 'Open file';
icon: (self iconNamed: #openIcon);
action: [ self inform: 'Open File' ] ].
group
addItem: [ :item |
item
name: nil;
description: 'Save File';
icon: (self iconNamed: #smallSaveIcon);
action: [ self inform: 'Save File' ] ].
group
addItem: [ :item |
item
name: nil;
description: 'Print file';
icon: (self iconNamed: #smallPrintIcon);
action: [ self inform: 'Print file' ] ] ];
addGroup: [ :group |
group
addItem: [ :item |
item
name: nil;
description: 'Undo';
icon: (self iconNamed: #smallUndoIcon);
action: [ self inform: 'Undo' ] ].
group
addItem: [ :item |
item
name: nil;
description: 'Redo';
icon: (self iconNamed: #smallRedoIcon);
action: [ self inform: 'Redo' ] ].
group
addItem: [ :item |
item
name: '';
icon: (self iconNamed: #add);
subMenu: self addingMenu ].
];
addGroup: [ :group |
group
addItem: [ :item |
item
name: nil;
description: 'Add menu item';
icon: (self iconNamed: #add);
action: [ self addItemTo: group ] ] ].
aMenu color: Color transparent.
^ aMenu
]
{ #category : #initialization }
GrafoscopioPerspective >> createViewport [
self subclassResponsibility
]
{ #category : #initialization }
GrafoscopioPerspective >> initializeWidgets [
super initializeWidgets.
toolbar := self createToolbar.
viewport := self createViewport
]
{ #category : #initialization }
GrafoscopioPerspective >> subMenu [
^ self newMenu
addItem: [ :item |
item
name: 'Open';
icon: (self iconNamed: #openIcon);
shortcut: $o meta;
action: [ self inform: 'Open' ] ];
addItem: [ :item |
item
name: 'Save';
icon: (self iconNamed: #smallSaveIcon);
shortcut: $s meta;
action: [ self inform: 'Save' ] ];
addItem: [ :item |
item
name: 'Print';
shortcut: $p meta;
icon: (self iconNamed: #smallPrintIcon);
action: [ self inform: 'Print' ] ];
addItem: [ :item |
item
name: 'Close';
shortcut: $c meta;
icon: (self iconNamed: #smallCancelIcon);
action: [ self inform: 'Kill' ] ];
yourself
]
{ #category : #'as yet unclassified' }
GrafoscopioPerspective >> updateModel: aModel [
]

View File

@ -0,0 +1,21 @@
Class {
#name : #GrafoscopioProject,
#superclass : #Object,
#instVars : [
'document',
'dictionary'
],
#category : 'Grafoscopio-Model'
}
{ #category : #'as yet unclassified' }
GrafoscopioProject >> document [
^ document
]
{ #category : #initialization }
GrafoscopioProject >> initialize [
super initialize.
document := GrafoscopioRootNode new.
dictionary := GrafoscopioRootNode new
]

View File

@ -0,0 +1,46 @@
"
This is a root node. It represents a document.
"
Class {
#name : #GrafoscopioRootNode,
#superclass : #GrafoscopioAbstractNode,
#instVars : [
'children'
],
#category : 'Grafoscopio-Model'
}
{ #category : #accessing }
GrafoscopioRootNode >> acceptVisitor: aGrafoscopioVisitor [
aGrafoscopioVisitor visitRootNode: self.
]
{ #category : #accessing }
GrafoscopioRootNode >> acceptsChildsOfClass: aClass [
^ {GrafoscopioUnitNode} includes: aClass
]
{ #category : #accessing }
GrafoscopioRootNode >> addChild: aBlock ofClass: aClass [
(self acceptsChildsOfClass: aClass)
ifTrue: [ self children add: aBlock value ]
]
{ #category : #'as yet unclassified' }
GrafoscopioRootNode >> children [
^ children
]
{ #category : #accessing }
GrafoscopioRootNode >> initialize [
super initialize.
children := SortedCollection new
sortBlock: [ :a :b | a order < b order ];
yourself
]
{ #category : #accessing }
GrafoscopioRootNode >> level [
^ 1
]

View File

@ -1,923 +1,37 @@
"
An UbakyeNode is and administrator of all node operations in a tree.
Instance Variables
node: <Object>
node
- xxxxx
This kind of a leafNodes holds plain text.
"
Class {
#name : #GrafoscopioTextNode,
#superclass : #GrafoscopioTrunkNode,
#superclass : #GrafoscopioLeafNode,
#instVars : [
'key',
'icon',
'body',
'links'
'text'
],
#category : 'Grafoscopio-Model'
}
{ #category : #'as yet unclassified' }
GrafoscopioTextNode class >> nameForSelection [
^ 'New Text Node'
]
{ #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 := SpTreeNodePresenter 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"
GrafoscopioTextNode class >> icon [
^ self iconNamed: #workspace
]
{ #category : #'as yet unclassified' }
GrafoscopioTextNode >> content [
^ body
GrafoscopioTextNode class >> nameForSelection [
^ 'New text node'
]
{ #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 ].
GrafoscopioTextNode class >> showInMenu [
^ true
]
{ #category : #accessing }
GrafoscopioTextNode >> hasChildren [
(self children size > 0)
ifTrue: [ ^true ]
ifFalse: [ ^false ]
]
GrafoscopioTextNode >> acceptVisitor: aGrafoscopioVisitor [
aGrafoscopioVisitor visitTextNode: self.
{ #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 >> instantiateBody [
| widget |
widget := super instantiateBody.
widget body whenTextChangedDo: [ :arg | self body: arg ].
^ widget
]
{ #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 header beginsWith: '%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 : #accessing }
GrafoscopioTextNode >> openIn: aNotebook [
super openIn: aNotebook.
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 [
"por defecto"
^ GrafoscopioNewTextModel
]
{ #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 ] ] ]
GrafoscopioTextNode >> text: aString [
text := aString
]

View File

@ -0,0 +1,85 @@
Class {
#name : #GrafoscopioTreeNotebook,
#superclass : #SpPresenter,
#instVars : [
'#sidebar',
'#viewport',
'#model',
'=>',
'SpObservableSlot',
'#empty',
'#perspectives'
],
#category : 'Grafoscopio-New-UI'
}
{ #category : #specs }
GrafoscopioTreeNotebook class >> defaultSpec [
^ SpBoxLayout newHorizontal
add:
(SpBoxLayout newVertical
add: #empty height: self toolbarHeight;
add: #sidebar;
yourself)
width: 51;
add: #viewport;
yourself
]
{ #category : #initialization }
GrafoscopioTreeNotebook >> createDefaultComponent [
^ GrafoscopioPerspective defaultPerspective new
]
{ #category : #initialization }
GrafoscopioTreeNotebook >> initialize [
super initialize.
perspectives := Dictionary new.
]
{ #category : #initialization }
GrafoscopioTreeNotebook >> initializePrivateAnnouncements [
super initializePrivateAnnouncements.
self property: #model whenChangedDo: [ self updateModel ]
]
{ #category : #initialization }
GrafoscopioTreeNotebook >> initializeWidgets [
super initializeWidgets.
sidebar := self sidebar.
viewport := self createDefaultComponent.
empty := self newLabel.
]
{ #category : #initialization }
GrafoscopioTreeNotebook >> installPerspective: aPerspective [
viewport ifNotNil: [ viewport aboutToBeUninstalledFrom: self ].
viewport := (perspectives at: aPerspective ifAbsentPut: [ self instantiate: aPerspective ]) .
self updateModel.
]
{ #category : #'as yet unclassified' }
GrafoscopioTreeNotebook >> open: aGrafoscopioProject [
model := aGrafoscopioProject.
self openWithSpec.
self updateModel
]
{ #category : #initialization }
GrafoscopioTreeNotebook >> sidebar [
| bar |
bar := self instantiate: SpSidebar.
GrafoscopioPerspective perspectives
do:
[ :p | bar addAction: [ self installPerspective: p ] icon: p icon ].
^ bar
]
{ #category : #initialization }
GrafoscopioTreeNotebook >> updateModel [
viewport updateModel: model.
self needRebuild: false.
self buildWithSpec
]

View File

@ -1,50 +0,0 @@
Class {
#name : #GrafoscopioTrunkNode,
#superclass : #GrafoscopioAbstractNode,
#instVars : [
'children'
],
#category : 'Grafoscopio-Model'
}
{ #category : #testing }
GrafoscopioTrunkNode class >> isAbstract [
^ self = GrafoscopioTrunkNode
]
{ #category : #accessing }
GrafoscopioTrunkNode >> children [
"Returns the receivers list of children"
^ children ifNil: [ children := OrderedCollection new ]
]
{ #category : #accessing }
GrafoscopioTrunkNode >> children: aCollection [
"Sets the receivers children"
aCollection do: [:currentNode | currentNode parent: self ].
children := aCollection.
]
{ #category : #accessing }
GrafoscopioTrunkNode >> isLeaf [
^ false
]
{ #category : #movement }
GrafoscopioTrunkNode >> 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 }
GrafoscopioTrunkNode >> 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)
]

View File

@ -0,0 +1,31 @@
Class {
#name : #GrafoscopioUnitNode,
#superclass : #GrafoscopioBranchNode,
#category : 'Grafoscopio-Model'
}
{ #category : #'instance creation' }
GrafoscopioUnitNode class >> icon [
^ self iconNamed: #smallHierarchyBrowser
]
{ #category : #'instance creation' }
GrafoscopioUnitNode class >> nameForSelection [
^ 'New unit'
]
{ #category : #'instance creation' }
GrafoscopioUnitNode class >> showInMenu [
^ true
]
{ #category : #accessing }
GrafoscopioUnitNode >> acceptVisitor: aGrafoscopioVisitor [
aGrafoscopioVisitor visitUnitNode: self.
]
{ #category : #accessing }
GrafoscopioUnitNode >> acceptsChildsOfClass: aClass [
^ aClass isLeaf or: [ aClass = self class ]
]

View File

@ -1,27 +0,0 @@
Class {
#name : #GrafoscopioUrlCachedNode,
#superclass : #GrafoscopioUrlNode,
#instVars : [
'content'
],
#category : 'Grafoscopio-Model'
}
{ #category : #'instance creation' }
GrafoscopioUrlCachedNode class >> nameForSelection [
^ 'New URL-Cached Node'
]
{ #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

@ -1,12 +1,20 @@
"
URL Node downloads content for rendering.
"
Class {
#name : #GrafoscopioUrlNode,
#superclass : #GrafoscopioAbstractNode,
#superclass : #GrafoscopioLeafNode,
#instVars : [
'link'
],
#category : 'Grafoscopio-Model'
}
{ #category : #'instance creation' }
GrafoscopioUrlNode class >> icon [
^ self iconNamed: #remote
]
{ #category : #'instance creation' }
GrafoscopioUrlNode class >> nameForSelection [
^ 'New URL Node'
@ -19,6 +27,17 @@ GrafoscopioUrlNode class >> new [
yourself
]
{ #category : #'instance creation' }
GrafoscopioUrlNode class >> showInMenu [
^ true
]
{ #category : #'as yet unclassified' }
GrafoscopioUrlNode >> acceptVisitor: aGrafoscopioVisitor [
aGrafoscopioVisitor visitUrlNode: self.
]
{ #category : #'as yet unclassified' }
GrafoscopioUrlNode >> content [
^ (self url

View File

@ -0,0 +1,45 @@
Class {
#name : #GrafoscopioViewportVisitor,
#superclass : #GrafoscopioVisitor,
#instVars : [
'viewport',
'stack',
'items',
'presenter'
],
#category : 'Grafoscopio-New-UI'
}
{ #category : #visiting }
GrafoscopioViewportVisitor >> createViewportFor: aDocumentNode into: aPresenter [
presenter := aPresenter.
items := OrderedCollection new.
aDocumentNode acceptVisitor: self.
viewport := aPresenter instantiate: SpComponentListPresenter.
viewport items: items.
^ viewport
]
{ #category : #visiting }
GrafoscopioViewportVisitor >> visitCodeNode: aNode [
| code |
code := presenter newCode.
code text: aNode text.
items add: code.
]
{ #category : #visiting }
GrafoscopioViewportVisitor >> visitTextNode: aNode [
| text |
text := presenter newText .
text text: aNode text.
items add: text.
]
{ #category : #visiting }
GrafoscopioViewportVisitor >> visitUrlNode: aNode [
| text |
text := presenter newText .
text text: aNode content.
items add: text.
]

View File

@ -0,0 +1,48 @@
Class {
#name : #GrafoscopioVisitor,
#superclass : #Object,
#category : 'Grafoscopio-Model'
}
{ #category : #visiting }
GrafoscopioVisitor >> visitBranchNode: aNode [
self visitNode: aNode.
aNode children do: [ : c | c acceptVisitor: self ].
]
{ #category : #visiting }
GrafoscopioVisitor >> visitCodeNode: aNode [
self visitNode: aNode
]
{ #category : #visiting }
GrafoscopioVisitor >> visitLeafNode: aNode [
self visitNode: aNode
]
{ #category : #visiting }
GrafoscopioVisitor >> visitNode: aNode [
" nothing to do here"
]
{ #category : #visiting }
GrafoscopioVisitor >> visitRootNode: aNode [
self visitNode: aNode.
aNode children do: [ : c | c acceptVisitor: self ].
]
{ #category : #visiting }
GrafoscopioVisitor >> visitTextNode: aNode [
self visitNode: aNode
]
{ #category : #visiting }
GrafoscopioVisitor >> visitUnitNode: aNode [
self visitNode: aNode.
aNode children do: [ : c | c acceptVisitor: self ].
]
{ #category : #visiting }
GrafoscopioVisitor >> visitUrlNode: aNode [
self visitNode: aNode
]

View File

@ -0,0 +1,15 @@
Class {
#name : #GrafoscopioZoaPediaPerspective,
#superclass : #GrafoscopioPerspective,
#category : 'Grafoscopio-New-UI'
}
{ #category : #accessing }
GrafoscopioZoaPediaPerspective class >> icon [
^ self iconNamed: #edit
]
{ #category : #initialization }
GrafoscopioZoaPediaPerspective >> createViewport [
^ self newLabel
]

View File

@ -0,0 +1,44 @@
Class {
#name : #SpSidebar,
#superclass : #SpPresenter,
#instVars : [
'container'
],
#category : 'Grafoscopio-New-UI'
}
{ #category : #specs }
SpSidebar class >> defaultSpec [
^ SpBoxLayout newHorizontal
add: #container ;
yourself
]
{ #category : #initialization }
SpSidebar >> addAction: aBlock icon: anIcon [
container
addPresenter:
(self createDefaultPresenter
parent: self;
action: [ :state |
container unselectAll.
aBlock cull: state ];
icon: anIcon;
yourself)
]
{ #category : #'as yet unclassified' }
SpSidebar >> buttons [
^ container items
]
{ #category : #initialization }
SpSidebar >> createDefaultPresenter [
^ self instantiate: SpSquareButton.
]
{ #category : #initialization }
SpSidebar >> initializeWidgets [
super initializeWidgets.
container := self instantiate: SpComponentListPresenter.
]

View File

@ -0,0 +1,52 @@
Class {
#name : #SpSquareButton,
#superclass : #SpPresenter,
#instVars : [
'button',
'parent'
],
#category : 'Grafoscopio-New-UI'
}
{ #category : #specs }
SpSquareButton class >> defaultSpec [
^ SpBoxLayout newVertical
add: (SpBoxLayout newHorizontal add: #button width: 50; yourself) height: 50;
yourself
]
{ #category : #initialization }
SpSquareButton >> action: anAction [
button
action: [ :state |
state
ifTrue: [ parent buttons
reject: [ :b | b = self ]
thenDo: [ :b | b toggleOff ].
anAction cull: state ]
ifFalse: [ ] ]
]
{ #category : #initialization }
SpSquareButton >> icon: icon [
button icon: icon
]
{ #category : #initialization }
SpSquareButton >> initializeWidgets [
super initializeWidgets.
button := self newToggleButton
extent: 90 @ 50;
color: Color transparent;
yourself
]
{ #category : #'as yet unclassified' }
SpSquareButton >> parent: aSpSidebar [
parent := aSpSidebar
]
{ #category : #initialization }
SpSquareButton >> toggleOff [
button state: false.
]