1 module vibelog.post;
2 
3 import vibe.data.bson;
4 import vibe.textfilter.markdown;
5 import vibe.textfilter.html;
6 
7 import std.array;
8 import std.conv;
9 public import std.datetime;
10 
11 
12 class Post {
13 	BsonObjectID id;
14 	bool isPublic;
15 	bool commentsAllowed;
16 	string slug; // url entity to identify this post - generated from the header by default
17 	string author;  // user name
18 	string category; // can be hierarchical using dotted.syntax.format
19 	SysTime date;
20 	string header;
21 	string subHeader;
22 	string content;
23 	string headerImage;
24 	string[] tags;
25 	Comment[] comments;
26 	string[] trackbacks;
27 
28 	this()
29 	{
30 		id  = BsonObjectID.generate();
31 		date = Clock.currTime().toUTC();
32 	}
33 
34 	@property string name() const { return slug.length ? slug : id.toString(); }
35 	
36 	static Post fromBson(Bson bson)
37 	{
38 		auto ret = new Post;
39 		ret.id = cast(BsonObjectID)bson["_id"];
40 		ret.isPublic = cast(bool)bson["isPublic"];
41 		ret.commentsAllowed = cast(bool)bson["commentsAllowed"];
42 		ret.slug = cast(string)bson["slug"];
43 		ret.author = cast(string)bson["author"];
44 		ret.category = cast(string)bson["category"];
45 		ret.date = SysTime.fromISOExtString(cast(string)bson["date"]);
46 		ret.header = cast(string)bson["header"];
47 		ret.subHeader = cast(string)bson["subHeader"];
48 		ret.headerImage = cast(string)bson["headerImage"];
49 		ret.content = cast(string)bson["content"];
50 		foreach( t; cast(Bson[])bson["tags"] )
51 			ret.tags ~= cast(string)t;
52 		foreach( c; cast(Bson[])bson["comments"] )
53 			ret.comments ~= Comment.fromBson(c);
54 		return ret;
55 	}
56 	
57 	Bson toBson()
58 	const {
59 		Bson[] btags;
60 		foreach( t; tags )
61 			btags ~= Bson(t);
62 
63 		Bson[] bcomments;
64 		foreach( c; comments )
65 			bcomments ~= c.toBson();
66 
67 		Bson[string] ret;
68 		ret["_id"] = Bson(id);
69 		ret["isPublic"] = Bson(isPublic);
70 		ret["commentsAllowed"] = Bson(commentsAllowed);
71 		ret["slug"] = Bson(slug);
72 		ret["author"] = Bson(author);
73 		ret["category"] = Bson(category);
74 		ret["date"] = Bson(date.toISOExtString());
75 		ret["header"] = Bson(header);
76 		ret["subHeader"] = Bson(subHeader);
77 		ret["headerImage"] = Bson(headerImage);
78 		ret["content"] = Bson(content);
79 		ret["tags"] = Bson(btags);
80 		ret["comments"] = Bson(bcomments);
81 
82 		return Bson(ret);
83 	}
84 
85 	string renderSubHeaderAsHtml()
86 	const {
87 		auto ret = appender!string();
88 		filterMarkdown(ret, subHeader);
89 		return ret.data;
90 	}
91 
92 	string renderContentAsHtml(string function(string)[] filters)
93 	const {
94 		auto html = filterMarkdown(content);
95 		foreach( flt; filters )
96 			html = flt(html);
97 		return html;
98 	}
99 }
100 
101 class Comment {
102 	bool isPublic;
103 	SysTime date;
104 	int answerTo;
105 	string authorName;
106 	string authorMail;
107 	string authorHomepage;
108 	string header;
109 	string content;
110 
111 	static Comment fromBson(Bson bson)
112 	{
113 		auto ret = new Comment;
114 		ret.isPublic = cast(bool)bson["isPublic"];
115 		ret.date = SysTime.fromISOExtString(cast(string)bson["date"]);
116 		ret.answerTo = cast(int)bson["answerTo"];
117 		ret.authorName = cast(string)bson["authorName"];
118 		ret.authorMail = cast(string)bson["authorMail"];
119 		ret.authorHomepage = cast(string)bson["authorHomepage"];
120 		ret.header = cast(string)bson["header"];
121 		ret.content = cast(string)bson["content"];
122 		return ret;
123 	}
124 	
125 	Bson toBson()
126 	const {
127 		Bson[string] ret;
128 		ret["isPublic"] = Bson(isPublic);
129 		ret["date"] = Bson(date.toISOExtString());
130 		ret["answerTo"] = Bson(answerTo);
131 		ret["authorName"] = Bson(authorName);
132 		ret["authorMail"] = Bson(authorMail);
133 		ret["authorHomepage"] = Bson(authorHomepage);
134 		ret["header"] = Bson(header);
135 		ret["content"] = Bson(content);
136 		return Bson(ret);
137 	}
138 
139 	string renderContentAsHtml()
140 	const {
141 		auto ret = appender!string();
142 		filterMarkdown(ret, htmlEscape(content));
143 		return ret.data;
144 	}
145 }
146 
147 string makeSlugFromHeader(string header)
148 {
149 	Appender!string ret;
150 	foreach( dchar ch; header ){
151 		switch( ch ){
152 			default:
153 				ret.put('-');
154 				break;
155 			case '"', '\'', '´', '`', '.', ',', ';', '!', '?':
156 				break;
157 			case 'A': .. case 'Z'+1:
158 				ret.put(cast(dchar)(ch - 'A' + 'a'));
159 				break;
160 			case 'a': .. case 'z'+1:
161 			case '0': .. case '9'+1:
162 				ret.put(ch);
163 				break;
164 		}
165 	}
166 	return ret.data;
167 }