Merge remote-tracking branch 'origin/main'
This commit is contained in:
+1
-1
@@ -2,5 +2,5 @@
|
||||
/.obsidian/workspace-mobile.json
|
||||
/.obsidian/plugins/recent-files-obsidian/data.json
|
||||
/.obsidian/plugins/novel-word-count/data.json
|
||||
/out/
|
||||
/.out/
|
||||
/ref/
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
"interfaceFontFamily": "",
|
||||
"cssTheme": "Typewriter",
|
||||
"theme": "moonstone",
|
||||
"baseFontSize": 16,
|
||||
"baseFontSize": 15,
|
||||
"enabledCssSnippets": [
|
||||
"tables"
|
||||
]
|
||||
|
||||
Vendored
-1
@@ -16,7 +16,6 @@
|
||||
"lilypond",
|
||||
"novel-word-count",
|
||||
"easy-copy",
|
||||
"anchor-display-text",
|
||||
"calendar",
|
||||
"periodic-notes",
|
||||
"spellcheck-toggler",
|
||||
|
||||
Vendored
+13
-5
@@ -1,14 +1,22 @@
|
||||
{
|
||||
"collapse-filter": true,
|
||||
"search": "path:/*.md",
|
||||
"collapse-filter": false,
|
||||
"search": "tag:#type/periodic ",
|
||||
"showTags": false,
|
||||
"showAttachments": false,
|
||||
"hideUnresolved": false,
|
||||
"showOrphans": true,
|
||||
"collapse-color-groups": false,
|
||||
"colorGroups": [],
|
||||
"colorGroups": [
|
||||
{
|
||||
"query": "tag:#occupational ",
|
||||
"color": {
|
||||
"a": 1,
|
||||
"rgb": 14048348
|
||||
}
|
||||
}
|
||||
],
|
||||
"collapse-display": false,
|
||||
"showArrow": true,
|
||||
"showArrow": false,
|
||||
"textFadeMultiplier": 0,
|
||||
"nodeSizeMultiplier": 1,
|
||||
"lineSizeMultiplier": 1,
|
||||
@@ -17,6 +25,6 @@
|
||||
"repelStrength": 10,
|
||||
"linkStrength": 1,
|
||||
"linkDistance": 250,
|
||||
"scale": 0.18454855383603447,
|
||||
"scale": 0.18454855383603405,
|
||||
"close": false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"includeNoteName": "headersOnly",
|
||||
"titleProperty": "title",
|
||||
"whichHeadings": "allHeaders",
|
||||
"includeNotice": false,
|
||||
"sep": " ",
|
||||
"suggest": true,
|
||||
"ignoreEmbedded": true
|
||||
}
|
||||
-306
@@ -1,306 +0,0 @@
|
||||
/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// main.ts
|
||||
var main_exports = {};
|
||||
__export(main_exports, {
|
||||
default: () => AnchorDisplayText
|
||||
});
|
||||
module.exports = __toCommonJS(main_exports);
|
||||
var import_obsidian = require("obsidian");
|
||||
var RE_ANCHOR_NO_DISPLAY = /!?\[\[([^\]]+#[^|\n\r\]]+)\]\]$/;
|
||||
var RE_ANCHOR_DISPLAY = /(\[\[([^\]]+#[^\n\r\]]+)\]\])$/;
|
||||
var RE_DISPLAY = /\|([^\]]+)/;
|
||||
var DEFAULT_SETTINGS = {
|
||||
includeNoteName: "headersOnly",
|
||||
titleProperty: "",
|
||||
whichHeadings: "allHeaders",
|
||||
includeNotice: false,
|
||||
sep: " ",
|
||||
suggest: true,
|
||||
ignoreEmbedded: true
|
||||
};
|
||||
var AnchorDisplayText = class extends import_obsidian.Plugin {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.suggestionsRegistered = false;
|
||||
}
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.addSettingTab(new AnchorDisplayTextSettingTab(this.app, this));
|
||||
if (this.settings.suggest) {
|
||||
this.registerEditorSuggest(new AnchorDisplaySuggest(this));
|
||||
this.suggestionsRegistered = true;
|
||||
}
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("editor-change", (editor) => {
|
||||
var _a;
|
||||
const cursor = editor.getCursor();
|
||||
const currentLine = editor.getLine(cursor.line);
|
||||
const lastChars = currentLine.slice(cursor.ch - 2, cursor.ch);
|
||||
if (lastChars !== "]]")
|
||||
return;
|
||||
const match = currentLine.slice(0, cursor.ch).match(RE_ANCHOR_NO_DISPLAY);
|
||||
if (match) {
|
||||
if (this.settings.ignoreEmbedded && match[0].charAt(0) === "!")
|
||||
return;
|
||||
const headings = match[1].split("#");
|
||||
let notename = headings[0];
|
||||
if (this.settings.titleProperty) {
|
||||
notename = this.getTitleFromFile(notename);
|
||||
}
|
||||
let displayText = "";
|
||||
if (this.settings.whichHeadings === "lastHeader") {
|
||||
displayText = headings[headings.length - 1];
|
||||
} else {
|
||||
displayText = headings[1];
|
||||
if (this.settings.whichHeadings === "allHeaders") {
|
||||
for (let i = 2; i < headings.length; i++) {
|
||||
displayText += this.settings.sep + headings[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
const startIndex = ((_a = match.index) != null ? _a : 0) + match[0].length - 2;
|
||||
if (this.settings.includeNoteName === "noteNameFirst") {
|
||||
displayText = `${notename}${this.settings.sep}${displayText}`;
|
||||
} else if (this.settings.includeNoteName === "noteNameLast") {
|
||||
displayText = `${displayText}${this.settings.sep}${notename}`;
|
||||
}
|
||||
if (displayText.startsWith("^")) {
|
||||
displayText = displayText.slice(1);
|
||||
}
|
||||
editor.replaceRange(`|${displayText}`, { line: cursor.line, ch: startIndex }, void 0, "headerDisplayText");
|
||||
if (this.settings.includeNotice) {
|
||||
new import_obsidian.Notice(`Updated anchor link display text.`);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
onunload() {
|
||||
}
|
||||
/**
|
||||
* Get title property value from file's frontmatter
|
||||
* @param filename - The filename to look up
|
||||
* @returns The title property value if found, otherwise returns the original filename
|
||||
*/
|
||||
getTitleFromFile(filename) {
|
||||
if (this.settings.titleProperty) {
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(filename, "");
|
||||
if (file) {
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
if (cache && cache.frontmatter && cache.frontmatter[this.settings.titleProperty]) {
|
||||
return String(cache.frontmatter[this.settings.titleProperty]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
};
|
||||
var AnchorDisplaySuggest = class extends import_obsidian.EditorSuggest {
|
||||
constructor(plugin) {
|
||||
super(plugin.app);
|
||||
this.suggestionSelected = null;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
onTrigger(cursor, editor) {
|
||||
if (!this.plugin.settings.suggest)
|
||||
return null;
|
||||
if (this.suggestionSelected) {
|
||||
if (this.suggestionSelected.ch === cursor.ch && this.suggestionSelected.line === cursor.line)
|
||||
return null;
|
||||
this.suggestionSelected = null;
|
||||
return null;
|
||||
}
|
||||
const currentLine = editor.getLine(cursor.line);
|
||||
const lastChars = currentLine.slice(cursor.ch - 2, cursor.ch);
|
||||
if (lastChars !== "]]")
|
||||
return null;
|
||||
const slice = currentLine.slice(0, cursor.ch);
|
||||
const match = slice.match(RE_ANCHOR_DISPLAY);
|
||||
if (!match)
|
||||
return null;
|
||||
if (this.plugin.settings.ignoreEmbedded && slice.charAt(match.index - 1) === "!")
|
||||
return null;
|
||||
return {
|
||||
start: {
|
||||
line: cursor.line,
|
||||
ch: match.index + match[1].length - 2
|
||||
// 2 less to keep closing brackets
|
||||
},
|
||||
end: {
|
||||
line: cursor.line,
|
||||
ch: match.index + match[1].length - 2
|
||||
},
|
||||
query: match[2]
|
||||
};
|
||||
}
|
||||
getSuggestions(context) {
|
||||
const headings = context.query.split("|")[0].split("#");
|
||||
let notename = headings[0];
|
||||
if (this.plugin.settings.titleProperty) {
|
||||
notename = this.plugin.getTitleFromFile(notename);
|
||||
}
|
||||
let displayText = headings[1];
|
||||
if (displayText.startsWith("^")) {
|
||||
displayText = displayText.slice(1);
|
||||
}
|
||||
for (let i = 2; i < headings.length; i++) {
|
||||
displayText += this.plugin.settings.sep + headings[i];
|
||||
}
|
||||
const suggestion1 = {
|
||||
displayText,
|
||||
source: "Don't include note name"
|
||||
};
|
||||
const suggestion2 = {
|
||||
displayText: `${notename}${this.plugin.settings.sep}${displayText}`,
|
||||
source: "Note name and than heading(s)"
|
||||
};
|
||||
const suggestion3 = {
|
||||
displayText: `${displayText}${this.plugin.settings.sep}${notename}`,
|
||||
source: "Heading(s) and than note name"
|
||||
};
|
||||
return [suggestion1, suggestion2, suggestion3];
|
||||
}
|
||||
renderSuggestion(value, el) {
|
||||
const suggestionEl = el.parentElement;
|
||||
const suggestionContainerEl = suggestionEl.parentElement;
|
||||
if (suggestionContainerEl.childElementCount < 2) {
|
||||
const promptInstructionsEl = suggestionContainerEl.createDiv({ cls: "prompt-instructions" });
|
||||
const instructionEl = promptInstructionsEl.createDiv({ cls: "prompt-instruction" });
|
||||
instructionEl.createEl("span", { cls: "prompt-instruction-command", text: "\u21B5" });
|
||||
instructionEl.createEl("span", { text: "to accept" });
|
||||
}
|
||||
el.setAttribute("class", "suggestion-item mod-complex");
|
||||
const suggestionContentEl = el.createDiv({ cls: "suggestion-content" });
|
||||
suggestionContentEl.createDiv({ cls: "suggestion-title", text: value.displayText });
|
||||
suggestionContentEl.createDiv({ cls: "suggestion-note", text: value.source });
|
||||
}
|
||||
selectSuggestion(value, evt) {
|
||||
const editor = this.context.editor;
|
||||
const match = this.context.query.match(RE_DISPLAY);
|
||||
if (match) {
|
||||
this.context.start.ch = this.context.start.ch - match[0].length;
|
||||
}
|
||||
editor.replaceRange(`|${value.displayText}`, this.context.start, this.context.end, "headerDisplayText");
|
||||
this.suggestionSelected = this.context.end;
|
||||
}
|
||||
};
|
||||
var AnchorDisplayTextSettingTab = class extends import_obsidian.PluginSettingTab {
|
||||
constructor(app, plugin) {
|
||||
super(app, plugin);
|
||||
this.sepWarning = null;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
validateSep(value) {
|
||||
let validValue = value;
|
||||
for (const c of value) {
|
||||
if ("[]#^|".includes(c)) {
|
||||
validValue = validValue.replace(c, "");
|
||||
}
|
||||
}
|
||||
if (validValue != value) {
|
||||
if (!this.sepWarning) {
|
||||
this.sepWarning = new import_obsidian.Notice(`Separators cannot contain any of the following characters: []#^|`, 0);
|
||||
}
|
||||
} else {
|
||||
if (this.sepWarning) {
|
||||
this.sepWarning.hide();
|
||||
this.sepWarning = null;
|
||||
}
|
||||
}
|
||||
return validValue;
|
||||
}
|
||||
display() {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
new import_obsidian.Setting(containerEl).setName("Include note name").setDesc("Include the title of the note in the display text.").addDropdown((dropdown) => {
|
||||
dropdown.addOption("headersOnly", "Don't include note name");
|
||||
dropdown.addOption("noteNameFirst", "Note name and then heading(s)");
|
||||
dropdown.addOption("noteNameLast", "Heading(s) and then note name");
|
||||
dropdown.setValue(this.plugin.settings.includeNoteName);
|
||||
dropdown.onChange((value) => {
|
||||
this.plugin.settings.includeNoteName = value;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
new import_obsidian.Setting(containerEl).setName("Title property").setDesc("If set, use the value of this property as the note name. (Leave blank to use file name)").addText((text) => {
|
||||
text.setValue(this.plugin.settings.titleProperty);
|
||||
text.onChange((value) => {
|
||||
this.plugin.settings.titleProperty = value;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
new import_obsidian.Setting(containerEl).setName("Include subheadings").setDesc("Change which headings and subheadings are in the display text.").addDropdown((dropdown) => {
|
||||
dropdown.addOption("allHeaders", "All linked headings");
|
||||
dropdown.addOption("lastHeader", "Last heading only");
|
||||
dropdown.addOption("firstHeader", "First heading only");
|
||||
dropdown.setValue(this.plugin.settings.whichHeadings);
|
||||
dropdown.onChange((value) => {
|
||||
this.plugin.settings.whichHeadings = value;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
new import_obsidian.Setting(containerEl).setName("Separator").setDesc("Choose what to insert between headings instead of #.").addText((text) => {
|
||||
text.setValue(this.plugin.settings.sep);
|
||||
text.onChange((value) => {
|
||||
this.plugin.settings.sep = this.validateSep(value);
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
new import_obsidian.Setting(containerEl).setName("Enable notifications").setDesc("Have a notice pop up whenever an anchor link is automatically changed.").addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.includeNotice);
|
||||
toggle.onChange((value) => {
|
||||
this.plugin.settings.includeNotice = value;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
new import_obsidian.Setting(containerEl).setName("Suggest alternatives").setDesc("Have a suggestion window to present alternative display text options when the cursor is directly after an anchor link.").addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.suggest);
|
||||
toggle.onChange((value) => {
|
||||
this.plugin.settings.suggest = value;
|
||||
this.plugin.saveSettings();
|
||||
if (!this.plugin.suggestionsRegistered) {
|
||||
this.plugin.registerEditorSuggest(new AnchorDisplaySuggest(this.plugin));
|
||||
this.plugin.suggestionsRegistered = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
new import_obsidian.Setting(containerEl).setName("Ignore embedded files").setDesc("Don't add or change display text for embedded files.").addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.ignoreEmbedded);
|
||||
toggle.onChange((value) => {
|
||||
this.plugin.settings.ignoreEmbedded = value;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* nosourcemap */
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "anchor-display-text",
|
||||
"name": "Anchor Link Display Text",
|
||||
"version": "1.4.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Automatically uses the linked heading as the display text for anchor links.",
|
||||
"author": "Robert C Arsenault",
|
||||
"authorUrl": "https://github.com/rca-umb",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
+622
-45
@@ -10732,6 +10732,48 @@ var import_obsidian2 = require("obsidian");
|
||||
|
||||
// src/repositoryConnection/RepositoryConnection.ts
|
||||
var import_js_logger = __toESM(require_logger());
|
||||
|
||||
// src/forestry/LimitReachedError.ts
|
||||
var LimitReachedError = class extends Error {
|
||||
constructor(errorType, responseData) {
|
||||
var _a2;
|
||||
const message = (_a2 = responseData.message) != null ? _a2 : "Usage limit reached";
|
||||
super(message);
|
||||
this.name = "LimitReachedError";
|
||||
this.errorType = errorType;
|
||||
this.buildsUsed = responseData.builds_used;
|
||||
this.monthlyLimit = responseData.monthly_limit;
|
||||
this.starterCreditsRemaining = responseData.starter_credits_remaining;
|
||||
}
|
||||
};
|
||||
function throwIfLimitError(error) {
|
||||
if (error && typeof error === "object" && "status" in error && error.status === 403) {
|
||||
const responseData = extractResponseData(error);
|
||||
if (!responseData) return;
|
||||
const errorType = responseData.error;
|
||||
if (errorType === "build_limit_reached" || errorType === "storage_limit_exceeded") {
|
||||
throw new LimitReachedError(errorType, responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
function extractResponseData(error) {
|
||||
const err = error;
|
||||
if (err.response && typeof err.response === "object") {
|
||||
const response = err.response;
|
||||
if (response.data && typeof response.data === "object") {
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
if (err.response && typeof err.response === "object") {
|
||||
const resp = err.response;
|
||||
if (resp.data && typeof resp.data === "object") {
|
||||
return resp.data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// src/repositoryConnection/RepositoryConnection.ts
|
||||
var logger = import_js_logger.default.get("repository-connection");
|
||||
var IMAGE_PATH_BASE = "src/site/";
|
||||
var NOTE_PATH_BASE = "src/site/notes/";
|
||||
@@ -10823,6 +10865,7 @@ var RepositoryConnection = class {
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
throwIfLimitError(error);
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
@@ -10875,6 +10918,7 @@ var RepositoryConnection = class {
|
||||
payload
|
||||
);
|
||||
} catch (error) {
|
||||
throwIfLimitError(error);
|
||||
logger.error(error);
|
||||
}
|
||||
});
|
||||
@@ -10973,6 +11017,7 @@ var RepositoryConnection = class {
|
||||
sha: blob.data.sha
|
||||
};
|
||||
} catch (error) {
|
||||
throwIfLimitError(error);
|
||||
logger.error(error);
|
||||
}
|
||||
}));
|
||||
@@ -11002,6 +11047,7 @@ var RepositoryConnection = class {
|
||||
sha: blob.data.sha
|
||||
};
|
||||
} catch (error) {
|
||||
throwIfLimitError(error);
|
||||
logger.error(error);
|
||||
}
|
||||
}));
|
||||
@@ -12902,7 +12948,8 @@ var CanvasCompiler = class {
|
||||
node,
|
||||
baseStyle,
|
||||
colorClass,
|
||||
file
|
||||
file,
|
||||
assets
|
||||
);
|
||||
case "file":
|
||||
return yield this.buildFileNode(
|
||||
@@ -12927,7 +12974,7 @@ var CanvasCompiler = class {
|
||||
}
|
||||
});
|
||||
}
|
||||
buildTextNode(node, baseStyle, colorClass, file) {
|
||||
buildTextNode(node, baseStyle, colorClass, file, assets) {
|
||||
return __async(this, null, function* () {
|
||||
var _a2;
|
||||
let processedText = node.text;
|
||||
@@ -12935,7 +12982,8 @@ var CanvasCompiler = class {
|
||||
try {
|
||||
processedText = yield this.textNodeProcessor.processTextNodeContent(
|
||||
file,
|
||||
node.text
|
||||
node.text,
|
||||
assets
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error processing canvas text node:", e);
|
||||
@@ -12955,6 +13003,45 @@ var CanvasCompiler = class {
|
||||
}
|
||||
buildFileNode(node, baseStyle, colorClass, file, assets) {
|
||||
return __async(this, null, function* () {
|
||||
const isPdf = /\.pdf$/i.test(node.file);
|
||||
if (isPdf) {
|
||||
const linkedFile = this.metadataCache.getFirstLinkpathDest(
|
||||
(0, import_obsidian4.getLinkpath)(node.file),
|
||||
file.getPath()
|
||||
);
|
||||
if (linkedFile) {
|
||||
try {
|
||||
const pdfData = yield this.vault.readBinary(linkedFile);
|
||||
const pdfBase64 = arrayBufferToBase64(pdfData);
|
||||
const pdfPath2 = `/img/user/${linkedFile.path}`;
|
||||
assets.push({
|
||||
path: pdfPath2,
|
||||
content: pdfBase64,
|
||||
localHash: generateBlobHashFromBase64(pdfBase64)
|
||||
});
|
||||
return `<div class="canvas-node canvas-node-file canvas-node-pdf ${colorClass}" data-node-id="${node.id}" style="${baseStyle}">
|
||||
<div class="canvas-node-container">
|
||||
<div class="canvas-node-content">
|
||||
<iframe src="${encodeURI(
|
||||
pdfPath2
|
||||
)}" class="canvas-pdf-iframe" loading="lazy" style="width:100%;height:100%;border:none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
} catch (e) {
|
||||
console.error("Error reading canvas PDF:", e);
|
||||
}
|
||||
}
|
||||
const resolvedPath = (linkedFile == null ? void 0 : linkedFile.path) || node.file;
|
||||
const pdfPath = encodeURI(`/img/user/${resolvedPath}`);
|
||||
return `<div class="canvas-node canvas-node-file canvas-node-pdf ${colorClass}" data-node-id="${node.id}" style="${baseStyle}">
|
||||
<div class="canvas-node-container">
|
||||
<div class="canvas-node-content">
|
||||
<iframe src="${pdfPath}" class="canvas-pdf-iframe" loading="lazy" style="width:100%;height:100%;border:none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
const isImage = /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(node.file);
|
||||
if (isImage) {
|
||||
const linkedFile = this.metadataCache.getFirstLinkpathDest(
|
||||
@@ -19415,6 +19502,18 @@ var GardenPageCompiler = class {
|
||||
linkedFileName = headerSplit[0];
|
||||
headerPath = headerSplit.length > 1 ? `#${headerSplit[1]}` : "";
|
||||
}
|
||||
if (linkedFileName === "" && headerPath !== "") {
|
||||
const currentFilePath = file.getPath();
|
||||
const currentExtensionlessPath = currentFilePath.substring(
|
||||
0,
|
||||
currentFilePath.lastIndexOf(".")
|
||||
);
|
||||
convertedText = convertedText.replace(
|
||||
linkMatch,
|
||||
`[[${currentExtensionlessPath}${headerPath}\\|${linkDisplayName}]]`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const fullLinkedFilePath = (0, import_obsidian5.getLinkpath)(linkedFileName);
|
||||
if (fullLinkedFilePath === "") {
|
||||
continue;
|
||||
@@ -20122,7 +20221,7 @@ ${headerSection}
|
||||
* Process text content from canvas text nodes through the same pipeline as notes.
|
||||
* This enables wiki-links, transclusions, dataview, etc. in canvas text nodes.
|
||||
*/
|
||||
processTextNodeContent(file, text2) {
|
||||
processTextNodeContent(file, text2, assets) {
|
||||
return __async(this, null, function* () {
|
||||
const CANVAS_TEXT_COMPILE_STEPS = [
|
||||
this.convertCustomFilters,
|
||||
@@ -20130,12 +20229,18 @@ ${headerSection}
|
||||
this.createTranscludedText(0),
|
||||
this.convertDataViews,
|
||||
this.convertLinksToFullPath,
|
||||
this.removeObsidianComments
|
||||
this.removeObsidianComments,
|
||||
this.createSvgEmbeds
|
||||
];
|
||||
return yield this.runCompilerSteps(
|
||||
const compiledText = yield this.runCompilerSteps(
|
||||
file,
|
||||
CANVAS_TEXT_COMPILE_STEPS
|
||||
)(text2);
|
||||
const [processedText, collectedAssets] = yield this.convertEmbeddedAssets(file)(compiledText);
|
||||
if (assets) {
|
||||
assets.push(...collectedAssets);
|
||||
}
|
||||
return processedText;
|
||||
});
|
||||
}
|
||||
generateMarkdown(file) {
|
||||
@@ -20371,6 +20476,9 @@ var Publisher = class {
|
||||
yield this.uploadAssets(assets, remoteImageHashes);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof LimitReachedError) {
|
||||
throw error;
|
||||
}
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
@@ -20416,6 +20524,9 @@ var Publisher = class {
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof LimitReachedError) {
|
||||
throw error;
|
||||
}
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
@@ -30017,6 +30128,22 @@ var ForestryApi = class {
|
||||
}
|
||||
});
|
||||
}
|
||||
getUserLimits() {
|
||||
return __async(this, null, function* () {
|
||||
try {
|
||||
const response = yield this.client.get(
|
||||
"user/limits"
|
||||
);
|
||||
if (response.status !== 200) {
|
||||
return null;
|
||||
}
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
import_js_logger8.default.error(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// src/views/SettingsView/ForestrySettings.svelte
|
||||
@@ -30033,11 +30160,11 @@ function create_else_block5(ctx) {
|
||||
pending: create_pending_block,
|
||||
then: create_then_block,
|
||||
catch: create_catch_block,
|
||||
value: 9,
|
||||
value: 12,
|
||||
blocks: [, , ,]
|
||||
};
|
||||
handle_promise(promise = /*getPageInfo*/
|
||||
ctx[5](), info);
|
||||
ctx[7](), info);
|
||||
return {
|
||||
c() {
|
||||
await_block_anchor = empty();
|
||||
@@ -30125,13 +30252,13 @@ function create_if_block6(ctx) {
|
||||
input,
|
||||
"input",
|
||||
/*input_input_handler*/
|
||||
ctx[8]
|
||||
ctx[10]
|
||||
),
|
||||
listen(
|
||||
button,
|
||||
"click",
|
||||
/*connect*/
|
||||
ctx[3]
|
||||
ctx[5]
|
||||
)
|
||||
];
|
||||
mounted = true;
|
||||
@@ -30191,7 +30318,7 @@ function create_catch_block(ctx) {
|
||||
button,
|
||||
"click",
|
||||
/*disconnect*/
|
||||
ctx[4]
|
||||
ctx[6]
|
||||
);
|
||||
mounted = true;
|
||||
}
|
||||
@@ -30218,7 +30345,7 @@ function create_then_block(ctx) {
|
||||
function select_block_type_1(ctx2, dirty) {
|
||||
if (
|
||||
/*pageInfo*/
|
||||
ctx2[9]
|
||||
ctx2[12]
|
||||
) return 0;
|
||||
return 1;
|
||||
}
|
||||
@@ -30287,7 +30414,7 @@ function create_else_block_12(ctx) {
|
||||
button,
|
||||
"click",
|
||||
/*disconnect*/
|
||||
ctx[4]
|
||||
ctx[6]
|
||||
);
|
||||
mounted = true;
|
||||
}
|
||||
@@ -30313,7 +30440,7 @@ function create_if_block_15(ctx) {
|
||||
let t0;
|
||||
let t1_value = (
|
||||
/*pageInfo*/
|
||||
((_a2 = ctx[9].value.pageName) != null ? _a2 : "Unknown") + ""
|
||||
((_a2 = ctx[12].value.pageName) != null ? _a2 : "Unknown") + ""
|
||||
);
|
||||
let t1;
|
||||
let t2;
|
||||
@@ -30326,11 +30453,25 @@ function create_if_block_15(ctx) {
|
||||
let a;
|
||||
let icon1;
|
||||
let t7;
|
||||
let t8;
|
||||
let if_block_anchor;
|
||||
let current;
|
||||
let mounted;
|
||||
let dispose;
|
||||
icon0 = new Icon_default({ props: { name: "check-circle" } });
|
||||
icon1 = new Icon_default({ props: { name: "external-link" } });
|
||||
function select_block_type_2(ctx2, dirty) {
|
||||
if (
|
||||
/*limitsLoading*/
|
||||
ctx2[4]
|
||||
) return create_if_block_25;
|
||||
if (
|
||||
/*limits*/
|
||||
ctx2[3]
|
||||
) return create_if_block_33;
|
||||
}
|
||||
let current_block_type = select_block_type_2(ctx, -1);
|
||||
let if_block = current_block_type && current_block_type(ctx);
|
||||
return {
|
||||
c() {
|
||||
div4 = element("div");
|
||||
@@ -30351,6 +30492,9 @@ function create_if_block_15(ctx) {
|
||||
a = element("a");
|
||||
create_component(icon1.$$.fragment);
|
||||
t7 = text(" Open Forestry.md Dashboard");
|
||||
t8 = space();
|
||||
if (if_block) if_block.c();
|
||||
if_block_anchor = empty();
|
||||
attr(div0, "class", "setting-item-name");
|
||||
set_style(div0, "display", "flex");
|
||||
set_style(div0, "align-items", "center");
|
||||
@@ -30384,18 +30528,32 @@ function create_if_block_15(ctx) {
|
||||
append(div5, a);
|
||||
mount_component(icon1, a, null);
|
||||
append(a, t7);
|
||||
insert(target, t8, anchor);
|
||||
if (if_block) if_block.m(target, anchor);
|
||||
insert(target, if_block_anchor, anchor);
|
||||
current = true;
|
||||
if (!mounted) {
|
||||
dispose = listen(
|
||||
button,
|
||||
"click",
|
||||
/*disconnect*/
|
||||
ctx[4]
|
||||
ctx[6]
|
||||
);
|
||||
mounted = true;
|
||||
}
|
||||
},
|
||||
p: noop,
|
||||
p(ctx2, dirty) {
|
||||
if (current_block_type === (current_block_type = select_block_type_2(ctx2, dirty)) && if_block) {
|
||||
if_block.p(ctx2, dirty);
|
||||
} else {
|
||||
if (if_block) if_block.d(1);
|
||||
if_block = current_block_type && current_block_type(ctx2);
|
||||
if (if_block) {
|
||||
if_block.c();
|
||||
if_block.m(if_block_anchor.parentNode, if_block_anchor);
|
||||
}
|
||||
}
|
||||
},
|
||||
i(local) {
|
||||
if (current) return;
|
||||
transition_in(icon0.$$.fragment, local);
|
||||
@@ -30412,14 +30570,362 @@ function create_if_block_15(ctx) {
|
||||
detach(div4);
|
||||
detach(t6);
|
||||
detach(div5);
|
||||
detach(t8);
|
||||
detach(if_block_anchor);
|
||||
}
|
||||
destroy_component(icon0);
|
||||
destroy_component(icon1);
|
||||
if (if_block) {
|
||||
if_block.d(detaching);
|
||||
}
|
||||
mounted = false;
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_if_block_33(ctx) {
|
||||
let div5;
|
||||
let div0;
|
||||
let t0;
|
||||
let t1_value = (
|
||||
/*limits*/
|
||||
ctx[3].plan + ""
|
||||
);
|
||||
let t1;
|
||||
let t2;
|
||||
let t3;
|
||||
let div4;
|
||||
let div1;
|
||||
let span0;
|
||||
let t5;
|
||||
let span1;
|
||||
let t6_value = (
|
||||
/*limits*/
|
||||
ctx[3].builds.monthlyLimit - /*limits*/
|
||||
ctx[3].builds.monthlyRemaining + ""
|
||||
);
|
||||
let t6;
|
||||
let t7;
|
||||
let t8_value = (
|
||||
/*limits*/
|
||||
ctx[3].builds.monthlyLimit + ""
|
||||
);
|
||||
let t8;
|
||||
let t9;
|
||||
let t10;
|
||||
let div2;
|
||||
let span2;
|
||||
let t12;
|
||||
let span3;
|
||||
let t13_value = (
|
||||
/*limits*/
|
||||
ctx[3].storage.usedFormatted + ""
|
||||
);
|
||||
let t13;
|
||||
let t14;
|
||||
let t15_value = (
|
||||
/*limits*/
|
||||
ctx[3].storage.limitFormatted + ""
|
||||
);
|
||||
let t15;
|
||||
let t16;
|
||||
let div3;
|
||||
let span4;
|
||||
let t18;
|
||||
let span5;
|
||||
let t19_value = (
|
||||
/*limits*/
|
||||
ctx[3].sites.current + ""
|
||||
);
|
||||
let t19;
|
||||
let t20;
|
||||
let t21_value = (
|
||||
/*limits*/
|
||||
ctx[3].sites.limit + ""
|
||||
);
|
||||
let t21;
|
||||
let t22;
|
||||
let if_block0 = (
|
||||
/*limits*/
|
||||
ctx[3].builds.starterCreditsRemaining > 0 && create_if_block_53(ctx)
|
||||
);
|
||||
let if_block1 = (
|
||||
/*limits*/
|
||||
(ctx[3].builds.monthlyRemaining === 0 || /*limits*/
|
||||
ctx[3].storage.usedBytes >= /*limits*/
|
||||
ctx[3].storage.limitBytes) && create_if_block_43(ctx)
|
||||
);
|
||||
return {
|
||||
c() {
|
||||
div5 = element("div");
|
||||
div0 = element("div");
|
||||
t0 = text("Usage \u2014 ");
|
||||
t1 = text(t1_value);
|
||||
t2 = text(" plan");
|
||||
t3 = space();
|
||||
div4 = element("div");
|
||||
div1 = element("div");
|
||||
span0 = element("span");
|
||||
span0.textContent = "Builds this month";
|
||||
t5 = space();
|
||||
span1 = element("span");
|
||||
t6 = text(t6_value);
|
||||
t7 = text(" / ");
|
||||
t8 = text(t8_value);
|
||||
t9 = space();
|
||||
if (if_block0) if_block0.c();
|
||||
t10 = space();
|
||||
div2 = element("div");
|
||||
span2 = element("span");
|
||||
span2.textContent = "Storage";
|
||||
t12 = space();
|
||||
span3 = element("span");
|
||||
t13 = text(t13_value);
|
||||
t14 = text(" / ");
|
||||
t15 = text(t15_value);
|
||||
t16 = space();
|
||||
div3 = element("div");
|
||||
span4 = element("span");
|
||||
span4.textContent = "Sites";
|
||||
t18 = space();
|
||||
span5 = element("span");
|
||||
t19 = text(t19_value);
|
||||
t20 = text(" / ");
|
||||
t21 = text(t21_value);
|
||||
t22 = space();
|
||||
if (if_block1) if_block1.c();
|
||||
set_style(div0, "font-weight", "600");
|
||||
set_style(div0, "margin-bottom", "8px");
|
||||
set_style(
|
||||
span1,
|
||||
"color",
|
||||
/*limits*/
|
||||
ctx[3].builds.monthlyRemaining === 0 ? "var(--text-error)" : "var(--text-normal)"
|
||||
);
|
||||
set_style(div1, "display", "flex");
|
||||
set_style(div1, "justify-content", "space-between");
|
||||
set_style(
|
||||
span3,
|
||||
"color",
|
||||
/*limits*/
|
||||
ctx[3].storage.usedBytes >= /*limits*/
|
||||
ctx[3].storage.limitBytes ? "var(--text-error)" : "var(--text-normal)"
|
||||
);
|
||||
set_style(div2, "display", "flex");
|
||||
set_style(div2, "justify-content", "space-between");
|
||||
set_style(div3, "display", "flex");
|
||||
set_style(div3, "justify-content", "space-between");
|
||||
set_style(div4, "display", "flex");
|
||||
set_style(div4, "flex-direction", "column");
|
||||
set_style(div4, "gap", "6px");
|
||||
set_style(div4, "font-size", "0.9em");
|
||||
set_style(div4, "color", "var(--text-muted)");
|
||||
set_style(div5, "margin-top", "16px");
|
||||
set_style(div5, "padding", "12px");
|
||||
set_style(div5, "background", "var(--background-secondary)");
|
||||
set_style(div5, "border-radius", "8px");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div5, anchor);
|
||||
append(div5, div0);
|
||||
append(div0, t0);
|
||||
append(div0, t1);
|
||||
append(div0, t2);
|
||||
append(div5, t3);
|
||||
append(div5, div4);
|
||||
append(div4, div1);
|
||||
append(div1, span0);
|
||||
append(div1, t5);
|
||||
append(div1, span1);
|
||||
append(span1, t6);
|
||||
append(span1, t7);
|
||||
append(span1, t8);
|
||||
append(div4, t9);
|
||||
if (if_block0) if_block0.m(div4, null);
|
||||
append(div4, t10);
|
||||
append(div4, div2);
|
||||
append(div2, span2);
|
||||
append(div2, t12);
|
||||
append(div2, span3);
|
||||
append(span3, t13);
|
||||
append(span3, t14);
|
||||
append(span3, t15);
|
||||
append(div4, t16);
|
||||
append(div4, div3);
|
||||
append(div3, span4);
|
||||
append(div3, t18);
|
||||
append(div3, span5);
|
||||
append(span5, t19);
|
||||
append(span5, t20);
|
||||
append(span5, t21);
|
||||
append(div5, t22);
|
||||
if (if_block1) if_block1.m(div5, null);
|
||||
},
|
||||
p(ctx2, dirty) {
|
||||
if (dirty & /*limits*/
|
||||
8 && t1_value !== (t1_value = /*limits*/
|
||||
ctx2[3].plan + "")) set_data(t1, t1_value);
|
||||
if (dirty & /*limits*/
|
||||
8 && t6_value !== (t6_value = /*limits*/
|
||||
ctx2[3].builds.monthlyLimit - /*limits*/
|
||||
ctx2[3].builds.monthlyRemaining + "")) set_data(t6, t6_value);
|
||||
if (dirty & /*limits*/
|
||||
8 && t8_value !== (t8_value = /*limits*/
|
||||
ctx2[3].builds.monthlyLimit + "")) set_data(t8, t8_value);
|
||||
if (dirty & /*limits*/
|
||||
8) {
|
||||
set_style(
|
||||
span1,
|
||||
"color",
|
||||
/*limits*/
|
||||
ctx2[3].builds.monthlyRemaining === 0 ? "var(--text-error)" : "var(--text-normal)"
|
||||
);
|
||||
}
|
||||
if (
|
||||
/*limits*/
|
||||
ctx2[3].builds.starterCreditsRemaining > 0
|
||||
) {
|
||||
if (if_block0) {
|
||||
if_block0.p(ctx2, dirty);
|
||||
} else {
|
||||
if_block0 = create_if_block_53(ctx2);
|
||||
if_block0.c();
|
||||
if_block0.m(div4, t10);
|
||||
}
|
||||
} else if (if_block0) {
|
||||
if_block0.d(1);
|
||||
if_block0 = null;
|
||||
}
|
||||
if (dirty & /*limits*/
|
||||
8 && t13_value !== (t13_value = /*limits*/
|
||||
ctx2[3].storage.usedFormatted + "")) set_data(t13, t13_value);
|
||||
if (dirty & /*limits*/
|
||||
8 && t15_value !== (t15_value = /*limits*/
|
||||
ctx2[3].storage.limitFormatted + "")) set_data(t15, t15_value);
|
||||
if (dirty & /*limits*/
|
||||
8) {
|
||||
set_style(
|
||||
span3,
|
||||
"color",
|
||||
/*limits*/
|
||||
ctx2[3].storage.usedBytes >= /*limits*/
|
||||
ctx2[3].storage.limitBytes ? "var(--text-error)" : "var(--text-normal)"
|
||||
);
|
||||
}
|
||||
if (dirty & /*limits*/
|
||||
8 && t19_value !== (t19_value = /*limits*/
|
||||
ctx2[3].sites.current + "")) set_data(t19, t19_value);
|
||||
if (dirty & /*limits*/
|
||||
8 && t21_value !== (t21_value = /*limits*/
|
||||
ctx2[3].sites.limit + "")) set_data(t21, t21_value);
|
||||
if (
|
||||
/*limits*/
|
||||
ctx2[3].builds.monthlyRemaining === 0 || /*limits*/
|
||||
ctx2[3].storage.usedBytes >= /*limits*/
|
||||
ctx2[3].storage.limitBytes
|
||||
) {
|
||||
if (if_block1) {
|
||||
} else {
|
||||
if_block1 = create_if_block_43(ctx2);
|
||||
if_block1.c();
|
||||
if_block1.m(div5, null);
|
||||
}
|
||||
} else if (if_block1) {
|
||||
if_block1.d(1);
|
||||
if_block1 = null;
|
||||
}
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching) {
|
||||
detach(div5);
|
||||
}
|
||||
if (if_block0) if_block0.d();
|
||||
if (if_block1) if_block1.d();
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_if_block_25(ctx) {
|
||||
let div2;
|
||||
return {
|
||||
c() {
|
||||
div2 = element("div");
|
||||
div2.innerHTML = `<div class="setting-item-info"><div class="setting-item-name">Loading usage info...</div></div>`;
|
||||
attr(div2, "class", "setting-item");
|
||||
set_style(div2, "margin-top", "12px");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div2, anchor);
|
||||
},
|
||||
p: noop,
|
||||
d(detaching) {
|
||||
if (detaching) {
|
||||
detach(div2);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_if_block_53(ctx) {
|
||||
let div;
|
||||
let span0;
|
||||
let t1;
|
||||
let span1;
|
||||
let t2_value = (
|
||||
/*limits*/
|
||||
ctx[3].builds.starterCreditsRemaining + ""
|
||||
);
|
||||
let t2;
|
||||
return {
|
||||
c() {
|
||||
div = element("div");
|
||||
span0 = element("span");
|
||||
span0.textContent = "Starter credits remaining";
|
||||
t1 = space();
|
||||
span1 = element("span");
|
||||
t2 = text(t2_value);
|
||||
set_style(div, "display", "flex");
|
||||
set_style(div, "justify-content", "space-between");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div, anchor);
|
||||
append(div, span0);
|
||||
append(div, t1);
|
||||
append(div, span1);
|
||||
append(span1, t2);
|
||||
},
|
||||
p(ctx2, dirty) {
|
||||
if (dirty & /*limits*/
|
||||
8 && t2_value !== (t2_value = /*limits*/
|
||||
ctx2[3].builds.starterCreditsRemaining + "")) set_data(t2, t2_value);
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching) {
|
||||
detach(div);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_if_block_43(ctx) {
|
||||
let div;
|
||||
return {
|
||||
c() {
|
||||
div = element("div");
|
||||
div.innerHTML = `You've reached your usage limit. <a href="https://dashboard.forestry.md/settings" target="_blank">Upgrade your plan</a> to continue publishing.`;
|
||||
set_style(div, "margin-top", "8px");
|
||||
set_style(div, "padding", "8px");
|
||||
set_style(div, "background", "var(--background-modifier-error)");
|
||||
set_style(div, "border-radius", "4px");
|
||||
set_style(div, "font-size", "0.85em");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div, anchor);
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching) {
|
||||
detach(div);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_pending_block(ctx) {
|
||||
let div2;
|
||||
return {
|
||||
@@ -30576,6 +31082,8 @@ function instance8($$self, $$props, $$invalidate) {
|
||||
let { saveSettings } = $$props;
|
||||
let { onConnect } = $$props;
|
||||
let apiKey = settings.forestrySettings.apiKey;
|
||||
let limits = null;
|
||||
let limitsLoading = false;
|
||||
const connect = () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
let pageInfo = yield getPageInfo();
|
||||
if (!pageInfo) {
|
||||
@@ -30594,24 +31102,45 @@ function instance8($$self, $$props, $$invalidate) {
|
||||
$$invalidate(0, settings.forestrySettings.forestryPageName = "", settings);
|
||||
yield saveSettings();
|
||||
$$invalidate(2, apiKey = "");
|
||||
$$invalidate(3, limits = null);
|
||||
});
|
||||
const getPageInfo = () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
let pageInfo = yield new ForestryApi(apiKey).getPageInfo();
|
||||
return pageInfo;
|
||||
});
|
||||
const fetchLimits = () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
if (!settings.forestrySettings.apiKey) return;
|
||||
$$invalidate(4, limitsLoading = true);
|
||||
try {
|
||||
$$invalidate(3, limits = yield new ForestryApi(settings.forestrySettings.apiKey).getUserLimits());
|
||||
} catch (_a2) {
|
||||
$$invalidate(3, limits = null);
|
||||
}
|
||||
$$invalidate(4, limitsLoading = false);
|
||||
});
|
||||
function input_input_handler() {
|
||||
apiKey = this.value;
|
||||
$$invalidate(2, apiKey);
|
||||
}
|
||||
$$self.$$set = ($$props2) => {
|
||||
if ("settings" in $$props2) $$invalidate(0, settings = $$props2.settings);
|
||||
if ("saveSettings" in $$props2) $$invalidate(6, saveSettings = $$props2.saveSettings);
|
||||
if ("onConnect" in $$props2) $$invalidate(7, onConnect = $$props2.onConnect);
|
||||
if ("saveSettings" in $$props2) $$invalidate(8, saveSettings = $$props2.saveSettings);
|
||||
if ("onConnect" in $$props2) $$invalidate(9, onConnect = $$props2.onConnect);
|
||||
};
|
||||
$$self.$$.update = () => {
|
||||
if ($$self.$$.dirty & /*settings*/
|
||||
1) {
|
||||
$: if (settings.forestrySettings.apiKey) {
|
||||
fetchLimits();
|
||||
}
|
||||
}
|
||||
};
|
||||
return [
|
||||
settings,
|
||||
unique,
|
||||
apiKey,
|
||||
limits,
|
||||
limitsLoading,
|
||||
connect,
|
||||
disconnect,
|
||||
getPageInfo,
|
||||
@@ -30625,8 +31154,8 @@ var ForestrySettings = class extends SvelteComponent {
|
||||
super();
|
||||
init(this, options, instance8, create_fragment8, safe_not_equal, {
|
||||
settings: 0,
|
||||
saveSettings: 6,
|
||||
onConnect: 7
|
||||
saveSettings: 8,
|
||||
onConnect: 9
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -30699,7 +31228,7 @@ var SettingView = class {
|
||||
const publishPlatformSettings = this.settingsRootElement.createEl(
|
||||
"div",
|
||||
{
|
||||
cls: "connection-status"
|
||||
cls: "publish-platform-settings"
|
||||
}
|
||||
);
|
||||
this.initializePublishPlatformSettings(publishPlatformSettings);
|
||||
@@ -31313,13 +31842,31 @@ var SettingView = class {
|
||||
cb.setButtonText("Apply settings to site");
|
||||
cb.setCta();
|
||||
cb.onClick((_ev) => __async(this, null, function* () {
|
||||
const octokit = new Octokit({
|
||||
auth: this.settings.githubToken
|
||||
});
|
||||
new import_obsidian15.Notice("Applying settings to site...");
|
||||
yield this.saveSettingsAndUpdateEnv();
|
||||
yield this.addFavicon(octokit);
|
||||
yield this.addLogo(octokit);
|
||||
const connection = yield PublishPlatformConnectionFactory.createPublishPlatformConnection(
|
||||
this.settings
|
||||
);
|
||||
const octokit = connection.octoKit;
|
||||
const owner = connection.userName;
|
||||
const repo = connection.pageName;
|
||||
try {
|
||||
yield this.addFavicon(octokit, owner, repo);
|
||||
} catch (error) {
|
||||
import_js_logger9.default.error("Failed to update favicon", error);
|
||||
new import_obsidian15.Notice(
|
||||
"Failed to update favicon. Check the developer console for details."
|
||||
);
|
||||
}
|
||||
try {
|
||||
yield this.addLogo(octokit, owner, repo);
|
||||
} catch (error) {
|
||||
import_js_logger9.default.error("Failed to update logo", error);
|
||||
new import_obsidian15.Notice(
|
||||
"Failed to update logo. Check the developer console for details."
|
||||
);
|
||||
}
|
||||
new import_obsidian15.Notice("Settings applied to site!");
|
||||
}));
|
||||
};
|
||||
new import_obsidian15.Setting(this.settingsRootElement).setName("Appearance").setDesc("Manage themes, sitename and styling on your site").addButton((cb) => {
|
||||
@@ -31724,7 +32271,7 @@ var SettingView = class {
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
addFavicon(octokit) {
|
||||
addFavicon(octokit, owner, repo) {
|
||||
return __async(this, null, function* () {
|
||||
let base64SettingsFaviconContent = "";
|
||||
if (this.settings.faviconPath) {
|
||||
@@ -31738,11 +32285,12 @@ var SettingView = class {
|
||||
const faviconContent = yield this.app.vault.readBinary(faviconFile);
|
||||
base64SettingsFaviconContent = arrayBufferToBase64(faviconContent);
|
||||
} else {
|
||||
const defaultFavicon = yield octokit.request(
|
||||
const baseConnection = PublishPlatformConnectionFactory.createBaseGardenConnection();
|
||||
const defaultFavicon = yield baseConnection.octoKit.request(
|
||||
"GET /repos/{owner}/{repo}/contents/{path}",
|
||||
{
|
||||
owner: "oleeskild",
|
||||
repo: "digitalgarden",
|
||||
owner: baseConnection.userName,
|
||||
repo: baseConnection.pageName,
|
||||
path: "src/site/favicon.svg"
|
||||
}
|
||||
);
|
||||
@@ -31755,13 +32303,13 @@ var SettingView = class {
|
||||
currentFaviconOnSite = yield octokit.request(
|
||||
"GET /repos/{owner}/{repo}/contents/{path}",
|
||||
{
|
||||
owner: this.settings.githubUserName,
|
||||
repo: this.settings.githubRepo,
|
||||
owner,
|
||||
repo,
|
||||
path: "src/site/favicon.svg"
|
||||
}
|
||||
);
|
||||
faviconsAreIdentical = // @ts-expect-error TODO: abstract octokit response
|
||||
currentFaviconOnSite.data.content === base64SettingsFaviconContent;
|
||||
currentFaviconOnSite.data.content.replace(/\n/g, "") === base64SettingsFaviconContent;
|
||||
if (faviconsAreIdentical) {
|
||||
import_js_logger9.default.info("Favicons are identical, skipping update");
|
||||
return;
|
||||
@@ -31771,8 +32319,8 @@ var SettingView = class {
|
||||
}
|
||||
if (!faviconExists || !faviconsAreIdentical) {
|
||||
yield octokit.request("PUT /repos/{owner}/{repo}/contents/{path}", {
|
||||
owner: this.settings.githubUserName,
|
||||
repo: this.settings.githubRepo,
|
||||
owner,
|
||||
repo,
|
||||
path: "src/site/favicon.svg",
|
||||
message: `Update favicon.svg`,
|
||||
content: base64SettingsFaviconContent,
|
||||
@@ -31782,9 +32330,12 @@ var SettingView = class {
|
||||
}
|
||||
});
|
||||
}
|
||||
addLogo(octokit) {
|
||||
addLogo(octokit, owner, repo) {
|
||||
return __async(this, null, function* () {
|
||||
var _a2;
|
||||
import_js_logger9.default.info(
|
||||
`addLogo called, logoPath setting: "${this.settings.logoPath}", owner: "${owner}", repo: "${repo}"`
|
||||
);
|
||||
const logoBasePath = "src/site/logo";
|
||||
const logoExtensions = ["png", "jpg", "jpeg", "gif", "svg", "webp"];
|
||||
for (const ext of logoExtensions) {
|
||||
@@ -31792,8 +32343,8 @@ var SettingView = class {
|
||||
const existingLogo = yield octokit.request(
|
||||
"GET /repos/{owner}/{repo}/contents/{path}",
|
||||
{
|
||||
owner: this.settings.githubUserName,
|
||||
repo: this.settings.githubRepo,
|
||||
owner,
|
||||
repo,
|
||||
path: `${logoBasePath}.${ext}`
|
||||
}
|
||||
);
|
||||
@@ -31804,8 +32355,8 @@ var SettingView = class {
|
||||
yield octokit.request(
|
||||
"DELETE /repos/{owner}/{repo}/contents/{path}",
|
||||
{
|
||||
owner: this.settings.githubUserName,
|
||||
repo: this.settings.githubRepo,
|
||||
owner,
|
||||
repo,
|
||||
path: `${logoBasePath}.${ext}`,
|
||||
message: `Remove logo.${ext}`,
|
||||
// @ts-expect-error TODO: abstract octokit response
|
||||
@@ -31841,13 +32392,13 @@ var SettingView = class {
|
||||
currentLogoOnSite = yield octokit.request(
|
||||
"GET /repos/{owner}/{repo}/contents/{path}",
|
||||
{
|
||||
owner: this.settings.githubUserName,
|
||||
repo: this.settings.githubRepo,
|
||||
owner,
|
||||
repo,
|
||||
path: logoPath
|
||||
}
|
||||
);
|
||||
logosAreIdentical = // @ts-expect-error TODO: abstract octokit response
|
||||
currentLogoOnSite.data.content === base64LogoContent;
|
||||
currentLogoOnSite.data.content.replace(/\n/g, "") === base64LogoContent;
|
||||
if (logosAreIdentical) {
|
||||
import_js_logger9.default.info("Logos are identical, skipping update");
|
||||
return;
|
||||
@@ -31858,8 +32409,8 @@ var SettingView = class {
|
||||
if (!logoExists || !logosAreIdentical) {
|
||||
try {
|
||||
const requestPayload = __spreadValues({
|
||||
owner: this.settings.githubUserName,
|
||||
repo: this.settings.githubRepo,
|
||||
owner,
|
||||
repo,
|
||||
path: logoPath,
|
||||
message: `Update logo.${logoExtension}`,
|
||||
content: base64LogoContent
|
||||
@@ -32550,6 +33101,10 @@ var DigitalGarden = class extends import_obsidian19.Plugin {
|
||||
} catch (e) {
|
||||
statusBarItem.remove();
|
||||
this.isPublishing = false;
|
||||
if (e instanceof LimitReachedError) {
|
||||
this.showLimitNotice(e);
|
||||
return;
|
||||
}
|
||||
console.error(e);
|
||||
new import_obsidian19.Notice(
|
||||
"Unable to publish multiple notes, something went wrong."
|
||||
@@ -32666,9 +33221,15 @@ var DigitalGarden = class extends import_obsidian19.Plugin {
|
||||
const publishSuccessful = yield publisher.publish(publishFile);
|
||||
if (publishSuccessful) {
|
||||
new import_obsidian19.Notice(`Successfully published note to your garden.`);
|
||||
} else {
|
||||
new import_obsidian19.Notice("Unable to publish note, something went wrong.");
|
||||
}
|
||||
return publishSuccessful;
|
||||
} catch (e) {
|
||||
if (e instanceof LimitReachedError) {
|
||||
this.showLimitNotice(e);
|
||||
return false;
|
||||
}
|
||||
console.error(e);
|
||||
new import_obsidian19.Notice("Unable to publish note, something went wrong.");
|
||||
return false;
|
||||
@@ -32762,6 +33323,22 @@ var DigitalGarden = class extends import_obsidian19.Plugin {
|
||||
}
|
||||
});
|
||||
}
|
||||
showLimitNotice(error) {
|
||||
var _a2, _b;
|
||||
if (error.errorType === "build_limit_reached") {
|
||||
const used = (_a2 = error.buildsUsed) != null ? _a2 : 0;
|
||||
const limit = (_b = error.monthlyLimit) != null ? _b : 0;
|
||||
new import_obsidian19.Notice(
|
||||
`Publishing blocked: You've used all ${used}/${limit} builds this month. Upgrade to Pro for 1000 builds/month at dashboard.forestry.md/settings`,
|
||||
1e4
|
||||
);
|
||||
} else {
|
||||
new import_obsidian19.Notice(
|
||||
`Publishing blocked: Storage limit exceeded. Free up space or upgrade at dashboard.forestry.md/settings`,
|
||||
1e4
|
||||
);
|
||||
}
|
||||
}
|
||||
openPublishModal() {
|
||||
const siteManager = new DigitalGardenSiteManager(
|
||||
this.app.metadataCache,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "digitalgarden",
|
||||
"name": "Digital Garden",
|
||||
"version": "2.69.0",
|
||||
"version": "2.72.0",
|
||||
"minAppVersion": "1.10.0",
|
||||
"description": "Publish your notes to the web for others to enjoy. For free.",
|
||||
"author": "Ole Eskild Steensen",
|
||||
|
||||
+3
-3
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.20.2",
|
||||
"version": "2.20.6",
|
||||
"minAppVersion": "1.5.7",
|
||||
"description": "Sketch Your Mind. An Obsidian plugin to edit and view Excalidraw drawings. Enter the world of 4D Visual PKM.",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+281
-271
File diff suppressed because one or more lines are too long
+1
-1
@@ -6,5 +6,5 @@
|
||||
"description": "Integrate Git version control with automatic backup and other advanced features.",
|
||||
"isDesktopOnly": false,
|
||||
"fundingUrl": "https://ko-fi.com/vinzent",
|
||||
"version": "2.36.1"
|
||||
"version": "2.38.0"
|
||||
}
|
||||
|
||||
+5
@@ -81,6 +81,11 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Re-enable wrapping of nav buttns to prevent overflow on smaller screens #*/
|
||||
.workspace-drawer .git-view .nav-buttons-container {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.git-tools {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
|
||||
+383
-15536
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"id": "obsidian-latex-suite",
|
||||
"name": "Latex Suite",
|
||||
"version": "1.9.8",
|
||||
"version": "1.11.0",
|
||||
"minAppVersion": "1.0.0",
|
||||
"description": "Make typesetting LaTeX math as fast as handwriting through snippets, text expansion, and editor enhancements",
|
||||
"author": "artisticat",
|
||||
"authorUrl": "https://github.com/artisticat1",
|
||||
"fundingUrl": "https://ko-fi.com/artisticat",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
}
|
||||
@@ -192,8 +192,25 @@ sup.cm-math, sub.cm-math {
|
||||
0px 3.4px 6.7px rgba(0, 0, 0, 0.15),
|
||||
0px 0px 30px rgba(0, 0, 0, 0.27);
|
||||
}
|
||||
/* CM6 puts the top of the tooltip higher than what's viewable (negative top value),
|
||||
so to compensate for this, we align the content to the bottom of the tooltip container,
|
||||
and limit the height.
|
||||
*/
|
||||
.cm-tooltip.cm-tooltip-cursor.cm-tooltip-above {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cm-tooltip.cm-tooltip-cursor.cm-tooltip-above > .MathJax {
|
||||
overflow-y: auto;
|
||||
max-height: 70%;
|
||||
display: inline-block;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.cm-tooltip.cm-tooltip-cursor.cm-tooltip-below > .MathJax {
|
||||
overflow-y: auto;
|
||||
max-height: 90%;
|
||||
}
|
||||
/* Highlight brackets */
|
||||
.theme-light .latex-suite-highlighted-bracket, .theme-light .latex-suite-highlighted-bracket [class^="latex-suite-color-bracket-"] {
|
||||
background-color: hsl(var(--accent-h), var(--accent-s), 40%, 0.3);
|
||||
@@ -233,3 +250,15 @@ sup.cm-math, sub.cm-math {
|
||||
/* .latex-suite-color-bracket-3 {
|
||||
color: #8de15c;
|
||||
} */
|
||||
|
||||
.latex-suite-math-preview-highlight {
|
||||
background-color: var(--text-selection);
|
||||
}
|
||||
.cm-snippetFieldPosition {
|
||||
vertical-align: text-top;
|
||||
width: 0;
|
||||
height: 1.15em;
|
||||
display: inline-block;
|
||||
margin: 0 -0.7px -.7em;
|
||||
border-left: 1.4px dotted #888;
|
||||
}
|
||||
|
||||
+4
-1
@@ -106,7 +106,8 @@
|
||||
"enabled": false
|
||||
},
|
||||
"move-footnotes-to-the-bottom": {
|
||||
"enabled": false
|
||||
"enabled": false,
|
||||
"include-blank-line-between-footnotes": false
|
||||
},
|
||||
"re-index-footnotes": {
|
||||
"enabled": false
|
||||
@@ -273,9 +274,11 @@
|
||||
"lintOnSave": true,
|
||||
"recordLintOnSaveLogs": false,
|
||||
"displayChanged": true,
|
||||
"suppressMessageWhenNoChange": false,
|
||||
"lintOnFileChange": false,
|
||||
"displayLintOnFileChangeNotice": false,
|
||||
"settingsConvertedToConfigKeyValues": true,
|
||||
"additionalFileExtensions": [],
|
||||
"foldersToIgnore": [],
|
||||
"filesToIgnore": [],
|
||||
"linterLocale": "system-default",
|
||||
|
||||
+189
-186
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "obsidian-linter",
|
||||
"name": "Linter",
|
||||
"version": "1.30.0",
|
||||
"minAppVersion": "1.9.0",
|
||||
"version": "1.31.2",
|
||||
"minAppVersion": "1.12.0",
|
||||
"description": "Formats and styles your notes. It can be used to format YAML tags, aliases, arrays, and metadata; footnotes; headings; spacing; math blocks; regular markdown contents like list, italics, and bold styles; and more with the use of custom rule options as well.",
|
||||
"author": "Victor Tao",
|
||||
"authorUrl": "https://github.com/platers",
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@
|
||||
"targetFolders": "",
|
||||
"scanDelay": 250,
|
||||
"useTitle": true,
|
||||
"reduceNestedParent": true,
|
||||
"reduceNestedParent": false,
|
||||
"frontmatterKey": "title",
|
||||
"useTagInfo": false,
|
||||
"tagInfo": "pininfo.md",
|
||||
@@ -26,7 +26,7 @@
|
||||
"useMultiPaneList": false,
|
||||
"archiveTags": "",
|
||||
"disableNarrowingDown": true,
|
||||
"expandUntaggedToRoot": false,
|
||||
"expandUntaggedToRoot": true,
|
||||
"disableDragging": false,
|
||||
"linkConfig": {
|
||||
"incoming": {
|
||||
|
||||
+81
-77
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-tasks-plugin",
|
||||
"name": "Tasks",
|
||||
"version": "7.22.0",
|
||||
"version": "7.23.1",
|
||||
"minAppVersion": "1.4.0",
|
||||
"description": "Track tasks across your vault. Supports due dates, recurring tasks, done dates, sub-set of checklist items, and filtering.",
|
||||
"helpUrl": "https://publish.obsidian.md/tasks/",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -6,7 +6,7 @@
|
||||
"devMode": false,
|
||||
"templateFolderPath": "",
|
||||
"announceUpdates": "none",
|
||||
"version": "2.10.0",
|
||||
"version": "2.12.0",
|
||||
"globalVariables": {},
|
||||
"onePageInputEnabled": false,
|
||||
"disableOnlineFeatures": true,
|
||||
|
||||
Vendored
+67
-61
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "quickadd",
|
||||
"name": "QuickAdd",
|
||||
"version": "2.10.0",
|
||||
"version": "2.12.0",
|
||||
"minAppVersion": "1.11.4",
|
||||
"description": "Quickly add new pages or content to your vault.",
|
||||
"author": "Christian B. B. Houmann",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-2
@@ -562,8 +562,7 @@ var SpellcheckTogglerPlugin = class extends import_obsidian2.Plugin {
|
||||
const editor = (_a2 = this.app.workspace.activeEditor) == null ? void 0 : _a2.editor;
|
||||
if (!editor)
|
||||
return;
|
||||
editor.replaceRange(" ", editor.getCursor());
|
||||
editor.undo();
|
||||
editor.refresh();
|
||||
})();
|
||||
}
|
||||
async onload() {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "spellcheck-toggler",
|
||||
"name": "Spellcheck Toggler",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.4",
|
||||
"minAppVersion": "1.5.3",
|
||||
"description": "Toggle spellchecking for types of text blocks in the editing view.",
|
||||
"author": "Julian Szachowicz",
|
||||
|
||||
Vendored
+2
-1
@@ -25,6 +25,7 @@
|
||||
"TQ_show_tree": "checkbox",
|
||||
"TQ_show_urgency": "checkbox",
|
||||
"dg-publish": "checkbox",
|
||||
"id": "datetime"
|
||||
"id": "datetime",
|
||||
"TQ_show_toolbar": "checkbox"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title:
|
||||
title: 107 Morgan Street
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
- occupational/project
|
||||
---
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# 107 Morgan Street
|
||||
|
||||
@@ -22,6 +22,7 @@ year: 1960
|
||||
description: |
|
||||
Frontpage newspaper article noting the accidental death
|
||||
of Rev. Dr. William Hamilton Bill Alexander and his wife Marylouise.
|
||||
dg-publish: false
|
||||
---
|
||||
# Rev. William Alexander And Wife Killed In Airplane Crash
|
||||
|
||||
|
||||
+5
-3
@@ -1,10 +1,12 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title:
|
||||
title: 1990 K Street
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
- occupational/project
|
||||
---
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# 1990 K Street
|
||||
|
||||
@@ -6,6 +6,7 @@ tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
dg-publish: true
|
||||
---
|
||||
# 2009 Honda Civic
|
||||
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: 2026-W07
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/weekly
|
||||
dg-publish: true
|
||||
date-created: 2026-03-06T10:45:40-05:00
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-W07
|
||||
@@ -7,5 +7,6 @@ tags:
|
||||
- destiny/permanent
|
||||
- occupational/project
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# 2100 Crystal Drive
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title:
|
||||
title: 450-460 James Robertson Parkway
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
- occupational/project
|
||||
---
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# 450-460 James Robertson Parkway
|
||||
|
||||
+78
-4
@@ -6,6 +6,7 @@ tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
dg-publish: false
|
||||
---
|
||||
# 463 Davison Ave NE
|
||||
|
||||
@@ -24,16 +25,85 @@ Flood Insurance:
|
||||
Insurer: [National General](https://eservice.nationalgeneral.com/)
|
||||
Policy Number: 0003457855
|
||||
|
||||
## Resources
|
||||
|
||||
### Websites
|
||||
|
||||
[South Florida Plant Guide](https://www.south-florida-plant-guide.com/)
|
||||
|
||||
### Contractors
|
||||
|
||||
* **HVAC:** Agape Air (semiannual service)
|
||||
* **Plumbing:** Hafke Plumbing
|
||||
* **Pest Control:** "Michael" (727) 410-2636 (semiannual service)
|
||||
|
||||
## Spaces
|
||||
|
||||
### General
|
||||
|
||||
#### TODO
|
||||
|
||||
* [ ] draw floorplan
|
||||
|
||||
### Exterior
|
||||
|
||||
#### Plans
|
||||
|
||||
* Gutters
|
||||
|
||||
#### Design Principles
|
||||
|
||||
Keyword is _cozy_.
|
||||
The sort of place you want to be on a weekend morning,
|
||||
planning a day of birdwatching, while sipping coffee.
|
||||
|
||||
### Living/Dining
|
||||
|
||||
#### Design Principles
|
||||
|
||||
Television must not be the focal point, only present.
|
||||
|
||||
### Main Bedroom
|
||||
|
||||
Largest by room area
|
||||
small closet
|
||||
|
||||
### Guest Bedroom A
|
||||
|
||||
Smaller than main
|
||||
large closet
|
||||
|
||||
### Guest Bedroom B
|
||||
|
||||
Smaller by far than other bedrooms
|
||||
|
||||
### West Yard
|
||||
|
||||
#### TODO
|
||||
|
||||
* [ ] cleanup brick, block pieces in west yard
|
||||
|
||||
#### Plans
|
||||
|
||||
* Fence in west yard
|
||||
* Carport
|
||||
|
||||
### Back Yard
|
||||
|
||||
#### Plans
|
||||
|
||||
* Fire pit
|
||||
* Suspended shades
|
||||
* Eureka palms?
|
||||
|
||||
### Front Yard
|
||||
|
||||
#### Plans
|
||||
|
||||
* Raised beds
|
||||
* Vegetation along driveway
|
||||
* Bahia/Bermuda grass?
|
||||
|
||||
## Needs
|
||||
|
||||
### Appliances
|
||||
@@ -50,9 +120,13 @@ Maybe take back from Val.
|
||||
|
||||
### Furnishings
|
||||
|
||||
#### Tables
|
||||
|
||||
* Coffee table
|
||||
* Dining table (island extension?)
|
||||
|
||||
#### Rugs
|
||||
|
||||
1 each bedroom (3)
|
||||
2--3 living/dining area
|
||||
|
||||
####
|
||||
* 1 each bedroom (total 3)
|
||||
* 2--3 living/dining area
|
||||
* long rug for hallway
|
||||
|
||||
@@ -11,6 +11,7 @@ tags:
|
||||
- type/media/book
|
||||
author: Erving Goffman
|
||||
date: 1959
|
||||
dg-publish: false
|
||||
---
|
||||
# The Presentation of Self in Everyday Life
|
||||
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
---
|
||||
id:
|
||||
aliases:
|
||||
- LICENSE
|
||||
title: UNLICENSE
|
||||
tags:
|
||||
- authorship/other
|
||||
- destiny/permanent
|
||||
- status/complete
|
||||
- topic/meta
|
||||
---
|
||||
# UNLICENSE
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish,
|
||||
|
||||
+6
-1
@@ -2,7 +2,12 @@
|
||||
id:
|
||||
aliases: []
|
||||
title: Advanced Tables
|
||||
tags: []
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/uncertain
|
||||
- status/incomplete
|
||||
- topic/meta
|
||||
dg-publish: true
|
||||
---
|
||||
# Advanced Tables
|
||||
|
||||
|
||||
@@ -141,5 +141,3 @@ but their effects cancel each other out rather than add.
|
||||
|
||||
The Venn Diagram of ferroelectric and ferromagnetic materials
|
||||
has a large but not complete intersection.
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ and undoing years of poor conditioning is... not easy.
|
||||
|
||||
## Naming Conventions (Use Case vs. Description)
|
||||
|
||||
Related topic: [[realism-vs-instrumentalism]]
|
||||
Related topic: [[heuristics#Realism vs. Instrumentalism]]
|
||||
|
||||
Naming by use case is intuitive for those without estimating or field experience,
|
||||
but has the side effect that those accustomed to the names
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
filters:
|
||||
and:
|
||||
- file.folder == "/"
|
||||
- file.ext == "md"
|
||||
- '!file.basename.endsWith(".excalidraw")'
|
||||
views:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title:
|
||||
title: Belle Meade Plaza
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
- occupational/project
|
||||
---
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# Belle Meade Plaza
|
||||
|
||||
@@ -91,7 +91,7 @@ $$
|
||||
where $\mathrm{Var}[C]$ is the [variance](https://en.wikipedia.org/wiki/Variance)
|
||||
and $\lambda>0$ is a risk-loading parameter.
|
||||
|
||||
> This mirrors mean-variance pricing common in portfolio theory.
|
||||
> This mirrors mean-variance pricing common in [[modern-portfolio-theory]].
|
||||
|
||||
## 3. Quantile-based pricing
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ dg-publish: true
|
||||
|
||||
* [ring-billed gull (_Larus delawarensis_)](https://en.wikipedia.org/wiki/Ring-billed_gull)
|
||||
|
||||
* [Laughing gull (_Leucophaeus atricilla_)](https://en.wikipedia.org/wiki/Laughing_gull)
|
||||
|
||||
## [Order Passeriformes ("perching birds")](https://en.wikipedia.org/wiki/Passeriformes)
|
||||
|
||||
> [!info]
|
||||
@@ -63,7 +65,7 @@ dg-publish: true
|
||||
> * _Aigron_ -> "Heron"
|
||||
> * _Aigrette_ -> "Egret"
|
||||
>
|
||||
> which itself just means "screecher".
|
||||
> _Aigron_ itself just means "screecher".
|
||||
|
||||
* [tricolored heron (_E. tricolor_)](https://en.wikipedia.org/wiki/Tricolored_heron)
|
||||
* [little blue heron (_E. caerulea_)](https://en.wikipedia.org/wiki/Little_blue_heron)
|
||||
@@ -73,6 +75,8 @@ dg-publish: true
|
||||
|
||||
* [green heron (_Butorides virescens_)](https://en.wikipedia.org/wiki/Green_heron)
|
||||
|
||||
* [Yellow-crowned night heron (_Nyctanassa violacea_)](https://en.wikipedia.org/wiki/Yellow-crowned_night_heron)
|
||||
|
||||
### [Family Threskiornithidae (Ibises and Spoonbills)](https://en.wikipedia.org/wiki/Threskiornithidae)
|
||||
|
||||
* [American white ibis (_Eudocimus albus_)](https://en.wikipedia.org/wiki/American_white_ibis)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Boll Weevil
|
||||
tags:
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
- topic/hobbies/music/banjo
|
||||
---
|
||||
# Boll Weevil
|
||||
|
||||
Boll weevil told the farmer \
|
||||
"You better treat me right \
|
||||
I'll eat up all o' your cotton \
|
||||
Sleep in your grain rail tonight"
|
||||
|
||||
Boll weevil told the farmer \
|
||||
"You don't need no Ford machine \
|
||||
I'll eat up all of your cotton \
|
||||
Can't buy no gasoline"
|
||||
|
||||
Yonder comes the spider \
|
||||
Crawled up and down the wall \
|
||||
He must've been going \
|
||||
To get his hash's haul
|
||||
|
||||
I don't see no water \
|
||||
But I'm about to drown \
|
||||
I don't see no fire \
|
||||
But I'm burning down
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Empirical Model-Building and Response Surfaces
|
||||
tags:
|
||||
- authorship/other
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
- topic/math/statistics
|
||||
- type/media/book
|
||||
dg-publish: false
|
||||
authors:
|
||||
- George E. P. Box
|
||||
- Norman Richard Draper
|
||||
date: 1987
|
||||
---
|
||||
# Empirical Model-Building and Response Surfaces
|
||||
|
||||
## The Use of Approximating Functions
|
||||
@@ -16,6 +16,7 @@ origlanguage: Czech
|
||||
translator: Dora Round
|
||||
type: incollection
|
||||
year: 1935
|
||||
dg-publish: false
|
||||
---
|
||||
# From the Point of View of a Cat
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Charlotte South End Hotel
|
||||
tags:
|
||||
- occupational/project
|
||||
---
|
||||
# Charlotte South End Hotel
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Chili
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/complete
|
||||
- topic/hobbies/cooking
|
||||
- type/recipe
|
||||
---
|
||||
# Chili
|
||||
|
||||
## Ingredients
|
||||
|
||||
* **2 tbsp.** olive oil
|
||||
* **1** yellow onion, diced
|
||||
* **2 cloves** garlic, minced
|
||||
* **1 lb** ground beef, turkey, or pork
|
||||
* **3⁄4 cup** dry kidney beans, soaked overnight and drained
|
||||
* **3⁄4 cup** dry black beans, soaked overnight and drained
|
||||
* **(1) 15oz can** diced or crushed tomatoes, not drained
|
||||
* **6 oz** canned tomato paste
|
||||
* **1 cup** water
|
||||
|
||||
### Chili Seasoning
|
||||
|
||||
* **1 tbsp.** chili powder
|
||||
* **1 tsp.** ground cumin
|
||||
* **1⁄4 tsp.** cayenne powder
|
||||
* **1⁄4 tsp.** garlic powder
|
||||
* **1⁄2 tsp.** onion powder
|
||||
* **1⁄2 tbsp.** brown sugar
|
||||
* **1 tsp.** salt
|
||||
* **1⁄2 tsp.** black pepper
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Heat the **oil** over medium heat in a large pot.
|
||||
Add the **onion**, season with salt and pepper,
|
||||
and cook until translucent, about 4-5 minutes.
|
||||
|
||||
2. Stir in the **garlic** and cook until fragrant, 30--60 seconds.
|
||||
|
||||
3. Add the **ground meat** and continue to cook,
|
||||
breaking up the meat with a spoon as it cooks,
|
||||
until it is browned and cooked through, about 5 minutes.
|
||||
|
||||
4. Add the **beans**, **tomatoes**, **tomato paste**, **water**, and **chili seasoning**.
|
||||
Stir to combine.
|
||||
|
||||
5. Cover and simmer over low heat for at least 30 minutes, stirring occasionally.
|
||||
|
||||
6. Serve hot with preferred toppings.
|
||||
|
||||
## Notes
|
||||
|
||||
* Makes 6 cups.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Chipotle Black Beans
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/complete
|
||||
- topic/hobbies/cooking
|
||||
- type/recipe
|
||||
---
|
||||
# Chipotle Black Beans
|
||||
|
||||
## Ingredients
|
||||
|
||||
* **2 tbsp.** water or vegetable broth
|
||||
* **1⁄2** medium yellow onion, diced
|
||||
* **3 cloves** garlic, minced
|
||||
* **3 cups** water or unsalted vegetable broth
|
||||
* **1 cup** dried black beans
|
||||
* **2 tsp.** cumin powder
|
||||
* **1 tsp.** Spanish paprika
|
||||
* **1 tsp.** chipotle powder
|
||||
* **1 tbsp.** lime juice
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Set the Instant Pot to Sauté.
|
||||
Add the 2 tbsp. **water or vegetable broth**, **onion** and **garlic**.
|
||||
Sauté for 5 minutes, stirring occasionally, or until the onions are soft and translucent
|
||||
|
||||
2. Add the **water or vegetable broth**, **dried black beans** and **all spices**.
|
||||
Press Stop to reset the Instant Pot,
|
||||
then press the Bean/Chili button (or Manual/Pressure Cook) and set the time to 35 minutes.
|
||||
|
||||
3. When the Instant Pot chimes, press Stop and allow the pressure to bleed off.
|
||||
|
||||
4. When the pressure is released, remove the lid.
|
||||
Add **lime juice** and stir, adding more **chipotle powder** to taste.
|
||||
|
||||
5. Use a handheld strainer to remove the beans.
|
||||
Save the liquid for adding to other recipes if desired.
|
||||
|
||||
## Notes
|
||||
|
||||
* Makes 3 cups
|
||||
|
||||
* If using soaked beans, reduce cooking time by 10--15 minutes.
|
||||
|
||||
* You can double the ingredients in this recipe. Cook for the same time.
|
||||
|
||||
* If the beans are not cooked to your liking at the end of the 35 minutes,
|
||||
you can cook for additional 5 minutes on the same settings.
|
||||
|
||||
* Store in the fridge in an airtight container for up to 5 days.
|
||||
@@ -13,6 +13,7 @@ authors:
|
||||
- James Clear
|
||||
published: 2018-10-16
|
||||
type: book
|
||||
dg-publish: false
|
||||
---
|
||||
# Atomic Habits
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ tags:
|
||||
- destiny/permanent
|
||||
- occupational
|
||||
- type/media
|
||||
- status/incomplete
|
||||
dg-publish: true
|
||||
---
|
||||
# ConEst Pre-Takeoff Email Template
|
||||
|
||||
@@ -49,10 +51,10 @@ Please confirm you have received this email and respond with any questions or co
|
||||
* Cover Material:
|
||||
%% Covers: Brass, Aluminum %%
|
||||
* Wiring Devices:
|
||||
* Units:
|
||||
%% 15A Decora %%
|
||||
* BOH/Common Areas:
|
||||
%% 20A Toggle %%
|
||||
* Units:
|
||||
%% 15A Decora %%
|
||||
* BOH/Common Areas:
|
||||
%% 20A Toggle %%
|
||||
* Switchboards/Panelboards:
|
||||
%% Acceptable Manufacturers, AL or CU Bussing, Breaker Types (Snap-on, bolt on), ACI Ratings %%
|
||||
* Transformers:
|
||||
@@ -62,7 +64,8 @@ Please confirm you have received this email and respond with any questions or co
|
||||
|
||||
### Project Drawing/Detail Callouts
|
||||
|
||||
* Describe conflicts between drawings / proposal: %% Plans show CU feeders, proposal calls for aluminum, Specification grade devices, etc. %%
|
||||
* Describe conflicts between drawings / proposal:
|
||||
%% "Plans show CU feeders, proposal calls for aluminum", Specification grade devices, etc. %%
|
||||
|
||||
* Cost drivers in details not shown elsewhere on plans:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Still deciding if I'm capable of being opinionated without rambling.
|
||||
* [[estimating-methodologies]]
|
||||
* [[construction-estimating-software]]
|
||||
* [[history-of-construction-estimating]]
|
||||
* [[labor-factoring]]
|
||||
* [[estimate-laboring]]
|
||||
|
||||
### New Cross-Topics
|
||||
|
||||
|
||||
+11
-11
@@ -15,6 +15,15 @@ dg-publish: true
|
||||
|
||||
Construction estimating is a subset of cost estimation.
|
||||
|
||||
%%
|
||||
|
||||
## TALK
|
||||
|
||||
This note should be wide and shallow,
|
||||
specific sub topics should be extracted and linked.
|
||||
|
||||
%%
|
||||
|
||||
## Scope
|
||||
|
||||
This note is intended for: _facts_
|
||||
@@ -32,7 +41,7 @@ For philosophy see [[bid-process-strategy]]
|
||||
* [[history-of-construction-estimating]]
|
||||
* [[estimating-methodologies]]
|
||||
* [[construction-estimating-software]]
|
||||
* [[labor-factoring]]
|
||||
* [[estimate-laboring]]
|
||||
|
||||
### Cross-Topics
|
||||
|
||||
@@ -82,13 +91,4 @@ Much bigger people are involved after the proposal is accepted.
|
||||
* [[electrical-estimators-manual]]
|
||||
* [[electrical-estimating-methods]]
|
||||
* [[construction-estimating-using-excel]]
|
||||
* [[mike-holts-illustrated-guide-to-electrical-estimating]]
|
||||
|
||||
%%
|
||||
|
||||
## TALK
|
||||
|
||||
This note should be wide and shallow,
|
||||
specific sub topics should be extracted and linked.
|
||||
|
||||
%%
|
||||
* [[holt_2023_estimating]]
|
||||
|
||||
@@ -54,7 +54,7 @@ Those around elevator shafts form a "shear core".
|
||||
|
||||
> [!info] Reference
|
||||
> * [Concrete Slab | Different types of Slabs in Construction](https://www.constructioncost.co/slabs-in-construction.html)
|
||||
> * [Concrete Slab Types -- Construction, Cost, and Applications -- theconstructor.org](https://theconstructor.org/practical-guide/concrete-slab-construction-cost/28153/)
|
||||
> * [Concrete Slab Types -- Construction, Cost, and Applications | theconstructor.org](https://theconstructor.org/practical-guide/concrete-slab-construction-cost/28153/)
|
||||
|
||||
### Conventional Slab
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ tags:
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
- type/guide
|
||||
- topic/hobbies/digitizing
|
||||
title: Converting Documents to Markdown
|
||||
dg-publish: true
|
||||
---
|
||||
|
||||
+5
-2
@@ -2,7 +2,7 @@
|
||||
id:
|
||||
aliases: []
|
||||
tags:
|
||||
- authorship/original
|
||||
- authorship/other-for-now
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
- topic/math
|
||||
@@ -12,7 +12,10 @@ dg-publish: true
|
||||
---
|
||||
# Convex Hull
|
||||
|
||||
[Convex Hull](https://en.wikipedia.org/wiki/Convex_hull)
|
||||
> [!quote] [Wikipedia](https://en.wikipedia.org/wiki/Convex_hull)
|
||||
> the **convex hull**, **convex envelope** or **convex closure** of a shape
|
||||
> is the smallest [convex set](https://en.wikipedia.org/wiki/Convex_set "Convex set")
|
||||
> that contains it.
|
||||
|
||||
```tikz
|
||||
\usepackage{pgfplots}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Create Your Own Vault
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
dg-publish: true
|
||||
---
|
||||
# Create Your Own Vault
|
||||
|
||||
## Setup Obsidian
|
||||
|
||||
1. Download and install [Obsidian](https://obsidian.md/)
|
||||
|
||||
2. Open Obsidian and select "Create New Vault".
|
||||
|
||||
## Create a GitHub Repository
|
||||
|
||||
1. Create a [GitHub](https://github.com/)
|
||||
account if you don't have one already.
|
||||
|
||||
2. Install [[git|Git]] via the `winget` package manager:
|
||||
|
||||
```
|
||||
winget install Git.Git
|
||||
```
|
||||
|
||||
%% TODO: %%
|
||||
|
||||
## Create and Push your Vault
|
||||
|
||||
```
|
||||
git init
|
||||
git commit -m "initial commit"
|
||||
git branch -M main
|
||||
git remote add origin https://github.com/ExampleName/myVault.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## Set Up Your Vault
|
||||
|
||||
Add and enable the Git Community Plugin.
|
||||
|
||||
%% TODO: %%
|
||||
@@ -7,10 +7,13 @@ tags:
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
- type/encyclopedia-entry
|
||||
dg-publish: true
|
||||
---
|
||||
# C\#
|
||||
|
||||
C\# (read "C sharp", like the [[music-theory|musical]] symbol)
|
||||
is modern high-level [[programming-languages|language]]
|
||||
supporting [[object-oriented-programing|object-oriented]] patterns.
|
||||
|
||||
It is the sane man's Java.
|
||||
|
||||
|
||||
+1
-1
@@ -11,4 +11,4 @@ dg-publish: true
|
||||
---
|
||||
# 2026-01-01
|
||||
|
||||
Made recipe: Granola
|
||||
Made recipe: [[granola]]
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
id: 2026-02-05
|
||||
aliases: []
|
||||
title: 2026-02-05
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
date-created: 2026-03-08T19:22:24-04:00
|
||||
dg-publish: true
|
||||
---
|
||||
# 2026-02-05
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-09
|
||||
aliases: []
|
||||
title: 2026-02-09
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-02-09T19:25:20-05:00
|
||||
weekly: "[[2026-W07]]"
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-09
|
||||
|
||||
Made recipe: [[granola ]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-12
|
||||
aliases: []
|
||||
title: 2026-02-12
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-02-12T19:25:36-05:00
|
||||
weekly: "[[2026-W07]]"
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-12
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-15
|
||||
aliases: []
|
||||
title: 2026-02-15
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
date-created: 2026-02-15T19:26:03-05:00
|
||||
dg-publish: true
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
weekly: "[[2026-W07]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-15
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-18
|
||||
aliases: []
|
||||
title: 2026-02-18
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-02-18T19:26:30-05:00
|
||||
weekly: "[[2026-W08]]"
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-18
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-19
|
||||
aliases: []
|
||||
title: 2026-02-19
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-02-19T19:26:53-05:00
|
||||
weekly: "[[2026-W08]]"
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-19
|
||||
|
||||
Made recipe: [[red-beans-and-rice]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-22
|
||||
aliases: []
|
||||
title: 2026-02-22
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-02-22T19:27:17-05:00
|
||||
weekly: "[[2026-W08]]"
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-22
|
||||
|
||||
Made recipe: [[french-toast]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-24
|
||||
aliases: []
|
||||
title: 2026-02-24
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
date-created: 2026-02-24T19:27:37-05:00
|
||||
dg-publish: true
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
weekly: "[[2026-W09]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-24
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: 2026-02-27
|
||||
aliases: []
|
||||
title: 2026-02-27
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-02-27T09:44:15-05:00
|
||||
weekly: "[[2026-W09]]"
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-27
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-02-28
|
||||
aliases: []
|
||||
title: 2026-02-28
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
date-created: 2026-02-28T09:44:05-05:00
|
||||
dg-publish: true
|
||||
monthly: "[[2026-02]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
weekly: "[[2026-W09]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-02-28
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: 2026-03-01
|
||||
aliases: []
|
||||
title: 2026-03-01
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-03-01T09:43:00-05:00
|
||||
weekly: "[[2026-W09]]"
|
||||
monthly: "[[2026-03]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-03-01
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-03-02
|
||||
aliases: []
|
||||
title: 2026-03-02
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
date-created: 2026-03-02T19:28:12-05:00
|
||||
dg-publish: true
|
||||
monthly: "[[2026-03]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
weekly: "[[2026-W10]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-03-02
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: 2026-03-05
|
||||
aliases: []
|
||||
title: 2026-03-05
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-03-05T08:30:04-05:00
|
||||
weekly: "[[2026-W10]]"
|
||||
monthly: "[[2026-03]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-03-05
|
||||
|
||||
## TODO
|
||||
|
||||
* [ ] Cancel renter's insurance
|
||||
* [ ] Start [St Pete utilities](https://pinellas.gov/services/request-utilities-service/)
|
||||
* [ ] Order washer/dryer
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-03-06
|
||||
aliases: []
|
||||
title: 2026-03-06
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-03-06T19:28:45-05:00
|
||||
weekly: "[[2026-W10]]"
|
||||
monthly: "[[2026-03]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-03-06
|
||||
|
||||
Made recipe: [[granola ]]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
id: 2026-03-08
|
||||
aliases: []
|
||||
title: 2026-03-08
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/draft
|
||||
- type/periodic/daily
|
||||
dg-publish: true
|
||||
date-created: 2026-03-08T08:12:41-04:00
|
||||
weekly: "[[2026-W10]]"
|
||||
monthly: "[[2026-03]]"
|
||||
quarterly: "[[2026-Q1]]"
|
||||
yearly: "[[2026]]"
|
||||
---
|
||||
# 2026-03-08
|
||||
|
||||
Made recipe: [[granola]]
|
||||
@@ -2,7 +2,11 @@
|
||||
id:
|
||||
aliases: []
|
||||
title: Digital Garden Homepage
|
||||
tags: []
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent/entry-point
|
||||
- status/incomplete
|
||||
- topic/meta
|
||||
dg-home: true
|
||||
dg-publish: true
|
||||
---
|
||||
@@ -47,7 +51,7 @@ see [[complaints]] for how you can help me fix it.
|
||||
### Broken Links
|
||||
|
||||
The private repository from which this site is built includes copyrighted materials,
|
||||
(which I own and have personally digitized, as is my right by case law)
|
||||
(which I own and have personally digitized, as is my right, supported by case law)
|
||||
of note, nearly the entirety of the [[nfpa-70_national-electric-code|NEC]],
|
||||
which [[conductor-sizing]] and [[alternating-current]] frequently reference.
|
||||
Because this website is public I choose not to publish those materials here,
|
||||
|
||||
@@ -78,7 +78,7 @@ for consistency.
|
||||
|
||||
> Name = "Power Monitor"
|
||||
|
||||
See [[switchgear#SPD's/TVSS's]]
|
||||
See [[switchgear-takeoff#SPD's/TVSS's]]
|
||||
|
||||
### Switchboards
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ tags:
|
||||
- destiny/permanent
|
||||
- type/task
|
||||
- status/incomplete
|
||||
dg-publish: true
|
||||
---
|
||||
# E-Ink Tablet
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Earned Value Management in Construction Estimating
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
- type/cross-topic
|
||||
---
|
||||
# Earned Value Management in Construction Estimating
|
||||
|
||||
Cross topic of [[earned-value-management]] and [[construction-estimating]].
|
||||
|
||||
The construction industry was an early adopter of EVM[^1]
|
||||
|
||||
[^1]: [Wikipedia](https://en.wikipedia.org/wiki/Earned_value_management)
|
||||
|
||||
I am not a fan of EVM as it is commonly used in construction
|
||||
since its spurious claims of objectivity
|
||||
are accepted without qualification.
|
||||
Estimating is systematically absolved of error,
|
||||
which is blamed on operations
|
||||
(estimate accuracy is not incentivized).
|
||||
|
||||
[[purpose-of-construction-estimating#The Myth of Estimate Accuracy]]
|
||||
@@ -8,6 +8,7 @@ tags:
|
||||
- status/incomplete
|
||||
- topic/ergonomics/organizational
|
||||
- type/encyclopedia-entry
|
||||
dg-publish: true
|
||||
---
|
||||
# Earned Value Management
|
||||
|
||||
@@ -17,8 +18,14 @@ is a [[project-management]] technique
|
||||
|
||||
EVM is frequently stated to be capable of
|
||||
"measuring project performance and progress
|
||||
in an _objective_ manner",[^1]
|
||||
_in an objective manner_",[^1]
|
||||
an analysis that is either optimistic
|
||||
or totally misleading, depending on the context.
|
||||
|
||||
[^1]: [Wikipedia](https://en.wikipedia.org/wiki/Earned_value_management) (emphasis added)
|
||||
|
||||
EVM can be an objective measure of _progress_,
|
||||
but measuring _performance_,
|
||||
variance of actual cost/hours versus estimated,
|
||||
(obviously) requires an estimate,
|
||||
which is subject to bias and error.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Eggroll in a Bowl
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- topic/hobbies/cooking
|
||||
- type/recipe
|
||||
---
|
||||
# Eggroll in a Bowl
|
||||
|
||||
## Ingredients
|
||||
|
||||
* **1 lb** ground pork, beef, or turkey
|
||||
* **1 tbsp.** sesame oil
|
||||
* **1 tbsp.** minced garlic
|
||||
* **1 tbsp.** minced ginger
|
||||
* **8 oz** mushrooms, finely diced
|
||||
* **1⁄2** white or yellow onion, diced
|
||||
* **1⁄4 cup** low-sodium soy sauce
|
||||
* **1 tbsp.** fish sauce
|
||||
* **12--16 oz** cole slaw mix (1 package)
|
||||
* **2 tbsp.** rice wine vinegar
|
||||
* **1 tbsp.** hoisin sauce
|
||||
* **1 tbsp.** sriracha or gochujang
|
||||
|
||||
## Directions
|
||||
|
||||
1. Heat the **oil** in a large pot at medium high.
|
||||
Add the **ground meat** and cook until browned.
|
||||
|
||||
2. Add the **garlic**, **ginger**, **mushrooms**, and **onion**.
|
||||
Saute ~~briefly~~.
|
||||
|
||||
3. Add the **soy sauce** and **fish sauce**.
|
||||
Mix to combine.
|
||||
|
||||
4. Pour the **cole slaw mix** on top, but do not stir.
|
||||
Cover and cook on medium low heat for 10 minutes
|
||||
|
||||
5. Stir and deglaze with the **vinegar**.
|
||||
If cabbage isn't cooked all the way cover again for a few minutes.
|
||||
|
||||
6. Remove from heat.
|
||||
Stir in the **hoisin sauce** and **sriracha or gochujang**.
|
||||
|
||||
7. Serve with wonton strips or over rice.
|
||||
|
||||
@@ -55,6 +55,8 @@ even where serving Amenity spaces.
|
||||
|
||||
Clubline Raceway, also called CRG Raceway,
|
||||
is a glorified cable management trough.
|
||||
It is not a [[nfpa-70_376_metal-wireways|wireway]],
|
||||
so conductors must be run in a [[nfpa-70_national-electric-code#Chapter 3 Wiring Methods and Materials|Chapter 3]] wiring method.
|
||||
|
||||
Count receptacles normally,
|
||||
using ==surface-mount EMT assemblies==.
|
||||
|
||||
+5
-3
@@ -1,10 +1,12 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title:
|
||||
title: Elliot St. Hotel
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
- occupational/project
|
||||
---
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# Elliot St. Hotel
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@ tags:
|
||||
- authorship/original
|
||||
- destiny/permanent/entry-point
|
||||
- status/incomplete
|
||||
- type/supertopic
|
||||
- type/encyclopedia
|
||||
dg-publish: true
|
||||
---
|
||||
# Ergonomics
|
||||
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Estimate Laboring
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/incomplete
|
||||
- topic/automation
|
||||
- topic/estimating
|
||||
- topic/software
|
||||
- type/idea
|
||||
- authorship/original
|
||||
title: Labor Factoring
|
||||
- type/encyclopedia-entry
|
||||
dg-publish: true
|
||||
---
|
||||
# Labor Factoring
|
||||
# Estimate Laboring
|
||||
|
||||
In construction estimating,
|
||||
**laboring** is the process of calculating the labor hours required
|
||||
to install takeoff material.
|
||||
|
||||
## Labor Factoring
|
||||
|
||||
**Labor factoring** is the adjustment of base labor hours
|
||||
to account for adverse or beneficial working conditions.
|
||||
|
||||
Labor factors are most often used to increase labor,
|
||||
but decrease is also possible,
|
||||
especially efficiency gains due to repetitive work.
|
||||
|
||||
%%
|
||||
|
||||
## TODO
|
||||
|
||||
Discuss purpose and practice of labor factoring,
|
||||
@@ -81,7 +95,6 @@ can be converted to functions using regressions.
|
||||
## Going Further
|
||||
|
||||
Base labor units suggest a highly functional nature,
|
||||
most sensitive to weight.
|
||||
most sensitive to _weight_.
|
||||
Greater understanding of this relationship would make practical
|
||||
the prediction of labor of unstudied items.
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@ tags:
|
||||
- status/not-started
|
||||
- topic/estimating
|
||||
- type/encyclopedia-entry
|
||||
dg-publish: true
|
||||
---
|
||||
# Estimating Golf
|
||||
|
||||
[[2025-12-18#Estimating Golf]]
|
||||
[[2025-12-18_14-18-00]]
|
||||
@@ -113,7 +113,7 @@ much more so to dismiss the tests used to prove the validity of statistical meth
|
||||
|
||||
### False Lucky Fools
|
||||
|
||||
Taleb repeatedly conflates legitimate lucky fools
|
||||
Taleb repeatedly conflates legitimate [[lucky-fools|lucky fools]]
|
||||
and people with ideas he doesn't like.
|
||||
|
||||
> [[hubbard_2020_failure]]
|
||||
@@ -133,12 +133,11 @@ One of many problems with this presentation
|
||||
is that John is described as young, inexperienced,
|
||||
and of low intelligence.
|
||||
John is a typical lucky fool:
|
||||
there is no indication that he has any strategy,
|
||||
much less that he is practicing modern portfolio theory.
|
||||
there is no indication that he has _any_ strategy,
|
||||
much less that he is practicing [[modern-portfolio-theory]].
|
||||
|
||||
If I mistake Taleb's intent,
|
||||
and the story was only meant to convey
|
||||
that experienced, conscientious, and cautious decision-making
|
||||
_can_ lead a person to success,
|
||||
then I'm not sure who he's arguing against.
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: French Toast
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- topic/hobbies/cooking
|
||||
- type/recipe
|
||||
---
|
||||
# French Toast
|
||||
|
||||
## Ingredients
|
||||
|
||||
* **4** large eggs or **1 cup** egg substitute
|
||||
* **2⁄3 cup** milk
|
||||
* **1⁄4 cup** all-purpose flour
|
||||
* **1⁄4 cup** granulated sugar
|
||||
* **1⁄4 tsp.** salt
|
||||
* **1 tsp.** ground cinnamon
|
||||
* **1 tsp.** vanilla extract
|
||||
* **8** thick slices bread
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Whisk together the eggs, milk, flour, sugar, salt, cinnamon, and vanilla.
|
||||
|
||||
2. Dip bread slices into the batter, dredging them well on both sides.
|
||||
Place on a greased skillet over medium heat.
|
||||
|
||||
3. Cook until the bottom of the bread starts to turn golden brown.
|
||||
Flip and cook on the other side the same.
|
||||
|
||||
4. Remove to a plate. Serve warm with syrup and a sprinkle of powdered sugar.
|
||||
|
||||
## Notes
|
||||
|
||||
* Bread that is slightly stale is preferable.
|
||||
|
||||
* Any kind of milk, even dairy-free, will work.
|
||||
Higher fat milk will yield creamier batter.
|
||||
|
||||
* The batter can be made up to 2 days ahead of cooking.
|
||||
|
||||
* To freeze, allow french toast cool completely
|
||||
before storing in a freezer-safe bag or container for up to 3 months.
|
||||
|
||||
Rewarm on a skillet, in the toaster, or for a few seconds in the microwave.
|
||||
+2
-1
@@ -61,4 +61,5 @@ dg-publish: true
|
||||
|
||||
## Next Steps
|
||||
|
||||
[[pdi-accubid-closeout]]
|
||||
1. [[pdi-accubid-closeout]]
|
||||
2. [[pdi-wbs]]
|
||||
|
||||
+2
-2
@@ -45,8 +45,8 @@ dg-publish: true
|
||||
|
||||
| Hunter A \ Hunter B | Hunter B: Hunt Stag | Hunter B: Hunt Hare |
|
||||
|:------------------- |:--------------------:|:--------------------:|
|
||||
| Hunter A: Hunt Stag | Eat stag \ Eat stag | Go hungry \ Eat hare |
|
||||
| Hunter A: Hunt Hare | Eat hare \ Go hungry | Eat hare \ Eat hare |
|
||||
| **Hunter A: Hunt Stag** | Eat stag \ Eat stag | Go hungry \ Eat hare |
|
||||
| **Hunter A: Hunt Hare** | Eat hare \ Go hungry | Eat hare \ Eat hare |
|
||||
|
||||
### Ultimatum Game
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Git
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# Git
|
||||
|
||||
[Git](https://git-scm.com/) is a **version control system**
|
||||
|
||||
Git was created by Linus Torvalds
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Goulash
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- topic/hobbies/cooking
|
||||
- type/recipe
|
||||
---
|
||||
# Goulash
|
||||
|
||||
## Ingredients
|
||||
|
||||
* **1 tbsp.** olive oil
|
||||
* **1** yellow onion, diced
|
||||
* **4** cloves garlic, minced
|
||||
* **2** bell peppers, diced
|
||||
* **1 lb** ground beef, turkey, or pork
|
||||
* **1⁄2 cup** red wine (Cabernet Sauvignon)
|
||||
* **(1) 28oz can** canned diced tomatoes (do not drain)
|
||||
* **(1) 15oz can** canned tomato sauce
|
||||
* **2 tbsp.** soy sauce
|
||||
* **1⁄2 cup** water
|
||||
* **8 oz** dry elbow macaroni
|
||||
* **? tsp.** table salt
|
||||
|
||||
### Spices
|
||||
|
||||
* **2** whole bay leaves
|
||||
* **1⁄2 tsp.** dried oregano
|
||||
* **1⁄2 tsp.** dried basil
|
||||
* **1⁄4 tsp.** crushed red pepper
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Heat the **olive oil** in a large soup pot over medium heat.
|
||||
Add the **onion** and **garlic** and sauté until the onions are translucent.
|
||||
|
||||
2. Add the **bell peppers** and continue to sauté for about two minutes more.
|
||||
|
||||
3. Add the **ground meat** and continue to sauté until the beef is cooked through.
|
||||
|
||||
4. Deglaze with the **red wine**.
|
||||
Add the **diced tomatoes**, **tomato sauce**, **soy sauce**, **spices**, and **water**.
|
||||
Stir to combine, then cover and allow the sauce to come to a boil.
|
||||
Once boiling, turn down the heat to low and simmer for 30 minutes, stirring occasionally.
|
||||
|
||||
5. Add the pasta and stir to combine.
|
||||
Re-cover and simmer, stirring occasionally, until the pasta is tender (10--12 minutes).
|
||||
|
||||
6. Remove from heat.
|
||||
Add salt and more to taste.
|
||||
Remove the bay leaves before serving.
|
||||
+5
-3
@@ -1,10 +1,12 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title:
|
||||
title: Grand Concourse
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- status/not-started
|
||||
- occupational/project
|
||||
---
|
||||
- status/not-started
|
||||
dg-publish: true
|
||||
---
|
||||
# Grand Concourse
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id:
|
||||
aliases: []
|
||||
title: Granola
|
||||
tags:
|
||||
- authorship/original
|
||||
- destiny/permanent
|
||||
- topic/hobbies/cooking
|
||||
- type/recipe
|
||||
---
|
||||
# Granola
|
||||
|
||||
## Ingredients
|
||||
|
||||
* **2 cups** old-fashioned rolled oats
|
||||
* **3⁄4 cup** raw nuts and/or seeds
|
||||
* **1⁄2 tsp.** fine-grain sea salt or standard table salt
|
||||
* **1⁄4 tsp.** ground cinnamon
|
||||
* **1⁄4 cup** melted coconut oil or olive oil
|
||||
* **1⁄4 cup** maple syrup or honey
|
||||
* **1⁄2 tsp.** vanilla extract
|
||||
* **1⁄4 cup** coconut flakes
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Preheat oven to 350°F and line a large, rimmed baking sheet with parchment paper.
|
||||
|
||||
2. In a large mixing bowl, combine the **oats**, **nuts and/or seeds**, **salt**, and **cinnamon**.
|
||||
Stir to blend.
|
||||
|
||||
3. Pour in the **oil**, **maple syrup or honey**, and **vanilla**.
|
||||
Mix well, until every oat is lightly coated.
|
||||
Pour the granola onto the prepared pan
|
||||
and use a large spoon to spread it into an even layer.
|
||||
|
||||
4. Bake for 10 minutes, add **coconut flakes** and stir.
|
||||
Press the stirred granola into the pan with a spatula to create a more even layer.
|
||||
|
||||
5. Bake until lightly golden (10--12 minutes).
|
||||
|
||||
6. Let the granola cool completely, undisturbed (at least 45 minutes).
|
||||
|
||||
## Notes
|
||||
|
||||
* Store in an airtight container at room temperature for up to 2 weeks,
|
||||
or in a sealed freezer bag in the freezer for up to 3 months.
|
||||
|
||||
* Use olive oil for a slightly savory granola,
|
||||
or coconut oil for a more neutral flavor.
|
||||
|
||||
* Use maple syrup for a sweet, distinctly maple-flavored granola
|
||||
with few or no clumps.
|
||||
|
||||
Use honey for a more neutral flavored, clumpy granola.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user