diff --git a/main.ts b/main.ts index def758c..21d59c7 100644 --- a/main.ts +++ b/main.ts @@ -1,99 +1,208 @@ -import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; -import { loremIpsum } from "lorem-ipsum"; -import {AnyType} from "@typescript-eslint/type-utils"; +import {App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, Vault} from 'obsidian'; +import {loremIpsum} from "lorem-ipsum"; +import {lineNumbers} from "@codemirror/view"; let wrap = require('word-wrap'); /* Defaults for the plugin: * Number of notes to make: 1000 - * Percentage of notes that will be leaf notes: 25% - * Percentage of notes that will be completely empty (zero bytes): 3% - * Percentage of orphaned nodes: 5% * Min number of links to other notes: 1 * Max number of links to other notes: 10 * Skew towards minimum, make the higher number of links harder to happen + + * From comment on Discord: + You might also want to consider adding other rando text generations, other than lorem ipsum, such as + https://www.npmjs.com/package/txtgen That way, you can have some actual English text to benchmark, + which might be interesting for linguistic-related plugins */ +interface INoteGenerator { + title?: string; // '' + minTitleWords?: number; // 4 + maxTitleWords?: number; // 10 + minSentenceWords?: number; // 5 + maxSentenceWords?: number; // 20 + minSentences?: number; // 4 + maxSentences?: number; // 20 + minParagraphs?: number; // 1 + maxParagraphs?: number; // 10 + minTags?: number; // 0 + maxTags?: number; // 5 + minAliases?: number; // 0 + maxAliases?: number; // 3 + aliasPercent?: number; // 10 + publishPercent?: number; // 50 + frontMatterPercent?: number; // 90 + alltitles?: Array; // [] + emptyFilesPercent?: number; // 3 + orphanedNotesPercent?: number; // 5 + leafNotesPercent?: number; // 25 + minLinks?: number; // 1 + maxLinks?: number; // 10 +} + const tags = Array.from(new Set(loremIpsum({count: 100, units: "words"}) - .split(' ').map((word) => {return `#${word}`}))) + .split(' ').map((word) => { + return `#${word}` + }))) -function randomInt(min:int, max:int, power:int=1) : int { - return Math.floor(Math.pow(Math.random() * (max - min +1), power)) + min; +const cssclasses = Array.from(new Set(loremIpsum({count: 100, units: "words"}) + .split(' '))); + +function randomInt(min: number, max: number, power: number = 1): number { + return Math.floor(Math.pow(Math.random() * (max - min + 1), power)) + min; } -function randomSubset(arr:Array, size:int):Array { - let shuffled = arr.slice(0), i = arr.length, temp, index; - while (i--) { - index = Math.floor((i + 1) * Math.random()); - temp = shuffled[index]; - shuffled[index] = shuffled[i]; - shuffled[i] = temp; - } - return shuffled.slice(0, size); +function randomSubset(arr: Array, size: number): Array { + let shuffled = arr.slice(0), i = arr.length, temp, index; + while (i--) { + index = Math.floor((i + 1) * Math.random()); + temp = shuffled[index]; + shuffled[index] = shuffled[i]; + shuffled[i] = temp; + } + return shuffled.slice(0, size); } -function randomChoice(arr:Array) : any { - return arr[randomInt(0, arr.length-1)]; +function randomChoice(arr: Array): any { + return arr[randomInt(0, arr.length - 1)]; } -function randomDateInRange(start:Date, end:Date) : Date { +function randomDateInRange(start: Date, end: Date): Date { const range = end.getTime() - start.getTime(); const newMs = randomInt(0, range); return new Date(start.getTime() + newMs); } -function newLoremNote(minTitleWords:int=4, maxTitleWords:int=10, minSentenceWords:int= 5, maxSentenceWords:int= 20, - minSentences:int=4, maxSentences:int=20, minParagraphs:int=1, maxParagraphs:int=10, minTags:int=0, maxTags:int=5, - minAliases:int=0, maxAliases:int=3, aliasPercent:int=10, publishPercent:int=50, - frontMatterPercent:int=90): {note:string, title:string} { +function newFrontMatter({publishPercent=50, aliasPercent=10, title='', minTags=0, maxTags=5}: INoteGenerator): string { + const cssclass = cssclasses[randomInt(0, cssclasses.length - 1)]; + const publish = Math.random() >= publishPercent / 100.0 ? "true" : " false"; + // generate aliases here + let alias = ''; + const aliasresult = Math.random(); + if (aliasresult <= aliasPercent / 100.0) { + alias = title.split(' ').map((word) => { + return word[0] + }).join(''); + } + // generate tags here + const numTags = randomInt(minTags, maxTags); + const chosenTags = randomSubset(tags, numTags).join(',') + const status = randomChoice(["Backlog", "In progress", "Done"]); + const published = randomChoice([0, 1]); + const weight = randomInt(1, 100); + const start = new Date(Date.now()); + const end = new Date(start.getTime() + 31536000000); // magic number, milliseconds in a year + const duedate = randomDateInRange(start, end); + + return `---\ntags: ${chosenTags}\ncssclass: ${cssclass}\n` + + `aliases: ${alias}\npublish: ${publish}\n` + + `status: ${status}\npublished: ${published}\n` + + `due: ${duedate.toISOString().substring(0, 10)}\n` + + `weight: ${weight}\n` + + `---\n`; +} + +function newTitle(titleWords: number): string { + return loremIpsum({count: titleWords, units: "words"}).toLowerCase().split(' ').map((word) => { + return word[0].toUpperCase() + word.substring(1)}).join(' '); +} +function newLoremNote({title = '', minTitleWords = 4, maxTitleWords = 10, minSentenceWords = 5, maxSentenceWords = 20, + minSentences = 4, maxSentences = 20, minParagraphs = 1, maxParagraphs = 10, minTags = 0, maxTags = 5, + minAliases = 0, maxAliases = 3, aliasPercent = 10, publishPercent = 50, + frontMatterPercent = 90, alltitles=[], minLinks=1, maxLinks=10}: INoteGenerator): { note: string, title: string } { const titleWords = randomInt(minTitleWords, maxTitleWords); const numParagraphs = randomInt(minParagraphs, maxParagraphs, 4); - const title = loremIpsum({count: titleWords, units: "words"}).toLowerCase().split(' ').map((word) => { return word[0].toUpperCase() + word.substring(1)}).join(' '); - const text = loremIpsum({count: numParagraphs, units: "paragraphs", format: "plain", + if (title.length == 0) { + title = newTitle(titleWords); + } + let text = loremIpsum({ + count: numParagraphs, units: "paragraphs", format: "plain", paragraphLowerBound: minSentences, paragraphUpperBound: maxSentences, sentenceLowerBound: minSentenceWords, sentenceUpperBound: maxSentenceWords, suffix: "\n\n" }); - const wrapped = wrap(text, {width: 75, trim: true, indent: ''}); - let frontmatter = ''; - const frontmatterresult = Math.random(); - if (frontmatterresult <= frontMatterPercent/100.0) { - try { - const cssclass = tags[randomInt(0, tags.length-1)].substring(1); - const publish = Math.random() >= publishPercent / 100.0 ? "true" : " false"; - // generate aliases here - let alias = ''; - const aliasresult = Math.random(); - if (aliasresult <= aliasPercent / 100.0) { - alias = title.split(' ').map((word) => { - return word[0] - }).join(''); - } - // generate tags here - const numTags = randomInt(minTags, maxTags); - const chosenTags = randomSubset(tags, numTags).join(',') - const status = randomChoice(["Backlog", "In progress", "Done"]); - const published = randomChoice([0,1]); - const weight = randomInt(1, 100); - const start = new Date(Date.now()); - const end = new Date(start.getTime() + 31536000000); // magic number, milliseconds in a year - const duedate = randomDateInRange(start, end); - - frontmatter = `---\ntags: ${chosenTags}\ncssclass: ${cssclass}\n` - +`aliases: ${alias}\npublish: ${publish}\n` - +`status: ${status}\npublished: ${published}\n` - +`due: ${duedate.toISOString().substring(0,10)}\n` - +`weight: ${weight}\n` - +`---\n`; - } - catch (err) { - // swallowing this since having errors in making frontmatter is acceptable. Empty frontmatter is valid note - // material, and if errors occur while generating, we're going to be okay with empty. - console.log(err); + /* + * If alltitles.length > 0: + * determine number of links + * get those subset of article titles from alltitles + * split text into paragraphs. + * for each link: + * pick random paragraph and add link to it + * join text back into one string + */ + if (alltitles?.length > 0) { + const numberofLinks = randomInt(minLinks, maxLinks); + const linkedTitles = randomSubset(alltitles, numberofLinks); + let paragraphs = text.split('\n'); + for (let i=0; i=1) && (i%20==0)) { + await notice.setMessage(`Progress: Created ${i}/${orphanedNotesCount} orphaned notes so far`); + } + let note = newLoremNote({title: titles[generated]}); + await vault.create(`${note.title}.md`, note.note); + generated++; + } + + await notice.setMessage("Generating Leaf Notes"); + const linksBegin = generated; + const leafCount = Math.floor(maxNotes*(leafNotesPercent/100.0)); + for (let i=0; i=1) && (i%20==0)) { + await notice.setMessage(`Progress: Created ${i}/${orphanedNotesCount} leaf notes so far`); + } + let note = newLoremNote({title: titles[generated], alltitles: titles.slice(linksBegin)}); + await vault.create(`${note.title}.md`, note.note); + generated++; + } + + for (let i=generated; i < maxNotes; i++) { + if ((i>=1) && (i%20==0)) { + await notice.setMessage(`Progress: Created ${i}/${maxNotes} notes so far`); + } + let note = newLoremNote({minTitleWords:minTitleWords, maxTitleWords:maxTitleWords, title: titles[i]}); + await vault.create(`${note.title}.md`, note.note); + } + await notice.setMessage(`Vault generated ${maxNotes} notes`); } interface TestingVaultPluginSettings { @@ -107,42 +216,46 @@ const DEFAULT_SETTINGS: TestingVaultPluginSettings = { export default class TestingVaultPlugin extends Plugin { settings: TestingVaultPluginSettings; - deleteVaultContents() { - this.app.vault.getFiles().map((fname) => {this.app.vault.delete(fname, true)}); + async deleteVaultContents() { + this.app.vault.getFiles().map(async (fname) => { + await this.app.vault.delete(fname, true) + }); } async onload() { await this.loadSettings(); -/* - // This creates an icon in the left ribbon. - const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => { - // Called when the user clicks the icon. - new Notice('This is a notice!'); - }); - // 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(); + /* + // This creates an icon in the left ribbon. + const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => { + // Called when the user clicks the icon. + new Notice('This is a notice!'); + }); + // Perform additional things with the ribbon + ribbonIconEl.addClass('my-plugin-ribbon-class'); + */ -/* - // This adds a simple command that can be triggered anywhere - this.addCommand({ - id: 'open-sample-modal-simple', - name: 'Open sample modal (simple)', - callback: () => { - new SampleModal(this.app).open(); - let note = newLoremNote({}); - this.app.vault.create(`${note.title}.md`, note.note); - } - }); -*/ + /* + // This adds a simple command that can be triggered anywhere + this.addCommand({ + id: 'open-sample-modal-simple', + name: 'Open sample modal (simple)', + callback: () => { + new SampleModal(this.app).open(); + let note = newLoremNote({}); + this.app.vault.create(`${note.title}.md`, note.note); + } + }); + */ // Create a single randomized note of lorem ipsum. this.addCommand({ id: 'testing-vault-single-note', name: 'Make a single randomized note in your vault', callback: () => { - let note = newLoremNote(); + let note = newLoremNote({}); this.app.vault.create(`${note.title}.md`, note.note); } }); @@ -151,10 +264,10 @@ export default class TestingVaultPlugin extends Plugin { id: 'testing-vault-fill-vault', name: 'Make a group of randomized notes in your vault', callback: () => { - for (let i=0; i<100; i++) { - let note = newLoremNote(); - this.app.vault.create(`${note.title}.md`, note.note); - } + const maxnotes=100; + let task_status = new Notice('Generating new testing vault', 0); + fillVault(maxnotes, task_status, this.app.vault, {}); + setTimeout(() => task_status.hide(), 4000); } }); this.addCommand({ @@ -164,52 +277,52 @@ export default class TestingVaultPlugin extends Plugin { this.deleteVaultContents(); } }) -/* - // This adds an editor command that can perform some operation on the current editor instance - this.addCommand({ - id: 'sample-editor-command', - name: 'Sample editor command', - editorCallback: (editor: Editor, view: MarkdownView) => { - console.log(editor.getSelection()); - editor.replaceSelection('Sample Editor Command'); - } - }); -*/ -/* - // This adds a complex command that can check whether the current state of the app allows execution of the command - this.addCommand({ - id: 'open-sample-modal-complex', - name: 'Open sample modal (complex)', - checkCallback: (checking: boolean) => { - // Conditions to check - const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView); - if (markdownView) { - // If checking is true, we're simply "checking" if the command can be run. - // If checking is false, then we want to actually perform the operation. - if (!checking) { - new SampleModal(this.app).open(); + /* + // This adds an editor command that can perform some operation on the current editor instance + this.addCommand({ + id: 'sample-editor-command', + name: 'Sample editor command', + editorCallback: (editor: Editor, view: MarkdownView) => { + console.log(editor.getSelection()); + editor.replaceSelection('Sample Editor Command'); } + }); + */ + /* + // This adds a complex command that can check whether the current state of the app allows execution of the command + this.addCommand({ + id: 'open-sample-modal-complex', + name: 'Open sample modal (complex)', + checkCallback: (checking: boolean) => { + // Conditions to check + const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView); + if (markdownView) { + // If checking is true, we're simply "checking" if the command can be run. + // If checking is false, then we want to actually perform the operation. + if (!checking) { + new SampleModal(this.app).open(); + } - // This command will only show up in Command Palette when the check function returns true - return true; - } - } - }); -*/ + // This command will only show up in Command Palette when the check function returns true + return true; + } + } + }); + */ // 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); - }); + /* + // 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)); -*/ + // 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)); + */ } onunload() {