Skip to content

Latest commit

 

History

History
395 lines (335 loc) · 10.8 KB

notevars.sfile.md

File metadata and controls

395 lines (335 loc) · 10.8 KB
obsidianUIMode
preview

This shortcut-file includes shortcuts to get and set note-variables. Note-variables are variables set in the YAML frontmatter. They can be used by many plugins, including DataView and TTRPG StatBlocks.

This shortcut file can be used by other shortcut-files to let them read and manipulate data in notes for many uses, including TTRPG character sheets.

This shortcut-file has a tutorial video available:

Uses state.sfile shortcut-file (optional). It uses this to save & load the isMarkdownRefreshed flag.

__

^sfile setup$

__

const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;

// Initialize state (just a flag)
confirmObjectPath(
	"_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed", true);

// Event callback - state.onReset
confirmObjectPath(
	"_inlineScripts.state.listeners.onReset.notevars",
	function()
	{
		expand("notevars reset noconfirm");
	});

__ Sets up this shortcut-file

__

^sfile shutdown$

__

// Event callback removal
delete _inlineScripts.state?.listeners?.onReset?.notevars;

// State removal
delete _inlineScripts.state?.sessionState?.notevars;

__ Shuts down this shortcut-file

__

^notevars reset$

__

// Confirm
if (!popups.confirm("Confirm resetting the <b>Notevars</b> system")) { return null; }

// Reset
expand("notevars reset noconfirm");

return expFormat("Notevars cleared.");

__ notevars reset - Clears the isMarkdownRefreshed flag.


__

^notevars reset noconfirm$

__

const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;

// Reset the state
confirmObjectPath("_inlineScripts.state.sessionState.notevars");
_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed = true;

__ hidden - No-confirm reset

__

^notevars isMarkdownRefreshed ?(|[y|n])$

__

// If parameter is specified, update the flag based on it
if ($1)
{
	_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed = ($1 === "y");
}

// Return a notification announcing the flag's current state
return expFormat(
	"notevars isMarkdownRefreshed is " +
	(_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed ?
	"__enabled__" : "__disabled__"));

__ notevars isMarkdownRefreshed {state: y OR n, default: ""} - If {state} is given, assigns it to the notevars "isMarkdownRefreshed" flag. Otherwise, displays the current "isMarkdownRefreshed" flag. - If isMarkdownRefreshed flag is set then a note's markdown is refreshed each time one of it's variables is set.


__

^notevars get ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*&#@!].*"|[^ \t\['`\|\{\}">*&#@!][^ \t]*)$

__

// Remove any quotes around parameters
$1 = $1.replace(/^"(.*)"$/, "$1")
$2 = $2.replace(/^"(.*)"$/, "$1")

// If notename is ".", change it to the current file
if ($1 === ".") { $1 = app.workspace.getActiveFile()?.path; }

// Get the file object for the specified note.  Early if not available or is a folder
const file = app.vault.fileMap[$1] || app.vault.fileMap[$1 + ".md"];
if (!file)
{
	return expFormat([ "", "No value.  Note __" + $1 + "__ not found." ]);
}
if (file.children)
{
	return expFormat(
		[ "", "No value.  __" + $1 + "__ is a folder." ]);
}

// Get the file's cached data.  Early out if not available.
const cache = app.metadataCache.getFileCache(file);
if (!cache)
{
	return expFormat(
		[ "", "No value.  Note __" + $1 + "__ is not properly cached by Obsidian." ]);
}

// Get the front-matter object
// If no front-matter, early out (no message, since it technically worked, but the
// variable is empty)
const fm = cache.frontmatter;
if (!fm)
{
	return null;
}

// Get the variable valuable
const result = fm[$2] || fm[$2 + ":"] || null;

// Return the result.  If it's an array, it's joined to a single string.
return Array.isArray(result) ? result.join(",") : result;

__ notevars get {note name: path text} {variable name: yaml name text} - Expands to the value of variable {variable name} in note {note name}. If {note name} is "." then it represents the current note.

__

^notevars getArray ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*&#@!].*"|[^ \t\['`\|\{\}">*&#@!][^ \t]*) ([0-9]+)$

__

// Remove any quotes around parameters
$1 = $1.replace(/^"(.*)"$/, "$1")
$2 = $2.replace(/^"(.*)"$/, "$1")

// If notename is ".", change it to the current file
if ($1 === ".") { $1 = app.workspace.getActiveFile()?.path; }

// Get the file object for the specified note.  Early out if unavailable, or folder
const file = app.vault.fileMap[$1] || app.vault.fileMap[$1 + ".md"];
if (!file)
{
	return expFormat([ "", "No value.  Note __" + $1 + "__ not found." ]);
}
if (file.children)
{
	return expFormat(
		[ "", "No value.  __" + $1 + "__ is a folder." ]);
}

// Get the file's cached data.  Early out if unavailable
const cache = app.metadataCache.getFileCache(file);
if (!cache)
{
	return expFormat(
		[ "", "No value.  Note __" + $1 + "__ is not properly cached by " +
		"Obsidian." ]);
}

// Get the front-matter object
// If no front-matter, early out (no message, since it technically worked, but the
// variable is empty)
const fm = cache.frontmatter;
if (!fm)
{
	return null;
}

// Get the variable valuable
const result = fm[$2] || fm[$2 + ":"] || null;

// Return the result, indexed by $3 (as an array)
return Array.isArray(result) ? result[$3] : ($3 === "0") ? result : null;

__ notevars getArray {note name: path text} {array name: yaml name text} {index: >=0} - Expands to the value of item {index} of array {array name} in note {note name}. If {note name} is "." then it represents the current note.

__

^notevars set ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*&#@!].*"|[^ \t\['`\|\{\}">*&#@!][^ \t]*) (.*)$

__

// Remove any quotes around parameters
$1 = $1.replace(/^"(.*)"$/, "$1")
$2 = $2.replace(/^"(.*)"$/, "$1")

// If notename is ".", change it to the current file
if ($1 === ".") { $1 = app.workspace.getActiveFile()?.path || ""; }

// Get the file object for the specified note.  Early out if unavailable or is folder
const file = app.vault.fileMap[$1] || app.vault.fileMap[$1 + ".md"];
if (!file)
{
	return expFormat(
		[ "", "Variable __" + $2 + "__ not set.  Note __" + $1 + "__ not found." ]);
}
if (file.children)
{
	return expFormat(
		[ "", "Variable __" + $2 + "__ not set.  __" + $1 + "__ is a folder." ]);
}

// Get the file's content.  Early out if unavailable.
const content = await app.vault.cachedRead(file);
if (content === null || content === undefined)
{
	return expFormat(
		[ "", "Variable __" + $2 + "__ not set. Note __" + $1 + "__ read failed." ]);
}

// Adjust value to resolve escape characters for newline and tab
$3 = $3.trim().replaceAll("\\n", "\n").replaceAll("\\t", "\t");

// Get the file content's frontmatter
const fmMatch = content.match(/^(\n*---)(\n[\S\s]*\n)(\n*---\n)/);

// Is the new value multiline?
const isValueMultiline = $3.includes("\n") || $3[0] === "-";

// Determine what to add and where
let toAdd = { text: "", start: 0, end: 0 };

// If there isn't yet a frontmatter, create it along with the variable name/value pair
if (!fmMatch)
{
	// Define what to add
	toAdd.text = "---\n" + $2 + ": " + $3 + "\n---\n\n";
}

// If there IS a frontmatter, add the variable name/value pair to it
else
{
	// Find the variable in the frontmatter
	const varMatch =
		fmMatch[2].match("\n( *)(" + $2 + " *:)(\n|(?: |\t)[^\n]*\n)");

	// If the variable is NOT found append it to the frontmatter
	if (!varMatch)
	{
		toAdd.text = $2 + (
			isValueMultiline ?
			":\n  " + $3.replaceAll("\n", "\n  ") + "\n" :
			": " + $3 + "\n"
		);
		toAdd.start = fmMatch[1].length + fmMatch[2].length;
		toAdd.end = toAdd.start;
	}

	// If the variable IS found, modify it
	else
	{
		// Define value-entry
		const lineIndent = "\n" + varMatch[1] + "  ";
		toAdd.text =
			isValueMultiline ?
			lineIndent + $3.replaceAll("\n", lineIndent) :
			" " + $3;
		// Define start of definition
		toAdd.start =
			fmMatch[1].length + varMatch.index + varMatch[1].length +
			varMatch[2].length + 1;
		// Define end of definition
		let lineStart = toAdd.start;
		let lineEnd = content.indexOf("\n", toAdd.start + 1);
		if (varMatch[3].trim())
		{
			// Old definition is single-line
			toAdd.end = lineEnd;
		}
		else
		{
			// Old definition is multi-line
			let lineStartRegex = new RegExp("^" + varMatch[1] + "(?:- | )");
			while (lineEnd !== -1)
			{
				var line = content.slice(lineStart + 1, lineEnd);
				if (!line.match(lineStartRegex))
				{
					toAdd.end = lineStart;
					break;
				}
				lineStart = lineEnd;
				lineEnd = content.indexOf("\n", lineStart + 1);
			}
		}
	}
}

// Force the reading views to update (dataview isn't currently working for this)
if (_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed)
{
	const leaves = _inlineScripts.inlineScripts.HelperFncs.getLeavesForFile(file);
	if (leaves.length)
	{
		const onChanged = (changedFile) =>
		{
			// If the changed file is the one we're monitoring
			if (changedFile === file)
			{
				// Stop monitoring for file changes
				app.metadataCache.off("changed", onChanged);

				// Force all views of the file to re-render on the next frame
				setTimeout(() =>
				{
					for (const leaf of leaves)
					{
						leaf.view.modes.preview.rerender(true);
					}
				}, 100);
			}
		}
		app.metadataCache.on("changed", onChanged);
	}
}

// Modify the variable in the frontmatter of the note
await _inlineScripts.inlineScripts.HelperFncs.addToNote(toAdd.text, toAdd, file.path);

return expFormat(
	"Note __" + $1 + "__, variable __" + $2 + "__ set to __" + $3 + "__.");

__ notevars set {note name: path text} {variable name: yaml name text} {value: text} - Sets the value of variable {variable name} to {value} in note {note name}. If {note name} is "." then it represents the current note.

__

^notevars setArray ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*&#@!].*"|[^ \t\['`\|\{\}">*&#@!][^ \t]*) (.*)$

__

// Remove any quotes around parameters
$1 = $1.replace(/^"(.*)"$/, "$1")
$2 = $2.replace(/^"(.*)"$/, "$1")

// Get the input array
const valueArray = $3.split(",").map(v => v.trim());

// Call "notevars set" with the input array
const result =
	expand("notevars set " + $1 + " " + $2 + " " + "- " + valueArray.join("\\n- "));

// If error with "notevars set", return that error
if (!result[0]) { return result; }

return expFormat(
	"Note __" + $1 + "__, variable __" + $2 + "__ set to array __\\[ " +
	valueArray.join(", ") + " \\]__.");

__ notevars setArray {note name: path text} {array name: yaml name text} {values: text (comma separated)} - Sets the values of array {array name} to {value1}, {value2}, etc. in note {note name}. If {note name} is "." then it represents the current note.