obsidian#FrontMatterCache TypeScript Examples

The following examples show how to use obsidian#FrontMatterCache. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: frontMatterParser.ts    From obsidian-switcher-plus with GNU General Public License v3.0 6 votes vote down vote up
static getAliases(frontMatter: FrontMatterCache): string[] {
    let aliases: string[] = [];

    if (frontMatter) {
      aliases = FrontMatterParser.getValueForKey(frontMatter, /^alias(es)?$/i);
    }

    return aliases;
  }
Example #2
Source File: frontMatterParser.ts    From obsidian-switcher-plus with GNU General Public License v3.0 6 votes vote down vote up
private static getValueForKey(
    frontMatter: FrontMatterCache,
    keyPattern: RegExp,
  ): string[] {
    const retVal: string[] = [];
    const fmKeys = Object.keys(frontMatter);
    const key = fmKeys.find((val) => keyPattern.test(val));

    if (key) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      let value = frontMatter[key];

      if (typeof value === 'string') {
        value = value.split(',');
      }

      if (Array.isArray(value)) {
        value.forEach((val) => {
          if (typeof val === 'string') {
            retVal.push(val.trim());
          }
        });
      }
    }

    return retVal;
  }
Example #3
Source File: watcher.worker.ts    From obsidian-fantasy-calendar with MIT License 6 votes vote down vote up
getDataFromFrontmatter(frontmatter: FrontMatterCache) {
        let name: string, fcCategory: string, eventDisplayName: string;
        if (frontmatter && "fc-ignore" in frontmatter) return {};
        if (frontmatter) {
            name = frontmatter?.["fc-calendar"];
            fcCategory = frontmatter?.["fc-category"];
            eventDisplayName = frontmatter?.["fc-display-name"];
        }
        if (this.addToDefaultIfMissing && (!name || !name.length)) {
            name = this.defaultCalendar;
        }
        name = name?.toLowerCase();
        const calendar = this.calendars.find(
            (calendar) => name == calendar.name.toLowerCase()
        );
        return { calendar, fcCategory, eventDisplayName };
    }
Example #4
Source File: watcher.worker.ts    From obsidian-fantasy-calendar with MIT License 6 votes vote down vote up
getDates(frontmatter: Partial<FrontMatterCache> = {}, basename: string) {
        const dateField = "fc-date" in frontmatter ? "fc-date" : "fc-start";
        let startDate: string | CurrentCalendarData;
        if (frontmatter && dateField in frontmatter) {
            startDate = frontmatter[dateField];
        }
        if (!startDate) {
            startDate = basename;
        }

        const date = this.parseDate(startDate);

        const endDate =
            "fc-end" in frontmatter
                ? (frontmatter["fc-end"] as string | CurrentCalendarData)
                : null;
        const end = this.parseDate(endDate);

        return { date, end };
    }
