diff --git a/main.ts b/main.ts index 60c74b9..e3afb53 100644 --- a/main.ts +++ b/main.ts @@ -1,5 +1,5 @@ import * as Handlebars from 'handlebars'; -import { App, Vault, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TAbstractFile } from 'obsidian'; +import { App, Vault, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TAbstractFile, TFile } from 'obsidian'; // Remember to rename these classes and interfaces! @@ -12,7 +12,7 @@ interface P2HLOSettings { const DEFAULT_SETTINGS: P2HLOSettings = { userToken: '', campaignToken: '', - notesFolder: '/' + notesFolder: '/', } const toolName = "p2hlo-bridge" @@ -38,39 +38,59 @@ interface convertReturn { export default class P2HLO extends Plugin { settings: P2HLOSettings; + notes: { // mapping of a note by full filepath including file extension to its aliases + [filePath: string]: string[], + } - private byPrefixCode(obj: object, prefix: string) { - return obj[Object.keys(obj).find( predicate => predicate.startsWith(prefix))] + private byPrefixCode(obj: object, prefix: string) { + return obj[Object.keys(obj).find( predicate => predicate.startsWith(prefix))] + } + + private filterKeysByPrefixCode(obj: object, prefix: string) { + const keys = [...Object.keys(obj).filter( predicate => predicate.startsWith(prefix))] + return keys.map (key => { + return { + ...obj[key] + } + }) + } + + private prefixNumber(n: number) { + if (n < 0) { + return `-${n}` + } else if (n > 0) { + return `+${n}` + } else { + return "+0" } - - private filterKeysByPrefixCode(obj: object, prefix: string) { - const keys = [...Object.keys(obj).filter( predicate => predicate.startsWith(prefix))] - return keys.map (key => { - return { - ...obj[key] - } - }) - } - - - private prefixNumber(n: number) { - if (n < 0) { - return `-${n}` - } else if (n > 0) { - return `+${n}` - } else { - return "+0" + } + + private hloTextToMarkdown(str: string) { + return str.replace(/\{b\}/gi, "**").replace(/\{\/b\}/gi, "**").replace(/\{br\}\{br\}/gi, "{br}").replace(/\{br\}/gi, "\\n") + } + + private makeLink(linkName: string, category: string = ""): string|undefined { + const sanitizedLinkName = linkName.replace("'", "'").toLowerCase().replace(/\ /g, "-") + console.log("finding link to ", sanitizedLinkName) + let found = undefined + const reName = new RegExp(`^${sanitizedLinkName}(?:-.{1,4})?\.md$`, "gi") + console.log("try to match", reName, category) + let fileName = "" + for (fileName in this.notes) { + if ((this.notes[fileName]??"").contains(linkName)) { + break; } } - - private hloTextToMarkdown(str: string) { - return str.replace(/\{b\}/gi, "**").replace(/\{\/b\}/gi, "**").replace(/\{br\}\{br\}/gi, "{br}").replace(/\{br\}/gi, "\\n") + if (fileName !== "") { + return fileName.split(".md")[0] + } else { + return undefined } + } + - - - convert(json: Object): convertReturn|null { - + private convert(json: Object): convertReturn|null { + const actor = this.byPrefixCode(json, "actor"); const name = actor["name"]; @@ -131,13 +151,13 @@ size: {{size}} level: {{classes}} trait_03: "Player" trait_04: "{{ancestry}}" -languages: "{{#languages}}{{this}}, {{/languages}}; " +languages: "{{#languages}} {{this}}, {{/languages}}; " perception: - name: "Perception" -- desc: "Perception {{perception}}" + desc: "Perception {{perception}}" skills: - name: "Skills" -- desc: "{{#skills}}__{{name}}__: {{mod}} (1d20{{mod}}); {{/skills}}" + desc: "{{#skills}}__{{name}}__: {{mod}} (1d20{{mod}}); {{/skills}}" abilitiyMods: [{{str}}, {{con}}, {{dex}}, {{int}}, {{cha}}, {{wis}}] abilities_top: @@ -155,6 +175,7 @@ abilities_bot: armorclass: - name: AC desc: "{{ac}}; __Fort__: {{fortitude}} (1d20{{fortitude}}); __Ref__: {{reflex}} (1d20{{reflex}}); __Will__: {{will}} (1d20{{will}})" + speed: "{{speeds}}" attacks: @@ -175,8 +196,8 @@ attacks: {{#items}} - {{name}} {{/items}} - ` - + ` + const template = Handlebars.compile(src) const data = { @@ -221,7 +242,7 @@ attacks: desc = predicate.name.split("(")[1].split(")")[0] } - link = `compendium/feats/${name.trim().replace(/\ /g, "-")}` + link = this.makeLink(name.trim(), "feats") ?? "" if (predicate.actions) { actionpoints = predicate.actions @@ -255,7 +276,7 @@ attacks: desc = predicate.name.split("(")[1].split(")")[0] } - link = `rules/actions/${name.trim().replace(/\ /g, "-")}` + link = this.makeLink(name.trim(), "actions") ?? "" if (predicate.actions) { actionpoints = predicate.actions @@ -283,9 +304,7 @@ attacks: }), items: [...alchemicalItems, ...gear].map(predicate => { let itemName = predicate.name - console.log(predicate.items) if (predicate.items) { - console.log(predicate.items, Object.values(predicate.items)) itemName += "(" itemName += Object.values(predicate.items).map(predicate2 => "*" + predicate2.name + "*").join(", ") itemName += ")" @@ -311,8 +330,8 @@ attacks: if (predicate.useInPlay) { desc = this.hloTextToMarkdown(predicate.useInPlay) } - - let link = `compendium/spells/${predicate.name.trim()}` + console.log(name.trim()) + let link = this.makeLink(predicate.name.trim(), "spells") ?? "" link = link.split(" (")[0] link = link.replace(/\ /g, "-") @@ -420,21 +439,21 @@ attacks: } async performTask() { + await this.precacheNotes() + console.log(this.notes) if (await this.preflightTest()) { const accessToken = await this.fetchAccessToken() const castMember = await this.fetchStage(accessToken) - console.log(castMember) if (castMember.length == 0) { new Notice("No Castmember found on Stage. Did you forgot to start a session?") return } const characters = await this.fetchCharacters(accessToken, castMember) - console.log(characters) try { const folder = this.app.vault.getFolderByPath(this.settings.notesFolder) - await this.app.vault.trash(folder as TAbstractFile, true) + await this.app.fileManager.trashFile(folder as TAbstractFile) } catch (e) { - console.log(e) + console.error(e) } try { await this.app.vault.createFolder(this.settings.notesFolder) @@ -448,12 +467,17 @@ attacks: try { await this.app.vault.create(`${this.settings.notesFolder}/${result.name}.md`, result.output) } catch (e) { - const file = this.app.vault.getFileByPath(`/${this.settings.notesFolder}/${result.name}.md`) + const directoryWithoutRoot = this.settings.notesFolder.substring(1) + const file = this.app.vault.getFileByPath(`${directoryWithoutRoot}/${result.name}.md`) + try { if (file) { await this.app.vault.modify(file, result.output) } else { console.error(file) } + } catch (e) { + console.error(e) + } } } } @@ -461,21 +485,42 @@ attacks: } } + + async getAliasesOfTFile(fileObject: TFile): Promise { + + return new Promise( (resolve, reject) => { + this.app.fileManager.processFrontMatter( + fileObject, + (frontMatter) => { + resolve(frontMatter.aliases) + } + ) + }) + + } + + private async precacheNotes() { + + this.notes = {} + + this.app.vault + .getMarkdownFiles() + .forEach( + async (fileObject: TFile) => { + this.notes[fileObject.path] = await this.getAliasesOfTFile(fileObject) + } + ) + } + async onload() { - await this.loadSettings(); + await this.loadSettings() // This creates an icon in the left ribbon. - const ribbonIconEl = this.addRibbonIcon('dice', 'Sync Stage', async (evt: MouseEvent) => { + const ribbonIconEl = this.addRibbonIcon('dot-network', 'Sync Stage', async (evt: MouseEvent) => { // Called when the user clicks the icon. await this.performTask() - }); - // Perform additional things with the ribbon - ribbonIconEl.addClass('my-plugin-ribbon-class'); - - // This adds a status bar item to the bottom of the app. Does not work on mobile apps. - const statusBarItemEl = this.addStatusBarItem(); - statusBarItemEl.setText('Status Bar Text'); + }) // This adds a simple command that can be triggered anywhere this.addCommand({ @@ -484,19 +529,9 @@ attacks: callback: () => { this.preflightTest() } - }); + }) - // This adds a settings tab so the user can configure various aspects of the plugin - this.addSettingTab(new SampleSettingTab(this.app, this)); - - // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin) - // Using this function will automatically remove the event listener when this plugin is disabled. - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - console.log('click', evt); - }); - - // When registering intervals, this function will automatically clear the interval when the plugin is disabled. - this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); + this.addSettingTab(new P2HLOSettingTab(this.app, this)) } onunload() { @@ -512,7 +547,7 @@ attacks: } } -class SampleSettingTab extends PluginSettingTab { +class P2HLOSettingTab extends PluginSettingTab { plugin: P2HLO; constructor(app: App, plugin: P2HLO) {