Index: src/builtin.c ================================================================== --- src/builtin.c +++ src/builtin.c @@ -636,10 +636,13 @@ CX("isDark: %s" "/*true if the current skin has the 'white-foreground' detail*/", skin_detail_boolean("white-foreground") ? "true" : "false"); CX("}\n"/*fossil.config.skin*/); CX("};\n"/* fossil.config */); + CX("window.fossil.user = {"); + CX("name: %!j", (g.zLogin&&*g.zLogin) ? g.zLogin : "guest"); + CX("};\n"/*fossil.user*/); CX("if(fossil.config.skin.isDark) " "document.body.classList.add('fossil-dark-style');\n"); #if 0 /* Is it safe to emit the CSRF token here? Some pages add it ** as a hidden form field. */ Index: src/capabilities.c ================================================================== --- src/capabilities.c +++ src/capabilities.c @@ -304,10 +304,12 @@ "Forum-Admin", "Grant capability '4' to other users" }, { '7', CAPCLASS_ALERT, 0, "Alerts", "Sign up for email alerts" }, { 'A', CAPCLASS_ALERT|CAPCLASS_SUPER, 0, "Announce", "Send announcements to all subscribers" }, + { 'C', CAPCLASS_FORUM, 0, + "Chat", "Read and/or writes messages in the chatroom" }, { 'D', CAPCLASS_OTHER, 0, "Debug", "Enable debugging features" }, }; /* @@ -391,11 +393,11 @@ " WHERE cap GLOB '*[as]*'" " ORDER BY 3 ASC", zSelfCap, hasPubPages, zSelfCap ); @ - @ while( db_step(&q)==SQLITE_ROW ){ const char *zId = db_column_text(&q, 0); const char *zCap = db_column_text(&q, 1); int n = db_column_int(&q, 3); @@ -446,10 +448,18 @@ /* Wiki */ if( sqlite3_strglob("*[asdfklm]*",zCap)==0 ){ eType = 2; }else if( sqlite3_strglob("*j*",zCap)==0 ){ eType = 1; + }else{ + eType = 0; + } + @ + + /* Chat */ + if( sqlite3_strglob("*C*",zCap)==0 ){ + eType = 2; }else{ eType = 0; } @ ADDED src/chat.c Index: src/chat.c ================================================================== --- /dev/null +++ src/chat.c @@ -0,0 +1,415 @@ +/* +** Copyright (c) 2020 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to implement the Fossil chatroom. +** +** Initial design goals: +** +** * Keep it simple. This chatroom is not intended as a competitor +** or replacement for IRC, Discord, Telegram, Slack, etc. The goal +** is zero- or near-zero-configuration, not an abundance of features. +** +** * Intended as a place for insiders to have ephemeral conversations +** about a project. This is not a public gather place. Think +** "boardroom", not "corner pub". +** +** * One chatroom per repository. +** +** * Chat content lives in a single repository. It is never synced. +** Content expires and is deleted after a set interval (a week or so). +** +** Notification is accomplished using the "hanging GET" or "long poll" design +** in which a GET request is issued but the server does not send a reply until +** new content arrives. Newer Web Sockets and Server Sent Event protocols are +** more elegant, but are not compatible with CGI, and would thus complicate +** configuration. +*/ +#include "config.h" +#include +#include "chat.h" + +/* +** WEBPAGE: chat +** +** Start up a browser-based chat session. +*/ +void chat_webpage(void){ + login_check_credentials(); + style_set_current_feature("chat"); + if( !g.perm.Chat ){ + style_header("Chat Not Authorized"); + @

Not Authorized

+ @

You do not have permission to use the chatroom on this + @ repository.

+ style_finish_page(); + return; + } + style_header("Chat"); + @ + @
+ @
+ @
+ @ + @ + @
+ @
+ @ + @
+ @
+ @
+ @ + @
+ + /* New chat messages get inserted immediately after this element */ + @ + + builtin_fossil_js_bundle_or("popupwidget", NULL); + /* Always in-line the javascript for the chat page */ + @ + + style_finish_page(); +} + +/* Definition of repository tables used by chat +*/ +static const char zChatSchema1[] = +@ CREATE TABLE repository.chat( +@ msgid INTEGER PRIMARY KEY AUTOINCREMENT, +@ mtime JULIANDAY, -- Time for this entry - Julianday Zulu +@ xfrom TEXT, -- Login of the sender +@ xmsg TEXT, -- Raw, unformatted text of the message +@ file BLOB, -- Text of the uploaded file, or NULL +@ fname TEXT, -- Filename of the uploaded file, or NULL +@ fmime TEXT, -- MIMEType of the upload file, or NULL +@ mdel INT -- msgid of another message to delete +@ ); +; + + +/* +** Make sure the repository data tables used by chat exist. Create them +** if they do not. +*/ +static void chat_create_tables(void){ + if( !db_table_exists("repository","chat") ){ + db_multi_exec(zChatSchema1/*works-like:""*/); + }else if( !db_table_has_column("repository","chat","mdel") ){ + db_multi_exec("ALTER TABLE chat ADD COLUMN mdel INT"); + } +} + +/* +** WEBPAGE: chat-send +** +** This page receives (via XHR) a new chat-message and/or a new file +** to be entered into the chat history. +*/ +void chat_send_webpage(void){ + int nByte; + const char *zMsg; + login_check_credentials(); + if( !g.perm.Chat ) return; + chat_create_tables(); + nByte = atoi(PD("file:bytes",0)); + zMsg = PD("msg",""); + if( nByte==0 ){ + if( zMsg[0] ){ + db_multi_exec( + "INSERT INTO chat(mtime,xfrom,xmsg)" + "VALUES(julianday('now'),%Q,%Q)", + g.zLogin, zMsg + ); + } + }else{ + Stmt q; + Blob b; + db_prepare(&q, + "INSERT INTO chat(mtime, xfrom,xmsg,file,fname,fmime)" + "VALUES(julianday('now'),%Q,%Q,:file,%Q,%Q)", + g.zLogin, zMsg, PD("file:filename",""), + PD("file:mimetype","application/octet-stream")); + blob_init(&b, P("file"), nByte); + db_bind_blob(&q, ":file", &b); + db_step(&q); + db_finalize(&q); + blob_reset(&b); + } +} + +/* +** WEBPAGE: chat-poll +** +** The chat page generated by /chat using a XHR to this page in order +** to ask for new chat content. The "name" argument should begin with +** an integer which is the largest "msgid" that the chat page currently +** holds. If newer content is available, this routine returns that +** content straight away. If no new content is available, this webpage +** blocks until the new content becomes available. In this way, the +** system implements "hanging-GET" or "long-poll" style event notification. +** +** /chat-poll/N +** +** If N is negative, then the return value is the N most recent messages. +** Hence a request like /chat-poll/-100 can be used to initialize a new +** chat session to just the most recent messages. +** +** Some webservers (althttpd) do not allow a term of the URL path to +** begin with "-". Then /chat-poll/-100 cannot be used. Instead you +** have to say "/chat-poll?name=-100". +** +** The reply from this webpage is JSON that describes the new content. +** Format of the json: +** +** | { +** | "msg":[ +** | { +** | "msgid": integer // message id +** | "mtime": text // When sent: YYYY-MM-DD HH:MM:SS UTC +** | "xfrom": text // Login name of sender +** | "uclr": text // Color string associated with the user +** | "xmsg": text // HTML text of the message +** | "fsize": integer // file attachment size in bytes +** | "fname": text // Name of file attachment +** | "fmime": text // MIME-type of file attachment +** | "mdel": integer // message id of prior message to delete +** | } +** | ] +** | } +** +** The "fname" and "fmime" fields are only present if "fsize" is greater +** than zero. The "xmsg" field may be an empty string if "fsize" is zero. +** +** The "msgid" values will be in increasing order. +** +** The "mdel" will only exist if "xmsg" is an empty string and "fsize" is zero. +*/ +void chat_poll_webpage(void){ + Blob json; /* The json to be constructed and returned */ + sqlite3_int64 dataVersion; /* Data version. Used for polling. */ + int iDelay = 1000; /* Delay until next poll (milliseconds) */ + const char *zSep = "{\"msgs\":[\n"; /* List separator */ + int msgid = atoi(PD("name","0")); + Stmt q1; + login_check_credentials(); + if( !g.perm.Chat ) return; + chat_create_tables(); + cgi_set_content_type("text/json"); + dataVersion = db_int64(0, "PRAGMA data_version"); + if( msgid<0 ){ + msgid = db_int(0, + "SELECT msgid FROM chat WHERE mdel IS NOT true" + " ORDER BY msgid DESC LIMIT 1 OFFSET %d", -msgid); + } + db_prepare(&q1, + "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file)," + " fname, fmime, mdel" + " FROM chat" + " WHERE msgid>%d" + " ORDER BY msgid", + msgid + ); + blob_init(&json, 0, 0); + while(1){ + int cnt = 0; + while( db_step(&q1)==SQLITE_ROW ){ + int id = db_column_int(&q1, 0); + const char *zDate = db_column_text(&q1, 1); + const char *zFrom = db_column_text(&q1, 2); + const char *zRawMsg = db_column_text(&q1, 3); + int nByte = db_column_int(&q1, 4); + const char *zFName = db_column_text(&q1, 5); + const char *zFMime = db_column_text(&q1, 6); + int iToDel = db_column_int(&q1, 7); + char *zMsg; + cnt++; + blob_append(&json, zSep, -1); + zSep = ",\n"; + blob_appendf(&json, "{\"msgid\":%d,\"mtime\":%!j,", id, zDate); + blob_appendf(&json, "\"xfrom\":%!j,", zFrom); + blob_appendf(&json, "\"uclr\":%!j,", hash_color(zFrom)); + + /* TBD: Convert the raw message into HTML, perhaps by running it + ** through a text formatter, or putting markup on @name phrases, + ** etc. */ + zMsg = mprintf("%h", zRawMsg ? zRawMsg : ""); + blob_appendf(&json, "\"xmsg\":%!j,", zMsg); + fossil_free(zMsg); + + if( nByte==0 ){ + blob_appendf(&json, "\"fsize\":0"); + }else{ + blob_appendf(&json, "\"fsize\":%d,\"fname\":%!j,\"fmime\":%!j", + nByte, zFName, zFMime); + } + if( iToDel ){ + blob_appendf(&json, ",\"mdel\":%d}", iToDel); + }else{ + blob_append(&json, "}", 1); + } + } + db_reset(&q1); + if( cnt ){ + blob_append(&json, "\n]}", 3); + cgi_set_content(&json); + break; + } + sqlite3_sleep(iDelay); + while( 1 ){ + sqlite3_int64 newDataVers = db_int64(0,"PRAGMA repository.data_version"); + if( newDataVers!=dataVersion ){ + dataVersion = newDataVers; + break; + } + sqlite3_sleep(iDelay); + } + } /* Exit by "break" */ + db_finalize(&q1); + return; +} + +/* +** WEBPAGE: chat-download +** +** Download the CHAT.FILE attachment associated with a single chat +** entry. The "name" query parameter begins with an integer that +** identifies the particular chat message. +*/ +void chat_download_webpage(void){ + int msgid; + Blob r; + const char *zMime; + login_check_credentials(); + if( !g.perm.Chat ){ + style_header("Chat Not Authorized"); + @

Not Authorized

+ @

You do not have permission to use the chatroom on this + @ repository.

+ style_finish_page(); + return; + } + chat_create_tables(); + msgid = atoi(PD("name","0")); + blob_zero(&r); + zMime = db_text(0, "SELECT fmime FROM chat wHERE msgid=%d", msgid); + if( zMime==0 ) return; + db_blob(&r, "SELECT file FROM chat WHERE msgid=%d", msgid); + cgi_set_content_type(zMime); + cgi_set_content(&r); +} + + +/* +** WEBPAGE: chat-delete +** +** Delete the chat entry identified by the name query parameter. +** Invoking fetch("chat-delete/"+msgid) from javascript in the client +** will delete a chat entry from the CHAT table. +** +** This routine both deletes the identified chat entry and also inserts +** a new entry with the current timestamp and with: +** +** * xmsg = NULL +** * file = NULL +** * mdel = The msgid of the row that was deleted +** +** This new entry will then be propagated to all listeners so that they +** will know to delete their copies of the message too. +*/ +void chat_delete_webpage(void){ + int mdel; + char *zOwner; + login_check_credentials(); + if( !g.perm.Chat ) return; + chat_create_tables(); + mdel = atoi(PD("name","0")); + zOwner = db_text(0, "SELECT xfrom FROM chat WHERE msgid=%d", mdel); + if( zOwner==0 ) return; + if( fossil_strcmp(zOwner, g.zLogin)!=0 && !g.perm.Admin ) return; + db_multi_exec( + "BEGIN;\n" + "DELETE FROM chat WHERE msgid=%d;\n" + "INSERT INTO chat(mtime, xfrom, mdel)" + " VALUES(julianday('now'), %Q, %d);\n" + "COMMIT;", + mdel, g.zLogin, mdel + ); +} ADDED src/chat.js Index: src/chat.js ================================================================== --- /dev/null +++ src/chat.js @@ -0,0 +1,243 @@ +(function(){ + const form = document.querySelector('#chat-form'); + let mxMsg = -50; + const F = window.fossil, D = F.dom; + const _me = F.user.name; + /* State for paste and drag/drop */ + const BlobXferState = { + dropDetails: document.querySelector('#chat-drop-details'), + blob: undefined + }; + /** Updates the paste/drop zone with details of the pasted/dropped + data. */ + const updateDropZoneContent = function(blob){ + const bx = BlobXferState, dd = bx.dropDetails; + bx.blob = blob; + D.clearElement(dd); + if(!blob){ + form.file.value = ''; + return; + } + D.append(dd, "Name: ", blob.name, + D.br(), "Size: ",blob.size); + if(blob.type && blob.type.startsWith("image/")){ + const img = D.img(); + D.append(dd, D.br(), img); + const reader = new FileReader(); + reader.onload = (e)=>img.setAttribute('src', e.target.result); + reader.readAsDataURL(blob); + } + const btn = D.button("Cancel"); + D.append(dd, D.br(), btn); + btn.addEventListener('click', ()=>updateDropZoneContent(), false); + }; + form.file.addEventListener('change', function(ev){ + //console.debug("this =",this); + updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined) + }); + + form.addEventListener('submit',(e)=>{ + e.preventDefault(); + const fd = new FormData(form); + if(BlobXferState.blob/*replace file content with this*/){ + fd.set("file", BlobXferState.blob); + } + if( form.msg.value.length>0 || form.file.value.length>0 || BlobXferState.blob ){ + fetch("chat-send",{ + method: 'POST', + body: fd + }); + } + BlobXferState.blob = undefined; + D.clearElement(BlobXferState.dropDetails); + form.msg.value = ""; + form.file.value = ""; + form.msg.focus(); + }); + /* Handle image paste from clipboard. TODO: figure out how we can + paste non-image binary data as if it had been selected via the + file selection element. */ + document.onpaste = function(event){ + const items = event.clipboardData.items, + item = items[0]; + if(!item || !item.type) return; + //console.debug("pasted item =",item); + if('file'===item.kind){ + updateDropZoneContent(false/*clear prev state*/); + updateDropZoneContent(items[0].getAsFile()); + }else if('string'===item.kind){ + item.getAsString((v)=>form.msg.value = v); + } + }; + if(true){/* Add help button for drag/drop/paste zone */ + const help = D.div(); + form.file.parentNode.insertBefore(help, form.file); + F.helpButtonlets.create( + help, + "Select a file to upload, drag/drop a file into this spot, ", + "or paste an image from the clipboard if supported by ", + "your environment." + ); + } + //////////////////////////////////////////////////////////// + // File drag/drop visual notification. + const dropHighlight = form.file /* target zone */; + const dropEvents = { + drop: function(ev){ + D.removeClass(dropHighlight, 'dragover'); + }, + dragenter: function(ev){ + ev.preventDefault(); + ev.dataTransfer.dropEffect = "copy"; + D.addClass(dropHighlight, 'dragover'); + }, + dragleave: function(ev){ + D.removeClass(dropHighlight, 'dragover'); + }, + dragend: function(ev){ + D.removeClass(dropHighlight, 'dragover'); + } + }; + Object.keys(dropEvents).forEach( + (k)=>form.file.addEventListener(k, dropEvents[k], true) + ); + + /* Injects element e as a new row in the chat, at the top of the list */ + const injectMessage = function f(e){ + if(!f.injectPoint){ + f.injectPoint = document.querySelector('#message-inject-point'); + } + if(f.injectPoint.nextSibling){ + f.injectPoint.parentNode.insertBefore(e, f.injectPoint.nextSibling); + }else{ + f.injectPoint.parentNode.appendChild(e); + } + }; + /* Returns a new TEXT node with the given text content. */ + const textNode = (T)=>document.createTextNode(T); + /** Returns the local time string of Date object d, defaulting + to the current time. */ + const localTimeString = function ff(d){ + if(!ff.pad){ + ff.pad = (x)=>(''+x).length>1 ? x : '0'+x; + } + d || (d = new Date()); + return [ + d.getFullYear(),'-',ff.pad(d.getMonth()+1/*sigh*/), + '-',ff.pad(d.getDate()), + ' ',ff.pad(d.getHours()),':',ff.pad(d.getMinutes()), + ':',ff.pad(d.getSeconds()) + ].join(''); + }; + /* Returns an almost-ISO8601 form of Date object d. */ + const iso8601ish = function(d){ + return d.toISOString() + .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' GMT'); + }; + /* Event handler for clicking .message-user elements to show their + timestamps. */ + const handleLegendClicked = function f(ev){ + if(!f.popup){ + /* Timestamp popup widget */ + f.popup = new F.PopupWidget({ + cssClass: ['fossil-tooltip', 'chat-timestamp'], + refresh:function(){ + const D = F.dom; + D.clearElement(this.e); + const d = new Date(this._timestamp+"Z"); + if(d.getMinutes().toString()!=="NaN"){ + // Date works, render informative timestamps + D.append(this.e, localTimeString(d)," client-local", D.br(), + iso8601ish(d)); + }else{ + // Date doesn't work, so dumb it down... + D.append(this.e, this._timestamp," GMT"); + } + } + }); + f.popup.installClickToHide(); + } + const rect = ev.target.getBoundingClientRect(); + f.popup._timestamp = ev.target.dataset.timestamp; + let x = rect.left, y = rect.top - 10; + f.popup.show(ev.target)/*so we can get its computed size*/; + if('right'===ev.target.getAttribute('align')){ + // Shift popup to the left for right-aligned messages to avoid + // truncation off the right edge of the page. + const pRect = f.popup.e.getBoundingClientRect(); + x -= pRect.width/3*2; + } + f.popup.show(x, y); + }; + /** Callback for poll() to inject new content into the page. */ + function newcontent(jx){ + var i; + for(i=0; imxMsg ) mxMsg = m.msgid; + row.classList.add('message-row'); + injectMessage(row); + const eWho = document.createElement('legend'); + eWho.dataset.timestamp = m.mtime; + eWho.addEventListener('click', handleLegendClicked, false); + if( m.xfrom==_me && window.outerWidth<1000 ){ + eWho.setAttribute('align', 'right'); + row.style.justifyContent = "flex-end"; + }else{ + eWho.setAttribute('align', 'left'); + } + eWho.style.backgroundColor = m.uclr; + row.appendChild(eWho); + eWho.classList.add('message-user'); + let whoName = m.xfrom; + var d = new Date(m.mtime + "Z"); + if( d.getMinutes().toString()!="NaN" ){ + /* Show local time when we can compute it */ + eWho.append(textNode(whoName+' @ '+ + d.getHours()+":"+(d.getMinutes()+100).toString().slice(1,3) + )) + }else{ + /* Show UTC on systems where Date() does not work */ + eWho.append(textNode(whoName+' @ '+m.mtime.slice(11,16))) + } + let span = document.createElement("div"); + span.classList.add('message-content'); + span.style.backgroundColor = m.uclr; + row.appendChild(span); + if( m.fsize>0 ){ + if( m.fmime && m.fmime.startsWith("image/") ){ + let img = document.createElement("img"); + img.src = "chat-download/" + m.msgid; + span.appendChild(img); + }else{ + let a = document.createElement("a"); + let txt = "(" + m.fname + " " + m.fsize + " bytes)"; + a.href = window.fossil.rootPath+ + 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname); + // ^^^ add m.fname to URL to cause downloaded file to have that name. + a.appendChild(textNode(txt)); + span.appendChild(a); + } + let br = document.createElement("br"); + br.style.clear = "both"; + span.appendChild(br); + } + if(m.xmsg){ + span.innerHTML += m.xmsg; + } + span.classList.add('chat-message'); + } + } + async function poll(){ + if(poll.running) return; + poll.running = true; + fetch("chat-poll?name=" + mxMsg) + .then(x=>x.json()) + .then(y=>newcontent(y)) + .catch(e=>console.error(e)) + .finally(()=>poll.running=false) + } + poll(); + setInterval(poll, 1000); +})(); Index: src/default.css ================================================================== --- src/default.css +++ src/default.css @@ -1461,5 +1461,43 @@ position: absolute !important; opacity: 0 !important; pointer-events: none !important; display: none !important; } + +/* Chat-related */ +span.at-name { /* for @USERNAME references */ + text-decoration: underline; + font-weight: bold; +} +/* A wrapper for a single single message (one row of the UI) */ +.message-row { + margin-bottom: 0.5em; + border: none; + display: flex; + flex-direction: row; + justify-content: flex-start; + /*border: 1px solid rgba(0,0,0,0.2); + border-radius: 0.25em; + box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29);*/ + border: none; +} +/* The content area of a message (the body element of a FIELDSET) */ +.message-content { + display: inline-block; + border-radius: 0.25em; + border: 1px solid rgba(0,0,0,0.2); + box-shadow: 0.2em 0.2em 0.2em rgba(0, 0, 0, 0.29); + padding: 0.25em 1em; + margin-top: -0.75em; + min-width: 9em /*avoid unsightly "underlap" with the user name label*/; +} +/* User name for the post (a LEGEND element) */ +.message-row .message-user { + border-radius: 0.25em 0.25em 0 0; + padding: 0 0.5em; + /*text-align: left; Firefox requires the 'align' attribute */ + margin: 0 0.15em; + padding: 0 0.5em 0em 0.5em; + margin-bottom: 0.4em; + cursor: pointer; +} Index: src/dispatch.c ================================================================== --- src/dispatch.c +++ src/dispatch.c @@ -349,10 +349,12 @@ ** followed by two spaces and a non-space.
elements can begin ** on the same line as long as they are separated by at least ** two spaces. ** ** * Indented text is show verbatim (
...
) +** +** * Lines that begin with "|" at the left margin are in
...
*/ static void help_to_html(const char *zHelp, Blob *pHtml){ int i; char c; int nIndent = 0; @@ -361,10 +363,11 @@ int aIndent[10]; const char *azEnd[10]; int iLevel = 0; int isLI = 0; int isDT = 0; + int inPRE = 0; static const char *zEndDL = ""; static const char *zEndPRE = ""; static const char *zEndUL = ""; static const char *zEndDD = "
"; @@ -380,16 +383,32 @@ wantBR = 1; continue; } i++; } - if( i>2 && zHelp[0]=='>' && zHelp[1]==' ' ){ - isDT = 1; - for(nIndent=1; nIndent2 && (zHelp[0]=='>' || zHelp[0]=='|') && zHelp[1]==' ' ){ + if( zHelp[0]=='>' ){ + isDT = 1; + for(nIndent=1; nIndent\n", -1); + inPRE = 1; + } + } }else{ + if( inPRE ){ + blob_append(pHtml, "\n", -1); + inPRE = 0; + } isDT = 0; for(nIndent=0; nIndent */ @@ -491,11 +510,11 @@ blob_append(pText, "fossil", 6); zHelp += i+7; i = -1; continue; } - if( c=='\n' && strncmp(zHelp+i+1,"> ",2)==0 ){ + if( c=='\n' && (zHelp[i+1]=='>' || zHelp[i+1]=='|') && zHelp[i+2]==' ' ){ blob_append(pText, zHelp, i+1); blob_append(pText, " ", 1); zHelp += i+2; i = -1; continue; Index: src/fossil.popupwidget.js ================================================================== --- src/fossil.popupwidget.js +++ src/fossil.popupwidget.js @@ -182,11 +182,27 @@ this.e.style.removeProperty('top'); } return this; }, - hide: function(){return this.show(false)} + hide: function(){return this.show(false)}, + + /** + A convenience method which adds click handlers to this popup's + main element and document.body to hide the popup when either + element is clicked or the ESC key is pressed. Only call this + once per instance, if at all. Returns this; + */ + installClickToHide: function f(){ + this.e.addEventListener('click', ()=>this.show(false), false); + document.body.addEventListener('click', ()=>this.show(false), true); + const self = this; + document.body.addEventListener('keydown', function(ev){ + if(self.isShown() && 27===ev.which) self.show(false); + }, true); + return this; + } }/*F.PopupWidget.prototype*/; /** Internal impl for F.toast() and friends. @@ -297,18 +313,11 @@ cssClass: ['fossil-tooltip', 'help-buttonlet-content'], refresh: function(){ } }); fch.popup.e.style.maxWidth = '80%'/*of body*/; - const hide = ()=>fch.popup.hide(); - fch.popup.e.addEventListener('click', hide, false); - document.body.addEventListener('click', hide, true); - document.body.addEventListener('keydown', function(ev){ - if(fch.popup.isShown() && 27===ev.which){ - fch.popup.hide(); - } - }, true); + fch.popup.installClickToHide(); } D.append(D.clearElement(fch.popup.e), ev.target.$helpContent); var popupRect = ev.target.getClientRects()[0]; var x = popupRect.left, y = popupRect.top; if(x<0) x = 0; Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -1230,11 +1230,11 @@ p->ApndWiki = p->Hyperlink = p->Clone = p->NewTkt = p->Password = p->RdAddr = p->TktFmt = p->Attach = p->ApndTkt = p->ModWiki = p->ModTkt = p->RdForum = p->WrForum = p->ModForum = - p->WrTForum = p->AdminForum = + p->WrTForum = p->AdminForum = p->Chat = p->EmailAlert = p->Announce = p->Debug = 1; /* Fall thru into Read/Write */ case 'i': p->Read = p->Write = 1; break; case 'o': p->Read = 1; break; case 'z': p->Zip = 1; break; @@ -1267,10 +1267,11 @@ case '3': p->WrForum = 1; case '2': p->RdForum = 1; break; case '7': p->EmailAlert = 1; break; case 'A': p->Announce = 1; break; + case 'C': p->Chat = 1; break; case 'D': p->Debug = 1; break; /* The "u" privilege recursively ** inherits all privileges of the user named "reader" */ case 'u': { @@ -1350,10 +1351,11 @@ case '4': rc = p->WrTForum; break; case '5': rc = p->ModForum; break; case '6': rc = p->AdminForum;break; case '7': rc = p->EmailAlert;break; case 'A': rc = p->Announce; break; + case 'C': rc = p->Chat; break; case 'D': rc = p->Debug; break; default: rc = 0; break; } } return rc; Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -107,10 +107,11 @@ char WrTForum; /* 4: Post to forums not subject to moderation */ char ModForum; /* 5: Moderate (approve or reject) forum posts */ char AdminForum; /* 6: Grant capability 4 to other users */ char EmailAlert; /* 7: Sign up for email notifications */ char Announce; /* A: Send announcements */ + char Chat; /* C: read or write the chatroom */ char Debug; /* D: show extra Fossil debugging features */ /* These last two are included to block infinite recursion */ char XReader; /* u: Inherit all privileges of "reader" */ char XDeveloper; /* v: Inherit all privileges of "developer" */ }; Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -32,10 +32,11 @@ $(SRCDIR)/bundle.c \ $(SRCDIR)/cache.c \ $(SRCDIR)/capabilities.c \ $(SRCDIR)/captcha.c \ $(SRCDIR)/cgi.c \ + $(SRCDIR)/chat.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ $(SRCDIR)/clone.c \ $(SRCDIR)/comformat.c \ @@ -219,10 +220,11 @@ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/accordion.js \ + $(SRCDIR)/chat.js \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ @@ -290,10 +292,11 @@ $(OBJDIR)/bundle_.c \ $(OBJDIR)/cache_.c \ $(OBJDIR)/capabilities_.c \ $(OBJDIR)/captcha_.c \ $(OBJDIR)/cgi_.c \ + $(OBJDIR)/chat_.c \ $(OBJDIR)/checkin_.c \ $(OBJDIR)/checkout_.c \ $(OBJDIR)/clearsign_.c \ $(OBJDIR)/clone_.c \ $(OBJDIR)/comformat_.c \ @@ -438,10 +441,11 @@ $(OBJDIR)/bundle.o \ $(OBJDIR)/cache.o \ $(OBJDIR)/capabilities.o \ $(OBJDIR)/captcha.o \ $(OBJDIR)/cgi.o \ + $(OBJDIR)/chat.o \ $(OBJDIR)/checkin.o \ $(OBJDIR)/checkout.o \ $(OBJDIR)/clearsign.o \ $(OBJDIR)/clone.o \ $(OBJDIR)/comformat.o \ @@ -776,10 +780,11 @@ $(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \ $(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \ $(OBJDIR)/capabilities_.c:$(OBJDIR)/capabilities.h \ $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \ $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \ + $(OBJDIR)/chat_.c:$(OBJDIR)/chat.h \ $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \ $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \ $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \ $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \ $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \ @@ -1054,10 +1059,18 @@ $(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/cgi.o -c $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h: $(OBJDIR)/headers + +$(OBJDIR)/chat_.c: $(SRCDIR)/chat.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/chat.c >$@ + +$(OBJDIR)/chat.o: $(OBJDIR)/chat_.c $(OBJDIR)/chat.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/chat.o -c $(OBJDIR)/chat_.c + +$(OBJDIR)/chat.h: $(OBJDIR)/headers $(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/checkin.c >$@ $(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -43,10 +43,11 @@ bundle cache capabilities captcha cgi + chat checkin checkout clearsign clone comformat Index: src/rebuild.c ================================================================== --- src/rebuild.c +++ src/rebuild.c @@ -394,11 +394,11 @@ " WHERE type='table'" " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias'," "'config','shun','private','reportfmt'," "'concealed','accesslog','modreq'," "'purgeevent','purgeitem','unversioned'," - "'subscriber','pending_alert','alert_bounce')" + "'subscriber','pending_alert','alert_bounce','chat')" " AND name NOT GLOB 'sqlite_*'" " AND name NOT GLOB 'fx_*'" ); while( db_step(&q)==SQLITE_ROW ){ blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0)); @@ -942,10 +942,11 @@ "UPDATE user SET photo=NULL, info='';\n" "DROP TABLE IF EXISTS purgeevent;\n" "DROP TABLE IF EXISTS purgeitem;\n" "DROP TABLE IF EXISTS admin_log;\n" "DROP TABLE IF EXISTS vcache;\n" + "DROP TABLE IF EXISTS chat;\n" ); } db_protect_pop(); } if( !bNeedRebuild ){ Index: src/setupuser.c ================================================================== --- src/setupuser.c +++ src/setupuser.c @@ -681,10 +681,12 @@ @ Supervise Forum%s(B('6')) @
  • @
  • + @
  • @
  • @ @ @
  • Index: src/sitemap.c ================================================================== --- src/sitemap.c +++ src/sitemap.c @@ -115,10 +115,13 @@ @ } if( srchFlags ){ @
  • %z(href("%R/search"))Search
  • } + if( g.perm.Chat ){ + @
  • %z(href("%R/chat"))Chat
  • + } if( g.perm.RdForum ){ @
  • %z(href("%R/forum"))Forum @
      @
    • %z(href("%R/timeline?y=f"))Recent activity
    • @
    Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -122,10 +122,13 @@ #define TIMELINE_DELTA 0x10000000 /* Background color shows delta manifests */ #endif /* ** Hash a string and use the hash to determine a background color. +** +** This value returned is in static space and is overwritten with +** each subsequent call. */ char *hash_color(const char *z){ int i; /* Loop counter */ unsigned int h = 0; /* Hash on the branch name */ int r, g, b; /* Values for red, green, and blue */ ADDED tools/chat.tcl Index: tools/chat.tcl ================================================================== --- /dev/null +++ tools/chat.tcl @@ -0,0 +1,509 @@ +#!/usr/bin/wapptclsh +# +# A chat program designed to run using the extcgi mechanism of Fossil. +# +encoding system utf-8 + +# The name of the chat database file +# +proc chat-db-name {} { + set x [wapp-param SCRIPT_FILENAME] + set dir [file dir $x] + set fn [file tail $x] + return $dir/-$fn.db +} + +# Verify permission to use chat. Return true if not authorized. +# Return false if the Fossil user is allowed to access chat. +# +proc not-authorized {} { + set cap [wapp-param FOSSIL_CAPABILITIES] + return [expr {![string match *i* $cap]}] +} + +# The default page. +# Load the initial chat screen. +# +proc wapp-default {} { + wapp-content-security-policy off + wapp-trim { +
    + } + if {[not-authorized]} { + wapp-trim { +

    Not authorized

    +

    You must have privileges to use this chatroom

    +
    + } + return + } + set scriptFile [wapp-param SCRIPT_FILENAME] + set cgiFn [file tail $scriptFile] + wapp-trim { +
    +
    +
    + + +
    +
    + File: + +
    +
    + +
    + + + +
    +

    + CGI environment | + Wapp script + + } + set nonce [wapp-param FOSSIL_NONCE] + set submiturl [wapp-param SCRIPT_NAME]/send + set pollurl [wapp-param SCRIPT_NAME]/poll + set downloadurl [wapp-param SCRIPT_NAME]/download + set me [wapp-param FOSSIL_USER] + wapp-trim { + + } + + # Make sure the chat database exists + sqlite3 db [chat-db-name] + if {[db one {PRAGMA journal_mode}]!="wal"} { + db eval {PRAGMA journal_mode=WAL} + } + db eval { + CREATE TABLE IF NOT EXISTS chat( + msgid INTEGER PRIMARY KEY AUTOINCREMENT, + mtime JULIANDAY, + xfrom TEXT, + xto TEXT, + xmsg TEXT, + file BLOB, + fname TEXT, + fmime TEXT + ); + CREATE TABLE IF NOT EXISTS ustat( + uname TEXT PRIMARY KEY, + mtime JULIANDAY, -- Last interaction + seen INT, -- Last message seen + logout JULIANDAY + ) WITHOUT ROWID; + } + db close +} + +# Show the CGI environment. Used for testing only. +# +proc wapp-page-env {} { + wapp-trim { +

    +
    %html([wapp-debug-env])
    +
    + } +} + +# Log the CGI environment into the "-logfile.txt" file in the same +# directory as the script. Used for testing and development only. +# +proc logenv {} { + set fn [file dir [wapp-param SCRIPT_FILENAME]]/-logfile.txt + set out [open $fn a] + puts $out {************************************************************} + puts $out [wapp-debug-env] + close $out +} + +# A no-op page. Used for testing and development only. +# +proc noop-page {} { + wapp-trim { +

    No-Op

    + } +} + +# Accept a new post via XHR. +# No reply expected. +# +proc wapp-page-send {} { + if {[not-authorized]} return + set user [wapp-param FOSSIL_USER] + set fcontent [wapp-param file.content] + set fname [wapp-param file.filename] + set fmime [wapp-param file.mimetype] + set msg [wapp-param msg] + sqlite3 db [chat-db-name] + db eval BEGIN + if {$fcontent!=""} { + db eval { + INSERT INTO chat(mtime,xfrom,xmsg,file,fname,fmime) + VALUES(julianday('now'),$user,@msg,@fcontent,$fname,$fmime) + } + } else { + db eval { + INSERT INTO chat(mtime,xfrom,xmsg) + VALUES(julianday('now'),$user,@msg) + } + } + db eval { + INSERT INTO ustat(uname,mtime,seen) VALUES($user,julianday('now'),0) + ON CONFLICT(uname) DO UPDATE set mtime=julianday('now') + } + db eval COMMIT + db close +} + +# Request updates. +# Delay the response until something changes (as this system works +# using the Hanging-GET or Long-Poll style of server-push). +# The result is javascript describing the new content. +# +# Call is like this: /poll/N +# Where N is the last message received so far. The reply stalls +# until newer messages are available. +# +proc wapp-page-poll {} { + if {[not-authorized]} return + wapp-mimetype text/json + set msglist {} + sqlite3 db [chat-db-name] + set id 0 + scan [wapp-param PATH_TAIL] %d id + while {1} { + set datavers [db one {PRAGMA data_version}] + db eval {SELECT msgid, datetime(mtime) AS dx, xfrom, CAST(xmsg AS text) mx, + length(file) AS lx, fname, fmime + FROM chat + WHERE msgid>$id + ORDER BY msgid} { + set quname [string map {\" \\\"} $xfrom] + set qmsg [string map {\" \\\"} $mx] + if {$lx==""} {set lx 0} + set qfname [string map {\" \\\"} $fname] + lappend msglist "\173\"msgid\":$msgid,\"mtime\":\"$dx\",\ + \"xfrom\":\"$quname\",\ + \"xmsg\":\"$qmsg\",\"fsize\":$lx,\ + \"fname\":\"$qfname\",\"fmime\":\"$fmime\"\175" + } + if {[llength $msglist]>0} { + wapp-unsafe "\173\042msgs\042:\133[join $msglist ,]\135\175" + db close + return + } + after 2000 + while {[db one {PRAGMA data_version}]==$datavers} {after 2000} + } +} + +# Show the text of this script. +# +proc wapp-page-self {} { + wapp-trim { +
    + } + set fd [open [wapp-param SCRIPT_FILENAME] rb] + set script [read $fd] + wapp-trim { +
    %html($script)
    + } + wapp-trim { +
    + } +} + +# Download the file associated with a message. +# +# Call like this: /download/N +# Where N is the message id. +# +proc wapp-page-download {} { + if {[not-authorized]} { + wapp-trim { +

    Not authorized

    +

    You must have privileges to use this chatroom

    + + } + return + } + set id 0 + scan [wapp-param PATH_TAIL] %d id + sqlite3 db [chat-db-name] + db eval {SELECT fname, fmime, file FROM chat WHERE msgid=$id} { + wapp-mimetype $fmime + wapp $file + } + db close +} + + +wapp-start $argv Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -28,13 +28,13 @@ SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen -SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pikchr_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c +SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pikchr_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c -OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchr$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O +OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchr$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ @@ -49,11 +49,11 @@ $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res - +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pikchr pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ + +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pikchr pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ @@ -229,10 +229,16 @@ $(OBJDIR)\cgi$O : cgi_.c cgi.h $(TCC) -o$@ -c cgi_.c cgi_.c : $(SRCDIR)\cgi.c +translate$E $** > $@ + +$(OBJDIR)\chat$O : chat_.c chat.h + $(TCC) -o$@ -c chat_.c + +chat_.c : $(SRCDIR)\chat.c + +translate$E $** > $@ $(OBJDIR)\checkin$O : checkin_.c checkin.h $(TCC) -o$@ -c checkin_.c checkin_.c : $(SRCDIR)\checkin.c @@ -999,7 +1005,7 @@ zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h builtin_data.h VERSION.h - +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pikchr_.c:pikchr.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h + +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pikchr_.c:pikchr.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h @copy /Y nul: headers Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -444,10 +444,11 @@ $(SRCDIR)/bundle.c \ $(SRCDIR)/cache.c \ $(SRCDIR)/capabilities.c \ $(SRCDIR)/captcha.c \ $(SRCDIR)/cgi.c \ + $(SRCDIR)/chat.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ $(SRCDIR)/clone.c \ $(SRCDIR)/comformat.c \ @@ -631,10 +632,11 @@ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/accordion.js \ + $(SRCDIR)/chat.js \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ @@ -702,10 +704,11 @@ $(OBJDIR)/bundle_.c \ $(OBJDIR)/cache_.c \ $(OBJDIR)/capabilities_.c \ $(OBJDIR)/captcha_.c \ $(OBJDIR)/cgi_.c \ + $(OBJDIR)/chat_.c \ $(OBJDIR)/checkin_.c \ $(OBJDIR)/checkout_.c \ $(OBJDIR)/clearsign_.c \ $(OBJDIR)/clone_.c \ $(OBJDIR)/comformat_.c \ @@ -850,10 +853,11 @@ $(OBJDIR)/bundle.o \ $(OBJDIR)/cache.o \ $(OBJDIR)/capabilities.o \ $(OBJDIR)/captcha.o \ $(OBJDIR)/cgi.o \ + $(OBJDIR)/chat.o \ $(OBJDIR)/checkin.o \ $(OBJDIR)/checkout.o \ $(OBJDIR)/clearsign.o \ $(OBJDIR)/clone.o \ $(OBJDIR)/comformat.o \ @@ -1213,10 +1217,11 @@ $(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \ $(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \ $(OBJDIR)/capabilities_.c:$(OBJDIR)/capabilities.h \ $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \ $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \ + $(OBJDIR)/chat_.c:$(OBJDIR)/chat.h \ $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \ $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \ $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \ $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \ $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \ @@ -1493,10 +1498,18 @@ $(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/cgi.o -c $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h: $(OBJDIR)/headers + +$(OBJDIR)/chat_.c: $(SRCDIR)/chat.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/chat.c >$@ + +$(OBJDIR)/chat.o: $(OBJDIR)/chat_.c $(OBJDIR)/chat.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/chat.o -c $(OBJDIR)/chat_.c + +$(OBJDIR)/chat.h: $(OBJDIR)/headers $(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/checkin.c >$@ $(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -366,10 +366,11 @@ "$(OX)\bundle_.c" \ "$(OX)\cache_.c" \ "$(OX)\capabilities_.c" \ "$(OX)\captcha_.c" \ "$(OX)\cgi_.c" \ + "$(OX)\chat_.c" \ "$(OX)\checkin_.c" \ "$(OX)\checkout_.c" \ "$(OX)\clearsign_.c" \ "$(OX)\clone_.c" \ "$(OX)\comformat_.c" \ @@ -552,10 +553,11 @@ "$(SRCDIR)\..\skins\xekri\css.txt" \ "$(SRCDIR)\..\skins\xekri\details.txt" \ "$(SRCDIR)\..\skins\xekri\footer.txt" \ "$(SRCDIR)\..\skins\xekri\header.txt" \ "$(SRCDIR)\accordion.js" \ + "$(SRCDIR)\chat.js" \ "$(SRCDIR)\ci_edit.js" \ "$(SRCDIR)\copybtn.js" \ "$(SRCDIR)\default.css" \ "$(SRCDIR)\diff.tcl" \ "$(SRCDIR)\forum.js" \ @@ -622,10 +624,11 @@ "$(OX)\bundle$O" \ "$(OX)\cache$O" \ "$(OX)\capabilities$O" \ "$(OX)\captcha$O" \ "$(OX)\cgi$O" \ + "$(OX)\chat$O" \ "$(OX)\checkin$O" \ "$(OX)\checkout$O" \ "$(OX)\clearsign$O" \ "$(OX)\clone$O" \ "$(OX)\comformat$O" \ @@ -851,10 +854,11 @@ echo "$(OX)\bundle.obj" >> $@ echo "$(OX)\cache.obj" >> $@ echo "$(OX)\capabilities.obj" >> $@ echo "$(OX)\captcha.obj" >> $@ echo "$(OX)\cgi.obj" >> $@ + echo "$(OX)\chat.obj" >> $@ echo "$(OX)\checkin.obj" >> $@ echo "$(OX)\checkout.obj" >> $@ echo "$(OX)\clearsign.obj" >> $@ echo "$(OX)\clone.obj" >> $@ echo "$(OX)\comformat.obj" >> $@ @@ -1158,10 +1162,11 @@ echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@ echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@ echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@ echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@ echo "$(SRCDIR)\accordion.js" >> $@ + echo "$(SRCDIR)\chat.js" >> $@ echo "$(SRCDIR)\ci_edit.js" >> $@ echo "$(SRCDIR)\copybtn.js" >> $@ echo "$(SRCDIR)\default.css" >> $@ echo "$(SRCDIR)\diff.tcl" >> $@ echo "$(SRCDIR)\forum.js" >> $@ @@ -1317,10 +1322,16 @@ "$(OX)\cgi$O" : "$(OX)\cgi_.c" "$(OX)\cgi.h" $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\cgi_.c" "$(OX)\cgi_.c" : "$(SRCDIR)\cgi.c" "$(OBJDIR)\translate$E" $** > $@ + +"$(OX)\chat$O" : "$(OX)\chat_.c" "$(OX)\chat.h" + $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\chat_.c" + +"$(OX)\chat_.c" : "$(SRCDIR)\chat.c" + "$(OBJDIR)\translate$E" $** > $@ "$(OX)\checkin$O" : "$(OX)\checkin_.c" "$(OX)\checkin.h" $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\checkin_.c" "$(OX)\checkin_.c" : "$(SRCDIR)\checkin.c" @@ -2108,10 +2119,11 @@ "$(OX)\bundle_.c":"$(OX)\bundle.h" \ "$(OX)\cache_.c":"$(OX)\cache.h" \ "$(OX)\capabilities_.c":"$(OX)\capabilities.h" \ "$(OX)\captcha_.c":"$(OX)\captcha.h" \ "$(OX)\cgi_.c":"$(OX)\cgi.h" \ + "$(OX)\chat_.c":"$(OX)\chat.h" \ "$(OX)\checkin_.c":"$(OX)\checkin.h" \ "$(OX)\checkout_.c":"$(OX)\checkout.h" \ "$(OX)\clearsign_.c":"$(OX)\clearsign.h" \ "$(OX)\clone_.c":"$(OX)\clone.h" \ "$(OX)\comformat_.c":"$(OX)\comformat.h" \
  •  CodeForumTicketsWiki\ + @
     CodeForumTicketsWikiChat\ @ Unversioned Content
    %s(azType[eType])%s(azType[eType])