From c842f30f87693e41cd3cd19fdffb19706cd3b05f Mon Sep 17 00:00:00 2001 From: inga-lovinde <52715130+inga-lovinde@users.noreply.github.com> Date: Thu, 1 Jul 2010 18:34:30 +0000 Subject: [PATCH] Private messages implemented --- Builder/IISMainHandler/build.txt | 2 +- Builder/IISUploadHandler/build.txt | 2 +- Common/Common.csproj | 3 + Common/SqlObject.cs | 2 +- Common/UBBParser.cs | 3 +- Common/actions/ChangeSet.cs | 2 + Common/actions/IncrementFieldValue.cs | 14 +- Common/actions/TwoWayReferenceFieldValue.cs | 7 + Common/dataobjects/Account.cs | 34 ++ Common/dataobjects/Board.cs | 5 +- Common/dataobjects/PMConversation.cs | 385 ++++++++++++++++++ Common/dataobjects/PMMessage.cs | 199 +++++++++ Common/dataobjects/Thread.cs | 17 +- IISMainHandler/HandlersFactory.cs | 12 + IISMainHandler/IISMainHandler.csproj | 6 + IISMainHandler/handlers/AbstractGetHandler.cs | 4 +- IISMainHandler/handlers/BoardHandler.cs | 2 +- .../handlers/request/AbstractPostHandler.cs | 2 +- .../handlers/request/SendPMHandler.cs | 40 ++ .../handlers/response/BoardAsThread.cs | 2 +- .../handlers/response/ConversationHandler.cs | 80 ++++ .../handlers/response/ConversationsHandler.cs | 37 ++ .../handlers/response/PMReplyHandler.cs | 30 ++ .../handlers/response/PMReplyToPostHandler.cs | 33 ++ .../handlers/response/PMSendHandler.cs | 26 ++ static/images/newpm.gif | Bin 0 -> 146 bytes templates/Full/Conversation.xslt | 93 +++++ templates/Full/Conversations.xslt | 92 +++++ templates/Full/PMReply.xslt | 107 +++++ templates/Full/PMReplyToPost.xslt | 104 +++++ templates/Full/PMSend.xslt | 56 +++ templates/Full/elems/ConversationInfo.xslt | 58 +++ templates/Full/elems/Header.xslt | 18 +- templates/Full/elems/PMInfo.xslt | 105 +++++ templates/Full/elems/PostInfo.xslt | 3 + templates/Full/result/PMSent.xslt | 30 ++ 36 files changed, 1584 insertions(+), 31 deletions(-) create mode 100644 Common/dataobjects/PMConversation.cs create mode 100644 Common/dataobjects/PMMessage.cs create mode 100644 IISMainHandler/handlers/request/SendPMHandler.cs create mode 100644 IISMainHandler/handlers/response/ConversationHandler.cs create mode 100644 IISMainHandler/handlers/response/ConversationsHandler.cs create mode 100644 IISMainHandler/handlers/response/PMReplyHandler.cs create mode 100644 IISMainHandler/handlers/response/PMReplyToPostHandler.cs create mode 100644 IISMainHandler/handlers/response/PMSendHandler.cs create mode 100644 static/images/newpm.gif create mode 100644 templates/Full/Conversation.xslt create mode 100644 templates/Full/Conversations.xslt create mode 100644 templates/Full/PMReply.xslt create mode 100644 templates/Full/PMReplyToPost.xslt create mode 100644 templates/Full/PMSend.xslt create mode 100644 templates/Full/elems/ConversationInfo.xslt create mode 100644 templates/Full/elems/PMInfo.xslt create mode 100644 templates/Full/result/PMSent.xslt diff --git a/Builder/IISMainHandler/build.txt b/Builder/IISMainHandler/build.txt index bc56e76..a8114de 100644 --- a/Builder/IISMainHandler/build.txt +++ b/Builder/IISMainHandler/build.txt @@ -1 +1 @@ -431 \ No newline at end of file +462 \ No newline at end of file diff --git a/Builder/IISUploadHandler/build.txt b/Builder/IISUploadHandler/build.txt index 2efea51..8e24a69 100644 --- a/Builder/IISUploadHandler/build.txt +++ b/Builder/IISUploadHandler/build.txt @@ -1 +1 @@ -167 \ No newline at end of file +198 \ No newline at end of file diff --git a/Common/Common.csproj b/Common/Common.csproj index 47023da..9a50095 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -35,6 +35,7 @@ 3.5 + 3.5 @@ -63,6 +64,8 @@ + + diff --git a/Common/SqlObject.cs b/Common/SqlObject.cs index 7e99404..a0dac00 100644 --- a/Common/SqlObject.cs +++ b/Common/SqlObject.cs @@ -53,7 +53,7 @@ namespace FLocal.Common { this.fromHash(Config.instance.mainConnection.LoadById(this.table, this.id.ToString())); } - protected void Load() { + private void Load() { lock(this.lockInitializer) { if(this.isLoaded) throw new CriticalException("already initialized"); this.doLoad(); diff --git a/Common/UBBParser.cs b/Common/UBBParser.cs index c6f2259..838c7e5 100644 --- a/Common/UBBParser.cs +++ b/Common/UBBParser.cs @@ -2,12 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Web; namespace FLocal.Common { public static class UBBParser { public static string UBBToIntermediate(string UBB) { - return UBB; + return HttpUtility.HtmlEncode(UBB).Replace("\r\n", "
\r\n"); } public static string ShallerToUBB(string shaller) { diff --git a/Common/actions/ChangeSet.cs b/Common/actions/ChangeSet.cs index 1cb2256..2006e74 100644 --- a/Common/actions/ChangeSet.cs +++ b/Common/actions/ChangeSet.cs @@ -27,6 +27,8 @@ namespace FLocal.Common.actions { dataobjects.User.TableSpec.TABLE, dataobjects.AccountSettings.TableSpec.TABLE, dataobjects.AccountIndicator.TableSpec.TABLE, + dataobjects.PMConversation.TableSpec.TABLE, + dataobjects.PMMessage.TableSpec.TABLE, dataobjects.Thread.ReadMarkerTableSpec.TABLE, dataobjects.Board.ReadMarkerTableSpec.TABLE, dataobjects.Session.TableSpec.TABLE, diff --git a/Common/actions/IncrementFieldValue.cs b/Common/actions/IncrementFieldValue.cs index cf739e1..8b611c7 100644 --- a/Common/actions/IncrementFieldValue.cs +++ b/Common/actions/IncrementFieldValue.cs @@ -6,21 +6,23 @@ using System.Text; namespace FLocal.Common.actions { class IncrementFieldValue : AbstractFieldValue { - private readonly Func incrementor; + public static readonly Func INCREMENTOR = s => (int.Parse(s)+1).ToString(); + public static readonly Func DECREMENTOR = s => (int.Parse(s)-1).ToString(); - public IncrementFieldValue(Func incrementor) { - this.incrementor = incrementor; + private readonly Func processor; + + public IncrementFieldValue(Func processor) { + this.processor = processor; } - public IncrementFieldValue() - : this(str => (int.Parse(str)+1).ToString()) { + public IncrementFieldValue() : this(INCREMENTOR) { } public override string getStringRepresentation() { throw new NotSupportedException(); } public override string getStringRepresentation(string oldInfo) { - return this.incrementor(oldInfo); + return this.processor(oldInfo); } } diff --git a/Common/actions/TwoWayReferenceFieldValue.cs b/Common/actions/TwoWayReferenceFieldValue.cs index 97efc60..8da875f 100644 --- a/Common/actions/TwoWayReferenceFieldValue.cs +++ b/Common/actions/TwoWayReferenceFieldValue.cs @@ -9,6 +9,13 @@ namespace FLocal.Common.actions { public delegate string Calculator(string old, string reference); + public static Calculator GREATEST = (old, reference) => { + if(old == null || old == "") { + return reference; + } + return Math.Max(int.Parse(old), int.Parse(reference)).ToString(); + }; + private Calculator calculator; public TwoWayReferenceFieldValue(AbstractChange referenced, Calculator calculator) diff --git a/Common/dataobjects/Account.cs b/Common/dataobjects/Account.cs index 8d3a9f2..5eb46da 100644 --- a/Common/dataobjects/Account.cs +++ b/Common/dataobjects/Account.cs @@ -61,6 +61,13 @@ namespace FLocal.Common.dataobjects { this._needsMigration = Util.string2bool(data[TableSpec.FIELD_NEEDSMIGRATION]); } + public XElement exportToXml(UserContext context) { + return new XElement("account", + new XElement("id", this.id), + this.user.exportToXmlForViewing(context) + ); + } + private static Dictionary name2id = new Dictionary(); public static Account LoadByName(string _name) { string name = _name.ToLower(); @@ -90,6 +97,33 @@ namespace FLocal.Common.dataobjects { return Account.LoadById(name2id[name]); } + private static Dictionary userid2id = new Dictionary(); + public static Account LoadByUser(User user) { + if(!userid2id.ContainsKey(user.id)) { + lock(userid2id) { + if(!userid2id.ContainsKey(user.id)) { + List ids = Config.instance.mainConnection.LoadIdsByConditions( + TableSpec.instance, + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_USERID), + ComparisonType.EQUAL, + user.id.ToString() + ), + Diapasone.unlimited + ); + if(ids.Count > 1) { + throw new CriticalException("not unique"); + } else if(ids.Count == 1) { + userid2id[user.id] = int.Parse(ids[0]); + } else { + throw new NotFoundInDBException(); + } + } + } + } + return Account.LoadById(userid2id[user.id]); + } + private string hashPassword(string password) { return Util.md5(password + " " + Config.instance.SaltMigration + " " + this.id); } diff --git a/Common/dataobjects/Board.cs b/Common/dataobjects/Board.cs index 1ad95b1..0ad92ad 100644 --- a/Common/dataobjects/Board.cs +++ b/Common/dataobjects/Board.cs @@ -216,7 +216,7 @@ namespace FLocal.Common.dataobjects { return result; } - public IEnumerable getThreads(Diapasone diapasone, UserContext context, SortSpec[] sortBy) { + public IEnumerable getThreads(Diapasone diapasone, SortSpec[] sortBy) { return Thread.LoadByIds( from stringId in Config.instance.mainConnection.LoadIdsByConditions( Thread.TableSpec.instance, @@ -232,10 +232,9 @@ namespace FLocal.Common.dataobjects { ); } - public IEnumerable getThreads(Diapasone diapasone, UserContext context) { + public IEnumerable getThreads(Diapasone diapasone) { return this.getThreads( diapasone, - context, new SortSpec[] { new SortSpec( Thread.TableSpec.instance.getColumnSpec(Thread.TableSpec.FIELD_ISANNOUNCEMENT), diff --git a/Common/dataobjects/PMConversation.cs b/Common/dataobjects/PMConversation.cs new file mode 100644 index 0000000..5e58a9f --- /dev/null +++ b/Common/dataobjects/PMConversation.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using FLocal.Core; +using FLocal.Core.DB; +using FLocal.Core.DB.conditions; +using FLocal.Common.actions; + +namespace FLocal.Common.dataobjects { + public class PMConversation : SqlObject { + + public class TableSpec : ISqlObjectTableSpec { + public const string TABLE = "PMConversations"; + public const string FIELD_ID = "Id"; + public const string FIELD_OWNERID = "OwnerAccountId"; + public const string FIELD_INTERLOCUTORID = "InterlocutorAccountId"; + public const string FIELD_TOTALMESSAGES = "TotalMessages"; + public const string FIELD_LASTMESSAGEID = "LastMessageId"; + public const string FIELD_LASTMESSAGEDATE = "LastMessageDate"; + public const string FIELD_LASTREADMESSAGEID = "LastReadMessageId"; + public static readonly TableSpec instance = new TableSpec(); + public string name { get { return TABLE; } } + public string idName { get { return FIELD_ID; } } + public void refreshSqlObject(int id) { Refresh(id); } + } + + protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } } + + private int _ownerId; + public int ownerId { + get { + this.LoadIfNotLoaded(); + return this._ownerId; + } + } + public Account owner { + get { + return Account.LoadById(this.ownerId); + } + } + + private int _interlocutorId; + public int interlocutorId { + get { + this.LoadIfNotLoaded(); + return this._interlocutorId; + } + } + public Account interlocutor { + get { + return Account.LoadById(this.interlocutorId); + } + } + + private int _totalMessages; + public int totalMessages { + get { + this.LoadIfNotLoaded(); + return this._totalMessages; + } + } + + private int _lastMessageId; + public int lastMessageId { + get { + this.LoadIfNotLoaded(); + return this._lastMessageId; + } + } + public PMMessage lastMessage { + get { + return PMMessage.LoadById(this.lastMessageId); + } + } + + private DateTime _lastMessageDate; + public DateTime lastMessageDate { + get { + this.LoadIfNotLoaded(); + return this._lastMessageDate; + } + } + + private int? _lastReadMessageId; + public int? lastReadMessageId { + get { + this.LoadIfNotLoaded(); + return this._lastReadMessageId; + } + } + public PMMessage lastReadMessage { + get { + return PMMessage.LoadById(this.lastReadMessageId.Value); + } + } + + protected override void doFromHash(Dictionary data) { + this._ownerId = int.Parse(data[TableSpec.FIELD_OWNERID]); + this._interlocutorId = int.Parse(data[TableSpec.FIELD_INTERLOCUTORID]); + this._totalMessages = int.Parse(data[TableSpec.FIELD_TOTALMESSAGES]); + this._lastMessageId = int.Parse(data[TableSpec.FIELD_LASTMESSAGEID]); + this._lastMessageDate = new DateTime(long.Parse(data[TableSpec.FIELD_LASTMESSAGEDATE])); + this._lastReadMessageId = Util.ParseInt(data[TableSpec.FIELD_LASTREADMESSAGEID]); + } + + public XElement exportToXmlSimpleWithParent(UserContext context) { + return new XElement("conversation", + new XElement("id", this.id), + new XElement("name", this.interlocutor.user.name) + ); + } + + public XElement exportToXml(UserContext context, bool includeFirstPost, params XElement[] additional) { + if((context.account == null) || (context.account.id != this.owner.id)) throw new AccessViolationException(); + XElement result = new XElement("conversation", + new XElement("id", this.id), + new XElement("owner", this.owner.exportToXml(context)), + new XElement("interlocutor", this.interlocutor.exportToXml(context)), + new XElement("totalMessages", this.totalMessages), + new XElement("lastMessageId", this.lastMessageId), + new XElement("lastMessageDate", this.lastMessageDate.ToXml()), + new XElement("lastReadMessageId", this.lastReadMessageId), + new XElement("afterLastRead", this.lastReadMessageId + 1), + new XElement( + "totalNewMessages", + Config.instance.mainConnection.GetCountByConditions( + PMMessage.TableSpec.instance, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + this.ownerId.ToString() + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID), + ComparisonType.EQUAL, + this.interlocutorId.ToString() + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getIdSpec(), + ComparisonType.GREATERTHAN, + (this.lastReadMessageId.HasValue ? this.lastReadMessageId.Value : 0).ToString() + ) + ) + ) + ), + context.formatTotalPosts(this.totalMessages) + ); + if(additional.Length > 0) { + result.Add(additional); + } + return result; + } + + public IEnumerable getMessages(Diapasone diapasone, UserContext context) { + return PMMessage.LoadByIds( + from stringId in Config.instance.mainConnection.LoadIdsByConditions( + PMMessage.TableSpec.instance, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + this.ownerId.ToString() + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID), + ComparisonType.EQUAL, + this.interlocutorId.ToString() + ) + ), + diapasone, + new JoinSpec[0], + new SortSpec[] { + new SortSpec( + PMMessage.TableSpec.instance.getIdSpec(), + true + ), + } + ) select int.Parse(stringId) + ); + } + + public void markAsRead(Account account, PMMessage minMessage, PMMessage maxMessage) { + if(this.ownerId != account.id) throw new AccessViolationException(); + ChangeSetUtil.ApplyChanges(new AbstractChange[] { + new UpdateChange( + TableSpec.instance, + new Dictionary { + { + TableSpec.FIELD_LASTREADMESSAGEID, + new IncrementFieldValue( + s => { + if((s == null) || (s == "")) { + s = "0"; //workaround + } + if(maxMessage.id < int.Parse(s)) { + return (s == "0") ? null : s; //if some newer posts were already read + } + long count = Config.instance.mainConnection.GetCountByConditions( + PMMessage.TableSpec.instance, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + this.ownerId.ToString() + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID), + ComparisonType.EQUAL, + this.interlocutorId.ToString() + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getIdSpec(), + ComparisonType.GREATERTHAN, + s + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getIdSpec(), + ComparisonType.LESSTHAN, + minMessage.id.ToString() + ) + ) + ); + if(count > 0) { + return (s == "0") ? null : s; //if there are some unread posts earlier than minPost + } else { + return maxMessage.id.ToString(); + } + } + ) + } + }, + this.id + ) + }); + } + + public static IEnumerable getConversations(Account owner, Diapasone diapasone) { + return LoadByIds( + from stringId in Config.instance.mainConnection.LoadIdsByConditions( + TableSpec.instance, + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + owner.id.ToString() + ), + diapasone + ) select int.Parse(stringId) + ); + } + + public static PMConversation LoadByAccounts(Account owner, Account interlocutor) { + return LoadById( + int.Parse(Config.instance.mainConnection.LoadIdsByConditions( + TableSpec.instance, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + owner.id.ToString() + ), + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID), + ComparisonType.EQUAL, + interlocutor.id.ToString() + ) + ), + Diapasone.unlimited + ).Single()) + ); + } + + public static PMMessage SendPMMessage(Account sender, Account receiver, string title, string bodyUBB) { + string bodyIntermediate = UBBParser.UBBToIntermediate(bodyUBB); + AbstractChange insertPmReceiver = new InsertChange( + PMMessage.TableSpec.instance, + new Dictionary { + { PMMessage.TableSpec.FIELD_OWNERID, new ScalarFieldValue(receiver.id.ToString()) }, + { PMMessage.TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(sender.id.ToString()) }, + { PMMessage.TableSpec.FIELD_DIRECTION, new ScalarFieldValue(PMMessage.ENUM_DIRECTION_INCOMING) }, + { PMMessage.TableSpec.FIELD_POSTDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) }, + { PMMessage.TableSpec.FIELD_TITLE, new ScalarFieldValue(title) }, + { PMMessage.TableSpec.FIELD_BODY, new ScalarFieldValue(bodyIntermediate) }, + { PMMessage.TableSpec.FIELD_BODYUBB, new ScalarFieldValue(bodyUBB) }, + { PMMessage.TableSpec.FIELD_INCOMINGPMID, new ScalarFieldValue(null) }, + { PMMessage.TableSpec.FIELD_ISREAD, new ScalarFieldValue("0") }, + } + ); + AbstractChange insertPmSender = new InsertChange( + PMMessage.TableSpec.instance, + new Dictionary { + { PMMessage.TableSpec.FIELD_OWNERID, new ScalarFieldValue(sender.id.ToString()) }, + { PMMessage.TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(receiver.id.ToString()) }, + { PMMessage.TableSpec.FIELD_DIRECTION, new ScalarFieldValue(PMMessage.ENUM_DIRECTION_OUTGOING) }, + { PMMessage.TableSpec.FIELD_POSTDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) }, + { PMMessage.TableSpec.FIELD_TITLE, new ScalarFieldValue(title) }, + { PMMessage.TableSpec.FIELD_BODY, new ScalarFieldValue(bodyIntermediate) }, + { PMMessage.TableSpec.FIELD_BODYUBB, new ScalarFieldValue(bodyUBB) }, + { PMMessage.TableSpec.FIELD_INCOMINGPMID, new ReferenceFieldValue(insertPmReceiver) }, + { PMMessage.TableSpec.FIELD_ISREAD, new ScalarFieldValue("1") }, + } + ); + AbstractChange updateConversationSender = new InsertOrUpdateChange( + TableSpec.instance, + new Dictionary { + { TableSpec.FIELD_OWNERID, new ScalarFieldValue(sender.id.ToString()) }, + { TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(receiver.id.ToString()) }, + { TableSpec.FIELD_TOTALMESSAGES, new ScalarFieldValue("1") }, + { TableSpec.FIELD_LASTMESSAGEID, new ReferenceFieldValue(insertPmSender) }, + { TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) }, + { TableSpec.FIELD_LASTREADMESSAGEID, new ReferenceFieldValue(insertPmSender) }, + }, + new Dictionary { + { TableSpec.FIELD_TOTALMESSAGES, new IncrementFieldValue() }, + { TableSpec.FIELD_LASTMESSAGEID, new TwoWayReferenceFieldValue(insertPmSender, TwoWayReferenceFieldValue.GREATEST) }, + }, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + sender.id.ToString() + ), + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID), + ComparisonType.EQUAL, + receiver.id.ToString() + ) + ) + ); + AbstractChange updateConversationReceiver = new InsertOrUpdateChange( + TableSpec.instance, + new Dictionary { + { TableSpec.FIELD_OWNERID, new ScalarFieldValue(receiver.id.ToString()) }, + { TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(sender.id.ToString()) }, + { TableSpec.FIELD_TOTALMESSAGES, new ScalarFieldValue("1") }, + { TableSpec.FIELD_LASTMESSAGEID, new ReferenceFieldValue(insertPmReceiver) }, + { TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) }, + { TableSpec.FIELD_LASTREADMESSAGEID, new ScalarFieldValue(null) }, + }, + new Dictionary { + { TableSpec.FIELD_TOTALMESSAGES, new IncrementFieldValue() }, + { TableSpec.FIELD_LASTMESSAGEID, new TwoWayReferenceFieldValue(insertPmReceiver, TwoWayReferenceFieldValue.GREATEST) }, + }, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + receiver.id.ToString() + ), + new ComparisonCondition( + TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID), + ComparisonType.EQUAL, + sender.id.ToString() + ) + ) + ); + AbstractChange updateIndicatorReceiver = new UpdateChange( + AccountIndicator.TableSpec.instance, + new Dictionary { + { AccountIndicator.TableSpec.FIELD_PRIVATEMESSAGES, new IncrementFieldValue() }, + { AccountIndicator.TableSpec.FIELD_UNREADPRIVATEMESSAGES, new IncrementFieldValue() }, + }, + AccountIndicator.LoadByAccount(receiver).id + ); + ChangeSetUtil.ApplyChanges( + insertPmReceiver, + insertPmSender, + updateConversationReceiver, + updateConversationSender, + updateIndicatorReceiver + ); + return PMMessage.LoadById(insertPmSender.getId().Value); + } + + } + +} diff --git a/Common/dataobjects/PMMessage.cs b/Common/dataobjects/PMMessage.cs new file mode 100644 index 0000000..bd9c715 --- /dev/null +++ b/Common/dataobjects/PMMessage.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using FLocal.Core; +using FLocal.Core.DB; +using FLocal.Common; +using FLocal.Common.actions; + +namespace FLocal.Common.dataobjects { + public class PMMessage : SqlObject { + + public const string ENUM_DIRECTION_INCOMING = "Incoming"; + public const string ENUM_DIRECTION_OUTGOING = "Outgoing"; + + public class TableSpec : ISqlObjectTableSpec { + public const string TABLE = "PMMessages"; + public const string FIELD_ID = "Id"; + public const string FIELD_OWNERID = "OwnerId"; + public const string FIELD_INTERLOCUTORID = "InterlocutorId"; + public const string FIELD_DIRECTION = "Direction"; + public const string FIELD_POSTDATE = "PostDate"; + public const string FIELD_TITLE = "Title"; + public const string FIELD_BODY = "Body"; + public const string FIELD_BODYUBB = "BodyUBB"; + public const string FIELD_INCOMINGPMID = "IncomingPMId"; + public const string FIELD_ISREAD = "IsRead"; + public static readonly TableSpec instance = new TableSpec(); + public string name { get { return TABLE; } } + public string idName { get { return FIELD_ID; } } + public void refreshSqlObject(int id) { Refresh(id); } + } + + protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } } + + private int _ownerId; + public int ownerId { + get { + this.LoadIfNotLoaded(); + return this._ownerId; + } + } + public Account owner { + get { + return Account.LoadById(this.ownerId); + } + } + + private int _interlocutorId; + public int interlocutorId { + get { + this.LoadIfNotLoaded(); + return this._interlocutorId; + } + } + public Account interlocutor { + get { + return Account.LoadById(this.interlocutorId); + } + } + + public Account poster { + get { + if(this.direction == ENUM_DIRECTION_INCOMING) { + return this.interlocutor; + } else { + return this.owner; + } + } + } + + private string _direction; + public string direction { + get { + this.LoadIfNotLoaded(); + return this._direction; + } + } + + private DateTime _postDate; + public DateTime postDate { + get { + this.LoadIfNotLoaded(); + return this._postDate; + } + } + + private string _title; + public string title { + get { + this.LoadIfNotLoaded(); + return this._title; + } + } + + private string _body; + public string body { + get { + this.LoadIfNotLoaded(); + return this._body; + } + } + + private string _bodyUBB; + public string bodyUBB { + get { + this.LoadIfNotLoaded(); + return this._bodyUBB; + } + } + + private int? _incomingPMId; + public int? incomingPMId { + get { + this.LoadIfNotLoaded(); + return this._incomingPMId; + } + } + + private bool _isRead; + public bool isRead { + get { + this.LoadIfNotLoaded(); + return this._isRead; + } + } + + public PMConversation conversation { + get { + return PMConversation.LoadByAccounts(this.owner, this.interlocutor); + } + } + + protected override void doFromHash(Dictionary data) { + this._ownerId = int.Parse(data[TableSpec.FIELD_OWNERID]); + this._interlocutorId = int.Parse(data[TableSpec.FIELD_INTERLOCUTORID]); + this._direction = data[TableSpec.FIELD_DIRECTION]; + this._postDate = Util.ParseDateTimeFromTimestamp(data[TableSpec.FIELD_POSTDATE]).Value; + this._title = data[TableSpec.FIELD_TITLE]; + this._body = data[TableSpec.FIELD_BODY]; + this._bodyUBB = data[TableSpec.FIELD_BODYUBB]; + this._incomingPMId = Util.ParseInt(data[TableSpec.FIELD_INCOMINGPMID]); + this._isRead = Util.string2bool(data[TableSpec.FIELD_ISREAD]); + } + + public XElement exportToXml(UserContext context, params XElement[] additional) { + if((context.account == null) || (context.account.id != this.owner.id)) { + throw new AccessViolationException(); + } + + XElement result = new XElement("message", + new XElement("id", this.id), + new XElement("poster", this.poster.exportToXml(context)), + new XElement("owner", this.owner.exportToXml(context)), + new XElement("interlocutor", this.interlocutor.exportToXml(context)), + new XElement("postDate", this.postDate.ToXml()), + new XElement("title", this.title), + new XElement("body", context.outputParams.preprocessBodyIntermediate(this.body)), + new XElement("bodyUBB", this.bodyUBB) + ); + if(additional.Length > 0) { + result.Add(additional); + } + return result; + } + + private readonly object MarkAsRead_locker = new object(); + public void MarkAsRead(Account account) { + if(account.id != this.owner.id) throw new AccessViolationException(); + if(!this.isRead) { + lock(MarkAsRead_locker) { + //so we can safely decrease ReadPrivateMessages counter + //Note that this code implicitly uses assumption of single-instance Common.dll; race condition is possible with more than one server + if(!this.isRead) { + AccountIndicator indicator = AccountIndicator.LoadByAccount(this.owner); + ChangeSetUtil.ApplyChanges( + new UpdateChange( + TableSpec.instance, + new Dictionary { + { TableSpec.FIELD_ISREAD, new ScalarFieldValue("1") }, + }, + this.id + ), + new UpdateChange( + AccountIndicator.TableSpec.instance, + new Dictionary { + { AccountIndicator.TableSpec.FIELD_UNREADPRIVATEMESSAGES, new IncrementFieldValue(IncrementFieldValue.DECREMENTOR) }, + }, + indicator.id + ) + ); + } + } + } + } + + } +} diff --git a/Common/dataobjects/Thread.cs b/Common/dataobjects/Thread.cs index 1385a02..9bb1db9 100644 --- a/Common/dataobjects/Thread.cs +++ b/Common/dataobjects/Thread.cs @@ -98,6 +98,11 @@ namespace FLocal.Common.dataobjects { return this._lastPostId; } } + public Post lastPost { + get { + return Post.LoadById(this.lastPostId); + } + } private DateTime _lastPostDate; public DateTime lastPostDate { @@ -392,17 +397,7 @@ namespace FLocal.Common.dataobjects { { Thread.TableSpec.FIELD_TOTALPOSTS, new IncrementFieldValue() }, { Thread.TableSpec.FIELD_LASTPOSTID, - new TwoWayReferenceFieldValue( - postInsert, - (oldStringId, newStringId) => { - if((oldStringId == null) || (oldStringId == "")) { - return newStringId; - } - int oldId = int.Parse(oldStringId); - int newId = int.Parse(newStringId); - return Math.Max(oldId, newId).ToString(); - } - ) + new TwoWayReferenceFieldValue(postInsert, TwoWayReferenceFieldValue.GREATEST) } }; if(isNewThread) { diff --git a/IISMainHandler/HandlersFactory.cs b/IISMainHandler/HandlersFactory.cs index 52bd72d..191f470 100644 --- a/IISMainHandler/HandlersFactory.cs +++ b/IISMainHandler/HandlersFactory.cs @@ -51,6 +51,8 @@ namespace FLocal.IISHandler { switch(context.requestParts[2].ToLower()) { case "reply": return new handlers.response.ReplyHandler(); + case "pmreply": + return new handlers.response.PMReplyToPostHandler(); default: return new handlers.WrongUrlHandler(); } @@ -64,6 +66,14 @@ namespace FLocal.IISHandler { return new handlers.response.UserInfoHandler(); case "settings": return new handlers.response.SettingsHandler(); + case "conversations": + return new handlers.response.ConversationsHandler(); + case "conversation": + return new handlers.response.ConversationHandler(); + case "pmsend": + return new handlers.response.PMSendHandler(); + case "pmreply": + return new handlers.response.PMReplyHandler(); case "upload": if(context.requestParts.Length < 2) { return new handlers.WrongUrlHandler(); @@ -97,6 +107,8 @@ namespace FLocal.IISHandler { return new handlers.request.CreateThreadHandler(); case "settings": return new handlers.request.SettingsHandler(); + case "sendpm": + return new handlers.request.SendPMHandler(); case "upload": return new handlers.request.UploadHandler(); default: diff --git a/IISMainHandler/IISMainHandler.csproj b/IISMainHandler/IISMainHandler.csproj index 4a3caca..1751623 100644 --- a/IISMainHandler/IISMainHandler.csproj +++ b/IISMainHandler/IISMainHandler.csproj @@ -65,13 +65,19 @@ + + + + + + diff --git a/IISMainHandler/handlers/AbstractGetHandler.cs b/IISMainHandler/handlers/AbstractGetHandler.cs index 54b898f..d00e64a 100644 --- a/IISMainHandler/handlers/AbstractGetHandler.cs +++ b/IISMainHandler/handlers/AbstractGetHandler.cs @@ -17,10 +17,10 @@ namespace FLocal.IISHandler.handlers { private XDocument getData(WebContext context) { return new XDocument( new XElement("root", + this.getSpecificData(context), new XElement("title", Config.instance.AppInfo), context.exportSession(), - context.userSettings.skin.exportToXml(), - this.getSpecificData(context) + context.userSettings.skin.exportToXml() ) ); } diff --git a/IISMainHandler/handlers/BoardHandler.cs b/IISMainHandler/handlers/BoardHandler.cs index 6fb953a..3692550 100644 --- a/IISMainHandler/handlers/BoardHandler.cs +++ b/IISMainHandler/handlers/BoardHandler.cs @@ -22,7 +22,7 @@ namespace FLocal.IISHandler.handlers { override protected XElement[] getSpecificData(WebContext context) { Board board = Board.LoadById(int.Parse(context.requestParts[1])); PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.threadsPerPage, 2); - IEnumerable threads = board.getThreads(pageOuter, context); + IEnumerable threads = board.getThreads(pageOuter); XElement[] result = new XElement[] { new XElement("currentLocation", board.exportToXmlSimpleWithParent(context)), new XElement("boards", from subBoard in board.subBoards select subBoard.exportToXml(context, true)), diff --git a/IISMainHandler/handlers/request/AbstractPostHandler.cs b/IISMainHandler/handlers/request/AbstractPostHandler.cs index 8e75f94..49fd80a 100644 --- a/IISMainHandler/handlers/request/AbstractPostHandler.cs +++ b/IISMainHandler/handlers/request/AbstractPostHandler.cs @@ -30,8 +30,8 @@ namespace FLocal.IISHandler.handlers.request { private XDocument getData(WebContext context) { return new XDocument( new XElement("root", - new XElement("title", Config.instance.AppInfo), this.Do(context), + new XElement("title", Config.instance.AppInfo), context.userSettings.skin.exportToXml(), context.exportSession() ) diff --git a/IISMainHandler/handlers/request/SendPMHandler.cs b/IISMainHandler/handlers/request/SendPMHandler.cs new file mode 100644 index 0000000..4205e4b --- /dev/null +++ b/IISMainHandler/handlers/request/SendPMHandler.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using FLocal.Common.dataobjects; + +namespace FLocal.IISHandler.handlers.request { + + class SendPMHandler : AbstractNewMessageHandler { + + protected override string templateName { + get { + return "result/PMSent.xslt"; + } + } + + protected override XElement[] Do(WebContext context) { + Account receiver; + if(context.httprequest.Form.AllKeys.Contains("receiverId")) { + receiver = Account.LoadById(int.Parse(context.httprequest.Form["receiverId"])); + } else if(context.httprequest.Form.AllKeys.Contains("receiverName")) { + receiver = Account.LoadByUser(User.LoadByName(context.httprequest.Form["receiverName"])); + } else { + throw new ApplicationException("receiverId/receiverName not passed"); + } + PMMessage newMessage = PMConversation.SendPMMessage( + context.account, + receiver, + this.getTitle(context), + this.getBody(context) + ); + + newMessage.conversation.markAsRead(context.session.account, newMessage, newMessage); + + return new XElement[] { newMessage.exportToXml(context) }; + } + + } +} diff --git a/IISMainHandler/handlers/response/BoardAsThread.cs b/IISMainHandler/handlers/response/BoardAsThread.cs index 4925576..7651fc7 100644 --- a/IISMainHandler/handlers/response/BoardAsThread.cs +++ b/IISMainHandler/handlers/response/BoardAsThread.cs @@ -23,7 +23,7 @@ namespace FLocal.IISHandler.handlers.response { PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.postsPerPage, 2); IEnumerable threads = board.getThreads( pageOuter, - context, new SortSpec[] { + new SortSpec[] { new SortSpec( Thread.TableSpec.instance.getColumnSpec(Thread.TableSpec.FIELD_ID), true diff --git a/IISMainHandler/handlers/response/ConversationHandler.cs b/IISMainHandler/handlers/response/ConversationHandler.cs new file mode 100644 index 0000000..cf7714d --- /dev/null +++ b/IISMainHandler/handlers/response/ConversationHandler.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Xml.Linq; +using FLocal.Common; +using FLocal.Common.dataobjects; +using FLocal.Core; +using FLocal.Core.DB; +using FLocal.Core.DB.conditions; + +namespace FLocal.IISHandler.handlers.response { + + class ConversationHandler : AbstractGetHandler { + + override protected string templateName { + get { + return "Conversation.xslt"; + } + } + + override protected XElement[] getSpecificData(WebContext context) { + Account interlocutor = Account.LoadById(int.Parse(context.requestParts[1])); + PMConversation conversation = PMConversation.LoadByAccounts(context.session.account, interlocutor); + PageOuter pageOuter = PageOuter.createFromGet( + context.requestParts, + context.userSettings.postsPerPage, + new Dictionary> { + { + 'p', + () => Config.instance.mainConnection.GetCountByConditions( + PMMessage.TableSpec.instance, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID), + ComparisonType.EQUAL, + context.session.account.id.ToString() + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID), + ComparisonType.EQUAL, + interlocutor.id.ToString() + ), + new ComparisonCondition( + PMMessage.TableSpec.instance.getIdSpec(), + ComparisonType.LESSTHAN, + int.Parse(context.requestParts[2].PHPSubstring(1)).ToString() + ) + ) + ) + } + }, + 2 + ); + IEnumerable messages = conversation.getMessages(pageOuter, context); + + XElement[] result = new XElement[] { + conversation.exportToXml(context, false), + new XElement("messages", + from message in messages select message.exportToXml(context), + pageOuter.exportToXml(2, 5, 2) + ) + }; + + if(messages.Count() > 0) { + conversation.markAsRead(context.session.account, messages.Min(), messages.Max()); + } + + foreach(var message in messages) { + message.MarkAsRead(context.session.account); + } + + return result; + } + + } + +} \ No newline at end of file diff --git a/IISMainHandler/handlers/response/ConversationsHandler.cs b/IISMainHandler/handlers/response/ConversationsHandler.cs new file mode 100644 index 0000000..aa4fef1 --- /dev/null +++ b/IISMainHandler/handlers/response/ConversationsHandler.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Xml.Linq; +using FLocal.Common; +using FLocal.Common.dataobjects; +using FLocal.Core; +using FLocal.Core.DB; + +namespace FLocal.IISHandler.handlers.response { + + class ConversationsHandler : AbstractGetHandler { + + override protected string templateName { + get { + return "Conversations.xslt"; + } + } + + override protected XElement[] getSpecificData(WebContext context) { + PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.threadsPerPage, 1); + IEnumerable conversations = PMConversation.getConversations(context.session.account, pageOuter); + XElement[] result = new XElement[] { + new XElement("conversations", + from conversation in conversations select conversation.exportToXml(context, false), + pageOuter.exportToXml(1, 5, 1) + ) + }; + + return result; + } + + } + +} \ No newline at end of file diff --git a/IISMainHandler/handlers/response/PMReplyHandler.cs b/IISMainHandler/handlers/response/PMReplyHandler.cs new file mode 100644 index 0000000..af8156f --- /dev/null +++ b/IISMainHandler/handlers/response/PMReplyHandler.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Xml.Linq; +using FLocal.Core; +using FLocal.Common; +using FLocal.Common.dataobjects; + +namespace FLocal.IISHandler.handlers.response { + + class PMReplyHandler : AbstractGetHandler { + + override protected string templateName { + get { + return "PMReply.xslt"; + } + } + + override protected XElement[] getSpecificData(WebContext context) { + PMMessage message = PMMessage.LoadById(int.Parse(context.requestParts[1])); + + return new XElement[] { + message.exportToXml(context) + }; + } + } + +} \ No newline at end of file diff --git a/IISMainHandler/handlers/response/PMReplyToPostHandler.cs b/IISMainHandler/handlers/response/PMReplyToPostHandler.cs new file mode 100644 index 0000000..a4530a5 --- /dev/null +++ b/IISMainHandler/handlers/response/PMReplyToPostHandler.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Xml.Linq; +using FLocal.Core; +using FLocal.Common; +using FLocal.Common.dataobjects; + +namespace FLocal.IISHandler.handlers.response { + + class PMReplyToPostHandler : AbstractGetHandler { + + override protected string templateName { + get { + return "PMReplyToPost.xslt"; + } + } + + override protected XElement[] getSpecificData(WebContext context) { + Post post = Post.LoadById(int.Parse(context.requestParts[1])); + + return new XElement[] { + post.thread.board.exportToXml(context, false), + post.thread.exportToXml(context, false), + post.exportToXmlWithoutThread(context, false), + new XElement("receiver", Account.LoadByUser(post.poster).exportToXml(context)), + }; + } + } + +} \ No newline at end of file diff --git a/IISMainHandler/handlers/response/PMSendHandler.cs b/IISMainHandler/handlers/response/PMSendHandler.cs new file mode 100644 index 0000000..3f85893 --- /dev/null +++ b/IISMainHandler/handlers/response/PMSendHandler.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Xml.Linq; +using FLocal.Core; +using FLocal.Common; +using FLocal.Common.dataobjects; + +namespace FLocal.IISHandler.handlers.response { + + class PMSendHandler : AbstractGetHandler { + + override protected string templateName { + get { + return "PMSend.xslt"; + } + } + + override protected XElement[] getSpecificData(WebContext context) { + return new XElement[0]; + } + } + +} \ No newline at end of file diff --git a/static/images/newpm.gif b/static/images/newpm.gif new file mode 100644 index 0000000000000000000000000000000000000000..7657d9cf7affd960b51ad045d34410a21c6648b6 GIT binary patch literal 146 zcmZ?wbhEHb6l36GSjfcip8*8^{|C{E|GE8KLxP;= zS{Ru0TJjg3w&9N#*b*gmVdi!zK^MX6wGS3B#uT?Mx>CB%ZPV1>lMLAufAtBQwsm_j bf{b8AGlF+c|H{*E`4`XGa;rO*k--`O6FDi+ literal 0 HcmV?d00001 diff --git a/templates/Full/Conversation.xslt b/templates/Full/Conversation.xslt new file mode 100644 index 0000000..c51a7b7 --- /dev/null +++ b/templates/Full/Conversation.xslt @@ -0,0 +1,93 @@ + + + + + + +
+ + + + +
+ + + + + + + + +
+ + + + +
+ ñòðàíèöû: + + /Conversation// + +
+
+ + + + +
+ ñòðàíèöû: + + /Conversation// + +
+
+
+
+ +
+ + + + + + +
+ + + + +
+ + + + + +
+ + Ïðèâàòíûå ñîîáùåíèÿ + >> + + + + + + + + +
+
+
+
+
+ +
\ No newline at end of file diff --git a/templates/Full/Conversations.xslt b/templates/Full/Conversations.xslt new file mode 100644 index 0000000..18d434f --- /dev/null +++ b/templates/Full/Conversations.xslt @@ -0,0 +1,92 @@ + + + + + + + + + +
+ + + + +
+ + + + + +
+ Ïðèâàòíûå ñîîáùåíèÿ + + + + + + + + +
+
+
+
+
+ + + + +
+ + + + + + + + + + + + +
ÑîáåñåäíèêÏîñòîâÏîñëåäíåå
+ + ñòðàíèöû: + + /Conversations/ + + +
+
+
+ +
\ No newline at end of file diff --git a/templates/Full/PMReply.xslt b/templates/Full/PMReply.xslt new file mode 100644 index 0000000..af75683 --- /dev/null +++ b/templates/Full/PMReply.xslt @@ -0,0 +1,107 @@ + + + + + + + + + +
+ + + + + + + + + + +
+ Îòâåò íà ïðèâàòíîå ñîîáùåíèå ( + + ) +
+ Çàïîëíèòå ïðèâåäåííóþ íèæå ôîðìó äëÿ îòïðàâêè ïðèâàòíîãî ñîîáùåíèÿ. HTML îòêëþ÷åí. UBBCode âêëþ÷åí, è âû ìîæåòå èñïîëüçîâàòü UBBCode â âàøèõ ñîîáùåíèÿõ. Àíîíèìíûå ñîîáùåíèÿ ðàçðåøåíû, è âû ìîæåòå âûáðàòü ëþáîå íåçàðåãèñòðèðîâàííîå èìÿ. +
+
+ + + + Ïîëüçîâàòåëü: + +
+
+ Òåìà: +
+ + + + + + + + Re: + + + + + +
+
+ + + [q] + + [/q] + + +
+ + ß õî÷ó ïðåäâàðèòåëüíî ïðîñìîòðåòü ñîîáùåíèå ïåðåä îòïðàâêîé +
+
+ + Ïðîâåðèòü ïðàâîïèñàíèå +
+
+ + +
+
+ +
+ + + + + +
+ + + + + + + + + + +
+ Îòâåò íà ñîîáùåíèå +
+ + Àâòîð: + +
+ Òåìà: + +
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/templates/Full/PMReplyToPost.xslt b/templates/Full/PMReplyToPost.xslt new file mode 100644 index 0000000..f3a13de --- /dev/null +++ b/templates/Full/PMReplyToPost.xslt @@ -0,0 +1,104 @@ + + + + + + + + + +
+ + + + + + + + + + +
+ Îòâåò íà ñîîáùåíèå ( + + ) +
+ Çàïîëíèòå ïðèâåäåííóþ íèæå ôîðìó äëÿ îòïðàâêè ñîîáùåíèÿ â ôîðóì. HTML îòêëþ÷åí. UBBCode âêëþ÷åí, è âû ìîæåòå èñïîëüçîâàòü UBBCode â âàøèõ ñîîáùåíèÿõ. Àíîíèìíûå ñîîáùåíèÿ ðàçðåøåíû, è âû ìîæåòå âûáðàòü ëþáîå íåçàðåãèñòðèðîâàííîå èìÿ. +
+
+ + + + Ïîëüçîâàòåëü: + +
+ Ïîëó÷àòåëü: + +
+
+ Òåìà: +
+ + + + + + + + Re: + + + + + +
+
+ +
+ + ß õî÷ó ïðåäâàðèòåëüíî ïðîñìîòðåòü ñîîáùåíèå ïåðåä îòïðàâêîé +
+
+ + Ïðîâåðèòü ïðàâîïèñàíèå +
+
+ + +
+
+ +
+ + + + + +
+ + + + + + + + + + +
+ Îòâåò íà ñîîáùåíèå +
+ + Àâòîð: + +
+ Òåìà: + +
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/templates/Full/PMSend.xslt b/templates/Full/PMSend.xslt new file mode 100644 index 0000000..fea243c --- /dev/null +++ b/templates/Full/PMSend.xslt @@ -0,0 +1,56 @@ + + + + + + + + + +
+ + + + + + + + + + +
+ Íîâîå ïðèâàòíîå ñîîáùåíèå +
+ Çàïîëíèòå ïðèâåäåííóþ íèæå ôîðìó äëÿ îòïðàâêè ïðèâàòíîãî ñîîáùåíèÿ. HTML îòêëþ÷åí. UBBCode âêëþ÷åí, è âû ìîæåòå èñïîëüçîâàòü UBBCode â âàøèõ ñîîáùåíèÿõ. Àíîíèìíûå ñîîáùåíèÿ ðàçðåøåíû, è âû ìîæåòå âûáðàòü ëþáîå íåçàðåãèñòðèðîâàííîå èìÿ. +
+
+ Ïîëüçîâàòåëü: + +
+ Ïîëó÷àòåëü: + +
+
+ Òåìà: +
+ +
+
+ +
+ + ß õî÷ó ïðåäâàðèòåëüíî ïðîñìîòðåòü ñîîáùåíèå ïåðåä îòïðàâêîé +
+
+ + Ïðîâåðèòü ïðàâîïèñàíèå +
+
+ + +
+
+ +
+ +
\ No newline at end of file diff --git a/templates/Full/elems/ConversationInfo.xslt b/templates/Full/elems/ConversationInfo.xslt new file mode 100644 index 0000000..1988a0d --- /dev/null +++ b/templates/Full/elems/ConversationInfo.xslt @@ -0,0 +1,58 @@ + + + + + + + + lighttable + + + darktable + + + + * + + + /static/images/message-normal-read.gif + + + /static/images/message-normal-notread.gif + + + + + + /Conversation/ + + / + + p + + + + + + + + /Conversation// + + + + + + + + () + + + + + + + + + + + \ No newline at end of file diff --git a/templates/Full/elems/Header.xslt b/templates/Full/elems/Header.xslt index 8d39fd9..c2edf8c 100644 --- a/templates/Full/elems/Header.xslt +++ b/templates/Full/elems/Header.xslt @@ -34,9 +34,23 @@ + + + + + + \ No newline at end of file diff --git a/templates/Full/elems/PostInfo.xslt b/templates/Full/elems/PostInfo.xslt index 61fc704..ef45165 100644 --- a/templates/Full/elems/PostInfo.xslt +++ b/templates/Full/elems/PostInfo.xslt @@ -85,6 +85,9 @@ diff --git a/templates/Full/result/PMSent.xslt b/templates/Full/result/PMSent.xslt new file mode 100644 index 0000000..0de5d07 --- /dev/null +++ b/templates/Full/result/PMSent.xslt @@ -0,0 +1,30 @@ + + + + +
+ + + + + + + + +
+ Message + + + + + + + +
+ + + + + /static/images/message-normal-notread.gif + + + /static/images/message-normal-read.gif + + + + + + + new + +
+ +
+ + + + +
+ + + + + + + +
+
+
+
+ + + + + + + + + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ + + +
+ + + + + + + +
+ Íîâîå ïðèâàòíîå ñîîáùåíèå +
+ Âàøå ñîîáùåíèå áûëî óñïåøíî ñîçäàíî +
+ + /Conversation//p + Ïðîñìîòðåòü ñîîáùåíèå + +
+
+ + + \ No newline at end of file