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