-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
621 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# Terminal Plugin | ||
# Spreadsheet | ||
|
||
A simplistic terminal based on xterm.js to provide a terminal which you can use for all your system-manipulation needs | ||
Spreadsheet is an Obsidian plugin which follows the Obsidian mindset of open-standards and supports rich spreadsheet | ||
handling through CSV. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import {FrontMatter} from "./spreadsheet.js"; | ||
import {editorLivePreviewField, parseYaml, stringifyYaml} from "obsidian"; | ||
import * as iter from "@j-cake/jcake-utils/iter"; | ||
import {Cell} from "./range.js"; | ||
|
||
export const typeDefList: Record<string, ((value: string) => any)> = { | ||
raw: column => column, | ||
} as const; | ||
|
||
export type Row<FM extends FrontMatter> = string[]; | ||
|
||
export default class DataSource<FM extends FrontMatter> { | ||
private onChange: () => void = () => void 0; | ||
|
||
public data: Row<FM>[] = []; | ||
|
||
frontMatter: FM = {} as any; | ||
|
||
parsers = typeDefList; | ||
|
||
serialise(): string { | ||
return `---\n${stringifyYaml(this.frontMatter)}\n---\n${this.data.map(i => i.join(this.frontMatter.columnSeparator ??= ';')).join('\n')}`; | ||
} | ||
|
||
clear() { | ||
this.data = []; | ||
this.onChange(); | ||
} | ||
|
||
fromString(data: string): this { | ||
const frontMatterMarker = data.indexOf("---\n"); | ||
|
||
if (frontMatterMarker > -1) { | ||
const end = data.indexOf("---\n", frontMatterMarker + 3); | ||
this.parseFrontMatter(data.slice(frontMatterMarker + 3, end).trim()); | ||
data = data.slice(end + 3).trim(); | ||
} | ||
|
||
const separator = this.frontMatter.columnSeparator ?? /[,;]/g; | ||
const rows = data.split(/\r?\n/); | ||
|
||
if (!this.frontMatter.columnTitles) | ||
this.frontMatter.columnTitles = rows.shift()?.split(separator) ?? []; | ||
|
||
this.data = rows | ||
.map(line => { | ||
const column = []; | ||
|
||
const iterator = zip(iter.IterSync(line.split(separator)), this.frontMatter.columnTypes ?? new InfiniteIterator("raw")); | ||
|
||
for (const [value, parser] of iterator) | ||
column.push((this.parsers[parser] ?? this.parsers.raw)(value)); | ||
|
||
return column; | ||
}); | ||
|
||
this.onChange(); | ||
return this; | ||
} | ||
|
||
private parseFrontMatter(frontMatter: string): number { | ||
this.frontMatter = parseYaml(frontMatter); | ||
return frontMatter.length; | ||
} | ||
|
||
onExternalChange(change: () => void) { | ||
this.onChange = change; | ||
} | ||
|
||
public get columnNames(): string[] { | ||
return this.frontMatter.columnTitles ?? []; | ||
} | ||
|
||
public valueAt(cell: Cell): string { | ||
return this.data[cell.row][cell.col]; | ||
} | ||
|
||
public setValueAt(cell: Cell, value: string) { | ||
this.data[cell.row][cell.col] = value; | ||
this.onChange(); | ||
} | ||
} | ||
|
||
export class InfiniteIterator<T> { | ||
[Symbol.iterator]() { | ||
return this.iter(); | ||
} | ||
|
||
constructor(private readonly data: T) {} | ||
|
||
private *iter(): Generator<T> { | ||
while (true) | ||
yield this.data; | ||
} | ||
} | ||
|
||
export function zip<A, B>(iter1: Iterable<A>, iter2: Iterable<B>): iter.iterSync.Iter<[A, B]> { | ||
const zip = function* (iter1: Iterable<A>, iter2: Iterable<B>): Generator<[A, B]> { | ||
for (const i of iter1) { | ||
const next = iter2[Symbol.iterator]().next(); | ||
|
||
yield [i, next.value]; | ||
|
||
if (next.done) | ||
break; | ||
} | ||
} | ||
|
||
return iter.IterSync(zip(iter1, iter2)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
export default class Range { | ||
public constructor(public from: Cell, public to: Cell) { | ||
} | ||
|
||
public eq(b: Range): boolean { | ||
return this.from.eq(b.from) && this.to.eq(b.to); | ||
} | ||
|
||
public get width(): number { | ||
return Math.max(this.from.col, this.to.col) - Math.min(this.from.col, this.to.col) + 1; | ||
} | ||
|
||
public get height(): number { | ||
return Math.max(this.from.row, this.to.row) - Math.min(this.from.row, this.to.row) + 1; | ||
} | ||
|
||
public get area(): number { | ||
return this.width * this.height; | ||
} | ||
|
||
public get topLeft(): Cell { | ||
return new Cell(Math.min(this.from.row, this.to.row), Math.min(this.from.col, this.to.col)); | ||
} | ||
|
||
public get topRight(): Cell { | ||
return new Cell(Math.max(this.from.row, this.to.row), Math.min(this.from.col, this.to.col)); | ||
} | ||
|
||
public get bottomLeft(): Cell { | ||
return new Cell(Math.min(this.from.row, this.to.row), Math.max(this.from.col, this.to.col)); | ||
} | ||
|
||
public get bottomRight(): Cell { | ||
return new Cell(Math.max(this.from.row, this.to.row), Math.max(this.from.col, this.to.col)); | ||
} | ||
|
||
public union(range: Range): Range { | ||
const topLeft1 = this.topLeft; | ||
const topLeft2 = range.topLeft; | ||
|
||
const bottomRight1 = this.bottomRight; | ||
const bottomRight2 = range.bottomRight; | ||
|
||
return new Range( | ||
new Cell(Math.min(topLeft1.row, topLeft2.row), Math.min(topLeft1.col, topLeft2.col)), | ||
new Cell(Math.max(bottomRight1.row, bottomRight2.row), Math.max(bottomRight1.col, bottomRight2.col)), | ||
); | ||
} | ||
|
||
public toString(): string { | ||
if (this.area == 1) | ||
return this.topLeft.toString(); | ||
else | ||
return `${this.topLeft.toString()}:${this.bottomRight.toString()}`; | ||
} | ||
} | ||
|
||
export class Cell { | ||
public constructor(public row: number, public col: number) { | ||
} | ||
|
||
public eq(b: Cell): boolean { | ||
return this.col == b.col && this.row == b.row; | ||
} | ||
|
||
public toString(): string { | ||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||
const alphabet2 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||
|
||
return `${[...this.col.toString(26).toUpperCase()].map(i => alphabet[alphabet2.indexOf(i)])}${this.row + 1}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import {ItemView, Menu, Notice, TextFileView, WorkspaceLeaf} from "obsidian"; | ||
import * as React from "react"; | ||
import * as rdom from "react-dom/client"; | ||
import DataSource from "./data.js"; | ||
import Table from "./table.js"; | ||
|
||
export const SPREADSHEET_VIEW = "spreadsheet-view"; | ||
|
||
export interface FrontMatter extends Record<string, any> { | ||
columnTypes?: string[], | ||
rowTypes?: string[], | ||
constrainToDefinedColumns?: boolean, | ||
constrainToDefinedRows?: boolean, | ||
columnTitles?: string[], | ||
rowTitles?: string[], | ||
allowedTypes?: string[], | ||
columnSeparator?: string | ||
} | ||
|
||
export default class Spreadsheet extends TextFileView { | ||
front: FrontMatter = {}; | ||
separator: string = ";"; | ||
|
||
dataSource: DataSource<any> = new DataSource(); | ||
|
||
getViewData(): string { | ||
return this.dataSource.serialise(); | ||
} | ||
|
||
setViewData(data: string, clear: boolean): void { | ||
if (clear) | ||
this.clear(); | ||
|
||
this.dataSource.fromString(data); | ||
} | ||
|
||
clear(): void { | ||
this.dataSource.clear(); | ||
} | ||
|
||
getViewType(): string { | ||
return SPREADSHEET_VIEW | ||
} | ||
|
||
getDisplayText(): string { | ||
return this.file?.basename ?? "Untitled Spreadsheet"; | ||
} | ||
|
||
getIcon(): string { | ||
return "sheet"; | ||
} | ||
|
||
onPaneMenu(menu: Menu, source: string) { | ||
menu.addItem(item => item | ||
.setIcon("settings") | ||
.setTitle("Spreadsheet Preferences")); | ||
} | ||
|
||
protected async onOpen(): Promise<void> { | ||
const spreadsheet = rdom.createRoot(this.contentEl); | ||
|
||
spreadsheet.render(<section className={"spreadsheet-container"}> | ||
<Table data={this.dataSource}/> | ||
</section>); | ||
} | ||
} |
Oops, something went wrong.