├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── cli └── makeproject.js ├── lib ├── dist │ ├── addConfigsToJson.js │ ├── addToJson.js │ ├── createGitIgnore.js │ ├── createJsonFromConfigs.js │ ├── findFirstPosition.js │ ├── insertToString.js │ ├── replaceHTML.js │ └── rewritePage.js ├── feedback │ ├── CHALKS.js │ ├── MESSAGES.js │ ├── alertUser.js │ └── terminal.js ├── helper │ ├── makemy │ │ ├── makemy.js │ │ ├── posts │ │ │ └── my-first-post.md │ │ └── template.html │ └── projectCreator.js ├── makemy.js ├── tools │ ├── createPage │ │ ├── createPage.js │ │ └── options │ │ │ └── OPTIONS.js │ ├── markdownParser │ │ └── markdownParser.js │ ├── sugarParser │ │ └── sugarParser.js │ ├── updateJson │ │ ├── options │ │ │ └── OPTIONS.js │ │ └── updateJson.js │ └── updateTemplates │ │ ├── options │ │ └── OPTIONS.js │ │ └── updateTemplates.js └── utils │ ├── CORE_INFO.js │ ├── HTML_FOR_POST │ ├── allTags.js │ ├── createHTML.js │ └── tags │ │ ├── .js │ │ ├──
.js │ │ └── .js │ ├── SYNTAX.js │ ├── checkers │ ├── checkMode.js │ ├── checkOptions.js │ ├── doesExist.js │ ├── doesPageExist.js │ └── lookForSyntaxTitles.js │ ├── client-files │ ├── code.js │ └── styling.css │ ├── configs │ └── createConfigForPost.js │ ├── createCustomItems │ └── createdQuotee.js │ ├── getters │ ├── findPostFile.js │ ├── findPreviousMode.js │ ├── findVoluntaryOptions.js │ └── getBirthtime.js │ ├── readwrite │ ├── HTML-WRITERS │ │ └── insertToHeadTag.js │ ├── configureReadStream.js │ ├── configureWriteStream.js │ └── directory-utils │ │ └── scanItems.js │ ├── setters │ ├── configureClientFiles.js │ ├── setClass.js │ ├── setHeadContent.js │ ├── setTag.js │ └── setTitleLevel.js │ └── validators │ ├── validateCoreInfo.js │ ├── validateFolderForThisPost.js │ ├── validateJson.js │ ├── validateLocationFolder.js │ ├── validatePostsFolder.js │ ├── validateSourceFolder.js │ └── validateTemplate.js ├── media └── makemy.png ├── package.json ├── readme.md └── test ├── .gitignore ├── client.js ├── index.html ├── json.js ├── page.js ├── posts-src ├── first-post.md ├── i-like-pancakes.txt ├── i-like-waffles.txt ├── pure-markdown.md └── second-post.md ├── posts ├── allPosts.json ├── c.js ├── first-post │ ├── .post.config │ └── index.html ├── i-like-pancakes │ ├── .post.config │ └── index.html ├── i-like-waffles │ ├── .post.config │ └── index.html ├── pure-markdown │ ├── .post.config │ └── index.html └── s.css ├── readme.md ├── styles.css ├── template.html └── template.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test 3 | media 4 | CONTRIBUTING.md -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '12' 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Pull requests are awesome. If you have fixes/ideas for the project, then feel free to send a pull request. All requests will be checked. 4 | 5 | Steps for contrinbuting: 6 | 7 | 1. Fork, then clone the repo. 8 | 9 | 2. Make your change. Add tests for your change. Make the tests pass: 10 | 11 | 3. Push to your fork and submit a pull request. 12 | 13 | At this point you're waiting on us. We like to at least comment on pull requests within three business days (and, typically, one business day). We may suggest some changes or improvements or alternatives. 14 | 15 | Some things that will increase the chance that your pull request is accepted: 16 | 17 | Write tests. 18 | Write readable code. 19 | Write a good commit message. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | 179 | Copyright [2020] [Mathias Picker] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /cli/makeproject.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const makemy = require('makemy'); 4 | 5 | makemy.init(process.cwd()); 6 | -------------------------------------------------------------------------------- /lib/dist/addConfigsToJson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const configureWriteStream = require('../utils/readwrite/configureWriteStream'); 7 | 8 | /** 9 | * addConfigsToJson creates the allPosts.json file with an array of all the config files found. 10 | * 11 | * Note: This function is somewhat similar to the addToJson.js function, but i decided to split them 12 | * up to keep things cleaner. 13 | * There is no reason to do the parse-validation and check if everything looks good, as the JSON file 14 | * was reset in the validation check before this part. 15 | * 16 | * @param {Path} directory - The pathname for the posts folder. 17 | * @param {Oject} info - An object containing the core-info about the post we want to add to our JSON file. 18 | * @param {String} option - Options for the JSON file 19 | * 20 | */ 21 | 22 | async function addConfigsToJson(directory, info, option) { 23 | try { 24 | const allPostJsonFile = path.join(directory, 'allPosts.json'); 25 | 26 | const json = await fs.promises.readFile(allPostJsonFile); 27 | 28 | let allPosts = JSON.parse(json); 29 | 30 | info.forEach(i => allPosts.posts.push(i)); 31 | 32 | if (option.toLowerCase() === 'ascending') { 33 | allPosts.posts.sort((a, b) => 34 | a.creationDateMS > b.creationDateMS ? -1 : 1 35 | ); 36 | } 37 | if (option.toLowerCase() === 'descending') { 38 | allPosts.posts.sort((a, b) => 39 | a.creationDateMS > b.creationDateMS ? 1 : -1 40 | ); 41 | } 42 | 43 | const updatedjson = JSON.stringify(allPosts); 44 | 45 | configureWriteStream(allPostJsonFile).write(updatedjson); 46 | } catch (error) { 47 | throw Error(error); 48 | } 49 | } 50 | 51 | module.exports = addConfigsToJson; 52 | -------------------------------------------------------------------------------- /lib/dist/addToJson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const alertUser = require('../feedback/alertUser'); 7 | 8 | const configureWriteStream = require('../utils/readwrite/configureWriteStream'); 9 | 10 | /** 11 | * addToJson adds the core-info about a post to a allPosts.json file which servers as a 'storage' of all the post files. 12 | * This file can be used with a fetch()-call on the client so that the user can use it on their site. 13 | * 14 | * @param {Path} directory - The pathname for the posts folder. 15 | * @param {Oject} info - An object containing the core-info about the post we want to add to our JSON file. 16 | * @param {Boolean} update - Telling if we are in update mode or not 17 | * @param {String} option - Voluntary argument, used in jsonFromConfigs. 18 | */ 19 | 20 | async function addToJson(directory, info, update) { 21 | /** 22 | * 23 | * The approach in this function is as follows: 24 | * 1. Check if the allPosts.json file exists, and if it doesn't add it to the posts folder. 25 | * 2. Try to parse the allPosts.json file. If it goes wrong, it's either because it's empty or because it has wrong formatting. 26 | * Tell the user what has been done; either if was stopping the process because of wrong formatting or creating a new template because the JSON was empty. 27 | * 3. Add the post to the JSON file. If we are in update-mode, then find the post and replace it. Else, add it to the top of the JSON file (last posts come first). 28 | * 4. Some specific error handling, just in case user has done something funny. 29 | * 30 | */ 31 | 32 | try { 33 | const allPostJsonFile = path.join(directory, 'allPosts.json'); 34 | 35 | let json = await fs.promises.readFile(allPostJsonFile); 36 | 37 | let allPosts = JSON.parse(json); 38 | 39 | const alreadyExists = allPosts.posts.find(post => post.url === info.url); 40 | 41 | if (update && alreadyExists) { 42 | // If we want to update the post then remove it and add the new version at the same place. 43 | const indexOfPost = allPosts.posts.indexOf(alreadyExists); 44 | 45 | const updatedPost = allPosts.posts[indexOfPost]; 46 | 47 | updatedPost.name = info.name; 48 | updatedPost.introduction = info.introduction; 49 | 50 | allPosts.posts[indexOfPost] = updatedPost; 51 | } else { 52 | allPosts.posts.unshift(info); 53 | } 54 | 55 | /** 56 | * If a post has the same name and introduction (maybe the user copied it), 57 | * then tell them that a post with the same name and introduction already exists but with a different url. 58 | * 59 | * Note: This error handling is quote specific, so if the setup of the preview-info of the posts gets changed, then this has 60 | * to be changed aswell. 61 | */ 62 | 63 | const siblingPost = allPosts.posts.find( 64 | post => 65 | post.name === info.name && 66 | post.introduction === info.introduction && 67 | post.url !== info.url 68 | ); 69 | 70 | const equalPostFeedback = sameDate => { 71 | alertUser('json', 'sibling', { sameDate }); 72 | }; 73 | 74 | // Subtracting 2 because we don't care about the creationDate and creationDateMS 75 | let counter = Object.keys(info).length - 2; 76 | 77 | if (counter > 0) { 78 | for (const key in siblingPost) { 79 | if (key !== 'creationDate' || key !== 'creationDateMS') 80 | if (siblingPost[key] === info[key]) { 81 | counter--; 82 | } 83 | } 84 | 85 | switch (counter) { 86 | case 1: 87 | equalPostFeedback(true); 88 | break; 89 | 90 | case 2: 91 | equalPostFeedback(false); 92 | break; 93 | } 94 | } 95 | 96 | /** 97 | * Sorting posts by creation date 98 | */ 99 | 100 | allPosts.posts.sort((a, b) => 101 | a.creationDateMS > b.creationDateMS ? -1 : 1 102 | ); 103 | 104 | const updatedjson = JSON.stringify(allPosts); 105 | 106 | configureWriteStream(allPostJsonFile).write(updatedjson); 107 | 108 | if (update && alreadyExists) { 109 | alertUser('json', 'complete', { 110 | version: 'updated in the allPosts.json file 🔁' 111 | }); 112 | } else { 113 | alertUser('json', 'complete', { 114 | version: 'added to the the allPosts.json file ✅' 115 | }); 116 | } 117 | } catch (error) { 118 | throw Error(error); 119 | } 120 | } 121 | 122 | module.exports = addToJson; 123 | -------------------------------------------------------------------------------- /lib/dist/createGitIgnore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const configureReadStream = require('../utils/readwrite/configureReadStream'); 6 | const configureWriteStream = require('../utils/readwrite/configureWriteStream'); 7 | 8 | const doesExist = require('../utils/checkers/doesExist'); 9 | 10 | const alertUser = require('../feedback/alertUser'); 11 | 12 | /** 13 | * Here the 'makemy' folder gets added to the .gitignore in the users project 14 | * so that it doesn't get uploaded to their website (if using github). 15 | * 16 | * Todo: Add some more specific configs in case the user doesn't use makemy folder. 17 | * @param {Path|String} directory - the directory of where the makemy function runs 18 | * @param {String} location - the path to the 'posts'-folder 19 | */ 20 | 21 | async function createGitIgnore(directory, location) { 22 | try { 23 | const rootPath = path.join(directory, location); 24 | const alreadyExists = await doesExist(rootPath, '.gitignore'); 25 | 26 | let gitignore = ''; 27 | 28 | if (alreadyExists) { 29 | const rl = configureReadStream(path.join(rootPath, '.gitignore')); 30 | 31 | for await (const line of rl) { 32 | gitignore += `${line} \n`; 33 | 34 | if (line.includes('makemy') && line.length === 'makemy'.length) { 35 | return; 36 | } 37 | } 38 | } else if (!alreadyExists) { 39 | alertUser('gitignore', 'created'); 40 | } 41 | 42 | gitignore += 'makemy'; 43 | 44 | alertUser('gitignore', 'added'); 45 | 46 | configureWriteStream(path.join(rootPath, '.gitignore')).write(gitignore); 47 | } catch (error) { 48 | throw Error(error); 49 | } 50 | } 51 | 52 | module.exports = createGitIgnore; 53 | -------------------------------------------------------------------------------- /lib/dist/createJsonFromConfigs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | const terminal = require('../feedback/terminal'); 7 | const configureReadStream = require('../utils/readwrite/configureReadStream'); 8 | const addConfigsToJson = require('./addConfigsToJson'); 9 | 10 | const scanItems = require('../utils/readwrite/directory-utils/scanItems'); 11 | 12 | /** 13 | * jsonFromConfigs searches through the posts folder and finds all the posts (validation is by checking if a path is a folder). 14 | * Note: readDirectory is a recursive function for searching through the folder-hierarchy. 15 | * @param {Path} directory - The pathname for the posts folder. 16 | * @param {Object/String} option - The options passed for generating the JSON file. Atm is there only 1 option, so the variable is just a string. 17 | */ 18 | 19 | async function jsonFromConfigs(directory, option) { 20 | /** 21 | * The approach to find create the allPosts.json file from every posts config file is as follows: 22 | * 1. Search through the 'posts' folder with the readDirectory function. 23 | * 2. Scan all the items withinn the 'posts' folder. 24 | * 3. Check which items are folders, and run the readDirectory function again with the found folders. 25 | * 4. Check if the folder contains a .post.config file. If it does, we increment the "recursive-counter" 26 | * and run the pushConfig function. This function reads the content of the config file and creates an object 27 | * with the information. This object gets pushed to an array containing the config-information for all the posts. 28 | * At the end of the pushconfig the recursion-counter gets decremented. 29 | * 30 | * Note: Because of the async-structure will all the directories (posts) found 31 | * have their config file read and added to the allConfigs array. 32 | * 33 | * That is why the 'recursion-counter' is used, so that when the 34 | * last config has been pushed will the counter be 0 again, 35 | * and the addToJson function can be run with an array of all the configs. 36 | * 37 | */ 38 | 39 | try { 40 | const allConfigs = []; 41 | let recursionCounter = 0; 42 | 43 | async function readDirectory(dir, post) { 44 | fs.readdir(dir, async function(err, items) { 45 | if (err) { 46 | return terminal('Unable to scan directory: ' + err); 47 | } 48 | 49 | /** 50 | * Check if the directory contains a .post.config file and there is a post provided. 51 | * The the recursion counter works because it gets incremented here before the pushConfig function gets run 52 | * and is decremented at the end of the pushConfig function. 53 | */ 54 | 55 | if (items.includes('.post.config') && post) { 56 | recursionCounter++; 57 | const configFile = path.join(directory, post, '.post.config'); 58 | pushConfig(configFile); 59 | } else { 60 | /** 61 | * Here is where the "async-mess" begins. All the items found in the 'posts' folder that 62 | * are directories gets run again with the readDirectory function. If the user has not done anything 63 | * with the posts folder, will all of these directories contain a .post.config file, and the pushConfig function 64 | * will be executed for all of the posts. 65 | */ 66 | await scanItems(items, dir, readDirectory); 67 | } 68 | }); 69 | } 70 | 71 | async function pushConfig(configFile) { 72 | const rl = configureReadStream(configFile); 73 | const coreInfo = {}; 74 | 75 | for await (const line of rl) { 76 | const infoPart = line.split('||'); 77 | 78 | coreInfo[infoPart[0]] = 79 | infoPart[0] === 'creationDateMS' 80 | ? parseFloat(infoPart[1]) 81 | : infoPart[1]; 82 | } 83 | 84 | allConfigs.push(coreInfo); 85 | 86 | recursionCounter--; 87 | 88 | /** 89 | * If the counter is back to 0 then all of the config files have been added, and 90 | * the addToJson function can be fired with an array of all the config-objects. 91 | */ 92 | 93 | if (recursionCounter === 0) { 94 | addConfigsToJson(directory, allConfigs, option); 95 | } 96 | } 97 | 98 | readDirectory(directory); 99 | } catch (error) { 100 | terminal(error); 101 | } 102 | } 103 | 104 | module.exports = jsonFromConfigs; 105 | -------------------------------------------------------------------------------- /lib/dist/findFirstPosition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * findFirstPosition finds which position is the first one when passed two positions 5 | * @param {Number} positionOne 6 | * @param {Number} positionTwo 7 | */ 8 | function findFirstPosition(positionOne, positionTwo) { 9 | if (positionOne !== -1 && positionTwo !== -1) { 10 | return positionOne > positionTwo ? positionTwo : positionOne; 11 | } else if (positionOne !== -1) { 12 | return positionOne; 13 | } else if (positionTwo !== -1) { 14 | return positionTwo; 15 | } 16 | } 17 | 18 | module.exports = findFirstPosition; 19 | -------------------------------------------------------------------------------- /lib/dist/insertToString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * insertToString inserts a string in a specific position within a string, 5 | * without having to use split and join array methods. 6 | * @param {String} main_string 7 | * @param {String} ins_string 8 | * @param {Number} pos 9 | */ 10 | 11 | function insertToString(main_string, ins_string, pos) { 12 | if (typeof pos == 'undefined') { 13 | pos = 0; 14 | } 15 | if (typeof ins_string == 'undefined') { 16 | ins_string = ''; 17 | } 18 | return main_string.slice(0, pos) + ins_string + main_string.slice(pos); 19 | } 20 | 21 | module.exports = insertToString; 22 | -------------------------------------------------------------------------------- /lib/dist/replaceHTML.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | const terminal = require('../feedback/terminal'); 7 | const rewritePage = require('./rewritePage'); 8 | 9 | const configureReadStream = require('../utils/readwrite/configureReadStream'); 10 | 11 | const scanItems = require('../utils/readwrite/directory-utils/scanItems'); 12 | 13 | /** 14 | * 15 | * @param {Directory|String} directory - the path to the project folder 16 | * @param {String} template - the name of the new template 17 | * @param {String/Array} posts - what posts should be updated. String = all, Array = [name of posts] 18 | */ 19 | 20 | async function replaceHTML(directory, template, posts) { 21 | try { 22 | const rl = configureReadStream(path.join(directory, template + '.html')); 23 | 24 | let templateHTML = ''; 25 | 26 | let pastHead = false; 27 | 28 | for await (let line of rl) { 29 | if (pastHead) { 30 | templateHTML += line; 31 | continue; 32 | } 33 | if (line.trim().startsWith(' tag and store all content above in the constant HEAD. 25 | * 3. Find the start and end offset of the post-section, remove it from the HTML-string 26 | * and store it into the constant POST. 27 | * 4. Replace the tag in the template with the post-section extracted from the old index.html file. 28 | * 5. Add together the head of the old index.html file and the body of the new template. 29 | * 6. Overwrite the index.html file with the new content. 30 | * 7. Done! 31 | */ 32 | 33 | try { 34 | const rl = configureReadStream(pathToIndex); 35 | 36 | let HTML = ''; 37 | 38 | // Turning the HTML of the current page into a single string 39 | 40 | for await (const line of rl) { 41 | HTML += line; 42 | } 43 | 44 | /** 45 | * Can easily find the end-position of the head tag with vanilla JS, but getting the end 46 | * position of the .post-section requires a library. 47 | */ 48 | 49 | const HeadSection = HTML.indexOf(''); 50 | const HeadEnd = HeadSection + ''.length; 51 | 52 | /** 53 | * Thanks to the parse5 package has the process of transfering the post-section 54 | * from the old page in to the new template become very efficient. 55 | * 56 | * The approach is to find the post-section with the parse5 API and then serialize 57 | * it so that the tool get's the full html-string of the post in the current page. 58 | * Then it's pretty straight-forward to just replace the in the template with 59 | * a new
and insert the serialized HTML in this section. 60 | */ 61 | 62 | const HEAD = HTML.slice(0, HeadEnd); 63 | 64 | const document = parse5.parse(HTML); 65 | 66 | const body = document.childNodes[1].childNodes.find( 67 | child => child.nodeName === 'body' 68 | ); 69 | 70 | let postSection; 71 | 72 | for (const child of body.childNodes) { 73 | if (Array.isArray(child.attrs)) { 74 | child.attrs.forEach(attribute => { 75 | if ( 76 | attribute.name === 'class' && 77 | attribute.value === 'post-section' 78 | ) { 79 | postSection = child; 80 | } 81 | }); 82 | } 83 | if (postSection !== undefined) { 84 | break; 85 | } 86 | } 87 | 88 | const POSTSECTION = parse5.serialize(postSection); 89 | 90 | const newBody = template.replace( 91 | '', 92 | `
${POSTSECTION}
` 93 | ); 94 | 95 | const newPage = HEAD + newBody; 96 | 97 | configureWriteStream(pathToIndex).write(newPage); 98 | } catch (error) { 99 | terminal(error); 100 | } 101 | } 102 | 103 | module.exports = rewritePage; 104 | -------------------------------------------------------------------------------- /lib/feedback/CHALKS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* i <3 chalk */ 4 | const chalk = require('chalk'); 5 | 6 | const error = chalk.bold.red; 7 | const warning = chalk.keyword('orange'); 8 | const info = chalk.keyword('yellow'); 9 | const success = chalk.bold.green; 10 | const underline = chalk.underline; 11 | 12 | module.exports = { 13 | error, 14 | warning, 15 | info, 16 | success, 17 | underline 18 | }; 19 | -------------------------------------------------------------------------------- /lib/feedback/MESSAGES.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Here are all the message used for giving feedback to a user stored. 5 | * The MESSAGES object works basically like a storage for the different messages. 6 | * Combined with the 'alertUser'-functions becomes the process of easily alerting the 7 | * users with custom messages pretty simple. 8 | * 9 | * If the message is dynamic (telling that a specific post is already created), then the 10 | * message must be wrapped in a function with a parameter (i use variable). Then when a 11 | * dynamic message should be sent, i just have to add an object at the end of alertUser() 12 | * with the different variables. 13 | * 14 | * The functions can also return arrays of messages, in case i want multiple messages to be 15 | * delivered as feedback. 16 | * 17 | */ 18 | 19 | const { error, warning, info, success, underline } = require('./CHALKS'); 20 | 21 | const MESSAGES = { 22 | // Messages for options when running function 23 | initOptions: { 24 | template: error('Please add the template for the page.'), 25 | sourcefolder: error( 26 | 'Please add the source folder where your document is located.' 27 | ), 28 | postname: error('Please add the name of the post.'), 29 | wrongOption: variable => { 30 | return error( 31 | `ERROR: The option ${variable.option} is not supported. Please insert a valid option.` 32 | ); 33 | } 34 | }, 35 | // Messages about the existence of items 36 | existence: { 37 | template: variable => { 38 | return error( 39 | `ERROR: The template ${variable.template}.html does not exist. Please insert a valid filename.` 40 | ); 41 | }, 42 | sourcefolder: variable => { 43 | return error( 44 | `ERROR: The folder ${variable.sourcefolder} does not exist. Please insert a valid directory.` 45 | ); 46 | }, 47 | location: variable => { 48 | return error( 49 | `ERROR: The location ${variable.location} does not exist. Please insert a valid directory` 50 | ); 51 | }, 52 | post: variable => { 53 | return error( 54 | `ERROR: The file ${variable.postFile} could not be found in ${variable.locationForFile} ⛔` 55 | ); 56 | }, 57 | postNoExtension: variable => { 58 | return error( 59 | `ERROR: The file ${variable.postname} could not be found in ${variable.locationForFile} ⛔` 60 | ); 61 | }, 62 | page: variable => { 63 | return [ 64 | error(`ERROR: The page for ${variable.postname} already exists! ⛔`), 65 | warning( 66 | 'If you wanted to update the post page, then the set the update option to true.' 67 | ) 68 | ]; 69 | }, 70 | TAG: warning( 71 | 'WARNING! YOU DID NOT PROVIDE ANY TAG IN YOUR HTML-TEMPLATE. POST PAGE WAS STILL CREATED BUT HAS NO CONTENT FROM YOUR POST.' 72 | ) 73 | }, 74 | // Messages about core info of a post 75 | coreInfo: { 76 | name: error( 77 | 'ERROR: You must provide a name for the post. Do this at the first line of the document. Use --name to do this.' 78 | ), 79 | introduction: error( 80 | 'ERROR: You must provide a introduction for the post. Do this at the second line of the document. Use --introduction to do this.' 81 | ) 82 | }, 83 | // Messages about the creation of folders/files 84 | creation: { 85 | postsFolder: info("The 'posts'-folder has been created."), 86 | jsonFile: info('The allPosts.json file has been created.') 87 | }, 88 | // Messages about the syntax of posts 89 | postSyntax: { 90 | notAdded: variable => { 91 | return `${warning('NOTE:')} '${variable.line}' ${warning( 92 | `ON ${underline( 93 | `LINE ${variable.counter}` 94 | )} WAS NOT ADDED TO THE PAGE BECAUSE NO SYNTAX WAS PROVIDED IN BEFOREHAND.` 95 | )}`; 96 | }, 97 | unvalidMode: variable => { 98 | return error(`${variable.wantedMode} is not a valid mode`); 99 | }, 100 | estimatedMispell: variable => { 101 | return `${info('YOU WROTE')} '${variable.line}' ${info( 102 | `ON ${underline(`LINE ${variable.counter}`)}` 103 | )}. MAYBE YOU MEANT TO WRITE '# ${variable.line}' ${info('instead?')}`; 104 | } 105 | }, 106 | // Messages about the JSON-handling 107 | json: { 108 | wrongFormat: variable => { 109 | return [ 110 | warning('Note: Your allPosts.json file has wrong formatting. 🚫'), 111 | error(variable.errorMessage) 112 | ]; 113 | }, 114 | empty: warning( 115 | 'The allPosts.json file was empty, so a new template was created and the post was added.' 116 | ), 117 | sibling: variable => { 118 | return [ 119 | warning( 120 | `👀 NOTE: A post with the same name${ 121 | variable.sameDate 122 | ? ', introduction and creation date' 123 | : ' and introduction' 124 | } exists in the allPosts.json file. 👀` 125 | ), 126 | warning.underline( 127 | 'The post was added nonetheless because their urls were different. ' 128 | ) 129 | ]; 130 | }, 131 | complete: variable => { 132 | return success(`The post has been ${variable.version}`); 133 | }, 134 | updated: variable => { 135 | return [ 136 | success( 137 | "The allPosts.json has succesfully been updated and now contains all the posts from the 'posts'-folder. 🎉" 138 | ), 139 | success(`The order is ${variable.newOrder}`) 140 | ]; 141 | }, 142 | reset: info('The allPosts.json file has been reset.'), 143 | wrongOrder: variable => { 144 | return [ 145 | warning( 146 | `Warning: Order must be either ascending or descending. You wrote "${variable.order}"` 147 | ), 148 | info( 149 | 'The allPosts.json file will still be generated with default option (ascending).' 150 | ) 151 | ]; 152 | } 153 | }, 154 | // Messages for template 155 | template: { 156 | start: info('Starting the template-updates'), 157 | success: success( 158 | 'The posts have succesfully gotten a template makeover! 🎉' 159 | ) 160 | }, 161 | // Generic messages/no category 162 | generic: { 163 | start: info('Starting page-creation 🎉'), 164 | stop: warning('Process was stopped.'), 165 | complete: variable => { 166 | return success( 167 | `THE PAGE ${variable.postFile} HAS SUCCESFULLY BEEN ${variable.version}` 168 | ); 169 | }, 170 | time: variable => { 171 | return `### ${variable.message} took ${ 172 | variable.endTime[0] 173 | } seconds and ${variable.endTime[1] / 1000000} milliseconds ###`; 174 | }, 175 | project: success('Succesfully created a pre-made makemy folder 🥳') 176 | }, 177 | // .gitignore messages 178 | gitignore: { 179 | created: info('A .gitignore file was added to your project.'), 180 | added: () => { 181 | return [ 182 | info( 183 | "The makemy folder was added to your .gitignore file, so that it doesn't get published to your Github." 184 | ), 185 | info( 186 | 'If the folder you use to store your makemy-file in is not called "makemy" then please change this.' 187 | ) 188 | ]; 189 | } 190 | }, 191 | // generic post messages 192 | post: { 193 | parsed: variable => { 194 | return success( 195 | `The post was parsed with the ${ 196 | variable.sugar ? 'makemy-sugar' : 'markdown' 197 | } syntax. 🖊️` 198 | ); 199 | } 200 | } 201 | }; 202 | 203 | module.exports = MESSAGES; 204 | -------------------------------------------------------------------------------- /lib/feedback/alertUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MESSAGES = require('./MESSAGES'); 4 | 5 | const terminal = require('./terminal'); 6 | 7 | /** 8 | * alertUser allows for a very simple and straightforward way to choose what messages 9 | * to send as feedback in the tool. It works with the MESSAGES object, and dynamically 10 | * displays the messages in the terminal based on the MESSAGE-type. 11 | * 12 | * @param {String} category - The name of the category that the message should be extracted from the messages object above. 13 | * @param {String} type - The type of message to choose within the category 14 | * @param {Object} variables - An object containing the different variables used for a message 15 | * @param {Boolean} stop - A boolean telling wether or not the program was stopped after this message 16 | */ 17 | 18 | function alertUser(category, type, variables, stop) { 19 | // Check if the message in the category is a function, because then it has variables to be inserted into the message. 20 | if (typeof MESSAGES[category][type] === 'function') { 21 | // Check if there's more than one message returned, because then we run the terminal function for each message 22 | if (Array.isArray(MESSAGES[category][type](variables))) { 23 | MESSAGES[category][type](variables).forEach(message => { 24 | terminal(message, false, true); 25 | }); 26 | } 27 | // If only one message returned from the function, then send it to terminal 28 | else { 29 | terminal(MESSAGES[category][type](variables), false, true); 30 | } 31 | } 32 | // Message was only a simple text-line without any variables 33 | else { 34 | terminal(MESSAGES[category][type], false, true); 35 | } 36 | 37 | if (stop) { 38 | terminal(MESSAGES.generic.stop); 39 | } 40 | } 41 | 42 | module.exports = alertUser; 43 | -------------------------------------------------------------------------------- /lib/feedback/terminal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Creating a shorthand for console.log 4 | const type = message => console.log(message); 5 | 6 | /** 7 | * @param {String} message - The message that should be displayed in the terminal 8 | * @param {Boolean} followUp - Set to true if a message should be in the same 'section' as the previous 9 | * @param {Boolean} first - Set to true if a message is the first one sent 10 | */ 11 | 12 | const terminal = (message, followUp = false, first = false) => { 13 | if (!followUp && !first) { 14 | type('---------'); 15 | } 16 | if (message) { 17 | if (!followUp) { 18 | type(''); 19 | } 20 | type(message); 21 | type(''); 22 | } 23 | }; 24 | 25 | module.exports = terminal; 26 | -------------------------------------------------------------------------------- /lib/helper/makemy/makemy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * To run, simply type node ./makemy/makemy.js in your terminal! 3 | */ 4 | 5 | const makemy = require('makemy'); 6 | 7 | const options = { 8 | template: 'template', 9 | 10 | sourcefolder: 'posts', 11 | 12 | postname: 'my-first-post', 13 | 14 | location: '../' 15 | }; 16 | 17 | makemy.page(__dirname, options); 18 | -------------------------------------------------------------------------------- /lib/helper/makemy/posts/my-first-post.md: -------------------------------------------------------------------------------- 1 | --name My first post 2 | --introduction This was written entirely in markdown and generated with makemy. 3 | 4 | # Makemy - the easiest static post generator 5 | 6 | Markdown is a simple text format whose goal is to be very easy to read and write, even when not converted to HTML. This demo page will let you type anything you like and see how it gets converted. Live. No more waiting around. 7 | 8 | ## Why Markdown? 9 | 10 | It's easy. It's not overly bloated, unlike HTML. Also, as the creator of [markdown] says, 11 | 12 | > The overriding design goal for Markdown's 13 | > formatting syntax is to make it as readable 14 | > as possible. The idea is that a 15 | > Markdown-formatted document should be 16 | > publishable as-is, as plain text, without 17 | > looking like it's been marked up with tags 18 | > or formatting instructions. 19 | 20 | ``` 21 | function AsyncTest() { 22 | return await; 23 | } 24 | ``` 25 | 26 | [markdown]: http://daringfireball.net/projects/markdown/ 27 | -------------------------------------------------------------------------------- /lib/helper/makemy/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
This is my footer
53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/helper/projectCreator.js: -------------------------------------------------------------------------------- 1 | const ncp = require('ncp'); 2 | 3 | const alertUser = require('../feedback/alertUser'); 4 | 5 | async function projectCreator(dir) { 6 | ncp(__dirname + '/makemy', dir + '/makemy', function(err) { 7 | if (err) { 8 | return console.error(err); 9 | } 10 | alertUser('generic', 'project'); 11 | console.log(dir); 12 | }); 13 | } 14 | 15 | module.exports = projectCreator; 16 | -------------------------------------------------------------------------------- /lib/makemy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * makemy is an open source tool created by Mathias Picker 3 | * 4 | * The tools goal is to simplify the process of writing, creating and publishing posts/documents 5 | * on static websites without the need of a back-end server. 6 | * 7 | * GitHub: https://github.com/make-my/makemy 8 | * NPM: https://www.npmjs.com/package/makemy 9 | */ 10 | 11 | 'use strict'; 12 | 13 | const makemy = { 14 | page: require('./tools/createPage/createPage'), 15 | json: require('./tools/updateJson/updateJson'), 16 | template: require('./tools/updateTemplates/updateTemplates'), 17 | init: require('./helper/projectCreator') 18 | }; 19 | 20 | module.exports = makemy; 21 | -------------------------------------------------------------------------------- /lib/tools/createPage/createPage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This program creates the post page from the html template and post document. It automatically creates a 'posts' folder in the root of the 5 | * project (or if already exists, uses that one) and creates a folder there with the name of the post. Within this folder will a 6 | * index.html file be created. 7 | * 8 | * The idea of the program is that we take in an HTML-file which serves as a template. 9 | * The program keeps everything that has been written in the template, and places the files needed on the client side 10 | * at the end of the tag. Only exception is the init styling for the post. This css file will be placed above 11 | * the tag containing a css file. This is done so that if user wants, they can style the post-components as they wish in 12 | * their css file in the template, and because of css's order of precedence will their stylings be used instead of the init-stylings. 13 | * 14 | * The program also automatically creates a .json file, containing all the posts in the posts-folder. This .json file 15 | * can be used to for example create preview components of the posts on the users homepage. This .json file will also be stored in the posts folder, 16 | * along with the styling.css and client.js file. 17 | * 18 | * Author: Mathias Picker 19 | */ 20 | 21 | const path = require('path'); 22 | 23 | /* FUNCTIONS FOR SETTING COLOR ON FEEDBACK IN TERMINAL */ 24 | const terminal = require('../../feedback/terminal'); 25 | 26 | /* Helpers */ 27 | const alertUser = require('../../feedback/alertUser'); 28 | const configureReadStream = require('../../utils/readwrite/configureReadStream'); 29 | const configureWriteStream = require('../../utils/readwrite/configureWriteStream'); 30 | 31 | /* Step one */ 32 | const checkOptions = require('../../utils/checkers/checkOptions'); 33 | const validateTemplate = require('../../utils/validators/validateTemplate'); 34 | const validateSourceFolder = require('../../utils/validators/validateSourceFolder'); 35 | const validateLocationFolder = require('../../utils/validators/validateLocationFolder'); 36 | const findPostFile = require('../../utils/getters/findPostFile'); 37 | const doesPageExist = require('../../utils/checkers/doesPageExist'); 38 | const createGitIgnore = require('../../dist/createGitIgnore'); 39 | 40 | /* Step two */ 41 | const validateCoreInfo = require('../../utils/validators/validateCoreInfo'); 42 | 43 | /* Step three */ 44 | const validatePostsFolder = require('../../utils/validators/validatePostsFolder'); 45 | const validateFolderForThisPost = require('../../utils/validators/validateFolderForThisPost'); 46 | const configureClientFiles = require('../../utils/setters/configureClientFiles'); 47 | const validateJson = require('../../utils/validators/validateJson'); 48 | const addToJson = require('../../dist/addToJson'); 49 | 50 | /* Step four */ 51 | const parse5 = require('parse5'); 52 | const setHeadContent = require('../../utils/setters/setHeadContent'); 53 | const markdownParser = require('../markdownParser/markdownParser'); 54 | const sugarParser = require('../sugarParser/sugarParser'); 55 | 56 | /* Step five */ 57 | const createConfigForPost = require('../../utils/configs/createConfigForPost'); 58 | 59 | /* Time measurement for function execution */ 60 | const startTime = process.hrtime(); 61 | 62 | /** 63 | * createPage creates the html page for the post page 64 | * @param {__dirname|String} directory - The dirname of where the template is 65 | * @param {Object} options - The option parameters for this post 66 | */ 67 | 68 | async function createPage(directory, options) { 69 | /** 70 | * This function does a couple of different things. 71 | * 72 | * 1. First of all we assign the values from the option parameter, and begin checking if everything is okay. 73 | * For every step, if something has gone wrong, we tell the user and stop the execution. 74 | * 75 | * Most of the code here is pure error-handling and feedback, but i find it important to give 76 | * proper feedback if the user has done something wrong. 77 | * 78 | */ 79 | 80 | let { template, sourcefolder, postname, location = '' } = options; 81 | 82 | /* 83 | If i want to change the options name, then i'll just assign the new names to the 84 | same variables used throughout the code. Can't be bothered (for now) to change all the variable names 85 | if i decide to change the options 86 | */ 87 | 88 | /*const*/ template = template; 89 | /*const*/ sourcefolder = sourcefolder; 90 | /*const*/ postname = postname; 91 | /*const*/ location = location; 92 | 93 | let { extension, update = false, sugar = false, css = true } = options; 94 | 95 | let postFile; 96 | let pageExists = false; 97 | let postCreated = false; 98 | 99 | // Checking if the input sent from user is fine. 100 | try { 101 | await checkOptions(options, 'createPage'); 102 | await validateTemplate(directory, template); 103 | await validateSourceFolder(directory, sourcefolder); 104 | await validateLocationFolder(directory, location); 105 | await createGitIgnore(directory, location); 106 | 107 | postFile = await findPostFile(directory, sourcefolder, postname, extension); 108 | 109 | pageExists = await doesPageExist(directory, location, postname, update); 110 | } catch (error) { 111 | return terminal(error); 112 | } 113 | 114 | /** 115 | * 116 | * 2. Good news! All the files existed, and if they didn't, the process has been stopped and the user knows what's up. 117 | * So now we begin parsing the template and create a new one. 118 | * 119 | * The approach here is that we first store the core info about the post, which will be added to the .json file and to a secret .config file in every post. 120 | * We also extract the birth-time of the post document so that each post has a date of when it was written. This is used for easily sorting by date. 121 | * 122 | */ 123 | 124 | let CORE_INFO_POSTNAME = { url: 'posts/' + postname }; 125 | 126 | let postCoreInfo = {}; 127 | 128 | const postFileLocation = path.join(directory, sourcefolder, postFile); 129 | 130 | const rlPost = configureReadStream(postFileLocation); 131 | 132 | try { 133 | postCoreInfo = await validateCoreInfo( 134 | postFileLocation, 135 | rlPost, 136 | CORE_INFO_POSTNAME 137 | ); 138 | } catch (error) { 139 | return terminal(error); 140 | } 141 | 142 | /** 143 | * 144 | * 3. Here we choose (or create) the posts-folder and then afterwards create the folder which will contain the page. 145 | * 146 | * When that's done, we copy over the styling.css file from our program and put it in the posts folder. I've chosen the approach of copying it over everytime, 147 | * so that if something get's changed in the styling.css file by accident, it will be fixed when a new post gets created. 148 | * Then the same gets done with the client.js file (but at the bottom of the tag). This file contains the init function for Highlight.js. 149 | * 150 | */ 151 | 152 | const publicPostsFolder = path.join(directory, location, 'posts'); 153 | let thisPost = path.join(publicPostsFolder, postname); 154 | 155 | try { 156 | await validatePostsFolder(publicPostsFolder); 157 | await configureClientFiles(publicPostsFolder); 158 | await validateJson(publicPostsFolder, false); 159 | await addToJson(publicPostsFolder, postCoreInfo, update); 160 | await validateFolderForThisPost(thisPost); 161 | } catch (error) { 162 | return terminal(error); 163 | } 164 | 165 | /** 166 | * 167 | * 4. The approach here is to add the head content that is needed for the init-configs for the post 168 | * and then afterwards replace the tag with the parsed post. 169 | * 170 | */ 171 | 172 | try { 173 | const rl = configureReadStream(path.join(directory, template + '.html')); 174 | 175 | let HTML = ''; 176 | 177 | for await (const line of rl) { 178 | HTML += line; 179 | } 180 | 181 | const DOCUMENT = parse5.parse(HTML); 182 | 183 | const headContent = setHeadContent(DOCUMENT, postCoreInfo); 184 | 185 | /** 186 | * Inserting the "parsed" head content 187 | */ 188 | HTML = HTML.replace(/(.*?)<\/head>/, `${headContent}`); 189 | 190 | /** 191 | * Finding the tag and replacing it with the post 192 | */ 193 | const match = HTML.match(//gi); 194 | const postPosition = HTML.indexOf(match[0]); 195 | 196 | if (postPosition !== -1) { 197 | /** 198 | * Adding creation date 199 | * Doing this here instead of in the parser, because those should only creates HTML based on document content. 200 | * The date is automatically added no matter what. 201 | */ 202 | let postHTML = ``; 203 | 204 | postHTML = sugar 205 | ? await sugarParser(directory, sourcefolder, postFile) 206 | : await markdownParser(directory, sourcefolder, postFile); 207 | 208 | HTML = HTML.replace( 209 | //gi, 210 | `
${postHTML}
` 211 | ); 212 | 213 | postCreated = true; 214 | 215 | alertUser('post', 'parsed', { sugar }); 216 | } 217 | 218 | /** 219 | * Writing the new index.html file 220 | */ 221 | configureWriteStream(path.join(thisPost, 'index.html')).write(HTML); 222 | } catch (error) { 223 | return terminal(error); 224 | } 225 | /** 226 | * 5. Now everything has been created succesfully, and the last thing to do is to tell 227 | * the user that the generation is done. Note that the page still get's generated if the user does not provide 228 | * a tag in their template file (maybe make it mandatory to have this tag?). 229 | * 230 | * The tool also created a config file with the core-info about the post. This config file is used in the 231 | * updateJSON() function, so that it's not necessary to parse all the posts just to create/update the JSON file. 232 | * 233 | */ 234 | 235 | if (!postCreated) { 236 | alertUser('existence', 'TAG'); 237 | } else { 238 | // Creating a hidden config-file which stores the core-info about this post. 239 | createConfigForPost(thisPost, postCoreInfo); 240 | } 241 | 242 | if (update) { 243 | alertUser('generic', 'complete', { postFile, version: 'UPDATED 🔁' }); 244 | } else { 245 | alertUser('generic', 'complete', { postFile, version: 'CREATED ✅' }); 246 | } 247 | 248 | const endTime = process.hrtime(startTime); 249 | 250 | alertUser('generic', 'time', { message: 'Creating this page', endTime }); 251 | } 252 | 253 | module.exports = createPage; 254 | -------------------------------------------------------------------------------- /lib/tools/createPage/options/OPTIONS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findVoluntaryOptions = require('../../../utils/getters/findVoluntaryOptions'); 4 | 5 | const allOptions = [ 6 | 'template', 7 | 'sourcefolder', 8 | 'postname', 9 | 'location', 10 | 'extension', 11 | 'update', 12 | 'sugar', 13 | 'css' 14 | ]; 15 | 16 | const requiredOptions = ['template', 'sourcefolder', 'postname']; 17 | 18 | const voluntaryOptions = findVoluntaryOptions(allOptions, requiredOptions); 19 | 20 | module.exports = { allOptions, requiredOptions, voluntaryOptions }; 21 | -------------------------------------------------------------------------------- /lib/tools/markdownParser/markdownParser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const configureReadStream = require('../../utils/readwrite/configureReadStream'); 6 | 7 | const marked = require('marked'); 8 | 9 | /** 10 | * Let me just say, this was a breeze compared to creating the sugarParser. 11 | * Funny thing is that this will 99% be used more than the sugarParser, but 12 | * it was a lot of fun creating my own parser. I'll keep working on that one aswell. 13 | * 14 | * 100% kudos to the marked library! 15 | * 16 | * @param {Directory|String} directory - Usually __dirname (but can be any other correct path to the root-folder of the project) 17 | * @param {Directory|String} sourcefolder - The folder where are all the post-files are 18 | * @param {File|String} postFile - The file we want to create a post for 19 | */ 20 | 21 | async function markdownParser(directory, sourcefolder, postFile) { 22 | try { 23 | const rl = configureReadStream( 24 | path.join(directory, sourcefolder, postFile) 25 | ); 26 | 27 | let HTML = ''; 28 | 29 | for await (const line of rl) { 30 | // Don't add the --name core-info or the --introduction core-info 31 | if (line.startsWith('--name') || line.startsWith('--introduction')) { 32 | continue; 33 | } 34 | 35 | HTML += `${line}\n`; 36 | } 37 | 38 | return marked(HTML); 39 | } catch (error) { 40 | throw Error(error); 41 | } 42 | } 43 | 44 | module.exports = markdownParser; 45 | -------------------------------------------------------------------------------- /lib/tools/sugarParser/sugarParser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This tools task it to create the HTML code from the document that the user has provided. 5 | * The code parses the text, and follows the rule of the SYNTAX.js file to create 6 | * the correct elements based on their syntax tags. 7 | * 8 | * Author: Mathias Picker 9 | */ 10 | 11 | const path = require('path'); 12 | 13 | const { SYNTAX, modes } = require('../../utils/SYNTAX'); 14 | 15 | const createHTML = require('../../utils/HTML_FOR_POST/createHTML'); 16 | const findPreviousMode = require('../../utils/getters/findPreviousMode'); 17 | const checkMode = require('../../utils/checkers/checkMode'); 18 | 19 | const { 20 | lineWithLinkTag, 21 | lineWithCodeTag, 22 | lineWithBlockquoteTag 23 | } = require('../../utils/HTML_FOR_POST/allTags'); 24 | 25 | const lookForSyntaxTitles = require('../../utils/checkers/lookForSyntaxTitles'); 26 | const configureReadStream = require('../../utils/readwrite/configureReadStream'); 27 | const setTitleLevel = require('../../utils/setters/setTitleLevel'); 28 | const createdQuotee = require('../../utils/createCustomItems/createdQuotee'); 29 | const alertUser = require('../../feedback/alertUser'); 30 | 31 | /** 32 | * The core principle of this code is to set the "parser" into different modes 33 | * based on the syntax found in the text. 34 | * 35 | * Thanks to the Proxy-object (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 36 | * can this elegantly be done by "listening" to the MODE-object. 37 | * 38 | * Whenever a new mode gets set to true, we know that the previous mode is finished 39 | * (except for the first time, but it doesn't really matter because then the sectionContent is empty anyways). 40 | * Therefore by using this listening-approach we can create the HTML-wrapper for the 41 | * previous section, and continue to keep looping through the content of the document by always knowing 42 | * which mode we are in. 43 | */ 44 | 45 | // The whole post will be added to this string. 46 | let POST = ''; 47 | 48 | // The content of a section will be added in this string. 49 | let sectionContent = ''; 50 | 51 | // Boolean if we are in any mode at all. 52 | let inMode = false; 53 | 54 | // Our Proxy object, created from the modes object. Contains all the different modes we have created in the SYNTAX object. 55 | const MODES = new Proxy(modes, { 56 | set: (target, key, value) => { 57 | /** 58 | * The approach in this handler is to first check if there is any content to be added. 59 | * Then we find the mode that is set to true, and create the HTML-wrapper. Note that this is still 60 | * the previous mode, because the key get's updated at the end of the caller. 61 | * 62 | * That is the quirky and fun thing of a Porxy object: 63 | * If we update the Proxy-object in our code, its value doesn't actually get updated 64 | * until we do it here in this caller function. 65 | */ 66 | if (sectionContent.length !== 0) { 67 | inMode = false; 68 | 69 | const { tag, className } = findPreviousMode(MODES); 70 | 71 | if (tag !== undefined && className !== undefined) { 72 | inMode = true; 73 | } 74 | 75 | POST += createHTML(tag, className, sectionContent); 76 | 77 | // Reset section content 78 | sectionContent = ''; 79 | } 80 | 81 | // Updating the target (MODES) to the mode we have set 82 | target[key] = value; 83 | 84 | // Have to return true to avoid the TypeError: 'set' on proxy: trap returned falsish for property 'error' when in strict mode. Solution found on https://github.com/GoogleChrome/proxy-polyfill/issues/20 85 | return true; 86 | } 87 | }); 88 | 89 | /** 90 | * setMode sets us in the desired mode when we parse the post and want to add it to the correct section. 91 | * @param {String} wantedMode - The mode we want to go in to. Has to be one of the modes in the MODES object 92 | */ 93 | 94 | function setMode(wantedMode) { 95 | inMode = true; 96 | // Check if the wanted mode is valid 97 | if (MODES.hasOwnProperty(wantedMode)) { 98 | // Reset all MODES and set us in the correct mode 99 | for (const mode in MODES) { 100 | mode === wantedMode ? (MODES[wantedMode] = true) : (MODES[mode] = false); 101 | } 102 | } else { 103 | alertUser('postSyntax', 'unvalidMode', { wantedMode }, true); 104 | return; 105 | } 106 | } 107 | 108 | /** 109 | * sugarParser reads the content of the document. 110 | * It sets us in the correct mode if it finds syntaxes, and also alters supported HTML-tags. 111 | * 112 | * @param {Directory|String} directory - Usually __dirname (but can be any other correct path to the root-folder of the project) 113 | * @param {Directory|String} sourcefolder - The folder where are all the post-files are 114 | * @param {File|String} postFile - The file we want to create a post for 115 | */ 116 | 117 | async function sugarParser(directory, sourcefolder, postFile) { 118 | // Creating readStream of post-file 119 | const rl = configureReadStream(path.join(directory, sourcefolder, postFile)); 120 | 121 | /** 122 | * In this for loop we parse the text in the document and create the correct HTML for the sections/items. 123 | */ 124 | let counter = 0; 125 | let firstEmpty = false; 126 | let addBreak = false; 127 | 128 | for await (let line of rl) { 129 | // Counting the lines. Used for improved feedback if user does something wrong/silly. 130 | counter++; 131 | 132 | /** 133 | * The approach for the error handling is to check if a line has less than 4 words (except if we are in code-mode because here you may only want to write a couple words on one line (for example 'let object;')). 134 | * If a line has less than four words then it's most likely supposed to be a syntax setter. 135 | * So if a line does not have the trigger symbol (#) but looks like a syntax, we check if the word actually contains 136 | * any of the syntax words (to just really make sure that it probably was a syntax-setter). 137 | * If it does, we tell the user that "Hey, you wrote Title - 2, but you probably meant to write # Title -2" 138 | */ 139 | 140 | if ( 141 | line.trim().split(' ').length < 4 && 142 | !line.includes('#') && 143 | !MODES.code 144 | ) { 145 | if (lookForSyntaxTitles(line) === true) { 146 | alertUser('postSyntax', 'estimatedMispell', { line, counter }); 147 | } 148 | } 149 | 150 | /** 151 | * Here we check if we are in different modes with the checkMode() function, which takes in the line and the mode we want to check. 152 | * If we get returned true from the checkMode then we set the program into the correct mode. 153 | * Note: 154 | * The title mode is a little different, because the user can optionally set a level 155 | * to the title syntax. So here we check if that has been done and sets the level of the title to the correct one. 156 | */ 157 | 158 | if (checkMode(line, 'title')) { 159 | setMode('title'); 160 | 161 | const level = setTitleLevel(line); 162 | 163 | SYNTAX.title[2] = `h${level}`; 164 | 165 | continue; 166 | } 167 | 168 | if (checkMode(line, 'text')) { 169 | setMode('text'); 170 | continue; 171 | } 172 | 173 | if (checkMode(line, 'code')) { 174 | setMode('code'); 175 | continue; 176 | } 177 | 178 | if (checkMode(line, 'quote')) { 179 | setMode('quote'); 180 | continue; 181 | } 182 | 183 | /** 184 | * Whenever we are in a mode there are ome couple of things we want to look after. 185 | * The idea here is to check the lines for specific things that the program does something with, 186 | * and alter them so that the output becomes correct. 187 | * The first check is always if the string is empty, so that we can just skip checking anything else. 188 | * However, because we want to allow paragraph spacing by just adding 2 empty lines, we save if a line is empty. 189 | * So if we find another empty line right after that: add a break and reset firstEmpty. 190 | * 191 | * The design principle here is that we just add and fix things to the current line, no matter how many findings we have of 192 | * the different items. So if we find a
-tag on the line, we fix all of those, and in the next if statement we might also find a code tag and alter that one. 193 | * The key here is that our customizations on the line in one check gets passed over to the next one, so that it doesn't matter how many triggers 194 | * we find in a line. 195 | * 196 | */ 197 | 198 | if (inMode) { 199 | if (line.trim() === '' && !MODES.code) { 200 | if (sectionContent.length !== 0) { 201 | if (!firstEmpty) { 202 | firstEmpty = true; 203 | } 204 | } 205 | continue; 206 | } 207 | 208 | if (!line.startsWith('#') && firstEmpty && sectionContent.length !== 0) { 209 | addBreak = true; 210 | } 211 | 212 | firstEmpty = false; 213 | 214 | if (MODES.quote) { 215 | // If we are in quote mode, look for the --- trigger so we can create the author/quotee styling 216 | 217 | if (line.includes(SYNTAX.quote[4].quotee)) { 218 | /** 219 | * Here we automatically skip to the next iteration, because this line should 220 | * only contain the --- trigger and the author of the tag, so we don't want to look for anything else. 221 | */ 222 | sectionContent += createdQuotee(line); 223 | 224 | continue; 225 | } 226 | } 227 | 228 | // Check if line includes an tag, so that we can automatically add a noreferrer (for safety reasons) 229 | if (line.includes(' tag 239 | if (line.includes('${line}

`; 251 | } 252 | 253 | // Add line to sectionContent. Because of the Proxy on our MODES we can easily just change modes and add 254 | // content to this string. Because when a mode changes, the string gets added to the POST and becomes empty again. 255 | sectionContent += addBreak ? line.replace(line, `
${line}`) : line; 256 | addBreak = false; 257 | } else { 258 | /** 259 | * If we are NOT inMode, then there is usually something wrong (because you have to use a syntax if you want the content beneath to be added to the post). 260 | * Because the --name and --introduction are seperate informations about the post (used for the preview in the json file containing all posts), 261 | * we have to check that the line does not contain those triggers. So if none of those are used, and the line is not empty, 262 | * we tell the user that the content was not added to the page. 263 | * Note: The page will still be generated, as this does not break the creation in any way. 264 | * Therefore it's important to tell the user that the content was not added. 265 | */ 266 | if ( 267 | line.trim().length !== 0 && 268 | !line.includes('--name') && 269 | !line.includes('--introduction') 270 | ) { 271 | alertUser('postSyntax', 'notAdded', { line, counter }); 272 | } 273 | } 274 | } 275 | 276 | // Setting all modes to false so that the Proxy will be called for the last time and everything left should be added. 277 | for (const mode in MODES) { 278 | MODES[mode] = false; 279 | } 280 | 281 | return POST; 282 | } 283 | 284 | module.exports = sugarParser; 285 | -------------------------------------------------------------------------------- /lib/tools/updateJson/options/OPTIONS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findVoluntaryOptions = require('../../../utils/getters/findVoluntaryOptions'); 4 | 5 | const allOptions = ['order']; 6 | 7 | const requiredOptions = []; 8 | 9 | const voluntaryOptions = findVoluntaryOptions(allOptions, requiredOptions); 10 | 11 | module.exports = { allOptions, requiredOptions, voluntaryOptions }; 12 | -------------------------------------------------------------------------------- /lib/tools/updateJson/updateJson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This function allows for update of the JSON file that contains the core information about every post. 5 | * There are not many configurations to do on the JSON file - but one of the more useful use-cases for this function 6 | * is to "revive" the JSON file in case something has happened (deleted, accidentally changed etc.). 7 | * 8 | * Thanks to the config files of every post can we easily gain the core-information and create the JSON file 9 | * without having to parse every single post. The JSON file will be entirely from scratch every time this function 10 | * gets called. 11 | * 12 | * Author: Mathias Picker 13 | */ 14 | 15 | const path = require('path'); 16 | 17 | const alertUser = require('../../feedback/alertUser'); 18 | const terminal = require('../../feedback/terminal'); 19 | 20 | const validateJson = require('../../utils/validators/validateJson'); 21 | const createJsonFromConfigs = require('../../dist/createJsonFromConfigs'); 22 | 23 | const checkOptions = require('../../utils/checkers/checkOptions'); 24 | 25 | /* Time measurement for function execution */ 26 | const startTime = process.hrtime(); 27 | 28 | /** 29 | * 30 | * @param {Path} directory - The pathname for the project 31 | * @param {Object} options - The option parameters for the JSON file 32 | */ 33 | 34 | async function updateJson(directory, options) { 35 | try { 36 | let { order } = options; 37 | /** 38 | * Currently the function only supports ordering the JSON file in ascending or descending order 39 | * If an order option was provided, but its value is not valid, then the user gets feedback and the default 40 | * option of 'ascending' gets set. 41 | */ 42 | 43 | if (!(order === 'ascending' || order === 'descending')) { 44 | alertUser('json', 'wrongOrder', { order }); 45 | order = 'ascending'; 46 | } 47 | 48 | /** 49 | * Some validation before the JSON file becomes created from the config-files. 50 | * Most of the work gets done in the createJsonFromConfigs function. 51 | */ 52 | 53 | try { 54 | const postsFolder = path.join(directory, 'posts'); 55 | 56 | await checkOptions(options, 'updateJson'); 57 | await validateJson(postsFolder, true); 58 | await createJsonFromConfigs(postsFolder, order); 59 | } catch (error) { 60 | return terminal(error); 61 | } 62 | 63 | if (order === 'ascending') { 64 | alertUser('json', 'updated', { newOrder: `${order} ⬆️ (newest first)` }); 65 | } 66 | if (order === 'descending') { 67 | alertUser('json', 'updated', { newOrder: `${order} ⬇️ (oldest first)` }); 68 | } 69 | 70 | const endTime = process.hrtime(startTime); 71 | 72 | alertUser('generic', 'time', { 73 | message: 'Updating allPosts.json', 74 | endTime 75 | }); 76 | } catch (error) { 77 | terminal(error); 78 | } 79 | } 80 | 81 | module.exports = updateJson; 82 | -------------------------------------------------------------------------------- /lib/tools/updateTemplates/options/OPTIONS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findVoluntaryOptions = require('../../../utils/getters/findVoluntaryOptions'); 4 | 5 | const allOptions = ['template', 'posts']; 6 | 7 | const requiredOptions = ['template', 'posts']; 8 | 9 | const voluntaryOptions = findVoluntaryOptions(allOptions, requiredOptions); 10 | 11 | module.exports = { allOptions, requiredOptions, voluntaryOptions }; 12 | -------------------------------------------------------------------------------- /lib/tools/updateTemplates/updateTemplates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This functions task is to update the templates of already generated index.html-pages. 5 | * The user can tell which posts should be updated (either an array or just 'all'), 6 | * and the function will automatically rewrite the HTML of the pages with the new template provided. 7 | * 8 | * Author: Mathias Picker 9 | */ 10 | 11 | const terminal = require('../../feedback/terminal'); 12 | const alertUser = require('../../feedback/alertUser'); 13 | 14 | const checkOptions = require('../../utils/checkers/checkOptions'); 15 | const validateTemplate = require('../../utils/validators/validateTemplate'); 16 | const replaceHTML = require('../../dist/replaceHTML'); 17 | 18 | /* Time measurement for function execution */ 19 | const startTime = process.hrtime(); 20 | 21 | /** 22 | * 23 | * @param {Path} directory - The pathname for the project 24 | * @param {Object} options - The option parameters for the JSON file 25 | */ 26 | 27 | async function updateTemplates(directory, options) { 28 | let { template, posts } = options; 29 | alertUser('template', 'start'); 30 | 31 | try { 32 | await checkOptions(options, 'updateTemplates'); 33 | await validateTemplate(directory, template); 34 | 35 | await replaceHTML(directory, template, posts); 36 | 37 | alertUser('template', 'success'); 38 | 39 | const endTime = process.hrtime(startTime); 40 | 41 | alertUser('generic', 'time', { 42 | message: 'Updating the templates', 43 | endTime 44 | }); 45 | } catch (error) { 46 | terminal(error); 47 | } 48 | } 49 | 50 | module.exports = updateTemplates; 51 | -------------------------------------------------------------------------------- /lib/utils/CORE_INFO.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This is the "core" info for every post. These are the values stored in the allPosts.json file. 5 | */ 6 | 7 | const CORE_INFO = { 8 | //url 9 | name: '', 10 | introduction: '', 11 | creationDate: '', 12 | creationDateMS: 0 13 | }; 14 | 15 | module.exports = CORE_INFO; 16 | -------------------------------------------------------------------------------- /lib/utils/HTML_FOR_POST/allTags.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const lineWithLinkTag = require('./tags/
'); 4 | const lineWithCodeTag = require('./tags/'); 5 | const lineWithBlockquoteTag = require('./tags/
'); 6 | 7 | module.exports = { 8 | lineWithLinkTag, 9 | lineWithCodeTag, 10 | lineWithBlockquoteTag 11 | }; 12 | -------------------------------------------------------------------------------- /lib/utils/HTML_FOR_POST/createHTML.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * createHTML creates a HTML-tag with the desired content 5 | * @param {String} tag - Will be the name of an HTML tag, for example a, div, section etc. 6 | * @param {String} class - Will be the class-name of the element 7 | * @param {String} content - The content that should be inserted into the blogPost 8 | */ 9 | 10 | function createHTML(tag, className, content) { 11 | // If tag is code add a
-tag
12 |   let insert =
13 |     tag === 'code' ? `
<${tag}>` : `<${tag} ${className}>`;
14 | 
15 |   insert += content;
16 |   insert += tag === 'code' ? `
` : ``; 17 | return insert; 18 | } 19 | 20 | module.exports = createHTML; 21 | -------------------------------------------------------------------------------- /lib/utils/HTML_FOR_POST/tags/
.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 5 | * @param {String} line - The line that contains one or more link tags 6 | */ 7 | 8 | function lineWithLinkTag(line) { 9 | let newLine = ''; 10 | 11 | const lineArr = line.split(' '); 12 | 13 | for (let word of lineArr) { 14 | if (word.includes('.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 5 | * @param {String} line - The line that contains one or more -tags 6 | */ 7 | 8 | function lineWithBlockquoteTag(line) { 9 | let newLine = ''; 10 | 11 | const lineArr = line.split(' '); 12 | 13 | for (let i = 0; i < lineArr.length; i++) { 14 | if (lineArr[i].includes('.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 5 | * @param {String} line - The line that contains one or more -tags 6 | */ 7 | 8 | function lineWithCodeTag(line) { 9 | let newLine = ''; 10 | 11 | const lineArr = line.split(' '); 12 | 13 | for (let i = 0; i < lineArr.length; i++) { 14 | if (lineArr[i].includes(' { 12 | if ( 13 | // The first item in SYNTAX is the trigger (#) 14 | line 15 | .toLowerCase() 16 | .trim() 17 | .includes(SYNTAX[mode][0]) && 18 | // The first item in SYNTAX is the syntax-value (Title, Code, Quote etc.) 19 | line 20 | .toLowerCase() 21 | .trim() 22 | .includes(SYNTAX[mode][1]) 23 | ) { 24 | return true; 25 | } else { 26 | return false; 27 | } 28 | }; 29 | 30 | module.exports = checkMode; 31 | -------------------------------------------------------------------------------- /lib/utils/checkers/checkOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const alertUser = require('../../feedback/alertUser'); 4 | 5 | /** 6 | * 7 | * @param {Object} optionsPassed - The options we are checking 8 | * @param {String} tool - The tool we are checking the options for 9 | */ 10 | 11 | async function checkOptions(optionsPassed, tool) { 12 | // Dynamically importing the options for the tool 13 | const { 14 | allOptions, 15 | requiredOptions 16 | } = await require(`../../tools/${tool}/options/OPTIONS`); 17 | 18 | // If there are no required optiobns, and no options were passed, then we don't need to check them. 19 | if ( 20 | requiredOptions.length === 0 && 21 | !optionsPassed && 22 | Object.keys(optionsPassed).length === 0 23 | ) { 24 | return; 25 | } 26 | 27 | const checkTheseOptions = Object.keys(optionsPassed); 28 | 29 | const leftoverOptions = []; 30 | 31 | /** 32 | * Here we check the options that have been passed in. 33 | * First we see if any of the options provided are not in the list of the tools options. 34 | * 35 | * Secondly we check if all of the required options are provided. 36 | * 37 | */ 38 | 39 | for (const option of checkTheseOptions) { 40 | if (!allOptions.includes(option)) { 41 | leftoverOptions.push(option); 42 | } else if (requiredOptions.includes(option)) { 43 | requiredOptions.splice(requiredOptions.indexOf(option), 1); 44 | } 45 | } 46 | 47 | if (leftoverOptions.length > 0) { 48 | leftoverOptions.forEach(option => { 49 | alertUser('initOptions', 'wrongOption', { option }, true); 50 | }); 51 | 52 | throw Error(); 53 | } 54 | 55 | if (requiredOptions.length > 0) { 56 | requiredOptions.forEach(option => { 57 | alertUser('initOptions', option, undefined, true); 58 | }); 59 | 60 | throw Error(); 61 | } 62 | 63 | return true; 64 | } 65 | 66 | module.exports = checkOptions; 67 | -------------------------------------------------------------------------------- /lib/utils/checkers/doesExist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | /** 7 | * 8 | * @param {Directory|String} directory - The place where the option is supposed to be 9 | * @param {File/Folder|String} file - The file/folder we are checking if exists 10 | */ 11 | 12 | async function doesExist(directory, file) { 13 | try { 14 | if (fs.existsSync(path.join(directory, file))) { 15 | return true; 16 | } else { 17 | return false; 18 | } 19 | } catch (err) { 20 | throw new Error(err); 21 | } 22 | } 23 | 24 | module.exports = doesExist; 25 | -------------------------------------------------------------------------------- /lib/utils/checkers/doesPageExist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const alertUser = require('../../feedback/alertUser'); 6 | const doesExist = require('./doesExist'); 7 | 8 | /** 9 | * 10 | * @param {Directory|String} directory - the directory where the post folder we are checking is located 11 | * @param {Folder|String} location - the name of the folder where the index.html of the post is stored 12 | * @param {String} postname - the name of the post 13 | * @param {Boolean} update - telling if we are in update mode or not 14 | */ 15 | 16 | async function doesPageExist(directory, location, postname, update) { 17 | try { 18 | const pageExists = await doesExist( 19 | path.join(directory, 'posts', location), 20 | postname 21 | ); 22 | 23 | if (pageExists && !update) { 24 | alertUser('existence', 'page', { postname }, true); 25 | 26 | throw Error(); 27 | } 28 | return pageExists; 29 | } catch (err) { 30 | throw new Error(err); 31 | } 32 | } 33 | 34 | module.exports = doesPageExist; 35 | -------------------------------------------------------------------------------- /lib/utils/checkers/lookForSyntaxTitles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SYNTAX = require('../SYNTAX'); 4 | 5 | /** 6 | 7 | * @param {String} line - A line that is empty, with no trigger and we are not in CODE-mode. 8 | */ 9 | 10 | function lookForSyntaxTitles(line) { 11 | for (const syntax in SYNTAX) { 12 | if ( 13 | line 14 | .trim() 15 | .toLowerCase() 16 | .startsWith(syntax[1]) 17 | ) { 18 | return true; 19 | } 20 | } 21 | } 22 | 23 | module.exports = lookForSyntaxTitles; 24 | -------------------------------------------------------------------------------- /lib/utils/client-files/code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | document.querySelectorAll('.post-section .big-code').forEach(block => { 4 | hljs.highlightBlock(block); 5 | }); 6 | -------------------------------------------------------------------------------- /lib/utils/client-files/styling.css: -------------------------------------------------------------------------------- 1 | /* Styling for the post-section wrapper */ 2 | .post-section { 3 | width: 100%; 4 | min-height: 50rem; 5 | margin-bottom: 4rem; 6 | font-size: 2.5rem; 7 | --black: rgb(37, 37, 37); 8 | --white: white; 9 | color: var(--black); 10 | background-color: var(--white); 11 | } 12 | 13 | /* Setting post-section to dark mode - maybe make this optional? */ 14 | @media (prefers-color-scheme: dark) { 15 | .post-section { 16 | --black: white; 17 | --white: rgb(37, 37, 37); 18 | } 19 | } 20 | 21 | /* Every section in post set to same width and with some spacing between */ 22 | .post-section > * { 23 | width: 60%; 24 | margin: 4rem auto 0; 25 | line-height: 1.2; 26 | box-sizing: border-box; 27 | position: relative; 28 | } 29 | 30 | .created-at { 31 | position: relative; 32 | display: block; 33 | width: 60%; 34 | margin: 2.5rem auto -2rem; 35 | opacity: 0.7; 36 | font-size: 1.15em; 37 | } 38 | 39 | @media only screen and (max-width: 62.5em) { 40 | .post-section { 41 | font-size: 2.2rem; 42 | } 43 | 44 | .post-section > * { 45 | width: 80%; 46 | } 47 | } 48 | 49 | /* Every paragraph should also have some additional space */ 50 | .post-section .text-section > p { 51 | margin: 1.8rem auto; 52 | } 53 | 54 | /* Styling for links */ 55 | .post-section a { 56 | color: rgb(25, 125, 192); 57 | border-bottom: 2px solid rgba(25, 125, 192, 0.5); 58 | text-decoration: none; 59 | padding: 0 0.2rem; 60 | } 61 | .post-section a:hover { 62 | background-color: rgba(25, 125, 192, 0.1); 63 | border-bottom: 2px solid currentColor; 64 | } 65 | 66 | /* HEADERS */ 67 | 68 | .post-section .title { 69 | margin-top: 5rem; 70 | } 71 | 72 | .post-section h1 { 73 | font-size: 2.6em; 74 | text-decoration: underline; 75 | } 76 | 77 | .post-section h2 { 78 | font-size: 2em; 79 | } 80 | 81 | .post-section h3 { 82 | font-size: 1.4em; 83 | } 84 | 85 | .post-section h4 { 86 | font-size: 1.2em; 87 | } 88 | 89 | .post-section h5 { 90 | font-size: 1.05em; 91 | } 92 | 93 | .post-section h6 { 94 | font-size: 0.9em; 95 | } 96 | 97 | /* QUOTE BLOCKS */ 98 | 99 | .big-quote { 100 | display: block; 101 | font-style: italic; 102 | padding: 2.5rem; 103 | background-color: #fff0d354; 104 | font-weight: bold; 105 | font-size: 1.3em; 106 | } 107 | 108 | .big-quote .quotee { 109 | text-align: right; 110 | font-weight: normal; 111 | margin: 3rem 3rem 0; 112 | } 113 | 114 | /* HTML ITEMS */ 115 | 116 | pre, 117 | .code-tag, 118 | .code-tag-flow { 119 | background-color: #282c34; 120 | color: white; 121 | } 122 | 123 | pre, 124 | .code-tag { 125 | display: block; 126 | padding: 1.5rem; 127 | margin: 1rem auto; 128 | } 129 | .code-tag-flow { 130 | display: inline; 131 | padding: 0.85rem; 132 | } 133 | -------------------------------------------------------------------------------- /lib/utils/configs/createConfigForPost.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const configureWriteStream = require('../readwrite/configureWriteStream'); 6 | 7 | /** 8 | * 9 | * @param {File|String} post - The post to create the config file for 10 | * @param {Object} postCoreInfo - Object with the core info of the psot 11 | */ 12 | 13 | async function createConfigForPost(post, postCoreInfo) { 14 | try { 15 | const coreInfo = configureWriteStream(path.join(post, '.post.config')); 16 | 17 | for (const info in postCoreInfo) { 18 | coreInfo.write(`${info}||${postCoreInfo[info]}\n`); 19 | } 20 | } catch (err) { 21 | throw new Error(err); 22 | } 23 | } 24 | 25 | module.exports = createConfigForPost; 26 | -------------------------------------------------------------------------------- /lib/utils/createCustomItems/createdQuotee.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 5 | * @param {String} line - Line in QUOTE-mode that had the --- trigger 6 | */ 7 | 8 | function createdQuotee(line) { 9 | let newLine = line.split('---')[1].trim(); 10 | 11 | line = `

