Browse Source

Post like, Hashtag nonce error fix, Rename toMarked to renderMarked, Temporary fix for dbschema error

HelloZeroNet 8 years ago
parent
commit
bbb0d6c364
10 changed files with 513 additions and 165 deletions
  1. 12 12
      content.json
  2. 39 0
      css/ZeroBlog.css
  3. 63 8
      css/all.css
  4. 10 3
      css/icons.css
  5. 15 5
      dbschema.json
  6. 12 3
      index.html
  7. 2 2
      js/Comments.coffee
  8. 173 59
      js/ZeroBlog.coffee
  9. 186 72
      js/all.js
  10. 1 1
      js/utils/Text.coffee

+ 12 - 12
content.json

@@ -6,8 +6,8 @@
  "domain": "Blog.ZeroNetwork.bit", 
  "files": {
   "css/all.css": {
-   "sha512": "ccdbbce8efef58f5259b97d7e79b35b3270bed909a2232e4bd09c2be7c387c83", 
-   "size": 113347
+   "sha512": "14db0f16dbaf910888666b2936cbd42fd5bb2c81dcf37c72d6127f45bd5de1d1", 
+   "size": 121643
   }, 
   "data-default/data.json": {
    "sha512": "aff6accbac7e950ff567105666ddf29b70ca7c2dcd9d90f9a969509a42a9b195", 
@@ -106,20 +106,20 @@
    "size": 477911
   }, 
   "dbschema.json": {
-   "sha512": "7b756e8e475d4d6b345a24e2ae14254f5c6f4aa67391a94491a026550fe00df8", 
-   "size": 1529
+   "sha512": "b387d0a1f93c159d0c89534e1c08115355b3b65262ab85ff79ec65768a514bde", 
+   "size": 1924
   }, 
   "img/loading.gif": {
    "sha512": "8a42b98962faea74618113166886be488c09dad10ca47fe97005edc5fb40cc00", 
    "size": 723
   }, 
   "index.html": {
-   "sha512": "ecaabb6d144dcbb84f1754b92110df1a834551309500d2e4421df9baca47a7ad", 
-   "size": 5081
+   "sha512": "ae881f5a52b36faac97fa06ac140a3a85e5fb9494482cc48de0405c6a890f9b8", 
+   "size": 5901
   }, 
   "js/all.js": {
-   "sha512": "57c730d07735b1a39c8a62bcb9447c506f518ccae079fccf862d1f4ce1d8ea5d", 
-   "size": 204752
+   "sha512": "f809256e3ab91c4bdd76c60e6cbaa469afeeee30cc18a56af1f03f6048aa26a5", 
+   "size": 210450
   }
  }, 
  "ignore": "((js|css)/(?!all.(js|css))|data/.*db|data/users/.*/.*)", 
@@ -129,14 +129,14 @@
    "signers_required": 1
   }
  }, 
- "modified": 1446168994.624, 
+ "modified": 1446775276.26, 
  "sign": [
-  96030868191800162957176973774699323652832370569335334056060165593728652091118, 
-  46783872076653677099073354530208373704913656113742582268614963747636587368554
+  57991881946952167454322340555223230381605467895626737690363212960004734314484, 
+  49307891265351103514194691191925165083176262395509091042088174280588230507122
  ], 
  "signers_sign": "G7W/oNvczE5nPTFYVOqv8+GOpQd23LS/Dc1Q6xQ1NRDDHlYzmoSE63UQ7Za05kD0rwIYXbuUSr8z8p6RhZmnUs8=", 
  "signs": {
-  "1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8": "Gxi4vVB8ZW+RAnSP33s8eohudGR2BP/2QAtvFVJ9EEV6ic6EMxxLIb1kzZIHK/zBrHlL3Std9KPI/SHSuIOQrCE="
+  "1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8": "G507Q9Vv1Nks2M/27cUbms2vIkA1hB0i76jhslJSw8t5Rpst5ZRxFX61iCRymkIr+rgti/Zo/kUZKSNw+CvQXqA="
  }, 
  "signs_required": 1, 
  "title": "ZeroBlog", 

+ 39 - 0
css/ZeroBlog.css

