Skip to content

Commit e7ea64b

Browse files
committed
functionality & UX improvements
- some code restructuring for readability - added context menu support for copying calculated values - fixed css behaviour when table cells loose focus - better css naming convention, providing more control over the appearance of calculation results and the last row containing calculations. - added explanatory comments for example css snippet
1 parent d82ce7b commit e7ea64b

4 files changed

Lines changed: 145 additions & 81 deletions

File tree

README.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
A plugin for Obsidian that performs mathematical operations on Markdown tables.
44
It dynamically calculates and displays results within your tables as you edit them.
5-
You can also copy the results to your clipboard.
5+
You can also copy the results to your clipboard, either via a keyboard shortcut or via the context menu.
66

77
https://github.com/user-attachments/assets/af3b295f-5bbd-497f-b507-696e9fcbb690
88

99
## How to Use
1010

11-
Simple Table Math allows you to perform calculations (sum, average, minimum, maximum, subtraction, multiplication) on columns or rows of numbers within your Markdown tables.
11+
The plugin allows you to perform calculations (sum, average, minimum, maximum, subtraction, multiplication) on columns or rows of numbers within your Markdown tables.
1212
To trigger a calculation, place a special tag within a table cell.
1313

1414
The tag follows this format: `[operation][direction][start:end][currency]`
@@ -70,40 +70,41 @@ The tag follows this format: `[operation][direction][start:end][currency]`
7070
* **Optional Range Selection:** Target specific cells for your calculations.
7171
* **Currency Formatting:** Display results with currency symbols for better readability.
7272
* **Locale-Aware Formatting:** Respects your system's locale for number formatting by default, with an option to override.
73-
* **Copy Results:** When copying a cell containing a calculated value (using `Ctrl + C` or `⌘ + C`), the numerical result will be copied to your clipboard.
73+
* **Copy Results:** When copying a cell containing an operation (using `Ctrl + C` or `⌘ + C`) or the context menu, the calculated result will be copied to your clipboard.
7474

7575
## Settings
7676

77-
You can configure Simple Table Math in the Obsidian settings under "Simple Table Math".
77+
You can configure the plugin in the Obsidian settings under "Simple Table Math".
7878
The following options are available:
7979

8080
* **Fractions:** Set the number of decimal places to display in the calculated results.
81-
* **Number formatting:** Enter a locale code (e.g., `en-US`, `de-DE`) to override the default number formatting. If left blank it will use the languagae defined for Obsidian.
81+
* **Number formatting:** Enter a locale code (e.g., `en-US`, `de-DE`) to override the default number formatting. If left blank, it will use the language defined for Obsidian.
82+
* **Highlight last row calculations:** Enable or disable styling for the last row in tables that contain calculations.
8283

8384
## CSS Look & Feel
8485

85-
Simple Table Math will add a CSS class `.stm-value` to every cell containing a calculated value.
86+
Simple Table Math adds the CSS class `.stm-value` to every cell containing a calculated value.
8687
You can use this class to style the cells in your tables. By default, these cells will be styled with a bolder font weight.
8788

8889
If the last row of a table contains calculations, it will be styled with a background color to make it easier to see the results.
8990
You can disable this behavior in the plugin settings or write your own CSS snippet to override it.
9091

