'From Squeak2.7 of 5 January 2000 [latest update: #1762] on 9 January 2000 at 7:13:19 pm'! "Change Set: FsBrowser Date: 9 January 2000 Author: Stefan Matthias Aust (sma@3plus4.de) FsDigest can parse and view Fading Suns mailing list digests as found on the their web page. However, it's also a simple outline editor which might be useful for other uses. FsDigest methods open - opens the browser window addArticle: - appends a FsArticle object to the brower's root object FsDigest class methods parseDigest: - reads in an HTML file and creates a FsArticle object "! ListItemWrapper subclass: #FsArticle instanceVariableNames: 'subject author text children ' classVariableNames: '' poolDictionaries: '' category: 'Sma-FSML'! AbstractHierarchicalList subclass: #FsDigest instanceVariableNames: 'root ' classVariableNames: '' poolDictionaries: '' category: 'Sma-FSML'! !FsArticle reorganize! ('accessing' author author: children subject subject: text text:) ('adding-removing' addArticle: addArticle:named: deleteIn:) ('morphic' asString contents hasContents hasEquivalentIn:) ('printing' printOn:) ! !FsArticle methodsFor: 'accessing' stamp: 'sma 1/9/2000 11:03'! author ^ author! ! !FsArticle methodsFor: 'accessing' stamp: 'sma 1/9/2000 11:03'! author: aString author _ aString! ! !FsArticle methodsFor: 'accessing' stamp: 'sma 1/9/2000 17:48'! children ^ children! ! !FsArticle methodsFor: 'accessing' stamp: 'sma 1/9/2000 11:03'! subject ^ subject! ! !FsArticle methodsFor: 'accessing' stamp: 'sma 1/9/2000 15:33'! subject: aString subject _ aString! ! !FsArticle methodsFor: 'accessing' stamp: 'sma 1/9/2000 11:03'! text ^ text! ! !FsArticle methodsFor: 'accessing' stamp: 'sma 1/9/2000 11:03'! text: aString text _ aString! ! !FsArticle methodsFor: 'adding-removing' stamp: 'sma 1/9/2000 18:38'! addArticle: a children ifNil: [children _ OrderedCollection with: a] ifNotNil: [children addLast: a]! ! !FsArticle methodsFor: 'adding-removing' stamp: 'sma 1/9/2000 17:05'! addArticle: a named: s "Private. Add another article to either directly the receiver or to one of the receiver's children, depending on the subject to reconstruct threading." | prefix child subj | subj _ s. prefix _ (subj copyFrom: 1 to: (3 min: subj size)) asLowercase. (prefix = 're:' or: [prefix = 'aw:']) ifTrue: [subj _ (subj copyFrom: 4 to: subj size) withBlanksTrimmed] ifFalse: [prefix _ '']. children ifNil: [children _ OrderedCollection new]. child _ children detect: [:each | each subject sameAs: subj] ifNone: [prefix isEmpty ifFalse: [children addLast: (FsArticle new author: ''; subject: subj; text: '(ongoing discussion)')]]. child ifNil: [^ children addLast: a]. child addArticle: a named: subj! ! !FsArticle methodsFor: 'adding-removing' stamp: 'sma 1/9/2000 18:29'! deleteIn: parent parent children ifNil: [^ false]. parent children remove: self ifAbsent: [parent children ifNil: [^ false]. parent children copy do: [:each | (self deleteIn: each) ifTrue: [^ true]]. ^ false]. ^ true! ! !FsArticle methodsFor: 'morphic' stamp: 'sma 1/9/2000 17:01'! asString "Display string in hierarchical list." ^ subject! ! !FsArticle methodsFor: 'morphic' stamp: 'sma 1/9/2000 17:02'! contents "Answer the receiver's children." ^ children! ! !FsArticle methodsFor: 'morphic' stamp: 'sma 1/9/2000 17:02'! hasContents "Answer true, if the receiver has children." ^ children notNil! ! !FsArticle methodsFor: 'morphic' stamp: 'sma 1/9/2000 18:27'! hasEquivalentIn: aCollection "I'm my own wrapper, so this method must be overwritten" ^ aCollection includes: self! ! !FsArticle methodsFor: 'printing' stamp: 'sma 1/9/2000 11:05'! printOn: aStream aStream nextPutAll: 'From: '; nextPutAll: self author; cr; nextPutAll: 'Subject: '; nextPutAll: self subject; cr; cr; nextPutAll: self text! ! !FsDigest reorganize! ('initialization' initialize open) ('adding' addArticle: addThreadedArticle:) ('menu' deleteMail loadMails newMail renameMail saveMails) ('morphic' arrowKey:from: genericMenu: getList getText newText: noteNewSelection:) ('private' colorized:) ! !FsDigest methodsFor: 'initialization' stamp: 'sma 1/9/2000 18:59'! initialize root _ FsArticle new author: ''; subject: '[Articles]'; text: '' ! ! !FsDigest methodsFor: 'initialization' stamp: 'sma 1/9/2000 18:37'! open myBrowser _ (SystemWindow labelled: 'Fading Suns Mail Archive Browser') model: self; color: Color blue. myBrowser addMorph: ((SimpleHierarchicalListMorph on: self list: #getList selected: #getCurrentSelection changeSelected: #noteNewSelection: menu: #genericMenu:) autoDeselect: false) frame: (0 @ 0 corner: 0.3 @ 1); addMorph: (PluggableTextMorph on: self text: #getText accept: #newText:) frame: (0.3 @ 0 corner: 1 @ 1). self noteNewSelection: root. myBrowser openInWorld! ! !FsDigest methodsFor: 'adding' stamp: 'sma 1/9/2000 18:40'! addArticle: a root addArticle: a! ! !FsDigest methodsFor: 'adding' stamp: 'sma 1/9/2000 18:40'! addThreadedArticle: a root addArticle: a named: a subject! ! !FsDigest methodsFor: 'menu' stamp: 'sma 1/9/2000 18:28'! deleteMail (self getCurrentSelection deleteIn: root) ifTrue: [self update: #hierarchicalList]! ! !FsDigest methodsFor: 'menu' stamp: 'sma 1/9/2000 19:06'! loadMails | stream | (SelectionMenu confirm: 'Do you really want to replace the current archive?') ifTrue: [stream _ ReferenceStream fileNamed: 'archives'. [root _ stream next] ensure: [stream close]. self update: #hierarchicalList]! ! !FsDigest methodsFor: 'menu' stamp: 'sma 1/9/2000 18:59'! newMail | s a | s _ FillInTheBlank request: 'Please enter the subject' initialAnswer: ''. s isEmpty ifFalse: [a _ FsArticle new author: Utilities authorInitials; subject: s; text: ''. self getCurrentSelection addArticle: a. self update: #hierarchicalList]! ! !FsDigest methodsFor: 'menu' stamp: 'sma 1/9/2000 18:44'! renameMail | s | s _ FillInTheBlank request: 'Please enter the new subject' initialAnswer: self getCurrentSelection subject. s isEmpty ifFalse: [self getCurrentSelection subject: s. self update: #hierarchicalList]! ! !FsDigest methodsFor: 'menu' stamp: 'sma 1/9/2000 19:06'! saveMails | stream | stream _ ReferenceStream fileNamed: 'archives'. [stream nextPut: root] ensure: [stream close] ! ! !FsDigest methodsFor: 'morphic' stamp: 'sma 1/9/2000 17:23'! arrowKey: aChar from: view "Keyboard support for hierarchical list morph." | keyEvent oldSelection nextSelection max min howMany m | (keyEvent := aChar asciiValue) > 31 ifTrue: [^ self]. "Quick return, out of range" oldSelection := view getCurrentSelectionIndex. nextSelection := oldSelection. max := view maximumSelection. min := view minimumSelection. howMany := view numSelectionsInView. "get this exactly??" keyEvent == 31 ifTrue: ["down-arrow; move down one, wrapping to top if needed" nextSelection := oldSelection + 1. nextSelection > max ifTrue: [nextSelection _ 1]]. keyEvent == 30 ifTrue: ["up arrow; move up one, wrapping to bottom if needed" nextSelection := oldSelection - 1. nextSelection < 1 ifTrue: [nextSelection _ max]]. keyEvent == 29 ifTrue: ["right arrow; expand item if needed" m _ view scroller submorphs at: oldSelection. m isExpanded ifFalse: [m toggleExpandedState. ^ view adjustSubmorphPositions]]. keyEvent == 28 ifTrue: ["left arrow; colapse item if needed" m _ view scroller submorphs at: oldSelection. m isExpanded ifTrue: [m toggleExpandedState. ^ view adjustSubmorphPositions]]. keyEvent == 1 ifTrue: [nextSelection := 1]. "home" keyEvent == 4 ifTrue: [nextSelection := max]. "end" keyEvent == 11 ifTrue: [nextSelection := min max: (oldSelection - howMany)]. "page up" keyEvent == 12 ifTrue: [nextSelection := (oldSelection + howMany) min: max]. "page down" nextSelection = oldSelection ifFalse: [self okToChange ifTrue: [view changeModelSelection: nextSelection. "view controller moveMarker"]]! ! !FsDigest methodsFor: 'morphic' stamp: 'sma 1/9/2000 18:44'! genericMenu: menu menu add: 'save messages' target: self selector: #saveMails. menu add: 'load messages' target: self selector: #loadMails. currentSelection ifNotNil: [menu addLine. menu add: 'new...' target: self selector: #newMail. menu add: 'rename' target: self selector: #renameMail. menu add: 'delete' target: self selector: #deleteMail]. ^ menu! ! !FsDigest methodsFor: 'morphic' stamp: 'sma 1/9/2000 17:23'! getList "Called by the hierarchical list morph to get its root object." ^ Array with: root! ! !FsDigest methodsFor: 'morphic' stamp: 'sma 1/9/2000 18:36'! getText "Called by the text morph to get its text." ^ self getCurrentSelection ifNil: ['no selection'] ifNotNil: [self colorized: self getCurrentSelection text]! ! !FsDigest methodsFor: 'morphic' stamp: 'sma 1/9/2000 18:36'! newText: aText "Called by the text morph on accept." self getCurrentSelection ifNil: [^ false]. self getCurrentSelection text: aText asString. ^ true! ! !FsDigest methodsFor: 'morphic' stamp: 'sma 1/9/2000 17:28'! noteNewSelection: aMorph "Called by the hierarchical list morph if the selection has been changed." myBrowser submorphs first setText: (self colorized: aMorph text). super noteNewSelection: aMorph! ! !FsDigest methodsFor: 'private' stamp: 'sma 1/9/2000 17:36'! colorized: aString "Private - colorize quotes." | text i j quote | text _ aString asText. i _ 1. [j _ i. j <= text size] whileTrue: [quote _ '>|:}^' includes: (text at: j). [j <= text size and: [(text at: j) ~= Character cr]] whileTrue: [j := j + 1]. quote ifTrue: [text addAttribute: TextColor blue from: i to: j]. i _ j + 1]. ^ text ! ! !FsDigest class reorganize! ('instance creation' new) ('utility' nextLine: parseDigest: parseSubject: parseText:) ! !FsDigest class methodsFor: 'instance creation' stamp: 'sma 1/9/2000 12:03'! new ^ super new initialize! ! !FsDigest class methodsFor: 'utility' stamp: 'sma 1/9/2000 18:50'! nextLine: aStream "Private. It seems, that the digests are in unix format." ^ aStream upTo: Character lf! ! !FsDigest class methodsFor: 'utility' stamp: 'sma 1/9/2000 19:09'! parseDigest: aString "Read in a Fading Suns mailing list digest (in HTML format) and break it into single articles. Answer root article." | f as s a text | f _ ReadStream on: (FileStream oldFileNamed: aString) contentsOfEntireFile. f ifNil: [self error: 'unknown file ' , aString]. as _ FsArticle new author: 'sma'; subject: 'Digest ' , aString; text: ''. [[s _ self nextLine: f. f atEnd ifTrue: [^ as]. s beginsWith: '
and trailing dashes and CRs. Also remove that annoying quoted-printable line continuation." | start stop | start _ 1. stop _ text size. (text beginsWith: '') ifTrue: [start _ 6]. [stop > start and: [' - ' includes: (text at: stop)]] whileTrue: [stop _ stop - 1]. ^ (text copyFrom: start to: stop + 1) copyReplaceAll: '= ' with: ''! ! !IndentingListItemMorph methodsFor: 'as yet unclassified' stamp: 'sma 1/9/2000 19:00'! firstChild "See FsDigest>>arrowKey:from:" ^ firstChild! ! !SimpleHierarchicalListMorph methodsFor: 'selection' stamp: 'sma 1/9/2000 19:00'! changeModelSelection: index "See FsDigest>>arrowKey:from:" self setSelectedMorph: (index = 0 ifFalse: [self scroller submorphs at: index])! ! !SimpleHierarchicalListMorph methodsFor: 'selection' stamp: 'sma 1/9/2000 19:00'! getCurrentSelectionIndex "See FsDigest>>arrowKey:from:" ^ scroller submorphs indexOf: selectedMorph! ! !String methodsFor: 'converting' stamp: 'sma 1/9/2000 11:36'! asUnHtml "Strip out all Html stuff (commands in angle brackets <>) and convert the characters &<> back to their real value. Leave actual cr and tab as they were in text." | in out char rest did | in _ ReadStream on: self. out _ WriteStream on: (String new: self size). [in atEnd] whileFalse: [in peek = $< ifTrue: [in unCommand] "Absorb <...><...>" ifFalse: [(char _ in next) = $& ifTrue: [rest _ in upTo: $;. did _ out position. rest = 'lt' ifTrue: [out nextPut: $<]. rest = 'gt' ifTrue: [out nextPut: $>]. rest = 'amp' ifTrue: [out nextPut: $&]. did = out position ifTrue: [ in skip: rest size negated. out nextPut: $& "self error: 'new HTML char encoding'." "Please add it to this code"]] ifFalse: [out nextPut: char]]. ]. ^ out contents! !