@@ -113,6 +113,16 @@ a:hover { color: #3498db }
 .post .details .comments-num { border: none; color: #BBB; font-weight: normal; }
 .post .details .comments-num .num { border-bottom: 1px solid #eee; color: #000; }
 .post .details .comments-num:hover .num { border-bottom: 1px solid #D6A1DE; }
+.post .details .like { display: inline-block; padding: 5px 5px; margin-top: -6px; border-radius: 7px; border: none; font-weight: normal; transition: all 0.3s }
+.post .details .like .icon-heart:before, .post .details .like .icon-heart:after { background-color: #CCC; transition: all 0.3s }
+.post .details .like .icon-heart { transform-style: preserve-3d; transition: all 0.3s }
+.post .details .like:hover .icon-heart { transform: scale(1.1) }
+.post .details .like:hover .icon-heart:before, .post .details .like:hover .icon-heart:after { background-color: #FF5442 }
+.post .details .like.active .icon-heart:before, .post .details .like.active .icon-heart:after { background-color: #FF5442 }
+.post .details .like.active .icon-heart-anim { transform: scale(1.8); opacity: 0; transition: all 1s ease-in-out }
+.post .details .like .num { margin-left: 21px; color: #CCC; transition: all 0.3s }
+.post .details .like:hover .num { color: #FA6C8D }
+.post .details .like.loading { pointer-events: none; animation: bounce .3s infinite alternate ease-out; animation-delay: 1s; }
 .post .body { font-size: 21.5px; line-height: 1.6; font-family: Tinos; margin-top: 20px }
 
 .post .body h1 { text-align: center; margin-top: 50px }
@@ -150,3 +160,32 @@ blockquote { border-left: 3px solid #333; margin-left: 0px; padding-left: 1em }
 .pager .next { float: right }
 .pager .prev:hover { box-shadow: inset -150px 0px 0px 0px #333; }
 .pager .prev:active { box-shadow: inset -150px 0px 0px 0px #AF3BFF; }
+
+/* Score */
+/*
+.score {
+	border: 1px solid #eee; margin-right: 5px; margin-left: 5px; padding: 2px 7px; border-radius: 10px; color: #AAA; background-position: left center; font-weight: bold; font-size: 12px;
+	display: inline-block; height: 1.2em; overflow: hidden; vertical-align: -0.5em; line-height: 7px; transition: background-color 0.3s; height: 20px; box-sizing: border-box
+}
+.score-active, .score-inactive { transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); display: inline-block; margin-top: 3px }
+.score-active { display: block; margin-top: 5px }
+.score.active { background-color: #2ecc71; color: white }
+.score.loading {
+	color: rgba(255,255,255,0) !important; background: #2ecc71 url(../img/loading.gif) no-repeat center center;
+	transition: background 0.5s ease-out; pointer-events: none; border: 1px solid #2ecc71
+}
+
+.score.active .score-inactive, .score:hover .score-inactive { transform: translateY(-14px); opacity: 0.5 }
+.score.active .score-active, .score:hover .score-active { transform: translateY(-14px) }
+.score:hover, .score.active { border: 1px solid #2ecc71; color: #AAA; }
+
+.score:active {  border: 1px solid #AAA !important; transform: translateY(1px) }
+
+.noscore .score-inactive .icon-up { margin-left: 6px }
+.noscore .score-inactive .score-num { display: none }
+*/
+
+@keyframes bounce {
+	  0% { transform: translateY(0); }
+	100% { transform: translateY(-3px); }
+}

+ 63 - 8
css/all.css

@@ -1,6 +1,6 @@
 
 
-/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/css/Comments.css ---- */
+/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/Comments.css ---- */
 
 
 .comments { margin-bottom: 60px }
@@ -56,7 +56,7 @@ input.text:focus, textarea:focus { border-color: #5FC0EA; outline: none; backgro
 
 
 
-/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/css/ZeroBlog.css ---- */
+/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/ZeroBlog.css ---- */
 
 
 /* Design based on medium */
@@ -174,6 +174,16 @@ a:hover { color: #3498db }
 .post .details .comments-num { border: none; color: #BBB; font-weight: normal; }
 .post .details .comments-num .num { border-bottom: 1px solid #eee; color: #000; }
 .post .details .comments-num:hover .num { border-bottom: 1px solid #D6A1DE; }
+.post .details .like { display: inline-block; padding: 5px 5px; margin-top: -6px; -webkit-border-radius: 7px; -moz-border-radius: 7px; -o-border-radius: 7px; -ms-border-radius: 7px; border-radius: 7px ; border: none; font-weight: normal; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }
+.post .details .like .icon-heart:before, .post .details .like .icon-heart:after { background-color: #CCC; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }
+.post .details .like .icon-heart { transform-style: preserve-3d; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }
+.post .details .like:hover .icon-heart { -webkit-transform: scale(1.1) ; -moz-transform: scale(1.1) ; -o-transform: scale(1.1) ; -ms-transform: scale(1.1) ; transform: scale(1.1)  }
+.post .details .like:hover .icon-heart:before, .post .details .like:hover .icon-heart:after { background-color: #FF5442 }
+.post .details .like.active .icon-heart:before, .post .details .like.active .icon-heart:after { background-color: #FF5442 }
+.post .details .like.active .icon-heart-anim { -webkit-transform: scale(1.8); -moz-transform: scale(1.8); -o-transform: scale(1.8); -ms-transform: scale(1.8); transform: scale(1.8) ; opacity: 0; -webkit-transition: all 1s ease-in-out ; -moz-transition: all 1s ease-in-out ; -o-transition: all 1s ease-in-out ; -ms-transition: all 1s ease-in-out ; transition: all 1s ease-in-out  }
+.post .details .like .num { margin-left: 21px; color: #CCC; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }
+.post .details .like:hover .num { color: #FA6C8D }
+.post .details .like.loading { pointer-events: none; -webkit-animation: bounce .3s infinite alternate ease-out; -moz-animation: bounce .3s infinite alternate ease-out; -o-animation: bounce .3s infinite alternate ease-out; -ms-animation: bounce .3s infinite alternate ease-out; animation: bounce .3s infinite alternate ease-out ; -webkit-animation-delay: 1s; -moz-animation-delay: 1s; -o-animation-delay: 1s; -ms-animation-delay: 1s; animation-delay: 1s ; }
 .post .body { font-size: 21.5px; line-height: 1.6; font-family: Tinos; margin-top: 20px }
 
 .post .body h1 { text-align: center; margin-top: 50px }
@@ -212,9 +222,47 @@ blockquote { border-left: 3px solid #333; margin-left: 0px; padding-left: 1em }
 .pager .prev:hover { -webkit-box-shadow: inset -150px 0px 0px 0px #333; -moz-box-shadow: inset -150px 0px 0px 0px #333; -o-box-shadow: inset -150px 0px 0px 0px #333; -ms-box-shadow: inset -150px 0px 0px 0px #333; box-shadow: inset -150px 0px 0px 0px #333 ; }
 .pager .prev:active { -webkit-box-shadow: inset -150px 0px 0px 0px #AF3BFF; -moz-box-shadow: inset -150px 0px 0px 0px #AF3BFF; -o-box-shadow: inset -150px 0px 0px 0px #AF3BFF; -ms-box-shadow: inset -150px 0px 0px 0px #AF3BFF; box-shadow: inset -150px 0px 0px 0px #AF3BFF ; }
 
+/* Score */
+/*
+.score {
+	border: 1px solid #eee; margin-right: 5px; margin-left: 5px; padding: 2px 7px; -webkit-border-radius: 10px; -moz-border-radius: 10px; -o-border-radius: 10px; -ms-border-radius: 10px; border-radius: 10px ; color: #AAA; background-position: left center; font-weight: bold; font-size: 12px;
+	display: inline-block; height: 1.2em; overflow: hidden; vertical-align: -0.5em; line-height: 7px; -webkit-transition: background-color 0.3s; -moz-transition: background-color 0.3s; -o-transition: background-color 0.3s; -ms-transition: background-color 0.3s; transition: background-color 0.3s ; height: 20px; box-sizing: border-box
+}
+.score-active, .score-inactive { -webkit-transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); -moz-transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); -o-transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); -ms-transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) ; display: inline-block; margin-top: 3px }
+.score-active { display: block; margin-top: 5px }
+.score.active { background-color: #2ecc71; color: white }
+.score.loading {
+	color: rgba(255,255,255,0) !important; background: #2ecc71 url(../img/loading.gif) no-repeat center center;
+	-webkit-transition: background 0.5s ease-out; -moz-transition: background 0.5s ease-out; -o-transition: background 0.5s ease-out; -ms-transition: background 0.5s ease-out; transition: background 0.5s ease-out ; pointer-events: none; border: 1px solid #2ecc71
+}
+
+.score.active .score-inactive, .score:hover .score-inactive { -webkit-transform: translateY(-14px); -moz-transform: translateY(-14px); -o-transform: translateY(-14px); -ms-transform: translateY(-14px); transform: translateY(-14px) ; opacity: 0.5 }
+.score.active .score-active, .score:hover .score-active { -webkit-transform: translateY(-14px) ; -moz-transform: translateY(-14px) ; -o-transform: translateY(-14px) ; -ms-transform: translateY(-14px) ; transform: translateY(-14px)  }
+.score:hover, .score.active { border: 1px solid #2ecc71; color: #AAA; }
 
+.score:active {  border: 1px solid #AAA !important; -webkit-transform: translateY(1px) ; -moz-transform: translateY(1px) ; -o-transform: translateY(1px) ; -ms-transform: translateY(1px) ; transform: translateY(1px)  }
 
-/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/css/fonts.css ---- */
+.noscore .score-inactive .icon-up { margin-left: 6px }
+.noscore .score-inactive .score-num { display: none }
+*/
+
+@keyframes bounce {
+	  0% { -webkit-transform: translateY(0); -moz-transform: translateY(0); -o-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0) ; }
+	100% { -webkit-transform: translateY(-3px); -moz-transform: translateY(-3px); -o-transform: translateY(-3px); -ms-transform: translateY(-3px); transform: translateY(-3px) ; }
+}
+@-webkit-keyframes bounce {
+	  0% { -webkit-transform: translateY(0); -moz-transform: translateY(0); -o-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0) ; }
+	100% { -webkit-transform: translateY(-3px); -moz-transform: translateY(-3px); -o-transform: translateY(-3px); -ms-transform: translateY(-3px); transform: translateY(-3px) ; }
+}
+@-moz-keyframes bounce {
+	  0% { -webkit-transform: translateY(0); -moz-transform: translateY(0); -o-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0) ; }
+	100% { -webkit-transform: translateY(-3px); -moz-transform: translateY(-3px); -o-transform: translateY(-3px); -ms-transform: translateY(-3px); transform: translateY(-3px) ; }
+}
+
+
+
+
+/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/fonts.css ---- */
 
 
 /* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */
@@ -251,7 +299,7 @@ blockquote { border-left: 3px solid #333; margin-left: 0px; padding-left: 1em }
 
 
 
-/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/css/github.css ---- */
+/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/github.css ---- */
 
 
 /*
@@ -381,21 +429,28 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
 
 
 
-/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/css/icons.css ---- */
+/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/icons.css ---- */
 
 
 .icon { display: inline-block; vertical-align: text-bottom; background-repeat: no-repeat; }
 .icon-profile { font-size: 6px; top: 0em; -webkit-border-radius: 0.7em 0.7em 0 0; -moz-border-radius: 0.7em 0.7em 0 0; -o-border-radius: 0.7em 0.7em 0 0; -ms-border-radius: 0.7em 0.7em 0 0; border-radius: 0.7em 0.7em 0 0 ; background: #FFFFFF; width: 1.5em; height: 0.7em; position: relative; display: inline-block; margin-right: 4px }
-.icon-profile:before { position: absolute; content: ""; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; -webkit-border-radius: 50%; -moz-border-radius: 50%; -o-border-radius: 50%; -ms-border-radius: 50%; border-radius: 50% ; background: #FFFFFF; } 
+.icon-profile:before { position: absolute; content: ""; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; -webkit-border-radius: 50%; -moz-border-radius: 50%; -o-border-radius: 50%; -ms-border-radius: 50%; border-radius: 50% ; background: #FFFFFF; }
 
 .icon-comment { width: 16px; height: 10px; -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; background: #B10DC9; margin-top: 0px; display: inline-block; position: relative; top: -2px; }
 .icon-comment:after { left: 9px; border: 2px solid transparent; border-top-color: #B10DC9; border-left-color: #B10DC9; background: transparent; content: ""; display: block; margin-top: 10px; width: 0px; margin-left: 7px; }
 
-.icon-edit { 
+.icon-edit {
 	width: 16px; height: 16px; background-repeat: no-repeat; background-position: 20px center;
 	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAOVBMVEUAAAC9w8e9w8e9w8e9w8e/xMi9w8e9w8e+w8e9w8e9w8e9w8e9w8e9w8e9w8e+w8e/xMi9w8e9w8fvY4+KAAAAEnRSTlMASPv3WQbwOTCkt4/psX4YDMWr+RRCAAAAUUlEQVQY06XLORKAMAxDUTs7kA3d/7AYGju0UfffjIgoHkxm0vB5bZyxKHx9eX0FJw0Y4bcXKQ4/CTtS5yqp5GFFOjGpVGl00k1pNDIb3Nv9AHC7BOZC4ZjvAAAAAElFTkSuQmCC+d0ckOwyAMRVGHUOO0gUyd+P8f7WApz4Iki9wFmyOEATrXLZcFp5LrGogPOxKp6zfFf9fZ1/I/cY7YZSS3U6S3XFZJmGBwL+FuJX/F1K0wUUlZyZGlXgXESthTEs4B8fh7xoVUDPGYJnsfkCRarKAgz8cAKbpD6pqDPz3XB8K6HdUEeN9NAAAAAElFTkSuQmCC);
 }
 .icon-reply {
 	width: 16px; height: 16px;
 	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAABmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYs5FxxAAAAC3RSTlMAgBFwYExAMHgoCDJmUTYAAAA3SURBVAjXY8APGGEMQZgAjCEoKBwEEQCCAoiIh6AQVM1kMaguJhGYOSJQjexiUMbiAChDCclCAOHqBBdHpwQTAAAAAElFTkSuQmCC);
-}
+}
+.icon-heart { position: absolute; width: 17px; height: 13px; margin-top: 5px }
+.icon-heart:before, .icon-heart:after {
+	position: absolute; content: ""; left: 8px; top: 0; width: 8px; height: 13px;
+	background: #FA6C8D; /*border-radius: 25px 25px 0 0;*/ -webkit-transform: rotate(-45deg); -moz-transform: rotate(-45deg); -o-transform: rotate(-45deg); -ms-transform: rotate(-45deg); transform: rotate(-45deg) ; transform-origin: 0 100%
+}
+.icon-heart:after { left: 0; -webkit-transform: rotate(45deg); -moz-transform: rotate(45deg); -o-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg) ; transform-origin :100% 100% }
+.icon-up { font-weight: normal !important; font-size: 15px; font-family: Tahoma; vertical-align: -4px; padding-right: 5px; display: inline; height: 1px; }

+ 10 - 3
css/icons.css

@@ -1,15 +1,22 @@
 .icon { display: inline-block; vertical-align: text-bottom; background-repeat: no-repeat; }
 .icon-profile { font-size: 6px; top: 0em; border-radius: 0.7em 0.7em 0 0; background: #FFFFFF; width: 1.5em; height: 0.7em; position: relative; display: inline-block; margin-right: 4px }
-.icon-profile:before { position: absolute; content: ""; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; border-radius: 50%; background: #FFFFFF; } 
+.icon-profile:before { position: absolute; content: ""; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; border-radius: 50%; background: #FFFFFF; }
 
 .icon-comment { width: 16px; height: 10px; border-radius: 2px; background: #B10DC9; margin-top: 0px; display: inline-block; position: relative; top: -2px; }
 .icon-comment:after { left: 9px; border: 2px solid transparent; border-top-color: #B10DC9; border-left-color: #B10DC9; background: transparent; content: ""; display: block; margin-top: 10px; width: 0px; margin-left: 7px; }
 
-.icon-edit { 
+.icon-edit {
 	width: 16px; height: 16px; background-repeat: no-repeat; background-position: 20px center;
 	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAOVBMVEUAAAC9w8e9w8e9w8e9w8e/xMi9w8e9w8e+w8e9w8e9w8e9w8e9w8e9w8e9w8e+w8e/xMi9w8e9w8fvY4+KAAAAEnRSTlMASPv3WQbwOTCkt4/psX4YDMWr+RRCAAAAUUlEQVQY06XLORKAMAxDUTs7kA3d/7AYGju0UfffjIgoHkxm0vB5bZyxKHx9eX0FJw0Y4bcXKQ4/CTtS5yqp5GFFOjGpVGl00k1pNDIb3Nv9AHC7BOZC4ZjvAAAAAElFTkSuQmCC+d0ckOwyAMRVGHUOO0gUyd+P8f7WApz4Iki9wFmyOEATrXLZcFp5LrGogPOxKp6zfFf9fZ1/I/cY7YZSS3U6S3XFZJmGBwL+FuJX/F1K0wUUlZyZGlXgXESthTEs4B8fh7xoVUDPGYJnsfkCRarKAgz8cAKbpD6pqDPz3XB8K6HdUEeN9NAAAAAElFTkSuQmCC);
 }
 .icon-reply {
 	width: 16px; height: 16px;
 	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAABmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYs5FxxAAAAC3RSTlMAgBFwYExAMHgoCDJmUTYAAAA3SURBVAjXY8APGGEMQZgAjCEoKBwEEQCCAoiIh6AQVM1kMaguJhGYOSJQjexiUMbiAChDCclCAOHqBBdHpwQTAAAAAElFTkSuQmCC);
-}
+}
+.icon-heart { position: absolute; width: 17px; height: 13px; margin-top: 5px }
+.icon-heart:before, .icon-heart:after {
+	position: absolute; content: ""; left: 8px; top: 0; width: 8px; height: 13px;
+	background: #FA6C8D; /*border-radius: 25px 25px 0 0;*/ transform: rotate(-45deg); transform-origin: 0 100%
+}
+.icon-heart:after { left: 0; transform: rotate(45deg); transform-origin :100% 100% }
+.icon-up { font-weight: normal !important; font-size: 15px; font-family: Tahoma; vertical-align: -4px; padding-right: 5px; display: inline; height: 1px; }

+ 15 - 5
dbschema.json

@@ -1,15 +1,16 @@
 {
-	"db_name": "ZeroID",
+	"db_name": "ZeroBlog",
 	"db_file": "data/zeroblog.db",
 	"version": 2,
 	"maps": {
 		"users/.+/data.json": {
-			"to_table": [ 
+			"to_table": [
 				"comment",
-				{"node": "comment_vote", "table": "comment_vote", "key_col": "comment_uri", "val_col": "vote"} 
+				{"node": "comment_vote", "table": "comment_vote", "key_col": "comment_uri", "val_col": "vote"},
+				{"node": "post_vote", "table": "post_vote", "key_col": "post_id", "val_col": "vote"}
 			]
 		},
-		"users/.+/content.json": { 
+		"users/.+/content.json": {
 			"to_keyvalue": [ "cert_user_id" ]
 		},
 		"data.json": {
@@ -21,7 +22,7 @@
 	"tables": {
 		"comment": {
 			"cols": [
-				["comment_id", "INTEGER"], 
+				["comment_id", "INTEGER"],
 				["post_id", "INTEGER"],
 				["body", "TEXT"],
 				["date_added", "INTEGER"],
@@ -49,6 +50,15 @@
 			],
 			"indexes": ["CREATE UNIQUE INDEX post_uri ON post(json_id, post_id)", "CREATE INDEX post_id ON post(post_id)"],
 			"schema_changed": 1426195823
+		},
+		"post_vote": {
+			"cols": [
+				["post_id", "INTEGER"],
+				["vote", "INTEGER"],
+				["json_id", "INTEGER REFERENCES json (json_id)"]
+			],
+			"indexes": ["CREATE INDEX post_vote_post_id ON post_vote(post_id)", "CREATE INDEX post_vote_json_id ON post_vote(post_id)"],
+			"schema_changed": 1426195826
 		}
 	}
 }

+ 12 - 3
index.html

@@ -7,7 +7,7 @@
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <link rel="stylesheet" href="css/all.css" />
  <base href="" target="_top" id="base">
- <script>base.href = document.location.href.replace("/media", "").replace("index.html", "").replace(/[&?]wrapper=False/, "")</script>
+ <script>base.href = document.location.href.replace("/media", "").replace("index.html", "").replace(/[&?]wrapper=False/, "").replace(/[&?]wrapper_nonce=[A-Za-z0-9]+/, "")</script>
 
 </head>
 <body>
@@ -84,7 +84,12 @@
    <h1 class="title"><a href="?Post:23:Title" data-editable="title" data-editable-mode="simple" class="editable">Title</a></h1>
    <div class="details">
     <span class="published" data-editable="date_published" data-editable-mode="timestamp">21 hours ago &middot; 2 min read</span>
-    <a href="?Post:23:title" class="comments-num">&middot; <div class='icon-comment'></div> <span class="num">3 comments</span></a>
+    <a href="?Post:23:title" class="comments-num">&middot; <div class="icon-comment"></div> <span class="num">3 comments</span></a>
+    <a href="#Like" class="like" title="Like this post"><div class="icon-heart"></div><div class="icon-heart icon-heart-anim"></div> <span class="num"></span></a>
+    <!--<a class="score" href="#Upvote">
+     <span class="score-inactive"> <span class="icon-up">^</span><span class="score-num">0</span> </span>
+     <span class="score-active"> <span class="icon-up">^</span><span class="score-num">1</span> </span>
+    </a>-->
    </div>
    <div class="body" data-editable="body">Body</div>
    <a class="more" href="#"><span class='readmore'>Read more</span> →</a>
@@ -97,7 +102,11 @@
  <!-- Single Post show -->
  <div class="post post-full" data-object="Post:23" data-deletable="True">
   <h1 class="title"><a href="?Post:23:Title" data-editable="title" data-editable-mode="simple" class="editable">Title</a></h1>
-  <div class="details"> <span class="published" data-editable="date_published" data-editable-mode="timestamp">21 hours ago &middot; 2 min read</span> </div>
+  <div class="details">
+   <span class="published" data-editable="date_published" data-editable-mode="timestamp">21 hours ago &middot; 2 min read</span>
+   <a href="#Like" class="like" title="Like this post"><div class="icon-heart"></div><div class="icon-heart icon-heart-anim"></div> <span class="num"></span></a>
+  </div>
+
   <div class="body" data-editable="body"></div>
 
   <h2 id="Comments"><span class="comments-num">0</span> Comments:</h2>

+ 2 - 2
js/Comments.coffee

@@ -47,7 +47,7 @@ class Comments extends Class
 	applyCommentData: (elem, comment) ->
 		[user_name, cert_domain] = comment.cert_user_id.split("@")
 		user_address = comment.directory.replace("users/", "")
-		$(".comment-body", elem).html Text.toMarked(comment.body, {"sanitize": true})
+		$(".comment-body", elem).html Text.renderMarked(comment.body, {"sanitize": true})
 		$(".user_name", elem).text(user_name).css("color": Text.toColor(comment.cert_user_id)).attr("title", "#{user_name}@#{cert_domain}: #{user_address}")
 		$(".added", elem).text(Time.since(comment.date_added)).attr("title", Time.date(comment.date_added, "long"))
 		#$(".cert_domain", elem).html("@#{cert_domain}").css("display", "none")
@@ -87,7 +87,7 @@ class Comments extends Class
 			if data
 				data = JSON.parse(data)
 			else # Default data
-				data = {"next_comment_id": 1, "comment": [], "comment_vote": {} }
+				data = {"next_comment_id": 1, "comment": [], "comment_vote": {}, "topic_vote": {} }
 
 			data.comment.push {
 				"comment_id": data.next_comment_id,

+ 173 - 59
js/ZeroBlog.coffee

@@ -4,6 +4,7 @@ class ZeroBlog extends ZeroFrame
 		@site_info = null
 		@server_info = null
 		@page = 1
+		@my_post_votes = {}
 
 		@event_page_load = $.Deferred()
 		@event_site_info = $.Deferred()
@@ -42,9 +43,8 @@ class ZeroBlog extends ZeroFrame
 				for row in res
 					@data[row.key] = row.value
 				$(".left h1 a:not(.editable-edit)").html(@data.title).data("content", @data.title)
-				$(".left h2").html(Text.toMarked(@data.description)).data("content", @data.description)
-				$(".left .links").html(Text.toMarked(@data.links)).data("content", @data.links)
-
+				$(".left h2").html(Text.renderMarked(@data.description)).data("content", @data.description)
+				$(".left .links").html(Text.renderMarked(@data.links)).data("content", @data.links)
 
 	loadLastcomments: (type="show", cb=false) ->
 		query = "
@@ -74,7 +74,7 @@ class ZeroBlog extends ZeroFrame
 	applyLastcommentdata: (elem, lastcomment) ->
 		elem.find(".user_name").text(lastcomment.cert_user_id.replace(/@.*/, "")+":")
 
-		body = Text.toMarked(lastcomment.body)
+		body = Text.renderMarked(lastcomment.body)
 		body = body.replace /[\r\n]/g, " "  # Remove whitespace
 		body = body.replace /\<blockquote\>.*?\<\/blockquote\>/g, " "  # Remove quotes
 		body = body.replace /\<.*?\>/g, " "  # Remove html codes
@@ -87,7 +87,6 @@ class ZeroBlog extends ZeroFrame
 
 	applyPagerdata: (page, limit, has_next) ->
 		pager = $(".pager")
-		console.log page, limit, has_next
 		if page > 1
 			pager.find(".prev").css("display", "inline-block").attr("href", "?page=#{page-1}")
 		if has_next
@@ -109,55 +108,94 @@ class ZeroBlog extends ZeroFrame
 
 	pagePost: () ->
 		s = (+ new Date)
-		@cmd "dbQuery", ["SELECT * FROM post WHERE post_id = #{@post_id} LIMIT 1"], (res) =>
-			if res.length
-				@applyPostdata($(".post-full"), res[0], true)
-				Comments.pagePost(@post_id)
+		@cmd "dbQuery", ["SELECT *, (SELECT COUNT(*) FROM post_vote WHERE post_vote.post_id = post.post_id) AS votes FROM post WHERE post_id = #{@post_id} LIMIT 1"], (res) =>
+			parse_res = (res) =>
+				if res.length
+					post = res[0]
+					@applyPostdata($(".post-full"), post, true)
+					$(".post-full .like").attr("id", "post_like_#{post.post_id}").on "click", @submitPostVote
+					Comments.pagePost(@post_id)
+				else
+					$(".post-full").html("<h1>Not found</h1>")
+				@pageLoaded()
+				Comments.checkCert()
+
+			# Temporary dbschema bug workaround
+			if res.error
+				@cmd "dbQuery", ["SELECT *, -1 AS votes FROM post WHERE post_id = #{@post_id} LIMIT 1"], parse_res
 			else
-				$(".post-full").html("<h1>Not found</h1>")
-			@pageLoaded()
+				parse_res(res)
 
 
 	pageMain: ->
 		limit = 15
-		@cmd "dbQuery", ["SELECT post.*, COUNT(comment_id) AS comments FROM post LEFT JOIN comment USING (post_id) GROUP BY post_id ORDER BY date_published DESC LIMIT #{(@page-1)*limit}, #{limit+1}"], (res) =>
-			s = (+ new Date)
-			if res.length > limit # Has next page
-				res.pop()
-				@applyPagerdata(@page, limit, true)
-			else
-				@applyPagerdata(@page, limit, false)
+		query = """
+			SELECT
+				post.*, COUNT(comment_id) AS comments,
+				(SELECT COUNT(*) FROM post_vote WHERE post_vote.post_id = post.post_id) AS votes
+			FROM post
+			LEFT JOIN comment USING (post_id)
+			GROUP BY post_id
+			ORDER BY date_published DESC
+			LIMIT #{(@page-1)*limit}, #{limit+1}
+		"""
+		@cmd "dbQuery", [query], (res) =>
+			parse_res = (res) =>
+				s = (+ new Date)
+				if res.length > limit # Has next page
+					res.pop()
+					@applyPagerdata(@page, limit, true)
+				else
+					@applyPagerdata(@page, limit, false)
 
-			res.reverse()
-			for post in res
-				elem = $("#post_#{post.post_id}")
-				if elem.length == 0 # Not exits yet
-					elem = $(".post.template").clone().removeClass("template").attr("id", "post_#{post.post_id}")
-					elem.prependTo(".posts")
-				@applyPostdata(elem, post)
-			@pageLoaded()
-			@log "Posts loaded in", ((+ new Date)-s),"ms"
-
-			$(".posts .new").on "click", => # Create new blog post
-				@cmd "fileGet", ["data/data.json"], (res) =>
-					data = JSON.parse(res)
-					# Add to data
-					data.post.unshift
-						post_id: data.next_post_id
-						title: "New blog post"
-						date_published: (+ new Date)/1000
-						body: "Blog post body"
-					data.next_post_id += 1
-
-					# Create html elements
-					elem = $(".post.template").clone().removeClass("template")
-					@applyPostdata(elem, data.post[0])
-					elem.hide()
-					elem.prependTo(".posts").slideDown()
-					@addInlineEditors(elem)
-
-					@writeData(data)
-				return false
+				res.reverse()
+				for post in res
+					elem = $("#post_#{post.post_id}")
+					if elem.length == 0 # Not exits yet
+						elem = $(".post.template").clone().removeClass("template").attr("id", "post_#{post.post_id}")
+						elem.prependTo(".posts")
+						# elem.find(".score").attr("id", "post_score_#{post.post_id}").on "click", @submitPostVote # Submit vote
+						elem.find(".like").attr("id", "post_like_#{post.post_id}").on "click", @submitPostVote
+					@applyPostdata(elem, post)
+				@pageLoaded()
+				@log "Posts loaded in", ((+ new Date)-s),"ms"
+
+				$(".posts .new").on "click", => # Create new blog post
+					@cmd "fileGet", ["data/data.json"], (res) =>
+						data = JSON.parse(res)
+						# Add to data
+						data.post.unshift
+							post_id: data.next_post_id
+							title: "New blog post"
+							date_published: (+ new Date)/1000
+							body: "Blog post body"
+						data.next_post_id += 1
+
+						# Create html elements
+						elem = $(".post.template").clone().removeClass("template")
+						@applyPostdata(elem, data.post[0])
+						elem.hide()
+						elem.prependTo(".posts").slideDown()
+						@addInlineEditors(elem)
+
+						@writeData(data)
+					return false
+
+			# Temporary dbschema bug workaround
+			if res.error
+				query = """
+					SELECT
+						post.*, COUNT(comment_id) AS comments,
+						-1 AS votes
+					FROM post
+					LEFT JOIN comment USING (post_id)
+					GROUP BY post_id
+					ORDER BY date_published DESC
+					LIMIT #{(@page-1)*limit}, #{limit+1}
+				"""
+				@cmd "dbQuery", [query], parse_res
+			else
+				parse_res(res)
 
 
 	# - EOF Pages -
@@ -224,24 +262,63 @@ class ZeroBlog extends ZeroFrame
 		else
 			$(".details .comments-num", elem).css("display", "none")
 
+		###
+		if @my_post_votes[post.post_id] # Voted on it
+			$(".score-inactive .score-num", elem).text post.votes-1
+			$(".score-active .score-num", elem).text post.votes
+			$(".score", elem).addClass("active")
+		else # Not voted on it
+			$(".score-inactive .score-num", elem).text post.votes
+			$(".score-active .score-num", elem).text post.votes+1
+
+		if post.votes == 0
+			$(".score", elem).addClass("noscore")
+		else
+			$(".score", elem).removeClass("noscore")
+		###
+		if post.votes > 0
+			$(".like .num", elem).text post.votes
+		else if post.votes == -1  # DB bug
+			$(".like", elem).css("display", "none")
+		else
+			$(".like .num", elem).text ""
+
+		if @my_post_votes[post.post_id] # Voted on it
+			$(".like", elem).addClass("active")
+
+
 		if full
 			body = post.body
 		else # On main page only show post until the first --- hr separator
 			body = post.body.replace(/^([\s\S]*?)\n---\n[\s\S]*$/, "$1")
 
-		$(".body", elem).html(Text.toMarked(body)).data("content", post.body)
+		if $(".body", elem).data("content") != post.body
+			$(".body", elem).html(Text.renderMarked(body)).data("content", post.body)
 
 
 	# Wrapper websocket connection ready
 	onOpenWebsocket: (e) =>
 		@loadData()
-		@routeUrl(window.location.search.substring(1))
-		@cmd "siteInfo", {}, @setSiteinfo
-		@cmd "serverInfo", {}, (ret) => # Get server info
-			@server_info = ret
-			if @server_info.rev < 160
-				@loadData("old")
-		@loadLastcomments("noanim")
+		@cmd "siteInfo", {}, (site_info) =>
+			@setSiteinfo(site_info)
+			query_my_votes = """
+				SELECT
+					'post_vote' AS type,
+					post_id AS uri
+				FROM json
+				LEFT JOIN post_vote USING (json_id)
+				WHERE directory = 'users/#{@site_info.auth_address}' AND file_name = 'data.json'
+			"""
+			@cmd "dbQuery", [query_my_votes], (res) =>
+				for row in res
+					@my_post_votes[row["uri"]] = 1
+				@routeUrl(window.location.search.substring(1))
+
+			@cmd "serverInfo", {}, (ret) => # Get server info
+				@server_info = ret
+				if @server_info.rev < 160
+					@loadData("old")
+			@loadLastcomments("noanim")
 
 
 	# Returns the elem parent object
@@ -260,7 +337,7 @@ class ZeroBlog extends ZeroFrame
 		if elem.data("editable-mode") == "simple" or raw # No markdown
 			return content
 		else
-			return Text.toMarked(content)
+			return Text.renderMarked(content)
 
 
 	# Save content to data.json
@@ -297,7 +374,7 @@ class ZeroBlog extends ZeroFrame
 						else if elem.data("editable-mode") == "timestamp" # Format timestamp
 							cb(Time.since(content))
 						else
-							cb(Text.toMarked(content))
+							cb(Text.renderMarked(content))
 					else # Error
 						cb(false)
 
@@ -315,7 +392,7 @@ class ZeroBlog extends ZeroFrame
 			@writePublish inner_path, btoa(json_raw), (res) =>
 				if res == true
 					Comments.checkCert("updaterules")
-					if cb then cb(Text.toMarked(content, {"sanitize": true}))
+					if cb then cb(Text.renderMarked(content, {"sanitize": true}))
 				else
 					@cmd "wrapperNotification", ["error", "File write error: #{res}"]
 					if cb then cb(false)
@@ -391,7 +468,43 @@ class ZeroBlog extends ZeroFrame
 				else
 					cb(res)
 
+	submitPostVote: (e) =>
+		if not Page.site_info.cert_user_id # No selected cert
+			Page.cmd "certSelect", [["zeroid.bit"]]
+			return false
+
+		elem = $(e.currentTarget)
+		elem.toggleClass("active").addClass("loading")
+		inner_path = "data/users/#{@site_info.auth_address}/data.json"
+		Page.cmd "fileGet", {"inner_path": inner_path, "required": false}, (data) =>
+			if data
+				data = JSON.parse(data)
+			else # Default data
+				data = {"next_comment_id": 1, "comment": [], "comment_vote": {}, "post_vote": {} }
+
+			if not data.post_vote
+				data.post_vote = {}
+			post_id = elem.attr("id").match("_([0-9]+)$")[1]
+
+			if elem.hasClass("active")
+				data.post_vote[post_id] = 1
+			else
+				delete data.post_vote[post_id]
+			json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t')))
+
+			current_num = parseInt elem.find(".num").text()
+			if not current_num
+				current_num = 0
+			if elem.hasClass("active")
+				elem.find(".num").text(current_num+1)
+			else
+				elem.find(".num").text(current_num-1)
+
+			Page.writePublish inner_path, btoa(json_raw), (res) =>
+				elem.removeClass("loading")
+				@log "Writepublish result", res
 
+		return false
 
 	# Parse incoming requests
 	onRequest: (cmd, message) ->
@@ -414,6 +527,7 @@ class ZeroBlog extends ZeroFrame
 		# User commented
 		if site_info.event?[0] == "file_done" and site_info.event[1].match /.*users.*data.json$/
 			if $("body").hasClass("page-post")
+				@pagePost()
 				Comments.loadComments() # Post page, reload comments
 				@loadLastcomments()
 			if $("body").hasClass("page-main")

+ 186 - 72
js/all.js

@@ -863,7 +863,7 @@
       return color;
     };
 
-    Text.prototype.toMarked = function(text, options) {
+    Text.prototype.renderMarked = function(text, options) {
       if (options == null) {
         options = {};
       }
@@ -1174,7 +1174,7 @@
       var cert_domain, user_address, user_name, _ref;
       _ref = comment.cert_user_id.split("@"), user_name = _ref[0], cert_domain = _ref[1];
       user_address = comment.directory.replace("users/", "");
-      $(".comment-body", elem).html(Text.toMarked(comment.body, {
+      $(".comment-body", elem).html(Text.renderMarked(comment.body, {
         "sanitize": true
       }));
       $(".user_name", elem).text(user_name).css({
@@ -1227,7 +1227,8 @@
             data = {
               "next_comment_id": 1,
               "comment": [],
-              "comment_vote": {}
+              "comment_vote": {},
+              "topic_vote": {}
             };
           }
           data.comment.push({
@@ -1357,6 +1358,7 @@
     function ZeroBlog() {
       this.setSiteinfo = __bind(this.setSiteinfo, this);
       this.actionSetSiteInfo = __bind(this.actionSetSiteInfo, this);
+      this.submitPostVote = __bind(this.submitPostVote, this);
       this.saveContent = __bind(this.saveContent, this);
       this.getContent = __bind(this.getContent, this);
       this.getObject = __bind(this.getObject, this);
@@ -1370,6 +1372,8 @@
       this.data = null;
       this.site_info = null;
       this.server_info = null;
+      this.page = 1;
+      this.my_post_votes = {};
       this.event_page_load = $.Deferred();
       this.event_site_info = $.Deferred();
       $.when(this.event_page_load, this.event_site_info).done((function(_this) {
@@ -1418,8 +1422,8 @@
               _this.data[row.key] = row.value;
             }
             $(".left h1 a:not(.editable-edit)").html(_this.data.title).data("content", _this.data.title);
-            $(".left h2").html(Text.toMarked(_this.data.description)).data("content", _this.data.description);
-            return $(".left .links").html(Text.toMarked(_this.data.links)).data("content", _this.data.links);
+            $(".left h2").html(Text.renderMarked(_this.data.description)).data("content", _this.data.description);
+            return $(".left .links").html(Text.renderMarked(_this.data.links)).data("content", _this.data.links);
           }
         };
       })(this));
@@ -1463,7 +1467,7 @@
     ZeroBlog.prototype.applyLastcommentdata = function(elem, lastcomment) {
       var body, title_hash;
       elem.find(".user_name").text(lastcomment.cert_user_id.replace(/@.*/, "") + ":");
-      body = Text.toMarked(lastcomment.body);
+      body = Text.renderMarked(lastcomment.body);
       body = body.replace(/[\r\n]/g, " ");
       body = body.replace(/\<blockquote\>.*?\<\/blockquote\>/g, " ");
       body = body.replace(/\<.*?\>/g, " ");
@@ -1478,7 +1482,6 @@
     ZeroBlog.prototype.applyPagerdata = function(page, limit, has_next) {
       var pager;
       pager = $(".pager");
-      console.log(page, limit, has_next);
       if (page > 1) {
         pager.find(".prev").css("display", "inline-block").attr("href", "?page=" + (page - 1));
       }
@@ -1497,77 +1500,96 @@
       } else {
         $("body").addClass("page-main");
         if (match = url.match(/page=([0-9]+)/)) {
-          return this.pageMain(parseInt(match[1]));
-        } else {
-          return this.pageMain();
+          this.page = parseInt(match[1]);
         }
+        return this.pageMain();
       }
     };
 
     ZeroBlog.prototype.pagePost = function() {
       var s;
       s = +(new Date);
-      return this.cmd("dbQuery", ["SELECT * FROM post WHERE post_id = " + this.post_id + " LIMIT 1"], (function(_this) {
+      return this.cmd("dbQuery", ["SELECT *, (SELECT COUNT(*) FROM post_vote WHERE post_vote.post_id = post.post_id) AS votes FROM post WHERE post_id = " + this.post_id + " LIMIT 1"], (function(_this) {
         return function(res) {
-          if (res.length) {
-            _this.applyPostdata($(".post-full"), res[0], true);
-            Comments.pagePost(_this.post_id);
+          var parse_res;
+          parse_res = function(res) {
+            var post;
+            if (res.length) {
+              post = res[0];
+              _this.applyPostdata($(".post-full"), post, true);
+              $(".post-full .like").attr("id", "post_like_" + post.post_id).on("click", _this.submitPostVote);
+              Comments.pagePost(_this.post_id);
+            } else {
+              $(".post-full").html("<h1>Not found</h1>");
+            }
+            _this.pageLoaded();
+            return Comments.checkCert();
+          };
+          if (res.error) {
+            return _this.cmd("dbQuery", ["SELECT *, -1 AS votes FROM post WHERE post_id = " + _this.post_id + " LIMIT 1"], parse_res);
           } else {
-            $(".post-full").html("<h1>Not found</h1>");
+            return parse_res(res);
           }
-          return _this.pageLoaded();
         };
       })(this));
     };
 
-    ZeroBlog.prototype.pageMain = function(page) {
-      var limit;
-      if (page == null) {
-        page = 1;
-      }
+    ZeroBlog.prototype.pageMain = function() {
+      var limit, query;
       limit = 15;
-      return this.cmd("dbQuery", ["SELECT post.*, COUNT(comment_id) AS comments FROM post LEFT JOIN comment USING (post_id) GROUP BY post_id ORDER BY date_published DESC LIMIT " + ((page - 1) * limit) + ", " + (limit + 1)], (function(_this) {
+      query = "SELECT\n	post.*, COUNT(comment_id) AS comments,\n	(SELECT COUNT(*) FROM post_vote WHERE post_vote.post_id = post.post_id) AS votes\nFROM post\nLEFT JOIN comment USING (post_id)\nGROUP BY post_id\nORDER BY date_published DESC\nLIMIT " + ((this.page - 1) * limit) + ", " + (limit + 1);
+      return this.cmd("dbQuery", [query], (function(_this) {
         return function(res) {
-          var elem, post, s, _i, _len;
-          s = +(new Date);
-          if (res.length > limit) {
-            res.pop();
-            _this.applyPagerdata(page, limit, true);
-          } else {
-            _this.applyPagerdata(page, limit, false);
-          }
-          res.reverse();
-          for (_i = 0, _len = res.length; _i < _len; _i++) {
-            post = res[_i];
-            elem = $("#post_" + post.post_id);
-            if (elem.length === 0) {
-              elem = $(".post.template").clone().removeClass("template").attr("id", "post_" + post.post_id);
-              elem.prependTo(".posts");
+          var parse_res;
+          parse_res = function(res) {
+            var elem, post, s, _i, _len;
+            s = +(new Date);
+            if (res.length > limit) {
+              res.pop();
+              _this.applyPagerdata(_this.page, limit, true);
+            } else {
+              _this.applyPagerdata(_this.page, limit, false);
             }
-            _this.applyPostdata(elem, post);
-          }
-          _this.pageLoaded();
-          _this.log("Posts loaded in", (+(new Date)) - s, "ms");
-          return $(".posts .new").on("click", function() {
-            _this.cmd("fileGet", ["data/data.json"], function(res) {
-              var data;
-              data = JSON.parse(res);
-              data.post.unshift({
-                post_id: data.next_post_id,
-                title: "New blog post",
-                date_published: (+(new Date)) / 1000,
-                body: "Blog post body"
+            res.reverse();
+            for (_i = 0, _len = res.length; _i < _len; _i++) {
+              post = res[_i];
+              elem = $("#post_" + post.post_id);
+              if (elem.length === 0) {
+                elem = $(".post.template").clone().removeClass("template").attr("id", "post_" + post.post_id);
+                elem.prependTo(".posts");
+                elem.find(".like").attr("id", "post_like_" + post.post_id).on("click", _this.submitPostVote);
+              }
+              _this.applyPostdata(elem, post);
+            }
+            _this.pageLoaded();
+            _this.log("Posts loaded in", (+(new Date)) - s, "ms");
+            return $(".posts .new").on("click", function() {
+              _this.cmd("fileGet", ["data/data.json"], function(res) {
+                var data;
+                data = JSON.parse(res);
+                data.post.unshift({
+                  post_id: data.next_post_id,
+                  title: "New blog post",
+                  date_published: (+(new Date)) / 1000,
+                  body: "Blog post body"
+                });
+                data.next_post_id += 1;
+                elem = $(".post.template").clone().removeClass("template");
+                _this.applyPostdata(elem, data.post[0]);
+                elem.hide();
+                elem.prependTo(".posts").slideDown();
+                _this.addInlineEditors(elem);
+                return _this.writeData(data);
               });
-              data.next_post_id += 1;
-              elem = $(".post.template").clone().removeClass("template");
-              _this.applyPostdata(elem, data.post[0]);
-              elem.hide();
-              elem.prependTo(".posts").slideDown();
-              _this.addInlineEditors(elem);
-              return _this.writeData(data);
+              return false;
             });
-            return false;
-          });
+          };
+          if (res.error) {
+            query = "SELECT\n	post.*, COUNT(comment_id) AS comments,\n	-1 AS votes\nFROM post\nLEFT JOIN comment USING (post_id)\nGROUP BY post_id\nORDER BY date_published DESC\nLIMIT " + ((_this.page - 1) * limit) + ", " + (limit + 1);
+            return _this.cmd("dbQuery", [query], parse_res);
+          } else {
+            return parse_res(res);
+          }
         };
       })(this));
     };
@@ -1645,27 +1667,65 @@
       } else {
         $(".details .comments-num", elem).css("display", "none");
       }
+
+      /*
+      		if @my_post_votes[post.post_id] # Voted on it
+      			$(".score-inactive .score-num", elem).text post.votes-1
+      			$(".score-active .score-num", elem).text post.votes
+      			$(".score", elem).addClass("active")
+      		else # Not voted on it
+      			$(".score-inactive .score-num", elem).text post.votes
+      			$(".score-active .score-num", elem).text post.votes+1
+      
+      		if post.votes == 0
+      			$(".score", elem).addClass("noscore")
+      		else
+      			$(".score", elem).removeClass("noscore")
+       */
+      if (post.votes > 0) {
+        $(".like .num", elem).text(post.votes);
+      } else if (post.votes === -1) {
+        $(".like", elem).css("display", "none");
+      } else {
+        $(".like .num", elem).text("");
+      }
+      if (this.my_post_votes[post.post_id]) {
+        $(".like", elem).addClass("active");
+      }
       if (full) {
         body = post.body;
       } else {
         body = post.body.replace(/^([\s\S]*?)\n---\n[\s\S]*$/, "$1");
       }
-      return $(".body", elem).html(Text.toMarked(body)).data("content", post.body);
+      if ($(".body", elem).data("content") !== post.body) {
+        return $(".body", elem).html(Text.renderMarked(body)).data("content", post.body);
+      }
     };
 
     ZeroBlog.prototype.onOpenWebsocket = function(e) {
       this.loadData();
-      this.routeUrl(window.location.search.substring(1));
-      this.cmd("siteInfo", {}, this.setSiteinfo);
-      this.cmd("serverInfo", {}, (function(_this) {
-        return function(ret) {
-          _this.server_info = ret;
-          if (_this.server_info.rev < 160) {
-            return _this.loadData("old");
-          }
+      return this.cmd("siteInfo", {}, (function(_this) {
+        return function(site_info) {
+          var query_my_votes;
+          _this.setSiteinfo(site_info);
+          query_my_votes = "SELECT\n	'post_vote' AS type,\n	post_id AS uri\nFROM json\nLEFT JOIN post_vote USING (json_id)\nWHERE directory = 'users/" + _this.site_info.auth_address + "' AND file_name = 'data.json'";
+          _this.cmd("dbQuery", [query_my_votes], function(res) {
+            var row, _i, _len;
+            for (_i = 0, _len = res.length; _i < _len; _i++) {
+              row = res[_i];
+              _this.my_post_votes[row["uri"]] = 1;
+            }
+            return _this.routeUrl(window.location.search.substring(1));
+          });
+          _this.cmd("serverInfo", {}, function(ret) {
+            _this.server_info = ret;
+            if (_this.server_info.rev < 160) {
+              return _this.loadData("old");
+            }
+          });
+          return _this.loadLastcomments("noanim");
         };
       })(this));
-      return this.loadLastcomments("noanim");
     };
 
     ZeroBlog.prototype.getObject = function(elem) {
@@ -1686,7 +1746,7 @@
       if (elem.data("editable-mode") === "simple" || raw) {
         return content;
       } else {
-        return Text.toMarked(content);
+        return Text.renderMarked(content);
       }
     };
 
@@ -1741,7 +1801,7 @@
                 } else if (elem.data("editable-mode") === "timestamp") {
                   return cb(Time.since(content));
                 } else {
-                  return cb(Text.toMarked(content));
+                  return cb(Text.renderMarked(content));
                 }
               } else {
                 return cb(false);
@@ -1782,7 +1842,7 @@
             if (res === true) {
               Comments.checkCert("updaterules");
               if (cb) {
-                return cb(Text.toMarked(content, {
+                return cb(Text.renderMarked(content, {
                   "sanitize": true
                 }));
               }
@@ -1936,6 +1996,59 @@
       })(this));
     };
 
+    ZeroBlog.prototype.submitPostVote = function(e) {
+      var elem, inner_path;
+      if (!Page.site_info.cert_user_id) {
+        Page.cmd("certSelect", [["zeroid.bit"]]);
+        return false;
+      }
+      elem = $(e.currentTarget);
+      elem.toggleClass("active").addClass("loading");
+      inner_path = "data/users/" + this.site_info.auth_address + "/data.json";
+      Page.cmd("fileGet", {
+        "inner_path": inner_path,
+        "required": false
+      }, (function(_this) {
+        return function(data) {
+          var current_num, json_raw, post_id;
+          if (data) {
+            data = JSON.parse(data);
+          } else {
+            data = {
+              "next_comment_id": 1,
+              "comment": [],
+              "comment_vote": {},
+              "post_vote": {}
+            };
+          }
+          if (!data.post_vote) {
+            data.post_vote = {};
+          }
+          post_id = elem.attr("id").match("_([0-9]+)$")[1];
+          if (elem.hasClass("active")) {
+            data.post_vote[post_id] = 1;
+          } else {
+            delete data.post_vote[post_id];
+          }
+          json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\t')));
+          current_num = parseInt(elem.find(".num").text());
+          if (!current_num) {
+            current_num = 0;
+          }
+          if (elem.hasClass("active")) {
+            elem.find(".num").text(current_num + 1);
+          } else {
+            elem.find(".num").text(current_num - 1);
+          }
+          return Page.writePublish(inner_path, btoa(json_raw), function(res) {
+            elem.removeClass("loading");
+            return _this.log("Writepublish result", res);
+          });
+        };
+      })(this));
+      return false;
+    };
+
     ZeroBlog.prototype.onRequest = function(cmd, message) {
       if (cmd === "setSiteInfo") {
         return this.actionSetSiteInfo(message);
@@ -1958,6 +2071,7 @@
       }
       if (((_ref = site_info.event) != null ? _ref[0] : void 0) === "file_done" && site_info.event[1].match(/.*users.*data.json$/)) {
         if ($("body").hasClass("page-post")) {
+          this.pagePost();
           Comments.loadComments();
           this.loadLastcomments();
         }

+ 1 - 1
js/utils/Text.coffee

@@ -15,7 +15,7 @@ class Text
 		return color
 
 
-	toMarked: (text, options={}) ->
+	renderMarked: (text, options={}) ->
 		options["gfm"] = true
 		options["breaks"] = true
 		if options.sanitize