├── .gitignore ├── Advanced.md ├── CHANGELOG.md ├── CodingGuide.md ├── LICENSE.md ├── Logo.png ├── MapsAndEncounters.md ├── README.md ├── documentation ├── CoverImage.jpg ├── CustomCSS.jpg ├── EncounterExport.png ├── EncounterPlus.ulstyle ├── Flavortext.jpg ├── Flowchart.jpg ├── Formats.jpg ├── Heading1.jpg ├── Heading2.jpg ├── Heading3.jpg ├── Heading4.jpg ├── Heading5.jpg ├── Heading6.jpg ├── ImageFloat.jpg ├── ImageResized.jpg ├── Images.jpg ├── Item.jpg ├── LargeQuote.jpg ├── LinkColors.jpg ├── Links.jpg ├── Logo.png ├── MapExport.png ├── ModulePackerWalkthrough.png ├── Monster-SingleColumn.jpg ├── Monster-TwoColumn.jpg ├── Paper.jpg ├── ReadAloud.jpg ├── ShopTable.jpg ├── Sidebar.jpg ├── Spell.jpg ├── StatblockColors.jpg ├── TableColors.jpg ├── TextBlock.jpg ├── Ulysses.png ├── VSCodeLabels.jpg ├── VisualStudioCode.png └── screenshot.png ├── examples.zip └── source ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── .vscodeignore ├── LICENSE.md ├── Logo.png ├── app ├── entitlements.mac.plist ├── main.ts ├── package.app.json ├── preload.ts ├── renderer.ts └── resources │ ├── css │ ├── bootstrap-grid.min.css │ ├── bootstrap-reboot.min.css │ ├── bootstrap.min.css │ ├── fonts.css │ └── main.css │ ├── fonts │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff │ ├── img │ ├── add.svg │ ├── bg.jpg │ ├── build-module.svg │ ├── export-to-pdf.svg │ ├── help.svg │ ├── icon.png │ └── stain.png │ ├── index.html │ └── js │ ├── bootstrap.bundle.min.js │ ├── bootstrap.min.js │ ├── jquery.min.js │ └── jquery.slim.min.js ├── assets ├── base │ ├── css │ │ ├── fontawesome │ │ │ ├── css │ │ │ │ └── all.min.css │ │ │ └── webfonts │ │ │ │ ├── fa-brands-400.eot │ │ │ │ ├── fa-brands-400.svg │ │ │ │ ├── fa-brands-400.ttf │ │ │ │ ├── fa-brands-400.woff │ │ │ │ ├── fa-brands-400.woff2 │ │ │ │ ├── fa-regular-400.eot │ │ │ │ ├── fa-regular-400.svg │ │ │ │ ├── fa-regular-400.ttf │ │ │ │ ├── fa-regular-400.woff │ │ │ │ ├── fa-regular-400.woff2 │ │ │ │ ├── fa-solid-900.eot │ │ │ │ ├── fa-solid-900.svg │ │ │ │ ├── fa-solid-900.ttf │ │ │ │ ├── fa-solid-900.woff │ │ │ │ └── fa-solid-900.woff2 │ │ ├── global.css │ │ └── global.less │ ├── font │ │ ├── AndadaSC-Bold.ttf │ │ ├── AndadaSC-BoldItalic.ttf │ │ ├── AndadaSC-Italic.ttf │ │ ├── AndadaSC-Regular.ttf │ │ ├── Bookinsanity-Bold-Italic.ttf │ │ ├── Bookinsanity-Bold.ttf │ │ ├── Bookinsanity-Italic.ttf │ │ ├── Bookinsanity.ttf │ │ ├── Dungeon-Drop-Case.ttf │ │ ├── Mr-Eaves-Small-Caps.ttf │ │ ├── Nodesto-Caps-Condensed-Bold-Italic.ttf │ │ ├── Nodesto-Caps-Condensed-Bold.ttf │ │ ├── Nodesto-Caps-Condensed-Italic.ttf │ │ ├── Nodesto-Caps-Condensed.ttf │ │ ├── Scaly-Sans-Bold-Italic.ttf │ │ ├── Scaly-Sans-Bold.ttf │ │ ├── Scaly-Sans-Caps-Bold-Italic.ttf │ │ ├── Scaly-Sans-Caps-Bold.ttf │ │ ├── Scaly-Sans-Caps-Italic.ttf │ │ ├── Scaly-Sans-Caps.ttf │ │ ├── Scaly-Sans-Italic.ttf │ │ ├── Scaly-Sans.ttf │ │ ├── Solbera-Imitation.ttf │ │ ├── Zatanna-Misdirection-Bold-Italic.ttf │ │ ├── Zatanna-Misdirection-Bold.ttf │ │ ├── Zatanna-Misdirection-Italic.ttf │ │ └── Zatanna-Misdirection.ttf │ └── img │ │ ├── bg.png │ │ ├── flavortext.png │ │ ├── flowchart.png │ │ ├── item.png │ │ └── note.png └── print │ ├── css │ ├── print.css │ ├── print.less │ ├── print_a4.css │ └── print_a4.less │ └── img │ ├── footer-a4.svg │ ├── footer.svg │ ├── print-background-a4.jpg │ └── print-background.jpg ├── build └── icon.icns ├── cli ├── main.ts └── package.cli.json ├── image-source ├── Footer.svg ├── FooterA4.svg ├── FooterCurves.afdesign ├── Logo.afdesign ├── PringBG.jpg ├── PringBGA4.jpg ├── build-module.afdesign ├── encounterplus-markdown.afdesign ├── export-to-pdf.afdesign ├── flowchart.afdesign ├── flowchart.png ├── item.afdesign ├── item.png ├── note.afdesign └── note.png ├── launcher.py ├── module-packer.code-workspace ├── shared ├── EncounterFileReference.ts ├── MapFileReference.ts ├── MarkdownRenderer.ts ├── Module Entities │ ├── Encounter.ts │ ├── Group.ts │ ├── Item.ts │ ├── Map.ts │ ├── Module.ts │ ├── ModuleEntity.ts │ ├── Monster.ts │ ├── Page.ts │ ├── Reference.ts │ ├── RollTable.ts │ ├── RollTableColumn.ts │ └── Spell.ts ├── ModuleProject.ts ├── PdfExporter.ts └── ReferenceInfo.ts ├── tsconfig.app.json ├── tsconfig.cli.json ├── tsconfig.extension.json ├── vscode-extension ├── Commands │ ├── BuildModuleCommand.ts │ ├── CommandBase.ts │ ├── CreateModuleProjectFileCommand.ts │ ├── ExportToPdfCommand.ts │ ├── MarkdownToggle.ts │ └── MarkdownToggler.ts ├── TreeViewProviders │ └── ModuleProjectProvider.ts ├── extension.ts ├── package.extension.json └── resources │ ├── build-module.svg │ ├── dark │ ├── build-module.svg │ ├── encounterplus-markdown.svg │ └── export-to-pdf.svg │ ├── encounterplus-markdown-viewcontainer.svg │ ├── encounterplus-markdown.svg │ ├── item-codeblock.json │ ├── light │ ├── build-module.svg │ ├── encounterplus-markdown.svg │ └── export-to-pdf.svg │ ├── monster-codeblock.json │ ├── spell-codeblock.json │ └── vscode.css └── webpack.extension.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /app-out 4 | /cli-out 5 | /extension-out 6 | /example 7 | /examples 8 | *.vsix 9 | .DS_Store 10 | package.json 11 | package-lock.json 12 | source/Advanced.md 13 | 14 | source/README.md 15 | 16 | source/Documentation/ 17 | -------------------------------------------------------------------------------- /Advanced.md: -------------------------------------------------------------------------------- 1 | ## Controlling IDs 2 | 3 | Every element (pages, groups, maps, and encounters) in a module for EncounterPlus has an `id` value, including the module itself. The same is true for compendium elements (items, spells, and monsters). AND, the module itself allows an `id` value to be specified. This ID is how EncounterPlus knows whether, when imported, your element will overwrite an existing element or whether it will create a new element. 4 | 5 | When you use module-packer to create an element, the ID for any given element is a function of the element's slug and the module's ID. This means that every time you recompile your module you can expect a given page to have the same ID as it has last time as long as neither the module ID nor the page's slug changed. 6 | 7 | So, for most cases, you want to follow these rules about specifying IDs: 8 | - **DO** specify a [randomly generated ID](https://www.uuidgenerator.net) for your module itself (i.e., **DO** set the ID value in module.yaml). 9 | - Do **NOT** specify an ID for any page, group, map, encounter, item, spell, or monster that you create. 10 | 11 | The only time you would really stray from these guidelines is if you are intentionally trying to replace the element from another module or the built-in compendium. However, to do this, you will need to know the ID of the existing item (which will involve extracting a module/compendium export and reading the XML to find out the ID). 12 | 13 | ## Attribute Targeting 14 | 15 | Usually, when writing content in a markdown file, adding a special attribute to an element on the page is as simple as placing the attribute(s) in curly braces after the element (e.g., `{.red}` to make a table/monster/item/etc. red-colored.) 16 | 17 | However, sometimes it becomes ambiguous as to which item is attempting to be attributed. Take, for example, the following blockquote: 18 | 19 | ```Markdown 20 | > "What is that smell?! Oh. Oh no. No, no, no! Gods no!" - **Percy** 21 | {.flavortext} 22 | ``` 23 | 24 | One might think that they are applying the `flavortext` attribute to the entire blockquote. However, because the blockquote's text ended with a bolded element, that attribute is actually being applied to the bold element. 25 | 26 | Luckily, there is a way to address this. The module-packer and the VS Code extension utilize the [markdown-it-decorate extension](https://github.com/stereoplegic/markdown-it-decorate) to allow targeting a specific element. Essentially, this allows you to say "apply this style to the most-recent blockquote". To do that, we would change our attribute to look like the following: 27 | 28 | ```Markdown 29 | > "What is that smell?! Oh. Oh no. No, no, no! Gods no!" - **Percy** 30 | 31 | ``` 32 | 33 | The [markdown-it-decorate extension](https://github.com/stereoplegic/markdown-it-decorate) also allows another powerful feature: inline CSS styles. If you're inclined to set an inline-CSS style, you can do so like the following: 34 | 35 | ```Markdown 36 | 37 | ``` 38 | 39 | This example would apply the `two-column` class to the preceding div and apply an inline-style of setting the height to 350 pixels. 40 | 41 | ## Manually Nesting Pages or Groups 42 | 43 | Pages, Groups, Maps, and Encounters defined in the Module have their parent automatically defined by what folder they are placed in. However, this can be manually overridden on any page by specifying the `parent` property for that item. 44 | 45 | In the case of a Page or Markdown file, this would be specified by setting the `parent` property in the YAML front-matter at the top of the Page. Example: 46 | ```yaml 47 | --- 48 | name: Page name 49 | parent: parent-slug 50 | --- 51 | ``` 52 | 53 | In the case of a Group, this would be specified by setting the `parent` property in the Group.YAML file created in the Group's folder: 54 | ```YAML 55 | name: Example Group 56 | parent: parent-slug 57 | copy-files: true 58 | ``` 59 | 60 | Any entity can be the parent to another entity. For example, Pages can be parents to Maps, Maps can be parents to Encounter, etc. Care should be taken not to create a circular chain of relationships between parents and children, or the Module will be unable to be packed. 61 | 62 | ## Linking Pages 63 | 64 | ### Linking to a Page in the Same Module 65 | 66 | Pages within a module can be linked to one-another by simply linking their slug. Example: 67 | 68 | ```Markdown 69 | This is a [link to another page](another-page-slug). 70 | ``` 71 | 72 | ### Linking to a Page in a Different Module 73 | 74 | Pages in another module can be linked to by creating a link with both the module slug and the page slug. The format is `/module/{module-slug}/page/{another-page-slug}` where `{module-slug}` is the slug of the other module and `{another-page-slug}` is the slug of the page in that module. Example: 75 | 76 | ```Markdown 77 | This is a [link to another page in another module](/module/another-module-slug/page/another-page-slug). 78 | ``` 79 | 80 | ### Linking to a Header on the Same Page 81 | 82 | A link to another section of the same page can be accomplished. A linkable anchor for each header on a page is automatically created when the module is built to either a `.module` file or a PDF. The name of the header is "slugified" when an anchor is being created. For example, the header "My Cool Header" would become "my-cool-header". To link within the same page, simply place a `#` symbol in front of the link destination. Example: 83 | 84 | ```Markdown 85 | This is a [link to another section in the same page](#my-other-section). 86 | ``` 87 | 88 | ## Roll Tables in EncounterPlus 89 | 90 | A roll table is generally a table where the top left column header specifies one or more dice to be rolled, and the rest of the table contains a list of outcomes for the various rolls. Now that EncounterPlus has native support for roll tables, Module-Packer can automatically detect the presence of such tables and create the necessary data for EncounterPlus when exporting as a `.module` file. This functionality is still a bit experimental and is likely to be improved or expanded upon in the future. 91 | 92 | The following is an example of a typical encounter roll table: 93 | ```Markdown 94 | |[2d6](/roll/2d6)|Encounter| 95 | |:---:|:---| 96 | |2-3|3 Kobolds| 97 | |4-5|2 Owlbears| 98 | |6-8|10 Giant Rats| 99 | |9-10|1 Vampire| 100 | |11-12|1 Tarrasque| 101 | ``` 102 | 103 | To enable this functionality, simple add the following line to your `Module.yaml` file: 104 | ```YAML 105 | create-roll-tables: true 106 | ``` 107 | 108 | There are currently two special types of roll tables that can be enabled with special attributes on the roll link. The first is "No Repeat" roll tables - where the rolls will automatically exclude any result that would repeat the row being used. The second is an "Each Row" roll table - where a roll will be executed for each row in the table. To use these features, add either the `{.no-repeat}` or `{.each-row}` attribute to the roll link. For example: 109 | 110 | ```Markdown 111 | |[2d6](/roll/2d6){.no-repeat}|Encounter| 112 | |:---:|:---| 113 | |2-3|3 Kobolds| 114 | |4-5|2 Owlbears| 115 | |6-8|10 Giant Rats| 116 | |9-10|1 Vampire| 117 | |11-12|1 Tarrasque| 118 | ``` 119 | 120 | ## Advanced Layouts 121 | 122 | TODO 123 | 124 | ### Float Left/Right 125 | 126 | TODO 127 | 128 | ### Wrap Text Around Images 129 | 130 | TODO 131 | 132 | ### Multi-Column Content in Single-Column Pages 133 | 134 | TODO 135 | 136 | 137 | ### Print-Only or Module-Only Items 138 | 139 | Any element can be modified with the `.print-only` element to show up only in PDF output. Likewise, any element can be modified with the `.screen-only` attribute to only show up in EncounterPlus module output. Below is an example of having two images, one that only shows up in EncounterPlus, and a subsequent that only shows up in PDF output. 140 | 141 | **Note**: When previewing in Visual Studio Code's live Markdown Preview window, items with `.print-only` will not appear (it treats the preview as if it were in EncounterPlus). 142 | 143 | ```Markdown 144 | ![My EncounterPlus Image](EncounterPlusImage.jpg){.screen-only} 145 | ![My PDF Image](PDFImage.jpg){.print-only} 146 | ``` 147 | 148 | ### Automatically Update Compendium Links 149 | 150 | When exporting to PDF, links to EncounterPlus's compendium entries will just appear as broken links in the PDF document. Often, a more useful thing to do is to have the PDF output link to D&D Beyond's item, spell, and monster entries when outputting to PDF. This can be done automatically with the `print-link-update` entry in your Module.yaml file: 151 | 152 | The following will update compendium links to individual D&D Beyond Entries (e.g., a link to `/spell/fireball` in EncounterPlus will appear as `https://www.dndbeyond.com/spells/fireball` in PDF output). 153 | 154 | ```YAML 155 | print-link-update: D&D Beyond Entries 156 | ``` 157 | 158 | The following will update compendium links to individual D&D Beyond Search Results (e.g., a link to `/spell/fireball` in EncounterPlus will appear as `https://www.dndbeyond.com/search?q=fireball` in PDF output). 159 | 160 | ```YAML 161 | print-link-update: D&D Beyond Search 162 | ``` 163 | 164 | ### Hiding Footer Text 165 | 166 | TODO 167 | 168 | ### Full Page Cover Images 169 | 170 | TODO 171 | 172 | ## Running from the Command Line 173 | 174 | The Module Packer can be run from the command line. It will, however, require NodeJS and Python 3 to be installed on the local system. 175 | 176 | 1. Download the source code from this repository. 177 | 2. Navigate to the source code directory in your terminal. 178 | 2. Enter the following in your terminal where is the root path of your module (i.e., the folder containing the Module.yaml file): 179 | `python3 launcher.py run --path ""` 180 | 181 | If you want to output PDF, add `--output pdf` to your command: 182 | `python3 launcher.py run --path "" --output pdf` 183 | 184 | ## Splitting Monster Block Columns on Specific Properties 185 | 186 | TODO 187 | 188 | ## Customizing Styles 189 | 190 | If you have knowledge of how to work with Cascading Style Sheets, modifications can be made to the way Markdown is rendered by adding a `custom.css` file in the `assets/css` folder of your module. This `custom.css` will be evaluated AFTER the default stylesheet. If you want to completely override or replace the main stylesheet, it may be done by replacing the `global.css` file in the same `assets/css` folder. Do note, however, replacing the `global.css` will prevent updates to the default style sheet from taking effect and is generally not recommended for most use cases. 191 | 192 | ### Previewing Custom Styles in Visual Studio 193 | 194 | To preview your custom style in Visual Studio Code, you must add your `custom.css` file to the list of VS Code's Markdown Preview stylesheets. Do so by going to Visual Studio Code's Settings, searching for "Markdown: Styles" and adding your `custom.css` file there. It is generally recommended that you set this Setting for the Workspace rather than for the User. 195 | 196 |

