service { html MsgDoc =

<[title]>

<[msg]>
; html EnterEmailDoc = Webboard: enter email

Enter Your Email

Please enter your email address:
Email:
Continue ; html KeyCheckDoc = Webboard: key check

WebBoard Key Verification

A (random) key has been sent to you by email. Please enter it below:
Key:
; html GuestLoginDoc = Webboard: guest login

Guest Login

You are logged in as a `guest' user. Restrictions apply.
Continue ; html RegisterDoc = Webboard: register

<bigwig> WebBoard Register

Login:
Password:
Name:
Email*:
*) As a security you will receive a (random) key via email.

Register
Cancel Quit ; html LoginDoc = Webboard Login

<bigwig> WebBoard Login

Guest
Login:
Password:

Login Register
Forgot Password Quit

; html PreferenceDoc = Webboard: preferences

Preferences

Identity:

Login:      Name: <[name]>
Password:      Email: <[email]>

Notification:

Send me new postings by email:
    Never.
    Only replies to my postings.
    Always.

Filter:

Enable filter:
    Filter messages older than days.
    Filter messages before (last logout): <[logout_time]>
 
Enable grayout:

Appearance:

Thread view:
Newest postings at the: top   /   bottom
Display time using am/pm:
Local timezone (GMT offset): GMT +
Reply message indent level:
Continue ; html ShowBoardDoc = Webboard: show board

<bigwig> WebBoard

Post New Message Refresh Preferences Search Quit
<[msgs]>

Number of messages: <[no_msgs]> (<[total_no_msgs]>).

Post New Message Refresh Preferences Search Quit

; html MainDoc = <[replies]> ; html MinusDoc = ; html PlusDoc = ; html CollapseDoc = <[collapse_symbol]> ; html NoIndentDoc = <[indent]>; html IndentDoc =    <[indent]>; html ReadSubjectDoc = <[subject]>; html UnReadSubjectDoc = <[subject]>; html GrayoutMessageDoc = <[indent]><[collapse]>"<[subject]>" - <[name]> (<[pretty_time]>)
<[replies]> ; html MessageDoc = <[indent]><[collapse]>"<[subject]>" - <[name]> (<[pretty_time]>)
<[replies]> ; html NewFirstDoc = <[reply]> <[replies]> ; html NewLastDoc = <[replies]> <[reply]> ; html ReplyDoc = Webboard: reply

Enter Reply

Submit Cancel
Subject:

Message:

Wrap lines: ( yes / no ) ?


Submit Cancel

; html NavBarDoc =

Local Navigation:

<[navbar]>
; html ReadMessageDoc = Webboard: read message

<[subject]>

Reply Back
<[content]>

/<[name]>

<[pretty_time]>

Reply Back

<[navbar]> ; html SearchDoc = Webboard: search

<[title]>


Please enter your substring regexp query:
Query:
Regexp Syntax:
cthe character 'c'
.any character
[a-z]any character n the range from 'a' to 'z'
RSconcatenation of R and S
R*zero-or-more repetitions of R
R+one-or-more repetitions of R
R\{m,n\}m to n repetitions of R

See man regexp for more details.

Search Back

; html BrowseSearchDoc = Webboard: browse search

Search Results

Exit Search Mode
<[msgs]>

Number of search results: <[no_msgs]>.

Exit Search Mode ; html GoodbyeInfoDoc = You read <[no_read]> message<[no_read_s]> and wrote <[no_write]>.
Number of times you have logged in: <[no_user_login]>.
; html GoodbyeDoc = Webboard: goodbye

Goodbye <[name]>.

Thank you for using the WebBoard.

<[info]> Total number of logins to the WebBoard: <[no_login]>.

; string WEBBOARD_NAME = ""; string SENDER_EMAIL = "bigwig@brics.dk"; int SERVER_GMTOFFSET = +1; int DEFAULT_INDENT = 3; int WRAP = 72; string QUOTE = "> "; tuple User DEFAULT_USER = tuple { login="", password="", name="", email="", no_login=0, indent=DEFAULT_INDENT, ampm=false, notify='n', gmtoffset=SERVER_GMTOFFSET, filter=true, grayout=true, custom_filter=false, logout_time=notime, filter_days=7, navbar=true, new_first=false, thread=true }; schema Message { int id, reply_to; string name, email, subject, content; time time_stamp; } schema User { string login, password, name, email; int no_login, indent, gmtoffset, filter_days; bool ampm, filter, grayout, custom_filter, navbar, new_first, thread; char notify; time logout_time; } schema MsgPrefs { int msg_id; string user_login; } protected shared int no_login ; protected shared relation tuple Message board ; protected shared int next_id ; protected shared relation tuple User users ; protected shared relation tuple MsgPrefs collapse ; protected shared relation tuple MsgPrefs has_read ; format Re = concat("[Re: ", star(anychar), "]"); format Digit = range('0', '9'); format Number = plus ( Digit ); format Alpha = union(range('a', 'z'), range('A', 'Z')); format Word = plus ( union(Alpha, Digit, "-", "_") ); format Email = concat(Word, "@", Word, star(concat(".", Word))); format GmtOffset = relax(-12, 12); format Line = concat([line = star(charcomplement ( '\n' ))], "\n", [rest = star(anychar)]); tuple User user; int no_read, no_write; session Notify(int new_msg_id) { relation tuple User U; tuple Message m; relation tuple Message B; string content; if (new_msg_id != 0) { flash Notifying users of new WebBoard message...; reader (board) B = board; m = getMessage(B, new_msg_id); content = "This is an autogenerated message.\nA new message has arrived to the " + WEBBOARD_NAME + " WebBoard:\n\n==================================================\nSUBJECT: " + m.subject + "\nAUTHOR: " + m.name + " (" + m.email + ")\nTIME: " + prettyTime(m.time_stamp, false) + "\n--------------------------------------------------\n" + m.content + "\n==================================================\n\nThe " + WEBBOARD_NAME + " WebBoard: \n" + url Use(); reader (users) U = users; factor(U; notify, email) { if (#.notify == 'y' || #.notify == ' ' && getFather(B, new_msg_id).email == #.email) { sendemail ( #.email , SENDER_EMAIL , "New " + WEBBOARD_NAME + " WebBoard Message" , content ) /* empty */; } }; } } time client2serverTime(time t) { if (t != notime) return setHour ( t , getHour(t) - (user.gmtoffset - SERVER_GMTOFFSET) ); return t; } time server2clientTime(time t) { if (t != notime) return setHour ( t , getHour(t) + user.gmtoffset - SERVER_GMTOFFSET ); return t; } string prettyMonth(time t) { return vector { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }[(getMonth(t) - 1)]; } string twoDigits(int n) { if (n < 10) return "0" + n; else return (string)n; } string prettyTime(time t, bool ampm) { string date; if (t == notime) return "notime"; t = server2clientTime(t); date = prettyMonth(t) + " " + getDay(t) + ", " + getYear(t) + " @ "; if (ampm) { date += (getHour(t) + 11) % 12 + 1 + ":" + twoDigits(getMinute(t)) + " "; if (getHour(t) < 12) date += "am"; else date += "pm"; } else { date += twoDigits(getHour(t)) + ":" + twoDigits(getMinute(t)); } return date; } tuple Message getMessage(relation tuple Message B, int id) { tuple Message m; factor(B) { if (#.id == id) m = #; }; return m; } tuple Message getFather(relation tuple Message B, int id) { tuple Message m, father; m = getMessage(B, id); factor(B) { if (#.id == m.reply_to) father = #; }; return father; } relation tuple Message getChildren(relation tuple Message B, int id) { return Select * from B where #.reply_to == id; } tuple User changePreferences(tuple User user) { html P = PreferenceDoc; P = P <[login=user.login, password=user.password, name=user.name, email=user.email, filter=user.filter ? "checked" : "", grayout=user.grayout ? "checked" : "", thread=user.thread ? "checked" : "", custom_filter_true=user.custom_filter ? "checked" : "", custom_filter_false=!user.custom_filter ? "checked" : "", filter_days=user.filter_days, logout_time=prettyTime(server2clientTime(user.logout_time), user.ampm), indent=user.indent, gmtoffset=user.gmtoffset, notify_no=user.notify == 'n' ? "checked" : "", notify_my=user.notify == ' ' ? "checked" : "", notify_yes=user.notify == 'y' ? "checked" : "", ampm=user.ampm ? "checked" : "", new_first_true=user.new_first ? "checked" : "", new_first_false=!user.new_first ? "checked" : ""]; show P receive [user.login=login, user.password=password, user.new_first=new_first, user.ampm=ampm, user.notify=notify, user.filter=filter, user.grayout=grayout, user.thread=thread, user.custom_filter=custom_filter, user.filter_days=filter_days, user.indent=indent, user.gmtoffset=gmtoffset]; return user; } void quit() { int n; html H; string name; if (user.login != "") { name = user.name; H = GoodbyeInfoDoc <[no_read=no_read, no_read_s=no_read != 1 ? "s" : "", no_write=no_write, no_user_login=user.no_login]; } else { name = "guest"; } reader (no_login) n = no_login; exit GoodbyeDoc <[name=name, no_login=n, info=H]; } bool filter(time msg_time) { time filter_time; if (user.custom_filter) { filter_time = server2clientTime(now()); filter_time = (setHour ( filter_time , 0 )); filter_time = (setMinute ( filter_time , 0 )); filter_time = (setSecond ( filter_time , 0 )); filter_time = client2serverTime(filter_time); filter_time = (setDay ( filter_time , getDay(filter_time) - user.filter_days )); } else { filter_time = user.logout_time; } return user.filter && msg_time <= filter_time; } html renderMessage(int indent, tuple Message message, relation tuple MsgPrefs Read, html CollapseImgDoc) { int i; html H = MainDoc; html I = NoIndentDoc; html SubjectDoc = UnReadSubjectDoc; factor(Read) { if (message.id == #.msg_id) SubjectDoc = ReadSubjectDoc; }; for (i = 0 ; i < indent ; i++) I =<[indent=IndentDoc]; if (user.grayout || !filter(message.time_stamp)) { if (user.grayout && filter(message.time_stamp)) { H = GrayoutMessageDoc; } else { H = MessageDoc; } H =<[indent=I, id=message.id, subject=SubjectDoc <[subject=message.subject], name=message.name, pretty_time=prettyTime(message.time_stamp, user.ampm), collapse=CollapseDoc <[collapse_symbol=CollapseImgDoc, collapse_id=-message.id]]; } return H; } html getCollapseDoc(int id, relation tuple MsgPrefs Collapse) { html H = MinusDoc; factor(Collapse) { if (id == #.msg_id) H = PlusDoc; }; return H; } html makeBoardRec(int indent, int id, relation tuple Message B, relation tuple MsgPrefs Collapse, relation tuple MsgPrefs Read) { int i; html H; tuple Message message; vector tuple Message children; if (id != 0) { message = getMessage(B, id); H = renderMessage(indent, message, Read, getCollapseDoc(message.id, Collapse)); } else { H = MainDoc; } children = sort((vector tuple Message)getChildren(B, id); id); for (i = 0 ; i < |children| ; i++) { html M; if (user.new_first) M = NewLastDoc; else M = NewFirstDoc; H =<[replies=M <[reply=makeBoardRec(indent + user.indent, children[i].id, B, Collapse, Read)]]; } return H <[replies=""]; } html makeBoardList(int indent, relation tuple Message B, relation tuple MsgPrefs Collapse, relation tuple MsgPrefs Read) { html H = MainDoc; vector tuple Message V = sort((vector tuple Message)B; id); int i; if (user.new_first) { for (i = |V| - 1 ; i >= 0 ; i--) { H =<[replies=renderMessage(indent, V[i], Read, getCollapseDoc(V[i].id, Collapse))]; } } else { for (i = 0 ; i < |V| ; i++) { H =<[replies=renderMessage(indent, V[i], Read, getCollapseDoc(V[i].id, Collapse))]; } } return H; } relation tuple Message collapseFilter(relation tuple Message B, relation tuple MsgPrefs Collapse) { relation tuple MsgPrefs C; if (|Collapse| == 0) return B; B = factor(B, rename in Collapse from msg_id to reply_to) { if (|@2| == 0) { return cart(@1, relation { # }); } else { C = (Union (C, factor(@1) { return tuple { msg_id=#.id, user_login="" }; })); } }; return collapseFilter(B, C); } html makeBoard() { html x; int total; relation tuple Message B; relation tuple MsgPrefs Collapse, Read; reader (board) B = board; total = |B|; if (user.login != "") { reader (collapse) { Collapse = (Select * from collapse where #.user_login == user.login); } reader (has_read) { Read = (Select * from has_read where #.user_login == user.login); } } B = collapseFilter(B, Collapse); if (user.thread) { x = makeBoardRec(0, 0, B, Collapse, Read); } else { x = makeBoardList(DEFAULT_INDENT, B, Collapse, Read); } return ShowBoardDoc <[no_msgs=|B|, total_no_msgs=total, msgs=x]; } session Use() { string action; void updateUser() { writer (users) { users = (Union (Select * from users where #.login != user.login, relation { user })); } } void register(string login, string password, string name, string email) { int ran; int key = -1; relation tuple User U; vector tuple User V; string action; show RegisterDoc <[login=login, password=password, name=name, email=email] receive [ login=login, password=password, name=name, email=email, action=continue]; switch (action) { case "REGISTER": reader (users) U = users; V = (vector tuple User)(Select * from U where #.login == login); if (|V| > 0) { show MsgDoc <[title="Login Already Taken", msg="Please choose another..."]; register("", password, name, email); } else { user = DEFAULT_USER << tuple { login=login, password=password, name=name, email=email }; ran = random(1000000); sendemail ( email , SENDER_EMAIL , "WebBoard Security Key" , "This is an autogenerated message.\nYour WebBoard security key is: " + ran + "\n\nClick here to continue: \n" + url + "\n\n/" + WEBBOARD_NAME + " WebBoard" ) /* empty */; while (key != ran) { show KeyCheckDoc receive [key=key]; } user = changePreferences(user); updateUser(); } break; case "CANCEL": doLogin(login, password); break; case "QUIT": quit(); break; } } void doLogin(string login, string password) { relation tuple User U; vector tuple User V; string action; show LoginDoc <[login=login, password=password] receive [ action=continue, login=login, password=password]; switch (action) { case "GUEST": user = DEFAULT_USER; show GuestLoginDoc; break; case "REGISTER": reader (users) U = users; V = (vector tuple User)(Select * from U where #.login == login); if (|V| > 0) { show MsgDoc <[title="Login Already Taken", msg="Please choose another..."]; doLogin(login, password); } else { register(login, password, "", ""); } break; case "LOGIN": reader (users) U = users; V = (vector tuple User)(Select * from U where #.login == login); if (|V| > 0 && V[0].password == password) { user = V[0]; user.no_login++; updateUser(); } else { show MsgDoc <[title="Login Incorrect", msg="Login or password incorrect. Please try again..."]; doLogin(login, password); } break; case "FORGOT": sendPassword(); doLogin("", ""); break; case "QUIT": quit(); break; } } string addReply(string s) { if (match (s, Re) []) return s; else return "[Re: " + s + "]"; } int lineCut(string s) { int i; for (i = WRAP - 1 ; i > 0 ; i--) if (s[i] == ' ') return i + 1; return WRAP; } string copyQuotes(string s) { if (|s| >= |QUOTE| && s[0..|QUOTE|] == QUOTE) return QUOTE + copyQuotes(s[|QUOTE|..|s|]); return ""; } string lineWrap(string rest) { string x, line; rest += "\n"; while (match (rest, Line) [line=line, rest=rest]) { while (|line| > WRAP) { string new; int cut; cut = lineCut(line); new = line[0..cut]; x += new + "\n"; line = copyQuotes(new) + line[cut..|line|]; } x += line + "\n"; } return x + rest; } string quote(string rest) { string x, line; while (match (rest, Line) [line=line, rest=rest]) { x += QUOTE + line + "\n"; } return x + QUOTE + rest; } void replyMessage(int reply_to) { int new_id; bool submit, linewrap; relation tuple Message B; tuple Message New, Old; string subject, content; if (user.login == "") { doLogin("", ""); if (user.login == "") return; } if (reply_to != 0) { reader (board) B = board; Old = getMessage(B, reply_to); subject = addReply(Old.subject); content = quote(Old.content); } show ReplyDoc <[cols=WRAP, subject=subject, content=content] receive [subject=subject, content=content, linewrap=linewrap, submit=continue]; if (submit) { if (subject == "") subject = "no subject"; if (linewrap) content = lineWrap(content); writer (next_id) new_id = ++next_id; New = tuple { id=new_id, reply_to=reply_to, name=user.name, email=user.email, subject=subject, content=content, time_stamp=now() }; no_write++; writer (board) board = (Union (board, relation { New })); writer (has_read) has_read = (Union (has_read, relation { tuple { user_login=user.login, msg_id=new_id } })); get(url Notify(New.id)) []; } } void readMessage(tuple Message message) { string action; relation tuple Message B; html N; no_read++; if (user.login != "") { writer (has_read) { has_read = (Union (has_read, relation { tuple { msg_id=message.id, user_login=user.login } })); } } if (user.navbar) { int father_id; tuple Message father; relation tuple Message B, children; relation tuple MsgPrefs C, R; reader (board) B = board; father_id = message.reply_to; father = getMessage(B, father_id); children = getChildren(B, message.id); B = (Union (relation { message }, children)); if (father.id != 0) B = (Union (B, relation { father })); if (user.login != "") { reader (collapse) { C = (Select * from collapse where #.user_login == user.login); } reader (has_read) { R = (Select * from has_read where #.user_login == user.login); } } N = NavBarDoc <[navbar=makeBoardRec(user.indent, father.id, B, C, R)]; } show ReadMessageDoc <[name=message.name, subject=message.subject, content=message.content, pretty_time=prettyTime(message.time_stamp, user.ampm), navbar=N] receive [action=continue]; if (match (action, Number) []) { reader (board) B = board; readMessage(getMessage(B, (int)action)); } else { if (action == "REPLY") replyMessage(message.id); } } void sendPassword() { int i; string email; relation tuple User U; vector tuple User V; show EnterEmailDoc receive [email=email]; reader (users) U = users; V = (vector tuple User)(Select * from U where #.email == email); if (|V| > 0) { for (i = 0 ; i < |V| ; i++) { sendemail ( email , SENDER_EMAIL , WEBBOARD_NAME + " WebBoard password" , "This is an autogenerated message.\nHere is your password for the " + WEBBOARD_NAME + " WebBoard:\n\nLOGIN: " + V[i].login + "\nPASSWORD: " + V[i].password + "\n\nThe " + WEBBOARD_NAME + " WebBoard: \n" + url Use() ) /* empty */; } show MsgDoc <[title="Password Sent", msg="Your login and password have been sent to you by email."]; } else { show MsgDoc <[title="No Such User", msg="Sorry, you are not listed as a user."]; } } void search(string title) { string query, action; relation tuple Message B; relation tuple MsgPrefs Read; show SearchDoc <[title=title] receive [query=query, action=continue]; if (action == "SEARCH" && query != "") { reader (board) B = board; B = factor(B) { if (match (#.content, query) []) return #; }; if (|B| != 0) { html H; reader (has_read) Read = has_read; H = makeBoardList(DEFAULT_INDENT, B, relation { }, Read); H = BrowseSearchDoc <[no_msgs=|B|, msgs=H]; repeat { show H receive [action=continue]; if (action != "BACK") handleRead((int)action); } until ( action == "BACK" ) ; } else { search("No Results Found"); } } } void invertMsgPrefs(int msg_id, string user_login) { bool was_collapsed; relation tuple MsgPrefs C; tuple MsgPrefs t = tuple { msg_id=msg_id, user_login=user_login }; writer (collapse) { C = factor(collapse) { if (# == t) was_collapsed = true; else return #; }; if (was_collapsed) collapse = C; else collapse = (Union (C, relation { t })); } } void handleRead(int msg_id) { if (msg_id > 0) { relation tuple Message B; reader (board) B = board; readMessage(getMessage(B, msg_id)); } else if (msg_id < 0) { if (user.login != "") { invertMsgPrefs(-msg_id, user.login); } else { doLogin("", ""); } } } doLogin("", ""); writer (no_login) no_login++; while (action != "QUIT") { show makeBoard() receive [action=continue]; switch (action) { case "NEW": replyMessage(0); break; case "PREFS": if (user.login == "") { doLogin("", ""); } else { user = changePreferences(user); updateUser(); } break; case "SEARCH": search("Search"); break; default: handleRead((int)action); break; } } if (user.login != "") { user.logout_time = now(); updateUser(); } quit(); } html AdminLoginDoc = Webboard: admin login

<bigwig> WebBoard Administration Login

Please enter the administration password:
Password:
Login Change Password ; html UpdatePasswordDoc = Webboard: admin update password

Update Password

Please enter your new password (twice):
Password:
(Again):
Update Cancel ; html UserDoc = <[login]> <[name]> <[email]> <[notify]> <[filter]> <[grayout]> <[filter_days]> <[custom_filter]> <[logout_time]> <[navbar]> <[indent]> <[ampm]> <[gmtoffset]> <[no_login]> <[users]> ; html DeleteUserDoc = Webboard: admin delete user

<bigwig> WebBoard Users

Delete Back
<[users]>
  Identity   Filter Misc.  
Delete? Login Name Email notify filter grayout days custom last navbar indent ampm gmt #Logins

Total number of users: <[no_users]>

Delete Back

; html AdminShowBoardDoc = Webboard: admin show board

<bigwig> WebBoard Administration

Show Users Quit
<[]>MESSAGES:
<[msgs]>

Total number of messages: <[no_msgs]>


Cut'n'Paste Buffer

Cut: Click on a message to move it (along with all its replies) to the cut'n'paste buffer.
Paste: Click on a message to move the cut'n'paste buffer onto the board as a reply to it.
<[cutpaste]>

Total number of messages: <[no_cutpaste_msgs]>

Empty Buffer




Show Users Quit ; html AdminByeByeDoc = Webboard: admin goodbye

Goodbye Administrator.

Thank you for using the WebBoard.

You made <[no_cut]> cut<[cut_s]> and <[no_paste]> paste<[paste_s]>.

; session Administrate() { protected shared string password ; int id, no_cut, no_paste; string action; bool cut; void updateAdminPassword() { bool done; string pass1, pass2; while (!done) { show UpdatePasswordDoc receive [pass1=pass1, pass2=pass2, action=continue]; if (action == "CANCEL") { done = true; show MsgDoc <[title="Password Update Cancelled", msg="Password not updated"]; } else { if (pass1 != pass2) { show MsgDoc <[title="Passwords Not Equal", msg="The two passwords are not equal as they should be"]; } else { done = true; writer (password) password = pass1; show MsgDoc <[title="Password Updated", msg="Your password has been updated"]; } } } } void adminLogin() { bool done; string pass, passwd; reader (password) passwd = password; if (passwd == "") updateAdminPassword(); while (pass != passwd) { show AdminLoginDoc receive [pass=pass, action=continue]; if (pass != passwd) show MsgDoc <[title="Incorrect Password", msg="Please try again..."]; } if (action == "CHANGE") { updateAdminPassword(); } } void showUsers() { int i; html H; string action; vector string del; relation tuple User U; vector tuple User V; relation tuple MsgPrefs M; while (action != "BACK") { H = DeleteUserDoc; reader (users) V = sort((vector tuple User)users; login); for (i = 0 ; i < |V| ; i++) H = H <[users=UserDoc <[login_id=V[i].login, login=V[i].login, name=V[i].name, mailto="mailto:" + V[i].email, email=V[i].email, notify=V[i].notify, filter=V[i].filter, grayout=V[i].grayout, filter_days=V[i].filter_days, custom_filter=V[i].custom_filter, logout_time=V[i].logout_time, navbar=V[i].navbar, indent=V[i].indent, ampm=V[i].ampm, gmtoffset=V[i].gmtoffset, no_login=V[i].no_login]]; show H <[no_users=|V|] receive [del=del, action=continue]; if (action == "DELETE") { writer (users) { U = users; for (i = 0 ; i < |del| ; i++) { U = (Select * from U where #.login != del[i]); } users = U; } writer (collapse) { M = collapse; for (i = 0 ; i < |del| ; i++) { M = (Select * from M where #.user_login != del[i]); } collapse = M; } writer (has_read) { M = has_read; for (i = 0 ; i < |del| ; i++) { M = (Select * from M where #.user_login != del[i]); } has_read = M; } } } } shared relation tuple Message cutpaste, source, target; shared int cutpaste_id; void moveMessage(int id) { int i; tuple Message message; vector tuple Message children; target = (Union (target, Select * from source where #.id == id)); source = (Select * from source where #.id != id); children = (vector tuple Message)getChildren(source, id); for (i = 0 ; i < |children| ; i++) { moveMessage(children[i].id); } } void cutMessage(int id) { source = board; target = relation { }; moveMessage(id); board = source; cutpaste = factor(target) { if (#.id == id) return # << tuple { reply_to=0 }; else return #; }; cutpaste_id = id; } void pasteMessage(int id) { source = factor(cutpaste) { if (#.id == cutpaste_id) return # << tuple { reply_to=id }; else return #; }; target = board; moveMessage(cutpaste_id); board = target; cutpaste = relation { }; cutpaste_id = 0; } html makeAdminBoard() { relation tuple Message B, C; relation tuple MsgPrefs empty; reader (board) { B = board; C = cutpaste; } return AdminShowBoardDoc <[no_msgs=|B|, msgs=makeBoardRec(0, 0, B, empty, empty), no_cutpaste_msgs=|C|, cutpaste=makeBoardRec(0, 0, C, empty, empty)]; } adminLogin(); user.indent = DEFAULT_INDENT; while (action != "QUIT") { show makeAdminBoard() receive [action=continue, cut=cut]; switch (action) { case "EMPTY": writer (board) { cutpaste = relation { }; cutpaste_id = 0; } break; case "USERS": showUsers(); break; default: if (match (action, Number) []) { tuple Message m; id = (int)action; reader (board) m = getMessage(board, id); if (id == m.id) { if (cut) { writer (board) cutMessage(id); no_cut++; } else { writer (board) pasteMessage(id); no_paste++; } } } break; } } exit AdminByeByeDoc <[no_cut=no_cut, no_paste=no_paste]; } }