Skip to content
This repository has been archived by the owner on Mar 12, 2024. It is now read-only.

Commit

Permalink
Use WeakMap for state handling and check md rendering context.
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandru-dinu committed Jan 23, 2022
1 parent ca4e6a9 commit a748bc5
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 106 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ https://user-images.githubusercontent.com/14110183/128138299-fd2a1bb2-6f87-4b50-
</details>

- Sorting numerical and string data types. Custom comparator functions are part of the roadmap (see this [issue](https://github.com/alexandru-dinu/obsidian-sortable/issues/12)).
- There is only one dependency: [uuid](https://www.npmjs.com/package/uuid) for uniquely identifying tables.
- No altering of the markdown source code. Sorting is done solely in preview mode by rearranging rows (i.e. `tr` elements).
- No dependencies.

## Usage
Download the files from the latest [release](https://github.com/alexandru-dinu/obsidian-sortable/releases),
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "obsidian-sortable",
"name": "Sortable",
"version": "0.2.1",
"version": "0.2.2",
"minAppVersion": "0.12.0",
"description": "Wiki-like table sorting.",
"author": "Alexandru Dinu",
Expand Down
4 changes: 0 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,5 @@
"rollup": "^2.32.1",
"tslib": "^2.2.0",
"typescript": "^4.2.4"
},
"dependencies": {
"@types/uuid": "^8.3.1",
"uuid": "^8.3.2"
}
}
110 changes: 38 additions & 72 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,38 @@
import { Plugin } from 'obsidian';
import { onHeadClick, TTableStates, AttributeName, resetTable } from 'src/sortable';

interface SortableSettings {
mySetting: string;
}

const DEFAULT_SETTINGS: SortableSettings = {
mySetting: 'default'
};

export default class SortablePlugin extends Plugin {
settings: SortableSettings;
tableStates: TTableStates;

async onload(): Promise<void> {
console.log('Sortable: loading plugin...');

await this.loadSettings();

this.tableStates = {};

this.registerDomEvent(document, 'click', (ev: MouseEvent) => onHeadClick(ev, this.tableStates));

console.log('Sortable: loaded plugin.');
}

onunload(): void {
// iterate through all table elements in the document
// and remove the table attribute
const tables = Array.from(document.querySelectorAll('table'));
for (const table of tables) {
const tableID: string | null = table.getAttribute(AttributeName.table);

// this table is in the default state
if (tableID === null) {
continue;
}

const state = this.tableStates[tableID];

// restore original order
resetTable(state, table.querySelector('tbody'));

// remove "sortable" attribute
table.removeAttribute(AttributeName.table);

// remove tableHeader attribute
const th = table.querySelector(`thead th:nth-child(${state.columnIdx + 1})`);
th.removeAttribute(AttributeName.tableHeader);

delete this.tableStates[tableID];
}

// delete remaining keys in the tableStates object
// ideally, this should not be necessary, see (#16)
const remKeys = Object.keys(this.tableStates);
for (const key of remKeys) {
delete this.tableStates[key];
}

console.log('Sortable: unloaded plugin.');
}

async loadSettings(): Promise<void> {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}

async saveSettings(): Promise<void> {
await this.saveData(this.settings);
}
}
import { Plugin } from "obsidian";
import { onHeadClick, TTableStates, AttributeName, resetTable } from "src/sortable";

export default class SortablePlugin extends Plugin {
tableStates: TTableStates;

async onload(): Promise<void> {
console.log("Sortable: loading plugin...");

this.tableStates = new WeakMap();

this.registerDomEvent(document, "click", (ev: MouseEvent) => onHeadClick(ev, this.tableStates));

console.log("Sortable: loaded plugin.");
}

onunload(): void {
// get all HTMLTableElements present in the map and reset their state
const tables = Array.from(document.querySelectorAll("table"));

for (const table of tables) {
if (this.tableStates.has(table)) {
const state = this.tableStates.get(table);

resetTable(state, table.querySelector("tbody"));

const th = table.querySelector(`thead th:nth-child(${state.columnIdx + 1})`);
th.removeAttribute(AttributeName.tableHeader);

this.tableStates.delete(table);
}
}

delete this.tableStates;

console.log("Sortable: unloaded plugin.");
}
}
51 changes: 23 additions & 28 deletions src/sortable.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { v4 as uuidv4 } from "uuid";


enum SortOrder {
DEFAULT, ASCENDING, DESCENDING
DEFAULT,
ASCENDING,
DESCENDING,
}

export enum AttributeName {
table = "sortable-id",
tableHeader = "sortable-style",
cssAscending = "sortable-asc",
cssDescending = "sortable-desc"
cssDescending = "sortable-desc",
}

export class TableState {
Expand All @@ -18,29 +16,29 @@ export class TableState {
defaultOrdering: Array<HTMLTableRowElement> = null;
}

// Each TableState is uniquely identified by a uuid.
export type TTableStates = { [key: string]: TableState };

export type TTableStates = WeakMap<HTMLTableElement, TableState>;

export function onHeadClick(evt: MouseEvent, tableStates: TTableStates): void {
const htmlEl = (<HTMLInputElement>evt.target);

// check if the clicked element is a table header
const htmlEl = <HTMLElement>evt.target;
if (!htmlEl.matchParent(".markdown-preview-view")) {
return;
}
const th = htmlEl.closest("thead th");
if (th === null) { return; }
if (th === null) {
return;
}

const table = htmlEl.closest("table");
const tableBody = table.querySelector("tbody");
const thArray = Array.from(th.parentNode.children);
const thIdx = thArray.indexOf(th);

let tableID: string | null = table.getAttribute(AttributeName.table);

if (tableID === null) {
tableID = uuidv4().slice(0, 8);
table.setAttribute(AttributeName.table, tableID);
tableStates[tableID] = new TableState();
// create a new table state if does not previously exist
if (!tableStates.has(table)) {
tableStates.set(table, new TableState());
}
const tableState = tableStates[tableID];
const tableState = tableStates.get(table);

thArray.forEach((th, i) => {
if (i !== thIdx) {
Expand Down Expand Up @@ -75,14 +73,11 @@ export function onHeadClick(evt: MouseEvent, tableStates: TTableStates): void {
break;
}

// If the current state is now the default one, then forget about this table
// if the current state is now the default one, then forget about this table
if (tableState.sortOrder === SortOrder.DEFAULT) {
delete tableStates[tableID];
table.removeAttribute(AttributeName.table);
tableStates.delete(table);
th.removeAttribute(AttributeName.tableHeader);
}

// TODO (#16): Remove table state on pane close
}

function sortTable(tableState: TableState, tableBody: HTMLTableSectionElement): void {
Expand Down Expand Up @@ -112,7 +107,7 @@ function compareRows(a: HTMLTableRowElement, b: HTMLTableRowElement, index: numb
[valueA, valueB] = [valueB, valueA];
}

if (typeof (valueA) === "number" && typeof (valueA) === "number") {
if (typeof valueA === "number" && typeof valueA === "number") {
return valueA < valueB ? -1 : 1;
}

Expand All @@ -129,10 +124,10 @@ function valueFromCell(element: HTMLTableCellElement) {
return tryParseFloat(element.textContent);
}

function emptyTable(tableBody:HTMLTableSectionElement, rows:Array<HTMLTableRowElement>) {
function emptyTable(tableBody: HTMLTableSectionElement, rows: Array<HTMLTableRowElement>) {
rows.forEach(() => tableBody.deleteRow(-1));
}

function fillTable(tableBody:HTMLTableSectionElement, rows:Array<HTMLTableRowElement>) {
function fillTable(tableBody: HTMLTableSectionElement, rows: Array<HTMLTableRowElement>) {
rows.forEach((row) => tableBody.appendChild(row));
}
}

0 comments on commit a748bc5

Please sign in to comment.