197 | Custom CSS in VS Code 198 |

199 | -------------------------------------------------------------------------------- /CodingGuide.md: -------------------------------------------------------------------------------- 1 | # Module Packer Coding Guide 2 | 3 | ## Launching From Code 4 | 5 | To run Module Packer or its Visual Studio Code extension from code, you will need the following prerequisites installed: 6 | - [Python](https://python.org) 7 | - [NodeJS](https://nodejs.org/) 8 | - [Node Package Manager (npm)](https://www.npmjs.com) 9 | 10 | To build and start the standalone module-packer, navigate to the code folder, issue the following command: 11 | ``` 12 | python3 launcher.py start-app 13 | ``` 14 | 15 | ## Working with Code 16 | 17 | ### Code Layout 18 | 19 | The project is configured with two main projects: an [Electron](https://www.electronjs.org) application that serves as the standalone Module Packer application, and a Visual Studio Code extension that enables module packing and markdown editing right from Visual Studio Code. They are each controlled as Node.js packages with NPM Modules. In additition, there is common shared code that actually contains most of the business logic for packing modules. 20 | 21 | Included launcher scripts will take care of copying the appropriate `package.json` file from the source and placing it in the main project workspace directory. 22 | 23 | The following folders exist in the repository 24 | - `app`: Contains code for the stanadlone Module Packer Electron application. 25 | - `assets`: Contains the images, stylesheets, and scripts needed to render the markdown in the EncounterPlus style. 26 | - `build`: The build resources for the standalone Module Packer Electron application builds. 27 | - `documentation`: Resources for help files and documentation. 28 | - `shared`: Contains code that is shared between the standalone application and the extension. 29 | - `vscode-extension`: Contains the code for the Visual Studio Code extension. 30 | 31 | In addition, the following folders may exist after various build processes are run: 32 | - `app-out`: Contains built javascript code after the typescript compiler has been run for the `app` folder. 33 | - `dist`: Contains compiled Windows and Mac binaries Exists after the packager/Electron-Build process has been run via the `package-app` launcher has been run. 34 | - `extension-out`: Contains built javascript code after the typescript compiler has been run for the `vscode-extension` folder. 35 | - `node_modules`: Contains node modules that have been installed by the build scripts. 36 | 37 | ### Compiling From Command Line 38 | 39 | To compile the standalone application from typescript, run the following command: 40 | ``` 41 | python3 launcher.py build-app 42 | ``` 43 | 44 | To compile the Visual Studio Code extension from typescript, run the following command: 45 | ``` 46 | python3 launcher.py build-extension 47 | ``` 48 | 49 | ### Packaging the Application 50 | 51 | The standalone module packer application can be packaged with the following command. Do note, however, that the project is currently configured to look for certificates that will not exist on your system. These will need to be changed. For more information, consule the [Electron-Builder documentation](https://www.electron.build): 52 | 53 | ``` 54 | python3 launcher.py package-app 55 | ``` 56 | 57 | ### Packaging the Visual Studio Code Extension 58 | The Visual Studio Code extension can be packaged as a VSIX with the following command. 59 | ``` 60 | python3 launcher.py package-extension 61 | ``` 62 | 63 | This script will require the Visual Studio Code Extensions node module to be installed on your system: 64 | ``` 65 | npm install -g vsce 66 | ``` 67 | 68 | ## Debugging 69 | 70 | The Module Packer project is configured for use with [Visual Studio Code](https://code.visualstudio.com), though other development environments may be used. If using Visual Studio Code, you will find two launch tasks are already configured for the workspace: 71 | - `Debug Module-Packer App`: Downloads all NPM modules, builds the typescript source for the Electron app, and launches the Module Packer application with debug hooks in Visual Studio Code. 72 | - `Run VS Code Extension`: Downloads all NPM modules, builds the typescript source for the Visual Studio Code Extesnion, and launches the extension with debug hooks in Visual Studio Code. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/Logo.png -------------------------------------------------------------------------------- /MapsAndEncounters.md: -------------------------------------------------------------------------------- 1 | # Tutorial: Including Maps and Encounters 2 | 3 | ## Maps 4 | 1. Create your map in EncounterPlus as you normally would. It can be created in a temporary module or campaign. 5 | 2. View the Map in EncounterPlus's Library View. 6 | 3. Click the "..." icon and choose "Export". Save the zip file somewhere. 7 | 8 |

9 | Map Export 10 |

11 | 12 | 4. In your Module's directory, create a subfolder for maps (I named mine "Maps"). 13 | 5. Create a `Group.yaml` file for this directory and set `include-in` to `files` and `copy-files` to `false` so a Group is not created and the raw zip files are not included in your module. 14 | 6. Copy the map's zip file into this folder. 15 | 7. In your `Module.yaml` file, create a property named `maps`. Then, as a children maps, create individual elements for each map. Each element must contain a "path" property pointing to the map's `.zip` file. Optionally, each element can also specify a `parent`, and `order`, and a `slug` property. These specify the parent the map will be nested under, the order it will show up under its parent, and its slug link respectively. 16 | 17 | Here is an example `Module.yaml` file with a couple of maps defined 18 | ```Markdown 19 | name: My Module 20 | slug: my-module 21 | description: A campaign setting. 22 | category: adventure 23 | maps: 24 | - path: Maps/my-first-map.zip 25 | order: 2 26 | parent: my-adventure-part-1 27 | slug: my-first-map 28 | - path: Maps/my-second-map.zip 29 | order: 5 30 | parent: my-adventure-part-1 31 | slug: my-second-map 32 | ``` 33 | 34 | ## Encounters 35 | Encounters are added in a way very similar to maps. 36 | 37 | 1. Create your encounter in EncounterPlus as your normally would - this includes creating your Encounter as part of a loaded map so monsters are positioned correctly. 38 | 2. View the Encounter in EncounterPlus's Library View. 39 | 3. Click the "..." icon and choose "Export". Save the zip file somewhere. 40 | 41 |

42 | Encounter Export 43 |

