├── .npmrc ├── .eslintignore ├── src ├── lang │ ├── locale │ │ ├── fr.ts │ │ ├── pt-br.ts │ │ ├── pt.ts │ │ ├── da.ts │ │ ├── hi.ts │ │ ├── ja.ts │ │ ├── ko.ts │ │ ├── no.ts │ │ ├── ro.ts │ │ ├── tr.ts │ │ ├── ar.ts │ │ ├── cz.ts │ │ ├── de.ts │ │ ├── es.ts │ │ ├── it.ts │ │ ├── nl.ts │ │ ├── ru.ts │ │ ├── id.ts │ │ ├── pl.ts │ │ ├── en-gb.ts │ │ ├── zh-tw.ts │ │ ├── zh-cn.ts │ │ └── en.ts │ └── helper.ts ├── styles.css ├── settings.ts └── main.ts ├── versions.json ├── images ├── FilePreviewDetail.png ├── FilePreviewOverall.png ├── FilePreviewSetting.png ├── ShowHidePreviewBtn.png └── ShowFirstImageInFile.png ├── styles.css ├── .editorconfig ├── manifest.json ├── .gitignore ├── tsconfig.json ├── version-bump.mjs ├── .eslintrc ├── package.json ├── README.md ├── LICENSE └── esbuild.config.mjs /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /src/lang/locale/fr.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /src/lang/locale/pt-br.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /src/lang/locale/pt.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.15.0" 3 | } 4 | -------------------------------------------------------------------------------- /src/lang/locale/da.ts: -------------------------------------------------------------------------------- 1 | // Dansk 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/hi.ts: -------------------------------------------------------------------------------- 1 | // हिन्दी 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/ja.ts: -------------------------------------------------------------------------------- 1 | // 日本語 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/ko.ts: -------------------------------------------------------------------------------- 1 | // 한국어 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/no.ts: -------------------------------------------------------------------------------- 1 | // Norsk 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/ro.ts: -------------------------------------------------------------------------------- 1 | // Română 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/tr.ts: -------------------------------------------------------------------------------- 1 | // Türkçe 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/ar.ts: -------------------------------------------------------------------------------- 1 | // العربية 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/cz.ts: -------------------------------------------------------------------------------- 1 | // čeština 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/de.ts: -------------------------------------------------------------------------------- 1 | // Deutsch 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/es.ts: -------------------------------------------------------------------------------- 1 | // Español 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/it.ts: -------------------------------------------------------------------------------- 1 | // Italiano 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/nl.ts: -------------------------------------------------------------------------------- 1 | // Nederlands 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/ru.ts: -------------------------------------------------------------------------------- 1 | // русский 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/id.ts: -------------------------------------------------------------------------------- 1 | // Bahasa Indonesia 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/pl.ts: -------------------------------------------------------------------------------- 1 | // język polski 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /src/lang/locale/en-gb.ts: -------------------------------------------------------------------------------- 1 | // British English 2 | 3 | export default {}; 4 | -------------------------------------------------------------------------------- /images/FilePreviewDetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xhuajin/obsidian-file-preview/HEAD/images/FilePreviewDetail.png -------------------------------------------------------------------------------- /images/FilePreviewOverall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xhuajin/obsidian-file-preview/HEAD/images/FilePreviewOverall.png -------------------------------------------------------------------------------- /images/FilePreviewSetting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xhuajin/obsidian-file-preview/HEAD/images/FilePreviewSetting.png -------------------------------------------------------------------------------- /images/ShowHidePreviewBtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xhuajin/obsidian-file-preview/HEAD/images/ShowHidePreviewBtn.png -------------------------------------------------------------------------------- /images/ShowFirstImageInFile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xhuajin/obsidian-file-preview/HEAD/images/ShowFirstImageInFile.png -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file will be included with your plugin, and 4 | available in the app when your plugin is enabled. 5 | 6 | If your plugin does not need CSS, delete this file. 7 | 8 | */ 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "file-preview", 3 | "name": "File Preview", 4 | "version": "1.1.3", 5 | "minAppVersion": "1.0.0", 6 | "description": "Add file preview contents under file in file explorer.", 7 | "author": "Huajin", 8 | "authorUrl": "https://github.com/xhuajin", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-preview", 3 | "version": "1.1.3", 4 | "description": "Add file preview contents under file in file explorer.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "5.29.0", 17 | "@typescript-eslint/parser": "5.29.0", 18 | "builtin-modules": "3.3.0", 19 | "esbuild": "0.17.3", 20 | "obsidian": "latest", 21 | "tslib": "2.4.0", 22 | "typescript": "4.7.4" 23 | } 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Add file preview contents under markdown file's title in file explorer. 4 | 5 | Overall screenshot 6 | 7 | ![ShowFirstImage](./images/ShowFirstImageInFile.png) 8 | 9 | ![Overall Screenshot](./images/FilePreviewOverall.png) 10 | 11 | Detail screenshot 12 | 13 | ![Detail Screenshot](./images/FilePreviewDetail.png) 14 | 15 | Setting screenshot 16 | 17 | ![Setting Screenshot](./images/FilePreviewSetting.png) 18 | 19 | This plugins will only add preview contents to existing files. If you create new files, you need to click the refresh botton on the left ribbon. 20 | 21 | You can click the button at the top of the file-explorer to quickly show/hide the preview contents. 22 | 23 | ![Button to hide preview](./images/ShowHidePreviewBtn.png) 24 | 25 | # Changelog 26 | 27 | - v1.0.2: Add a setting to modify indents of the preview contents. 28 | - v1.0.3: Add a quick switching button. 29 | - v1.0.5: Add an image into preview contents from file. (Wiki type image first) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Huajin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/lang/helper.ts: -------------------------------------------------------------------------------- 1 | // Code from https://github.com/valentine195/obsidian-admonition/blob/master/src/lang/helpers.ts 2 | 3 | import ar from './locale/ar'; 4 | import cz from './locale/cz'; 5 | import da from './locale/da'; 6 | import de from './locale/de'; 7 | import en from './locale/en'; 8 | import enGB from './locale/en-gb'; 9 | import es from './locale/es'; 10 | import fr from './locale/fr'; 11 | import hi from './locale/hi'; 12 | import id from './locale/id'; 13 | import it from './locale/it'; 14 | import ja from './locale/ja'; 15 | import ko from './locale/ko'; 16 | import { moment } from 'obsidian'; 17 | import nl from './locale/nl'; 18 | import no from './locale/no'; 19 | import pl from './locale/pl'; 20 | import pt from './locale/pt'; 21 | import ptBR from './locale/pt-br'; 22 | import ro from './locale/ro'; 23 | import ru from './locale/ru'; 24 | import tr from './locale/tr'; 25 | import zhCN from './locale/zh-cn'; 26 | import zhTW from './locale/zh-tw'; 27 | 28 | const localeMap: { [k: string]: Partial } = { 29 | ar, 30 | cs: cz, 31 | da, 32 | de, 33 | en, 34 | 'en-gb': enGB, 35 | es, 36 | fr, 37 | hi, 38 | id, 39 | it, 40 | ja, 41 | ko, 42 | nl, 43 | nn: no, 44 | pl, 45 | pt, 46 | 'pt-br': ptBR, 47 | ro, 48 | ru, 49 | tr, 50 | 'zh-cn': zhCN, 51 | 'zh-tw': zhTW, 52 | }; 53 | 54 | const locale = localeMap[moment.locale()]; 55 | 56 | export function t(str: keyof typeof en): string { 57 | return (locale && locale[str]) || en[str]; 58 | } 59 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import builtins from "builtin-modules"; 2 | import esbuild from "esbuild"; 3 | import fs from "fs"; 4 | import process from "process"; 5 | 6 | const banner = `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = process.argv[2] === "production"; 13 | 14 | const targetFolder = "../FilePreviewTestVault/.obsidian/plugins/file-preview/"; 15 | 16 | const copyManifest = { 17 | name: "copy-manifest", 18 | setup: (build) => { 19 | build.onEnd(() => { 20 | fs.copyFileSync("manifest.json", targetFolder + "manifest.json"); 21 | }); 22 | }, 23 | }; 24 | 25 | const context = await esbuild 26 | .context({ 27 | banner: { 28 | js: banner, 29 | }, 30 | entryPoints: ["src/main.ts", "src/styles.css"], 31 | bundle: true, 32 | minify: prod, 33 | external: [ 34 | "obsidian", 35 | "electron", 36 | "@codemirror/autocomplete", 37 | "@codemirror/collab", 38 | "@codemirror/commands", 39 | "@codemirror/language", 40 | "@codemirror/lint", 41 | "@codemirror/search", 42 | "@codemirror/state", 43 | "@codemirror/view", 44 | "@lezer/common", 45 | "@lezer/highlight", 46 | "@lezer/lr", 47 | ...builtins, 48 | ], 49 | format: "cjs", 50 | target: "es2018", 51 | logLevel: "info", 52 | sourcemap: prod ? false : "inline", 53 | treeShaking: true, 54 | outdir: targetFolder, 55 | plugins: [copyManifest], 56 | }) 57 | .catch(() => process.exit(1)); 58 | 59 | if (prod) { 60 | await context.rebuild(); 61 | process.exit(0); 62 | } else { 63 | await context.watch(); 64 | } 65 | -------------------------------------------------------------------------------- /src/lang/locale/zh-tw.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'created at': '創建於', 3 | 'updated at': '更新於', 4 | 'Show preview contents': '顯示預覽內容', 5 | 'Show preview contents in the file explorer.': '在文件管理器查看部分筆記內容', 6 | 'Show image': '顯示圖片', 7 | 'Show image in the preview contents from file content.': '在筆記內容中顯示圖片', 8 | 'Length of the preview contents.': '預覽內容長度', 9 | 'default: 50': '預設 50', 10 | 'Line clamp': '行數限制', 11 | 'The number of lines to show in the preview contents.(1 - 10, default: 2).': '預覽內容行數限制(1-10, 預設2).', 12 | 'Indents of the preview contents': '預覽內容縮進', 13 | 'The indents of the preview contents.': '預覽內容縮進', 14 | 'Exclude special file/folder': '排除特定文件/文件夾', 15 | 'Path to file/folder that you want to exclude. Split multiple paths with line breaks.': '被排除的文件/文件夾路徑,換行分割多個路徑', 16 | 'path/to/file.md or path/to/folder': 'path/to/file.md or path/to/folder', 17 | 18 | 'Format preview contents': '格式化預覽內容', 19 | 'Remove frontmatter': '刪除 frontmatter', 20 | 'Remove frontmatter of the file.': '刪除文件的 frontmatter', 21 | 'Remove bold and italic symbols': '刪除粗體和斜體符號', 22 | 'Remove bold and italic symbols of the file.': '刪除文件的粗體和斜體符號', 23 | 'Remove highlight symbols': '刪除高亮符號', 24 | 'Remove highlight symbols of the file.': '刪除文件的高亮符號', 25 | 'Remove code block': '刪除代碼塊', 26 | 'Remove code block of the file.': '刪除文件的代碼塊', 27 | 'Remove quote': '刪除引用符號', 28 | 'Remove quote of the file.': '刪除文件的引用符號', 29 | 'Remove blank line': '刪除空行', 30 | 'Remove blank line of the file.': '刪除文件的空行', 31 | 'Remove title symbol': '刪除標題符號', 32 | 'Remove title symbol of the file.': '刪除文件的標題符號', 33 | 'Remove first h1': '刪除第一個 h1', 34 | 'Remove the first heading 1 of the file.': '刪除文件的第一個一級標題', 35 | 'Remove first h2': '刪除第一個 h2', 36 | 'Remove the first heading 2 of the file.': '刪除文件的第一個二級標題', 37 | 'Custom remove rules': '自定義刪除規則', 38 | 'Custom regular expression formatting preview text will be applied at the end. Multiple expressions can be wrapped': '自定義正則表達式格式化預覽文本,會在最後應用。多個表達式換行即可', 39 | 'The content that you want to delete.': '你想刪除的內容', 40 | 41 | 'File properties': '文件屬性', 42 | 'Display properties': '顯示屬性', 43 | 'Display file properties under detaile message. For example, created time, updated time, file size.': '在詳細信息中顯示文件屬性. 例如, 創建時間, 更新時間, 文件大小.', 44 | 'Created time format': '創建時間格式', 45 | "ctime. Time of creation. Default is 'YYYY-MM-DD HH:mm:ss'": "ctime. 創建時間. 預設是 'YYYY-MM-DD HH:mm:ss'", 46 | 'Modified time format': '修改時間格式', 47 | "mtime. Time of last modification. Default is 'YYYY-MM-DD HH:mm:ss'": "mtime. 最後修改時間. 預設是 'YYYY-MM-DD HH:mm:ss'", 48 | 'Properties format': '屬性格式', 49 | "Format of the file properties. Use 'ctime' and 'mtime'.": "屬性格式. 使用 'ctime' 和 'mtime'.", 50 | 'Reset to default': '重設為預設', 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /src/lang/locale/zh-cn.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'created at': '创建于', 3 | 'updated at': '更新于', 4 | 'Show preview contents': '开启内容预览', 5 | 'Show preview contents in the file explorer.': '在文件管理器查看部分笔记内容', 6 | 'Show image': '显示图片', 7 | 'Show image in the preview contents from file content.': '在笔记内容中显示图片', 8 | 'Length of the preview contents.': '预览内容长度', 9 | 'default: 50': '默认 50', 10 | 'Line clamp': '行数限制', 11 | 'The number of lines to show in the preview contents.(1 - 10, default: 2).': '预览内容行数限制(1-10, 默认2).', 12 | 'Indents of the preview contents': '预览内容缩进', 13 | 'The indents of the preview contents.': '预览内容缩进', 14 | 'Exclude special file/folder': '排除特定文件/文件夹', 15 | 'Path to file/folder that you want to exclude. Split multiple paths with line breaks.': '被排除的文件/文件夹路径,换行分割多个路径', 16 | 'path/to/file.md or path/to/folder': 'path/to/file.md or path/to/folder', 17 | 18 | 'Format preview contents': '格式化预览内容', 19 | 'Remove frontmatter': '删除frontmatter', 20 | 'Remove frontmatter of the file.': '删除文件的frontmatter', 21 | 'Remove bold and italic symbols': '删除粗体和斜体符号', 22 | 'Remove bold and italic symbols of the file.': '删除文件的粗体和斜体符号', 23 | 'Remove highlight symbols': '删除高亮符号', 24 | 'Remove highlight symbols of the file.': '删除文件的高亮符号', 25 | 'Remove code block': '删除代码块', 26 | 'Remove code block of the file.': '删除文件的代码块', 27 | 'Remove quote': '删除引用符号', 28 | 'Remove quote of the file.': '删除文件的引用符号', 29 | 'Remove blank line': '删除空行', 30 | 'Remove blank line of the file.': '删除文件的空行', 31 | 'Remove title symbol': '删除标题符号', 32 | 'Remove title symbol of the file.': '删除文件的标题符号', 33 | 'Remove first h1': '删除第一个 h1', 34 | 'Remove the first heading 1 of the file.': '删除文件的第一个一级标题', 35 | 'Remove first h2': '删除第一个 h2', 36 | 'Remove the first heading 2 of the file.': '删除文件的第一个二级标题', 37 | 'Custom remove rules': '自定义删除规则', 38 | 'Custom regular expression formatting preview text will be applied at the end. Split multiple expressions with line breaks.': '自定义正则表达式格式化预览文本,会在最后应用。换行分割多个表达式', 39 | 'The content that you want to delete.': '你想删除的内容', 40 | 41 | 'File properties': '文件属性', 42 | 'Display properties': '显示属性', 43 | 'Display file properties under detaile message. For example, created time, updated time, file size.': '在详细信息中显示文件属性. 例如, 创建时间, 更新时间, 文件大小.', 44 | 'Created time format': '创建时间格式', 45 | "ctime. Time of creation. Default is 'YYYY-MM-DD HH:mm:ss'": "ctime. 创建时间. 默认是 'YYYY-MM-DD HH:mm:ss'", 46 | 'Modified time format': '修改时间格式', 47 | "mtime. Time of last modification. Default is 'YYYY-MM-DD HH:mm:ss'": "mtime. 最后修改时间. 默认是 'YYYY-MM-DD HH:mm:ss'", 48 | 'Properties format': '属性格式', 49 | "Format of the file properties. Use 'ctime' and 'mtime'.": "属性格式. 使用 'ctime' 和 'mtime'.", 50 | 'Reset to default': '重置为默认值', 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /src/lang/locale/en.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'created at': 'created at', 3 | 'updated at': 'updated at', 4 | 'Show preview contents': 'Show preview contents', 5 | 'Show preview contents in the file explorer.': 'Show preview contents in the file explorer.', 6 | 'Show image': 'Show image', 7 | 'Show image in the preview contents from file content.': 'Show image in the preview contents from file content.', 8 | 'Length of the preview contents.': 'Length of the preview contents.', 9 | 'default: 50': 'default: 50', 10 | 'Line clamp': 'Line clamp', 11 | 'The number of lines to show in the preview contents.(1 - 10, default: 2).': 'The number of lines to show in the preview contents.(1 - 10, default: 2).', 12 | 'Indents of the preview contents': 'Indents of the preview contents', 13 | 'The indents of the preview contents.': 'The indents of the preview contents.', 14 | 'Exclude special file/folder': 'Exclude special file/folder', 15 | 'Path to file/folder that you want to exclude. Split multiple paths with line breaks.': 'Path to file that you want to exclude. Split multiple paths with line breaks.', 16 | 'path/to/file.md or path/to/folder': 'path/to/file.md or path/to/folder', 17 | 18 | 'Format preview contents': 'Format preview contents', 19 | 'Remove frontmatter': 'Remove frontmatter', 20 | 'Remove frontmatter of the file.': 'Remove frontmatter of the file.', 21 | 'Remove bold and italic symbols': 'Remove bold and italic symbols', 22 | 'Remove bold and italic symbols of the file.': 'Remove bold and italic symbols of the file.', 23 | 'Remove highlight symbols': 'Remove highlight symbols', 24 | 'Remove highlight symbols of the file.': 'Remove highlight symbols of the file.', 25 | 'Remove code block': 'Remove code block', 26 | 'Remove code block of the file.': 'Remove code block of the file.', 27 | 'Remove quote': 'Remove quote', 28 | 'Remove quote of the file.': 'Remove quote of the file.', 29 | 'Remove blank line': 'Remove blank line', 30 | 'Remove blank line of the file.': 'Remove blank line of the file.', 31 | 'Remove title symbol': 'Remove title symbol', 32 | 'Remove title symbol of the file.': 'Remove title symbol of the file.', 33 | 'Remove first h1': 'Remove first h1', 34 | 'Remove the first heading 1 of the file.': 'Remove the first heading 1 of the file.', 35 | 'Remove first h2': 'Remove first h2', 36 | 'Remove the first heading 2 of the file.': 'Remove the first heading 2 of the file.', 37 | 'Custom remove rules': 'Custom remove rules', 38 | 'Custom regular expression formatting preview text will be applied at the end. Split multiple expressions with line breaks.': 'Custom regular expression formatting preview text will be applied at the end. Split multiple expressions with line breaks.', 39 | 'The content that you want to delete.': 'The content that you want to delete.', 40 | 41 | 'File properties': 'File properties', 42 | 'Display properties': 'Display properties', 43 | 'Display file properties under detaile message. For example, created time, updated time, file size.': 'Display file properties under detaile message. For example, created time, updated time, file size.', 44 | 'Created time format': 'Created time format', 45 | "ctime. Time of creation. Default is 'YYYY-MM-DD HH:mm:ss'": "ctime. Time of creation. Default is 'YYYY-MM-DD HH:mm:ss'", 46 | 'Modified time format': 'Modified time format', 47 | "mtime. Time of last modification. Default is 'YYYY-MM-DD HH:mm:ss'": "mtime. Time of last modification. Default is 'YYYY-MM-DD HH:mm:ss'", 48 | 'Properties format': 'Properties format', 49 | "Format of the file properties. Use 'ctime' and 'mtime'.": "Format of the file properties. Use 'ctime' and 'mtime'.", 50 | 'Reset to default': 'Reset to default', 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | .hide-preview-contents .tree-item-inner.nav-file-details, 2 | .hide-preview-contents .nav-file-properties, 3 | .hide-preview-contents .tree-item-inner.nav-file-img { 4 | display: none; 5 | } 6 | 7 | .file-preview-nav:not(.hide-preview-contents) 8 | .tree-item-self.nav-file-title[data-path$=".md"]:has(> .nav-file-details) { 9 | flex-direction: column; 10 | gap: 2px; 11 | overflow: hidden; 12 | } 13 | 14 | .file-preview-nav:not(.hide-preview-contents) 15 | .tree-item-self.nav-file-title::before { 16 | content: none; 17 | } 18 | 19 | .file-preview-nav:not(.hide-preview-contents) 20 | .tree-item-self.nav-file-title:has(> .nav-file-details) 21 | .tree-item-inner.nav-file-title-content { 22 | flex: 1; 23 | width: 100%; 24 | font-weight: 500; 25 | } 26 | 27 | .file-preview-nav:not(.hide-preview-contents) 28 | .tree-item-self.nav-file-title.is-active 29 | .tree-item-inner.nav-file-details, 30 | .file-preview-nav:not(.hide-preview-contents) 31 | .tree-item-self.nav-file-title 32 | .tree-item-inner.nav-file-details, 33 | .file-preview-nav:not(.hide-preview-contents) 34 | .tree-item-self.nav-file-title.is-active 35 | .nav-file-properties, 36 | .file-preview-nav:not(.hide-preview-contents) 37 | .tree-item-self.nav-file-title 38 | .nav-file-properties { 39 | display: -webkit-box; 40 | white-space: normal; 41 | word-break: break-all; 42 | color: var(--nav-item-color); 43 | font-size: var(--font-ui-smaller); 44 | font-weight: 400; 45 | -webkit-box-orient: vertical; 46 | } 47 | 48 | /* Adaptation plugin novel-word-count's Right-aligned */ 49 | .file-preview-nav:not(.hide-preview-contents).novel-word-count--note-right 50 | .nav-files-container 51 | .nav-file-title::after { 52 | margin-left: auto; 53 | } 54 | 55 | .file-preview-nav:not(.hide-preview-contents) 56 | .file-preview-show-img.tree-item.nav-file:has( 57 | .tree-item-inner.nav-file-details 58 | ) { 59 | display: grid; 60 | grid-template-columns: 4fr minmax(8rem, 1fr); 61 | margin: 2px 0; 62 | } 63 | 64 | .file-preview-nav:not(.hide-preview-contents) 65 | .file-preview-show-img.tree-item.nav-file 66 | .tree-item-inner.nav-file-title-content { 67 | width: 100%; 68 | height: 1.6em; 69 | min-height: 1.6em; 70 | max-height: 1.6em; 71 | } 72 | 73 | .file-preview-nav:not(.hide-preview-contents) 74 | .file-preview-show-img 75 | .tree-item-inner.tree-item-inner.nav-file-img { 76 | display: flex; 77 | margin: 10px; 78 | border-radius: var(--radius-s); 79 | align-items: center; 80 | } 81 | 82 | .file-preview-nav:not(.hide-preview-contents) 83 | .file-preview-show-img 84 | .tree-item-inner.tree-item-inner.nav-file-img 85 | .preview-img { 86 | width: 100%; 87 | height: 100%; 88 | background-size: cover; 89 | background-position: center; 90 | } 91 | /* 将原来的背景和边框隐藏,移动到子元素的背景和边框上 */ 92 | body:not(.is-grabbing) 93 | .file-preview-nav:not(.hide-preview-contents) 94 | .file-preview-show-img 95 | .tree-item-self.is-clickable:hover, 96 | body .tree-item-self.is-clickable.nav-file-title.is-active, 97 | body .tree-item-self.is-clickable.nav-file-title:hover { 98 | background-color: unset; 99 | } 100 | body:not(.is-phone) 101 | .workspace-leaf.mod-active 102 | .file-preview-nav:not(.hide-preview-contents) 103 | .tree-item.nav-file 104 | .tree-item-self.has-focus { 105 | box-shadow: unset; 106 | } 107 | .file-preview-nav:not(.hide-preview-contents) .tree-item.nav-file:hover, 108 | .file-preview-nav:not(.hide-preview-contents) 109 | .tree-item.nav-file:has(> .nav-file-title.is-active) { 110 | background-color: var(--nav-item-background-active); 111 | border-radius: var(--radius-s); 112 | } 113 | 114 | .file-preview-nav:not(.hide-preview-contents) 115 | .tree-item.nav-file:has(> .nav-file-title.has-focus) { 116 | box-shadow: 0 0 0 2px var(--background-modifier-border-focus); 117 | } 118 | 119 | .file-preview-nav:not(.hide-preview-contents) 120 | .file-preview-show-img.tree-item.nav-file 121 | .tree-item-children { 122 | display: none; 123 | } 124 | 125 | .file-preview-nav:not(.hide-preview-contents) .nav-file-properties { 126 | margin-top: 0.25rem; 127 | } 128 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, PluginSettingTab, Setting } from 'obsidian'; 2 | 3 | import FilePreview from './main' 4 | import { t } from 'src/lang/helper'; 5 | 6 | export interface FilePreviewSettings { 7 | showpreview: boolean; 8 | lineClamp: number; 9 | indents: number; 10 | previewcontentslength: string; 11 | ispreview: boolean; 12 | format: FormatSetting; 13 | showImg: boolean; 14 | showFileProperties: boolean; 15 | ctimeFormat: string; 16 | mtimeFormat: string; 17 | propertiesFormat: string; 18 | excluded: string[]; 19 | } 20 | 21 | export const DEFAULT_SETTINGS: FilePreviewSettings = { 22 | showpreview: true, 23 | lineClamp: 2, 24 | indents: 0, 25 | previewcontentslength: '50', 26 | ispreview: false, 27 | format: { 28 | frontmatter: true, 29 | bolditalic: true, 30 | highlight: true, 31 | codeblock: true, 32 | quote: true, 33 | blankline: true, 34 | title: true, 35 | firsth1: true, 36 | firsth2: false, 37 | customregex: [], 38 | }, 39 | showImg: true, 40 | showFileProperties: false, 41 | ctimeFormat: 'YYYY-MM-DD HH:mm:ss', 42 | mtimeFormat: 'YYYY-MM-DD HH:mm:ss', 43 | propertiesFormat: `${t('created at')} ctime, ${t('updated at')} mtime`, 44 | excluded: [], 45 | } 46 | 47 | interface FormatSetting { 48 | frontmatter: boolean; 49 | bolditalic: boolean; 50 | highlight: boolean; 51 | codeblock: boolean; 52 | quote: boolean; 53 | blankline: boolean; 54 | title: boolean; 55 | firsth1: boolean; 56 | firsth2: boolean; 57 | customregex: string[]; 58 | } 59 | 60 | export class FilePreviewSettingTab extends PluginSettingTab { 61 | plugin: FilePreview; 62 | 63 | constructor(app: App, plugin: FilePreview) { 64 | super(app, plugin); 65 | this.plugin = plugin; 66 | } 67 | 68 | display() { 69 | const { containerEl } = this 70 | containerEl.empty(); 71 | 72 | new Setting(containerEl) 73 | .setName(t("Show preview contents")) 74 | .setDesc(t("Show preview contents in the file explorer.")) 75 | .addToggle(toggle => toggle 76 | .setValue(this.plugin.settings.showpreview) 77 | .onChange(async (value) => { 78 | this.plugin.settings.showpreview = value; 79 | if (value) { 80 | await this.plugin.initialize(); 81 | } else { 82 | await this.plugin.deletePreviewContents(); 83 | } 84 | await this.plugin.saveSettings(); 85 | })); 86 | 87 | new Setting(containerEl) 88 | .setName(t('Show image')) 89 | .setDesc(t("Show image in the preview contents from file content.")) 90 | .addToggle(toggle => toggle 91 | .setValue(this.plugin.settings.showImg) 92 | .onChange(async (value) => { 93 | this.plugin.settings.showImg = value; 94 | await this.plugin.refreshPreviewContents(); 95 | await this.plugin.saveSettings(); 96 | })); 97 | 98 | new Setting(containerEl) 99 | .setName(t("Length of the preview contents.")) 100 | .setDesc(t("default: 50")) 101 | .addText(text => text 102 | .setPlaceholder("50") 103 | .setValue(this.plugin.settings.previewcontentslength) 104 | .onChange(async (value) => { 105 | this.plugin.settings.previewcontentslength = value; 106 | await this.plugin.saveSettings(); 107 | })); 108 | 109 | new Setting(containerEl) 110 | .setName(t("Line clamp")) 111 | .setDesc(t("The number of lines to show in the preview contents.(1 - 10, default: 2).")) 112 | .addSlider(slider => slider 113 | .setLimits(1, 10, 1) 114 | .setValue(this.plugin.settings.lineClamp) 115 | .onChange(async (value) => { 116 | this.plugin.settings.lineClamp = value; 117 | await this.plugin.refreshPreviewContents(); 118 | await this.plugin.saveSettings(); 119 | }) 120 | .setDynamicTooltip() 121 | ) 122 | 123 | new Setting(containerEl) 124 | .setName(t('Indents of the preview contents')) 125 | .setDesc(t('The indents of the preview contents.')) 126 | .addSlider(slider => slider 127 | .setLimits(0, 10, 1) 128 | .setValue(this.plugin.settings.indents) 129 | .onChange(async (value) => { 130 | this.plugin.settings.indents = value; 131 | await this.plugin.refreshPreviewContents(); 132 | await this.plugin.saveSettings(); 133 | }) 134 | .setDynamicTooltip() 135 | ) 136 | 137 | new Setting(containerEl) 138 | .setName(t('Exclude special file/folder')) 139 | .setDesc(t('Path to file/folder that you want to exclude. Split multiple paths with line breaks.')) 140 | .addTextArea(text => text 141 | .setPlaceholder('path/to/file.md or path/to/folder') 142 | .setValue(this.plugin.settings.excluded.join('\n')) 143 | .onChange(async (value) => { 144 | this.plugin.settings.excluded = value.trim().split('\n'); 145 | await this.plugin.saveSettings(); 146 | await this.plugin.refreshPreviewContents(); 147 | })); 148 | 149 | // Format preview contents 150 | new Setting(containerEl).setName(t('Format preview contents')).setHeading(); 151 | 152 | new Setting(containerEl) 153 | .setName(t("Remove frontmatter")) 154 | .setDesc(t("Remove frontmatter of the file.")) 155 | .addToggle(toggle => toggle 156 | .setValue(this.plugin.settings.format.frontmatter) 157 | .onChange(async (value) => { 158 | this.plugin.settings.format.frontmatter = value; 159 | await this.plugin.saveSettings(); 160 | })); 161 | 162 | new Setting(containerEl) 163 | .setName(t("Remove bold and italic symbols")) 164 | .setDesc(t("Remove bold and italic symbols of the file.")) 165 | .addToggle(toggle => toggle 166 | .setValue(this.plugin.settings.format.bolditalic) 167 | .onChange(async (value) => { 168 | this.plugin.settings.format.bolditalic = value; 169 | await this.plugin.saveSettings(); 170 | })); 171 | 172 | new Setting(containerEl) 173 | .setName(t("Remove highlight symbols")) 174 | .setDesc(t("Remove highlight symbols of the file.")) 175 | .addToggle(toggle => toggle 176 | .setValue(this.plugin.settings.format.highlight) 177 | .onChange(async (value) => { 178 | this.plugin.settings.format.highlight = value; 179 | await this.plugin.saveSettings(); 180 | })); 181 | 182 | new Setting(containerEl) 183 | .setName(t("Remove code block")) 184 | .setDesc(t("Remove code block of the file.")) 185 | .addToggle(toggle => toggle 186 | .setValue(this.plugin.settings.format.codeblock) 187 | .onChange(async (value) => { 188 | this.plugin.settings.format.codeblock = value; 189 | await this.plugin.saveSettings(); 190 | })); 191 | 192 | new Setting(containerEl) 193 | .setName(t("Remove quote")) 194 | .setDesc(t("Remove quote of the file.")) 195 | .addToggle(toggle => toggle 196 | .setValue(this.plugin.settings.format.quote) 197 | .onChange(async (value) => { 198 | this.plugin.settings.format.quote = value; 199 | await this.plugin.saveSettings(); 200 | })); 201 | 202 | new Setting(containerEl) 203 | .setName(t("Remove blank line")) 204 | .setDesc(t("Remove blank line of the file.")) 205 | .addToggle(toggle => toggle 206 | .setValue(this.plugin.settings.format.blankline) 207 | .onChange(async (value) => { 208 | this.plugin.settings.format.blankline = value; 209 | await this.plugin.saveSettings(); 210 | })); 211 | 212 | new Setting(containerEl) 213 | .setName(t("Remove title symbol")) 214 | .setDesc(t("Remove title symbol of the file.")) 215 | .addToggle(toggle => toggle 216 | .setValue(this.plugin.settings.format.title) 217 | .onChange(async (value) => { 218 | this.plugin.settings.format.title = value; 219 | await this.plugin.saveSettings(); 220 | })); 221 | 222 | new Setting(containerEl) 223 | .setName(t("Remove first h1")) 224 | .setDesc(t("Remove the first heading 1 of the file.")) 225 | .addToggle(toggle => toggle 226 | .setValue(this.plugin.settings.format.firsth1) 227 | .onChange(async (value) => { 228 | this.plugin.settings.format.firsth1 = value; 229 | await this.plugin.saveSettings(); 230 | })); 231 | 232 | new Setting(containerEl) 233 | .setName(t("Remove first h2")) 234 | .setDesc(t("Remove the first heading 2 of the file.")) 235 | .addToggle(toggle => toggle 236 | .setValue(this.plugin.settings.format.firsth2) 237 | .onChange(async (value) => { 238 | this.plugin.settings.format.firsth2 = value; 239 | await this.plugin.saveSettings(); 240 | })); 241 | 242 | new Setting(containerEl) 243 | .setName(t("Custom remove rules")) 244 | .setDesc(t("Custom regular expression formatting preview text will be applied at the end. Split multiple expressions with line breaks.")) 245 | .addTextArea(text => text 246 | .setPlaceholder("The content that you want to delete.") 247 | .setValue(this.plugin.settings.format.customregex?.join('\n') ?? "") 248 | .onChange(async (value) => { 249 | this.plugin.settings.format.customregex = value.trim().split('\n'); 250 | await this.plugin.saveSettings(); 251 | })); 252 | 253 | // File Properties 254 | new Setting(containerEl).setName(t('File properties')).setHeading(); 255 | 256 | new Setting(containerEl) 257 | .setName(t("Display properties")) 258 | .setDesc(t("Display file properties under detaile message. For example, created time, updated time, file size.")) 259 | .addToggle(toggle => toggle 260 | .setValue(this.plugin.settings.showFileProperties) 261 | .onChange(async (value) => { 262 | this.plugin.settings.showFileProperties = value; 263 | await this.plugin.saveSettings(); 264 | })); 265 | 266 | new Setting(containerEl) 267 | .setName(t("Created time format")) 268 | .setDesc(t("ctime. Time of creation. Default is 'YYYY-MM-DD HH:mm:ss'")) 269 | .addText(text => text 270 | .setPlaceholder(this.plugin.settings.ctimeFormat) 271 | .setValue(this.plugin.settings.ctimeFormat) 272 | .onChange(async (value) => { 273 | this.plugin.settings.ctimeFormat = value; 274 | await this.plugin.saveSettings(); 275 | })) 276 | .then((settingEl) => this.addResetButton(settingEl, 'ctimeFormat')) 277 | 278 | new Setting(containerEl) 279 | .setName(t("Modified time format")) 280 | .setDesc(t("mtime. Time of last modification. Default is 'YYYY-MM-DD HH:mm:ss'")) 281 | .addText(text => text 282 | .setPlaceholder(this.plugin.settings.mtimeFormat) 283 | .setValue(this.plugin.settings.mtimeFormat) 284 | .onChange(async (value) => { 285 | this.plugin.settings.mtimeFormat = value; 286 | await this.plugin.saveSettings(); 287 | })) 288 | .then((settingEl) => this.addResetButton(settingEl, 'mtimeFormat')) 289 | 290 | new Setting(containerEl) 291 | .setName(t("Properties format")) 292 | .setDesc(t("Format of the file properties. Use 'ctime' and 'mtime'.")) 293 | .addText(text => text 294 | .setPlaceholder(this.plugin.settings.propertiesFormat) 295 | .setValue(this.plugin.settings.propertiesFormat) 296 | .onChange(async (value) => { 297 | this.plugin.settings.propertiesFormat = value; 298 | await this.plugin.saveSettings(); 299 | })) 300 | .then((settingEl) => this.addResetButton(settingEl, 'propertiesFormat')) 301 | 302 | 303 | } 304 | 305 | addResetButton(settingElement: Setting, settingKey: string, refreshView = true) { 306 | settingElement 307 | .addExtraButton((button) => button 308 | .setIcon('reset') 309 | .setTooltip(t('Reset to default')) 310 | .onClick(() => { 311 | // @ts-ignore 312 | this.plugin.settings[settingKey] = DEFAULT_SETTINGS[settingKey] 313 | this.plugin.saveSettings() 314 | if (refreshView) { 315 | this.display() 316 | } 317 | }) 318 | ) 319 | } 320 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_SETTINGS, FilePreviewSettingTab, FilePreviewSettings, } from "./settings"; 2 | import { Plugin, TFile, TFolder, View, WorkspaceLeaf, addIcon, moment, normalizePath, setIcon, } from "obsidian"; 3 | 4 | declare module "obsidian" { 5 | interface WorkspaceSidedock { 6 | containerEl: HTMLElement; 7 | } 8 | interface WorkspaceMobileDrawer { 9 | containerEl: HTMLElement; 10 | } 11 | interface TFolder { 12 | extension: string; 13 | } 14 | interface TFile { 15 | extension: string; 16 | stat: FileStats; 17 | } 18 | } 19 | 20 | interface FileItem { 21 | titleEl: HTMLElement; 22 | el: HTMLElement; 23 | innerEl: HTMLElement; 24 | selfEl: HTMLElement; 25 | file: TFile | TFolder; 26 | } 27 | 28 | interface FileExplorerLeaf extends WorkspaceLeaf { 29 | view: FileExplorerView; 30 | } 31 | 32 | interface FileExplorerView extends View { 33 | fileItems: { [path: string]: FileItem }; 34 | } 35 | 36 | export default class FilePreview extends Plugin { 37 | settings: FilePreviewSettings; 38 | settingTab: FilePreviewSettingTab; 39 | fileExplorerView: FileExplorerView; 40 | fileNavEl: HTMLElement; 41 | showpreviewBtn: HTMLElement; 42 | previewContentsEl: HTMLElement[] = []; 43 | imagePaths: string[]; 44 | ttl: number; 45 | 46 | async onload() { 47 | await this.loadSettings(); 48 | // Apply custom regex (New config in 1.1.3) 49 | if (!this.settings.format?.customregex) { 50 | this.settings.format.customregex = []; 51 | this.saveData(this.settings); 52 | } else if (!this.settings.excluded) { 53 | this.settings.excluded = []; 54 | this.saveData(this.settings); 55 | } 56 | this.settingTab = new FilePreviewSettingTab(this.app, this); 57 | this.addSettingTab(this.settingTab); 58 | 59 | await this.initialize(); 60 | 61 | this.registerCommands(); 62 | 63 | // Add hot reload with debounce 64 | const debounce = (func: () => void, wait: number) => { 65 | let timeout: number; 66 | return () => { 67 | clearTimeout(timeout); 68 | timeout = window.setTimeout(() => func.apply(this), wait); 69 | }; 70 | }; 71 | 72 | const debouncedRefresh = debounce(async () => { 73 | if (this.settings.showpreview && this.settings.ispreview) { 74 | const activeFile = this.app.workspace.getActiveFile(); 75 | if (activeFile) { 76 | const activeFilePath = activeFile.path; 77 | const fileItem = 78 | this.fileExplorerView.fileItems[activeFilePath]; 79 | if (fileItem) { 80 | this.displayPreviewContentsByFileItem(fileItem); 81 | } 82 | } 83 | } 84 | }, 10); 85 | 86 | this.app.workspace.on("editor-change", debouncedRefresh); 87 | 88 | // file-menu 右键菜单:将文件/文件夹排除在外 89 | this.app.workspace.on("file-menu", (menu, file) => { 90 | if (!file || this.settings.excluded.includes(file.path)) { 91 | return; 92 | } 93 | menu.addItem((item) => { 94 | item.setIcon("trash"); 95 | item.setTitle(file instanceof TFolder ? "Exclude folder" : "Exclude file"); 96 | item.onClick(() => { 97 | this.settings.excluded.push(file.path); 98 | this.saveSettings(); 99 | this.refreshPreviewContents(); 100 | }); 101 | }); 102 | }); 103 | 104 | this.ttl = 5; 105 | 106 | addIcon( 107 | "captions", 108 | '' 109 | ); 110 | addIcon( 111 | "captions-off", 112 | '' 113 | ); 114 | 115 | await this.saveSettings(); 116 | } 117 | 118 | onunload() { 119 | this.showpreviewBtn.remove(); 120 | this.deletePreviewContents(); 121 | this.saveSettings(); 122 | } 123 | 124 | public async initialize() { 125 | this.app.workspace.onLayoutReady(async () => { 126 | try { 127 | this.fileExplorerView = await this.getFileExplorerView(); // 测试文件夹树是否加载 128 | this.fileNavEl = this.fileExplorerView.containerEl; 129 | this.initImagePaths(); 130 | this.createShowPreviewButton( 131 | this.fileNavEl.querySelector( 132 | ".nav-header > .nav-buttons-container" 133 | ) as HTMLElement 134 | ); 135 | if (this.settings.showpreview) { 136 | await this.refreshPreviewContents(); 137 | } 138 | } catch (err) { 139 | console.log(err); 140 | if (this.ttl <= 0) { 141 | return; 142 | } 143 | // File Explorer panel may not be loaded yet 144 | this.ttl -= 1; 145 | setTimeout(() => { 146 | this.initialize(); 147 | }, 1000); 148 | } 149 | }); 150 | } 151 | 152 | public createShowPreviewButton(fileNavHeader: HTMLElement) { 153 | if (this.showpreviewBtn) { 154 | return; 155 | } 156 | this.showpreviewBtn = fileNavHeader.createDiv({ 157 | cls: "clickable-icon nav-action-button show-preview-button", 158 | attr: { "aria-label": "Show/Hide preview contents" }, 159 | }); 160 | if (this.settings.ispreview) { 161 | setIcon(this.showpreviewBtn, "captions-off"); 162 | } else { 163 | setIcon(this.showpreviewBtn, "captions"); 164 | } 165 | this.registerDomEvent(this.showpreviewBtn, "click", () => { 166 | this.togglePreviewContents(); 167 | if (this.settings.ispreview) { 168 | setIcon(this.showpreviewBtn, "captions-off"); 169 | this.settings.ispreview = false; 170 | } else { 171 | setIcon(this.showpreviewBtn, "captions"); 172 | this.settings.ispreview = true; 173 | } 174 | this.saveSettings(); 175 | }); 176 | } 177 | 178 | public async displayPreviewContents() { 179 | this.fileNavEl.classList.add("file-preview-nav"); 180 | const fileItems = this.fileExplorerView.fileItems; 181 | for (const path in fileItems) { 182 | const item = fileItems[path]; 183 | if ( 184 | path === "/" || 185 | this.isExcluded(item.file) || 186 | !(item.file instanceof TFile) || 187 | item.file.extension !== "md" || 188 | this.isExcluded(item.file) 189 | ) { 190 | continue; 191 | } 192 | this.displayPreviewContentsByFileItem(item); 193 | } 194 | 195 | this.settings.ispreview = true; 196 | } 197 | 198 | public isExcluded(file: TFile | TFolder): boolean { 199 | return this.settings.excluded.some((path) => path && file.path.startsWith(path)); 200 | } 201 | 202 | 203 | public async getFileExplorerView(): Promise { 204 | return new Promise((resolve, reject) => { 205 | let foundLeaf: FileExplorerLeaf | null = null; 206 | const leafs = this.app.workspace.getLeavesOfType( 207 | "file-explorer" 208 | ) as FileExplorerLeaf[]; 209 | if (leafs.length === 0) { 210 | reject(Error("Could not find file explorer view.")); 211 | } else { 212 | foundLeaf = leafs[0]; 213 | resolve(foundLeaf.view); 214 | } 215 | if (!foundLeaf) { 216 | reject(Error("Could not find file explorer view.")); 217 | } 218 | }); 219 | } 220 | 221 | public togglePreviewContents() { 222 | this.fileNavEl.classList.toggle("hide-preview-contents"); 223 | } 224 | 225 | public async deletePreviewContents() { 226 | this.fileNavEl.classList.remove("file-preview-nav"); 227 | this.fileNavEl.classList.remove("hide-preview-contents"); 228 | this.previewContentsEl.forEach((el) => { 229 | el.remove(); 230 | }); 231 | this.previewContentsEl = []; 232 | this.settings.ispreview = false; 233 | this.saveSettings(); 234 | } 235 | 236 | public async refreshPreviewContents() { 237 | if (this.settings.ispreview) { 238 | this.deletePreviewContents(); 239 | } 240 | this.displayPreviewContents(); 241 | } 242 | 243 | public async displayPreviewContentsByFileItem(item: FileItem) { 244 | const file = item.file as TFile; 245 | if (!file) { 246 | return; 247 | } 248 | await this.app.vault.cachedRead(file).then((contents) => { 249 | const formattedContents = this.formatContents(contents.trim(), file.basename); 250 | if (!formattedContents) { 251 | return; 252 | } 253 | if (item.innerEl && item.el.querySelector(".nav-file-details")) { 254 | const currentContentEl = item.selfEl.querySelector(".tree-item-inner.nav-file-details"); 255 | if (currentContentEl && formattedContents !== currentContentEl.innerHTML) { 256 | currentContentEl.innerHTML = formattedContents; 257 | } 258 | if (this.settings.showFileProperties) { 259 | const ctime = moment(file.stat.ctime).format( 260 | this.settings.ctimeFormat 261 | ); 262 | const mtime = moment(file.stat.mtime).format( 263 | this.settings.mtimeFormat 264 | ); 265 | const timeInfoString = this.settings.propertiesFormat 266 | .replace("ctime", ctime) 267 | .replace("mtime", mtime); 268 | const currentPropertiesEl = item.selfEl.querySelector(".nav-file-properties"); 269 | if (currentPropertiesEl) { 270 | currentPropertiesEl.innerHTML = timeInfoString; 271 | } 272 | } 273 | return; 274 | } 275 | // item.selfEl.classList.add("fp-nav-file"); 276 | this.previewContentsEl.push( 277 | item.selfEl.createEl("div", { 278 | text: formattedContents, 279 | attr: { 280 | class: "tree-item-inner nav-file-details", 281 | style: `-webkit-line-clamp: ${this.settings.lineClamp}; text-indent: ${this.settings.indents}em;`, 282 | }, 283 | }) 284 | ); 285 | 286 | if (this.settings.showFileProperties) { 287 | const ctime = moment(file.stat.ctime).format( 288 | this.settings.ctimeFormat 289 | ); 290 | const mtime = moment(file.stat.mtime).format( 291 | this.settings.mtimeFormat 292 | ); 293 | const timeInfoString = this.settings.propertiesFormat 294 | .replace("ctime", ctime) 295 | .replace("mtime", mtime); 296 | 297 | this.previewContentsEl.push( 298 | item.selfEl.createEl("div", { 299 | text: timeInfoString, 300 | attr: { 301 | class: "nav-file-properties", 302 | }, 303 | }) 304 | ); 305 | } 306 | if (this.settings.showImg) { 307 | let imgpath = this.getFirstImgPath(contents); 308 | if (!imgpath || item.el.querySelector('.nav-file-img')) { return; } 309 | item.el.classList.add("file-preview-show-img"); 310 | const fileimg = item.el.createEl("div", { 311 | attr: { 312 | class: "tree-item-inner nav-file-img", 313 | }, 314 | }); 315 | if (!imgpath.startsWith("http")) { 316 | const absolutePath = 317 | this.imagePaths.find((path) => 318 | path.endsWith(imgpath) 319 | ) || imgpath; 320 | imgpath = this.app.vault.adapter.getResourcePath(normalizePath(absolutePath)); 321 | } 322 | fileimg.createEl("div", { 323 | attr: { 324 | class: "preview-img", 325 | style: `background-image: url(${imgpath})`, 326 | }, 327 | }); 328 | this.previewContentsEl.push(fileimg); 329 | } 330 | }); 331 | } 332 | 333 | public formatContents(contents: string, basename: string): string { 334 | let formatContents = contents; 335 | // Remove frontmatter 336 | if (contents.startsWith("---") && this.settings.format.frontmatter) { 337 | formatContents = contents.replace(/---[\s\S]*?---/, ""); 338 | } 339 | 340 | const endOfFirstLine = formatContents.indexOf("\n"); 341 | const firstLine = formatContents.slice(0, endOfFirstLine).trim(); 342 | 343 | // Remove first h1 title 344 | if ( 345 | this.settings.format.firsth1 && 346 | (firstLine.startsWith("# ") || firstLine === basename) 347 | ) { 348 | formatContents = formatContents.slice(endOfFirstLine); 349 | } 350 | 351 | // Remove first h2 title 352 | if (this.settings.format.firsth2 && firstLine.startsWith("## ")) { 353 | formatContents = formatContents.slice(endOfFirstLine); 354 | } 355 | 356 | // Remove bold and italic 357 | if (this.settings.format.bolditalic) { 358 | formatContents = formatContents.replace(/(\*\*|__)(.*?)\1/g, "$2"); 359 | formatContents = formatContents.replace(/(\*|_)(.*?)\1/g, "$2"); 360 | } 361 | // Remove highlight 362 | if (this.settings.format.highlight) { 363 | formatContents = formatContents.replace(/===(.*?)===/g, "$1"); 364 | } 365 | // Remove code block 366 | if (this.settings.format.codeblock) { 367 | formatContents = formatContents.replace(/`{5}[\s\S]*?`{5}/g, ""); 368 | formatContents = formatContents.replace(/`{4}[\s\S]*?`{4}/g, ""); 369 | formatContents = formatContents.replace(/`{3}[\s\S]*?`{3}/g, ""); 370 | } 371 | // Remove quote '> ..\n>..' 372 | if (this.settings.format.quote) { 373 | formatContents = formatContents.replace(/(^>.*\n*)+/g, ""); 374 | } 375 | // clear blank line 376 | if (this.settings.format.blankline) { 377 | formatContents = formatContents.replace(/^\s*\n/g, ""); 378 | } 379 | // Remove title symbol 380 | formatContents = formatContents.replace(/#+/g, ""); 381 | 382 | // Remove wiki link: [[]] & markdown link: []() & image link: ![]() 383 | formatContents = formatContents.replace(/!\[.*?\]\(.*?\)/g, ""); 384 | formatContents = formatContents.replace(/\[\[.*?\]\]/g, ""); 385 | formatContents = formatContents.replace(/\[.*?\]\(.*?\)/g, ""); 386 | 387 | for (let i = 0; i < this.settings.format.customregex.length; i++) { 388 | formatContents = formatContents.replace(new RegExp(this.settings.format.customregex[i], "g"), ""); 389 | } 390 | 391 | return formatContents.slice(0, parseInt(this.settings.previewcontentslength)).trim(); 392 | } 393 | 394 | public getFirstImgPath(contents: string): string { 395 | // 正则匹配获取 ![[]], ![]() 的图片路径,匹配第一个,判断后缀是否为图片格式 396 | const imgReg = /!\[\[(.*?)\](?!\|bb\]\])|!\[(.*?)\]\((.*?)\)/; 397 | 398 | const ImgMatch = contents.match(imgReg); 399 | if (ImgMatch) { 400 | const imgPath = ImgMatch[1] || ImgMatch[3]; 401 | if (imgPath.includes("|")) { 402 | return imgPath.split("|")[0]; 403 | } 404 | return imgPath; 405 | } 406 | 407 | return ""; 408 | } 409 | 410 | public async initImagePaths() { 411 | const allpaths = this.app.vault.getFiles().map((file) => file.path); 412 | const imgpaths = allpaths.filter((path) => this.pathIsImg(path)); 413 | this.imagePaths = imgpaths; 414 | } 415 | 416 | public pathIsImg(path: string): boolean { 417 | return ( 418 | path.endsWith(".png") || 419 | path.endsWith(".jpg") || 420 | path.endsWith(".jpeg") || 421 | path.endsWith(".gif") || 422 | path.endsWith(".webp") 423 | ); 424 | } 425 | 426 | public registerCommands() { 427 | this.addCommand({ 428 | id: "refresh-preview-contents", 429 | name: "Refresh preview contents", 430 | callback: async () => { 431 | this.refreshPreviewContents(); 432 | }, 433 | }); 434 | 435 | this.addCommand({ 436 | id: "show-preview-contents", 437 | name: "Show preview contents", 438 | callback: async () => { 439 | await this.displayPreviewContents(); 440 | }, 441 | }); 442 | 443 | this.addCommand({ 444 | id: "hide-preview-contents", 445 | name: "Hide preview contents", 446 | callback: async () => { 447 | this.fileNavEl.classList.add("hide-preview-contents"); 448 | this.settings.ispreview = false; 449 | this.saveSettings(); 450 | }, 451 | }); 452 | 453 | this.addCommand({ 454 | id: "show-preview-contents", 455 | name: "Show preview contents", 456 | callback: async () => { 457 | this.fileNavEl.classList.remove("hide-preview-contents"); 458 | this.settings.ispreview = true; 459 | this.saveSettings(); 460 | }, 461 | }); 462 | } 463 | 464 | public async loadSettings() { 465 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 466 | } 467 | 468 | public async saveSettings() { 469 | await this.saveData(this.settings); 470 | } 471 | } 472 | --------------------------------------------------------------------------------