Example #5
Source File: markdown-file.ts    From obsidian-dataview with MIT License 5 votes vote down vote up
/** Extract tags intelligently from frontmatter. Handles arrays, numbers, and strings. */
export function extractTags(metadata: FrontMatterCache): string[] {
    let tagKeys = Object.keys(metadata).filter(t => t.toLowerCase() == "tags" || t.toLowerCase() == "tag");

    return tagKeys.map(k => splitFrontmatterTagOrAlias(metadata[k])).reduce((p, c) => p.concat(c), []);
}
Example #6
Source File: markdown-file.ts    From obsidian-dataview with MIT License 5 votes vote down vote up
/** Extract tags intelligently from frontmatter. Handles arrays, numbers, and strings.  */
export function extractAliases(metadata: FrontMatterCache): string[] {
    let aliasKeys = Object.keys(metadata).filter(t => t.toLowerCase() == "alias" || t.toLowerCase() == "aliases");

    return aliasKeys.map(k => splitFrontmatterTagOrAlias(metadata[k])).reduce((p, c) => p.concat(c), []);
}
Example #7
Source File: watcher.worker.ts    From obsidian-fantasy-calendar with MIT License 5 votes vote down vote up
parseFrontmatterEvents(
        calendar: Calendar,
        fcCategory: string,
        frontmatter: FrontMatterCache,
        file: { path: string; basename: string },
        eventDisplayName?: string
    ): Event[] {
        const { date, end } = this.getDates(
            frontmatter,
            this.parseTitle ? file.basename : ""
        );
        if (!date) {
            return [];
        }

        if (date?.month && typeof date?.month == "string") {
            let month = calendar.static.months.find(
                (m) => m.name == (date.month as unknown as string)
            );
            if (!month) {
                date.month = null;
            } else {
                date.month = calendar.static.months.indexOf(month);
            }
        } else if (date?.month && typeof date?.month == "number") {
            date.month = wrap(date.month - 1, calendar.static.months.length);
        }

        if (end?.month && typeof end?.month == "string") {
            let month = calendar.static.months.find(
                (m) => m.name == (end.month as unknown as string)
            );
            if (!month) {
                end.month = null;
            } else {
                end.month = calendar.static.months.indexOf(month);
            }
        } else if (end?.month && typeof end?.month == "number") {
            end.month = wrap(end.month - 1, calendar.static.months.length);
        }

        const timestamp = Number(`${date.year}${date.month}${date.day}00`);

        const category = calendar.categories.find(
            (cat) => cat?.name == fcCategory
        );

        return [
            {
                id: nanoid(6),
                name: eventDisplayName ?? file.basename,
                note: file.path,
                date,
                end,
                category: category?.id,
                description: "",
                auto: true
            }
        ];
    }
