Compare commits
	
		
			15 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 706111f8ba | |||
| b0a56bec64 | |||
| 5ee8ea9b60 | |||
| 215cef976f | |||
| ceb90c2bfa | |||
| b5223b1053 | |||
| a9cb7a58a3 | |||
| c81ff7a441 | |||
| 9131fb9a42 | |||
| 0909c8e9cc | |||
| 60e2764a1b | |||
| d77b91e27a | |||
| eaf0c9bfba | |||
|  | 11484146e9 | ||
|  | 7c10a5e993 | 
							
								
								
									
										1
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| {} | ||||
							
								
								
									
										94
									
								
								DATA.md
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								DATA.md
									
									
									
									
									
								
							| @ -1,12 +1,10 @@ | ||||
| Data | ||||
| ==== | ||||
| # Data | ||||
| 
 | ||||
| Every template gets an object with the exact same structure - whether it's a template or a widget or a page or a post. | ||||
| 
 | ||||
| Here we only document Desirae's default behavior, but there are many objects added for compatibility with Ruhoh that are not documented. | ||||
| 
 | ||||
| config.yml vs site.yml | ||||
| ------ | ||||
| ## config.yml vs site.yml | ||||
| 
 | ||||
| site.yml is for anything that changes the content of the site (navigation, title, analytic and ad ids, default author, etc) | ||||
| 
 | ||||
| @ -16,66 +14,60 @@ config.yml is for anything that doesn't change the site (from where to read dire | ||||
| desi = {} | ||||
| ``` | ||||
| 
 | ||||
| desi | ||||
| ==== | ||||
| # desi | ||||
| 
 | ||||
| * `config` - literally `config.yml`, parsed | ||||
| * `site` - literally `site.yml`, parsed | ||||
| * `authors` - literally the authors from `authors/*.yml`, parsed | ||||
| * `author` - the primary author of the site | ||||
| * `env` - urls and paths for this build (be it production, development, staging, etc) | ||||
| * `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout) | ||||
| * `collection` - config related to this collection | ||||
| * `entity` - the page, post, article, etc that is the focus of the present template process | ||||
| * `themes` - all themes | ||||
| * `theme` - the default theme | ||||
| * `layout` - the selected layout for this theme | ||||
| * `satch` - the selected swatch for this theme | ||||
| * `categories` - all categories | ||||
| * `tags` - all tags | ||||
| * `styles` - ??? goes into the final template in the head | ||||
| * `scripts` - ?? that goes into the final template just before the body close | ||||
| - `config` - literally `config.yml`, parsed | ||||
| - `site` - literally `site.yml`, parsed | ||||
| - `authors` - literally the authors from `authors/*.yml`, parsed | ||||
| - `author` - the primary author of the site | ||||
| - `env` - urls and paths for this build (be it production, development, staging, etc) | ||||
| - `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout) | ||||
| - `collection` - config related to this collection | ||||
| - `entity` - the page, post, article, etc that is the focus of the present template process | ||||
| - `themes` - all themes | ||||
| - `theme` - the default theme | ||||
| - `layout` - the selected layout for this theme | ||||
| - `satch` - the selected swatch for this theme | ||||
| - `categories` - all categories | ||||
| - `tags` - all tags | ||||
| - `styles` - ??? goes into the final template in the head | ||||
| - `scripts` - ?? that goes into the final template just before the body close | ||||
| 
 | ||||
| desi.entity | ||||
| =========== | ||||
| # desi.entity | ||||
| 
 | ||||
| stuff | ||||
| 
 | ||||
| * `uuid` | ||||
| * `title` | ||||
| * `disqus_url` | ||||
| * `disqus_identifier` | ||||
| - `uuid` | ||||
| - `title` | ||||
| - `disqus_url` | ||||
| - `disqus_identifier` | ||||
| 
 | ||||
| more stuff | ||||
| 
 | ||||
| * `type` - `post`, `page`, etc | ||||
| * `authors` - literally the relevant authors from `authors/*.yml`, parsed | ||||
| * `author` - the primary author of this entity | ||||
| * `theme` - null or a non-default theme | ||||
| * `layout` - null or a non-default layout for this theme | ||||
| * `swatch` - null or a non-default swatch for this theme | ||||
| * `categories`: []    // *all* categories in all collections | ||||
| * `tags`: []    // *all* categories in all collections | ||||
| * `production_canonical_url` the PRODUCTION canonical_url for this entity | ||||
| * `production_url` the PRODUCTION url for this entity | ||||
| * `production_path` the PRODUCTION path for this entity | ||||
| * `url` the full url in the current environment (might be production, development, etc) | ||||
| * `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`) | ||||
| * `previous` the previous entity in this collection | ||||
| * `next` the next entitiy in this collection | ||||
| - `type` - `post`, `page`, etc | ||||
| - `authors` - literally the relevant authors from `authors/*.yml`, parsed | ||||
| - `author` - the primary author of this entity | ||||
| - `theme` - null or a non-default theme | ||||
| - `layout` - null or a non-default layout for this theme | ||||
| - `swatch` - null or a non-default swatch for this theme | ||||
| - `categories`: [] // _all_ categories in all collections | ||||
| - `tags`: [] // _all_ categories in all collections | ||||
| - `production_canonical_url` the PRODUCTION canonical_url for this entity | ||||
| - `production_url` the PRODUCTION url for this entity | ||||
| - `production_path` the PRODUCTION path for this entity | ||||
| - `url` the full url in the current environment (might be production, development, etc) | ||||
| - `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`) | ||||
| - `previous` the previous entity in this collection | ||||
| - `next` the next entitiy in this collection | ||||
| 
 | ||||
| NOTE: Plugins, widgets, etc SHOULD NOT modify config, site, authors, author, or env. | ||||
| 
 | ||||
| desi.posts | ||||
| ========== | ||||
| # desi.posts | ||||
| 
 | ||||
|       , posts: { collated: desi.collated } | ||||
| 
 | ||||
| desi.config | ||||
| =========== | ||||
| # desi.config | ||||
| 
 | ||||
| desi.site | ||||
| =========== | ||||
| # desi.site | ||||
| 
 | ||||
| desi.env | ||||
| =========== | ||||
| # desi.env | ||||
|  | ||||
							
								
								
									
										84
									
								
								ENTITY.md
									
									
									
									
									
								
							
							
						
						
									
										84
									
								
								ENTITY.md
									
									
									
									
									
								
							| @ -2,61 +2,65 @@ This is what an entity looks like: | ||||
| 
 | ||||
| ```yml | ||||
| # inherited from File Entity | ||||
| path              : My Posts/My-Old-Name.html | ||||
| lastModifiedDate  : 2015-07-04T13:56:01Z | ||||
| createdDate       : 2015-07-04T13:56:01Z | ||||
| contents          : '...'           # whatever the file is | ||||
| path: My Posts/My-Old-Name.html | ||||
| lastModifiedDate: 2015-07-04T13:56:01Z | ||||
| createdDate: 2015-07-04T13:56:01Z | ||||
| contents: "..." # whatever the file is | ||||
| 
 | ||||
| # inherited from Collection Entity | ||||
| name              : My-Old-Name.html | ||||
| relativePath      : My Posts | ||||
| ext               : .html | ||||
| collection        : posts | ||||
| name: My-Old-Name.html | ||||
| relativePath: My Posts | ||||
| ext: .html | ||||
| collection: posts | ||||
| 
 | ||||
| # inherited from Content Entity | ||||
| frontmatter       : '---\n...\n---' # frontmatter as a string | ||||
| yml               : {}              # frontmatter, parsed | ||||
| body              : 'I think ...'   # body, after frontmatter | ||||
| frontmatter: '---\n...\n---' # frontmatter as a string | ||||
| yml: {} # frontmatter, parsed | ||||
| body: "I think ..." # body, after frontmatter | ||||
| 
 | ||||
| # inherited from Normalized Entity | ||||
| title             : My Title        # yml.title | titlize(entity.name) | ||||
| slug              : my-title        # slugify(title) | ||||
| slug_path         : my-posts        # slugifyPath(relativePath) | ||||
| title: My Title # yml.title | titlize(entity.name) | ||||
| slug: my-title # slugify(title) | ||||
| slug_path: my-posts # slugifyPath(relativePath) | ||||
| 
 | ||||
| year              : 2014 | ||||
| month             : 07 | ||||
| day               : 04 | ||||
| hour              : 13 | ||||
| twelve_hour       : 1 | ||||
| meridian          : pm | ||||
| minute            : 22 | ||||
| year: 2014 | ||||
| month: 07 | ||||
| day: 04 | ||||
| hour: 13 | ||||
| twelve_hour: 1 | ||||
| meridian: pm | ||||
| minute: 22 | ||||
| 
 | ||||
| categories        : ['tech'] | ||||
| tags              : ['http','url','website'] | ||||
| categories: ["tech"] | ||||
| tags: | ||||
|   ["http", "url", "website"] | ||||
| 
 | ||||
|                     # includes index.html | ||||
| relative_file     : /posts/foo/index.html | ||||
|   # includes index.html | ||||
| relative_file: | ||||
|   /posts/foo/index.html | ||||
| 
 | ||||
|                     # excludes index.html | ||||
| relative_href     : /posts/foo/ | ||||
|   # excludes index.html | ||||
| relative_href: | ||||
|   /posts/foo/ | ||||
| 
 | ||||
|                     # actual url of this file, even if redirect | ||||
|                     # excludes index.html | ||||
| url               : http://dev.example.com/posts/foo/ | ||||
|   # actual url of this file, even if redirect | ||||
|   # excludes index.html | ||||
| url: | ||||
|   http://dev.example.com/posts/foo/ | ||||
| 
 | ||||
|                     # the appropriate url, even in a redirect or duplicate | ||||
|                     # excludes index.html | ||||
| canonical_url     : http://dev.example.com/posts/foo/ | ||||
|   # the appropriate url, even in a redirect or duplicate | ||||
|   # excludes index.html | ||||
| canonical_url: | ||||
|   http://dev.example.com/posts/foo/ | ||||
| 
 | ||||
|                     # production url, even in development (for disqus, etc) | ||||
|                     # excludes index.html | ||||
| production_url    : http://example.com/posts/foo/ | ||||
|   # production url, even in development (for disqus, etc) | ||||
|   # excludes index.html | ||||
| production_url: http://example.com/posts/foo/ | ||||
| ``` | ||||
| 
 | ||||
| Note: The option `env.explicitIndexes` turns on `/index.html`. This option is automatically turned on when Dropbox is the host. | ||||
| 
 | ||||
| TODO | ||||
| ---- | ||||
| ## TODO | ||||
| 
 | ||||
| * path relative from / in the browser | ||||
| * path relative from base_path on the file system | ||||
| - path relative from / in the browser | ||||
| - path relative from base_path on the file system | ||||
|  | ||||
							
								
								
									
										32
									
								
								GLOSSARY.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								GLOSSARY.md
									
									
									
									
									
								
							| @ -1,14 +1,12 @@ | ||||
| Glossary | ||||
| ======== | ||||
| # Glossary | ||||
| 
 | ||||
| Canonical URL | ||||
| -------- | ||||
| base\_url + base\_path + permalink | ||||
| ## Canonical URL | ||||
| 
 | ||||
| Base URL | ||||
| ---- | ||||
| base_url + base_path + permalink | ||||
| 
 | ||||
| base\_url is the point of ownership | ||||
| ## Base URL | ||||
| 
 | ||||
| base_url is the point of ownership | ||||
| 
 | ||||
| In most cases that would be https://johndoe.com | ||||
| 
 | ||||
| @ -16,27 +14,25 @@ In some cases that might be https://school.edu/~/johndoe | ||||
| 
 | ||||
| It does NOT include a trailing / | ||||
| 
 | ||||
| Base Path | ||||
| ----- | ||||
| ## Base Path | ||||
| 
 | ||||
| base\_path is the blog directory | ||||
| base_path is the blog directory | ||||
| 
 | ||||
| In most cases that would be / or /blog/ | ||||
| 
 | ||||
| It DOES include BOTH a LEADING and TRAILING slash. | ||||
| 
 | ||||
| In the case of https://school.edu/~/johndoe/weblog, the base\_path would  be /weblog/. | ||||
| In the case of https://school.edu/~/johndoe/weblog, the base_path would be /weblog/. | ||||
| 
 | ||||
| Permalink | ||||
| ------ | ||||
| ## Permalink | ||||
| 
 | ||||
| The permalink is the permanent part of the URL, after the path to the blog. | ||||
| 
 | ||||
| For example: | ||||
| 
 | ||||
|   * http://blog.johndoe.com/articles/first-post.html the permalink is articles/first-post.html | ||||
|   * http://johndoe.com/blog/articles/first-post.html the permalink is still articles/first-post.html | ||||
|   * http://school.edu/~/johndoe/blog/articles/first-post.html the permalink is yet still articles/first-post.html | ||||
| - http://blog.johndoe.com/articles/first-post.html the permalink is articles/first-post.html | ||||
| - http://johndoe.com/blog/articles/first-post.html the permalink is still articles/first-post.html | ||||
| - http://school.edu/~/johndoe/blog/articles/first-post.html the permalink is yet still articles/first-post.html | ||||
| 
 | ||||
| The permalink is ALWAYS RELATIVE (no leading slash) | ||||
| 
 | ||||
| @ -44,5 +40,5 @@ It is designed so that if you ever move your blog from one domain, point of owne | ||||
| a very simple one-line redirect can be made to your webserver and all of the posts will end up in the right place | ||||
| once again. | ||||
| 
 | ||||
| base\_url  the | ||||
| base_url the | ||||
| permalink refers to | ||||
|  | ||||
							
								
								
									
										206
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,202 +1,4 @@ | ||||
| Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
| 
 | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
| 
 | ||||
|    1. Definitions. | ||||
| 
 | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
| 
 | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
| 
 | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
| 
 | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
| 
 | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
| 
 | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
| 
 | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
| 
 | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
| 
 | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
| 
 | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
| 
 | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
| 
 | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
| 
 | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
| 
 | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
| 
 | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
| 
 | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
| 
 | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
| 
 | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
| 
 | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
| 
 | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
| 
 | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
| 
 | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
| 
 | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
| 
 | ||||
|    END OF TERMS AND CONDITIONS | ||||
| 
 | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
| 
 | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "{}" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
| 
 | ||||
|    Copyright {yyyy} {name of copyright owner} | ||||
| 
 | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
| 
 | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
| 
 | ||||
| This Source Code Form is subject to the terms of the Mozilla | ||||
| Public License, v. 2.0. If a copy of the MPL was not distributed | ||||
| with this file, You can obtain one at | ||||
| https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
							
								
								
									
										206
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								README.md
									
									
									
									
									
								
							| @ -1,5 +1,4 @@ | ||||
| Did you mean DearDesi? | ||||
| ====================== | ||||
| # Did you mean DearDesi? | ||||
| 
 | ||||
| If you're looking for [DearDesi](http://dear.desi), the DIY blog platform for normal people | ||||
| you should go to <http://dear.desi>. | ||||
| @ -7,8 +6,7 @@ you should go to <http://dear.desi>. | ||||
| Desirae (this repo) is the code that programmers to use to make DearDesi better | ||||
| for everyone and to make Desirae-compatible blog platforms. (it's blog-ception!) | ||||
| 
 | ||||
| Desirae (v0.9) | ||||
| ======= | ||||
| # Desirae (v0.9) | ||||
| 
 | ||||
| Desirae is a static webpage compiler written in JavaScript. | ||||
| 
 | ||||
| @ -19,18 +17,17 @@ It can also run entirely from the commandline (with io.js / node.js). | ||||
| 
 | ||||
| **Features:** | ||||
| 
 | ||||
|   * `JavaScript` - it's so stable it takes 10 years to get new any features. | ||||
|     * Won't break every time you upgrade OS X and reinstall `brew` (*cough* ruby) | ||||
|   * Decent use of `try { ... } catch(e) ...` and `promise.catch()` | ||||
|     * the idea is that it shouldn't blow up at the slightest parse error without telling you which page is to blame (*cough* ruhoh *cough* jekyll)... bless me | ||||
|   * Browser (optional) | ||||
|     * using your front-end templates to build in your front-end? Imagine that! | ||||
|   * io.js (node.js) (optional) | ||||
|     * if you'd prefer to go headless, you can. | ||||
|   * The server is *very* minimal and could easily be implemented in any language (such as ruby or python). | ||||
| - `JavaScript` - it's so stable it takes 10 years to get new any features. | ||||
|   - Won't break every time you upgrade OS X and reinstall `brew` (_cough_ ruby) | ||||
| - Decent use of `try { ... } catch(e) ...` and `promise.catch()` | ||||
|   - the idea is that it shouldn't blow up at the slightest parse error without telling you which page is to blame (_cough_ ruhoh _cough_ jekyll)... bless me | ||||
| - Browser (optional) | ||||
|   - using your front-end templates to build in your front-end? Imagine that! | ||||
| - io.js (node.js) (optional) | ||||
|   - if you'd prefer to go headless, you can. | ||||
| - The server is _very_ minimal and could easily be implemented in any language (such as ruby or python). | ||||
| 
 | ||||
| Installation | ||||
| ============ | ||||
| # Installation | ||||
| 
 | ||||
| ```bash | ||||
| bower install --save desirae | ||||
| @ -38,8 +35,7 @@ bower install --save desirae | ||||
| npm install --save desirae | ||||
| ``` | ||||
| 
 | ||||
| Why | ||||
| === | ||||
| # Why | ||||
| 
 | ||||
| Because I hate ruby. | ||||
| 
 | ||||
| @ -51,8 +47,7 @@ more (last year I made dozens of gists and 0 blog posts) and I just couldn't get | ||||
| versions and gems and whatnot... so I gave up and wrote my own (because I needed something | ||||
| compatible with ruhoh). | ||||
| 
 | ||||
| Usage Overview | ||||
| ============== | ||||
| # Usage Overview | ||||
| 
 | ||||
| ### Before we get started | ||||
| 
 | ||||
| @ -70,11 +65,10 @@ It's an artifact of this project being born out of the ashes of my | ||||
| 
 | ||||
| ### Getting Started | ||||
| 
 | ||||
| First off you need to declare a state object that will be used in every *desirae* action. | ||||
| First off you need to declare a state object that will be used in every _desirae_ action. | ||||
| 
 | ||||
| ```javascript | ||||
| var desi = {} | ||||
|   ; | ||||
| var desi = {}; | ||||
| ``` | ||||
| 
 | ||||
| After that you'll load any plugins you need. | ||||
| @ -156,26 +150,24 @@ Desi.registerRenderer( | ||||
| ); | ||||
| ``` | ||||
| 
 | ||||
| And then you'll initialize Desirae with an *environment*. | ||||
| And then you'll initialize Desirae with an _environment_. | ||||
| 
 | ||||
| ```javascript | ||||
| Desirae.init( | ||||
|   desi | ||||
| , { url:                'https://johndoe.exmaple.com/blog' | ||||
|   , base_url:           'https://johndoe.exmaple.com' | ||||
|   , base_path:          '/blog' | ||||
|   , compiled_path:      'compiled_dev' | ||||
| Desirae.init(desi, { | ||||
|   url: "https://johndoe.exmaple.com/blog", | ||||
|   base_url: "https://johndoe.exmaple.com", | ||||
|   base_path: "/blog", | ||||
|   compiled_path: "compiled_dev", | ||||
| 
 | ||||
|                         // default: continue when possible | ||||
|   , onError:            function (e) { | ||||
|                           return Promise.reject(e); | ||||
|                         } | ||||
|   // default: continue when possible | ||||
|   onError: function (e) { | ||||
|     return Promise.reject(e); | ||||
|   }, | ||||
| 
 | ||||
|                         // io.js / node.js only | ||||
|   , working_path:       './path/to/blog' | ||||
|   } | ||||
| ).then(function () { | ||||
|   console.log('Desirae is initialized'); | ||||
|   // io.js / node.js only | ||||
|   working_path: "./path/to/blog", | ||||
| }).then(function () { | ||||
|   console.log("Desirae is initialized"); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| @ -198,43 +190,38 @@ Desirae.write(desi, env).then(function () { | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ### Plugins | ||||
| 
 | ||||
| You need to start every file with a wrapper that is browser and io.js/node.js compatible | ||||
| 
 | ||||
| ```javascript | ||||
| /*jshint -W054 */ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
|   | ||||
|   var DesiraeMyModule = {} | ||||
|     ; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   var DesiraeMyModule = {}; | ||||
|   // ... a bunch of code ... | ||||
| 
 | ||||
