diff --git a/about.json b/about.json index 387fc7d..c1a8c50 100644 --- a/about.json +++ b/about.json @@ -9,5 +9,11 @@ "minimum_discourse_version": null, "maximum_discourse_version": null, "assets": {}, - "modifiers": {} + "modifiers": { + "svg_icons": [ + "align-left", + "align-center", + "align-right" + ] + } } \ No newline at end of file diff --git a/common/common.scss b/common/common.scss index e69de29..ffc36b6 100644 --- a/common/common.scss +++ b/common/common.scss @@ -0,0 +1,69 @@ +.table-builder-modal-modal { + .modal-inner-container { + --modal-max-width: 90vw; + } +} + +.table-header-fields { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; + + .header-column { + border: 1px solid var(--primary-low-mid); + padding: 1rem; + display: flex; + flex-direction: column; + } + + .column-action-buttons { + display: flex; + width: 100%; + gap: 0.5rem; + margin-top: auto; + border-top: 1px solid var(--primary-low-mid); + padding-top: 1rem; + + .btn { + flex: 1; + } + + &.column-aligned { + &-right .btn-align-right { + background: var(--primary-high); + .d-icon { + color: var(--primary-very-low); + } + } + &-left .btn-align-left { + background: var(--primary-high); + .d-icon { + color: var(--primary-very-low); + } + } + &-center .btn-align-center { + background: var(--primary-high); + .d-icon { + color: var(--primary-very-low); + } + } + } + } + + .body-inputs { + display: flex; + flex-direction: column; + } + + .body-row { + display: flex; + align-items: center; + justify-content: flex-start; + margin-bottom: 1rem; + gap: 0.5rem; + + input { + margin-bottom: 0; + } + } +} diff --git a/javascripts/discourse/api-initializers/table-builder.js b/javascripts/discourse/api-initializers/table-builder.js index 3970b0b..87c9775 100644 --- a/javascripts/discourse/api-initializers/table-builder.js +++ b/javascripts/discourse/api-initializers/table-builder.js @@ -1,3 +1,24 @@ import { apiInitializer } from "discourse/lib/api"; +import { action } from "@ember/object"; +import showModal from "discourse/lib/show-modal"; -export default apiInitializer("0.11.1", (api) => {}); +export default apiInitializer("0.11.1", (api) => { + api.modifyClass("component:d-editor", { + pluginId: "discourse-table-builder", + + @action + showTableBuilder(event) { + showModal("table-builder-modal").set("toolbarEvent", event); + }, + }); + + api.onToolbarCreate((toolbar) => { + toolbar.addButton({ + id: "table-builder", + group: "insertions", + icon: "table", + sendAction: (event) => toolbar.context.send("showTableBuilder", event), + title: themePrefix("discourse_table_builder.composer.button"), + }); + }); +}); diff --git a/javascripts/discourse/components/body-row.js b/javascripts/discourse/components/body-row.js new file mode 100644 index 0000000..27ee33e --- /dev/null +++ b/javascripts/discourse/components/body-row.js @@ -0,0 +1,34 @@ +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 new file mode 100644 index 0000000..b98c1c9 --- /dev/null +++ b/javascripts/discourse/components/header-column.js @@ -0,0 +1,53 @@ +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/controllers/table-builder-modal.js b/javascripts/discourse/controllers/table-builder-modal.js new file mode 100644 index 0000000..604da18 --- /dev/null +++ b/javascripts/discourse/controllers/table-builder-modal.js @@ -0,0 +1,145 @@ +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 }]) }, + ]); + + @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.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 new file mode 100644 index 0000000..53659a5 --- /dev/null +++ b/javascripts/discourse/templates/components/body-row.hbs @@ -0,0 +1,20 @@ +