Example #8
Source File: frontMatterParser.test.ts    From obsidian-switcher-plus with GNU General Public License v3.0 4 votes vote down vote up
describe('FrontMatterParser', () => {
  describe('getAliases', () => {
    it('should return empty array with falsy input', () => {
      const results = FrontMatterParser.getAliases(null);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(0);
    });

    it('should return empty array with missing key', () => {
      const fm: FrontMatterCache = {
        position: null,
      };

      const results = FrontMatterParser.getAliases(fm);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(0);
    });

    it('should parse alias key', () => {
      const fm: FrontMatterCache = {
        alias: 'foo',
        position: null,
      };

      const results = FrontMatterParser.getAliases(fm);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(1);
      expect(results[0]).toBe('foo');
    });

    it('should parse aliases key', () => {
      const fm: FrontMatterCache = {
        aliases: 'foo',
        position: null,
      };

      const results = FrontMatterParser.getAliases(fm);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(1);
      expect(results[0]).toBe('foo');
    });

    it('should parse string values', () => {
      const fm: FrontMatterCache = {
        aliases: 'one, two ,three',
        position: null,
      };

      const results = FrontMatterParser.getAliases(fm);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(3);
      expect(results).toEqual((fm.aliases as string).split(',').map((val) => val.trim()));
    });

    it('should parse array values', () => {
      const fm: FrontMatterCache = {
        aliases: ['one', 'two   ', 'three'],
        position: null,
      };

      const results = FrontMatterParser.getAliases(fm);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(3);
      expect(results).toEqual((fm.aliases as string[]).map((val) => val.trim()));
    });

    it('should ignore non-string/non-array values', () => {
      const fm: FrontMatterCache = {
        aliases: {},
        position: null,
      };

      const results = FrontMatterParser.getAliases(fm);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(0);
    });

    it('should ignore nested non-string values', () => {
      const fm: FrontMatterCache = {
        aliases: ['one', ['two'], 'three'],
        position: null,
      };

      const results = FrontMatterParser.getAliases(fm);

      expect(results).toBeInstanceOf(Array);
      expect(results).toHaveLength(2);
      expect(results).toEqual(
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (fm.aliases as any[]).filter((val) => typeof val === 'string'),
      );
    });
  });
});
Example #9
Source File: main.ts    From obsidian-spaced-repetition with MIT License 4 votes vote down vote up
async sync(): Promise<void> {
        if (this.syncLock) {
            return;
        }
        this.syncLock = true;

        // reset notes stuff
        graph.reset();
        this.easeByPath = {};
        this.incomingLinks = {};
        this.pageranks = {};
        this.dueNotesCount = 0;
        this.dueDatesNotes = {};
        this.reviewDecks = {};

        // reset flashcards stuff
        this.deckTree = new Deck("root", null);
        this.dueDatesFlashcards = {};
        this.cardStats = {
            eases: {},
            intervals: {},
            newCount: 0,
            youngCount: 0,
            matureCount: 0,
        };

        const now = window.moment(Date.now());
        const todayDate: string = now.format("YYYY-MM-DD");
        // clear bury list if we've changed dates
        if (todayDate !== this.data.buryDate) {
            this.data.buryDate = todayDate;
            this.data.buryList = [];
        }

        const notes: TFile[] = this.app.vault.getMarkdownFiles();
        for (const note of notes) {
            if (
                this.data.settings.noteFoldersToIgnore.some((folder) =>
                    note.path.startsWith(folder)
                )
            ) {
                continue;
            }

            if (this.incomingLinks[note.path] === undefined) {
                this.incomingLinks[note.path] = [];
            }

            const links = this.app.metadataCache.resolvedLinks[note.path] || {};
            for (const targetPath in links) {
                if (this.incomingLinks[targetPath] === undefined)
                    this.incomingLinks[targetPath] = [];

                // markdown files only
                if (targetPath.split(".").pop().toLowerCase() === "md") {
                    this.incomingLinks[targetPath].push({
                        sourcePath: note.path,
                        linkCount: links[targetPath],
                    });

                    graph.link(note.path, targetPath, links[targetPath]);
                }
            }

            const deckPath: string[] = this.findDeckPath(note);
            if (deckPath.length !== 0) {
                const flashcardsInNoteAvgEase: number = await this.findFlashcardsInNote(
                    note,
                    deckPath
                );

                if (flashcardsInNoteAvgEase > 0) {
                    this.easeByPath[note.path] = flashcardsInNoteAvgEase;
                }
            }

            const fileCachedData = this.app.metadataCache.getFileCache(note) || {};

            const frontmatter: FrontMatterCache | Record<string, unknown> =
                fileCachedData.frontmatter || {};
            const tags = getAllTags(fileCachedData) || [];

            let shouldIgnore = true;
            const matchedNoteTags = [];

            for (const tagToReview of this.data.settings.tagsToReview) {
                if (tags.some((tag) => tag === tagToReview || tag.startsWith(tagToReview + "/"))) {
                    if (!Object.prototype.hasOwnProperty.call(this.reviewDecks, tagToReview)) {
                        this.reviewDecks[tagToReview] = new ReviewDeck(tagToReview);
                    }
                    matchedNoteTags.push(tagToReview);
                    shouldIgnore = false;
                    break;
                }
            }
            if (shouldIgnore) {
                continue;
            }

            // file has no scheduling information
            if (
                !(
                    Object.prototype.hasOwnProperty.call(frontmatter, "sr-due") &&
                    Object.prototype.hasOwnProperty.call(frontmatter, "sr-interval") &&
                    Object.prototype.hasOwnProperty.call(frontmatter, "sr-ease")
                )
            ) {
                for (const matchedNoteTag of matchedNoteTags) {
                    this.reviewDecks[matchedNoteTag].newNotes.push(note);
                }
                continue;
            }

            const dueUnix: number = window
                .moment(frontmatter["sr-due"], ["YYYY-MM-DD", "DD-MM-YYYY", "ddd MMM DD YYYY"])
                .valueOf();

            for (const matchedNoteTag of matchedNoteTags) {
                this.reviewDecks[matchedNoteTag].scheduledNotes.push({ note, dueUnix });
                if (dueUnix <= now.valueOf()) {
                    this.reviewDecks[matchedNoteTag].dueNotesCount++;
                }
            }

            if (Object.prototype.hasOwnProperty.call(this.easeByPath, note.path)) {
                this.easeByPath[note.path] =
                    (this.easeByPath[note.path] + frontmatter["sr-ease"]) / 2;
            } else {
                this.easeByPath[note.path] = frontmatter["sr-ease"];
            }

            if (dueUnix <= now.valueOf()) {
                this.dueNotesCount++;
            }

            const nDays: number = Math.ceil((dueUnix - now.valueOf()) / (24 * 3600 * 1000));
            if (!Object.prototype.hasOwnProperty.call(this.dueDatesNotes, nDays)) {
                this.dueDatesNotes[nDays] = 0;
            }
            this.dueDatesNotes[nDays]++;
        }

        graph.rank(0.85, 0.000001, (node: string, rank: number) => {
            this.pageranks[node] = rank * 10000;
        });

        // sort the deck names
        this.deckTree.sortSubdecksList();
        if (this.data.settings.showDebugMessages) {
            console.log(`SR: ${t("EASES")}`, this.easeByPath);
            console.log(`SR: ${t("DECKS")}`, this.deckTree);
        }

        for (const deckKey in this.reviewDecks) {
            this.reviewDecks[deckKey].sortNotes(this.pageranks);
        }

        if (this.data.settings.showDebugMessages) {
            console.log(
                "SR: " +
                    t("SYNC_TIME_TAKEN", {
                        t: Date.now() - now.valueOf(),
                    })
            );
        }

        this.statusBar.setText(
            t("STATUS_BAR", {
                dueNotesCount: this.dueNotesCount,
                dueFlashcardsCount: this.deckTree.dueFlashcardsCount,
            })
        );
        this.reviewQueueView.redraw();

        this.syncLock = false;
    }
