" I am a Grafoscopio Notebook. Example: | testTree nb | testTree := GrafoscopioNode new becomeDefaultTestTree. nb := GrafoscopioNotebook new. nb notebookContent: testTree. nb openWithSpec " Class { #name : #GrafoscopioNewNotebook, #superclass : #SpPresenter, #instVars : [ 'tree', 'header', 'body', 'links', 'windowMainMenu', 'workingFile', 'notebook', 'debugMessage', 'imagesList', 'exporting' ], #classInstVars : [ 'recents' ], #category : #'Grafoscopio-New-UI' } { #category : #utility } GrafoscopioNewNotebook class >> SHA1For: aFile is: aSHA1String [ "I verify that a file has the same signature that the one in a given string, returning true in that case or false otherwise" ^ (SHA1 new hashMessage: aFile asFileReference binaryReadStream contents) hex = aSHA1String ] { #category : #specs } GrafoscopioNewNotebook class >> defaultSpec [ ^ SpBoxLayout newVertical add: #windowMainMenu withConstraints: [ :constraints | constraints height: self toolbarHeight ]; add: (SpPanedLayout newHorizontal position: 250; add: #tree; add: (SpBoxLayout newVertical add: #header height: self toolbarHeight; add: #body; add: #links height: self toolbarHeight; yourself); yourself); yourself ] { #category : #'instance creation' } GrafoscopioNewNotebook class >> kindsOfNode [ ^ GrafoscopioAbstractNode allSubclasses select: [ : c | c isAbstract not ] ] { #category : #'instance creation' } GrafoscopioNewNotebook class >> newDefault [ ^ self new. ] { #category : #'instance creation' } GrafoscopioNewNotebook class >> open: aFileReference [ self newDefault openFromFile: aFileReference ] { #category : #'instance creation' } GrafoscopioNewNotebook class >> recents [ ^ recents ifNil: [ recents := Set new. ] ] { #category : #'instance creation' } GrafoscopioNewNotebook class >> registerRecent: aFileReference [ recents add: aFileReference ] { #category : #utilities } GrafoscopioNewNotebook >> addCommandFrom: dictionary into: stream [ dictionary keysAndValuesDo: [ :k :v | k = 'thisNotebook' ifTrue: [ stream nextPutAll: (GrafoscopioUtils perform: v on: self) ]] ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> addNode [ | newNode kind previousPath | kind := self chooseKindOfNode. kind isClass ifTrue: [ newNode := kind new. self currentNode addNodeAfterMe: newNode. previousPath := tree selection selectedPath. self notebookContent: notebook. self selectedPathNextTo: previousPath ] ] { #category : #persistence } GrafoscopioNewNotebook >> askToSaveBeforeClosing [ | saveChanges | saveChanges := UIManager default question: 'Do you want to save changes in the notebook before closing?' title: 'Save changes before closing?'. saveChanges ifNil: [ ^ self notebook unsavedNodes inspect ]. ^ saveChanges ] { #category : #accessing } GrafoscopioNewNotebook >> body [ ^ body ] { #category : #accessing } GrafoscopioNewNotebook >> body: anObject [ body := anObject ] { #category : #utilities } GrafoscopioNewNotebook >> checksumForRootSubtree [ "I return the checksum (crypto hash) of the workingFile where this notebook is being stored. I'm useful for data provenance and traceability of derivated files coming from this source notebook." self workingFile ifNil: [ ^ self ]. self workingFile contents = '' ifTrue: [ ^ self ]. ^ GrafoscopioUtils checksumFor: self workingFile ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> chooseKindOfNode [ | idx values | values := self kindsOfNode. idx := UIManager default chooseFrom: (values collect: [ :v | v nameForSelection ]) lines: {} title: 'What kind of node? '. ^ idx = 0 ifTrue: [ nil ] ifFalse: [ values at: idx ] ] { #category : #'as yet unclassified' } GrafoscopioNewNotebook >> content [ self shouldBeImplemented. ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> copyNodeToClipboard [ tree selectedItem copyToClipboard. self notebookContent: notebook ] { #category : #persistence } GrafoscopioNewNotebook >> createNewExample [ | node0 node1 | node0 := GrafoscopioTextNode new created: DateAndTime now ; header: 'Arbol principal'; yourself. node1 := GrafoscopioTextNode new created: DateAndTime now; header: 'Node 1'; body: ''; yourself. node0 addNode: node1. ^ node0 ] { #category : #operation } GrafoscopioNewNotebook >> currentNode [ ^ tree selectedItem ifNil: [ notebook children ifEmpty: [ notebook root ] ifNotEmpty: [ notebook children first ] ] ] { #category : #operation } GrafoscopioNewNotebook >> currentNodeContent [ ^ self currentNode content ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> cutNodeToClipboard [ self copyNodeToClipboard; removeNode. ] { #category : #accessing } GrafoscopioNewNotebook >> debugMessage [ ^ debugMessage ifNil: [ self defineDebugMessageUI ] ] { #category : #accessing } GrafoscopioNewNotebook >> debugMessage: aGrafoscopioNodeSelector [ "I define a message that can be used for debugging purposes in the current notebook." debugMessage := aGrafoscopioNodeSelector ] { #category : #operation } GrafoscopioNewNotebook >> debugWithSelector: aSymbol [ "I invoke a message to debug in the current node. In the future the debugging scope can be changed to include different elements instead of the current node." | currentNode nodeContent | currentNode := tree highlightedItem. currentNode ifNil: [ ^ self ]. nodeContent := currentNode content. ^ (nodeContent perform: aSymbol asSymbol) inspect ] { #category : #utilities } GrafoscopioNewNotebook >> defineDebugMessageUI [ | answer | answer := UIManager default request: 'Define debug message to be send to a selected node in this notebook.' initialAnswer: 'messageNoDebugSelector'. self debugMessage: answer ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> demoteNode [ | editedNode | editedNode := tree selectedItem. editedNode demote. self notebookContent: notebook. ] { #category : #'as yet unclassified' } GrafoscopioNewNotebook >> downloadImages [ "I download all images in a notebook into a local folder that respects relative paths. So if a image refers to http://mysite.com/uploads/chap1/myimage.png, it will be stored into: 'uploads/chap1/myimage.png' in the same folder where the notebook is stored. This is helpful for notebooks conversions that expect to have local images in particular locations." | parentFolder | parentFolder := UIManager default chooseDirectory: 'Please, choose a destination for the images' from: (self workingFile ifNil: [ FileLocator home ] ifNotNil: [ self workingFile parent ]). parentFolder ifNotNil: [ ^ self imagesList do: [ :each | | relativePathString link | link := each contents asUrl. relativePathString := link directory. relativePathString ifNotEmpty: [ GrafoscopioUtils ensureCreateDirectory: relativePathString into: parentFolder ] ] ] ] { #category : #persistence } GrafoscopioNewNotebook >> ensureNotExporting [ self isAlreadyExporting ifTrue: [ ^ self error: ' Already exporting! Please wait ' ] ] { #category : #persistence } GrafoscopioNewNotebook >> exportAllSubtreesAsMarkup [ | toBeExported | toBeExported := self notebook selectMarkupSubtreesToExport. toBeExported ifEmpty: [ ^ self ]. toBeExported do: [ :each | self subtreeAsMarkdownFileFor: each ]. self inform: toBeExported size asString , ' exported markdown subtrees.' ] { #category : #persistence } GrafoscopioNewNotebook >> exportAsHTML [ "I export the current tree/document to a HTML file, using pandoc external app. I suppose pandoc is already installed and available in the system." | htmlFile | self markdownFile exists ifTrue: [ self markdownFile delete ]. self exportAsMarkdown. htmlFile := self htmlFile. htmlFile exists ifTrue: [ htmlFile delete ]. self exportUsing: {'--standalone'. self markdownFile fullName. '--output'. htmlFile fullName} output: htmlFile fullName " Smalltalk platformName = 'Win32' ifTrue: [WinProcess createProcess: 'pandoc --standalone ', self markdownFile fullName, ' -o ', htmlFile]." ] { #category : #persistence } GrafoscopioNewNotebook >> exportAsLaTeX [ "I export the current tree/document to a LaTeX file, using pandoc external app. I suppose pandoc is already installed and available in the system." | texFile | self markdownFile exists ifTrue: [ self markdownFile delete ]. self halt. "self exportAsMarkdown.""<- This violates the separation of concenrs. Markdown exportation should be explicit. There is still the issue of how to deal with desynchronization between a notebook which has unsaved changes as markdown.... TO BE REVIWED!" texFile := self markdownFile parent fullName,'/', self markdownFile basenameWithoutExtension, '.tex'. texFile asFileReference exists ifTrue: [ texFile asFileReference delete ]. "OSProcess command: 'pandoc --standalone ', self markdownFile fullName, ' -o ', texFile." self inform: ('File exported as: ', String cr, texFile). ] { #category : #persistence } GrafoscopioNewNotebook >> exportAsMarkdown [ "I export the current working tree/document to a markdown file." workingFile ifNil: [ self inform: 'File NOT exported. Please save the notebook on hard drive first' ] ifNotNil: [ self exportNode: (self notebook) asMarkdownIn: (self markdownFile) ] ] { #category : #persistence } GrafoscopioNewNotebook >> exportAsPDF [ "I export the current tree/document to a PDF file, using pandoc and LaTeX external apps. The latex engine used is xelatex, to minimize errors and warnings related with UTF8 support. I suppose all them are already installed and defined in the system." | pandocCommonCommand | self ensureNotExporting. self exportAsMarkdown. self pdfFile ensureDelete. pandocCommonCommand := self pandocOptionsComputed , ' ' , self markdownFile fullName , ' --output ' , self pdfFile fullName. ^ self exportUsing: ((' ' split: pandocCommonCommand) reject: #isEmpty) output: self pdfFile fullName ] { #category : #persistence } GrafoscopioNewNotebook >> exportAsSton: aNotebook on: aFileStream [ aNotebook flatten. self notebook root updateEditionTimestamp. (STON writer on: aFileStream) newLine: String crlf; prettyPrint: true; keepNewLines: true; nextPut: aNotebook children. ] { #category : #utility } GrafoscopioNewNotebook >> exportLinkContent [ Transcript show: (self currentNodeContent asMarkdown) ] { #category : #persistence } GrafoscopioNewNotebook >> exportNode: aGrafoscopioNode asMarkdownIn: aFile [ "I export the current tree/document to a markdown file" aFile ensureDelete. aFile ensureCreateFile; writeStreamDo: [:stream | stream nextPutAll: ('---', String cr, 'exportedFrom: ', self checksumForRootSubtree, String cr) withInternetLineEndings. aGrafoscopioNode metadataAsYamlIn: stream. stream nextPutAll: ('---', String cr, String cr) withInternetLineEndings, aGrafoscopioNode asMarkdown ]. self inform: 'Exported as: ', String cr, aFile fullName ] { #category : #persistence } GrafoscopioNewNotebook >> exportUsing: arguments [ self ensureNotExporting. exporting := (#pandoc command arguments: arguments) future. exporting onSuccessDo: [ :val | exporting := nil. self inform: 'File exported as: ' , String cr , self pdfFile fullName ]. exporting onFailureDo: [ :e | exporting := nil. self inform: 'Error exporting, ' , self pdfFile fullName , ': ' , e messageText ] ] { #category : #persistence } GrafoscopioNewNotebook >> exportUsing: arguments output: aName [ self ensureNotExporting. exporting := (#pandoc command arguments: arguments) future. exporting onSuccessDo: [ :val | exporting := nil. self inform: 'File exported as: ' , String cr , aName ]. exporting onFailureDo: [ :e | exporting := nil. self inform: 'Error exporting, ' , aName , ': ' , e messageText ]. ^ exporting ] { #category : #api } GrafoscopioNewNotebook >> extent [ ^900@500 ] { #category : #'as yet unclassified' } GrafoscopioNewNotebook >> findAndReplace [ | currentNode replaceGUI findString replaceString | currentNode := tree selectedItem. replaceGUI := GrafoscopioReplace new. replaceGUI openWithSpec. replaceGUI ok on: [ ] do: [ findString := replaceGUI returnValues at: 'find'. replaceString := replaceGUI returnValues at: 'replace'. currentNode find: findString andReplaceWith: replaceString. ] ] { #category : #testing } GrafoscopioNewNotebook >> hasAWorkingFileDefined [ self workingFile ifNil: [ ^ false ] ifNotNil: [ ^ true ] ] { #category : #accessing } GrafoscopioNewNotebook >> header [ ^ header ] { #category : #accessing } GrafoscopioNewNotebook >> header: anObject [ header := anObject ] { #category : #persistence } GrafoscopioNewNotebook >> htmlFile [ ^ (self markdownFile parent fullName , '/' , self markdownFile basenameWithoutExtension , '.html') asFileReference ] { #category : #operation } GrafoscopioNewNotebook >> htmlToMarkdown [ self currentNodeContent htmlToMarkdown. self updateBodyFor: self currentNode ] { #category : #operation } GrafoscopioNewNotebook >> htmlToMarkdownSubtree [ self currentNodeContent htmlToMarkdownSubtree. self updateBodyFor: self currentNode ] { #category : #accessing } GrafoscopioNewNotebook >> imagesList [ imagesList ifNil: [ ^ #('No images list for this notebook') ]. ^ imagesList ] { #category : #accessing } GrafoscopioNewNotebook >> imagesList: anObject [ self halt. imagesList := anObject ] { #category : #'as yet unclassified' } GrafoscopioNewNotebook >> importImages [ self imagesList: (Pandoc listImagesFrom: self markdownFile). self inform: 'All notebook images has been imported.', String cr, 'Now you can list and download them.' ] { #category : #operation } GrafoscopioNewNotebook >> importLinkContent [ "I see if a node link is an url located at 'http://ws.stfx.eu', wich means that is a shared workspace, and convert the node body to an interactive playground" | currentNode | currentNode := tree selectedItem. currentNode ifNil: [ ^ self ]. currentNode importPlaygroundLink. currentNode importHtmlLink. self updateBodyFor: currentNode ] { #category : #initialization } GrafoscopioNewNotebook >> initialize [ super initialize. self notebook: self createNewExample. self notebookContent: self notebook ] { #category : #initialization } GrafoscopioNewNotebook >> initializePresenter [ tree whenSelectionChangedDo: [ :selection | selection selectedItem ifNotNil: [ self updateBodyFor: selection selectedItem ] ]. header whenTextChangedDo: [ :arg | tree selectedItem header = arg ifFalse: [ | item | item := tree selectedItem. tree selectedItem header: arg. tree selectedItem updateEditionTimestamp. tree roots: tree roots. tree selectItem: item ] ]. links whenTextChangedDo: [ :arg | (tree respondsTo: #link:) ifTrue: [ tree selectedItem link: arg. tree selectedItem updateEditionTimestamp ] ] ] { #category : #initialization } GrafoscopioNewNotebook >> initializeWidgets [ windowMainMenu := self topBar. header := self newTextInput. header autoAccept: true. body := self newText. body class traceCr. body disable. body text: '<- Select a node'. body autoAccept: true. links := self newTextInput. tree := self newTreeTable. tree addColumn: (SpStringTableColumn evaluated: #title); children: [ :node | node isLeaf ifTrue: [ {} ] ifFalse: [ node children ] ]. self focusOrder add: tree; add: header; add: body; add: links ] { #category : #persistence } GrafoscopioNewNotebook >> initializeWindow: aWindowPresenter [ super initializeWindow: aWindowPresenter. aWindowPresenter title: ' New | Grafoscopio 2.0 notebook'; windowIcon: self taskbarIcon; askOkToClose: true ] { #category : #persistence } GrafoscopioNewNotebook >> isAlreadyExporting [ ^ exporting isNotNil ] { #category : #persistence } GrafoscopioNewNotebook >> isSaved [ "I tell if a notebook has been saved in a persistence storage, including last editions." ^ self hasAWorkingFileDefined and: [self isSavedAfterLastEdition ]. ] { #category : #testing } GrafoscopioNewNotebook >> isSavedAfterLastEdition [ ^ self notebook isSavedAfterLastEdition ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> kindsOfNode [ ^ self class kindsOfNode ] { #category : #accessing } GrafoscopioNewNotebook >> links [ ^ links ] { #category : #accessing } GrafoscopioNewNotebook >> links: anObject [ links := anObject ] { #category : #'as yet unclassified' } GrafoscopioNewNotebook >> linksList [ | currentNode | currentNode := tree selectedItem. GrafoscopioLinksList new content: currentNode; openWithSpec ] { #category : #utilities } GrafoscopioNewNotebook >> listImagesUI [ ListPresenter new title: 'Images files list'; items: self imagesList ; openWithSpec ] { #category : #persistence } GrafoscopioNewNotebook >> loadFromFile: aFileReference [ "I load the contents of aFileReference into a GrafoscopioNotebook, without opening it." (aFileReference basename endsWith: 'ston') ifFalse: [ ^ self ]. self workingFile: aFileReference. self notebook: ((STON fromString: self workingFile contents) at: 1) parent. self title: self workingFile basenameWithIndicator, ' | Grafoscopio notebook'. self notebookContent: self notebook. ] { #category : #persistence } GrafoscopioNewNotebook >> markdownFile [ "I define the location of the markdown file where the notebook will be exported" | markdownFile | markdownFile := (((workingFile parent) / workingFile basenameWithoutExtension) fullName, '.markdown') asFileReference. ^ markdownFile ] { #category : #utilities } GrafoscopioNewNotebook >> markdownFileChecksum [ self workingFile ifNil: [ ^ self ]. self workingFile contents = '' ifTrue: [ ^ self ]. ^ GrafoscopioUtils checksumFor: self markdownFile ] { #category : #utilities } GrafoscopioNewNotebook >> markdownFileChecksumUpto: anInteger [ "I cut the markdownFileCheckup upto a given integer. Type coersion is needed, because this message argument can be read from a string in %metadata nodes. Maybe the way used by playgrounds to import text as commands can be useful here." ^ self markdownFileChecksum copyFrom: 1 to: anInteger asInteger. ] { #category : #utilities } GrafoscopioNewNotebook >> metadata [ ^ self notebook metadata ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> moveSelectedNodeDown [ | editedNode | editedNode := tree selectedItem. editedNode moveDown. self notebookContent: notebook. tree needRebuild: true. tree selectItem: editedNode. ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> moveSelectedNodeUp [ | editedNode | editedNode := tree selectedItem. editedNode moveUp. self notebookContent: notebook ] { #category : #utilities } GrafoscopioNewNotebook >> navigateRelativePathFor: aFileString [ "Given a relative path according to location of the notebook's workingFile, I navigate to that file if exist and create it, including subdirectories if it does not exist. If the relative path is located in a subdirectory that shares the route with the notebooks working file, it must start with the folders name, without using './' to point the same shared root " | finalLocation pathSegments | aFileString ifEmpty: [ ^ self ]. aFileString asUrl host ifNotNil: [ ^self ]. finalLocation := workingFile parent. pathSegments := aFileString splitOn: '/'. pathSegments allButLastDo: [ :segment | (segment = '..') ifTrue: [ finalLocation := finalLocation parent ] ifFalse: [ finalLocation := finalLocation / segment. finalLocation exists ifFalse: [ finalLocation ensureCreateDirectory ]]]. finalLocation := finalLocation / (pathSegments last). finalLocation exists ifFalse: [ finalLocation ensureCreateFile ]. ^ finalLocation ] { #category : #accessing } GrafoscopioNewNotebook >> notebook [ ^ notebook ] { #category : #accessing } GrafoscopioNewNotebook >> notebook: anObject [ notebook := anObject ] { #category : #api } GrafoscopioNewNotebook >> notebookContent: aTree [ tree roots: aTree children ] { #category : #initialization } GrafoscopioNewNotebook >> notebookSubMenu [ ^ SpMenuBarPresenter new addGroup: [ :group | group addItem: [ :item | item name: 'Save'; icon: (Smalltalk ui icons iconNamed: #smallSave); shortcut: $s command; action: [ self saveWorkingNotebook ] ]. group addItem: [ :item | item name: 'Save as...'; icon: (Smalltalk ui icons iconNamed: #smallSaveAs); action: [ self saveToFileUI ] ]. group addItem: [ :item | item name: 'Import images'; icon: (Smalltalk ui icons iconNamed: #processBrowser); action: [ self importImages ] ]. group addItem: [ :item | item name: 'See images list'; icon: (Smalltalk ui icons iconNamed: #processBrowser); action: [ self listImagesUI ] ]. group addItem: [ :item | item name: 'Download images'; icon: (Smalltalk ui icons iconNamed: #processBrowser); action: [ self downloadImages ] ]. group addItem: [ :item | item name: 'Export as markdown'; icon: (Smalltalk ui icons iconNamed: #smallSaveAs); action: [ self exportAsMarkdown ] ]. group addItem: [ :item | item name: 'Export as html'; icon: (Smalltalk ui icons iconNamed: #smallWindow); action: [ self exportAsHTML ] ]. group addItem: [ :item | item name: 'Export as LaTeX'; icon: (Smalltalk ui icons iconNamed: #smallPrint); action: [ self exportAsLaTeX ] ]. group addItem: [ :item | item name: 'Export as pdf'; icon: (Smalltalk ui icons iconNamed: #smallPrint); action: [ self exportAsPDF ] ]. group addItem: [ :item | item name: 'See html'; icon: (self iconNamed: #smallInspectIt); action: [ self seeHtml ] ]. group addItem: [ :item | item name: 'See pdf'; icon: (Smalltalk ui icons iconNamed: #smallInspectIt); action: [ self seePdf ] ]. group addItem: [ :item | item name: 'Define debug message...'; icon: Smalltalk ui icons glamorousBug; action: [ self defineDebugMessageUI ] ] ] ] { #category : #'event handling' } GrafoscopioNewNotebook >> okToChange [ self isSaved ifTrue: [ ^ true ] ifFalse: [ ^ self askToSaveBeforeClosing ] ] { #category : #persistence } GrafoscopioNewNotebook >> openDefault [ "I open a new default notebook" ^ self class new openWithSpec. ] { #category : #persistence } GrafoscopioNewNotebook >> openFromFile: aFileReference [ self class registerRecent: aFileReference. self loadFromFile: aFileReference. ^ self openWithSpec. ] { #category : #persistence } GrafoscopioNewNotebook >> openFromFileSelector [ | file nb | file := UIManager default chooseExistingFileReference:'Choose a file' extensions: #('ston') path: FileLocator documents. file ifNil: [ self inform: 'No file selected'. ^ self ]. self workingFile: file. nb := self class new. nb openFromFile: self workingFile. GfUIHelpers updateRecentNotebooksWith: workingFile ] { #category : #persistence } GrafoscopioNewNotebook >> openFromUrl: url [ "Opens a tree from a file named aFileName" | fileName sanitized | sanitized := GrafoscopioUtils sanitize: url. fileName := sanitized segments last. GrafoscopioUtils downloadingFrom: sanitized withMessage: 'Downloading document...' into: FileLocator temp. self class new openFromFile: (FileLocator temp / fileName) ] { #category : #persistence } GrafoscopioNewNotebook >> openFromUrlUI [ "This method generates the UI for the openFromUrl: method, it asks for a URL from the user" | fileUrl | "GrafoscopioBrowser configureSettings." fileUrl := UIManager default textEntry: 'Enter the URL' title: 'Open notebook from URL'. fileUrl isNil ifTrue: [ ^nil ]. self class new openFromUrl: fileUrl ] { #category : #utilities } GrafoscopioNewNotebook >> pandocOptions [ ^ self notebook pandocOptions ] { #category : #utilities } GrafoscopioNewNotebook >> pandocOptionsComputed [ "I convert the pandoc options array into a single line that can be used with the pandoc command." | result | result := '' writeStream. self pandocOptions ifNil: [ ^ '' ]. self pandocOptions do: [ :option | option isDictionary ifTrue: [ self addCommandFrom: option into: result ] ifFalse: [ result nextPutAll: option] ]. ^ result contents ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> pasteNodeFromClipboard [ tree selectedItem pasteFromClipboard. self notebookContent: notebook. ] { #category : #persistence } GrafoscopioNewNotebook >> pdfFile [ "I define the location of the markdown file where the notebook will be exported" | pdfFile | pdfFile := (self markdownFile parent fullName,'/', self markdownFile basenameWithoutExtension, '.pdf') asFileReference. ^ pdfFile. ] { #category : #initialization } GrafoscopioNewNotebook >> projectSubMenu [ ^ MenuModel new addGroup: [ :group | group addItem: [ :item | item name: 'Activate remote repository...'; icon: Smalltalk ui icons smallPushpinIcon; action: [ self inform: 'To be implemented ...' ] ]. group addItem: [ :item | item name: 'Activate local repository...'; icon: Smalltalk ui icons homeIcon; action: [ self inform: 'To be implemented ...' ] ]. group addItem: [ :item | item name: 'Add file...'; icon: Smalltalk ui icons newerPackagesAvailableIcon; action: [ self inform: 'To be implemented ...' ] ]. group addItem: [ :item | item name: 'Delete file...'; icon: Smalltalk ui icons packageDeleteIcon; action: [ self inform: 'To be implemented ...' ] ]. group addItem: [ :item | item name: 'Commit to repository'; icon: Smalltalk ui icons smallScreenshotIcon; action: [ self inform: 'To be implemented ...' ] ]. group addItem: [ :item | item name: 'Credentials'; icon: (self iconNamed: #userIcon); action: [ self inform: 'To be implemented ...' ] ] ] ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> promoteNode [ | editedNote | editedNote := tree selectedItem. editedNote promote. self notebookContent: notebook ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> removeNode [ | contentToDelete parentContent newSelectedContent children path | tree selectedItem ifNil: [ ^ self inform: 'No node available or properly selected ' ]. path := tree selection selectedPath. contentToDelete := tree selectedItem. contentToDelete shouldAskBeforeRemove ifTrue: [ (UIManager default questionWithoutCancel: 'The selected node has children and / or content. This change so far cannot be undone. Are you sure you want to proceed? ' title: 'Remove node') ifFalse: [ ^ self ] ]. parentContent := tree selectedItem parent. children := parentContent children. children size > 1 ifTrue: [ children last = contentToDelete ifTrue: [ newSelectedContent := children at: children size - 1 ] ] ifFalse: [ newSelectedContent := parentContent ]. contentToDelete parent removeNode: contentToDelete. self notebookContent: notebook. self resetSelectedItemTo: path. ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> resetSelectedItem [ tree selectedIndex: (tree selectedIndex min: notebook children size). tree highlightedItem: tree selectedItem. tree updatePresenter. ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> resetSelectedItemTo: aPath [ self selectedPathPreviousTo: aPath ] { #category : #persistence } GrafoscopioNewNotebook >> saveToFile: aFileReference [ "I save the current tree/document to a file and update storage timestamp." aFileReference ifNil: [ self inform: 'No file selected for saving. Save NOT done.'. ^ self ]. workingFile := aFileReference. self workingFile ensureDelete. self workingFile writeStreamDo: [:stream | self exportAsSton: self notebook on: stream ]. self title: self workingFile basenameWithIndicator, ' | Grafoscopio notebook'. self inform: ('File saved at: ', String cr, self workingFile fullName). GfUIHelpers updateRecentNotebooksWith: aFileReference. ] { #category : #persistence } GrafoscopioNewNotebook >> saveToFileUI [ | file | file := UIManager default chooseForSaveFileReference: 'Save notebook to file as...' extensions: #('ston') path: (workingFile ifNotNil: [ workingFile parent ] ifNil: [ FileLocator documents ] ). file ifNil: [ self inform: 'Saving to file cancelled'. ^ self ] ifNotNil:[self saveToFile: file]. ] { #category : #persistence } GrafoscopioNewNotebook >> saveWorkingNotebook [ "Saves the current tree to the user predefined file location used when he/she opened it." self workingFile ifNil: [ self saveToFileUI ] ifNotNil: [ self saveToFile: workingFile ]. self notebook root updateEditionTimestamp. GfUIHelpers updateRecentNotebooksWith: workingFile ] { #category : #inspecting } GrafoscopioNewNotebook >> seeHtml [ self pdfFile exists ifTrue: [ (#open command argument: self htmlFile fullName) schedule ] ] { #category : #persistence } GrafoscopioNewNotebook >> seePdf [ self exportAsPDF onSuccessDo: [ :v | (#open command argument: self pdfFile fullName) schedule ] ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> selectedItem: anItem [ tree selectItem: (tree roots detect: [ : p | p = anItem ]). ] { #category : #'asdas yet unclassified' } GrafoscopioNewNotebook >> selectedItem: aGrafoscopioTextNode path: aPath [ aPath at: aPath size put: (aPath at: aPath size) + 1. tree selectPath: aPath ] { #category : #'asdas yet unclassified' } GrafoscopioNewNotebook >> selectedPathNextTo: aPath [ aPath at: aPath size put: (aPath at: aPath size) + 1. tree selectPath: aPath ] { #category : #'asdas yet unclassified' } GrafoscopioNewNotebook >> selectedPathPreviousTo: aPath [ (aPath at: aPath size) - 1 > 0 ifTrue: [ aPath at: aPath size put: (aPath at: aPath size) - 1. tree selectPath: aPath ] ifFalse: [ self selectedPathPreviousTo: aPath allButLast ] ] { #category : #persistence } GrafoscopioNewNotebook >> subtreeAsMarkdown [ | currentNode | currentNode := tree selectedItem. self inform: ('Exported as: ', String cr, (self subtreeAsMarkdownFileFor: currentNode) fullName ) ] { #category : #persistence } GrafoscopioNewNotebook >> subtreeAsMarkdownFileFor: aNode [ | exportedFile | aNode links ifEmpty: [ ^ self ]. exportedFile:= self navigateRelativePathFor: aNode links last. exportedFile class = self class ifTrue: [ ^ self ]. self exportNode: aNode asMarkdownIn: exportedFile. ^ exportedFile ] { #category : #'editing nodes' } GrafoscopioNewNotebook >> toggleCodeNode [ | currentNode | currentNode := tree selectedItem. currentNode toggleCodeText. self updateBodyFor: currentNode ] { #category : #initialization } GrafoscopioNewNotebook >> topBar [ ^ SpMenuBarPresenter new addGroup: [ :group | group addItem: [ :item | item name: 'Notebook'; icon: (self iconNamed: #smallObjects); subMenu: self notebookSubMenu ]. group addItem: [ :item | item name: 'Project'; icon: (self iconNamed: #catalog); subMenu: self projectSubMenu ] ]; addGroup: [ :group | group addItem: [ :item | item name: ''; description: 'Save notebook'; icon: (self iconNamed: #glamorousSave); action: [ self saveWorkingNotebook ] ]. group addItem: [ :item | item name: ''; description: 'Export all Markdown subtrees'; icon: (self iconNamed: #glamorousMore); action: [ self exportAllSubtreesAsMarkup ] ]. group addItem: [ :item | item name: ''; description: 'Cut'; icon: (self iconNamed: #smallCut); action: [ self cutNodeToClipboard ] ]. group addItem: [ :item | item name: ''; description: 'Copy'; icon: (self iconNamed: #smallCopy); action: [ self copyNodeToClipboard ] ]. group addItem: [ :item | item name: ''; description: 'Paste'; icon: (self iconNamed: #smallPaste); action: [ self pasteNodeFromClipboard ] ]. group addItem: [ :item | item name: ''; description: 'Find & Replace'; icon: (self iconNamed: #smallFind); action: [ self findAndReplace ] ] ]; addGroup: [ :group | group addItem: [ :item | item name: ''; description: 'Add node'; icon: MendaIcons new plusIcon; action: [ self addNode ] ]. group addItem: [ :item | item name: ''; description: 'Delete node'; icon: MendaIcons new minusIcon; action: [ self removeNode ] ]. group addItem: [ :item | item name: ''; description: 'Move node up'; icon: MendaIcons new arrowUpIcon; action: [ self moveSelectedNodeUp ] ]. group addItem: [ :item | item name: ''; description: 'Move node down'; icon: MendaIcons new arrowDownIcon; action: [ self moveSelectedNodeDown ] ]. group addItem: [ :item | item name: ''; description: 'Move node left'; icon: MendaIcons new arrowLeftIcon; action: [ self promoteNode ] ]. group addItem: [ :item | item name: ''; description: 'Move node right'; icon: MendaIcons new arrowRightIcon; action: [ self demoteNode ] ] ]; addGroup: [ :group | group addItem: [ :item | item name: ''; description: 'Toggle: code <--> text'; icon: MendaIcons new smalltalkCodeIcon; action: [ self toggleCodeNode ] ]. group addItem: [ :item | item name: ''; description: 'List node links'; icon: (self iconNamed: #tinyMenu); action: [ self linksList ] ]. group addItem: [ :item | item name: ''; description: 'Visit link'; icon: (self iconNamed: #glamorousRight); action: [ self visitNodeLink ] ]. group addItem: [ :item | item name: ''; description: 'Import link content'; icon: (self iconNamed: #glamorousOpenFromUrl); action: [ self importLinkContent ] ]. group addItem: [ :item | item name: ''; description: 'Export link content'; icon: (self iconNamed: #glamorousSaveToUrl); action: [ self exportLinkContent ] ]. group addItem: [ :item | item name: ''; description: 'HTML to Markdown'; icon: (self iconNamed: #smallProfile); action: [ self htmlToMarkdown ] ]. group addItem: [ :item | item name: ''; description: 'HTML to Markdown subtree'; icon: (self iconNamed: #hierarchy); action: [ self htmlToMarkdownSubtree ] ]. group addItem: [ :item | item name: ''; description: 'Tag as...'; icon: MendaIcons new tagAddIcon; action: [ self inform: 'To be implemented...' ] ]. group addItem: [ :item | item name: ''; description: 'Untag ....'; icon: MendaIcons new tagMinusIcon; action: [ self inform: 'To be implemented...' ] ]. group addItem: [ :item | item name: ''; description: 'Edit tags...'; icon: FontAwesomeIcons new tagsIcon; action: [ self inform: 'To be implemented...' ] ] ]; addGroup: [ :debug | debug addItem: [ :item | item name: ''; description: 'Debug'; icon: (self iconNamed: #glamorousBug); action: [ self debugWithSelector: self debugMessage ] ] ] ] { #category : #accessing } GrafoscopioNewNotebook >> tree [ ^ tree ] { #category : #accessing } GrafoscopioNewNotebook >> tree: anObject [ tree := anObject ] { #category : #operation } GrafoscopioNewNotebook >> updateBodyFor: aNode [ | item | self needRebuild: false. tree needRebuild: false. item := tree selectedItem. body needRebuild: true. aNode openIn: self. self buildWithSpec. tree selectItem: item. ] { #category : #operation } GrafoscopioNewNotebook >> visitNodeLink [ tree selectedItem visitLastLink ] { #category : #accessing } GrafoscopioNewNotebook >> windowMainMenu [ ^ windowMainMenu ] { #category : #accessing } GrafoscopioNewNotebook >> windowMainMenu: anObject [ windowMainMenu := anObject ] { #category : #accessing } GrafoscopioNewNotebook >> workingFile [ ^ workingFile ] { #category : #accessing } GrafoscopioNewNotebook >> workingFile: aFileReference [ workingFile := aFileReference. ] { #category : #'as yet unclassified' } GrafoscopioNewNotebook >> wrapBodyLines [ self currentNodeContent wrapBodyLines. self updateBodyFor: self currentNode ]