FEATURE: Ability to build a table in the composer
- Adds a button to the composer tools which triggers a modal window for a table builder - Table builder contains fields to add table contents, as well as buttons to align contents, and add additional tables/rows
This commit is contained in:
parent
fc9898daac
commit
4c8cfa9559
|
@ -9,5 +9,11 @@
|
|||
"minimum_discourse_version": null,
|
||||
"maximum_discourse_version": null,
|
||||
"assets": {},
|
||||
"modifiers": {}
|
||||
"modifiers": {
|
||||
"svg_icons": [
|
||||
"align-left",
|
||||
"align-center",
|
||||
"align-right"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<div class="body-row">
|
||||
<Input
|
||||
@value={{bodyRowValue}}
|
||||
@class="table-builder-input"
|
||||
@placeholderKey={{theme-prefix "discourse_table_builder.modal.body"}}
|
||||
{{on "change" this.addBodyValue}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="plus"
|
||||
@title={{theme-prefix "discourse_table_builder.modal.buttons.add_row"}}
|
||||
@action={{action "addRow"}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="trash-alt"
|
||||
@class="btn-danger"
|
||||
@title={{theme-prefix "discourse_table_builder.modal.buttons.remove_row"}}
|
||||
@action={{action "removeRow"}}
|
||||
@disabled={{this.disableRemoveRow}}
|
||||
/>
|
||||
</div>
|
|
@ -0,0 +1,62 @@
|
|||
<div class="header-column">
|
||||
<div class="header-row">
|
||||
<Input
|
||||
@value={{columnHeaderValue}}
|
||||
@class="table-builder-input"
|
||||
@id="header-column-{{@columnId}}"
|
||||
@placeholderKey={{theme-prefix "discourse_table_builder.modal.header"}}
|
||||
{{on "change" this.addColumnHeader}}
|
||||
/>
|
||||
<div class="body-inputs">
|
||||
{{#each @column.rows as |item|}}
|
||||
<BodyRow
|
||||
@addRow={{this.addRow}}
|
||||
@columnId={{@columnId}}
|
||||
@allRows={{@column.rows}}
|
||||
@row={{item}}
|
||||
@removeRow={{this.removeRow}}
|
||||
@setRowValue={{this.setRowValue}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="column-action-buttons column-aligned-{{this.alignment}}">
|
||||
<DButton
|
||||
@icon="align-left"
|
||||
@class="btn-align-left"
|
||||
@title={{theme-prefix "discourse_table_builder.modal.buttons.align_left"}}
|
||||
@action={{action "alignColumn" "left"}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="align-center"
|
||||
@class="btn-align-center"
|
||||
@title={{theme-prefix
|
||||
"discourse_table_builder.modal.buttons.align_center"
|
||||
}}
|
||||
@action={{action "alignColumn" "center"}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="align-right"
|
||||
@class="btn-align-right"
|
||||
@title={{theme-prefix
|
||||
"discourse_table_builder.modal.buttons.align_right"
|
||||
}}
|
||||
@action={{action "alignColumn" "right"}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="trash-alt"
|
||||
@class="btn-danger"
|
||||
@action={{action "removeColumn"}}
|
||||
@title={{theme-prefix
|
||||
"discourse_table_builder.modal.buttons.remove_column"
|
||||
}}
|
||||
@disabled={{this.disableRemoveColumn}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="plus"
|
||||
@action={{action "addColumn"}}
|
||||
@title={{theme-prefix "discourse_table_builder.modal.buttons.add_column"}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,40 @@
|
|||
<DModalBody
|
||||
@title={{theme-prefix "discourse_table_builder.modal.title"}}
|
||||
@class="table-builder-modal"
|
||||
>
|
||||
<form id="insert-markdown-table-form" {{action "createTable" on="submit"}}>
|
||||
<section class="header-section">
|
||||
<div class="table-header-fields">
|
||||
{{#each this.tableItems as |item|}}
|
||||
<HeaderColumn
|
||||
@column={{item}}
|
||||
@columnId={{item.column}}
|
||||
@tableItems={{this.tableItems}}
|
||||
@addColumn={{this.addColumn}}
|
||||
@removeColumn={{this.removeColumn}}
|
||||
@setColumnHeader={{this.setColumnHeader}}
|
||||
@addRow={{this.addRow}}
|
||||
@removeRow={{this.removeRow}}
|
||||
@setRowValue={{this.setRowValue}}
|
||||
@alignColumn={{this.alignColumn}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@label={{theme-prefix "discourse_table_builder.modal.create"}}
|
||||
@icon="plus"
|
||||
@action={{action "createTable"}}
|
||||
/>
|
||||
|
||||
<DButton
|
||||
@class="btn-flat"
|
||||
@label={{theme-prefix "discourse_table_builder.modal.cancel"}}
|
||||
@action={{action "cancelTableCreation"}}
|
||||
/>
|
||||
</div>
|
|
@ -1,3 +1,21 @@
|
|||
en:
|
||||
discourse_table_builder:
|
||||
title: "Table Builder"
|
||||
composer:
|
||||
button: "Insert Title"
|
||||
modal:
|
||||
title: "Table Builder"
|
||||
create: "Build Table"
|
||||
cancel: "cancel"
|
||||
header: "Header"
|
||||
body: "Body"
|
||||
buttons:
|
||||
add_column: "Add Column"
|
||||
remove_column: "Remove Column"
|
||||
add_row: "Add Row"
|
||||
remove_row: "Remove Row"
|
||||
align_left: "Align Left"
|
||||
align_center: "Align Center"
|
||||
align_right: "Align Right"
|
||||
theme_metadata:
|
||||
description: "Adds a button to the composer to easily build tables in markdown"
|
||||
|
|
Loading…
Reference in New Issue