Example #10
Source File: main.ts    From obsidian-spaced-repetition with MIT License 4 votes vote down vote up
async saveReviewResponse(note: TFile, response: ReviewResponse): Promise<void> {
        const fileCachedData = this.app.metadataCache.getFileCache(note) || {};
        const frontmatter: FrontMatterCache | Record<string, unknown> =
            fileCachedData.frontmatter || {};

        const tags = getAllTags(fileCachedData) || [];
        if (this.data.settings.noteFoldersToIgnore.some((folder) => note.path.startsWith(folder))) {
            new Notice(t("NOTE_IN_IGNORED_FOLDER"));
            return;
        }

        let shouldIgnore = true;
        for (const tag of tags) {
            if (
                this.data.settings.tagsToReview.some(
                    (tagToReview) => tag === tagToReview || tag.startsWith(tagToReview + "/")
                )
            ) {
                shouldIgnore = false;
                break;
            }
        }

        if (shouldIgnore) {
            new Notice(t("PLEASE_TAG_NOTE"));
            return;
        }

        let fileText: string = await this.app.vault.read(note);
        let ease: number, interval: number, delayBeforeReview: number;
        const now: number = Date.now();
        // new note
        if (
            !(
                Object.prototype.hasOwnProperty.call(frontmatter, "sr-due") &&
                Object.prototype.hasOwnProperty.call(frontmatter, "sr-interval") &&
                Object.prototype.hasOwnProperty.call(frontmatter, "sr-ease")
            )
        ) {
            let linkTotal = 0,
                linkPGTotal = 0,
                totalLinkCount = 0;

            for (const statObj of this.incomingLinks[note.path] || []) {
                const ease: number = this.easeByPath[statObj.sourcePath];
                if (ease) {
                    linkTotal += statObj.linkCount * this.pageranks[statObj.sourcePath] * ease;
                    linkPGTotal += this.pageranks[statObj.sourcePath] * statObj.linkCount;
                    totalLinkCount += statObj.linkCount;
                }
            }

            const outgoingLinks = this.app.metadataCache.resolvedLinks[note.path] || {};
            for (const linkedFilePath in outgoingLinks) {
                const ease: number = this.easeByPath[linkedFilePath];
                if (ease) {
                    linkTotal +=
                        outgoingLinks[linkedFilePath] * this.pageranks[linkedFilePath] * ease;
                    linkPGTotal += this.pageranks[linkedFilePath] * outgoingLinks[linkedFilePath];
                    totalLinkCount += outgoingLinks[linkedFilePath];
                }
            }

            const linkContribution: number =
                this.data.settings.maxLinkFactor *
                Math.min(1.0, Math.log(totalLinkCount + 0.5) / Math.log(64));
            ease =
                (1.0 - linkContribution) * this.data.settings.baseEase +
                (totalLinkCount > 0
                    ? (linkContribution * linkTotal) / linkPGTotal
                    : linkContribution * this.data.settings.baseEase);
            // add note's average flashcard ease if available
            if (Object.prototype.hasOwnProperty.call(this.easeByPath, note.path)) {
                ease = (ease + this.easeByPath[note.path]) / 2;
            }
            ease = Math.round(ease);
            interval = 1.0;
            delayBeforeReview = 0;
        } else {
            interval = frontmatter["sr-interval"];
            ease = frontmatter["sr-ease"];
            delayBeforeReview =
                now -
                window
                    .moment(frontmatter["sr-due"], ["YYYY-MM-DD", "DD-MM-YYYY", "ddd MMM DD YYYY"])
                    .valueOf();
        }

        const schedObj: Record<string, number> = schedule(
            response,
            interval,
            ease,
            delayBeforeReview,
            this.data.settings,
            this.dueDatesNotes
        );
        interval = schedObj.interval;
        ease = schedObj.ease;

        const due = window.moment(now + interval * 24 * 3600 * 1000);
        const dueString: string = due.format("YYYY-MM-DD");

        // check if scheduling info exists
        if (SCHEDULING_INFO_REGEX.test(fileText)) {
            const schedulingInfo = SCHEDULING_INFO_REGEX.exec(fileText);
            fileText = fileText.replace(
                SCHEDULING_INFO_REGEX,
                `---\n${schedulingInfo[1]}sr-due: ${dueString}\n` +
                    `sr-interval: ${interval}\nsr-ease: ${ease}\n` +
                    `${schedulingInfo[5]}---`
            );
        } else if (YAML_FRONT_MATTER_REGEX.test(fileText)) {
            // new note with existing YAML front matter
            const existingYaml = YAML_FRONT_MATTER_REGEX.exec(fileText);
            fileText = fileText.replace(
                YAML_FRONT_MATTER_REGEX,
                `---\n${existingYaml[1]}sr-due: ${dueString}\n` +
                    `sr-interval: ${interval}\nsr-ease: ${ease}\n---`
            );
        } else {
            fileText =
                `---\nsr-due: ${dueString}\nsr-interval: ${interval}\n` +
                `sr-ease: ${ease}\n---\n\n${fileText}`;
        }

        if (this.data.settings.burySiblingCards) {
            await this.findFlashcardsInNote(note, [], true); // bury all cards in current note
            await this.savePluginData();
        }
        await this.app.vault.modify(note, fileText);

        new Notice(t("RESPONSE_RECEIVED"));

        await this.sync();
        if (this.data.settings.autoNextNote) {
            this.reviewNextNote(this.lastSelectedReviewDeck);
        }
    }
