You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
746 lines
58 KiB
746 lines
58 KiB
3 years ago
|
/*
|
||
|
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
|
||
|
if you want to view the source visit the plugin's github repository
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var obsidian = require('obsidian');
|
||
|
|
||
|
/*! *****************************************************************************
|
||
|
Copyright (c) Microsoft Corporation.
|
||
|
|
||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||
|
purpose with or without fee is hereby granted.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||
|
PERFORMANCE OF THIS SOFTWARE.
|
||
|
***************************************************************************** */
|
||
|
|
||
|
function __awaiter(thisArg, _arguments, P, generator) {
|
||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var codemirror = CodeMirror;
|
||
|
|
||
|
function createCommonjsModule(fn) {
|
||
|
var module = { exports: {} };
|
||
|
return fn(module, module.exports), module.exports;
|
||
|
}
|
||
|
|
||
|
createCommonjsModule(function (module, exports) {
|
||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||
|
|
||
|
(function(mod) {
|
||
|
mod(codemirror);
|
||
|
})(function(CodeMirror) {
|
||
|
|
||
|
CodeMirror.defineMode("cook", function() {
|
||
|
return {
|
||
|
token: function(stream, state) {
|
||
|
var sol = stream.sol() || state.afterSection;
|
||
|
var eol = stream.eol();
|
||
|
|
||
|
state.afterSection = false;
|
||
|
|
||
|
if (sol) {
|
||
|
if (state.nextMultiline) {
|
||
|
state.inMultiline = true;
|
||
|
state.nextMultiline = false;
|
||
|
} else {
|
||
|
state.position = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (eol && ! state.nextMultiline) {
|
||
|
state.inMultiline = false;
|
||
|
state.position = null;
|
||
|
}
|
||
|
|
||
|
if (sol) {
|
||
|
while(stream.eatSpace()) {}
|
||
|
}
|
||
|
|
||
|
var ch = stream.next();
|
||
|
|
||
|
|
||
|
if (sol && ch === ">") {
|
||
|
if (stream.eat(">")) {
|
||
|
state.position = "metadata-key";
|
||
|
return "metadata"
|
||
|
}
|
||
|
}
|
||
|
if(state.position === "metadata");
|
||
|
else if(state.position === "metadata-key") {
|
||
|
if(ch === ':') state.position = "metadata";
|
||
|
}
|
||
|
else {
|
||
|
if (ch === "-") {
|
||
|
if (stream.eat("-")) {
|
||
|
stream.skipToEnd();
|
||
|
return "comment";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (stream.match(/\[-.+?-\]/))
|
||
|
return "comment";
|
||
|
|
||
|
if(stream.match(/^@([^@#~]+?(?={))/))
|
||
|
return "ingredient";
|
||
|
else if(stream.match(/^@(.+?\b)/))
|
||
|
return "ingredient";
|
||
|
|
||
|
if(stream.match(/^#([^@#~]+?(?={))/))
|
||
|
return "cookware";
|
||
|
else if(stream.match(/^#(.+?\b)/))
|
||
|
return "cookware";
|
||
|
|
||
|
if(ch === '~'){
|
||
|
state.position = "timer";
|
||
|
return "formatting";
|
||
|
}
|
||
|
if(ch === '{'){
|
||
|
if(state.position != "timer") state.position = "measurement";
|
||
|
return "formatting";
|
||
|
}
|
||
|
if(ch === '}'){
|
||
|
state.position = null;
|
||
|
return "formatting";
|
||
|
}
|
||
|
if(ch === '%' && (state.position === "measurement" || state.position === "timer")){
|
||
|
state.position = "unit";
|
||
|
return "formatting";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return state.position;
|
||
|
},
|
||
|
|
||
|
startState: function() {
|
||
|
return {
|
||
|
formatting : false,
|
||
|
nextMultiline : false, // Is the next line multiline value
|
||
|
inMultiline : false, // Is the current line a multiline value
|
||
|
afterSection : false // Did we just open a section
|
||
|
};
|
||
|
}
|
||
|
|
||
|
};
|
||
|
});
|
||
|
|
||
|
CodeMirror.defineMIME("text/x-cook", "cook");
|
||
|
CodeMirror.defineMIME("text/x-cooklang", "cook");
|
||
|
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// utility class for parsing cooklang files
|
||
|
class CookLang {
|
||
|
static parse(source) {
|
||
|
const recipe = new Recipe();
|
||
|
source.split('\n').forEach(line => {
|
||
|
let match;
|
||
|
// clear comments
|
||
|
line = line.replace(/(--.*)|(\[-.+?-\])/, '');
|
||
|
// skip blank lines
|
||
|
if (line.trim().length === 0)
|
||
|
return;
|
||
|
// metadata lines
|
||
|
else if (match = Metadata.regex.exec(line)) {
|
||
|
recipe.metadata.push(new Metadata(match[0]));
|
||
|
}
|
||
|
// method lines
|
||
|
else {
|
||
|
// ingredients on a line
|
||
|
while (match = Ingredient.regex.exec(line)) {
|
||
|
const ingredient = new Ingredient(match[0]);
|
||
|
recipe.ingredients.push(ingredient);
|
||
|
line = line.replace(match[0], ingredient.methodOutput());
|
||
|
}
|
||
|
// cookware on a line
|
||
|
while (match = Cookware.regex.exec(line)) {
|
||
|
const c = new Cookware(match[0]);
|
||
|
recipe.cookware.push(c);
|
||
|
line = line.replace(match[0], c.methodOutput());
|
||
|
}
|
||
|
// timers on a line
|
||
|
while (match = Timer.regex.exec(line)) {
|
||
|
const t = new Timer(match[0]);
|
||
|
recipe.timers.push(t);
|
||
|
line = line.replace(match[0], t.methodOutput());
|
||
|
}
|
||
|
// add in the method line
|
||
|
recipe.method.push(line.trim());
|
||
|
}
|
||
|
});
|
||
|
return recipe;
|
||
|
}
|
||
|
}
|
||
|
// a class representing a recipe
|
||
|
class Recipe {
|
||
|
constructor() {
|
||
|
this.metadata = [];
|
||
|
this.ingredients = [];
|
||
|
this.cookware = [];
|
||
|
this.timers = [];
|
||
|
this.method = [];
|
||
|
this.methodImages = new Map();
|
||
|
}
|
||
|
calculateTotalTime() {
|
||
|
let time = 0;
|
||
|
this.timers.forEach(timer => {
|
||
|
let amount = 0;
|
||
|
if (parseFloat(timer.amount) + '' == timer.amount)
|
||
|
amount = parseFloat(timer.amount);
|
||
|
else if (timer.amount.contains('/')) {
|
||
|
const split = timer.amount.split('/');
|
||
|
if (split.length == 2) {
|
||
|
const num = parseFloat(split[0]);
|
||
|
const den = parseFloat(split[1]);
|
||
|
if (num && den) {
|
||
|
amount = num / den;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (amount > 0) {
|
||
|
if (timer.unit.toLowerCase().startsWith('s')) {
|
||
|
time += amount;
|
||
|
}
|
||
|
else if (timer.unit.toLowerCase().startsWith('m')) {
|
||
|
time += amount * 60;
|
||
|
}
|
||
|
else if (timer.unit.toLowerCase().startsWith('h')) {
|
||
|
time += amount * 60 * 60;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
return time;
|
||
|
}
|
||
|
}
|
||
|
// a class representing an ingredient
|
||
|
class Ingredient {
|
||
|
constructor(s) {
|
||
|
var _a;
|
||
|
this.originalString = null;
|
||
|
this.name = null;
|
||
|
this.amount = null;
|
||
|
this.unit = null;
|
||
|
this.methodOutput = () => {
|
||
|
let s = `<span class='ingredient'>`;
|
||
|
if (this.amount !== null) {
|
||
|
s += `<span class='amount'>${this.amount} </span>`;
|
||
|
}
|
||
|
if (this.unit !== null) {
|
||
|
s += `<span class='unit'>${this.unit} </span>`;
|
||
|
}
|
||
|
s += `${this.name}</span>`;
|
||
|
return s;
|
||
|
};
|
||
|
this.listOutput = () => {
|
||
|
let s = ``;
|
||
|
if (this.amount !== null) {
|
||
|
s += `<span class='amount'>${this.amount}</span> `;
|
||
|
}
|
||
|
if (this.unit !== null) {
|
||
|
s += `<span class='unit'>${this.unit}</span> `;
|
||
|
}
|
||
|
s += this.name;
|
||
|
return s;
|
||
|
};
|
||
|
this.originalString = s;
|
||
|
const match = Ingredient.regex.exec(s);
|
||
|
this.name = match[1] || match[3];
|
||
|
const attrs = (_a = match[2]) === null || _a === void 0 ? void 0 : _a.split('%');
|
||
|
this.amount = attrs && attrs.length > 0 ? attrs[0] : null;
|
||
|
this.unit = attrs && attrs.length > 1 ? attrs[1] : null;
|
||
|
}
|
||
|
}
|
||
|
// starts with an @, ends at a word boundary or {}
|
||
|
// (also capture what's inside the {})
|
||
|
Ingredient.regex = /@(?:([^@#~]+?)(?:{(.*?)}|{}))|@(.+?\b)/;
|
||
|
// a class representing an item of cookware
|
||
|
class Cookware {
|
||
|
constructor(s) {
|
||
|
this.originalString = null;
|
||
|
this.name = null;
|
||
|
this.methodOutput = () => {
|
||
|
return `<span class='cookware'>${this.name}</span>`;
|
||
|
};
|
||
|
this.listOutput = () => {
|
||
|
return this.name;
|
||
|
};
|
||
|
this.originalString = s;
|
||
|
const match = Cookware.regex.exec(s);
|
||
|
this.name = match[1] || match[2];
|
||
|
}
|
||
|
}
|
||
|
// starts with a #, ends at a word boundary or {}
|
||
|
Cookware.regex = /#(?:([^@#~]+?)(?:{}))|#(.+?\b)/;
|
||
|
// a class representing a timer
|
||
|
class Timer {
|
||
|
constructor(s) {
|
||
|
this.originalString = null;
|
||
|
this.amount = null;
|
||
|
this.unit = null;
|
||
|
this.methodOutput = () => {
|
||
|
return `<span class='time'><span class='time-amount'>${this.amount}</span> <span class='time-unit'>${this.unit}</span></span>`;
|
||
|
};
|
||
|
this.listOutput = () => {
|
||
|
return `<span class='time-amount'>${this.amount}</span> <span class='time-unit'>${this.unit}</span>`;
|
||
|
};
|
||
|
const match = Timer.regex.exec(s);
|
||
|
this.amount = match[1];
|
||
|
this.unit = match[2];
|
||
|
}
|
||
|
}
|
||
|
// contained within ~{}
|
||
|
Timer.regex = /~{([0-9]+)%(.+?)}/;
|
||
|
// a class representing metadata item
|
||
|
class Metadata {
|
||
|
constructor(s) {
|
||
|
this.originalString = null;
|
||
|
this.key = null;
|
||
|
this.value = null;
|
||
|
this.methodOutput = () => {
|
||
|
return `<span class='metadata metadata-key'>${this.key}</span> <span class='metadata metadata-value'>${this.value}</span>`;
|
||
|
};
|
||
|
this.listOutput = () => {
|
||
|
return `<span class='metadata-key'>${this.key}</span> <span class='metadata-value'>${this.value}</span>`;
|
||
|
};
|
||
|
const match = Metadata.regex.exec(s);
|
||
|
this.key = match[1].trim();
|
||
|
this.value = match[2].trim();
|
||
|
}
|
||
|
}
|
||
|
// starts with >>
|
||
|
Metadata.regex = />>\s*(.*?):\s*(.*)/;
|
||
|
|
||
|
// This is the custom view
|
||
|
class CookView extends obsidian.TextFileView {
|
||
|
constructor(leaf, settings) {
|
||
|
super(leaf);
|
||
|
this.settings = settings;
|
||
|
// Add Preview Mode Container
|
||
|
this.previewEl = this.contentEl.createDiv({ cls: 'cook-preview-view', attr: { 'style': 'display: none' } });
|
||
|
// Add Source Mode Container
|
||
|
this.sourceEl = this.contentEl.createDiv({ cls: 'cook-source-view', attr: { 'style': 'display: block' } });
|
||
|
// Add container for CodeMirror editor
|
||
|
this.editorEl = this.sourceEl.createEl('textarea', { cls: 'cook-cm-editor' });
|
||
|
// Create CodeMirror Editor with specific config
|
||
|
this.editor = CodeMirror.fromTextArea(this.editorEl, {
|
||
|
lineNumbers: false,
|
||
|
lineWrapping: true,
|
||
|
scrollbarStyle: null,
|
||
|
keyMap: "default",
|
||
|
theme: "obsidian"
|
||
|
});
|
||
|
}
|
||
|
onload() {
|
||
|
// Save file on change
|
||
|
this.editor.on('change', () => {
|
||
|
this.requestSave();
|
||
|
});
|
||
|
// add the action to switch between source and preview mode
|
||
|
this.changeModeButton = this.addAction('lines-of-text', 'Preview (Ctrl+Click to open in new pane)', (evt) => this.switchMode(evt), 17);
|
||
|
// undocumented: Get the current default view mode to switch to
|
||
|
let defaultViewMode = this.app.vault.getConfig('defaultViewMode');
|
||
|
this.setState(Object.assign(Object.assign({}, this.getState()), { mode: defaultViewMode }), {});
|
||
|
}
|
||
|
getState() {
|
||
|
return super.getState();
|
||
|
}
|
||
|
setState(state, result) {
|
||
|
return super.setState(state, result).then(() => {
|
||
|
if (state.mode)
|
||
|
this.switchMode(state.mode);
|
||
|
});
|
||
|
}
|
||
|
// function to switch between source and preview mode
|
||
|
switchMode(arg) {
|
||
|
let mode = arg;
|
||
|
// if force mode not provided, switch to opposite of current mode
|
||
|
if (!mode || mode instanceof MouseEvent)
|
||
|
mode = this.currentView === 'source' ? 'preview' : 'source';
|
||
|
if (arg instanceof MouseEvent) {
|
||
|
if (obsidian.Keymap.isModEvent(arg)) {
|
||
|
this.app.workspace.duplicateLeaf(this.leaf).then(() => {
|
||
|
var _a;
|
||
|
const cookLeaf = (_a = this.app.workspace.activeLeaf) === null || _a === void 0 ? void 0 : _a.view;
|
||
|
if (cookLeaf) {
|
||
|
cookLeaf.setState(Object.assign(Object.assign({}, cookLeaf.getState()), { mode: mode }), {});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
this.setState(Object.assign(Object.assign({}, this.getState()), { mode: mode }), {});
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// switch to preview mode
|
||
|
if (mode === 'preview') {
|
||
|
this.currentView = 'preview';
|
||
|
obsidian.setIcon(this.changeModeButton, 'pencil');
|
||
|
this.changeModeButton.setAttribute('aria-label', 'Edit (Ctrl+Click to edit in new pane)');
|
||
|
this.renderPreview(this.recipe);
|
||
|
this.previewEl.style.setProperty('display', 'block');
|
||
|
this.sourceEl.style.setProperty('display', 'none');
|
||
|
}
|
||
|
// switch to source mode
|
||
|
else {
|
||
|
this.currentView = 'source';
|
||
|
obsidian.setIcon(this.changeModeButton, 'lines-of-text');
|
||
|
this.changeModeButton.setAttribute('aria-label', 'Preview (Ctrl+Click to open in new pane)');
|
||
|
this.previewEl.style.setProperty('display', 'none');
|
||
|
this.sourceEl.style.setProperty('display', 'block');
|
||
|
this.editor.refresh();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// get the data for save
|
||
|
getViewData() {
|
||
|
this.data = this.editor.getValue();
|
||
|
// may as well parse the recipe while we're here.
|
||
|
this.recipe = CookLang.parse(this.data);
|
||
|
return this.data;
|
||
|
}
|
||
|
// load the data into the view
|
||
|
setViewData(data, clear) {
|
||
|
return __awaiter(this, void 0, void 0, function* () {
|
||
|
this.data = data;
|
||
|
if (clear) {
|
||
|
this.editor.swapDoc(CodeMirror.Doc(data, "text/x-cook"));
|
||
|
this.editor.clearHistory();
|
||
|
}
|
||
|
this.editor.setValue(data);
|
||
|
this.recipe = CookLang.parse(data);
|
||
|
// if we're in preview view, also render that
|
||
|
if (this.currentView === 'preview')
|
||
|
this.renderPreview(this.recipe);
|
||
|
});
|
||
|
}
|
||
|
// clear the editor, etc
|
||
|
clear() {
|
||
|
this.previewEl.empty();
|
||
|
this.editor.setValue('');
|
||
|
this.editor.clearHistory();
|
||
|
this.recipe = new Recipe();
|
||
|
this.data = null;
|
||
|
}
|
||
|
getDisplayText() {
|
||
|
if (this.file)
|
||
|
return this.file.basename;
|
||
|
else
|
||
|
return "Cooklang (no file)";
|
||
|
}
|
||
|
canAcceptExtension(extension) {
|
||
|
return extension == 'cook';
|
||
|
}
|
||
|
getViewType() {
|
||
|
return "cook";
|
||
|
}
|
||
|
// when the view is resized, refresh CodeMirror (thanks Licat!)
|
||
|
onResize() {
|
||
|
this.editor.refresh();
|
||
|
}
|
||
|
// icon for the view
|
||
|
getIcon() {
|
||
|
return "document-cook";
|
||
|
}
|
||
|
// render the preview view
|
||
|
renderPreview(recipe) {
|
||
|
// clear the preview before adding the rest
|
||
|
this.previewEl.empty();
|
||
|
// we can't render what we don't have...
|
||
|
if (!recipe)
|
||
|
return;
|
||
|
if (this.settings.showImages) {
|
||
|
// add any files following the cooklang conventions to the recipe object
|
||
|
// https://cooklang.org/docs/spec/#adding-pictures
|
||
|
const otherFiles = this.file.parent.children.filter(f => (f instanceof obsidian.TFile) && (f.basename == this.file.basename || f.basename.startsWith(this.file.basename + '.')) && f.name != this.file.name);
|
||
|
otherFiles.forEach(f => {
|
||
|
// convention specifies JPEGs and PNGs. Added GIFs as well. Why not?
|
||
|
if (f.extension == "jpg" || f.extension == "jpeg" || f.extension == "png" || f.extension == "gif") {
|
||
|
// main recipe image
|
||
|
if (f.basename == this.file.basename)
|
||
|
recipe.image = f;
|
||
|
else {
|
||
|
const split = f.basename.split('.');
|
||
|
// individual step images
|
||
|
if (split.length == 2 && parseInt(split[1])) {
|
||
|
recipe.methodImages.set(parseInt(split[1]), f);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
// if there is a main image, put it as a banner image at the top
|
||
|
if (recipe.image) {
|
||
|
const img = this.previewEl.createEl('img', { cls: 'main-image' });
|
||
|
img.src = this.app.vault.getResourcePath(recipe.image);
|
||
|
}
|
||
|
}
|
||
|
if (this.settings.showIngredientList) {
|
||
|
// Add the Ingredients header
|
||
|
this.previewEl.createEl('h2', { cls: 'ingredients-header', text: 'Ingredients' });
|
||
|
// Add the ingredients list
|
||
|
const ul = this.previewEl.createEl('ul', { cls: 'ingredients' });
|
||
|
recipe.ingredients.forEach(ingredient => {
|
||
|
const li = ul.createEl('li');
|
||
|
if (ingredient.amount !== null) {
|
||
|
li.createEl('span', { cls: 'amount', text: ingredient.amount });
|
||
|
li.appendText(' ');
|
||
|
}
|
||
|
if (ingredient.unit !== null) {
|
||
|
li.createEl('span', { cls: 'unit', text: ingredient.unit });
|
||
|
li.appendText(' ');
|
||
|
}
|
||
|
li.appendText(ingredient.name);
|
||
|
});
|
||
|
}
|
||
|
if (this.settings.showCookwareList) {
|
||
|
// Add the Cookware header
|
||
|
this.previewEl.createEl('h2', { cls: 'cookware-header', text: 'Cookware' });
|
||
|
// Add the Cookware list
|
||
|
const ul = this.previewEl.createEl('ul', { cls: 'cookware' });
|
||
|
recipe.cookware.forEach(item => {
|
||
|
ul.createEl('li', { text: item.name });
|
||
|
});
|
||
|
}
|
||
|
if (this.settings.showTotalTime) {
|
||
|
let time = recipe.calculateTotalTime();
|
||
|
if (time > 0) {
|
||
|
// Add the Timers header
|
||
|
this.previewEl.createEl('h2', { cls: 'time-header', text: 'Total Time' });
|
||
|
this.previewEl.createEl('p', { cls: 'time', text: this.formatTime(time) });
|
||
|
}
|
||
|
}
|
||
|
// add the method header
|
||
|
this.previewEl.createEl('h2', { cls: 'method-header', text: 'Method' });
|
||
|
// add the method list
|
||
|
const mol = this.previewEl.createEl('ol', { cls: 'method' });
|
||
|
let i = 1;
|
||
|
recipe.method.forEach(line => {
|
||
|
var _a, _b;
|
||
|
const mli = mol.createEl('li');
|
||
|
mli.innerHTML = line;
|
||
|
if (!this.settings.showQuantitiesInline) {
|
||
|
(_a = mli.querySelectorAll('.amount')) === null || _a === void 0 ? void 0 : _a.forEach(el => el.remove());
|
||
|
(_b = mli.querySelectorAll('.unit')) === null || _b === void 0 ? void 0 : _b.forEach(el => el.remove());
|
||
|
}
|
||
|
if (this.settings.showImages && recipe.methodImages.has(i)) {
|
||
|
const img = mli.createEl('img', { cls: 'method-image' });
|
||
|
img.src = this.app.vault.getResourcePath(recipe.methodImages.get(i));
|
||
|
}
|
||
|
i++;
|
||
|
});
|
||
|
}
|
||
|
formatTime(time) {
|
||
|
let minutes = Math.floor(time / 60);
|
||
|
let hours = Math.floor(minutes / 60);
|
||
|
minutes = minutes % 60;
|
||
|
let result = "";
|
||
|
if (hours > 0)
|
||
|
result += hours + " hours ";
|
||
|
if (minutes > 0)
|
||
|
result += minutes + " minutes ";
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CookLangSettings {
|
||
|
constructor() {
|
||
|
this.showImages = true;
|
||
|
this.showIngredientList = true;
|
||
|
this.showCookwareList = true;
|
||
|
this.showTotalTime = true;
|
||
|
this.showQuantitiesInline = false;
|
||
|
}
|
||
|
}
|
||
|
class CookSettingsTab extends obsidian.PluginSettingTab {
|
||
|
constructor(app, plugin) {
|
||
|
super(app, plugin);
|
||
|
this.plugin = plugin;
|
||
|
}
|
||
|
display() {
|
||
|
let { containerEl } = this;
|
||
|
containerEl.empty();
|
||
|
new obsidian.Setting(containerEl)
|
||
|
.setName('Preview Options')
|
||
|
.setHeading();
|
||
|
new obsidian.Setting(containerEl)
|
||
|
.setName('Show images')
|
||
|
.setDesc('Show images in the recipe (see https://cooklang.org/docs/spec/#adding-pictures)')
|
||
|
.addToggle(toggle => toggle
|
||
|
.setValue(this.plugin.settings.showImages)
|
||
|
.onChange((value) => {
|
||
|
this.plugin.settings.showImages = value;
|
||
|
this.plugin.saveData(this.plugin.settings);
|
||
|
this.plugin.reloadCookViews();
|
||
|
}));
|
||
|
new obsidian.Setting(containerEl)
|
||
|
.setName('Show ingredient list')
|
||
|
.setDesc('Show the list of ingredients at the top of the recipe')
|
||
|
.addToggle(toggle => toggle
|
||
|
.setValue(this.plugin.settings.showIngredientList)
|
||
|
.onChange((value) => {
|
||
|
this.plugin.settings.showIngredientList = value;
|
||
|
this.plugin.saveData(this.plugin.settings);
|
||
|
this.plugin.reloadCookViews();
|
||
|
}));
|
||
|
new obsidian.Setting(containerEl)
|
||
|
.setName('Show cookware list')
|
||
|
.setDesc('Show the list of cookware at the top of the recipe')
|
||
|
.addToggle(toggle => toggle
|
||
|
.setValue(this.plugin.settings.showCookwareList)
|
||
|
.onChange((value) => {
|
||
|
this.plugin.settings.showCookwareList = value;
|
||
|
this.plugin.saveData(this.plugin.settings);
|
||
|
this.plugin.reloadCookViews();
|
||
|
}));
|
||
|
new obsidian.Setting(containerEl)
|
||
|
.setName('Show total time')
|
||
|
.setDesc('Show the total of all timers at the top of the recipe')
|
||
|
.addToggle(toggle => toggle
|
||
|
.setValue(this.plugin.settings.showTotalTime)
|
||
|
.onChange((value) => {
|
||
|
this.plugin.settings.showTotalTime = value;
|
||
|
this.plugin.saveData(this.plugin.settings);
|
||
|
this.plugin.reloadCookViews();
|
||
|
}));
|
||
|
new obsidian.Setting(containerEl)
|
||
|
.setName('Show quantities inline')
|
||
|
.setDesc('Show the ingredient quantities inline in the recipe method')
|
||
|
.addToggle(toggle => toggle
|
||
|
.setValue(this.plugin.settings.showQuantitiesInline)
|
||
|
.onChange((value) => {
|
||
|
this.plugin.settings.showQuantitiesInline = value;
|
||
|
this.plugin.saveData(this.plugin.settings);
|
||
|
this.plugin.reloadCookViews();
|
||
|
}));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CookPlugin extends obsidian.Plugin {
|
||
|
constructor() {
|
||
|
super(...arguments);
|
||
|
this.cookFileCreator = () => __awaiter(this, void 0, void 0, function* () {
|
||
|
var _a, _b;
|
||
|
let newFileFolderPath = null;
|
||
|
const newFileLocation = this.app.vault.getConfig('newFileLocation');
|
||
|
if (!newFileLocation || newFileLocation === "root") {
|
||
|
newFileFolderPath = '/';
|
||
|
}
|
||
|
else if (newFileLocation === "current") {
|
||
|
newFileFolderPath = (_b = (_a = this.app.workspace.getActiveFile()) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.path;
|
||
|
}
|
||
|
else {
|
||
|
newFileFolderPath = this.app.vault.getConfig('newFileFolderPath');
|
||
|
}
|
||
|
if (!newFileFolderPath)
|
||
|
newFileFolderPath = '/';
|
||
|
else if (!newFileFolderPath.endsWith('/'))
|
||
|
newFileFolderPath += '/';
|
||
|
const originalPath = newFileFolderPath;
|
||
|
newFileFolderPath = newFileFolderPath + 'Untitled.cook';
|
||
|
let i = 0;
|
||
|
while (this.app.vault.getAbstractFileByPath(newFileFolderPath)) {
|
||
|
newFileFolderPath = `${originalPath}Untitled ${++i}.cook`;
|
||
|
}
|
||
|
const newFile = yield this.app.vault.create(newFileFolderPath, '');
|
||
|
return newFile;
|
||
|
});
|
||
|
// function to create the view
|
||
|
this.cookViewCreator = (leaf) => {
|
||
|
return new CookView(leaf, this.settings);
|
||
|
};
|
||
|
// this function provides the icon for the document
|
||
|
// I added a modification of the CookLang icon with no colours or shadows
|
||
|
this.addDocumentIcon = (extension) => {
|
||
|
obsidian.addIcon(`document-${extension}`, `
|
||
|
<svg viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
|
<path d="M30 48C39.9411 48 48 39.9411 48 30H12C12 39.9411 20.0589 48 30 48Z" fill="currentColor"/>
|
||
|
<circle cx="18" cy="18" r="4" fill="currentColor"/>
|
||
|
<circle cx="42" cy="18" r="4" fill="currentColor"/>
|
||
|
<circle cx="30" cy="16" r="4" fill="currentColor"/>
|
||
|
</svg>
|
||
|
`);
|
||
|
};
|
||
|
}
|
||
|
onload() {
|
||
|
const _super = Object.create(null, {
|
||
|
onload: { get: () => super.onload }
|
||
|
});
|
||
|
return __awaiter(this, void 0, void 0, function* () {
|
||
|
_super.onload.call(this);
|
||
|
this.settings = Object.assign(new CookLangSettings(), yield this.loadData());
|
||
|
// register a custom icon
|
||
|
this.addDocumentIcon("cook");
|
||
|
// register the view and extensions
|
||
|
this.registerView("cook", this.cookViewCreator);
|
||
|
this.registerExtensions(["cook"], "cook");
|
||
|
this.addSettingTab(new CookSettingsTab(this.app, this));
|
||
|
// commands:
|
||
|
// - Create new recipe
|
||
|
// - Create recipe in new pane
|
||
|
// - Convert markdown file to `.cook`
|
||
|
this.addCommand({
|
||
|
id: "create-cook",
|
||
|
name: "Create new recipe",
|
||
|
callback: () => __awaiter(this, void 0, void 0, function* () {
|
||
|
const newFile = yield this.cookFileCreator();
|
||
|
this.app.workspace.getLeaf().openFile(newFile);
|
||
|
})
|
||
|
});
|
||
|
this.addCommand({
|
||
|
id: "create-cook-new-pane",
|
||
|
name: "Create recipe in new pane",
|
||
|
callback: () => __awaiter(this, void 0, void 0, function* () {
|
||
|
const newFile = yield this.cookFileCreator();
|
||
|
yield this.app.workspace.getLeaf(true).openFile(newFile);
|
||
|
})
|
||
|
});
|
||
|
// register the convert to cook command
|
||
|
this.addCommand({
|
||
|
id: "convert-to-cook",
|
||
|
name: "Convert markdown file to `.cook`",
|
||
|
checkCallback: (checking) => {
|
||
|
const file = this.app.workspace.getActiveFile();
|
||
|
const isMd = file.extension === "md";
|
||
|
if (checking) {
|
||
|
return isMd;
|
||
|
}
|
||
|
else if (isMd) {
|
||
|
// replace last instance of .md with .cook
|
||
|
this.app.vault.rename(file, file.path.replace(/\.md$/, ".cook")).then(() => {
|
||
|
this.app.workspace.activeLeaf.openFile(file);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
reloadCookViews() {
|
||
|
this.app.workspace.getLeavesOfType('cook').forEach(leaf => {
|
||
|
if (leaf.view instanceof CookView) {
|
||
|
leaf.view.settings = this.settings;
|
||
|
if (leaf.view.recipe)
|
||
|
leaf.view.renderPreview(leaf.view.recipe);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = CookPlugin;
|
||
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9saWIvY29kZW1pcnJvci5qcyIsInNyYy9tb2RlL2Nvb2svY29vay5qcyIsInNyYy9jb29rbGFuZy50cyIsInNyYy9jb29rVmlldy50cyIsInNyYy9zZXR0aW5ncy50cyIsInNyYy9tYWluLnRzIl0sInNvdXJjZXNDb250ZW50IjpudWxsLCJuYW1lcyI6WyJyZXF1aXJlJCQwIiwiVGV4dEZpbGVWaWV3IiwiS2V5bWFwIiwic2V0SWNvbiIsIlRGaWxlIiwiUGx1Z2luU2V0dGluZ1RhYiIsIlNldHRpbmciLCJQbHVnaW4iLCJhZGRJY29uIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBdURBO0FBQ08sU0FBUyxTQUFTLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDLEVBQUUsU0FBUyxFQUFFO0FBQzdELElBQUksU0FBUyxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsT0FBTyxLQUFLLFlBQVksQ0FBQyxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsQ0FBQyxVQUFVLE9BQU8sRUFBRSxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFO0FBQ2hILElBQUksT0FBTyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLEVBQUUsVUFBVSxPQUFPLEVBQUUsTUFBTSxFQUFFO0FBQy9ELFFBQVEsU0FBUyxTQUFTLENBQUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtBQUNuRyxRQUFRLFNB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
|