From 7ffe715010c6ed51fab227884b0dbbc8468cc3bb Mon Sep 17 00:00:00 2001 From: Gabor Adam Toth Date: Mon, 1 Mar 2010 03:50:58 +0100 Subject: [PATCH] place/threads: web posting & commenting, entry edit, new settings; place/archetype.gen: _request_set_default_(text|list|bool) place/threads new features: - add/comment forms after /login @ place url (cookie auth) - new settings: /place showform|showcomments|addaction|editaction --- world/net/library/signature.c | 44 ++-- world/net/place/archetype.gen | 73 +++++- world/net/place/threads.c | 409 +++++++++++++++++++--------------- world/net/place/userthreads.c | 39 ++-- world/net/storage.c | 4 + world/net/user.c | 11 +- world/static/examine.css | 46 +++- 7 files changed, 389 insertions(+), 237 deletions(-) diff --git a/world/net/library/signature.c b/world/net/library/signature.c index 99ad6e1..6cd78d8 100644 --- a/world/net/library/signature.c +++ b/world/net/library/signature.c @@ -80,35 +80,47 @@ private volatile mapping _sigs = ([ "_request_ni": ({ "_request_nick_local", 0, "_nick_local", "_INTERNAL_stuss" }), "_request_public": ({ "_request_public", 0, "_flag_public" }), "_request_pub": ({ "_request_public", 0, "_flag_public" }), - + // threads "_request_entries": ({ "_request_entries", 0, "_num" }), "_request_ents": ({ "_request_entries", 0, "_num" }), "_request_entry": ({ "_request_entry", 0, "_id" }), "_request_ent": ({ "_request_entry", 0, "_id" }), - "_request_comment": ({ "_request_comment", 0, "_id", "_text" }), - "_request_com": ({ "_request_comment", 0, "_id", "_text" }), - "_request_title": ({ "_request_title", 0, "_id", "_title" }), - "_request_addentry": ({ "_request_addentry", 0, "_text" }), - "_request_addent": ({ "_request_addentry", 0, "_text" }), - "_request_submit": ({ "_request_addentry", 0, "_text" }), - "_request_blog": ({ "_request_addentry", 0, "_text" }), - "_request_delentry": ({ "_request_delentry", 0, "_id" }), - "_request_delent": ({ "_request_delentry", 0, "_id" }), - "_request_unsubmit": ({ "_request_delentry", 0, "_id" }), - "_request_unblog": ({ "_request_delentry", 0, "_id" }), + "_request_entry_reply": ({ "_request_entry_reply", 0, "_parent", "_text" }), + "_request_comment": ({ "_request_entry_reply", 0, "_parent", "_text" }), + "_request_com": ({ "_request_entry_reply", 0, "_parent", "_text" }), + "_request_entry_add": ({ "_request_entry_add", 0, "_text" }), + "_request_addentry": ({ "_request_entry_add", 0, "_text" }), + "_request_addent": ({ "_request_entry_add", 0, "_text" }), + "_request_submit": ({ "_request_entry_add", 0, "_text" }), + "_request_blog": ({ "_request_entry_add", 0, "_text" }), + "_request_entry_del": ({ "_request_entry_del", 0, "_id" }), + "_request_delentry": ({ "_request_entry_del", 0, "_id" }), + "_request_delent": ({ "_request_entry_del", 0, "_id" }), + "_request_unsubmit": ({ "_request_entry_del", 0, "_id" }), + "_request_unblog": ({ "_request_entry_del", 0, "_id" }), + "_request_entry_edit": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_editentry": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_edentry": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_edent": ({ "_request_entry_edit", 0, "_id", "_text" }), + "_request_set_addact": ({ "_request_set_addaction", 0, "_value" }), + "_request_set_addaction":({ "_request_set_addaction", 0, "_value" }), + "_request_set_editact": ({ "_request_set_editaction", 0, "_value" }), + "_request_set_editaction":({ "_request_set_editaction", 0, "_value" }), + "_request_set_showform":({ "_request_set_showform", 0, "_value" }), + "_request_set_showcomments":({ "_request_set_showcomments", 0, "_value" }), #ifdef _flag_enable_module_microblogging "_request_add": ({ "_request_add", 0, "_person" }), "_request_remove": ({ "_request_remove", 0, "_person" }), "_request_set_priv": ({ "_request_set_privacy", 0, "_value" }), "_request_set_privacy": ({ "_request_set_privacy", 0, "_value" }), -#ifdef TWITTER +# ifdef TWITTER "_request_set_tw": ({ "_request_set_twitter", 0, "_value" }), "_request_set_twitter": ({ "_request_set_twitter", 0, "_value" }), -#endif -#ifdef IDENTICA +# endif +# ifdef IDENTICA "_request_set_ica": ({ "_request_set_identica", 0, "_value" }), "_request_set_identica":({ "_request_set_identica", 0, "_value" }), -#endif +# endif #endif #ifdef EXPERIMENTAL // stuff to play around with diff --git a/world/net/place/archetype.gen b/world/net/place/archetype.gen index 60efc8d..e8b6c14 100644 --- a/world/net/place/archetype.gen +++ b/world/net/place/archetype.gen @@ -216,14 +216,6 @@ int qSaveImmediately() { #endif } -int qHistoryPersistentLimit() { - return _limit_amount_history_persistent; -} - -int qHistoryExportLimit() { - return _limit_amount_history_export; -} - // to be overloaded by place.gen qNewsfeed() { return 0; } // _request_list_feature uses this in *all* place objects, dont ifdef @@ -233,7 +225,9 @@ mayLog(mc) { return abbrev("_message", mc); } qHistory() { return v("log"); } -qHistoryGlimpse() { return DEFAULT_GLIMPSE; } +int qHistoryGlimpse() { return DEFAULT_GLIMPSE; } +int qHistoryPersistentLimit() { return _limit_amount_history_persistent; } +int qHistoryExportLimit() { return _limit_amount_history_export; } histClear(a, b, source, vars) { int l = logSize(); @@ -463,6 +457,7 @@ showTopic(rcpt, verbose, mc) { return 1; } +# ifdef SIGS _request_set_topic(source, mc, data, vars, b) { string value = vars["_value"] || vars["_topic"]; if (strlen(value)) { @@ -473,7 +468,7 @@ _request_set_topic(source, mc, data, vars, b) { } return 1; } - +# endif #endif #if HAS_PORT(HTTP_PORT, HTTP_PATH) || HAS_PORT(HTTPS_PORT, HTTP_PATH) @@ -2254,6 +2249,64 @@ _request_set(source, mc, data, vars, b) { return 1; } +_request_set_default_text(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value = vars["_value"]; + + if (strlen(value)) { + vSet(key, value); + save(); + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is \"[_value]\".", (["_key_set": key, "_value": v(key)])); + return 1; +} + +_request_set_default_list(source, mc, data, vars, b, list) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value; + + foreach (string item : list) { + if (item == vars["_value"]) { + value = vars["_value"]; + break; + } + } + + if (strlen(value)) { + vSet(key, value); + save(); + } else if (strlen(vars["_value"])) { + sendmsg(source, "_error_invalid_setting_value", + "Sorry, that is not a valid value for the [_key_set] setting. Valid values are: [_values].", + ([ "_key_set": key, "_values": implode(list, ", ") ]) ); + return 1; + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is \"[_value]\".", (["_key_set": key, "_value": v(key)])); + return 1; +} + +_request_set_default_bool(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; + string key = vars["_key"]; + string value = vars["_value"]; + + if (is_true(value)) { + vSet(key, 1); + save(); + } else if (is_false(value)) { + vSet(key, 0); + save(); + } + + sendmsg(source, "_info_set_place_" + key, "Setting \"[_key_set]\" is [_value].", (["_key_set": key, "_value": v(key) ? "on" : "off"])); + return 1; +} + + #ifdef PLACE_OWNED _request_owners(source, mc, data, vars, b) { mixed t; diff --git a/world/net/place/threads.c b/world/net/place/threads.c index 22fe4dd..fa58d5b 100644 --- a/world/net/place/threads.c +++ b/world/net/place/threads.c @@ -15,24 +15,22 @@ inherit NET_PATH "place/owned"; -qHistoryPersistentLimit() { +int qHistoryPersistentLimit() { return 0; } -canPost(snicker) { - return qAide(snicker); -} - -canDeleteOwn(snicker) { - return qAide(snicker); -} - -canDeleteEverything(snicker) { - return qOwner(snicker); -} +canPost(snicker) { return qAide(snicker); } +canReply(snicker) { return qAide(snicker); } +canEditOwn(snicker) { return qAide(snicker); } +canEditAll(snicker) { return qOwner(snicker); } +canDeleteOwn(snicker) { return qAide(snicker); } +canDeleteAll(snicker) { return qOwner(snicker); } int mayLog(string mc) { - return abbrev("_notice_thread", mc) || abbrev("_message", mc); + if (abbrev("_notice_thread", mc)) + return regmatch(mc, "_edit\\b") ? 0 : 1; + + return abbrev("_message", mc); } int showWebLog() { @@ -51,6 +49,17 @@ create() { logSet(0, ({0, 0, 0, 0})); } +load(name, keep) { + int ret = ::load(name, keep); + + unless (v("addaction")) vSet("addaction", "adds"); + unless (v("editaction")) vSet("editaction", "edits"); + unless (vExist("showform")) vSet("showform", 1); + unless (vExist("showcomments")) vSet("showcomments", 1); + + return ret; +} + varargs array(mixed) entries(int limit, int offset, int reverse, int parent, int id) { P3((">> entries(%O, %O, %O)\n", limit, offset, parent)) array(mixed) entries = ({}), entry, children, child; @@ -90,54 +99,69 @@ varargs array(mixed) entry(int id) { return entries(0, 0, 0, 0, id); } -varargs int addEntry(mixed source, string snicker, string text, string title, int parent_id) { - P3((">> addEntry(%O, %O, %O, %O, %O)\n", source, snicker, text, title, parent_id)) - int id = logSize(); +varargs int addEntry(mixed source, mapping vars, string _data, string _mc) { + P3((">> addEntry(%O, %O, %O, %O)\n", source, vars, _data, _mc)) string mc = "_notice_thread_entry"; string data = "[_nick] [_action]: "; + vars["_id"] = logSize(); + vars["_action"] ||= v("addaction"); + // this should only be set after a reply + m_delete(vars, "_children"); - mapping vars = ([ - "_id": id, - "_text": text, - "_nick": snicker, - "_action": "adds", //TODO: add a /set'ting for it, or find a better name - ]); - - if (parent_id) { - P3((">>> parent_id: %O\n", parent_id)) + if (vars["_parent"]) { array(mixed) parent; - unless (parent = logPick(parent_id)) return 0; - P3((">>> parent: %O\n", parent)) + vars["_parent"] = to_int(vars["_parent"]); + unless (parent = logPick(vars["_parent"])) return 0; + PT((">>> parent: %O\n", parent)) unless (parent[LOG_VARS]["_children"]) parent[LOG_VARS]["_children"] = ({ }); - parent[LOG_VARS]["_children"] += ({ id }); - save(); + parent[LOG_VARS]["_children"] += ({ vars["_id"] }); mc += "_reply"; data = member(parent[LOG_VARS], "_title") ? "[_nick] [_action] in reply to #[_parent] ([_parent_title]): " : - "[_nick] [_action] in reply to #[_parent]: ", - vars += ([ "_parent": parent_id ]); + "[_nick] [_action] in reply to #[_parent]: "; } - if (title && strlen(title)) { - vars += ([ "_title": title ]); + if (strlen(vars["_title"])) { data += "[_title]\n[_text]"; } else { data += "[_text]"; } - data += " (#[_id] in [_nick_place])"; + if (_mc) mc += _mc; + if (_data) data = _data; + castmsg(source, mc, data, vars); return 1; } +int editEntry(mixed source, mapping vars, string data) { + P3((">> editEntry(%O, %O, %O)\n", source, vars, data)) + array(mixed) entry; + vars["_id"] = to_int(vars["_id"]); + unless (entry = logPick(vars["_id"])) return 0; + + string unick; + unless (canEditAll(SNICKER)) + unless (canEditOwn(SNICKER) && lower_case(psyc_name(source)) == lower_case(entry[LOG_SOURCE][LOG_SOURCE_UNI])) + return 0; + + if (strlen(data)) entry[LOG_DATA] = data; + foreach (string key : vars) + if (key != "_children") entry[LOG_VARS][key] = vars[key]; + + save(); + castmsg(source, entry[LOG_MC] + "_edit", entry[LOG_DATA], vars + ([ "_action": v("editaction") ])); + return 1; +} + int delEntry(int id, mixed source, mapping vars) { array(mixed) entry; unless (entry = logPick(id)) return 0; string unick; - unless (canDeleteEverything(SNICKER)) + unless (canDeleteAll(SNICKER)) unless (canDeleteOwn(SNICKER) && lower_case(psyc_name(source)) == lower_case(entry[LOG_SOURCE][LOG_SOURCE_UNI])) return 0; @@ -155,8 +179,8 @@ sendEntries(mixed source, array(mixed) entries, int level) { PT(("entry: %O\n", entry)) vars = entry[LOG_VARS]; sendmsg(source, regreplace(entry[LOG_MC], "^_notice", "_list", 1), - "[_indent][_nick]: " + (vars["_title"] ? "[_title]\n" : "") + "[_text] (#[_id])", - vars + ([ "_level": level, "_indent": x(" ", level) ])); + "[_indent][_nick]: "+ (vars["_title"] ? "[_title]\n" : "") +"[_text] (#[_id])", + vars + ([ "_level": level, "_indent": x(" ", level), "_postfix_time_log": 1 ])); if (sizeof(entry) >= LOG_CHILDREN + 1) sendEntries(source, entry[LOG_CHILDREN], level + 1); n++; } @@ -181,46 +205,58 @@ _request_entry(source, mc, data, vars, b) { sendmsg(source, "_error_thread_invalid_entry", "#[_id]: no such entry", (["_id": id])); } - return 1; } -_request_addentry(source, mc, data, vars, b) { +_request_entry_add(source, mc, data, vars, b) { P3((">> _request_addentry(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) unless (canPost(SNICKER)) return 0; unless (vars["_text"] && strlen(vars["_text"])) { - sendmsg(source, "_warning_usage_addentry", + sendmsg(source, "_warning_usage_entry_add", "Usage: /addentry ", ([ ])); return 1; } - addEntry(source, SNICKER, vars["_text"], vars["_title"]); + addEntry(source, vars, data); return 1; } -_request_comment(source, mc, data, vars, b) { - P3((">> _request_comment(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - unless (vars["_id"] && strlen(vars["_id"]) && - vars["_text"] && strlen(vars["_text"])) { - sendmsg(source, "_warning_usage_reply", +_request_entry_reply(source, mc, data, vars, b) { + P3((">> _request_entry_reply(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (canReply(SNICKER)) return 0; + unless (vars["_parent"] && strlen(vars["_text"])) { + sendmsg(source, "_warning_usage_entry_reply", "Usage: /comment ", ([ ])); return 1; } - int id = to_int(vars["_id"]); - string snicker = SNICKER; - P3((">>> id: %O, vars: %O\n", id, vars)); - unless (addEntry(source, snicker, vars["_text"], vars["_title"], id)) + unless (addEntry(source, vars)) sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); + "#[_id]: no such entry", (["_id": vars["_id"]])); return 1; } -_request_delentry(source, mc, data, vars, b) { - P3((">> _request_delentry(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - unless (canPost(SNICKER)) return 0; +_request_entry_edit(source, mc, data, vars, b) { + P3((">> _request_title(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (canEditOwn(SNICKER)) return 0; unless (vars["_id"] && strlen(vars["_id"])) { - sendmsg(source, "_warning_usage_delentry", + sendmsg(source, "_warning_usage_entry_edit", + "Usage: /editentry ", ([ ])); + return 1; + } + + unless (editEntry(source, vars)) + sendmsg(source, "_error_thread_invalid_entry", + "#[_id]: no such entry", (["_id": vars["_id"]])); + + return 1; +} + +_request_entry_del(source, mc, data, vars, b) { + P3((">> _request_entry_del(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) + unless (canPost(SNICKER)) return 0; + unless (vars["_id"]) { + sendmsg(source, "_warning_usage_entry_del", "Usage: /delentry ", ([ ])); return 1; } @@ -236,23 +272,21 @@ _request_delentry(source, mc, data, vars, b) { return 1; } -#if 0 -_request_title(source, mc, data, vars, b) { - P3((">> _request_title(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - unless (vars["_id"] && strlen(vars["_id"])) { - sendmsg(source, "_warning_usage_title", - "Usage: /title ", ([ ])); - return 1; - } - - int id = to_int(vars["_id"]); - unless (setTitle(id, vars["_title"])) - sendmsg(source, "_error_thread_invalid_entry", - "#[_id]: no such entry", (["_id": id])); - - return 1; +_request_set_addaction(source, mc, data, vars, b) { + return _request_set_default_text(source, mc, data, vars, b); +} + +_request_set_editaction(source, mc, data, vars, b) { + return _request_set_default_text(source, mc, data, vars, b); +} + +_request_set_showcomments(source, mc, data, vars, b) { + return _request_set_default_bool(source, mc, data, vars, b); +} + +_request_set_showform(source, mc, data, vars, b) { + return _request_set_default_bool(source, mc, data, vars, b); } -#endif msg(source, mc, data, vars){ P3(("thread:msg(%O, %O, %O, %O)", source, mc, data, vars)) @@ -269,28 +303,49 @@ msg(source, mc, data, vars){ return ::msg(source, mc, data, vars); } -varargs string htmlComments(array(mixed) entries, int level) { +varargs array(mixed) htmlComments(array(mixed) entries, int submit, int level, int n) { + array(mixed) ret; mapping entry, vars; string ht = "", style; foreach(entry : entries) { vars = entry[LOG_VARS]; - style = level ? "style='padding-left: " + level + "em'" : ""; - ht += "<div class='comment' title='" + isotime(ctime(vars["_time_place"]), 1) + "' " + style + "><span class='comment-author'>" + vars["_nick"] + "</span>: <span class='comment-text'>" + htquote(vars["_text"], 1) + "</span></div>\n"; - if (sizeof(entry) >= LOG_CHILDREN + 1) ht += htmlComments(entry[LOG_CHILDREN], level + 1); + style = level ? "style='padding-left: "+ level +"em'" : ""; + ht += "<div class='comment' title='"+ isotime(ctime(vars["_time_place"]), 1) +"' "+ style +"><span class='comment-author'>"+ vars["_nick"] +"</span>: <span class='comment-text'>" + (submit ? "<a class='comment-form-toggle' onclick='toggleForm(this.parentNode, "+ vars["_id"] +")'>»</a>" : "")+ htquote(vars["_text"], 1) +"</span></div>\n"; + n++; + if (sizeof(entry) >= LOG_CHILDREN + 1) { + ret = htmlComments(entry[LOG_CHILDREN], submit, level + 1, n); + ht += ret[0]; + n = ret[1]; + } } - return ht; + return ({ ht, n }); } -varargs string htmlEntries(array(mixed) entries, int nojs, string chan, string submit, string url_prefix) { - P3((">> threads:htmlentries(%O, %O, %O, %O, %O)\n", entries, nojs, chan, submit, url_prefix)) +varargs string htmlEntries(array(mixed) entries, int submit, int show_comments, int nojs, string chan, string submit_target, string url_prefix) { + P3((">> threads:htmlentries(%O, %O, %O, %O, %O, %O)\n", entries, submit, nojs, chan, submit_target, url_prefix)) string text, ht = ""; - string id_prefix = chan ? chan + "-" : ""; + string id_prefix = chan ? chan +"-" : ""; unless (url_prefix) url_prefix = ""; unless (nojs) ht += "<script type='text/javascript'>\n" - "function toggle(e) { if (typeof e == 'string') e = document.getElementById(e); e.className = e.className.match('hidden') ? e.className.replace(/ *hidden/, '') : e.className + ' hidden'; }\n" + "function $(id) { return document.getElementById(id); }" + "function toggle(e) { if (!e) return 0; if (typeof e == 'string') e = $(e); e.className = e.className.match('hidden') ? e.className.replace(/ *hidden/, '') : e.className + ' hidden'; return 1; }\n" + "function toggleForm(e, id) { toggle(e.nextSibling) || e.parentNode.appendChild($('entry-form')) && ($('entry-form').className=''); $('form-parent').value = id }" "</script>\n"; + if (submit) ht += + "<form id='entry-form' class='hidden' action='"+ url_prefix +"'"+ + (0 && submit_target + //FIXME: cmd is executed twice, because after a set-cookie it's parsed again + ? "onsubmit=\"cmd('comment '+ $('form-parent').value +' '+ this.previousSibling.value, '"+ submit_target +"')\"" + : "method='post'") + + ">" + "<input type='hidden' name='request' value='post' />" + "<input type='hidden' id='form-parent' name='_parent' value='' />" + "<textarea name='_text' autocomplete='off'></textarea>" + "<input type='submit' value='Send'>" + "</form>"; + mapping entry, vars; foreach (entry : entries) { P3((">>> entry: %O\n", entry)) @@ -298,41 +353,64 @@ varargs string htmlEntries(array(mixed) entries, int nojs, string chan, string s text = htquote(vars["_text"], 1); - string comments = ""; - if (sizeof(entry) >= LOG_CHILDREN + 1) comments = htmlComments(entry[LOG_CHILDREN]); + array(mixed) comments = ({ "", 0 }); + if (sizeof(entry) >= LOG_CHILDREN + 1) comments = htmlComments(entry[LOG_CHILDREN], submit); ht += "<div class='entry'>\n" "<div class='header'>\n" - "<a href=\"" + url_prefix + "?id=" + vars["_id"] + "\">" - "<span class='id'>#" + vars["_id"] + "</span> - \n" - "<span class='author'>" + vars["_nick"] + "</span>\n" + "<a href=\""+ url_prefix +"?id="+ vars["_id"] +"\">" + "<span class='id'>#"+ vars["_id"] +"</span> - \n" + "<span class='author'>"+ vars["_nick"] +"</span>\n" + (vars["_title"] && strlen(vars["_title"]) ? " - " : "") + - "<span class='title'>" + htquote(vars["_title"] || "") + "</span>\n" + "<span class='title'>"+ htquote(vars["_title"] || "") +"</span>\n" "</a>" "</div>\n" "<div class='body'>\n" - "<div class='text'>" + text + "</div>\n" - "<div id='comments-" + id_prefix + vars["_id"] + "' class='comments'>" + comments + - (submit && strlen(submit) ? - "<a onclick=\"toggle(this.nextSibling)\">» reply</a>" - "<div class='comment-submit hidden'>" - "<textarea autocomplete='off'></textarea>" - //FIXME: cmd is executed twice, because after a set-cookie it's parsed again - "<input type='button' value='Send' onclick=\"cmd('comment " + vars["_id"] + " '+ this.previousSibling.value, '" + submit + "')\">" - "</div>" : "") + - "</div>\n" + "<div class='text'>"+ text +"</div>\n"+ + (show_comments ? + "<div id='comments-"+ id_prefix + vars["_id"] +"' class='comments'>"+ comments[0] + + (submit ? "<a onclick=\"toggleForm(this, "+ vars["_id"] +")\">» reply</a>" : "") + + "</div>\n" : "") + "</div>\n" "<div class='footer'>\n" - "<span class='date'>" + isotime(ctime(vars["_time_place"]), 1) + "</span>\n" + "<span class='date'>"+ isotime(ctime(vars["_time_place"]), 1) +"</span>\n" "<span class='comments-link'>" - "<a onclick=\"toggle('comments-" + id_prefix + vars["_id"] + "')\">" + sizeof(vars["_children"]) + " comments</a>" + "<a " + + (show_comments + ? "onclick=\"toggle('comments-"+ id_prefix + vars["_id"] +"')\"" + : "href='"+ url_prefix +"?id="+ vars["_id"] +"''") + + ">"+ comments[1] +" comments</a>" "</span>\n" "</div>\n" "</div>\n"; } P3((">>> ht: %O\n", ht)) - return "<div class='threads'>" + ht + "</div>"; + return "<div class='threads'>"+ ht +"</div>"; +} + +string htmlForm(int link, int title) { + if (link) + return + "<div class='threads'><a class='add-entry' href='?request=form'>Add entry</a></div>\n"; + return + "<div class='threads'>" + "<div class='entry entry-form'>\n" + "<div class='header'>Add entry</div>\n" + "<div class='body'>\n" + "<form method='post'>\n" + "<input type='hidden' name='request' value='post' />\n" + + (title ? + "Title:<br><input type='text' name='_title'><br>\n" + "Text:<br/>" : "") + + "<textarea name='_text'></textarea>\n" + "<input type='submit' value='Send' />\n" + "</form>\n" + "</div>\n" + "<div class='footer'>\n" + "</div>\n" + "</div>\n" + "</div>\n"; } // TODO: fix markup, not displayed correctly (in firefox at least) @@ -354,10 +432,10 @@ string rssEntries(array(mixed) entries) { rss += "\n<item>\n" "\t<title>"+ (vars["_title"] || "no title") +"\n" - "\thttp://" + HTTP_OR_HTTPS_URL + "/" + pathName() + "?id=" + vars["_id"] + "\n" - "\t" + vars["_text"] + "\n" - "\t" + isotime(ctime(vars["_time_place"]), 1) + "\n" - "\t" + vars["_nick"] + "\n" + "\thttp://"+ HTTP_OR_HTTPS_URL +"/"+ pathName() + "?id="+ vars["_id"] +"\n" + "\t"+ vars["_text"] +"\n" + "\t"+ isotime(ctime(vars["_time_place"]), 1) +"\n" + "\t"+ vars["_nick"] +"\n" "\n"; } @@ -379,14 +457,14 @@ string jsEntries(array(mixed) entries) { mapping entry, vars; foreach (entry : entries) { vars = entry[LOG_VARS]; - js += "new Entry(" + vars["_id"] + "," - "\"" + vars["_title"] + "\"," - "\"" + vars["_nick"] + "\"," - + isotime(ctime(vars["_time_place"]), 1) + "," - "\"" + vars["_text"] + "\"),\n"; + js += "new Entry("+ vars["_id"] +"," + "\""+ vars["_title"] +"\"," + "\""+ vars["_nick"] +"\"," + + isotime(ctime(vars["_time_place"]), 1) +"," + "\""+ vars["_text"] +"\"),\n"; } - return js[..<3] + ");"; + return js[..<3] +");"; } varargs string jsonEntries(int limit, int offset) { @@ -405,20 +483,24 @@ varargs void rssExport(int limit, int offset) { write(rssEntries(entries(limit, offset, 1))); } -varargs string htMain(int limit, int offset, string chan) { - return htmlEntries(entries(limit, offset, 1), 0, chan); +varargs string htMain(int limit, int offset, int submit, string chan) { + return htmlEntries(entries(limit, offset, 1), submit, v("showcomments"), 0, chan); } -varargs void displayMain(int limit, int offset) { - write(htMain(limit, offset)); +varargs void displayMain(int limit, int offset, int submit) { + write(htMain(limit, offset, submit)); } -string htEntry(int id) { - return htmlEntries(entry(id)); +void displayForm(int link, int title) { + write(htmlForm(link, title)); } -void displayEntry(int id) { - write(htEntry(id) || "No such entry."); +string htEntry(int id, int submit) { + return htmlEntries(entry(id), submit, 1); +} + +void displayEntry(int id, int submit) { + write(htEntry(id, submit) || "No such entry."); } // wir können zwei strategien fahren.. die technisch einfachere ist es @@ -430,10 +512,14 @@ void displayEntry(int id) { // halten. womöglich kann man auch nachträglich plan A in plan B // umwandeln..... hmmm -lynX // -void displayHeader() { +void displayHeader(string class) { w("_HTML_head_threads", - "\n"+ - "\n\n"); + "" + "" + ""+ MYNICK +"" + "\n"+ + "\n" + "

"+ MYNICK +"

\n"); } void displayFooter() { w("_HTML_tail_threads", ""); @@ -443,6 +529,7 @@ htget(prot, query, headers, qs, data) { mapping entrymap; mixed target; string nick; + object user; int a; int limit = to_int(query["limit"]) || DEFAULT_BACKLOG; int offset = to_int(query["offset"]); @@ -454,14 +541,14 @@ htget(prot, query, headers, qs, data) { if (query["id"]) { htok(prot); // kommentare + urspruengliche Nachricht anzeigen - displayHeader(); - displayEntry(to_int(query["id"])); + displayHeader("entry"); + displayEntry(to_int(query["id"]), checkToken(query) ? 1 : 0); #if 0 // eingabeformular ohne betreff - write("
\n" + write("\n" "\n" "PSYC Uni:
\n" - "\n" + "\n" "
\n" "\n" "
\n"); @@ -472,61 +559,32 @@ htget(prot, query, headers, qs, data) { return 1; } - // formularbehandlung - if (query["request"] == "post" && query["uni"]) { + if (query["request"] == "post") { htok(prot); - /* - sendmsg uni -> _request_authentication mit thread und text drin - dann auf die antwort warten die nen vars mapping mit thread + text hat wieder - */ - if (nick = legal_name(target = query["uni"])) { - target = summon_person(nick); - nick = target->qNick(); - } else { - nick = target; - // write("Hello " + query["uni"] + "
\n"); - // write("Remote auth doesn't work yet. TODO!!!\n"); - // return 1; + + // TODO: remote user auth + unless (user = checkToken(query)) { + write("Not authenticated!\n"); + return 1; } -#ifdef OWNED - if (canPost(nick)) { -#endif -#if 0 - sendmsg(target, "_request_authentication", "please auth me!", - (["_host_IP" : query_ip_number(), - "_blog_thread" : query["thread"], - "_blog_text" : query["text"] ])); - write("your submit is avaiting authentication by " + query["uni"] + "
\n"); -#endif // 0 - if (target->checkAuthentication(ME, ([ "_host_IP" : query_ip_number() ]) ) > 0) { - // check ob reply auf irgendwas ist... - if (query["reply"]) { - addComment(query["text"], query["uni"], to_int(query["reply"])); - } else { - addEntry(query["text"], query["uni"], query["thread"]); - } - write("authentication successful!\n"); - } else { - write("not authenticated!\n"); - } -#ifdef OWNED - } else { + unless (canPost(query["user"])) { write("You are not owner or aide of this place.\n"); + return 1; } -#endif - return 1; + + object vars = ([]); + // add query params beginning with _ as vars + foreach (string key, string value : query) + if (abbrev("_", key)) vars[key] = value; + + vars["_nick"] = user->qName(); + addEntry(user, vars); } // neuen Eintrag verfassen if (query["request"] == "form") { htok(prot); - displayHeader(); - write("
\n" - "\n" - "PSYC Identity:

\n" - "Thread:

\n" - "Text:
\n
\n" - "\n" - "
\n"); + displayHeader("entry-add"); + displayForm(0, 1); displayFooter(); return 1; } @@ -552,12 +610,13 @@ htget(prot, query, headers, qs, data) { } else { // normaler Export //P2(("all entries: %O\n", _thread)) - htok3(prot, "text/html", "Cache-Control: no-cache\n"); - displayHeader(); + htok3(prot, "text/html", "Cache-Control: no-cache\n"); + displayHeader("entries"); + if ((user = checkToken(query)) && canPost(user->qName())) + displayForm(!v("showform")); // display the blog - displayMain(limit, offset); + displayMain(limit, offset, checkToken(query) ? 1 : 0); // display the chatlog - if (showWebLog()) logView(a < 24 ? a : 12, "html", 15); displayFooter(); } diff --git a/world/net/place/userthreads.c b/world/net/place/userthreads.c index 0c091a8..d7907dc 100644 --- a/world/net/place/userthreads.c +++ b/world/net/place/userthreads.c @@ -24,17 +24,19 @@ volatile object identica; load(name, keep) { P3((">> userthreads:load(%O, %O)\n", name, keep)) + int ret = ::load(name, keep); sscanf(name, "~%s#%s", owner, channel); - vSet("owners", ([ lower_case(owner) ])); - vSet("privacy", "private"); - vSet("twitter", 0); - vSet("identica", 0); + + unless (v("owners")) vSet("owners", ([ lower_case(owner) ])); + unless (v("privacy")) vSet("privacy", "private"); + unless (vExist("twitter")) vSet("twitter", 0); + unless (vExist("identica")) vSet("identica", 0); vSet("_restrict_invitation", BLAME); vSet("_filter_conversation", BLAME); - return ::load(name, keep); + return ret; } enter(source, mc, data, vars) { @@ -127,19 +129,13 @@ _request_remove(source, mc, data, vars, b) { // set privacy: private or public // - private: only friends & invited people can enter (default) // - public: anyone can enter -_request_privacy(source, mc, data, vars, b) { - P3((">> userthreads:_request_privace(%O, %O, %O, %O, %O)\n", source, mc, data, vars, b)) - string value = vars["_value"]; - if (value == "public" || value == "private") { - vSet("privacy", value); - save(); - } - sendmsg(source, "_info_set_place_privacy", "Privacy is: [_value].", (["_value": v("privacy")])); - return 1; +_request_set_privacy(source, mc, data, vars, b) { + return _request_set_default_list(source, mc, data, vars, b, ({"public", "private"})); } #ifdef TWITTER _request_set_twitter(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; string value = vars["_value"]; if (is_true(value)) { unless (twitter) twitter = clone_object(NET_PATH "twitter/client")->load(source); @@ -158,6 +154,7 @@ _request_set_twitter(source, mc, data, vars, b) { #ifdef IDENTICA _request_set_identica(source, mc, data, vars, b) { + unless (qOwner(SNICKER)) return 0; string value = vars["_value"]; if (is_true(value)) { unless (identica) identica = clone_object(NET_PATH "identica/client")->load(source); @@ -174,21 +171,21 @@ _request_set_identica(source, mc, data, vars, b) { } #endif -varargs int addEntry(mixed source, string snicker, string text, string title, int parent_id) { +varargs int addEntry(mixed source, mapping vars, string _data, string _mc) { int ret; - if (ret = ::addEntry(source, snicker, text, title, parent_id)) { + if (ret = ::addEntry(source, vars, _data, _mc)) { #ifdef TWITTER - if (v("twitter") && twitter) twitter->status_update(text); + if (v("twitter") && twitter) twitter->status_update(vars["_text"]); #endif #ifdef IDENTICA - if (v("identica") && identica) identica->status_update(text); + if (v("identica") && identica) identica->status_update(vars["_text"]); #endif } return ret; } -htMain(int limit, int offset) { - return ::htMain(limit, offset, channel); +htMain(int limit, int offset, int submit) { + return ::htMain(limit, offset, submit, channel); } canPost(snicker) { @@ -207,7 +204,7 @@ qChannel() { return channel; } -qHistoryGlimpse() { +int qHistoryGlimpse() { return HISTORY_GLIMPSE; } diff --git a/world/net/storage.c b/world/net/storage.c index 94ac052..65deb70 100644 --- a/world/net/storage.c +++ b/world/net/storage.c @@ -68,6 +68,10 @@ vInc(k, howmuch) { } // vDec(k) { _v[k]--; } +vExist(k) { + return member(_v, k); +} + vEmpty() { return sizeof(_v) == 0; } vSize() { return sizeof(_v); } diff --git a/world/net/user.c b/world/net/user.c index 6aa7295..e7f2bde 100644 --- a/world/net/user.c +++ b/world/net/user.c @@ -319,7 +319,7 @@ htDescription(anonymous, query, headers, qs, variant, vars) { + (first ? "class='selected'" : "") + ">#" + channel + "
\n"; contents += "
" + threads->htmlEntries(entries, !first, channel, anonymous ? "" : vars["_identification"] + "#" + channel, vars["_profile_url"] + "/" + channel) + "
\n"; + + (first ? "class='selected'" : "") + ">" + threads->htmlEntries(entries, 1, 1, !first, channel, anonymous ? "" : vars["_identification"] + "#" + channel, vars["_profile_url"] + "/" + channel) + "\n"; first = 0; } @@ -1322,7 +1322,7 @@ w(string mc, string data, mapping vars, mixed source, int showingLog) { else t = vars["_time_log"] || vars["_time_place"]; // would be nicer to have _time_log in /log rather than showingLog if (!t && showingLog) t = vars["_time_INTERNAL"]; - if (t && intp(t)) di["_prefix"] = time_or_date(t) +" "; + if (t && intp(t)) di["_prefix"] = time_or_date(t); #if 0 template = T(di["_method"] || mc, 0); #else @@ -1448,7 +1448,12 @@ w(string mc, string data, mapping vars, mixed source, int showingLog) { // who is that output for anyway? output = psyctext(template, vars, data, source); - if (di["_prefix"]) output = di["_prefix"]+output; + if (di["_prefix"]) { + if (vars["_postfix_time_log"]) + output += " " + di["_prefix"]; + else + output = di["_prefix"] +" "+ output; + } if (output) { if (template == "") { diff --git a/world/static/examine.css b/world/static/examine.css index 40e919d..3a0d1e2 100644 --- a/world/static/examine.css +++ b/world/static/examine.css @@ -19,7 +19,7 @@ body.threads, padding: 4; border: 1px solid #f33; } -.comment-submit textarea, .comment-submit input, +.entry textarea, .entry input, .Pe form input,select,textarea { background: black; color: #f33; @@ -74,6 +74,7 @@ body.threads, width: 400; } +a.add-entry, .entry { margin: 22px 44px; } @@ -102,33 +103,54 @@ body.threads, margin: 0 1em; } -.entry .body .comment-submit input, -.entry .body .comment-submit textarea { +.entry .body .comment-form-toggle { + float: right; +} + +.entry input, +.entry textarea { font-size: 12px; } -.entry .body .comment-submit textarea { +.entry input[type=text], +.entry textarea { width: 100%; } +body.entry-add textarea { + height: 40em; +} + +.entry form { + margin: 0.2em 0; + padding: 0; +} + +h1 a, +.entry a { + cursor: pointer; + text-decoration: none; +} +.entry a:hover { + text-decoration: underline; +} + .entry .header a { color: black; } .entry .header .author {} .entry .header .title {} +.entry .body a { + color: #f33; +} + +h1 a, +a.add-entry, .entry .footer a, .entry .footer a:visited { color: white; } -.entry a { - cursor: pointer; - text-decoration: none; -} -.entry a:hover { - text-decoration: underline; -} - .entry .footer .comments-link { float: right; }