0.1.7-DEV-8: Added export support (MD for now)

This commit is contained in:
Elvanos 2021-05-17 22:36:41 +02:00
parent a68a8cc2c5
commit 470c0001a1
22 changed files with 1106 additions and 41 deletions

15
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "fantasiaarchive",
"version": "0.1.5",
"version": "0.1.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -8980,6 +8980,11 @@
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true
},
"indento": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/indento/-/indento-1.1.13.tgz",
"integrity": "sha512-YZWk3mreBEM7sBPddsiQnW9Z8SGg/gNpFfscJq00HCDS7pxcQWWWMSVKJU7YkTRyDu1Zv2s8zaK8gQWKmCXHlg=="
},
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
@ -9643,6 +9648,14 @@
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json2md": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/json2md/-/json2md-1.10.0.tgz",
"integrity": "sha512-Rq7mx1wnLZIKn4vb4hrVUMVxHYNRzMVoG3PwYzx6uRlGGjcgjqIS7J0h553Ri5mbLq8zX61jVP+eL8rGZ68zSA==",
"requires": {
"indento": "^1.1.7"
}
},
"json3": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",

View file

@ -18,6 +18,7 @@
"apexcharts": "^3.26.0",
"axios": "^0.18.1",
"core-js": "^3.6.5",
"json2md": "^1.10.0",
"katex": "^0.12.0",
"lodash": "^4.17.20",
"mermaid": "^8.8.4",
@ -81,4 +82,4 @@
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
}
}

View file

