[MJ] - While contemplating ideas to use Tcl and [tcom] as a Getting Things Done frontend with all the data in Outlook, it occured to me that it's fairly trivial to build a basic wiki which stores the pages as Outlook notes. The code below is an initial version. A [starpack] can be downloaded from [http://mpc-soft.nl/files/owiki.exe], this starpack contains a sample css file and a file with all the outlook constants.
This version is probably not very efficient and it is missing search, which is an essential part of any wiki. But it does allow wiki syntax in your Outlook notes and displaying it in a browser with links between the notes.
Note that it requires the Redemption COM server [http://www.dimastr.com/redemption/] for bypassing outlook security pop-ups. However if you don't mind the popups, changing the store to use Application.Outlook instead is trivial. Just change:
set rdo [tcom::ref createobject "Redemption.RDOSession"]
$rdo Logon
set notesFolder [$rdo GetDefaultFolder $c(OlFolderNotes)]
to
set rdo [tcom::ref createobject "Outlook.Application"]
set notesFolder [[$rdo GetNamespace "MAPI] GetDefaultFolder $c(OlFolderNotes)]
Current todos are:
* Adding search, probably naive search at first (will be slow) and later maybe use outlook support for search folders to speed it up.
* More extensive formatting
* Efficiency improvements (caching?)
* Clean up web server part or use some existing server ([Wub]?)
* Add error checking :-)
* Add Unicode support
* Possibly change wiki-links to CamelCase because that's easier to type on a PDA
Code for the application:
package require tcom
package require snit
set appdir [pwd]
proc decodeUrl data {
regsub -all {\+} $data { } data
regsub -all {([][$\\])} $data {\\\1} data
regsub -all {%([0-9a-fA-F][0-9a-fA-F])} $data {[format %c 0x\1]} data
set data [subst $data]
}
snit::type WikiPage {
option -id
option -title
option -body
option -lastupdate
variable header
variable footer
variable raw
constructor args {
$self configurelist $args
set header [list "'[$self cget -title]"]
lappend header "\[Homepage | /\] |"
lappend header "\[Today | [clock format [clock seconds] -format {/view/Logbook_%Y_%m_%d}]\]"
lappend header "----"
set footer [list ----]
lappend footer "\[Edit | /edit/[$self cget -title]\]"
lappend footer "
"
}
method id {} {
$self cget -id
}
method asHTML {} {
set result {}
set raw [list {*}$header {*}[$self cget -body] {*}$footer]
set inUL 0
foreach line $raw {
set line [regsub -all {\[(.*?) \| (.*?)\]} $line {\1}]
set line [regsub -all {\[(.*?)\]} $line {\1}]
if {[string trimright $line] eq "----"} {
set line ""
}
if {[regexp {^('+)(.*)$} $line -> ticks text]} {
set numTicks [string length $ticks]
set line "$text"
}
if {$line eq {}} {
set line "
"
}
if {[string index $line 0] eq {*}} {
if {!$inUL} {
lappend result
set inUL 1
}
set line
[string range $line 1 end]
} elseif {$inUL} {
lappend result
set inUL 0
}
lappend result $line
}
join $result \r\n
}
}
#=========================================
snit::type OutlookStore {
# Outlook by default creates notes where the Body has the Subject
# as first line unless the title is not unique.
# Hence the Subject field is used to identify the page
typevariable c
typevariable rdo
typevariable notesFolder
typeconstructor {
set constantsFile [open [file join $::appdir constants.txt]]
foreach line [split [read $constantsFile] \n] {
set c([lindex $line 0]) [lindex $line 1]
}
close $constantsFile
set rdo [tcom::ref createobject "Redemption.RDOSession"]
$rdo Logon
set notesFolder [$rdo GetDefaultFolder $c(OlFolderNotes)]
}
method getPage {title} {
tcom::foreach note [$notesFolder Items] {
if {$title eq [string trim [$note Subject]]} {
return [list [lrange [split [string map [list \r {}] [$note Body]] \n] 1 end] $note]
}
}
return "{} 0"
}
method savePage {title body} {
lassign [$self getPage $title] _ id
if {$id != 0} {
$id Body $title\r\n$body
} else {
set id [[$notesFolder Items] Add]
$id Subject $title
$id Body $title\r\n$body
}
$id Save
}
}
#=========================================
snit::type Server {
option -port 5151
option -store
variable socket
variable css
constructor args {
$self configurelist $args
set cssFile [open [file join $::appdir wiki.css]]
set css [read $cssFile]
close $cssFile
set socket [socket -server [list $self handle] [$self cget -port]]
}
method handle {chan add port} {
fconfigure $chan -translation crlf -buffering line
set request [string trimleft [lindex [gets $chan] 1] /]
lassign [split $request /] action page
set page [decodeUrl $page]
if {$action eq {}} {
set action view
set page HomePage
}
puts "Action: $action for page: $page"
switch $action {
view {
puts $chan "HTTP/1.0 200 OK"
puts $chan "Content-Type: text/html"
puts $chan ""
puts $chan [$self viewPage $page]
}
edit {
puts $chan "HTTP/1.0 200 OK"
puts $chan "Content-Type: text/html"
puts $chan ""
lassign [[$self cget -store] getPage $page] content id
puts $content
set content [join $content \n]
puts $chan "Edit $page"
puts $chan "
Edit $page
"
puts $chan " "
}
save {
# assume save
while {[gets $chan line]!=-1} {
lassign [split $line :] key val
puts "$key: $val"
if {$key eq "Content-Length"} {
set length [string trim $val]
incr length
break
}
}
set request [read $chan $length]
set data [join [lrange [split $request =] 1 end] =]
set data [decodeUrl $data]
set store [$self cget -store]
$store savePage $page $data
# redirect to page
puts $chan "HTTP/1.1 303 See other"
puts $chan "Location: /view/$page"
puts $chan ""
}
css {
puts $chan "HTTP/1.0 200 OK"
puts $chan "Content-Type: text/html"
puts $chan ""
puts $chan $css
}
default {
puts $chan "HTTP/1.0 200 OK"
puts $chan "Content-Type: text/html"
puts $chan ""
puts $chan "
Unsupported request: $request
"
puts stderr "Unsupported request: $request"
}
}
close $chan
}
method viewPage page {
set store [$self cget -store]
lassign [$store getPage $page] body id
set view [WikiPage %AUTO% -title $page -body $body -id $id]
set body [$view asHTML]
$view destroy
set result "
$page
$body
"
}
}
OutlookStore store
Server %AUTO% -store store
vwait forever
----
!!!!!!
%| enter categories here |%
!!!!!!