91-
You can find an example CSS snippet in the [snippet.css](https://github.com/eatcodeplay/obsidian-simple-table-math/blob/0.1.0/assets/snippet.css) file.
92+
For the adventurous: any row that contains calculations will also get a `.stm-row` class.
93+
94+
You can find an example CSS snippet in the [snippet.css](https://github.com/eatcodeplay/obsidian-simple-table-math/blob/main/assets/snippet.css) file.
9295

9396
## Gotchas & Known Issues
9497

95-
* **Any column found it the calculation path, will be included in the result.**
98+
* **Any column found in the calculation path will be included in the result.**
9699
* If your headers contain numbers, make sure exclude them from calculations by using range selection.
97-
* **MOBILE:** Switching between Reading-Mode and Editing-Mode can result in the column not rerendering correctly.
98-
* Touching/Clicking inside the table or outside should solve that.
99100

100101
## Installation
101102

102-
This Plugin is currently not available in the Obsidian Community Plugins.
103-
You can install it manually by following the instructions below:
103+
This plugin is currently not available in the Obsidian Community Plugins.
104+
You can install it either by using [BRAT](https://obsidian.md/plugins?id=obsidian42-brat) or manually by following the instructions below:
104105

105106
1. Download the latest release from the [Releases](https://github.com/eatcodeplay/obsidian-simple-table-math/releases) page.
106-
2. Extract the downloaded ZIP file into your Obsidian vault's plugins folder (e.g., `<your_vault>/.obsidian/plugins/obsidian-simple-table-math`).
107+
2. Extract the downloaded ZIP content into a new folder in your Obsidian vault's plugins folder (e.g., `<your_vault>/.obsidian/plugins/obsidian-simple-table-math`).
107108
3. **Note:** On some operating systems, the `.obsidian` folder might be hidden. Make sure to show hidden files in your file explorer.
108109
4. Open Obsidian.
109110
5. Go to `Settings` -> `Community plugins`.

assets/snippet.css

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
1-
div.markdown-rendered table.table-editor tr.stm > td.stm-value,
2-
div.markdown-rendered table.table-editor tr.stm > td > .stm-value {
1+
/* -------------------------------
2+
Source / Live View Styles
3+
------------------------------- */
4+
5+
/* Styles the value cells of a table with calculated values */
6+
div.markdown-rendered table.table-editor tr > td.stm-cell.stm-value,
7+
div.markdown-rendered table.table-editor tr > td.stm-cell > .stm-value {
38
padding: 4px 12px;
49
font-weight: calc(var(--font-weight) + var(--bold-modifier));
510
}
6-
div.markdown-rendered table.table-editor tr.stm > td.stm-value + .table-cell-wrapper,
7-
div.markdown-rendered table.table-editor tr.stm > td > .stm-value + .table-cell-wrapper {
8-
display: none;
9-
}
10-
div.markdown-rendered table.table-editor tr.stm > td:focus-within > .stm-value {
11-
display: none;
12-
}
13-
div.markdown-rendered table.table-editor tr.stm:last-of-type:not(.off) {
11+
12+
/* Styles the last row of a table with calculated values */
13+
div.markdown-rendered table.table-editor tr.stm-row:last-of-type:not(.off) {
1414
background-color: var(--background-secondary);
1515
}
16-
div.el-table > table tr.stm > td.stm-value,
17-
div.el-table > table tr.stm > td > .stm-value {
16+
17+
/* -------------------------------
18+
Reading View Styles
19+
------------------------------- */
20+
21+
/* Styles the value cells of a table with calculated values */
22+
div.el-table > table tr.stm-row > td.stm-cell.stm-value,
23+
div.el-table > table tr.stm-row > td.stm-cell > .stm-value {
1824
font-weight: calc(var(--font-weight) + var(--bold-modifier));
1925
}
20-
div.el-table > table tr.stm:last-of-type:not(.off) {
26+
27+
/* Styles the last row of a table with calculated values */
28+
div.el-table > table tr.stm-row:last-of-type:not(.off) {
2129
background-color: var(--background-secondary);
2230
}

src/main.ts

Lines changed: 95 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
getIcon,
66
getLanguage,
77
MarkdownView,
8+
Menu,
9+
MenuItem,
810
Notice,
911
Plugin,
1012
PluginSettingTab,
@@ -41,20 +43,25 @@ const createFragment = (htmlstr: string) => {
4143
* The plugin actively listens to user interactions and processes table data accordingly.
4244
*/
4345
export default class SimpleTableMath extends Plugin {
44-
//----------------------------------
45-
// Variables
46-
//----------------------------------
46+
//---------------------------------------------------
47+
//
48+
// Variables
49+
//
50+
//---------------------------------------------------
4751
workspace: Workspace;
4852
layoutChangeEventRef: EventRef;
4953
editorChangeEventRef: EventRef;
54+
editorMenuEventRef: EventRef;
5055
debouncedProcessing: () => void;
5156
settings: SimpleTableMathSettings;
5257
preventProcessing: boolean = false;
5358
forceProcessing: boolean = false;
5459

55-
//----------------------------------
56-
// Plugin Lifecycle Methods
57-
//----------------------------------
60+
//---------------------------------------------------
61+
//
62+
// Plugin Lifecycle
63+
//
64+
//---------------------------------------------------
5865
async onload() {
5966
await this.loadSettings();
6067
this.addSettingTab(new SettingTab(this.app, this));
@@ -65,6 +72,7 @@ export default class SimpleTableMath extends Plugin {
6572

6673
this.editorChangeEventRef = this.workspace.on('layout-change', this.debouncedProcessing);
6774
this.layoutChangeEventRef = this.workspace.on('editor-change', this.debouncedProcessing);
75+
this.editorMenuEventRef = this.workspace.on('editor-menu', this.handleEditorMenuEvent.bind(this));
6876

6977
app.scope.register(null, 'Tab', this.debouncedProcessing);
7078
app.scope.register(null, 'ArrowLeft', this.debouncedProcessing);
@@ -73,43 +81,28 @@ export default class SimpleTableMath extends Plugin {
7381
app.scope.register(null, 'ArrowDown', this.debouncedProcessing);
7482

7583
this.registerDomEvent(document, 'click', this.debouncedProcessing);
76-
this.registerDomEvent(document, 'keydown', (evt) => {
77-
const isCopy = (evt.ctrlKey || evt.metaKey) && evt.key === 'c';
78-
if (isCopy) {
79-
this.process();
80-
this.copyResult();
81-
}
82-
});
84+
this.registerDomEvent(document, 'keydown', this.handleKeyDownEvent.bind(this));
8385

8486
this.workspace.on('file-open', () => {
8587
this.forceProcessing = true;
86-
this.process();
88+
this.debouncedProcessing();
8789
});
8890
}
8991

9092
onunload() {
9193
this.workspace.offref(this.layoutChangeEventRef);
9294
this.workspace.offref(this.editorChangeEventRef);
95+
this.workspace.offref(this.editorMenuEventRef);
9396
}
9497

95-
async loadSettings() {
96-
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
97-
}
98-
99-
async saveSettings() {
100-
await this.saveData(this.settings);
101-
const trs = document.querySelectorAll('tr.stm');
102-
trs.forEach((tr) => {
103-
if (this.settings.styleLastRow) {
104-
tr.classList.remove('off');
105-
} else {
106-
tr.classList.add('off');
107-
}
108-
})
109-
}
98+
//---------------------------------------------------
99+
//
100+
// Methods
101+
//
102+
//---------------------------------------------------
110103

111104
//----------------------------------
112-
// Table Processing Method
105+
// Table Processing
113106
//----------------------------------
114107
process() {
115108
if (!this.preventProcessing) {
@@ -132,7 +125,7 @@ export default class SimpleTableMath extends Plugin {
132125
}
133126

134127
const rowClasses = [
135-
'stm',
128+
'stm-row',
136129
...(this.settings.styleLastRow ? [] : ['off']),
137130
];
138131

@@ -227,7 +220,8 @@ export default class SimpleTableMath extends Plugin {
227220
}
228221

229222
if (vElement) {
230-
cell.classList.add('stm');
223+
cell.classList.add('stm-cell');
224+
cell.tabIndex = -1;
231225
cell.closest('tr')?.classList.add(...rowClasses);
232226
const defaultLocale = getLanguage();
233227
vElement.innerText = result.toLocaleString(this.settings.locale || defaultLocale, {
@@ -238,7 +232,7 @@ export default class SimpleTableMath extends Plugin {
238232
});
239233
}
240234
}
241-
} else if (!isActiveElement && cell.classList.contains('stm')) {
235+
} else if (!isActiveElement && cell.classList.contains('stm-cell')) {
242236
let vElement = cell.querySelector('div.stm-value') as HTMLElement | null;
243237
if (vElement) {
244238
cell.removeChild(vElement);
@@ -258,22 +252,74 @@ export default class SimpleTableMath extends Plugin {
258252
this.preventProcessing = false;
259253
}
260254

261-
copyResult() {
262-
const column = document.activeElement?.closest('td, th');
263-
if (column) {
264-
const value = column.querySelector('.stm-value') as HTMLElement | null;
265-
if (value) {
266-
setTimeout(() => {
267-
let html = '';
268-
269-
const icon = getIcon('copy-check');
270-
if (icon) {
271-
html += icon.outerHTML;
272-
}
273-
const fragment = createFragment(`${html}<span class="stm-copy-success">Copied!</span>`);
274-
new Notice(fragment, 1000);
275-
navigator.clipboard.writeText(value.textContent || '');
276-
}, 25);
255+
//----------------------------------
256+
// Settings Methods
257+
//----------------------------------
258+
async loadSettings() {
259+
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
260+
}
261+
262+
async saveSettings() {
263+
await this.saveData(this.settings);
264+
const trs = document.querySelectorAll('tr.stm-row');
265+
trs.forEach((tr) => {
266+
if (this.settings.styleLastRow) {
267+
tr.classList.remove('off');
268+
} else {
269+
tr.classList.add('off');
270+
}
271+
})
272+
}
273+
274+
//----------------------------------
275+
// UI Methods
276+
//----------------------------------
277+
showNotice(message: string, icon: string | null = null, duration: number = 1000) {
278+
let svg = '';
279+
if (icon) {
280+
const svgEl = getIcon(icon);
281+
if (svgEl) {
282+
svg = svgEl.outerHTML;
283+
}
284+
}
285+
new Notice(
286+
createFragment(`${svg}<span class="stm-notice">${message}</span>`),
287+
duration,
288+
);
289+
}
290+
291+
//----------------------------------
292+
// Event Handlers
293+
//----------------------------------
294+
handleEditorMenuEvent(menu: Menu) {
295+
const cell = document.activeElement?.closest('td.stm-cell');
296+
const value = cell?.querySelector('.stm-value') as HTMLElement | null;
297+
if (value) {
298+
menu.addItem((item: MenuItem) => {
299+
item
300+
.setTitle('Copy calculated value')
301+
.setIcon('square-equal')
302+
.onClick(async () => {
303+
navigator.clipboard.writeText(value.textContent || '');
304+
this.showNotice('Copied!', 'copy-check');
305+
});
306+
});
307+
}
308+
}
309+
310+
handleKeyDownEvent(evt: KeyboardEvent) {
311+
const isCopy = (evt.ctrlKey || evt.metaKey) && evt.key === 'c';
312+
if (isCopy) {
313+
this.process();
314+
const cell = document.activeElement?.closest('td.stm-cell, th.stm-cell');
315+
if (cell) {
316+
const value = cell.querySelector('.stm-value') as HTMLElement | null;
317+
if (value) {
318+
setTimeout(() => {
319+
navigator.clipboard.writeText(value.textContent || '');
320+
this.showNotice('Copied!', 'copy-check');
321+
}, 25);
322+
}
277323
}
278324
}
279325
}

src/styles.scss

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
div.markdown-rendered table.table-editor {
2-
tr.stm > td {
2+
tr > td.stm-cell {
33
&.stm-value, & > .stm-value {
44
padding: 4px 12px;
55
font-weight: calc(var(--font-weight) + var(--bold-modifier));
66

7-
& + .table-cell-wrapper {
7+
& + .table-cell-wrapper {
88
display: none;
99
}
1010
}
@@ -13,24 +13,33 @@ div.markdown-rendered table.table-editor {
1313
display: none;
1414
}
1515
}
16+
&:not(:focus-within) {
17+
& > .stm-value ~ .table-cell-wrapper {
18+
padding: 0;
19+
opacity: 0;
20+
max-width: 0;
21+
max-height: 0;
22+
overflow: hidden;
23+
}
24+
}
1625
}
17-
tr.stm:last-of-type:not(.off) {
26+
tr.stm-row:last-of-type:not(.off) {
1827
background-color: var(--background-secondary);
1928
}
2029
}
2130

2231
div.el-table > table {
23-
tr.stm > td {
32+
tr.stm-row > td.stm-cell {
2433
&.stm-value, & > .stm-value {
2534
font-weight: calc(var(--font-weight) + var(--bold-modifier));
2635
}
2736
}
28-
tr.stm:last-of-type:not(.off) {
37+
tr.stm-row:last-of-type:not(.off) {
2938
background-color: var(--background-secondary);
3039
}
3140
}
3241

33-
.stm-copy-success {
42+
.notice > .notice-message > svg + .stm-notice {
3443
position: relative;
3544
top: -3px;
3645
margin-left: 8px;

0 commit comments

Comments
 (0)