@ -562,8 +562,8 @@ export default class BaseClass extends Vue {
const parentDocInDB = (list.find(p => p._id === parentDoc?._id))
if (!parentDoc || (parentDoc && !parentDocInDB)) {
const singleBlueprintName = this.SGET_allBlueprints.find(e => e._id === document.type)?.nameSingular
hierarchicalString += singleBlueprintName
const pluralBlueprintName = this.SGET_allBlueprints.find(e => e._id === document.type)?.namePlural
hierarchicalString += pluralBlueprintName
return hierarchicalString
}

View file

@ -38,6 +38,13 @@
@trigger-dialog-close="tipsTricksDialogClose"
/>
<!-- Export project dialog -->
<exportProjectDialog
:dialog-trigger="exportProjectDialogTrigger"
:prepicked-ids="[prepickedID]"
@trigger-dialog-close="exportProjectDialogClose"
/>
<q-page-sticky position="top-right"
class="documentControl bg-dark"
:class="{'fullScreen': hideHierarchyTree}"
@ -294,6 +301,35 @@
</q-tooltip>
</q-btn>
<q-separator vertical inset color="accent"
v-if="!currentlyNew && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
/>
<q-btn
:color="(!hasEdits) ? 'secondary' : 'primary'"
icon="mdi-database-export-outline"
@click="commenceExport"
outline
v-if="!currentlyNew && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
>
Export current project
<span class="text-secondary" v-if="!hasEdits">
<br>
<br>
Document has active edits.
<br>
These will not be exported.
<br>
Please save first.
</span>
</q-tooltip>
</q-btn>
<q-separator vertical inset color="accent"
v-if="!currentlyNew && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
/>
@ -333,6 +369,7 @@ import deleteDocumentCheckDialog from "src/components/dialogs/DeleteDocumentChec
import advancedSearchGuideDialog from "src/components/dialogs/AdvancedSearchGuide.vue"
import keybindCheatsheetDialog from "src/components/dialogs/KeybindCheatsheet.vue"
import tipsTricksTriviaDialog from "src/components/dialogs/TipsTricksTrivia.vue"
import exportProjectDialog from "src/components/dialogs/ExportProject.vue"
import { I_OpenedDocument } from "src/interfaces/I_OpenedDocument"
import { extend, Loading, QSpinnerGears } from "quasar"
@ -349,7 +386,8 @@ import { retrieveCurrentProjectName, saveProject } from "src/scripts/projectMana
deleteDocumentCheckDialog,
advancedSearchGuideDialog,
keybindCheatsheetDialog,
tipsTricksTriviaDialog
tipsTricksTriviaDialog,
exportProjectDialog
}
})
export default class DocumentControl extends BaseClass {
@ -398,8 +436,13 @@ export default class DocumentControl extends BaseClass {
this.deleteObjectAssignUID()
}
// Export document - NONE
if (this.determineKeyBind("exportDocument") && this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
this.commenceExport()
}
// Edit document - CTRL + E
if (this.determineKeyBind("editDocument") && this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
if (this.determineKeyBind("editDocument") && !this.currentlyNew && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
this.toggleEditMode()
}
@ -489,6 +532,19 @@ export default class DocumentControl extends BaseClass {
this.deleteObjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// Export project dialog
/****************************************************************/
exportProjectDialogTrigger: string | false = false
exportProjectDialogClose () {
this.exportProjectDialogTrigger = false
}
exportProjectAssignUID () {
this.exportProjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// New document dialog
/****************************************************************/
@ -774,6 +830,16 @@ export default class DocumentControl extends BaseClass {
this.SSET_updateDocument({ doc: this.mapShortDocument(doc, this.SGET_allDocumentsByType(doc.type).docs) })
}
}
prepickedID = ""
commenceExport () {
const currentDocument = this.findRequestedOrActiveDocument()
// @ts-ignore
this.prepickedID = currentDocument._id
this.exportProjectAssignUID()
}
}
</script>

View file

@ -89,6 +89,12 @@
@trigger-dialog-close="repairProjectDialogClose"
/>
<!-- Export project dialog -->
<exportProjectDialog
:dialog-trigger="exportProjectDialogTrigger"
@trigger-dialog-close="exportProjectDialogClose"
/>
<q-btn-group
flat
class="AppControl__buttons"
@ -241,21 +247,6 @@
</q-item-section>
</q-item>
<q-item
v-close-popup
clickable
active
active-class="bg-gunmetal-light text-cultured"
class="noHigh"
@click="mergeProjectAssignUID"
:disable="!projectExists"
>
<q-item-section>Merge another project into the current one</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-folder-plus-outline" />
</q-item-section>
</q-item>
<q-separator dark />
<q-item clickable>
@ -266,6 +257,38 @@
<q-menu anchor="top end" self="top start">
<q-list class="bg-gunmetal text-accent">
<q-item
v-close-popup
clickable
active
active-class="bg-gunmetal-light text-cultured"
class="noHigh"
@click="mergeProjectAssignUID"
:disable="!projectExists"
>
<q-item-section>Merge another project into the current one</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-folder-plus-outline" />
</q-item-section>
</q-item>
<q-item
v-close-popup
clickable
active
active-class="bg-gunmetal-light text-cultured"
class="noHigh"
@click="exportProjectAssignUID"
:disable="!projectExists"
>
<q-item-section>Export project/documents</q-item-section>
<q-item-section avatar>
<q-icon name="mdi-database-export-outline" />
</q-item-section>
</q-item>
<q-separator dark />
<q-item
v-close-popup
clickable
@ -483,6 +506,7 @@ import newDocumentDialog from "src/components/dialogs/NewDocument.vue"
import existingDocumentDialog from "src/components/dialogs/ExistingDocument.vue"
import tipsTricksTriviaDialog from "src/components/dialogs/TipsTricksTrivia.vue"
import licenseDialog from "src/components/dialogs/License.vue"
import exportProjectDialog from "src/components/dialogs/ExportProject.vue"
import { Loading, QSpinnerGears } from "quasar"
import { retrieveCurrentProjectName, saveProject } from "src/scripts/projectManagement/projectManagent"
@ -505,7 +529,8 @@ import appLogo from "src/assets/appLogo.png"
existingDocumentDialog,
tipsTricksTriviaDialog,
licenseDialog,
repairProjectDialog
repairProjectDialog,
exportProjectDialog
}
})
export default class AppControl extends BaseClass {
@ -659,6 +684,19 @@ export default class AppControl extends BaseClass {
this.mergeProjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// Export project dialog
/****************************************************************/
exportProjectDialogTrigger: string | false = false
exportProjectDialogClose () {
this.exportProjectDialogTrigger = false
}
exportProjectAssignUID () {
this.exportProjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// New project dialog
/****************************************************************/

View file

@ -9,7 +9,7 @@
class="existingDocumentPopup"
>
<q-card-section class="row items-center">
<q-card-section class="row items-center">
<h6 class="text-center q-my-sm">Open existing document</h6>
</q-card-section>

View file

@ -0,0 +1,834 @@
<template>
<q-dialog
v-model="dialogModel"
@before-hide="triggerDialogClose"
:persistent="exportOngoing"
>
<q-card
v-if="!exportOngoing"
class="exportDialog"
dark
>
<q-card-section class="row justify-center text-center">
<h6 class="text-center q-my-sm">Export project/documents</h6>
</q-card-section>
<q-card-section>
<div class="row justify-center">
<div class="col-4">
<div class="q-mx-lg">
<q-select
class="exportTypeSelect"
dark
popup-content-class="menuResizer"
:options="exportFormats"
label="Export file format"
filled
input-debounce="0"
v-model="selectedExportFormat"
/>
<q-checkbox
class="q-mt-lg"
dark color="primary"
v-model="exportWholeProject"
label="Export whole project?"
/>
<q-checkbox
class="q-mt-lg"
dark color="primary"
v-model="includeTags"
label="Include tags in the export?"
/>
<q-checkbox
class="q-mt-lg"
dark color="primary"
v-model="includeHierarchyPath"
label="Include hierarchical path in the export?"
/>
<q-checkbox
class="q-mt-lg"
dark color="primary"
v-model="includeIsDead"
label="Include dead/gone/destroyed documents in the export?"
/>
<q-checkbox
v-if="includeIsDead"
class="q-mt-lg"
dark color="primary"
v-model="hideDeadInformation"
label="Hide dead/gone/destroyed status in the exported documents?"
/>
</div>
</div>
<div class="col-8">
<div
style="height: 100%;"
class="q-mx-lg"
>
<div
style="height: 100%; line-height: 2;"
class="column justify-center items-center"
v-if="exportWholeProject"
>
Please note that:
<span class="text-bold text-secondary">
The more documents, the slower export.
</span>
<span>
FA currently needs to generate individual files for <span class="text-bold text-primary">{{SGET_allDocuments.docs.length}} </span> documents.
</span>
</div>
<q-select
ref="ref_exportDocument"
class="exportDocumentSelect"
dark
popup-content-class="menuResizer"
v-if="!exportWholeProject"
menu-anchor="bottom middle"
menu-self="top middle"
:options="filteredExistingInput"
use-input
multiple
use-chips
filled
label="Selected documents"
input-debounce="500"
v-model="exportDocumentsModel"
@filter="filterExistingSelect"
>
<template v-slot:append v-if="!hideAdvSearchCheatsheetButton">
<q-btn round dense flat icon="mdi-help-rhombus" @click.stop.prevent="SSET_setAdvSearchWindowVisible"
>
<q-tooltip :delay="500">
Open search cheatsheet
</q-tooltip>
</q-btn>
</template>
<template v-slot:option="{ itemProps, itemEvents, opt }">
<q-item
:class="{'hasTextShadow': textShadow, 'isMinor':opt.isMinor}"
v-bind="itemProps"
v-on="itemEvents"
:key="opt.id"
:style="`color: ${opt.color}; background-color: ${opt.bgColor}`"
@mouseleave="setDocumentPreviewClose"
>
<documentPreview
:document-id="opt._id"
:external-close-trigger="documentPreviewClose"
:special-z-index="999999999"
:custom-anchor="'top end'"
:custom-self="'center left'"
:custom-delay="1200"
/>
<q-item-section avatar>
<q-icon
:style="`color: ${retrieveIconColor(opt)}`"
:name="(opt.isCategory) ? 'fas fa-folder-open' : opt.icon"
/>
</q-item-section>
<q-item-section>
<q-item-label>
<span class="isDeadIndicator" v-if="opt.isDead">
</span>
<span :class="{'isDead': (opt.isDead && !hideDeadCrossThrough)}" v-html="opt.label">
</span>
</q-item-label>
<q-item-label caption class="text-cultured" v-html="opt.hierarchicalPath"></q-item-label>
<q-item-label caption class="text-cultured" v-if="opt.tags">
<q-chip
v-for="(input,index) in opt.tags" :key="index"
outline
style="opacity: 0.8;"
size="12px"
class="text-cultured"
v-html="`${input}`"
>
</q-chip>
</q-item-label>
</q-item-section>
</q-item>
</template>
<template v-slot:selected-item="scope">
<q-chip
removable
dense
@remove="removeInput(scope)"
:tabindex="scope.tabindex"
:color="(scope.opt.isAutoGenerated) ? 'teal-3' : 'accent'"
text-color="dark"
class="text-bold"
>
<div
class="relationShipChipOverlay"
@mouseleave="setDocumentPreviewClose"
/>
<div class="relationShipChipContent">
<template v-if="scope.opt.isDead">
</template>
{{ stripTags(scope.opt.label) }}
</div>
<documentPreview
:special-z-index="999999999"
:custom-delay="1200"
:document-id="scope.opt._id"
:external-close-trigger="documentPreviewClose"
/>
</q-chip>
</template>
</q-select>
</div>
</div>
</div>
</q-card-section>
<q-card-actions align="right" class="q-mb-lg q-mr-xl">
<q-btn flat label="Cancel" color="accent" v-close-popup class="q-mr-lg" />
<q-btn
:flat="!exportWholeProject && exportDocumentsModel.length === 0"
:outline="exportWholeProject || exportDocumentsModel.length > 0"
label="Export"
color="primary"
:disable="!exportWholeProject && exportDocumentsModel.length === 0"
@click="exportDocuments"
/>
</q-card-actions>
</q-card>
<q-card v-if="exportOngoing" dark class="exportDialog">
<q-card-section class="row justify-center">
<h6 class="text-center q-my-sm">Exporting...</h6>
</q-card-section>
<q-card-section class="row justify-center q-mx-xl">
<div>
Current document: {{currentDocName}}
</div>
</q-card-section>
<q-card-section class="row justify-center q-mx-xl q-mb-lg">
<q-linear-progress stripe round dark size="20px" :value="progressCounter" color="primary" class="q-mt-sm">
<div class="absolute-full flex flex-center">
<q-badge text-color="accent" color="dark" :label="`${exportedDocuments}/${exportList.length}`" />
</div>
</q-linear-progress>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script lang="ts">
import { Component, Watch, Prop } from "vue-property-decorator"
import { remote } from "electron"
import { retrieveCurrentProjectName } from "src/scripts/projectManagement/projectManagent"
// @ts-ignore
import json2md from "json2md/lib/index.js"
import DialogBase from "src/components/dialogs/_DialogBase"
import { uid, extend } from "quasar"
import fs from "fs-extra"
import documentPreview from "src/components/DocumentPreview.vue"
import { I_ExportObject } from "src/interfaces/I_ExportObject"
import { I_ShortenedDocument } from "src/interfaces/I_OpenedDocument"
import { I_Blueprint } from "src/interfaces/I_Blueprint"
import { advancedDocumentFilter } from "src/scripts/utilities/advancedDocumentFilter"
@Component({
components: {
documentPreview
}
})
export default class ExportProject extends DialogBase {
/**
* React to dialog opening request
*/
@Watch("dialogTrigger")
openDialog (val: string|false) {
if (val) {
if (this.SGET_getDialogsState) {
return
}
this.SSET_setDialogState(true)
this.dialogModel = true
this.resetLocalData()
this.reloadOptions()
this.populateExportObjectDialog()
if (this.prepickedIds.length > 0) {
// @ts-ignore
this.exportDocumentsModel = this.SGET_allDocuments.docs.filter(doc => {
return this.prepickedIds.includes(doc._id)
})
}
}
}
resetLocalData () {
this.selectedExportFormat = "Markdown - MD"
this.exportWholeProject = false
this.includeTags = false
this.includeHierarchyPath = false
this.hideDeadInformation = false
this.includeIsDead = true
this.exportDocumentsModel = []
this.exportOngoing = false
this.exportList = []
}
@Prop(({
default () {
return []
}
})) readonly prepickedIds!: string[]
exportFormats = [
"Markdown - MD"
// "Adobe Reader - PDF",
// "Open office document - ODT",
// "MS Word document - DOCX"
]
selectedExportFormat = "Markdown - MD"
exportWholeProject = false
includeTags = false
includeHierarchyPath = false
hideDeadInformation = false
includeIsDead = true
setDocumentPreviewClose () {
this.documentPreviewClose = uid()
}
/**
* Reloads local options
*/
reloadOptions () {
this.textShadow = this.SGET_options.textShadow
this.hideDeadCrossThrough = this.SGET_options.hideDeadCrossThrough
this.hideAdvSearchCheatsheetButton = this.SGET_options.hideAdvSearchCheatsheetButton
}
/**
* Hides the advanced search cheatsheet help button in relationship type fields.
*/
hideAdvSearchCheatsheetButton = false
/**
* Determines if the "dead" document type should have a cross-text decoration or not
*/
hideDeadCrossThrough = false
/**
* Determines if text shadow will be shows for accesiblity reasons or not
*/
textShadow = false
documentPreviewClose = ""
/**
* Currently being opened document
*/
exportDocumentsModel = []
/**
* Pre-filtered list based on the category inclussion or exlcussion
*/
existingObjectsFullList = [] as I_ShortenedDocument[]
/**
* All currently loaded blueprints
*/
allDocumentBluePrints = [] as I_Blueprint[]
/**
* Filtered list of items
*/
filteredExistingInput = null as unknown as I_ShortenedDocument[]
/**
* Local list copty for filtering in order to not mess up the original list
*/
listCopy: I_ShortenedDocument[] = []
/**
* Refocuses the first value in the selct upon filtering for intuitive keyboard control
*/
async refocusSelect () {
await this.$nextTick()
/*eslint-disable */
// @ts-ignore
this.$refs.ref_exportDocument.setOptionIndex(-1)
// @ts-ignore
this.$refs.ref_exportDocument.moveOptionSelection(1, true)
/* eslint-enable */
}
/**
* Filter the pre-filtered list
*/
filterExistingSelect (val: string, update: (e: () => void) => void) {
if (val === "") {
update(() => {
this.filteredExistingInput = this.existingObjectsFullList.filter((obj) => !obj.isMinor)
if (this.$refs.ref_existingDocument && this.filteredExistingInput.length > 0) {
this.refocusSelect().catch(e => console.log(e))
}
})
return
}
update(() => {
const needle = val.toLowerCase()
this.listCopy = extend(true, [], this.existingObjectsFullList)
this.filteredExistingInput = advancedDocumentFilter(needle, this.listCopy, this.allDocumentBluePrints, this.existingObjectsFullList)
if (this.$refs.ref_existingDocument && this.filteredExistingInput.length > 0) {
this.refocusSelect().catch(e => console.log(e))
}
})
}
/**
* Set up up all data in to the dialog on popup load
*/
populateExportObjectDialog () {
this.allDocumentBluePrints = this.SGET_allBlueprints
this.existingObjectsFullList = this.SGET_allDocuments.docs
}
async removeInput (scope: {
index: number
removeAtIndex: (index: number) => void
}) {
scope.removeAtIndex(scope.index)
await this.$nextTick()
/*eslint-disable */
// @ts-ignore
this.$refs.ref_exportDocument.hidePopup()
/* eslint-enable */
}
exportOngoing = false
exportList:I_ShortenedDocument[] = []
exportedDocuments = 0
currentDocName = ""
get progressCounter () {
return (this.exportedDocuments / this.exportList.length)
}
exportDocuments () {
remote.dialog.showOpenDialog({
properties: ["openDirectory"]
}).then(async (result) => {
const folderPath = result.filePaths[0]
if (!folderPath) {
return
}
const projectName: string = await retrieveCurrentProjectName()
const exportPath = `${folderPath}/${projectName} - Export`
if (!fs.existsSync(exportPath)) {
fs.mkdirSync(exportPath)
}
this.exportOngoing = true
this.exportedDocuments = 0
this.exportList = (this.exportWholeProject) ? this.SGET_allDocuments.docs : this.exportDocumentsModel
if (!this.includeIsDead) {
this.exportList = this.exportList.filter(doc => {
return doc.extraFields.find(e => e.id === "deadSwitch")?.value !== true
})
}
// @ts-ignore
this.exportList = this.exportList.map(doc => {
return this.SGET_allDocuments.docs.find(subDoc => subDoc._id === doc._id)
})
for (const document of this.exportList) {
this.currentDocName = document.label
// Build the export data object
const exportObject = this.buildExportObject(document)
// MD export
if (this.selectedExportFormat === "Markdown - MD") {
this.exportFile_MD(exportObject, exportPath)
}
await this.sleep(10)
this.exportedDocuments++
}
// Cleanup
this.exportOngoing = false
this.$q.notify({
group: false,
type: "positive",
message: "Export finished"
})
}).catch(err => {
console.log(err)
})
}
buildExportObject (input: I_ShortenedDocument): I_ExportObject {
const matchingBlueprint = this.SGET_blueprint(input.type)
const exportObject = {
name: input.extraFields.find(e => e.id === "name")?.value,
documentType: matchingBlueprint.nameSingular,
documentDirectory: matchingBlueprint.namePlural,
isCategory: input.extraFields.find(e => e.id === "categorySwitch")?.value,
fieldValues: this.buildFieldValues(input, matchingBlueprint)
} as I_ExportObject
if (!this.hideDeadInformation && this.includeIsDead) {
exportObject.isDead = input.extraFields.find(e => e.id === "deadSwitch")?.value
}
else {
exportObject.isDead = false
}
if (this.includeTags) {
exportObject.tags = input.extraFields.find(e => e.id === "tags")?.value
}
if (this.includeHierarchyPath) {
// @ts-ignore
exportObject.hierarchicalPath = this.getDocumentHieararchicalPath(input, this.SGET_allDocuments.docs)
}
return exportObject
}
buildFieldValues (input: I_ShortenedDocument, blueprint: I_Blueprint) {
const catIgnoreList = ["breakDocumentSettings", "name", "documentColor", "documentBackgroundColor", "parentDoc", "order", "categorySwitch", "minorSwitch", "deadSwitch", "finishedSwitch", "tags", "otherNames", "categoryDescription"]
// Filter and map all fields
const mappedFields = blueprint.extraFields
.filter(field => field.type !== "tags")
.filter(field => field.type !== "switch")
.filter(field => field.id !== "name")
.filter(field => field.id !== "order")
.filter(field => field.id !== "deadSwitch")
.filter(field => field.id !== "categorySwitch")
.filter(field => field.id !== "parentDoc")
.filter(field => field.id !== "documentColor")
.filter(field => field.id !== "documentBackgroundColor")
.filter(field => field.id !== "breakDocumentSettings")
.filter(field => !field.isLegacy)
.filter(field => {
if (input.extraFields.find(e => e.id === "categorySwitch")?.value) {
if (catIgnoreList.includes(field.id)) {
return true
}
else {
return false
}
}
else {
return true
}
})
.map(field => {
const matchedField = input.extraFields.find(sub => sub.id === field.id)
let returnValue = matchedField?.value
// Convert numbers to strings
if (field.type === "number" && typeof returnValue === "number") {
returnValue = returnValue.toString()
}
// Build string out of lists
if (field.type === "list" && Array.isArray(returnValue)) {
if (!field.predefinedListExtras) {
returnValue = returnValue.map((e: {value: string}) => `${e.value}`)
}
else {
returnValue = (field.predefinedListExtras?.reverse)
? returnValue.map((e: {value: string, affix: string}) => `${e.affix} (${e.value})`)
: returnValue.map((e: {value: string, affix: string}) => `${e.value}: ${e.affix}`)
}
}
// Build string out of single-relationship
if ((
field.type === "singleToManyRelationship" ||
field.type === "singleToSingleRelationship" ||
field.type === "singleToNoneRelationship"
) && returnValue && returnValue.value
) {
const valueToMap = Array.isArray(returnValue.value) ? returnValue.value[0] : returnValue.value
// @ts-ignore
const matchingDocument = this.SGET_allDocuments.docs.find(doc => doc.id === valueToMap._id)
if (matchingDocument) {
// @ts-ignore
let localReturnValue = matchingDocument.extraFields.find(e => e.id === "name")?.value as string
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const matchedNote = returnValue?.addedValues
if (matchedNote?.value?.length > 0) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
localReturnValue = `${localReturnValue} (${matchedNote.value})`
}
returnValue = localReturnValue
}
else {
returnValue = ""
}
}
// Build string out of multi-relationship
if ((
field.type === "manyToManyRelationship" ||
field.type === "manyToSingleRelationship" ||
field.type === "manyToNoneRelationship"
) && returnValue && returnValue.value
) {
const valuesToMap = returnValue.value as {_id: string, type: string}[]
const mappedValues = valuesToMap
.filter(value => {
return value.type === field?.relationshipSettings?.connectedObjectType
})
.map(value => {
// @ts-ignore
const matchingDocument = this.SGET_allDocuments.docs.find(doc => doc.id === value._id)
if (matchingDocument) {
// @ts-ignore
let localReturnValue = matchingDocument.extraFields.find(e => e.id === "name")?.value as string
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const matchedNote = returnValue?.addedValues?.find((e: {pairedId: string}) => e.pairedId === value._id)
if (matchedNote?.value?.length > 0) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
localReturnValue = `${localReturnValue} (${matchedNote.value})`
}
return localReturnValue
}
return " "
})
.filter(e => e !== " ")
console.log(valuesToMap)
console.log(mappedValues)
returnValue = mappedValues
}
// Fix all missing values that slipped through
if (returnValue === undefined) {
returnValue = ""
}
const returnValueFormat = returnValue as string
const name = field.name
return {
label: name,
value: returnValueFormat,
type: field.type,
id: field.id
}
})
.filter(field => field.value.length > 0 || field.type === "break")
// Map empty breaks
const idsToRemove = []
for (let index = 0; index < mappedFields.length; index++) {
const field = mappedFields[index]
if (field.type === "break" && mappedFields[index + 1]?.type === "break") {
idsToRemove.push(field.id)
}
}
// Remove empty breaks
idsToRemove.forEach(id => {
const indexToRemove = mappedFields.findIndex(field => field.id === id)
if (indexToRemove > -1) {
mappedFields.splice(indexToRemove, 1)
}
})
// Check for last, hanging break field
if (mappedFields[mappedFields.length - 1]?.type === "break") {
mappedFields.splice(mappedFields.length - 1, 1)
}
return mappedFields
}
exportFile_MD (input: I_ExportObject, exportPath: string) {
// Build the proper JSON file for export
const JSONExport: any[] = []
// Name/Title
JSONExport.push({ h1: input.name })
if (input.isCategory) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
JSONExport[0] = `${JSONExport[0]} - Category`
}
// Document type
JSONExport.push({ h2: "Document type" })
JSONExport.push({ ul: [input.documentType] })
// Document type
if (!this.hideDeadInformation) {
JSONExport.push({ h2: "Status" })
JSONExport.push({ ul: [(input.isDead) ? "Dead/Gone/Destroyed" : "Active/Alive"] })
}
// Hierarchy path
if (this.includeHierarchyPath) {
JSONExport.push({ h2: "Hierarchical path" })
JSONExport.push({ ul: [input.hierarchicalPath] })
}
// Tags
if (this.includeTags) {
JSONExport.push({ h2: "Tags" })
JSONExport.push({ ul: (Array.isArray(input.tags) ? input.tags : []) })
}
// Other fields
input.fieldValues.forEach(field => {
if (field.type === "break") {
JSONExport.push({ hr: "" })
JSONExport.push({ h1: field.label })
}
else if (field.type === "wysiwyg") {
JSONExport.push({ h2: field.label })
JSONExport.push({ p: field.value })
}
else {
JSONExport.push({ h2: field.label })
if (Array.isArray(field.value)) {
JSONExport.push({ ul: field.value })
}
else {
JSONExport.push({ ul: [field.value] })
}
}
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
let mdContent: string = json2md(JSONExport)
const reservedCharacterList = [
"/",
">",
"<",
"|",
":",
"&",
"\\",
"-",
"[",
"]",
"{",
"}",
"*",
"?",
"'",
"\"",
"#",
"%",
"$",
"!",
"@"
]
let exportFileDirectory = input.documentDirectory
reservedCharacterList.forEach(char => {
exportFileDirectory = exportFileDirectory.replace(char, "-")
exportFileDirectory = exportFileDirectory.replace(char, "-")
exportFileDirectory = exportFileDirectory.replace(char, "-")
exportFileDirectory = exportFileDirectory.replace(char, "-")
exportFileDirectory = exportFileDirectory.replace(char, "-")
exportFileDirectory = exportFileDirectory.replace(char, "-")
})
const documentDirectory = `${exportPath}/${exportFileDirectory}`
// Create directory
if (!fs.existsSync(documentDirectory)) {
fs.mkdirSync(documentDirectory)
}
// Fix invalid characters in document file name
let exportFileName = input.name
reservedCharacterList.forEach(char => {
exportFileName = exportFileName.replace(char, "-")
exportFileName = exportFileName.replace(char, "-")
exportFileName = exportFileName.replace(char, "-")
exportFileName = exportFileName.replace(char, "-")
exportFileName = exportFileName.replace(char, "-")
exportFileName = exportFileName.replace(char, "-")
})
if (input.isCategory) {
exportFileName = `_${exportFileName}`
}
// Fix double-empty lines
var EOL = mdContent.match(/\r\n/gm) ? "\r\n" : "\n"
var regExp = new RegExp("(" + EOL + "){3,}", "gm")
mdContent = mdContent.replace(regExp, EOL + EOL)
// Write the file
fs.writeFileSync(`${documentDirectory}/${exportFileName}.md`, mdContent)
}
}
</script>
<style lang="scss">
.exportDialog {
width: 1000px;
max-width: calc(100vw - 100px) !important;
margin-top: 100px;
align-self: flex-start;
h6 {
display: block;
}
}
</style>

View file

@ -118,7 +118,12 @@ export default class NewProjectCheck extends DialogBase {
"*",
"?",
"'",
"\""
"\"",
"#",
"%",
"$",
"!",
"@"
]
get isInvalid () {

View file

@ -15,7 +15,7 @@
If you are working with project created (or added via Merge) before version 0.1.7, this process might significantly improve your performance.
<br>
<br>
However, before proceeding, please export your current project first to prevent a <span class="text-bold text-secondary">POSSIBLE CORRUPTION</span> of your current project data!
However, before proceeding, please save your current project first to prevent a <span class="text-bold text-secondary">POSSIBLE CORRUPTION</span> of your current project data!
</div>
</q-card-section>
@ -27,7 +27,7 @@
v-close-popup />
<q-btn
flat
label="Export project"
label="Save project"
color="primary"
@click="commenceSave"
/>
@ -298,12 +298,12 @@ export default class RepairProjectDialog extends DialogBase {
}
/**
* Export the current project
* Save the current project
*/
async commenceSave () {
const projectName = await retrieveCurrentProjectName()
const setup = {
message: "<h4>Exporting current project...</h4>",
message: "<h4>Saving current project...</h4>",
spinnerColor: "primary",
messageColor: "cultured",
spinnerSize: 120,

View file

@ -10,7 +10,7 @@
<span v-html="toolTip"/>
</q-tooltip>
</q-icon>
<q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="16px" class="documentLabelExtra" color="amber-14">
<q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="17px" class="documentLabelExtra" color="amber-14">
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a one-way relationship. <br> Editing this value <span class="text-secondary">WILL NOT</span> have any effect on the connected document/s.
<br>
@ -20,7 +20,7 @@
Middle-clicking the linked document in non-edit mode will open it in new tab and not focus on it.
</q-tooltip>
</q-icon>
<q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="16px" class="documentLabelExtra" color="teal-14">
<q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="17px" class="documentLabelExtra" color="teal-14">
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a two-way relationship. <br> Editing this value <span class="text-secondary">WILL</span> also affect the connected document/s.
<br>

View file

@ -10,7 +10,7 @@
<span v-html="toolTip"/>
</q-tooltip>
</q-icon>
<q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="16px" class="documentLabelExtra" color="amber-14">
<q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="17px" class="documentLabelExtra" color="amber-14">
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a one-way relationship. <br> Editing this value <span class="text-secondary">WILL NOT</span> have any effect on the connected document/s.
<br>
@ -20,7 +20,7 @@
Middle-clicking the linked document in non-edit mode will open it in new tab and not focus on it.
</q-tooltip>
</q-icon>
<q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="16px" class="documentLabelExtra" color="teal-14">
<q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="17px" class="documentLabelExtra" color="teal-14">
<q-tooltip :delay="500" v-if="!disableDocumentToolTips">
This is a two-way relationship. <br> Editing this value <span class="text-secondary">WILL</span> also affect the connected document/s.
<br>

View file

@ -11,7 +11,11 @@ export default class FieldBase extends BaseClass {
/**
* Blueprint data for the specific field
*/
@Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields
@Prop({
default () {
return []
}
}) readonly inputDataBluePrint!: I_ExtraFields
/**
* Determines if the document is curently in edit mode or not

View file

@ -303,7 +303,8 @@ a {
}
.existingDocumentPopup,
.newDocumentPopup {
.newDocumentPopup,
.exportDialog {
.q-checkbox__bg {
border-color: #d4d0c9 !important;
background: transparent !important;

View file

@ -23,6 +23,7 @@
### New features
- **Added export support for Markdown**
- **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**
@ -41,8 +42,10 @@
- Updated the legacy project repair tool to also transfer old stat fields into the new setup
- Added an checker tool for removal of legacy fields for the user
- Added both keybind and a button for mass-saving of documents with edits
- Added export option into the app menu
- Added a keybind: "Save active document and mark it as finished"
- Added a keybind: "Toggle Developer tools"
- Added a keybind: "Export document"
### QoL adjustments

View file

@ -0,0 +1,15 @@
export interface I_ExportObject{
name: string
documentType: string
documentDirectory: string
isCategory: boolean
isDead?: boolean,
hierarchicalPath?: string
tags?: string[]
fieldValues: {
label: string,
type: string
id: string
value?: string | string[]
}[]
}

View file

@ -15,6 +15,13 @@
@trigger-dialog-close="deleteObjectDialogClose"
/>
<!-- Export project dialog -->
<exportProjectDialog
:dialog-trigger="exportProjectDialogTrigger"
:prepicked-ids="[currentData._id]"
@trigger-dialog-close="exportProjectDialogClose"
/>
<div class="row justify-start q-col-gutter-x-xl">
<div
@ -108,6 +115,46 @@
</q-tooltip>
</q-btn>
<q-separator
vertical
inset
:color="(isDarkMode) ? 'accent' : 'black'"
class="q-mr-md"
/>
<q-btn
:color="(hasEdits) ? 'secondary' : 'primary'"
icon="mdi-database-export-outline"
@click="exportProjectAssignUID"
:outline="isDarkMode"
class="q-mr-md"
v-if="!currentData.isNew"
>
<q-tooltip
:delay="500"
anchor="bottom middle"
self="top middle"
>
Export current project
<span class="text-secondary" v-if="hasEdits">
<br>
<br>
Document has active edits.
<br>
These will not be exported.
<br>
Please save first.
</span>
</q-tooltip>
</q-btn>
<q-separator
vertical
inset
:color="(isDarkMode) ? 'accent' : 'black'"
class="q-mr-md"
/>
<q-btn
color="secondary"
icon="mdi-text-box-remove-outline"
@ -126,7 +173,7 @@
</div>
<div class="col-12 q-mt-xl justify-end" v-if="showDocumentID">
<q-input style="width: 100%;" readonly outlined label="Document ID" stack-label @click="copyID" ref="idCopy" v-model="currentData._id">
<q-input style="width: 375px;" readonly outlined label="Document ID" stack-label @click="copyID" ref="idCopy" v-model="currentData._id">
</q-input>
</div>
@ -290,6 +337,7 @@ import { copyDocument } from "src/scripts/documentActions/copyDocument"
import { saveDocument } from "src/scripts/databaseManager/documentManager"
import deleteDocumentCheckDialog from "src/components/dialogs/DeleteDocumentCheck.vue"
import exportProjectDialog from "src/components/dialogs/ExportProject.vue"
import Field_Break from "src/components/fields/Field_Break.vue"
import Field_Text from "src/components/fields/Field_Text.vue"
@ -319,6 +367,7 @@ import Field_Tags from "src/components/fields/Field_Tags.vue"
Field_Wysiwyg,
Field_Tags,
exportProjectDialog,
deleteDocumentCheckDialog
}
})
@ -899,6 +948,19 @@ export default class PageDocumentDisplay extends BaseClass {
this.deleteObjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// Export project dialog
/****************************************************************/
exportProjectDialogTrigger: string | false = false
exportProjectDialogClose () {
this.exportProjectDialogTrigger = false
}
exportProjectAssignUID () {
this.exportProjectDialogTrigger = this.generateUID()
}
/****************************************************************/
// ADD NEW DOCUMENT UNDER PARENT
/****************************************************************/

View file

@ -220,6 +220,17 @@ export const defaultKeybinds = [
tooltip: "Edit active document"
},
// Export document - NONE
{
altKey: false,
ctrlKey: false,
shiftKey: false,
which: false,
editable: true,
id: "exportDocument",
tooltip: "Export current document"
},
// Add a new document with current as parent - CTRL + SHIFT + N
{
altKey: false,

View file

@ -2,7 +2,7 @@ import { I_Blueprint } from "../../../interfaces/I_Blueprint"
export const cultureBlueprint: I_Blueprint = {
_id: "culture",
order: 320,
namePlural: "Cultures/Art",
namePlural: "Cultures/Arts",
nameSingular: "Culture/Art",
icon: "fas fa-archway",
category: "World",

View file

@ -282,7 +282,7 @@ export const locationsBlueprint: I_Blueprint = {
name: "Common Occupations/Classes",
type: "manyToManyRelationship",
icon: "fab fa-pied-piper-hat",
sizing: 4,
sizing: 6,
relationshipSettings: {
connectedObjectType: "professions",
connectedField: "connectedLocations"
@ -293,18 +293,29 @@ export const locationsBlueprint: I_Blueprint = {
name: "Local Resources/Materials",
type: "manyToManyRelationship",
icon: "mdi-gold",
sizing: 4,
sizing: 6,
relationshipSettings: {
connectedObjectType: "resources",
connectedField: "connectedLocations"
}
},
{
id: "neighbourLocations",
name: "Neighbouring Locations",
type: "manyToManyRelationship",
icon: "mdi-map-marker-radius",
sizing: 6,
relationshipSettings: {
connectedObjectType: "locations",
connectedField: "neighbourLocations"
}
},
{
id: "connectedLocations",
name: "Other connected Locations",
type: "manyToManyRelationship",
icon: "mdi-map-marker-radius",
sizing: 4,
sizing: 6,
relationshipSettings: {
connectedObjectType: "locations",
connectedField: "connectedLocations"

View file

@ -3,7 +3,7 @@ export const loreNotesBlueprint: I_Blueprint = {
_id: "loreNotes",
order: 440,
namePlural: "Lore notes/Other notes",
nameSingular: "Lore notes/Other note",
nameSingular: "Lore note/Other note",
icon: "mdi-script-text-outline",
category: "Story/Lore",
extraFields: [

View file

@ -4,7 +4,7 @@ export const racesBlueprint: I_Blueprint = {
_id: "races",
order: 340,
namePlural: "Species/Races/Flora/Fauna",
nameSingular: "Species/Races/Flora/Fauna",
nameSingular: "Species/Race/Flora/Fauna",
icon: "fas fa-dragon",
category: "World",
extraFields: [

View file

@ -106,6 +106,7 @@ export const advancedDocumentFilter = (inputString: string, currentDocumentList:
.filter(field => field.type !== "switch")
.filter(field => field.id !== "name")
.filter(field => field.id !== "parentDoc")
.filter(field => !field.isLegacy)
.map(field => {
const matchedField = doc.extraFields.find(sub => sub.id === field.id)
let returnValue = matchedField?.value