├── LICENSE ├── README.md ├── alloy-editor ├── all.css ├── all.js ├── alloy-editor-all-min.js ├── alloy-editor-config.js ├── alloy-editor-ocean.css ├── editor.css ├── meditor.css └── to-markdown.js ├── content.json ├── css ├── Comments.css ├── Follow.css ├── Menu.css ├── ZeroBlog.css ├── all.css ├── dark.css ├── fonts.css ├── hljs-github.css ├── hljs-railscasts.css ├── icons.css ├── mobile.css └── zoom.css ├── data-default ├── data.json └── users │ └── content-default.json ├── dbschema.json ├── img ├── avatar.png └── loading.gif ├── index.html ├── js ├── Comments.coffee ├── ZeroBlog.coffee ├── all.js ├── lib │ ├── 00-jquery.min.js │ ├── all.js │ ├── highlight.min.js │ ├── identicon.js │ ├── jquery.cssanim.coffee │ ├── jquery.csslater.coffee │ ├── marked.min.js │ ├── pnglib.js │ └── zoom.min.js └── utils │ ├── Class.coffee │ ├── CustomAlloyEditor.coffee │ ├── Follow.coffee │ ├── InlineEditor.coffee │ ├── Meditor.coffee │ ├── Menu.coffee │ ├── RateLimit.coffee │ ├── Text.coffee │ ├── Time.coffee │ └── ZeroFrame.coffee └── languages ├── de.json ├── es.json ├── fr.json ├── it.json ├── nl.json ├── pl.json ├── pt-br └── zh.json /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZeroBlog 2 | Demo for decentralized, self publishing blogging platform. 3 | 4 | ## Screenshot 5 | 6 | ![Screenshot](http://i.imgur.com/diTYHcm.png) 7 | 8 | ZeroNet address: http://127.0.0.1:43110/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8 9 | 10 | To change the avatar replace the image /img/avatar.png with a new png, 60 by 60 pixels is recommended. -------------------------------------------------------------------------------- /alloy-editor/alloy-editor-config.js: -------------------------------------------------------------------------------- 1 | CKEDITOR.lang['en'] = {"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protocol","upload":"Upload","uploadSubmit":"Send it to the Server","image":"Image","flash":"Flash","form":"Form","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"","id":"Id","name":"Name","langDir":"Language Direction","langDirLtr":"Left to Right (LTR)","langDirRtl":"Right to Left (RTL)","langCode":"Language Code","longDescr":"Long Description URL","cssClass":"Stylesheet Classes","advisoryTitle":"Advisory Title","cssStyle":"Style","ok":"OK","cancel":"Cancel","close":"Close","preview":"Preview","resize":"Resize","generalTab":"General","advancedTab":"Advanced","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Target","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"Width","height":"Height","align":"Alignment","alignLeft":"Left","alignRight":"Right","alignCenter":"Center","alignJustify":"Justify","alignTop":"Top","alignMiddle":"Middle","alignBottom":"Bottom","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1, unavailable","keyboard":{"8":"Backspace","13":"Enter","16":"Shift","17":"Ctrl","18":"Alt","32":"Space","35":"End","36":"Home","46":"Delete","224":"Command"},"keyboardShortcut":"Keyboard shortcut"},"basicstyles":{"bold":"Bold","italic":"Italic","strike":"Strikethrough","subscript":"Subscript","superscript":"Superscript","underline":"Underline"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Copy","copyError":"Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl/Cmd+C).","cut":"Cut","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"Paste","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (Ctrl/Cmd+V) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Paste"},"horizontalrule":{"toolbar":"Insert Horizontal Line"},"indent":{"indent":"Increase Indent","outdent":"Decrease Indent"},"justify":{"block":"Justify","center":"Center","left":"Align Left","right":"Align Right"},"list":{"bulletedlist":"Insert/Remove Bulleted List","numberedlist":"Insert/Remove Numbered List"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Paste from Word","toolbar":"Paste from Word"},"removeformat":{"toolbar":"Remove Format"},"undo":{"redo":"Redo","undo":"Undo"},"widget":{"move":"Click and drag to move","label":"%1 widget"}}; 2 | CKEDITOR.config.stylesSet = []; // We don't need styles.js 3 | CKEDITOR.document.appendStyleSheet = function(e) { return e }; // We load editor.css manually 4 | AlloyEditor.Strings = {"alignCenter":"Center","alignJustify":"Justify","alignLeft":"Left","alignRight":"Right","bold":"Bold","bulletedlist":"Insert/Remove Bulleted List","cancel":"Cancel","horizontalrule":"Insert Horizontal Line","italic":"Italic","numberedlist":"Insert/Remove Numbered List","quote":"Block Quote","removeformat":"Remove Format","strike":"Strikethrough","subscript":"Subscript","superscript":"Superscript","underline":"Underline","formatted":"Formatted","h1":"Heading 1","h2":"Heading 2","normal":"Normal","blockStyles":"Block Styles","inlineStyles":"Inline Styles","objectStyles":"Object Styles","styles":"Styles","cell":"Cell","cellDelete":"Delete Cells","cellInsertAfter":"Insert Cell After","cellInsertBefore":"Insert Cell Before","cellMerge":"Merge Cells","cellMergeDown":"Merge Down","cellMergeRight":"Merge Right","cellSplitHorizontal":"Split Cell Horizontally","cellSplitVertical":"Split Cell Vertically","column":"Column","columnDelete":"Delete Columns","columnInsertAfter":"Insert Column After","columnInsertBefore":"Insert Column Before","deleteTable":"Delete Table","row":"Row","rowDelete":"Delete Rows","rowInsertAfter":"Insert Row After","rowInsertBefore":"Insert Row Before","add":"Add","ariaUpdateNoToolbar":"No toolbars are available","ariaUpdateOneToolbar":"{toolbars} toolbar is available. Press ALT+F10 to focus.","ariaUpdateManyToolbars":"{toolbars} toolbars are available. Press ALT+F10 to focus.","camera":"Insert Image from Camera","cite":"Cite","code":"Code","clearInput":"Clear Input Field","confirm":"Confirm","editLink":"Type or paste link here","image":"Insert Image","link":"Link","removeLink":"Remove link","table":"Insert Table","twitter":"Twitter","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","linkTargetBlank":"_blank (new tab)","linkTargetDefault":"default","linkTargetParent":"_parent","linkTargetSelf":"_self (same tab)","linkTargetTop":"_top","cameraDisabled":"The browser does not support this action, or it is available on https only (Chrome).","indent":"Increase Indent","outdent":"Decrease Indent","deleteEmbed":"Delete embed","columns":"Cols","rows":"Rows"}; 5 | AlloyEditor._langResourceRequested = true; // We include en.js 6 | 7 | // Inline code button 8 | React = AlloyEditor.React; 9 | var ButtonInlineCode = React.createClass({ 10 | displayName: 'ButtonInlineCode', 11 | mixins: [AlloyEditor.ButtonStyle, AlloyEditor.ButtonStateClasses, AlloyEditor.ButtonActionStyle], 12 | propTypes: { 13 | editor: React.PropTypes.object.isRequired, 14 | label: React.PropTypes.string, 15 | tabIndex: React.PropTypes.number 16 | }, 17 | 18 | statics: { 19 | key: 'inlinecode' 20 | }, 21 | 22 | getDefaultProps: function getDefaultProps() { 23 | return { 24 | style: { 25 | element: 'code' 26 | } 27 | }; 28 | }, 29 | 30 | render: function render() { 31 | var cssClass = 'ae-button ' + this.getStateClasses(); 32 | 33 | return React.createElement( 34 | 'button', 35 | { 'aria-label': AlloyEditor.Strings.code, 'aria-pressed': cssClass.indexOf('pressed') !== -1, className: cssClass, 'data-type': 'button-code', onClick: this.applyStyle, tabIndex: this.props.tabIndex, title: AlloyEditor.Strings.code }, 36 | React.createElement('span', { className: 'ae-icon-code' }) 37 | ); 38 | } 39 | }); 40 | 41 | AlloyEditor.Buttons[ButtonInlineCode.key] = AlloyEditor.ButtonInlineCode = ButtonInlineCode; 42 | 43 | 44 | var ButtonH3 = React.createClass({ 45 | displayName: 'ButtonH3', 46 | mixins: [AlloyEditor.ButtonStyle, AlloyEditor.ButtonStateClasses, AlloyEditor.ButtonActionStyle], 47 | propTypes: { 48 | editor: React.PropTypes.object.isRequired, 49 | label: React.PropTypes.string, 50 | tabIndex: React.PropTypes.number 51 | }, 52 | 53 | statics: { 54 | key: 'h3' 55 | }, 56 | 57 | getDefaultProps: function getDefaultProps() { 58 | return { 59 | style: { 60 | element: 'h3' 61 | } 62 | }; 63 | }, 64 | 65 | render: function render() { 66 | var cssClass = 'ae-button ' + this.getStateClasses(); 67 | 68 | return React.createElement( 69 | 'button', 70 | { 'aria-label': AlloyEditor.Strings.h3, 'aria-pressed': cssClass.indexOf('pressed') !== -1, className: cssClass, 'data-type': 'button-h3', onClick: this.applyStyle, tabIndex: this.props.tabIndex, title: "Heading 3" }, 71 | React.createElement('span', { className: 'ae-icon-h3' }) 72 | ); 73 | } 74 | }); 75 | 76 | AlloyEditor.Buttons[ButtonH3.key] = AlloyEditor.ButtonH3 = ButtonH3; 77 | 78 | 79 | selections = AlloyEditor.Selections 80 | selections.unshift({ 81 | name: 'text', 82 | buttons: ['bold', 'italic', 'strike', 'inlinecode', 'link'], 83 | test: AlloyEditor.SelectionTest.text 84 | }) 85 | // Remove Image toolbar 86 | AlloyEditor.Core.ATTRS.toolbars.value.styles.selections.forEach( 87 | function(selection, i, selections) { 88 | if (selection.name == "image") selections.splice(i, 1) 89 | } 90 | ) 91 | 92 | 93 | // Other settings 94 | CKEDITOR.config.language = "en" 95 | CKEDITOR.config.title = "" 96 | CKEDITOR.config.fullPage = true 97 | CKEDITOR.config.pasteFromWordRemoveFontStyles = true 98 | CKEDITOR.config.removePlugins = "dragdrop" 99 | AlloyEditor.Core.ATTRS.removePlugins.value += ',ae_embed,ae_imagealignment,ae_dragresize' 100 | AlloyEditor.Buttons.linkEdit.defaultProps.appendProtocol = false 101 | CKEDITOR.config.buttonCfg = { 102 | buttonLinkEdit: { 103 | appendProtocol: false 104 | } 105 | } 106 | AlloyEditor.Core.ATTRS.toolbars.value = { 107 | add: { 108 | buttons: ['h2', 'h3', 'quote', 'code', 'hline', 'table', 'image'], 109 | tabIndex: 2 110 | }, 111 | styles: { 112 | selections: selections, 113 | tabIndex: 1 114 | } 115 | } 116 | CKEDITOR.config.customConfig = ""; 117 | 118 | // Helpers 119 | 120 | function createSelectionFromPoint(startX, startY, endX, endY) { // Select text based on x y coordinates 121 | var doc = document; 122 | var start, end, range = null; 123 | if (typeof doc.caretPositionFromPoint != "undefined") { 124 | start = doc.caretPositionFromPoint(startX, startY); 125 | end = doc.caretPositionFromPoint(endX, endY); 126 | range = doc.createRange(); 127 | range.setStart(start.offsetNode, start.offset); 128 | range.setEnd(end.offsetNode, end.offset); 129 | } else if (typeof doc.caretRangeFromPoint != "undefined") { 130 | start = doc.caretRangeFromPoint(startX, startY); 131 | end = doc.caretRangeFromPoint(endX, endY); 132 | range = doc.createRange(); 133 | range.setStart(start.startContainer, start.startOffset); 134 | range.setEnd(end.startContainer, end.startOffset); 135 | } 136 | if (range !== null && typeof window.getSelection != "undefined") { 137 | var sel = window.getSelection(); 138 | sel.removeAllRanges(); 139 | sel.addRange(range); 140 | } else if (typeof doc.body.createTextRange != "undefined") { 141 | range = doc.body.createTextRange(); 142 | range.moveToPoint(startX, startY); 143 | var endRange = range.duplicate(); 144 | endRange.moveToPoint(endX, endY); 145 | range.setEndPoint("EndToEnd", endRange); 146 | range.select(); 147 | } 148 | } 149 | 150 | function selectElement(editor, elem) { // Select CKEditor element 151 | var range = new CKEDITOR.dom.range(editor.document); 152 | range.moveToElementEditablePosition(elem, true); 153 | range.setStart(elem, 0) 154 | range.setEnd(elem, elem.getChildCount()) 155 | editor.getSelection().selectRanges([range]); 156 | } -------------------------------------------------------------------------------- /alloy-editor/meditor.css: -------------------------------------------------------------------------------- 1 | .meditor { z-index: 999; position: relative; } 2 | .meditor-editmode { 3 | float: right; font-family: monospace; border: none; font-weight: normal; font-size: 12px; z-index: 999; position: relative; 4 | padding: 5px; border: 1px solid #d2d0d0; border-radius: 5px; transition: all 0.3s; margin-top: -38px; 5 | } 6 | .meditor-editmode:hover { border-color: #b381bb; transition: none; color: #333; } 7 | .meditor-editmode:focus { transition: all 0.3s; } 8 | .meditor-editmode.markdown { border-color: #9C27B0; background-color: #9C27B0; color: white; } 9 | .meditor-markdown { 10 | width: 100%; font-family: monospace; font-size: 14px; line-height: 1.6; box-sizing: border-box; padding: 10px; 11 | border: 1px solid #bbb; border-radius: 3px; box-shadow: inset 0px 4px 5px -5px rgba(0, 0, 0, 0.4); overflow: hidden; 12 | } 13 | 14 | .ae-icon-h2:before { content: "T"; display: inline-block; line-height: 0px; vertical-align: 3px; font-size: 23px; font-family: serif; } 15 | .ae-icon-h3:before { content: "T"; display: inline-block; line-height: 0px; vertical-align: 4px; font-size: 16px; font-family: serif; } 16 | 17 | .ae-button:focus { outline: none } 18 | .ae-ui .ae-toolbar-styles, .ae-ui .ae-toolbar-add, .ae-ui .ae-toolbar { z-index: 999 } 19 | 20 | .ae-icon-inlinecode { font-family: monospace; font-size: 10px; word-break: break-all; line-height: 9px; padding: 4px; } 21 | .ae-icon-inlinecode:before { content: "0101"; } 22 | .ae-ui .ae-toolbar-add { transition: top 0.3s, opacity 0.3s; opacity: 0 !important; } 23 | .ae-ui .ae-toolbar-add.emptyline, .ae-ui .ae-toolbar-add:hover { opacity: 1 !important; } 24 | .ae-container-edit-link-target { display: none !important; } 25 | .cke_editable { outline: none; } 26 | 27 | h2.empty:before { content: "Heading"; color: #DDD; position: absolute; pointer-events: none; } 28 | h3.empty:before { content: "Sub-Heading"; color: #DDD; position: absolute; pointer-events: none; } 29 | 30 | .meditor hr:first-of-type { display: block !important; position: absolute; margin: 0px; border-bottom: 1px dashed #DEDEDE; width: 100% } -------------------------------------------------------------------------------- /alloy-editor/to-markdown.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.toMarkdown = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { 62 | elem = inqueue.shift() 63 | outqueue.push(elem) 64 | children = elem.childNodes 65 | for (i = 0; i < children.length; i++) { 66 | if (children[i].nodeType === 1) inqueue.push(children[i]) 67 | } 68 | } 69 | outqueue.shift() 70 | return outqueue 71 | } 72 | 73 | /* 74 | * Contructs a Markdown string of replacement text for a given node 75 | */ 76 | 77 | function getContent (node) { 78 | var text = '' 79 | for (var i = 0; i < node.childNodes.length; i++) { 80 | if (node.childNodes[i].nodeType === 1) { 81 | text += node.childNodes[i]._replacement 82 | } else if (node.childNodes[i].nodeType === 3) { 83 | text += node.childNodes[i].data 84 | } else continue 85 | } 86 | return text 87 | } 88 | 89 | /* 90 | * Returns the HTML string of an element with its contents converted 91 | */ 92 | 93 | function outer (node, content) { 94 | return node.cloneNode(false).outerHTML.replace('><', '>' + content + '<') 95 | } 96 | 97 | function canConvert (node, filter) { 98 | if (typeof filter === 'string') { 99 | return filter === node.nodeName.toLowerCase() 100 | } 101 | if (Array.isArray(filter)) { 102 | return filter.indexOf(node.nodeName.toLowerCase()) !== -1 103 | } else if (typeof filter === 'function') { 104 | return filter.call(toMarkdown, node) 105 | } else { 106 | throw new TypeError('`filter` needs to be a string, array, or function') 107 | } 108 | } 109 | 110 | function isFlankedByWhitespace (side, node) { 111 | var sibling 112 | var regExp 113 | var isFlanked 114 | 115 | if (side === 'left') { 116 | sibling = node.previousSibling 117 | regExp = / $/ 118 | } else { 119 | sibling = node.nextSibling 120 | regExp = /^ / 121 | } 122 | 123 | if (sibling) { 124 | if (sibling.nodeType === 3) { 125 | isFlanked = regExp.test(sibling.nodeValue) 126 | } else if (sibling.nodeType === 1 && !isBlock(sibling)) { 127 | isFlanked = regExp.test(sibling.textContent) 128 | } 129 | } 130 | return isFlanked 131 | } 132 | 133 | function flankingWhitespace (node, content) { 134 | var leading = '' 135 | var trailing = '' 136 | 137 | if (!isBlock(node)) { 138 | var hasLeading = /^[ \r\n\t]/.test(content) 139 | var hasTrailing = /[ \r\n\t]$/.test(content) 140 | 141 | if (hasLeading && !isFlankedByWhitespace('left', node)) { 142 | leading = ' ' 143 | } 144 | if (hasTrailing && !isFlankedByWhitespace('right', node)) { 145 | trailing = ' ' 146 | } 147 | } 148 | 149 | return { leading: leading, trailing: trailing } 150 | } 151 | 152 | /* 153 | * Finds a Markdown converter, gets the replacement, and sets it on 154 | * `_replacement` 155 | */ 156 | 157 | function process (node) { 158 | var replacement 159 | var content = getContent(node) 160 | 161 | // Remove blank nodes 162 | if (!isVoid(node) && !/A|TH|TD/.test(node.nodeName) && /^\s*$/i.test(content)) { 163 | node._replacement = '' 164 | return 165 | } 166 | 167 | for (var i = 0; i < converters.length; i++) { 168 | var converter = converters[i] 169 | 170 | if (canConvert(node, converter.filter)) { 171 | if (typeof converter.replacement !== 'function') { 172 | throw new TypeError( 173 | '`replacement` needs to be a function that returns a string' 174 | ) 175 | } 176 | 177 | var whitespace = flankingWhitespace(node, content) 178 | 179 | if (whitespace.leading || whitespace.trailing) { 180 | content = content.trim() 181 | } 182 | replacement = whitespace.leading + 183 | converter.replacement.call(toMarkdown, content, node) + 184 | whitespace.trailing 185 | break 186 | } 187 | } 188 | 189 | node._replacement = replacement 190 | } 191 | 192 | toMarkdown = function (input, options) { 193 | options = options || {} 194 | 195 | if (typeof input !== 'string') { 196 | throw new TypeError(input + ' is not a string') 197 | } 198 | 199 | if (input === '') { 200 | return '' 201 | } 202 | 203 | // Escape potential ol triggers 204 | input = input.replace(/(\d+)\. /g, '$1\\. ') 205 | 206 | var clone = htmlToDom(input).body 207 | var nodes = bfsOrder(clone) 208 | var output 209 | 210 | converters = mdConverters.slice(0) 211 | if (options.gfm) { 212 | converters = gfmConverters.concat(converters) 213 | } 214 | 215 | if (options.converters) { 216 | converters = options.converters.concat(converters) 217 | } 218 | 219 | // Process through nodes in reverse (so deepest child elements are first). 220 | for (var i = nodes.length - 1; i >= 0; i--) { 221 | process(nodes[i]) 222 | } 223 | output = getContent(clone) 224 | 225 | return output.replace(/^[\t\r\n]+|[\t\r\n\s]+$/g, '') 226 | .replace(/\n\s+\n/g, '\n\n') 227 | .replace(/\n{3,}/g, '\n\n') 228 | } 229 | 230 | toMarkdown.isBlock = isBlock 231 | toMarkdown.isVoid = isVoid 232 | toMarkdown.outer = outer 233 | 234 | module.exports = toMarkdown 235 | 236 | },{"./lib/gfm-converters":2,"./lib/html-parser":3,"./lib/md-converters":4,"collapse-whitespace":7}],2:[function(require,module,exports){ 237 | 'use strict' 238 | 239 | function cell (content, node) { 240 | var index = Array.prototype.indexOf.call(node.parentNode.childNodes, node) 241 | var prefix = ' ' 242 | if (index === 0) prefix = '| ' 243 | return prefix + content + ' |' 244 | } 245 | 246 | var highlightRegEx = /lang-(\S+)/ 247 | 248 | module.exports = [ 249 | { 250 | filter: 'br', 251 | replacement: function () { 252 | return '\n' 253 | } 254 | }, 255 | { 256 | filter: ["main", "article", "section", "figure", "footer", "header", "body", "figcaption"], 257 | replacement: function (content) { 258 | return content 259 | } 260 | }, 261 | { 262 | filter: ['del', 's', 'strike'], 263 | replacement: function (content) { 264 | return '~~' + content + '~~' 265 | } 266 | }, 267 | 268 | { 269 | filter: function (node) { 270 | return node.type === 'checkbox' && node.parentNode.nodeName === 'LI' 271 | }, 272 | replacement: function (content, node) { 273 | return (node.checked ? '[x]' : '[ ]') + ' ' 274 | } 275 | }, 276 | 277 | { 278 | filter: ['th', 'td'], 279 | replacement: function (content, node) { 280 | return cell(content, node) 281 | } 282 | }, 283 | 284 | { 285 | filter: 'tr', 286 | replacement: function (content, node) { 287 | var borderCells = '' 288 | var alignMap = { left: ':--', right: '--:', center: ':-:' } 289 | 290 | if (node.parentNode.nodeName === 'THEAD') { 291 | for (var i = 0; i < node.childNodes.length; i++) { 292 | var align = node.childNodes[i].attributes.align 293 | var border = '---' 294 | 295 | if (align) border = alignMap[align.value] || border 296 | 297 | borderCells += cell(border, node.childNodes[i]) 298 | } 299 | } 300 | return '\n' + content + (borderCells ? '\n' + borderCells : '') 301 | } 302 | }, 303 | 304 | { 305 | filter: 'table', 306 | replacement: function (content) { 307 | if (content.indexOf("-") == -1) { 308 | var firstline = content.split("\n")[1] 309 | console.log("table", content) 310 | content = firstline.replace(/[^|]/g, " ") + "\n" + firstline.replace(/[^|]/g, "-") + content 311 | } 312 | return '\n\n' + content + '\n\n' 313 | } 314 | }, 315 | 316 | { 317 | filter: ['thead', 'tbody', 'tfoot'], 318 | replacement: function (content) { 319 | return content 320 | } 321 | }, 322 | 323 | // Syntax-highlighted code blocks 324 | { 325 | filter: function (node) { 326 | return node.nodeName === 'PRE' && 327 | node.firstChild && 328 | node.firstChild.nodeName === 'CODE' && 329 | highlightRegEx.test(node.firstChild.className) 330 | }, 331 | replacement: function (content, node) { 332 | var language = node.firstChild.className.match(highlightRegEx)[1] 333 | return '\n\n```' + language + '\n' + node.innerText.trimRight() + '\n```\n\n' 334 | } 335 | }, 336 | 337 | // Fenced code blocks 338 | { 339 | filter: function (node) { 340 | return node.nodeName === 'PRE' && 341 | node.firstChild && 342 | node.firstChild.nodeName === 'CODE' 343 | }, 344 | replacement: function (content, node) { 345 | return '\n\n```\n' + node.firstChild.innerText.trimRight() + '\n```\n\n' 346 | } 347 | }, 348 | 349 | // Fenced code blocks 350 | { 351 | filter: function (node) { 352 | return node.nodeName === 'PRE' 353 | }, 354 | replacement: function (content, node) { 355 | return '\n\n```\n' + node.innerText.trimRight() + '\n```\n\n' 356 | } 357 | }, 358 | 359 | { 360 | filter: function (node) { 361 | return node.nodeName === 'DIV' && 362 | highlightRegEx.test(node.className) 363 | }, 364 | replacement: function (content) { 365 | return '\n\n' + content + '\n\n' 366 | } 367 | } 368 | ] 369 | 370 | },{}],3:[function(require,module,exports){ 371 | /* 372 | * Set up window for Node.js 373 | */ 374 | 375 | var _window = (typeof window !== 'undefined' ? window : this) 376 | 377 | /* 378 | * Parsing HTML strings 379 | */ 380 | 381 | function canParseHtmlNatively () { 382 | var Parser = _window.DOMParser 383 | var canParse = false 384 | 385 | // Adapted from https://gist.github.com/1129031 386 | // Firefox/Opera/IE throw errors on unsupported types 387 | try { 388 | // WebKit returns null on unsupported types 389 | if (new Parser().parseFromString('', 'text/html')) { 390 | canParse = true 391 | } 392 | } catch (e) {} 393 | 394 | return canParse 395 | } 396 | 397 | function createHtmlParser () { 398 | var Parser = function () {} 399 | 400 | // For Node.js environments 401 | if (typeof document === 'undefined') { 402 | var jsdom = require('jsdom') 403 | Parser.prototype.parseFromString = function (string) { 404 | return jsdom.jsdom(string, { 405 | features: { 406 | FetchExternalResources: [], 407 | ProcessExternalResources: false 408 | } 409 | }) 410 | } 411 | } else { 412 | if (!shouldUseActiveX()) { 413 | Parser.prototype.parseFromString = function (string) { 414 | var doc = document.implementation.createHTMLDocument('') 415 | doc.open() 416 | doc.write(string) 417 | doc.close() 418 | return doc 419 | } 420 | } else { 421 | Parser.prototype.parseFromString = function (string) { 422 | var doc = new window.ActiveXObject('htmlfile') 423 | doc.designMode = 'on' // disable on-page scripts 424 | doc.open() 425 | doc.write(string) 426 | doc.close() 427 | return doc 428 | } 429 | } 430 | } 431 | return Parser 432 | } 433 | 434 | function shouldUseActiveX () { 435 | var useActiveX = false 436 | 437 | try { 438 | document.implementation.createHTMLDocument('').open() 439 | } catch (e) { 440 | if (window.ActiveXObject) useActiveX = true 441 | } 442 | 443 | return useActiveX 444 | } 445 | 446 | module.exports = canParseHtmlNatively() ? _window.DOMParser : createHtmlParser() 447 | 448 | },{"jsdom":6}],4:[function(require,module,exports){ 449 | 'use strict' 450 | 451 | module.exports = [ 452 | { 453 | filter: 'p', 454 | replacement: function (content) { 455 | return '\n\n' + content + '\n\n' 456 | } 457 | }, 458 | 459 | { 460 | filter: 'br', 461 | replacement: function () { 462 | return ' \n' 463 | } 464 | }, 465 | 466 | { 467 | filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], 468 | replacement: function (content, node) { 469 | var hLevel = node.nodeName.charAt(1) 470 | var hPrefix = '' 471 | for (var i = 0; i < hLevel; i++) { 472 | hPrefix += '#' 473 | } 474 | return '\n\n' + hPrefix + ' ' + content + '\n\n' 475 | } 476 | }, 477 | 478 | { 479 | filter: 'hr', 480 | replacement: function () { 481 | return '\n\n* * *\n\n' 482 | } 483 | }, 484 | 485 | { 486 | filter: ['em', 'i'], 487 | replacement: function (content) { 488 | return '_' + content + '_' 489 | } 490 | }, 491 | 492 | { 493 | filter: ['strong', 'b'], 494 | replacement: function (content) { 495 | return '**' + content + '**' 496 | } 497 | }, 498 | 499 | // Inline code 500 | { 501 | filter: function (node) { 502 | var hasSiblings = node.previousSibling || node.nextSibling 503 | var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings 504 | 505 | return node.nodeName === 'CODE' && !isCodeBlock 506 | }, 507 | replacement: function (content) { 508 | return '`' + content + '`' 509 | } 510 | }, 511 | 512 | { 513 | filter: function (node) { 514 | return node.nodeName === 'A' && node.getAttribute('href') 515 | }, 516 | replacement: function (content, node) { 517 | var titlePart = node.title ? ' "' + node.title + '"' : '' 518 | return '[' + content + '](' + node.getAttribute('href') + titlePart + ')' 519 | } 520 | }, 521 | 522 | { 523 | filter: 'img', 524 | replacement: function (content, node) { 525 | var alt = node.alt || '' 526 | var src = node.getAttribute('src') || '' 527 | var title = node.title || '' 528 | var titlePart = title ? ' "' + title + '"' : '' 529 | return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '' 530 | } 531 | }, 532 | 533 | // Code blocks 534 | { 535 | filter: function (node) { 536 | return node.nodeName === 'PRE' && node.firstChild.nodeName === 'CODE' 537 | }, 538 | replacement: function (content, node) { 539 | return '\n\n ' + node.firstChild.textContent.replace(/\n/g, '\n ') + '\n\n' 540 | } 541 | }, 542 | 543 | { 544 | filter: 'blockquote', 545 | replacement: function (content) { 546 | content = content.trim() 547 | content = content.replace(/\n{3,}/g, '\n\n') 548 | content = content.replace(/^/gm, '> ') 549 | return '\n\n' + content + '\n\n' 550 | } 551 | }, 552 | 553 | { 554 | filter: 'li', 555 | replacement: function (content, node) { 556 | content = content.replace(/^\s+/, '').replace(/\n/gm, '\n ') 557 | var prefix = '* ' 558 | var parent = node.parentNode 559 | var index = Array.prototype.indexOf.call(parent.children, node) + 1 560 | 561 | prefix = /ol/i.test(parent.nodeName) ? index + '. ' : '* ' 562 | return prefix + content 563 | } 564 | }, 565 | 566 | { 567 | filter: ['ul', 'ol'], 568 | replacement: function (content, node) { 569 | var strings = [] 570 | for (var i = 0; i < node.childNodes.length; i++) { 571 | strings.push(node.childNodes[i]._replacement) 572 | } 573 | 574 | if (/li/i.test(node.parentNode.nodeName)) { 575 | return '\n' + strings.join('\n') 576 | } 577 | return '\n\n' + strings.join('\n') + '\n\n' 578 | } 579 | }, 580 | 581 | { 582 | filter: function (node) { 583 | return this.isBlock(node) 584 | }, 585 | replacement: function (content, node) { 586 | return '\n\n' + this.outer(node, content) + '\n\n' 587 | } 588 | }, 589 | 590 | // Anything else! 591 | { 592 | filter: function () { 593 | return true 594 | }, 595 | replacement: function (content, node) { 596 | return this.outer(node, content) 597 | } 598 | } 599 | ] 600 | 601 | },{}],5:[function(require,module,exports){ 602 | /** 603 | * This file automatically generated from `build.js`. 604 | * Do not manually edit. 605 | */ 606 | 607 | module.exports = [ 608 | "address", 609 | "article", 610 | "aside", 611 | "audio", 612 | "blockquote", 613 | "canvas", 614 | "dd", 615 | "div", 616 | "dl", 617 | "fieldset", 618 | "figcaption", 619 | "figure", 620 | "footer", 621 | "form", 622 | "h1", 623 | "h2", 624 | "h3", 625 | "h4", 626 | "h5", 627 | "h6", 628 | "header", 629 | "hgroup", 630 | "hr", 631 | "main", 632 | "nav", 633 | "noscript", 634 | "ol", 635 | "output", 636 | "p", 637 | "pre", 638 | "section", 639 | "table", 640 | "tfoot", 641 | "ul", 642 | "video" 643 | ]; 644 | 645 | },{}],6:[function(require,module,exports){ 646 | 647 | },{}],7:[function(require,module,exports){ 648 | 'use strict'; 649 | 650 | var voidElements = require('void-elements'); 651 | Object.keys(voidElements).forEach(function (name) { 652 | voidElements[name.toUpperCase()] = 1; 653 | }); 654 | 655 | var blockElements = {}; 656 | require('block-elements').forEach(function (name) { 657 | blockElements[name.toUpperCase()] = 1; 658 | }); 659 | 660 | /** 661 | * isBlockElem(node) determines if the given node is a block element. 662 | * 663 | * @param {Node} node 664 | * @return {Boolean} 665 | */ 666 | function isBlockElem(node) { 667 | return !!(node && blockElements[node.nodeName]); 668 | } 669 | 670 | /** 671 | * isVoid(node) determines if the given node is a void element. 672 | * 673 | * @param {Node} node 674 | * @return {Boolean} 675 | */ 676 | function isVoid(node) { 677 | return !!(node && voidElements[node.nodeName]); 678 | } 679 | 680 | /** 681 | * whitespace(elem [, isBlock]) removes extraneous whitespace from an 682 | * the given element. The function isBlock may optionally be passed in 683 | * to determine whether or not an element is a block element; if none 684 | * is provided, defaults to using the list of block elements provided 685 | * by the `block-elements` module. 686 | * 687 | * @param {Node} elem 688 | * @param {Function} blockTest 689 | */ 690 | function collapseWhitespace(elem, isBlock) { 691 | if (!elem.firstChild || elem.nodeName === 'PRE') return; 692 | 693 | if (typeof isBlock !== 'function') { 694 | isBlock = isBlockElem; 695 | } 696 | 697 | var prevText = null; 698 | var prevVoid = false; 699 | 700 | var prev = null; 701 | var node = next(prev, elem); 702 | 703 | while (node !== elem) { 704 | if (node.nodeType === 3) { 705 | // Node.TEXT_NODE 706 | var text = node.data.replace(/[ \r\n\t]+/g, ' '); 707 | 708 | if ((!prevText || / $/.test(prevText.data)) && !prevVoid && text[0] === ' ') { 709 | text = text.substr(1); 710 | } 711 | 712 | // `text` might be empty at this point. 713 | if (!text) { 714 | node = remove(node); 715 | continue; 716 | } 717 | 718 | node.data = text; 719 | prevText = node; 720 | } else if (node.nodeType === 1) { 721 | // Node.ELEMENT_NODE 722 | if (isBlock(node) || node.nodeName === 'BR') { 723 | if (prevText) { 724 | prevText.data = prevText.data.replace(/ $/, ''); 725 | } 726 | 727 | prevText = null; 728 | prevVoid = false; 729 | } else if (isVoid(node)) { 730 | // Avoid trimming space around non-block, non-BR void elements. 731 | prevText = null; 732 | prevVoid = true; 733 | } 734 | } else { 735 | node = remove(node); 736 | continue; 737 | } 738 | 739 | var nextNode = next(prev, node); 740 | prev = node; 741 | node = nextNode; 742 | } 743 | 744 | if (prevText) { 745 | prevText.data = prevText.data.replace(/ $/, ''); 746 | if (!prevText.data) { 747 | remove(prevText); 748 | } 749 | } 750 | } 751 | 752 | /** 753 | * remove(node) removes the given node from the DOM and returns the 754 | * next node in the sequence. 755 | * 756 | * @param {Node} node 757 | * @return {Node} node 758 | */ 759 | function remove(node) { 760 | var next = node.nextSibling || node.parentNode; 761 | 762 | node.parentNode.removeChild(node); 763 | 764 | return next; 765 | } 766 | 767 | /** 768 | * next(prev, current) returns the next node in the sequence, given the 769 | * current and previous nodes. 770 | * 771 | * @param {Node} prev 772 | * @param {Node} current 773 | * @return {Node} 774 | */ 775 | function next(prev, current) { 776 | if (prev && prev.parentNode === current || current.nodeName === 'PRE') { 777 | return current.nextSibling || current.parentNode; 778 | } 779 | 780 | return current.firstChild || current.nextSibling || current.parentNode; 781 | } 782 | 783 | module.exports = collapseWhitespace; 784 | 785 | },{"block-elements":5,"void-elements":8}],8:[function(require,module,exports){ 786 | /** 787 | * This file automatically generated from `pre-publish.js`. 788 | * Do not manually edit. 789 | */ 790 | 791 | module.exports = { 792 | "area": true, 793 | "base": true, 794 | "br": true, 795 | "col": true, 796 | "embed": true, 797 | "hr": true, 798 | "img": true, 799 | "input": true, 800 | "keygen": true, 801 | "link": true, 802 | "menuitem": true, 803 | "meta": true, 804 | "param": true, 805 | "source": true, 806 | "track": true, 807 | "wbr": true 808 | }; 809 | 810 | },{}]},{},[1])(1) 811 | }); -------------------------------------------------------------------------------- /content.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8", 3 | "background-color": "white", 4 | "background-color-dark": "#30363c", 5 | "cloneable": true, 6 | "default_page": "index.html", 7 | "description": "Blogging platform Demo", 8 | "domain": "Blog.ZeroNetwork.bit", 9 | "files": { 10 | "LICENSE": { 11 | "sha512": "d281feecb7d1218e1aea8269f288fcd63385da1a130681fadae77262637cb65f", 12 | "size": 18027 13 | }, 14 | "README.md": { 15 | "sha512": "07dbb7b8386b600d7eea237f237269ed705cf3d97a46edc02c8ba43fa63b59c2", 16 | "size": 312 17 | }, 18 | "alloy-editor/all.css": { 19 | "sha512": "7f92b9206df4994b119df0123734193ad95c5961c2b5190aa1e60f394026d150", 20 | "size": 63384 21 | }, 22 | "alloy-editor/all.js": { 23 | "sha512": "0c917acb6b3f7397d37263a8ee01100929e7aed3c752b2103cb884f65aef7b23", 24 | "size": 737537 25 | }, 26 | "css/all.css": { 27 | "sha512": "7f5d792397331b71403edf67510b812c597bf200ae8ca538d0864b2eb098cb50", 28 | "size": 136196 29 | }, 30 | "data-default/data.json": { 31 | "sha512": "c8952629acf0c47ac1a53c7b8910e3aae2eda34371cb5182b9c9a50e483a297c", 32 | "size": 437 33 | }, 34 | "data-default/users/content-default.json": { 35 | "sha512": "0603ce08f7abb92b3840ad0cf40e95ea0b3ed3511b31524d4d70e88adba83daa", 36 | "size": 679 37 | }, 38 | "data/data.json": { 39 | "sha512": "7da2160bce9f61cc52513f02fed7861e5e76a2fe4f6a235d79f4dcb9617c9f0b", 40 | "size": 187072 41 | }, 42 | "data/files/ZeroFrame.coffee": { 43 | "sha512": "e9d440c26e858ff524313e60881714bcaf292470ce63d2b33b93d9920a21a7c4", 44 | "size": 1467 45 | }, 46 | "data/img/allony.png": { 47 | "sha512": "2cf2ef70d1dbf2395384581ffb0e1cc9e8dfa5a64441e5ab7c08477127fb6d9a", 48 | "size": 1572 49 | }, 50 | "data/img/autoupdate.png": { 51 | "sha512": "d2b4dc8e0da2861ea051c0c13490a4eccf8933d77383a5b43de447c49d816e71", 52 | "size": 24460 53 | }, 54 | "data/img/clone.png": { 55 | "sha512": "07df9f39af99b2a4348f1f0984b0e8667add7821a689e0a737df49bd68411def", 56 | "size": 11539 57 | }, 58 | "data/img/decentralized_web_summit.png": { 59 | "sha512": "90f83d91ea366eeb13c5874c0d25ef6efa6d20cc6e3f556fdc4de949d58248a4", 60 | "size": 25428 61 | }, 62 | "data/img/direct_domains.png": { 63 | "sha512": "5f14b30c1852735ab329b22496b1e2ea751cb04704789443ad73a70587c59719", 64 | "size": 16185 65 | }, 66 | "data/img/domain.png": { 67 | "sha512": "ce87e0831f4d1e95a95d7120ca4d33f8273c6fce9f5bbedf7209396ea0b57b6a", 68 | "size": 11881 69 | }, 70 | "data/img/getd.png": { 71 | "sha512": "22987f498e2dd6e5dee9f6367fec22fa1d2ad3538b2455d1d9e3fc400d1933db", 72 | "size": 36735 73 | }, 74 | "data/img/memory.png": { 75 | "sha512": "dd56515085b4a79b5809716f76f267ec3a204be3ee0d215591a77bf0f390fa4e", 76 | "size": 12775 77 | }, 78 | "data/img/multiuser.png": { 79 | "sha512": "88e3f795f9b86583640867897de6efc14e1aa42f93e848ed1645213e6cc210c6", 80 | "size": 29480 81 | }, 82 | "data/img/new_zeronet_logos.png": { 83 | "sha512": "a8685d5d8d9d84a83710a1316ee7b8f998f14918cef32e14217d5e45f156aa20", 84 | "size": 59816 85 | }, 86 | "data/img/optional.png": { 87 | "sha512": "84df27a2ee59f18bc646ed4e6610a6ca7569fad2e85198ba9aa278a3258f35c3", 88 | "size": 104799 89 | }, 90 | "data/img/optional_manager.png": { 91 | "sha512": "f0df2c1df5106f63e922ac0b65388d97bab1e7c26f6bf06469b44eae8b798cec", 92 | "size": 29008 93 | }, 94 | "data/img/peers.jpg": { 95 | "sha512": "3142ae89ed2347953033068fd789ac316692a3bdcbbf01257326685fc1402be9", 96 | "size": 67768 97 | }, 98 | "data/img/pocketchip.jpg": { 99 | "sha512": "05ddad3659e71dfbc6afbba3faaf680c810636ea2319e1e9950ed462cfa75d01", 100 | "size": 131607 101 | }, 102 | "data/img/post_101_ezgif.com-f1dae34e05.gif": { 103 | "sha512": "ee45043be1c79a1ef952f2ab030d24b63592790b572750b8bf42544d80a228e3", 104 | "size": 15609 105 | }, 106 | "data/img/post_101_plus.png": { 107 | "sha512": "e8d3bd6fe509e79389385389d66874a388a8474218c3ef5b3cf9339196d0633d", 108 | "size": 243 109 | }, 110 | "data/img/post_102_1484303724.jpg": { 111 | "sha512": "1e75a71128f3675389ccef9748d071b779ef8635db826f933dffdccf2e6fed61", 112 | "size": 90260 113 | }, 114 | "data/img/post_102_peers.jpg": { 115 | "sha512": "37c064e0b8fafa801a6264e9495d515b7468fa387566d329debd80e66cc7ebcf", 116 | "size": 79172 117 | }, 118 | "data/img/post_103_publish.png": { 119 | "sha512": "ace77f3163cd0de7dc560d18cecd73b3e6cd8a5b32751d4038c65c400521e88c", 120 | "size": 1087 121 | }, 122 | "data/img/post_104_mute.png": { 123 | "sha512": "6bdad39da3dfbc3db3a57e52fc10d9205cf0729a6cd124e7e5c8704a130161b4", 124 | "size": 15562 125 | }, 126 | "data/img/post_107_1489106790.jpg": { 127 | "sha512": "d26b09e35853fe3d2b69ce2e1b3532af4fa830dff7fc8bf8248114a21a54aea0", 128 | "size": 97447 129 | }, 130 | "data/img/post_108_tor_hs_limit.png": { 131 | "sha512": "f42e8b8e03856ede429c7bf97ce7490f33a541881a5943eedb9601b617696aab", 132 | "size": 4254 133 | }, 134 | "data/img/post_109_blacklist.png": { 135 | "sha512": "d8211426370fe3ee03306b15c2896d1242dacf44d026d09953a42c13dd727876", 136 | "size": 6186 137 | }, 138 | "data/img/post_110_zerosites.png": { 139 | "sha512": "07a87584e2f95b42bf0880594b4aab54809b04c3acc14ba297370bec444e9d4f", 140 | "size": 40366 141 | }, 142 | "data/img/post_113_mavo.jpg": { 143 | "sha512": "174eb14349a5f6e9679b3acf7cb0545cd91544fb1058312c460e873fa5eb288e", 144 | "size": 121176 145 | }, 146 | "data/img/post_115_mobile-sm.png": { 147 | "sha512": "e0b76406a9ef06d3f7fdc29036d77162e661efe063c9038b53f7611f432622b4", 148 | "size": 70656 149 | }, 150 | "data/img/post_116_20785768_1658990647475567_7520728110450357066_o.jpg": { 151 | "sha512": "958bb514ec019267ca6c1c7f25d9950530728e04cc1eee33b571b9f8f1c3a58d", 152 | "size": 72858 153 | }, 154 | "data/img/post_121_zeroup.png": { 155 | "sha512": "2cf2f8ed080534251a5ef4e454fe25674411391ec1b142e20bc4c20c755dbce9", 156 | "size": 51890 157 | }, 158 | "data/img/post_124_chart_top.jpg": { 159 | "sha512": "6d4f6e8d72b8612bef0e62e4d36a72800344c834f72311ee5f9bb5e97718806a", 160 | "size": 170450 161 | }, 162 | "data/img/post_131_blocklist.png": { 163 | "sha512": "27f3e449aa327b135b995b964e9191460968bb70201aaca1814e0d93a0e61ee4", 164 | "size": 14440 165 | }, 166 | "data/img/post_132_Config.png": { 167 | "sha512": "fdba7451da4a782bdc1770cfd9a341314b2ffde01dddea63edfa97fca9616b40", 168 | "size": 20278 169 | }, 170 | "data/img/post_134_dws2018.jpg": { 171 | "sha512": "959e1c4b1daddc4e62edeb6706934f4adfa14d9ac8c94243900c1525f314b797", 172 | "size": 93811 173 | }, 174 | "data/img/post_139_TEDSalon.jpg": { 175 | "sha512": "dc884a044124d5ebe1d1cb98f52d41f5dfa50fb42014d6b7d0395a57a9e58ba5", 176 | "size": 67023 177 | }, 178 | "data/img/post_140_dark-theme.png": { 179 | "sha512": "7de42c38798c8dc6b940ff679b3052f98aaecd1bfff42954cf05c9ab05b7161d", 180 | "size": 46002 181 | }, 182 | "data/img/post_147_console.png": { 183 | "sha512": "3fcd9cf43f78513f2b2bfc3542ede0aa98a994fdd3ec3e92a0f1154578152138", 184 | "size": 34446 185 | }, 186 | "data/img/post_151_benchmark.png": { 187 | "sha512": "4f238b57db77c55b080a3b63113c1bf94a6206e160f9726fd20c11eeb9536d49", 188 | "size": 5729 189 | }, 190 | "data/img/progressbar.png": { 191 | "sha512": "23d592ae386ce14158cec34d32a3556771725e331c14d5a4905c59e0fe980ebf", 192 | "size": 13294 193 | }, 194 | "data/img/search.png": { 195 | "sha512": "b5adf6562b0078bdda257d216a07b43fa6f81e0989fcabf83817ef072cbeb1f1", 196 | "size": 18735 197 | }, 198 | "data/img/sidebar.png": { 199 | "sha512": "e5fdafcc7226f830eb27a1296236aa985625e27480e6829660db978ceae0bba9", 200 | "size": 98641 201 | }, 202 | "data/img/slides.png": { 203 | "sha512": "1933db3b90ab93465befa1bd0843babe38173975e306286e08151be9992f767e", 204 | "size": 14439 205 | }, 206 | "data/img/slots_memory.png": { 207 | "sha512": "82a250e6da909d7f66341e5b5c443353958f86728cd3f06e988b6441e6847c29", 208 | "size": 9488 209 | }, 210 | "data/img/startup.png": { 211 | "sha512": "d9f4d652396bbb89f0508e3e3908bd03408e99681f33e47f790b53a9b1c4fafc", 212 | "size": 4257 213 | }, 214 | "data/img/trayicon.png": { 215 | "sha512": "e7ae65bf280f13fb7175c1293dad7d18f1fcb186ebc9e1e33850cdaccb897b8f", 216 | "size": 19040 217 | }, 218 | "data/img/tutorial-1.png": { 219 | "sha512": "885d322f4e189775cbacb6e7d4f9ee89554d5709077e3777f0d382b0e9d315f1", 220 | "size": 18729 221 | }, 222 | "data/img/uipassword.png": { 223 | "sha512": "2f7ddb406cb318da51c36dc4e30e79aca17ef6730b9ba1fa5a9768127d3d0f02", 224 | "size": 5383 225 | }, 226 | "data/img/xmas_tree.jpg": { 227 | "sha512": "fec017f87867f43d0f2b98e0b275b59a1997746e6cae9d322c551d7ab357488c", 228 | "size": 43706 229 | }, 230 | "data/img/zeroblog-comments.png": { 231 | "sha512": "efe4e815a260e555303e5c49e550a689d27a8361f64667bd4a91dbcccb83d2b4", 232 | "size": 24001 233 | }, 234 | "data/img/zerochat.png": { 235 | "sha512": "b1512ad6514a87d8052dac880b3ef0d9f2e70cf2e4605bfebd4a6c792d1c2ba9", 236 | "size": 12029 237 | }, 238 | "data/img/zeroid.png": { 239 | "sha512": "b46d541a9e51ba2ddc8a49955b7debbc3b45fd13467d3c20ef104e9d938d052b", 240 | "size": 18875 241 | }, 242 | "data/img/zeromail.png": { 243 | "sha512": "f2ced47d30bbf76549ae990b63dab00f3764ad48b6085464b122b108f31da368", 244 | "size": 34638 245 | }, 246 | "data/img/zerome.png": { 247 | "sha512": "2b97eadf18d93c52c3316d14d8d54b1564a058e332fbf7bbb107bf24ad07e302", 248 | "size": 21698 249 | }, 250 | "data/img/zeroname.png": { 251 | "sha512": "bab45a1bb2087b64e4f69f756b2ffa5ad39b7fdc48c83609cdde44028a7a155d", 252 | "size": 36031 253 | }, 254 | "data/img/zerotalk-mark.png": { 255 | "sha512": "a335b2fedeb8d291ca68d3091f567c180628e80f41de4331a5feb19601d078af", 256 | "size": 44862 257 | }, 258 | "data/img/zerotalk-upvote.png": { 259 | "sha512": "b1ffd7f948b4f99248dde7efe256c2efdfd997f7e876fb9734f986ef2b561732", 260 | "size": 41092 261 | }, 262 | "data/img/zerotalk.png": { 263 | "sha512": "54d10497a1ffca9a4780092fd1bd158c15f639856d654d2eb33a42f9d8e33cd8", 264 | "size": 26606 265 | }, 266 | "data/pdf/zeronet_presentation.pdf": { 267 | "sha512": "70274438cf2fbb63e1d3c75a74250ca5d968d63cadbb4426305b3b51f2608a01", 268 | "size": 472084 269 | }, 270 | "data/video/local_peer_discovery.jpg": { 271 | "sha512": "a3d33cf1eba1560bf5c23d984053ea812aa83551752ee0f95125e3d6306756eb", 272 | "size": 64818 273 | }, 274 | "dbschema.json": { 275 | "sha512": "fa70ee2fce3f8342c6909e0e9b853bca71fa996f23f85739f94c086f2bda5ceb", 276 | "size": 2571 277 | }, 278 | "img/avatar.png": { 279 | "sha512": "a656c470967e27c2b385182a361db8bab1696fe3b2d79c86394236d02ce42eb8", 280 | "size": 87 281 | }, 282 | "img/loading.gif": { 283 | "sha512": "8a42b98962faea74618113166886be488c09dad10ca47fe97005edc5fb40cc00", 284 | "size": 723 285 | }, 286 | "index.html": { 287 | "sha512": "82b962d4c7ae0ece5bde1938f16431e4f8b44e84d857d4291fbcd6a7335c8c8c", 288 | "size": 6542 289 | }, 290 | "js/all.js": { 291 | "sha512": "8f12f6a7eed48d004777d9546bedbc2a4228a4ad52c4c3d77072e5fc19386472", 292 | "size": 262515 293 | }, 294 | "languages/de.json": { 295 | "sha512": "8fef5db6d592cc1d6baaf27489cdbaa2649c14ab5735fc18fd8c2506292b1289", 296 | "size": 2437 297 | }, 298 | "languages/es.json": { 299 | "sha512": "9005bf63e41b2670af8eba0103badfe9ba24ae94b6cac64d00193cfbb2624d85", 300 | "size": 871 301 | }, 302 | "languages/fr.json": { 303 | "sha512": "6e87d27a3ee084c8300642ac83133d9b967d8ba3997d7bb0dfbe104ee3e1b327", 304 | "size": 1773 305 | }, 306 | "languages/it.json": { 307 | "sha512": "f5c15cf8f36984c29b38911946c18a0414bb2739fd95196bc42004d90979f43e", 308 | "size": 804 309 | }, 310 | "languages/nl.json": { 311 | "sha512": "1844365e54a2a5b53fcff4f36ad2e870fe738ba033dbae77172de3126e72bcb7", 312 | "size": 912 313 | }, 314 | "languages/pl.json": { 315 | "sha512": "f84ea772597afc947fb0e759318aaa3371a36efe542abdbd6b6411ea99468826", 316 | "size": 864 317 | }, 318 | "languages/pt-br": { 319 | "sha512": "804a255da65cb8de0e425326bce69874aab03a6d58c05b95bf6c5102b337f9d1", 320 | "size": 822 321 | }, 322 | "languages/zh.json": { 323 | "sha512": "fdc18e8db95603481f107036f77ed0e851afa3259fb4f3476117ceb98462f752", 324 | "size": 1963 325 | } 326 | }, 327 | "files_optional": { 328 | "data/video/local_peer_discovery.mp4": { 329 | "piece_size": 1048576, 330 | "piecemap": "data/video/local_peer_discovery.mp4.piecemap.msgpack", 331 | "sha512": "06c34ee9a5e86ab6312e8676e6273dca77af5716e0ea549564fc961b8041d299", 332 | "size": 7809567 333 | }, 334 | "data/video/local_peer_discovery.mp4.piecemap.msgpack": { 335 | "sha512": "6f8186658cc2aa27a2a91cf4f655bafcc4c39a8ee0d0d600c2865ca0fe48139c", 336 | "size": 322 337 | } 338 | }, 339 | "ignore": "((js|css|alloy-editor)/(?!all.(js|css))|data/.*db|data/users/.*)", 340 | "includes": { 341 | "data/users/content.json": { 342 | "signers": [], 343 | "signers_required": 1 344 | } 345 | }, 346 | "inner_path": "content.json", 347 | "modified": 1594422823, 348 | "optional": "data/video/.*mp4", 349 | "postmessage_nonce_security": true, 350 | "signers_sign": "G7W/oNvczE5nPTFYVOqv8+GOpQd23LS/Dc1Q6xQ1NRDDHlYzmoSE63UQ7Za05kD0rwIYXbuUSr8z8p6RhZmnUs8=", 351 | "signs": {"1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8": "G8uuWUYFhzXWl5qolK3+EZTiCNpJHPI8G+vWDDLO8NOBRb9MlR1M4C7kOW8v24zTevWHYRuNHXk4Vl3ZsJj480k="}, 352 | "signs_required": 1, 353 | "title": "ZeroBlog", 354 | "translate": ["index.html","js/all.js","alloy-editor/all.js"], 355 | "viewport": "width=device-width, initial-scale=0.8", 356 | "zeronet_version": "0.7.1" 357 | } -------------------------------------------------------------------------------- /css/Comments.css: -------------------------------------------------------------------------------- 1 | .comments { margin-bottom: 60px } 2 | .comment { background-color: white; padding: 25px 0px; margin: 1px; border-top: 1px solid #EEE } 3 | .comment .user_name { font-size: 14px; font-weight: bold } 4 | .comment .added { color: #AAA } 5 | .comment .reply { color: #CCC; opacity: 0; transition: opacity 0.3s; border: none; position: relative; } 6 | .comment:hover .reply { opacity: 1 } 7 | .comment .reply .icon { opacity: 0.3 } 8 | .comment .reply:hover { border-bottom: none; color: #666 } 9 | .comment .reply:hover .icon { opacity: 1 } 10 | .comment .info { font-size: 12px; color: #AAA; margin-bottom: 7px } 11 | .comment .info .score { margin-left: 5px } 12 | .comment .comment-body { line-height: 1.5em; margin-top: 0.5em; margin-bottom: 0.5em; word-wrap: break-word; } 13 | .comment .comment-body p { margin-bottom: 0px; margin-top: 0.5em; } 14 | .comment .comment-body p:first-child { margin: 0px; margin-top: 0px; } 15 | .comment .comment-body.editor { margin-top: 0.5em !important; margin-bottom: 0.5em !important } 16 | .comment .comment-body h1, .comment .body h2, .comment .body h3 { font-size: 110% } 17 | .comment .comment-body blockquote { padding: 1px 15px; border-left: 2px solid #E7E7E7; margin: 0px; margin-top: 30px } 18 | .comment .comment-body blockquote:first-child { margin-top: 0px } 19 | .comment .comment-body blockquote p { margin: 0px; color: #999; font-size: 90% } 20 | .comment .comment-body blockquote a { color: #333; font-weight: normal; border-bottom: 0px } 21 | .comment .comment-body blockquote a:hover { border-bottom: 1px solid #999 } 22 | .comment .editable-edit { margin-top: -5px } 23 | 24 | .comment-new { margin-bottom: 5px; border-top: 0px } 25 | .comment-new .button-submit { 26 | margin: 0px; font-weight: normal; padding: 5px 15px; display: inline-block; 27 | background-color: #FFF85F; border-bottom: 2px solid #CDBD1E; font-size: 15px; line-height: 30px 28 | } 29 | .comment-new h2 { margin-bottom: 25px } 30 | 31 | /* Input */ 32 | .comment-new textarea { 33 | line-height: 1.5em; width: 100%; padding: 10px; font-family: 'Roboto', sans-serif; font-size: 16px; 34 | transition: border 0.3s; border: 2px solid #eee; box-sizing: border-box; overflow-y: auto 35 | } 36 | input.text:focus, textarea:focus { border-color: #5FC0EA; outline: none; background-color: white } 37 | 38 | .comment-nocert textarea { opacity: 0.5; pointer-events: none } 39 | .comment-nocert .info { opacity: 0.1; pointer-events: none } 40 | .comment-nocert .button-submit-comment { opacity: 0.1; pointer-events: none } 41 | .comment-nocert .button.button-certselect { display: inherit } 42 | .button.button-certselect { 43 | position: absolute; left: 50%; white-space: nowrap; transform: translateX(-50%); z-index: 99; 44 | margin-top: 13px; background-color: #007AFF; color: white; border-bottom-color: #3543F9; display: none 45 | } 46 | .button.button-certselect:hover { background-color: #3396FF; color: white; border-bottom-color: #5D68FF; } 47 | .button.button-certselect:active { position: absolute; transform: translateX(-50%) translateY(1px); top: auto; } 48 | 49 | .user-size { font-size: 11px; margin-top: 6px; box-sizing: border-box; text-transform: uppercase; display: inline-block; color: #AAA } 50 | .user-size-used { position: absolute; color: #B10DC9; overflow: hidden; width: 40px; white-space: nowrap } 51 | -------------------------------------------------------------------------------- /css/Follow.css: -------------------------------------------------------------------------------- 1 | .icon-feed { 2 | width: 3px; height: 3px; display: inline-block; border-radius: 10px; background-color: #333; 3 | position: relative; margin-right: 10px; margin-top: 10px; margin-left: 5px; transition: all 0.3s 4 | } 5 | .icon-feed:after { 6 | content: ""; width: 5px; height: 5px; border-top: 2px solid #333; border-right: 2px solid #333; border-radius: 0px 6px 0px 0px; 7 | display: block; border-right: 2px solid #333; bottom: 0px; position: absolute; transition: all 0.3s 8 | } 9 | .icon-feed:before{ 10 | content: ""; width: 9px; height: 9px; border-top: 2px solid #333; border-right: 2px solid #333; border-radius: 0px 10px 0px 0px; 11 | display: block; bottom: 0px; position: absolute; transition: all 0.3s 12 | } 13 | 14 | .feed-follow { 15 | font-size: 14px; display: inline-block; padding: 7px 14px; font-weight: normal; display: none; 16 | border-radius: 4px; border: 1px solid #EEE; border-bottom: 1px solid #DDD; transition: all 0.3s 17 | } 18 | .feed-follow .text-follow, .feed-follow .text-following { 19 | max-width: 0px; opacity: 0; overflow: hidden; display: inline-block; vertical-align: bottom; transition: all 0.3s; white-space: nowrap; 20 | } 21 | .feed-follow .text-follow { max-width: 170px; opacity: 1 } 22 | .feed-follow.following .text-following { max-width: 60px; opacity: 1; color: #7A26E2; } 23 | .feed-follow.following .text-follow { max-width: 0px; opacity: 0 } 24 | 25 | .feed-follow:hover { background-color: #FEFEFE; transition: none; border-color: #C392FF; color: black } 26 | .feed-follow:active { border-color: #7A26E2 } 27 | .feed-follow.following .icon-feed { background-color: #7A26E2; transition: none } 28 | .feed-follow.following .icon-feed:after, .feed-follow.following .icon-feed:before { border-color: #7A26E2; transition: none } 29 | -------------------------------------------------------------------------------- /css/Menu.css: -------------------------------------------------------------------------------- 1 | .menu { 2 | background-color: white; padding: 10px 0px; position: absolute; top: 0px; left: 0px; max-height: 0px; overflow: hidden; 3 | transform: translate(0px, -30px); pointer-events: none; box-shadow: 0px 2px 8px rgba(0,0,0,0.3); border-radius: 2px; 4 | opacity: 0; transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; z-index: 999; 5 | } 6 | .menu.visible { opacity: 1; max-height: 350px; transform: translate(0px, 0px); transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out; pointer-events: all } 7 | .menu-item { display: block; text-decoration: none; color: black; padding: 6px 24px; transition: all 0.2s; border-bottom: none; font-weight: normal; padding-left: 30px; } 8 | .menu-item-separator { margin-top: 5px; border-top: 1px solid #eee } 9 | 10 | .menu-item:hover { background-color: #F6F6F6; transition: none; color: inherit; } 11 | .menu-item:active, .menu-item:focus { background-color: #AF3BFF; color: white; transition: none } 12 | .menu-item.selected:before { 13 | content: "L"; display: inline-block; transform: rotateZ(45deg) scaleX(-1); 14 | font-weight: bold; position: absolute; margin-left: -17px; font-size: 12px; margin-top: 2px; 15 | } -------------------------------------------------------------------------------- /css/ZeroBlog.css: -------------------------------------------------------------------------------- 1 | /* Design based on medium */ 2 | 3 | body { background-color: white; color: #333332; margin: 10px; padding: 0px; font-family: 'Roboto Light', sans-serif; height: 15000px; overflow: hidden; backface-visibility: hidden } 4 | body.loaded { height: auto; overflow: auto } 5 | h1, h2, h3, h4 { font-family: 'Roboto Light', sans-serif; font-weight: normal; margin: 0px; padding: 0px } 6 | h1 { font-size: 32px; line-height: 1.2em; font-weight: bold; letter-spacing: -0.5px; margin-bottom: 5px } 7 | h2 { margin-top: 2em } 8 | h3 { font-size: 24px; margin-top: 2em } 9 | h1 + h2, h2 + h3 { margin-top: inherit } 10 | 11 | p { margin-top: 0.9em; margin-bottom: 0.9em } 12 | hr { margin: 20px 0px; border: none; border-bottom: 1px solid #eee; margin-left: auto; margin-right: auto; width: 120px; } 13 | small { font-size: 80%; color: #999; } 14 | 15 | a { border-bottom: 1px solid #3498db; text-decoration: none; color: black; font-weight: bold } 16 | a.nolink { border-bottom: none } 17 | a:hover { color: #3498db } 18 | 19 | .button { 20 | padding: 5px 10px; margin-left: 10px; background-color: #DDE0E0; border-bottom: 2px solid #999998; background-position: left center; 21 | border-radius: 2px; text-decoration: none; transition: all 0.5s ease-out; color: #333 22 | } 23 | .button:hover { background-color: #FFF400; border-color: white; border-bottom: 2px solid #4D4D4C; transition: none; color: inherit } 24 | .button:active { position: relative; top: 1px } 25 | 26 | /*.button-delete { background-color: #e74c3c; border-bottom-color: #A83F34; color: white }*/ 27 | .button-outline { background-color: white; color: #CACACA; border: 1px solid #eee } 28 | 29 | .button-delete:hover { background-color: #FF5442; border: 1px solid #FF5442; color: white } 30 | .button-ok:hover { background-color: #27AE60; border: 1px solid #27AE60; color: white } 31 | 32 | .button.loading { 33 | color: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center; 34 | transition: all 0.5s ease-out; pointer-events: none; border-bottom: 2px solid #666 35 | } 36 | .button.publish { background-color: #2ecc71; border-color: #2ecc71; color: white } 37 | .button.publish:hover { background-color: #35D679; border-color: #35D679 } 38 | 39 | .cancel { margin-left: 10px; font-size: 80%; color: #999; } 40 | 41 | 42 | .template { display: none } 43 | 44 | /* Editable */ 45 | .editable { outline: none } 46 | .editable-edit:hover { opacity: 1; border: none; color: #333 } 47 | .editable-edit { 48 | opacity: 0; float: left; margin-top: 0px; margin-left: -40px; padding: 8px 20px; transition: all 0.3s; width: 0px; display: inline-block; padding-right: 0px; 49 | color: rgba(100,100,100,0.5); text-decoration: none; font-size: 18px; font-weight: normal; border: none; 50 | } 51 | /*.editing { white-space: pre-wrap; z-index: 1; position: relative; outline: 10000px solid rgba(255,255,255,0.9) !important; } 52 | .editing p { margin: 0px; padding: 0px }*/ /* IE FIX */ 53 | .editor { width: 100%; display: block; overflow :hidden; resize: none; border: none; box-sizing: border-box; z-index: 900; position: relative } 54 | .editor:focus { border: 0px; outline-offset: 0px } 55 | .editbg { 56 | display: none; width: 100%; height: 100%; position: fixed; opacity: 0; background-color: white; transition: opacity 0.5s; 57 | left: 0px; top: 0px; z-index: 500; backface-visibility: hidden; 58 | } 59 | 60 | /* -- Editbar -- */ 61 | 62 | .bottombar { 63 | display: none; position: fixed; padding: 10px 20px; opacity: 0; background-color: rgba(255,255,255,0.9); 64 | right: 30px; bottom: 0px; z-index: 1000; transition: all 0.3s; transform: translateY(50px) 65 | } 66 | .bottombar.visible { transform: translateY(0px); opacity: 1 } 67 | .publishbar { z-index: 990} 68 | .publishbar.visible { display: inline-block; } 69 | .editbar { perspective: 900px } 70 | .markdown-help { 71 | position: absolute; bottom: 30px; transform: translateX(0px) rotateY(-40deg); transform-origin: right; right: 0px; 72 | list-style-type: none; background-color: rgba(255,255,255,0.9); padding: 10px; opacity: 0; transition: all 0.6s; display: none 73 | } 74 | .markdown-help.visible { transform: none; opacity: 1 } 75 | .markdown-help li { margin: 10px 0px } 76 | .markdown-help code { font-size: 100% } 77 | .icon-help { border: 1px solid #EEE; padding: 2px; display: inline-block; width: 17px; text-align: center; font-size: 13px; margin-right: 6px; vertical-align: 1px } 78 | .icon-help.active { background-color: #EEE } 79 | 80 | /* -- Left -- */ 81 | 82 | .left { float: left; position: absolute; width: 220px; padding-left: 20px; padding-right: 20px; margin-top: 60px; text-align: right } 83 | .right { float: left; padding-left: 60px; margin-left: 240px; max-width: 700px; padding-right: 60px; padding-top: 60px } 84 | 85 | .left .avatar { 86 | background-color: #F0F0F0; width: 60px; height: 60px; border-radius: 100%; margin-bottom: 10px; 87 | background-position: center center; background-size: 70%; display: inline-block; image-rendering: pixelated; 88 | } 89 | .left h1 a.nolink { font-family: Tinos; display: inline-block; padding: 1px } 90 | .left h1 a.editable-edit { float: none } 91 | .left h2 { font-size: 15px; font-family: Tinos; line-height: 1.6em; color: #AAA; margin-top: 14px; letter-spacing: 0.2px } 92 | .left ul, .left li { padding: 0px; margin: 0px; list-style-type: none; line-height: 2em } 93 | .left hr { margin-left: 100px; margin-right: 0px; width: auto } 94 | /*.left .links { width: 230px; margin-left: -60px }*/ 95 | .left .links.editor { text-align: left !important } 96 | 97 | .lastcomments { background-color: #FAFAFA; margin-left: -25px; padding-right: 15px; padding-bottom: 5px; padding-top: 3px; margin-top: 40px; margin-right: -15px; display: none } 98 | .lastcomments h3 { margin-top: 20px; margin-left: 15px; } 99 | .lastcomments .lastcomment { font-size: 85%; margin-top: 20px; margin-bottom: 30px; margin-left: 20px; overflow: hidden; padding-right: 15px; } 100 | .lastcomments .lastcomment .user_name { font-weight: bold; } 101 | .lastcomments .lastcomment .details { font-size: 90%; } 102 | .lastcomments .lastcomment .details .at { color: #999 } 103 | .lastcomments .lastcomment .details .postlink { border-bottom: 1px solid #BBB; } 104 | 105 | /* -- Post -- */ 106 | 107 | .posts .new { display: none; position: absolute; top: -50px; margin-left: 0px; left: 50%; transform: translateX(-50%) } 108 | 109 | .posts, .post-full { display: none; position: relative; } 110 | .page-main .posts { display: block } 111 | .page-post.loaded .post-full { display: block; border-bottom: none } 112 | 113 | 114 | .post { margin-bottom: 50px; padding-bottom: 50px; border-bottom: 1px solid #eee; min-width: 450px } 115 | .post .title a { text-decoration: none; color: inherit; display: inline-block; border-bottom: none; font-weight: inherit } 116 | .posts .title a:visited { color: #666969 } 117 | .post .details { color: #BBB; margin-top: 5px; margin-bottom: 20px } 118 | .post .details .comments-num { border: none; color: #BBB; font-weight: normal; } 119 | .post .details .comments-num .num { border-bottom: 1px solid #eee; color: #000; } 120 | .post .details .comments-num:hover .num { border-bottom: 1px solid #D6A1DE; } 121 | .post .details .like { display: inline-block; padding: 5px 5px; margin-top: -6px; border-radius: 7px; border: none; font-weight: normal; transition: all 0.3s } 122 | .post .details .like .icon-heart:before, .post .details .like .icon-heart:after { background-color: #CCC; transition: all 0.3s } 123 | .post .details .like .icon-heart { transform-style: preserve-3d; transition: all 0.3s } 124 | .post .details .like:hover .icon-heart { transform: scale(1.1) } 125 | .post .details .like:hover .icon-heart:before, .post .details .like:hover .icon-heart:after { background-color: #FF5442 } 126 | .post .details .like.active .icon-heart:before, .post .details .like.active .icon-heart:after { background-color: #FF5442 } 127 | .post .details .like.active .icon-heart-anim { transform: scale(1.8); opacity: 0; transition: all 1s ease-in-out } 128 | .post .details .like .num { margin-left: 21px; color: #CCC; transition: all 0.3s } 129 | .post .details .like:hover .num { color: #FA6C8D } 130 | .post .details .like.loading { pointer-events: none; animation: bounce .3s infinite alternate ease-out; animation-delay: 1s; } 131 | .post .body { font-size: 21.5px; line-height: 1.6; font-family: Tinos; margin-top: 20px; overflow-wrap: break-word; } 132 | 133 | .post .body h1 { text-align: center; margin-top: 50px } 134 | .post .body h1:before { content: " "; border-top: 1px solid #EEE; width: 120px; display: block; margin-left: auto; margin-right: auto; margin-bottom: 50px; } 135 | 136 | .post .body p + ul { margin-top: -0.5em } 137 | .post .body li { margin-top: 0.5em; margin-bottom: 0.5em } 138 | .post .body hr:first-of-type { display: none } 139 | 140 | .post .body a img { margin-bottom: -8px } 141 | .post .body img { max-width: 100% } 142 | .post .body table { border-collapse: collapse; margin-bottom: 10px; margin: auto } 143 | .post .body td, .post .body th { padding: 5px 10px; border: 1px solid #EEE; border-collapse: collapse; text-align: left } 144 | 145 | code { 146 | background-color: #f5f5f5; border: 1px solid #ccc; padding: 0px 5px; overflow: auto; border-radius: 2px; display: inline-block; 147 | color: #444; font-weight: normal; font-size: 13px; vertical-align: text-bottom; border-bottom-width: 2px; 148 | } 149 | .post .body pre { table-layout: fixed; width: 100%; box-sizing: border-box; display: table; white-space: normal; } 150 | .post .body pre code { padding: 10px 20px; white-space: pre; display: block; max-width: 100%; box-sizing: border-box; } 151 | 152 | blockquote { border-left: 3px solid #333; margin-left: 0px; padding-left: 1em } 153 | /*.post .more { 154 | display: inline-block; border: 1px solid #eee; padding: 10px 25px; border-radius: 26px; font-size: 11px; color: #AAA; font-weight: normal; 155 | left: 50%; position: relative; transform: translateX(-50%); 156 | }*/ 157 | 158 | .post .more { border: 2px solid #333; padding: 10px 20px; font-size: 15px; margin-top: 30px; display: none; transition: all 0.3s } 159 | .post .more .readmore { } 160 | .post .more:hover { color: white; box-shadow: inset 150px 0px 0px 0px #333; } 161 | .post .more:active { color: white; box-shadow: inset 150px 0px 0px 0px #AF3BFF; transition: none; border-color: #AF3BFF } 162 | 163 | .pager { margin-bottom: 200px } 164 | .pager a { border: 2px solid #333; padding: 10px 20px; font-size: 15px; display: none; transition: all 0.3s } 165 | .pager a:hover { color: white; box-shadow: inset 150px 0px 0px 0px #333; } 166 | .pager a:active { color: white; box-shadow: inset 150px 0px 0px 0px #AF3BFF; transition: none; border-color: #AF3BFF } 167 | .pager .next { float: right } 168 | .pager .prev:hover { box-shadow: inset -150px 0px 0px 0px #333; } 169 | .pager .prev:active { box-shadow: inset -150px 0px 0px 0px #AF3BFF; } 170 | 171 | .zoom-img { 172 | filter: none; -webkit-filter: blur(0px); -moz-filter: blur(0px); -ms-filter: blur(0px); filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='0'); 173 | backface-visibility: hidden; transform: translateZ(0); image-rendering: optimizeSpeed; 174 | } 175 | /* Score */ 176 | /* 177 | .score { 178 | 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; 179 | 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 180 | } 181 | .score-active, .score-inactive { transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); display: inline-block; margin-top: 3px } 182 | .score-active { display: block; margin-top: 5px } 183 | .score.active { background-color: #2ecc71; color: white } 184 | .score.loading { 185 | color: rgba(255,255,255,0) !important; background: #2ecc71 url(../img/loading.gif) no-repeat center center; 186 | transition: background 0.5s ease-out; pointer-events: none; border: 1px solid #2ecc71 187 | } 188 | 189 | .score.active .score-inactive, .score:hover .score-inactive { transform: translateY(-14px); opacity: 0.5 } 190 | .score.active .score-active, .score:hover .score-active { transform: translateY(-14px) } 191 | .score:hover, .score.active { border: 1px solid #2ecc71; color: #AAA; } 192 | 193 | .score:active { border: 1px solid #AAA !important; transform: translateY(1px) } 194 | 195 | .noscore .score-inactive .icon-up { margin-left: 6px } 196 | .noscore .score-inactive .score-num { display: none } 197 | */ 198 | 199 | @keyframes bounce { 200 | 0% { transform: translateY(0); } 201 | 100% { transform: translateY(-3px); } 202 | } 203 | -------------------------------------------------------------------------------- /css/dark.css: -------------------------------------------------------------------------------- 1 | .theme-dark { background-color: #30363c; color: #e0e0e6; } 2 | .theme-dark a { color: #FFF; font-weight: normal; border-color: #7aa4ab } 3 | .theme-dark a:hover { border-color: #83efff } 4 | .theme-dark .editbg { background-color: #3a333f } 5 | .theme-dark .post { border-color: #47505a } 6 | .theme-dark .post .title a { color: #97f1ff; font-weight: lighter; } 7 | .theme-dark .post .details .comments-num .num { color: #c6ced7; border-bottom-color: #4b5258 } 8 | .theme-dark .post .details .comments-num .num:hover { border-bottom-color: #83efff; color: #83efff; } 9 | .theme-dark .button-outline { background-color: transparent; border: 1px solid #47505a; color: #83EFFF; } 10 | .theme-dark .button-outline:hover { border-color: #83EFFF; } 11 | .theme-dark .button:hover { border-bottom-color: inherit; } 12 | .theme-dark .button.publish:hover { color: white; } 13 | .theme-dark .bottombar { background-color: rgba(56, 63, 70, 0.9) } 14 | .theme-dark textarea { background-color: #383d44; border-color: #4c5864; color: #F6FEF7 } 15 | .theme-dark .post .body td, .theme-dark .post .body th { border-color: #4b4f54 } 16 | .theme-dark .meditor-editmode:hover { color: #83efff; } 17 | .theme-dark .meditor-editmode.markdown { background-color: #83efff; color: #30363c; border-color: #83efff; } 18 | .theme-dark .feed-follow { border-color: #47505a } 19 | .theme-dark .feed-follow .text-following { color: #F6FEF7 } 20 | .theme-dark .feed-follow.following .text-following { color: #83EFFF } 21 | .theme-dark .feed-follow:hover { background-color: #333940; border-color: #83EFFF; color: #83EFFF } 22 | .theme-dark .menu-item { color: black } 23 | .theme-dark .menu-item:active, .theme-dark .menu-item:focus { color: white } 24 | .theme-dark .feed-follow .icon-feed { background-color: #83EFFF } 25 | .theme-dark .feed-follow .icon-feed:before { border-color: #83EFFF } 26 | .theme-dark .feed-follow .icon-feed:after { border-color: #83EFFF } 27 | .theme-dark .comment .reply .icon { opacity: 1 } 28 | .theme-dark .comment .reply:hover { color: white } 29 | .theme-dark blockquote { border-left-color: #83efff } 30 | 31 | .theme-dark .lastcomments { background-color: transparent; } 32 | .theme-dark .comment { background-color: transparent; border-color: #47505a } 33 | .theme-dark .comment .comment-body blockquote a { color: white; } 34 | .theme-dark hr { border-color: #47505a } 35 | .theme-dark .pager a { border-color: #83efff; background-color: #83efff17; } 36 | .theme-dark .button-submit-comment { color: black; } 37 | .theme-dark code { background-color: #232323; border-color: #0A0A0A; color: #d3d3d3; } 38 | .theme-dark .zoom-overlay { background-color: #111 } 39 | .theme-dark .bottombar .save { background-color: #83EFFF; color: black; border-bottom-color: #4b6a6e; } 40 | .theme-dark .markdown-help { background-color: rgba(69, 58, 80, 0.93); } 41 | .theme-dark .left .avatar { background-color: #2f353b; } 42 | 43 | .theme-dark .icon-comment { background: #83efff } 44 | .theme-dark .icon-comment:after { border-top-color: #83efff; border-left-color: #83efff; } 45 | 46 | .theme-dark .user-size-used { color: #83efff; } 47 | -------------------------------------------------------------------------------- /css/hljs-github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } -------------------------------------------------------------------------------- /css/hljs-railscasts.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Railscasts-like style (c) Visoft, Inc. (Damien White) 4 | 5 | */ 6 | 7 | .theme-dark .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #232323; 12 | color: #e6e1dc; 13 | } 14 | 15 | .theme-dark .hljs-comment, 16 | .theme-dark .hljs-quote { 17 | color: #bc9458; 18 | font-style: italic; 19 | } 20 | 21 | .theme-dark .hljs-keyword, 22 | .theme-dark .hljs-selector-tag { 23 | color: #c26230; 24 | } 25 | 26 | .theme-dark .hljs-string, 27 | .theme-dark .hljs-number, 28 | .theme-dark .hljs-regexp, 29 | .theme-dark .hljs-variable, 30 | .theme-dark .hljs-template-variable { 31 | color: #a5c261; 32 | } 33 | 34 | .theme-dark .hljs-subst { 35 | color: #519f50; 36 | } 37 | 38 | .theme-dark .hljs-tag, 39 | .theme-dark .hljs-name { 40 | color: #e8bf6a; 41 | } 42 | 43 | .theme-dark .hljs-type { 44 | color: #da4939; 45 | } 46 | 47 | 48 | .theme-dark .hljs-symbol, 49 | .theme-dark .hljs-bullet, 50 | .theme-dark .hljs-built_in, 51 | .theme-dark .hljs-builtin-name, 52 | .theme-dark .hljs-attr, 53 | .theme-dark .hljs-link { 54 | color: #6d9cbe; 55 | } 56 | 57 | .theme-dark .hljs-params { 58 | color: #d0d0ff; 59 | } 60 | 61 | .theme-dark .hljs-attribute { 62 | color: #cda869; 63 | } 64 | 65 | .theme-dark .hljs-meta { 66 | color: #9b859d; 67 | } 68 | 69 | .theme-dark .hljs-title, 70 | .theme-dark .hljs-section { 71 | color: #ffc66d; 72 | } 73 | 74 | .theme-dark .hljs-addition { 75 | background-color: #144212; 76 | color: #e6e1dc; 77 | display: inline-block; 78 | width: 100%; 79 | } 80 | 81 | .theme-dark .hljs-deletion { 82 | background-color: #600; 83 | color: #e6e1dc; 84 | display: inline-block; 85 | width: 100%; 86 | } 87 | 88 | .theme-dark .hljs-selector-class { 89 | color: #9b703f; 90 | } 91 | 92 | .theme-dark .hljs-selector-id { 93 | color: #8b98ab; 94 | } 95 | 96 | .theme-dark .hljs-emphasis { 97 | font-style: italic; 98 | } 99 | 100 | .theme-dark .hljs-strong { 101 | font-weight: bold; 102 | } 103 | 104 | .theme-dark .hljs-link { 105 | text-decoration: underline; 106 | } 107 | -------------------------------------------------------------------------------- /css/icons.css: -------------------------------------------------------------------------------- 1 | .icon { display: inline-block; vertical-align: text-bottom; background-repeat: no-repeat; } 2 | .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 } 3 | .icon-profile:before { position: absolute; content: ""; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; border-radius: 50%; background: #FFFFFF; } 4 | 5 | .icon-comment { width: 16px; height: 10px; border-radius: 2px; background: #B10DC9; margin-top: 0px; display: inline-block; position: relative; top: -2px; } 6 | .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; } 7 | 8 | .icon-edit { 9 | width: 16px; height: 16px; background-repeat: no-repeat; background-position: 20px center; 10 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAOVBMVEUAAAC9w8e9w8e9w8e9w8e/xMi9w8e9w8e+w8e9w8e9w8e9w8e9w8e9w8e9w8e+w8e/xMi9w8e9w8fvY4+KAAAAEnRSTlMASPv3WQbwOTCkt4/psX4YDMWr+RRCAAAAUUlEQVQY06XLORKAMAxDUTs7kA3d/7AYGju0UfffjIgoHkxm0vB5bZyxKHx9eX0FJw0Y4bcXKQ4/CTtS5yqp5GFFOjGpVGl00k1pNDIb3Nv9AHC7BOZC4ZjvAAAAAElFTkSuQmCC+d0ckOwyAMRVGHUOO0gUyd+P8f7WApz4Iki9wFmyOEATrXLZcFp5LrGogPOxKp6zfFf9fZ1/I/cY7YZSS3U6S3XFZJmGBwL+FuJX/F1K0wUUlZyZGlXgXESthTEs4B8fh7xoVUDPGYJnsfkCRarKAgz8cAKbpD6pqDPz3XB8K6HdUEeN9NAAAAAElFTkSuQmCC); 11 | } 12 | .icon-reply { 13 | width: 16px; height: 16px; 14 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAABmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYs5FxxAAAAC3RSTlMAgBFwYExAMHgoCDJmUTYAAAA3SURBVAjXY8APGGEMQZgAjCEoKBwEEQCCAoiIh6AQVM1kMaguJhGYOSJQjexiUMbiAChDCclCAOHqBBdHpwQTAAAAAElFTkSuQmCC); 15 | } 16 | .icon-heart { position: absolute; width: 17px; height: 13px; margin-top: 5px } 17 | .icon-heart:before, .icon-heart:after { 18 | position: absolute; content: ""; left: 8px; top: 0; width: 8px; height: 13px; 19 | background: #FA6C8D; /*border-radius: 25px 25px 0 0;*/ transform: rotate(-45deg); transform-origin: 0 100% 20 | } 21 | .icon-heart:after { left: 0; transform: rotate(45deg); transform-origin :100% 100% } 22 | .icon-up { font-weight: normal !important; font-size: 15px; font-family: Tahoma; vertical-align: -4px; padding-right: 5px; display: inline; height: 1px; } -------------------------------------------------------------------------------- /css/mobile.css: -------------------------------------------------------------------------------- 1 | .left .left-more-link { display: none; } 2 | 3 | @media screen and (max-width: 960px) { 4 | 5 | .right { padding-right: 5px; padding-left: 0px; margin-left: 5px; max-width: 100%; box-sizing: border-box; } 6 | .left { 7 | display: block; width: 95%; box-sizing: border-box; text-align: left; margin-top: 20px; position: relative; margin-left: 10px; 8 | } 9 | .left .avatar-link { display: inline-block; float: left; } 10 | .left h1, .left .feed-follow { display: inline-block; float: left; vertical-align: top; margin-left: 25px; } 11 | .left .feed-follow { display: none; margin-top: 5px; margin-bottom: 35px; } 12 | .left .description { margin-left: 95px; margin-top: -35px; clear: both; float: left; } 13 | .left .left-more { display: none; clear: both; float: left } 14 | .left .left-more-link { margin-left: -25px; margin-right: 20px; margin-top: 13px; font-size: 22px; border: 0px; float: left; display: inline-block; width: 22px; } 15 | .left .left-more-link:before { content: "\002630"; transition: all 1s cubic-bezier(0.22, 1, 0.36, 1); position: absolute; } 16 | .left .left-more-link:after { 17 | content: "\0002DF"; font-size: 40px; top: -4px; position: relative; left: 4px; transform: rotateZ(90deg); transform-origin: 7px 19px; 18 | opacity: 0; transition: all 1s cubic-bezier(0.22, 1, 0.36, 1); display: block; 19 | } 20 | .left.show-more .left-more-link:before { opacity: 0; transform: scaleY(0); } 21 | .left.show-more .left-more-link:after { opacity: 1; transform: rotateZ(0); } 22 | .left.show-more { box-shadow: 0px 20px 15px -20px #9c27b04a; margin-bottom: 40px; padding-bottom: 20px; } 23 | .left.show-more .left-more { max-height: none; } 24 | .left .lastcomments { margin-left: 0px; } 25 | .post { min-width: auto; } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /css/zoom.css: -------------------------------------------------------------------------------- 1 | img[data-action="zoom"] { 2 | cursor: pointer; 3 | cursor: -webkit-zoom-in; 4 | cursor: -moz-zoom-in; 5 | } 6 | .zoom-img, 7 | .zoom-img-wrap { 8 | position: relative; 9 | z-index: 1666; 10 | -webkit-transition: all 300ms; 11 | -o-transition: all 300ms; 12 | transition: all 300ms; 13 | display: inline-block; 14 | } 15 | img.zoom-img { 16 | cursor: pointer; 17 | cursor: -webkit-zoom-out; 18 | cursor: -moz-zoom-out; 19 | } 20 | .zoom-overlay { 21 | z-index: 1420; 22 | background: #fff; 23 | position: fixed; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | bottom: 0; 28 | pointer-events: none; 29 | filter: "alpha(opacity=0)"; 30 | opacity: 0; 31 | -webkit-transition: opacity 300ms; 32 | -o-transition: opacity 300ms; 33 | transition: opacity 300ms; 34 | } 35 | .zoom-overlay-open .zoom-overlay { 36 | filter: "alpha(opacity=100)"; 37 | opacity: 1; 38 | } 39 | .zoom-overlay-open, 40 | .zoom-overlay-transitioning { 41 | cursor: default; 42 | } 43 | -------------------------------------------------------------------------------- /data-default/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "MyZeroBlog", 3 | "description": "My ZeroBlog.", 4 | "links": "- [Source code](https://github.com/HelloZeroNet)", 5 | "next_post_id": 2, 6 | "demo": false, 7 | "footer": "Powered by [ZeroNet](https://zeronet.io) [open, free, and uncensored]", 8 | "modified": 1432515193, 9 | "post": [ 10 | { 11 | "post_id": 1, 12 | "title": "Congratulations!", 13 | "date_published": 1433033779.604, 14 | "body": "Your zeronet blog has been successfully created!" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /data-default/users/content-default.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": {}, 3 | "ignore": ".*", 4 | "modified": 1432466966.003, 5 | "signs": { 6 | "1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8": "HChU28lG4MCnAiui6wDAaVCD4QUrgSy4zZ67+MMHidcUJRkLGnO3j4Eb1N0AWQ86nhSBwoOQf08Rha7gRyTDlAk=" 7 | }, 8 | "user_contents": { 9 | "cert_signers": { 10 | "zeroid.bit": [ "1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz" ] 11 | }, 12 | "permission_rules": { 13 | ".*": { 14 | "files_allowed": "data.json", 15 | "max_size": 10000 16 | }, 17 | "bitid/.*@zeroid.bit": { "max_size": 40000 }, 18 | "bitmsg/.*@zeroid.bit": { "max_size": 15000 } 19 | }, 20 | "permissions": { 21 | "banexample@zeroid.bit": false, 22 | "nofish@zeroid.bit": { "max_size": 20000 } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /dbschema.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "ZeroBlog", 3 | "db_file": "data/zeroblog.db", 4 | "version": 2, 5 | "maps": { 6 | "users/.+/data.json": { 7 | "to_table": [ 8 | "comment", 9 | {"node": "comment_vote", "table": "comment_vote", "key_col": "comment_uri", "val_col": "vote"}, 10 | {"node": "post_vote", "table": "post_vote", "key_col": "post_id", "val_col": "vote"} 11 | ] 12 | }, 13 | "users/.+/content.json": { 14 | "to_keyvalue": [ "cert_user_id" ] 15 | }, 16 | "data.json": { 17 | "to_table": [ "post" ], 18 | "to_keyvalue": [ "title", "description", "links", "next_post_id", "demo", "modified" ] 19 | } 20 | 21 | }, 22 | "tables": { 23 | "comment": { 24 | "cols": [ 25 | ["comment_id", "INTEGER"], 26 | ["post_id", "INTEGER"], 27 | ["body", "TEXT"], 28 | ["date_added", "INTEGER"], 29 | ["json_id", "INTEGER REFERENCES json (json_id)"] 30 | ], 31 | "indexes": ["CREATE UNIQUE INDEX comment_key ON comment(json_id, comment_id)", "CREATE INDEX comment_post_id ON comment(post_id)"], 32 | "schema_changed": 1426195823 33 | }, 34 | "comment_vote": { 35 | "cols": [ 36 | ["comment_uri", "TEXT"], 37 | ["vote", "INTEGER"], 38 | ["json_id", "INTEGER REFERENCES json (json_id)"] 39 | ], 40 | "indexes": ["CREATE INDEX comment_vote_comment_uri ON comment_vote(comment_uri)", "CREATE INDEX comment_vote_json_id ON comment_vote(json_id)"], 41 | "schema_changed": 1426195822 42 | }, 43 | "post": { 44 | "cols": [ 45 | ["post_id", "INTEGER"], 46 | ["title", "TEXT"], 47 | ["body", "TEXT"], 48 | ["date_published", "INTEGER"], 49 | ["json_id", "INTEGER REFERENCES json (json_id)"] 50 | ], 51 | "indexes": ["CREATE UNIQUE INDEX post_uri ON post(json_id, post_id)", "CREATE INDEX post_id ON post(post_id)"], 52 | "schema_changed": 1426195823 53 | }, 54 | "post_vote": { 55 | "cols": [ 56 | ["post_id", "INTEGER"], 57 | ["vote", "INTEGER"], 58 | ["json_id", "INTEGER REFERENCES json (json_id)"] 59 | ], 60 | "indexes": ["CREATE INDEX post_vote_post_id ON post_vote(post_id)", "CREATE INDEX post_vote_json_id ON post_vote(post_id)"], 61 | "schema_changed": 1426195826 62 | } 63 | }, 64 | "feeds": { 65 | "Comments": "SELECT 'comment' AS type, date_added, post.title AS title, keyvalue.value || ': ' || comment.body AS body, '?Post:' || comment.post_id || '#Comments' AS url FROM comment LEFT JOIN json USING (json_id) LEFT JOIN json AS json_content ON (json_content.directory = json.directory AND json_content.file_name='content.json') LEFT JOIN keyvalue ON (keyvalue.json_id = json_content.json_id AND key = 'cert_user_id') LEFT JOIN post ON (comment.post_id = post.post_id)", 66 | "Posts": "SELECT post_id AS event_uri, 'post' AS type, date_published AS date_added, title AS title, body AS body, '?Post:' || post_id AS url FROM post" 67 | } 68 | } -------------------------------------------------------------------------------- /img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloZeroNet/ZeroBlog/848980e91b564fe4cbd45a0ec81b13d017614895/img/avatar.png -------------------------------------------------------------------------------- /img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloZeroNet/ZeroBlog/848980e91b564fe4cbd45a0ec81b13d017614895/img/loading.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ZeroBlog Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
    19 |
  • # H1
  • 20 |
  • ## H2
  • 21 |
  • ### H3
  • 22 |
  • _italic_
  • 23 |
  • **bold**
  • 24 |
  • ~~strikethrough~~
  • 25 |
  • - Lists
  • 26 |
  • 1. Numbered lists
  • 27 |
  • [Links](http://www.zeronet.io)
  • 28 |
  • [References][1]
    [1]: Can be used
  • 29 |
  • ![image alt](img/logo.png)
  • 30 |
  • Inline `code`
  • 31 |
  • ```python
    print "Code block"
    ```
  • 32 |
  • > Quotes
  • 33 |
  • --- Horizontal rule
  • 34 |
35 | ? Editing: Post:21.body Save Delete Cancel 36 |
37 | 38 | 39 | 40 | 41 |
42 | Content changed Sign & Publish new content 43 |
44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | 52 | 53 |

54 | Follow in NewsfeedFollowing
55 |

56 |
57 |
58 | 59 | 60 | 61 |
62 |

Latest comments:

63 |
    64 |
  • 65 | nofish: 66 | WebGL under linux can be problematic, here is some tips... 67 |
    68 | @ 69 | Changelog, August 16, 2015 70 |
    71 |
  • 72 |
73 |
74 |
75 | 76 | 77 |
78 | 79 | 80 | 81 | 82 |
83 | 84 | 85 | 86 |
87 | Add new post 88 | 89 | 90 |
91 |

Title

92 |
93 | 21 hours ago · 2 min read 94 | ·
3 comments
95 | 96 | 100 |
101 |
Body
102 | Read more 103 |
104 | 105 | 106 |
107 | 108 | 109 | 110 |

Not found

111 |
112 |

Title

113 |
114 | 21 hours ago · 2 min read 115 | 116 |
117 | 118 |
119 | 120 |

0 Comments:

121 | 122 |
123 |
124 | Please sign in 125 | ━ 126 | new comment 127 |
128 |
129 |
Sign in as...
130 | 131 | Submit comment 132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | 140 | 141 |
142 | 143 |
144 |
145 | user_name 146 | 147 | ━ 148 | 1 day ago 149 |
Reply
150 |
151 |
Body
152 |
153 | 154 |
155 |
156 | 157 | 158 | 159 |
160 | 161 | 162 |
163 | 164 | 165 |
166 | 167 | 168 | 169 |
170 | 171 | 172 | 173 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /js/Comments.coffee: -------------------------------------------------------------------------------- 1 | class Comments extends Class 2 | pagePost: (post_id, cb=false) -> 3 | @post_id = post_id 4 | @rules = {} 5 | $(".button-submit-comment").off("click").on "click", => 6 | @submitComment() 7 | return false 8 | @loadComments("noanim", cb) 9 | @autoExpand $(".comment-textarea") 10 | 11 | $(".certselect").off("click").on "click", => 12 | if Page.server_info.rev < 160 13 | Page.cmd "wrapperNotification", ["error", "Comments requires at least ZeroNet 0.3.0 Please upgade!"] 14 | else 15 | Page.cmd "certSelect", [["zeroid.bit"]] 16 | return false 17 | 18 | 19 | loadComments: (type="show", cb=false) -> 20 | query = "SELECT comment.*, json_content.json_id AS content_json_id, keyvalue.value AS cert_user_id, json.directory, 21 | (SELECT COUNT(*) FROM comment_vote WHERE comment_vote.comment_uri = comment.comment_id || '@' || json.directory)+1 AS votes 22 | FROM comment 23 | LEFT JOIN json USING (json_id) 24 | LEFT JOIN json AS json_content ON (json_content.directory = json.directory AND json_content.file_name='content.json') 25 | LEFT JOIN keyvalue ON (keyvalue.json_id = json_content.json_id AND key = 'cert_user_id') 26 | WHERE post_id = #{@post_id} ORDER BY date_added DESC" 27 | 28 | Page.cmd "dbQuery", query, (comments) => 29 | $("#Comments_header").text(comments.length + if comments.length > 1 then " Comments:" else " Comment:") 30 | for comment in comments 31 | user_address = comment.directory.replace("users/", "") 32 | comment_address = "#{comment.comment_id}_#{user_address}" 33 | elem = $("#comment_"+comment_address) 34 | if elem.length == 0 # Create if not exits 35 | elem = $(".comment.template").clone().removeClass("template").attr("id", "comment_"+comment_address).data("post_id", @post_id) 36 | if type != "noanim" 37 | elem.cssSlideDown() 38 | $(".reply", elem).off("click").on "click", (e) => # Reply link 39 | return @buttonReply $(e.target).parents(".comment") 40 | @applyCommentData(elem, comment) 41 | elem.appendTo(".comments") 42 | setTimeout (-> 43 | Page.addInlineEditors(".comments") 44 | ), 1000 45 | 46 | 47 | applyCommentData: (elem, comment) -> 48 | [user_name, cert_domain] = comment.cert_user_id.split("@") 49 | user_address = comment.directory.replace("users/", "") 50 | $(".comment-body", elem).html Text.renderMarked(comment.body, {"sanitize": true}) 51 | $(".user_name", elem).text(user_name).css("color": Text.toColor(comment.cert_user_id)).attr("title", "#{user_name}@#{cert_domain}: #{user_address}") 52 | $(".added", elem).text(Time.since(comment.date_added)).attr("title", Time.date(comment.date_added, "long")) 53 | #$(".cert_domain", elem).html("@#{cert_domain}").css("display", "none") 54 | # Add inline editor 55 | if user_address == Page.site_info.auth_address 56 | $(elem).attr("data-object", "Comment:#{comment.comment_id}").attr("data-deletable", "yes") 57 | $(".comment-body", elem).attr("data-editable", "body").data("content", comment.body) 58 | 59 | 60 | buttonReply: (elem) -> 61 | @log "Reply to", elem 62 | user_name = $(".user_name", elem).text() 63 | post_id = elem.attr("id") 64 | body_add = "> [#{user_name}](\##{post_id}): " 65 | elem_quote = $(".comment-body", elem).clone() 66 | $("blockquote", elem_quote).remove() # Remove other people's quotes 67 | body_add+= elem_quote.text().trim("\n").replace(/\n/g, "\n> ") 68 | body_add+= "\n\n" 69 | $(".comment-new .comment-textarea").val( $(".comment-new .comment-textarea").val()+body_add ) 70 | $(".comment-new .comment-textarea").trigger("input").focus() # Autosize 71 | return false 72 | 73 | 74 | submitComment: -> 75 | if not Page.site_info.cert_user_id # Not registered 76 | Page.cmd "wrapperNotification", ["info", "Please, select your account."] 77 | return false 78 | 79 | body = $(".comment-new .comment-textarea").val() 80 | if not body 81 | $(".comment-new .comment-textarea").focus() 82 | return false 83 | 84 | $(".comment-new .button-submit").addClass("loading") 85 | inner_path = "data/users/#{Page.site_info.auth_address}/data.json" 86 | Page.cmd "fileGet", {"inner_path": inner_path, "required": false}, (data) => 87 | if data 88 | data = JSON.parse(data) 89 | else # Default data 90 | data = {"next_comment_id": 1, "comment": [], "comment_vote": {}, "topic_vote": {} } 91 | 92 | data.comment.push { 93 | "comment_id": data.next_comment_id, 94 | "body": body, 95 | "post_id": @post_id, 96 | "date_added": Time.timestamp() 97 | } 98 | data.next_comment_id += 1 99 | json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t'))) 100 | Page.writePublish inner_path, btoa(json_raw), (res) => 101 | $(".comment-new .button-submit").removeClass("loading") 102 | @loadComments() 103 | setTimeout (-> 104 | Page.loadLastcomments() 105 | ), 1000 106 | @checkCert("updaterules") 107 | @log "Writepublish result", res 108 | if res != false 109 | $(".comment-new .comment-textarea").val("") 110 | 111 | 112 | checkCert: (type) -> 113 | last_cert_user_id = $(".comment-new .user_name").text() 114 | if Page.site_info.cert_user_id 115 | $(".comment-new").removeClass("comment-nocert") 116 | $(".comment-new .user_name").text(Page.site_info.cert_user_id) 117 | else 118 | $(".comment-new").addClass("comment-nocert") 119 | $(".comment-new .user_name").text("Please sign in") 120 | 121 | if $(".comment-new .user_name").text() != last_cert_user_id or type == "updaterules" # User changed 122 | # Update used/allowed space 123 | if Page.site_info.cert_user_id 124 | Page.cmd "fileRules", "data/users/#{Page.site_info.auth_address}/content.json", (rules) => 125 | @rules = rules 126 | if rules.max_size 127 | @setCurrentSize(rules.current_size) 128 | else 129 | @setCurrentSize(0) 130 | else 131 | @setCurrentSize(0) 132 | 133 | 134 | setCurrentSize: (current_size) -> 135 | if current_size 136 | current_size_kb = current_size/1000 137 | $(".user-size").text("used: #{current_size_kb.toFixed(1)}k/#{Math.round(@rules.max_size/1000)}k") 138 | $(".user-size-used").css("width", Math.round(70*current_size/@rules.max_size)) 139 | else 140 | $(".user-size").text("") 141 | 142 | 143 | autoExpand: (elem) -> 144 | editor = elem[0] 145 | # Autoexpand 146 | if elem.height() > 0 then elem.height(1) 147 | 148 | elem.off("input").on "input", => 149 | if editor.scrollHeight > elem.height() 150 | old_height = elem.height() 151 | elem.height(1) 152 | new_height = editor.scrollHeight 153 | new_height += parseFloat elem.css("borderTopWidth") 154 | new_height += parseFloat elem.css("borderBottomWidth") 155 | new_height -= parseFloat elem.css("paddingTop") 156 | new_height -= parseFloat elem.css("paddingBottom") 157 | 158 | min_height = parseFloat(elem.css("lineHeight"))*2 # 2 line minimum 159 | if new_height < min_height then new_height = min_height+4 160 | 161 | elem.height(new_height-4) 162 | # Update used space 163 | if @rules.max_size 164 | if elem.val().length > 0 165 | current_size = @rules.current_size + elem.val().length + 90 166 | else 167 | current_size = @rules.current_size 168 | @setCurrentSize(current_size) 169 | if elem.height() > 0 then elem.trigger "input" 170 | else elem.height("48px") 171 | 172 | 173 | window.Comments = new Comments() 174 | -------------------------------------------------------------------------------- /js/ZeroBlog.coffee: -------------------------------------------------------------------------------- 1 | class ZeroBlog extends ZeroFrame 2 | init: -> 3 | @data = null 4 | @site_info = null 5 | @server_info = null 6 | @page = 1 7 | @my_post_votes = {} 8 | 9 | @event_page_load = $.Deferred() 10 | @event_site_info = $.Deferred() 11 | 12 | # Editable items on own site 13 | $.when(@event_page_load, @event_site_info).done => 14 | if @site_info.settings.own or @data.demo 15 | @addInlineEditors() 16 | @checkPublishbar() 17 | $(".publishbar").off("click").on "click", @publish 18 | $(".posts .button.new").css("display", "inline-block") 19 | $(".editbar .icon-help").off("click").on "click", => 20 | $(".editbar .markdown-help").css("display", "block") 21 | $(".editbar .markdown-help").toggleClassLater("visible", 10) 22 | $(".editbar .icon-help").toggleClass("active") 23 | return false 24 | 25 | $.when(@event_site_info).done => 26 | @log "event site info" 27 | # Set avatar 28 | imagedata = new Identicon(@site_info.address, 70).toString(); 29 | $("body").append("") 30 | @initFollowButton() 31 | 32 | $(".left-more-link").on "click", => 33 | $(".left .left-more").slideToggle() 34 | $(".left").toggleClass("show-more") 35 | return false 36 | 37 | @log "inited!" 38 | 39 | 40 | initFollowButton: -> 41 | @follow = new Follow($(".feed-follow")) 42 | @follow.addFeed("Posts", " 43 | SELECT 44 | post_id AS event_uri, 45 | 'post' AS type, 46 | date_published AS date_added, 47 | title AS title, 48 | body AS body, 49 | '?Post:' || post_id AS url 50 | FROM post", true) 51 | 52 | if Page.site_info.cert_user_id 53 | username = Page.site_info.cert_user_id.replace /@.*/, "" 54 | @follow.addFeed("Username mentions", " 55 | SELECT 56 | 'mention' AS type, 57 | date_added, 58 | post.title AS title, 59 | keyvalue.value || ': ' || comment.body AS body, 60 | '?Post:' || comment.post_id || '#Comments' AS url 61 | FROM comment 62 | LEFT JOIN json USING (json_id) 63 | LEFT JOIN json AS json_content ON (json_content.directory = json.directory AND json_content.file_name='content.json') 64 | LEFT JOIN keyvalue ON (keyvalue.json_id = json_content.json_id AND key = 'cert_user_id') 65 | LEFT JOIN post ON (comment.post_id = post.post_id) 66 | WHERE 67 | comment.body LIKE '%[#{username}%' OR comment.body LIKE '%@#{username}%' 68 | ", true) 69 | 70 | @follow.addFeed("Comments", " 71 | SELECT 72 | 'comment' AS type, 73 | date_added, 74 | post.title AS title, 75 | keyvalue.value || ': ' || comment.body AS body, 76 | '?Post:' || comment.post_id || '#Comments' AS url 77 | FROM comment 78 | LEFT JOIN json USING (json_id) 79 | LEFT JOIN json AS json_content ON (json_content.directory = json.directory AND json_content.file_name='content.json') 80 | LEFT JOIN keyvalue ON (keyvalue.json_id = json_content.json_id AND key = 'cert_user_id') 81 | LEFT JOIN post ON (comment.post_id = post.post_id)") 82 | @follow.init() 83 | 84 | 85 | loadData: (query="new") -> 86 | # Get blog parameters 87 | if query == "old" # Old type query for pre 0.3.0 88 | query = "SELECT key, value FROM json LEFT JOIN keyvalue USING (json_id) WHERE path = 'data.json'" 89 | else 90 | query = "SELECT key, value FROM json LEFT JOIN keyvalue USING (json_id) WHERE directory = '' AND file_name = 'data.json'" 91 | @cmd "dbQuery", [query], (res) => 92 | @data = {} 93 | if res 94 | for row in res 95 | @data[row.key] = row.value 96 | 97 | if @data.title then $(".left h1 a:not(.editable-edit)").html(@data.title).data("content", @data.title) 98 | if @data.description then $(".left h2").html(Text.renderMarked(@data.description)).data("content", @data.description) 99 | if @data.links then $(".left .links").html(Text.renderMarked(@data.links)).data("content", @data.links) 100 | 101 | loadLastcomments: (type="show", cb=false) -> 102 | query = " 103 | SELECT comment.*, json_content.json_id AS content_json_id, keyvalue.value AS cert_user_id, json.directory, post.title AS post_title 104 | FROM comment 105 | LEFT JOIN json USING (json_id) 106 | LEFT JOIN json AS json_content ON (json_content.directory = json.directory AND json_content.file_name='content.json') 107 | LEFT JOIN keyvalue ON (keyvalue.json_id = json_content.json_id AND key = 'cert_user_id') 108 | LEFT JOIN post ON (comment.post_id = post.post_id) 109 | WHERE post.title IS NOT NULL 110 | ORDER BY date_added DESC LIMIT 3" 111 | 112 | @cmd "dbQuery", [query], (res) => 113 | if res.length 114 | $(".lastcomments").css("display", "block") 115 | res.reverse() 116 | for lastcomment in res 117 | elem = $("#lastcomment_#{lastcomment.json_id}_#{lastcomment.comment_id}") 118 | if elem.length == 0 # Not exits yet 119 | elem = $(".lastcomment.template").clone().removeClass("template").attr("id", "lastcomment_#{lastcomment.json_id}_#{lastcomment.comment_id}") 120 | if type != "noanim" 121 | elem.cssSlideDown() 122 | elem.prependTo(".lastcomments ul") 123 | @applyLastcommentdata(elem, lastcomment) 124 | if cb then cb() 125 | 126 | applyLastcommentdata: (elem, lastcomment) -> 127 | elem.find(".user_name").text(lastcomment.cert_user_id.replace(/@.*/, "")+":") 128 | 129 | body = Text.renderMarked(lastcomment.body) 130 | body = body.replace /[\r\n]/g, " " # Remove whitespace 131 | body = body.replace /\.*?\<\/blockquote\>/g, " " # Remove quotes 132 | body = body.replace /\<.*?\>/g, " " # Remove html codes 133 | if body.length > 60 # Strip if too long 134 | body = body[0..60].replace(/(.*) .*?$/, "$1") + " ..." # Keep the last 60 character and strip back until last space 135 | elem.find(".body").html(body) 136 | 137 | title_hash = lastcomment.post_title.replace(/[#?& ]/g, "+").replace(/[+]+/g, "+") 138 | elem.find(".postlink").text(lastcomment.post_title).attr("href", "?Post:#{lastcomment.post_id}:#{title_hash}#Comments") 139 | 140 | applyPagerdata: (page, limit, has_next) -> 141 | pager = $(".pager") 142 | if page > 1 143 | pager.find(".prev").css("display", "inline-block").attr("href", "?page=#{page-1}") 144 | if has_next 145 | pager.find(".next").css("display", "inline-block").attr("href", "?page=#{page+1}") 146 | 147 | routeUrl: (url) -> 148 | @log "Routing url:", url 149 | if match = url.match /Post:([0-9]+)/ 150 | $("body").addClass("page-post") 151 | @post_id = parseInt(match[1]) 152 | @pagePost() 153 | else 154 | $("body").addClass("page-main") 155 | if match = url.match /page=([0-9]+)/ 156 | @page = parseInt(match[1]) 157 | @pageMain() 158 | 159 | # - Pages - 160 | 161 | pagePost: () -> 162 | s = (+ new Date) 163 | @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) => 164 | parse_res = (res) => 165 | if res.length 166 | post = res[0] 167 | @applyPostdata($(".post-full"), post, true) 168 | $(".post-full").css("display", "block") 169 | $(".post-full .like").attr("id", "post_like_#{post.post_id}").off("click").off("click").on "click", @submitPostVote 170 | $(".notfound").css("display", "none") 171 | Comments.pagePost(@post_id) 172 | else 173 | $(".notfound").css("display", "block") 174 | $(".post-full").css("display", "none") 175 | @pageLoaded() 176 | Comments.checkCert() 177 | 178 | # Temporary dbschema bug workaround 179 | if res.error 180 | @cmd "dbQuery", ["SELECT *, -1 AS votes FROM post WHERE post_id = #{@post_id} LIMIT 1"], parse_res 181 | else 182 | parse_res(res) 183 | 184 | 185 | pageMain: -> 186 | limit = 15 187 | query = """ 188 | SELECT 189 | post.*, COUNT(comment_id) AS comments, 190 | (SELECT COUNT(*) FROM post_vote WHERE post_vote.post_id = post.post_id) AS votes 191 | FROM post 192 | LEFT JOIN comment USING (post_id) 193 | GROUP BY post_id 194 | ORDER BY date_published DESC 195 | LIMIT #{(@page-1)*limit}, #{limit+1} 196 | """ 197 | @cmd "dbQuery", [query], (res) => 198 | parse_res = (res) => 199 | s = (+ new Date) 200 | if res.length > limit # Has next page 201 | res.pop() 202 | @applyPagerdata(@page, limit, true) 203 | else 204 | @applyPagerdata(@page, limit, false) 205 | 206 | res.reverse() 207 | for post in res 208 | elem = $("#post_#{post.post_id}") 209 | if elem.length == 0 # Not exits yet 210 | elem = $(".post.template").clone().removeClass("template").attr("id", "post_#{post.post_id}") 211 | elem.prependTo(".posts") 212 | # elem.find(".score").attr("id", "post_score_#{post.post_id}").on "click", @submitPostVote # Submit vote 213 | elem.find(".like").attr("id", "post_like_#{post.post_id}").off("click").on "click", @submitPostVote 214 | @applyPostdata(elem, post) 215 | @pageLoaded() 216 | @log "Posts loaded in", ((+ new Date)-s),"ms" 217 | 218 | $(".posts .new").off("click").on "click", => # Create new blog post 219 | @cmd "fileGet", ["data/data.json"], (res) => 220 | data = JSON.parse(res) 221 | # Add to data 222 | data.post.unshift 223 | post_id: data.next_post_id 224 | title: "New blog post" 225 | date_published: (+ new Date)/1000 226 | body: "Blog post body" 227 | data.next_post_id += 1 228 | 229 | # Create html elements 230 | elem = $(".post.template").clone().removeClass("template") 231 | @applyPostdata(elem, data.post[0]) 232 | elem.hide() 233 | elem.prependTo(".posts").slideDown() 234 | @addInlineEditors(elem) 235 | 236 | @writeData(data) 237 | return false 238 | 239 | # Temporary dbschema bug workaround 240 | if res.error 241 | query = """ 242 | SELECT 243 | post.*, COUNT(comment_id) AS comments, 244 | -1 AS votes 245 | FROM post 246 | LEFT JOIN comment USING (post_id) 247 | GROUP BY post_id 248 | ORDER BY date_published DESC 249 | LIMIT #{(@page-1)*limit}, #{limit+1} 250 | """ 251 | @cmd "dbQuery", [query], parse_res 252 | else 253 | parse_res(res) 254 | 255 | 256 | # - EOF Pages - 257 | 258 | 259 | # All page content loaded 260 | pageLoaded: => 261 | $("body").addClass("loaded") # Back/forward button keep position support 262 | $('pre code').each (i, block) -> # Higlight code blocks 263 | hljs.highlightBlock(block) 264 | @event_page_load.resolve() 265 | @cmd "innerLoaded", true 266 | 267 | 268 | addInlineEditors: (parent) -> 269 | @logStart "Adding inline editors" 270 | elems = $("[data-editable]:visible", parent) 271 | for elem in elems 272 | elem = $(elem) 273 | if not elem.data("editor") and not elem.hasClass("editor") 274 | editor = new InlineEditor(elem, @getContent, @saveContent, @getObject) 275 | elem.data("editor", editor) 276 | @logEnd "Adding inline editors" 277 | 278 | addImageZoom: (parent) -> 279 | $("img", parent).each (i, img_elem) => 280 | img_elem.onload = => 281 | img_elem = $(img_elem) 282 | size = img_elem.attr("alt")?.match("([0-9]+)x([0-9]+)") 283 | if not size 284 | return 285 | if img_elem.width() < parseInt(size[1]) or img_elem.height() < parseInt(size[2]) 286 | img_elem.attr("data-action", "zoom") 287 | img_elem.onload = null 288 | if img_elem.complete 289 | img_elem.onload() 290 | 291 | # Check if publishing is necessary 292 | checkPublishbar: -> 293 | if @data? and (not @data["modified"] or @data["modified"] > @site_info.content.modified) 294 | $(".publishbar").addClass("visible") 295 | else 296 | $(".publishbar").removeClass("visible") 297 | 298 | 299 | # Sign and Publish site 300 | publish: => 301 | if @site_info.privatekey # Privatekey stored in users.json 302 | @cmd "sitePublish", ["stored"], (res) => 303 | @log "Publish result:", res 304 | else 305 | @cmd "wrapperPrompt", ["Enter your private key:", "password"], (privatekey) => # Prompt the private key 306 | $(".publishbar .button").addClass("loading") 307 | @cmd "sitePublish", [privatekey], (res) => 308 | $(".publishbar .button").removeClass("loading") 309 | @log "Publish result:", res 310 | 311 | return false # Ignore link default event 312 | 313 | 314 | # Apply from data to post html element 315 | applyPostdata: (elem, post, full=false) -> 316 | title_hash = post.title.replace(/[#?& ]/g, "+").replace(/[+]+/g, "+") 317 | elem.data("object", "Post:"+post.post_id) 318 | $(".title .editable", elem).html(post.title).attr("href", "?Post:#{post.post_id}:#{title_hash}").data("content", post.title) 319 | date_published = Time.since(post.date_published) 320 | # Published date 321 | post.body = post.body.replace(/^\* \* \*/m, "---") 322 | if post.body.match /^---/m # Has more over fold 323 | date_published += " · #{Time.readtime(post.body)}" # If has break add readtime 324 | $(".more", elem).css("display", "inline-block").attr("href", "?Post:#{post.post_id}:#{title_hash}") 325 | $(".details .published", elem).html(date_published).data("content", post.date_published) 326 | # Comments num 327 | if post.comments > 0 328 | $(".details .comments-num", elem).css("display", "inline").attr("href", "?Post:#{post.post_id}:#{title_hash}#Comments") 329 | if post.comments > 1 330 | $(".details .comments-num .num", elem).text("#{post.comments} comments") 331 | else 332 | $(".details .comments-num .num", elem).text("#{post.comments} comment") 333 | else 334 | $(".details .comments-num", elem).css("display", "none") 335 | 336 | ### 337 | if @my_post_votes[post.post_id] # Voted on it 338 | $(".score-inactive .score-num", elem).text post.votes-1 339 | $(".score-active .score-num", elem).text post.votes 340 | $(".score", elem).addClass("active") 341 | else # Not voted on it 342 | $(".score-inactive .score-num", elem).text post.votes 343 | $(".score-active .score-num", elem).text post.votes+1 344 | 345 | if post.votes == 0 346 | $(".score", elem).addClass("noscore") 347 | else 348 | $(".score", elem).removeClass("noscore") 349 | ### 350 | if post.votes > 0 351 | $(".like .num", elem).text post.votes 352 | else if post.votes == -1 # DB bug 353 | $(".like", elem).css("display", "none") 354 | else 355 | $(".like .num", elem).text "" 356 | 357 | if @my_post_votes[post.post_id] # Voted on it 358 | $(".like", elem).addClass("active") 359 | 360 | 361 | if full 362 | body = post.body 363 | else # On main page only show post until the first --- hr separator 364 | body = post.body.replace(/^([\s\S]*?)\n---\n[\s\S]*$/, "$1") 365 | 366 | if $(".body", elem).data("content") != post.body 367 | $(".body", elem).html(Text.renderMarked(body)).data("content", post.body) 368 | @addImageZoom(elem) 369 | 370 | # Wrapper websocket connection ready 371 | onOpenWebsocket: (e) => 372 | @loadData() 373 | @cmd "siteInfo", {}, (site_info) => 374 | @setSiteinfo(site_info) 375 | query_my_votes = """ 376 | SELECT 377 | 'post_vote' AS type, 378 | post_id AS uri 379 | FROM json 380 | LEFT JOIN post_vote USING (json_id) 381 | WHERE directory = 'users/#{@site_info.auth_address}' AND file_name = 'data.json' 382 | """ 383 | @cmd "dbQuery", [query_my_votes], (res) => 384 | for row in res 385 | @my_post_votes[row["uri"]] = 1 386 | @routeUrl(window.location.search.substring(1)) 387 | 388 | @cmd "serverInfo", {}, (ret) => # Get server info 389 | @server_info = ret 390 | if @server_info.rev < 160 391 | @loadData("old") 392 | @loadLastcomments("noanim") 393 | 394 | 395 | # Returns the elem parent object 396 | getObject: (elem) => 397 | return elem.parents("[data-object]:first") 398 | 399 | 400 | # Get content from data.json 401 | getContent: (elem, raw=false) => 402 | [type, id] = @getObject(elem).data("object").split(":") 403 | id = parseInt(id) 404 | content = elem.data("content") 405 | if elem.data("editable-mode") == "timestamp" # Convert to time 406 | content = Time.date(content, "full") 407 | 408 | if elem.data("editable-mode") == "simple" or raw # No markdown 409 | return content 410 | else 411 | return Text.renderMarked(content) 412 | 413 | 414 | # Save content to data.json 415 | saveContent: (elem, content, cb=false) => 416 | if elem.data("deletable") and content == null then return @deleteObject(elem, cb) # Its a delete request 417 | if elem.data('editableMode') == "timestamp" then elem.data("content", Time.timestamp(content)) else elem.data("content", content) 418 | [type, id] = @getObject(elem).data("object").split(":") 419 | id = parseInt(id) 420 | if type == "Post" or type == "Site" 421 | @saveSite(elem, type, id, content, cb) 422 | else if type == "Comment" 423 | @saveComment(elem, type, id, content, cb) 424 | 425 | 426 | 427 | saveSite: (elem, type, id, content, cb) -> 428 | @cmd "fileGet", ["data/data.json"], (res) => 429 | data = JSON.parse(res) 430 | if type == "Post" 431 | post = (post for post in data.post when post.post_id == id)[0] 432 | 433 | if elem.data("editable-mode") == "timestamp" # Time parse to timestamp 434 | content = Time.timestamp(content) 435 | 436 | post[elem.data("editable")] = content 437 | else if type == "Site" 438 | data[elem.data("editable")] = content 439 | 440 | @writeData data, (res) => 441 | if cb 442 | if res == true # OK 443 | @cleanupImages() 444 | if elem.data("editable-mode") == "simple" # No markdown 445 | cb(content) 446 | else if elem.data("editable-mode") == "timestamp" # Format timestamp 447 | cb(Time.since(content)) 448 | else 449 | cb(Text.renderMarked(content)) 450 | else # Error 451 | cb(false) 452 | 453 | 454 | 455 | saveComment: (elem, type, id, content, cb) -> 456 | @log "Saving comment...", id 457 | @getObject(elem).css "height", "auto" 458 | inner_path = "data/users/#{Page.site_info.auth_address}/data.json" 459 | Page.cmd "fileGet", {"inner_path": inner_path, "required": false}, (data) => 460 | data = JSON.parse(data) 461 | comment = (comment for comment in data.comment when comment.comment_id == id)[0] 462 | comment[elem.data("editable")] = content 463 | json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t'))) 464 | @writePublish inner_path, btoa(json_raw), (res) => 465 | if res == true 466 | Comments.checkCert("updaterules") 467 | if cb then cb(Text.renderMarked(content, {"sanitize": true})) 468 | else 469 | @cmd "wrapperNotification", ["error", "File write error: #{res}"] 470 | if cb then cb(false) 471 | 472 | 473 | 474 | 475 | deleteObject: (elem, cb=False) -> 476 | [type, id] = elem.data("object").split(":") 477 | id = parseInt(id) 478 | 479 | if type == "Post" 480 | @cmd "fileGet", ["data/data.json"], (res) => 481 | data = JSON.parse(res) 482 | if type == "Post" 483 | post = (post for post in data.post when post.post_id == id)[0] 484 | if not post then return false # No post found for this id 485 | data.post.splice(data.post.indexOf(post), 1) # Remove from data 486 | 487 | @writeData data, (res) => 488 | if cb then cb() 489 | if res == true then elem.slideUp() 490 | else if type == "Comment" 491 | inner_path = "data/users/#{Page.site_info.auth_address}/data.json" 492 | @cmd "fileGet", {"inner_path": inner_path, "required": false}, (data) => 493 | data = JSON.parse(data) 494 | comment = (comment for comment in data.comment when comment.comment_id == id)[0] 495 | data.comment.splice(data.comment.indexOf(comment), 1) 496 | json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t'))) 497 | @writePublish inner_path, btoa(json_raw), (res) => 498 | if res == true 499 | elem.slideUp() 500 | if cb then cb() 501 | 502 | 503 | 504 | writeData: (data, cb=null) -> 505 | if not data 506 | return @log "Data missing" 507 | @data["modified"] = data.modified = Time.timestamp() 508 | json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t'))) # Encode to json, encode utf8 509 | @cmd "fileWrite", ["data/data.json", btoa(json_raw)], (res) => # Convert to to base64 and send 510 | if res == "ok" 511 | if cb then cb(true) 512 | else 513 | @cmd "wrapperNotification", ["error", "File write error: #{res}"] 514 | if cb then cb(false) 515 | @checkPublishbar() 516 | 517 | # Updating title in content.json 518 | @cmd "fileGet", ["content.json"], (content) => 519 | content = content.replace /"title": ".*?"/, "\"title\": \"#{data.title}\"" # Load as raw html to prevent js bignumber problems 520 | content = unescape(encodeURIComponent(content)) 521 | @cmd "fileWrite", ["content.json", btoa(content)], (res) => 522 | if res != "ok" 523 | @cmd "wrapperNotification", ["error", "Content.json write error: #{res}"] 524 | 525 | # If the privatekey is stored sign the new content 526 | if @site_info["privatekey"] 527 | @cmd "siteSign", ["stored", "content.json"], (res) => 528 | @log "Sign result", res 529 | 530 | 531 | writePublish: (inner_path, data, cb) -> 532 | @cmd "fileWrite", [inner_path, data], (res) => 533 | if res != "ok" # fileWrite failed 534 | @cmd "wrapperNotification", ["error", "File write error: #{res}"] 535 | cb(false) 536 | return false 537 | 538 | @cmd "sitePublish", {"inner_path": inner_path}, (res) => 539 | if res == "ok" 540 | cb(true) 541 | else 542 | cb(res) 543 | 544 | submitPostVote: (e) => 545 | if not Page.site_info.cert_user_id # No selected cert 546 | Page.cmd "certSelect", [["zeroid.bit"]] 547 | return false 548 | 549 | elem = $(e.currentTarget) 550 | elem.toggleClass("active").addClass("loading") 551 | inner_path = "data/users/#{@site_info.auth_address}/data.json" 552 | Page.cmd "fileGet", {"inner_path": inner_path, "required": false}, (data) => 553 | if data 554 | data = JSON.parse(data) 555 | else # Default data 556 | data = {"next_comment_id": 1, "comment": [], "comment_vote": {}, "post_vote": {} } 557 | 558 | if not data.post_vote 559 | data.post_vote = {} 560 | post_id = elem.attr("id").match("_([0-9]+)$")[1] 561 | 562 | if elem.hasClass("active") 563 | data.post_vote[post_id] = 1 564 | else 565 | delete data.post_vote[post_id] 566 | json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t'))) 567 | 568 | current_num = parseInt elem.find(".num").text() 569 | if not current_num 570 | current_num = 0 571 | if elem.hasClass("active") 572 | elem.find(".num").text(current_num+1) 573 | else 574 | elem.find(".num").text(current_num-1) 575 | 576 | Page.writePublish inner_path, btoa(json_raw), (res) => 577 | elem.removeClass("loading") 578 | @log "Writepublish result", res 579 | 580 | return false 581 | 582 | # Delete non-referenced images 583 | cleanupImages: -> 584 | @cmd "fileGet", ["data/data.json"], (data) => 585 | Page.cmd "fileList", "data/img", (files) => 586 | for file in files 587 | if file.indexOf("post_") != 0 588 | continue 589 | if data.indexOf(file) == -1 590 | @log "Deleting image", file, "..." 591 | @cmd "fileDelete", "data/img/#{file}" 592 | 593 | 594 | # Parse incoming requests 595 | onRequest: (cmd, message) -> 596 | if cmd == "setSiteInfo" # Site updated 597 | @actionSetSiteInfo(message) 598 | else 599 | @log "Unknown command", message 600 | 601 | 602 | # Siteinfo changed 603 | actionSetSiteInfo: (message) => 604 | @setSiteinfo(message.params) 605 | @checkPublishbar() 606 | 607 | 608 | setSiteinfo: (site_info) => 609 | @site_info = site_info 610 | @event_site_info.resolve(site_info) 611 | if $("body").hasClass("page-post") then Comments.checkCert() # Update if username changed 612 | # User commented 613 | if site_info.event?[0] == "file_done" and site_info.event[1].match /.*users.*data.json$/ 614 | if $("body").hasClass("page-post") 615 | @pagePost() 616 | Comments.loadComments() # Post page, reload comments 617 | @loadLastcomments() 618 | if $("body").hasClass("page-main") 619 | RateLimit 500, => 620 | @pageMain() 621 | @loadLastcomments() 622 | else if site_info.event?[0] == "file_done" and site_info.event[1] == "data/data.json" 623 | @loadData() 624 | if $("body").hasClass("page-main") then @pageMain() 625 | if $("body").hasClass("page-post") then @pagePost() 626 | else if site_info.event?[0] == "cert_changed" and site_info.cert_user_id 627 | # Auto click follow username mentions on cert change 628 | @initFollowButton() 629 | mentions_menu_elem = @follow.feeds["Username mentions"][1] 630 | setTimeout ( => 631 | if not mentions_menu_elem.hasClass("selected") 632 | mentions_menu_elem.trigger("click") 633 | ), 100 634 | 635 | 636 | window.Page = new ZeroBlog() 637 | -------------------------------------------------------------------------------- /js/lib/identicon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Identicon.js v1.0 3 | * http://github.com/stewartlord/identicon.js 4 | * 5 | * Requires PNGLib 6 | * http://www.xarg.org/download/pnglib.js 7 | * 8 | * Copyright 2013, Stewart Lord 9 | * Released under the BSD license 10 | * http://www.opensource.org/licenses/bsd-license.php 11 | */ 12 | 13 | (function() { 14 | Identicon = function(hash, size, margin){ 15 | this.hash = hash; 16 | this.size = size || 64; 17 | this.margin = margin || .08; 18 | } 19 | 20 | Identicon.prototype = { 21 | hash: null, 22 | size: null, 23 | margin: null, 24 | 25 | render: function(){ 26 | var hash = this.hash, 27 | size = this.size, 28 | margin = Math.floor(size * this.margin), 29 | cell = Math.floor((size - (margin * 2)) / 5), 30 | image = new PNGlib(size, size, 256); 31 | 32 | // light-grey background 33 | var bg = image.color(240, 240, 240); 34 | 35 | // foreground is last 7 chars as hue at 50% saturation, 70% brightness 36 | var rgb = this.hsl2rgb(parseInt(hash.substr(-7), 16) / 0xfffffff, .5, .7), 37 | fg = image.color(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255); 38 | 39 | // the first 15 characters of the hash control the pixels (even/odd) 40 | // they are drawn down the middle first, then mirrored outwards 41 | var i, color; 42 | for (i = 0; i < 15; i++) { 43 | color = parseInt(hash.charAt(i), 16) % 2 ? bg : fg; 44 | if (i < 5) { 45 | this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image); 46 | } else if (i < 10) { 47 | this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); 48 | this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); 49 | } else if (i < 15) { 50 | this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); 51 | this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); 52 | } 53 | } 54 | 55 | return image; 56 | }, 57 | 58 | rectangle: function(x, y, w, h, color, image) { 59 | var i, j; 60 | for (i = x; i < x + w; i++) { 61 | for (j = y; j < y + h; j++) { 62 | image.buffer[image.index(i, j)] = color; 63 | } 64 | } 65 | }, 66 | 67 | // adapted from: https://gist.github.com/aemkei/1325937 68 | hsl2rgb: function(h, s, b){ 69 | h *= 6; 70 | s = [ 71 | b += s *= b < .5 ? b : 1 - b, 72 | b - h % 1 * s * 2, 73 | b -= s *= 2, 74 | b, 75 | b + h % 1 * s, 76 | b + s 77 | ]; 78 | 79 | return[ 80 | s[ ~~h % 6 ], // red 81 | s[ (h|16) % 6 ], // green 82 | s[ (h|8) % 6 ] // blue 83 | ]; 84 | }, 85 | 86 | toString: function(){ 87 | return this.render().getBase64(); 88 | } 89 | } 90 | 91 | window.Identicon = Identicon; 92 | })(); -------------------------------------------------------------------------------- /js/lib/jquery.cssanim.coffee: -------------------------------------------------------------------------------- 1 | jQuery.fn.cssSlideDown = -> 2 | elem = @ 3 | elem.css({"opacity": 0, "margin-bottom": 0, "margin-top": 0, "padding-bottom": 0, "padding-top": 0, "display": "none", "transform": "scale(0.8)"}) 4 | setTimeout (-> 5 | elem.css("display", "") 6 | height = elem.outerHeight() 7 | elem.css({"height": 0, "display": ""}).cssLater("transition", "all 0.3s ease-out", 20) 8 | elem.cssLater({"height": height, "opacity": 1, "margin-bottom": "", "margin-top": "", "padding-bottom": "", "padding-top": "", "transform": "scale(1)"}, null, 40) 9 | elem.cssLater({"transition": "", "transform": ""}, null, 1000, "noclear") 10 | ), 10 11 | return @ 12 | 13 | 14 | jQuery.fn.fancySlideDown = -> 15 | elem = @ 16 | elem.css({"opacity": 0, "transform":"scale(0.9)"}).slideDown().animate({"opacity": 1, "scale": 1}, {"duration": 600, "queue": false, "easing": "easeOutBack"}) 17 | 18 | 19 | jQuery.fn.fancySlideUp = -> 20 | elem = @ 21 | elem.delay(600).slideUp(600).animate({"opacity": 0, "scale": 0.9}, {"duration": 600, "queue": false, "easing": "easeOutQuad"}) 22 | -------------------------------------------------------------------------------- /js/lib/jquery.csslater.coffee: -------------------------------------------------------------------------------- 1 | jQuery.fn.readdClass = (class_name) -> 2 | elem = @ 3 | elem.removeClass class_name 4 | setTimeout ( -> 5 | elem.addClass class_name 6 | ), 1 7 | return @ 8 | 9 | jQuery.fn.removeLater = (time = 500) -> 10 | elem = @ 11 | setTimeout ( -> 12 | elem.remove() 13 | ), time 14 | return @ 15 | 16 | jQuery.fn.hideLater = (time = 500) -> 17 | @.cssLater("display", "none", time) 18 | return @ 19 | 20 | jQuery.fn.addClassLater = (class_name, time=5, mode="clear") -> 21 | elem = @ 22 | elem[0].timers ?= {} 23 | timers = elem[0].timers 24 | 25 | if timers[class_name] and mode == "clear" then clearInterval(timers[class_name]) 26 | timers[class_name] = setTimeout ( -> 27 | elem.addClass(class_name) 28 | ), time 29 | return @ 30 | 31 | jQuery.fn.removeClassLater = (class_name, time=500, mode="clear") -> 32 | elem = @ 33 | elem[0].timers ?= {} 34 | timers = elem[0].timers 35 | 36 | if timers[class_name] and mode == "clear" then clearInterval(timers[class_name]) 37 | timers[class_name] = setTimeout ( -> 38 | elem.removeClass(class_name) 39 | ), time 40 | return @ 41 | 42 | jQuery.fn.cssLater = (name, val, time=500, mode="clear") -> 43 | elem = @ 44 | elem[0].timers ?= {} 45 | timers = elem[0].timers 46 | 47 | if timers[name] and mode == "clear" then clearInterval(timers[name]) 48 | if time == "now" 49 | elem.css name, val 50 | else 51 | timers[name] = setTimeout ( -> 52 | elem.css name, val 53 | ), time 54 | return @ 55 | 56 | 57 | jQuery.fn.toggleClassLater = (name, val, time=10, mode="clear") -> 58 | elem = @ 59 | elem[0].timers ?= {} 60 | timers = elem[0].timers 61 | 62 | if timers[name] and mode == "clear" then clearInterval(timers[name]) 63 | timers[name] = setTimeout ( -> 64 | elem.toggleClass name, val 65 | ), time 66 | return @ -------------------------------------------------------------------------------- /js/lib/marked.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked 0.6.3 - a markdown parser 3 | * Copyright (c) 2011-2018, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/markedjs/marked 5 | */ 6 | !function(e){"use strict";var x={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:f,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,nptable:f,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|\\n*|\\n*|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,table:f,lheading:/^([^\n]+)\n {0,3}(=|-){2,} *(?:\n+|$)/,paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/,text:/^[^\n]+/};function a(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||b.defaults,this.rules=x.normal,this.options.pedantic?this.rules=x.pedantic:this.options.gfm&&(this.options.tables?this.rules=x.tables:this.rules=x.gfm)}x._label=/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,x._title=/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/,x.def=i(x.def).replace("label",x._label).replace("title",x._title).getRegex(),x.bullet=/(?:[*+-]|\d{1,9}\.)/,x.item=/^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/,x.item=i(x.item,"gm").replace(/bull/g,x.bullet).getRegex(),x.list=i(x.list).replace(/bull/g,x.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+x.def.source+")").getRegex(),x._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",x._comment=//,x.html=i(x.html,"i").replace("comment",x._comment).replace("tag",x._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),x.paragraph=i(x.paragraph).replace("hr",x.hr).replace("heading",x.heading).replace("lheading",x.lheading).replace("tag",x._tag).getRegex(),x.blockquote=i(x.blockquote).replace("paragraph",x.paragraph).getRegex(),x.normal=d({},x),x.gfm=d({},x.normal,{fences:/^ {0,3}(`{3,}|~{3,})([^`\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),x.gfm.paragraph=i(x.paragraph).replace("(?!","(?!"+x.gfm.fences.source.replace("\\1","\\2")+"|"+x.list.source.replace("\\1","\\3")+"|").getRegex(),x.tables=d({},x.gfm,{nptable:/^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/,table:/^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/}),x.pedantic=d({},x.normal,{html:i("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",x._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/}),a.rules=x,a.lex=function(e,t){return new a(t).lex(e)},a.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},a.prototype.token=function(e,t){var n,r,s,i,l,o,a,h,p,u,c,g,f,d,m,b;for(e=e.replace(/^ +$/gm,"");e;)if((s=this.rules.newline.exec(e))&&(e=e.substring(s[0].length),1 ?/gm,""),this.token(s,t),this.tokens.push({type:"blockquote_end"});else if(s=this.rules.list.exec(e)){for(e=e.substring(s[0].length),a={type:"list_start",ordered:d=1<(i=s[2]).length,start:d?+i:"",loose:!1},this.tokens.push(a),n=!(h=[]),f=(s=s[0].match(this.rules.item)).length,c=0;c?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:f,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,strong:/^__([^\s_])__(?!_)|^\*\*([^\s*])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/,em:/^_([^\s_])_(?!_)|^\*([^\s*<\[])\*(?!\*)|^_([^\s<][\s\S]*?[^\s_])_(?!_|[^\spunctuation])|^_([^\s_<][\s\S]*?[^\s])_(?!_|[^\spunctuation])|^\*([^\s<"][\s\S]*?[^\s\*])\*(?!\*|[^\spunctuation])|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:f,text:/^(`+|[^`])(?:[\s\S]*?(?:(?=[\\?@\\[^_{|}~",n.em=i(n.em).replace(/punctuation/g,n._punctuation).getRegex(),n._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,n._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,n._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,n.autolink=i(n.autolink).replace("scheme",n._scheme).replace("email",n._email).getRegex(),n._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,n.tag=i(n.tag).replace("comment",x._comment).replace("attribute",n._attribute).getRegex(),n._label=/(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|`(?!`)|[^\[\]\\`])*?/,n._href=/\s*(<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*)/,n._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,n.link=i(n.link).replace("label",n._label).replace("href",n._href).replace("title",n._title).getRegex(),n.reflink=i(n.reflink).replace("label",n._label).getRegex(),n.normal=d({},n),n.pedantic=d({},n.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,link:i(/^!?\[(label)\]\((.*?)\)/).replace("label",n._label).getRegex(),reflink:i(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",n._label).getRegex()}),n.gfm=d({},n.normal,{escape:i(n.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~+(?=\S)([\s\S]*?\S)~+/,text:/^(`+|[^`])(?:[\s\S]*?(?:(?=[\\/i.test(i[0])&&(this.inLink=!1),!this.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(i[0])?this.inRawBlock=!0:this.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(i[0])&&(this.inRawBlock=!1),e=e.substring(i[0].length),o+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):u(i[0]):i[0];else if(i=this.rules.link.exec(e)){var a=m(i[2],"()");if(-1$/,"$1"),o+=this.outputLink(i,{href:p.escapes(r),title:p.escapes(s)}),this.inLink=!1}else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){o+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,o+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),o+=this.renderer.strong(this.output(i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),o+=this.renderer.em(this.output(i[6]||i[5]||i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),o+=this.renderer.codespan(u(i[2].trim(),!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),o+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),o+=this.renderer.del(this.output(i[1]));else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),r="@"===i[2]?"mailto:"+(n=u(this.mangle(i[1]))):n=u(i[1]),o+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.text.exec(e))e=e.substring(i[0].length),this.inRawBlock?o+=this.renderer.text(i[0]):o+=this.renderer.text(u(this.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else{if("@"===i[2])r="mailto:"+(n=u(i[0]));else{for(;l=i[0],i[0]=this.rules._backpedal.exec(i[0])[0],l!==i[0];);n=u(i[0]),r="www."===i[1]?"http://"+n:n}e=e.substring(i[0].length),o+=this.renderer.link(r,null,n)}return o},p.escapes=function(e){return e?e.replace(p.rules._escapes,"$1"):e},p.prototype.outputLink=function(e,t){var n=t.href,r=t.title?u(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,u(e[1]))},p.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},p.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,s=0;s'+(n?e:u(e,!0))+"\n":"
"+(n?e:u(e,!0))+"
"},r.prototype.blockquote=function(e){return"
\n"+e+"
\n"},r.prototype.html=function(e){return e},r.prototype.heading=function(e,t,n,r){return this.options.headerIds?"'+e+"\n":""+e+"\n"},r.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"},r.prototype.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"},r.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},r.prototype.checkbox=function(e){return" "},r.prototype.paragraph=function(e){return"

    "+e+"

    \n"},r.prototype.table=function(e,t){return t&&(t=""+t+""),"\n\n"+e+"\n"+t+"
    \n"},r.prototype.tablerow=function(e){return"\n"+e+"\n"},r.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"\n"},r.prototype.strong=function(e){return""+e+""},r.prototype.em=function(e){return""+e+""},r.prototype.codespan=function(e){return""+e+""},r.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},r.prototype.del=function(e){return""+e+""},r.prototype.link=function(e,t,n){if(null===(e=l(this.options.sanitize,this.options.baseUrl,e)))return n;var r='"},r.prototype.image=function(e,t,n){if(null===(e=l(this.options.sanitize,this.options.baseUrl,e)))return n;var r=''+n+'":">"},r.prototype.text=function(e){return e},s.prototype.strong=s.prototype.em=s.prototype.codespan=s.prototype.del=s.prototype.text=function(e){return e},s.prototype.link=s.prototype.image=function(e,t,n){return""+n},s.prototype.br=function(){return""},h.parse=function(e,t){return new h(t).parse(e)},h.prototype.parse=function(e){this.inline=new p(e.links,this.options),this.inlineText=new p(e.links,d({},this.options,{renderer:new s})),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},h.prototype.next=function(){return this.token=this.tokens.pop(),this.token},h.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},h.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},h.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,c(this.inlineText.output(this.token.text)),this.slugger);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,s="",i="";for(n="",e=0;e?@[\]^`{|}~]/g,"").replace(/\s/g,"-");if(this.seen.hasOwnProperty(t))for(var n=t;this.seen[n]++,t=n+"-"+this.seen[n],this.seen.hasOwnProperty(t););return this.seen[t]=0,t},u.escapeTest=/[&<>"']/,u.escapeReplace=/[&<>"']/g,u.replacements={"&":"&","<":"<",">":">",'"':""","'":"'"},u.escapeTestNoEncode=/[<>"']|&(?!#?\w+;)/,u.escapeReplaceNoEncode=/[<>"']|&(?!#?\w+;)/g;var o={},g=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function f(){}function d(e){for(var t,n,r=1;rt)n.splice(t);else for(;n.lengthAn error occurred:

    "+u(e.message+"",!0)+"
    ";throw e}}f.exec=f,b.options=b.setOptions=function(e){return d(b.defaults,e),b},b.getDefaults=function(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:new r,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tables:!0,xhtml:!1}},b.defaults=b.getDefaults(),b.Parser=h,b.parser=h.parse,b.Renderer=r,b.TextRenderer=s,b.Lexer=a,b.lexer=a.lex,b.InlineLexer=p,b.inlineLexer=p.output,b.Slugger=t,b.parse=b,"undefined"!=typeof module&&"object"==typeof exports?module.exports=b:"function"==typeof define&&define.amd?define(function(){return b}):e.marked=b}(this||("undefined"!=typeof window?window:global)); -------------------------------------------------------------------------------- /js/lib/pnglib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A handy class to calculate color values. 3 | * 4 | * @version 1.0 5 | * @author Robert Eisele 6 | * @copyright Copyright (c) 2010, Robert Eisele 7 | * @link http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/ 8 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 9 | * 10 | */ 11 | 12 | (function() { 13 | 14 | // helper functions for that ctx 15 | function write(buffer, offs) { 16 | for (var i = 2; i < arguments.length; i++) { 17 | for (var j = 0; j < arguments[i].length; j++) { 18 | buffer[offs++] = arguments[i].charAt(j); 19 | } 20 | } 21 | } 22 | 23 | function byte2(w) { 24 | return String.fromCharCode((w >> 8) & 255, w & 255); 25 | } 26 | 27 | function byte4(w) { 28 | return String.fromCharCode((w >> 24) & 255, (w >> 16) & 255, (w >> 8) & 255, w & 255); 29 | } 30 | 31 | function byte2lsb(w) { 32 | return String.fromCharCode(w & 255, (w >> 8) & 255); 33 | } 34 | 35 | window.PNGlib = function(width,height,depth) { 36 | 37 | this.width = width; 38 | this.height = height; 39 | this.depth = depth; 40 | 41 | // pixel data and row filter identifier size 42 | this.pix_size = height * (width + 1); 43 | 44 | // deflate header, pix_size, block headers, adler32 checksum 45 | this.data_size = 2 + this.pix_size + 5 * Math.floor((0xfffe + this.pix_size) / 0xffff) + 4; 46 | 47 | // offsets and sizes of Png chunks 48 | this.ihdr_offs = 0; // IHDR offset and size 49 | this.ihdr_size = 4 + 4 + 13 + 4; 50 | this.plte_offs = this.ihdr_offs + this.ihdr_size; // PLTE offset and size 51 | this.plte_size = 4 + 4 + 3 * depth + 4; 52 | this.trns_offs = this.plte_offs + this.plte_size; // tRNS offset and size 53 | this.trns_size = 4 + 4 + depth + 4; 54 | this.idat_offs = this.trns_offs + this.trns_size; // IDAT offset and size 55 | this.idat_size = 4 + 4 + this.data_size + 4; 56 | this.iend_offs = this.idat_offs + this.idat_size; // IEND offset and size 57 | this.iend_size = 4 + 4 + 4; 58 | this.buffer_size = this.iend_offs + this.iend_size; // total PNG size 59 | 60 | this.buffer = new Array(); 61 | this.palette = new Object(); 62 | this.pindex = 0; 63 | 64 | var _crc32 = new Array(); 65 | 66 | // initialize buffer with zero bytes 67 | for (var i = 0; i < this.buffer_size; i++) { 68 | this.buffer[i] = "\x00"; 69 | } 70 | 71 | // initialize non-zero elements 72 | write(this.buffer, this.ihdr_offs, byte4(this.ihdr_size - 12), 'IHDR', byte4(width), byte4(height), "\x08\x03"); 73 | write(this.buffer, this.plte_offs, byte4(this.plte_size - 12), 'PLTE'); 74 | write(this.buffer, this.trns_offs, byte4(this.trns_size - 12), 'tRNS'); 75 | write(this.buffer, this.idat_offs, byte4(this.idat_size - 12), 'IDAT'); 76 | write(this.buffer, this.iend_offs, byte4(this.iend_size - 12), 'IEND'); 77 | 78 | // initialize deflate header 79 | var header = ((8 + (7 << 4)) << 8) | (3 << 6); 80 | header+= 31 - (header % 31); 81 | 82 | write(this.buffer, this.idat_offs + 8, byte2(header)); 83 | 84 | // initialize deflate block headers 85 | for (var i = 0; (i << 16) - 1 < this.pix_size; i++) { 86 | var size, bits; 87 | if (i + 0xffff < this.pix_size) { 88 | size = 0xffff; 89 | bits = "\x00"; 90 | } else { 91 | size = this.pix_size - (i << 16) - i; 92 | bits = "\x01"; 93 | } 94 | write(this.buffer, this.idat_offs + 8 + 2 + (i << 16) + (i << 2), bits, byte2lsb(size), byte2lsb(~size)); 95 | } 96 | 97 | /* Create crc32 lookup table */ 98 | for (var i = 0; i < 256; i++) { 99 | var c = i; 100 | for (var j = 0; j < 8; j++) { 101 | if (c & 1) { 102 | c = -306674912 ^ ((c >> 1) & 0x7fffffff); 103 | } else { 104 | c = (c >> 1) & 0x7fffffff; 105 | } 106 | } 107 | _crc32[i] = c; 108 | } 109 | 110 | // compute the index into a png for a given pixel 111 | this.index = function(x,y) { 112 | var i = y * (this.width + 1) + x + 1; 113 | var j = this.idat_offs + 8 + 2 + 5 * Math.floor((i / 0xffff) + 1) + i; 114 | return j; 115 | } 116 | 117 | // convert a color and build up the palette 118 | this.color = function(red, green, blue, alpha) { 119 | 120 | alpha = alpha >= 0 ? alpha : 255; 121 | var color = (((((alpha << 8) | red) << 8) | green) << 8) | blue; 122 | 123 | if (typeof this.palette[color] == "undefined") { 124 | if (this.pindex == this.depth) return "\x00"; 125 | 126 | var ndx = this.plte_offs + 8 + 3 * this.pindex; 127 | 128 | this.buffer[ndx + 0] = String.fromCharCode(red); 129 | this.buffer[ndx + 1] = String.fromCharCode(green); 130 | this.buffer[ndx + 2] = String.fromCharCode(blue); 131 | this.buffer[this.trns_offs+8+this.pindex] = String.fromCharCode(alpha); 132 | 133 | this.palette[color] = String.fromCharCode(this.pindex++); 134 | } 135 | return this.palette[color]; 136 | } 137 | 138 | // output a PNG string, Base64 encoded 139 | this.getBase64 = function() { 140 | 141 | var s = this.getDump(); 142 | 143 | var ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 144 | var c1, c2, c3, e1, e2, e3, e4; 145 | var l = s.length; 146 | var i = 0; 147 | var r = ""; 148 | 149 | do { 150 | c1 = s.charCodeAt(i); 151 | e1 = c1 >> 2; 152 | c2 = s.charCodeAt(i+1); 153 | e2 = ((c1 & 3) << 4) | (c2 >> 4); 154 | c3 = s.charCodeAt(i+2); 155 | if (l < i+2) { e3 = 64; } else { e3 = ((c2 & 0xf) << 2) | (c3 >> 6); } 156 | if (l < i+3) { e4 = 64; } else { e4 = c3 & 0x3f; } 157 | r+= ch.charAt(e1) + ch.charAt(e2) + ch.charAt(e3) + ch.charAt(e4); 158 | } while ((i+= 3) < l); 159 | return r; 160 | } 161 | 162 | // output a PNG string 163 | this.getDump = function() { 164 | 165 | // compute adler32 of output pixels + row filter bytes 166 | var BASE = 65521; /* largest prime smaller than 65536 */ 167 | var NMAX = 5552; /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ 168 | var s1 = 1; 169 | var s2 = 0; 170 | var n = NMAX; 171 | 172 | for (var y = 0; y < this.height; y++) { 173 | for (var x = -1; x < this.width; x++) { 174 | s1+= this.buffer[this.index(x, y)].charCodeAt(0); 175 | s2+= s1; 176 | if ((n-= 1) == 0) { 177 | s1%= BASE; 178 | s2%= BASE; 179 | n = NMAX; 180 | } 181 | } 182 | } 183 | s1%= BASE; 184 | s2%= BASE; 185 | write(this.buffer, this.idat_offs + this.idat_size - 8, byte4((s2 << 16) | s1)); 186 | 187 | // compute crc32 of the PNG chunks 188 | function crc32(png, offs, size) { 189 | var crc = -1; 190 | for (var i = 4; i < size-4; i += 1) { 191 | crc = _crc32[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff); 192 | } 193 | write(png, offs+size-4, byte4(crc ^ -1)); 194 | } 195 | 196 | crc32(this.buffer, this.ihdr_offs, this.ihdr_size); 197 | crc32(this.buffer, this.plte_offs, this.plte_size); 198 | crc32(this.buffer, this.trns_offs, this.trns_size); 199 | crc32(this.buffer, this.idat_offs, this.idat_size); 200 | crc32(this.buffer, this.iend_offs, this.iend_size); 201 | 202 | // convert PNG to string 203 | return "\211PNG\r\n\032\n"+this.buffer.join(''); 204 | } 205 | } 206 | 207 | })(); 208 | -------------------------------------------------------------------------------- /js/lib/zoom.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * zoom.js - It's the best way to zoom an image 3 | * @version v0.0.2 4 | * @link https://github.com/fat/zoom.js 5 | * @license MIT 6 | */ 7 | 8 | +function ($) { "use strict"; 9 | 10 | /** 11 | * The zoom service 12 | */ 13 | function ZoomService () { 14 | this._activeZoom = 15 | this._initialScrollPosition = 16 | this._initialTouchPosition = 17 | this._touchMoveListener = null 18 | 19 | this._$document = $(document) 20 | this._$window = $(window) 21 | this._$body = $(document.body) 22 | 23 | this._boundClick = $.proxy(this._clickHandler, this) 24 | } 25 | 26 | ZoomService.prototype.listen = function () { 27 | this._$body.on('click', '[data-action="zoom"]', $.proxy(this._zoom, this)) 28 | } 29 | 30 | ZoomService.prototype._zoom = function (e) { 31 | var target = e.target 32 | 33 | if (!target || target.tagName != 'IMG') return 34 | 35 | if (this._$body.hasClass('zoom-overlay-open')) return 36 | 37 | if (e.metaKey || e.ctrlKey) { 38 | return window.open((e.target.getAttribute('data-original') || e.target.src), '_blank') 39 | } 40 | 41 | //if (target.width >= ($(window).width() - Zoom.OFFSET)) return 42 | 43 | this._activeZoomClose(true) 44 | 45 | this._activeZoom = new Zoom(target) 46 | this._activeZoom.zoomImage() 47 | 48 | // todo(fat): probably worth throttling this 49 | this._$window.on('scroll.zoom', $.proxy(this._scrollHandler, this)) 50 | 51 | this._$document.on('keyup.zoom', $.proxy(this._keyHandler, this)) 52 | this._$document.on('touchstart.zoom', $.proxy(this._touchStart, this)) 53 | 54 | // we use a capturing phase here to prevent unintended js events 55 | // sadly no useCapture in jquery api (http://bugs.jquery.com/ticket/14953) 56 | if (document.addEventListener) { 57 | document.addEventListener('click', this._boundClick, true) 58 | } else { 59 | document.attachEvent('onclick', this._boundClick, true) 60 | } 61 | 62 | if ('bubbles' in e) { 63 | if (e.bubbles) e.stopPropagation() 64 | } else { 65 | // Internet Explorer before version 9 66 | e.cancelBubble = true 67 | } 68 | } 69 | 70 | ZoomService.prototype._activeZoomClose = function (forceDispose) { 71 | if (!this._activeZoom) return 72 | 73 | if (forceDispose) { 74 | this._activeZoom.dispose() 75 | } else { 76 | this._activeZoom.close() 77 | } 78 | 79 | this._$window.off('.zoom') 80 | this._$document.off('.zoom') 81 | 82 | document.removeEventListener('click', this._boundClick, true) 83 | 84 | this._activeZoom = null 85 | } 86 | 87 | ZoomService.prototype._scrollHandler = function (e) { 88 | if (this._initialScrollPosition === null) this._initialScrollPosition = $(window).scrollTop() 89 | var deltaY = this._initialScrollPosition - $(window).scrollTop() 90 | if (Math.abs(deltaY) >= 40) this._activeZoomClose() 91 | } 92 | 93 | ZoomService.prototype._keyHandler = function (e) { 94 | if (e.keyCode == 27) this._activeZoomClose() 95 | } 96 | 97 | ZoomService.prototype._clickHandler = function (e) { 98 | if (e.preventDefault) e.preventDefault() 99 | else event.returnValue = false 100 | 101 | if ('bubbles' in e) { 102 | if (e.bubbles) e.stopPropagation() 103 | } else { 104 | // Internet Explorer before version 9 105 | e.cancelBubble = true 106 | } 107 | 108 | this._activeZoomClose() 109 | } 110 | 111 | ZoomService.prototype._touchStart = function (e) { 112 | this._initialTouchPosition = e.touches[0].pageY 113 | $(e.target).on('touchmove.zoom', $.proxy(this._touchMove, this)) 114 | } 115 | 116 | ZoomService.prototype._touchMove = function (e) { 117 | if (Math.abs(e.touches[0].pageY - this._initialTouchPosition) > 10) { 118 | this._activeZoomClose() 119 | $(e.target).off('touchmove.zoom') 120 | } 121 | } 122 | 123 | 124 | /** 125 | * The zoom object 126 | */ 127 | function Zoom (img) { 128 | this._fullHeight = 129 | this._fullWidth = 130 | this._overlay = 131 | this._targetImageWrap = null 132 | 133 | this._targetImage = img 134 | 135 | this._$body = $(document.body) 136 | } 137 | 138 | Zoom.OFFSET = 80 139 | Zoom._MAX_WIDTH = 2560 140 | Zoom._MAX_HEIGHT = 4096 141 | 142 | Zoom.prototype.zoomImage = function () { 143 | var img = document.createElement('img') 144 | img.onload = $.proxy(function () { 145 | this._fullHeight = Number(img.height) 146 | this._fullWidth = Number(img.width) 147 | this._zoomOriginal() 148 | }, this) 149 | img.src = this._targetImage.src 150 | } 151 | 152 | Zoom.prototype._zoomOriginal = function () { 153 | this._targetImageWrap = document.createElement('div') 154 | this._targetImageWrap.className = 'zoom-img-wrap' 155 | 156 | this._targetImage.parentNode.insertBefore(this._targetImageWrap, this._targetImage) 157 | this._targetImageWrap.appendChild(this._targetImage) 158 | 159 | $(this._targetImage) 160 | .addClass('zoom-img') 161 | .attr('data-action', 'zoom-out') 162 | 163 | this._overlay = document.createElement('div') 164 | this._overlay.className = 'zoom-overlay' 165 | 166 | document.body.appendChild(this._overlay) 167 | 168 | this._calculateZoom() 169 | this._triggerAnimation() 170 | } 171 | 172 | Zoom.prototype._calculateZoom = function () { 173 | this._targetImage.offsetWidth // repaint before animating 174 | 175 | var originalFullImageWidth = this._fullWidth 176 | var originalFullImageHeight = this._fullHeight 177 | 178 | var scrollTop = $(window).scrollTop() 179 | 180 | var maxScaleFactor = originalFullImageWidth / this._targetImage.width 181 | 182 | var viewportHeight = ($(window).height() - Zoom.OFFSET) 183 | var viewportWidth = ($(window).width() - Zoom.OFFSET) 184 | 185 | var imageAspectRatio = originalFullImageWidth / originalFullImageHeight 186 | var viewportAspectRatio = viewportWidth / viewportHeight 187 | 188 | if (originalFullImageWidth < viewportWidth && originalFullImageHeight < viewportHeight) { 189 | this._imgScaleFactor = maxScaleFactor 190 | 191 | } else if (imageAspectRatio < viewportAspectRatio) { 192 | this._imgScaleFactor = (viewportHeight / originalFullImageHeight) * maxScaleFactor 193 | 194 | } else { 195 | this._imgScaleFactor = (viewportWidth / originalFullImageWidth) * maxScaleFactor 196 | } 197 | } 198 | 199 | Zoom.prototype._triggerAnimation = function () { 200 | this._targetImage.offsetWidth // repaint before animating 201 | 202 | var imageOffset = $(this._targetImage).offset() 203 | var scrollTop = $(window).scrollTop() 204 | 205 | var viewportY = scrollTop + ($(window).height() / 2) 206 | var viewportX = ($(window).width() / 2) 207 | 208 | var imageCenterY = imageOffset.top + (this._targetImage.height / 2) 209 | var imageCenterX = imageOffset.left + (this._targetImage.width / 2) 210 | 211 | this._translateY = viewportY - imageCenterY 212 | this._translateX = viewportX - imageCenterX 213 | 214 | var targetTransform = 'scale(' + this._imgScaleFactor + ')' 215 | var imageWrapTransform = 'translate(' + this._translateX + 'px, ' + this._translateY + 'px)' 216 | 217 | if ($.support.transition) { 218 | imageWrapTransform += ' translateZ(0)' 219 | } 220 | 221 | $(this._targetImage) 222 | .css({ 223 | '-webkit-transform': targetTransform, 224 | '-ms-transform': targetTransform, 225 | 'transform': targetTransform 226 | }) 227 | 228 | $(this._targetImageWrap) 229 | .css({ 230 | '-webkit-transform': imageWrapTransform, 231 | '-ms-transform': imageWrapTransform, 232 | 'transform': imageWrapTransform 233 | }) 234 | 235 | this._$body.addClass('zoom-overlay-open') 236 | } 237 | 238 | Zoom.prototype.close = function () { 239 | this._$body 240 | .removeClass('zoom-overlay-open') 241 | .addClass('zoom-overlay-transitioning') 242 | 243 | // we use setStyle here so that the correct vender prefix for transform is used 244 | $(this._targetImage) 245 | .css({ 246 | '-webkit-transform': '', 247 | '-ms-transform': '', 248 | 'transform': '' 249 | }) 250 | 251 | $(this._targetImageWrap) 252 | .css({ 253 | '-webkit-transform': '', 254 | '-ms-transform': '', 255 | 'transform': '' 256 | }) 257 | 258 | $(this._targetImage) 259 | .one("transitionend", $.proxy(this.dispose, this)) 260 | } 261 | 262 | Zoom.prototype.dispose = function (e) { 263 | if (this._targetImageWrap && this._targetImageWrap.parentNode) { 264 | $(this._targetImage) 265 | .removeClass('zoom-img') 266 | .attr('data-action', 'zoom') 267 | 268 | this._targetImageWrap.parentNode.replaceChild(this._targetImage, this._targetImageWrap) 269 | this._overlay.parentNode.removeChild(this._overlay) 270 | 271 | this._$body.removeClass('zoom-overlay-transitioning') 272 | } 273 | } 274 | 275 | // wait for dom ready (incase script included before body) 276 | $(function () { 277 | new ZoomService().listen() 278 | }) 279 | 280 | }(jQuery); 281 | -------------------------------------------------------------------------------- /js/utils/Class.coffee: -------------------------------------------------------------------------------- 1 | class Class 2 | trace: true 3 | 4 | log: (args...) -> 5 | return unless @trace 6 | return if typeof console is 'undefined' 7 | args.unshift("[#{@.constructor.name}]") 8 | console.log(args...) 9 | @ 10 | 11 | logStart: (name, args...) -> 12 | return unless @trace 13 | @logtimers or= {} 14 | @logtimers[name] = +(new Date) 15 | @log "#{name}", args..., "(started)" if args.length > 0 16 | @ 17 | 18 | logEnd: (name, args...) -> 19 | ms = +(new Date)-@logtimers[name] 20 | @log "#{name}", args..., "(Done in #{ms}ms)" 21 | @ 22 | 23 | window.Class = Class -------------------------------------------------------------------------------- /js/utils/CustomAlloyEditor.coffee: -------------------------------------------------------------------------------- 1 | class CustomAlloyEditor extends Class 2 | constructor: (@tag) -> 3 | editor = AlloyEditor.editable(@tag) 4 | 5 | # Add top padding to avoid toolbar movement 6 | el = editor._editor.element.$ 7 | height_before = el.getClientRects()[0].height 8 | style = getComputedStyle(el) 9 | el.style.position = "relative" 10 | el.style.paddingTop = (parseInt(style["padding-top"]) + 20) + "px" 11 | height_added = el.getClientRects()[0].height - height_before 12 | el.style.top = (parseInt(style["marginTop"]) - 20 - height_added) + "px" 13 | el.style.marginBottom = (parseInt(style["marginBottom"]) + parseInt(el.style.top)) + "px" 14 | 15 | # Add listeners 16 | editor.get('nativeEditor').on "selectionChange", @handleSelectionChange 17 | editor.get('nativeEditor').on "focus", (e) => 18 | setTimeout ( => 19 | @handleSelectionChange(e) 20 | ), 100 21 | editor.get('nativeEditor').on "click", @handleSelectionChange 22 | editor.get('nativeEditor').on "change", @handleChange 23 | editor.get('nativeEditor').on 'imageAdd', (e) => 24 | if e.data.el.$.width > 0 25 | @handleImageAdd(e) 26 | else 27 | setTimeout ( => 28 | @handleImageAdd(e) 29 | ), 100 30 | editor.get('nativeEditor').on "actionPerformed", @handleAction 31 | editor.get('nativeEditor').on 'afterCommandExec', @handleCommand 32 | 33 | window.editor = editor 34 | 35 | @el_last_created = null 36 | 37 | @image_size_limit = 200*1024 38 | @image_resize_width = 1200 39 | @image_resize_height = 900 40 | @image_preverse_ratio = true 41 | @image_try_png = false 42 | 43 | return @ 44 | 45 | 46 | calcSize: (source_width, source_height, target_width, target_height) -> 47 | if source_width <= target_width and source_height <= target_height 48 | return [source_width, source_height] 49 | 50 | width = target_width 51 | height = width * (source_height / source_width); 52 | if height > target_height 53 | height = target_height 54 | width = height * (source_width / source_height) 55 | return [Math.round(width), Math.round(height)] 56 | 57 | 58 | scaleHalf: (image) -> 59 | canvas = document.createElement("canvas") 60 | canvas.width = image.width / 1.5 61 | canvas.height = image.height / 1.5 62 | ctx = canvas.getContext("2d") 63 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height) 64 | return canvas 65 | 66 | 67 | resizeImage: (image, width, height) => 68 | canvas = document.createElement("canvas") 69 | if @image_preverse_ratio 70 | [canvas.width, canvas.height] = @calcSize(image.width, image.height, width, height) 71 | else 72 | canvas.width = width 73 | canvas.height = height 74 | 75 | ctx = canvas.getContext("2d") 76 | ctx.fillStyle = "#FFF" 77 | ctx.fillRect(0, 0, canvas.width, canvas.height) 78 | image_resized = image 79 | while image_resized.width > width * 1.5 80 | image_resized = @scaleHalf(image_resized) 81 | ctx.drawImage(image_resized, 0, 0, canvas.width, canvas.height) 82 | 83 | if @image_try_png and @getExtension(image.src) == "png" # and canvas.width < 1400 and canvas.height < 1000 84 | ### 85 | quant = new RgbQuant({colors: 256, method: 1}) 86 | quant.sample(canvas) 87 | quant.palette(true) 88 | canvas_quant = drawPixels(quant.reduce(canvas), width) 89 | optimizer = new CanvasTool.PngEncoder(canvas_quant, { bitDepth: 8, colourType: CanvasTool.PngEncoder.ColourType.TRUECOLOR }) 90 | image_base64uri = "data:image/png;base64," + btoa(optimizer.convert()) 91 | ### 92 | image_base64uri = canvas.toDataURL("image/png", 0.1) 93 | if image_base64uri.length > @image_size_limit 94 | # Too large, convert to jpg 95 | @log "PNG too large (#{image_base64uri.length} bytes), convert to jpg instead" 96 | image_base64uri = canvas.toDataURL("image/jpeg", 0.7) 97 | else 98 | @log "Converted to PNG" 99 | else 100 | image_base64uri = canvas.toDataURL("image/jpeg", 0.7) 101 | 102 | @log "Resized #{image.width}x#{image.height} to #{canvas.width}x#{canvas.height} (#{image_base64uri.length} bytes)" 103 | return [image_base64uri, canvas.width, canvas.height] 104 | 105 | getExtension: (data) => 106 | return data.match("/[a-z]+")[0].replace("/", "").replace("jpeg", "jpg") 107 | 108 | handleImageAdd: (e) => 109 | if e.data.file.name 110 | name = e.data.file.name.replace(/[^\w\.-]/gi, "_") 111 | else 112 | name = Time.timestamp() + "." + @getExtension(e.data.file.type) 113 | e.data.el.$.style.maxWidth = "2400px" # Better resize quality 114 | 115 | if e.data.file.size > @image_size_limit 116 | @log "File size #{e.data.file.size} larger than allowed #{@image_size_limit}, resizing..." 117 | [image_base64uri, width, height] = @resizeImage(e.data.el.$, @image_resize_width, @image_resize_height) 118 | e.data.el.$.src = image_base64uri 119 | name = name.replace(/(png|gif|jpg)/, @getExtension(image_base64uri)) # Change extension if necessary 120 | else 121 | image_base64uri = e.data.el.$.src 122 | width = e.data.el.$.width 123 | height = e.data.el.$.height 124 | # e.data.el.remove() # Don't allow image upload yet 125 | e.data.el.$.style.maxWidth = "" # Show in standard size 126 | e.data.el.$.alt = "#{name} (#{width}x#{height})" 127 | @handleImageSave(name, image_base64uri, e.data.el.$) 128 | 129 | 130 | # Html fixes 131 | handleAction: (e) => 132 | el = e.editor.getSelection().getStartElement() 133 | # Convert Pre to Pre > Code 134 | if el.getName() == "pre" 135 | @log("Fix pre") 136 | new_el = new CKEDITOR.dom.element("code") 137 | new_el.setHtml(el.getHtml().replace(/\u200B/g, '')) 138 | el.setHtml("") 139 | 140 | e.editor.insertElement(new_el) 141 | ranges = e.editor.getSelection().getRanges() 142 | ranges[0].startContainer = new_el 143 | e.editor.getSelection().selectRanges(ranges) 144 | 145 | # Remove Pre > Code 146 | if el.getName() == "pre" and e.data._style.hasOwnProperty("removeFromRange") 147 | @log("Remove pre") 148 | new_el = new CKEDITOR.dom.element("p"); 149 | new_el.insertAfter(el) 150 | new_el.setHtml(el.getFirst().getHtml().replace(/\n/g, "
    ").replace(/\u200B/g, '')) 151 | el.remove() 152 | selectElement(e.editor, new_el) 153 | 154 | # Remove Pre > Code focused on code 155 | else if el.getName() == "code" and e.data._style.hasOwnProperty("removeFromRange") 156 | @log("Remove code") 157 | new_el = new CKEDITOR.dom.element("p") 158 | new_el.insertAfter(el.getParent()) 159 | new_el.setHtml(el.getHtml().replace(/\n/g, "
    ").replace(/\u200B/g, '')) 160 | el.getParent().remove() 161 | selectElement(e.editor, new_el) 162 | 163 | # Convert multi-line code to Pre > Code 164 | else if el.getName() == "code" && el.getHtml().indexOf("
    ") > 0 165 | @log("Fix multiline code") 166 | new_el = new CKEDITOR.dom.element("pre"); 167 | new_el.insertAfter(el) 168 | el.appendTo(new_el) 169 | selectElement(e.editor, new_el) 170 | 171 | if el.getName() == "h2" or el.getName() == "h3" 172 | selectElement(e.editor, el) 173 | 174 | @handleChange(e) 175 | 176 | 177 | handleCommand: (e) => 178 | # Reset style on enter 179 | if e.data.name == 'enter' 180 | el = e.editor.getSelection().getStartElement() 181 | 182 | if el.getText().replace(/\u200B/g, '') == "" and el.getName() != "p" and el.getParent().getName() == "p" 183 | el.remove() 184 | 185 | # Reset style on shift+enter within code 186 | else if e.data.name == 'shiftEnter' 187 | el = e.editor.getSelection().getStartElement(); 188 | if el.getName() == "code" && el.getNext() && el.getNext().getName && el.getNext().getName() == "br" 189 | el.getNext().remove() 190 | 191 | 192 | handleChange: (e) => 193 | @handleSelectionChange(e) 194 | 195 | 196 | handleSelectionChange: (e) => 197 | if @el_last_created and @el_last_created.getText().replace(/\u200B/g, '').trim() != "" 198 | @el_last_created.removeClass("empty") 199 | @el_last_created = null 200 | 201 | el = e.editor.getSelection().getStartElement() 202 | if el.getName() == "br" 203 | el = el.getParent() 204 | toolbar_add = document.querySelector(".ae-toolbar-add") 205 | if !toolbar_add or !el 206 | return false 207 | 208 | if el.getText().replace(/\u200B/g, '').trim() == "" 209 | if el.getName() == "h2" or el.getName() == "h3" 210 | el.addClass("empty") 211 | @el_last_created = el 212 | toolbar_add.classList.remove("lineselected") 213 | toolbar_add.classList.add("emptyline") 214 | else 215 | toolbar_add.classList.add("lineselected") 216 | toolbar_add.classList.remove("emptyline") 217 | 218 | # Remove toolbar moving 219 | ### 220 | if e.editor.element.getPrivate().events.mouseout?.listeners.length 221 | e.editor.element.removeListener("mouseout", e.editor.element.getPrivate().events.mouseout.listeners[0].fn) 222 | 223 | if e.editor.element.getPrivate().events.mouseleave?.listeners.length 224 | # Keep only mouseout 225 | func = e.editor.element.getPrivate().events.mouseleave.listeners[0] 226 | console.log "remove", e.editor.element.removeListener("mouseleave", func.fn) 227 | e.editor.element.on "mouseleave", (e_leave) -> 228 | if document.querySelector(".ae-toolbar-styles") == null 229 | window.editor._mainUI.forceUpdate() 230 | func(e_leave, e_leave.data) 231 | ### 232 | 233 | 234 | 235 | 236 | 237 | window.CustomAlloyEditor = CustomAlloyEditor -------------------------------------------------------------------------------- /js/utils/Follow.coffee: -------------------------------------------------------------------------------- 1 | class Follow extends Class 2 | constructor: (@elem) -> 3 | @menu = new Menu(@elem) 4 | @feeds = {} 5 | @follows = {} 6 | @elem.off "click" 7 | @elem.on "click", => 8 | if Page.server_info.rev > 850 9 | if @elem.hasClass "following" 10 | @showFeeds() 11 | else 12 | @followDefaultFeeds() 13 | for title, [query, menu_item, is_default_feed, param] of @feeds 14 | if not menu_item.hasClass "selected" 15 | @showFeeds() 16 | break 17 | else 18 | Page.cmd "wrapperNotification", ["info", "Please update your ZeroNet client to use this feature"] 19 | return false 20 | 21 | init: => 22 | if not @feeds 23 | return 24 | Page.cmd "feedListFollow", [], (@follows) => 25 | for title, [query, menu_item, is_default_feed, param] of @feeds 26 | if @follows[title] and param in @follows[title][1] 27 | menu_item.addClass("selected") 28 | else 29 | menu_item.removeClass("selected") 30 | @updateListitems() 31 | @elem.css "display", "inline-block" 32 | 33 | setTimeout ( => 34 | if typeof(Page.site_info.feed_follow_num) != "undefined" and Page.site_info.feed_follow_num == null # Has not manipulated followings yet 35 | @followDefaultFeeds() 36 | ), 100 37 | 38 | 39 | addFeed: (title, query, is_default_feed=false, param="") -> 40 | menu_item = @menu.addItem title, @handleMenuClick 41 | @feeds[title] = [query, menu_item, is_default_feed, param] 42 | 43 | 44 | handleMenuClick: (item) => 45 | item.toggleClass("selected") 46 | @updateListitems() 47 | @saveFeeds() 48 | return true 49 | 50 | 51 | showFeeds: -> 52 | @menu.show() 53 | 54 | 55 | followDefaultFeeds: -> 56 | for title, [query, menu_item, is_default_feed, param] of @feeds 57 | if is_default_feed 58 | menu_item.addClass "selected" 59 | @log "Following", title, menu_item 60 | @updateListitems() 61 | @saveFeeds() 62 | 63 | 64 | updateListitems: -> 65 | if @menu.elem.find(".selected").length > 0 66 | @elem.addClass "following" 67 | else 68 | @elem.removeClass "following" 69 | 70 | 71 | saveFeeds: -> 72 | Page.cmd "feedListFollow", [], (follows) => 73 | @follows = follows 74 | for title, [query, menu_item, is_default_feed, param] of @feeds 75 | if follows[title] 76 | params = (item for item in follows[title][1] when item != param) # Remove current param from follow list 77 | else 78 | params = [] 79 | 80 | if menu_item.hasClass "selected" # Add if selected 81 | params.push(param) 82 | 83 | if params.length == 0 # Empty params 84 | delete follows[title] 85 | else 86 | follows[title] = [query, params] 87 | 88 | Page.cmd "feedFollow", [follows] 89 | 90 | 91 | window.Follow = Follow -------------------------------------------------------------------------------- /js/utils/InlineEditor.coffee: -------------------------------------------------------------------------------- 1 | class InlineEditor 2 | constructor: (@elem, @getContent, @saveContent, @getObject) -> 3 | @edit_button = $("
    ") 4 | @edit_button.on "click", @startEdit 5 | @elem.addClass("editable").before(@edit_button) 6 | @editor = null 7 | @elem.on "mouseenter click", (e) => 8 | @edit_button.css("opacity", "0.4") 9 | # Keep in display 10 | scrolltop = $(window).scrollTop() 11 | top = @edit_button.offset().top-parseInt(@edit_button.css("margin-top")) 12 | if scrolltop > top 13 | @edit_button.css("margin-top", scrolltop-top+e.clientY-20) 14 | else 15 | @edit_button.css("margin-top", "") 16 | @elem.on "mouseleave", => 17 | @edit_button.css("opacity", "") 18 | 19 | if @elem.is(":hover") then @elem.trigger "mouseenter" 20 | 21 | 22 | startEdit: => 23 | @content_before = @elem.html() # Save current to restore on cancel 24 | 25 | if @elem.data("editable-mode") == "meditor" 26 | @editor = new Meditor(@elem[0], @getContent(@elem, "raw")) 27 | @editor.handleImageSave = @handleImageSave 28 | @editor.load() 29 | else 30 | @editor = $("") 31 | @editor.val @getContent(@elem, "raw") 32 | @elem.after(@editor) 33 | 34 | @elem.html [1..50].join("fill the width") # To make sure we span the editor as far as we can 35 | @copyStyle(@elem, @editor) # Copy elem style to editor 36 | @elem.html @content_before # Restore content 37 | 38 | 39 | @autoExpand(@editor) # Set editor to autoexpand 40 | @elem.css("display", "none") # Hide elem 41 | 42 | if $(window).scrollTop() == 0 # Focus textfield if scroll on top 43 | @editor[0].selectionEnd = 0 44 | @editor.focus() 45 | 46 | $(".editbg").css("display", "block").cssLater("opacity", 0.9, 10) 47 | $(".editable-edit").css("display", "none") # Hide all edit button until its not finished 48 | 49 | $(".editbar").css("display", "inline-block").addClassLater("visible", 10) 50 | $(".publishbar").css("opacity", 0) # Hide publishbar 51 | $(".editbar .object").text @getObject(@elem).data("object")+"."+@elem.data("editable") 52 | $(".editbar .button").removeClass("loading") 53 | 54 | $(".editbar .save").off("click").on "click", @saveEdit 55 | $(".editbar .delete").off("click").on "click", @deleteObject 56 | $(".editbar .cancel").off("click").on "click", @cancelEdit 57 | 58 | # Deletable button show/hide 59 | if @getObject(@elem).data("deletable") 60 | $(".editbar .delete").css("display", "").html("Delete "+@getObject(@elem).data("object").split(":")[0]) 61 | else 62 | $(".editbar .delete").css("display", "none") 63 | 64 | window.onbeforeunload = -> 65 | return 'Your unsaved blog changes will be lost!' 66 | 67 | return false 68 | 69 | handleImageSave: (name, image_base64uri, el) => 70 | el.style.opacity = 0.5 71 | object_name = @getObject(@elem).data("object").replace(/[^A-Za-z0-9]/g, "_").toLowerCase() 72 | file_path = "data/img/#{object_name}_#{name}" 73 | Page.cmd "fileWrite", [file_path, image_base64uri.replace(/.*,/, "")], => 74 | el.style.opacity = 1 75 | el.src = file_path 76 | 77 | stopEdit: => 78 | @editor.remove() 79 | @editor = null 80 | @elem.css("display", "").css("z-index", 999).css("position", "relative").cssLater("z-index", "").cssLater("position", "") 81 | $(".editbg").css("opacity", 0).cssLater("display", "none") 82 | 83 | $(".editable-edit").css("display", "") # Show edit buttons 84 | 85 | $(".editbar").cssLater("display", "none", 1000).removeClass("visible") # Hide editbar 86 | $(".publishbar").css("opacity", 1) # Show publishbar 87 | 88 | window.onbeforeunload = null 89 | 90 | 91 | saveEdit: => 92 | content = @editor.val() 93 | $(".editbar .save").addClass("loading") 94 | @saveContent @elem, content, (content_html) => 95 | if content_html # File write ok 96 | $(".editbar .save").removeClass("loading") 97 | @stopEdit() 98 | if typeof content_html == "string" # Returned the new content 99 | @elem.html content_html 100 | 101 | $('pre code').each (i, block) -> # Higlight code blocks 102 | hljs.highlightBlock(block) 103 | 104 | Page.addImageZoom(@elem) 105 | else 106 | $(".editbar .save").removeClass("loading") 107 | 108 | return false 109 | 110 | 111 | deleteObject: => 112 | object_type = @getObject(@elem).data("object").split(":")[0] 113 | Page.cmd "wrapperConfirm", ["Are you sure you sure to delete this #{object_type}?", "Delete"], (confirmed) => 114 | $(".editbar .delete").addClass("loading") 115 | Page.saveContent @getObject(@elem), null, => 116 | @stopEdit() 117 | return false 118 | 119 | 120 | cancelEdit: => 121 | @stopEdit() 122 | @elem.html @content_before 123 | 124 | $('pre code').each (i, block) -> # Higlight code blocks 125 | hljs.highlightBlock(block) 126 | 127 | Page.cleanupImages() 128 | 129 | return false 130 | 131 | 132 | copyStyle: (elem_from, elem_to) -> 133 | elem_to.addClass(elem_from[0].className) 134 | from_style = getComputedStyle(elem_from[0]) 135 | 136 | elem_to.css 137 | fontFamily: from_style.fontFamily 138 | fontSize: from_style.fontSize 139 | fontWeight: from_style.fontWeight 140 | marginTop: from_style.marginTop 141 | marginRight: from_style.marginRight 142 | marginBottom: from_style.marginBottom 143 | marginLeft: from_style.marginLeft 144 | paddingTop: from_style.paddingTop 145 | paddingRight: from_style.paddingRight 146 | paddingBottom: from_style.paddingBottom 147 | paddingLeft: from_style.paddingLeft 148 | lineHeight: from_style.lineHeight 149 | textAlign: from_style.textAlign 150 | color: from_style.color 151 | letterSpacing: from_style.letterSpacing 152 | 153 | if elem_from.innerWidth() < 1000 # inline elems fix 154 | elem_to.css "minWidth", elem_from.innerWidth() 155 | 156 | 157 | autoExpand: (elem) -> 158 | editor = elem[0] 159 | # Autoexpand 160 | elem.height(1) 161 | elem.on "input", -> 162 | if editor.scrollHeight > elem.height() 163 | elem.height(1).height(editor.scrollHeight + parseFloat(elem.css("borderTopWidth")) + parseFloat(elem.css("borderBottomWidth"))) 164 | elem.trigger "input" 165 | 166 | # Tab key support 167 | elem.on 'keydown', (e) -> 168 | if e.which == 9 169 | e.preventDefault() 170 | s = this.selectionStart 171 | val = elem.val() 172 | elem.val(val.substring(0,this.selectionStart) + "\t" + val.substring(this.selectionEnd)) 173 | this.selectionEnd = s+1; 174 | 175 | 176 | window.InlineEditor = InlineEditor -------------------------------------------------------------------------------- /js/utils/Meditor.coffee: -------------------------------------------------------------------------------- 1 | class Meditor extends Class 2 | constructor: (@tag_original, body) -> 3 | @log "Create", @ 4 | 5 | @tag_original.insertAdjacentHTML('beforeBegin', "
    ") 6 | @tag_container = @tag_original.previousSibling 7 | 8 | @tag_container.insertAdjacentHTML('afterBegin', @tag_original.outerHTML) 9 | @tag_original.style.display = "none" 10 | @tag = @tag_container.firstChild 11 | 12 | if body 13 | @tag.innerHTML = marked(body, {gfm: true, breaks: true}) 14 | @ 15 | 16 | 17 | load: => 18 | if not window.AlloyEditor 19 | style = document.createElement("link") 20 | style.href = "alloy-editor/all.css" 21 | style.rel = "stylesheet" 22 | document.head.appendChild(style) 23 | 24 | script = document.createElement("script") 25 | script.src = "alloy-editor/all.js" 26 | document.head.appendChild(script) 27 | 28 | script.onload = @handleEditorLoad 29 | else 30 | @handleEditorLoad() 31 | 32 | 33 | handleEditorLoad: => 34 | # Create ckeditor<>markdown edit mode switch button 35 | @tag.insertAdjacentHTML('beforeBegin', "</>") 36 | @tag_editmode = @tag.previousSibling 37 | @tag_editmode.onclick = @handleEditmodeChange 38 | 39 | # Create ckeditor 40 | @editor = new CustomAlloyEditor(@tag) 41 | if @handleImageSave then @editor.handleImageSave = @handleImageSave 42 | 43 | # Create markdown editor textfield 44 | @tag.insertAdjacentHTML('beforeBegin', @tag_original.outerHTML) 45 | @tag_markdown = @tag.previousSibling 46 | @tag_markdown.innerHTML = "" 47 | @autoHeight(@tag_markdown.firstChild) 48 | @tag_markdown.firstChild.oninput = => 49 | @autoHeight(@tag_markdown.firstChild) 50 | 51 | @tag_markdown.style.display = "none" 52 | 53 | # Call onLoad for external scripts 54 | setTimeout ( => 55 | @onLoad?() 56 | ), 1 57 | 58 | 59 | autoHeight: (elem) -> 60 | height_before = elem.style.height 61 | if height_before 62 | elem.style.height = "0px" 63 | h = elem.offsetHeight 64 | scrollh = elem.scrollHeight 65 | elem.style.height = height_before 66 | if scrollh > h 67 | elem.style.height = scrollh+"px" 68 | elem.style.scrollTop = "0px" 69 | else 70 | elem.style.height = height_before 71 | 72 | 73 | getMarkdown: -> 74 | if @tag_editmode.classList.contains("markdown") 75 | back = @tag_markdown.firstChild.value 76 | else 77 | back = toMarkdown(@tag.innerHTML, {gfm: true}) 78 | return back 79 | 80 | 81 | getHtml: -> 82 | if @tag_editmode.classList.contains("markdown") 83 | back = marked(@tag_markdown.firstChild.value, {gfm: true, breaks: true}) 84 | else 85 | back = marked(@getMarkdown(), {gfm: true, breaks: true}) 86 | 87 | handleEditmodeChange: => 88 | if @tag_editmode.classList.contains("markdown") 89 | # Change to ckeditor 90 | @tag_markdown.style.display = "none" 91 | @tag.style.display = "" 92 | @tag.innerHTML = @getHtml() 93 | else 94 | # Change to markdown 95 | @tag_markdown.style.display = "" 96 | @tag_markdown.style.width = @tag.offsetWidth+"px" 97 | @tag.style.display = "none" 98 | @tag_markdown.firstChild.value = @getMarkdown() 99 | @autoHeight(@tag_markdown.firstChild) 100 | @tag_editmode.classList.toggle("markdown") 101 | 102 | return false 103 | 104 | 105 | save: => 106 | @tag_original.innerHTML = @getHtml() 107 | 108 | 109 | remove: => 110 | @tag_editmode.remove() 111 | @tag_markdown.remove() 112 | @tag_original.style.display = "" 113 | @tag.remove() 114 | 115 | val: => 116 | return @getMarkdown() 117 | 118 | window.Meditor = Meditor -------------------------------------------------------------------------------- /js/utils/Menu.coffee: -------------------------------------------------------------------------------- 1 | class Menu 2 | constructor: (@button) -> 3 | @elem = $(".menu.template").clone().removeClass("template") 4 | @elem.appendTo("body") 5 | @items = [] 6 | 7 | show: -> 8 | if window.visible_menu and window.visible_menu.button[0] == @button[0] # Same menu visible then hide it 9 | window.visible_menu.hide() 10 | @hide() 11 | else 12 | button_pos = @button.offset() 13 | @elem.css({"top": button_pos.top+@button.outerHeight(), "left": button_pos.left + @button.outerWidth() - @elem.outerWidth()}) 14 | @button.addClass("menu-active") 15 | @elem.addClass("visible") 16 | if window.visible_menu then window.visible_menu.hide() 17 | window.visible_menu = @ 18 | 19 | 20 | hide: -> 21 | @elem.removeClass("visible") 22 | @button.removeClass("menu-active") 23 | window.visible_menu = null 24 | 25 | 26 | addItem: (title, cb) -> 27 | item = $(".menu-item.template", @elem).clone().removeClass("template") 28 | item.html(title) 29 | item.on "click", => 30 | if not cb(item) 31 | @hide() 32 | return false 33 | item.appendTo(@elem) 34 | @items.push item 35 | return item 36 | 37 | 38 | log: (args...) -> 39 | console.log "[Menu]", args... 40 | 41 | window.Menu = Menu 42 | 43 | # Hide menu on outside click 44 | $("body").on "click", (e) -> 45 | if window.visible_menu and e.target != window.visible_menu.button[0] and $(e.target).parent()[0] != window.visible_menu.elem[0] 46 | window.visible_menu.hide() -------------------------------------------------------------------------------- /js/utils/RateLimit.coffee: -------------------------------------------------------------------------------- 1 | limits = {} 2 | call_after_interval = {} 3 | window.RateLimit = (interval, fn) -> 4 | if not limits[fn] 5 | call_after_interval[fn] = false 6 | fn() # First call is not delayed 7 | limits[fn] = setTimeout (-> 8 | if call_after_interval[fn] 9 | fn() 10 | delete limits[fn] 11 | delete call_after_interval[fn] 12 | ), interval 13 | else # Called within iterval, delay the call 14 | call_after_interval[fn] = true 15 | -------------------------------------------------------------------------------- /js/utils/Text.coffee: -------------------------------------------------------------------------------- 1 | class Renderer extends marked.Renderer 2 | image: (href, title, text) -> 3 | return ("![#{text}](#{href})") 4 | 5 | class Text 6 | toColor: (text) -> 7 | hash = 0 8 | for i in [0..text.length-1] 9 | hash = text.charCodeAt(i) + ((hash << 5) - hash) 10 | color = '#' 11 | if Page.server_info?.user_settings?.theme == "dark" 12 | return "hsl(" + (hash % 360) + ",80%,70%)" 13 | else 14 | return "hsl(" + (hash % 360) + ",30%,50%)" 15 | 16 | 17 | renderMarked: (text, options={}) -> 18 | options["gfm"] = true 19 | options["breaks"] = true 20 | if options.sanitize 21 | options["renderer"] = renderer # Dont allow images 22 | text = text.replace(/((?<=\s|^)http[s]?:\/\/.*?)(?=\s|$)/g, '<$1>') # Auto linkify IPv6 urls by adding <> around urls 23 | text = marked(text, options) 24 | text = text.replace(/(https?:\/\/)%5B(.*?)%5D/g, '$1[$2]') # Fix IPv6 links 25 | return @fixHtmlLinks text 26 | 27 | 28 | # Convert zeronet html links to relaitve 29 | fixHtmlLinks: (text) -> 30 | if window.is_proxy 31 | return text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/g, 'href="http://zero') 32 | else 33 | return text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/g, 'href="') 34 | 35 | 36 | # Convert a single link to relative 37 | fixLink: (link) -> 38 | if window.is_proxy 39 | return link.replace(/http:\/\/(127.0.0.1|localhost):43110/, 'http://zero') 40 | else 41 | return link.replace(/http:\/\/(127.0.0.1|localhost):43110/, '') 42 | 43 | 44 | toUrl: (text) => 45 | return text.replace(/[^A-Za-z0-9]/g, "+").replace(/[+]+/g, "+").replace(/[+]+$/, "") 46 | 47 | window.is_proxy = (window.location.pathname == "/") 48 | window.renderer = new Renderer() 49 | window.Text = new Text() 50 | -------------------------------------------------------------------------------- /js/utils/Time.coffee: -------------------------------------------------------------------------------- 1 | class Time 2 | since: (time) -> 3 | now = +(new Date)/1000 4 | secs = now - time 5 | if secs < 60 6 | back = "Just now" 7 | else if secs < 60*60 8 | back = "#{Math.round(secs/60)} minutes ago" 9 | else if secs < 60*60*24 10 | back = "#{Math.round(secs/60/60)} hours ago" 11 | else if secs < 60*60*24*3 12 | back = "#{Math.round(secs/60/60/24)} days ago" 13 | else 14 | back = "on "+@date(time) 15 | back = back.replace(/1 ([a-z]+)s/, "1 $1") # 1 days ago fix 16 | return back 17 | 18 | 19 | date: (timestamp, format="short") -> 20 | parts = (new Date(timestamp*1000)).toString().split(" ") 21 | if format == "short" 22 | display = parts.slice(1, 4) 23 | else 24 | display = parts.slice(1, 5) 25 | return display.join(" ").replace(/( [0-9]{4})/, ",$1") 26 | 27 | 28 | timestamp: (date="") -> 29 | if date == "now" or date == "" 30 | return parseInt(+(new Date)/1000) 31 | else 32 | return parseInt(Date.parse(date)/1000) 33 | 34 | 35 | # Get elistamated read time for post 36 | readtime: (text) -> 37 | chars = text.length 38 | if chars > 1500 39 | return parseInt(chars/1500)+" min read" 40 | else 41 | return "less than 1 min read" 42 | 43 | 44 | window.Time = new Time -------------------------------------------------------------------------------- /js/utils/ZeroFrame.coffee: -------------------------------------------------------------------------------- 1 | class ZeroFrame extends Class 2 | constructor: (url) -> 3 | @url = url 4 | @waiting_cb = {} 5 | @wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1") 6 | @connect() 7 | @next_message_id = 1 8 | @init() 9 | 10 | 11 | init: -> 12 | @ 13 | 14 | 15 | connect: -> 16 | @target = window.parent 17 | window.addEventListener("message", @onMessage, false) 18 | @cmd("innerReady") 19 | 20 | 21 | onMessage: (e) => 22 | message = e.data 23 | cmd = message.cmd 24 | if cmd == "response" 25 | if @waiting_cb[message.to]? 26 | @waiting_cb[message.to](message.result) 27 | else 28 | @log "Websocket callback not found:", message 29 | else if cmd == "wrapperReady" # Wrapper inited later 30 | @cmd("innerReady") 31 | else if cmd == "ping" 32 | @response message.id, "pong" 33 | else if cmd == "wrapperOpenedWebsocket" 34 | @onOpenWebsocket() 35 | else if cmd == "wrapperClosedWebsocket" 36 | @onCloseWebsocket() 37 | else 38 | @onRequest cmd, message 39 | 40 | 41 | onRequest: (cmd, message) => 42 | @log "Unknown request", message 43 | 44 | 45 | response: (to, result) -> 46 | @send {"cmd": "response", "to": to, "result": result} 47 | 48 | 49 | cmd: (cmd, params={}, cb=null) -> 50 | @send {"cmd": cmd, "params": params}, cb 51 | 52 | 53 | send: (message, cb=null) -> 54 | message.wrapper_nonce = @wrapper_nonce 55 | message.id = @next_message_id 56 | @next_message_id += 1 57 | @target.postMessage(message, "*") 58 | if cb 59 | @waiting_cb[message.id] = cb 60 | 61 | 62 | onOpenWebsocket: => 63 | @log "Websocket open" 64 | 65 | 66 | onCloseWebsocket: => 67 | @log "Websocket close" 68 | 69 | 70 | 71 | window.ZeroFrame = ZeroFrame 72 | -------------------------------------------------------------------------------- /languages/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "Einen Beitrag hinzufügen", 3 | "Follow in Newsfeed": "Abonniere diesen Blog", 4 | "Username mentions": "Erwähnungen", 5 | "Posts": "Beiträge", 6 | "Post": "Beitrag", 7 | " post(s)": " Beiträg(e)", 8 | "0 Comments:": "0 Kommentare:", 9 | "Comments": "Kommentare", 10 | "Following": "Abonniert", 11 | 12 | "Like this post": "Gefällt mir", 13 | "Latest comments:": "Neueste Kommentare:", 14 | "Reply": "Antworten", 15 | 16 | "Bold": "Fett", 17 | "Italic": "Kursiv", 18 | "Strikethrough": "Durchgestrichen", 19 | "Code": "Code", 20 | "Link": "Link", 21 | "Type or paste link here": "Tippe oder füge einen Link hier ein", 22 | 23 | "**bold**": "**fett**", 24 | "_italic_": "_kursiv_", 25 | "strikethrough": "durchgestrichen", 26 | "- Lists": "- Listen", 27 | "1. Numbered lists": "Erste Liste", 28 | "[Links](http://www.zeronet.io)": "[Links](http://www.zeronet.io)", 29 | "[References][1]
    [1]: Can be used": "[Referenz][1]
    [1]: Kann benutzt werden", 30 | "> Quotes": "> Ziate", 31 | "--- Horizontal rule": "--- Horizontale Linie", 32 | 33 | "More comments": "Mehr Kommentare...", 34 | "Sign in as...": "Anmelden als...", 35 | "Submit comment": "Kommentieren", 36 | "Please sign in": "Bitte anmelden", 37 | "new comment": "Neuer Kommentar", 38 | "used: ": "Benutzt: ", 39 | 40 | " minutes ago": " Minuten vergangen", 41 | " hours ago": " Stunden vergangen", 42 | " days ago": " Tage vergangen", 43 | "on ": "den ", 44 | "Just now": "Gerade", 45 | "less than 1 min read": "Unter einer Minute zu lesen", 46 | " min read": " Minuten zu lesen", 47 | 48 | "tag:": "Stichwort:", 49 | "tagged:": "Stichworte:", 50 | "not tagged": "nicht markiert", 51 | "TOC by date": "Sortieren nach Datum", 52 | "TOC by tag": "Sortieren nach Stichwort", 53 | "index by date": "Sortierung nach Datum", 54 | "index by tag": "Sortierung nach Stichwort", 55 | "posts of tag:": "Beiträge nach dem Stichwort: ", 56 | "all untagged": "alle Unmarkierte", 57 | "untagged:": "unmarkiert", 58 | 59 | "Content changed": "Inhalt wurde verändert", 60 | "Sign & Publish new content": "Anmelden und veröffentliche neuen Inhalt", 61 | 62 | " Editing: ": " Bearbeiten: ", 63 | "Save": "Speichern", 64 | "Cancel": "Abbrechen", 65 | "Delete ": "Lösche ", 66 | 67 | "Prev page": "Vorherige Seite", 68 | "Next page": "Nächste Seite", 69 | 70 | "Not found": "Nicht gefunden", 71 | "Content changed": "Inhalt wurde verändert", 72 | "Are you sure you sure to delete this Post?": "Bist du sicher, diesen Beitrag zu löschen?" 73 | } 74 | -------------------------------------------------------------------------------- /languages/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "Agregar nueva publicación", 3 | "Follow in Newsfeed": "Seguir en Noticias", 4 | "Posts": "Publicaciones", 5 | "Comments": "Comentarios", 6 | "Following": "Siguiendo", 7 | 8 | "Like this post": "Me gusta esta publicación", 9 | "Latest comments:": "Ultimos comentarios:", 10 | "Reply": "Responder", 11 | 12 | "Bold": "Negrita", 13 | "Italic": "Itálica", 14 | "Strikethrough": "Tachada", 15 | "Code": "Codigo", 16 | "Link": "Enlace", 17 | 18 | "More comments": "Mas comentarios", 19 | "Sign in as...": "Iniciar sesión como...", 20 | "Submit comment": "Enviar mensaje", 21 | "Please sign in": "Por favor inicie sesión", 22 | "new comment": "Nuevo comentario", 23 | "used: ": "usado: ", 24 | 25 | " minutes ago": " minutos atras", 26 | " hours ago": " horas atras", 27 | " days ago": " dias atras", 28 | "on ": "en ", 29 | "Just now": "Justo ahora", 30 | 31 | "Save": "Guardar", 32 | "Cancel": "Cancelar", 33 | "Delete ": "Borrar " 34 | } 35 | -------------------------------------------------------------------------------- /languages/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "Ajouter un article", 3 | "Follow in Newsfeed": "Suivre dans le fil d'actualités", 4 | "Username mentions": "Mentions", 5 | "Posts": "Articles", 6 | "0 Comments:": "0 Commentaire :", 7 | "Comments": "Commentaires", 8 | "Following": "Suivi", 9 | 10 | "Like this post": "Aimer cet article", 11 | "Latest comments:": "Derniers commentaires :", 12 | "Reply": "Répondre", 13 | 14 | "Bold": "Gras", 15 | "Italic": "Italique", 16 | "Strikethrough": "Strikethrough", 17 | "Code": "Code", 18 | "Link": "Lien", 19 | "Type or paste link here": "Écrire ou copier le lien ici", 20 | 21 | "**bold**": "**gras**", 22 | "_italic_": "_italique_", 23 | "strikethrough": "barré", 24 | "- Lists": "- Listes", 25 | "1. Numbered lists": "1. Listes numérotées", 26 | "[Links](http://www.zeronet.io)": "[Liens](http://www.zeronet.io)", 27 | "[References][1]
    [1]: Can be used": "[Référence][1]
    [1]: Peut être utilisé", 28 | "> Quotes": "> Citations", 29 | "--- Horizontal rule": "--- Barre horizontale", 30 | 31 | "More comments": "Plus de commentaires...", 32 | "Sign in as...": "Se connecter...", 33 | "Submit comment": "Commenter", 34 | "Please sign in": "Veuillez vous connecter", 35 | "new comment": "Nouveau commentaire", 36 | "used: ": "Utilisé : ", 37 | 38 | " minutes ago": " minutes", 39 | " hours ago": " heures", 40 | " days ago": " jours", 41 | "on ": "le ", 42 | "Just now": "À l'instant", 43 | "less than 1 min read": "moins d'une minute de lecture", 44 | " min read": " minutes de lecture", 45 | 46 | "Content changed": "Contenu modifié", 47 | "Sign & Publish new content": "Signer et publier le nouveau contenu", 48 | 49 | " Editing: ": " Édition de : ", 50 | "Save": "Enregistrer", 51 | "Cancel": "Annuler", 52 | "Delete ": "Supprimer " 53 | } 54 | -------------------------------------------------------------------------------- /languages/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "Aggiungere un messaggio", 3 | "Follow in Newsfeed": "Seguire nelle notifiche", 4 | "Posts": "Messaggi", 5 | "Comments": "Commenti", 6 | "Following": "Osservati", 7 | 8 | "Like this post": "Mi piace guesto messaggio", 9 | "Latest comments:": "Ultimi commenti", 10 | "Reply": "Rispondi", 11 | 12 | "Bold": "Grassetto", 13 | "Italic": "Italico", 14 | "Strikethrough": "Barrato", 15 | "Code": "Codice", 16 | "Link": "Collegamento", 17 | 18 | "More comments": "Altri commenti", 19 | "Sign in as...": "Accedi come...", 20 | "Submit comment": "Invia commento", 21 | "Please sign in": "Si prega di accedere", 22 | "new comment": "nuovo commento", 23 | "used: ": "utilizzato: ", 24 | 25 | " minutes ago": " minuti fa", 26 | " hours ago": " ore fa", 27 | " days ago": " giorni fa", 28 | "on ": "il ", 29 | "Just now": "Adesso", 30 | 31 | "Save": "Salva", 32 | "Cancel": "Annulla", 33 | "Delete ": "Cancella " 34 | } 35 | -------------------------------------------------------------------------------- /languages/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "Nieuwe post", 3 | "Follow in Newsfeed": "Volgen in Nieuwsfeed", 4 | "Posts": "Posts", 5 | "Comments": "Commentaar", 6 | "Following": "Volgend", 7 | 8 | "Like this post": "Vind deze post leuk", 9 | "Latest comments:": "Laatste commentaar:", 10 | "Reply": "Antwoord", 11 | 12 | "Bold": "Vet", 13 | "Italic": "Schuin", 14 | "Strikethrough": "Doorhalen", 15 | "Code": "Code", 16 | "Link": "Link", 17 | 18 | "More comments": "Meer commentaar", 19 | "Sign in as...": "Inloggen als...", 20 | "Submit comment": "Commentaar toevoegen", 21 | "Please sign in": "Log alsjeblieft in", 22 | "new comment": "nieuw commentaar", 23 | "used: ": "gebruikt: ", 24 | 25 | " minutes ago": " minuten geleden", 26 | " hours ago": " uren geleden", 27 | " days ago": " dagen geleden", 28 | "on ": "op", 29 | "Just now": "Zojuist", 30 | 31 | "Save": "Opslaan", 32 | "Cancel": "Annuleren", 33 | "Delete ": "Verwijderen" 34 | } 35 | -------------------------------------------------------------------------------- /languages/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "Dodaj nowy wpis", 3 | "Follow in Newsfeed": "Śledź na Tablicy", 4 | "Posts": "Wpisy", 5 | "Comments": "Komentarze", 6 | "Following": "Śledzony", 7 | 8 | "Like this post": "Polub ten wpis", 9 | "Latest comments:": "Ostatnie komentarze:", 10 | "Reply": "Odpowiedz", 11 | 12 | "Bold": "Wytłuszczenie", 13 | "Italic": "Kursywa", 14 | "Strikethrough": "Przekreślenie", 15 | "Code": "Kod", 16 | "Link": "Odnośnik", 17 | 18 | "More comments": "Więcej komentarzy", 19 | "Sign in as...": "Zaloguj się jako...", 20 | "Submit comment": "Wyślij komentarz", 21 | "Please sign in": "Proszę, zaloguj się", 22 | "new comment": "nowy komentarz", 23 | "used: ": "zużyto: ", 24 | 25 | " minutes ago": " minut(y/ę) temu", 26 | " hours ago": " godzin(y/ę) temu", 27 | " days ago": " dni(dzień) temu", 28 | "on ": "", 29 | "Just now": "Właśnie teraz", 30 | 31 | "Save": "Zapisz", 32 | "Cancel": "Anuluj", 33 | "Delete ": "Usuń " 34 | } 35 | -------------------------------------------------------------------------------- /languages/pt-br: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "Adicionar nova publicação", 3 | "Follow in Newsfeed": "Seguir as Noticias", 4 | "Posts": "Postagens", 5 | "Comments": "Comentários", 6 | "Following": "Seguindo", 7 | 8 | "Like this post": "Gostei deste publicação", 9 | "Latest comments:": "Últimos comentários:", 10 | "Reply": "Responder", 11 | 12 | "Bold": "Negrito", 13 | "Italic": "Itálico", 14 | "Strikethrough": "Tachado", 15 | "Code": "Código", 16 | "Link": "Link", 17 | 18 | "More comments": "Mais comentários", 19 | "Sign in as...": "Entrar como...", 20 | "Submit comment": "Enviar comentário", 21 | "Please sign in": "Por favor, ente", 22 | "new comment": "Novo comentário", 23 | "used: ": "usado: ", 24 | 25 | " minutes ago": "minutos atrás", 26 | " hours ago": " horas atras", 27 | " days ago": " dias atras", 28 | "on ": "en ", 29 | "Just now": "agora mesmo", 30 | 31 | "Save": "Salvar", 32 | "Cancel": "Cancelar", 33 | "Delete ": "Excluir " 34 | } 35 | -------------------------------------------------------------------------------- /languages/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add new post": "发布新文章", 3 | "Follow in Newsfeed": "在新闻源中关注", 4 | "Posts": "文章", 5 | "Username mentions": "提到的用户名", 6 | "Comments": "评论", 7 | " Comments:": " 条评论:", 8 | " Comment:": " 条评论:", 9 | " comments": " 条评论", 10 | " comment": " 条评论", 11 | "Following": "已关注", 12 | 13 | "Like this post": "喜欢这篇文章", 14 | "Latest comments:": "最新评论:", 15 | "Reply": "回复", 16 | 17 | "Bold": "粗体", 18 | "Italic": "斜体", 19 | "Strikethrough": "删除线", 20 | "Code": "代码", 21 | "Link": "链接", 22 | "Heading 2": "2级标题", 23 | "Heading 3": "3级标题", 24 | "Block Quote": "区块引用", 25 | "Insert Horizontal Line": "插入水平线", 26 | "Insert Table": "插入表格", 27 | "Insert Image": "插入图片", 28 | 29 | "More comments": "更多评论", 30 | "Sign in as...": "登录为...", 31 | "Submit comment": "提交评论", 32 | "Please sign in": "请先登录", 33 | "new comment": "发表评论", 34 | "used: ": "已使用: ", 35 | 36 | " minutes ago": " 分钟以前", 37 | " hours ago": " 小时以前", 38 | " days ago": " 天以前", 39 | "on ": "于 ", 40 | "Just now": "刚刚", 41 | 42 | " min read": "分钟内阅读完", 43 | "less than 1 min read": "1分钟内阅读完", 44 | "Read more": "阅读更多", 45 | "Prev page": "上一页", 46 | "Next page": "下一页", 47 | "Content changed": "内容已改变", 48 | "Sign & Publish new content": "签名并发布新内容", 49 | " Editing: ": " 编辑: ", 50 | "# H1": "# 1级标题", 51 | "## H2": "## 2级标题", 52 | "### H3": "### 3级标题", 53 | "**bold**": "**粗体**", 54 | "_italic_": "_斜体_", 55 | "~~strikethrough~~": "~~删除线~~", 56 | "- Lists": "- 列表", 57 | "1. Numbered lists": "1. 编号列表", 58 | "[Links](http://www.zeronet.io)": "[链接](http://www.zeronet.io)", 59 | "[References][1]
    [1]: Can be used": "[参考][1]
    [1]: 这样使用", 60 | "![image alt](img/logo.png)": "![图片说明文字](img/logo.png)", 61 | "> Quotes": "> 引用", 62 | "--- Horizontal rule": "--- 水平规则", 63 | 64 | "Save": "保存", 65 | "Cancel": "取消", 66 | "Delete ": "删除" 67 | } 68 | --------------------------------------------------------------------------------