mirror of
https://github.com/liubinfighter/csv-lite.git
synced 2026-07-22 05:43:52 +00:00
更新README.md,添加个人数据库设计计划;优化CSV视图,增加标签页和导入导出选项,支持自定义分隔符和表头设置
This commit is contained in:
parent
3e844b0cf7
commit
60a5534994
5 changed files with 841 additions and 2440 deletions
|
|
@ -8,6 +8,8 @@ Keep your mind on track! Don't waste time creating fancy tables.
|
|||
|
||||
A plugin designed to view and edit CSV files directly within Obsidian.
|
||||
|
||||
I have a plan to design my own database using json and csv only. If you have fancy idea about tables or csv, please wait for another plugin or search it in community. For personal preference and in-markdown edit, I recommend `anyblock`.
|
||||
|
||||
## Why another csv plugin?
|
||||
|
||||
There are so many csv plugin, why you need this one?
|
||||
|
|
@ -30,9 +32,10 @@ No fancy feature, just open and edit.
|
|||
- All in TextFileView/workspace.
|
||||
- No more pollution to your vault, all metadata store in `./.obsidian/plugins/csv` in json format.
|
||||
- Every function must be completed within 3 steps:
|
||||
0. Locate it visually
|
||||
1. Click/Hotkey
|
||||
2. Input (if needed)
|
||||
3. Confirm
|
||||
3. Confirm/Leave
|
||||
- The interface should remain minimal yet functional
|
||||
- Users shouldn't need to leave their workflow environment
|
||||
- CSV manipulation should be as natural as text editing.
|
||||
|
|
|
|||
2987
package-lock.json
generated
2987
package-lock.json
generated
File diff suppressed because it is too large
Load diff
46
package.json
46
package.json
|
|
@ -1,24 +1,26 @@
|
|||
{
|
||||
"name": "obsidian-csv-plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a csv plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
"name": "obsidian-csv",
|
||||
"version": "1.0.0",
|
||||
"description": "CSV viewer and editor for Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"papaparse": "^5.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
173
src/view.ts
173
src/view.ts
|
|
@ -1,4 +1,5 @@
|
|||
import { TextFileView, ButtonComponent, Notice, DropdownComponent } from "obsidian";
|
||||
import { TextFileView, ButtonComponent, Notice, DropdownComponent, requestUrl } from "obsidian";
|
||||
import * as Papa from 'papaparse';
|
||||
|
||||
export const VIEW_TYPE_CSV = "csv-view";
|
||||
|
||||
|
|
@ -15,20 +16,78 @@ export class CSVView extends TextFileView {
|
|||
// 添加列宽调整设置
|
||||
private columnWidths: number[] = [];
|
||||
private autoResize: boolean = true; // 始终启用自动调整
|
||||
|
||||
// Papa Parse 配置选项
|
||||
private papaConfig = {
|
||||
header: false,
|
||||
dynamicTyping: false, // 保持所有值为字符串
|
||||
skipEmptyLines: false // 保留空行以保持行号一致
|
||||
};
|
||||
|
||||
getViewData() {
|
||||
return this.tableData.map((row) => row.join(",")).join("\n");
|
||||
// 使用 Papa Parse 将数据序列化为CSV字符串
|
||||
return Papa.unparse(this.tableData, {
|
||||
header: false,
|
||||
newline: "\n"
|
||||
});
|
||||
}
|
||||
|
||||
setViewData(data: string, clear: boolean) {
|
||||
this.tableData = data.split("\n").map((line) => line.split(","));
|
||||
try {
|
||||
// 使用 Papa Parse 解析CSV数据
|
||||
const parseResult = Papa.parse(data, this.papaConfig);
|
||||
|
||||
if (parseResult.errors && parseResult.errors.length > 0) {
|
||||
// 显示解析错误
|
||||
console.warn("CSV解析警告:", parseResult.errors);
|
||||
new Notice(`CSV解析提示: ${parseResult.errors[0].message}`);
|
||||
}
|
||||
|
||||
this.tableData = parseResult.data as string[][];
|
||||
|
||||
// 确保至少有一行一列
|
||||
if (!this.tableData || this.tableData.length === 0) {
|
||||
this.tableData = [[""]];
|
||||
}
|
||||
|
||||
// 使所有行的列数一致
|
||||
this.normalizeTableData();
|
||||
|
||||
// 初始化历史记录
|
||||
if (clear) {
|
||||
this.initHistory();
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
} catch (error) {
|
||||
console.error("CSV解析错误:", error);
|
||||
new Notice("CSV解析失败,请检查文件格式");
|
||||
|
||||
// 出错时设置为空表格
|
||||
this.tableData = [[""]];
|
||||
if (clear) {
|
||||
this.initHistory();
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// 确保表格数据规整(所有行有相同的列数)
|
||||
private normalizeTableData() {
|
||||
if (!this.tableData || this.tableData.length === 0) return;
|
||||
|
||||
// 初始化历史记录
|
||||
if (clear) {
|
||||
this.initHistory();
|
||||
// 找出最大列数
|
||||
let maxCols = 0;
|
||||
for (const row of this.tableData) {
|
||||
maxCols = Math.max(maxCols, row.length);
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
// 确保每行都有相同的列数
|
||||
for (let i = 0; i < this.tableData.length; i++) {
|
||||
while (this.tableData[i].length < maxCols) {
|
||||
this.tableData[i].push("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化历史记录
|
||||
|
|
@ -271,53 +330,82 @@ export class CSVView extends TextFileView {
|
|||
// 创建操作区
|
||||
this.operationEl = this.contentEl.createEl("div", { cls: "csv-operations" });
|
||||
|
||||
// 添加常用操作按钮
|
||||
const buttonContainer = this.operationEl.createEl("div", { cls: "csv-operation-buttons" });
|
||||
// 创建两个标签页容器
|
||||
const tabsContainer = this.operationEl.createEl("div", { cls: "csv-tabs" });
|
||||
|
||||
// 创建标签头部
|
||||
const tabHeaders = tabsContainer.createEl("div", { cls: "csv-tab-headers" });
|
||||
const contentTabHeader = tabHeaders.createEl("div", { cls: "csv-tab-header csv-tab-active", text: "内容" });
|
||||
const appearanceTabHeader = tabHeaders.createEl("div", { cls: "csv-tab-header", text: "外观" });
|
||||
|
||||
// 创建内容标签页面板
|
||||
const contentTabPanel = tabsContainer.createEl("div", { cls: "csv-tab-panel csv-tab-panel-active" });
|
||||
|
||||
// 添加内容相关操作按钮
|
||||
const contentButtonContainer = contentTabPanel.createEl("div", { cls: "csv-operation-buttons" });
|
||||
|
||||
// 撤销按钮
|
||||
new ButtonComponent(buttonContainer)
|
||||
new ButtonComponent(contentButtonContainer)
|
||||
.setButtonText("撤销")
|
||||
.setIcon("undo")
|
||||
.onClick(() => this.undo());
|
||||
|
||||
// 重做按钮
|
||||
new ButtonComponent(buttonContainer)
|
||||
new ButtonComponent(contentButtonContainer)
|
||||
.setButtonText("重做")
|
||||
.setIcon("redo")
|
||||
.onClick(() => this.redo());
|
||||
|
||||
// 添加行按钮
|
||||
new ButtonComponent(buttonContainer)
|
||||
new ButtonComponent(contentButtonContainer)
|
||||
.setButtonText("添加行")
|
||||
.onClick(() => this.addRow());
|
||||
|
||||
// 删除行按钮
|
||||
new ButtonComponent(buttonContainer)
|
||||
new ButtonComponent(contentButtonContainer)
|
||||
.setButtonText("删除行")
|
||||
.onClick(() => this.deleteRow());
|
||||
|
||||
// 添加列按钮
|
||||
new ButtonComponent(buttonContainer)
|
||||
new ButtonComponent(contentButtonContainer)
|
||||
.setButtonText("添加列")
|
||||
.onClick(() => this.addColumn());
|
||||
|
||||
// 删除列按钮
|
||||
new ButtonComponent(buttonContainer)
|
||||
new ButtonComponent(contentButtonContainer)
|
||||
.setButtonText("删除列")
|
||||
.onClick(() => this.deleteColumn());
|
||||
|
||||
// 添加列宽自动调整选项
|
||||
const settingsContainer = this.operationEl.createEl("div", { cls: "csv-settings" });
|
||||
|
||||
// 删除自动调整行高开关,只保留重置列宽按钮
|
||||
new ButtonComponent(settingsContainer)
|
||||
// 创建外观标签页面板
|
||||
const appearanceTabPanel = tabsContainer.createEl("div", { cls: "csv-tab-panel" });
|
||||
|
||||
// 添加外观相关设置
|
||||
const appearanceSettings = appearanceTabPanel.createEl("div", { cls: "csv-settings" });
|
||||
|
||||
// 重置列宽按钮
|
||||
new ButtonComponent(appearanceSettings)
|
||||
.setButtonText("重置列宽")
|
||||
.onClick(() => {
|
||||
this.columnWidths = [];
|
||||
this.calculateColumnWidths();
|
||||
this.refresh();
|
||||
});
|
||||
|
||||
|
||||
// 添加标签切换功能
|
||||
contentTabHeader.addEventListener("click", () => {
|
||||
contentTabHeader.addClass("csv-tab-active");
|
||||
appearanceTabHeader.removeClass("csv-tab-active");
|
||||
contentTabPanel.addClass("csv-tab-panel-active");
|
||||
appearanceTabPanel.removeClass("csv-tab-panel-active");
|
||||
});
|
||||
|
||||
appearanceTabHeader.addEventListener("click", () => {
|
||||
appearanceTabHeader.addClass("csv-tab-active");
|
||||
contentTabHeader.removeClass("csv-tab-active");
|
||||
appearanceTabPanel.addClass("csv-tab-panel-active");
|
||||
contentTabPanel.removeClass("csv-tab-panel-active");
|
||||
});
|
||||
|
||||
// 创建一个分隔线
|
||||
this.contentEl.createEl("hr");
|
||||
|
||||
|
|
@ -343,6 +431,42 @@ export class CSVView extends TextFileView {
|
|||
}
|
||||
});
|
||||
|
||||
// 添加CSV导入导出选项到内容标签页
|
||||
const exportImportContainer = contentTabPanel.createEl("div", { cls: "csv-export-import" });
|
||||
|
||||
// 添加分隔符选择
|
||||
const delimiterContainer = exportImportContainer.createEl("div", { cls: "csv-delimiter-container" });
|
||||
delimiterContainer.createEl("span", { text: "分隔符: " });
|
||||
|
||||
const delimiterDropdown = new DropdownComponent(delimiterContainer);
|
||||
delimiterDropdown.addOption(",", "逗号 (,)");
|
||||
delimiterDropdown.addOption(";", "分号 (;)");
|
||||
delimiterDropdown.addOption("\t", "制表符 (Tab)");
|
||||
delimiterDropdown.setValue(",");
|
||||
|
||||
// 设置分隔符改变时的处理函数
|
||||
delimiterDropdown.onChange(value => {
|
||||
this.papaConfig.delimiter = value;
|
||||
this.refresh();
|
||||
});
|
||||
|
||||
// 添加高级解析设置
|
||||
const advancedContainer = appearanceSettings.createEl("div", { cls: "csv-advanced-settings" });
|
||||
|
||||
// 添加首行作为表头选项
|
||||
const headerOption = advancedContainer.createEl("div", { cls: "csv-setting-item" });
|
||||
headerOption.createEl("span", { text: "首行作为表头" });
|
||||
const headerToggle = headerOption.createEl("input", { attr: { type: "checkbox" } });
|
||||
|
||||
headerToggle.addEventListener("change", e => {
|
||||
if (e.currentTarget instanceof HTMLInputElement) {
|
||||
const headerRow = document.querySelector("thead tr");
|
||||
if (headerRow) {
|
||||
headerRow.toggleClass("csv-header-row", e.currentTarget.checked);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化时刷新视图
|
||||
this.refresh();
|
||||
}
|
||||
|
|
@ -356,8 +480,13 @@ export class CSVView extends TextFileView {
|
|||
// 保存当前状态到历史记录
|
||||
this.saveSnapshot();
|
||||
|
||||
// 获取最大列数
|
||||
const colCount = this.tableData.length > 0
|
||||
? this.tableData[0].length
|
||||
: 1;
|
||||
|
||||
// 在表格末尾添加一个空行
|
||||
const newRow = Array(this.tableData[0]?.length || 1).fill("");
|
||||
const newRow = Array(colCount).fill("");
|
||||
this.tableData.push(newRow);
|
||||
this.refresh();
|
||||
this.requestSave();
|
||||
|
|
|
|||
70
styles.css
70
styles.css
|
|
@ -102,3 +102,73 @@ td input:focus {
|
|||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 标签页样式 */
|
||||
.csv-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.csv-tab-headers {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.csv-tab-header {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.csv-tab-header:hover {
|
||||
color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.csv-tab-header.csv-tab-active {
|
||||
color: var(--interactive-accent);
|
||||
border-bottom-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.csv-tab-panel {
|
||||
display: none;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.csv-tab-panel-active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* CSV导入导出选项样式 */
|
||||
.csv-export-import {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.csv-delimiter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.csv-advanced-settings {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.csv-setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 表头行样式 */
|
||||
.csv-header-row th {
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue