feat: 编辑模式也能预览sheet, 文件格式变为 .sheet.md

This commit is contained in:
leijun 2023-09-06 14:45:48 +08:00
parent b5d9f413fd
commit f166222309
14 changed files with 1047 additions and 1660 deletions

View file

@ -16,7 +16,7 @@ const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["main.ts"],
entryPoints: ["src/main.ts"],
bundle: true,
external: [
"obsidian",

130
main.ts
View file

@ -1,130 +0,0 @@
import {
TFile,
Plugin,
WorkspaceLeaf,
normalizePath,
} from "obsidian";
import { ExcelSettings, DEFAULT_SETTINGS } from "./src/utils/Settings";
import {
PaneTarget,
} from "./src/utils/ModifierkeyHelper";
import { ExcelView, VIEW_TYPE_EXCEL } from "./src/ExcelView";
import {
getExcelFilename,
} from "./src/utils/FileUtils";
import {
initializeMarkdownPostProcessor,
markdownPostProcessor,
} from "./src/MarkdownPostProcessor";
export default class ExcelPlugin extends Plugin {
public settings: ExcelSettings;
async onload() {
this.registerView(
VIEW_TYPE_EXCEL,
(leaf: WorkspaceLeaf) => new ExcelView(leaf, this)
);
this.registerExtensions(["sheet"], VIEW_TYPE_EXCEL);
// This creates an icon in the left ribbon.
this.addRibbonIcon(
"table",
"Excel",
(e: MouseEvent) => {
// Called when the user clicks the icon.
this.createAndOpenExcel(getExcelFilename(this.settings));
}
);
// markdwon后处理
this.addMarkdownPostProcessor();
}
private addMarkdownPostProcessor() {
initializeMarkdownPostProcessor(this);
this.registerMarkdownPostProcessor(markdownPostProcessor);
}
onunload() {}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
public async createExcel(
filename: string,
foldername?: string,
initData?: string
): Promise<TFile> {
// const folderpath = normalizePath(
// foldername ? foldername : "Excel"
// );
// await checkAndCreateFolder(folderpath); //create folder if it does not exist
const fname = normalizePath(filename);
const file = await this.app.vault.create(fname, initData ?? "{}");
return file;
}
public async createAndOpenExcel(
filename: string,
foldername?: string,
initData?: string
): Promise<string> {
const file = await this.createExcel(filename, foldername, initData);
this.openExcel(file, "new-pane", true, undefined);
return file.path;
}
public openExcel(
excelFile: TFile,
location: PaneTarget,
active = false,
subpath?: string
) {
if (location === "md-properties") {
location = "new-tab";
}
var leaf: WorkspaceLeaf | null = null;
if (location === "popout-window") {
leaf = this.app.workspace.openPopoutLeaf();
}
if (location === "new-tab") {
leaf = this.app.workspace.getLeaf("tab");
}
if (!leaf) {
leaf = this.app.workspace.getLeaf(false);
if (
leaf.view.getViewType() !== "empty" &&
location === "new-pane"
) {
leaf = this.app.workspace.getMostRecentLeaf();
}
}
leaf?.openFile(
excelFile,
!subpath || subpath === ""
? { active }
: { active, eState: { subpath } }
).then(() => {});
}
public isExcelFile(f: TFile) {
if (!f) return false;
if (f.extension === "sheet") {
return true;
}
return false;
}
}

View file

@ -1,7 +1,7 @@
{
"id": "excel",
"name": "Excel",
"version": "1.2.3",
"version": "1.3.0",
"minAppVersion": "0.15.0",
"description": "Create spreadsheets and easily embed them in Markdown",
"author": "ljcoder",

1820
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,7 @@
"typescript": "4.7.4"
},
"dependencies": {
"monkey-around": "^2.3.0",
"x-data-spreadsheet": "^1.1.9",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz"
}

View file

@ -1,38 +0,0 @@
import { MarkdownRenderChild } from "obsidian";
import Spreadsheet from "x-data-spreadsheet";
export class Excel extends MarkdownRenderChild {
data: string;
index: number;
constructor(containerEl: HTMLElement, data: string, index: number) {
super(containerEl);
this.data = data;
this.index = index;
}
onload(): void {
console.log
const sheetEle = this.containerEl.createDiv({
cls: 'sheet-iframe',
attr: {
id: `x-spreadsheet-${this.index}`,
},
});
const jsonData = JSON.parse(this.data || "{}") || {};
//@ts-ignore
const sheet = new Spreadsheet(sheetEle, {
mode: "read",
showToolbar: false,
showBottomBar: true,
view: {
height: () => 300,
width: () => this.containerEl.clientWidth,
},
}).loadData(jsonData); // load data
// @ts-ignore
sheet.validate();
}
}

View file

@ -1,45 +0,0 @@
import { syntaxTree } from "@codemirror/language";
import {
Extension,
RangeSetBuilder,
StateField,
Transaction,
} from "@codemirror/state";
import {
Decoration,
DecorationSet,
EditorView,
} from "@codemirror/view";
import { ExcelWidget } from "./ExcelWidget";
export const ExcelField = StateField.define<DecorationSet>({
create(state): DecorationSet {
return Decoration.none;
},
update(oldState: DecorationSet, transaction: Transaction): DecorationSet {
const builder = new RangeSetBuilder<Decoration>();
syntaxTree(transaction.state).iterate({
enter(node) {
console.log(node)
// if (node.type.name.startsWith("list")) {
// // Position of the '-' or the '*'.
// const listCharFrom = node.from - 2;
// builder.add(
// listCharFrom,
// listCharFrom + 1,
// Decoration.replace({
// widget: new EmojiWidget(),
// })
// );
// }
},
});
return builder.finish();
},
provide(field: StateField<DecorationSet>): Extension {
return EditorView.decorations.from(field);
},
});

View file

@ -1,10 +1,9 @@
import ExcelPlugin from "main";
import ExcelPlugin from "src/main";
import { TextFileView, WorkspaceLeaf, Platform, Notice } from "obsidian";
import Spreadsheet from "x-data-spreadsheet";
import * as XLSX from "xlsx";
import { stox, xtos } from "./utils/xlsxspread";
export const VIEW_TYPE_EXCEL = "excel-view";
import { VIEW_TYPE_EXCEL, FRONTMATTER } from "./constants";
export class ExcelView extends TextFileView {
public plugin: ExcelPlugin;
@ -16,18 +15,18 @@ export class ExcelView extends TextFileView {
public copyHTMLEle: HTMLElement;
public sheetEle: HTMLElement;
public cellsSelected: {
sheet: Record<string, any> | null,
sri: number | null, // 选中开始行 index
sci: number | null, // 选中开始列 index
eri: number | null, // 选中结束行 index
eci: number | null, // 选中结束列 index
sheet: Record<string, any> | null;
sri: number | null; // 选中开始行 index
sci: number | null; // 选中开始列 index
eri: number | null; // 选中结束行 index
eci: number | null; // 选中结束列 index
} = {
sheet: null,
sri: null,
sci: null,
eri: null,
eci: null,
}
};
constructor(leaf: WorkspaceLeaf, plugin: ExcelPlugin) {
super(leaf);
@ -38,6 +37,31 @@ export class ExcelView extends TextFileView {
return this.data;
}
getExcelData(): string {
const tagText = "# Excel\n";
const trimLocation = this.data.search(tagText);
if (trimLocation == -1) return this.data;
const excelData = this.data.substring(
trimLocation + tagText.length,
this.data.length
);
// console.log("trimLocation", trimLocation, excelData, this.data);
return excelData;
}
headerData() {
return FRONTMATTER + "\n# Excel\n";
}
saveData(data: string) {
this.data = this.headerData() + data;
// console.log("saveData", this.data)
}
clear(): void {
this.data = this.headerData();
}
setViewData(data: string, clear: boolean): void {
this.data = data;
@ -57,18 +81,18 @@ export class ExcelView extends TextFileView {
//@ts-ignore
const files = e.target?.files;
if (!files) {
new Notice('Failed to get file')
return
new Notice("Failed to get file");
return;
}
const f = files[0];
const reader = new FileReader();
reader.onload = (e) => {
const data = e.target?.result;
if (data) {
this.process_wb(XLSX.read(data));
} else {
new Notice('Read file error')
new Notice("Read file error");
}
};
reader.readAsArrayBuffer(f);
@ -80,7 +104,7 @@ export class ExcelView extends TextFileView {
this.sheet.loadData(sheetData);
this.data = JSON.stringify(sheetData);
} else {
new Notice('Data parsing error')
new Notice("Data parsing error");
}
}
@ -92,14 +116,12 @@ export class ExcelView extends TextFileView {
XLSX.writeFile(new_wb, title + ".xlsx", {});
}
handleEmbedLink(e:Event) {
handleEmbedLink(e: Event) {
if (this.file) {
navigator.clipboard.writeText(
`![[${this.file.path}]]`,
);
new Notice('Copy embed link to clipboard')
navigator.clipboard.writeText(`![[${this.file.path}]]`);
new Notice("Copy embed link to clipboard");
} else {
new Notice('Copy embed link failed')
new Notice("Copy embed link failed");
}
}
@ -126,9 +148,7 @@ export class ExcelView extends TextFileView {
super.onload();
}
clear(): void {
this.data = "";
}
getViewType(): string {
return VIEW_TYPE_EXCEL;
@ -159,96 +179,96 @@ export class ExcelView extends TextFileView {
);
// 初始化 sheet
const jsonData = JSON.parse(this.data || "{}") || {};
const jsonData = JSON.parse(this.getExcelData() || "{}") || {};
//@ts-ignore
this.sheet = new Spreadsheet(this.sheetEle, {
showBottomBar: true,
view: {
height: () => this.contentEl.clientHeight,
width: () => this.contentEl.clientWidth,
},
})
view: {
height: () => this.contentEl.clientHeight,
width: () => this.contentEl.clientWidth,
},
})
.loadData(jsonData) // load data
.change(() => {
// save data to db
const data = this.sheet.getData()
console.log('save data to db', data)
this.data = JSON.stringify(data);
const data = this.sheet.getData();
// console.log("save data to db", data);
this.saveData(JSON.stringify(data))
})
.onAddSheet(()=> {
const data = this.sheet.getData()
.onAddSheet(() => {
const data = this.sheet.getData();
// console.log('onAddSheet', data)
this.data = JSON.stringify(data);
this.saveData(JSON.stringify(data))
})
.onRenameSheet(()=> {
const data = this.sheet.getData()
.onRenameSheet(() => {
const data = this.sheet.getData();
// console.log('onRenameSheet', data)
this.data = JSON.stringify(data);
})
this.sheet.on('cells-selected', (sheetData, {sri, sci, eri, eci}) => {
this.saveData(JSON.stringify(data))
});
this.sheet.on("cells-selected", (sheetData, { sri, sci, eri, eci }) => {
// console.log('cells-selected',sheetData, sri, sci, eri, eci)
this.cellsSelected.sheet = sheetData
this.cellsSelected.sri = sri
this.cellsSelected.sci = sci
this.cellsSelected.eri = eri
this.cellsSelected.eci = eci
})
this.sheet.on('cell-selected', (sheetData, ri, ci) => {
this.cellsSelected.sheet = sheetData;
this.cellsSelected.sri = sri;
this.cellsSelected.sci = sci;
this.cellsSelected.eri = eri;
this.cellsSelected.eci = eci;
});
this.sheet.on("cell-selected", (sheetData, ri, ci) => {
// console.log('cell-selected',sheetData, ri, ci)
this.cellsSelected.sheet = sheetData
this.cellsSelected.sri = ri
this.cellsSelected.sci = ci
this.cellsSelected.eri = ri
this.cellsSelected.eci = ci
})
this.cellsSelected.sheet = sheetData;
this.cellsSelected.sri = ri;
this.cellsSelected.sci = ci;
this.cellsSelected.eri = ri;
this.cellsSelected.eci = ci;
});
// @ts-ignore
this.sheet.validate();
}
copyToHTML() {
const data = this.cellsSelected.sheet
const sri = this.cellsSelected.sri
const sci = this.cellsSelected.sci
const eri = this.cellsSelected.eri
const eci = this.cellsSelected.eci
const data = this.cellsSelected.sheet;
const sri = this.cellsSelected.sri;
const sci = this.cellsSelected.sci;
const eri = this.cellsSelected.eri;
const eci = this.cellsSelected.eci;
// console.log('data', data, sri, sci, eri, eci)
var html = "<table>"
var html = "<table>";
if (data && sri && sci && eri && eci) {
for (var row = sri; row <= eri; row ++) {
html += "<tr>"
const cells = data.rows._[`${row}`]
for (var row = sri; row <= eri; row++) {
html += "<tr>";
const cells = data.rows._[`${row}`];
// console.log('cells', row, cells.cells)
if (cells) {
for (var col = sci; col <= eci; col ++ ) {
const cell = cells.cells[`${col}`]
for (var col = sci; col <= eci; col++) {
const cell = cells.cells[`${col}`];
// console.log('cell', row, col, cell)
if (cell) {
if (cell.merge) {
html += `<td rowspan="${cell.merge[0]}" colspan="${cell.merge[1]}">${cell.text}</td>`
html += `<td rowspan="${cell.merge[0]}" colspan="${cell.merge[1]}">${cell.text}</td>`;
} else {
html += `<td>${cell.text}</td>`
html += `<td>${cell.text}</td>`;
}
}
}
}
html += "</tr>"
html += "</tr>";
}
} else {
new Notice("Please first select the data to copy")
new Notice("Please first select the data to copy");
}
html +="</table>"
html += "</table>";
navigator.clipboard.writeText(html);
new Notice("copied")
new Notice("copied");
}
onResize() {
@ -256,6 +276,6 @@ export class ExcelView extends TextFileView {
this.refresh();
}
// console.log('resize')
super.onResize()
super.onResize();
}
}

View file

@ -1,37 +0,0 @@
import { EditorView, WidgetType } from "@codemirror/view"
import Spreadsheet from "x-data-spreadsheet";
export class ExcelWidget extends WidgetType {
data: string
constructor(data: string) {
super()
this.data = data
}
toDOM(view: EditorView): HTMLElement {
const time = new Date().getTime()
const sheetEle = document.createDiv({
cls: 'sheet-iframe',
attr: {
id: `x-spreadsheet-${time}`,
},
});
const jsonData = JSON.parse(this.data || "{}") || {};
//@ts-ignore
const sheet = new Spreadsheet(sheetEle, {
mode: "read",
showToolbar: false,
showBottomBar: true,
view: {
height: () => 300,
width: () => document.body.clientWidth,
},
}).loadData(jsonData); // load data
// @ts-ignore
sheet.validate();
return sheetEle
}
}

View file

@ -4,8 +4,8 @@ import {
TFile,
Vault,
} from "obsidian";
import ExcelPlugin from "../main";
import { Excel } from "./Excel";
import ExcelPlugin from "./main";
import Spreadsheet from "x-data-spreadsheet";
let plugin: ExcelPlugin;
let vault: Vault;
@ -23,7 +23,7 @@ const tmpObsidianWYSIWYG = async (
ctx: MarkdownPostProcessorContext
) => {
const file = plugin.app.vault.getAbstractFileByPath(ctx.sourcePath);
console.log('tmpObsidianWYSIWYG',file, el, ctx.sourcePath)
console.log("tmpObsidianWYSIWYG");
if (!(file instanceof TFile)) return;
if (!plugin.isExcelFile(file)) return;
@ -72,19 +72,81 @@ const tmpObsidianWYSIWYG = async (
//the excalidraw file in markdown preview mode
const isFrontmatterDiv = Boolean(el.querySelector(".frontmatter"));
el.empty();
if (!isFrontmatterDiv) {
if (el.parentElement === containerEl) containerEl.removeChild(el);
return;
}
internalEmbedDiv.empty();
const data = await vault.read(file);
const excel = new Excel(el, data, new Date().getTime());
ctx.addChild(excel);
const sheetDiv = createSheetEl(
getExcelData(data),
internalEmbedDiv.clientWidth
);
if (markdownEmbed) {
//display image on canvas without markdown frame
internalEmbedDiv.removeClass("markdown-embed");
internalEmbedDiv.removeClass("inline-embed");
}
internalEmbedDiv.appendChild(sheetDiv);
// console.log('internalEmbedDiv', internalEmbedDiv, markdownEmbed)
}
el.empty();
if (internalEmbedDiv.hasAttribute("ready")) {
return;
}
internalEmbedDiv.setAttribute("ready", "");
internalEmbedDiv.empty();
const data = await vault.read(file);
const excel = new Excel(el, data, 0);
ctx.addChild(excel);
const sheetDiv = createSheetEl(
getExcelData(data),
internalEmbedDiv.clientWidth
);
if (markdownEmbed) {
//display image on canvas without markdown frame
internalEmbedDiv.removeClass("markdown-embed");
internalEmbedDiv.removeClass("inline-embed");
}
internalEmbedDiv.appendChild(sheetDiv);
};
const createSheetEl = (data: string, width: number): HTMLDivElement => {
const sheetEle = createDiv({
cls: "sheet-iframe",
attr: {
id: `x-spreadsheet-${new Date().getTime()}`,
},
});
const jsonData = JSON.parse(data || "{}") || {};
//@ts-ignore
const sheet = new Spreadsheet(sheetEle, {
mode: "read",
showToolbar: false,
showBottomBar: true,
view: {
height: () => 300,
width: () => width,
},
}).loadData(jsonData); // load data
// @ts-ignore
sheet.validate();
return sheetEle;
};
const getExcelData = (data: string): string => {
const tagText = "# Excel\n";
const trimLocation = data.search(tagText);
if (trimLocation == -1) return data;
const excelData = data.substring(
trimLocation + tagText.length,
data.length
);
// console.log("trimLocation", trimLocation, excelData, this.data);
return excelData;
};
/**
@ -99,7 +161,9 @@ export const markdownPostProcessor = async (
//check to see if we are rendering in editing mode or live preview
//if yes, then there should be no .internal-embed containers
const embeddedItems = el.querySelectorAll(".internal-embed");
console.log("markdownPostProcessor", embeddedItems.length);
if (embeddedItems.length === 0) {
tmpObsidianWYSIWYG(el, ctx);
return;
}
@ -110,6 +174,7 @@ const processReadingMode = async (
embeddedItems: NodeListOf<Element> | [HTMLElement],
ctx: MarkdownPostProcessorContext
) => {
console.log("processReadingMode");
//We are processing a non-excalidraw file in reading mode
//Embedded files will be displayed in an .internal-embed container
@ -123,21 +188,31 @@ const processReadingMode = async (
if (!fname) return true;
const file = metadataCache.getFirstLinkpathDest(fname, ctx.sourcePath);
console.log("forEach", file, ctx.sourcePath);
// console.log("forEach", file, ctx.sourcePath);
//if the embeddedFile exits and it is an Excalidraw file
//then lets replace the .internal-embed with the generated PNG or SVG image
if (file && file instanceof TFile && plugin.isExcelFile(file)) {
const data = await vault.read(file);
const parent = maybeDrawing.parentElement;
if (data && parent) {
const excel = new Excel(
maybeDrawing.parentElement,
data,
index
);
ctx.addChild(excel);
}
maybeDrawing.parentElement?.replaceChild(
await processInternalEmbed(maybeDrawing,file),
maybeDrawing
);
}
});
};
const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Promise<HTMLDivElement> => {
const src = internalEmbedEl.getAttribute("src");
//@ts-ignore
if (!src) return;
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1059
internalEmbedEl.removeClass("markdown-embed");
internalEmbedEl.removeClass("inline-embed");
const data = await vault.read(file);
return await createSheetEl(getExcelData(data), internalEmbedEl.clientWidth);
}

5
src/constants.ts Normal file
View file

@ -0,0 +1,5 @@
export const VIEW_TYPE_EXCEL = "excel-view";
export const FRONTMATTER_KEY = "excel-plugin";
export const RERENDER_EVENT = "excel-embed-rerender";
export const FRONTMATTER = ["---","",`${FRONTMATTER_KEY}: parsed`,"","---", "", ""].join("\n");

336
src/main.ts Normal file
View file

@ -0,0 +1,336 @@
import {
TFile,
Plugin,
WorkspaceLeaf,
normalizePath,
ViewState,
MarkdownView,
Workspace,
} from "obsidian";
import { ExcelSettings, DEFAULT_SETTINGS } from "./utils/Settings";
import { PaneTarget } from "./utils/ModifierkeyHelper";
import { ExcelView } from "./ExcelView";
import { getExcelFilename } from "./utils/FileUtils";
import { around, dedupe } from "monkey-around";
import {
initializeMarkdownPostProcessor,
markdownPostProcessor,
} from "./MarkdownPostProcessor";
import { FRONTMATTER_KEY, FRONTMATTER, VIEW_TYPE_EXCEL } from "src/constants";
export default class ExcelPlugin extends Plugin {
public settings: ExcelSettings;
public excelFileModes: { [file: string]: string } = {};
private _loaded: boolean = false;
async onload() {
this.registerView(
VIEW_TYPE_EXCEL,
(leaf: WorkspaceLeaf) => new ExcelView(leaf, this)
);
this.registerExtensions(["sheet"], VIEW_TYPE_EXCEL);
// This creates an icon in the left ribbon.
this.addRibbonIcon("table", "Excel", (e: MouseEvent) => {
// Called when the user clicks the icon.
this.createAndOpenExcel(getExcelFilename(this.settings), undefined, this.getBlackData());
});
// markdwon后处理
this.addMarkdownPostProcessor();
//inspiration taken from kanban: https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
this.registerMonkeyPatches();
this.switchToExcelAfterLoad();
this.registerEventListeners();
}
onunload() {}
private getBlackData() {
return FRONTMATTER + "\n# Excel\n";
}
private addMarkdownPostProcessor() {
initializeMarkdownPostProcessor(this);
this.registerMarkdownPostProcessor(markdownPostProcessor);
}
private registerEventListeners() {
// const self = this;
// //save Excalidraw leaf and update embeds when switching to another leaf
// const activeLeafChangeEventHandler = async (leaf: WorkspaceLeaf) => {
// console.log('activeLeafChangeEventHandler', leaf)
// // this.switchToExcelAfterLoad()
// };
// self.registerEvent(
// this.app.workspace.on(
// "active-leaf-change",
// activeLeafChangeEventHandler
// )
// );
}
private switchToExcelAfterLoad() {
const self = this;
this.app.workspace.onLayoutReady(() => {
let leaf: WorkspaceLeaf;
let markdownLeaf = this.app.workspace.getLeavesOfType("markdown")
console.log("switchToExcelAfterLoad", markdownLeaf);
for (leaf of markdownLeaf) {
if (
leaf.view instanceof MarkdownView &&
leaf.view.file &&
self.isExcelFile(leaf.view.file)
) {
self.excelFileModes[
(leaf as any).id || leaf.view.file?.path
] = VIEW_TYPE_EXCEL;
self.setExcelView(leaf);
}
}
});
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
private registerMonkeyPatches() {
const key =
"https://github.com/zsviczian/obsidian-excalidraw-plugin/issues";
this.register(
around(Workspace.prototype, {
getActiveViewOfType(old) {
console.log("Workspace.prototype", old);
return dedupe(key, old, function (...args) {
const result = old && old.apply(this, args);
const maybeSheetView =
this.app?.workspace?.activeLeaf?.view;
if (
!maybeSheetView ||
!(maybeSheetView instanceof ExcelView)
)
return result;
});
},
})
);
//@ts-ignore
if (!this.app.plugins?.plugins?.["obsidian-hover-editor"]) {
this.register(
//stolen from hover editor
around(WorkspaceLeaf.prototype, {
getRoot(old) {
console.log("stolen from hover editor");
return function () {
const top = old.call(this);
return top.getRoot === this.getRoot
? top
: top.getRoot();
};
},
})
);
}
this.registerEvent(
this.app.workspace.on("editor-menu", (menu, editor, view) => {
if (!view || !(view instanceof MarkdownView)) return;
const file = view.file;
const leaf = view.leaf;
if (!file) return;
const cache = this.app.metadataCache.getFileCache(file);
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEY])
return;
menu.addItem((item) =>
item
.setTitle("OPEN_AS_EXCEL")
.setIcon("grid")
.setSection("excel")
.onClick(() => {
//@ts-ignore
this.excelFileModes[leaf.id || file.path] =
VIEW_TYPE_EXCEL;
this.setExcelView(leaf);
})
);
})
);
this.registerEvent(
this.app.workspace.on("file-menu", (menu, file, source, leaf) => {
if (!leaf || !(leaf.view instanceof MarkdownView)) return;
if (!(file instanceof TFile)) return;
const cache = this.app.metadataCache.getFileCache(file);
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEY])
return;
menu.addItem((item) => {
item.setTitle("OPEN_AS_EXCEL")
.setIcon("grid")
.setSection("pane")
.onClick(() => {
//@ts-ignore
this.excelFileModes[leaf.id || file.path] =
VIEW_TYPE_EXCEL;
this.setExcelView(leaf);
});
});
//@ts-ignore
menu.items.unshift(menu.items.pop());
})
);
const self = this;
// Monkey patch WorkspaceLeaf to open Excalidraw drawings with ExcalidrawView by default
this.register(
around(WorkspaceLeaf.prototype, {
// Drawings can be viewed as markdown or Excalidraw, and we keep track of the mode
// while the file is open. When the file closes, we no longer need to keep track of it.
detach(next) {
return function () {
const state = this.view?.getState();
console.log('state--', state.file)
if (
state?.file &&
self.excelFileModes[this.id || state.file]
) {
delete self.excelFileModes[this.id || state.file];
}
return next.apply(this);
};
},
setViewState(next) {
return function (state: ViewState, ...rest: any[]) {
if (
// Don't force excalidraw mode during shutdown
self._loaded &&
// If we have a markdown file
state.type === "markdown" &&
state.state?.file &&
// And the current mode of the file is not set to markdown
self.excelFileModes[this.id || state.state.file] !==
"markdown"
) {
// Then check for the excalidraw frontMatterKey
const cache = app.metadataCache.getCache(
state.state.file
);
if (
cache?.frontmatter &&
cache.frontmatter[FRONTMATTER_KEY]
) {
// If we have it, force the view type to excalidraw
const newState = {
...state,
type: VIEW_TYPE_EXCEL,
};
self.excelFileModes[state.state.file] =
VIEW_TYPE_EXCEL;
return next.apply(this, [newState, ...rest]);
}
}
return next.apply(this, [state, ...rest]);
};
},
})
);
}
public async setExcelView(leaf: WorkspaceLeaf) {
await leaf.setViewState({
type: VIEW_TYPE_EXCEL,
state: leaf.view.getState(),
popstate: true,
} as ViewState);
}
public async createExcel(
filename: string,
foldername?: string,
initData?: string
): Promise<TFile> {
const fname = normalizePath(filename);
const file = await this.app.vault.create(fname, initData ?? "{}");
return file;
}
public async createAndOpenExcel(
filename: string,
foldername?: string,
initData?: string
): Promise<string> {
const file = await this.createExcel(filename, foldername, initData);
this.openExcel(file, "new-pane", true, undefined);
return file.path;
}
public openExcel(
excelFile: TFile,
location: PaneTarget,
active = false,
subpath?: string
) {
if (location === "md-properties") {
location = "new-tab";
}
var leaf: WorkspaceLeaf | null = null;
if (location === "popout-window") {
leaf = this.app.workspace.openPopoutLeaf();
}
if (location === "new-tab") {
leaf = this.app.workspace.getLeaf("tab");
}
if (!leaf) {
leaf = this.app.workspace.getLeaf(false);
if (
leaf.view.getViewType() !== "empty" &&
location === "new-pane"
) {
leaf = this.app.workspace.getMostRecentLeaf();
}
}
leaf?.openFile(
excelFile,
!subpath || subpath === ""
? { active, state: { type: VIEW_TYPE_EXCEL } }
: { active, eState: { subpath }, state: { type: VIEW_TYPE_EXCEL } }
).then(() => {
if (leaf) {
this.setExcelView(leaf)
}
});
}
public isExcelFile(f: TFile) {
if (!f) return false;
if (f.extension === "sheet") {
return true;
}
const fileCache = f ? this.app.metadataCache.getFileCache(f) : null;
return (
!!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEY]
);
}
}

View file

@ -79,8 +79,8 @@ export function getNewUniqueFilepath(
let fname = normalizePath(`${folderpath}/${filename}`);
let file: TAbstractFile | null = vault.getAbstractFileByPath(fname);
let i = 0;
const extension = filename.endsWith(".xlsx.md")
? ".xlsx.md"
const extension = filename.endsWith(".sheet.md")
? ".sheet.md"
: filename.slice(filename.lastIndexOf("."));
while (file) {
fname = normalizePath(
@ -99,7 +99,7 @@ export function getExcelFilename(settings: ExcelSettings): string {
return (
"Excel " +
window.moment().format('YYYY-MM-DD HH.mm.ss') +
".sheet"
".sheet.md"
);
}

View file

@ -1,4 +1,4 @@
import ExcelPlugin from "main";
import ExcelPlugin from "src/main";
import { WorkspaceLeaf } from "obsidian";
const getLeafLoc = (