vault backup: 2024-09-30 23:15:41

This commit is contained in:
sdfsdf 2024-09-30 23:15:46 +02:00
parent 29cfb8ab81
commit 6c7d6baf4c
24 changed files with 51195 additions and 26 deletions

View File

@ -3,5 +3,9 @@
"obsidian-style-settings", "obsidian-style-settings",
"obsidian-icon-folder", "obsidian-icon-folder",
"obsidian-file-color", "obsidian-file-color",
"obsidian-git" "obsidian-git",
"tag-wrangler",
"obsidian-thumbnails",
"dataview",
"awesome-image"
] ]

View File

@ -0,0 +1,291 @@
let Plugin = class {}
let MarkdownRenderer = {}
let MarkdownRenderChild = class {}
let htmlToMarkdown = (html) => html
if (isObsidian()) {
const obsidian = require('obsidian')
Plugin = obsidian.Plugin
MarkdownRenderer = obsidian.MarkdownRenderer
MarkdownRenderChild = obsidian.MarkdownRenderChild
htmlToMarkdown = obsidian.htmlToMarkdown
}
const codeblockId = 'table-of-contents'
const codeblockIdShort = 'toc'
const availableOptions = {
title: {
type: 'string',
default: '',
comment: '',
},
style: {
type: 'value',
default: 'nestedList',
values: ['nestedList', 'nestedOrderedList', 'inlineFirstLevel'],
comment: 'TOC style (nestedList|nestedOrderedList|inlineFirstLevel)',
},
minLevel: {
type: 'number',
default: 0,
comment: 'Include headings from the specified level',
},
maxLevel: {
type: 'number',
default: 0,
comment: 'Include headings up to the specified level',
},
includeLinks: {
type: 'boolean',
default: true,
comment: 'Make headings clickable',
},
debugInConsole: {
type: 'boolean',
default: false,
comment: 'Print debug info in Obsidian console',
},
}
class ObsidianAutomaticTableOfContents extends Plugin {
async onload() {
const handler = (sourceText, element, context) => {
context.addChild(new Renderer(this.app, element, context.sourcePath, sourceText))
}
this.registerMarkdownCodeBlockProcessor(codeblockId, handler)
this.registerMarkdownCodeBlockProcessor(codeblockIdShort, handler)
this.addCommand({
id: 'insert-automatic-table-of-contents',
name: 'Insert table of contents',
editorCallback: onInsertToc,
})
this.addCommand({
id: 'insert-automatic-table-of-contents-docs',
name: 'Insert table of contents (documented)',
editorCallback: onInsertTocWithDocs,
})
}
}
function onInsertToc(editor) {
const markdown = '```' + codeblockId + '\n```'
editor.replaceRange(markdown, editor.getCursor())
}
function onInsertTocWithDocs(editor) {
let markdown = ['```' + codeblockId]
Object.keys(availableOptions).forEach((optionName) => {
const option = availableOptions[optionName]
const comment = option.comment.length > 0 ? ` # ${option.comment}` : ''
markdown.push(`${optionName}: ${option.default}${comment}`)
})
markdown.push('```')
editor.replaceRange(markdown.join('\n'), editor.getCursor())
}
class Renderer extends MarkdownRenderChild {
constructor(app, element, sourcePath, sourceText) {
super(element)
this.app = app
this.element = element
this.sourcePath = sourcePath
this.sourceText = sourceText
}
// Render on load
onload() {
this.render()
this.registerEvent(this.app.metadataCache.on('changed', this.onMetadataChange.bind(this)))
}
// Render on file change
onMetadataChange() {
this.render()
}
render() {
try {
const options = parseOptionsFromSourceText(this.sourceText)
if (options.debugInConsole) debug('Options', options)
const metadata = this.app.metadataCache.getCache(this.sourcePath)
const headings = metadata && metadata.headings ? metadata.headings : []
if (options.debugInConsole) debug('Headings', headings)
const markdown = getMarkdownFromHeadings(headings, options)
if (options.debugInConsole) debug('Markdown', markdown)
this.element.empty()
MarkdownRenderer.renderMarkdown(markdown, this.element, this.sourcePath, this)
} catch(error) {
const readableError = `_💥 Could not render table of contents (${error.message})_`
MarkdownRenderer.renderMarkdown(readableError, this.element, this.sourcePath, this)
}
}
}
function getMarkdownFromHeadings(headings, options) {
const markdownHandlersByStyle = {
nestedList: getMarkdownNestedListFromHeadings,
nestedOrderedList: getMarkdownNestedOrderedListFromHeadings,
inlineFirstLevel: getMarkdownInlineFirstLevelFromHeadings,
}
let markdown = ''
if (options.title && options.title.length > 0) {
markdown += options.title + '\n'
}
const noHeadingMessage = '_Table of contents: no headings found_'
markdown += markdownHandlersByStyle[options.style](headings, options) || noHeadingMessage
return markdown
}
function getMarkdownNestedListFromHeadings(headings, options) {
return getMarkdownListFromHeadings(headings, false, options)
}
function getMarkdownNestedOrderedListFromHeadings(headings, options) {
return getMarkdownListFromHeadings(headings, true, options)
}
function getMarkdownListFromHeadings(headings, isOrdered, options) {
const prefix = isOrdered ? '1.' : '-'
const lines = []
const minLevel = options.minLevel > 0
? options.minLevel
: Math.min(...headings.map((heading) => heading.level))
headings.forEach((heading) => {
if (heading.level < minLevel) return
if (options.maxLevel > 0 && heading.level > options.maxLevel) return
lines.push(`${'\t'.repeat(heading.level - minLevel)}${prefix} ${getMarkdownHeading(heading, options)}`)
})
return lines.length > 0 ? lines.join('\n') : null
}
function getMarkdownInlineFirstLevelFromHeadings(headings, options) {
const minLevel = options.minLevel > 0
? options.minLevel
: Math.min(...headings.map((heading) => heading.level))
const items = headings
.filter((heading) => heading.level === minLevel)
.map((heading) => {
return getMarkdownHeading(heading, options)
})
return items.length > 0 ? items.join(' | ') : null
}
function getMarkdownHeading(heading, options) {
const stripMarkdown = (text) => {
text = text.replaceAll('*', '').replaceAll('_', '').replaceAll('`', '')
text = text.replaceAll('==', '').replaceAll('~~', '')
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Strip markdown links
return text
}
const stripHtml = (text) => stripMarkdown(htmlToMarkdown(text))
const stripWikilinks = (text, isForLink) => {
// Strip [[link|text]] format
// For the text part of the final link we only keep "text"
// For the link part we need the text + link
// Example: "# Some [[file.md|heading]]" must be translated to "[[#Some file.md heading|Some heading]]"
text = text.replace(/\[\[([^\]]+)\|([^\]]+)\]\]/g, isForLink ? '$1 $2' : '$2')
text = text.replace(/\[\[([^\]]+)\]\]/g, '$1') // Strip [[link]] format
// Replace malformed links & reserved wikilinks chars
text = text.replaceAll('[[', '').replaceAll('| ', isForLink ? '' : '- ').replaceAll('|', isForLink ? ' ' : '-')
return text
}
const stripTags = (text) => text.replaceAll('#', '')
if (options.includeLinks) {
// Remove markdown, HTML & wikilinks from text for readability, as they are not rendered in a wikilink
let text = heading.heading
text = stripMarkdown(text)
text = stripHtml(text)
text = stripWikilinks(text, false)
// Remove wikilinks & tags from link or it won't be clickable (on the other hand HTML & markdown must stay)
let link = heading.heading
link = stripWikilinks(link, true)
link = stripTags(link)
// Return wiklink style link
return `[[#${link}|${text}]]`
// Why not markdown links? Because even if it looks like the text part would have a better compatibility
// with complex headings (as it would support HTML, markdown, etc) the link part is messy,
// because it requires some encoding that looks buggy and undocumented; official docs state the link must be URL encoded
// (https://help.obsidian.md/Linking+notes+and+files/Internal+links#Supported+formats+for+internal+links)
// but it doesn't work properly, example: "## Some <em>heading</em> with simple HTML" must be encoded as:
// [Some <em>heading</em> with simple HTML](#Some%20<em>heading</em>%20with%20simpler%20HTML)
// and not
// [Some <em>heading</em> with simple HTML](#Some%20%3Cem%3Eheading%3C%2Fem%3E%20with%20simpler%20HTML)
// Also it won't be clickable at all if the heading contains #tags or more complex HTML
// (example: ## Some <em style="background: red">heading</em> #with-a-tag)
// (unless there is a way to encode these use cases that I didn't find)
}
return heading.heading
}
function parseOptionsFromSourceText(sourceText = '') {
const options = {}
Object.keys(availableOptions).forEach((option) => {
options[option] = availableOptions[option].default
})
sourceText.split('\n').forEach((line) => {
const option = parseOptionFromSourceLine(line)
if (option !== null) {
options[option.name] = option.value
}
})
return options
}
function parseOptionFromSourceLine(line) {
const matches = line.match(/([a-zA-Z0-9._ ]+):(.*)/)
if (line.startsWith('#') || !matches) return null
const possibleName = matches[1].trim()
const optionParams = availableOptions[possibleName]
let possibleValue = matches[2].trim()
if (!optionParams || optionParams.type !== 'string') {
// Strip comments from values except for strings (as a string may contain markdown)
possibleValue = possibleValue.replace(/#[^#]*$/, '').trim()
}
const valueError = new Error(`Invalid value for \`${possibleName}\``)
if (optionParams && optionParams.type === 'number') {
const value = parseInt(possibleValue)
if (value < 0) throw valueError
return { name: possibleName, value }
}
if (optionParams && optionParams.type === 'boolean') {
if (!['true', 'false'].includes(possibleValue)) throw valueError
return { name: possibleName, value: possibleValue === 'true' }
}
if (optionParams && optionParams.type === 'value') {
if (!optionParams.values.includes(possibleValue)) throw valueError
return { name: possibleName, value: possibleValue }
}
if (optionParams && optionParams.type === 'string') {
return { name: possibleName, value: possibleValue }
}
return null
}
function debug(type, data) {
console.log(...[
`%cAutomatic Table Of Contents %c${type}:\n`,
'color: orange; font-weight: bold',
'font-weight: bold',
data,
])
}
function isObsidian() {
if (typeof process !== 'object') {
return true // Obsidian mobile doesn't have a global process object
}
return !process.env || !process.env.JEST_WORKER_ID // Jest runtime is not Obsidian
}
if (isObsidian()) {
module.exports = ObsidianAutomaticTableOfContents
} else {
module.exports = {
parseOptionsFromSourceText,
getMarkdownFromHeadings,
}
}

View File

@ -0,0 +1,10 @@
{
"id": "automatic-table-of-contents",
"name": "Automatic Table Of Contents",
"version": "1.4.0",
"minAppVersion": "1.3.0",
"description": "Create a table of contents in a note, that updates itself when the note changes",
"author": "Johan Satgé",
"authorUrl": "https://github.com/johansatge",
"isDesktopOnly": false
}

View File

@ -0,0 +1,34 @@
{
"viewImageEditor": true,
"viewImageInCPB": true,
"viewImageWithALink": true,
"viewImageOther": true,
"pinMode": false,
"pinMaximum": 3,
"pinCoverMode": true,
"imageMoveSpeed": 10,
"imgTipToggle": true,
"imgFullScreenMode": "FIT",
"imgViewBackgroundColor": "#00000000",
"imageBorderToggle": false,
"imageBorderWidth": "medium",
"imageBorderStyle": "solid",
"imageBorderColor": "red",
"galleryNavbarToggle": true,
"galleryNavbarDefaultColor": "#0000001A",
"galleryNavbarHoverColor": "#0000004D",
"galleryImgBorderActive": true,
"galleryImgBorderActiveColor": "#FF0000",
"moveTheImageHotkey": "ALT",
"switchTheImageHotkey": "NONE",
"doubleClickToolbar": "toolbar_full_screen",
"viewTriggerHotkey": "CTRL",
"realTimeUpdate": true,
"excludedFolders": [
".git/",
".obsidian/",
".trash/"
],
"includedFileRegex": ".*\\.md",
"mediaRootDirectory": "Misc/Storage/Attachments"
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
{
"id": "awesome-image",
"name": "Awesome Image",
"version": "0.1.3",
"minAppVersion": "0.15.0",
"description": "One-stop solution for image management.",
"author": "AwesomeDog",
"authorUrl": "https://github.com/AwesomeDog",
"isDesktopOnly": true
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,27 @@
{
"renderNullAs": "\\-",
"taskCompletionTracking": false,
"taskCompletionUseEmojiShorthand": false,
"taskCompletionText": "completion",
"taskCompletionDateFormat": "yyyy-MM-dd",
"recursiveSubTaskCompletion": false,
"warnOnEmptyResult": true,
"refreshEnabled": true,
"refreshInterval": 2500,
"defaultDateFormat": "dd MMMM yyyy",
"defaultDateTimeFormat": "h:mm a - dd MMMM yyyy",
"maxRecursiveRenderDepth": 4,
"tableIdColumnName": "File",
"tableGroupColumnName": "Group",
"showResultCount": false,
"allowHtml": true,
"inlineQueryPrefix": "=",
"inlineJsQueryPrefix": "$=",
"inlineQueriesInCodeblocks": true,
"enableInlineDataview": true,
"enableDataviewJs": true,
"enableInlineDataviewJs": true,
"prettyRenderInlineFields": true,
"prettyRenderInlineFieldsInLivePreview": true,
"dataviewJsKeyword": "dataviewjs"
}

20723
GGDoc/.obsidian/plugins/dataview/main.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,11 @@
{
"id": "dataview",
"name": "Dataview",
"version": "0.5.67",
"minAppVersion": "0.13.11",
"description": "Complex data views for the data-obsessed.",
"author": "Michael Brenan <blacksmithgu@gmail.com>",
"authorUrl": "https://github.com/blacksmithgu",
"helpUrl": "https://blacksmithgu.github.io/obsidian-dataview/",
"isDesktopOnly": false
}

View File

@ -0,0 +1,146 @@
/** Live Preview padding fixes, specifically for DataviewJS custom HTML elements. */
.is-live-preview .block-language-dataviewjs > p, .is-live-preview .block-language-dataviewjs > span {
line-height: 1.0;
}
.block-language-dataview {
overflow-y: auto;
}
/*****************/
/** Table Views **/
/*****************/
/* List View Default Styling; rendered internally as a table. */
.table-view-table {
width: 100%;
}
.table-view-table > thead > tr, .table-view-table > tbody > tr {
margin-top: 1em;
margin-bottom: 1em;
text-align: left;
}
.table-view-table > tbody > tr:hover {
background-color: var(--table-row-background-hover);
}
.table-view-table > thead > tr > th {
font-weight: 700;
font-size: larger;
border-top: none;
border-left: none;
border-right: none;
border-bottom: solid;
max-width: 100%;
}
.table-view-table > tbody > tr > td {
text-align: left;
border: none;
font-weight: 400;
max-width: 100%;
}
.table-view-table ul, .table-view-table ol {
margin-block-start: 0.2em !important;
margin-block-end: 0.2em !important;
}
/** Rendered value styling for any view. */
.dataview-result-list-root-ul {
padding: 0em !important;
margin: 0em !important;
}
.dataview-result-list-ul {
margin-block-start: 0.2em !important;
margin-block-end: 0.2em !important;
}
/** Generic grouping styling. */
.dataview.result-group {
padding-left: 8px;
}
/*******************/
/** Inline Fields **/
/*******************/
.dataview.inline-field-key {
padding-left: 8px;
padding-right: 8px;
font-family: var(--font-monospace);
background-color: var(--background-primary-alt);
color: var(--text-nav-selected);
}
.dataview.inline-field-value {
padding-left: 8px;
padding-right: 8px;
font-family: var(--font-monospace);
background-color: var(--background-secondary-alt);
color: var(--text-nav-selected);
}
.dataview.inline-field-standalone-value {
padding-left: 8px;
padding-right: 8px;
font-family: var(--font-monospace);
background-color: var(--background-secondary-alt);
color: var(--text-nav-selected);
}
/***************/
/** Task View **/
/***************/
.dataview.task-list-item, .dataview.task-list-basic-item {
margin-top: 3px;
margin-bottom: 3px;
transition: 0.4s;
}
.dataview.task-list-item:hover, .dataview.task-list-basic-item:hover {
background-color: var(--text-selection);
box-shadow: -40px 0 0 var(--text-selection);
cursor: pointer;
}
/*****************/
/** Error Views **/
/*****************/
div.dataview-error-box {
width: 100%;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
border: 4px dashed var(--background-secondary);
}
.dataview-error-message {
color: var(--text-muted);
text-align: center;
}
/*************************/
/** Additional Metadata **/
/*************************/
.dataview.small-text {
font-size: smaller;
color: var(--text-muted);
margin-left: 3px;
}
.dataview.small-text::before {
content: "(";
}
.dataview.small-text::after {
content: ")";
}

View File

@ -78,11 +78,11 @@
"color": "6MiKFs3bdHnnogc4PtXTJ" "color": "6MiKFs3bdHnnogc4PtXTJ"
}, },
{ {
"path": "Design", "path": "10 Design",
"color": "CPrDSGPNfbzbEdB5ZVbIp" "color": "CPrDSGPNfbzbEdB5ZVbIp"
}, },
{ {
"path": "Tech", "path": "20 Tech",
"color": "l9aBWg3KMh7IeMZWLwcjR" "color": "l9aBWg3KMh7IeMZWLwcjR"
} }
] ]

View File

@ -0,0 +1,8 @@
{
"storeInfo": true,
"saveImages": true,
"imageLocation": "specifiedFolder",
"imageFolder": "Misc/Storage/Thumbnails",
"responsiveCardStyle": true,
"youtubeApiKey": ""
}

View File

@ -0,0 +1,616 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __export = (target, all) => {
__markAsModule(target);
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __reExport = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
}
return target;
};
var __toModule = (module2) => {
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// main.ts
__export(exports, {
default: () => ThumbyPlugin
});
var import_obsidian2 = __toModule(require("obsidian"));
// settings.ts
var import_obsidian = __toModule(require("obsidian"));
var ThumbySettingTab = class extends import_obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", { text: "Thumbnails Settings" });
console.log(this.plugin.settings);
new import_obsidian.Setting(containerEl).setName("Save Thumbnail Info").setDesc("Save thumbnail information inside your note, so they work offline").addToggle((toggle) => toggle.setValue(this.plugin.settings.storeInfo).onChange((value) => __async(this, null, function* () {
this.plugin.settings.storeInfo = value;
yield this.plugin.saveSettings();
this.display();
})));
if (this.plugin.settings.storeInfo) {
new import_obsidian.Setting(containerEl).setName("Save Images").setDesc("Save thumbnail images locally in vault").addToggle((toggle) => toggle.setValue(this.plugin.settings.saveImages).onChange((value) => __async(this, null, function* () {
this.plugin.settings.saveImages = value;
yield this.plugin.saveSettings();
this.display();
})));
if (this.plugin.settings.saveImages) {
new import_obsidian.Setting(containerEl).setName("Image Location").setDesc("Where thumbnail images should be saved").addDropdown((dropdown) => dropdown.addOption("defaultAttachment", "Default attachment location").addOption("specifiedFolder", "In the folder specified below").setValue(this.plugin.settings.imageLocation).onChange((value) => __async(this, null, function* () {
this.plugin.settings.imageLocation = value;
this.display();
yield this.plugin.saveSettings();
})));
if (this.plugin.settings.imageLocation === "defaultAttachment") {
const attachmentLocation = this.app.vault.getConfig("attachmentFolderPath");
new import_obsidian.Setting(containerEl).setName("Default attachment location").setDesc("Options > Files & Links > Default location for new attachments").addText((text) => text.setValue(attachmentLocation).setDisabled(true)).setClass("default-attachment-info");
} else if (this.plugin.settings.imageLocation === "specifiedFolder") {
new import_obsidian.Setting(containerEl).setName("Image Folder").setDesc("The folder where thumbnail images should be saved").addText((text) => text.setPlaceholder("ex: Files/Thumbnails").setValue(this.plugin.settings.imageFolder).onChange((value) => __async(this, null, function* () {
this.plugin.settings.imageFolder = value;
yield this.plugin.saveSettings();
})));
}
}
}
new import_obsidian.Setting(containerEl).setName("Responsive Card-Style Thumbnails").setDesc("Switch to card-style thumbnails for narrow screens").addToggle((toggle) => toggle.setValue(this.plugin.settings.responsiveCardStyle).onChange((value) => __async(this, null, function* () {
this.plugin.settings.responsiveCardStyle = value;
yield this.plugin.saveSettings();
this.display();
})));
new import_obsidian.Setting(containerEl).setName("YouTube API Key (optional)").setDesc("An API Key for the YouTube Data API").addExtraButton((btn) => btn.setIcon("info").setTooltip("A few videos have been discovered that can't be found the normal way. If you provide an API key for the YouTube Data API, this plugin will use the API as a backup.", { placement: "top" }).setDisabled(true)).addText((text) => text.setValue(this.plugin.settings.youtubeApiKey).onChange((value) => __async(this, null, function* () {
this.plugin.settings.youtubeApiKey = value;
yield this.plugin.saveSettings();
})));
}
};
// main.ts
var DEFAULT_SETTINGS = {
storeInfo: false,
saveImages: false,
imageLocation: "defaultAttachment",
imageFolder: "",
responsiveCardStyle: true,
youtubeApiKey: ""
};
var URL_TYPES = {
youtube: [
{ match: "https://www.youtube.com/watch?v=", idPattern: /v=([-\w\d]+)/ },
{ match: "https://youtu.be/", idPattern: /youtu.be\/([-\w\d]+)/ },
{ match: "youtube.com/shorts/", idPattern: /shorts\/([-\w\d]+)/ },
{ match: "youtube.com/live/", idPattern: /live\/(\w+)/ }
],
vimeo: [
{ match: "https://vimeo.com/", idPattern: /vimeo.com\/([\w\d]+)/ }
]
};
var ThumbyPlugin = class extends import_obsidian2.Plugin {
loadSettings() {
return __async(this, null, function* () {
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
});
}
saveSettings() {
return __async(this, null, function* () {
yield this.saveData(this.settings);
const editors = document.querySelectorAll(".cm-editor");
for (const key in editors) {
if (Object.prototype.hasOwnProperty.call(editors, key)) {
const editor = editors[key];
this.responsiveCardCheck(editor);
}
}
});
}
responsiveCardCheck(editor) {
const vidBlocks = editor.querySelectorAll(".block-language-vid");
for (const key in vidBlocks) {
if (Object.prototype.hasOwnProperty.call(vidBlocks, key)) {
const block = vidBlocks[key];
if (this.settings.responsiveCardStyle && block && block.offsetWidth < 370) {
block.addClass("thumbnail-card-style");
} else {
block.removeClass("thumbnail-card-style");
}
}
}
}
onload() {
return __async(this, null, function* () {
yield this.loadSettings();
this.addSettingTab(new ThumbySettingTab(this.app, this));
this.editorObserver = new ResizeObserver((entries) => {
for (const editor of entries) {
this.responsiveCardCheck(editor.target);
}
});
const editors = document.querySelectorAll(".cm-editor");
for (const key in editors) {
if (Object.prototype.hasOwnProperty.call(editors, key)) {
const editor = editors[key];
this.editorObserver.observe(editor);
}
}
this.registerMarkdownCodeBlockProcessor("vid", (source, el, ctx) => __async(this, null, function* () {
var _a, _b, _c;
this.createDummyBlock(el);
const sourceLines = source.trim().split("\n");
const url = sourceLines[0];
let info;
if (this.settings.storeInfo) {
info = this.parseStoredInfo(source);
}
if (!this.settings.storeInfo || !info.infoStored) {
info = yield this.getVideoInfo(url);
}
if (info.networkError && !info.infoStored) {
this.removeDummyBlock(el);
const url2 = source.trim().split("\n")[0];
el.createEl("a", { text: url2, href: url2 });
return;
}
const sourcePath = typeof ctx == "string" ? ctx : (_c = (_b = ctx == null ? void 0 : ctx.sourcePath) != null ? _b : (_a = this.app.workspace.getActiveFile()) == null ? void 0 : _a.path) != null ? _c : "";
if (!info.vidFound) {
const component = new import_obsidian2.MarkdownRenderChild(el);
this.removeDummyBlock(el);
import_obsidian2.MarkdownRenderer.renderMarkdown(`>[!WARNING] Cannot find video
>${info.url}`, el, sourcePath, component);
return;
}
if (this.hasManyUrls(sourceLines)) {
const component = new import_obsidian2.MarkdownRenderChild(el);
this.removeDummyBlock(el);
import_obsidian2.MarkdownRenderer.renderMarkdown(`>[!WARNING] Cannot accept multiple URLs yet`, el, sourcePath, component);
return;
}
if (this.settings.storeInfo && !info.infoStored) {
this.storeVideoInfo(info, el, ctx);
}
if (!this.settings.storeInfo && sourceLines.length > 1) {
this.removeStoredInfo(info, el, ctx);
}
this.removeDummyBlock(el);
this.createThumbnail(el, info);
}));
this.addCommand({
id: "insert-thumbnail-from-clipboard",
name: "Insert thumbnail from URL in clipboard",
editorCallback: (editor, view) => __async(this, null, function* () {
const clipText = yield navigator.clipboard.readText();
const id = yield this.getVideoId(clipText);
if (id === "") {
new import_obsidian2.Notice("No valid video in clipboard", 2e3);
return;
}
editor.replaceSelection(`\`\`\`vid
${clipText}
\`\`\``);
})
});
this.addCommand({
id: "insert-video-title-link",
name: "Insert link with video title from URL in clipboard",
editorCallback: (editor, view) => __async(this, null, function* () {
const clipText = yield navigator.clipboard.readText();
const id = yield this.getVideoId(clipText);
if (id === "") {
new import_obsidian2.Notice("No valid video in clipboard", 2e3);
return;
}
const info = yield this.getVideoInfo(clipText);
editor.replaceSelection(`[${info.title}](${info.url})`);
})
});
});
}
onunload() {
this.editorObserver.disconnect();
}
hasManyUrls(lines) {
return lines.length > 1 && lines.every((e) => /^((https*:\/\/)|(www\.))+\S*$/.test(e.trim()));
}
createThumbnail(el, info) {
let thumbnailUrl = info.thumbnail;
if (this.pathIsLocal(thumbnailUrl)) {
const file = this.app.vault.getAbstractFileByPath(thumbnailUrl);
if (file) {
thumbnailUrl = this.app.vault.getResourcePath(file);
}
}
const container = el.createEl("a", { href: info.url });
container.addClass("thumbnail");
container.createEl("img", { attr: { "src": thumbnailUrl } }).addClass("thumbnail-img");
const textBox = container.createDiv();
textBox.addClass("thumbnail-text");
textBox.createDiv({ text: info.title, title: info.title }).addClass("thumbnail-title");
textBox.createEl("a", { text: info.author, href: info.authorUrl, title: info.author }).addClass("thumbnail-author");
const timestamp = this.getTimestamp(info.url);
if (timestamp !== "") {
container.createDiv({ text: timestamp }).addClass("timestamp");
}
}
createDummyBlock(el) {
const container = el.createDiv();
container.addClass("dummy-container");
}
removeDummyBlock(el) {
const dummy = el.querySelector(".dummy-container");
if (dummy) {
el.removeChild(dummy);
}
}
getTimestamp(url) {
let tIndex = url.indexOf("?t=");
if (tIndex === -1) {
tIndex = url.indexOf("&t=");
}
if (tIndex === -1) {
tIndex = url.indexOf("#t=");
}
if (tIndex === -1) {
return "";
}
const search = /[?&#]t=(?:(\d+)h)*(?:(\d+)m)*(?:(\d+)s)*(\d+)*/.exec(url);
search.shift();
const times = search.map((v) => parseInt(v) || 0);
let seconds = times.pop();
if (times[2] > 59) {
seconds = times[2];
}
if (seconds) {
times[2] = seconds % 60;
times[1] = Math.floor(seconds / 60) % 60;
times[0] = Math.floor(seconds / 3600);
}
const secStr = String(times[2]).padStart(2, "0");
let minStr = String(times[1]);
const hrStr = String(times[0]);
let timeStr = `${minStr}:${secStr}`;
if (times[0]) {
minStr = minStr.padStart(2, "0");
timeStr = `${hrStr}:${minStr}:${secStr}`;
}
return timeStr;
}
pathIsLocal(path) {
return path.indexOf("https://") !== 0;
}
parseStoredInfo(source) {
const info = {
url: "",
thumbnail: "",
title: "",
author: "",
authorUrl: "",
vidFound: false,
networkError: false,
infoStored: false,
imageSaved: false
};
const input = source.trim().split("\n");
if (input.length !== 5) {
return info;
}
const parsedInput = {
Url: "",
Title: "",
Author: "",
Thumbnail: "",
AuthorUrl: ""
};
for (const [i, line] of input.entries()) {
if (i !== 0) {
const matches = line.match(/(\w+): (.+)/);
if (matches === null) {
return info;
}
const key = matches[1];
const val = matches[2];
parsedInput[key] = val;
} else {
parsedInput["Url"] = input[0];
}
}
for (const key in parsedInput) {
if (Object.prototype.hasOwnProperty.call(parsedInput, key)) {
const value = parsedInput[key];
if (!value || value === "") {
return info;
}
}
}
info.url = parsedInput["Url"];
info.title = parsedInput["Title"];
info.author = parsedInput["Author"];
info.thumbnail = parsedInput["Thumbnail"];
info.authorUrl = parsedInput["AuthorUrl"];
info.vidFound = true;
if (this.pathIsLocal(info.thumbnail)) {
const existingFile = this.app.vault.getAbstractFileByPath(info.thumbnail);
if (existingFile) {
info.imageSaved = true;
} else if (this.settings.saveImages) {
return info;
}
if (!this.settings.saveImages) {
return info;
}
} else if (this.settings.saveImages) {
return info;
}
info.infoStored = true;
return info;
}
storeVideoInfo(info, el, ctx) {
return __async(this, null, function* () {
const section = ctx.getSectionInfo(el);
if (!section) {
return;
}
if (this.settings.saveImages && !info.imageSaved) {
info.thumbnail = yield this.saveImage(info);
}
const content = `\`\`\`vid
${info.url}
Title: ${info.title}
Author: ${info.author}
Thumbnail: ${info.thumbnail}
AuthorUrl: ${info.authorUrl}
\`\`\``;
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
if (view) {
const startPos = {
line: section.lineStart,
ch: 0
};
const endPos = {
line: section.lineEnd,
ch: view.editor.getLine(section.lineEnd).length
};
view.editor.replaceRange(content, startPos, endPos);
}
});
}
saveImage(info) {
return __async(this, null, function* () {
const id = yield this.getVideoId(info.url);
let filePath = "";
const currentNote = this.app.workspace.getActiveFile();
if (this.settings.imageLocation === "specifiedFolder") {
filePath = `${this.settings.imageFolder}/${id}.jpg`;
} else {
filePath = yield this.app.vault.getAvailablePathForAttachments(id, "jpg", currentNote);
const pathRegex = /(.*) \d+\.jpg/;
filePath = filePath.replace(pathRegex, "$1.jpg");
}
const existingFile = this.app.vault.getAbstractFileByPath(filePath);
if (existingFile) {
return existingFile.path;
}
const folderMatch = filePath.match(/(.+)\/.+\.jpg/);
if (folderMatch) {
const folderPath = folderMatch[1];
const existingFolder = this.app.vault.getAbstractFileByPath(folderPath);
if (this.settings.imageLocation === "specifiedFolder" && !existingFolder) {
new import_obsidian2.Notice(`Thumbnails: The folder you specified (${this.settings.imageFolder}) does not exist.`);
return info.thumbnail;
}
}
const reqParam = {
url: info.thumbnail
};
let file;
try {
const req = yield (0, import_obsidian2.requestUrl)(reqParam);
if (req.status === 200) {
file = yield this.app.vault.createBinary(filePath, req.arrayBuffer);
} else {
}
} catch (error) {
console.log(error);
return info.thumbnail;
}
if (file) {
const localUrl = file.path;
return localUrl;
}
return info.thumbnail;
});
}
getTrimmedResourcePath(file) {
const path = this.app.vault.getResourcePath(file);
const endPos = path.indexOf(".jpg") + 4;
return path.substring(0, endPos);
}
removeStoredInfo(info, el, ctx) {
const section = ctx.getSectionInfo(el);
if (!section) {
return;
}
const content = `\`\`\`vid
${info.url}
\`\`\``;
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
if (view) {
const startPos = {
line: section.lineStart,
ch: 0
};
const endPos = {
line: section.lineEnd,
ch: view.editor.getLine(section.lineEnd).length
};
view.editor.replaceRange(content, startPos, endPos);
}
}
getVideoInfo(url) {
return __async(this, null, function* () {
const info = {
url,
thumbnail: "",
title: "",
author: "",
authorUrl: "",
vidFound: false,
networkError: false,
infoStored: false,
imageSaved: false
};
let reqUrl = "";
let isYoutube = false;
for (const type of URL_TYPES.youtube) {
if (url.includes(type.match)) {
isYoutube = true;
}
}
let isVimeo = false;
for (const type of URL_TYPES.vimeo) {
if (url.includes(type.match)) {
isVimeo = true;
}
}
if (isYoutube) {
reqUrl = `https://www.youtube.com/oembed?format=json&url=${url}`;
} else if (isVimeo) {
reqUrl = `https://vimeo.com/api/oembed.json?url=${url}`;
} else {
return info;
}
try {
const reqParam = {
url: reqUrl,
throw: false
};
const res = yield (0, import_obsidian2.requestUrl)(reqParam);
if (res.status === 200) {
info.title = res.json.title;
info.author = res.json.author_name;
info.authorUrl = res.json.author_url;
info.vidFound = true;
} else if (this.settings.youtubeApiKey && isYoutube) {
console.log("Thumbnails: Oembed failed, using YouTube API");
const videoId = yield this.getVideoId(url);
const youtubeUrl = `https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}&key=${this.settings.youtubeApiKey}`;
const youtubeReqParam = {
url: youtubeUrl,
throw: false
};
const youtubeApiRes = yield (0, import_obsidian2.requestUrl)(youtubeReqParam);
if (youtubeApiRes.status === 200) {
const vidSnippet = youtubeApiRes.json.items[0].snippet;
info.authorUrl = "javascript:void(0)";
const channelQueryUrl = `https://youtube.googleapis.com/youtube/v3/channels?part=snippet&id=${vidSnippet.channelId}&key=${this.settings.youtubeApiKey}`;
const channelQueryParam = {
url: channelQueryUrl,
throw: false
};
const channelQueryRes = yield (0, import_obsidian2.requestUrl)(channelQueryParam);
if (channelQueryRes.status === 200) {
const channelSnippet = channelQueryRes.json.items[0].snippet;
const channelCustomUrl = channelSnippet.customUrl;
const channelUrl = `https://www.youtube.com/${channelCustomUrl}`;
info.authorUrl = channelUrl;
}
info.title = vidSnippet.title;
info.author = vidSnippet.channelTitle;
info.vidFound = true;
}
}
if (info.vidFound) {
if (isYoutube) {
const videoId = yield this.getVideoId(url);
info.thumbnail = `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`;
} else {
info.thumbnail = res.json.thumbnail_url;
}
}
} catch (error) {
console.error(error);
info.networkError = true;
}
return info;
});
}
getVideoId(url) {
return __async(this, null, function* () {
let id = "";
for (const type of URL_TYPES.youtube) {
if (url.includes(type.match)) {
const matches = url.match(type.idPattern);
if (matches !== null) {
id = matches[1];
}
}
}
const vimeoType = URL_TYPES.vimeo[0];
if (url.includes(vimeoType.match)) {
const matches = url.match(vimeoType.idPattern);
if (matches !== null) {
id = matches[1];
if (!/^[0-9]+$/.exec(id)) {
id = yield this.fetchVimeoVideoId(url);
}
}
}
return id;
});
}
fetchVimeoVideoId(url) {
return __async(this, null, function* () {
let id = "";
try {
const reqParam = {
url: `https://vimeo.com/api/oembed.json?url=${url}`
};
const res = yield (0, import_obsidian2.requestUrl)(reqParam);
if (res.status === 200 && res.json.video_id) {
id = res.json.video_id.toString();
}
} catch (error) {
console.error(error);
}
return id;
});
}
};

View File

@ -0,0 +1,10 @@
{
"id": "obsidian-thumbnails",
"name": "Thumbnails",
"version": "1.3.0",
"minAppVersion": "1.0.0",
"description": "Insert video thumbnails into your notes",
"author": "Michael Harris",
"authorUrl": "https://github.com/meikul",
"isDesktopOnly": false
}

View File

@ -0,0 +1,178 @@
.block-language-vid{
position: relative;
/* display: block; */
}
.block-language-vid~.edit-block-button{
z-index: 10;
}
.block-language-vid>a.thumbnail{
display: flex;
flex-direction: row;
color: var(--text-default);
text-decoration: none;
margin: 10px 0;
max-width: 400px;
width: 100%;
/* border-radius: 8px; */
/* overflow: hidden; */
box-shadow: none;
}
.block-language-vid .thumbnail-text{
padding: 0 16px;
margin-left: -8px;
flex-grow: 1;
line-height: 1.3;
transition: background 0.3s;
position: relative;
z-index: 1;
}
/* .block-language-vid>a:hover{
box-shadow: 0 .2rem .5rem var(--background-modifier-box-shadow);
} */
.block-language-vid>a.thumbnail:hover .thumbnail-text{
background: var(--background-primary-alt);
}
.block-language-vid .thumbnail-title{
position: relative;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.block-language-vid .thumbnail-img{
width: 150px;
border-radius: 8px;
position: relative;
z-index: 2;
}
.block-language-vid .thumbnail .timestamp{
font-size: calc(var(--font-text-size) - 5px);
display: block;
position: absolute;
bottom: 4px;
left: 4px;
padding: 0 5px 0 5px;
background: rgba(0, 0, 0, 0.75);
border-radius: 4px;
z-index: 3;
}
.block-language-vid .thumbnail-author{
display: block;
color: var(--text-faint);
font-size: calc(var(--font-text-size) - 3px);
width: fit-content;
margin-top: 3px;
text-decoration: none;
transition: color 0.1s;
}
.block-language-vid .thumbnail-author:hover{
color: var(--text-muted);
}
.block-language-vid .thumbnail-error{
color: var(--text-error);
}
.block-language-vid .dummy-container{
position: relative;
height: 84.34px;
width: 400px;
/* background: var(--background-primary-alt); */
/* background: linear-gradient(-80deg, var(--background-primary-alt) 45%, rgba(125, 125, 125, 0.2) 50%, var(--background-primary-alt) 55%); */
background: linear-gradient(-80deg, rgba(125, 125, 125, 0.05) 45%, rgba(125, 125, 125, 0.2) 50%, rgba(125, 125, 125, 0.05) 55%);
animation: gradient 2s linear infinite;
background-size: 400% 400%;
margin: 10px 0;
opacity: 1;
/* transition: opacity 1s linear;
&.hidden{
position: absolute;
opacity: 0;
} */
}
@keyframes gradient {
0% {
background-position: 100% 50%;
}
60% {
background-position: 0% 50%;
}
100% {
background-position: 0% 50%;
}
}
.block-language-vid .dummy-image{
height: 100%;
width: 150px;
background: rgba(125, 125, 125, 0.2);
}
.block-language-vid .dummy-title{
position: absolute;
left: 160px;
top: 6px;
height: 20px;
width: calc(100% - 170px);
background: rgba(125, 125, 125, 0.2);
}
.mod-settings .setting-item.default-attachment-info{
border: none;
padding-top: 0;
}
.mod-settings .setting-item.default-attachment-info input{
color: var(--text-muted);
}
.mod-settings .setting-item.default-attachment-info .setting-item-description{
font-style: italic;
}
.kanban-plugin__item-title .block-language-vid,
:not(.kanban-plugin__item-title) .block-language-vid.thumbnail-card-style{
&>a.thumbnail{
margin: auto;
flex-direction: column;
max-width: 250px;
}
.thumbnail-img{
width: 100%;
}
.thumbnail-text{
padding: 16px 2px;
margin: -8px 0px;
}
}
/* :not(.kanban-plugin__item-title) .block-language-vid.thumbnail-card-style {
&>a.thumbnail {
margin: auto;
flex-direction: column;
max-width: 250px;
}
.thumbnail-img {
width: 100%;
}
.thumbnail-text {
padding: 16px 2px;
margin: -8px 0px;
}
} */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,11 @@
{
"id": "tag-wrangler",
"name": "Tag Wrangler",
"author": "PJ Eby",
"authorUrl": "https://github.com/pjeby",
"version": "0.6.1",
"minAppVersion": "1.2.8",
"description": "Rename, merge, toggle, and search tags from the tag pane",
"fundingUrl": "https://dirtsimple.org/tips/tag-wrangler",
"isDesktopOnly": false
}

View File

@ -0,0 +1 @@
.ophidian-dialog .dialog-text{margin-bottom:.75em}.ophidian-dialog.mod-confirmation input[type=text]:invalid,.ophidian-dialog.mod-confirmation input[type=text]:invalid:enabled:focus,.ophidian-dialog.mod-confirmation input[type=text][aria-invalid=true],.ophidian-dialog.mod-confirmation input[type=text][aria-invalid=true]:enabled:focus{border-color:var(--text-error);background-color:var(--background-modifier-error)}

View File

@ -13,11 +13,11 @@
"state": { "state": {
"type": "canvas", "type": "canvas",
"state": { "state": {
"file": "Design/Hypothèses.canvas", "file": "10 Design/Hypothèses.canvas",
"viewState": { "viewState": {
"x": 1550.8400312707965, "x": 1545.0747584536132,
"y": 668.2292932868143, "y": 711.910177720503,
"zoom": -0.6511609903979585 "zoom": -0.6831531004462654
} }
} }
} }
@ -72,7 +72,8 @@
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 300 "width": 300,
"collapsed": true
}, },
"right": { "right": {
"id": "d596c1e5acc9495a", "id": "d596c1e5acc9495a",
@ -88,7 +89,7 @@
"state": { "state": {
"type": "backlink", "type": "backlink",
"state": { "state": {
"file": "Design/Hypothèses.canvas", "file": "10 Design/Hypothèses.canvas",
"collapseAll": false, "collapseAll": false,
"extraContext": false, "extraContext": false,
"sortOrder": "alphabetical", "sortOrder": "alphabetical",
@ -105,7 +106,7 @@
"state": { "state": {
"type": "outgoing-link", "type": "outgoing-link",
"state": { "state": {
"file": "Design/Hypothèses.canvas", "file": "10 Design/Hypothèses.canvas",
"linksCollapsed": false, "linksCollapsed": false,
"unlinkedCollapsed": true "unlinkedCollapsed": true
} }
@ -128,10 +129,17 @@
"state": { "state": {
"type": "outline", "type": "outline",
"state": { "state": {
"file": "Design/Hypothèses.canvas" "file": "10 Design/Hypothèses.canvas"
} }
} }
}, }
],
"currentTab": 3
},
{
"id": "d534e6d64903cf46",
"type": "tabs",
"children": [
{ {
"id": "f00663bec52e7345", "id": "f00663bec52e7345",
"type": "leaf", "type": "leaf",
@ -140,29 +148,40 @@
"state": {} "state": {}
} }
} }
], ]
"currentTab": 4
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 336.5 "width": 336.5,
"collapsed": true
}, },
"left-ribbon": { "left-ribbon": {
"hiddenItems": { "hiddenItems": {
"switcher:Open quick switcher": false, "switcher:Open quick switcher": true,
"graph:Open graph view": false, "graph:Open graph view": true,
"canvas:Create new canvas": false, "canvas:Create new canvas": true,
"daily-notes:Open today's daily note": false, "daily-notes:Open today's daily note": true,
"templates:Insert template": false, "templates:Insert template": true,
"command-palette:Open command palette": false, "command-palette:Open command palette": true,
"obsidian-git:Open Git source control": false "obsidian-git:Open Git source control": false
} }
}, },
"active": "f00663bec52e7345", "active": "f3ec35ffdace1d82",
"lastOpenFiles": [ "lastOpenFiles": [
"Welcome.md", "Misc/How to Obsidian.md",
"Design/Hypothèses.canvas", "Misc/Storage/Attachments/8/2/8",
"Tech", "Misc/Storage/Attachments/8/2",
"Design" "Misc/Storage/Attachments/8",
"Misc/Storage/Attachments/8/2/8/828b38e1763369b52b2efba15d4668326e86f70408af7ebaf2d99b9f1210a5ed.png",
"Misc/Storage/Attachments/9/4/e",
"Misc/Storage/Attachments/9/4",
"Misc/Storage/Attachments/9",
"Misc/Storage/Attachments/9/4/e/94ebc76d29274d246b1bd9bcb4b1714cc49112ad86de394b437f3e65d4b38ec5.png",
"10 Design/Hypothèses.canvas",
"Misc/Storage/Attachments",
"Misc/Storage/Thumbnails",
"Misc/Storage",
"Misc",
"Welcome.md"
] ]
} }

View File

@ -0,0 +1,6 @@
# Shortcuts
![[94ebc76d29274d246b1bd9bcb4b1714cc49112ad86de394b437f3e65d4b38ec5.png]]
![[828b38e1763369b52b2efba15d4668326e86f70408af7ebaf2d99b9f1210a5ed.png]]

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB