Skip to content

Instantly share code, notes, and snippets.

@chhoumann
Last active July 23, 2022 20:31
Show Gist options
  • Save chhoumann/2a90b203a4faaa35971a83f9e7f29d99 to your computer and use it in GitHub Desktop.
Save chhoumann/2a90b203a4faaa35971a83f9e7f29d99 to your computer and use it in GitHub Desktop.
Quick edit for YAML properties and Dataview inline fields
<%*
/*
# MetaEdit
Made by Christian B. B. Houmann
Discord: Chhrriissyy#6548
Twitter: [https://twitter.com/chrisbbh](https://twitter.com/chrisbbh)
Feel free to @ me if you have any questions.
Link to Gist: [Quick edit for YAML properties and Dataview inline fields · GitHub](https://gist.github.com/chhoumann/2a90b203a4faaa35971a83f9e7f29d99)
Use that to check for updates. :)
Also from dev: [NoteTweet: Post tweets directly from Obsidian.](https://github.com/chhoumann/notetweet_obsidian)
## Prerequisites
You will need [Templater: A template plugin for obsidian](https://github.com/SilentVoid13/Templater) for this to work.
## Features
- Update Yaml properties and Dataview fields
- Add new Yaml properties or Dataview fields
- Ignored properties that do not show up in the suggester
- Auto Props that have customizable, pre-defined values selectable through a suggester
- Multi-Value Mode that allows you to detect and vectorize/create arrays from your values
## Instructions
0. _Note the user settings below. You can customize the behavior of this template. _
1. Run the template script (run the template)
2. Choose whether you want to...
1. Add new Yaml property
1. Prompt: Insert a name
2. Prompt: Insert a value
3. Property is automatically appended to the top of your Yaml properties. If there are none, a Yaml header will be made.
2. Add new Dataview field
1. Prompt: Insert a name
2. Prompt: Insert a value
3. Select a line to insert the property _above_.
3. Update an existing property/field (a list will show)
1. Prompt: Change the existing value
## Changelog
- 2021-05-05 Fixed bug where you would be prompted to edit a variable even when you selected another option.
- 2021-05-04 Fixed bug that stopped the user from editing properties. Fixed bug with empty properties in MultiValue Mode.
- 2021-05-02 Bugfix: if there are YAML delimiters but no properties it still adds delimiters
- 2021-05-01 Part 3 - Added Progress Props. Automatically update task count, completed count, and incomplete count. Fixed bug where props and fields with intersecting names would be replaced by one of them. Fixed bug for YAML properties in MultiValue Mode where they 1. wouldn't vectorize and 2. wouldn't get updated.
- 2021-05-01 Part 2 - Smoother UX when adding and editing properties. When creating a new property with an autoprop name, you'll be prompted to select the autoprop value. When editing in multivalue, options have been reordered for convenience.
- 2021-05-01 User settings: Ignored properties, Auto Props, Multi-Value Mode.
- 2021-04-29 Initial release. Bugfix. Add features to add new fields & properties.
*/
/*
## ==USER SETTINGS== */
// ==Ignored Properties==
// Insert properties you wish to ignore in this array. They should be encapsulated by quotes and separated by commas. This setting is case sensitive. Example:
// const ignoredProperties = ["Week", "Year"]
const ignoredProperties = ["Week", "Year"];
// ==Automation==
// Have a defined set of values for specific properties? Here you can set them. You'll be prompted to set the value to one of those when you try to edit a property with that name. This uses the JavaScript object format. Please enter property names as strings, and assign a string array to them. Properties should be separated by commas. Example:
const autoProps = {
"Status": ["To do", "In Progress", "Completed"],
}
// ==Progress Props==
// Working with tasks? Let's automate it.
// Currently, there is support for auto-filling some properties when you click select Update.
// Supported: Total task count, complete task count, and incomplete task count
// Enter the property names you want to update in the arrays below. If you want the amount of total tasks to be put in property "Total", put an entry for "Total" in the array.
const progressProps = {
"totalTasks": ["Total"],
"completedTasks": ["Complete"],
"incompleteTasks": ["Incomplete"]
}
// ==Multi-Value Mode==
// This changes how you edit values. You will be prompted to either update the existing value or add a new value, which would change the property to an array/vector. This is on by default.
// Options: 0 = off. 1 = on. 2 = on for certain properties.
// If you choose 2, please enter which properties multi-value mode should be enabled for. This should be done as an array of strings, where the strings are the property names. Just like 'Ignored Properties'.
const mvMode = 1;
const mvProps = [""];
// ==============
function parseYaml() {
const {position, ...rest} = tp.frontmatter;
return rest;
}
function parseInlineFields() {
let content = tp.file.content;
return content.split("\n").reduce((obj, str) => {
let parts = str.split("::");
if (parts[0] && parts[1]) {
obj[parts[0]] = parts[1].trim();
}
else if (str.includes("::")) {
obj[str.replace("::",'')] = "";
}
return obj;
}, {});
}
async function createNewProperty() {
let propName = await tp.system.prompt("Enter a property name");
if (!propName) return null;
let propValue;
if (autoProps[propName])
propValue = await handleAutoProps(propName);
else
propValue = await tp.system.prompt("Enter a property value");
if (!propValue) return null;
return {propName, propValue};
}
async function addYamlProp() {
let file = this.app.workspace.getActiveFile();
let content = tp.file.content;
let isYamlEmpty = Object.keys(tp.frontmatter).length === 0 && !content.match(/^-{3}\s*\n*\r*-{3}/);
let newProp = await createNewProperty();
if (!newProp) return;
let {propName, propValue} = newProp;
try {
if (mvMode == 1 || (mvMode == 2 && mvProps.includes(propName)))
propValue = `[${propValue}]`;
} catch {}
let newFileContent = content.split("\n");
if (isYamlEmpty) {
newFileContent.unshift("---");
newFileContent.unshift(`${propName}: ${propValue}`);
newFileContent.unshift("---");
}
else {
newFileContent.splice(1, 0, `${propName}: ${propValue}`);
}
newFileContent = newFileContent.join("\n");
await app.vault.modify(file, newFileContent);
}
async function addDataviewField() {
let newProp = await createNewProperty();
if (!newProp) return;
const {propName, propValue} = newProp;
let content = tp.file.content;
let lines = content.split("\n").reduce((obj, line, idx) => {
obj[idx] = !!line ? line : "";
return obj;
}, {});
let appendAfter = await tp.system.suggester(line => lines[line], Object.keys(lines));
if (!appendAfter) return;
let newFileContent = content.split("\n");
newFileContent.splice(appendAfter, 0, `${propName}:: ${propValue}`);
newFileContent = newFileContent.join("\n");
let file = this.app.workspace.getActiveFile();
await app.vault.modify(file, newFileContent);
}
async function editMetaElement(meta, choice) {
try {
if (mvMode > 0)
await multiValueMode(meta, choice);
else
await standardMode(meta, choice);
}
catch {
await standardMode(meta, choice);
console.log("MetaEdit: Error with Multi-Value Mode. Check settings.")
}
}
async function handleAutoProps(choice) {
try {
if (!autoProps[choice]) return false;
const newValue = await tp.system.suggester(autoProps[choice], autoProps[choice]);
if (!!newValue) return newValue;
}
catch{
console.log("MetaEdit: Autoprops failed");
}
return null;
}
async function standardMode(meta, choice) {
let newValue;
if (!!autoProps[choice])
newValue = await handleAutoProps(choice);
else
newValue = await tp.system.prompt("Enter a new value", meta[choice]);
if (newValue) {
await updateFile(choice, newValue);
}
}
async function multiValueMode(meta, choice) {
const choiceIsYaml = !!parseYaml()[choice];
let newValue;
try {
if (mvMode == 2 && !mvProps.includes(choice)) {
await standardMode(meta, choice);
return false;
}
let options, selected, tempValue, splitValues;
const metaString = meta[choice].toString();
if (metaString !== null)
metaString = metaString.toString();
else
metaString = "";
if (choiceIsYaml) {
splitValues = metaString.split().filter(c => !c.includes("[]")).join().split(",");
} else {
splitValues = metaString.split(",").map(prop => prop.trim());
}
if (splitValues.length == 0 || (splitValues.length == 1 && splitValues[0] == "")) {
options = ["Add new value"];
selected = await tp.system.suggester(options, ["cmd:addfirst"]);
}
else if (splitValues.length == 1) {
options = [splitValues[0], "Add to end", "Add to beginning"];
selected = await tp.system.suggester(options, [splitValues[0], "cmd:end", "cmd:beg"]);
} else {
options = ["Add to end", ...splitValues, "Add to beginning"];
selected = await tp.system.suggester(options, ["cmd:end", ...splitValues, "cmd:beg"]);
}
if (!selected) return;
let selectedIndex;
if (autoProps[choice]) {
tempValue = await handleAutoProps(choice);
} else if (selected.includes("cmd")) {
tempValue = await tp.system.prompt("Enter a new value");
} else {
selectedIndex = splitValues.findIndex(el => el == selected);
tempValue = await tp.system.prompt(`Change ${selected} to`, selected);
}
if (!tempValue) return;
switch(selected) {
case "cmd:addfirst":
newValue = `${tempValue}`;
break;
case "cmd:beg":
newValue = `${[tempValue, ...splitValues].join(", ")}`;
break;
case "cmd:end":
newValue = `${[...splitValues, tempValue].join(", ")}`;
break;
default:
if (selectedIndex)
splitValues[selectedIndex] = tempValue;
else
splitValues = [tempValue];
newValue = `${splitValues.join(", ")}`;
break;
}
if (choiceIsYaml)
newValue = `[${newValue}]`;
}
catch(e) {
console.log("MetaEdit: Error with Multi-Value Mode. Check settings.");
return false;
}
if (!!newValue) {
await updateFile(choice, newValue);
return true;
}
return false;
}
async function updateFile(choice, newValue) {
const file = this.app.workspace.getActiveFile();
const choiceIsYaml = !!parseYaml()[choice];
let content = await this.app.vault.cachedRead(file);
let newFileContent = content.split("\n").map(line => {
const regexp = new RegExp(`^\s*${choice}:`);
if (line.match(regexp)) {
if (choiceIsYaml)
line = `${choice}: ${newValue}`;
else
line = `${choice}:: ${newValue}`;
}
return line;
}).join("\n");
await app.vault.modify(file, newFileContent);
}
async function updateMultipleInFile(props) {
const file = this.app.workspace.getActiveFile();
let content = await this.app.vault.cachedRead(file);
let newFileContent = content.split("\n").map(line => {
Object.keys(props).forEach(prop => {
const regexp = new RegExp(`^\s*${prop}:`);
if (line.match(regexp)) {
if (!!parseYaml()[prop])
line = `${prop}: ${props[prop]}`;
else
line = `${prop}:: ${props[prop]}`;
}
})
return line;
}).join("\n");
await app.vault.modify(file, newFileContent);
}
async function startMetaEdit() {
let yaml = parseYaml();
let inlineFields = parseInlineFields();
let meta = {...yaml, ...inlineFields};
meta = handleIgnoreProperties(meta);
let choices = ["Update Progress Properties", "New YAML property", "New Dataview field", ...Object.keys(meta)];
let choice = await tp.system.suggester(choices.map(x => x.trim()), choices);
if (choice == "New YAML property") {
await addYamlProp();
return;
}
if (choice == "New Dataview field") {
await addDataviewField();
return;
}
if (choice == "Update Progress Properties") {
await handleProgressProps(meta);
return;
}
if (choice) {
await editMetaElement(meta, choice);
return;
}
}
function handleIgnoreProperties(allProperties) {
try {
ignoredProperties.forEach(prop => {
if (allProperties[prop])
delete allProperties[prop];
});
}
catch {
console.log("MetaEdit: IgnoreProperties setting not found / is invalid.")
}
return allProperties;
}
async function handleProgressProps(meta) {
if (!progressProps) return;
const file = this.app.workspace.getActiveFile();
const listItems = app.metadataCache.getFileCache(file).listItems;
if (!listItems || listItems.length == 0) return;
const tasks = listItems.filter(i => i.task);
const totalTaskCount = tasks.length;
if (!totalTaskCount) return;
const completedTaskCount = tasks.filter(i => i.task != " ").length;
const incompleteTaskCount = totalTaskCount - completedTaskCount;
const props = {
...await progressPropHelper("totalTasks", meta, totalTaskCount),
...await progressPropHelper("completedTasks", meta, completedTaskCount),
...await progressPropHelper("incompleteTasks", meta, incompleteTaskCount)
}
await updateMultipleInFile(props);
}
const validStringArray = array => array && (array.length >= 1 && array[0] != "");
await startMetaEdit();
%>
@chhoumann
Copy link
Author

Update: Now you can add a new Dataview inline field whereever you please. You can also add new Yaml properties.

Dataview fields will be added above the selected line.
Yaml properties will simply be appended to the top of your property list.

@argenos
Copy link

argenos commented Apr 30, 2021

I would say that this is worth a PR to dataview cc @blacksmithgu

@chhoumann
Copy link
Author

Here's another update. I've tried to make some beneficial features that would help you automate your workflow.
That's why I've added these three things through customizable user settings:

  • Ignored Properties
  • Auto Props
  • Multi-Value Mode (see @GitMurf comment above)

Starting in reverse order...

Multi-Value Mode

This changes how you edit values. You will be prompted to either update the existing value or add a new value, which would change the property to an array/vector.
There are three options: 0 = off. 1 = on. 2 = on for certain properties.
image

Auto Props

Have a defined set of values for specific properties? Here you can set them. You'll be prompted to set the value to one of those when you try to edit a property with that name. This is, of course, also customizable.
LPBglgUtwt

Ignored Properties

These properties will not be shown in the suggester. This is for those properties that you know won't change.
2ScpjYwGEm

@chhoumann
Copy link
Author

And just sneaked in some error handling for Progress Properties :)

@chhoumann
Copy link
Author

Bugfix: if there are YAML delimiters but no properties it still adds delimiters

@chhoumann
Copy link
Author

2021-05-04 Fixed bug that stopped the user from editing properties. Fixed bug with empty properties in MultiValue Mode.

@chhoumann
Copy link
Author

2021-05-05 Fixed bug where you would be prompted to edit a variable even when you selected another option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment