├── 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 | 
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 | - 
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 |
44 |
45 |
46 |
47 |
48 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
Add new post
88 |
89 |
90 |
91 |
92 |
93 |
21 hours ago · 2 min read
94 |
95 |
96 |
100 |
101 |
Body
102 |
Read more →
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
Not found
111 |
112 |
113 |
114 |
21 hours ago · 2 min read
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
139 |
140 |
141 |
155 |
156 |
157 |
158 |
159 |
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]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|\\n*|\\n*|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?!script|pre|style)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *([^\s>]+)>?(?:(?: +\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]+?\\1> *(?:\\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:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\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:-]*\\s*>|^<[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?"\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+""+r+">\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"},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+">\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='"+n+""},r.prototype.image=function(e,t,n){if(null===(e=l(this.options.sanitize,this.options.baseUrl,e)))return n;var r='
":">"},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 ("
")
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 | "": "",
61 | "> Quotes": "> 引用",
62 | "--- Horizontal rule": "--- 水平规则",
63 |
64 | "Save": "保存",
65 | "Cancel": "取消",
66 | "Delete ": "删除"
67 | }
68 |
--------------------------------------------------------------------------------
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 |