diff --git a/.github/images/cover.png b/.github/images/cover.png index 6369960..4203e1c 100644 Binary files a/.github/images/cover.png and b/.github/images/cover.png differ diff --git a/common/common.scss b/common/common.scss index e170146..d97b245 100644 --- a/common/common.scss +++ b/common/common.scss @@ -2,6 +2,5 @@ @import "vendor/jspreadsheet-datatables"; @import "vendor/jspreadsheet-theme"; @import "vendor/jsuites"; - -@import "table-builder"; -@import "table-editor"; +@import "post/table-edit-decorator"; +@import "modal/insert-table-modal"; diff --git a/javascripts/discourse/api-initializers/table-builder.js b/javascripts/discourse/api-initializers/table-builder.js index 87c9775..cc9bd3c 100644 --- a/javascripts/discourse/api-initializers/table-builder.js +++ b/javascripts/discourse/api-initializers/table-builder.js @@ -3,22 +3,24 @@ import { action } from "@ember/object"; import showModal from "discourse/lib/show-modal"; export default apiInitializer("0.11.1", (api) => { - api.modifyClass("component:d-editor", { + api.modifyClass("controller:composer", { pluginId: "discourse-table-builder", @action - showTableBuilder(event) { - showModal("table-builder-modal").set("toolbarEvent", event); + showTableBuilder() { + showModal("insert-table-modal").setProperties({ + toolbarEvent: this.toolbarEvent, + tableHtml: null, + }); }, }); - api.onToolbarCreate((toolbar) => { - toolbar.addButton({ + api.addToolbarPopupMenuOptionsCallback(() => { + return { id: "table-builder", - group: "insertions", + action: "showTableBuilder", icon: "table", - sendAction: (event) => toolbar.context.send("showTableBuilder", event), - title: themePrefix("discourse_table_builder.composer.button"), - }); + label: themePrefix("discourse_table_builder.composer.button"), + }; }); }); diff --git a/javascripts/discourse/api-initializers/table-editor.js b/javascripts/discourse/api-initializers/table-editor.js index 4a16fb9..bf87cae 100644 --- a/javascripts/discourse/api-initializers/table-editor.js +++ b/javascripts/discourse/api-initializers/table-editor.js @@ -35,7 +35,7 @@ export default apiInitializer("0.11.1", (api) => { return ajax(`/posts/${this.id}`, { type: "GET" }) .then((post) => { - showModal("table-editor-modal", { + showModal("insert-table-modal", { model: post, }).setProperties({ tableHtml: tempTable, diff --git a/javascripts/discourse/components/body-row.js b/javascripts/discourse/components/body-row.js deleted file mode 100644 index 27ee33e..0000000 --- a/javascripts/discourse/components/body-row.js +++ /dev/null @@ -1,34 +0,0 @@ -import GlimmerComponent from "discourse/components/glimmer"; -import { action } from "@ember/object"; - -export default class BodyRow extends GlimmerComponent { - get disableRemoveRow() { - if (this.args.allRows.length > 1) { - return false; - } else { - return true; - } - } - - @action - addBodyValue() { - const columnId = this.args.columnId; - const rowId = this.args.row.id; - const value = this.bodyRowValue; - - this.args.setRowValue(columnId, rowId, value); - } - - @action - addRow() { - const columnId = this.args.columnId; - const rowId = this.args.allRows.length + 1; - - this.args.addRow(columnId, rowId); - } - - @action - removeRow() { - this.args.removeRow(this.args.columnId, this.args.row); - } -} diff --git a/javascripts/discourse/components/header-column.js b/javascripts/discourse/components/header-column.js deleted file mode 100644 index b98c1c9..0000000 --- a/javascripts/discourse/components/header-column.js +++ /dev/null @@ -1,53 +0,0 @@ -import GlimmerComponent from "discourse/components/glimmer"; -import { action } from "@ember/object"; -import { tracked } from "@glimmer/tracking"; - -export default class HeaderColumn extends GlimmerComponent { - @tracked alignment; - - get disableRemoveColumn() { - if (this.args.tableItems.length > 1) { - return false; - } else { - return true; - } - } - - @action - addColumn() { - const newColumnId = this.args.tableItems.length + 1; - this.args.addColumn(newColumnId); - } - - @action - removeColumn() { - this.args.removeColumn(this.args.column); - } - - @action - addColumnHeader() { - const index = this.args.columnId - 1; - this.args.setColumnHeader(index, this.columnHeaderValue); - } - - @action - addRow(columnId, rowId) { - this.args.addRow(columnId, rowId); - } - - @action - removeRow(columnId, row) { - this.args.removeRow(columnId, row); - } - - @action - setRowValue(columnId, rowId, value) { - this.args.setRowValue(columnId, rowId, value); - } - - @action - alignColumn(alignment) { - this.args.alignColumn(this.args.columnId, alignment); - this.alignment = alignment; - } -} diff --git a/javascripts/discourse/components/spreadsheet-editor.js b/javascripts/discourse/components/spreadsheet-editor.js index 4f878be..0313473 100644 --- a/javascripts/discourse/components/spreadsheet-editor.js +++ b/javascripts/discourse/components/spreadsheet-editor.js @@ -1,4 +1,3 @@ -// import Controller from "@ember/controller"; import { action } from "@ember/object"; import loadScript from "discourse/lib/load-script"; import { arrayToTable, findTableRegex, tableToObj } from "../lib/utilities"; @@ -6,26 +5,115 @@ import Component from "@ember/component"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import I18n from "I18n"; +import { schedule } from "@ember/runloop"; export default Component.extend({ tagName: "", showEditReason: false, + spreadsheet: null, + // Lifecycle Methods: didInsertElement() { this._super(...arguments); - this.loadLibraries().then(() => { - this.buildTable(this.tableHtml); + schedule("afterRender", () => { + this.loadLibraries().then(() => { + if (this.isEditingTable) { + this.buildPopulatedTable(this.tableHtml); + } else { + this.buildNewTable(); + } + }); }); }, + willDestroyElement() { + this._super(...arguments); + this.spreadsheet?.destroy(); + }, + + // Getters: + get isEditingTable() { + if (this.tableHtml) { + return true; + } + + return false; + }, + + get modalAttributes() { + if (this.isEditingTable) { + return { + title: themePrefix("discourse_table_builder.edit.modal.title"), + insertTable: { + title: themePrefix("discourse_table_builder.edit.modal.create"), + icon: "pencil-alt", + }, + }; + } else { + return { + title: themePrefix("discourse_table_builder.modal.title"), + insertTable: { + title: themePrefix("discourse_table_builder.modal.create"), + icon: "plus", + }, + }; + } + }, + + // Actions: + @action + showEditReasonField() { + if (this.showEditReason) { + return this.set("showEditReason", false); + } else { + return this.set("showEditReason", true); + } + }, + + @action + cancelTableInsertion() { + this.triggerModalClose(); + }, + + @action + insertTable() { + const updatedHeaders = this.spreadsheet.getHeaders().split(","); // keys + const updatedData = this.spreadsheet.getData(); // values + const markdownTable = this.buildTableMarkdown(updatedHeaders, updatedData); + + if (!this.isEditingTable) { + this.toolbarEvent.addText(markdownTable); + return this.triggerModalClose(); + } else { + return this.updateTable(markdownTable); + } + }, + + // Helper Methods: loadLibraries() { return loadScript(settings.theme_uploads.jsuites).then(() => { return loadScript(settings.theme_uploads.jspreadsheet); }); }, - buildTable(table) { + buildNewTable() { + const data = [ + ["", "", ""], + ["", "", ""], + ["", "", ""], + ]; + + const columns = [ + { title: "Column 1", width: 150 }, + { title: "Column 2", width: 150 }, + { title: "Column 3", width: 150 }, + ]; + + return this.buildSpreadsheet(data, columns); + }, + + buildPopulatedTable(table) { const tableObject = tableToObj(table); const headings = []; const tableData = []; @@ -47,47 +135,20 @@ export default Component.extend({ }; }); + return this.buildSpreadsheet(tableData, columns); + }, + + buildSpreadsheet(data, columns, opts = {}) { const spreadsheetContainer = document.querySelector("#spreadsheet"); // eslint-disable-next-line no-undef this.spreadsheet = jspreadsheet(spreadsheetContainer, { - data: tableData, + data, columns, + ...opts, }); }, - @action - showEditReasonField() { - if (this.showEditReason) { - return this.set("showEditReason", false); - } else { - return this.set("showEditReason", true); - } - }, - - @action - cancelTableEdit() { - this.triggerModalClose(); - }, - - @action - editTable() { - const updatedHeaders = this.spreadsheet.getHeaders().split(","); // keys - const updatedData = this.spreadsheet.getData(); // values - - const markdownTable = this.buildTableMarkdown(updatedHeaders, updatedData); - const tableId = this.get("tableId"); - const postId = this.model.id; - const newRaw = markdownTable; - const editReason = - this.get("editReason") || - I18n.t(themePrefix("discourse_table_builder.edit.default_edit_reason")); - const raw = this.model.raw; - const newPostRaw = this.buildUpdatedPost(tableId, raw, newRaw); - - this.updateTable(postId, newPostRaw, editReason); - }, - buildUpdatedPost(tableId, raw, newRaw) { const tableToEdit = raw.match(findTableRegex()); let editedTable; @@ -101,7 +162,20 @@ export default Component.extend({ return editedTable; }, - updateTable(postId, raw, edit_reason) { + updateTable(markdownTable) { + const tableId = this.get("tableId"); + const postId = this.model.id; + const newRaw = markdownTable; + const editReason = + this.get("editReason") || + I18n.t(themePrefix("discourse_table_builder.edit.default_edit_reason")); + const raw = this.model.raw; + const newPostRaw = this.buildUpdatedPost(tableId, raw, newRaw); + + return this.sendTableUpdate(postId, newPostRaw, editReason); + }, + + sendTableUpdate(postId, raw, edit_reason) { return ajax(`/posts/${postId}.json`, { type: "PUT", data: { diff --git a/javascripts/discourse/controllers/table-editor-modal.js b/javascripts/discourse/controllers/insert-table-modal.js similarity index 100% rename from javascripts/discourse/controllers/table-editor-modal.js rename to javascripts/discourse/controllers/insert-table-modal.js diff --git a/javascripts/discourse/controllers/table-builder-modal.js b/javascripts/discourse/controllers/table-builder-modal.js deleted file mode 100644 index e61b6f7..0000000 --- a/javascripts/discourse/controllers/table-builder-modal.js +++ /dev/null @@ -1,153 +0,0 @@ -import Controller from "@ember/controller"; -import { action } from "@ember/object"; -import { tracked } from "@glimmer/tracking"; -import { A } from "@ember/array"; - -export default class extends Controller { - @tracked tableItems = A([ - { column: 1, rows: A([{ id: 1 }]) }, - { column: 2, rows: A([{ id: 1 }]) }, - ]); - - resetData() { - this.tableItems = A([ - { column: 1, rows: A([{ id: 1 }]) }, - { column: 2, rows: A([{ id: 1 }]) }, - ]); - } - - @action - cancelTableCreation() { - this.send("closeModal"); - } - - createDivider(alignment) { - switch (alignment) { - case "left": - return ":--"; - break; - case "right": - return "--:"; - break; - case "center": - return ":--:"; - break; - default: - return "--"; - break; - } - } - - buildTable(table) { - const headings = []; - const divider = []; - const rows = []; - - table.forEach((item) => { - headings.push(item.header); - divider.push(this.createDivider(item.alignment)); - item.rows.forEach((r) => rows.push(r)); - }); - - // Make an object for each row rather than by column - const rowItems = rows.reduce((row, { id, content }) => { - row[id] ??= { id, content: [] }; - if (Array.isArray(content)) { - row[id].content = row[id].value.concat(content); - } else { - row[id].content.push(content); - } - return row; - }, {}); - - const header = `|${headings.join("|")}|\n`; - const tableDivider = `|${divider.join("|")}|\n`; - - let rowValues; - Object.values(rowItems).forEach((item) => { - item.content.forEach((line) => { - if (line === undefined) { - line = ""; - } - - if (rowValues) { - rowValues += `${line}|`; - } else { - rowValues = `|${line}|`; - } - }); - rowValues += "\n"; - }); - - let tableMarkdown = header + tableDivider + rowValues; - - this.toolbarEvent.addText(tableMarkdown); - } - - @action - createTable() { - this.buildTable(this.tableItems); - this.resetData(); - this.send("closeModal"); - } - - @action - removeColumn(column) { - this.tableItems.removeObject(column); - } - - @action - addColumn(columnId) { - this.tableItems.pushObject({ - column: columnId, - rows: A([{ id: 1 }]), - }); - } - - @action - setColumnHeader(index, value) { - this.tableItems[index].header = value; - } - - @action - addRow(columnId, rowId) { - this.tableItems.find((item) => { - if (item.column === columnId) { - item.rows.pushObject({ id: rowId }); - } - }); - } - - @action - removeRow(columnId, row) { - this.tableItems.find((item) => { - if (item.column === columnId) { - if (item.rows.length === 1) { - // do not allow deleting if only one row left - return; - } else { - item.rows.removeObject(row); - } - } - }); - } - - @action - setRowValue(columnId, rowId, value) { - const index = columnId - 1; - this.tableItems[index].rows.find((row) => { - if (row.id === rowId) { - row.content = value; - } - }); - } - - @action - alignColumn(columnId, alignment) { - this.tableItems.find((item) => { - if (item.column === columnId) { - item.alignment = alignment; - } - }); - } -} diff --git a/javascripts/discourse/templates/components/body-row.hbs b/javascripts/discourse/templates/components/body-row.hbs deleted file mode 100644 index 3230f8c..0000000 --- a/javascripts/discourse/templates/components/body-row.hbs +++ /dev/null @@ -1,20 +0,0 @@ -