diff --git a/src/components/DocumentControl.vue b/src/components/DocumentControl.vue index c79ed57..c92cc88 100644 --- a/src/components/DocumentControl.vue +++ b/src/components/DocumentControl.vue @@ -585,7 +585,7 @@ export default class DocumentControl extends BaseClass { } /****************************************************************/ - // Export project + // Save project /****************************************************************/ retrieveCurrentProjectName = retrieveCurrentProjectName diff --git a/src/components/ObjectTree.vue b/src/components/ObjectTree.vue index 0db2d76..af53975 100644 --- a/src/components/ObjectTree.vue +++ b/src/components/ObjectTree.vue @@ -10,6 +10,13 @@ @trigger-dialog-close="deleteObjectDialogClose" /> + + +
+ + Export document + + + + + Delete this document @@ -309,10 +323,12 @@ import { retrieveCurrentProjectName } from "src/scripts/projectManagement/projec import { createNewWithParent } from "src/scripts/documentActions/createNewWithParent" import { copyDocumentName, copyDocumentTextColor, copyDocumentBackgroundColor } from "src/scripts/documentActions/uniqueFieldCopy" import { copyDocument } from "src/scripts/documentActions/copyDocument" +import exportProjectDialog from "src/components/dialogs/ExportProject.vue" @Component({ components: { deleteDocumentCheckDialog, + exportProjectDialog, documentPreview: () => import("src/components/DocumentPreview.vue") } }) @@ -1136,6 +1152,27 @@ export default class ObjectTree extends BaseClass { createNewWithParent(currentDoc, this) } + /****************************************************************/ + // Export project dialog + /****************************************************************/ + + exportProjectDialogTrigger: string | false = false + exportProjectDialogClose () { + this.exportProjectDialogTrigger = false + } + + exportProjectAssignUID () { + this.exportProjectDialogTrigger = this.generateUID() + } + + prepickedID = "" + + commenceExport (node: {_id: string}) { + this.prepickedID = node._id + + this.exportProjectAssignUID() + } + /****************************************************************/ // Delete dialog /****************************************************************/ diff --git a/src/components/appHeader/AppControl.vue b/src/components/appHeader/AppControl.vue index 6a75a4c..8311ac3 100644 --- a/src/components/appHeader/AppControl.vue +++ b/src/components/appHeader/AppControl.vue @@ -129,6 +129,168 @@ > Project + + + + + New project + + + + + + + + + Save current project + + + + + + + Load existing project + + + + + + + + + Export project/documents + + + + + + + + + Show project overview + + + + + + + Close project + + + + + + + + + Advanced project tools + + + + + + + + Merge another project into the current one + + + + + + + + + Repair legacy project + + + + + + + + + + + + + + + Tools + - - New project - - - - - - - - - Save current project - - - - - - - Load existing project - - - - - - - - - Advanced project tools - - - - - - - - Merge another project into the current one - - - - - - - Export project/documents - - - - - - - - - Repair legacy project - - - - - - - - - - - - - Show project overview - - - - - - - Close project - - - - - - - - - - - Help, Settings & Info - - - + + + + + + + Help & Info + + - + + + -
Open existing document
+
Search through existing documents
@@ -202,6 +209,13 @@
+ + + Export document + + + + @@ -220,6 +234,8 @@ +
+ diff --git a/src/components/dialogs/ExportProject.vue b/src/components/dialogs/ExportProject.vue index 13c1aac..04c6e0b 100644 --- a/src/components/dialogs/ExportProject.vue +++ b/src/components/dialogs/ExportProject.vue @@ -19,16 +19,41 @@
- + +
+
+ +
+
+ + + Please note that the PDF export doesn't play nice +
+ with underlined text if different parts of the same paragraph +
+ have increased/decreased font sizes +
+ OR +
+ if you have underlines in headings. +
+
+ If your document contains such text, +
+ then the result might not very appealing aesthetically. +
+
+
+
+ + + + { - if (field.type === "break") { + if (field.type === "break" && !this.writerMode) { JSONExport.push({ hr: "" }) JSONExport.push({ h1: field.label }) } else if (field.type === "wysiwyg") { - JSONExport.push({ h2: field.label }) - JSONExport.push({ p: field.value }) + if (!this.writerMode || this.writerModeTitles) { + JSONExport.push({ h2: field.label }) + } + + let localValue = field.value as unknown as string + + var replacements = [ + [/\*/g, "\\*", "asterisks"], + [/#/g, "\\#", "number signs"], + [/\(/g, "\\(", "parentheses"], + [/\)/g, "\\)", "parentheses"], + [/\[/g, "\\[", "square brackets"], + [/\]/g, "\\]", "square brackets"], + [/_/g, "\\_", "underscores"] + ] + + // Escape special Characters + replacements.forEach(rep => { + // @ts-ignore + localValue = localValue.replace(rep[0], rep[1]) + }) + + JSONExport.push({ p: localValue }) } - else { + else if (!this.writerMode) { JSONExport.push({ h2: field.label }) if (Array.isArray(field.value)) { JSONExport.push({ ul: field.value }) @@ -865,62 +943,75 @@ export default class ExportProject extends DialogBase { // Next line doc.fontSize(textFont).moveDown().moveDown() - // Document type - doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) - .text("Document type", textPadding, undefined, paragraphOptions) - doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) - .list([input.documentType], listPadding, undefined, paragraphOptions) - .moveDown() + if (!this.writerMode) { + // Document type + doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) + .text("Document type", textPadding, undefined, paragraphOptions) + doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) + .list([input.documentType], listPadding, undefined, paragraphOptions) + .moveDown() + } + if (!this.writerMode) { // Status - if (!this.hideDeadInformation) { - doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) - .text("Status", textPadding, undefined, paragraphOptions) - doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) - .list([((input.isDead) ? "Dead/Gone/Destroyed" : "Active/Alive")], listPadding, undefined, paragraphOptions) - .moveDown() + if (!this.hideDeadInformation) { + doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) + .text("Status", textPadding, undefined, paragraphOptions) + doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) + .list([((input.isDead) ? "Dead/Gone/Destroyed" : "Active/Alive")], listPadding, undefined, paragraphOptions) + .moveDown() + } } - // Hierarchy path - if (this.includeHierarchyPath) { - doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) - .text("Hierarchical path", textPadding, undefined, paragraphOptions) - doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) - .list([input.hierarchicalPath], listPadding) - .moveDown() + if (!this.writerMode) { + // Hierarchy path + if (this.includeHierarchyPath) { + doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) + .text("Hierarchical path", textPadding, undefined, paragraphOptions) + doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) + .list([input.hierarchicalPath], listPadding) + .moveDown() + } } + if (!this.writerMode) { // Tags - if (this.includeTags) { - doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) - .text("Tags", textPadding, undefined, paragraphOptions) - doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) - .list((Array.isArray(input.tags) ? input.tags : []), listPadding, undefined, paragraphOptions) - .moveDown() + if (this.includeTags) { + doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) + .text("Tags", textPadding, undefined, paragraphOptions) + doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) + .list((Array.isArray(input.tags) ? input.tags : []), listPadding, undefined, paragraphOptions) + .moveDown() + } } // Other fields input.fieldValues.forEach(field => { - if (field.type === "break") { + if (field.type === "break" && !this.writerMode) { doc.moveDown() .font("Times-Bold").fillColor("#000000").fontSize(subTitleFont) .text(field.label, textPadding, undefined, paragraphOptions) .moveDown() } else if (field.type === "wysiwyg") { - doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) - .text(field.label, textPadding, undefined, paragraphOptions) - .moveDown() + if (!this.writerMode || this.writerModeTitles) { + doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) + .text(field.label, textPadding, undefined, paragraphOptions) + .moveDown() + } // @ts-ignore const returnList = this.buildPDFWysiwygContent(field.value) doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) - const wysiwygOptions: {[key:string]: any} = extend(true, {}, paragraphOptions) - returnList.forEach(node => { if (node.type === "text") { + const wysiwygOptions: {[key:string]: any} = extend(true, {}, paragraphOptions) + wysiwygOptions.baseline = "alphabetic" + + doc.fontSize(textFont) + // Italic wysiwygOptions.oblique = node.attrs.italic @@ -930,6 +1021,23 @@ export default class ExportProject extends DialogBase { // Bold doc.font((node?.attrs?.bold) ? "Times-Bold" : "Times-Roman") + // Heading font sizing + if (node?.attrs?.hasHeadingFontSize) { + // @ts-ignore + doc.fontSize(node.attrs.nodeHeadingSize) + // @ts-ignore + wysiwygOptions.lineGap = node.attrs.nodeHeadingSize / 3 + doc.font("Times-Bold") + } + + // Custom font sizing + if (node?.attrs?.hasSpecialFontSize) { + // @ts-ignore + doc.fontSize(node.attrs.specialFontSize) + // @ts-ignore + wysiwygOptions.lineGap = node.attrs.specialFontSize / 3 + } + // Continued wysiwygOptions.continued = node.attrs.continued @@ -948,7 +1056,7 @@ export default class ExportProject extends DialogBase { // @ts-ignore doc.moveDown() } - else { + else if (!this.writerMode) { doc.font("Times-Bold").fillColor("#000000").fontSize(textFont) .text(field.label, textPadding, undefined, paragraphOptions) doc.font("Times-Roman").fillColor("#000000").fontSize(textFont) @@ -961,6 +1069,27 @@ export default class ExportProject extends DialogBase { } buildPDFWysiwygContent (input: string) { + const blockTagList = [ + "div", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "li", + "blockquote" + ] + + const headingsList = [ + "h1", + "h2", + "h3", + "h4", + "h5", + "h6" + ] + const returnNodeList: I_HtmlParserNode[] = [] // eslint-disable-next-line @typescript-eslint/no-unsafe-call @@ -977,6 +1106,60 @@ export default class ExportProject extends DialogBase { } } + const processHeadingFontSize = (heading: string) => { + switch (heading) { + case "h1": + return 24 + + case "h2": + return 20 + + case "h3": + return 18 + + case "h4": + return 16 + + case "h5": + return 14 + + case "h6": + return 12 + + default: + return 11 + } + } + + const processNodeFontSize = (fontString: string) => { + const fontNumber = parseInt(fontString) + switch (fontNumber) { + case 1: + return 7 + + case 2: + return 9 + + case 3: + return 11 + + case 4: + return 13 + + case 5: + return 16 + + case 6: + return 19 + + case 7: + return 23 + + default: + return 11 + } + } + const processNode = (node: I_HtmlParserNode) => { // ------------- NODE EXTRA ATTRIBUTES ------------------ let nodeStyles: false|string = false @@ -985,6 +1168,12 @@ export default class ExportProject extends DialogBase { nodeStyles = (processNodeStyles(snapshot.style)) ? snapshot.style : false } + let nodeFontSize: false|string = false + if (node?.attrs?.size) { + const snapshot: {size:string} = extend(true, {}, node.attrs) + nodeFontSize = (snapshot.size) || false + } + let parentIsBlockquote = false if (node.parentNode?.attrs?.blockquotePadding) { parentIsBlockquote = true @@ -997,11 +1186,39 @@ export default class ExportProject extends DialogBase { // eslint-disable-next-line @typescript-eslint/restrict-plus-operands const nextNode = node.selfNodeList[node.selfIndex + 1] + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + const nextParentNode = node?.parentNode?.selfNodeList[node?.parentNode?.selfIndex + 1] + + // If it is the last one, obviously dont continue anything + if (!nextNode) { + node.isLast = true + } + + // Text modifier - Headings + if ((node.type === "tag" && headingsList.includes(node.name)) || node?.parentNode?.attrs.hasHeadingFontSize === true) { + node.attrs.hasHeadingFontSize = true + + if (headingsList.includes(node.name)) { + node.attrs.nodeHeadingSize = processHeadingFontSize(node.name) + } + else if (node?.parentNode?.attrs?.nodeHeadingSize) { + node.attrs.nodeHeadingSize = node?.parentNode?.attrs?.nodeHeadingSize + } + + node.attrs.continued = false + } + else { + node.attrs.hasHeadingFontSize = false + } + // If next if bold, italic or underline if (nextNode) { if ((nextNode.type === "tag" && nextNode.name === "i") || (nextNode.type === "tag" && nextNode.name === "b") || - (nextNode.type === "tag" && nextNode.name === "u") + (nextNode.type === "tag" && nextNode.name === "u") || + (nextNode.type === "tag" && nextNode.name === "font") || + (nextNode.type === "tag" && nextNode.name === "span") ) { node.attrs.continued = true } @@ -1018,6 +1235,15 @@ export default class ExportProject extends DialogBase { node.attrs.align = node.parentNode.attrs.align } + // Temporary fix for spans + if ((node.type === "tag" && node.name === "span") || node?.parentNode?.attrs.isSpan === true) { + node.attrs.isSpan = true + node.attrs.continued = true + } + else { + node.attrs.isSpan = false + } + // Text modifier - Italic if ((node.type === "tag" && node.name === "i") || node?.parentNode?.attrs.italic === true) { node.attrs.italic = true @@ -1045,13 +1271,33 @@ export default class ExportProject extends DialogBase { node.attrs.underline = false } - // If it is the last one, obviously dont continue anything - if (!nextNode && node.type !== "text") { - node.isLast = true + // Text modifier - Font size + if ((node.type === "tag" && node.name === "font") || node?.parentNode?.attrs.hasSpecialFontSize === true) { + node.attrs.hasSpecialFontSize = true + // @ts-ignore + node.attrs.specialFontSize = (nodeFontSize) + // @ts-ignore + ? processNodeFontSize(nodeFontSize) + : node?.parentNode?.attrs?.specialFontSize + node.attrs.continued = true + } + else { + node.attrs.hasSpecialFontSize = false } - // Don't continue is this lack a continuing node - if (node.parentNode?.isLast && !nextNode) { + // Don't continue is this lack a continuing node OR if next node is 'div' + if ((node.parentNode?.isLast && !nextNode) || + (nextNode && nextNode.type === "tag" && blockTagList.includes(nextNode.name)) || + (node.isLast && nextParentNode?.type === "tag" && blockTagList.includes(nextParentNode?.name)) || + (node.isLast && node.parentNode?.isLast) + ) { + if (node.content === "great and brilliant Lord Demarcus Katari'") { + console.log((node.parentNode?.isLast && !nextNode)) + console.log((nextNode && nextNode.type === "tag" && blockTagList.includes(nextNode.name))) + console.log((node.isLast && nextParentNode?.type === "tag" && blockTagList.includes(nextParentNode?.name))) + console.log((node.isLast && node.parentNode?.isLast)) + } + node.attrs.continued = false } @@ -1063,29 +1309,36 @@ export default class ExportProject extends DialogBase { // ------------- NODE PROCESSING ---------------------- // Return text node value OR a break - if ((node.type === "text" && node.content) || node.type === "br") { + if ((node.type === "text" && node.content)) { const returnNode = node // @ts-ignore returnNode.content = returnNode.content.replace(/ /g, "").replace(/(\r\n|\n|\r)/gm, "") + if (node.attrs.isSpan) { + returnNode.content = returnNode.content + " " + } returnNodeList.push(returnNode) } - // Process subnodes else if (node?.children?.length > 0) { node.children.forEach((childNode, i) => { childNode.selfIndex = i - childNode.selfNodeList = node.children + childNode.selfNodeList = node.children.filter(subNode => subNode.name !== "br") childNode.parentNode = node + if (node.name === "span") { + console.log((childNode)) + } + processNode(childNode) }) } } // Generate return value - parsedHTML[0].selfNodeList = [parsedHTML[0]] + parsedHTML[0].selfNodeList = [parsedHTML[0]].filter(subNode => subNode.name !== "br") parsedHTML[0].selfIndex = 0 parsedHTML[0].attrs = {} + processNode(parsedHTML[0]) return returnNodeList diff --git a/src/css/app.scss b/src/css/app.scss index b1100c4..48e7096 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -190,6 +190,70 @@ a { > div { margin: 12.5px 0; } + + font[size="1"] { + font-size: 10px; + } + + font[size="2"] { + font-size: 13px; + } + + font[size="3"] { + font-size: 16px; + } + + font[size="4"] { + font-size: 20px; + } + + font[size="5"] { + font-size: 24px; + } + + font[size="6"] { + font-size: 28px; + } + + font[size="7"] { + font-size: 34px; + } + + h1 { + font-size: 50px; + font-weight: 400; + line-height: 1.4; + } + + h2 { + font-size: 40px; + font-weight: 400; + line-height: 1.6; + } + + h3 { + font-size: 35px; + font-weight: 400; + line-height: 1.6; + } + + h4 { + font-size: 30px; + font-weight: 400; + line-height: 1.8; + } + + h5 { + font-size: 25px; + font-weight: 400; + line-height: 1.8; + } + + h6 { + font-size: 20px; + font-weight: 400; + line-height: 1.8; + } } .q-editor__toolbars-container { @@ -693,3 +757,73 @@ body .q-tooltip { right: 0; top: 0; } + +font[size="1"] { + font-size: 10px; +} + +font[size="2"] { + font-size: 13px; +} + +font[size="3"] { + font-size: 16px; +} + +font[size="4"] { + font-size: 20px; +} + +font[size="5"] { + font-size: 24px; +} + +font[size="6"] { + font-size: 28px; +} + +font[size="7"] { + font-size: 34px; +} + +.q-menu.q-position-engine .q-list { + h1 { + font-size: 50px; + font-weight: 400; + line-height: 1.4; + } + + h2 { + font-size: 40px; + font-weight: 400; + line-height: 1.6; + } + + h3 { + font-size: 35px; + font-weight: 400; + line-height: 1.6; + } + + h4 { + font-size: 30px; + font-weight: 400; + line-height: 1.8; + } + + h5 { + font-size: 25px; + font-weight: 400; + line-height: 1.8; + } + + h6 { + font-size: 20px; + font-weight: 400; + line-height: 1.8; + } + + .text-no-wrap { + font-size: 16px; + } +} diff --git a/src/documents/changeLog.md b/src/documents/changeLog.md index 3a791ad..adb0245 100644 --- a/src/documents/changeLog.md +++ b/src/documents/changeLog.md @@ -24,6 +24,7 @@ ### New features - **Added export support for Markdown** +- **Added export support for PDF** - **Added on-the-fly relationship documents generation** - **Added stat/attribute support for multiple RPG systems** - **Added option to search through the `Other names` field via `@` modifier** @@ -60,6 +61,8 @@ - Added a tiny bit of color to help discern which relationship is one-way and which is two-way - Adjusted the single and multi relationship fields not showing suggestion popups when a value is removed the list - Adjusted the single relationship fields to hide the suggest list when a value is selected from the list +- Adjust font sizes for titles and "sizes" in text editor field to be actually useable without looks ridiculous +- Rearanged the menu and added a new `Tools` category to make finding stuff a bit easier ## 0.1.6a diff --git a/src/interfaces/I_HtmlParserNode.ts b/src/interfaces/I_HtmlParserNode.ts index 1ff81e8..ab1d113 100644 --- a/src/interfaces/I_HtmlParserNode.ts +++ b/src/interfaces/I_HtmlParserNode.ts @@ -4,7 +4,7 @@ export interface I_HtmlParserNode { voidElement: boolean; name: string; attrs: { - [key: string]: string|boolean + [key: string]: string|boolean|number }; children: I_HtmlParserNode[]; selfIndex?: number; diff --git a/src/scripts/databaseManager/extraFieldLists/RPGSystemsStats.ts b/src/scripts/databaseManager/extraFieldLists/RPGSystemsStats.ts index 0fc8a66..3afff77 100644 --- a/src/scripts/databaseManager/extraFieldLists/RPGSystemsStats.ts +++ b/src/scripts/databaseManager/extraFieldLists/RPGSystemsStats.ts @@ -524,6 +524,74 @@ export const RPGSystemsStats = [ "Reflexes (ref) - Speed (spd)" ] }, + { + title: "Fallout", + values: [ + "Strength", + "Perception", + "Endurance", + "Charisma", + "Intelligence", + "Agility", + "Luck" + ] + }, + { + title: "Star Trek Adventures - Personnel (Modiphius 2d20 System)", + values: [ + "Control - Attribute", + "Daring - Attribute", + "Fitness - Attribute", + "Insight - Attribute", + "Presence - Attribute", + "Reason - Attribute", + "Command - Discipline", + "Conn - Discipline", + "Security - Discipline", + "Engineering - Discipline", + "Science - Discipline", + "Medicine - Discipline" + ] + }, + { + title: "Star Trek Adventures - Ships (Modiphius 2d20 System)", + values: [ + "Engines - Systems", + "Structure - Systems", + "Computers - Systems", + "Sensors - Systems", + "Weapons - Systems", + "Communications - Systems", + "Command - Department", + "Conn - Department", + "Security - Department", + "Engineering - Department", + "Science - Department", + "Medicine - Department" + ] + }, + { + title: "Warhammer 40k (Dark Heresy 2)", + values: [ + "Weapon Skill", + "Ballistic Skill", + "Strength", + "Toughness", + "Agility", + "Intelligence", + "Perception", + "Willpower", + "Fellowship", + "Psy Rating", + "Wounds", + "Fatigue", + "Insanity", + "Corruption", + "Influence", + "Profit Factor", + "Infamy" + ] + }, "Absorption", "Alertness", diff --git a/suggestionList.md b/suggestionList.md index ce13364..f6835fe 100644 --- a/suggestionList.md +++ b/suggestionList.md @@ -1,14 +1,15 @@ ## THE GM BATCH START - 0.1.7 -- "Save all" keybind and "Save all and exit" option on the exiting - -- Export for MD/PDF/ODT/DOCX +- Add `Font` and `hX` elements to PDF support... maybe something else +Suggestion: In Species/Races/Flora/Fauna docs, move Related Species/Races/Flora/Fauna and Characters of Species/Races/Flora/Fauna down and move the text fields (Description & History and Traditions & Customs) up. I have a lot of characters and have to scroll quite a bit to get down to the text fields to read them. ## THE GM BATCH END - 0.1.7 ## PROJECT SETTINGS BATCH 1 START - 0.1.8 - Add `SHIFT + ENTER` add mode to relationship searches to add documents without deleting the entered text +- Rewrite document display algortithmn and add option to show description (text editor fields) on top of the section (under break) +- Breaks/titles in lists - Pin document preview floating window - Project setting dialog/options - Project rename @@ -71,6 +72,7 @@ ### High priority +- Export for ODT/DOCX - Dynamic table field - Add subtabs (character stats, general info, etc) - Global text find/replace @@ -82,6 +84,7 @@ ### Low priority +- Double decimals (dates) in order field - Horizontal fields (title AND value next to each other. Might need a designer) - Simple data imports (maybe?) - Vanity project-wide word count