44 | 45 | 4. In your Module's directory, create a subfolder for maps (I named mine "Encounters"). 46 | 5. Create a `Group.yaml` file for this directory and set `include-in` to `files` and `copy-files` to `false` so a Group is not created and the raw zip files are not included in your module. 47 | 6. Copy the encounter's zip file into this folder. 48 | 7. In your `Module.yaml` file, create a property named `encounters`. Then, as children of encounters, create individual elements for each encounter. Each element must contain a "path" property pointing to the encounter's `.zip` file. Optionally, each element can also specify a `parent`, and `order`, and a `slug` property. These specify the parent the encounter will be nested under, the order it will show up under its parent, and its slug link respectively. 49 | 50 | Here is an example `Module.yaml` file with a couple of encounters defined after the maps. Note that you may nest encounters under maps as demonstrated. 51 | ```Markdown 52 | name: My Module 53 | slug: my-module 54 | description: A campaign setting. 55 | category: adventure 56 | maps: 57 | - path: Maps/my-first-map.zip 58 | order: 2 59 | parent: my-adventure-part-1 60 | slug: my-first-map 61 | - path: Maps/my-second-map.zip 62 | order: 5 63 | parent: my-adventure-part-1 64 | slug: my-second-map 65 | encounters: 66 | - path: Encounters/my-first-encounter.zip 67 | order: 1 68 | parent: my-first-map 69 | slug: my-first-encounter 70 | - path: Encounters/my-second-encounter.zip 71 | order: 2 72 | parent: my-first-map 73 | slug: my-second-encounter 74 | ``` -------------------------------------------------------------------------------- /documentation/CoverImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/CoverImage.jpg -------------------------------------------------------------------------------- /documentation/CustomCSS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/CustomCSS.jpg -------------------------------------------------------------------------------- /documentation/EncounterExport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/EncounterExport.png -------------------------------------------------------------------------------- /documentation/EncounterPlus.ulstyle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/EncounterPlus.ulstyle -------------------------------------------------------------------------------- /documentation/Flavortext.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Flavortext.jpg -------------------------------------------------------------------------------- /documentation/Flowchart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Flowchart.jpg -------------------------------------------------------------------------------- /documentation/Formats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Formats.jpg -------------------------------------------------------------------------------- /documentation/Heading1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Heading1.jpg -------------------------------------------------------------------------------- /documentation/Heading2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Heading2.jpg -------------------------------------------------------------------------------- /documentation/Heading3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Heading3.jpg -------------------------------------------------------------------------------- /documentation/Heading4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Heading4.jpg -------------------------------------------------------------------------------- /documentation/Heading5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Heading5.jpg -------------------------------------------------------------------------------- /documentation/Heading6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Heading6.jpg -------------------------------------------------------------------------------- /documentation/ImageFloat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/ImageFloat.jpg -------------------------------------------------------------------------------- /documentation/ImageResized.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/ImageResized.jpg -------------------------------------------------------------------------------- /documentation/Images.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Images.jpg -------------------------------------------------------------------------------- /documentation/Item.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Item.jpg -------------------------------------------------------------------------------- /documentation/LargeQuote.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/LargeQuote.jpg -------------------------------------------------------------------------------- /documentation/LinkColors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/LinkColors.jpg -------------------------------------------------------------------------------- /documentation/Links.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Links.jpg -------------------------------------------------------------------------------- /documentation/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Logo.png -------------------------------------------------------------------------------- /documentation/MapExport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/MapExport.png -------------------------------------------------------------------------------- /documentation/ModulePackerWalkthrough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/ModulePackerWalkthrough.png -------------------------------------------------------------------------------- /documentation/Monster-SingleColumn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Monster-SingleColumn.jpg -------------------------------------------------------------------------------- /documentation/Monster-TwoColumn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Monster-TwoColumn.jpg -------------------------------------------------------------------------------- /documentation/Paper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Paper.jpg -------------------------------------------------------------------------------- /documentation/ReadAloud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/ReadAloud.jpg -------------------------------------------------------------------------------- /documentation/ShopTable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/ShopTable.jpg -------------------------------------------------------------------------------- /documentation/Sidebar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Sidebar.jpg -------------------------------------------------------------------------------- /documentation/Spell.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Spell.jpg -------------------------------------------------------------------------------- /documentation/StatblockColors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/StatblockColors.jpg -------------------------------------------------------------------------------- /documentation/TableColors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/TableColors.jpg -------------------------------------------------------------------------------- /documentation/TextBlock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/TextBlock.jpg -------------------------------------------------------------------------------- /documentation/Ulysses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/Ulysses.png -------------------------------------------------------------------------------- /documentation/VSCodeLabels.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/VSCodeLabels.jpg -------------------------------------------------------------------------------- /documentation/VisualStudioCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/VisualStudioCode.png -------------------------------------------------------------------------------- /documentation/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/documentation/screenshot.png -------------------------------------------------------------------------------- /examples.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/examples.zip -------------------------------------------------------------------------------- /source/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /app-out 4 | /cli-out 5 | /extension-out 6 | /example 7 | /examples 8 | /.local-chromium 9 | *.vsix 10 | .DS_Store 11 | package.json 12 | package-lock.json 13 | -------------------------------------------------------------------------------- /source/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run VS Code Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/extension-out/**/*.js"], 15 | "preLaunchTask": "Build Extension", 16 | "resolveSourceMapLocations": null, 17 | "sourceMaps": true 18 | }, 19 | { 20 | "name": "Debug Module-Packer App", 21 | "type": "node", 22 | "request": "launch", 23 | "cwd": "${workspaceFolder}", 24 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 25 | "runtimeArgs": ["--remote-debugging-port=9223", "."], 26 | "windows": { 27 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 28 | }, 29 | "args": ["./app-out/app/main.js"], 30 | "preLaunchTask": "Build App", 31 | "outputCapture": "std", 32 | "resolveSourceMapLocations": null, 33 | "sourceMaps": true 34 | }, 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /source/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch-extension", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | }, 18 | "dependsOn": ["Build Extension"] 19 | }, 20 | { 21 | "type": "npm", 22 | "script": "watch-app", 23 | "problemMatcher": "$tsc-watch", 24 | "isBackground": true, 25 | "presentation": { 26 | "reveal": "never" 27 | }, 28 | "group": { 29 | "kind": "build", 30 | "isDefault": true 31 | }, 32 | "dependsOn": ["Build App"] 33 | }, 34 | { 35 | "label": "Build Extension", 36 | "type": "process", 37 | "problemMatcher": "$tsc-watch", 38 | "command": "${config:python.pythonPath}", 39 | "args": ["${workspaceFolder}/launcher.py", "build-extension"], 40 | "options": { 41 | "cwd": "${workspaceFolder}" 42 | } 43 | }, 44 | { 45 | "label": "Build App", 46 | "type": "process", 47 | "problemMatcher": "$tsc-watch", 48 | "command": "${config:python.pythonPath}", 49 | "args": ["${workspaceFolder}/launcher.py", "build-app"], 50 | "options": { 51 | "cwd": "${workspaceFolder}" 52 | } 53 | }, 54 | { 55 | "label": "Package App", 56 | "type": "process", 57 | "problemMatcher": "$tsc-watch", 58 | "command": "${config:python.pythonPath}", 59 | "args": ["${workspaceFolder}/launcher.py", "package-app"], 60 | "options": { 61 | "cwd": "${workspaceFolder}" 62 | } 63 | }, 64 | { 65 | "label": "Package Extension", 66 | "type": "process", 67 | "problemMatcher": "$tsc-watch", 68 | "command": "${config:python.pythonPath}", 69 | "args": ["${workspaceFolder}/launcher.py", "package-extension"], 70 | "options": { 71 | "cwd": "${workspaceFolder}" 72 | } 73 | } 74 | ] 75 | } -------------------------------------------------------------------------------- /source/.vscodeignore: -------------------------------------------------------------------------------- 1 | * 2 | ** 3 | !Logo.png 4 | !README.md 5 | !LICENSE.md 6 | !CHANGELOG.md 7 | !package.json 8 | !documentation 9 | !extension-out/ 10 | !node_modules/markdown-it-anchor 11 | !node_modules/markdown-it-attrs 12 | !node_modules/markdown-it-decorate 13 | !node_modules/markdown-it-fontawesome 14 | !node_modules/markdown-it-imsize 15 | !node_modules/markdown-it-mark 16 | !node_modules/markdown-it-multimd-table 17 | !node_modules/markdown-it-regexp 18 | !node_modules/markdown-it-sub 19 | !node_modules/markdown-it-sup 20 | !node_modules/markdown-it-underline -------------------------------------------------------------------------------- /source/LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /source/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/Logo.png -------------------------------------------------------------------------------- /source/app/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | -------------------------------------------------------------------------------- /source/app/main.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron' 2 | import * as FileSystem from 'fs-extra' 3 | import * as Path from 'path' 4 | import * as Logger from 'winston' 5 | import * as Transport from 'winston-transport' 6 | import { Module, ModuleMode } from '../shared/Module Entities/Module' 7 | import { ModuleProject } from '../shared/ModuleProject' 8 | import { PdfExporter } from '../shared/PdfExporter' 9 | 10 | /** 11 | * The Main Window 12 | * 13 | * Keep a global reference of the window object, if you don't, the window will 14 | * be closed automatically when the JavaScript object is garbage collected. 15 | */ 16 | let mainWindow: BrowserWindow 17 | 18 | /** The base path for processing one or more modules */ 19 | let basePath: string | undefined = undefined 20 | 21 | /** The path at which a module or PDF was exported */ 22 | let exportedPath: string | undefined = undefined 23 | 24 | // --------------------------------------------------------------- 25 | // Main Window Creation & App Messages 26 | // --------------------------------------------------------------- 27 | 28 | /** 29 | * Creates the app's window 30 | */ 31 | function createWindow() { 32 | const modulePackerLogger = new ModulePackerLogger() 33 | Logger.add(modulePackerLogger) 34 | 35 | // Create the browser window. 36 | mainWindow = new BrowserWindow({ 37 | width: 400, 38 | height: 600, 39 | resizable: true, 40 | backgroundColor: '#333333', 41 | icon: Path.join(__dirname, '../resources/img/icon.png'), 42 | webPreferences: { 43 | contextIsolation: false, 44 | nodeIntegration: true, 45 | preload: Path.join(__dirname, 'preload.js') 46 | }, 47 | }) 48 | 49 | // If running in a development environment, customize the window size 50 | let dev = false 51 | if (process.defaultApp || /[\\/]electron-prebuilt[\\/]/.test(process.execPath) || /[\\/]electron[\\/]/.test(process.execPath)) { 52 | dev = true 53 | } 54 | 55 | // and load the index.html of the app. 56 | mainWindow.loadFile(Path.join(__dirname, '../index.html')) 57 | 58 | // Open the DevTools. 59 | if (dev) { 60 | mainWindow.webContents.openDevTools() 61 | mainWindow.setSize(950, 600, false) 62 | mainWindow.center() 63 | } 64 | } 65 | 66 | /** 67 | * Quit when all windows are closed, except on macOS. There, it's common 68 | * for applications and their menu bar to stay active until the user quits 69 | * explicitly with Cmd + Q. 70 | */ 71 | app.on("window-all-closed", () => { 72 | if (process.platform !== "darwin") { 73 | app.quit() 74 | } 75 | }) 76 | 77 | /** 78 | * This method will be called when Electron has finished 79 | * initialization and is ready to create browser windows. 80 | * Some APIs can only be used after this event occurs. 81 | */ 82 | app.whenReady().then(() => { 83 | createWindow() 84 | app.on('activate', function () { 85 | // On macOS it's common to re-create a window in the app when the 86 | // dock icon is clicked and there are no other windows open. 87 | if (BrowserWindow.getAllWindows().length === 0) { 88 | createWindow() 89 | } 90 | }) 91 | }) 92 | 93 | /** 94 | * Handles an uncaught exception 95 | */ 96 | process.on('uncaughtException', function (error) { 97 | console.error((error as Error).message) 98 | mainWindow.webContents.send('error', (error as Error).message) 99 | }) 100 | 101 | // --------------------------------------------------------------- 102 | // IPC Main Message Handlers: 103 | // These messages come from the Renderer and should be handled 104 | // with Application Logic 105 | // --------------------------------------------------------------- 106 | 107 | ipcMain.on('exportToPdf', async (event: Electron.IpcMainInvokeEvent) => { 108 | exportToPdf() 109 | }) 110 | 111 | ipcMain.on('createModule', async (event: Electron.IpcMainInvokeEvent, name: string) => { 112 | createModule(name) 113 | }) 114 | 115 | ipcMain.on('openDirectory', async (event: Electron.IpcMainInvokeEvent) => { 116 | openDirectory() 117 | }) 118 | 119 | ipcMain.on('handlePathsSelection', async (event: Electron.IpcMainInvokeEvent, paths: string[]) => { 120 | handlePathsSelection(paths) 121 | }) 122 | 123 | ipcMain.on('showExportItem', async (event: Electron.IpcMainInvokeEvent) => { 124 | showExportItem() 125 | }) 126 | 127 | // --------------------------------------------------------------- 128 | // App Functionality 129 | // --------------------------------------------------------------- 130 | 131 | /** 132 | * Shows the export item in the system's file explorer 133 | */ 134 | function showExportItem() { 135 | shell.showItemInFolder(exportedPath) 136 | } 137 | 138 | /** 139 | * Creates a module from the specified path with the specified name 140 | * @param name The name of the module 141 | */ 142 | function createModule(name: string) { 143 | if (basePath === undefined) { 144 | return 145 | } 146 | 147 | try { 148 | createModuleFromPath(basePath, name) 149 | } catch (error: any) { 150 | mainWindow.webContents.send('error', (error as Error).message) 151 | } 152 | } 153 | 154 | /** 155 | * Triggers an Open Directory prompt 156 | */ 157 | function openDirectory() { 158 | try { 159 | dialog.showOpenDialog(({ properties: ['openDirectory'] })).then((result) => { 160 | let paths: string[] = [] 161 | if (!result.canceled) { 162 | paths = result.filePaths 163 | } 164 | 165 | handlePathsSelection(paths) 166 | }) 167 | } catch (error: any) { 168 | mainWindow.webContents.send('error', (error as Error).message) 169 | } 170 | } 171 | 172 | /** 173 | * Handles one or more paths being selected for module packing 174 | */ 175 | function handlePathsSelection(paths: string[]) { 176 | if (paths.length === 0) { 177 | return 178 | } 179 | 180 | let path = paths[0] 181 | basePath = FileSystem.statSync(path).isDirectory() ? path : Path.dirname(path) 182 | let moduleJsonName = Module.getModuleName(basePath) 183 | let moduleName = "" 184 | if (moduleJsonName !== undefined) { 185 | moduleName = moduleJsonName 186 | } else { 187 | moduleName = Path.basename(path) 188 | } 189 | 190 | mainWindow.webContents.send('pathSelected', moduleName, basePath) 191 | } 192 | 193 | /** 194 | * Updates the chromium install progress 195 | * @param progress The progress of the chromium install 196 | */ 197 | function updateChromiumInstallProgress(progress: number) { 198 | mainWindow.webContents.send('installProgressUpdate', progress) 199 | } 200 | 201 | /** 202 | * Creates the module from a path and name 203 | * @param path The path of the module 204 | * @param name The name of the module 205 | */ 206 | async function createModuleFromPath(path: string, name: string) { 207 | try { 208 | let moduleProjects = ModuleProject.findModuleProjects(path) 209 | let appRootPath = Path.join(__dirname, '..') 210 | 211 | if (moduleProjects.length > 1) { 212 | Logger.error('Error: Multiple modules at the specified path') 213 | mainWindow.webContents.send('error', 'There are multiple modules in the specified path.') 214 | return 215 | } 216 | 217 | let pathToExport = moduleProjects.length === 1 ? Path.dirname(moduleProjects[0].moduleProjectPath) : path 218 | let module = await Module.createModuleFromPath(pathToExport, appRootPath, name, ModuleMode.ModuleExport) 219 | exportedPath = module.moduleArchivePath 220 | mainWindow.webContents.send('successModule', module.moduleProjectInfo.name) 221 | Logger.info('Module created successfully') 222 | } catch(error) { 223 | mainWindow.webContents.send('error', (error as Error).message) 224 | } 225 | } 226 | 227 | /** 228 | * Creates the PDF from a path and name 229 | * @param path The path of the module 230 | * @param name The name of the module 231 | */ 232 | async function createPDFFromPath(path: string) { 233 | try { 234 | let moduleProjects = ModuleProject.findModuleProjects(path) 235 | let appRootPath = Path.join(__dirname, '..') 236 | 237 | if (moduleProjects.length > 1) { 238 | Logger.error('Error: Multiple modules at the specified path') 239 | mainWindow.webContents.send('error', 'There are multiple modules in the specified path.') 240 | return 241 | } 242 | 243 | let pathToExport = moduleProjects.length === 1 ? Path.dirname(moduleProjects[0].moduleProjectPath) : path 244 | const chromiumPath = Path.join(app.getPath("userData"), "chromium") 245 | PdfExporter.downloadFolder = chromiumPath 246 | await PdfExporter.installChromiumForRendering(updateChromiumInstallProgress) 247 | mainWindow.webContents.send('showStatusMessage', 'Exporting to PDF...') 248 | let outputPath = await PdfExporter.exportToPdf(pathToExport, appRootPath) 249 | exportedPath = outputPath 250 | let fileName = Path.basename(outputPath) 251 | mainWindow.webContents.send('successPdf', fileName) 252 | Logger.info('Module PDF created successfully') 253 | } catch(error) { 254 | mainWindow.webContents.send('error', (error as Error).message) 255 | } 256 | } 257 | 258 | /** 259 | * Triggers an export to PDF 260 | */ 261 | function exportToPdf() { 262 | if (basePath === undefined) { 263 | return 264 | } 265 | 266 | try { 267 | createPDFFromPath(basePath) 268 | } catch (error: any) { 269 | mainWindow.webContents.send('error', (error as Error).message) 270 | } 271 | } 272 | 273 | // --------------------------------------------------------------- 274 | // ModulePackerLogger class 275 | // --------------------------------------------------------------- 276 | 277 | /** 278 | * A simple logger transport for directing 279 | * Winston logs to Module Packer console output. 280 | */ 281 | export class ModulePackerLogger extends Transport { 282 | /** 283 | * Processes a log message 284 | * @param info The log info 285 | * @param callback The log callback 286 | */ 287 | log(info: any, callback: any) { 288 | setImmediate(() => { 289 | setImmediate(() => this.emit('logged', info)) 290 | }) 291 | 292 | switch (info['level']) { 293 | case 'warn': 294 | console.warn(info['message']) 295 | break 296 | case 'error': 297 | console.error(info['message']) 298 | break 299 | default: 300 | console.log(info['message']) 301 | break 302 | } 303 | 304 | if (callback) { 305 | callback() 306 | } 307 | } 308 | } -------------------------------------------------------------------------------- /source/app/package.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-packer", 3 | "displayName": "EncounterPlus Module Packer", 4 | "description": "Tools for creating EncounterPlus modules from markdown.", 5 | "icon": "build/icon.icns", 6 | "version": "1.0.64", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/encounterplus/module-packer" 10 | }, 11 | "author": "QBIT, s.r.o", 12 | "license": "CC0-1.0", 13 | "bugs": { 14 | "url": "https://github.com/encounterplus/module-packer/issues" 15 | }, 16 | "homepage": "https://github.com/encounterplus/module-packer", 17 | "categories": [ 18 | "Other" 19 | ], 20 | "keywords": [ 21 | "EncounterPlus", 22 | "iOS", 23 | "module", 24 | "packer" 25 | ], 26 | "main": "./app-out/app/main.js", 27 | "build": { 28 | "appId": "com.jjohnston.modulepacker", 29 | "files": ["app-out/**/*", "node_modules/**/*", "package.json"], 30 | "win": { 31 | "target": { 32 | "target": "NSIS", 33 | "arch": [ 34 | "x64" 35 | ] 36 | } 37 | }, 38 | "mac": { 39 | "category": "public.app-category.utilities", 40 | "identity": "UMJUH72CLG", 41 | "hardenedRuntime": true, 42 | "entitlements": "./app/entitlements.mac.plist", 43 | "entitlementsInherit": "./app/entitlements.mac.plist", 44 | "target": [ 45 | { 46 | "target": "dmg", 47 | "arch": "universal" 48 | } 49 | ] 50 | }, 51 | "asar": true 52 | }, 53 | "publish": { 54 | "provider": "github", 55 | "owner": "encounterplus", 56 | "repo": "module-packer" 57 | }, 58 | "scripts": { 59 | "lint": "eslint . --ext .ts,.tsx", 60 | "compile-css": "lessc ./assets/base/css/global.less ./assets/base/css/global.css && lessc ./assets/print/css/print.less ./assets/print/css/print.css && lessc ./assets/print/css/print_a4.less ./assets/print/css/print_a4.css", 61 | "compile-app": "tsc -p tsconfig.app.json && ncp ./app/resources ./app-out && ncp ./assets ./app-out/assets", 62 | "watch-app": "tsc -watch -p tsconfig.app.json", 63 | "start": "npm run compile-app && electron ./app-out/app/main.js", 64 | "build-mac": "electron-builder build --mac", 65 | "build-win": "electron-builder build --win", 66 | "build-all": "electron-builder -mw" 67 | }, 68 | "devDependencies": { 69 | "@types/node": "^18.15.0", 70 | "@typescript-eslint/eslint-plugin": "^6.7.4", 71 | "@typescript-eslint/parser": "^6.7.4", 72 | "electron": "^29.1.0", 73 | "electron-builder": "^24.13.3", 74 | "eslint": "^8.51.0", 75 | "less": "^4.2.0", 76 | "ncp": "^2.0.0", 77 | "typescript": "^5.2.2" 78 | }, 79 | "dependencies": { 80 | "@lillallol/outline-pdf": "^4.0.0", 81 | "@types/archiver": "^5.3.2", 82 | "@types/cheerio": "0.22.22", 83 | "@types/fs-extra": "11.0.1", 84 | "@types/markdown-it": "^12.2.3", 85 | "@types/uuid": "^8.3.1", 86 | "archiver": "^7.0.0", 87 | "cheerio": "1.0.0-rc.12", 88 | "extract-zip": "^2.0.1", 89 | "fast-xml-parser": "^4.3.5", 90 | "fs-extra": "^11.2.0", 91 | "glob": "^10.3.10", 92 | "gray-matter": "^4.0.3", 93 | "markdown-it": "^12.3.2", 94 | "markdown-it-anchor": "^8.6.7", 95 | "markdown-it-attrs": "^4.1.6", 96 | "markdown-it-decorate": "^1.2.2", 97 | "markdown-it-fontawesome": "^0.3.0", 98 | "markdown-it-imsize": "^2.0.1", 99 | "markdown-it-mark": "^4.0.0", 100 | "markdown-it-multimd-table": "^4.2.3", 101 | "markdown-it-regexp": "^0.4.0", 102 | "markdown-it-sub": "^2.0.0", 103 | "markdown-it-sup": "^2.0.0", 104 | "markdown-it-underline": "^1.0.1", 105 | "path": "^0.12.7", 106 | "puppeteer-core": "19.8.3", 107 | "slugify": "^1.6.6", 108 | "uuid": "^9.0.1", 109 | "winston": "^3.11.0", 110 | "yaml": "^2.4.0" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /source/app/preload.ts: -------------------------------------------------------------------------------- 1 | window.addEventListener('DOMContentLoaded', () => { 2 | const replaceText = (selector: string, text: string) => { 3 | const element = document.getElementById(selector) 4 | if (element) { 5 | element.innerText = text 6 | } 7 | } 8 | 9 | for (const dependency of ['chrome', 'node', 'electron']) { 10 | replaceText(`${dependency}-version`, process.versions[dependency]) 11 | } 12 | }) -------------------------------------------------------------------------------- /source/app/renderer.ts: -------------------------------------------------------------------------------- 1 | // This file is required by the index.html file and will 2 | // be executed in the renderer process for that window. 3 | // No Node.js APIs are available in this process unless 4 | // nodeIntegration is set to true in webPreferences. 5 | // Use preload.js to selectively enable features 6 | // needed in the renderer process. 7 | 8 | import * as Electron from 'electron' 9 | 10 | const addButton = document.getElementById('add-button') 11 | const addLabel = document.getElementById('add-label') 12 | const createButton = document.getElementById('build-module-button') 13 | const exportButton = document.getElementById('export-to-pdf-button') 14 | const moduleSection = document.getElementById('module-section') 15 | const nameInput = document.getElementById('name-input') as HTMLInputElement 16 | const statusInfo = document.getElementById('status-info') 17 | const addModuleJson = document.getElementById('add-moduleJson') 18 | const statusLink = document.getElementById('status-link') 19 | const container = document.getElementById('container') 20 | 21 | addButton.onclick = async () => { 22 | statusInfo.classList.add('invisible') 23 | statusLink.classList.add('invisible') 24 | Electron.ipcRenderer.send('openDirectory') 25 | } 26 | 27 | statusLink.onclick = (event) => { 28 | event.preventDefault() 29 | Electron.ipcRenderer.send('showExportItem', name) 30 | } 31 | 32 | createButton.onclick = (event) => { 33 | event.preventDefault() 34 | let name = nameInput.value 35 | statusInfo.classList.remove('invisible') 36 | statusInfo.innerHTML = 'Processing' 37 | statusLink.classList.add('invisible') 38 | Electron.ipcRenderer.send('createModule', name) 39 | } 40 | 41 | exportButton.onclick = (event) => { 42 | event.preventDefault() 43 | statusInfo.classList.remove('invisible') 44 | statusInfo.innerHTML = 'Processing' 45 | statusLink.classList.add('invisible') 46 | Electron.ipcRenderer.send('exportToPdf') 47 | } 48 | 49 | Electron.ipcRenderer.on('pathSelected', (event: Electron.IpcRendererEvent, moduleName: string | undefined, path: string) => { 50 | addLabel.innerText = path 51 | nameInput.value = moduleName 52 | nameInput.disabled = true 53 | addModuleJson.classList.remove('invisible') 54 | addLabel.classList.remove('d-none') 55 | moduleSection.classList.remove('d-none') 56 | statusInfo.classList.add('invisible') 57 | statusLink.classList.add('invisible') 58 | }) 59 | 60 | Electron.ipcRenderer.on('successModule', (event: Electron.IpcRendererEvent, moduleName: string) => { 61 | statusInfo.classList.remove('invisible') 62 | statusInfo.innerHTML = 'Successfully Created Module' 63 | statusLink.innerHTML = moduleName 64 | statusLink.classList.remove('invisible') 65 | }) 66 | 67 | Electron.ipcRenderer.on('successPdf', (event: Electron.IpcRendererEvent, fileName: string) => { 68 | statusInfo.classList.remove('invisible') 69 | statusInfo.innerHTML = 'Successfully Exported PDF' 70 | statusLink.innerHTML = fileName 71 | statusLink.classList.remove('invisible') 72 | }) 73 | 74 | Electron.ipcRenderer.on('installProgressUpdate', (event: Electron.IpcRendererEvent, progress: number) => { 75 | statusInfo.classList.remove('invisible') 76 | statusInfo.innerHTML = `Rendering Engine Install: ${progress.toFixed(1)}%` 77 | statusLink.classList.add('invisible') 78 | }) 79 | 80 | Electron.ipcRenderer.on('showStatusMessage', (event: Electron.IpcRendererEvent, message: string) => { 81 | statusInfo.classList.remove('invisible') 82 | statusInfo.innerHTML = message 83 | statusLink.classList.add('invisible') 84 | }) 85 | 86 | Electron.ipcRenderer.on('error', (event: Electron.IpcRendererEvent, message: string) => { 87 | statusInfo.classList.remove('invisible') 88 | statusInfo.innerHTML = 89 | 'Error: ' + message + '' 90 | statusLink.classList.add('invisible') 91 | console.error(message) 92 | }) 93 | 94 | container.ondragover = () => { 95 | return false 96 | } 97 | 98 | container.ondragleave = () => { 99 | return false 100 | } 101 | 102 | container.ondragend = () => { 103 | return false 104 | } 105 | 106 | container.ondrop = (event) => { 107 | event.preventDefault() 108 | let paths = [] 109 | let files = event.dataTransfer.files 110 | for (var i = 0; i < files.length; i++) { 111 | paths.push(files.item(i).path) 112 | } 113 | Electron.ipcRenderer.send('handlePathsSelection', paths) 114 | return false 115 | } -------------------------------------------------------------------------------- /source/app/resources/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.5.0 (https://getbootstrap.com/) 3 | * Copyright 2011-2020 The Bootstrap Authors 4 | * Copyright 2011-2020 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /source/app/resources/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | html, body { 3 | height: 100%; 4 | background-color: #333; 5 | } 6 | 7 | body { 8 | font-size: 13px; 9 | line-height: 18px; 10 | padding: 0px; 11 | margin: 0px; 12 | color: #ddd; 13 | background: url('../img/bg.jpg') #333333 repeat; 14 | 15 | text-align: center; 16 | display: flex; 17 | 18 | -webkit-user-select: none; 19 | -webkit-app-region: drag; 20 | } 21 | 22 | #container { 23 | width: 100%; 24 | height: 100%; 25 | 26 | /* background: url('../img/stain.png') center -440px; 27 | background-repeat: no-repeat;*/ 28 | } 29 | 30 | #footer { 31 | margin-top: auto; 32 | } 33 | 34 | /*#footer a { 35 | color: white; 36 | }*/ 37 | 38 | #content { 39 | padding: 14px; 40 | margin-top: auto; 41 | } 42 | 43 | #add-section p { 44 | margin-bottom: 5px; 45 | font-weight: 300; 46 | 47 | } 48 | 49 | #add-button { 50 | background-color: rgba(255,255,255, 0.1); 51 | display: inline-block; 52 | border-radius: 50%; 53 | transition: 0.3s; 54 | margin: 30px; 55 | } 56 | 57 | #add-button:hover { 58 | background-color: rgba(255,255,255, 0.3); 59 | } 60 | 61 | #help-button img { 62 | color: white; 63 | padding: 20%; 64 | } 65 | 66 | #help-button { 67 | position: absolute; 68 | right: 0px; 69 | background-color: rgba(255,255,255, 0.1); 70 | border-radius: 50%; 71 | transition: 0.3s; 72 | margin: 5px; 73 | width: 32px; 74 | height: 32px; 75 | } 76 | 77 | #name-input:disabled { 78 | background-color: #495057 79 | } 80 | 81 | #help-button:hover { 82 | background-color: rgba(255,255,255, 0.3); 83 | } 84 | 85 | #add-button img { 86 | width: 100px; 87 | height: 100px; 88 | color: white; 89 | padding: 20%; 90 | } 91 | 92 | #build-module-button { 93 | width: 200px; 94 | height: 40px; 95 | margin: 10px; 96 | } 97 | 98 | #export-to-pdf-button { 99 | width: 200px; 100 | height: 40px; 101 | margin: 10px; 102 | } 103 | 104 | #module-section { 105 | max-width: 260px; 106 | margin-top: 30px; 107 | margin-left: auto; 108 | margin-right: auto; 109 | } 110 | 111 | #status { 112 | padding: 10px; 113 | } 114 | 115 | #status-info { 116 | margin-bottom: 5px; 117 | } 118 | 119 | .form-control { 120 | color: white; 121 | border-color: rgba(255,255,255,0.15); 122 | background-color: rgba(255,255,255,0.05); 123 | } 124 | 125 | .form-control:focus { 126 | color: white; 127 | background-color: rgba(0,0,0,0.2); 128 | border-color: rgba(255,255,255,0.2); 129 | outline: 0; 130 | box-shadow: none; 131 | } 132 | 133 | img { 134 | max-width: 100%; 135 | height: auto; 136 | } 137 | 138 | .button-image { 139 | width: 24px; 140 | height: 24px; 141 | margin-right: 10px; 142 | } -------------------------------------------------------------------------------- /source/app/resources/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/app/resources/fonts/icomoon.eot -------------------------------------------------------------------------------- /source/app/resources/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/app/resources/fonts/icomoon.ttf -------------------------------------------------------------------------------- /source/app/resources/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/app/resources/fonts/icomoon.woff -------------------------------------------------------------------------------- /source/app/resources/img/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /source/app/resources/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/app/resources/img/bg.jpg -------------------------------------------------------------------------------- /source/app/resources/img/build-module.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/app/resources/img/export-to-pdf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /source/app/resources/img/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/app/resources/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/app/resources/img/icon.png -------------------------------------------------------------------------------- /source/app/resources/img/stain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/app/resources/img/stain.png -------------------------------------------------------------------------------- /source/app/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EncounterPlus Module Packer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | Help 19 | 20 |
21 | 22 | 23 |
24 | Add Module 25 |

