Initial commit

This commit is contained in:
Docker7530
2026-03-01 01:43:46 +08:00
commit c6125c117b
3840 changed files with 415340 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
{
"readableLineLength": false,
"strictLineBreaks": true,
"showLineNumber": true,
"promptDelete": true,
"alwaysUpdateLinks": true,
"newLinkFormat": "relative",
"useMarkdownLinks": true,
"showUnsupportedFiles": true,
"newFileLocation": "folder",
"newFileFolderPath": "000-inbox",
"attachmentFolderPath": "attachment",
"pdfExportSettings": {
"pageSize": "A4",
"landscape": false,
"margin": "0",
"downscalePercent": 100
},
"showInlineTitle": true,
"autoPairMarkdown": true,
"useTab": false,
"showIndentGuide": false,
"spellcheck": false,
"foldIndent": false
}
+8
View File
@@ -0,0 +1,8 @@
{
"enabledCssSnippets": [
"myTheme"
],
"monospaceFontFamily": "JetBrainsMono Nerd Font Mono",
"textFontFamily": "微软雅黑",
"interfaceFontFamily": "JetBrainsMono Nerd Font Mono"
}
+3
View File
@@ -0,0 +1,3 @@
{
"backlinkInDocument": false
}
+15
View File
@@ -0,0 +1,15 @@
{
"items": [
{
"type": "file",
"ctime": 1736742940138,
"path": "work/移动杭研/业务梳理/接口收纳.md"
},
{
"type": "file",
"ctime": 1768964533760,
"path": "work/移动杭研/开发记录/7.18.0/开发笔记.md",
"title": "7.18.0 开发笔记"
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"snapToObjects": true,
"snapToGrid": true,
"newFileLocation": "folder",
"newFileFolderPath": "00-Inbox"
}
+8
View File
@@ -0,0 +1,8 @@
{
"pinned": [
"file-explorer:reveal-active-file",
"file-explorer:move-file",
"open-with-default-app:show",
"app:reload"
]
}
+12
View File
@@ -0,0 +1,12 @@
[
"obsidian-paste-image-rename",
"update-relative-links",
"obsidian-linter",
"templater-obsidian",
"quickadd",
"calendar",
"recent-files-obsidian",
"oz-clear-unused-images",
"obsidian-excalidraw-plugin",
"copy-url-in-preview"
]
+30
View File
@@ -0,0 +1,30 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": false,
"outgoing-link": true,
"tag-pane": true,
"properties": true,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": true,
"file-recovery": true,
"publish": false,
"sync": true
}
+33
View File
@@ -0,0 +1,33 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": false,
"outgoing-link": true,
"tag-pane": true,
"properties": true,
"page-preview": true,
"daily-notes": true,
"templates": false,
"note-composer": false,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": true,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": false,
"webviewer": false,
"footnotes": true,
"bases": true
}
+5
View File
@@ -0,0 +1,5 @@
{
"folder": "calendar/diary",
"template": "attachment/templates/日记模板",
"autorun": false
}
+22
View File
@@ -0,0 +1,22 @@
{
"collapse-filter": false,
"search": "",
"showTags": false,
"showAttachments": false,
"hideUnresolved": false,
"showOrphans": true,
"collapse-color-groups": true,
"colorGroups": [],
"collapse-display": false,
"showArrow": false,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": false,
"centerStrength": 0.518713248970312,
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 250,
"scale": 0.09848280266864458,
"close": true
}
+51
View File
@@ -0,0 +1,51 @@
{
"workspace:goto-tab-1": [],
"workspace:goto-tab-2": [],
"workspace:goto-tab-3": [],
"workspace:goto-tab-4": [],
"workspace:goto-tab-5": [],
"workspace:goto-tab-6": [],
"workspace:goto-tab-7": [],
"workspace:goto-tab-8": [],
"editor:set-heading-1": [
{
"modifiers": [
"Mod"
],
"key": "1"
}
],
"editor:set-heading-2": [
{
"modifiers": [
"Mod"
],
"key": "2"
}
],
"editor:set-heading-3": [
{
"modifiers": [
"Mod"
],
"key": "3"
}
],
"workspace:goto-last-tab": [],
"app:open-help": [],
"app:open-settings": [],
"obsidian-memos:show-thino-editor": [
{
"modifiers": [],
"key": "F1"
}
],
"editor:toggle-comments": [],
"quickadd:runQuickAdd": [
{
"modifiers": [],
"key": "F1"
}
],
"workspace:close-window": []
}
+1
View File
@@ -0,0 +1 @@
{}
+10
View File
@@ -0,0 +1,10 @@
{
"shouldConfirmBeforeCreate": false,
"weekStart": "locale",
"wordsPerDot": 9999,
"showWeeklyNote": true,
"weeklyNoteFormat": "",
"weeklyNoteTemplate": "",
"weeklyNoteFolder": "calendar/weeks",
"localeOverride": "system-default"
}
+4459
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
{
"id": "calendar",
"name": "Calendar",
"description": "Calendar view of your daily notes",
"version": "1.5.10",
"author": "Liam Cain",
"authorUrl": "https://github.com/liamcain/",
"isDesktopOnly": false,
"minAppVersion": "0.9.11"
}
+6
View File
@@ -0,0 +1,6 @@
{
"middleClickNewTab": false,
"revealInNavigation": true,
"enableDefaultOnCanvas": false,
"pdfMenu": false
}
+9
View File
@@ -0,0 +1,9 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
"use strict";var v=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var L=Object.prototype.hasOwnProperty;var O=(n,i,t)=>i in n?v(n,i,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[i]=t;var _=(n,i)=>{for(var t in i)v(n,t,{get:i[t],enumerable:!0})},R=(n,i,t,e)=>{if(i&&typeof i=="object"||typeof i=="function")for(let o of A(i))!L.call(n,o)&&o!==t&&v(n,o,{get:()=>i[o],enumerable:!(e=B(i,o))||e.enumerable});return n};var W=n=>R(v({},"__esModule",{value:!0}),n);var g=(n,i,t)=>O(n,typeof i!="symbol"?i+"":i,t);var $={};_($,{default:()=>y});module.exports=W($);var c=require("obsidian");var m=require("obsidian"),f={loadImageBlob:5e3,notice:1800};function w(n){let i=["avif","bmp","gif","jpg","jpeg","png","svg","webp","heic"];return n=n.toLowerCase(),i.some(t=>n.endsWith(`.${t}`))}function T(n){return n=new URL(n),n.search="",n.toString()}async function h(n){let i=()=>{new m.Notice(i18next.t("interface.copied_generic"),f.notice)},t=()=>{new m.Notice(i18next.t("Failed to copy image to clipboard"),f.notice)};if(n instanceof m.TFile){let a=new Blob([await n.vault.readBinary(n)],{type:`image/${n.extension}`});if(await d(a)){i();return}else{t();return}}let e=await k(n);if(e&&await d(e)){i();return}let o=`https://api.allorigins.win/raw?url=${encodeURIComponent(n)}`;if(e=await k(o),e&&await d(e)){i();return}if(e=await U(f.loadImageBlob,S(n)),e&&await d(e)){i();return}if(e=await U(f.loadImageBlob,S(o)),e&&await d(e)){i();return}t()}async function d(n){try{if(n.type!=="image/svg+xml")return await navigator.clipboard.write([new ClipboardItem({[n.type]:n})]),!0}catch(i){console.warn("Failed copying image with original mimetype, using PNG fallback - ",i)}try{return n=new Blob([n],{type:"image/png"}),await navigator.clipboard.write([new ClipboardItem({[n.type]:n})]),!0}catch(i){console.warn("Failed copying image with PNG mimetype - ",i)}return!1}async function k(n){try{return await(await fetch(n,{signal:AbortSignal.timeout(f.loadImageBlob)})).blob()}catch(i){console.warn("Failed to fetch image - ",i)}return null}function S(n){return new Promise(i=>{let t=new Image;t.crossOrigin="anonymous",t.onload=()=>{let e=document.createElement("canvas");e.width=t.width,e.height=t.height,e.getContext("2d").drawImage(t,0,0),e.toBlob(o=>{i(o)})},t.onerror=()=>{i(null)},t.src=n})}function U(n,i){let t=new Promise(e=>setTimeout(()=>{e(null)},n));return Promise.race([i,t])}function C(n,i,t,e,o){return n.on(i,t,e,o),()=>{n.off(i,t,e,o)}}function x(n,i){let t=(0,m.normalizePath)(n.vault.adapter.basePath);t=t.replace("file://","");let e=i.pathname;e=e.replace("/_capacitor_file_",""),e=e.split("/").filter(a=>a!=="").join("/");let o=decodeURI(e);if(o.startsWith(t)){let a=o.slice(t.length+1);return n.vault.getFileByPath(a)}return null}function b(n,i){n.workspace.getLeaf(!0).openFile(i,{active:!0})}var F=require("obsidian"),M=["file","open","info","system"],P={"copy-to-clipboard":{section:"info",icon:"image-file",title:"interface.label-copy"},"open-in-new-tab":{section:"open",icon:"file-plus",title:"interface.menu.open-in-new-tab"},"open-in-default-app":{section:"system",icon:"arrow-up-right",title:"plugins.open-with-default-app.action-open-file"},"show-in-explorer":{section:"system",icon:"arrow-up-right",title:`plugins.open-with-default-app.action-show-in-folder${F.Platform.isMacOS?"-mac":""}`},"reveal-in-navigation":{section:"system",icon:"folder",title:"plugins.file-explorer.action-reveal-file"},"reveal-in-navigation-tree":{section:"system",icon:"folder",title:"Reveal in File Tree Alternative"},"rename-file":{section:"info",icon:"pencil",title:"interface.menu.rename"}};function p(n,i){return n.setSection(P[i].section).setIcon(P[i].icon).setTitle(i18next.t(P[i].title))}var u=require("obsidian");var D={middleClickNewTab:!0,revealInNavigation:!0,enableDefaultOnCanvas:!1},I=class extends u.PluginSettingTab{constructor(t,e){super(t,e);g(this,"plugin");this.plugin=e}display(){let{containerEl:t}=this;t.empty(),t.createEl("h3",{text:"Image Context Menus settings"}),new u.Setting(t).setName("Middle mouse click on image link to open in new tab").addToggle(e=>{e.setValue(this.plugin.settings.middleClickNewTab).onChange(o=>{this.plugin.settings.middleClickNewTab=o,this.plugin.saveSettings()})}),new u.Setting(t).setName("Reveal file in navigation menu item").setDesc("You might want to disable this if you use a plugin for replacing default Obsidian file navigation. This plugin supports File Tree Alternative by displaying a reveal menu item for it if installed.").addToggle(e=>{e.setValue(this.plugin.settings.revealInNavigation).onChange(o=>{this.plugin.settings.revealInNavigation=o,this.plugin.saveSettings()})}),new u.Setting(t).setName("Enable regular context menu on canvas").setDesc(`The regular context menu sometimes duplicates the context menu on the canvas, so it's disabled there by default.
There is a separate context menu for images directly on the canvas, but if that's not enough (for example for images in notes on canvas), you can enable the regular context menu here too.`).addToggle(e=>{e.setValue(this.plugin.settings.enableDefaultOnCanvas).onChange(o=>{this.plugin.settings.enableDefaultOnCanvas=o,this.plugin.saveSettings()})})}};var y=class extends c.Plugin{constructor(){super(...arguments);g(this,"canvasCardMenu");g(this,"settings")}async loadSettings(){this.settings=Object.assign({},D,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}async onload(){await this.loadSettings(),this.addSettingTab(new I(this.app,this)),this.registerDocument(document),this.app.workspace.on("window-open",(t,e)=>{this.registerDocument(e.document)}),this.registerEvent(this.app.workspace.on("file-menu",(t,e,o)=>{o==="canvas-menu"&&e instanceof c.TFile&&w(`.${e.extension}`)&&(t.addItem(a=>p(a,"open-in-new-tab").onClick(()=>{b(this.app,e)})),t.addItem(a=>p(a,"copy-to-clipboard").onClick(()=>{h(e)})))})),this.registerEvent(this.app.workspace.on("canvas:node-menu",(t,e)=>{let o=e.unknownData;if(o.type==="link"){let a=T(o.url);if(!w(a))return;t.addItem(r=>p(r,"copy-to-clipboard").setSection("canvas").onClick(()=>{h(a)}))}})),this.registerEvent(this.app.workspace.on("url-menu",(t,e)=>{e=T(e),w(e)&&t.addItem(o=>p(o,"copy-to-clipboard").onClick(()=>{h(e)}))}))}registerDocument(t){let e=[C(t,"contextmenu","img",this.onImageContextMenu.bind(this),{capture:!0}),C(t,"mouseup","img",this.onImageMouseUp.bind(this))];this.register(()=>{for(let o of e)o()})}onImageContextMenu(t){var E;if(!this.settings.enableDefaultOnCanvas&&((E=this.app.workspace.getActiveFile())==null?void 0:E.extension)==="canvas")return;t.preventDefault();let e=t.target,o=new URL(e.src);if(!["app:","data:","http:","https:"].includes(o.protocol)){new c.Notice(`No handler for ${o.protocol} protocol`);return}let r=new c.Menu,l=x(this.app,o);r.addSections(Array.from(M)),l&&r.addItem(s=>p(s,"rename-file").onClick(()=>this.app.fileManager.promptForFileRename(l))),r.addItem(s=>p(s,"copy-to-clipboard").onClick(()=>{h(l!=null?l:e.src)})),l&&(c.Platform.isMobile&&r.addItem(s=>s.setTitle(l.name).setSection("file").setIsLabel(!0)),r.addItem(s=>p(s,"open-in-new-tab").onClick(()=>{b(this.app,l)})),c.Platform.isDesktop&&(r.addItem(s=>p(s,"open-in-default-app").onClick(()=>{this.app.openWithDefaultApp(l.path)})),r.addItem(s=>p(s,"show-in-explorer").onClick(()=>{this.app.showInFolder(l.path)}))),this.settings.revealInNavigation&&r.addItem(s=>p(s,"reveal-in-navigation").onClick(()=>{var N;(N=this.app.internalPlugins.getEnabledPluginById("file-explorer"))==null||N.revealInFolder(l)})),this.app.plugins.enabledPlugins.has("file-tree-alternative")&&r.addItem(s=>p(s,"reveal-in-navigation-tree").onClick(()=>{self.dispatchEvent(new CustomEvent("fta-reveal-file",{detail:{file:l}}))}))),r.showAtPosition({x:t.pageX,y:t.pageY})}onImageMouseUp(t){let e=t.target;if(t.button===1&&this.settings.middleClickNewTab){let a=x(this.app,new URL(e.src));if(!a)return;b(this.app,a)}}};
/* nosourcemap */
+11
View File
@@ -0,0 +1,11 @@
{
"id": "copy-url-in-preview",
"name": "Image Context Menus",
"version": "1.11.2",
"minAppVersion": "1.6.6",
"description": "Copy to clipboard, Open in default app, Show in system explorer, Reveal file in navigation, Open in new tab, Rename context menus for images.",
"author": "NomarCub",
"authorUrl": "https://github.com/NomarCub",
"fundingUrl": "https://ko-fi.com/nomarcub",
"isDesktopOnly": false
}
+817
View File
@@ -0,0 +1,817 @@
{
"copyLinkToElemenetAnchorTo100": false,
"copyFrameLinkByName": false,
"disableDoubleClickTextEditing": false,
"folder": "excalidraw",
"cropFolder": "excalidraw/cropped",
"annotateFolder": "excalidraw/annotations",
"embedUseExcalidrawFolder": true,
"templateFilePath": "excalidraw/Template.excalidraw",
"scriptFolderPath": "excalidraw/scripts",
"fontAssetsPath": "excalidraw/fonts",
"loadChineseFonts": false,
"loadJapaneseFonts": false,
"loadKoreanFonts": false,
"compress": true,
"decompressForMDView": false,
"onceOffCompressFlagReset": true,
"onceOffGPTVersionReset": true,
"autosave": true,
"autosaveIntervalDesktop": 60000,
"autosaveIntervalMobile": 30000,
"drawingFilenamePrefix": "Drawing ",
"drawingEmbedPrefixWithFilename": true,
"drawingFilnameEmbedPostfix": " ",
"drawingFilenameDateTime": "YYYY-MM-DD HH.mm.ss",
"useExcalidrawExtension": true,
"cropSuffix": "",
"cropPrefix": "cropped_",
"annotateSuffix": "",
"annotatePrefix": "annotated_",
"annotatePreserveSize": false,
"previewImageType": "SVGIMG",
"renderingConcurrency": 3,
"allowImageCache": true,
"allowImageCacheInScene": true,
"displayExportedImageIfAvailable": false,
"previewMatchObsidianTheme": false,
"width": "400",
"height": "",
"overrideObsidianFontSize": false,
"dynamicStyling": "colorful",
"isLeftHanded": false,
"desktopUIMode": "tray",
"tabletUIMode": "compact",
"iframeMatchExcalidrawTheme": true,
"matchTheme": false,
"matchThemeAlways": false,
"matchThemeTrigger": false,
"defaultMode": "normal",
"defaultPenMode": "never",
"penModeDoubleTapEraser": true,
"penModeSingleFingerPanning": true,
"penModeCrosshairVisible": true,
"panWithRightMouseButton": false,
"renderImageInMarkdownReadingMode": false,
"renderImageInHoverPreviewForMDNotes": false,
"renderImageInMarkdownToPDF": false,
"allowPinchZoom": false,
"allowWheelZoom": true,
"zoomToFitOnOpen": true,
"zoomToFitOnResize": true,
"zoomToFitMaxLevel": 2,
"zoomStep": 0.05,
"zoomMin": 0.1,
"zoomMax": 30,
"linkPrefix": "📍",
"urlPrefix": "🌐",
"parseTODO": false,
"todo": "☐",
"done": "🗹",
"hoverPreviewWithoutCTRL": false,
"linkOpacity": 1,
"openInAdjacentPane": true,
"showSecondOrderLinks": true,
"focusOnFileTab": true,
"openInMainWorkspace": true,
"showLinkBrackets": true,
"syncElementLinkWithText": false,
"allowCtrlClick": true,
"forceWrap": false,
"pageTransclusionCharLimit": 200,
"wordWrappingDefault": 0,
"removeTransclusionQuoteSigns": true,
"iframelyAllowed": true,
"pngExportScale": 1,
"exportWithTheme": true,
"exportWithBackground": true,
"exportPaddingSVG": 10,
"exportEmbedScene": false,
"keepInSync": false,
"autoexportSVG": false,
"autoexportPNG": false,
"autoExportLightAndDark": false,
"autoexportExcalidraw": false,
"embedType": "excalidraw",
"embedMarkdownCommentLinks": true,
"embedWikiLink": true,
"syncExcalidraw": false,
"experimentalFileType": false,
"experimentalFileTag": "✏️",
"experimentalLivePreview": true,
"fadeOutExcalidrawMarkup": false,
"loadPropertySuggestions": true,
"experimentalEnableFourthFont": false,
"experimantalFourthFont": "Virgil",
"addDummyTextElement": false,
"zoteroCompatibility": false,
"fieldSuggester": true,
"compatibilityMode": false,
"drawingOpenCount": 0,
"library": "deprecated",
"library2": {
"type": "excalidrawlib",
"version": 2,
"source": "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/2.19.1",
"libraryItems": []
},
"imageElementNotice": true,
"mdSVGwidth": 500,
"mdSVGmaxHeight": 800,
"mdFont": "Virgil",
"mdFontColor": "Black",
"mdBorderColor": "Black",
"mdCSS": "",
"scriptEngineSettings": {},
"previousRelease": "2.20.4",
"showReleaseNotes": true,
"compareManifestToPluginVersion": true,
"showNewVersionNotification": true,
"latexBoilerplate": "\\color{blue}",
"latexPreambleLocation": "preamble.sty",
"taskboneEnabled": false,
"taskboneAPIkey": "",
"pinnedScripts": [],
"sidepanelTabs": [],
"customPens": [
{
"type": "default",
"freedrawOnly": false,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 0,
"roughness": 0,
"penOptions": {
"highlighter": false,
"constantPressure": false,
"hasOutline": false,
"outlineWidth": 1,
"options": {
"thinning": 0.6,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "easeOutSine",
"start": {
"cap": true,
"taper": 0,
"easing": "linear"
},
"end": {
"cap": true,
"taper": 0,
"easing": "linear"
}
}
}
},
{
"type": "highlighter",
"freedrawOnly": true,
"strokeColor": "#FFC47C",
"backgroundColor": "#FFC47C",
"fillStyle": "solid",
"strokeWidth": 2,
"roughness": null,
"penOptions": {
"highlighter": true,
"constantPressure": true,
"hasOutline": true,
"outlineWidth": 4,
"options": {
"thinning": 1,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "linear",
"start": {
"taper": 0,
"cap": true,
"easing": "linear"
},
"end": {
"taper": 0,
"cap": true,
"easing": "linear"
}
}
}
},
{
"type": "finetip",
"freedrawOnly": false,
"strokeColor": "#3E6F8D",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 0.5,
"roughness": 0,
"penOptions": {
"highlighter": false,
"hasOutline": false,
"outlineWidth": 1,
"constantPressure": true,
"options": {
"smoothing": 0.4,
"thinning": -0.5,
"streamline": 0.4,
"easing": "linear",
"start": {
"taper": 5,
"cap": false,
"easing": "linear"
},
"end": {
"taper": 5,
"cap": false,
"easing": "linear"
}
}
}
},
{
"type": "fountain",
"freedrawOnly": false,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 2,
"roughness": 0,
"penOptions": {
"highlighter": false,
"constantPressure": false,
"hasOutline": false,
"outlineWidth": 1,
"options": {
"smoothing": 0.2,
"thinning": 0.6,
"streamline": 0.2,
"easing": "easeInOutSine",
"start": {
"taper": 150,
"cap": true,
"easing": "linear"
},
"end": {
"taper": 1,
"cap": true,
"easing": "linear"
}
}
}
},
{
"type": "marker",
"freedrawOnly": true,
"strokeColor": "#B83E3E",
"backgroundColor": "#FF7C7C",
"fillStyle": "dashed",
"strokeWidth": 2,
"roughness": 3,
"penOptions": {
"highlighter": false,
"constantPressure": true,
"hasOutline": true,
"outlineWidth": 4,
"options": {
"thinning": 1,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "linear",
"start": {
"taper": 0,
"cap": true,
"easing": "linear"
},
"end": {
"taper": 0,
"cap": true,
"easing": "linear"
}
}
}
},
{
"type": "thick-thin",
"freedrawOnly": true,
"strokeColor": "#CECDCC",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 0,
"roughness": null,
"penOptions": {
"highlighter": true,
"constantPressure": true,
"hasOutline": false,
"outlineWidth": 1,
"options": {
"thinning": 1,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "linear",
"start": {
"taper": 0,
"cap": true,
"easing": "linear"
},
"end": {
"cap": true,
"taper": true,
"easing": "linear"
}
}
}
},
{
"type": "thin-thick-thin",
"freedrawOnly": true,
"strokeColor": "#CECDCC",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 0,
"roughness": null,
"penOptions": {
"highlighter": true,
"constantPressure": true,
"hasOutline": false,
"outlineWidth": 1,
"options": {
"thinning": 1,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "linear",
"start": {
"cap": true,
"taper": true,
"easing": "linear"
},
"end": {
"cap": true,
"taper": true,
"easing": "linear"
}
}
}
},
{
"type": "default",
"freedrawOnly": false,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 0,
"roughness": 0,
"penOptions": {
"highlighter": false,
"constantPressure": false,
"hasOutline": false,
"outlineWidth": 1,
"options": {
"thinning": 0.6,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "easeOutSine",
"start": {
"cap": true,
"taper": 0,
"easing": "linear"
},
"end": {
"cap": true,
"taper": 0,
"easing": "linear"
}
}
}
},
{
"type": "default",
"freedrawOnly": false,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 0,
"roughness": 0,
"penOptions": {
"highlighter": false,
"constantPressure": false,
"hasOutline": false,
"outlineWidth": 1,
"options": {
"thinning": 0.6,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "easeOutSine",
"start": {
"cap": true,
"taper": 0,
"easing": "linear"
},
"end": {
"cap": true,
"taper": 0,
"easing": "linear"
}
}
}
},
{
"type": "default",
"freedrawOnly": false,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 0,
"roughness": 0,
"penOptions": {
"highlighter": false,
"constantPressure": false,
"hasOutline": false,
"outlineWidth": 1,
"options": {
"thinning": 0.6,
"smoothing": 0.5,
"streamline": 0.5,
"easing": "easeOutSine",
"start": {
"cap": true,
"taper": 0,
"easing": "linear"
},
"end": {
"cap": true,
"taper": 0,
"easing": "linear"
}
}
}
}
],
"numberOfCustomPens": 0,
"pdfScale": 4,
"pdfBorderBox": true,
"pdfFrame": false,
"pdfGapSize": 20,
"pdfGroupPages": false,
"pdfLockAfterImport": true,
"pdfNumColumns": 1,
"pdfNumRows": 1,
"pdfDirection": "right",
"pdfImportScale": 0.3,
"gridSettings": {
"DYNAMIC_COLOR": true,
"COLOR": "#000000",
"OPACITY": 50
},
"laserSettings": {
"DECAY_LENGTH": 50,
"DECAY_TIME": 1000,
"COLOR": "#ff0000"
},
"embeddableMarkdownDefaults": {
"useObsidianDefaults": false,
"backgroundMatchCanvas": false,
"backgroundMatchElement": true,
"backgroundColor": "#fff",
"backgroundOpacity": 60,
"borderMatchElement": true,
"borderColor": "#fff",
"borderOpacity": 0,
"filenameVisible": false
},
"markdownNodeOneClickEditing": false,
"canvasImmersiveEmbed": true,
"startupScriptPath": "",
"aiEnabled": true,
"openAIAPIToken": "",
"openAIDefaultTextModel": "gpt-3.5-turbo-1106",
"openAIDefaultTextModelMaxTokens": 4096,
"openAIDefaultVisionModel": "gpt-4o",
"openAIDefaultImageGenerationModel": "dall-e-3",
"openAIURL": "https://api.openai.com/v1/chat/completions",
"openAIImageGenerationURL": "https://api.openai.com/v1/images/generations",
"openAIImageEditsURL": "https://api.openai.com/v1/images/edits",
"openAIImageVariationURL": "https://api.openai.com/v1/images/variations",
"modifierKeyConfig": {
"Mac": {
"LocalFileDragAction": {
"defaultAction": "image-import",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-import"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": true,
"meta_ctrl": false,
"result": "link"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-url"
},
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": true,
"meta_ctrl": false,
"result": "embeddable"
}
]
},
"WebBrowserDragAction": {
"defaultAction": "image-url",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-url"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": true,
"meta_ctrl": false,
"result": "link"
},
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": true,
"meta_ctrl": false,
"result": "embeddable"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-import"
}
]
},
"InternalDragAction": {
"defaultAction": "link",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "link"
},
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": true,
"result": "embeddable"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": true,
"result": "image-fullsize"
}
]
},
"LinkClickAction": {
"defaultAction": "new-tab",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "active-pane"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": false,
"result": "new-tab"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": true,
"meta_ctrl": false,
"result": "new-pane"
},
{
"shift": true,
"ctrl_cmd": true,
"alt_opt": true,
"meta_ctrl": false,
"result": "popout-window"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": true,
"result": "md-properties"
}
]
}
},
"Win": {
"LocalFileDragAction": {
"defaultAction": "image-import",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-import"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": false,
"result": "link"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-url"
},
{
"shift": true,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": false,
"result": "embeddable"
}
]
},
"WebBrowserDragAction": {
"defaultAction": "image-url",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-url"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": false,
"result": "link"
},
{
"shift": true,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": false,
"result": "embeddable"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image-import"
}
]
},
"InternalDragAction": {
"defaultAction": "link",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "link"
},
{
"shift": true,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": false,
"result": "embeddable"
},
{
"shift": true,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "image"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": true,
"meta_ctrl": false,
"result": "image-fullsize"
}
]
},
"LinkClickAction": {
"defaultAction": "new-tab",
"rules": [
{
"shift": false,
"ctrl_cmd": false,
"alt_opt": false,
"meta_ctrl": false,
"result": "active-pane"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": false,
"result": "new-tab"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": true,
"meta_ctrl": false,
"result": "new-pane"
},
{
"shift": true,
"ctrl_cmd": true,
"alt_opt": true,
"meta_ctrl": false,
"result": "popout-window"
},
{
"shift": false,
"ctrl_cmd": true,
"alt_opt": false,
"meta_ctrl": true,
"result": "md-properties"
}
]
}
}
},
"slidingPanesSupport": false,
"areaZoomLimit": 1,
"longPressDesktop": 500,
"longPressMobile": 500,
"doubleClickLinkOpenViewMode": true,
"isDebugMode": false,
"rank": "Bronze",
"modifierKeyOverrides": [
{
"modifiers": [
"Mod"
],
"key": "Enter"
},
{
"modifiers": [
"Mod"
],
"key": "k"
},
{
"modifiers": [
"Mod"
],
"key": "G"
}
],
"showSplashscreen": true,
"pdfSettings": {
"pageSize": "A4",
"pageOrientation": "portrait",
"fitToPage": 1,
"paperColor": "white",
"customPaperColor": "#ffffff",
"alignment": "center",
"margin": "normal"
},
"disableContextMenu": false,
"defaultTrayMode": true,
"compactModeOnTablets": true
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.20.4",
"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",
"authorUrl": "https://excalidraw-obsidian.online",
"fundingUrl": "https://ko-fi.com/zsolt",
"helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme",
"isDesktopOnly": false
}
File diff suppressed because one or more lines are too long
+297
View File
@@ -0,0 +1,297 @@
{
"ruleConfigs": {
"add-blank-line-after-yaml": {
"enabled": true
},
"dedupe-yaml-array-values": {
"enabled": true,
"dedupe-alias-key": true,
"dedupe-tag-key": true,
"dedupe-array-keys": true,
"ignore-keys": ""
},
"escape-yaml-special-characters": {
"enabled": true,
"try-to-escape-single-line-arrays": false
},
"force-yaml-escape": {
"enabled": false,
"force-yaml-escape-keys": ""
},
"format-tags-in-yaml": {
"enabled": true
},
"format-yaml-array": {
"enabled": true,
"alias-key": true,
"tag-key": true,
"default-array-style": "multi-line",
"default-array-keys": true,
"force-single-line-array-style": "",
"force-multi-line-array-style": ""
},
"insert-yaml-attributes": {
"enabled": false,
"text-to-insert": "aliases: \ntags: "
},
"move-tags-to-yaml": {
"enabled": false,
"how-to-handle-existing-tags": "Remove whole tag",
"tags-to-ignore": ""
},
"remove-yaml-keys": {
"enabled": false,
"yaml-keys-to-remove": ""
},
"sort-yaml-array-values": {
"enabled": false,
"sort-alias-key": false,
"sort-tag-key": false,
"sort-array-keys": false,
"ignore-keys": "",
"sort-order": "Ascending Alphabetical"
},
"yaml-key-sort": {
"enabled": false,
"yaml-key-priority-sort-order": "",
"priority-keys-at-start-of-yaml": true,
"yaml-sort-order-for-other-keys": "None"
},
"yaml-timestamp": {
"enabled": false,
"date-created": true,
"date-created-key": "date created",
"date-modified": true,
"date-modified-key": "date modified",
"format": "dddd, MMMM Do YYYY, h:mm:ss a",
"date-created-source-of-truth": "file system",
"date-modified-source-of-truth": "file system",
"convert-to-utc": false,
"update-on-file-contents-updated": "never"
},
"yaml-title": {
"enabled": false,
"title-key": "title",
"mode": "first-h1-or-filename-if-h1-missing"
},
"yaml-title-alias": {
"enabled": false,
"preserve-existing-alias-section-style": true,
"keep-alias-that-matches-the-filename": false,
"use-yaml-key-to-keep-track-of-old-filename-or-heading": true,
"alias-helper-key": "linter-yaml-title-alias"
},
"capitalize-headings": {
"enabled": false,
"style": "Title Case",
"ignore-case-words": true,
"ignore-words": "macOS, iOS, iPhone, iPad, JavaScript, TypeScript, AppleScript, I",
"lowercase-words": "a, an, the, aboard, about, abt., above, abreast, absent, across, after, against, along, aloft, alongside, amid, amidst, mid, midst, among, amongst, anti, apropos, around, round, as, aslant, astride, at, atop, ontop, bar, barring, before, B4, behind, below, beneath, neath, beside, besides, between, 'tween, beyond, but, by, chez, circa, c., ca., come, concerning, contra, counting, cum, despite, spite, down, during, effective, ere, except, excepting, excluding, failing, following, for, from, in, including, inside, into, less, like, minus, modulo, mod, near, nearer, nearest, next, notwithstanding, of, o', off, offshore, on, onto, opposite, out, outside, over, o'er, pace, past, pending, per, plus, post, pre, pro, qua, re, regarding, respecting, sans, save, saving, short, since, sub, than, through, thru, throughout, thruout, till, times, to, t', touching, toward, towards, under, underneath, unlike, until, unto, up, upon, versus, vs., v., via, vice, vis-à-vis, wanting, with, w/, w., c̄, within, w/i, without, 'thout, w/o, abroad, adrift, aft, afterward, afterwards, ahead, apart, ashore, aside, away, back, backward, backwards, beforehand, downhill, downstage, downstairs, downstream, downward, downwards, downwind, east, eastward, eastwards, forth, forward, forwards, heavenward, heavenwards, hence, henceforth, here, hereby, herein, hereof, hereto, herewith, home, homeward, homewards, indoors, inward, inwards, leftward, leftwards, north, northeast, northward, northwards, northwest, now, onward, onwards, outdoors, outward, outwards, overboard, overhead, overland, overseas, rightward, rightwards, seaward, seawards, skywards, skyward, south, southeast, southwards, southward, southwest, then, thence, thenceforth, there, thereby, therein, thereof, thereto, therewith, together, underfoot, underground, uphill, upstage, upstairs, upstream, upward, upwards, upwind, west, westward, westwards, when, whence, where, whereby, wherein, whereto, wherewith, although, because, considering, given, granted, if, lest, once, provided, providing, seeing, so, supposing, though, unless, whenever, whereas, wherever, while, whilst, ago, according to, as regards, counter to, instead of, owing to, pertaining to, at the behest of, at the expense of, at the hands of, at risk of, at the risk of, at variance with, by dint of, by means of, by virtue of, by way of, for the sake of, for sake of, for lack of, for want of, from want of, in accordance with, in addition to, in case of, in charge of, in compliance with, in conformity with, in contact with, in exchange for, in favor of, in front of, in lieu of, in light of, in the light of, in line with, in place of, in point of, in quest of, in relation to, in regard to, with regard to, in respect to, with respect to, in return for, in search of, in step with, in touch with, in terms of, in the name of, in view of, on account of, on behalf of, on grounds of, on the grounds of, on the part of, on top of, with a view to, with the exception of, à la, a la, as soon as, as well as, close to, due to, far from, in case, other than, prior to, pursuant to, regardless of, subsequent to, as long as, as much as, as far as, by the time, in as much as, inasmuch, in order to, in order that, even, provide that, if only, whether, whose, whoever, why, how, or not, whatever, what, both, and, or, not only, but also, either, neither, nor, just, rather, no sooner, such, that, yet, is, it"
},
"file-name-heading": {
"enabled": false
},
"header-increment": {
"enabled": false,
"start-at-h2": false
},
"headings-start-line": {
"enabled": false
},
"remove-trailing-punctuation-in-heading": {
"enabled": false,
"punctuation-to-remove": ".,;:!。,;:!"
},
"footnote-after-punctuation": {
"enabled": false
},
"move-footnotes-to-the-bottom": {
"enabled": false,
"include-blank-line-between-footnotes": false
},
"re-index-footnotes": {
"enabled": false
},
"auto-correct-common-misspellings": {
"enabled": false,
"ignore-words": "",
"skip-words-with-multiple-capitals": false,
"extra-auto-correct-files": []
},
"blockquote-style": {
"enabled": true,
"style": "space"
},
"convert-bullet-list-markers": {
"enabled": true
},
"default-language-for-code-fences": {
"enabled": false,
"default-language": ""
},
"emphasis-style": {
"enabled": true,
"style": "asterisk"
},
"no-bare-urls": {
"enabled": false,
"no-bare-uris": false
},
"ordered-list-style": {
"enabled": true,
"number-style": "ascending",
"list-end-style": "."
},
"proper-ellipsis": {
"enabled": true
},
"quote-style": {
"enabled": false,
"single-quote-enabled": true,
"single-quote-style": "''",
"double-quote-enabled": true,
"double-quote-style": "\"\""
},
"remove-consecutive-list-markers": {
"enabled": false
},
"remove-empty-list-markers": {
"enabled": true
},
"remove-hyphenated-line-breaks": {
"enabled": false
},
"remove-multiple-spaces": {
"enabled": false
},
"strong-style": {
"enabled": true,
"style": "asterisk"
},
"two-spaces-between-lines-with-content": {
"enabled": false,
"line-break-indicator": " "
},
"unordered-list-style": {
"enabled": true,
"list-style": "-"
},
"compact-yaml": {
"enabled": true,
"inner-new-lines": true
},
"consecutive-blank-lines": {
"enabled": true
},
"convert-spaces-to-tabs": {
"enabled": false,
"tabsize": 4
},
"empty-line-around-blockquotes": {
"enabled": true
},
"empty-line-around-code-fences": {
"enabled": true
},
"empty-line-around-math-blocks": {
"enabled": true
},
"empty-line-around-tables": {
"enabled": true
},
"heading-blank-lines": {
"enabled": true,
"bottom": true,
"empty-line-after-yaml": true
},
"line-break-at-document-end": {
"enabled": true
},
"move-math-block-indicators-to-their-own-line": {
"enabled": true
},
"paragraph-blank-lines": {
"enabled": true
},
"remove-empty-lines-between-list-markers-and-checklists": {
"enabled": true
},
"remove-link-spacing": {
"enabled": true
},
"remove-space-around-characters": {
"enabled": false,
"include-fullwidth-forms": true,
"include-cjk-symbols-and-punctuation": true,
"include-dashes": true,
"other-symbols": ""
},
"remove-space-before-or-after-characters": {
"enabled": false,
"characters-to-remove-space-before": ",!?;:).’”]",
"characters-to-remove-space-after": "¿¡‘“(["
},
"space-after-list-markers": {
"enabled": true
},
"space-between-chinese-japanese-or-korean-and-english-or-numbers": {
"enabled": false,
"english-symbols-punctuation-before": "-+;:'\"°%$)]",
"english-symbols-punctuation-after": "-+'\"([¥$"
},
"trailing-spaces": {
"enabled": true,
"two-space-line-break": true
},
"add-blockquote-indentation-on-paste": {
"enabled": false
},
"prevent-double-checklist-indicator-on-paste": {
"enabled": false
},
"prevent-double-list-item-indicator-on-paste": {
"enabled": false
},
"proper-ellipsis-on-paste": {
"enabled": false
},
"remove-hyphens-on-paste": {
"enabled": false
},
"remove-leading-or-trailing-whitespace-on-paste": {
"enabled": false
},
"remove-leftover-footnotes-from-quote-on-paste": {
"enabled": false
},
"remove-multiple-blank-lines-on-paste": {
"enabled": false
},
"empty-line-around-horizontal-rules": {
"enabled": true
}
},
"lintOnSave": true,
"recordLintOnSaveLogs": false,
"displayChanged": true,
"suppressMessageWhenNoChange": false,
"lintOnFileChange": true,
"displayLintOnFileChangeNotice": true,
"settingsConvertedToConfigKeyValues": true,
"foldersToIgnore": [
"attachment",
"excalidraw"
],
"filesToIgnore": [],
"linterLocale": "system-default",
"logLevel": "ERROR",
"lintCommands": [],
"customRegexes": [],
"commonStyles": {
"aliasArrayStyle": "multi-line",
"tagArrayStyle": "multi-line",
"minimumNumberOfDollarSignsToBeAMathBlock": 2,
"escapeCharacter": "\"",
"removeUnnecessaryEscapeCharsForMultiLineArrays": true
}
}
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
{
"id": "obsidian-linter",
"name": "Linter",
"version": "1.31.0",
"minAppVersion": "1.9.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",
"helpUrl": "https://platers.github.io/obsidian-linter/",
"isDesktopOnly": false
}
+1
View File
@@ -0,0 +1 @@
.linter-navigation-item{align-items:center;background-color:var(--background-primary-secondary-alt);border:1px solid var(--background-modifier-border);border-radius:100px;border-radius:8px 8px 2px 2px;cursor:pointer;display:flex;flex-direction:row;font-size:16px;font-weight:700;gap:4px;height:32px;overflow:hidden;padding:4px 6px;transition:color .25s ease-in-out,padding .25s ease-in-out,background-color .35s cubic-bezier(.45,.25,.83,.67),max-width .35s cubic-bezier(.57,.04,.58,1);white-space:nowrap}@media screen and (max-width:1325px){.linter-navigation-item.linter-desktop{max-width:32px}}@media screen and (max-width:800px){.linter-navigation-item.linter-mobile{max-width:32px}}.linter-navigation-item-icon,.linter-warning{padding-top:5px}.linter-navigation-item:hover{border-color:var(--interactive-accent-hover);border-bottom:0}.linter-navigation-item-selected{background-color:var(--interactive-accent)!important;border:1px solid var(--background-modifier-border);border-bottom:0;border-radius:8px 8px 2px 2px;color:var(--text-on-accent);max-width:100%!important;padding:4px 9px!important;transition:color .25s ease-in-out,padding .25s ease-in-out,background-color .35s cubic-bezier(.45,.25,.83,.67),max-width .45s cubic-bezier(.57,.04,.58,1) .2s}.linter{transition:transform .4s 0s}.linter-setting-title{align-items:baseline;display:flex;gap:30px;justify-content:space-between}.linter-setting-title.linter-mobile{justify-content:space-around}.linter-setting-title h1{font-weight:900;margin-bottom:12px;margin-top:6px}.linter-setting-header{margin-bottom:24px;overflow-x:auto;overflow-y:hidden}.linter-setting-header .linter-setting-tab-group{align-items:flex-end;display:flex;flex-wrap:wrap;width:100%}.linter-setting-tab-group{border-bottom:2px solid var(--background-modifier-border);margin-top:6px;padding-left:2px;padding-right:2px}.linter-setting-header .linter-tab-settings{border-left:2px solid transparent;border-right:2px solid transparent;cursor:pointer;font-weight:600;padding:6px 12px;white-space:nowrap}.linter-setting-header .linter-tab-settings:first-child{margin-left:6px}.linter-setting-header .linter-tab-settings.linter-tab-settings-active{border:2px solid var(--background-modifier-border);border-bottom-color:var(--background-primary);border-radius:2px;transform:translateY(2px)}.linter-navigation-item:not(.linter-navigation-item-selected)>span:nth-child(2),.linter-visually-hidden{border:0;clip:rect(0 0 0 0);clip-path:rect(0 0 0 0);height:auto;margin:0;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}textarea.full-width{margin-bottom:.8em;margin-top:.8em;min-height:10em;width:100%}.full-width-textbox-input-wrapper{position:relative}.settings-copy-button{margin:0 0 0 auto;padding:4px;position:absolute;right:.8em;top:.8em}.settings-copy-button svg.linter-clipboard path{fill:var(--text-faint)}.settings-copy-button svg.linter-success path{fill:var(--interactive-success)}.settings-copy-button:active,.settings-copy-button:hover{cursor:pointer}.settings-copy-button:active svg path,.settings-copy-button:hover svg path{fill:var(--text-accent-hover);transition:all .3s ease}.settings-copy-button:focus{outline:0}.linter-custom-regex-replacement-container div:last-child{border:none}.linter-custom-regex-replacement{border:none;border-bottom:var(--hr-thickness) solid;border-color:var(--hr-color);margin-bottom:15px}.linter-custom-regex-replacement-row2{flex-wrap:wrap}.linter-custom-regex-replacement-normal-input{width:40%}.linter-custom-regex-replacement-flags{width:15%}.linter-custom-regex-replacement-label{flex-direction:row-reverse}.linter-custom-regex-replacement-label-input{width:50%}.linter-files-to-ignore-container div:last-child{border:none}.linter-files-to-ignore{border:none;border-bottom:var(--hr-thickness) solid;border-color:var(--hr-color);margin-bottom:15px}.linter-files-to-ignore-normal-input{width:40%}.linter-files-to-ignore-flags{width:15%}.linter-no-border{border:none}.linter-border-bottom{border-bottom:1px solid var(--background-modifier-border);border-top:0;margin-bottom:.75em}.linter-no-padding-top{padding-top:0}.custom-row-description{margin-top:0}.modal-warn,.search-zero-state{font-weight:700}.modal-heading,.search-zero-state{text-align:center}
+10
View File
@@ -0,0 +1,10 @@
{
"imageNamePattern": "image-{{DATE:YYYYMMDDHHmmssSSS}}",
"dupNumberAtStart": false,
"dupNumberDelimiter": "-",
"dupNumberAlways": false,
"autoRename": true,
"handleAllAttachments": false,
"excludeExtensionPattern": "",
"disableRenameNotice": true
}
+942
View File
@@ -0,0 +1,942 @@
/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD */
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
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);
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());
});
};
// package.json
var require_package = __commonJS({
"package.json"(exports, module2) {
module2.exports = {
name: "obsidian-paste-image-rename",
version: "1.6.1",
main: "main.js",
scripts: {
start: "node esbuild.config.mjs",
build: "tsc -noEmit -skipLibCheck && BUILD_ENV=production node esbuild.config.mjs && cp manifest.json build",
version: "node version-bump.mjs && git add manifest.json versions.json",
release: "npm run build && gh release create ${npm_package_version} build/*"
},
keywords: [],
author: "Reorx",
license: "MIT",
devDependencies: {
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"builtin-modules": "^3.3.0",
esbuild: "0.16.17",
obsidian: "^1.1.1",
tslib: "2.5.0",
typescript: "4.9.4"
},
dependencies: {
"cash-dom": "^8.1.2"
}
};
}
});
// src/main.ts
var main_exports = {};
__export(main_exports, {
default: () => PasteImageRenamePlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian2 = require("obsidian");
// src/batch.ts
var import_obsidian = require("obsidian");
// src/utils.ts
var DEBUG = false;
if (DEBUG)
console.log("DEBUG is enabled");
function debugLog(...args) {
if (DEBUG) {
console.log(new Date().toISOString().slice(11, 23), ...args);
}
}
function createElementTree(rootEl, opts) {
const result = {
el: rootEl.createEl(opts.tag, opts),
children: []
};
const children = opts.children || [];
for (const child of children) {
result.children.push(createElementTree(result.el, child));
}
return result;
}
var path = {
// Credit: @creationix/path.js
join(...partSegments) {
let parts = [];
for (let i = 0, l = partSegments.length; i < l; i++) {
parts = parts.concat(partSegments[i].split("/"));
}
const newParts = [];
for (let i = 0, l = parts.length; i < l; i++) {
const part = parts[i];
if (!part || part === ".")
continue;
else
newParts.push(part);
}
if (parts[0] === "")
newParts.unshift("");
return newParts.join("/");
},
// returns the last part of a path, e.g. 'foo.jpg'
basename(fullpath) {
const sp = fullpath.split("/");
return sp[sp.length - 1];
},
// return extension without dot, e.g. 'jpg'
extension(fullpath) {
const positions = [...fullpath.matchAll(new RegExp("\\.", "gi"))].map((a) => a.index);
return fullpath.slice(positions[positions.length - 1] + 1);
}
};
var filenameNotAllowedChars = /[^\p{L}0-9~`!@$&*()\-_=+{};'",<.>? ]/ug;
var sanitizer = {
filename(s) {
return s.replace(filenameNotAllowedChars, "").trim();
},
delimiter(s) {
s = this.filename(s);
if (!s)
s = "-";
return s;
}
};
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function lockInputMethodComposition(el) {
const state = {
lock: false
};
el.addEventListener("compositionstart", () => {
state.lock = true;
});
el.addEventListener("compositionend", () => {
state.lock = false;
});
return state;
}
// src/batch.ts
var ImageBatchRenameModal = class extends import_obsidian.Modal {
constructor(app, activeFile, renameFunc, onClose) {
super(app);
this.activeFile = activeFile;
this.renameFunc = renameFunc;
this.onCloseExtra = onClose;
this.state = {
namePattern: "",
extPattern: "",
nameReplace: "",
renameTasks: []
};
}
onOpen() {
this.containerEl.addClass("image-rename-modal");
const { contentEl, titleEl } = this;
titleEl.setText("Batch rename embeded files");
const namePatternSetting = new import_obsidian.Setting(contentEl).setName("Name pattern").setDesc("Please input the name pattern to match files (regex)").addText((text) => text.setValue(this.state.namePattern).onChange(
(value) => __async(this, null, function* () {
this.state.namePattern = value;
})
));
const npInputEl = namePatternSetting.controlEl.children[0];
npInputEl.focus();
const npInputState = lockInputMethodComposition(npInputEl);
npInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !npInputState.lock) {
e.preventDefault();
if (!this.state.namePattern) {
errorEl.innerText = 'Error: "Name pattern" could not be empty';
errorEl.style.display = "block";
return;
}
this.matchImageNames(tbodyEl);
}
}));
const extPatternSetting = new import_obsidian.Setting(contentEl).setName("Extension pattern").setDesc("Please input the extension pattern to match files (regex)").addText((text) => text.setValue(this.state.extPattern).onChange(
(value) => __async(this, null, function* () {
this.state.extPattern = value;
})
));
const extInputEl = extPatternSetting.controlEl.children[0];
extInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter") {
e.preventDefault();
this.matchImageNames(tbodyEl);
}
}));
const nameReplaceSetting = new import_obsidian.Setting(contentEl).setName("Name replace").setDesc("Please input the string to replace the matched name (use $1, $2 for regex groups)").addText((text) => text.setValue(this.state.nameReplace).onChange(
(value) => __async(this, null, function* () {
this.state.nameReplace = value;
})
));
const nrInputEl = nameReplaceSetting.controlEl.children[0];
const nrInputState = lockInputMethodComposition(nrInputEl);
nrInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !nrInputState.lock) {
e.preventDefault();
this.matchImageNames(tbodyEl);
}
}));
const matchedContainer = contentEl.createDiv({
cls: "matched-container"
});
const tableET = createElementTree(matchedContainer, {
tag: "table",
children: [
{
tag: "thead",
children: [
{
tag: "tr",
children: [
{
tag: "td",
text: "Original path"
},
{
tag: "td",
text: "Renamed Name"
}
]
}
]
},
{
tag: "tbody"
}
]
});
const tbodyEl = tableET.children[1].el;
const errorEl = contentEl.createDiv({
cls: "error",
attr: {
style: "display: none;"
}
});
new import_obsidian.Setting(contentEl).addButton((button) => {
button.setButtonText("Rename all").setClass("mod-cta").onClick(() => {
new ConfirmModal(
this.app,
"Confirm rename all",
`Are you sure? This will rename all the ${this.state.renameTasks.length} images matched the pattern.`,
() => {
this.renameAll();
this.close();
}
).open();
});
}).addButton((button) => {
button.setButtonText("Cancel").onClick(() => {
this.close();
});
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.onCloseExtra();
}
renameAll() {
return __async(this, null, function* () {
debugLog("renameAll", this.state);
for (const task of this.state.renameTasks) {
yield this.renameFunc(task.file, task.name);
}
});
}
matchImageNames(tbodyEl) {
const { state } = this;
const renameTasks = [];
tbodyEl.empty();
const fileCache = this.app.metadataCache.getFileCache(this.activeFile);
if (!fileCache || !fileCache.embeds)
return;
const namePatternRegex = new RegExp(state.namePattern, "g");
const extPatternRegex = new RegExp(state.extPattern);
fileCache.embeds.forEach((embed) => {
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, this.activeFile.path);
if (!file) {
console.warn("file not found", embed.link);
return;
}
if (state.extPattern) {
const m0 = extPatternRegex.exec(file.extension);
if (!m0)
return;
}
const stem = file.basename;
namePatternRegex.lastIndex = 0;
const m1 = namePatternRegex.exec(stem);
if (!m1)
return;
let renamedName = file.name;
if (state.nameReplace) {
namePatternRegex.lastIndex = 0;
renamedName = stem.replace(namePatternRegex, state.nameReplace);
renamedName = `${renamedName}.${file.extension}`;
}
renameTasks.push({
file,
name: renamedName
});
createElementTree(tbodyEl, {
tag: "tr",
children: [
{
tag: "td",
children: [
{
tag: "span",
text: file.name
},
{
tag: "div",
text: file.path,
attr: {
class: "file-path"
}
}
]
},
{
tag: "td",
children: [
{
tag: "span",
text: renamedName
},
{
tag: "div",
text: path.join(file.parent.path, renamedName),
attr: {
class: "file-path"
}
}
]
}
]
});
});
debugLog("new renameTasks", renameTasks);
state.renameTasks = renameTasks;
}
};
var ConfirmModal = class extends import_obsidian.Modal {
constructor(app, title, message, onConfirm) {
super(app);
this.title = title;
this.message = message;
this.onConfirm = onConfirm;
}
onOpen() {
const { contentEl, titleEl } = this;
titleEl.setText(this.title);
contentEl.createEl("p", {
text: this.message
});
new import_obsidian.Setting(contentEl).addButton((button) => {
button.setButtonText("Yes").setClass("mod-warning").onClick(() => {
this.onConfirm();
this.close();
});
}).addButton((button) => {
button.setButtonText("No").onClick(() => {
this.close();
});
});
}
};
// src/template.ts
var dateTmplRegex = /{{DATE:([^}]+)}}/gm;
var frontmatterTmplRegex = /{{frontmatter:([^}]+)}}/gm;
var replaceDateVar = (s, date) => {
const m = dateTmplRegex.exec(s);
if (!m)
return s;
return s.replace(m[0], date.format(m[1]));
};
var replaceFrontmatterVar = (s, frontmatter) => {
if (!frontmatter)
return s;
const m = frontmatterTmplRegex.exec(s);
if (!m)
return s;
return s.replace(m[0], frontmatter[m[1]] || "");
};
var renderTemplate = (tmpl, data, frontmatter) => {
const now = window.moment();
let text = tmpl;
let newtext;
while ((newtext = replaceDateVar(text, now)) != text) {
text = newtext;
}
while ((newtext = replaceFrontmatterVar(text, frontmatter)) != text) {
text = newtext;
}
text = text.replace(/{{imageNameKey}}/gm, data.imageNameKey).replace(/{{fileName}}/gm, data.fileName).replace(/{{dirName}}/gm, data.dirName).replace(/{{firstHeading}}/gm, data.firstHeading);
return text;
};
// src/main.ts
var DEFAULT_SETTINGS = {
imageNamePattern: "{{fileName}}",
dupNumberAtStart: false,
dupNumberDelimiter: "-",
dupNumberAlways: false,
autoRename: false,
handleAllAttachments: false,
excludeExtensionPattern: "",
disableRenameNotice: false
};
var PASTED_IMAGE_PREFIX = "Pasted image ";
var PasteImageRenamePlugin = class extends import_obsidian2.Plugin {
constructor() {
super(...arguments);
this.modals = [];
}
onload() {
return __async(this, null, function* () {
const pkg = require_package();
console.log(`Plugin loading: ${pkg.name} ${pkg.version} BUILD_ENV=${"production"}`);
yield this.loadSettings();
this.registerEvent(
this.app.vault.on("create", (file) => {
if (!(file instanceof import_obsidian2.TFile))
return;
const timeGapMs = new Date().getTime() - file.stat.ctime;
if (timeGapMs > 1e3)
return;
if (isMarkdownFile(file))
return;
if (isPastedImage(file)) {
debugLog("pasted image created", file);
this.startRenameProcess(file, this.settings.autoRename);
} else {
if (this.settings.handleAllAttachments) {
debugLog("handleAllAttachments for file", file);
if (this.testExcludeExtension(file)) {
debugLog("excluded file by ext", file);
return;
}
this.startRenameProcess(file, this.settings.autoRename);
}
}
})
);
const startBatchRenameProcess = () => {
this.openBatchRenameModal();
};
this.addCommand({
id: "batch-rename-embeded-files",
name: "Batch rename embeded files (in the current file)",
callback: startBatchRenameProcess
});
if (DEBUG) {
this.addRibbonIcon("wand-glyph", "Batch rename embeded files", startBatchRenameProcess);
}
const batchRenameAllImages = () => {
this.batchRenameAllImages();
};
this.addCommand({
id: "batch-rename-all-images",
name: "Batch rename all images instantly (in the current file)",
callback: batchRenameAllImages
});
if (DEBUG) {
this.addRibbonIcon("wand-glyph", "Batch rename all images instantly (in the current file)", batchRenameAllImages);
}
this.addSettingTab(new SettingTab(this.app, this));
});
}
startRenameProcess(file, autoRename = false) {
return __async(this, null, function* () {
const activeFile = this.getActiveFile();
if (!activeFile) {
new import_obsidian2.Notice("Error: No active file found.");
return;
}
const { stem, newName, isMeaningful } = this.generateNewName(file, activeFile);
debugLog("generated newName:", newName, isMeaningful);
if (!isMeaningful || !autoRename) {
this.openRenameModal(file, isMeaningful ? stem : "", activeFile.path);
return;
}
this.renameFile(file, newName, activeFile.path, true);
});
}
renameFile(file, inputNewName, sourcePath, replaceCurrentLine) {
return __async(this, null, function* () {
const { name: newName } = yield this.deduplicateNewName(inputNewName, file);
debugLog("deduplicated newName:", newName);
const originName = file.name;
const linkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
const newPath = path.join(file.parent.path, newName);
try {
yield this.app.fileManager.renameFile(file, newPath);
} catch (err) {
new import_obsidian2.Notice(`Failed to rename ${newName}: ${err}`);
throw err;
}
if (!replaceCurrentLine) {
return;
}
const newLinkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
debugLog("replace text", linkText, newLinkText);
const editor = this.getActiveEditor();
if (!editor) {
new import_obsidian2.Notice(`Failed to rename ${newName}: no active editor`);
return;
}
const cursor = editor.getCursor();
const line = editor.getLine(cursor.line);
const replacedLine = line.replace(linkText, newLinkText);
debugLog("current line -> replaced line", line, replacedLine);
editor.transaction({
changes: [
{
from: __spreadProps(__spreadValues({}, cursor), { ch: 0 }),
to: __spreadProps(__spreadValues({}, cursor), { ch: line.length }),
text: replacedLine
}
]
});
if (!this.settings.disableRenameNotice) {
new import_obsidian2.Notice(`Renamed ${originName} to ${newName}`);
}
});
}
openRenameModal(file, newName, sourcePath) {
const modal = new ImageRenameModal(
this.app,
file,
newName,
(confirmedName) => {
debugLog("confirmedName:", confirmedName);
this.renameFile(file, confirmedName, sourcePath, true);
},
() => {
this.modals.splice(this.modals.indexOf(modal), 1);
}
);
this.modals.push(modal);
modal.open();
debugLog("modals count", this.modals.length);
}
openBatchRenameModal() {
const activeFile = this.getActiveFile();
const modal = new ImageBatchRenameModal(
this.app,
activeFile,
(file, name) => __async(this, null, function* () {
yield this.renameFile(file, name, activeFile.path);
}),
() => {
this.modals.splice(this.modals.indexOf(modal), 1);
}
);
this.modals.push(modal);
modal.open();
}
batchRenameAllImages() {
return __async(this, null, function* () {
const activeFile = this.getActiveFile();
const fileCache = this.app.metadataCache.getFileCache(activeFile);
if (!fileCache || !fileCache.embeds)
return;
const extPatternRegex = /jpe?g|png|gif|tiff|webp/i;
for (const embed of fileCache.embeds) {
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, activeFile.path);
if (!file) {
console.warn("file not found", embed.link);
return;
}
const m0 = extPatternRegex.exec(file.extension);
if (!m0)
return;
const { newName, isMeaningful } = this.generateNewName(file, activeFile);
debugLog("generated newName:", newName, isMeaningful);
if (!isMeaningful) {
new import_obsidian2.Notice("Failed to batch rename images: the generated name is not meaningful");
break;
}
yield this.renameFile(file, newName, activeFile.path, false);
}
});
}
// returns a new name for the input file, with extension
generateNewName(file, activeFile) {
let imageNameKey = "";
let firstHeading = "";
let frontmatter;
const fileCache = this.app.metadataCache.getFileCache(activeFile);
if (fileCache) {
debugLog("frontmatter", fileCache.frontmatter);
frontmatter = fileCache.frontmatter;
imageNameKey = (frontmatter == null ? void 0 : frontmatter.imageNameKey) || "";
firstHeading = getFirstHeading(fileCache.headings);
} else {
console.warn("could not get file cache from active file", activeFile.name);
}
const stem = renderTemplate(
this.settings.imageNamePattern,
{
imageNameKey,
fileName: activeFile.basename,
dirName: activeFile.parent.name,
firstHeading
},
frontmatter
);
const meaninglessRegex = new RegExp(`[${this.settings.dupNumberDelimiter}\\s]`, "gm");
return {
stem,
newName: stem + "." + file.extension,
isMeaningful: stem.replace(meaninglessRegex, "") !== ""
};
}
// newName: foo.ext
deduplicateNewName(newName, file) {
return __async(this, null, function* () {
const dir = file.parent.path;
const listed = yield this.app.vault.adapter.list(dir);
debugLog("sibling files", listed);
const newNameExt = path.extension(newName), newNameStem = newName.slice(0, newName.length - newNameExt.length - 1), newNameStemEscaped = escapeRegExp(newNameStem), delimiter = this.settings.dupNumberDelimiter, delimiterEscaped = escapeRegExp(delimiter);
let dupNameRegex;
if (this.settings.dupNumberAtStart) {
dupNameRegex = new RegExp(
`^(?<number>\\d+)${delimiterEscaped}(?<name>${newNameStemEscaped})\\.${newNameExt}$`
);
} else {
dupNameRegex = new RegExp(
`^(?<name>${newNameStemEscaped})${delimiterEscaped}(?<number>\\d+)\\.${newNameExt}$`
);
}
debugLog("dupNameRegex", dupNameRegex);
const dupNameNumbers = [];
let isNewNameExist = false;
for (let sibling of listed.files) {
sibling = path.basename(sibling);
if (sibling == newName) {
isNewNameExist = true;
continue;
}
const m = dupNameRegex.exec(sibling);
if (!m)
continue;
dupNameNumbers.push(parseInt(m.groups.number));
}
if (isNewNameExist || this.settings.dupNumberAlways) {
const newNumber = dupNameNumbers.length > 0 ? Math.max(...dupNameNumbers) + 1 : 1;
if (this.settings.dupNumberAtStart) {
newName = `${newNumber}${delimiter}${newNameStem}.${newNameExt}`;
} else {
newName = `${newNameStem}${delimiter}${newNumber}.${newNameExt}`;
}
}
return {
name: newName,
stem: newName.slice(0, newName.length - newNameExt.length - 1),
extension: newNameExt
};
});
}
getActiveFile() {
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
const file = view == null ? void 0 : view.file;
debugLog("active file", file == null ? void 0 : file.path);
return file;
}
getActiveEditor() {
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
return view == null ? void 0 : view.editor;
}
onunload() {
this.modals.map((modal) => modal.close());
}
testExcludeExtension(file) {
const pattern = this.settings.excludeExtensionPattern;
if (!pattern)
return false;
return new RegExp(pattern).test(file.extension);
}
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);
});
}
};
function getFirstHeading(headings) {
if (headings && headings.length > 0) {
for (const heading of headings) {
if (heading.level === 1) {
return heading.heading;
}
}
}
return "";
}
function isPastedImage(file) {
if (file instanceof import_obsidian2.TFile) {
if (file.name.startsWith(PASTED_IMAGE_PREFIX)) {
return true;
}
}
return false;
}
function isMarkdownFile(file) {
if (file instanceof import_obsidian2.TFile) {
if (file.extension === "md") {
return true;
}
}
return false;
}
var ImageRenameModal = class extends import_obsidian2.Modal {
constructor(app, src, stem, renameFunc, onClose) {
super(app);
this.src = src;
this.stem = stem;
this.renameFunc = renameFunc;
this.onCloseExtra = onClose;
}
onOpen() {
this.containerEl.addClass("image-rename-modal");
const { contentEl, titleEl } = this;
titleEl.setText("Rename image");
const imageContainer = contentEl.createDiv({
cls: "image-container"
});
imageContainer.createEl("img", {
attr: {
src: this.app.vault.getResourcePath(this.src)
}
});
let stem = this.stem;
const ext = this.src.extension;
const getNewName = (stem2) => stem2 + "." + ext;
const getNewPath = (stem2) => path.join(this.src.parent.path, getNewName(stem2));
const infoET = createElementTree(contentEl, {
tag: "ul",
cls: "info",
children: [
{
tag: "li",
children: [
{
tag: "span",
text: "Origin path"
},
{
tag: "span",
text: this.src.path
}
]
},
{
tag: "li",
children: [
{
tag: "span",
text: "New path"
},
{
tag: "span",
text: getNewPath(stem)
}
]
}
]
});
const doRename = () => __async(this, null, function* () {
debugLog("doRename", `stem=${stem}`);
this.renameFunc(getNewName(stem));
});
const nameSetting = new import_obsidian2.Setting(contentEl).setName("New name").setDesc("Please input the new name for the image (without extension)").addText((text) => text.setValue(stem).onChange(
(value) => __async(this, null, function* () {
stem = sanitizer.filename(value);
infoET.children[1].children[1].el.innerText = getNewPath(stem);
})
));
const nameInputEl = nameSetting.controlEl.children[0];
nameInputEl.focus();
const nameInputState = lockInputMethodComposition(nameInputEl);
nameInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !nameInputState.lock) {
e.preventDefault();
if (!stem) {
errorEl.innerText = 'Error: "New name" could not be empty';
errorEl.style.display = "block";
return;
}
doRename();
this.close();
}
}));
const errorEl = contentEl.createDiv({
cls: "error",
attr: {
style: "display: none;"
}
});
new import_obsidian2.Setting(contentEl).addButton((button) => {
button.setButtonText("Rename").onClick(() => {
doRename();
this.close();
});
}).addButton((button) => {
button.setButtonText("Cancel").onClick(() => {
this.close();
});
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.onCloseExtra();
}
};
var imageNamePatternDesc = `
The pattern indicates how the new name should be generated.
Available variables:
- {{fileName}}: name of the active file, without ".md" extension.
- {{imageNameKey}}: this variable is read from the markdown file's frontmatter, from the same key "imageNameKey".
- {{DATE:$FORMAT}}: use "$FORMAT" to format the current date, "$FORMAT" must be a Moment.js format string, e.g. {{DATE:YYYY-MM-DD}}.
Here are some examples from pattern to image names (repeat in sequence), variables: fileName = "My note", imageNameKey = "foo":
- {{fileName}}: My note, My note-1, My note-2
- {{imageNameKey}}: foo, foo-1, foo-2
- {{imageNameKey}}-{{DATE:YYYYMMDD}}: foo-20220408, foo-20220408-1, foo-20220408-2
`;
var SettingTab = class extends import_obsidian2.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
new import_obsidian2.Setting(containerEl).setName("Image name pattern").setDesc(imageNamePatternDesc).setClass("long-description-setting-item").addText((text) => text.setPlaceholder("{{imageNameKey}}").setValue(this.plugin.settings.imageNamePattern).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.imageNamePattern = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Duplicate number at start (or end)").setDesc(`If enabled, duplicate number will be added at the start as prefix for the image name, otherwise it will be added at the end as suffix for the image name.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAtStart).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberAtStart = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Duplicate number delimiter").setDesc(`The delimiter to generate the number prefix/suffix for duplicated names. For example, if the value is "-", the suffix will be like "-1", "-2", "-3", and the prefix will be like "1-", "2-", "3-". Only characters that are valid in file names are allowed.`).addText((text) => text.setValue(this.plugin.settings.dupNumberDelimiter).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberDelimiter = sanitizer.delimiter(value);
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Always add duplicate number").setDesc(`If enabled, duplicate number will always be added to the image name. Otherwise, it will only be added when the name is duplicated.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAlways).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberAlways = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Auto rename").setDesc(`By default, the rename modal will always be shown to confirm before renaming, if this option is set, the image will be auto renamed after pasting.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.autoRename).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.autoRename = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Handle all attachments").setDesc(`By default, the plugin only handles images that starts with "Pasted image " in name,
which is the prefix Obsidian uses to create images from pasted content.
If this option is set, the plugin will handle all attachments that are created in the vault.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.handleAllAttachments).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.handleAllAttachments = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Exclude extension pattern").setDesc(`This option is only useful when "Handle all attachments" is enabled.
Write a Regex pattern to exclude certain extensions from being handled. Only the first line will be used.`).setClass("single-line-textarea").addTextArea((text) => text.setPlaceholder("docx?|xlsx?|pptx?|zip|rar").setValue(this.plugin.settings.excludeExtensionPattern).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.excludeExtensionPattern = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Disable rename notice").setDesc(`Turn off this option if you don't want to see the notice when renaming images.
Note that Obsidian may display a notice when a link has changed, this option cannot disable that.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.disableRenameNotice).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.disableRenameNotice = value;
yield this.plugin.saveSettings();
})
));
}
};
@@ -0,0 +1,10 @@
{
"id": "obsidian-paste-image-rename",
"name": "Paste image rename",
"version": "1.6.1",
"minAppVersion": "0.12.0",
"description": "Rename pasted images and all the other attchments added to the vault",
"author": "Reorx",
"authorUrl": "https://github.com/reorx",
"isDesktopOnly": false
}
@@ -0,0 +1,79 @@
/* src/styles.css */
:root {
--shadow-color: 0deg 0% 0%;
--shadow-elevation-medium:
0.5px 0.5px 0.7px hsl(var(--shadow-color) / 0.14),
1.1px 1.1px 1.5px -0.9px hsl(var(--shadow-color) / 0.12),
2.4px 2.5px 3.3px -1.8px hsl(var(--shadow-color) / 0.1),
5.3px 5.6px 7.3px -2.7px hsl(var(--shadow-color) / 0.09),
11px 11.4px 15.1px -3.6px hsl(var(--shadow-color) / 0.07);
}
.image-rename-modal .modal {
width: 65%;
min-width: 600px;
}
.image-rename-modal .modal-content {
padding: 10px 5px;
}
.image-rename-modal .image-container {
display: flex;
justify-content: center;
}
.image-rename-modal .info {
padding: 10px 0;
color: var(--text-muted);
user-select: text;
}
.image-rename-modal .info li > span:nth-of-type(1) {
display: inline-block;
width: 6em;
margin-right: .5em;
}
.image-rename-modal .info li > span:nth-of-type(1):after {
content: ":";
float: right;
}
.image-rename-modal .image-container img {
display: block;
max-height: 300px;
box-shadow: var(--shadow-elevation-medium);
}
.image-rename-modal .setting-item-control input {
min-width: 300px;
}
.image-rename-modal .error {
border: 1px solid rgb(201, 90, 90);
color: rgb(134, 22, 22);
padding: 10px;
}
.image-rename-modal table {
font-size: .9em;
line-height: 1.8;
margin-bottom: 1.5em;
user-select: text;
}
.image-rename-modal table td {
padding-right: 1em;
}
.image-rename-modal table thead td {
font-weight: 700;
}
.image-rename-modal table tbody td .file-path {
font-size: .8em;
color: var(--text-faint);
line-height: 1;
}
.long-description-setting-item {
flex-wrap: wrap;
}
.long-description-setting-item .setting-item-description {
white-space: pre-wrap;
line-height: 1.3em;
}
.long-description-setting-item .setting-item-control {
padding-top: 10px;
}
.long-description-setting-item .setting-item-control input {
min-width: 300px;
width: 50%;
}
+7
View File
@@ -0,0 +1,7 @@
{
"deleteOption": "system-trash",
"logsModal": true,
"excludedFolders": "000-inbox",
"ribbonIcon": true,
"excludeSubfolders": true
}
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
{
"id": "oz-clear-unused-images",
"name": "Clear Unused Images",
"version": "1.1.1",
"minAppVersion": "0.11.13",
"description": "Clear the images that you are not using anymore in your markdown notes to save space.",
"author": "Ozan",
"authorUrl": "https://www.ozan.pl",
"fundingUrl": "https://ko-fi.com/ozante",
"isDesktopOnly": false
}
+10
View File
@@ -0,0 +1,10 @@
.unused-images-logs {
margin-bottom: 13px;
margin-top: 5px;
}
.unused-images-center-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
+194
View File
@@ -0,0 +1,194 @@
{
"choices": [
{
"id": "0462f0ba-4349-4531-b1a0-6634e5fc146f",
"name": "日常记录-闪念笔记",
"type": "Template",
"command": false,
"templatePath": "attachment/templates/时间戳笔记.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:YYYYMMDDHHmmSS}}"
},
"folder": {
"enabled": true,
"folders": [
"000-Inbox"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"openFileInNewTab": {
"enabled": true,
"direction": "vertical",
"focus": true
},
"openFile": true,
"openFileInMode": "default",
"fileExistsMode": "Increment the file name",
"setFileExistsBehavior": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"focus": true,
"mode": "default"
}
},
{
"id": "eccc9792-8b8b-42bd-a20d-8c061ece98cf",
"name": "移动杭研-问题记录",
"type": "Template",
"command": false,
"templatePath": "attachment/templates/移动杭研-问题记录.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:MMDD}}-{{value}}"
},
"folder": {
"enabled": true,
"folders": [
"000-Inbox"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"openFileInNewTab": {
"enabled": true,
"direction": "vertical",
"focus": true
},
"openFile": true,
"openFileInMode": "default",
"fileExistsMode": "Increment the file name",
"setFileExistsBehavior": false,
"fileOpening": {
"location": "split",
"direction": "vertical",
"focus": true,
"mode": "default"
}
},
{
"id": "4ad23b51-d9fd-445e-aed8-f332e99f2350",
"name": "移动杭研-开发笔记",
"type": "Template",
"command": false,
"templatePath": "attachment/templates/移动杭研-开发笔记.md",
"fileNameFormat": {
"enabled": true,
"format": "开发笔记"
},
"folder": {
"enabled": true,
"folders": [
"000-Inbox"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"openFileInNewTab": {
"enabled": true,
"direction": "vertical",
"focus": true
},
"openFile": true,
"openFileInMode": "default",
"fileExistsMode": "Nothing",
"setFileExistsBehavior": true,
"fileOpening": {
"location": "split",
"direction": "vertical",
"focus": true,
"mode": "default"
}
}
],
"inputPrompt": "multi-line",
"persistInputPromptDrafts": true,
"useSelectionAsCaptureValue": true,
"devMode": false,
"templateFolderPath": "attachment/templates",
"announceUpdates": "all",
"version": "2.11.0",
"globalVariables": {},
"onePageInputEnabled": false,
"disableOnlineFeatures": true,
"enableRibbonIcon": true,
"showCaptureNotification": true,
"showInputCancellationNotification": false,
"enableTemplatePropertyTypes": false,
"dateAliases": {
"t": "today",
"tm": "tomorrow",
"yd": "yesterday",
"nw": "next week",
"nm": "next month",
"ny": "next year",
"lw": "last week",
"lm": "last month",
"ly": "last year"
},
"ai": {
"defaultModel": "Ask me",
"defaultSystemPrompt": "As an AI assistant within Obsidian, your primary goal is to help users manage their ideas and knowledge more effectively. Format your responses using Markdown syntax. Please use the [[Obsidian]] link format. You can write aliases for the links by writing [[Obsidian|the alias after the pipe symbol]]. To use mathematical notation, use LaTeX syntax. LaTeX syntax for larger equations should be on separate lines, surrounded with double dollar signs ($$). You can also inline math expressions by wrapping it in $ symbols. For example, use $$w_{ij}^{\text{new}}:=w_{ij}^{\text{current}}+etacdotdelta_jcdot x_{ij}$$ on a separate line, but you can write \"($eta$ = learning rate, $delta_j$ = error term, $x_{ij}$ = input)\" inline.",
"promptTemplatesFolderPath": "",
"showAssistant": true,
"providers": [
{
"name": "OpenAI",
"endpoint": "https://api.openai.com/v1",
"apiKey": "",
"models": [
{
"name": "gpt-3.5-turbo",
"maxTokens": 4096
},
{
"name": "gpt-3.5-turbo-16k",
"maxTokens": 16384
},
{
"name": "gpt-3.5-turbo-1106",
"maxTokens": 16385
},
{
"name": "gpt-4",
"maxTokens": 8192
},
{
"name": "gpt-4-32k",
"maxTokens": 32768
},
{
"name": "gpt-4-1106-preview",
"maxTokens": 128000
},
{
"name": "text-davinci-003",
"maxTokens": 4096
}
],
"modelSource": "auto"
}
]
},
"migrations": {
"migrateToMacroIDFromEmbeddedMacro": true,
"useQuickAddTemplateFolder": true,
"incrementFileNameSettingMoveToDefaultBehavior": true,
"mutualExclusionInsertAfterAndWriteToBottomOfFile": true,
"setVersionAfterUpdateModalRelease": true,
"addDefaultAIProviders": true,
"removeMacroIndirection": true,
"migrateFileOpeningSettings": true,
"setProviderModelDiscoveryMode": true,
"backfillFileOpeningDefaults": true,
"migrateProviderApiKeysToSecretStorage": true
}
}
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
{
"id": "quickadd",
"name": "QuickAdd",
"version": "2.11.0",
"minAppVersion": "1.11.4",
"description": "Quickly add new pages or content to your vault.",
"author": "Christian B. B. Houmann",
"authorUrl": "https://bagerbach.com",
"fundingUrl": "https://www.buymeacoffee.com/chhoumann",
"helpUrl": "https://quickadd.obsidian.guide/docs/",
"isDesktopOnly": false
}
File diff suppressed because one or more lines are too long
+211
View File
@@ -0,0 +1,211 @@
{
"recentFiles": [
{
"basename": "常用命令",
"path": "resource/常用命令.md"
},
{
"basename": "服务器-香港",
"path": "personal/服务器-香港.md"
},
{
"basename": "20260228151875",
"path": "000-Inbox/20260228151875.md"
},
{
"basename": "ibs-ai 项目 AI 对话流程",
"path": "000-inbox/ibs-ai 项目 AI 对话流程.md"
},
{
"basename": "20260228151875",
"path": "000-inbox/20260228151875.md"
},
{
"basename": "20260228121029",
"path": "000-inbox/20260228121029.md"
},
{
"basename": "React 工程师",
"path": "000-inbox/React 工程师.md"
},
{
"basename": "20260228160576",
"path": "000-Inbox/20260228160576.md"
},
{
"basename": "20260228121029",
"path": "000-Inbox/20260228121029.md"
},
{
"basename": "词性",
"path": "000-inbox/词性.md"
},
{
"basename": "开发笔记",
"path": "work/移动杭研/开发记录/7.18.0/开发笔记.md"
},
{
"basename": "Ubuntu 24.04 LTS ELK 8 安装指南",
"path": "resource/组件/Ubuntu 24.04 LTS ELK 8 安装指南.md"
},
{
"basename": "句子",
"path": "000-inbox/句子.md"
},
{
"basename": "20260227091585",
"path": "000-Inbox/20260227091585.md"
},
{
"basename": "20260227085218",
"path": "000-Inbox/20260227085218.md"
},
{
"basename": "Rime 快捷键",
"path": "resource/工具/Rime 快捷键.md"
},
{
"basename": "20260226205814",
"path": "000-Inbox/20260226205814.md"
},
{
"basename": "20260226171537",
"path": "000-Inbox/20260226171537.md"
},
{
"basename": "20260226100117",
"path": "000-Inbox/20260226100117.md"
},
{
"basename": "20260226102970",
"path": "000-Inbox/20260226102970.md"
},
{
"basename": "20260226143206",
"path": "000-Inbox/20260226143206.md"
},
{
"basename": "ubuntu",
"path": "resource/系统/ubuntu.md"
},
{
"basename": "服务器-华为",
"path": "personal/服务器-华为.md"
},
{
"basename": "20260225210630",
"path": "000-Inbox/20260225210630.md"
},
{
"basename": "tg-bot",
"path": "000-inbox/tg-bot.md"
},
{
"basename": "DNS 泄露问题",
"path": "resource/系统/网络/DNS 泄露问题.md"
},
{
"basename": "vim 快捷键与操作手册",
"path": "resource/系统/vim 快捷键与操作手册.md"
},
{
"basename": "OpenClaw",
"path": "resource/ai/OpenClaw.md"
},
{
"basename": "ibs-export",
"path": "work/移动杭研/业务梳理/ibs-export.md"
},
{
"basename": "20260223203842",
"path": "000-Inbox/20260223203842.md"
},
{
"basename": "Claude Code 配置文件",
"path": "resource/配置/Claude Code 配置文件.md"
},
{
"basename": "Claude Code 提示词",
"path": "resource/配置/Claude Code 提示词.md"
},
{
"basename": "20260211113769",
"path": "000-Inbox/20260211113769.md"
},
{
"basename": "Rime YAML Custom Patch 语法笔记",
"path": "resource/工具/Rime YAML Custom Patch 语法笔记.md"
},
{
"basename": "Rime 小狼毫",
"path": "resource/工具/Rime 小狼毫.md"
},
{
"basename": "HCDN-备忘录",
"path": "work/移动杭研/开发记录/7.17.0/HCDN-备忘录.md"
},
{
"basename": "domain_request 分支梳理 2",
"path": "work/移动杭研/开发记录/7.18.0/domain_request 分支梳理 2.md"
},
{
"basename": "domain_request 分支梳理",
"path": "work/移动杭研/开发记录/7.18.0/domain_request 分支梳理.md"
},
{
"basename": "域名配置需求工单-提单",
"path": "excalidraw/域名配置需求工单-提单.md"
},
{
"basename": "ESOP-域名配置需求-新增",
"path": "work/移动杭研/业务梳理/运营工单/ESOP-域名配置需求-新增.md"
},
{
"basename": "idea jvm",
"path": "resource/配置/idea jvm.md"
},
{
"basename": "20260208143789",
"path": "000-Inbox/20260208143789.md"
},
{
"basename": "20260207215002",
"path": "000-Inbox/20260207215002.md"
},
{
"basename": "20260207103531",
"path": "000-Inbox/20260207103531.md"
},
{
"basename": "20260206231116",
"path": "000-Inbox/20260206231116.md"
},
{
"basename": "开发笔记",
"path": "work/移动杭研/开发记录/7.17.0/开发笔记.md"
},
{
"basename": "20260206081367",
"path": "000-Inbox/20260206081367.md"
},
{
"basename": "信安业务流程及代码梳理",
"path": "work/移动杭研/业务梳理/信安信息/信安业务流程及代码梳理.md"
},
{
"basename": "域名配置需求工单-终止",
"path": "excalidraw/域名配置需求工单-终止.md"
},
{
"basename": "This is the prompt",
"path": "resource/ai/This is the prompt.md"
}
],
"omittedPaths": [
"^attachment/",
"^calendar/"
],
"omittedTags": [],
"updateOn": "file-open",
"omitBookmarks": false
}
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
{
"id": "recent-files-obsidian",
"name": "Recent Files",
"version": "1.7.6",
"minAppVersion": "0.16.3",
"description": "List files by most recently opened",
"author": "Tony Grosinger",
"authorUrl": "https://grosinger.net",
"isDesktopOnly": false,
"fundingUrl": {
"Github Sponsor": "https://github.com/sponsors/tgrosinger",
"Buy me a Coffee": "https://buymeacoffee.com/tgrosinger",
"Paypal": "https://paypal.me/tgrosinger"
},
"donation": "https://buymeacoffee.com/tgrosinger"
}
+34
View File
@@ -0,0 +1,34 @@
.recent-files-file {
.tree-item-spacer {
flex-grow: 1;
}
}
.recent-files-title {
justify-content: flex-start;
align-items: unset;
}
.recent-files-file-delete {
justify-content: right;
display: none;
}
.recent-files-title:hover .recent-files-file-delete {
display: flex;
cursor: var(--cursor);
}
.recent-files-file-delete:hover {
color: var(--nav-item-color-hover);
}
.recent-files-donation {
width: 70%;
margin: 0 auto;
text-align: center;
}
.recent-files-donate-button {
margin: 10px;
}
+30
View File
@@ -0,0 +1,30 @@
{
"command_timeout": 5,
"templates_folder": "attachment/templates",
"templates_pairs": [
[
"",
""
]
],
"trigger_on_file_creation": true,
"auto_jump_to_cursor": true,
"enable_system_commands": false,
"shell_path": "",
"user_scripts_folder": "",
"enable_folder_templates": false,
"folder_templates": [],
"enable_file_templates": false,
"file_templates": [
{
"regex": ".*",
"template": ""
}
],
"syntax_highlighting": true,
"syntax_highlighting_mobile": false,
"enabled_templates_hotkeys": [],
"startup_templates": [],
"intellisense_render": 1,
"enable_ribbon_icon": true
}
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
{
"id": "templater-obsidian",
"name": "Templater",
"version": "2.18.1",
"description": "Create and use templates",
"minAppVersion": "1.5.0",
"author": "SilentVoid",
"authorUrl": "https://github.com/SilentVoid13",
"helpUrl": "https://silentvoid13.github.io/Templater/",
"isDesktopOnly": false
}
+226
View File
@@ -0,0 +1,226 @@
.templater_search {
width: calc(100% - 20px);
}
.templater_div {
border-top: 1px solid var(--background-modifier-border);
}
.templater_div > .setting-item {
border-top: none !important;
align-self: center;
}
.templater_div > .setting-item > .setting-item-control {
justify-content: space-around;
padding: 0;
width: 100%;
}
.templater_div
> .setting-item
> .setting-item-control
> .setting-editor-extra-setting-button {
align-self: center;
}
.templater_donating {
margin: 10px;
}
.templater_title {
margin: 0;
padding: 0;
margin-top: 5px;
text-align: center;
}
.templater_template {
align-self: center;
margin-left: 5px;
margin-right: 5px;
width: 70%;
}
.templater_cmd {
margin-left: 5px;
margin-right: 5px;
font-size: 14px;
width: 100%;
}
.templater_div2 > .setting-item {
align-content: center;
justify-content: center;
}
.templater-prompt-div,
.templater-multisuggester-div {
display: flex;
}
.templater-prompt-form {
display: flex;
flex-grow: 1;
}
.templater-prompt-input,
.templater-multisuggester-input {
flex-grow: 1;
}
.templater-button-div {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 1rem;
}
textarea.templater-prompt-input {
height: 10rem;
}
textarea.templater-prompt-input:focus {
border-color: var(--interactive-accent);
}
.templater-multisuggester-list {
margin: 1.5em 0;
}
.cm-s-obsidian .templater-command-bg {
left: 0px;
right: 0px;
background-color: var(--background-primary-alt);
}
.cm-s-obsidian .cm-templater-command {
font-size: 0.85em;
font-family: var(--font-monospace);
line-height: 1.3;
}
.cm-s-obsidian .templater-inline .cm-templater-command {
background-color: var(--background-primary-alt);
}
.cm-s-obsidian .cm-templater-command.cm-templater-opening-tag {
font-weight: bold;
}
.cm-s-obsidian .cm-templater-command.cm-templater-closing-tag {
font-weight: bold;
}
.cm-s-obsidian .cm-templater-command.cm-templater-interpolation-tag {
color: var(--code-property, #008bff);
}
.cm-s-obsidian .cm-templater-command.cm-templater-execution-tag {
color: var(--code-function, #c0d700);
}
.cm-s-obsidian .cm-templater-command.cm-keyword {
color: var(--code-keyword, #00a7aa);
font-weight: normal;
}
.cm-s-obsidian .cm-templater-command.cm-atom {
color: var(--code-normal, #f39b35);
}
.cm-s-obsidian .cm-templater-command.cm-value,
.cm-s-obsidian .cm-templater-command.cm-number,
.cm-s-obsidian .cm-templater-command.cm-type {
color: var(--code-value, #a06fca);
}
.cm-s-obsidian .cm-templater-command.cm-def,
.cm-s-obsidian .cm-templater-command.cm-type.cm-def {
color: var(--code-normal, var(--text-normal));
}
.cm-s-obsidian .cm-templater-command.cm-property,
.cm-s-obsidian .cm-templater-command.cm-property.cm-def,
.cm-s-obsidian .cm-templater-command.cm-attribute {
color: var(--code-function, #98e342);
}
.cm-s-obsidian .cm-templater-command.cm-variable,
.cm-s-obsidian .cm-templater-command.cm-variable-2,
.cm-s-obsidian .cm-templater-command.cm-variable-3,
.cm-s-obsidian .cm-templater-command.cm-meta {
color: var(--code-property, #d4d4d4);
}
.cm-s-obsidian .cm-templater-command.cm-callee,
.cm-s-obsidian .cm-templater-command.cm-operator,
.cm-s-obsidian .cm-templater-command.cm-qualifier,
.cm-s-obsidian .cm-templater-command.cm-builtin {
color: var(--code-operator, #fc4384);
}
.cm-s-obsidian .cm-templater-command.cm-tag {
color: var(--code-tag, #fc4384);
}
.cm-s-obsidian .cm-templater-command.cm-comment,
.cm-s-obsidian .cm-templater-command.cm-comment.cm-tag,
.cm-s-obsidian .cm-templater-command.cm-comment.cm-attribute {
color: var(--code-comment, #696d70);
}
.cm-s-obsidian .cm-templater-command.cm-string,
.cm-s-obsidian .cm-templater-command.cm-string-2 {
color: var(--code-string, #e6db74);
}
.cm-s-obsidian .cm-templater-command.cm-header,
.cm-s-obsidian .cm-templater-command.cm-hr {
color: var(--code-keyword, #da7dae);
}
.cm-s-obsidian .cm-templater-command.cm-link {
color: var(--code-normal, #696d70);
}
.cm-s-obsidian .cm-templater-command.cm-error {
border-bottom: 1px solid #c42412;
}
.CodeMirror-hints {
position: absolute;
z-index: 10;
overflow: hidden;
list-style: none;
margin: 0;
padding: 2px;
-webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
border-radius: 3px;
border: 1px solid silver;
background: white;
font-size: 90%;
font-family: monospace;
max-height: 20em;
overflow-y: auto;
}
.CodeMirror-hint {
margin: 0;
padding: 0 4px;
border-radius: 2px;
white-space: pre;
color: black;
cursor: pointer;
}
li.CodeMirror-hint-active {
background: #08f;
color: white;
}
+8
View File
@@ -0,0 +1,8 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var $=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var B=Object.prototype.hasOwnProperty;var O=(e,n)=>{for(var o in n)$(e,o,{get:n[o],enumerable:!0})},L=(e,n,o,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let u of I(n))!B.call(e,u)&&u!==o&&$(e,u,{get:()=>n[u],enumerable:!(i=b(n,u))||i.enumerable});return e};var j=e=>L($({},"__esModule",{value:!0}),e);var A={};O(A,{default:()=>x});module.exports=j(A);var c=require("obsidian");function T(e){return P(y(e).slice(0,-1))}function F(e,n){if(!e)return n;let o=y(e),i=y(n),u=o.findIndex((h,w)=>h!=i[w]),f=[];for(let h=u;h<o.length-1;h++)f.push("..");for(let h=u;h<i.length;h++)f.push(i[h]);return P(f)}function y(e){return e.split("/")}function P(e){return e.join("/")}var E=class extends c.Modal{constructor(n,o,i){super(n),this.content=o,this.onConfirm=i}onOpen(){let{contentEl:n}=this;n.createEl("h1",{text:"Update Releate Links Plugin"}),n.createEl("p",{text:this.content}),new c.Setting(n).addButton(o=>o.setButtonText("Yes").setCta().onClick(()=>{this.close(),this.onConfirm()})).addButton(o=>o.setButtonText("No").onClick(()=>{this.close()}))}onClose(){this.contentEl.empty()}},x=class extends c.Plugin{async onload(){let{app:n}=this,{metadataCache:o,vault:i}=n,u="This command will modify all links in the entire vault (not just the current file) to relative paths, and this action cannot be undone. It is recommended that you back up the vault in advance. Please confirm whether you want to execute the command.";this.addCommand({id:"update-all-relative-links",name:"Update all relative links",callback(){new E(n,u,()=>{let t=i.getMarkdownFiles().map(r=>f(r,!1));Promise.all(t).then(r=>{let s=r.filter(m=>m>0),a=s.reduce((m,C)=>m+C,0),l=s.length;new c.Notice(`Update ${a} links in ${l} file${l>1?"s":""}.`)}).catch(r=>{new c.Notice("Update links error, see console."),console.error(r)})}).open()}}),this.registerEvent(i.on("rename",(t,r)=>{var s;!r||!t.path.toLocaleLowerCase().endsWith(".md")||((s=t.parent)==null?void 0:s.path)===T(r)||t instanceof c.TFile&&setTimeout(()=>f(t,!0),100)}));async function f(t,r){var m,C;let s=o.getFileCache(t),l=[...(m=s==null?void 0:s.links)!=null?m:[],...(C=s==null?void 0:s.embeds)!=null?C:[]].map(({link:d,original:v})=>{var S;let g=d.replace(/#.*$/,"");if(!g)return null;let k=o.getFirstLinkpathDest(g,t.path);if(!k)return null;let p=((S=t.parent)==null?void 0:S.path)==="/"?k.path:F(t.path,k.path);if(g===p)return null;let U=h(v,g,p);return[v,U]}).filter(d=>d);if(!(l!=null&&l.length))return 0;try{let d=await i.read(t),v=l.reduce((k,p)=>(p==null?void 0:p.length)===2?k.replace(p[0],p[1]):k,d);await i.modify(t,v);let g=`Update ${l.length} links in ${t.path}.`;return console.log(g),r&&new c.Notice(g),l.length}catch(d){throw console.error(d),r&&new c.Notice("Update links error, see console."),d}}function h(t,r,s){let a=w(t,r,s,l=>l.replace(/ /g,"%20"));return t===a&&(a=w(t,r,s,encodeURI)),t===a&&(a=t.replace(/^(!?\[.*?\]).*$/,`$1(${encodeURI(s)})`)),a}function w(t,r,s,a){return t.replace(a(r),a(s))}}};
/* nosourcemap */
+10
View File
@@ -0,0 +1,10 @@
{
"id": "update-relative-links",
"name": "Update Relative Links",
"version": "2.1.4",
"minAppVersion": "0.15.0",
"description": "Update relative links.",
"author": "val",
"authorUrl": "https://github.com/val3344/obsidian-update-relative-links",
"isDesktopOnly": false
}
+13
View File
@@ -0,0 +1,13 @@
{
"queries": [
{
"createdAt": "2024/10/27 17:03:06",
"id": "202410271703062",
"pinnedAt": "2024/10/27 17:03:16",
"querystring": "[{\"type\":\"LIST\",\"value\":{\"operator\":\"IS\",\"value\":\"TODO\"},\"relation\":\"AND\"},{\"type\":\"TAG\",\"value\":{\"operator\":\"CONTAIN\",\"value\":\"日记/待办\"},\"relation\":\"OR\"}]",
"title": "任务",
"updatedAt": "2024/10/27 17:03:06",
"userId": "Thino"
}
]
}
+25
View File
@@ -0,0 +1,25 @@
/* === 颜色变量 === */
:root {
--h1-color: #172a4f;
--h2-color: #4a5c7a;
--h3-color: #6b7280;
--bold-color: var(--h1-color);
}
/* === 标题样式 === */
h1 {
color: var(--h1-color);
}
h2 {
color: var(--h2-color);
}
h3 {
color: var(--h3-color);
}
/* === 粗体样式 === */
div.cm-line span.cm-strong,
div p strong {
color: var(--bold-color) !important;
}
+5
View File
@@ -0,0 +1,5 @@
{
"showExistingOnly": false,
"showAttachments": true,
"showAllFileTypes": true
}
+3
View File
@@ -0,0 +1,3 @@
{
"folder": ""
}
+29
View File
@@ -0,0 +1,29 @@
{
"types": {
"aliases": "aliases",
"cssclasses": "multitext",
"tags": "tags",
"excalidraw-plugin": "text",
"excalidraw-export-transparent": "checkbox",
"excalidraw-mask": "checkbox",
"excalidraw-export-dark": "checkbox",
"excalidraw-export-padding": "number",
"excalidraw-export-pngscale": "number",
"excalidraw-export-embed-scene": "checkbox",
"excalidraw-link-prefix": "text",
"excalidraw-url-prefix": "text",
"excalidraw-link-brackets": "checkbox",
"excalidraw-onload-script": "text",
"excalidraw-linkbutton-opacity": "number",
"excalidraw-default-mode": "text",
"excalidraw-font": "text",
"excalidraw-font-color": "text",
"excalidraw-border-color": "text",
"excalidraw-css": "text",
"excalidraw-autoexport": "text",
"excalidraw-embeddable-theme": "text",
"excalidraw-open-md": "checkbox",
"excalidraw-embed-md": "checkbox",
"excalidraw-export-internal-links": "checkbox"
}
}
+278
View File
@@ -0,0 +1,278 @@
{
"main": {
"id": "5bcfdfb3906a10d9",
"type": "split",
"children": [
{
"id": "52c75605242e548c",
"type": "tabs",
"children": [
{
"id": "27e8f607bdc36b8c",
"type": "leaf",
"state": {
"type": "empty",
"state": {},
"icon": "lucide-file",
"title": "新标签页"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "35df6ec3039dcc8d",
"type": "split",
"children": [
{
"id": "6836caf2765d7139",
"type": "tabs",
"dimension": 69.27016645326505,
"children": [
{
"id": "c8718c0c63702202",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "文件列表"
}
},
{
"id": "b5a3cd3bad8e4921",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "搜索"
}
},
{
"id": "48045e844afa284a",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "书签"
}
}
]
},
{
"id": "729e97c586d5eaa9",
"type": "tabs",
"dimension": 30.72983354673495,
"children": [
{
"id": "4c0b938080320781",
"type": "leaf",
"state": {
"type": "recent-files",
"state": {},
"icon": "clock",
"title": "Recent Files"
}
}
]
}
],
"direction": "horizontal",
"width": 335.5
},
"right": {
"id": "ca733f6d5936ae40",
"type": "split",
"children": [
{
"id": "1f21045435bfa327",
"type": "tabs",
"dimension": 57.40291262135923,
"children": [
{
"id": "43a2e8e1d9201229",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "大纲"
}
},
{
"id": "d8fdece29a1174b9",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "000-Inbox/010-时间戳/20250516111714.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "20250516111714 的反向链接列表"
}
},
{
"id": "e4cd4c732455d6a5",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "出链"
}
},
{
"id": "937e0fc00804e8ba",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "标签"
}
},
{
"id": "313efe6d54d147a2",
"type": "leaf",
"state": {
"type": "all-properties",
"state": {
"sortOrder": "frequency",
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-archive",
"title": "添加笔记属性"
}
},
{
"id": "4e96951a5d10d474",
"type": "leaf",
"state": {
"type": "footnotes",
"state": {
"file": "900-Excalidraw/退回接口.md"
},
"icon": "lucide-file-signature",
"title": "脚注"
}
}
]
},
{
"id": "ea8a40d57ef90e62",
"type": "tabs",
"dimension": 42.59708737864077,
"children": [
{
"id": "499dbd491c6fb256",
"type": "leaf",
"state": {
"type": "calendar",
"state": {},
"icon": "calendar-with-checkmark",
"title": "Calendar"
}
}
]
}
],
"direction": "horizontal",
"width": 306.5
},
"left-ribbon": {
"hiddenItems": {
"command-palette:打开命令面板": false,
"switcher:打开快速切换": false,
"graph:查看关系图谱": false,
"daily-notes:打开/创建今天的日记": false,
"bases:新建数据库": false,
"obsidian-excalidraw-plugin:新建绘图文件": false,
"templater-obsidian:Templater": false,
"quickadd:QuickAdd": false,
"oz-clear-unused-images:Clear Unused Images": false
}
},
"active": "27e8f607bdc36b8c",
"lastOpenFiles": [
"resource/常用命令.md",
"personal/服务器-香港.md",
"calendar/diary/2026-02-28.md",
"calendar/diary/2026-02-27.md",
"000-Inbox/20260228151875.md",
"000-inbox/ibs-ai 项目 AI 对话流程.md",
"000-inbox/20260228151875.md",
"000-inbox/20260228121029.md",
"000-inbox/React 工程师.md",
"000-Inbox/20260228160576.md",
"attachment/Pasted image 20260228121021.png",
"attachment/Pasted image 20260228121011.png",
"attachment/Pasted image 20260228121002.png",
"000-Inbox/20260228121029.md",
"calendar/diary/2026-02-26.md",
"000-inbox/词性.md",
"work/移动杭研/开发记录/7.18.0/开发笔记.md",
"resource/组件/Ubuntu 24.04 LTS ELK 8 安装指南.md",
"000-inbox/20260226171537.md",
"000-inbox/句子.md",
"000-inbox/20260226205814.md",
"000-Inbox/20260227091585.md",
"000-Inbox/20260227085218.md",
"resource/工具/Rime 快捷键.md",
"000-Inbox/20260226205814.md",
"000-Inbox/20260226171537.md",
"calendar/diary/2026-02-05.md",
"calendar/diary/2026-02-11.md",
"calendar/diary/2026-02-09.md",
"attachment/images-uuid/64be7a370c2241658123dec75afa8ebb.png",
"attachment/images-paste/image-20260206105036743.png",
"resource/工具",
"attachment/images-paste/image-20260206105528982.png",
"attachment/image-20260130172212583.png",
"attachment/image-20260130171118370.png",
"attachment/Pasted image 20260129215401.png",
"attachment/images-paste/image-20260125202524521.png",
"personal/家人/小杨-年报-AI",
"未命名",
"resource/脚本",
"work/移动杭研/开发记录/7.18.0",
"attachment/霞鹜文楷-Mono-Bold.ttf",
"attachment/霞鹜文楷-Mono-Regular.ttf",
"excalidraw/fonts",
"resource/配置",
"gitleaks.toml"
]
}
+4
View File
@@ -0,0 +1,4 @@
{
"workspaces": {},
"active": "1-左右分屏-源码、阅读"
}
+5
View File
@@ -0,0 +1,5 @@
{
"folder": "000-Inbox",
"template": "100-Attachment/templates/时间戳笔记",
"format": ""
}
+3
View File
@@ -0,0 +1,3 @@
![](../attachment/Pasted%20image%2020260228121011.png)
![](../attachment/Pasted%20image%2020260228121021.png)
+30
View File
@@ -0,0 +1,30 @@
```
curl --location 'http://183.252.183.117/api/v1/chats_openai/d06e1690377d11f0a5df0242ac120006/chat/completions' \
--header 'Authorization: Bearer ragflow-VjOWEyN2U0M2M2MTExZjBiM2UxMDI0Mm' \
--header 'Content-Type: application/json' \
--data '{
"model": "DeepSeek-R1-Distill-Qwen-32B",
"stream": true,
"messages": [
{
"role": "user",
"content": "你是谁?"
}
]
}'
curl --location 'http://183.252.183.117:30021/v1/chat/completions' \
--header 'Content-Type: application/json' \
--data '{
"model": "DeepSeek-R1-Distill-Qwen-32B",
"stream": true,
"messages": [
{
"role": "user",
"content": "你是谁?"
}
]
}'
```
+72
View File
@@ -0,0 +1,72 @@
# 关于 Node
| Node.js | Java |
| --------------- | ----------- |
| Node.js version | JDK version |
| V8 Engine | JVM |
| npm | Maven |
| N-API | JNI |
## Node.js 版本
[Node.js — Node.js 版本](https://nodejs.org/zh-cn/about/previous-releases)
# 学习资料
https://u19tul1sz9g.feishu.cn/docx/WEWJdSzF5oTnwvxHGGAc0Vojnxe?from=from_copylink
密码:M6923&65
# 第一章 开发环境与核心概念
UI = f(State)
用户界面仅仅是应用程序状态的一个函数
初始化环境
```
pnpm create vite@latest basic --template react-ts
```
导入方式:
具名导入:
```
import { ... } from 'react'
```
默认导入:
```
import ... from 'react'
```
副作用导入:
```
import './index.css'
```
# CSS
顶级作用域
```
:root {
```
媒体查询
```
@media (prefers-color-scheme: light) {
}
```
ID 选择器
```
#root
```
+360
View File
@@ -0,0 +1,360 @@
## 1. 对话相关模块与入口
### 1.1 HTTP 接口入口(后端)
聊天(SSE 流式):`POST /session/chat`
查询会话列表:`POST /session/page/list`
查询某会话的历史消息列表:`POST /session/message/list`
消息反馈(点赞/点踩/评论):`POST /session/message_feedback`
### 1.2 鉴权与用户上下文
`/session/**` 路径做拦截:
Filter`web/src/main/java/com/cmcc/ai/web/filter/UserAuthFilter.java:31-55`
---
## 2. 前端怎么传参(对话接口)
### 2.1 发送消息:`POST /session/chat`
```json
{
"content": "用户输入的文本",
"sessionId": 123,
"parentMessageId": 456,
"contentType": "text",
"role": "user"
}
```
#### 实际必填/有效字段(按当前后端实现)
- **必填**`content`
- **可选**`sessionId`
- `sessionId == null`:后端会创建一个新会话
- `sessionId != null`:后端按该会话进行多轮
### 2.2 前端接收方式:SSE
`core/src/main/java/com/cmcc/ai/core/vo/MessageVO.java:20-72`
```json
{
"sessionId": 1,
"messageId": 10002,
"parentMessageId": 10001,
"role": "assistant",
"contentType": "text",
"msgStatus": "sending",
"content": "本次增量 token",
"reasoningContent": "(可选)模型推理内容"
}
```
当流结束时,后端会发送一条 `msgStatus=finished` 的消息(通常 `content` 为空或为最后一段),并结束 SSE 连接。
---
## 3. 后端对话编排流程(从 Controller 到落库)
核心实现类:
- `core/src/main/java/com/cmcc/ai/core/service/impl/SessionServiceImpl.java`
### 3.1 会话创建 / parentMessageId 选择
入口:`generateChatResponses(TextMessageParam param, Long userId)`
- `SessionServiceImpl.java:446-464`
逻辑:
1. **首次对话**`param.sessionId == null`
- `parentMessageId` 被强制设为 `0L`(常量 `DEFAULT_PARENT_MSG_ID`
- 创建 `session_info` 记录(summary 取首条消息截断)
- 代码:`SessionServiceImpl.java:449-453`
2. **非首次对话**`param.sessionId != null`
- 查询该 session 下 `message_info` 的最大 id 作为 `parentMessageId`
- 代码:`SessionServiceImpl.java:454-456`
- Mapper`MessageInfoMapper.xml:143-145``select max(id)`
> 这意味着当前实现的父子关系是“链式”的:每次提问默认挂在上一条消息后面,而不是维护一棵分支对话树。
### 3.2 历史消息拼接(多轮上下文)
历史消息查询:
- `queryHistoryMsg(sessionId)``SessionServiceImpl.java:596-607`
构造规则:
1. 第一条固定是 **system prompt**
- `generateFirstMsg()``SessionServiceImpl.java:614-617`
- system prompt 来源:系统配置 `OLLAMA_CHART_SYSTEM_MSG`
- 若配置不存在则使用默认常量 `SYSTEM_MSG`
2. 从 DB 查出该 session 的所有历史 `message_content`(按 `mc.id asc`
- SQL`MessageContentMapper.xml:151-157`
- 每条记录映射为 `ChatMessage{role, content}`
3. 把“本次用户提问”也 append 到 historyMsg(作为最后一条 user 消息)
- `aiChatDialog()``SessionServiceImpl.java:632-634`
最终 historyMsg 会被完整送给大模型(多轮上下文就是这么实现的)。
### 3.3 用户消息落库
在真正请求大模型之前,用户消息会先落库:
- 插入 `message_info`(用户侧):`insertMessage(…)`
- `SessionServiceImpl.aiChatDialog():628-630`
- 插入 `message_content`(用户文本):`insertMessageContent(…)`
- `SessionServiceImpl.aiChatDialog():630-631`
对应表结构见 `web/src/main/resources/init.sql`
- `session_info`
- `message_info`
- `message_content`
---
## 4. 调用的哪个 AI 接口?请求长什么样?
### 4.1 AI 接口地址与模型配置来自哪里
后端不在配置文件里写死模型与地址,而是从 DB 的 `system_config` 表读取:
- 读取 service`SystemConfigServiceImpl.java:31-50`
- key 枚举:`common/src/main/java/com/cmcc/ai/enums/SysConfigKeyEnum.java:14-31`
对话相关的 key
- `OLLAMA_MODEL`:模型名
- `OLLAMA_CHART_URL`:多轮对话接口地址
- `OLLAMA_CHART_SYSTEM_MSG`system prompt
- `API_KEY`:调用上游 AI 服务时的 Authorization(代码里默认按 Bearer 使用,具体值建议视为敏感信息)
在示例初始化 SQL 中(`init.sql:70-75`),`OLLAMA_CHART_URL` 指向一个 **OpenAI 兼容风格** 的路径:`/v1/chat/completions`
> 虽然命名叫 `OLLAMA_*`,但实际对接的上游接口形态更接近 OpenAI/DeepSeek 的 Chat Completions 流式接口。
### 4.2 后端发给上游 AI 的请求体
组装请求的代码:
- `aiChatDialog()``SessionServiceImpl.java:648-653`
请求体类型:`OllamaMutiplePrompt`
- `common/src/main/java/com/cmcc/ai/model/OllamaMutiplePrompt.java:20-36`
结构:
```json
{
"model": "<来自 system_config.OLLAMA_MODEL>",
"messages": [
{
"role": "system",
"content": "..."
},
{
"role": "user",
"content": "..."
},
{
"role": "assistant",
"content": "..."
},
{
"role": "user",
"content": "本次问题"
}
],
"stream": true
}
```
HTTP 请求(后端 → 上游 AI):
- 方法:POST
- URL`system_config.OLLAMA_CHART_URL`
- Header`Authorization: <system_config.API_KEY>`
- 代码:`SessionServiceImpl.handleMessageWithWebClient():156-175`
- Accept`text/event-stream`
---
## 5. AI 接口返回的什么?后端如何解析?
上游 AI 返回被按“流式”逐行消费:
- `bodyToFlux(String.class)``SessionServiceImpl.handleMessageWithWebClient():170-178`
后端假定每一行 `line`
- 要么是 `"[DONE]"`
- 要么是一个 JSON chunk,可反序列化为 `ChatCompletionChunk`
解析模型(按 DeepSeek/OpenAI streaming chunk 风格):
- `common/src/main/java/com/cmcc/ai/model/ChatCompletionChunk.java`
- 主要取值:`choices[0].delta.content`
- 推理字段:`choices[0].delta.reasoning_content`
对应解析与映射:
- `generateDeepSeekChatMessage()``SessionServiceImpl.java:272-311`
映射结果是内部统一的 `MessageVO`
- `messageVO.content = delta.content`
- `messageVO.reasoningContent = delta.reasoningContent`
- `messageVO.msgStatus`
- chunk 中包含 `finish_reason / stop_reason` 或 line 为 `[DONE]` 时,置为 `finished`
- 否则为 `sending`
> 文件里还存在 `generateChatMessage()` 用于解析 `OllamaStreamResponse``SessionServiceImpl.java:321-345`),但当前 `handleMessageWithWebClient()` 实际调用的是 `generateDeepSeekChatMessage()``SessionServiceImpl.java:184`)。
---
## 6. 后端给前端返回的什么?
### 6.1 SSE 推送的内容
后端对每个上游 chunk 都会向前端推一个 SSE event:
- `ServerSentEvent.builder(JSON.toJSONString(message)).build()`
- 代码:`SessionServiceImpl.handleMessageWithWebClient():178-189`
也就是:
- SSE 的 `data` = `MessageVO` 的 JSON 字符串
前端收到后:
- 通过 `messageId` 把同一条 assistant 消息的 token 拼起来
- 通过 `msgStatus` 判断是否完成
- 通过 `sessionId` 确定归属会话
### 6.2 对话完成后的落库与状态更新
后端在发起上游请求时,会先创建一条“assistant message_infosending)”:
- `assistantMessage = insertMessage(…, MsgStatusEnum.SENDING)`
- 代码:`SessionServiceImpl.handleMessageWithWebClient():162-168`
流式接收过程中:
- `contentBuilder` 累积每个 chunk 的 `delta.content`
- `generateDeepSeekChatMessage():303-306`
流结束后(`doFinally`):
- 正常完成:
- `message_content` 插入完整文本(role=assistant
- `message_info.msg_status` 更新为 `finished`
- 并触发异步评价(见下一节)
- 代码:`SessionServiceImpl.handleMessageWithWebClient():191-195` + `handlerResponseMessage():208-218`
- 异常 / 取消:
- 仍会尽量保存已累积内容
- 并将 `message_info.msg_status` 更新为 `error/cancelled`
- 代码:`handlerResponseMessage():219-235`
### 6.3 非流式查询:历史消息列表接口返回
- `POST /session/message/list`
- 返回:`ApiResponse<List<MessageListVO>>`
- `MessageListVO``core/src/main/java/com/cmcc/ai/core/vo/MessageListVO.java`
- 内含 `contents: List<MessageContentVO>`
这用于“进入会话后加载历史对话”的场景。
---
## 7. 多轮对话是如何实现的?(关键点总结)
本项目的多轮对话实现方式可以概括为:
1. 每次用户发消息时:
- 先把用户消息写入 DB`message_info + message_content`
2. 组装“system + 历史 messages + 本次 user message”形成完整 messages 数组
- 历史消息来自 DB `message_content`,按 `mc.id asc`
3. 把完整 messages 一次性发给上游 `/v1/chat/completions`stream=true
4. 上游返回流式 chunk
- 后端逐 chunk 转成 `MessageVO`,通过 SSE 推给前端
5. 流结束后:
- 把 assistant 的完整文本一次性落库
即:**上下文存储在 DB,每次请求时“全量带上历史”**。
---
## 8. 评价(可选链路,和对话强相关)
对话完成后会触发一次“问答质量评估”的异步调用:
- 触发点:`handlerResponseMessage()``ON_COMPLETE` 分支
- `SessionServiceImpl.java:209-217`
调用实现:`EvaluationServiceImpl.evaluationResult()`
- `core/src/main/java/com/cmcc/ai/core/service/impl/EvaluationServiceImpl.java:115-160`
它会:
1. 从 `system_config` 读取:
- `EVALUATE_TASK_URL`
- `CALLBACK_URL`
2. 读取“本次问/答”的 message_content 内容
3. POST 到评估系统(请求包含 query/response/callbackUrl
4. 评估系统回调:`POST /evaluation/callback`
- Controller`web/src/main/java/com/cmcc/ai/web/rest/EvaluationCallController.java:36-46`
这条链路不影响前端展示主流程,但会写入 `evaluation_result` 表并支撑后续统计/导出。
---
## 9. 给前端的最小对接清单(建议)
### 9.1 发起对话
- `POST /session/chat`
- body
```json
{ "content": "...", "sessionId": 123 }
```
### 9.2 处理 SSE
- 每个 event 的 `data` 是 JSON 字符串(`MessageVO`
- 用 `content` 做增量拼接
- 用 `msgStatus` 判断完成
- **首次对话**从第一条 event 里取 `sessionId`,用于后续多轮
### 9.3 加载历史
- `POST /session/message/list`
- body
```json
{ "sessionId": 123 }
```
(后端只读取 `sessionId` 字段:`SessionController.java:78-83`
---
## 10. 关键代码定位索引
- 对话接口:`web/…/SessionController.java:98-105`
- 对话编排:`core/…/SessionServiceImpl.java:446-654`
- 上游流式请求与解析:`core/…/SessionServiceImpl.java:149-196` + `272-311`
- 历史消息查询 SQL`core/…/MessageContentMapper.xml:151-157`
- 系统配置 key`common/…/SysConfigKeyEnum.java:14-31`
- 初始化表结构与默认配置:`web/src/main/resources/init.sql`
+20
View File
@@ -0,0 +1,20 @@
```
telegrambots 相关源码路径:D:\MyCode\Study\TelegramBots
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-longpolling</artifactId>
<version>${telegrambots.version}</version>
</dependency>
因为我是 Spring Boot 项目,那这里是不是用官方推荐的 telegrambots-springboot-longpolling-starter 更方便呢?
现在请帮我实现一个最基础的机器人的功能:
1. 双向机器人的功能,当有人给这个机器人发消息的时候,可以通知到我。可以参考下比较成熟的双项机器人实现的手段。
2. 给我提供一个前台页面,让我可以在上面发布通知,使得所有使用我这个机器人的用户都能收到消息
```
```
7786028912:AAGxQ1jmD4a7j6k-KGsSainVpsFGplSXp-0
```
+95
View File
@@ -0,0 +1,95 @@
### 第一梯队:句子的绝对核心(没他们开不了机)
#### 1. 主语 (Subject) —— “男/女主角”
- **是什么**:整句话的主导者,也就是**“谁”**或者**“什么东西”**。它通常站在句子的最开头。
- **谁来演**:通常由**名词**或**代词**(我、你、他)来演。
- **例子**
- **I** love apples. **我**爱苹果。) 👉 “我”是主语。
- **The dog** is running. (**狗**在跑。) 👉 “狗”是主语。
#### 2. 谓语 (Predicate) —— “核心动作”
- **是什么**:主角(主语)**“做了什么”**动作。可以说,没有谓语就没有英语句子!
- **谁来演****只能由动词**来演!
- **例子**
- I **love** apples. (我**爱**苹果。) 👉 “爱”是谓语。
- The dog **bites** the cat. (狗**咬**猫。) 👉 “咬”是谓语。
#### 3. 宾语 (Object) —— “承受者 / 倒霉蛋”
- **是什么**:动作的承受者。也就是主角的动作发泄在谁身上了。通常放在谓语(动作)的后面。
- **谁来演**:通常由**名词**或**代词**来演。
- **例子**
- I love **apples**. (我爱**苹果**。) 👉 “苹果”被爱,是宾语。
- The dog bites **the cat**. (狗咬**猫**。) 👉 “猫”被咬,是宾语。
> **💡 小结一下:主谓宾 (S-V-O)**
> **[狗]** (主语/谁) + **[咬]** (谓语/动作) + **[猫]** (宾语/承受者)。
> 这就是英语中最常见、最重要的骨架!
---
### 第二梯队:说明状态的特殊组合
#### 4. 表语 (Predicative) —— “身份状态标签”
- **是什么**:当句子里没有明显的动作(比如跑、跳、吃),而是想说主语**“是什么”**或**“怎么样”**时,用来贴标签的词,就叫表语。
- **位置**:它永远跟在 `am/is/are` (系动词) 的后面。
- **例子**
- I am **a teacher**. (我是**老师**。) 👉 “老师”说明我的身份,是表语。
- She is **beautiful**. (她是**美丽的**。) 👉 “美丽的”说明她的状态,是表语。
---
### 第三梯队:让句子变得更丰富的“配角”
如果你只会主谓宾,句子就像光秃秃的树干(我吃苹果)。要想句子好听,就需要下面这几个“配角”来添枝加叶:
#### 5. 定语 (Attribute) —— “名词的专属化妆师”
- **是什么**:用来修饰、限定**名词**的。它告诉你这个东西“是什么样的”、“谁的”、“多少个”。
- **谁来演**:主要是**形容词**。
- **例子**
- I love **red** apples. (我爱**红色的**苹果。) 👉 “红色的”修饰苹果,是定语。
- This is **my** book. (这是**我的**书。) 👉 “我的”修饰书,是定语。
#### 6. 状语 (Adverbial) —— “电影的背景板”
- **是什么**:用来交代动作发生的**时间、地点、原因、方式**等。它修饰的是动词(动作)或整个句子。
- **谁来演**:通常是**副词**或**介词短语**。
- **例子**
- I eat apples **in the morning**. (我**在早上**吃苹果。) 👉 “在早上”交代时间,是时间状语。
- The dog runs **fast**. (狗跑得**快**。) 👉 “快”交代跑的方式,是方式状语。
#### 7. 补语 (Complement) —— “最后的补充说明”
- **是什么**:有时候有了主语、谓语、宾语,意思还不完整,需要再补充一下。
- **例子**
- Apples make me **happy**. (苹果让我**开心**。)
- *解释*:如果只说 Apples make me (苹果让我…),话没说完。加上 happy,补充说明“我”的状态,这个 happy 就是宾语补足语(简称宾补)。
---
### 👑 终极实战演练(一句话把它们全串起来)
我们来看一句完整的话,看看大家都在演什么角色:
> **Yesterday, my cute dog bit a bad thief seriously.**
> (昨天,我可爱的狗狠狠地咬了一个坏小偷。)
我们来拆解一下这部“电影”的卡司阵容:
1. **dog** (狗) 👉 **主语** (谁干的?狗干的)
2. **bit** (咬了) 👉 **谓语** (干了什么动作?咬)
3. **thief** (小偷) 👉 **宾语** (谁被咬了?小偷被咬了)
4. **my cute** (我的可爱的) 👉 **定语** (给狗化妆:什么样的狗?)
5. **a bad** (一个坏的) 👉 **定语** (给小偷化妆:什么样的小偷?)
6. **Yesterday** (昨天) 👉 **状语** (背景板:什么时候咬的?)
7. **seriously** (狠狠地) 👉 **状语** (背景板:怎么咬的?)
**给新手的建议:**
你现在基础比较弱,**绝对不要去死记硬背 4、5、6、7**!
你现在只需要在看每一句英语的时候,能像侦探一样,准确地把 **“主语(谁)”**、**“谓语(动作)”**、**“宾语(承受者)”** 抓出来,你的英语阅读和造句就能直接及格了!
+136
View File
@@ -0,0 +1,136 @@
# 第一部分:英语的8大核心“词性”(积木的种类)
要查字典或学语法,必须先认识词性。每个单词都有自己的“身份”。
## 1. 名词 (Noun, 缩写: **n.**) —— “名字”
- **是什么**:人、物品、地点、概念的名称。
- **怎么用**:通常作句子的主角(主语)或接收动作的对象(宾语)。
- **例子**
- `apple` (苹果), `water` (水), `teacher` (老师), `China` (中国)
- **造句**The **teacher** likes **apples**. (老师喜欢苹果。)
## 2. 代词 (Pronoun, 缩写: **pron.**) —— “替身演员”
- **是什么**:用来代替名词,为了说话不啰嗦。(比如不说“李雷吃饭,李雷喝水”,而说“他吃饭,他喝水”)
- **怎么用**:用法和名词一样。
- **例子**
- `I` (我), `you` (你), `he/she/it` (他/她/它), `they` (他们)
- **造句****He** likes apples. (**他**喜欢苹果。)
## 3. 动词 (Verb, 缩写: **v.**) —— “动作或状态” (核心发动机!)
- **是什么**:表示“做什么”或“是什么”。一个完整的句子**必须**有动词!
- **怎么用**:放在主角(主语)后面。
- **例子**
- 动作:`run` (跑), `eat` (吃), `study` (学习)
- 状态(联系动词):`am / is / are` (是)
- **造句**I **eat** apples. (我**吃**苹果。) / I **am** happy. (我**是**快乐的。)
## 4. 形容词 (Adjective, 缩写: **adj.**) —— “化妆师”
- **是什么**:用来修饰**名词**,说明人或事物“怎么样”。
- **怎么用**:放在名词前面,或者 `am/is/are` 后面。
- **例子**
- `big` (大的), `beautiful` (美丽的), `red` (红色的)
- **造句**A **red** apple. (一个**红色的**苹果。) / The apple is **big**. (这个苹果很**大**。)
## 5. 副词 (Adverb, 缩写: **adv.**) —— “特效师”
- **是什么**:用来修饰**动词**、**形容词**。通常表示时间、地点、程度、方式。很多副词以 `-ly` 结尾。
- **怎么用**:一般放在动词之后,或形容词之前。
- **例子**
- `very` (非常), `quickly` (快速地), `here` (这里), `yesterday` (昨天)
- **造句**I eat **quickly**. (我吃得**很快**。) / It is **very** big. (它**非常**大。)
## 6. 介词 (Preposition, 缩写: **prep.**) —— “桥梁”
- **是什么**:表示方位、时间、关系,它不能单独存在,后面必须跟名词。
- **怎么用**:和名词手拉手,组成“介词短语”。
- **例子**
- `in` (在…里), `on` (在…上), `at` (在…点钟), `with` (和…一起)
- **造句**The apple is **on** the table. (苹果**在**桌子**上**。)
## 7. 连词 (Conjunction, 缩写: **conj.**) —— “胶水”
- **是什么**:把单词和单词、句子和句子连在一起。
- **怎么用**:放在两个要连接的部分中间。
- **例子**
- `and` (和), `but` (但是), `because` (因为), `so` (所以)
- **造句**I like apples **and** bananas. (我喜欢苹果**和**香蕉。)
## 8. 感叹词 (Interjection, 缩写: **int.**) —— “情绪包”
- **是什么**:表达惊讶、高兴、痛苦等情绪的词。(最简单,基本不影响语法)。
- **例子**`Wow!` (哇), `Ouch!` (哎哟), `Oh!` (哦)
# 第二部分:必学的核心语法(怎么拼积木)
基础差的朋友,千万不要去背什么“虚拟语气”、“非谓语动词”。你只需要掌握两样东西:**句子结构(骨架)**和**时态(时间)**。
## 第一关:英语的两大“万能骨架”(句型)
**骨架1:主语 + 谓语(动词) + 宾语 (简称:主谓宾)**
- **通俗解释**:谁 (主) + 做了什么动作 (谓) + 承受动作的事物 (宾)。
- **例子**
- I (我) + love (爱) + you (你).
- The dog (狗) + bit (咬了) + him (他).
- *注意:英语的语序和中文基本一样。*
**骨架2:主语 + 系动词(am/is/are) + 表语 (简称:主系表)**
- **通俗解释**:谁 (主) + 是 (系) + 身份/特征 (表)。当你想描述一个事物的状态时用这个。
- **系动词怎么用**:我用 `am`,你用 `are``is` 连着他、她、它。
- **例子**
- I (我) + am (是) + a student (学生). -> 描述身份
- She (她) + is (是) + beautiful (美丽的). -> 描述特征
## 第二关:英语的四大基础“时态”(时间机器)
中文说时间,动词不变(我昨天“吃”,我明天“吃”)。但**英语里,不同的时间,动词要跟着变身!** 初学者先拿下这4个最常用的:
**1. 一般现在时 (习惯、真理)**
- **什么时候用**:经常发生的动作,或者客观事实。
- **怎么变**:动词用原形。(⚠️大坑注意:如果主语是“他/她/它/单个人”,动词后面要加 `s``es`,这叫“第三人称单数”)。
- **例子**
- I **play** games every day. (我每天玩游戏。)
- He **plays** games every day. (他每天玩游戏。—— 动词加了s)
**2. 现在进行时 (此时此刻)**
- **什么时候用**:正在发生的事情(中文里的“正在…”)。
- **怎么变**am/is/are + 动词加 `ing`
- **例子**
- I **am playing** games now. (我现在**正在玩**游戏。)
- She **is sleeping**. (她**正在睡觉**。)
**3. 一般过去时 (事情已结束)**
- **什么时候用**:过去发生的事情。
- **怎么变**:动词变成过去式(通常是在后面加 `ed`,有些是不规则的需要背,比如 go变成went)。
- **例子**
- I **played** games yesterday. (我昨天玩游戏了。—— 加了ed)
- I **went** to Beijing last year. (我去年去了北京。—— went是go的过去式)
**4. 一般将来时 (未来计划)**
- **什么时候用**:还没发生,将要发生的事情。
- **怎么变**:在动词原形前面加 `will` (将会),或者用 `am/is/are going to` (打算)。
- **例子**
- I **will play** games tomorrow. (我明天**将会**玩游戏。)
- We **are going to eat** pizza. (我们**打算去吃**披萨。)
# 第三部分:如何把这套指南用起来?(给新手的3条建议)
1. **先背名词和动词**
不要一开始就背高级形容词。先背日常见到的东西(名词:桌子水杯)和每天做的动作(动词:吃喝拉撒睡)。有这两个,你就能说出最简单的“主谓宾”了。
2. **套公式造句(重点练习)**
拿一个最简单的词,用四个时态去造句,锻炼你的大脑反应:
- 我每天喝水 (I drink water.)
- 我正在喝水 (I am drinking water.)
- 我昨天喝水了 (I drank water. *drink的过去式是drank*)
- 我明天要喝水 (I will drink water.)
3. **大声读出来**
语法不是做选择题做会的,是嘴巴读顺了产生“语感”的。遇到看不懂的句子,就去找它的**主语**和**动词**,找到这两个,句子大意就明白了。
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More