|   DesiraeMyModule.doStuff = doStuff; | ||||
| 
 | ||||
|   exports.DesiraeMyModule = DesiraeMyModule.DesiraeMyModule = DesiraeMyModule; | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
| ``` | ||||
| 
 | ||||
| Other than that, just be mindful that your code needs to run in both iojs/node and browser environments | ||||
| so steer away from things that are super iojs/node-ish or super window-ish. | ||||
| 
 | ||||
| Configuration | ||||
| ============= | ||||
| # Configuration | ||||
| 
 | ||||
| There are a few configuration files: | ||||
| 
 | ||||
| * `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc) | ||||
| * `authors/<<your-handle.yml>>` contains information about you (name, handle, facebook, etc) | ||||
| * `config.yml` contains directives that describe *how* the blog should be compiled - more technical stuff. | ||||
| - `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc) | ||||
| - `authors/<<your-handle.yml>>` contains information about you (name, handle, facebook, etc) | ||||
| - `config.yml` contains directives that describe _how_ the blog should be compiled - more technical stuff. | ||||
| 
 | ||||
| If any of these files change, the entire site needs to be retemplated. | ||||
| 
 | ||||
| API | ||||
| === | ||||
| # API | ||||
| 
 | ||||
| I'd like to make the plugin system connect-style API for adding plugins or whatever so that, | ||||
| for example, so you could have a custom markdown preprocessor (that handles includes, perhaps) | ||||
| @ -246,28 +233,26 @@ But here's what I've got so far: | ||||
| 
 | ||||
| ### (html, markdown, jade) | ||||
| 
 | ||||
| * `Desirae.registerRenderer(ext, fn)` | ||||
| - `Desirae.registerRenderer(ext, fn)` | ||||
| 
 | ||||
| For example, if you want to add the ability to render from `slim` or `haml` | ||||
| instead of just `markdown` you could find the appropriate | ||||
| JavaScript module (or make requests to an API service that renders them for you) and do this | ||||
| 
 | ||||
| ```javascript | ||||
| var slim = exports.slimjs || require('slimjs') | ||||
|   ; | ||||
| 
 | ||||
