WIP: New approach (see description)

- Add edit table button (beside expand table button)
- Parse the table contents
- Display the table as a spreadsheet using the Importabular library
This commit is contained in:
Keegan George
2022-07-12 15:27:03 -07:00
parent b62f595eeb
commit 9bfc23c599
12 changed files with 912 additions and 52 deletions
@@ -0,0 +1,86 @@
import { apiInitializer } from "discourse/lib/api";
import showModal from "discourse/lib/show-modal";
import { schedule } from "@ember/runloop";
import I18n from "I18n";
import { iconNode } from "discourse-common/lib/icon-library";
import { create } from "virtual-dom";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default apiInitializer("0.11.1", (api) => {
const site = api.container.lookup("site:main");
const currentUser = api.getCurrentUser();
function createButton() {
const openPopupBtn = document.createElement("button");
openPopupBtn.classList.add(
"open-popup-link",
"btn-default",
"btn",
"btn-icon-text"
);
const expandIcon = create(
iconNode("pencil-alt", { class: "edit-table-icon" })
);
const openPopupText = document.createTextNode(
I18n.t(themePrefix("discourse_table_builder.edit.btn_edit"))
);
openPopupBtn.append(expandIcon, openPopupText);
return openPopupBtn;
}
function generateModal(event) {
const table = event.target.nextElementSibling;
const tempTable = table.cloneNode(true);
const postId = this.id;
return ajax(`/posts/${postId}`, {
type: "GET",
cache: false,
})
.then((result) => {
const attrs = {
widget: this,
raw: result.raw,
};
showModal("table-editor-modal", {
model: attrs,
}).set("tableHtml", tempTable);
return result.raw;
})
.catch(popupAjaxError);
}
function generatePopups(tables, attrs) {
tables.forEach((table) => {
if (site.isMobileDevice) {
return;
}
const popupBtn = createButton();
table.parentNode.classList.add("fullscreen-table-wrapper");
table.parentNode.insertBefore(popupBtn, table);
popupBtn.addEventListener("click", generateModal.bind(attrs), false);
});
}
api.decorateCookedElement(
(post, helper) => {
const postOwner = helper.widget.attrs.username;
if (postOwner !== currentUser.username) {
return;
}
schedule("afterRender", () => {
const tables = post.querySelectorAll("table");
generatePopups(tables, helper.widget.attrs);
});
},
{
onlyStream: true,
id: "edit-table",
}
);
});
@@ -1,9 +0,0 @@
{{#if context._canEditPost}}
<DButton
@class="btn-flat"
@title={{theme-prefix "discourse_table_builder.edit.btn_edit"}}
@label={{theme-prefix "discourse_table_builder.edit.btn_edit"}}
@icon="pencil-alt"
@action={{action "showEditTableModal"}}
/>
{{/if}}
@@ -1,32 +0,0 @@
import { action } from "@ember/object";
import showModal from "discourse/lib/show-modal";
export default {
setupComponent(args, component) {
console.log(args, component, this);
},
@action
showEditTableModal() {
const selection = window.getSelection();
const selectedTable = selection.focusNode;
const { context } = this.args;
// TODO: Improve table checking logic AND change so it only shows button when selected content is a table
if (
selectedTable.nodeName === "DIV" &&
selectedTable.classList.contains("md-table")
) {
// ? TODO: simply pass quoteState object only?
const attrs = {
table: context.quoteState.buffer,
postId: context.quoteState.postId,
};
showModal("table-editor-modal", {
model: attrs,
});
} else {
console.warn("This is not a table:", selection);
}
},
};
@@ -1,9 +1,46 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
import { A } from "@ember/array";
import loadScript from "discourse/lib/load-script";
import { tableToObj } from "../lib/utilities";
export default class extends Controller {
onShow() {
// ? TODO move to component (read about not allowing Controllers to do DOM manipulation)
this._super(...arguments);
loadScript(settings.theme_uploads.importabular).then(() => {
this.buildTable(this.tableHtml);
});
}
buildTable(table) {
const tableObject = tableToObj(table);
const headings = [];
const tableData = [];
tableObject.forEach((object) => {
// Build Headings
if (!headings.includes(...Object.keys(object))) {
headings.push(...Object.keys(object));
}
tableData.push([...Object.values(object)]);
});
const columns = headings.map((heading) => {
return {
label: heading,
};
});
// eslint-disable-next-line no-unused-vars, no-undef
const sheet = new Importabular({
node: document.getElementById("table-editor-spreadsheet"),
columns,
data: tableData,
width: "100vw",
});
}
@action
cancelTableEdit() {
this.send("closeModal");
@@ -12,7 +49,6 @@ export default class extends Controller {
@action
editTable() {
// TODO: insert table edit submission logic
console.log("Table has been successfully edited");
this.send("closeModal");
}
}
+31
View File
@@ -0,0 +1,31 @@
// SRC: https://gist.github.com/mattheo-gist/4151867
/* eslint-disable */
export function tableToObj(table) {
var rows = table.rows;
var propCells = rows[0].cells;
var propNames = [];
var results = [];
var obj, row, cells;
// Use the first row for the property names
// Could use a header section but result is the same if
// there is only one header row
for (var i = 0, iLen = propCells.length; i < iLen; i++) {
propNames.push(propCells[i].textContent || propCells[i].innerText);
}
// Use the rows for data
// Could use tbody rows here to exclude header & footer
// but starting from 1 gives required result
for (var j = 1, jLen = rows.length; j < jLen; j++) {
cells = rows[j].cells;
obj = {};
for (var k = 0; k < iLen; k++) {
obj[propNames[k]] = cells[k].textContent || cells[k].innerText;
}
results.push(obj);
}
return results;
}
@@ -3,7 +3,10 @@
@class="table-editor-modal"
>
{{! TODO: Parse .md to json and fill table }}
{{model.table}}
{{! TODO: Fix instances where multiple tables in a single post }}
{{! TODO better id name }}
<div class="table-spreadsheet-container" id="table-editor-spreadsheet"></div>
</DModalBody>
@@ -11,7 +14,7 @@
<DButton
@class="btn-primary btn-edit-table"
@label={{theme-prefix "discourse_table_builder.edit.modal.create"}}
@icon="plus"
@icon="pencil-alt"
@action={{action "editTable"}}
/>