Select a folder you want to pack.

26 | Unknown path 27 |
28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 |
36 | 37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 | 45 |
46 | 47 | 48 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /source/assets/base/css/fontawesome/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/css/fontawesome/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /source/assets/base/font/AndadaSC-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/AndadaSC-Bold.ttf -------------------------------------------------------------------------------- /source/assets/base/font/AndadaSC-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/AndadaSC-BoldItalic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/AndadaSC-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/AndadaSC-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/AndadaSC-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/AndadaSC-Regular.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Bookinsanity-Bold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Bookinsanity-Bold-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Bookinsanity-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Bookinsanity-Bold.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Bookinsanity-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Bookinsanity-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Bookinsanity.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Bookinsanity.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Dungeon-Drop-Case.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Dungeon-Drop-Case.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Mr-Eaves-Small-Caps.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Mr-Eaves-Small-Caps.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Nodesto-Caps-Condensed-Bold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Nodesto-Caps-Condensed-Bold-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Nodesto-Caps-Condensed-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Nodesto-Caps-Condensed-Bold.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Nodesto-Caps-Condensed-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Nodesto-Caps-Condensed-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Nodesto-Caps-Condensed.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Nodesto-Caps-Condensed.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans-Bold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans-Bold-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans-Bold.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans-Caps-Bold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans-Caps-Bold-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans-Caps-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans-Caps-Bold.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans-Caps-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans-Caps-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans-Caps.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans-Caps.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Scaly-Sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Scaly-Sans.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Solbera-Imitation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Solbera-Imitation.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Zatanna-Misdirection-Bold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Zatanna-Misdirection-Bold-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Zatanna-Misdirection-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Zatanna-Misdirection-Bold.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Zatanna-Misdirection-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Zatanna-Misdirection-Italic.ttf -------------------------------------------------------------------------------- /source/assets/base/font/Zatanna-Misdirection.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/font/Zatanna-Misdirection.ttf -------------------------------------------------------------------------------- /source/assets/base/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/img/bg.png -------------------------------------------------------------------------------- /source/assets/base/img/flavortext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/img/flavortext.png -------------------------------------------------------------------------------- /source/assets/base/img/flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/img/flowchart.png -------------------------------------------------------------------------------- /source/assets/base/img/item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/img/item.png -------------------------------------------------------------------------------- /source/assets/base/img/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/base/img/note.png -------------------------------------------------------------------------------- /source/assets/print/css/print.less: -------------------------------------------------------------------------------- 1 | /** Bugs in chromium prevent print-only fonts and images from being rendered. Embedding the images 2 | ** and fonts as base64 encoded resources serves as a workaround. See here: 3 | ** https://bugs.chromium.org/p/chromium/issues/detail?id=284840 4 | **/ 5 | 6 | body, 7 | blockquote.flavortext { 8 | font-family: 'Bookinsanity Remake', '-apple-system', sans-serif !important; 9 | } 10 | 11 | @media print { 12 | .footer-background { 13 | background-image: data-uri('../img/footer.svg'); 14 | } 15 | 16 | .print-page { 17 | background-image: data-uri('../img/print-background.jpg'); 18 | } 19 | } -------------------------------------------------------------------------------- /source/assets/print/css/print_a4.less: -------------------------------------------------------------------------------- 1 | /** Bugs in chromium prevent print-only fonts and images from being rendered. Embedding the images 2 | ** and fonts as base64 encoded resources serves as a workaround. See here: 3 | ** https://bugs.chromium.org/p/chromium/issues/detail?id=284840 4 | **/ 5 | 6 | body, 7 | blockquote.flavortext { 8 | font-family: 'Bookinsanity Remake', '-apple-system', sans-serif !important; 9 | } 10 | 11 | @media print { 12 | div.print-two-column { 13 | &:not(.statblock) { 14 | width: 210mm - 1in; 15 | } 16 | } 17 | 18 | .print-cover-page, 19 | .print-section-cover-page { 20 | height: 297mm; 21 | width: 210mm; 22 | } 23 | 24 | .print-page { 25 | background-image: data-uri('../img/print-background-a4.jpg'); 26 | height: 297mm; 27 | width: 210mm; 28 | 29 | .footer-background { 30 | background-image: data-uri('../img/footer-a4.svg'); 31 | height: 0.5in; 32 | width: 210mm; 33 | background-size: 210mm 0.5in; 34 | } 35 | 36 | .page-content { 37 | height: 275mm - 1.1in; 38 | width: 210mm - 1in; 39 | } 40 | 41 | .footer-content { 42 | width: 210mm; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /source/assets/print/img/footer-a4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/assets/print/img/footer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/assets/print/img/print-background-a4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/print/img/print-background-a4.jpg -------------------------------------------------------------------------------- /source/assets/print/img/print-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/assets/print/img/print-background.jpg -------------------------------------------------------------------------------- /source/build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/build/icon.icns -------------------------------------------------------------------------------- /source/cli/main.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path' 2 | import * as Logger from 'winston' 3 | import * as Transport from 'winston-transport' 4 | import { Module, ModuleMode } from '../shared/Module Entities/Module' 5 | import { ModuleProject } from '../shared/ModuleProject' 6 | import { PdfExporter } from '../shared/PdfExporter' 7 | 8 | async function main() 9 | { 10 | const modulePackerLogger = new ModulePackerLogger() 11 | Logger.add(modulePackerLogger) 12 | let args = process.argv 13 | 14 | if (args.length < 3) { 15 | console.error('A Module path must be specified') 16 | return 17 | } 18 | let isPdfOutput = args.length > 3 && process.argv[3].toLowerCase() === 'pdf' 19 | let path = process.argv[2] 20 | if (isPdfOutput) { 21 | await createPDFFromPath(path, Path.basename(path)) 22 | } else { 23 | await createModuleFromPath(path, Path.basename(path)) 24 | } 25 | } 26 | 27 | 28 | /** 29 | * Creates the module from a path and name 30 | * @param path The path of the module 31 | * @param name The name of the module 32 | */ 33 | async function createModuleFromPath(path: string, name: string) { 34 | try { 35 | let moduleProjects = ModuleProject.findModuleProjects(path) 36 | let appRootPath = Path.join(__dirname, '..') 37 | if (moduleProjects.length === 0) { 38 | await Module.createModuleFromPath(path, appRootPath, name, ModuleMode.ModuleExport) 39 | Logger.info('Module created successfully') 40 | } else if (moduleProjects.length === 1) { 41 | let modulePath = Path.dirname(moduleProjects[0].moduleProjectPath) 42 | await Module.createModuleFromPath(modulePath, appRootPath, name, ModuleMode.ModuleExport) 43 | Logger.info('Module created successfully') 44 | } else { 45 | Logger.error('Error: Multiple modules at the specified path') 46 | } 47 | } catch(error) { 48 | Logger.error((error as Error).message) 49 | } 50 | } 51 | 52 | /** 53 | * Creates the PDF from a path and name 54 | * @param path The path of the module 55 | * @param name The name of the module 56 | */ 57 | async function createPDFFromPath(path: string, name: string) { 58 | try { 59 | let moduleProjects = ModuleProject.findModuleProjects(path) 60 | let appRootPath = Path.join(__dirname, '..') 61 | if (moduleProjects.length === 0) { 62 | await PdfExporter.installChromiumForRendering(updateChromiumInstallProgress) 63 | await PdfExporter.exportToPdf(path, appRootPath) 64 | Logger.info('Module PDF created successfully') 65 | } else if (moduleProjects.length === 1) { 66 | let moduleFolderPath = Path.dirname(moduleProjects[0].moduleProjectPath) 67 | await PdfExporter.installChromiumForRendering(updateChromiumInstallProgress) 68 | await PdfExporter.exportToPdf(moduleFolderPath, appRootPath) 69 | Logger.info('Module PDF created successfully') 70 | } else { 71 | Logger.error('Error: Multiple modules at the specified path') 72 | } 73 | } catch(error) { 74 | Logger.error((error as Error).message) 75 | } 76 | } 77 | 78 | function updateChromiumInstallProgress(progress: number) { 79 | Logger.info(`Installing chromium renderer: ${progress}%`) 80 | } 81 | 82 | /** 83 | * A simple logger transport for directing 84 | * Winston logs to Module Packer console output. 85 | */ 86 | export class ModulePackerLogger extends Transport { 87 | /** 88 | * Processes a log message 89 | * @param info The log info 90 | * @param callback The log callback 91 | */ 92 | log(info: any, callback: any) { 93 | setImmediate(() => { 94 | setImmediate(() => this.emit('logged', info)) 95 | }) 96 | 97 | switch (info['level']) { 98 | case 'warn': 99 | console.warn(info['message']) 100 | break 101 | case 'error': 102 | console.error(info['message']) 103 | break 104 | default: 105 | console.log(info['message']) 106 | break 107 | } 108 | 109 | if (callback) { 110 | callback() 111 | } 112 | } 113 | } 114 | 115 | main() -------------------------------------------------------------------------------- /source/cli/package.cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-packer", 3 | "displayName": "EncounterPlus Module Packer", 4 | "description": "Tools for creating EncounterPlus modules from markdown.", 5 | "version": "1.0.64", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/encounterplus/module-packer" 9 | }, 10 | "author": "QBIT, s.r.o", 11 | "license": "CC0-1.0", 12 | "bugs": { 13 | "url": "https://github.com/encounterplus/module-packer/issues" 14 | }, 15 | "homepage": "https://github.com/encounterplus/module-packer", 16 | "categories": [ 17 | "Other" 18 | ], 19 | "keywords": [ 20 | "EncounterPlus", 21 | "iOS", 22 | "module", 23 | "packer" 24 | ], 25 | "main": "./cli-out/cli/main.js", 26 | "publish": { 27 | "provider": "github", 28 | "owner": "encounterplus", 29 | "repo": "module-packer" 30 | }, 31 | "scripts": { 32 | "lint": "eslint . --ext .ts,.tsx", 33 | "compile-css": "lessc ./assets/base/css/global.less ./assets/base/css/global.css && lessc ./assets/print/css/print.less ./assets/print/css/print.css && lessc ./assets/print/css/print_a4.less ./assets/print/css/print_a4.css", 34 | "compile-cli": "tsc -p tsconfig.cli.json && ncp ./assets ./cli-out/assets" 35 | }, 36 | "devDependencies": { 37 | "@types/node": "^18.15.0", 38 | "@typescript-eslint/eslint-plugin": "^6.7.4", 39 | "@typescript-eslint/parser": "^6.7.4", 40 | "eslint": "^8.51.0", 41 | "less": "^4.2.0", 42 | "ncp": "^2.0.0", 43 | "typescript": "^5.2.2" 44 | }, 45 | "dependencies": { 46 | "@lillallol/outline-pdf": "^4.0.0", 47 | "@types/archiver": "^5.3.2", 48 | "@types/cheerio": "0.22.22", 49 | "@types/fs-extra": "11.0.1", 50 | "@types/markdown-it": "^12.2.3", 51 | "@types/uuid": "^8.3.1", 52 | "archiver": "^7.0.0", 53 | "cheerio": "1.0.0-rc.12", 54 | "extract-zip": "^2.0.1", 55 | "fast-xml-parser": "^4.3.5", 56 | "fs-extra": "^11.2.0", 57 | "glob": "^10.3.10", 58 | "gray-matter": "^4.0.3", 59 | "markdown-it": "^12.3.2", 60 | "markdown-it-anchor": "^8.6.7", 61 | "markdown-it-attrs": "^4.1.6", 62 | "markdown-it-decorate": "^1.2.2", 63 | "markdown-it-fontawesome": "^0.3.0", 64 | "markdown-it-imsize": "^2.0.1", 65 | "markdown-it-mark": "^4.0.0", 66 | "markdown-it-multimd-table": "^4.2.3", 67 | "markdown-it-regexp": "^0.4.0", 68 | "markdown-it-sub": "^2.0.0", 69 | "markdown-it-sup": "^2.0.0", 70 | "markdown-it-underline": "^1.0.1", 71 | "path": "^0.12.7", 72 | "puppeteer-core": "19.8.3", 73 | "slugify": "^1.6.6", 74 | "uuid": "^9.0.1", 75 | "winston": "^3.11.0", 76 | "yaml": "^2.4.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /source/image-source/Footer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/image-source/FooterA4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/image-source/FooterCurves.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/FooterCurves.afdesign -------------------------------------------------------------------------------- /source/image-source/Logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/Logo.afdesign -------------------------------------------------------------------------------- /source/image-source/PringBG.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/PringBG.jpg -------------------------------------------------------------------------------- /source/image-source/PringBGA4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/PringBGA4.jpg -------------------------------------------------------------------------------- /source/image-source/build-module.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/build-module.afdesign -------------------------------------------------------------------------------- /source/image-source/encounterplus-markdown.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/encounterplus-markdown.afdesign -------------------------------------------------------------------------------- /source/image-source/export-to-pdf.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/export-to-pdf.afdesign -------------------------------------------------------------------------------- /source/image-source/flowchart.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/flowchart.afdesign -------------------------------------------------------------------------------- /source/image-source/flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/flowchart.png -------------------------------------------------------------------------------- /source/image-source/item.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/item.afdesign -------------------------------------------------------------------------------- /source/image-source/item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/item.png -------------------------------------------------------------------------------- /source/image-source/note.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/note.afdesign -------------------------------------------------------------------------------- /source/image-source/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encounterplus/module-packer/d8229ee926635e09444eae9977092d01f33b73c5/source/image-source/note.png -------------------------------------------------------------------------------- /source/launcher.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import contextlib 3 | import os 4 | import shutil 5 | import subprocess 6 | import sys 7 | 8 | 9 | # Defines common parameters for running shell script 10 | def run(cmd, capture_output=False, print_command=True, print_output=True, return_must_be_zero=True): 11 | if print_command: 12 | print('Running Process: ' + cmd, flush=True) 13 | run_complete = subprocess.run(cmd, shell=True, cwd=os.getcwd(), capture_output=capture_output) 14 | output_string = run_complete.stdout if run_complete.returncode == 0 else run_complete.stderr 15 | if capture_output == True and print_output == True: 16 | print(output_string, flush=True) 17 | if run_complete.returncode != 0 and return_must_be_zero: 18 | sys.exit(run_complete.returncode) 19 | return output_string 20 | 21 | def removeIfExists(path): 22 | if os.path.exists(path): 23 | print('Deleting file {}'.format(path), flush=True) 24 | os.remove(path) 25 | 26 | def removeDirIfExists(path): 27 | if os.path.exists(path): 28 | print('Deleting folder {}'.format(path), flush=True) 29 | shutil.rmtree(path) 30 | 31 | def copy(source, destination): 32 | print('Copying from {} to {}'.format(source, destination), flush=True) 33 | shutil.copy(source, destination) 34 | 35 | def copyDir(source, destination): 36 | print('Copying from {} to {}'.format(source, destination), flush=True) 37 | shutil.copytree(source, destination) 38 | 39 | def createFolder(folder): 40 | if not os.path.exists(folder): 41 | print('Creating folder {}'.format(folder), flush=True) 42 | os.mkdir(folder) 43 | # Mark this folder to be ignored on Dropbox on macOS 44 | if sys.platform == 'darwin': 45 | run('xattr -w com.dropbox.ignored 1 {}'.format(folder), False, False) 46 | elif sys.platform == 'win32': 47 | run('powershell.exe Set-Content -Path "{}" -Stream com.dropbox.ignored -Value 1'.format(folder), False, False) 48 | 49 | def processTarget(target): 50 | if target == 'makeFolders': 51 | createFolder('./node_modules') 52 | createFolder('./dist') 53 | createFolder('./app-out') 54 | createFolder('./cli-out') 55 | createFolder('./extension-out') 56 | createFolder('./.local-chromium') 57 | elif target == 'clean': 58 | removeIfExists('./package.json') 59 | removeIfExists('./package-lock.json') 60 | removeDirIfExists('./node_modules') 61 | removeDirIfExists('./dist') 62 | removeDirIfExists('./app-out') 63 | removeDirIfExists('./extension-out') 64 | removeDirIfExists('./Documentation') 65 | removeDirIfExists('./.local-chromium') 66 | removeIfExists('./README.md') 67 | removeIfExists('./LICENSE.md') 68 | removeIfExists('./Advanced.md') 69 | processTarget('makeFolders') 70 | elif target == 'build-extension': 71 | removeIfExists('./package.json') 72 | removeIfExists('./package-lock.json') 73 | removeDirIfExists('./extension-out') 74 | processTarget('makeFolders') 75 | copy('./vscode-extension/package.extension.json', './package.json') 76 | run('npm install') 77 | run('npm run compile-css') 78 | run('npm run compile-extension') 79 | elif target == 'build-app': 80 | removeIfExists('./package.json') 81 | removeIfExists('./package-lock.json') 82 | removeDirIfExists('./app-out') 83 | processTarget('makeFolders') 84 | copy('./app/package.app.json', './package.json') 85 | run('npm install') 86 | run('npm run compile-css') 87 | run('npm run compile-app') 88 | elif target == 'start-app': 89 | processTarget('build-app') 90 | run('npm run start') 91 | elif target == 'run': 92 | removeIfExists('./package.json') 93 | removeIfExists('./package-lock.json') 94 | removeDirIfExists('./cli-out') 95 | processTarget('makeFolders') 96 | copy('./cli/package.cli.json', './package.json') 97 | run('npm install') 98 | run('npm run compile-css') 99 | run('npm run compile-cli') 100 | run('node ./cli-out/cli/main.js "{}" "{}"'.format(path or "", output or "")) 101 | elif target == 'package-extension': 102 | processTarget('clean') 103 | copyDir('../Documentation', './Documentation') 104 | copy('../README.md', './README.md') 105 | copy('../LICENSE.md', './LICENSE.md') 106 | copy('../Advanced.md', './Advanced.md') 107 | processTarget('build-extension') 108 | run('vsce package') 109 | elif target == 'package-app': 110 | processTarget('clean') 111 | processTarget('build-app') 112 | run('npm run build-all') 113 | else: 114 | print('Error - unrecognized launch target specified: \"{}\"'.format(target)) 115 | exit(1) 116 | 117 | # Parse the arguments 118 | parser = argparse.ArgumentParser() 119 | parser.add_argument("target", help='The target to run') 120 | parser.add_argument("--path", help='The path to build when running CLI') 121 | parser.add_argument("--output", help='If specified, PDF output will be used') 122 | args = parser.parse_args() 123 | target = args.target 124 | path = args.path 125 | output = args.output 126 | 127 | processTarget(target) -------------------------------------------------------------------------------- /source/module-packer.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "editor.tabSize": 2, 9 | "prettier.semi": false, 10 | "prettier.singleQuote": true, 11 | "prettier.printWidth": 140, 12 | "cSpell.words": [ 13 | "autohiding", 14 | "ignoregroup", 15 | "ms", 16 | "ms autohiding scrollbar", 17 | "opentype", 18 | "truetype", 19 | "pagebreak", 20 | "pagebreaks", 21 | "scrollbar", 22 | "solbera", 23 | "statblock" 24 | ], 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/shared/EncounterFileReference.ts: -------------------------------------------------------------------------------- 1 | import * as FileSystem from 'fs-extra' 2 | import * as Path from 'path' 3 | import * as ExtractZip from 'extract-zip' 4 | import { XMLParser } from 'fast-xml-parser' 5 | import * as Logger from 'winston' 6 | import { Encounter } from './Module Entities/Encounter' 7 | 8 | /** 9 | * Defines a reference to an encounter file. 10 | */ 11 | export class EncounterFileReference { 12 | 13 | // --------------------------------------------------------------- 14 | // Public Properties 15 | // --------------------------------------------------------------- 16 | 17 | /** The path to the encounter file */ 18 | path: string = '' 19 | 20 | /** The sort order of the entity */ 21 | sort: number | undefined = undefined 22 | 23 | /** The specified slug for the entity */ 24 | slug: string | undefined = undefined 25 | 26 | /** The parent entity */ 27 | parentSlug: string | undefined = undefined 28 | 29 | // --------------------------------------------------------------- 30 | // Initialization & Cleanup 31 | // --------------------------------------------------------------- 32 | 33 | /** 34 | * Initializes an instance of `EncounterFileReference` 35 | */ 36 | constructor(path: string, sort: number | undefined, slug: string | undefined, parentSlug: string | undefined) { 37 | this.path = path 38 | this.sort = sort 39 | this.slug = slug 40 | this.parentSlug = parentSlug 41 | } 42 | 43 | // --------------------------------------------------------------- 44 | // Public Methods 45 | // --------------------------------------------------------------- 46 | 47 | /** 48 | * Extracts an encounter object from an encounter file 49 | * @param moduleBuildPath The module build path 50 | * @param projectPath The project file path 51 | * @param moduleUUID The module UUID 52 | */ 53 | public extractEncounterObject = async (moduleBuildPath: string, projectPath: string, moduleUUID: string): Promise => { 54 | let pathBaseName = Path.basename(this.path) 55 | let encounterExtractTempPath = Path.join(moduleBuildPath, pathBaseName + 'TempEncounter') 56 | let fullEncounterPath = Path.join(projectPath, this.path) 57 | 58 | Logger.info(`Processing encounter file "${this.path}"`) 59 | 60 | // Create temporary unzip path for encounter file 61 | FileSystem.ensureDirSync(encounterExtractTempPath) 62 | 63 | // Unzip the encounter file 64 | await ExtractZip(fullEncounterPath, { dir: encounterExtractTempPath }) 65 | 66 | let encounterModuleXmlFile = Path.join(encounterExtractTempPath, 'module.xml') 67 | let encounterCampaignXmlFile = Path.join(encounterExtractTempPath, 'campaign.xml') 68 | 69 | let encounterXmlFile: string | undefined = undefined 70 | if (FileSystem.existsSync(encounterModuleXmlFile)) { 71 | encounterXmlFile = encounterModuleXmlFile 72 | } else if (FileSystem.existsSync(encounterCampaignXmlFile)) { 73 | encounterXmlFile = encounterCampaignXmlFile 74 | } 75 | 76 | if (encounterXmlFile === undefined) { 77 | throw Error('Encounter file has an invalid format. Could not locate module.xml or campaign.xml for encounter file.') 78 | } 79 | 80 | // Copy all files that aren't module.xml or campaign.xml to the moduleBuildPath 81 | let encounterFiles: string[] = FileSystem.readdirSync(encounterExtractTempPath).filter(function (file) { 82 | return Path.basename(file) !== 'module.xml' && Path.basename(file) !== 'campaign.xml' 83 | }) 84 | 85 | encounterFiles.forEach(fileName => { 86 | let tempPath = Path.join(encounterExtractTempPath, fileName) 87 | let newPath = Path.join(moduleBuildPath, fileName) 88 | FileSystem.copyFileSync(tempPath, newPath) 89 | }) 90 | 91 | let xmlOptions = { 92 | ignoreAttributes: false, 93 | attributeNamePrefix : "@_" 94 | }; 95 | let xmlParser = new XMLParser(xmlOptions) 96 | let encounterModuleBuffer = FileSystem.readFileSync(encounterXmlFile) 97 | let parseResult = xmlParser.parse(encounterModuleBuffer.toString()) 98 | 99 | let rootElement = parseResult['module'] as any || parseResult['campaign'] as any 100 | if (rootElement === undefined) { 101 | throw Error('Encounter file has an invalid format. Could not locate module or campaign element.') 102 | } 103 | 104 | let encounterObject = rootElement['encounter'] as any 105 | let encounterName = (encounterObject['name'] as string) || Path.basename(this.path) 106 | let encounter = new Encounter(encounterName, moduleUUID, this, this.slug) 107 | if (this.sort !== undefined) { 108 | encounter.sort = this.sort 109 | } 110 | 111 | encounter.encounterData = encounterObject 112 | 113 | // Cleanup the temp directory 114 | FileSystem.rmSync(encounterExtractTempPath, {recursive: true, force: true}) 115 | 116 | return encounter 117 | } 118 | } -------------------------------------------------------------------------------- /source/shared/MapFileReference.ts: -------------------------------------------------------------------------------- 1 | import * as FileSystem from 'fs-extra' 2 | import * as Path from 'path' 3 | import * as ExtractZip from 'extract-zip' 4 | import { XMLParser } from 'fast-xml-parser' 5 | import * as Logger from 'winston' 6 | import { Map } from './Module Entities/Map' 7 | 8 | /** 9 | * Defines a reference to a map file. 10 | */ 11 | export class MapFileReference { 12 | 13 | // --------------------------------------------------------------- 14 | // Public Properties 15 | // --------------------------------------------------------------- 16 | 17 | /** The path to the map file */ 18 | path: string = '' 19 | 20 | /** The sort order of the entity */ 21 | sort: number | undefined = undefined 22 | 23 | /** The specified slug for the entity */ 24 | slug: string | undefined = undefined 25 | 26 | /** The parent entity */ 27 | parentSlug: string | undefined = undefined 28 | 29 | // --------------------------------------------------------------- 30 | // Initialization & Cleanup 31 | // --------------------------------------------------------------- 32 | 33 | /** 34 | * Initializes an instance of `MapFileReference` 35 | */ 36 | constructor(path: string, sort: number | undefined, slug: string | undefined, parentSlug: string | undefined) { 37 | this.path = path 38 | this.sort = sort 39 | this.slug = slug 40 | this.parentSlug = parentSlug 41 | } 42 | 43 | // --------------------------------------------------------------- 44 | // Public Methods 45 | // --------------------------------------------------------------- 46 | 47 | /** 48 | * Extracts a map object from a map file 49 | * @param moduleBuildPath The module build path 50 | * @param projectPath The project file path 51 | * @param moduleUUID The module UUID 52 | */ 53 | public extractMapObject = async (moduleBuildPath: string, projectPath: string, moduleUUID: string): Promise => { 54 | let pathBaseName = Path.basename(this.path) 55 | let mapExtractTempPath = Path.join(moduleBuildPath, pathBaseName + 'TempMap') 56 | let fullMapPath = Path.join(projectPath, this.path) 57 | 58 | Logger.info(`Processing map file "${this.path}"`) 59 | 60 | // Create temporary unzip path for map file 61 | FileSystem.ensureDirSync(mapExtractTempPath) 62 | 63 | // Unzip the map file 64 | await ExtractZip(fullMapPath, { dir: mapExtractTempPath }) 65 | 66 | let mapModuleXmlFile = Path.join(mapExtractTempPath, 'module.xml') 67 | let mapCampaignXmlFile = Path.join(mapExtractTempPath, 'campaign.xml') 68 | 69 | let mapXmlFile: string | undefined = undefined 70 | if (FileSystem.existsSync(mapModuleXmlFile)) { 71 | mapXmlFile = mapModuleXmlFile 72 | } else if (FileSystem.existsSync(mapCampaignXmlFile)) { 73 | mapXmlFile = mapCampaignXmlFile 74 | } 75 | 76 | if (mapXmlFile === undefined) { 77 | throw Error('Map file has an invalid format. Could not locate module.xml or campaign.xml for map file.') 78 | } 79 | 80 | // Copy all files that aren't module.xml or campaign.xml to the moduleBuildPath 81 | let mapFiles: string[] = FileSystem.readdirSync(mapExtractTempPath).filter(function (file) { 82 | return Path.basename(file) !== 'module.xml' && Path.basename(file) !== 'campaign.xml' 83 | }) 84 | 85 | mapFiles.forEach(fileName => { 86 | let tempPath = Path.join(mapExtractTempPath, fileName) 87 | let newPath = Path.join(moduleBuildPath, fileName) 88 | FileSystem.copyFileSync(tempPath, newPath) 89 | }) 90 | 91 | let xmlOptions = { 92 | ignoreAttributes: false, 93 | attributeNamePrefix : "@_" 94 | }; 95 | let xmlParser = new XMLParser(xmlOptions) 96 | let mapModuleBuffer = FileSystem.readFileSync(mapXmlFile) 97 | let mapModuleString = mapModuleBuffer.toString() 98 | let parseResult = xmlParser.parse(mapModuleString) 99 | 100 | let rootElement = parseResult['module'] as any || parseResult['campaign'] as any 101 | if (rootElement === undefined) { 102 | throw Error('Map file has an invalid format. Could not locate module or campaign element.') 103 | } 104 | 105 | let mapObject = rootElement['map'] as any 106 | let mapName = (mapObject['name'] as string) || Path.basename(this.path) 107 | let map = new Map(mapName, moduleUUID, this, this.slug) 108 | if (this.sort !== undefined) { 109 | map.sort = this.sort 110 | } 111 | 112 | map.mapData = mapObject 113 | 114 | // Cleanup the temp directory 115 | FileSystem.rmSync(mapExtractTempPath, {recursive: true, force: true}) 116 | 117 | return map 118 | } 119 | } -------------------------------------------------------------------------------- /source/shared/Module Entities/Encounter.ts: -------------------------------------------------------------------------------- 1 | import { ModuleEntity } from './ModuleEntity' 2 | import { EncounterFileReference } from '../EncounterFileReference' 3 | 4 | /** 5 | * Defines information about an Encounter 6 | */ 7 | export class Encounter extends ModuleEntity { 8 | 9 | // --------------------------------------------------------------- 10 | // Initialization & Cleanup 11 | // --------------------------------------------------------------- 12 | 13 | /** 14 | * Initializes an instance of `Encounter` 15 | * @param name The name of the encounter 16 | * @param moduleUUID The UUID of the module 17 | * @param fileReference The encounter's file reference 18 | * @param slug A manually specified slug (optional - will be auto-generated if undefined) 19 | */ 20 | constructor(name: string, moduleUUID: string, fileReference: EncounterFileReference, slug: string | undefined = undefined) { 21 | super(name, moduleUUID, slug) 22 | this.fileReference = fileReference 23 | this.parentSlug = fileReference.parentSlug 24 | } 25 | 26 | // --------------------------------------------------------------- 27 | // Public Properties 28 | // --------------------------------------------------------------- 29 | 30 | /** The encounter object data */ 31 | encounterData: Object = {} 32 | 33 | /** The encounter's file reference */ 34 | fileReference: EncounterFileReference 35 | 36 | } -------------------------------------------------------------------------------- /source/shared/Module Entities/Group.ts: -------------------------------------------------------------------------------- 1 | import * as YAML from 'yaml' 2 | import * as FileSystem from 'fs-extra' 3 | import * as Path from 'path' 4 | import { Module } from './Module' 5 | import { IncludeMode, ModuleEntity } from './ModuleEntity' 6 | 7 | /** Represents a Group in a Module */ 8 | export class Group extends ModuleEntity { 9 | 10 | // --------------------------------------------------------------- 11 | // Initialization & Cleanup 12 | // --------------------------------------------------------------- 13 | 14 | /** 15 | * Initializes an instance of `Group` 16 | * @param name The name of the group 17 | * @param moduleUUID The UUID of the module 18 | * @param groupPath The path of the group 19 | */ 20 | constructor(name: string = 'Unnamed Group', moduleUUID: string, groupPath: string) { 21 | let effectiveName = name 22 | let slug: string | undefined = undefined 23 | let order: number | undefined = undefined 24 | let parentSlug: string | undefined = undefined 25 | 26 | let includeIn = 'all' 27 | let copyFiles = true 28 | let groupSettingsPath = Path.join(groupPath, Group.groupSettingsFileName) 29 | if (FileSystem.existsSync(groupSettingsPath)) { 30 | let groupDataBuffer = FileSystem.readFileSync(groupSettingsPath) 31 | 32 | let groupData: any = undefined 33 | try { 34 | groupData = YAML.parse(groupDataBuffer.toString()) 35 | } catch (error: any) { 36 | throw Error(`Failed to parse ${groupSettingsPath}. Error: ${(error as Error).message}`) 37 | } 38 | 39 | let groupName = groupData['name'] as string 40 | if (groupName) { 41 | effectiveName = groupName 42 | } 43 | 44 | let parentSlugFromSettings = groupData['parent'] 45 | if (parentSlugFromSettings) { 46 | parentSlug = parentSlugFromSettings 47 | } 48 | 49 | let slugFromSettings = groupData['slug'] 50 | if (slugFromSettings) { 51 | slug = slugFromSettings 52 | } 53 | 54 | let includeInFromSettings = groupData['include-in'] 55 | if(includeInFromSettings) { 56 | includeIn = includeInFromSettings 57 | } 58 | 59 | let copyFilesFromSettings = groupData['copy-files'] as boolean 60 | if(copyFilesFromSettings !== undefined) { 61 | copyFiles = copyFilesFromSettings 62 | } 63 | 64 | order = groupData['order'] as number 65 | } 66 | 67 | if (!slug) { 68 | slug = Module.getSlugFromValue(`group-${Module.getSlugFromValue(effectiveName)}`) 69 | } 70 | super(effectiveName, moduleUUID, slug) 71 | this.groupPath = groupPath 72 | this.copyFiles = copyFiles 73 | this.includeIn = ModuleEntity.getIncludeModeFromString(includeIn) 74 | if(this.includeIn === IncludeMode.Compendium) 75 | { 76 | throw Error(`The 'include-in' value for groups cannot be 'compendium'`) 77 | } 78 | 79 | this.parentSlug = parentSlug 80 | 81 | if(order) { 82 | this.sort = order 83 | } 84 | } 85 | 86 | // --------------------------------------------------------------- 87 | // Public Properties 88 | // --------------------------------------------------------------- 89 | 90 | /** The group settings file name */ 91 | static groupSettingsFileName = 'Group.yaml' 92 | 93 | /** The path of the group */ 94 | groupPath: string 95 | 96 | /** Whether to copy files to module output */ 97 | copyFiles: boolean 98 | } 99 | -------------------------------------------------------------------------------- /source/shared/Module Entities/Map.ts: -------------------------------------------------------------------------------- 1 | import { ModuleEntity } from './ModuleEntity' 2 | import {MapFileReference} from '../MapFileReference' 3 | 4 | /** 5 | * Defines information about a Map 6 | */ 7 | export class Map extends ModuleEntity { 8 | 9 | // --------------------------------------------------------------- 10 | // Initialization & Cleanup 11 | // --------------------------------------------------------------- 12 | 13 | /** 14 | * Initializes an instance of `Map` 15 | * @param name The name of the map 16 | * @param moduleUUID The UUID of the module 17 | * @param fileReference The map's file reference 18 | * @param slug A manually specified slug (optional - will be auto-generated if undefined) 19 | */ 20 | constructor(name: string, moduleUUID: string, fileReference: MapFileReference, slug: string | undefined = undefined) { 21 | super(name, moduleUUID, slug) 22 | this.fileReference = fileReference 23 | this.parentSlug = fileReference.parentSlug 24 | } 25 | 26 | // --------------------------------------------------------------- 27 | // Public Properties 28 | // --------------------------------------------------------------- 29 | 30 | /** The map object data */ 31 | mapData: Object = {} 32 | 33 | /** The map's file reference */ 34 | fileReference: MapFileReference 35 | 36 | } -------------------------------------------------------------------------------- /source/shared/Module Entities/ModuleEntity.ts: -------------------------------------------------------------------------------- 1 | import { v5 as UUIDV5 } from 'uuid' 2 | import { Module } from "./Module" 3 | 4 | /** Represents referencable entities in a module */ 5 | export abstract class ModuleEntity { 6 | // --------------------------------------------------------------- 7 | // Initialization & Cleanup 8 | // --------------------------------------------------------------- 9 | 10 | /** 11 | * Initializes an instance of `ModuleEntity` 12 | * @param name The name of the entity 13 | * @param moduleUUID The UUID of the module 14 | */ 15 | constructor( 16 | readonly name: string, 17 | readonly moduleUUID: string, 18 | slug: string | undefined = undefined 19 | ) { 20 | // If a slug is manually specified, make sure it doesn't exist. 21 | // Auto-generated slugs will automatically append a number if 22 | // they are duplicated. 23 | if (slug !== undefined) { 24 | let sanitizedSlug = Module.sanitizeSlug(slug) 25 | if (Module.existingSlugs.includes(sanitizedSlug)) { 26 | throw new Error( 27 | `The slug "${slug}" results in a duplicate slug for the module.` 28 | ) 29 | } 30 | } 31 | 32 | // Once the slug has been determined, add to the list of existing slugs 33 | // so there can't be overlap 34 | this.slug = slug ?? Module.getSlugFromValue(name) 35 | Module.existingSlugs.push(this.slug) 36 | 37 | // Derive UUID from slug using module ID as namespace. This 38 | // makes IDs deterministic for a given module (so repeated 39 | // packing results in same IDs). 40 | this.id = UUIDV5(this.slug, moduleUUID) 41 | } 42 | 43 | // --------------------------------------------------------------- 44 | // Public Properties 45 | // --------------------------------------------------------------- 46 | 47 | /** The parent entity */ 48 | parent: ModuleEntity | undefined = undefined 49 | 50 | /** The parent slug for the entity - to be resolved to a parent object later */ 51 | parentSlug: string | undefined = undefined 52 | 53 | /** The slug for the entity */ 54 | slug: string 55 | 56 | /** The ID of the entity (UUIDV5) */ 57 | id: string 58 | 59 | /** The sort order of the entity */ 60 | sort: number | undefined = undefined 61 | 62 | /** The children of this module */ 63 | children: ModuleEntity[] = [] 64 | 65 | /** Which build targets to include in */ 66 | includeIn: IncludeMode = IncludeMode.All 67 | 68 | // --------------------------------------------------------------- 69 | // Public Methods 70 | // --------------------------------------------------------------- 71 | 72 | /** Gets the include mode from a string */ 73 | static getIncludeModeFromString(includeString: string): IncludeMode { 74 | switch(includeString.toLowerCase()) { 75 | case 'print': 76 | return IncludeMode.Print 77 | case 'module': 78 | return IncludeMode.Module 79 | case 'compendium': 80 | return IncludeMode.Compendium 81 | case 'files': 82 | return IncludeMode.Files 83 | default: 84 | return IncludeMode.All 85 | } 86 | } 87 | } 88 | 89 | /** The module include mode */ 90 | export enum IncludeMode { 91 | /** All output formats include the entity */ 92 | All = 1, 93 | 94 | /** 95 | * Only print targets include the entities. Image files 96 | * will not be copied for module targets. 97 | */ 98 | Print, 99 | 100 | /** Only Encounter+ module targets include the entity. */ 101 | Module, 102 | 103 | /** Only Encounter+ module targets include the entity, 104 | * and only for the purposes of adding compendium entries. 105 | * No pages are created. (Only applicable to pages) */ 106 | Compendium, 107 | 108 | /** Copy Files only, do not include page content (only applicable to groups) */ 109 | Files 110 | } 111 | -------------------------------------------------------------------------------- /source/shared/Module Entities/Page.ts: -------------------------------------------------------------------------------- 1 | import { ModuleEntity } from './ModuleEntity' 2 | 3 | /** Represents a Page in a Module */ 4 | 5 | export class Page extends ModuleEntity { 6 | // --------------------------------------------------------------- 7 | // Initialization & Cleanup 8 | // --------------------------------------------------------------- 9 | 10 | /** 11 | * Initializes an instance of `Page` 12 | * @param name The name of the page 13 | * @param moduleUUID The UUID of the module 14 | * @param pagePath The path of the page file 15 | * @param slug A manually specified slug (optional - will be auto-generated if undefined) 16 | */ 17 | constructor(name: string, moduleUUID: string, pagePath: string, slug: string | undefined = undefined) { 18 | super(name, moduleUUID, slug) 19 | this.pagePath = pagePath 20 | } 21 | 22 | // --------------------------------------------------------------- 23 | // Public Properties 24 | // --------------------------------------------------------------- 25 | 26 | /** The HTML Content of the page */ 27 | content: string = '' 28 | 29 | /** The path of the page file */ 30 | pagePath: string = '' 31 | 32 | /** Whether the page is for print cover only */ 33 | printCoverOnly: boolean = false 34 | } 35 | -------------------------------------------------------------------------------- /source/shared/Module Entities/Reference.ts: -------------------------------------------------------------------------------- 1 | import { ModuleEntity } from './ModuleEntity' 2 | 3 | /** 4 | * Defines information about a Reference 5 | */ 6 | export class Reference extends ModuleEntity { 7 | 8 | // --------------------------------------------------------------- 9 | // Initialization & Cleanup 10 | // --------------------------------------------------------------- 11 | 12 | /** 13 | * Initializes an instance of `Reference` 14 | * @param name The name of the reference 15 | * @param moduleUUID The UUID of the module 16 | * @param path: The path of the reference 17 | * @param slug A manually specified slug (optional - will be auto-generated if undefined) 18 | */ 19 | constructor(name: string, moduleUUID: string, path: string, slug: string | undefined = undefined) { 20 | super(name, moduleUUID, slug) 21 | this.path = path 22 | } 23 | 24 | // --------------------------------------------------------------- 25 | // Public Properties 26 | // --------------------------------------------------------------- 27 | 28 | /** The reference's path */ 29 | path: string 30 | 31 | } -------------------------------------------------------------------------------- /source/shared/Module Entities/RollTable.ts: -------------------------------------------------------------------------------- 1 | import { RollTableColumn } from './RollTableColumn' 2 | import { ModuleEntity } from './ModuleEntity' 3 | 4 | /** Represents a Roll Table in a Module */ 5 | export class RollTable extends ModuleEntity { 6 | 7 | // --------------------------------------------------------------- 8 | // Initialization & Cleanup 9 | // --------------------------------------------------------------- 10 | 11 | /** 12 | * Initializes an instance of `RollTable` 13 | * @param name The name of the roll table 14 | * @param moduleUUID The UUID of the module 15 | * @param slug A manually specified slug (optional - will be auto-generated if undefined) 16 | */ 17 | constructor(name: string, moduleUUID: string, slug: string | undefined = undefined) { 18 | super(name, moduleUUID, slug) 19 | this.source = moduleUUID 20 | } 21 | 22 | // --------------------------------------------------------------- 23 | // Public Properties 24 | // --------------------------------------------------------------- 25 | 26 | /** The roll table's source */ 27 | source: string | undefined = undefined 28 | 29 | /** The roll table's description */ 30 | description: string | undefined = undefined 31 | 32 | /** The roll table's rows */ 33 | rows: string[][] = [] 34 | 35 | /** The roll table's columns */ 36 | columns: RollTableColumn[] = [] 37 | 38 | /** The roll table's roll mode */ 39 | rollMode: RollMode = RollMode.Normal 40 | } 41 | 42 | /** The module include mode */ 43 | export enum RollMode { 44 | /** Normal Roll Mode */ 45 | Normal = 'normal', 46 | 47 | /** No Repeat Roll Mode */ 48 | NoRepeat = 'noRepeat', 49 | 50 | /** Each Row Roll Mode */ 51 | EachRow = 'eachRow', 52 | } -------------------------------------------------------------------------------- /source/shared/Module Entities/RollTableColumn.ts: -------------------------------------------------------------------------------- 1 | /** Represents a Roll Table Column in a Module */ 2 | export class RollTableColumn { 3 | 4 | // --------------------------------------------------------------- 5 | // Initialization & Cleanup 6 | // --------------------------------------------------------------- 7 | 8 | /** 9 | * Initializes an instance of `RollTable` 10 | * @param name The name of the roll table column 11 | * @param alignment The alignment of the roll table column 12 | */ 13 | constructor(name: string, alignment: RollTableColumnAlignment) { 14 | this.name = name 15 | this.alignment = alignment 16 | } 17 | 18 | // --------------------------------------------------------------- 19 | // Public Properties 20 | // --------------------------------------------------------------- 21 | 22 | /** The roll table column's name */ 23 | name: string 24 | 25 | /** The roll table column's alignment */ 26 | alignment: RollTableColumnAlignment 27 | 28 | } 29 | 30 | /** The module include mode */ 31 | export enum RollTableColumnAlignment { 32 | /** Left column alignment */ 33 | Left = 'left', 34 | 35 | /** Right column alignment */ 36 | Right = 'right', 37 | 38 | /** Center column alignment */ 39 | Center = 'center', 40 | } -------------------------------------------------------------------------------- /source/shared/Module Entities/Spell.ts: -------------------------------------------------------------------------------- 1 | import { v4 as UUIDV4 } from 'uuid' 2 | import * as YAML from 'yaml' 3 | import { ModuleEntity } from './ModuleEntity' 4 | import { Module } from './Module' 5 | 6 | /** Represents a Spell in a Module */ 7 | export class Spell extends ModuleEntity { 8 | // --------------------------------------------------------------- 9 | // Initialization & Cleanup 10 | // --------------------------------------------------------------- 11 | 12 | /** 13 | * Initializes an instance of `Spell` 14 | * @param name The name of the page 15 | * @param moduleUUID The UUID of the module 16 | * @param slug A manually specified slug (optional - will be auto-generated if undefined) 17 | */ 18 | constructor(name: string, moduleUUID: string, slug: string | undefined = undefined) { 19 | super(name, moduleUUID, slug) 20 | } 21 | 22 | // --------------------------------------------------------------- 23 | // Public Properties 24 | // --------------------------------------------------------------- 25 | 26 | /** The spell's level */ 27 | level: number | undefined = undefined 28 | 29 | /** The spell's school */ 30 | school: string | undefined = undefined 31 | 32 | /** Whether the spell is a ritual */ 33 | ritual: boolean = false 34 | 35 | /** The spell's time to cast */ 36 | time: string | undefined = undefined 37 | 38 | /** The spell's range */ 39 | range: string | undefined = undefined 40 | 41 | /** The spell's components */ 42 | components: string | undefined = undefined 43 | 44 | /** The spell's duration */ 45 | duration: string | undefined = undefined 46 | 47 | /** The spell's classes */ 48 | classes: string | undefined = undefined 49 | 50 | /** The spell's source */ 51 | source: string | undefined = undefined 52 | 53 | /** The filename of an image of the spell */ 54 | image: string | undefined = undefined 55 | 56 | /** The spell's description */ 57 | description: string | undefined = undefined 58 | 59 | /** Whether the spell block should show the image of the spell */ 60 | showImage: boolean = false 61 | 62 | // --------------------------------------------------------------- 63 | // Public Methods 64 | // --------------------------------------------------------------- 65 | 66 | /** 67 | * Gets a Spell object instance from a YAML representation 68 | * @param spellYamlContent The YAML content that represents the spell 69 | * @param module The module to which the spell belongs 70 | */ 71 | static fromYAMLContent(spellYamlContent: string, module: Module | undefined = undefined) { 72 | // Parse spell from YAML 73 | let spellData: any = undefined 74 | try { 75 | spellData = YAML.parse(spellYamlContent) 76 | } catch (error: any) { 77 | throw Error(`Failed to parse Spell. Error: ${(error as Error).message}`) 78 | } 79 | 80 | // A name must be defined - if it isn't, 81 | // go with "Unnamed Spell" 82 | let name = spellData['name'] as string 83 | if (!name) { 84 | name = 'Unnamed Spell' 85 | } 86 | 87 | // Generate a slug if one isn't manually defined 88 | let slug = spellData['slug'] as string 89 | if (!slug || module === undefined) { 90 | slug = Module.getSlugFromValue(name) 91 | } 92 | 93 | // If the module project ID isn't defined, generate a random one. This 94 | // will happen when the Spell is being generated as part of a preview 95 | // for the VS Code extension 96 | let spell = new Spell(name, module?.moduleProjectInfo.id ?? UUIDV4(), slug) 97 | 98 | const level = spellData['level'] as number 99 | if (level !== undefined) { 100 | spell.level = level 101 | } else { 102 | throw Error('Spell must have a level') 103 | } 104 | 105 | const school = spellData['school'] as string 106 | if (school) { 107 | spell.school = school 108 | } else { 109 | throw Error('Spell must have a school') 110 | } 111 | 112 | const ritual = spellData['ritual'] as boolean 113 | if (ritual !== undefined) { 114 | spell.ritual = ritual 115 | } 116 | 117 | const time = spellData['time'] as string 118 | if (time) { 119 | spell.time = time 120 | } 121 | 122 | const range = spellData['range'] as string 123 | if (range) { 124 | spell.range = range 125 | } 126 | 127 | const components = spellData['components'] as string 128 | if (components) { 129 | spell.components = components 130 | } 131 | 132 | const duration = spellData['duration'] as string 133 | if (duration) { 134 | spell.duration = duration 135 | } 136 | 137 | const classes = spellData['classes'] as string 138 | if (classes) { 139 | spell.classes = classes 140 | } 141 | 142 | const source = spellData['source'] as string 143 | if (source) { 144 | spell.source = source 145 | } 146 | 147 | const description = spellData['description'] as string 148 | if (description) { 149 | spell.description = description 150 | } 151 | 152 | const image = spellData['image'] as string 153 | if (image) { 154 | spell.image = image 155 | } 156 | 157 | const showImage = spellData['show-image'] as boolean 158 | if (showImage !== undefined) { 159 | spell.showImage = showImage 160 | } 161 | 162 | return spell 163 | } 164 | 165 | /** 166 | * Converts a spell's school description to a compendium-compatible entry 167 | * @param item The item 168 | */ 169 | static getCompendiumSchool(spell: Spell): string | undefined { 170 | if (spell.school === undefined) { 171 | return undefined 172 | } 173 | 174 | switch (spell.school.toLowerCase()) { 175 | case 'abjuration': 176 | return 'A' 177 | case 'conjuration': 178 | return 'C' 179 | case 'divination': 180 | return 'D' 181 | case 'enchantment': 182 | return 'EN' 183 | case 'evocation': 184 | return 'EV' 185 | case 'illusion': 186 | return "I" 187 | case 'necromancy': 188 | return 'N' 189 | case 'transmutation': 190 | return 'T' 191 | default: 192 | throw Error(`Invalid spell school "${spell.school}". Supported values are: Abjuration, Conjuration, Divination, Enchantment, Evocation, Illusion, Necromancy, Transmutation`) 193 | } 194 | } 195 | 196 | /** 197 | * Gets the HTML representation of the spell 198 | */ 199 | getHTML = (classes: string[] = []): string => { 200 | let levelText = 'Unknown' 201 | switch(this.level) { 202 | case 0: 203 | levelText = 'Cantrip' 204 | break 205 | case 1: 206 | levelText = 'First' 207 | break 208 | case 2: 209 | levelText = 'Second' 210 | break 211 | case 3: 212 | levelText = 'Third' 213 | break 214 | case 4: 215 | levelText = 'Fourth' 216 | break 217 | case 5: 218 | levelText = 'Fifth' 219 | break 220 | case 6: 221 | levelText = 'Sixth' 222 | break 223 | case 7: 224 | levelText = 'Seventh' 225 | break 226 | case 8: 227 | levelText = 'Eighth' 228 | break 229 | case 9: 230 | levelText = 'Ninth' 231 | break 232 | case 10: 233 | levelText = 'Tenth' 234 | break 235 | default: 236 | levelText = `${this.level}` 237 | break 238 | } 239 | 240 | function formatDescription(description: string): string { 241 | let newDescription = description 242 | newDescription = newDescription.replace(/[\r\n]/g, '
') 243 | return newDescription 244 | } 245 | 246 | let allClasses = Array.from(classes) 247 | allClasses.splice(0, 0, 'spell-block') 248 | let classesString = allClasses.join(' ') 249 | let spellHTML = `
` 250 | spellHTML += `

${this.name}

` 251 | spellHTML += `
` 252 | spellHTML += `
` 253 | if(this.description !== undefined) { 254 | spellHTML += `

${formatDescription(this.description)}

` 255 | } 256 | spellHTML += `
` 257 | spellHTML += '

' 258 | if (this.level) { 259 | spellHTML += `Level: ${levelText}
` 260 | } 261 | if (this.school) { 262 | spellHTML += `School: ${this.school}
` 263 | } 264 | if (this.time) { 265 | spellHTML += `Casting Time: ${this.time}
` 266 | } 267 | if (this.range) { 268 | spellHTML += `Range/Area: ${this.range}
` 269 | } 270 | if (this.components) { 271 | spellHTML += `Components: ${this.components}
` 272 | } 273 | if (this.duration) { 274 | spellHTML += `Duration: ${this.duration}
` 275 | } 276 | if (this.classes) { 277 | spellHTML += `Classes: ${this.classes}
` 278 | } 279 | spellHTML += `Ritual: ${this.ritual ? 'Yes' : 'No'}
` 280 | spellHTML += '

' 281 | 282 | if (this.showImage && this.image !== undefined) { 283 | spellHTML += '
' 284 | spellHTML += `` 285 | spellHTML += '
' // spell-image-block 286 | } 287 | 288 | spellHTML += `
` 289 | spellHTML += `
` // spell-block-body 290 | spellHTML += `
` // spell-block 291 | return spellHTML 292 | } 293 | 294 | 295 | } 296 | -------------------------------------------------------------------------------- /source/shared/ReferenceInfo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines a reference for a module 3 | */ 4 | export class ReferenceInfo { 5 | 6 | // --------------------------------------------------------------- 7 | // Public Properties 8 | // --------------------------------------------------------------- 9 | 10 | /** The name of the reference */ 11 | name: string = '' 12 | 13 | /** The path to the reference */ 14 | path: string = '' 15 | 16 | /** The sort order of the entity */ 17 | sort: number | undefined = undefined 18 | 19 | /** The specified slug for the entity */ 20 | slug: string | undefined = undefined 21 | 22 | /** The parent entity */ 23 | parentSlug: string | undefined = undefined 24 | 25 | // --------------------------------------------------------------- 26 | // Initialization & Cleanup 27 | // --------------------------------------------------------------- 28 | 29 | /** 30 | * Initializes an instance of `ReferenceInfo` 31 | */ 32 | constructor(name: string, path: string, sort: number | undefined, slug: string | undefined, parentSlug: string | undefined) { 33 | this.name = name 34 | this.path = path 35 | this.sort = sort 36 | this.slug = slug 37 | this.parentSlug = parentSlug 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /source/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019", "DOM"], 6 | "declaration": true, 7 | "outDir": "./app-out", 8 | "noUnusedLocals": true, 9 | "stripInternal": true, 10 | "downlevelIteration": true, 11 | "sourceMap": true 12 | }, 13 | "exclude": ["node_modules", ".vscode-test", "extension-out", "app-out"], 14 | "include": [ 15 | "app/**/*", 16 | "shared/**/*" 17 | ] 18 | } -------------------------------------------------------------------------------- /source/tsconfig.cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019", "DOM"], 6 | "noImplicitAny": true, 7 | "sourceMap": true, 8 | "outDir": "./cli-out", 9 | "baseUrl": ".", 10 | "paths": { 11 | "*": ["node_modules/*"] 12 | } 13 | }, 14 | "include": [ 15 | "cli/**/*", 16 | "shared/**/*" 17 | ] 18 | } -------------------------------------------------------------------------------- /source/tsconfig.extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019", "DOM"], 6 | "declaration": true, 7 | "outDir": "./extension-out", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "stripInternal": true, 11 | "downlevelIteration": true, 12 | "sourceMap": true 13 | }, 14 | "exclude": ["node_modules", ".vscode-test", "extension-out", "app-out"], 15 | "include": [ 16 | "vscode-extension/**/*", 17 | "shared/**/*" 18 | ] 19 | } -------------------------------------------------------------------------------- /source/vscode-extension/Commands/BuildModuleCommand.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path' 2 | import * as vscode from 'vscode' 3 | import { Module, ModuleMode } from '../../shared/Module Entities/Module' 4 | import { ModuleProject } from '../../shared/ModuleProject' 5 | import { CommandBase } from './CommandBase' 6 | 7 | export class BuildModuleCommand extends CommandBase { 8 | 9 | /** The module project that will be built */ 10 | private moduleProject: ModuleProject | undefined = undefined 11 | 12 | /** 13 | * If specified, this will be displayed in the status bar 14 | * as the command executes 15 | */ 16 | statusMessage = 'Building EncounterPlus Module...' 17 | 18 | /** 19 | * Starts a module project building 20 | * @param moduleProject The module project to build 21 | */ 22 | async startModuleBuild(moduleProject: ModuleProject) { 23 | this.moduleProject = moduleProject 24 | this.startCommand() 25 | } 26 | 27 | /** 28 | * Contains execution code for the command 29 | */ 30 | protected async executeCommand() { 31 | await vscode.workspace.saveAll(false) 32 | let moduleProjectPath = this.moduleProject?.moduleProjectPath 33 | let projectDirectory = this.moduleProject?.moduleProjectDirectory 34 | 35 | // Ensure we have a proper project path 36 | if (moduleProjectPath === undefined) { 37 | throw Error('Could not locate module project path.') 38 | } 39 | if (projectDirectory === undefined) { 40 | throw Error('Could not locate module project directory.') 41 | } 42 | 43 | let appRootPath = Path.join(__dirname, "..") 44 | let module = await Module.createModuleFromPath(projectDirectory, appRootPath, Path.basename(projectDirectory), ModuleMode.ModuleExport) 45 | 46 | let completeMessage = `Successfully created module: ${module.moduleProjectInfo.name}.` 47 | vscode.window.showInformationMessage(completeMessage, 'View Module File').then((selection) => { 48 | if (module.moduleArchivePath) { 49 | let archiveURI = vscode.Uri.file(module.moduleArchivePath) 50 | vscode.commands.executeCommand('revealFileInOS', archiveURI) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /source/vscode-extension/Commands/CommandBase.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as Logger from 'winston' 3 | 4 | export abstract class CommandBase { 5 | // --------------------------------------------------------------- 6 | // Private Fields 7 | // --------------------------------------------------------------- 8 | 9 | /** 10 | * A flag to indicate if a command is already running - only allow 11 | * one command to be executed at a time. 12 | */ 13 | private static isRunningCommand: boolean = false 14 | 15 | // --------------------------------------------------------------- 16 | // Public Properties 17 | // --------------------------------------------------------------- 18 | 19 | /** 20 | * If specified, this will be displayed in the status bar 21 | * as the command executes 22 | */ 23 | statusMessage: string | undefined = undefined 24 | 25 | /** 26 | * If specified, this will be displayed as an info 27 | * message whtn the command succeeds. 28 | */ 29 | successMessage: string | undefined = undefined 30 | 31 | // --------------------------------------------------------------- 32 | // Public & Protected Methods 33 | // --------------------------------------------------------------- 34 | 35 | /** 36 | * Starts a command execution 37 | */ 38 | async startCommand() { 39 | // Allow only 1 command to occur at a time 40 | if (CommandBase.isRunningCommand) { 41 | return 42 | } 43 | 44 | CommandBase.isRunningCommand = true 45 | 46 | // Create a status bar item message 47 | let statusBarMessage: vscode.Disposable | undefined = undefined 48 | if (this.statusMessage !== undefined) { 49 | Logger.info(this.statusMessage) 50 | statusBarMessage = vscode.window.setStatusBarMessage(this.statusMessage) 51 | } 52 | 53 | try { 54 | CommandBase.isRunningCommand = true 55 | 56 | await this.executeCommand() 57 | 58 | if (this.successMessage !== undefined) { 59 | Logger.info(this.successMessage) 60 | vscode.window.showInformationMessage(this.successMessage) 61 | } 62 | 63 | statusBarMessage?.dispose() 64 | CommandBase.isRunningCommand = false 65 | } catch (error: any) { 66 | let errorMessage = (error as Error).message 67 | vscode.window.showErrorMessage(errorMessage) 68 | Logger.error(`${errorMessage}\nStack: \n${(error as Error).stack}`) 69 | statusBarMessage?.dispose() 70 | CommandBase.isRunningCommand = false 71 | } 72 | } 73 | 74 | /** 75 | * Contains execution code for the command 76 | */ 77 | protected abstract executeCommand(): Promise 78 | } 79 | -------------------------------------------------------------------------------- /source/vscode-extension/Commands/CreateModuleProjectFileCommand.ts: -------------------------------------------------------------------------------- 1 | import * as FileSystem from 'fs-extra' 2 | import * as Path from 'path' 3 | import { v4 as UUIDV4 } from 'uuid' 4 | import * as vscode from 'vscode' 5 | import * as YAML from 'yaml' 6 | import { VSCodeUtilities } from '../extension' 7 | import { Module } from '../../shared/Module Entities/Module' 8 | import { CommandBase } from './CommandBase' 9 | 10 | export class CreateModuleProjectFileCommand extends CommandBase { 11 | 12 | /** 13 | * If specified, this will be displayed in the status bar 14 | * as the command executes 15 | */ 16 | statusMessage = 'Creating Module Project File...' 17 | 18 | /** 19 | * If specified, this will be displayed as an info 20 | * message whtn the command succeeds. 21 | */ 22 | successMessage = 'Module Project File created successfully.' 23 | 24 | /** 25 | * Contains execution code for the command 26 | */ 27 | protected async executeCommand() { 28 | let projectPath = VSCodeUtilities.getPrimaryWorkspaceFolderPath() 29 | if (projectPath === undefined) { 30 | throw Error('Could not locate folder for Module.yaml.') 31 | } 32 | 33 | // See if a Module.yaml file already exists 34 | let moduleProjectFilePath = Path.join(projectPath, Module.moduleProjectFileName) 35 | if (FileSystem.existsSync(moduleProjectFilePath)) { 36 | throw Error('Module.yaml already exists.') 37 | } 38 | 39 | // Check module path again in case it was deleted 40 | // while we were waiting for user input 41 | if (FileSystem.existsSync(moduleProjectFilePath)) { 42 | throw Error('Module.yaml already exists.') 43 | } 44 | 45 | let moduleName = Path.basename(projectPath) 46 | 47 | // Format Module.yaml data 48 | let moduleFileContent = { 49 | id: UUIDV4(), 50 | name: moduleName, 51 | slug: Module.sanitizeSlug(moduleName), 52 | description: 'TBD', 53 | category: 'adventure', 54 | author: 'Anonymous', 55 | code: 'TBD', 56 | version: 1, 57 | autoIncrementVersion: true, 58 | } 59 | 60 | // Write Module.yaml 61 | let outputProjectFile = YAML.stringify(moduleFileContent) 62 | FileSystem.writeFileSync(moduleProjectFilePath, outputProjectFile) 63 | vscode.commands.executeCommand('encounterPlusMarkdown.refreshModules') 64 | } 65 | } -------------------------------------------------------------------------------- /source/vscode-extension/Commands/ExportToPdfCommand.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as Path from 'path' 3 | import { CommandBase } from './CommandBase' 4 | import { PdfExporter } from '../../shared/PdfExporter' 5 | import { ModuleProject } from '../../shared/ModuleProject' 6 | 7 | export class ExportToPdfCommand extends CommandBase { 8 | 9 | /** The module project that will be exported to PDF */ 10 | private moduleProject: ModuleProject | undefined = undefined 11 | 12 | /** 13 | * If specified, this will be displayed in the status bar 14 | * as the command executes 15 | */ 16 | statusMessage = 'Exporting Module to PDF...' 17 | 18 | /** 19 | * Starts a module project exporting to PDF 20 | * @param moduleProject The module project to export to PDF 21 | */ 22 | async startModuleExport(moduleProject: ModuleProject) { 23 | this.moduleProject = moduleProject 24 | this.startCommand() 25 | } 26 | 27 | /** 28 | * Contains execution code for the command 29 | */ 30 | protected async executeCommand() { 31 | await vscode.workspace.saveAll(false) 32 | let moduleProjectPath = this.moduleProject?.moduleProjectPath 33 | 34 | // Ensure we have a proper project path 35 | if (moduleProjectPath === undefined) { 36 | throw Error('Could not locate module project path.') 37 | } 38 | 39 | let projectDirectory = Path.dirname(moduleProjectPath) 40 | 41 | vscode.window.showInformationMessage(`Beginning export of "${this.moduleProject?.name}" to PDF`) 42 | await PdfExporter.installChromiumForRendering(this.onBrowserDownload) 43 | let appRootPath = Path.join(__dirname, '..') 44 | let pdfPath = await PdfExporter.exportToPdf(projectDirectory, appRootPath, (path) => { 45 | return vscode.Uri.file(path).toString() 46 | }) 47 | 48 | let completeMessage = `Successfully exported module to PDF.` 49 | vscode.window 50 | .showInformationMessage(completeMessage, 'Open PDF Location') 51 | .then((selection) => { 52 | if (selection !== 'Open PDF Location') { 53 | return 54 | } 55 | 56 | if (pdfPath) { 57 | let archiveURI = vscode.Uri.file(pdfPath) 58 | vscode.commands.executeCommand('revealFileInOS', archiveURI) 59 | } 60 | }) 61 | } 62 | 63 | /** 64 | * Handles browser download progress changes 65 | * @param progress The percent progress of the download (1.5 = 1.5%) 66 | */ 67 | private onBrowserDownload(progress: number) { 68 | if(progress === 0) { 69 | vscode.window.showInformationMessage('[EncounterPlus Markdown] Installing Chromium for PDF rendering...') 70 | vscode.window.setStatusBarMessage('Installing Chromium PDF engine...', 1000) 71 | } 72 | 73 | vscode.window.setStatusBarMessage(`Downloading PDF engine: ${progress.toFixed(1)}%`, 1000) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /source/vscode-extension/Commands/MarkdownToggle.ts: -------------------------------------------------------------------------------- 1 | 2 | /** An interface for a markdown toggle command */ 3 | export interface MarkdownToggle { 4 | 5 | /** Whether the markdown for this toggle can be multiline */ 6 | isMultiline: boolean 7 | 8 | /** The regular expression to detect whether this markdown state is enabled or disabled (e.g., is it bolded already or not) */ 9 | detectRegExp: RegExp 10 | 11 | /** The regular expression to capture the enabled markdown at the cursor */ 12 | enableRegExp: RegExp 13 | 14 | /** The regular expression to capture the disabled markdown at the cursor */ 15 | disableRegExp: RegExp 16 | 17 | /** The format to apply to the matched text to enable the markdown state from a disabled state */ 18 | enableFormat: string 19 | 20 | /** The format to apply to the matched text to disable the markdown state from an enabled state */ 21 | disableFormat: string 22 | 23 | /** The line offset count when the format is applied */ 24 | lineOffset: number 25 | 26 | /** The character offset count when the format is applied */ 27 | characterOffset: number 28 | 29 | /** Whether the command should deselect after the format is applied */ 30 | deselectAfter: boolean 31 | 32 | } -------------------------------------------------------------------------------- /source/vscode-extension/TreeViewProviders/ModuleProjectProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as Path from 'path' 3 | import { ModuleProject } from '../../shared/ModuleProject' 4 | import { Module, ModuleMode } from '../../shared/Module Entities/Module' 5 | import { Page } from '../../shared/Module Entities/Page' 6 | import { Group } from '../../shared/Module Entities/Group' 7 | import { ModuleEntity } from '../../shared/Module Entities/ModuleEntity' 8 | 9 | /** A TreeView proivider for module projects and their pages/groups */ 10 | export class ModuleProjectProvider implements vscode.TreeDataProvider { 11 | // --------------------------------------------------------------- 12 | // Private Fields 13 | // --------------------------------------------------------------- 14 | 15 | /** An event emitter for the refresh action */ 16 | private onDidChangeTreeDataEmitter = new vscode.EventEmitter() 17 | 18 | // --------------------------------------------------------------- 19 | // Initialization and Cleanup 20 | // --------------------------------------------------------------- 21 | 22 | /** 23 | * Initializes an instance of `ModuleProjectProvider` 24 | * @param workspaceRoot The workspace root path 25 | */ 26 | constructor(private workspaceRoot: string) { } 27 | 28 | // --------------------------------------------------------------- 29 | // Public Properties 30 | // --------------------------------------------------------------- 31 | 32 | /** The Did Change Tree Data event */ 33 | readonly onDidChangeTreeData: vscode.Event = this.onDidChangeTreeDataEmitter.event 34 | 35 | // --------------------------------------------------------------- 36 | // Public Methods 37 | // --------------------------------------------------------------- 38 | 39 | /** Refreshes the tree view contents */ 40 | refresh(): void { 41 | // Refresh from the root 42 | this.onDidChangeTreeDataEmitter.fire(undefined) 43 | } 44 | 45 | /** 46 | * Gets a tree item 47 | * @param element The element to get the tree item for 48 | */ 49 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem { 50 | return element 51 | } 52 | 53 | /** 54 | * Gets the children of a tree item 55 | * @param element The tree item to get children for 56 | */ 57 | getChildren(element?: vscode.TreeItem): Thenable { 58 | if (!this.workspaceRoot) { 59 | return Promise.resolve([]) 60 | } 61 | 62 | if (!element) { 63 | let moduleTreeItemReturns: Promise[] = [] 64 | let moduleProjects = ModuleProject.findModuleProjects(this.workspaceRoot) 65 | moduleProjects.forEach((moduleProject) => { 66 | if (!moduleProject.moduleProjectDirectory) { 67 | return 68 | } 69 | 70 | let appRootPath = Path.join(__dirname, "..") 71 | let moduleTreeReturn = Module.createModuleFromPath(moduleProject.moduleProjectDirectory, appRootPath, moduleProject.name, ModuleMode.ScanModule).then((module) => { 72 | return new ModuleTreeItem(module) 73 | }) 74 | 75 | moduleTreeItemReturns.push(moduleTreeReturn) 76 | }) 77 | return Promise.all(moduleTreeItemReturns) 78 | } else if (element instanceof ModuleTreeItem) { 79 | return Promise.resolve(ModuleProjectProvider.getTreeItemsForChildren(element.module.children)) 80 | } else if (element instanceof GroupTreeItem) { 81 | return Promise.resolve(ModuleProjectProvider.getTreeItemsForChildren(element.group.children)) 82 | } else if (element instanceof PageTreeItem) { 83 | return Promise.resolve(ModuleProjectProvider.getTreeItemsForChildren(element.page.children)) 84 | } else { 85 | return Promise.resolve([]) 86 | } 87 | } 88 | 89 | // --------------------------------------------------------------- 90 | // Private Methods 91 | // --------------------------------------------------------------- 92 | 93 | /** 94 | * Gets all tree items for a given directory 95 | * @param directoryPath The path to the directory to get tree items for 96 | */ 97 | private static getTreeItemsForChildren(moduleEntities: ModuleEntity[]): vscode.TreeItem[] { 98 | let treeItems: vscode.TreeItem[] = [] 99 | moduleEntities.forEach((entity) => { 100 | if (entity instanceof Group) { 101 | treeItems.push(new GroupTreeItem(entity)) 102 | } else if (entity instanceof Page) { 103 | treeItems.push(new PageTreeItem(entity)) 104 | } 105 | }) 106 | return treeItems 107 | } 108 | } 109 | 110 | /** A module group tree item */ 111 | export class GroupTreeItem extends vscode.TreeItem { 112 | 113 | /** 114 | * Initializes an instance of a `GroupTreeItem` 115 | * @param group The group 116 | * @param groupPath The group directory path 117 | */ 118 | constructor(public readonly group: Group) { 119 | super(group.name, vscode.TreeItemCollapsibleState.Collapsed) 120 | const groupFilePath = Path.join(group.groupPath, Group.groupSettingsFileName) 121 | this.command = { 122 | command: 'encounterPlusMarkdown.openGroupFile', 123 | title: 'Open Group', 124 | arguments: [groupFilePath], 125 | } 126 | this.contextValue = 'moduleGroup' 127 | this.tooltip = this.group.groupPath 128 | this.description = '' 129 | } 130 | } 131 | 132 | /** A module page tree item */ 133 | export class PageTreeItem extends vscode.TreeItem { 134 | 135 | /** 136 | * Initializes an instance of a `PageTreeItem` 137 | * @param page The page name 138 | */ 139 | constructor(public readonly page: Page) { 140 | super(page.name, page.children.length > 0 ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None) 141 | this.command = { 142 | command: 'encounterPlusMarkdown.openPage', 143 | title: 'Open Page', 144 | arguments: [page.pagePath, page.name], 145 | } 146 | this.contextValue = 'modulePage' 147 | this.tooltip = this.page.pagePath 148 | this.description = '' 149 | } 150 | } 151 | 152 | /** A module project tree item */ 153 | export class ModuleTreeItem extends vscode.TreeItem { 154 | /** 155 | * Initializes an instance of a `ModuleTreeItem` 156 | * @param moduleProject The module project 157 | */ 158 | constructor(public readonly module: Module) { 159 | super(module.moduleProjectInfo.name, vscode.TreeItemCollapsibleState.Expanded) 160 | this.command = { 161 | command: 'encounterPlusMarkdown.openModuleProjectFile', 162 | title: 'Open Module', 163 | arguments: [module.moduleProjectInfo.moduleProjectPath], 164 | } 165 | this.contextValue = 'moduleProject' 166 | this.tooltip = this.module.moduleProjectInfo.description ?? '' 167 | this.description = '' 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/build-module.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/dark/build-module.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/dark/encounterplus-markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/dark/export-to-pdf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/encounterplus-markdown-viewcontainer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/encounterplus-markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/item-codeblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:text.html.markdown", 4 | "patterns": [ 5 | { 6 | "include": "#item-code-block" 7 | } 8 | ], 9 | "repository": { 10 | "item-code-block": { 11 | "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(item)(\\s+[^`~]*)?$)", 12 | "name": "markup.fenced_code.block.markdown", 13 | "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", 14 | "beginCaptures": { 15 | "3": { 16 | "name": "punctuation.definition.markdown" 17 | }, 18 | "5": { 19 | "name": "fenced_code.block.language" 20 | }, 21 | "6": { 22 | "name": "fenced_code.block.language.attributes" 23 | } 24 | }, 25 | "endCaptures": { 26 | "3": { 27 | "name": "punctuation.definition.markdown" 28 | } 29 | }, 30 | "patterns": [ 31 | { 32 | "begin": "(^|\\G)(\\s*)(.*)", 33 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", 34 | "contentName": "meta.embedded.block.item", 35 | "patterns": [ 36 | { 37 | "include": "source.yaml" 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | }, 44 | "scopeName": "markdown.item.codeblock" 45 | } -------------------------------------------------------------------------------- /source/vscode-extension/resources/light/build-module.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/light/encounterplus-markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/light/export-to-pdf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /source/vscode-extension/resources/monster-codeblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:text.html.markdown", 4 | "patterns": [ 5 | { 6 | "include": "#monster-code-block" 7 | } 8 | ], 9 | "repository": { 10 | "monster-code-block": { 11 | "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(monster)(\\s+[^`~]*)?$)", 12 | "name": "markup.fenced_code.block.markdown", 13 | "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", 14 | "beginCaptures": { 15 | "3": { 16 | "name": "punctuation.definition.markdown" 17 | }, 18 | "5": { 19 | "name": "fenced_code.block.language" 20 | }, 21 | "6": { 22 | "name": "fenced_code.block.language.attributes" 23 | } 24 | }, 25 | "endCaptures": { 26 | "3": { 27 | "name": "punctuation.definition.markdown" 28 | } 29 | }, 30 | "patterns": [ 31 | { 32 | "begin": "(^|\\G)(\\s*)(.*)", 33 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", 34 | "contentName": "meta.embedded.block.monster", 35 | "patterns": [ 36 | { 37 | "include": "source.yaml" 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | }, 44 | "scopeName": "markdown.monster.codeblock" 45 | } -------------------------------------------------------------------------------- /source/vscode-extension/resources/spell-codeblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:text.html.markdown", 4 | "patterns": [ 5 | { 6 | "include": "#spell-code-block" 7 | } 8 | ], 9 | "repository": { 10 | "spell-code-block": { 11 | "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(spell)(\\s+[^`~]*)?$)", 12 | "name": "markup.fenced_code.block.markdown", 13 | "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", 14 | "beginCaptures": { 15 | "3": { 16 | "name": "punctuation.definition.markdown" 17 | }, 18 | "5": { 19 | "name": "fenced_code.block.language" 20 | }, 21 | "6": { 22 | "name": "fenced_code.block.language.attributes" 23 | } 24 | }, 25 | "endCaptures": { 26 | "3": { 27 | "name": "punctuation.definition.markdown" 28 | } 29 | }, 30 | "patterns": [ 31 | { 32 | "begin": "(^|\\G)(\\s*)(.*)", 33 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", 34 | "contentName": "meta.embedded.block.spell", 35 | "patterns": [ 36 | { 37 | "include": "source.yaml" 38 | } 39 | ] 40 | } 41 | ] 42 | } 43 | }, 44 | "scopeName": "markdown.spell.codeblock" 45 | } -------------------------------------------------------------------------------- /source/vscode-extension/resources/vscode.css: -------------------------------------------------------------------------------- 1 | /**** Overrides for VS Code styles ****/ 2 | 3 | html, 4 | body { 5 | font-family: '-apple-system', sans-serif; 6 | font-size: 13px; 7 | padding: 0px; 8 | line-height: 18px; 9 | word-wrap: break-word; 10 | } 11 | 12 | img { 13 | max-width: 100%; 14 | height: auto; 15 | } 16 | 17 | a { 18 | color: #58180d; 19 | text-decoration: none; 20 | } 21 | 22 | a:hover { 23 | text-decoration: none; 24 | } 25 | 26 | a:focus, 27 | input:focus, 28 | select:focus, 29 | textarea:focus { 30 | outline: none; 31 | outline-offset: none; 32 | } 33 | 34 | hr { 35 | border-color: #58180d; 36 | height: 1px; 37 | border-bottom: 1px solid; 38 | } 39 | 40 | h1 { 41 | border-bottom-width: 0px; 42 | border-bottom-style: none; 43 | } 44 | 45 | table > thead > tr > th { 46 | text-align: left; 47 | border-bottom: 1px solid; 48 | } 49 | 50 | table > thead > tr > th, 51 | table > thead > tr > td, 52 | table > tbody > tr > th, 53 | table > tbody > tr > td { 54 | padding: 10px; 55 | } 56 | 57 | table > tbody > tr + tr > td { 58 | border-top: none; 59 | } 60 | 61 | blockquote { 62 | border-left-width: 0px; 63 | border-left-style: none; 64 | } 65 | 66 | code { 67 | font-family: Menlo, Monaco, Consolas, 'Droid Sans Mono', 'Courier New', monospace, 'Droid Sans Fallback'; 68 | font-size: 1em; 69 | line-height: 1.357em; 70 | } 71 | 72 | /** Theming */ 73 | .vscode-light pre { 74 | background-color: #f5f5f5; 75 | } 76 | 77 | .vscode-dark pre { 78 | background-color: #f5f5f5; 79 | } 80 | 81 | .vscode-high-contrast pre { 82 | background-color: #f5f5f5; 83 | } 84 | 85 | .vscode-high-contrast h1 { 86 | border-color: none; 87 | } 88 | 89 | .vscode-light table > thead > tr > th { 90 | border-width: 0px; 91 | } 92 | 93 | .vscode-dark table > thead > tr > th { 94 | border-width: 0px; 95 | } 96 | 97 | .vscode-light h1 { 98 | border-width: 0px; 99 | } 100 | 101 | .vscode-light hr { 102 | border-color: #58180d; 103 | height: 1px; 104 | border-bottom: 1px solid; 105 | } 106 | 107 | .vscode-light table > tbody > tr + tr > td { 108 | border-width: 0px; 109 | } 110 | 111 | .vscode-light table.shop > tbody > tr + tr > td { 112 | border-color: #58180d; 113 | border-bottom-width: 1px; 114 | border-bottom-style: solid; 115 | } 116 | 117 | .vscode-dark h1 { 118 | border-width: 0px; 119 | } 120 | 121 | .vscode-dark hr { 122 | border-color: #58180d; 123 | height: 1px; 124 | border-bottom: 1px solid; 125 | } 126 | 127 | .vscode-dark table > tbody > tr + tr > td { 128 | border-width: 0px; 129 | } 130 | 131 | .vscode-dark table.shop > tbody > tr + tr > td { 132 | border-color: #58180d; 133 | border-bottom-width: 1px; 134 | border-bottom-style: solid; 135 | } 136 | 137 | .vscode-light hr.statblock-border { 138 | border-color: #000; 139 | height: 5px; 140 | } 141 | 142 | .vscode-dark hr.statblock-border { 143 | border-color: #000; 144 | height: 5px; 145 | } 146 | 147 | .vscode-body { 148 | font-size: 13px; 149 | padding: 1.5rem 2rem; 150 | } 151 | -------------------------------------------------------------------------------- /source/webpack.extension.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: 'node', 10 | entry: './vscode-extension/extension.ts', 11 | output: { 12 | path: path.resolve(__dirname, 'extension-out/vscode-extension'), 13 | filename: 'extension-packed.js', 14 | libraryTarget: 'commonjs2', 15 | devtoolModuleFilenameTemplate: '../../[resource-path]' 16 | }, 17 | optimization: { 18 | minimize: false 19 | }, 20 | node: { 21 | __dirname: false 22 | }, 23 | devtool: 'source-map', 24 | externals: { 25 | vscode: 'commonjs vscode', 26 | }, 27 | resolve: { 28 | extensions: ['.ts', '.js'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader', 38 | options: { 39 | configFile: "tsconfig.extension.json" 40 | } 41 | } 42 | ] 43 | } 44 | ] 45 | } 46 | }; 47 | module.exports = config; --------------------------------------------------------------------------------