| function render(contentstr/*, desi*/) { | ||||
|   return PromiseA.resolve(slim(contentstr)); | ||||
| var slim = exports.slimjs || require("slimjs"); | ||||
| function render(contentstr /*, desi*/) { | ||||
|   return Promise.resolve(slim(contentstr)); | ||||
| } | ||||
| 
 | ||||
| Desirae.registerRenderer('.slim', render); | ||||
| Desirae.registerRenderer(".slim", render); | ||||
| ``` | ||||
| 
 | ||||
| ## Data Mapping | ||||
| 
 | ||||
| ### (desirae, ruhoh, etc) | ||||
| 
 | ||||
| * `Desirae.registerDataMapper(ext, fn)` | ||||
| - `Desirae.registerDataMapper(ext, fn)` | ||||
| 
 | ||||
| If you want to use a non-desirae theme that uses attributes in a different than | ||||
| how Desirae creates the view object internally | ||||
| @ -276,17 +261,17 @@ data mapper to accomplish this. | ||||
| 
 | ||||
| Please try not to modify the original object if you can avoid it. | ||||
| 
 | ||||
| *TODO: maybe pass in a two-level deep shallow copy ?* | ||||
| _TODO: maybe pass in a two-level deep shallow copy ?_ | ||||
| 
 | ||||
| ```javascript | ||||
| Desirae.registerDataMapper('ruhoh@3.0', function (view) { | ||||
| Desirae.registerDataMapper("ruhoh@3.0", function (view) { | ||||
|   return { | ||||
|     page: { | ||||
|       name: view.entity.title | ||||
|     } | ||||
|   , author: { | ||||
|       nickname: view.author.twitter | ||||
|     } | ||||
|       name: view.entity.title, | ||||
|     }, | ||||
|     author: { | ||||
|       nickname: view.author.twitter, | ||||
|     }, | ||||
|     // ... | ||||
|   }; | ||||
| }); | ||||
| @ -305,54 +290,54 @@ I'd love to work with anyone who is familiar with the Dropbox or similar APIs. | ||||
| 
 | ||||
| I think it would be awesome to support various means of storage. Perhaps github gists too. | ||||
| 
 | ||||
| 
 | ||||
| Server | ||||
| ====== | ||||
| # Server | ||||
| 
 | ||||
| Obviously there has to be a server with some sort of storage and retrieval mechanism. | ||||
| 
 | ||||
| I've implemented a very simple node server using the filesystem. | ||||
| 
 | ||||
| GET /api/fs/walk | ||||
| ------------ | ||||
| ## GET /api/fs/walk | ||||
| 
 | ||||
| `GET http://local.dear.desi:8080/api/fs/walk?dir=posts&dotfiles=true&extensions=md,markdown,jade,htm,html` | ||||
| 
 | ||||
| * `dir` **must** be supplied. returns a flat list of all files, recursively | ||||
| * `dotfiles` default to `false`. includes dotfiles when `true`. | ||||
| * `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`. | ||||
| - `dir` **must** be supplied. returns a flat list of all files, recursively | ||||
| - `dotfiles` default to `false`. includes dotfiles when `true`. | ||||
| - `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`. | ||||
| 
 | ||||
| ```json | ||||
| [ | ||||
|   { "name": "happy-new-year.md" | ||||
|   , "createdDate": "2015-01-05T18:19:30.000Z" | ||||
|   , "lastModifiedDate": "2015-01-05T18:19:30.000Z" | ||||
|   , "size": 2121 | ||||
|   , "relativePath": "posts/2015" | ||||
|   } | ||||
|   { | ||||
|     "name": "happy-new-year.md", | ||||
|     "createdDate": "2015-01-05T18:19:30.000Z", | ||||
|     "lastModifiedDate": "2015-01-05T18:19:30.000Z", | ||||
|     "size": 2121, | ||||
|     "relativePath": "posts/2015" | ||||
|   }, | ||||
| 
 | ||||
| , { "name": "tips-for-the-ages.jade" | ||||
|   , "createdDate": "2014-06-16T18:19:30.000Z" | ||||
|   , "lastModifiedDate": "2014-06-16T18:19:30.000Z" | ||||
|   , "size": 389 | ||||
|   , "relativePath": "posts" | ||||
|   } | ||||
| , { "name": "my-first-post.html" | ||||
|   , "createdDate": "2013-08-01T22:47:37.000Z" | ||||
|   , "lastModifiedDate": "2013-08-01T22:47:37.000Z" | ||||
|   , "size": 4118 | ||||
|   , "relativePath": "posts/2013" | ||||
|   { | ||||
|     "name": "tips-for-the-ages.jade", | ||||
|     "createdDate": "2014-06-16T18:19:30.000Z", | ||||
|     "lastModifiedDate": "2014-06-16T18:19:30.000Z", | ||||
|     "size": 389, | ||||
|     "relativePath": "posts" | ||||
|   }, | ||||
|   { | ||||
|     "name": "my-first-post.html", | ||||
|     "createdDate": "2013-08-01T22:47:37.000Z", | ||||
|     "lastModifiedDate": "2013-08-01T22:47:37.000Z", | ||||
|     "size": 4118, | ||||
|     "relativePath": "posts/2013" | ||||
|   } | ||||
| ] | ||||
| ``` | ||||
| 
 | ||||
| To retrieve multiple dir listings at once: | ||||
| 
 | ||||
| * for a few simple dirs without special chars just change `dir` to `dirs` and separate with commas | ||||
| - for a few simple dirs without special chars just change `dir` to `dirs` and separate with commas | ||||
| 
 | ||||
| `GET http://local.dear.desi:8080/api/fs/walk?dirs=posts/2015,posts/2013&dotfiles=true&extensions=md,markdown,jade,htm,html` | ||||
| 
 | ||||
| * for many dirs, or dirs with special chars, `POST` an object containing an array of `dirs` with  `&_method=GET` appended to the url. | ||||
| - for many dirs, or dirs with special chars, `POST` an object containing an array of `dirs` with `&_method=GET` appended to the url. | ||||
| 
 | ||||
| ``` | ||||
| POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET | ||||
| @ -367,27 +352,27 @@ POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdow | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| GET /api/fs/files | ||||
| ------------- | ||||
| ## GET /api/fs/files | ||||
| 
 | ||||
| `GET http://local.dear.desi:8080/api/fs/files?path=posts/happy-new-year.md` | ||||
| 
 | ||||
| ```json | ||||
| { "path": "posts/intro-to-http-with-netcat-node-connect.md" | ||||
| , "createdDate": "2013-08-01T22:47:37.000Z" | ||||
| , "lastModifiedDate": "2013-08-01T22:47:37.000Z" | ||||
| , "contents": "..." | ||||
| , "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46" | ||||
| { | ||||
|   "path": "posts/intro-to-http-with-netcat-node-connect.md", | ||||
|   "createdDate": "2013-08-01T22:47:37.000Z", | ||||
|   "lastModifiedDate": "2013-08-01T22:47:37.000Z", | ||||
|   "contents": "...", | ||||
|   "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| To retrieve multiple files at once: | ||||
| 
 | ||||
| * for a few simple files without special chars just change `path` to `paths` and separate with commas | ||||
| - for a few simple files without special chars just change `path` to `paths` and separate with commas | ||||
| 
 | ||||
| `GET http://local.dear.desi:8080/api/fs/files?paths=posts/foo.md,posts/bar.md` | ||||
| 
 | ||||
| * for many files, or files with special chars, `POST` an object containing an array of `pathss` with  `&_method=GET` appended to the url. | ||||
| - for many files, or files with special chars, `POST` an object containing an array of `pathss` with `&_method=GET` appended to the url. | ||||
| 
 | ||||
| ``` | ||||
| POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET | ||||
| @ -406,8 +391,7 @@ POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdo | ||||
| ] | ||||
| ``` | ||||
| 
 | ||||
| POST /api/fs/files | ||||
| ------------------ | ||||
| ## POST /api/fs/files | ||||
| 
 | ||||
| By default this should assume that you intended to write to the compiled directory | ||||
| and return an error if you try to write to any other directory, unless `compiled=false` (not yet implemented). | ||||
| @ -452,9 +436,15 @@ The response may include errors of all shapes and sizes. | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| POST /api/fs/copy | ||||
| ------------------ | ||||
| ## POST /api/fs/copy | ||||
| 
 | ||||
| ```json | ||||
| { files: { "assets/logo.png": "compiled/assets/logo.png" } } | ||||
| { "files": { "assets/logo.png": "compiled/assets/logo.png" } } | ||||
| ``` | ||||
| 
 | ||||
| # License | ||||
| 
 | ||||
| This Source Code Form is subject to the terms of the Mozilla \ | ||||
| Public License, v. 2.0. If a copy of the MPL was not distributed \ | ||||
| with this file, You can obtain one at \ | ||||
| https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
							
								
								
									
										44
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								TODO.md
									
									
									
									
									
								
							| @ -4,7 +4,6 @@ show file path | ||||
| show prod url | ||||
| show dev url | ||||
| 
 | ||||
| 
 | ||||
| POST tests | ||||
| create a title and delete it (no error) | ||||
| change the format. does the permalink change? (yes) | ||||
| @ -16,22 +15,17 @@ change the format in the frontmatter permalink. does the format change? (yes) | ||||
| create a description and delete it (no error) | ||||
| create a description. does the frontmatter change? (yes) | ||||
| 
 | ||||
| 
 | ||||
| protection | ||||
| Don't allow changing the uuid, original_url, or original_date | ||||
| 
 | ||||
| TODO | ||||
| --- | ||||
| ## TODO | ||||
| 
 | ||||
| check that no other post uses the same permalink | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| default data-model 'ruhoh@2.2' | ||||
| other data-model 'desirae@1.0' | ||||
| 
 | ||||
| Widgets | ||||
| ======= | ||||
| # Widgets | ||||
| 
 | ||||
| All widgets should export an object with a `create(widgetConf, desiState)` function that returns a promise. | ||||
| 
 | ||||
| @ -52,11 +46,10 @@ widgets: | ||||
| ``` | ||||
| 
 | ||||
| ```javascript | ||||
| 'use strict'; | ||||
| "use strict"; | ||||
| 
 | ||||
| module.exports.Foogizmo.create = function (foogizmoConf, desiState) { | ||||
|   return new Promise(function (resolve) { | ||||
| 
 | ||||
|     function pager(desiPageState) { | ||||
|       // Do processing | ||||
| 
 | ||||
| @ -68,29 +61,28 @@ module.exports.Foogizmo.create = function (foogizmoConf, desiState) { | ||||
| 
 | ||||
|       desiPostState.fooembedinator = function (fooval) { | ||||
|         // figure out what type of link fooval is and return iframe html | ||||
|         return '<iframe src="http://embedinator.com/"' + foovalProcessed + '></iframe>' | ||||
|       } | ||||
|         return ( | ||||
|           '<iframe src="http://embedinator.com/"' + | ||||
|           foovalProcessed + | ||||
|           "></iframe>" | ||||
|         ); | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
|     resolve({ foopager: pager, fooposter: poster }); | ||||
|   }); | ||||
| } | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| Overlays | ||||
| -------- | ||||
| ## Overlays | ||||
| 
 | ||||
| For any config a widget uses, it should also check on post.fooconfig and theme.fooconfig to make sure that they don't override the foogizmo.config.fooconfig | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| Migrating from Ruhoh | ||||
| ==================== | ||||
| # Migrating from Ruhoh | ||||
| 
 | ||||
| There are only a few things in Ruhoh that could only be done in ruby or were otherwise difficult to work around. | ||||
| 
 | ||||
| config.yml | ||||
| ---------- | ||||
| ## config.yml | ||||
| 
 | ||||
| Instead of having special names for some properties (`_root`) | ||||
| and `use` sub attributes for others (`twitter` theme, posts directory), | ||||
| @ -117,19 +109,15 @@ widgets [NO CHANGE] | ||||
| 
 | ||||
| All directories are ignored by default. If you want a directory to be interpreted as a collection of pages you need to specify it in the `collections` hash. | ||||
| 
 | ||||
| data.yml | ||||
| -------- | ||||
| ## data.yml | ||||
| 
 | ||||
| No changes | ||||
| 
 | ||||
| 
 | ||||
| config.ru | ||||
| --------- | ||||
| ## config.ru | ||||
| 
 | ||||
| REMOVED (ruby only) | ||||
| 
 | ||||
| themes layout | ||||
| ------------- | ||||
| ## themes layout | ||||
| 
 | ||||
| TODO | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										18
									
								
								bower.json
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								bower.json
									
									
									
									
									
								
							| @ -1,15 +1,10 @@ | ||||
| { | ||||
|   "name": "desirae", | ||||
|   "version": "0.11.2", | ||||
|   "authors": [ | ||||
|     "AJ ONeal <awesome@coolaj86.com>" | ||||
|   ], | ||||
|   "authors": ["AJ ONeal <awesome@coolaj86.com>"], | ||||
|   "description": "A blogging platform in the browser. Wow!", | ||||
|   "main": "desirae.js", | ||||
|   "moduleType": [ | ||||
|     "globals", | ||||
|     "node" | ||||
|   ], | ||||
|   "moduleType": ["globals", "node"], | ||||
|   "keywords": [ | ||||
|     "desirae", | ||||
|     "dear", | ||||
| @ -26,15 +21,8 @@ | ||||
|   ], | ||||
|   "license": "Apache2", | ||||
|   "homepage": "http://github.com/DearDesi/desirae", | ||||
|   "ignore": [ | ||||
|     "**/.*", | ||||
|     "node_modules", | ||||
|     "bower_components", | ||||
|     "test", | ||||
|     "tests" | ||||
|   ], | ||||
|   "ignore": ["**/.*", "node_modules", "bower_components", "test", "tests"], | ||||
|   "dependencies": { | ||||
|     "bluebird": "~2.6.2", | ||||
|     "escape-string-regexp": "~1.0.2", | ||||
|     "forEachAsync": "~5.0.5", | ||||
|     "js-yaml": "~3.2.5", | ||||
|  | ||||
							
								
								
									
										1159
									
								
								desirae.js
									
									
									
									
									
								
							
							
						
						
									
										1159
									
								
								desirae.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,25 +1,23 @@ | ||||
| /*jshint -W054 */ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var path = exports.path || require('path') | ||||
|     , months | ||||
|     , cores = {} | ||||
|     ; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   var path = exports.path || require("path"), | ||||
|     months, | ||||
|     cores = {}; | ||||
|   months = { | ||||
|     1: 'January' | ||||
|   , 2: 'February' | ||||
|   , 3: 'March' | ||||
|   , 4: 'April' | ||||
|   , 5: 'May' | ||||
|   , 6: 'June' | ||||
|   , 7: 'July' | ||||
|   , 8: 'August' | ||||
|   , 9: 'September' | ||||
|   , 10: 'October' | ||||
|   , 11: 'November' | ||||
|   , 12: 'December' | ||||
|     1: "January", | ||||
|     2: "February", | ||||
|     3: "March", | ||||
|     4: "April", | ||||
|     5: "May", | ||||
|     6: "June", | ||||
|     7: "July", | ||||
|     8: "August", | ||||
|     9: "September", | ||||
|     10: "October", | ||||
|     11: "November", | ||||
|     12: "December", | ||||
|   }; | ||||
| 
 | ||||
|   function byDate(a, b) { | ||||
| @ -67,14 +65,12 @@ | ||||
|   } | ||||
| 
 | ||||
|   function collate(entities, env) { | ||||
|     var yearsArr = [] | ||||
|       ; | ||||
| 
 | ||||
|     var yearsArr = []; | ||||
|     entities.forEach(function (f) { | ||||
|       var set | ||||
|         , yindex = 3000 - f.year | ||||
|         , mindex = 12 - f.month | ||||
|         ; | ||||
|       var set; | ||||
|       var yindex = 3000 - f.year; | ||||
|       var mindex = 12 - f.month; | ||||
|       var monthName = months[parseInt(f.month, 10)]; | ||||
| 
 | ||||
|       f.url = path.join(env.base_path, f.permalink); | ||||
| 
 | ||||
| @ -84,10 +80,17 @@ | ||||
|       set = yearsArr[yindex]; | ||||
| 
 | ||||
|       if (!set.months[mindex]) { | ||||
|         set.months[mindex] = { month: months[parseInt(f.month, 10)], pages: [] }; | ||||
|         set.months[mindex] = { | ||||
|           month_name: monthName, | ||||
|           month_number: mindex, | ||||
|           month: monthName, | ||||
|           pages: [], | ||||
|         }; | ||||
|       } | ||||
|       set = set.months[mindex]; | ||||
| 
 | ||||
|       f.month_name = months[parseInt(f.month, 10)]; | ||||
|       f.month_number = parseInt(f.month, 10); | ||||
|       set.pages.push(f); | ||||
|     }); | ||||
| 
 | ||||
| @ -118,4 +121,4 @@ | ||||
|   }; | ||||
| 
 | ||||
|   exports.DesiraeAggregateCore = cores.DesiraeAggregateCore = cores; | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
| @ -1,67 +1,66 @@ | ||||
| /*jshint -W054 */ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   function create(Desi) { | ||||
|     // Chrome, Firefox, and even MSIE11+ all support crypto
 | ||||
|     var crypto = window.crypto || window.msCrypto | ||||
|       , PromiseA = window.Promise | ||||
|       , algos | ||||
|       ; | ||||
|     var crypto = window.crypto || window.msCrypto, | ||||
|       Promise = window.Promise, | ||||
|       algos; | ||||
| 
 | ||||
|     // convenience mappings for common digest algorithms
 | ||||
|     algos = { | ||||
|       'sha1': 'SHA-1' | ||||
|     , 'sha256': 'SHA-256' | ||||
|     , 'sha512': 'SHA-512' | ||||
|       sha1: "SHA-1", | ||||
|       sha256: "SHA-256", | ||||
|       sha512: "SHA-512", | ||||
|     }; | ||||
| 
 | ||||
|     // The function to generate a sha1sum is the same as generating any digest
 | ||||
|     // but here's a shortcut function anyway
 | ||||
|     function sha1sum(str) { | ||||
|       return hashsum('sha1', str); | ||||
|       return hashsum("sha1", str); | ||||
|     } | ||||
| 
 | ||||
|     // a more general convenience function
 | ||||
|     function hashsum(hash, str) { | ||||
|         // you have to convert from string to array buffer
 | ||||
|       var ab | ||||
|       // you have to convert from string to array buffer
 | ||||
|       var ab, | ||||
|         // you have to represent the algorithm as an object
 | ||||
|         , algo = { name: algos[hash] } | ||||
|         ; | ||||
| 
 | ||||
|       if ('string' === typeof str) { | ||||
|         algo = { name: algos[hash] }; | ||||
|       if ("string" === typeof str) { | ||||
|         ab = str2ab(str); | ||||
|       } else { | ||||
|         ab = str; | ||||
|       } | ||||
| 
 | ||||
|       // All crypto digest methods return a promise
 | ||||
|       return crypto.subtle.digest(algo, ab).then(function (digest) { | ||||
|         // you have to convert the ArrayBuffer to a DataView and then to a hex String
 | ||||
|         return ab2hex(digest); | ||||
|       }).catch(function (e) { | ||||
|         // if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
 | ||||
|         console.error('sha1sum ERROR'); | ||||
|         console.error(e); | ||||
|         throw e; | ||||
|       }); | ||||
|       return crypto.subtle | ||||
|         .digest(algo, ab) | ||||
|         .then(function (digest) { | ||||
|           // you have to convert the ArrayBuffer to a DataView and then to a hex String
 | ||||
|           return ab2hex(digest); | ||||
|         }) | ||||
|         .catch(function (e) { | ||||
|           // if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
 | ||||
|           console.error("sha1sum ERROR"); | ||||
|           console.error(e); | ||||
|           throw e; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // convert from arraybuffer to hex
 | ||||
|     function ab2hex(ab) { | ||||
|       var dv = new DataView(ab) | ||||
|         , i | ||||
|         , len | ||||
|         , hex = '' | ||||
|         , c | ||||
|         ; | ||||
|       var dv = new DataView(ab), | ||||
|         i, | ||||
|         len, | ||||
|         hex = "", | ||||
|         c; | ||||
| 
 | ||||
|       for (i = 0, len = dv.byteLength; i < len; i += 1) { | ||||
|         c = dv.getUint8(i).toString(16); | ||||
| 
 | ||||
|         if (c.length < 2) { | ||||
|           c = '0' + c; | ||||
|           c = "0" + c; | ||||
|         } | ||||
| 
 | ||||
|         hex += c; | ||||
| @ -72,36 +71,31 @@ | ||||
| 
 | ||||
|     // convert from string to arraybuffer
 | ||||
|     function str2ab(stringToEncode, insertBom) { | ||||
|       stringToEncode = stringToEncode.replace(/\r\n/g,"\n"); | ||||
|       stringToEncode = stringToEncode.replace(/\r\n/g, "\n"); | ||||
| 
 | ||||
|       var utftext = [] | ||||
|         , n | ||||
|         , c | ||||
|         ; | ||||
|       var utftext = [], | ||||
|         n, | ||||
|         c; | ||||
| 
 | ||||
|       if (true === insertBom)  { | ||||
|         utftext[0] =  0xef; | ||||
|         utftext[1] =  0xbb; | ||||
|         utftext[2] =  0xbf; | ||||
|       if (true === insertBom) { | ||||
|         utftext[0] = 0xef; | ||||
|         utftext[1] = 0xbb; | ||||
|         utftext[2] = 0xbf; | ||||
|       } | ||||
| 
 | ||||
|       for (n = 0; n < stringToEncode.length; n += 1) { | ||||
| 
 | ||||
|         c = stringToEncode.charCodeAt(n); | ||||
| 
 | ||||
|         if (c < 128) { | ||||
|             utftext[utftext.length]= c; | ||||
|           utftext[utftext.length] = c; | ||||
|         } else if (c > 127 && c < 2048) { | ||||
|           utftext[utftext.length] = (c >> 6) | 192; | ||||
|           utftext[utftext.length] = (c & 63) | 128; | ||||
|         } else { | ||||
|           utftext[utftext.length] = (c >> 12) | 224; | ||||
|           utftext[utftext.length] = ((c >> 6) & 63) | 128; | ||||
|           utftext[utftext.length] = (c & 63) | 128; | ||||
|         } | ||||
|         else if((c > 127) && (c < 2048)) { | ||||
|             utftext[utftext.length] = (c >> 6) | 192; | ||||
|             utftext[utftext.length] = (c & 63) | 128; | ||||
|         } | ||||
|         else { | ||||
|             utftext[utftext.length] = (c >> 12) | 224; | ||||
|             utftext[utftext.length] = ((c >> 6) & 63) | 128; | ||||
|             utftext[utftext.length] = (c & 63) | 128; | ||||
|         } | ||||
| 
 | ||||
|       } | ||||
|       return new Uint8Array(utftext).buffer; | ||||
|     } | ||||
| @ -112,26 +106,21 @@ | ||||
|     //
 | ||||
|     // FSAPI
 | ||||
|     //
 | ||||
|     var fsapi | ||||
|       ; | ||||
|     var fsapi; | ||||
| 
 | ||||
|     function request() { | ||||
|     } | ||||
|     request.get = function (url/*, query*/) { | ||||
|     function request() {} | ||||
|     request.get = function (url /*, query*/) { | ||||
|       // Return a new promise.
 | ||||
|       return new PromiseA(function(resolve, reject) { | ||||
|       return new Promise(function (resolve, reject) { | ||||
|         // Do the usual XHR stuff
 | ||||
|         var req = new XMLHttpRequest() | ||||
|           ; | ||||
| 
 | ||||
|         req.onload = function() { | ||||
|         var req = new XMLHttpRequest(); | ||||
|         req.onload = function () { | ||||
|           // This is called even on 404 etc
 | ||||
|           // so check the status
 | ||||
|           if (200 === req.status) { | ||||
|             // Resolve the promise with the response text
 | ||||
|             resolve(req.response); | ||||
|           } | ||||
|           else { | ||||
|           } else { | ||||
|             // Otherwise reject with the status text
 | ||||
|             // which will hopefully be a meaningful error
 | ||||
|             reject(Error(req.statusText)); | ||||
| @ -139,30 +128,27 @@ | ||||
|         }; | ||||
| 
 | ||||
|         // Handle network errors
 | ||||
|         req.onerror = function() { | ||||
|         req.onerror = function () { | ||||
|           reject(Error("Network Error")); | ||||
|         }; | ||||
| 
 | ||||
|         // Make the request
 | ||||
|         req.open('GET', url); | ||||
|         req.open("GET", url); | ||||
|         req.send(); | ||||
|       }); | ||||
|     }; | ||||
|     request.post = function (url/*, query*/, body) { | ||||
|     request.post = function (url /*, query*/, body) { | ||||
|       // Return a new promise.
 | ||||
|       return new PromiseA(function(resolve, reject) { | ||||
|       return new Promise(function (resolve, reject) { | ||||
|         // Do the usual XHR stuff
 | ||||
|         var req = new XMLHttpRequest() | ||||
|           ; | ||||
| 
 | ||||
|         req.onload = function() { | ||||
|         var req = new XMLHttpRequest(); | ||||
|         req.onload = function () { | ||||
|           // This is called even on 404 etc
 | ||||
|           // so check the status
 | ||||
|           if (200 === req.status) { | ||||
|             // Resolve the promise with the response text
 | ||||
|             resolve(req.response); | ||||
|           } | ||||
|           else { | ||||
|           } else { | ||||
|             // Otherwise reject with the status text
 | ||||
|             // which will hopefully be a meaningful error
 | ||||
|             reject(Error(req.statusText)); | ||||
| @ -170,14 +156,14 @@ | ||||
|         }; | ||||
| 
 | ||||
|         // Handle network errors
 | ||||
|         req.onerror = function() { | ||||
|         req.onerror = function () { | ||||
|           reject(Error("Network Error")); | ||||
|         }; | ||||
| 
 | ||||
|         req.open('POST', url); | ||||
|         req.open("POST", url); | ||||
|         req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); | ||||
|         // Make the request
 | ||||
|         if ('string' !== typeof body) { | ||||
|         if ("string" !== typeof body) { | ||||
|           body = JSON.stringify(body); | ||||
|         } | ||||
|         req.send(body); | ||||
| @ -188,87 +174,93 @@ | ||||
|     fsapi.getMeta = function (collections, opts) { | ||||
|       opts = opts || {}; | ||||
| 
 | ||||
|       var extensions = '' | ||||
|         , dotfiles = '' | ||||
|         , contents = '' | ||||
|         , sha1sum = '' | ||||
|         ; | ||||
| 
 | ||||
|       var extensions = "", | ||||
|         dotfiles = "", | ||||
|         contents = "", | ||||
|         sha1sum = ""; | ||||
|       if (Array.isArray(opts.extensions)) { | ||||
|         extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
 | ||||
|         extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
 | ||||
|       } | ||||
|       if (opts.dotfiles) { | ||||
|         dotfiles = '&dotfiles=true'; | ||||
|         dotfiles = "&dotfiles=true"; | ||||
|       } | ||||
|       if (opts.contents) { | ||||
|         contents = '&contents=true'; | ||||
|         contents = "&contents=true"; | ||||
|       } | ||||
|       if (false === opts.sha1sum) { | ||||
|         sha1sum = '&sha1sum=false'; | ||||
|         sha1sum = "&sha1sum=false"; | ||||
|       } | ||||
| 
 | ||||
|       return request.post('/api/fs/walk?_method=GET' + dotfiles + extensions + contents + sha1sum, { | ||||
|         dirs: collections | ||||
|       }).then(function (resp) { | ||||
|         return JSON.parse(resp); | ||||
|       }).catch(function (e) { | ||||
|         throw e; | ||||
|       }); | ||||
|       return request | ||||
|         .post( | ||||
|           "/api/fs/walk?_method=GET" + | ||||
|             dotfiles + | ||||
|             extensions + | ||||
|             contents + | ||||
|             sha1sum, | ||||
|           { | ||||
|             dirs: collections, | ||||
|           } | ||||
|         ) | ||||
|         .then(function (resp) { | ||||
|           return JSON.parse(resp); | ||||
|         }) | ||||
|         .catch(function (e) { | ||||
|           throw e; | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     fsapi.getContents = function (filepaths) { | ||||
|       return request.post('/api/fs/files?_method=GET', { | ||||
|         paths: filepaths | ||||
|       }).then(function (resp) { | ||||
|         return JSON.parse(resp); | ||||
|       }); | ||||
|       return request | ||||
|         .post("/api/fs/files?_method=GET", { | ||||
|           paths: filepaths, | ||||
|         }) | ||||
|         .then(function (resp) { | ||||
|           return JSON.parse(resp); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     fsapi.getCache = function () { | ||||
|       return request.get('/api/fs/static/cache.json').then(function (resp) { | ||||
|         return JSON.parse(resp); | ||||
|       }).catch(function (/*e*/) { | ||||
|         return {}; | ||||
|       }).then(function (obj) { | ||||
|         return obj; | ||||
|       }); | ||||
|       return request | ||||
|         .get("/api/fs/static/cache.json") | ||||
|         .then(function (resp) { | ||||
|           return JSON.parse(resp); | ||||
|         }) | ||||
|         .catch(function (/*e*/) { | ||||
|           return {}; | ||||
|         }) | ||||
|         .then(function (obj) { | ||||
|           return obj; | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     fsapi.copy = function (files) { | ||||
|       var body = { files: files }; | ||||
|       body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
 | ||||
|       return request.post('/api/fs/copy', body).then(function (resp) { | ||||
|         var response = JSON.parse(resp) | ||||
|           ; | ||||
| 
 | ||||
|       return request.post("/api/fs/copy", body).then(function (resp) { | ||||
|         var response = JSON.parse(resp); | ||||
|         // not accurate for utf8/unicode, but close enough
 | ||||
|         response.size = body.length; | ||||
|         return response; | ||||
|       }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     fsapi.putFiles = function (files) { | ||||
|       var body = { files: files } | ||||
|         ; | ||||
| 
 | ||||
|       var body = { files: files }; | ||||
|       files.forEach(function (file) { | ||||
|         if (!file.contents || 'string' === typeof file.contents) { | ||||
|         if (!file.contents || "string" === typeof file.contents) { | ||||
|           return; | ||||
|         } | ||||
|         if (/\.json$/i.test(file.path)) { | ||||
|           file.contents = JSON.stringify(file.contents); | ||||
|         } | ||||
|         else if (/\.ya?ml$/i.test(file.path)) { | ||||
|         } else if (/\.ya?ml$/i.test(file.path)) { | ||||
|           file.contents = exports.jsyaml.dump(file.contents); | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
 | ||||
|       return request.post('/api/fs/files', body).then(function (resp) { | ||||
|         var response = JSON.parse(resp) | ||||
|           ; | ||||
| 
 | ||||
|       return request.post("/api/fs/files", body).then(function (resp) { | ||||
|         var response = JSON.parse(resp); | ||||
|         // not accurate for utf8/unicode, but close enough
 | ||||
|         response.size = body.length; | ||||
|         return response; | ||||
| @ -281,4 +273,4 @@ | ||||
|   } else { | ||||
|     exports.create = create; | ||||
|   } | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| 'use strict'; | ||||
| "use strict"; | ||||
| 
 | ||||
| module.exports.convert = function () { | ||||
|   console.error("I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do."); | ||||
|   console.error( | ||||
|     "I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do." | ||||
|   ); | ||||
|   console.error("see https://github.com/coolaj86/nuhoh/tree/master/MIGRATE.md"); | ||||
|   throw new Error('Not Implemented.'); | ||||
|   throw new Error("Not Implemented."); | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| /*jshint -W054 */ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   function desiMap(obj) { | ||||
|     obj.desi = obj; | ||||
| @ -8,4 +8,4 @@ | ||||
|   } | ||||
| 
 | ||||
|   exports.DesiraeDatamapCore = desiMap.DesiraeDatamapCore = desiMap; | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
| @ -1,19 +1,17 @@ | ||||
| /*jshint -W054 */ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   function create(Desi) { | ||||
|     Desi.YAML           = {}; | ||||
|     Desi.YAML.parse     = (exports.jsyaml || require('js-yaml')).load; | ||||
|     Desi.YAML.stringify = (exports.jsyaml || require('js-yaml')).dump; | ||||
|     Desi.YAML = {}; | ||||
|     Desi.YAML.parse = (exports.jsyaml || require("js-yaml")).load; | ||||
|     Desi.YAML.stringify = (exports.jsyaml || require("js-yaml")).dump; | ||||
| 
 | ||||
|     function readFrontMatter(text) { | ||||
|       var lines | ||||
|         , line | ||||
|         , padIndent = '' | ||||
|         , ymllines = [] | ||||
|         ; | ||||
| 
 | ||||
|       var lines, | ||||
|         line, | ||||
|         padIndent = "", | ||||
|         ymllines = []; | ||||
|       lines = text.split(/\n/); | ||||
|       line = lines.shift(); | ||||
| 
 | ||||
| @ -25,14 +23,14 @@ | ||||
|       // that start without indentation, so
 | ||||
|       // we can add it if this is the case
 | ||||
|       if (lines[0] && lines[0].match(/^\S/)) { | ||||
|         padIndent = ''; | ||||
|         padIndent = ""; | ||||
|       } | ||||
| 
 | ||||
|       while (true) { | ||||
|         line = lines.shift(); | ||||
| 
 | ||||
|         // premature end-of-file (unsupported yaml)
 | ||||
|         if (!line && '' !== line) { | ||||
|         if (!line && "" !== line) { | ||||
|           ymllines = []; | ||||
|           break; | ||||
|         } | ||||
| @ -48,20 +46,17 @@ | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       // XXX can't be sorted because arrays get messed up
 | ||||
|       //ymllines.sort();
 | ||||
|       if (ymllines) { | ||||
|         return '---\n' + ymllines.join('\n'); | ||||
|         return "---\n" + ymllines.join("\n"); | ||||
|       } | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     function separateText(text, fm) { | ||||
|       var len | ||||
|         , yml | ||||
|         ; | ||||
|       var len, yml; | ||||
| 
 | ||||
|       yml = readFrontMatter(fm); | ||||
|       // strip frontmatter from text, if any
 | ||||
| @ -72,27 +67,26 @@ | ||||
|         len = 0; | ||||
|       } | ||||
| 
 | ||||
|       return text.split(/\n/).slice(len).join('\n'); | ||||
|       return text.split(/\n/).slice(len).join("\n"); | ||||
|     } | ||||
| 
 | ||||
|     function parseText(text) { | ||||
|       var fm = readFrontMatter(text) | ||||
|         , body = fm && separateText(text, fm) | ||||
|         , yml | ||||
|         ; | ||||
|       var fm = readFrontMatter(text), | ||||
|         body = fm && separateText(text, fm), | ||||
|         yml; | ||||
| 
 | ||||
|       if (fm) { | ||||
|         try { | ||||
|           yml = Desi.YAML.parse(fm); | ||||
|         } catch(e) { | ||||
|         } catch (e) { | ||||
|           //
 | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return { | ||||
|         yml: yml | ||||
|       , frontmatter: fm | ||||
|       , body: body | ||||
|         yml: yml, | ||||
|         frontmatter: fm, | ||||
|         body: body, | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
| @ -112,4 +106,4 @@ | ||||
|   } else { | ||||
|     exports.create = create; | ||||
|   } | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
| @ -1,51 +1,47 @@ | ||||
| 'use strict'; | ||||
| "use strict"; | ||||
| 
 | ||||
| var PromiseA = require('bluebird').Promise | ||||
|   , fs = PromiseA.promisifyAll(require('fs')) | ||||
|   ; | ||||
| var fs = require('fs').promises; | ||||
| 
 | ||||
| function create(Desi, options) { | ||||
|   var fsapi = Desi.fsapi | ||||
|     ; | ||||
| 
 | ||||
|   var fsapi = Desi.fsapi; | ||||
|   options.blogdir = options.blogdir || options.working_path; | ||||
| 
 | ||||
|   fsapi.getMeta = function (dirnames, opts) { | ||||
|     opts = opts || {}; | ||||
| 
 | ||||
|     var extensions = '' | ||||
|       , dotfiles = '' | ||||
|       , contents = '' | ||||
|       , sha1sum = '' | ||||
|       ; | ||||
| 
 | ||||
|     var extensions = "", | ||||
|       dotfiles = "", | ||||
|       contents = "", | ||||
|       sha1sum = ""; | ||||
|     if (Array.isArray(opts.extensions)) { | ||||
|       extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
 | ||||
|       extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
 | ||||
|     } | ||||
|     if (opts.dotfiles) { | ||||
|       dotfiles = '&dotfiles=true'; | ||||
|       dotfiles = "&dotfiles=true"; | ||||
|     } | ||||
|     if (opts.contents) { | ||||
|       contents = '&contents=true'; | ||||
|       contents = "&contents=true"; | ||||
|     } | ||||
|     if (false === opts.sha1sum) { | ||||
|       sha1sum = '&sha1sum=false'; | ||||
|       sha1sum = "&sha1sum=false"; | ||||
|     } | ||||
| 
 | ||||
|     return fsapi.walk.walkDirs(options.blogdir, dirnames, opts); | ||||
|   }; | ||||
| 
 | ||||
|   fsapi.getContents = function (filepaths) { | ||||
| 
 | ||||
|     return fsapi.getfs(options.blogdir, filepaths); | ||||
|   }; | ||||
| 
 | ||||
|   fsapi.getCache = function () { | ||||
|     return fs.readFileAsync(options.blogdir, '/cache.json').catch(function (/*e*/) { | ||||
|       return {}; | ||||
|     }).then(function (obj) { | ||||
|       return obj; | ||||
|     }); | ||||
|     return fs | ||||
|       .readFile(options.blogdir, "/cache.json") | ||||
|       .catch(function (/*e*/) { | ||||
|         return {}; | ||||
|       }) | ||||
|       .then(function (obj) { | ||||
|         return obj; | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
|   fsapi.copy = function (files) { | ||||
| @ -55,13 +51,12 @@ function create(Desi, options) { | ||||
| 
 | ||||
|   fsapi.putFiles = function (files, opts) { | ||||
|     files.forEach(function (file) { | ||||
|       if (!file.contents || 'string' === typeof file.contents) { | ||||
|       if (!file.contents || "string" === typeof file.contents) { | ||||
|         return; | ||||
|       } | ||||
|       if (/\.json$/i.test(file.path)) { | ||||
|         file.contents = JSON.stringify(file.contents); | ||||
|       } | ||||
|       else if (/\.ya?ml$/i.test(file.path)) { | ||||
|       } else if (/\.ya?ml$/i.test(file.path)) { | ||||
|         file.contents = Desi.YAML.stringify(file.contents); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
| @ -1,18 +1,18 @@ | ||||
| 'use strict'; | ||||
| "use strict"; | ||||
| 
 | ||||
| var PromiseA      = require('bluebird').Promise | ||||
|   , fs            = PromiseA.promisifyAll(require('fs')) | ||||
|   , forEachAsync  = require('foreachasync').forEachAsync | ||||
|   , path          = require('path') | ||||
|   , walk          = require('walk') | ||||
|   , escapeRegExp  = require('escape-string-regexp') | ||||
|   , safeResolve   = require('../utils').safeResolve | ||||
|   , sha1sum       = function (str) { return require('secret-utils').hashsum('sha1', str); } | ||||
|   , mkdirp        = PromiseA.promisify(require('mkdirp')) | ||||
|   , fsExtra       = PromiseA.promisifyAll(require('fs.extra')) | ||||
|   //, tmpdir        = require('os').tmpdir()
 | ||||
|   ; | ||||
| var fs = require("fs").promises; | ||||
| var path = require("path"); | ||||
| 
 | ||||
| var forEachAsync = require("foreachasync").forEachAsync; | ||||
| var walk = require("walk"); | ||||
| var escapeRegExp = require("escape-string-regexp"); | ||||
| var safeResolve = require("../utils").safeResolve; | ||||
| var sha1sum = function (str) { | ||||
|   return require("secret-utils").hashsum("sha1", str); | ||||
| }; | ||||
| var copyAll = require("util").promisify(require("fs.extra").copy); | ||||
| 
 | ||||
| //, tmpdir        = require('os').tmpdir()
 | ||||
| function strip(prefix, pathname) { | ||||
|   return pathname.substr(prefix.length + 1); | ||||
| } | ||||
| @ -23,24 +23,24 @@ function walkDir(parent, sub, opts) { | ||||
|     opts.sha1sum = true; | ||||
|   } | ||||
| 
 | ||||
|   var prefix = path.resolve(parent) | ||||
|     , trueRoot = path.resolve(prefix, sub) | ||||
|     , files = [] | ||||
|     ; | ||||
| 
 | ||||
|   var prefix = path.resolve(parent); | ||||
|   var trueRoot = path.resolve(prefix, sub); | ||||
|   var files = []; | ||||
|   function filter(name) { | ||||
|     if (!name) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if (!opts.dotfiles && ('.' === name[0])) { | ||||
|     if (!opts.dotfiles && "." === name[0]) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if (opts.extensions && opts.extensions.length) { | ||||
|       if (!opts.extensions.some(function (ext) { | ||||
|         return new RegExp('\\.' + escapeRegExp(ext) + '$').test(name); | ||||
|       })) { | ||||
|       if ( | ||||
|         !opts.extensions.some(function (ext) { | ||||
|           return new RegExp("\\." + escapeRegExp(ext) + "$").test(name); | ||||
|         }) | ||||
|       ) { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
| @ -48,11 +48,9 @@ function walkDir(parent, sub, opts) { | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   return new PromiseA(function (resolve) { | ||||
|     var walker = walk.walk(trueRoot) | ||||
|       ; | ||||
| 
 | ||||
|     walker.on('nodeError', function (filepath, stat, next) { | ||||
|   return new Promise(function (resolve) { | ||||
|     var walker = walk.walk(trueRoot); | ||||
|     walker.on("nodeError", function (filepath, stat, next) { | ||||
|       //stats.forEach(function (stat) {
 | ||||
|       if (!filter(stat.name)) { | ||||
|         return; | ||||
| @ -60,59 +58,56 @@ function walkDir(parent, sub, opts) { | ||||
| 
 | ||||
|       stat.error.path = path.join(strip(prefix, filepath), stat.name); | ||||
|       files.push({ | ||||
|         name: stat.name | ||||
|       , relativePath: strip(prefix, filepath) | ||||
|       , path: path.join(strip(prefix, filepath), stat.name) | ||||
|         name: stat.name, | ||||
|         relativePath: strip(prefix, filepath), | ||||
|         path: path.join(strip(prefix, filepath), stat.name), | ||||
| 
 | ||||
|       , type: undefined | ||||
|       , error: stat.error | ||||
|         type: undefined, | ||||
|         error: stat.error, | ||||
|       }); | ||||
|       //});
 | ||||
| 
 | ||||
|       next(); | ||||
|     }); | ||||
| 
 | ||||
|     walker.on('files', function (root, stats, next) { | ||||
|       var dirname = strip(prefix, root) | ||||
|         ; | ||||
| 
 | ||||
|     walker.on("files", function (root, stats, next) { | ||||
|       var dirname = strip(prefix, root); | ||||
|       function eachFile(stat) { | ||||
|         var file | ||||
|           ; | ||||
|         var file; | ||||
| 
 | ||||
|         if (!filter(stat.name)) { | ||||
|           return PromiseA.resolve(); | ||||
|           return Promise.resolve(); | ||||
|         } | ||||
| 
 | ||||
|         file = { | ||||
|           name: stat.name | ||||
|         , relativePath: dirname | ||||
|         , path: path.join(dirname, stat.name) | ||||
|           name: stat.name, | ||||
|           relativePath: dirname, | ||||
|           path: path.join(dirname, stat.name), | ||||
| 
 | ||||
|         , createdDate: (stat.birthtime||stat.ctime).toISOString() | ||||
|         , lastModifiedDate: stat.mtime.toISOString() | ||||
|           createdDate: (stat.birthtime || stat.ctime).toISOString(), | ||||
|           lastModifiedDate: stat.mtime.toISOString(), | ||||
| 
 | ||||
|         , size: stat.size | ||||
|         , type: undefined // TODO include mimetype
 | ||||
|           size: stat.size, | ||||
|           type: undefined, // TODO include mimetype
 | ||||
|         }; | ||||
|         files.push(file); | ||||
| 
 | ||||
|         if (!(opts.sha1sum || opts.content)) { | ||||
|           return PromiseA.resolve(); | ||||
|           return Promise.resolve(); | ||||
|         } | ||||
| 
 | ||||
|         // TODO stream sha1 (for assets)
 | ||||
|         return fs.readFileAsync(path.join(root, stat.name), null).then(function (buffer) { | ||||
|           var contents = buffer.toString('utf8') | ||||
|             ; | ||||
|         return fs | ||||
|           .readFile(path.join(root, stat.name), null) | ||||
|           .then(function (buffer) { | ||||
|             var contents = buffer.toString("utf8"); | ||||
|             file.sha1 = sha1sum(contents); | ||||
|             file.type = undefined; | ||||
| 
 | ||||
|           file.sha1 = sha1sum(contents); | ||||
|           file.type = undefined; | ||||
| 
 | ||||
|           if (opts.contents) { | ||||
|             file.contents = contents; | ||||
|           } | ||||
|         }); | ||||
|             if (opts.contents) { | ||||
|               file.contents = contents; | ||||
|             } | ||||
|           }); | ||||
|       } | ||||
| 
 | ||||
|       if (!opts.contents) { | ||||
| @ -125,7 +120,7 @@ function walkDir(parent, sub, opts) { | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     walker.on('end', function () { | ||||
|     walker.on("end", function () { | ||||
|       resolve(files); | ||||
|     }); | ||||
|   }); | ||||
| @ -134,9 +129,7 @@ function walkDir(parent, sub, opts) { | ||||
| function walkDirs(parent, subs, opts) { | ||||
|   opts = opts || {}; | ||||
| 
 | ||||
|   var collections = {} | ||||
|     ; | ||||
| 
 | ||||
|   var collections = {}; | ||||
|   return forEachAsync(subs, function (sub) { | ||||
|     return walkDir(parent, sub, opts).then(function (results) { | ||||
|       collections[sub] = results; | ||||
| @ -146,59 +139,53 @@ function walkDirs(parent, subs, opts) { | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function getfs(blogdir, filepaths) { | ||||
|   var files = [] | ||||
|     ; | ||||
| 
 | ||||
|   var files = []; | ||||
|   return forEachAsync(filepaths, function (filepath) { | ||||
|     var pathname = safeResolve(blogdir, filepath) | ||||
|       ; | ||||
|     var pathname = safeResolve(blogdir, filepath); | ||||
|     return fs | ||||
|       .lstat(pathname) | ||||
|       .then(function (stat) { | ||||
|         return fs.readFile(pathname, null).then(function (buffer) { | ||||
|           files.push({ | ||||
|             name: path.basename(pathname), | ||||
|             relativePath: path.dirname(filepath), | ||||
|             path: filepath, | ||||
| 
 | ||||
|     return fs.lstatAsync(pathname).then(function (stat) { | ||||
|       return fs.readFileAsync(pathname, null).then(function (buffer) { | ||||
|             createdDate: (stat.birthtime || stat.ctime).toISOString(), | ||||
|             lastModifiedDate: stat.mtime.toISOString(), | ||||
| 
 | ||||
|         files.push({ | ||||
|           name: path.basename(pathname) | ||||
|         , relativePath: path.dirname(filepath) | ||||
|         , path: filepath | ||||
| 
 | ||||
|         , createdDate: (stat.birthtime||stat.ctime).toISOString() | ||||
|         , lastModifiedDate: stat.mtime.toISOString() | ||||
| 
 | ||||
|         , contents: buffer.toString('utf8') | ||||
|         , size: buffer.length | ||||
|         , sha1: sha1sum(buffer) | ||||
|         , type: undefined | ||||
|             contents: buffer.toString("utf8"), | ||||
|             size: buffer.length, | ||||
|             sha1: sha1sum(buffer), | ||||
|             type: undefined, | ||||
|           }); | ||||
|         }); | ||||
|       }) | ||||
|       .catch(function (e) { | ||||
|         files.push({ path: filepath, error: e.message }); | ||||
|       }); | ||||
|     }).catch(function (e) { | ||||
|       files.push({ path: filepath, error: e.message }); | ||||
|     }); | ||||
|   }).then(function () { | ||||
|     return files; | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function makeAllDirs(dirpaths) { | ||||
|   var errors = [] | ||||
|     ; | ||||
| 
 | ||||
|   var errors = []; | ||||
|   return forEachAsync(dirpaths, function (pathname) { | ||||
|     return mkdirp(pathname).catch(function (e) { | ||||
|     return fs.mkdir(pathname, { recursive: true }).catch(function (e) { | ||||
|       // TODO exclude attempting to write files to this dir?
 | ||||
|       errors.push({ | ||||
|         type: 'directory' | ||||
|         type: "directory", | ||||
| 
 | ||||
|       , directory: pathname | ||||
|         directory: pathname, | ||||
| 
 | ||||
|       , message: e.message | ||||
|       , code: e.code | ||||
|       , errno: e.errno | ||||
|       , status: e.status | ||||
|       , syscall: e.syscall | ||||
|         message: e.message, | ||||
|         code: e.code, | ||||
|         errno: e.errno, | ||||
|         status: e.status, | ||||
|         syscall: e.syscall, | ||||
|       }); | ||||
| 
 | ||||
|     }); | ||||
|   }).then(function () { | ||||
|     return errors; | ||||
| @ -207,156 +194,162 @@ function makeAllDirs(dirpaths) { | ||||
| 
 | ||||
| function copyfs(blogdir, files) { | ||||
|   // TODO switch format to { source: ..., dest: ..., opts: ... } ?
 | ||||
|   var results = { errors: [] } | ||||
|     , dirpaths = {} | ||||
|     , sources = Object.keys(files) | ||||
|     ; | ||||
| 
 | ||||
|   var results = { errors: [] }, | ||||
|     dirpaths = {}, | ||||
|     sources = Object.keys(files); | ||||
|   return forEachAsync(sources, function (source) { | ||||
|     /* | ||||
|     var nsource = safeResolve(blogdir, source) | ||||
|       ; | ||||
|     */ | ||||
| 
 | ||||
|     var dest = safeResolve(blogdir, files[source]) | ||||
|       , pathname = path.dirname(dest) | ||||
|       //, filename = path.basename(dest)
 | ||||
|       ; | ||||
| 
 | ||||
|     var dest = safeResolve(blogdir, files[source]), | ||||
|       pathname = path.dirname(dest); | ||||
|     //, filename = path.basename(dest)
 | ||||
|     dirpaths[pathname] = true; | ||||
| 
 | ||||
|     return PromiseA.resolve(); | ||||
|   }).then(function () { | ||||
|     // TODO is it better to do this lazy-like or as a batch?
 | ||||
|     // I figure as batch when there may be hundreds of files,
 | ||||
|     // likely within 2 or 3 directories
 | ||||
|     return makeAllDirs(Object.keys(dirpaths)).then(function (errors) { | ||||
|       errors.forEach(function (e) { | ||||
|         results.errors.push(e); | ||||
|       }); | ||||
|     }); | ||||
|   }).then(function () { | ||||
|     // TODO allow delete?
 | ||||
|     return forEachAsync(sources, function (source) { | ||||
|       return fsExtra.copyAsync( | ||||
|         safeResolve(blogdir, source) | ||||
|       , safeResolve(blogdir, files[source]) | ||||
|       , { replace: true } | ||||
|       ).catch(function (e) { | ||||
|         results.errors.push({ | ||||
|           type: 'file' | ||||
| 
 | ||||
|         , source: source | ||||
|         , destination: files[source] | ||||
| 
 | ||||
|         , message: e.message | ||||
|         , code: e.code | ||||
|         , errno: e.errno | ||||
|         , status: e.status | ||||
|         , syscall: e.syscall | ||||
|     return Promise.resolve(); | ||||
|   }) | ||||
|     .then(function () { | ||||
|       // TODO is it better to do this lazy-like or as a batch?
 | ||||
|       // I figure as batch when there may be hundreds of files,
 | ||||
|       // likely within 2 or 3 directories
 | ||||
|       return makeAllDirs(Object.keys(dirpaths)).then(function (errors) { | ||||
|         errors.forEach(function (e) { | ||||
|           results.errors.push(e); | ||||
|         }); | ||||
|       }); | ||||
|     }) | ||||
|     .then(function () { | ||||
|       // TODO allow delete?
 | ||||
|       return forEachAsync(sources, function (source) { | ||||
|         return copyAll( | ||||
|             safeResolve(blogdir, source), | ||||
|             safeResolve(blogdir, files[source]), | ||||
|             { replace: true } | ||||
|           ) | ||||
|           .catch(function (e) { | ||||
|             results.errors.push({ | ||||
|               type: "file", | ||||
| 
 | ||||
|               source: source, | ||||
|               destination: files[source], | ||||
| 
 | ||||
|               message: e.message, | ||||
|               code: e.code, | ||||
|               errno: e.errno, | ||||
|               status: e.status, | ||||
|               syscall: e.syscall, | ||||
|             }); | ||||
|           }); | ||||
|       }); | ||||
|     }) | ||||
|     .catch(function (e) { | ||||
|       results.error = { | ||||
|         message: e.message, | ||||
|         code: e.code, | ||||
|         errno: e.errno, | ||||
|         status: e.status, | ||||
|         syscall: e.syscall, | ||||
|       }; | ||||
|     }) | ||||
|     .then(function () { | ||||
|       return results; | ||||
|     }); | ||||
|   }).catch(function (e) { | ||||
|     results.error = { | ||||
|       message: e.message | ||||
|     , code: e.code | ||||
|     , errno: e.errno | ||||
|     , status: e.status | ||||
|     , syscall: e.syscall | ||||
|     }; | ||||
|   }).then(function () { | ||||
|     return results; | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function putfs(blogdir, files, options) { | ||||
|   options = options || {}; | ||||
| 
 | ||||
|   var putfsResults = { errors: [] } | ||||
|     , dirpaths = {} | ||||
|     ; | ||||
| 
 | ||||
|   var putfsResults = { errors: [] }, | ||||
|     dirpaths = {}; | ||||
|   return forEachAsync(files, function (file) { | ||||
|     var filepath = safeResolve(blogdir, file.path || path.join(file.relativePath, file.name)) | ||||
|       , pathname = path.dirname(filepath) | ||||
|       , filename = file.name || path.basename(filepath) | ||||
|       ; | ||||
| 
 | ||||
|     var filepath = safeResolve( | ||||
|         blogdir, | ||||
|         file.path || path.join(file.relativePath, file.name) | ||||
|       ), | ||||
|       pathname = path.dirname(filepath), | ||||
|       filename = file.name || path.basename(filepath); | ||||
|     file.realPath = filepath; | ||||
|     file.name = filename; | ||||
| 
 | ||||
|     dirpaths[pathname] = true; | ||||
| 
 | ||||
|     return PromiseA.resolve(); | ||||
|   }).then(function () { | ||||
|     // TODO is it better to do this lazy-like or as a batch?
 | ||||
|     // I figure as batch when there may be hundreds of files,
 | ||||
|     // likely within 2 or 3 directories
 | ||||
|     return forEachAsync(Object.keys(dirpaths), function (pathname) { | ||||
|       return mkdirp(pathname).catch(function (e) { | ||||
|         // TODO exclude attempting to write files to this dir?
 | ||||
|         putfsResults.errors.push({ | ||||
|           type: 'directory' | ||||
|     return Promise.resolve(); | ||||
|   }) | ||||
|     .then(function () { | ||||
|       // TODO is it better to do this lazy-like or as a batch?
 | ||||
|       // I figure as batch when there may be hundreds of files,
 | ||||
|       // likely within 2 or 3 directories
 | ||||
|       return forEachAsync(Object.keys(dirpaths), function (pathname) { | ||||
|         return fs.mkdir(pathname, { recursive: true }).catch(function (e) { | ||||
|           // TODO exclude attempting to write files to this dir?
 | ||||
|           putfsResults.errors.push({ | ||||
|             type: "directory", | ||||
| 
 | ||||
|         , directory: pathname | ||||
|             directory: pathname, | ||||
| 
 | ||||
|         , message: e.message | ||||
|         , code: e.code | ||||
|         , errno: e.errno | ||||
|         , status: e.status | ||||
|         , syscall: e.syscall | ||||
|         }); | ||||
| 
 | ||||
|       }); | ||||
|     }); | ||||
|   }).then(function () { | ||||
|     // TODO sort deletes last
 | ||||
|     return forEachAsync(files, function (file) { | ||||
|       // TODO use lastModifiedDate as per client request?
 | ||||
|       // TODO compare sha1 sums for integrity
 | ||||
|       // NOTE existsAsync is backwards
 | ||||
|       return fs.existsAsync(file.realPath).then(function () { | ||||
|         return fs.writeFileAsync(file.realPath, file.contents, 'utf8'); | ||||
|       }).catch(function (/*exists*/) { | ||||
|         if (file.delete || !file.contents) { | ||||
|           return fs.unlinkAsync(file.realPath); | ||||
|         } | ||||
| 
 | ||||
|         if (false === options.replace || false === options.overwrite) { | ||||
|           throw new Error('EEXIST: the file already exists'); | ||||
|         } | ||||
| 
 | ||||
|         return fs.writeFileAsync(file.realPath, file.contents, 'utf8'); | ||||
|       }).catch(function (e) { | ||||
|         putfsResults.errors.push({ | ||||
|           type: 'file' | ||||
| 
 | ||||
|         , file: file.realPath | ||||
|         , delete: !file.contents | ||||
|         , path: file.path | ||||
|         , relativePath: file.relativePath | ||||
|         , name: file.name | ||||
| 
 | ||||
|         , message: e.message | ||||
|         , code: e.code | ||||
|         , errno: e.errno | ||||
|         , status: e.status | ||||
|         , syscall: e.syscall | ||||
|             message: e.message, | ||||
|             code: e.code, | ||||
|             errno: e.errno, | ||||
|             status: e.status, | ||||
|             syscall: e.syscall, | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     }) | ||||
|     .then(function () { | ||||
|       // TODO sort deletes last
 | ||||
|       return forEachAsync(files, function (file) { | ||||
|         // TODO use lastModifiedDate as per client request?
 | ||||
|         // TODO compare sha1 sums for integrity
 | ||||
|         return fs | ||||
|           .access(file.realPath) | ||||
|           .then(function () { | ||||
|             if (file.delete || !file.contents) { | ||||
|               return fs.unlink(file.realPath); | ||||
|             } | ||||
| 
 | ||||
|             if (false === options.replace || false === options.overwrite) { | ||||
|               throw new Error("EEXIST: the file already exists"); | ||||
|             } | ||||
| 
 | ||||
|             return fs.writeFile(file.realPath, file.contents, "utf8"); | ||||
|           }) | ||||
|           .catch(function () { | ||||
|             return fs.writeFile(file.realPath, file.contents, "utf8"); | ||||
|           }) | ||||
|           .catch(function (e) { | ||||
|             putfsResults.errors.push({ | ||||
|               type: "file", | ||||
| 
 | ||||
|               file: file.realPath, | ||||
|               delete: !file.contents, | ||||
|               path: file.path, | ||||
|               relativePath: file.relativePath, | ||||
|               name: file.name, | ||||
| 
 | ||||
|               message: e.message, | ||||
|               code: e.code, | ||||
|               errno: e.errno, | ||||
|               status: e.status, | ||||
|               syscall: e.syscall, | ||||
|             }); | ||||
|           }); | ||||
|       }); | ||||
|     }) | ||||
|     .catch(function (e) { | ||||
|       putfsResults.error = { | ||||
|         message: e.message, | ||||
|         code: e.code, | ||||
|         errno: e.errno, | ||||
|         status: e.status, | ||||
|         syscall: e.syscall, | ||||
|       }; | ||||
|     }) | ||||
|     .then(function () { | ||||
|       return putfsResults; | ||||
|     }); | ||||
|   }).catch(function (e) { | ||||
|     putfsResults.error = { | ||||
|       message: e.message | ||||
|     , code: e.code | ||||
|     , errno: e.errno | ||||
|     , status: e.status | ||||
|     , syscall: e.syscall | ||||
|     }; | ||||
|   }).then(function () { | ||||
|     return putfsResults; | ||||
|   }); | ||||
| } | ||||
| /* | ||||
| walkDirs('blog', ['posts'], { contents: false }).then(function (stats) { | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| 'use strict'; | ||||
| "use strict"; | ||||
| 
 | ||||
| exports.fsapi = require('./fsapi'); | ||||
| exports.sha1sum = require('./sha1sum').sha1sum; | ||||
| exports.realFsapi = require('./fsapi-real'); | ||||
| exports.fsapi = require("./fsapi"); | ||||
| exports.sha1sum = require("./sha1sum").sha1sum; | ||||
| exports.realFsapi = require("./fsapi-real"); | ||||
|  | ||||
| @ -1,9 +1,7 @@ | ||||
| 'use strict'; | ||||
| "use strict"; | ||||
| 
 | ||||
| var PromiseA = require('bluebird').Promise | ||||
|   , secretutils = require('secret-utils') | ||||
|   ; | ||||
| var secretutils = require("secret-utils"); | ||||
| 
 | ||||
| module.exports.sha1sum = function (str) { | ||||
|   return PromiseA.resolve( secretutils.hashsum('sha1', str) ); | ||||
|   return Promise.resolve(secretutils.hashsum("sha1", str)); | ||||
| }; | ||||
|  | ||||
| @ -1,39 +1,35 @@ | ||||
| /*jshint -W054 */ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   var PromiseA = exports.Promise || require('bluebird').Promise | ||||
|     ; | ||||
| 
 | ||||
|   function renderMd(contentstr/*, desi*/) { | ||||
|     var markitdown = (exports.markdownit || require('markdown-it'))({ html: true, linkify: true }) | ||||
|       ; | ||||
| 
 | ||||
|     return PromiseA.resolve( | ||||
|   function renderMd(contentstr /*, desi*/) { | ||||
|     var markitdown = (exports.markdownit || require("markdown-it"))({ | ||||
|       html: true, | ||||
|       linkify: true, | ||||
|     }); | ||||
|     return Promise.resolve( | ||||
|       markitdown.render(contentstr) | ||||
|         //.replace('"', '"')
 | ||||
|         //.replace(''', "'")
 | ||||
|         //.replace('/', '/')
 | ||||
|       //.replace('"', '"')
 | ||||
|       //.replace(''', "'")
 | ||||
|       //.replace('/', '/')
 | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   function renderNoop(contentstr/*, desi*/) { | ||||
|   function renderNoop(contentstr /*, desi*/) { | ||||
|     // hmmm... that was easy
 | ||||
|     return PromiseA.resolve(contentstr); | ||||
|     return Promise.resolve(contentstr); | ||||
|   } | ||||
| 
 | ||||
|   function renderJade(contentstr, desi, options) { | ||||
|     options = options || {}; | ||||
|     if (!('pretty' in options)) { | ||||
|     if (!("pretty" in options)) { | ||||
|       options.pretty = true; | ||||
|     } | ||||
| 
 | ||||
|     var jade = (exports.jade || require('jade')) | ||||
|       , fn = jade.compile(contentstr, options) | ||||
|       , html = fn(desi) | ||||
|       ; | ||||
| 
 | ||||
|     return PromiseA.resolve(html); | ||||
|     var jade = exports.jade || require("jade"), | ||||
|       fn = jade.compile(contentstr, options), | ||||
|       html = fn(desi); | ||||
|     return Promise.resolve(html); | ||||
|   } | ||||
| 
 | ||||
|   exports.DesiraeRenderMarkdown = renderMd.DesiraeRenderMarkdown = renderMd; | ||||
| @ -41,4 +37,4 @@ | ||||
|   exports.DesiraeRenderCss = renderNoop.DesiraeRenderCss = renderNoop; | ||||
|   exports.DesiraeRenderJs = renderNoop.DesiraeRenderJs = renderNoop; | ||||
|   exports.DesiraeRenderJade = renderJade.DesiraeRenderJade = renderJade; | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
| @ -1,141 +1,151 @@ | ||||
| /*jshint -W054 */ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var cores = {} | ||||
|     , Desi = exports.Desirae || require('desirae').Desirae | ||||
|     , path          = exports.path          || require('path') | ||||
|     ; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   var cores = {}, | ||||
|     Desi = exports.Desirae || require("desirae").Desirae, | ||||
|     path = exports.path || require("path"); | ||||
|   cores.lint = function (desi, env, collection, entity) { | ||||
|     // TODO splice
 | ||||
|     //desi.content.collections = desi.content.collections.filter(function (entity) {
 | ||||
|       // TODO throw for any files that don't have a registered renderer
 | ||||
|       if (!entity.yml) { | ||||
|         if (!desi.config.empty_frontmatter) { | ||||
|           throw new Error("no frontmatter for " + (entity.path || entity.name) + "." | ||||
|             + "Set `config.yml.empty_frontmatter: include|skip` to ignore this error." | ||||
|           ); | ||||
|         } | ||||
| 
 | ||||
|         if ('include' === desi.config.empty_frontmatter) { | ||||
|           entity.yml = {}; | ||||
|         } | ||||
|         else if ('skip' === desi.config.empty_frontmatter) { | ||||
|           return false; | ||||
|         } | ||||
|         else { | ||||
|           throw new Error('unrecognize option ' + desi.config.empty_frontmatter +  | ||||
|             ' for `config.yml.empty_frontmatter: include|skip`.'); | ||||
|         } | ||||
|     // TODO throw for any files that don't have a registered renderer
 | ||||
|     if (!entity.yml) { | ||||
|       if (!desi.config.empty_frontmatter) { | ||||
|         throw new Error( | ||||
|           "no frontmatter for " + | ||||
|             (entity.path || entity.name) + | ||||
|             "." + | ||||
|             "Set `config.yml.empty_frontmatter: include|skip` to ignore this error." | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       if (!entity.body || !entity.body.trim()) { | ||||
|         if (!desi.config.empty_body) { | ||||
|           throw new Error('empty content file ' + (entity.path || entity.name) | ||||
|             + '. Set `config.yml.empty_body: include|skip` to ignore this error.' | ||||
|           ); | ||||
|         } | ||||
|       if ("include" === desi.config.empty_frontmatter) { | ||||
|         entity.yml = {}; | ||||
|       } else if ("skip" === desi.config.empty_frontmatter) { | ||||
|         return false; | ||||
|       } else { | ||||
|         throw new Error( | ||||
|           "unrecognize option " + | ||||
|             desi.config.empty_frontmatter + | ||||
|             " for `config.yml.empty_frontmatter: include|skip`." | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|         if ('include' === desi.config.empty_body) { | ||||
|           entity.body = ''; | ||||
|         } | ||||
|         else if ('skip' === desi.config.empty_body) { | ||||
|           return false; | ||||
|         } | ||||
|         else { | ||||
|           throw new Error('unrecognize option ' + desi.config.empty_frontmatter +  | ||||
|             ' for `config.yml.empty_body: include|skip`.'); | ||||
|         } | ||||
|     if (!entity.body || !entity.body.trim()) { | ||||
|       if (!desi.config.empty_body) { | ||||
|         throw new Error( | ||||
|           "empty content file " + | ||||
|             (entity.path || entity.name) + | ||||
|             ". Set `config.yml.empty_body: include|skip` to ignore this error." | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       return true; | ||||
|       if ("include" === desi.config.empty_body) { | ||||
|         entity.body = ""; | ||||
|       } else if ("skip" === desi.config.empty_body) { | ||||
|         return false; | ||||
|       } else { | ||||
|         throw new Error( | ||||
|           "unrecognize option " + | ||||
|             desi.config.empty_frontmatter + | ||||
|             " for `config.yml.empty_body: include|skip`." | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
|     //});
 | ||||
|   }; | ||||
| 
 | ||||
|   cores.root = function (desi, env, collection, entity) { | ||||
|     entity.yml = entity.yml || {}; | ||||
| 
 | ||||
|     entity.layout = entity.yml.layout || '__page__'; | ||||
|     entity.layout = entity.yml.layout || "__page__"; | ||||
| 
 | ||||
|     // _root is not subject to the same permalink rules as collections,
 | ||||
|     // so we just go ahead and define that here
 | ||||
|     if (/^index\.\w+$/.test(entity.path)) { | ||||
|       entity.permalink = '/'; | ||||
|       entity.permalink = "/"; | ||||
|     } else { | ||||
|       entity.permalink = entity.yml.permalink || entity.path.replace(/\.\w+$/, '/'); | ||||
|       entity.permalink = | ||||
|         entity.yml.permalink || entity.path.replace(/\.\w+$/, "/"); | ||||
|       entity.redirects = entity.redirects || []; | ||||
|       entity.redirects.push(entity.permalink.replace(/\/$/, '/index.html')); | ||||
|       entity.redirects.push(entity.permalink.replace(/\/$/, "/index.html")); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   cores.normalize = function (desi, env, collection, entity) { | ||||
|     entity.title          = entity.yml.title || Desi.firstCap(entity.name.replace(/\.\w+$/, '')); | ||||
|     entity.date           = entity.yml.date; | ||||
|     entity.title = | ||||
|       entity.yml.title || Desi.firstCap(entity.name.replace(/\.\w+$/, "")); | ||||
|     entity.date = entity.yml.date; | ||||
| 
 | ||||
|     if (!entity.date) { | ||||
|       // TODO tell YAML parser to keep the date a string
 | ||||
|       entity.date = new Date(entity.yml.created_at | ||||
|         || entity.yml.time | ||||
|         || entity.yml.updated_at | ||||
|         || entity.createdDate | ||||
|         || entity.lastModifiedDate | ||||
|       entity.date = new Date( | ||||
|         entity.yml.created_at || | ||||
|           entity.yml.time || | ||||
|           entity.yml.updated_at || | ||||
|           entity.createdDate || | ||||
|           entity.lastModifiedDate | ||||
|       ).toISOString(); | ||||
|     } | ||||
|     if ('object' === typeof entity.date) { | ||||
|     if ("object" === typeof entity.date) { | ||||
|       entity.date = entity.date.toISOString(); | ||||
|     } | ||||
| 
 | ||||
|     entity.updated_at     = entity.yml.updated_at || entity.lastModifiedDate; | ||||
|     entity.updated_at = entity.yml.updated_at || entity.lastModifiedDate; | ||||
| 
 | ||||
|     entity.published_at   = Desi.fromLocaleDate(entity.date || entity.lastModifiedDate); | ||||
|     entity.year           = entity.published_at.year; | ||||
|     entity.month          = entity.published_at.month; | ||||
|     entity.day            = entity.published_at.day; | ||||
|     entity.hour           = entity.published_at.hour; | ||||
|     entity.twelve_hour    = entity.published_at.twelve_hour; | ||||
|     entity.meridian       = entity.published_at.meridian; | ||||
|     entity.minute         = entity.published_at.minute; | ||||
|     entity.published_at = Desi.fromLocaleDate( | ||||
|       entity.date || entity.lastModifiedDate | ||||
|     ); | ||||
|     entity.year = entity.published_at.year; | ||||
|     entity.month = entity.published_at.month; | ||||
|     entity.day = entity.published_at.day; | ||||
|     entity.hour = entity.published_at.hour; | ||||
|     entity.twelve_hour = entity.published_at.twelve_hour; | ||||
|     entity.meridian = entity.published_at.meridian; | ||||
|     entity.minute = entity.published_at.minute; | ||||
|     // let's just agree that that's too far
 | ||||
|     //entity.second = entity.published_at.second;
 | ||||
| 
 | ||||
|     entity.slug           = Desi.slugify(entity.title); | ||||
|     entity.slug_path      = Desi.slugifyPath(entity.relativePath); | ||||
|     entity.slugPath       = Desi.slugifyPath(entity.relativePath); | ||||
|     entity.slug = Desi.slugify(entity.title); | ||||
|     entity.slug_path = Desi.slugifyPath(entity.relativePath); | ||||
|     entity.slugPath = Desi.slugifyPath(entity.relativePath); | ||||
|     // TODO type checking like below
 | ||||
|     entity.redirects      = Array.isArray(entity.yml.redirects) && entity.yml.redirects|| []; | ||||
|     entity.redirects = | ||||
|       (Array.isArray(entity.yml.redirects) && entity.yml.redirects) || []; | ||||
| 
 | ||||
|     // categories
 | ||||
|     if (Array.isArray(entity.yml.categories)) { | ||||
|       entity.categories   = entity.yml.categories; | ||||
|     } | ||||
|     else if ('string' === typeof entity.yml.categories) { | ||||
|       entity.categories   = [entity.yml.categories]; | ||||
|     } | ||||
|     else if ('string' === typeof entity.yml.category) { | ||||
|       entity.categories   = [entity.yml.category]; | ||||
|     } | ||||
|     else { | ||||
|       entity.categories   = []; | ||||
|       entity.categories = entity.yml.categories; | ||||
|     } else if ("string" === typeof entity.yml.categories) { | ||||
|       entity.categories = [entity.yml.categories]; | ||||
|     } else if ("string" === typeof entity.yml.category) { | ||||
|       entity.categories = [entity.yml.category]; | ||||
|     } else { | ||||
|       entity.categories = []; | ||||
|     } | ||||
| 
 | ||||
|     // tags
 | ||||
|     if (Array.isArray(entity.yml.tags)) { | ||||
|       entity.tags         = entity.yml.tags; | ||||
|     } | ||||
|     else if ('string' === typeof entity.yml.tags) { | ||||
|       entity.tags         = [entity.yml.tags]; | ||||
|     } | ||||
|     else { | ||||
|       entity.tags         = []; | ||||
|       entity.tags = entity.yml.tags; | ||||
|     } else if ("string" === typeof entity.yml.tags) { | ||||
|       entity.tags = [entity.yml.tags]; | ||||
|     } else { | ||||
|       entity.tags = []; | ||||
|     } | ||||
| 
 | ||||
|     entity.permalink      = entity.permalink || entity.yml.permalink; | ||||
|     entity.permalink = entity.permalink || entity.yml.permalink; | ||||
| 
 | ||||
|     if (!entity.permalink) { | ||||
|       // try the fallback_permalink first (because we're looking at files that don't have yml)
 | ||||
|       // then try the normal permalink (because :filename -> :title and whatnot, so it'll work)
 | ||||
|       entity.permalink = Desi.permalinkify(desi, collection.fallback_permalink || collection.permalink, entity); | ||||
|       entity.permalink = Desi.permalinkify( | ||||
|         desi, | ||||
|         collection.fallback_permalink || collection.permalink, | ||||
|         entity | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
| @ -145,38 +155,51 @@ | ||||
|     */ | ||||
| 
 | ||||
|     // relative to the site
 | ||||
|     entity.relative_file  = path.join(env.base_path, entity.permalink) | ||||
|                               .replace(/\/$/, '/index.html'); | ||||
|     entity.relative_href  = path.join(env.base_path, entity.permalink) | ||||
|                               .replace(/\/index\.html$/, '/'); | ||||
|     entity.relative_link  = entity.relative_href; | ||||
|     entity.relative_file = path | ||||
|       .join(env.base_path, entity.permalink) | ||||
|       .replace(/\/$/, "/index.html"); | ||||
|     entity.relative_href = path | ||||
|       .join(env.base_path, entity.permalink) | ||||
|       .replace(/\/index\.html$/, "/"); | ||||
|     entity.relative_link = entity.relative_href; | ||||
| 
 | ||||
|     entity.url            = env.base_url + path.join(env.base_path, entity.permalink) | ||||
|                               .replace(/\/index\.html$/, '/'); | ||||
|     entity.canonical_url  = env.base_url + path.join(env.base_path, entity.permalink) | ||||
|                               .replace(/\/index\.html$/, '/'); | ||||
|     entity.production_url = desi.site.base_url + path.join(desi.site.base_path, entity.permalink) | ||||
|                               .replace(/\/index\.html$/, '/'); | ||||
|     entity.relative_url   = path.join(env.base_path, entity.permalink) | ||||
|                               .replace(/\/index\.html$/, '/'); | ||||
|     entity.url = | ||||
|       env.base_url + | ||||
|       path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/"); | ||||
|     entity.canonical_url = | ||||
|       env.base_url + | ||||
|       path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/"); | ||||
|     entity.production_url = | ||||
|       desi.site.base_url + | ||||
|       path | ||||
|         .join(desi.site.base_path, entity.permalink) | ||||
|         .replace(/\/index\.html$/, "/"); | ||||
|     entity.relative_url = path | ||||
|       .join(env.base_path, entity.permalink) | ||||
|       .replace(/\/index\.html$/, "/"); | ||||
| 
 | ||||
|     if (env.explicitIndexes || env.explicitIndices || env.explicit_indexes || env.explicit_indices) { | ||||
|     if ( | ||||
|       env.explicitIndexes || | ||||
|       env.explicitIndices || | ||||
|       env.explicit_indexes || | ||||
|       env.explicit_indices | ||||
|     ) { | ||||
|       // NOTE: file_url is NOT replaced
 | ||||
|       ['url', 'canonical_url', 'production_url', 'relative_url'].forEach(function (url) { | ||||
|         entity[url] = entity[url].replace(/\/$/, '/index.html'); | ||||
|       }); | ||||
|       ["url", "canonical_url", "production_url", "relative_url"].forEach( | ||||
|         function (url) { | ||||
|           entity[url] = entity[url].replace(/\/$/, "/index.html"); | ||||
|         } | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     // i.e. bootstrap, hero page, darkly
 | ||||
|     entity.theme          = entity.theme || entity.yml.theme; | ||||
|     entity.layout         = entity.layout || entity.yml.layout; | ||||
|     entity.swatch         = entity.swatch || entity.yml.swatch; | ||||
|     entity.theme = entity.theme || entity.yml.theme; | ||||
|     entity.layout = entity.layout || entity.yml.layout; | ||||
|     entity.swatch = entity.swatch || entity.yml.swatch; | ||||
|   }; | ||||
| 
 | ||||
|   cores.disqus = function (desi, env, collection, entity) { | ||||
|     var yml = entity.yml | ||||
|       ; | ||||
| 
 | ||||
|     var yml = entity.yml; | ||||
|     if (yml.uuid) { | ||||
|       entity.disqus_identifier = yml.uuid; | ||||
|     } | ||||
| @ -184,4 +207,4 @@ | ||||
|   }; | ||||
| 
 | ||||
|   exports.DesiraeTransformCore = cores.DesiraeTransformCore = cores; | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
							
								
								
									
										101
									
								
								lib/utils.js
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								lib/utils.js
									
									
									
									
									
								
							| @ -1,9 +1,7 @@ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| 
 | ||||
|   var path  = exports.path  || require('path') | ||||
|     ; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   var path = exports.path || require("path"); | ||||
|   function escapeRegExp(str) { | ||||
|     return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | ||||
|   } | ||||
| @ -11,10 +9,8 @@ | ||||
|   function safeResolve(basename, target) { | ||||
|     basename = path.resolve(basename); | ||||
| 
 | ||||
|     var targetname = path.resolve(basename, target) | ||||
|       , re = new RegExp('^' + escapeRegExp(basename) + '(/|$)') | ||||
|       ; | ||||
| 
 | ||||
|     var targetname = path.resolve(basename, target), | ||||
|       re = new RegExp("^" + escapeRegExp(basename) + "(/|$)"); | ||||
|     return re.test(targetname) && targetname; | ||||
|   } | ||||
| 
 | ||||
| @ -22,29 +18,24 @@ | ||||
|   exports.escapeRegExp = escapeRegExp; | ||||
| 
 | ||||
|   function create(Desi) { | ||||
|     var fsapi = Desi.fsapi || require('./node-adapters').fsapi | ||||
|       ; | ||||
| 
 | ||||
|     var fsapi = Desi.fsapi || require("./node-adapters").fsapi; | ||||
|     fsapi.getConfigs = function (confs) { | ||||
|       var opts = { extensions: ['yml', 'yaml', 'json'], dotfiles: false, contents: true, sha1sum: true } | ||||
|         ; | ||||
| 
 | ||||
|       var opts = { | ||||
|         extensions: ["yml", "yaml", "json"], | ||||
|         dotfiles: false, | ||||
|         contents: true, | ||||
|         sha1sum: true, | ||||
|       }; | ||||
|       return fsapi.getMeta(confs, opts).then(function (collections) { | ||||
|         var obj = {} | ||||
|           ; | ||||
| 
 | ||||
|         var obj = {}; | ||||
|         Object.keys(collections).forEach(function (key) { | ||||
|           var files = collections[key] | ||||
|             , keyname = key.replace(/\.(json|ya?ml|\/)$/i, '') | ||||
|             ; | ||||
| 
 | ||||
|           var files = collections[key], | ||||
|             keyname = key.replace(/\.(json|ya?ml|\/)$/i, ""); | ||||
|           obj[keyname] = obj[keyname] || {}; | ||||
| 
 | ||||
|           files.forEach(function (file) { | ||||
|             var filename = file.name.replace(/\.(json|ya?ml)$/i, '') | ||||
|               , data = {} | ||||
|               ; | ||||
| 
 | ||||
|             var filename = file.name.replace(/\.(json|ya?ml)$/i, ""), | ||||
|               data = {}; | ||||
|             if (file.error) { | ||||
|               console.error(file); | ||||
|               console.error(file.error); | ||||
| @ -57,17 +48,16 @@ | ||||
|                 if ("undefined" === data) { | ||||
|                   data = {}; | ||||
|                 } | ||||
|               } catch(e) { | ||||
|               } catch (e) { | ||||
|                 data = { error: e }; | ||||
|                 console.error("Could not parse yaml for " + filename); | ||||
|                 console.error(file); | ||||
|                 console.error(e); | ||||
|               } | ||||
|             } | ||||
|             else if (/\.(json)$/i.test(file.name)) { | ||||
|             } else if (/\.(json)$/i.test(file.name)) { | ||||
|               try { | ||||
|                 data = JSON.parse(file.contents) || {}; | ||||
|               } catch(e) { | ||||
|               } catch (e) { | ||||
|                 data = { error: e }; | ||||
|                 console.error("Could not parse json for " + filename); | ||||
|                 console.error(file); | ||||
| @ -99,36 +89,34 @@ | ||||
|     }; | ||||
| 
 | ||||
|     fsapi.getAllPartials = function () { | ||||
|       return fsapi.getConfigs(['partials', 'partials.yml']).then(function (results) { | ||||
|         var partials = {} | ||||
|           ; | ||||
|       return fsapi | ||||
|         .getConfigs(["partials", "partials.yml"]) | ||||
|         .then(function (results) { | ||||
|           var partials = {}; | ||||
|           Object.keys(results.partials).forEach(function (key) { | ||||
|             var partial = results.partials[key]; | ||||
|             Object.keys(partial).forEach(function (prop) { | ||||
|               if (partials[prop]) { | ||||
|                 console.warn("partial '" + prop + "' overwritten by " + key); | ||||
|               } | ||||
| 
 | ||||
|         Object.keys(results.partials).forEach(function (key) { | ||||
|           var partial = results.partials[key] | ||||
|             ; | ||||
| 
 | ||||
|           Object.keys(partial).forEach(function (prop) { | ||||
|             if (partials[prop]) { | ||||
|               console.warn('partial \'' + prop + '\' overwritten by ' + key); | ||||
|             } | ||||
| 
 | ||||
|             partials[prop] = partial[prop]; | ||||
|               partials[prop] = partial[prop]; | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
| 
 | ||||
|         return partials; | ||||
|       }); | ||||
|           return partials; | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     fsapi.getAllConfigFiles = function () { | ||||
|       return fsapi.getConfigs(['config.yml', 'site.yml', 'authors']).then(function (results) { | ||||
|         var authors = results.authors | ||||
|           , config = results.config.config | ||||
|           , site = results.site.site | ||||
|           ; | ||||
| 
 | ||||
|         return { config: config, authors: authors, site: site }; | ||||
|       }); | ||||
|       return fsapi | ||||
|         .getConfigs(["config.yml", "site.yml", "authors"]) | ||||
|         .then(function (results) { | ||||
|           var authors = results.authors, | ||||
|             config = results.config.config, | ||||
|             site = results.site.site; | ||||
|           return { config: config, authors: authors, site: site }; | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     return exports; | ||||
| @ -136,8 +124,7 @@ | ||||
| 
 | ||||
|   if (exports.Desirae) { | ||||
|     create(exports.Desirae); | ||||
|   } | ||||
|   else { | ||||
|   } else { | ||||
|     exports.create = create; | ||||
|   } | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| ;(function (exports) { | ||||
|   'use strict'; | ||||
| (function (exports) { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   exports.verifyConfig = function (conf) { | ||||
|     if (!conf.NuhohSpec) { | ||||
| @ -28,14 +28,18 @@ | ||||
| 
 | ||||
|     if (!Array.isArray(conf.collections)) { | ||||
|       if (conf.posts) { | ||||
|         console.error("Please indent and nest 'posts' under the key 'collection' to continue"); | ||||
|         console.error( | ||||
|           "Please indent and nest 'posts' under the key 'collection' to continue" | ||||
|         ); | ||||
|       } | ||||
|       throw new Error("missing key 'collections'."); | ||||
|     } | ||||
| 
 | ||||
|     if (!conf.themes) { | ||||
|       if (conf.twitter) { | ||||
|         console.error("Please indent and nest 'twitter' under the key 'themes' to continue"); | ||||
|         console.error( | ||||
|           "Please indent and nest 'twitter' under the key 'themes' to continue" | ||||
|         ); | ||||
|       } | ||||
|       throw new Error("missing key 'themes'"); | ||||
|     } | ||||
| @ -55,4 +59,4 @@ | ||||
|       throw new Error("missing key root"); | ||||
|     } | ||||
|   }; | ||||
| }('undefined' !== typeof exports && exports || window)); | ||||
| })(("undefined" !== typeof exports && exports) || window); | ||||
|  | ||||
							
								
								
									
										175
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,175 @@ | ||||
| { | ||||
|   "name": "desirae", | ||||
|   "version": "0.12.4", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|     "argparse": { | ||||
|       "version": "1.0.10", | ||||
|       "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", | ||||
|       "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", | ||||
|       "requires": { | ||||
|         "sprintf-js": "~1.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "crypto-rand": { | ||||
|       "version": "0.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/crypto-rand/-/crypto-rand-0.0.2.tgz", | ||||
|       "integrity": "sha1-Hn3CMQLhiRo+6zQPtwrElYCzLt0=" | ||||
|     }, | ||||
|     "escape-string-regexp": { | ||||
|       "version": "1.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", | ||||
|       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" | ||||
|     }, | ||||
|     "esprima": { | ||||
|       "version": "4.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", | ||||
|       "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" | ||||
|     }, | ||||
|     "foreachasync": { | ||||
|       "version": "5.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-5.1.3.tgz", | ||||
|       "integrity": "sha512-q3/l5D2pyZG8aYe9xZQDAnS/KyP7QJakLUNrU/qGkd4bQUTzCUmvVFjagRggoxxjtb1ZrJB+jQeG4ojOLWkztw==" | ||||
|     }, | ||||
|     "fs-extra": { | ||||
|       "version": "0.6.4", | ||||
|       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", | ||||
|       "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", | ||||
|       "requires": { | ||||
|         "jsonfile": "~1.0.1", | ||||
|         "mkdirp": "0.3.x", | ||||
|         "ncp": "~0.4.2", | ||||
|         "rimraf": "~2.2.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "mkdirp": { | ||||
|           "version": "0.3.5", | ||||
|           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", | ||||
|           "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "fs.extra": { | ||||
|       "version": "1.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", | ||||
|       "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", | ||||
|       "requires": { | ||||
|         "fs-extra": "~0.6.1", | ||||
|         "mkdirp": "~0.3.5", | ||||
|         "walk": "^2.3.9" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "mkdirp": { | ||||
|           "version": "0.3.5", | ||||
|           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", | ||||
|           "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "js-yaml": { | ||||
|       "version": "3.14.0", | ||||
|       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", | ||||
|       "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", | ||||
|       "requires": { | ||||
|         "argparse": "^1.0.7", | ||||
|         "esprima": "^4.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "jsonfile": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", | ||||
|       "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=" | ||||
|     }, | ||||
|     "linkify-it": { | ||||
|       "version": "0.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-0.1.5.tgz", | ||||
|       "integrity": "sha1-OMWD0y+pPtcm2gDHrwAQeL+2uUU=", | ||||
|       "requires": { | ||||
|         "uc.micro": "^1.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "uc.micro": { | ||||
|           "version": "1.0.6", | ||||
|           "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", | ||||
|           "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "markdown-it": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-3.1.0.tgz", | ||||
|       "integrity": "sha1-IKcejmexKXyWrEfQD3tuaQ1uDDY=", | ||||
|       "requires": { | ||||
|         "argparse": "~ 1.0.0", | ||||
|         "linkify-it": "~ 0.1.2", | ||||
|         "mdurl": "~ 1.0.0", | ||||
|         "uc.micro": "~ 0.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "mdurl": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", | ||||
|       "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" | ||||
|     }, | ||||
|     "mustache": { | ||||
|       "version": "2.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", | ||||
|       "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==" | ||||
|     }, | ||||
|     "ncp": { | ||||
|       "version": "0.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", | ||||
|       "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=" | ||||
|     }, | ||||
|     "node-uuid": { | ||||
|       "version": "1.4.8", | ||||
|       "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", | ||||
|       "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" | ||||
|     }, | ||||
|     "rimraf": { | ||||
|       "version": "2.2.8", | ||||
|       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", | ||||
|       "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" | ||||
|     }, | ||||
|     "secret-utils": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/secret-utils/-/secret-utils-1.0.2.tgz", | ||||
|       "integrity": "sha1-88GhRhCpTH+gMX3RVqj+AJgDb6c=", | ||||
|       "requires": { | ||||
|         "crypto-rand": "0.0.2", | ||||
|         "urlsafe-base64": "0.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "sprintf-js": { | ||||
|       "version": "1.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", | ||||
|       "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" | ||||
|     }, | ||||
|     "uc.micro": { | ||||
|       "version": "0.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-0.1.0.tgz", | ||||
|       "integrity": "sha1-7aESHR/blhVO1v3oJHu724MzCMo=" | ||||
|     }, | ||||
|     "urlsafe-base64": { | ||||
|       "version": "0.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-0.0.2.tgz", | ||||
|       "integrity": "sha1-+VqmedXqb86RQtY8tXCd4x7r7UI=" | ||||
|     }, | ||||
|     "walk": { | ||||
|       "version": "2.3.14", | ||||
|       "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz", | ||||
|       "integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==", | ||||
|       "requires": { | ||||
|         "foreachasync": "^3.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "foreachasync": { | ||||
|           "version": "3.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", | ||||
|           "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=" | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										19
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								package.json
									
									
									
									
									
								
							| @ -1,14 +1,15 @@ | ||||
| { | ||||
|   "name": "desirae", | ||||
|   "version": "0.11.2", | ||||
|   "version": "0.12.4", | ||||
|   "description": "An in-browser static blog library and static site generator. Similar to Jekyll, Octopress, Nanoc, etc", | ||||
|   "main": "desirae.js", | ||||
|   "scripts": { | ||||
|     "prettier": "prettier --write './**/*.{js,css,html,json,md,py,xml}'", | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git@git.daplie.com:Daplie/desirae.git" | ||||
|     "type": "git+https", | ||||
|     "url": "https://git.coolaj86.com/coolaj86/desirae.js.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "dear", | ||||
| @ -22,21 +23,19 @@ | ||||
|     "octopress", | ||||
|     "nanoc" | ||||
|   ], | ||||
|   "author": "AJ ONeal", | ||||
|   "license": "Apache2", | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "MPL-2.0", | ||||
|   "bugs": { | ||||
|     "url": "https://git.daplie.com/Daplie/desirae/issues" | ||||
|     "url": "https://git.coolaj86.com/coolaj86/desirae.js/issues" | ||||
|   }, | ||||
|   "homepage": "https://git.daplie.com/Daplie/desirae", | ||||
|   "homepage": "https://git.coolaj86.com/coolaj86/desirae.js", | ||||
|   "dependencies": { | ||||
|     "bluebird": "^2.5.3", | ||||
|     "escape-string-regexp": "^1.0.2", | ||||
|     "foreachasync": "^5.0.5", | ||||
|     "fs.extra": "^1.3.2", | ||||
|     "js-yaml": "^3.2.5", | ||||
|     "markdown-it": "^3.0.2", | ||||
|     "mkdirp": "^0.5.0", | ||||
|     "mustache": "^1.0.0", | ||||
|     "mustache": "^2.3.2", | ||||
|     "node-uuid": "^1.4.2", | ||||
|     "secret-utils": "^1.0.2", | ||||
|     "walk": "^2.3.9" | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| 'use strict'; | ||||
| "use strict"; | ||||
| 
 | ||||
| // http://ruhoh.com/docs/2/pages/#toc_41
 | ||||
| /* | ||||
| @ -6,32 +6,31 @@ | ||||
|   Otherwise it should use the the permalink for that collection. | ||||
| */ | ||||
| 
 | ||||
| var tags | ||||
|   , permalinkTransforms | ||||
|   , cases | ||||
|   , path    = /*exports.path ||*/ require('path') | ||||
|   ; | ||||
|    | ||||
| var tags, | ||||
|   permalinkTransforms, | ||||
|   cases, | ||||
|   path = /*exports.path ||*/ require("path"); | ||||
| tags = { | ||||
|   year:           "Year from the page’s filename" | ||||
| , month:          "Month from the page’s filename" | ||||
| , day:            "Day from the page’s filename" | ||||
| , path:           "The page file's path relative to the base of your website." | ||||
| , relative_path:  "The page file's path relative to its name-spaced directory." | ||||
| , filename:       "The page file's filename (path is not included)." | ||||
| , categories:     "The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter." | ||||
| , i_month:        "Month from the page’s filename without leading zeros." | ||||
| , i_day:          "Day from the page’s filename without leading zeros." | ||||
| , title:          "The title, as a slug." | ||||
| , slug:           "alias of title" | ||||
| , name:           "alias of title" | ||||
| , collection:     "i.e. posts/ or essays/ or whatever/" | ||||
|   year: "Year from the page’s filename", | ||||
|   month: "Month from the page’s filename", | ||||
|   day: "Day from the page’s filename", | ||||
|   path: "The page file's path relative to the base of your website.", | ||||
|   relative_path: "The page file's path relative to its name-spaced directory.", | ||||
|   filename: "The page file's filename (path is not included).", | ||||
|   categories: | ||||
|     "The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter.", | ||||
|   i_month: "Month from the page’s filename without leading zeros.", | ||||
|   i_day: "Day from the page’s filename without leading zeros.", | ||||
|   title: "The title, as a slug.", | ||||
|   slug: "alias of title", | ||||
|   name: "alias of title", | ||||
|   collection: "i.e. posts/ or essays/ or whatever/", | ||||
| }; | ||||
| 
 | ||||
| function pad(str, n) { | ||||
|   str = str.toString(); | ||||
|   if (str.length < n) { | ||||
|     str = '0' + str; | ||||
|     str = "0" + str; | ||||
|   } | ||||
| 
 | ||||
|   return str; | ||||
| @ -39,49 +38,51 @@ function pad(str, n) { | ||||
| 
 | ||||
| // https://www.youtube.com/watch?v=1NryFD9_hR0&list=RDOeLUK4a6Ojc&index=2
 | ||||
| cases = { | ||||
|   "/:title.html"                  : "/my-title.html" | ||||
| , ":title/"                       : "/my-title/" | ||||
| , "/:bad/:title/"                 : "/:bad/my-title/" | ||||
| , "/:slug/"                       : "/my-title/" | ||||
| , "/:path/:name.html"             : "/posts/fun/my-title.html" | ||||
| , "/:relative_path/:name/"        : "/fun/my-title/" | ||||
| , "/:year-:month-:day/:name"      : "/2015-07-04/my-title/" | ||||
| , "/:year/:i_month/:i_day/:name"  : "/2015/7/4/my-title/" | ||||
| , "/:filename.html"               : "/my-file-name.html" | ||||
| , "/:filename"                    : "/my-file-name/" | ||||
| , "/:filename/"                   : "/my-file-name/" | ||||
| , "/:collection/:title/"          : "/posts/my-title/" | ||||
| , "/:collection/:filename"        : "/posts/my-file-name/" | ||||
| , "/:something/:or/:other"        : "/:something/:or/:other/" | ||||
| , "/:categories/:title/"          : "/desi/my-title/" | ||||
|   "/:title.html": "/my-title.html", | ||||
|   ":title/": "/my-title/", | ||||
|   "/:bad/:title/": "/:bad/my-title/", | ||||
|   "/:slug/": "/my-title/", | ||||
|   "/:path/:name.html": "/posts/fun/my-title.html", | ||||
|   "/:relative_path/:name/": "/fun/my-title/", | ||||
|   "/:year-:month-:day/:name": "/2015-07-04/my-title/", | ||||
|   "/:year/:i_month/:i_day/:name": "/2015/7/4/my-title/", | ||||
|   "/:filename.html": "/my-file-name.html", | ||||
|   "/:filename": "/my-file-name/", | ||||
|   "/:filename/": "/my-file-name/", | ||||
|   "/:collection/:title/": "/posts/my-title/", | ||||
|   "/:collection/:filename": "/posts/my-file-name/", | ||||
|   "/:something/:or/:other": "/:something/:or/:other/", | ||||
|   "/:categories/:title/": "/desi/my-title/", | ||||
| }; | ||||
| 
 | ||||
| Object.keys(cases).forEach(function (tpl) { | ||||
|   var entity | ||||
|     , tpld | ||||
|     ; | ||||
|   var entity, tpld; | ||||
| 
 | ||||
|   entity = { | ||||
|     year          : '2015' | ||||
|   , month         : '07' | ||||
|   , day           : '04' | ||||
|   , title         : "My Title" | ||||
|   , slug          : "my-title" | ||||
|   , name          : "My-File-Name.html" | ||||
|   , relativePath  : "posts/fun" | ||||
|   , path          : "posts/fun/My-File-Name.html" | ||||
|   , collection    : "posts" | ||||
|   , yml           : { categories: ['desi'] } | ||||
|     year: "2015", | ||||
|     month: "07", | ||||
|     day: "04", | ||||
|     title: "My Title", | ||||
|     slug: "my-title", | ||||
|     name: "My-File-Name.html", | ||||
|     relativePath: "posts/fun", | ||||
|     path: "posts/fun/My-File-Name.html", | ||||
|     collection: "posts", | ||||
|     yml: { categories: ["desi"] }, | ||||
|   }; | ||||
| 
 | ||||
|   tpld = permalinker(tpl, entity); | ||||
| 
 | ||||
|   if (cases[tpl] !== tpld) { | ||||
|     console.error('[ERROR]'); | ||||
|     console.error(tpl + ' ' + tpld + ' ' + cases[tpl]); | ||||
|     console.error("[ERROR]"); | ||||
|     console.error(tpl + " " + tpld + " " + cases[tpl]); | ||||
|     throw new Error( | ||||
|       "Did not template permalink correctly. " | ||||
|       + tpl + ' ' + tpld + ' ' + cases[tpl] | ||||
|       "Did not template permalink correctly. " + | ||||
|         tpl + | ||||
|         " " + | ||||
|         tpld + | ||||
|         " " + | ||||
|         cases[tpl] | ||||
|     ); | ||||
|   } | ||||
| }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user