– ${newLine}

`; 12 | 13 | return line; 14 | } 15 | 16 | module.exports = createdQuotee; 17 | -------------------------------------------------------------------------------- /lib/utils/getters/findPostFile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const alertUser = require('../../feedback/alertUser'); 7 | const doesExist = require('../checkers/doesExist'); 8 | 9 | /** 10 | * 11 | * @param {Directory|String} directory - The place where the option is supposed to be 12 | * @param {File/Folder|String} sourcefolder - The sourcefolder/folder where the post-file is 13 | * @param {String} postname - name of post-file 14 | * @param {String} extension - optional for user to use, but tells the extension of the file. Different approaches to find the post-file depending on extenstion was provided. 15 | */ 16 | 17 | async function findPostFile(directory, sourcefolder, postname, extension) { 18 | if (extension === undefined) { 19 | const locationForFile = path.join(directory, sourcefolder); 20 | 21 | try { 22 | const postFilesArray = await fs.promises.readdir( 23 | locationForFile, 24 | function(err, files) { 25 | if (err) { 26 | terminal(err); 27 | return; 28 | } 29 | } 30 | ); 31 | 32 | const postFile = postFilesArray.find( 33 | postFile => postFile.split('.')[0] === postname 34 | ); 35 | 36 | if (!postFile) { 37 | alertUser( 38 | 'existence', 39 | 'postNoExtension', 40 | { 41 | postname, 42 | locationForFile 43 | }, 44 | true 45 | ); 46 | throw Error(); 47 | } 48 | return postFile; 49 | } catch (error) { 50 | throw Error(error); 51 | } 52 | } else { 53 | const locationForFile = path.join(directory, sourcefolder); 54 | 55 | extension = extension.includes('.') ? extension : `.${extension}`; 56 | 57 | const postFile = postname + extension; 58 | 59 | const postExists = await doesExist(locationForFile, postFile); 60 | 61 | if (!postExists) { 62 | alertUser('existence', 'post', { postFile, locationForFile }, true); 63 | 64 | throw Error(); 65 | } 66 | 67 | return postFile; 68 | } 69 | } 70 | 71 | module.exports = findPostFile; 72 | -------------------------------------------------------------------------------- /lib/utils/getters/findPreviousMode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const setTag = require('../setters/setTag'); 4 | const setClass = require('../setters/setClass'); 5 | 6 | /** 7 | * 8 | * @param {Object} MODES - The proxy containing our modes 9 | */ 10 | 11 | function findPreviousMode(MODES) { 12 | for (const keys in MODES) { 13 | if (MODES[keys] === true) { 14 | const tag = setTag(keys); 15 | const className = setClass(keys); 16 | 17 | return { tag, className }; 18 | } 19 | } 20 | } 21 | 22 | module.exports = findPreviousMode; 23 | -------------------------------------------------------------------------------- /lib/utils/getters/findVoluntaryOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 5 | * @param {Array} allOptions - array with all the options for the tool 6 | * @param {Array} requiredOptions - array with all the required options for the tool 7 | */ 8 | 9 | function findVoluntaryOptions(allOptions, requiredOptions) { 10 | const voluntaryOptions = []; 11 | 12 | for (const option of allOptions) { 13 | if (requiredOptions.indexOf(option) === -1) { 14 | voluntaryOptions.push(option); 15 | } 16 | } 17 | 18 | return voluntaryOptions; 19 | } 20 | 21 | module.exports = findVoluntaryOptions; 22 | -------------------------------------------------------------------------------- /lib/utils/getters/getBirthtime.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | /** 6 | * 7 | * @param {Directory|String} postFileLocation - the location for the post file 8 | */ 9 | 10 | async function getBirthtime(postFileLocation) { 11 | try { 12 | const stat = fs.statSync(postFileLocation); 13 | 14 | const creationDate = stat.birthtime.toString().split(' '); 15 | 16 | let day = creationDate[0]; 17 | 18 | let month = creationDate[1]; 19 | 20 | let date = creationDate[2]; 21 | let suffix = 'th'; 22 | 23 | const year = creationDate[3]; 24 | 25 | const days = [ 26 | 'Monday', 27 | 'Tuesday', 28 | 'Wednesday', 29 | 'Thursday', 30 | 'Friday', 31 | 'Saturday', 32 | 'Sunday' 33 | ]; 34 | const months = [ 35 | 'January', 36 | 'February', 37 | 'March', 38 | 'April', 39 | 'June', 40 | 'July', 41 | 'August', 42 | 'September', 43 | 'October', 44 | 'November', 45 | 'December' 46 | ]; 47 | 48 | day = days.find(d => d.startsWith(day)); 49 | month = months.find(d => d.startsWith(month)); 50 | 51 | // If date starts with a 0, then only show the second integer. (04 becomes 4) 52 | date = date.startsWith(0) ? date[1] : date; 53 | 54 | // Add correct suffix for date 55 | if (date.endsWith(1)) { 56 | suffix = 'st'; 57 | } else if (date.endsWith(2)) { 58 | suffix = 'nd'; 59 | } else if (date.endsWith(3)) { 60 | suffix = 'rd'; 61 | } 62 | 63 | return [`${day} ${date + suffix} of ${month} ${year}`, stat.birthtimeMs]; 64 | } catch (err) { 65 | throw Error(err); 66 | } 67 | } 68 | 69 | module.exports = getBirthtime; 70 | -------------------------------------------------------------------------------- /lib/utils/readwrite/HTML-WRITERS/insertToHeadTag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * insertToHeadTag simply inserts content at the end of the head 5 | * @param {String} HEAD_STRING 6 | */ 7 | 8 | function insertToHeadTag(HEAD_STRING) { 9 | try { 10 | /** 11 | * Thank you to the Highligt.js library for making my life 10x easier for highlighting the syntax of the code in the code blocks :) 12 | */ 13 | 14 | const headLines = [ 15 | '', 16 | '', 17 | '' 18 | ]; 19 | 20 | headLines.forEach(line => { 21 | HEAD_STRING += line; 22 | }); 23 | 24 | return HEAD_STRING; 25 | } catch (err) { 26 | throw new Error(err); 27 | } 28 | } 29 | 30 | module.exports = insertToHeadTag; 31 | -------------------------------------------------------------------------------- /lib/utils/readwrite/configureReadStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const readline = require('readline'); 5 | 6 | /** 7 | * 8 | * @param {File|String} file - the file to create a readstream for 9 | */ 10 | 11 | function configureReadStream(file) { 12 | const readStream = fs.createReadStream(file, 'utf8'); 13 | 14 | const rl = readline.createInterface({ 15 | input: readStream, 16 | crlfDelay: Infinity 17 | }); 18 | 19 | return rl; 20 | } 21 | 22 | module.exports = configureReadStream; 23 | -------------------------------------------------------------------------------- /lib/utils/readwrite/configureWriteStream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | /** 6 | * 7 | * @param {File|String} file - The name of the file to create a writestream for 8 | */ 9 | 10 | function configureWriteStream(file) { 11 | const writeStream = fs.createWriteStream(file, 'utf8'); 12 | 13 | return writeStream; 14 | } 15 | 16 | module.exports = configureWriteStream; 17 | -------------------------------------------------------------------------------- /lib/utils/readwrite/directory-utils/scanItems.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | /** 7 | * 8 | * @param {Array} items - a list of all the items in a directory 9 | * @param {Directory} dir - the directory one step above where the items were found 10 | * @param {Function} callback - callback function for when a folder has been found, usually "readDirectory" 11 | */ 12 | 13 | async function scanItems(items, dir, callback) { 14 | items.forEach(item => { 15 | const itemDir = path.join(dir, item); 16 | fs.stat(itemDir, async function(err, stat) { 17 | if (err) { 18 | return self.onStatError(err); 19 | } 20 | 21 | if (stat.isDirectory()) { 22 | await callback(itemDir, item); 23 | } 24 | }); 25 | }); 26 | } 27 | 28 | module.exports = scanItems; 29 | -------------------------------------------------------------------------------- /lib/utils/setters/configureClientFiles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | /** 7 | * 8 | * @param {Folder|String} folder - The folder where the generated posts are stored 9 | */ 10 | 11 | async function configureClientFiles(folder) { 12 | try { 13 | fs.copyFile( 14 | path.join(__dirname, '../client-files', 'styling.css'), 15 | path.join(folder, 's.css'), 16 | err => { 17 | if (err) throw err; 18 | } 19 | ); 20 | 21 | fs.copyFile( 22 | path.join(__dirname, '../client-files', 'code.js'), 23 | path.join(folder, 'c.js'), 24 | err => { 25 | if (err) throw err; 26 | } 27 | ); 28 | } catch (err) { 29 | throw new Error(err); 30 | } 31 | } 32 | 33 | module.exports = configureClientFiles; 34 | -------------------------------------------------------------------------------- /lib/utils/setters/setClass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { SYNTAX } = require('../SYNTAX'); 4 | 5 | /** 6 | * setTag returns the correct classname for element used in the createHTML function 7 | * @param {String} mode - The mode we want to extract the class from 8 | */ 9 | 10 | const setTag = mode => { 11 | return SYNTAX[mode][3]; 12 | }; 13 | 14 | module.exports = setTag; 15 | -------------------------------------------------------------------------------- /lib/utils/setters/setHeadContent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const parse5 = require('parse5'); 4 | 5 | const insertToHeadTag = require('../readwrite/HTML-WRITERS/insertToHeadTag'); 6 | const insertToString = require('../../dist/insertToString'); 7 | const findFirstPosition = require('../../dist/findFirstPosition'); 8 | 9 | /** 10 | * setHeadContent inserts items that makemy wants to insert to the head of a post. 11 | * @param {Object} DOCUMENT - a parsed version of the template.html file 12 | * @param {Object} postCoreInfo - object containing the core info about the post 13 | */ 14 | 15 | function setHeadContent(DOCUMENT, postCoreInfo) { 16 | /** 17 | * The approach to correctly inserting and modifying the content in the head-section is as follows: 18 | * 1. Find the head-section with parse5 so that all the children are also found. 19 | * 2. Change all the hrefs of the css-tags. 20 | * 3. Change the title tag content. 21 | * 4. Insert makemy init items (specific place the stylesheet before any other stylesheet in the template so the css order of precedence is correct). 22 | */ 23 | /** 24 | * Finding the in the document 25 | */ 26 | 27 | let HEAD = DOCUMENT.childNodes[1].childNodes.find( 28 | child => child.nodeName === 'head' 29 | ); 30 | 31 | /** 32 | * Changing the href of all links to one step above. Todo: Make this configurable 33 | */ 34 | 35 | const links = HEAD.childNodes.filter(child => child.nodeName === 'link'); 36 | 37 | links.forEach(link => { 38 | const href = link.attrs.find(attribute => attribute.name === 'href'); 39 | href.value = `../../${href.value}`; 40 | }); 41 | 42 | /** 43 | * Replacing the title tag with the name of the post. Todo: maybe allow to keep what was used in title in template? 44 | */ 45 | 46 | let HEAD_STRING = parse5.serialize(HEAD); 47 | 48 | HEAD_STRING = HEAD_STRING.replace( 49 | /(.*?)<\/title>/gi, 50 | `<title>${postCoreInfo.name}` 51 | ); 52 | 53 | /** 54 | * Inserting the init stylesheet above any other stylesheets 55 | */ 56 | 57 | const inlineStylingPosition = HEAD_STRING.toLowerCase().indexOf('

This is the title of post

Here i could write some text about something I care about.

Click here!!

Above me is a link that you can click on, and this link works also Click for a list.


You can also write seperate paragraphs within a section. To do this, simply add 2 empty lines where you want the gap to be created (done right above this line)

Here is some code

function sayHello() {

return 'Hello';

}

New title again, should be h2

“Two things are infinite: the universe and human stupidity;

and I'm not sure about the universe.”

– Albert Einstein


This is another title of my blog-post

Here i could write some text about something I care about.

Click here!!

Above me is a link that you can click on, and this link works also Click for a list.


You can also write seperate paragraphs within a section. The program will automatically create a gap for you in the html and css (at the same place you have done in your blog document).

“Two things are infinite: the universe and human stupidity;

and I'm not sure about the universe.”

– Albert Einstein

This is my footer
-------------------------------------------------------------------------------- /test/posts/pure-markdown/.post.config: -------------------------------------------------------------------------------- 1 | url||posts/pure-markdown 2 | name||My pure markdown post 3 | introduction||This was written entirely in markdown 4 | creationDate||Tuesday 14th of April 2020 5 | creationDateMS||1586887526873.5598 6 | -------------------------------------------------------------------------------- /test/posts/pure-markdown/index.html: -------------------------------------------------------------------------------- 1 | My pure markdown post

Marked - Markdown Parser

2 |

Marked lets you convert Markdown into HTML. Markdown is a simple text format whose goal is to be very easy to read and write, even when not converted to HTML. This demo page will let you type anything you like and see how it gets converted. Live. No more waiting around.

3 |

How To Use The Demo

4 |
    5 |
  1. Type in stuff on the left.
  2. 6 |
  3. See the live updates on the right.
  4. 7 |
8 |

That's it. Pretty simple. There's also a drop-down option in the upper right to switch between various views:

9 |
    10 |
  • Preview: A live display of the generated HTML as it would render in a browser.
  • 11 |
  • HTML Source: The generated HTML before your browser makes it pretty.
  • 12 |
  • Lexer Data: What marked uses internally, in case you like gory stuff like this.
  • 13 |
  • Quick Reference: A brief run-down of how to format things using markdown.
  • 14 |
15 |

Why Markdown?

16 |

It's easy. It's not overly bloated, unlike HTML. Also, as the creator of markdown says,

17 |
18 |

The overriding design goal for Markdown's 19 | formatting syntax is to make it as readable 20 | as possible. The idea is that a 21 | Markdown-formatted document should be 22 | publishable as-is, as plain text, without 23 | looking like it's been marked up with tags 24 | or formatting instructions.

25 |
26 |
function AsyncTest() {
27 |     return await;
28 | }
29 | 
This is my footer
-------------------------------------------------------------------------------- /test/posts/s.css: -------------------------------------------------------------------------------- 1 | /* Styling for the post-section wrapper */ 2 | .post-section { 3 | width: 100%; 4 | min-height: 50rem; 5 | margin-bottom: 4rem; 6 | font-size: 2.5rem; 7 | --black: rgb(37, 37, 37); 8 | --white: white; 9 | color: var(--black); 10 | background-color: var(--white); 11 | } 12 | 13 | /* Setting post-section to dark mode - maybe make this optional? */ 14 | @media (prefers-color-scheme: dark) { 15 | .post-section { 16 | --black: white; 17 | --white: rgb(37, 37, 37); 18 | } 19 | } 20 | 21 | /* Every section in post set to same width and with some spacing between */ 22 | .post-section > * { 23 | width: 60%; 24 | margin: 4rem auto 0; 25 | line-height: 1.2; 26 | box-sizing: border-box; 27 | position: relative; 28 | } 29 | 30 | .created-at { 31 | position: relative; 32 | display: block; 33 | width: 60%; 34 | margin: 2.5rem auto -2rem; 35 | opacity: 0.7; 36 | font-size: 1.15em; 37 | } 38 | 39 | @media only screen and (max-width: 62.5em) { 40 | .post-section { 41 | font-size: 2.2rem; 42 | } 43 | 44 | .post-section > * { 45 | width: 80%; 46 | } 47 | } 48 | 49 | /* Every paragraph should also have some additional space */ 50 | .post-section .text-section > p { 51 | margin: 1.8rem auto; 52 | } 53 | 54 | /* Styling for links */ 55 | .post-section a { 56 | color: rgb(25, 125, 192); 57 | border-bottom: 2px solid rgba(25, 125, 192, 0.5); 58 | text-decoration: none; 59 | padding: 0 0.2rem; 60 | } 61 | .post-section a:hover { 62 | background-color: rgba(25, 125, 192, 0.1); 63 | border-bottom: 2px solid currentColor; 64 | } 65 | 66 | /* HEADERS */ 67 | 68 | .post-section .title { 69 | margin-top: 5rem; 70 | } 71 | 72 | .post-section h1 { 73 | font-size: 2.6em; 74 | text-decoration: underline; 75 | } 76 | 77 | .post-section h2 { 78 | font-size: 2em; 79 | } 80 | 81 | .post-section h3 { 82 | font-size: 1.4em; 83 | } 84 | 85 | .post-section h4 { 86 | font-size: 1.2em; 87 | } 88 | 89 | .post-section h5 { 90 | font-size: 1.05em; 91 | } 92 | 93 | .post-section h6 { 94 | font-size: 0.9em; 95 | } 96 | 97 | /* QUOTE BLOCKS */ 98 | 99 | .big-quote { 100 | display: block; 101 | font-style: italic; 102 | padding: 2.5rem; 103 | background-color: #fff0d354; 104 | font-weight: bold; 105 | font-size: 1.3em; 106 | } 107 | 108 | .big-quote .quotee { 109 | text-align: right; 110 | font-weight: normal; 111 | margin: 3rem 3rem 0; 112 | } 113 | 114 | /* HTML ITEMS */ 115 | 116 | pre, 117 | .code-tag, 118 | .code-tag-flow { 119 | background-color: #282c34; 120 | color: white; 121 | } 122 | 123 | pre, 124 | .code-tag { 125 | display: block; 126 | padding: 1.5rem; 127 | margin: 1rem auto; 128 | } 129 | .code-tag-flow { 130 | display: inline; 131 | padding: 0.85rem; 132 | } 133 | -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | In this folder is everything needed to create a post page. 4 | 5 | - In the ./posts-src folder are some txt/md files that can be used to create a post. 6 | - In the ./posts folder contains one post generated from the ./posts-src/i-like-waffles.txt file + the autogenerated js, css and json file from running the tool. 7 | - The template.html file contains the template for each post. 8 | - The client.js file has code for fetching the allPosts.json data in the ./posts folder. 9 | - The index.html file is the frontpage for the test-page. 10 | - Styles.css is contains some basic styling for the test-page. 11 | 12 | ### How to run/test program: 13 | 14 | Run `node main.js`. 15 | 16 | ### Todo: 17 | 18 | - Create test files for testing many different cases. 19 | -------------------------------------------------------------------------------- /test/styles.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | html { 9 | font-size: 62.5%; 10 | box-sizing: border-box; 11 | } 12 | 13 | nav { 14 | height: 10rem; 15 | background-color: aliceblue; 16 | width: 100%; 17 | position: relative; 18 | font-size: 4rem; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | main { 25 | margin: 10rem 0; 26 | } 27 | 28 | .preview-wrapper { 29 | font-size: 2rem; 30 | min-height: 10rem; 31 | margin: 5rem auto; 32 | padding: 2rem; 33 | position: relative; 34 | cursor: pointer; 35 | max-width: 80rem; 36 | box-shadow: 1px 1px 0.4rem rgba(0, 0, 0, 0.3); 37 | } 38 | 39 | .preview-wrapper:hover .preview-title { 40 | text-decoration: underline; 41 | } 42 | 43 | .preview-wrapper:hover .preview-date { 44 | opacity: 1; 45 | } 46 | 47 | .preview-wrapper .preview-title { 48 | font-size: 2em; 49 | display: -webkit-box; 50 | -webkit-line-clamp: 2; 51 | -webkit-box-orient: vertical; 52 | overflow: hidden; 53 | } 54 | 55 | .preview-wrapper .preview-introduction { 56 | margin-top: 1rem; 57 | margin-bottom: 5rem; 58 | display: -webkit-box; 59 | -webkit-line-clamp: 4; 60 | -webkit-box-orient: vertical; 61 | overflow: hidden; 62 | } 63 | 64 | .preview-wrapper .preview-date { 65 | position: absolute; 66 | bottom: 2rem; 67 | opacity: 0.6; 68 | } 69 | 70 | footer { 71 | height: 10rem; 72 | background-color: beige; 73 | width: 100%; 74 | position: relative; 75 | font-size: 3rem; 76 | display: flex; 77 | justify-content: center; 78 | align-items: center; 79 | } 80 | -------------------------------------------------------------------------------- /test/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/template.js: -------------------------------------------------------------------------------- 1 | const makemy = require('../lib/makemy'); 2 | 3 | const templateOptions = { 4 | template: 'template', 5 | posts: 'all' // 'all' or array with postnames ['first-post', 'i-like-waffles'] 6 | }; 7 | 8 | makemy.template(__dirname, templateOptions); 9 | --------------------------------------------------------------------------------