Example #11
Source File: main.ts    From obsidian-initiative-tracker with GNU General Public License v3.0 4 votes vote down vote up
async onload() {
        registerIcons();

        await this.loadSettings();

        this.addSettingTab(new InitiativeTrackerSettings(this));

        this.registerView(
            INTIATIVE_TRACKER_VIEW,
            (leaf: WorkspaceLeaf) => new TrackerView(leaf, this)
        );
        this.registerView(
            CREATURE_TRACKER_VIEW,
            (leaf: WorkspaceLeaf) => new CreatureView(leaf, this)
        );

        this.addCommands();

        this.registerMarkdownCodeBlockProcessor("encounter", (src, el, ctx) => {
            const handler = new EncounterBlock(this, src, el);
            ctx.addChild(handler);
        });
        this.registerMarkdownCodeBlockProcessor(
            "encounter-table",
            (src, el, ctx) => {
                const handler = new EncounterBlock(this, src, el, true);
                ctx.addChild(handler);
            }
        );

        this.registerMarkdownPostProcessor(async (el, ctx) => {
            if (!el || !el.firstElementChild) return;

            const codeEls = el.querySelectorAll<HTMLElement>("code");
            if (!codeEls || !codeEls.length) return;

            const codes = Array.from(codeEls).filter((code) =>
                /^encounter:\s/.test(code.innerText)
            );
            if (!codes.length) return;

            for (const code of codes) {
                const creatures = code.innerText
                    .replace(`encounter:`, "")
                    .trim()
                    .split(",")
                    .map((s) => parseYaml(s.trim()));
                const parser = new EncounterParser(this);
                const parsed = await parser.parse({ creatures });

                if (!parsed || !parsed.creatures || !parsed.creatures.size)
                    continue;

                const target = createSpan("initiative-tracker-encounter-line");
                new EncounterLine({
                    target,
                    props: {
                        ...parsed,
                        plugin: this
                    }
                });

                code.replaceWith(target);
            }
        });

        this.playerCreatures = new Map(
            this.data.players.map((p) => [p.name, Creature.from(p)])
        );
        this.homebrewCreatures = new Map(
            this.bestiary.map((p) => [p.name, Creature.from(p)])
        );

        this.app.workspace.onLayoutReady(async () => {
            this.addTrackerView();
            //Update players from < 7.2
            for (const player of this.data.players) {
                if (player.path) continue;
                if (!player.note) continue;
                const file = await this.app.metadataCache.getFirstLinkpathDest(
                    player.note,
                    ""
                );
                if (
                    !file ||
                    !this.app.metadataCache.getFileCache(file)?.frontmatter
                ) {
                    new Notice(
                        `Initiative Tracker: There was an issue with the linked note for ${player.name}.\n\nPlease re-link it in settings.`
                    );
                    continue;
                }
            }
            this.registerEvent(
                this.app.metadataCache.on("changed", (file) => {
                    if (!(file instanceof TFile)) return;
                    const players = this.data.players.filter(
                        (p) => p.path == file.path
                    );
                    if (!players.length) return;
                    const frontmatter: FrontMatterCache =
                        this.app.metadataCache.getFileCache(file)?.frontmatter;
                    if (!frontmatter) return;
                    for (let player of players) {
                        const { ac, hp, modifier, level } = frontmatter;
                        player.ac = ac;
                        player.hp = hp;
                        player.modifier = modifier;
                        player.level = level;

                        this.playerCreatures.set(
                            player.name,
                            Creature.from(player)
                        );
                        if (this.view) {
                            const creature = this.view.ordered.find(
                                (c) => c.name == player.name
                            );
                            if (creature) {
                                this.view.updateCreature(creature, {
                                    max: player.hp,
                                    ac: player.ac
                                });
                            }
                        }
                    }
                })
            );
            this.registerEvent(
                this.app.vault.on("rename", (file, old) => {
                    if (!(file instanceof TFile)) return;
                    const players = this.data.players.filter(
                        (p) => p.path == old
                    );
                    if (!players.length) return;
                    for (const player of players) {
                        player.path = file.path;
                        player.note = file.basename;
                    }
                })
            );
            this.registerEvent(
                this.app.vault.on("delete", (file) => {
                    if (!(file instanceof TFile)) return;
                    const players = this.data.players.filter(
                        (p) => p.path == file.path
                    );
                    if (!players.length) return;
                    for (const player of players) {
                        player.path = null;
                        player.note = null;
                    }
                })
            );
        });

        console.log("Initiative Tracker v" + this.manifest.version + " loaded");
    }