mirror of
https://github.com/akaswan/table-list.git
synced 2026-07-22 05:49:21 +00:00
Initial table finished
This commit is contained in:
parent
a0cd532701
commit
6eb0f4273c
10 changed files with 2618 additions and 140 deletions
|
|
@ -15,7 +15,7 @@ const context = await esbuild.context({
|
|||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
entryPoints: ["src/main/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
|
|
|
|||
42
main.css
Normal file
42
main.css
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* styles.css */
|
||||
.table-container {
|
||||
border: var(--border-width) solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
--one-seventh-width: 0px;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
}
|
||||
.table-header {
|
||||
background-color: var(--background-secondary);
|
||||
}
|
||||
.table th {
|
||||
padding: 12px 16px;
|
||||
font-size: small;
|
||||
font-weight: 500;
|
||||
}
|
||||
.top-left {
|
||||
width: 128px;
|
||||
}
|
||||
.table td {
|
||||
padding: 12px 16px;
|
||||
font-size: small;
|
||||
height: 68px;
|
||||
border-top: var(--border-width) solid var(--background-modifier-border);
|
||||
}
|
||||
.taskcell-enclosure {
|
||||
border-left: var(--border-width) solid var(--background-modifier-border);
|
||||
}
|
||||
.project-input {
|
||||
border: none !important;
|
||||
width: 100%;
|
||||
}
|
||||
.date {
|
||||
border-left: var(--border-width) solid var(--background-modifier-border);
|
||||
width: var(--taskcell-enclosure-width);
|
||||
}
|
||||
/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3R5bGVzLmNzcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyogRGVmaW5lIGEgY3VzdG9tIGJvcmRlciBjb2xvciAqL1xyXG4udGFibGUtY29udGFpbmVyIHtcclxuICAgIGJvcmRlcjogdmFyKC0tYm9yZGVyLXdpZHRoKSBzb2xpZCB2YXIoLS1iYWNrZ3JvdW5kLW1vZGlmaWVyLWJvcmRlcik7IC8qIEN1c3RvbSBib3JkZXIgY29sb3IgKi9cclxuICAgIGJvcmRlci1yYWRpdXM6IDhweDsgLyogUm91bmRlZCBjb3JuZXJzICovXHJcbiAgICBvdmVyZmxvdzogaGlkZGVuO1xyXG4gICAgbWF4LXdpZHRoOiAxMDAlO1xyXG4gICAgLS1vbmUtc2V2ZW50aC13aWR0aDogMHB4O1xyXG4gIH1cclxuICBcclxuICAudGFibGUge1xyXG4gICAgd2lkdGg6IDEwMCU7XHJcbiAgICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlO1xyXG4gICAgdGV4dC1hbGlnbjogbGVmdDtcclxuICB9XHJcbiAgXHJcbiAgLnRhYmxlLWhlYWRlciB7XHJcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1iYWNrZ3JvdW5kLXNlY29uZGFyeSk7XHJcbiAgfVxyXG4gIFxyXG4gIC50YWJsZSB0aCB7XHJcbiAgICBwYWRkaW5nOiAxMnB4IDE2cHg7XHJcbiAgICBmb250LXNpemU6IHNtYWxsO1xyXG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcclxuICB9IFxyXG5cclxuICAudG9wLWxlZnQge1xyXG4gICAgd2lkdGg6IDEyOHB4O1xyXG4gIH1cclxuXHJcbiAgLnRhYmxlIHRkIHtcclxuICAgIHBhZGRpbmc6IDEycHggMTZweDtcclxuICAgIGZvbnQtc2l6ZTogc21hbGw7XHJcbiAgICBoZWlnaHQ6IDY4cHg7XHJcbiAgICBib3JkZXItdG9wOiB2YXIoLS1ib3JkZXItd2lkdGgpIHNvbGlkIHZhcigtLWJhY2tncm91bmQtbW9kaWZpZXItYm9yZGVyKTtcclxuICB9XHJcblxyXG4gIC50YXNrY2VsbC1lbmNsb3N1cmUge1xyXG4gICAgYm9yZGVyLWxlZnQ6IHZhcigtLWJvcmRlci13aWR0aCkgc29saWQgdmFyKC0tYmFja2dyb3VuZC1tb2RpZmllci1ib3JkZXIpO1xyXG4gIH1cclxuXHJcbiAgLnByb2plY3QtaW5wdXQge1xyXG4gICAgYm9yZGVyOiBub25lICFpbXBvcnRhbnQ7XHJcbiAgICB3aWR0aDogMTAwJTtcclxuICB9XHJcblxyXG4gIC5kYXRlIHtcclxuICAgIGJvcmRlci1sZWZ0OiB2YXIoLS1ib3JkZXItd2lkdGgpIHNvbGlkIHZhcigtLWJhY2tncm91bmQtbW9kaWZpZXItYm9yZGVyKTsgXHJcbiAgICB3aWR0aDogdmFyKC0tdGFza2NlbGwtZW5jbG9zdXJlLXdpZHRoKTtcclxuICB9Il0sCiAgIm1hcHBpbmdzIjogIjtBQUNBO0FBQ0k7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBOyIsCiAgIm5hbWVzIjogW10KfQo= */
|
||||
134
main.ts
134
main.ts
|
|
@ -1,134 +0,0 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
}
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
|
||||
// Called when the user clicks the icon.
|
||||
new Notice('This is a notice!');
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
|
||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
});
|
||||
// This adds an editor command that can perform some operation on the current editor instance
|
||||
this.addCommand({
|
||||
id: 'sample-editor-command',
|
||||
name: 'Sample editor command',
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
console.log(editor.getSelection());
|
||||
editor.replaceSelection('Sample Editor Command');
|
||||
}
|
||||
});
|
||||
// This adds a complex command that can check whether the current state of the app allows execution of the command
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-complex',
|
||||
name: 'Open sample modal (complex)',
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (markdownView) {
|
||||
// If checking is true, we're simply "checking" if the command can be run.
|
||||
// If checking is false, then we want to actually perform the operation.
|
||||
if (!checking) {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||
|
||||
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
||||
// Using this function will automatically remove the event listener when this plugin is disabled.
|
||||
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
||||
console.log('click', evt);
|
||||
});
|
||||
|
||||
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
|
||||
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class SampleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.setText('Woah!');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Setting #1')
|
||||
.setDesc('It\'s a secret')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your secret')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.mySetting = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
2291
package-lock.json
generated
Normal file
2291
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,8 @@
|
|||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
|
|
@ -20,5 +22,10 @@
|
|||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "^4.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
47
src/main/main.ts
Normal file
47
src/main/main.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { Plugin, WorkspaceLeaf } from "obsidian";
|
||||
import { TableView, TABLE_VIEW_TYPE } from "./views/TableView";
|
||||
|
||||
export default class TableList extends Plugin {
|
||||
async onload() {
|
||||
this.registerView(TABLE_VIEW_TYPE, (leaf) => new TableView(leaf));
|
||||
|
||||
this.addRibbonIcon("table-2", "Activate view", () => {
|
||||
this.activateView();
|
||||
});
|
||||
|
||||
if (!this.app.vault.getFolderByPath("TableList")) {
|
||||
this.app.vault.createFolder("TableList");
|
||||
} else {
|
||||
console.log("Folder already exists");
|
||||
}
|
||||
}
|
||||
|
||||
async onunload() {}
|
||||
|
||||
async activateView() {
|
||||
const { workspace } = this.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
const leaves = workspace.getLeavesOfType(TABLE_VIEW_TYPE);
|
||||
|
||||
if (leaves.length > 0) {
|
||||
// A leaf with our view already exists, use that
|
||||
leaf = leaves[0];
|
||||
} else {
|
||||
// Our view could not be found in the workspace, create a new leaf
|
||||
// in the right sidebar for it
|
||||
leaf = workspace.getLeaf(false);
|
||||
if (leaf) {
|
||||
await leaf.setViewState({
|
||||
type: TABLE_VIEW_TYPE,
|
||||
active: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// "Reveal" the leaf in case it is in a collapsed sidebar
|
||||
if (leaf) {
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
src/main/react/Table.tsx
Normal file
144
src/main/react/Table.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import "styles.css";
|
||||
import { format, addDays, startOfWeek } from "date-fns";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import * as React from "react";
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
tasks: Task[];
|
||||
}
|
||||
|
||||
interface Task {
|
||||
id: number;
|
||||
name: string;
|
||||
date: Date;
|
||||
parentProjectId: number;
|
||||
}
|
||||
|
||||
const getInitialDates = () => {
|
||||
const startDate = startOfWeek(new Date(), { weekStartsOn: 1 });
|
||||
const dates = Array.from({ length: 7 }, (_, i) => {
|
||||
return format(addDays(startDate, i), "yyyy-MM-dd");
|
||||
});
|
||||
return dates;
|
||||
};
|
||||
|
||||
const getInitialProjects = () => {
|
||||
const projects: Project[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Project 1",
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Task 1",
|
||||
date: new Date(),
|
||||
parentProjectId: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Project 2",
|
||||
tasks: [
|
||||
{
|
||||
id: 2,
|
||||
name: "Task 2",
|
||||
date: new Date(),
|
||||
parentProjectId: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return projects;
|
||||
};
|
||||
|
||||
const Table: React.FC = () => {
|
||||
const [dates, setDates] = useState(getInitialDates);
|
||||
const [projects, setProjects] = useState(getInitialProjects);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const containerWidth = containerRef.current?.clientWidth;
|
||||
if (containerWidth) {
|
||||
document.documentElement.style.setProperty(
|
||||
"--taskcell-enclosure-width",
|
||||
`${(containerWidth - 128) / 7}px`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const resizeObserver = new ResizeObserver(handleResize);
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}
|
||||
|
||||
// Initial call to set the variable
|
||||
handleResize();
|
||||
|
||||
return () => {
|
||||
if (containerRef.current) {
|
||||
resizeObserver.unobserve(containerRef.current);
|
||||
}
|
||||
};
|
||||
}, [containerRef]);
|
||||
|
||||
const handleProjectNameChange = (id: number, newName: string) => {
|
||||
setProjects((prevProjects) =>
|
||||
prevProjects.map((project) =>
|
||||
project.id === id ? { ...project, name: newName } : project
|
||||
)
|
||||
);
|
||||
|
||||
console.log(projects);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="table-container" ref={containerRef}>
|
||||
<table className="table headings-center">
|
||||
<thead className="table-header">
|
||||
<tr>
|
||||
<th>Projects</th>
|
||||
{dates.map((date) => (
|
||||
<th className="date" key={date}>{`${format(
|
||||
date,
|
||||
"EEEE"
|
||||
)} - ${format(date, "yyyy-MM-dd")}`}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{projects.map((project) => (
|
||||
<tr key={project.id}>
|
||||
<td key={project.id}>
|
||||
<input
|
||||
type="text"
|
||||
value={project.name}
|
||||
className="project-input"
|
||||
placeholder="New Project"
|
||||
onChange={(e) =>
|
||||
handleProjectNameChange(
|
||||
project.id,
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
{dates.map((date, index) => (
|
||||
<td
|
||||
className="taskcell-enclosure"
|
||||
key={`${project.id}:${date}:${index}`}
|
||||
></td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
39
src/main/views/TableView.tsx
Normal file
39
src/main/views/TableView.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { StrictMode } from 'react';
|
||||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
import { Root, createRoot } from 'react-dom/client';
|
||||
import Table from 'src/main/react/Table';
|
||||
|
||||
export const TABLE_VIEW_TYPE = 'table-view';
|
||||
|
||||
export class TableView extends ItemView {
|
||||
root: Root | null = null;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
return TABLE_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return 'TableList';
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
return 'table-2';
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.root = createRoot(this.containerEl.children[1]);
|
||||
this.root.render(
|
||||
<StrictMode>
|
||||
<Table />
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
this.root?.unmount();
|
||||
}
|
||||
}
|
||||
51
styles.css
51
styles.css
|
|
@ -1,8 +1,49 @@
|
|||
/*
|
||||
/* Define a custom border color */
|
||||
.table-container {
|
||||
border: var(--border-width) solid var(--background-modifier-border); /* Custom border color */
|
||||
border-radius: 8px; /* Rounded corners */
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
--one-seventh-width: 0px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: var(--background-secondary);
|
||||
}
|
||||
|
||||
.table th {
|
||||
padding: 12px 16px;
|
||||
font-size: small;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
This CSS file will be included with your plugin, and
|
||||
available in the app when your plugin is enabled.
|
||||
.top-left {
|
||||
width: 128px;
|
||||
}
|
||||
|
||||
If your plugin does not need CSS, delete this file.
|
||||
.table td {
|
||||
padding: 12px 16px;
|
||||
font-size: small;
|
||||
height: 68px;
|
||||
border-top: var(--border-width) solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
*/
|
||||
.taskcell-enclosure {
|
||||
border-left: var(--border-width) solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.project-input {
|
||||
border: none !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date {
|
||||
border-left: var(--border-width) solid var(--background-modifier-border);
|
||||
width: var(--taskcell-enclosure-width);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue