DEV: Port `discourse-table-builder` theme component to core (#24441)
This commit is contained in:
parent
bcca1692c6
commit
d2b53ccac2
|
@ -0,0 +1,389 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DModal from "discourse/components/d-modal";
|
||||
import DModalCancel from "discourse/components/d-modal-cancel";
|
||||
import TextField from "discourse/components/text-field";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import {
|
||||
arrayToTable,
|
||||
findTableRegex,
|
||||
tokenRange,
|
||||
} from "discourse/lib/utilities";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import I18n from "discourse-i18n";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
export default class SpreadsheetEditor extends Component {
|
||||
@service dialog;
|
||||
@tracked showEditReason = false;
|
||||
@tracked loading = null;
|
||||
spreadsheet = null;
|
||||
defaultColWidth = 150;
|
||||
isEditingTable = !!this.args.model.tableTokens;
|
||||
|
||||
get modalAttributes() {
|
||||
if (this.isEditingTable) {
|
||||
return {
|
||||
title: "table_builder.edit.modal.title",
|
||||
insertTable: {
|
||||
title: "table_builder.edit.modal.create",
|
||||
icon: "pencil-alt",
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
title: "table_builder.modal.title",
|
||||
insertTable: {
|
||||
title: "table_builder.modal.create",
|
||||
icon: "plus",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
createSpreadsheet(spreadsheet) {
|
||||
this.spreadsheet = spreadsheet;
|
||||
|
||||
schedule("afterRender", () => {
|
||||
this.loadLibraries().then(() => {
|
||||
if (this.isEditingTable) {
|
||||
this.buildPopulatedTable(this.args.model.tableTokens);
|
||||
} else {
|
||||
this.buildNewTable();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
showEditReasonField() {
|
||||
this.showEditReason = !this.showEditReason;
|
||||
}
|
||||
|
||||
@action
|
||||
interceptCloseModal() {
|
||||
if (this._hasChanges()) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("table_builder.modal.confirm_close"),
|
||||
didConfirm: () => this.args.closeModal(),
|
||||
});
|
||||
} else {
|
||||
this.args.closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
@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.args.model.toolbarEvent.addText(markdownTable);
|
||||
return this.args.closeModal();
|
||||
} else {
|
||||
return this.updateTable(markdownTable);
|
||||
}
|
||||
}
|
||||
|
||||
_hasChanges() {
|
||||
if (this.isEditingTable) {
|
||||
const originalSpreadsheetData = this.extractTableContent(
|
||||
tokenRange(this.args.model.tableTokens, "tr_open", "tr_close")
|
||||
);
|
||||
const currentHeaders = this.spreadsheet.getHeaders().split(",");
|
||||
const currentRows = this.spreadsheet.getData();
|
||||
const currentSpreadsheetData = currentHeaders.concat(currentRows.flat());
|
||||
|
||||
return (
|
||||
JSON.stringify(currentSpreadsheetData) !==
|
||||
JSON.stringify(originalSpreadsheetData)
|
||||
);
|
||||
} else {
|
||||
return this.spreadsheet
|
||||
.getData()
|
||||
.flat()
|
||||
.some((element) => element !== "");
|
||||
}
|
||||
}
|
||||
|
||||
loadLibraries() {
|
||||
this.loading = true;
|
||||
return loadScript("/javascripts/jsuites/jsuites.js")
|
||||
.then(() => {
|
||||
return loadScript("/javascripts/jspreadsheet/jspreadsheet.js");
|
||||
})
|
||||
.finally(() => (this.loading = false));
|
||||
}
|
||||
|
||||
buildNewTable() {
|
||||
const data = [
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: I18n.t("table_builder.default_header.col_1"),
|
||||
width: this.defaultColWidth,
|
||||
},
|
||||
{
|
||||
title: I18n.t("table_builder.default_header.col_2"),
|
||||
width: this.defaultColWidth,
|
||||
},
|
||||
{
|
||||
title: I18n.t("table_builder.default_header.col_3"),
|
||||
|
||||
width: this.defaultColWidth,
|
||||
},
|
||||
{
|
||||
title: I18n.t("table_builder.default_header.col_4"),
|
||||
|
||||
width: this.defaultColWidth,
|
||||
},
|
||||
];
|
||||
|
||||
return this.buildSpreadsheet(data, columns);
|
||||
}
|
||||
|
||||
extractTableContent(data) {
|
||||
return data
|
||||
.flat()
|
||||
.filter((t) => t.type === "inline")
|
||||
.map((t) => t.content);
|
||||
}
|
||||
|
||||
buildPopulatedTable(tableTokens) {
|
||||
const contentRows = tokenRange(tableTokens, "tr_open", "tr_close");
|
||||
const rows = [];
|
||||
let headings;
|
||||
const rowWidthFactor = 8;
|
||||
|
||||
contentRows.forEach((row, index) => {
|
||||
if (index === 0) {
|
||||
// headings
|
||||
headings = this.extractTableContent(row).map((heading) => {
|
||||
return {
|
||||
title: heading,
|
||||
width: Math.max(
|
||||
heading.length * rowWidthFactor,
|
||||
this.defaultColWidth
|
||||
),
|
||||
align: "left",
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// rows:
|
||||
rows.push(this.extractTableContent(row));
|
||||
}
|
||||
});
|
||||
|
||||
return this.buildSpreadsheet(rows, headings);
|
||||
}
|
||||
|
||||
buildSpreadsheet(data, columns, opts = {}) {
|
||||
const postNumber = this.args.model?.post_number;
|
||||
const exportFileName = postNumber
|
||||
? `post-${postNumber}-table-export`
|
||||
: `post-table-export`;
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
this.spreadsheet = jspreadsheet(this.spreadsheet, {
|
||||
data,
|
||||
columns,
|
||||
defaultColAlign: "left",
|
||||
wordWrap: true,
|
||||
csvFileName: exportFileName,
|
||||
text: this.localeMapping(),
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
buildUpdatedPost(tableIndex, raw, newRaw) {
|
||||
const tableToEdit = raw.match(findTableRegex());
|
||||
let editedTable;
|
||||
|
||||
if (tableToEdit.length) {
|
||||
editedTable = raw.replace(tableToEdit[tableIndex], newRaw);
|
||||
} else {
|
||||
return raw;
|
||||
}
|
||||
|
||||
// replace null characters
|
||||
editedTable = editedTable.replace(/\0/g, "\ufffd");
|
||||
return editedTable;
|
||||
}
|
||||
|
||||
updateTable(markdownTable) {
|
||||
const tableIndex = this.args.model.tableIndex;
|
||||
const postId = this.args.model.post.id;
|
||||
const newRaw = markdownTable;
|
||||
|
||||
const editReason =
|
||||
this.editReason || I18n.t("table_builder.edit.default_edit_reason");
|
||||
const raw = this.args.model.post.raw;
|
||||
const newPostRaw = this.buildUpdatedPost(tableIndex, raw, newRaw);
|
||||
|
||||
return this.sendTableUpdate(postId, newPostRaw, editReason);
|
||||
}
|
||||
|
||||
sendTableUpdate(postId, raw, edit_reason) {
|
||||
return ajax(`/posts/${postId}.json`, {
|
||||
type: "PUT",
|
||||
data: {
|
||||
post: {
|
||||
raw,
|
||||
edit_reason,
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => {
|
||||
this.args.closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
buildTableMarkdown(headers, data) {
|
||||
const table = [];
|
||||
data.forEach((row) => {
|
||||
const result = {};
|
||||
|
||||
headers.forEach((_key, index) => {
|
||||
const columnKey = `col${index}`;
|
||||
return (result[columnKey] = row[index]);
|
||||
});
|
||||
table.push(result);
|
||||
});
|
||||
|
||||
return arrayToTable(table, headers);
|
||||
}
|
||||
|
||||
localeMapping() {
|
||||
return {
|
||||
noRecordsFound: prefixedLocale("no_records_found"),
|
||||
show: prefixedLocale("show"),
|
||||
entries: prefixedLocale("entries"),
|
||||
insertANewColumnBefore: prefixedLocale("context_menu.col.before"),
|
||||
insertANewColumnAfter: prefixedLocale("context_menu.col.after"),
|
||||
deleteSelectedColumns: prefixedLocale("context_menu.col.delete"),
|
||||
renameThisColumn: prefixedLocale("context_menu.col.rename"),
|
||||
orderAscending: prefixedLocale("context_menu.order.ascending"),
|
||||
orderDescending: prefixedLocale("context_menu.order.descending"),
|
||||
insertANewRowBefore: prefixedLocale("context_menu.row.before"),
|
||||
insertANewRowAfter: prefixedLocale("context_menu.row.after"),
|
||||
deleteSelectedRows: prefixedLocale("context_menu.row.delete"),
|
||||
copy: prefixedLocale("context_menu.copy"),
|
||||
paste: prefixedLocale("context_menu.paste"),
|
||||
saveAs: prefixedLocale("context_menu.save"),
|
||||
about: prefixedLocale("about"),
|
||||
areYouSureToDeleteTheSelectedRows: prefixedLocale(
|
||||
"prompts.delete_selected_rows"
|
||||
),
|
||||
areYouSureToDeleteTheSelectedColumns: prefixedLocale(
|
||||
"prompts.delete_selected_cols"
|
||||
),
|
||||
thisActionWillDestroyAnyExistingMergedCellsAreYouSure: prefixedLocale(
|
||||
"prompts.will_destroy_merged_cells"
|
||||
),
|
||||
thisActionWillClearYourSearchResultsAreYouSure: prefixedLocale(
|
||||
"prompts.will_clear_search_results"
|
||||
),
|
||||
thereIsAConflictWithAnotherMergedCell: prefixedLocale(
|
||||
"prompts.conflict_with_merged_cells"
|
||||
),
|
||||
invalidMergeProperties: prefixedLocale("invalid_merge_props"),
|
||||
cellAlreadyMerged: prefixedLocale("cells_already_merged"),
|
||||
noCellsSelected: prefixedLocale("no_cells_selected"),
|
||||
};
|
||||
}
|
||||
|
||||
<template>
|
||||
<DModal
|
||||
@title={{i18n this.modalAttributes.title}}
|
||||
@closeModal={{this.interceptCloseModal}}
|
||||
class="insert-table-modal"
|
||||
>
|
||||
<:body>
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
||||
<div
|
||||
{{didInsert this.createSpreadsheet}}
|
||||
tabindex="1"
|
||||
class="jexcel_container"
|
||||
></div>
|
||||
</ConditionalLoadingSpinner>
|
||||
</:body>
|
||||
|
||||
<:footer>
|
||||
<div class="primary-actions">
|
||||
<DButton
|
||||
@class="btn-insert-table"
|
||||
@label={{this.modalAttributes.insertTable.title}}
|
||||
@icon={{this.modalAttributes.insertTable.icon}}
|
||||
@action={{this.insertTable}}
|
||||
/>
|
||||
|
||||
<DModalCancel @close={{this.interceptCloseModal}} />
|
||||
</div>
|
||||
|
||||
<div class="secondary-actions">
|
||||
{{#if this.isEditingTable}}
|
||||
<div class="edit-reason">
|
||||
<DButton
|
||||
@icon="info-circle"
|
||||
@title="table_builder.edit.modal.trigger_reason"
|
||||
@action={{this.showEditReasonField}}
|
||||
@class="btn-edit-reason"
|
||||
/>
|
||||
{{#if this.showEditReason}}
|
||||
<TextField
|
||||
@value={{this.editReason}}
|
||||
@placeholderKey="table_builder.edit.modal.reason"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<DTooltip
|
||||
@icon="question"
|
||||
@triggers="click"
|
||||
@arrow={{false}}
|
||||
class="btn btn-icon no-text"
|
||||
>
|
||||
<ul>
|
||||
<h4>{{i18n "table_builder.modal.help.title"}}</h4>
|
||||
<li>
|
||||
<kbd>
|
||||
{{i18n "table_builder.modal.help.enter_key"}}
|
||||
</kbd>
|
||||
{{i18n "table_builder.modal.help.new_row"}}
|
||||
</li>
|
||||
<li>
|
||||
<kbd>
|
||||
{{i18n "table_builder.modal.help.tab_key"}}
|
||||
</kbd>
|
||||
{{i18n "table_builder.modal.help.new_col"}}
|
||||
</li>
|
||||
<li>{{i18n "table_builder.modal.help.options"}}</li>
|
||||
</ul>
|
||||
</DTooltip>
|
||||
</div>
|
||||
</:footer>
|
||||
</DModal>
|
||||
</template>
|
||||
}
|
||||
|
||||
function prefixedLocale(localeString) {
|
||||
return I18n.t(`table_builder.spreadsheet.${localeString}`);
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
import { schedule } from "@ember/runloop";
|
||||
import { create } from "virtual-dom";
|
||||
import FullscreenTableModal from "discourse/components/modal/fullscreen-table";
|
||||
import SpreadsheetEditor from "discourse/components/modal/spreadsheet-editor";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import Columns from "discourse/lib/columns";
|
||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||
import { nativeLazyLoading } from "discourse/lib/lazy-load-images";
|
||||
import lightbox from "discourse/lib/lightbox";
|
||||
import { SELECTORS } from "discourse/lib/lightbox/constants";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { parseAsync } from "discourse/lib/text";
|
||||
import { setTextDirections } from "discourse/lib/text-direction";
|
||||
import { tokenRange } from "discourse/lib/utilities";
|
||||
import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
|
@ -106,21 +111,33 @@ export default {
|
|||
});
|
||||
});
|
||||
|
||||
function _createButton() {
|
||||
function _createButton(props) {
|
||||
const openPopupBtn = document.createElement("button");
|
||||
openPopupBtn.classList.add(
|
||||
const defaultClasses = [
|
||||
"open-popup-link",
|
||||
"btn-default",
|
||||
"btn",
|
||||
"btn-icon",
|
||||
"btn-expand-table",
|
||||
"no-text"
|
||||
);
|
||||
const expandIcon = create(
|
||||
iconNode("discourse-expand", { class: "expand-table-icon" })
|
||||
);
|
||||
openPopupBtn.title = I18n.t("fullscreen_table.expand_btn");
|
||||
openPopupBtn.append(expandIcon);
|
||||
"no-text",
|
||||
];
|
||||
|
||||
openPopupBtn.classList.add(...defaultClasses);
|
||||
|
||||
if (props.classes) {
|
||||
openPopupBtn.classList.add(...props.classes);
|
||||
}
|
||||
|
||||
if (props.title) {
|
||||
openPopupBtn.title = I18n.t(props.title);
|
||||
}
|
||||
|
||||
if (props.icon) {
|
||||
const icon = create(
|
||||
iconNode(props.icon.name, { class: props.icon?.class })
|
||||
);
|
||||
openPopupBtn.append(icon);
|
||||
}
|
||||
|
||||
return openPopupBtn;
|
||||
}
|
||||
|
||||
|
@ -128,14 +145,64 @@ export default {
|
|||
return scrollWidth > clientWidth;
|
||||
}
|
||||
|
||||
function generateModal(event) {
|
||||
function generateFullScreenTableModal(event) {
|
||||
const table = event.currentTarget.parentElement.nextElementSibling;
|
||||
const tempTable = table.cloneNode(true);
|
||||
modal.show(FullscreenTableModal, { model: { tableHtml: tempTable } });
|
||||
}
|
||||
|
||||
function generatePopups(tables) {
|
||||
tables.forEach((table) => {
|
||||
function generateSpreadsheetModal() {
|
||||
const tableIndex = this.tableIndex;
|
||||
|
||||
return ajax(`/posts/${this.id}`, { type: "GET" })
|
||||
.then((post) => {
|
||||
parseAsync(post.raw).then((tokens) => {
|
||||
const allTables = tokenRange(tokens, "table_open", "table_close");
|
||||
const tableTokens = allTables[tableIndex];
|
||||
|
||||
modal.show(SpreadsheetEditor, {
|
||||
model: {
|
||||
post,
|
||||
tableIndex,
|
||||
tableTokens,
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
function generatePopups(tables, attrs) {
|
||||
tables.forEach((table, index) => {
|
||||
const buttonWrapper = document.createElement("div");
|
||||
buttonWrapper.classList.add("fullscreen-table-wrapper__buttons");
|
||||
|
||||
const tableEditorBtn = _createButton({
|
||||
classes: ["btn-edit-table"],
|
||||
title: "table_builder.edit.btn_edit",
|
||||
icon: {
|
||||
name: "pencil-alt",
|
||||
class: "edit-table-icon",
|
||||
},
|
||||
});
|
||||
|
||||
table.parentNode.setAttribute("data-table-index", index);
|
||||
table.parentNode.classList.add("fullscreen-table-wrapper");
|
||||
|
||||
if (attrs.canEdit) {
|
||||
buttonWrapper.append(tableEditorBtn);
|
||||
tableEditorBtn.addEventListener(
|
||||
"click",
|
||||
generateSpreadsheetModal.bind({
|
||||
tableIndex: index,
|
||||
...attrs,
|
||||
}),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
table.parentNode.insertBefore(buttonWrapper, table);
|
||||
|
||||
if (!isOverflown(table.parentNode)) {
|
||||
return;
|
||||
}
|
||||
|
@ -144,28 +211,50 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
const popupBtn = _createButton();
|
||||
table.parentNode.classList.add("fullscreen-table-wrapper");
|
||||
// Create a button wrapper for case of multiple buttons (i.e. table builder extension)
|
||||
const buttonWrapper = document.createElement("div");
|
||||
buttonWrapper.classList.add("fullscreen-table-wrapper--buttons");
|
||||
buttonWrapper.append(popupBtn);
|
||||
popupBtn.addEventListener("click", generateModal, false);
|
||||
const expandTableBtn = _createButton({
|
||||
classes: ["btn-expand-table"],
|
||||
title: "fullscreen_table.expand_btn",
|
||||
icon: { name: "discourse-expand", class: "expand-table-icon" },
|
||||
});
|
||||
buttonWrapper.append(expandTableBtn);
|
||||
expandTableBtn.addEventListener(
|
||||
"click",
|
||||
generateFullScreenTableModal,
|
||||
false
|
||||
);
|
||||
table.parentNode.insertBefore(buttonWrapper, table);
|
||||
});
|
||||
}
|
||||
|
||||
function cleanupPopupBtns() {
|
||||
const editTableBtn = document.querySelector(
|
||||
".open-popup-link.btn-edit-table"
|
||||
);
|
||||
const expandTableBtn = document.querySelector(
|
||||
".open-popup-link.btn-expand-table"
|
||||
);
|
||||
|
||||
expandTableBtn?.removeEventListener(
|
||||
"click",
|
||||
generateFullScreenTableModal
|
||||
);
|
||||
editTableBtn?.removeEventListener("click", generateSpreadsheetModal);
|
||||
}
|
||||
|
||||
api.decorateCookedElement(
|
||||
(post) => {
|
||||
(post, helper) => {
|
||||
schedule("afterRender", () => {
|
||||
const tables = post.querySelectorAll("table");
|
||||
generatePopups(tables);
|
||||
const tables = post.querySelectorAll(".md-table table");
|
||||
generatePopups(tables, helper.widget.attrs);
|
||||
});
|
||||
},
|
||||
{
|
||||
onlyStream: true,
|
||||
id: "table-wrapper",
|
||||
}
|
||||
);
|
||||
|
||||
api.cleanupStream(cleanupPopupBtns);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -613,3 +613,79 @@ export function getCaretPosition(element, options) {
|
|||
|
||||
return adjustedPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate markdown table from an array of objects
|
||||
* Inspired by https://github.com/Ygilany/array-to-table
|
||||
*
|
||||
* @param {Array} array Array of objects
|
||||
* @param {Array} columns Column headings
|
||||
* @param {String} colPrefix Table column prefix
|
||||
*
|
||||
* @return {String} Markdown table
|
||||
*/
|
||||
export function arrayToTable(array, cols, colPrefix = "col") {
|
||||
let table = "";
|
||||
|
||||
// Generate table headers
|
||||
table += "|";
|
||||
table += cols.join(" | ");
|
||||
table += "|\r\n|";
|
||||
|
||||
// Generate table header separator
|
||||
table += cols
|
||||
.map(function () {
|
||||
return "---";
|
||||
})
|
||||
.join(" | ");
|
||||
table += "|\r\n";
|
||||
|
||||
// Generate table body
|
||||
array.forEach(function (item) {
|
||||
table += "|";
|
||||
|
||||
table +=
|
||||
cols
|
||||
.map(function (_key, index) {
|
||||
return String(item[`${colPrefix}${index}`] || "").replace(
|
||||
/\r?\n|\r/g,
|
||||
" "
|
||||
);
|
||||
})
|
||||
.join(" | ") + "|\r\n";
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns a regular expression finding all markdown tables
|
||||
*/
|
||||
export function findTableRegex() {
|
||||
return /((\r?){2}|^)(^\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)/gm;
|
||||
}
|
||||
|
||||
export function tokenRange(tokens, start, end) {
|
||||
const contents = [];
|
||||
let startPushing = false;
|
||||
let items = [];
|
||||
|
||||
tokens.forEach((token) => {
|
||||
if (token.type === start) {
|
||||
startPushing = true;
|
||||
}
|
||||
|
||||
if (token.type === end) {
|
||||
contents.push(items);
|
||||
items = [];
|
||||
startPushing = false;
|
||||
}
|
||||
|
||||
if (startPushing) {
|
||||
items.push(token);
|
||||
}
|
||||
});
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import $ from "jquery";
|
|||
import { Promise } from "rsvp";
|
||||
import DiscardDraftModal from "discourse/components/modal/discard-draft";
|
||||
import PostEnqueuedModal from "discourse/components/modal/post-enqueued";
|
||||
import SpreadsheetEditor from "discourse/components/modal/spreadsheet-editor";
|
||||
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
||||
import {
|
||||
cannotPostAgain,
|
||||
|
@ -424,6 +425,14 @@ export default class ComposerService extends Service {
|
|||
})
|
||||
);
|
||||
|
||||
options.push(
|
||||
this._setupPopupMenuOption({
|
||||
action: "toggleSpreadsheet",
|
||||
icon: "table",
|
||||
label: "composer.insert_table",
|
||||
})
|
||||
);
|
||||
|
||||
return options.concat(
|
||||
customPopupMenuOptions
|
||||
.map((option) => this._setupPopupMenuOption(option))
|
||||
|
@ -723,6 +732,16 @@ export default class ComposerService extends Service {
|
|||
this.toggleProperty("model.whisper");
|
||||
}
|
||||
|
||||
@action
|
||||
toggleSpreadsheet() {
|
||||
this.modal.show(SpreadsheetEditor, {
|
||||
model: {
|
||||
toolbarEvent: this.toolbarEvent,
|
||||
tableTokens: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
toggleInvisible() {
|
||||
this.toggleProperty("model.unlistTopic");
|
||||
|
|
|
@ -17,13 +17,13 @@ acceptance("Post Table Wrapper Test", function () {
|
|||
|
||||
assert.ok(
|
||||
exists(
|
||||
`${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper--buttons .open-popup-link`
|
||||
`${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper__buttons .open-popup-link`
|
||||
),
|
||||
"buttons for the table wrapper appear inside a separate div"
|
||||
);
|
||||
|
||||
const fullscreenButtonWrapper = query(
|
||||
`${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper--buttons`
|
||||
`${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper__buttons`
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { click, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
acceptance("Table Builder", function (needs) {
|
||||
needs.user();
|
||||
|
||||
test("Can see table builder button when creating a topic", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
await click(".d-editor-button-bar .options");
|
||||
await selectKit(".toolbar-popup-menu-options").expand();
|
||||
|
||||
assert
|
||||
.dom(`.select-kit-row[data-name='${I18n.t("composer.insert_table")}']`)
|
||||
.exists("it shows the builder button");
|
||||
});
|
||||
|
||||
test("Can see table builder button when editing post", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await click("#post_1 .show-more-actions");
|
||||
await click("#post_1 .edit");
|
||||
assert.ok(exists("#reply-control"));
|
||||
await click(".d-editor-button-bar .options");
|
||||
await selectKit(".toolbar-popup-menu-options").expand();
|
||||
|
||||
assert
|
||||
.dom(`.select-kit-row[data-name='${I18n.t("composer.insert_table")}']`)
|
||||
.exists("it shows the builder button");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
export const mdTable = `|Make | Model | Year|\r\n|--- | --- | ---|\r\n|Toyota | Supra | 1998|\r\n|Nissan | Skyline | 1999|\r\n|Honda | S2000 | 2001|\r\n`;
|
||||
export const mdTableSpecialChars = `|Make | Model | Price|\r\n|--- | --- | ---|\r\n|Toyota | Supra | $50,000|\r\n| | Celica | $20,000|\r\n|Nissan | GTR | $80,000|\r\n`;
|
||||
export const mdTableNonUniqueHeadings = `|col1 | col2 | col1|\r\n|--- | --- | ---|\r\n|Col A | Col B | Col C|\r\n`;
|
|
@ -6,6 +6,7 @@ import Handlebars from "handlebars";
|
|||
import { module, test } from "qunit";
|
||||
import sinon from "sinon";
|
||||
import {
|
||||
arrayToTable,
|
||||
caretRowCol,
|
||||
clipboardCopyAsync,
|
||||
defaultHomepage,
|
||||
|
@ -13,6 +14,7 @@ import {
|
|||
escapeExpression,
|
||||
extractDomainFromUrl,
|
||||
fillMissingDates,
|
||||
findTableRegex,
|
||||
inCodeBlock,
|
||||
initializeDefaultHomepage,
|
||||
mergeSortedLists,
|
||||
|
@ -22,6 +24,11 @@ import {
|
|||
slugify,
|
||||
toAsciiPrintable,
|
||||
} from "discourse/lib/utilities";
|
||||
import {
|
||||
mdTable,
|
||||
mdTableNonUniqueHeadings,
|
||||
mdTableSpecialChars,
|
||||
} from "discourse/tests/fixtures/md-table";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { chromeTest } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
|
@ -373,3 +380,158 @@ module("Unit | Utilities | clipboard", function (hooks) {
|
|||
}
|
||||
);
|
||||
});
|
||||
|
||||
module("Unit | Utilities | table-builder", function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test("arrayToTable", function (assert) {
|
||||
const tableData = [
|
||||
{
|
||||
col0: "Toyota",
|
||||
col1: "Supra",
|
||||
col2: "1998",
|
||||
},
|
||||
{
|
||||
col0: "Nissan",
|
||||
col1: "Skyline",
|
||||
col2: "1999",
|
||||
},
|
||||
{
|
||||
col0: "Honda",
|
||||
col1: "S2000",
|
||||
col2: "2001",
|
||||
},
|
||||
];
|
||||
|
||||
assert.strictEqual(
|
||||
arrayToTable(tableData, ["Make", "Model", "Year"]),
|
||||
mdTable,
|
||||
"it creates a markdown table from an array of objects (with headers as keys)"
|
||||
);
|
||||
|
||||
const specialCharsTableData = [
|
||||
{
|
||||
col0: "Toyota",
|
||||
col1: "Supra",
|
||||
col2: "$50,000",
|
||||
},
|
||||
{
|
||||
col0: "",
|
||||
col1: "Celica",
|
||||
col2: "$20,000",
|
||||
},
|
||||
{
|
||||
col0: "Nissan",
|
||||
col1: "GTR",
|
||||
col2: "$80,000",
|
||||
},
|
||||
];
|
||||
|
||||
assert.strictEqual(
|
||||
arrayToTable(specialCharsTableData, ["Make", "Model", "Price"]),
|
||||
mdTableSpecialChars,
|
||||
"it creates a markdown table with special characters in correct alignment"
|
||||
);
|
||||
|
||||
const nonUniqueColumns = ["col1", "col2", "col1"];
|
||||
|
||||
assert.strictEqual(
|
||||
arrayToTable(
|
||||
[{ col0: "Col A", col1: "Col B", col2: "Col C" }],
|
||||
nonUniqueColumns
|
||||
),
|
||||
mdTableNonUniqueHeadings,
|
||||
"it does not suppress a column if heading is the same as another column"
|
||||
);
|
||||
});
|
||||
test("arrayToTable with custom column prefix", function (assert) {
|
||||
const tableData = [
|
||||
{
|
||||
A0: "hey",
|
||||
A1: "you",
|
||||
},
|
||||
{
|
||||
A0: "over",
|
||||
A1: "there",
|
||||
},
|
||||
];
|
||||
|
||||
assert.strictEqual(
|
||||
arrayToTable(tableData, ["Col 1", "Col 2"], "A"),
|
||||
`|Col 1 | Col 2|\r\n|--- | ---|\r\n|hey | you|\r\n|over | there|\r\n`,
|
||||
"it works"
|
||||
);
|
||||
});
|
||||
|
||||
test("arrayToTable returns valid table with multiline cell data", function (assert) {
|
||||
const tableData = [
|
||||
{
|
||||
col0: "Jane\nDoe",
|
||||
col1: "Teri",
|
||||
},
|
||||
{
|
||||
col0: "Finch",
|
||||
col1: "Sami",
|
||||
},
|
||||
];
|
||||
|
||||
assert.strictEqual(
|
||||
arrayToTable(tableData, ["Col 1", "Col 2"]),
|
||||
`|Col 1 | Col 2|\r\n|--- | ---|\r\n|Jane Doe | Teri|\r\n|Finch | Sami|\r\n`,
|
||||
"it creates a valid table"
|
||||
);
|
||||
});
|
||||
|
||||
test("findTableRegex", function (assert) {
|
||||
const oneTable = `|Make|Model|Year|\r\n|--- | --- | ---|\r\n|Toyota|Supra|1998|`;
|
||||
|
||||
assert.strictEqual(
|
||||
oneTable.match(findTableRegex()).length,
|
||||
1,
|
||||
"finds one table in markdown"
|
||||
);
|
||||
|
||||
const threeTables = `## Heading
|
||||
|Table1 | PP Port | Device | DP | Medium|
|
||||
|--- | --- | --- | --- | ---|
|
||||
| Something | (1+2) | Dude | Mate | Bro |
|
||||
|
||||
|Table2 | PP Port | Device | DP | Medium|
|
||||
|--- | --- | --- | --- | ---|
|
||||
| Something | (1+2) | Dude | Mate | Bro |
|
||||
| ✅ | (1+2) | Dude | Mate | Bro |
|
||||
| ✅ | (1+2) | Dude | Mate | Bro |
|
||||
|
||||
|Table3 | PP Port | Device | DP |
|
||||
|--- | --- | --- | --- |
|
||||
| Something | (1+2) | Dude | Sound |
|
||||
| | (1+2) | Dude | OW |
|
||||
| | (1+2) | Dude | OI |
|
||||
|
||||
Random extras
|
||||
`;
|
||||
|
||||
assert.strictEqual(
|
||||
threeTables.match(findTableRegex()).length,
|
||||
3,
|
||||
"finds three tables in markdown"
|
||||
);
|
||||
|
||||
const ignoreUploads = `
|
||||
:information_source: Something
|
||||
|
||||
[details=Example of a cross-connect in Equinix]
|
||||
![image|603x500, 100%](upload://fURYa9mt00rXZITdYhhyeHFJE8J.png)
|
||||
[/details]
|
||||
|
||||
|Table1 | PP Port | Device | DP | Medium|
|
||||
|--- | --- | --- | --- | ---|
|
||||
| Something | (1+2) | Dude | Mate | Bro |
|
||||
`;
|
||||
assert.strictEqual(
|
||||
ignoreUploads.match(findTableRegex()).length,
|
||||
1,
|
||||
"finds on table, ignoring upload markup"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,3 +19,4 @@
|
|||
@import "common/loading-slider";
|
||||
@import "common/float-kit/_index";
|
||||
@import "common/login/_index";
|
||||
@import "common/table-builder/_index";
|
||||
|
|
|
@ -1648,10 +1648,13 @@ iframe {
|
|||
}
|
||||
|
||||
.open-popup-link {
|
||||
display: inline;
|
||||
margin-inline: 0.25em;
|
||||
position: sticky;
|
||||
left: 1rem;
|
||||
opacity: 0%;
|
||||
white-space: nowrap;
|
||||
transition: 0.25s ease-in-out opacity;
|
||||
}
|
||||
|
||||
.fullscreen-table-wrapper {
|
||||
|
@ -1659,7 +1662,7 @@ iframe {
|
|||
display: block;
|
||||
position: relative;
|
||||
|
||||
&--buttons {
|
||||
&__buttons {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
@import "vendor/jspreadsheet";
|
||||
@import "vendor/jsuites";
|
||||
@import "jspreadsheet-theme";
|
||||
@import "table-edit-decorator";
|
||||
@import "insert-table-modal";
|
|
@ -0,0 +1,78 @@
|
|||
.btn-insert-table {
|
||||
background: var(--tertiary);
|
||||
color: var(--secondary);
|
||||
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.discourse-no-touch & {
|
||||
&:hover {
|
||||
background-color: var(--tertiary-hover);
|
||||
color: var(--secondary);
|
||||
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.insert-table-modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.d-modal__container,
|
||||
.modal-inner-container {
|
||||
--modal-max-width: $reply-area-max-width;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: unset;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.d-modal__footer,
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.secondary-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
.edit-reason {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-actions .tippy-content {
|
||||
h4 {
|
||||
color: var(--primary);
|
||||
}
|
||||
li {
|
||||
margin-block: 0.25rem;
|
||||
color: var(--primary-high);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
:root {
|
||||
--jexcel_header_color: var(--primary-high);
|
||||
--jexcel_header_color_highlighted: var(--primary-high);
|
||||
--jexcel_header_background: var(--primary-very-low);
|
||||
--jexcel_header_background_highlighted: var(--primary-low);
|
||||
|
||||
--jexcel_content_color: var(--primary);
|
||||
--jexcel_content_color_highlighted: var(--primary-high);
|
||||
--jexcel_content_background: var(--secondary);
|
||||
--jexcel_content_background_highlighted: var(--tertiary-very-low);
|
||||
|
||||
--jexcel_menu_background: var(--secondary);
|
||||
--jexcel_menu_background_highlighted: var(--secondary-very-high);
|
||||
--jexcel_menu_color: var(--primary-medium);
|
||||
--jexcel_menu_color_highlighted: var(--primary);
|
||||
|
||||
--jexcel_border_color: var(--primary-low-mid);
|
||||
--jexcel_border_color_highlighted: var(--tertiary-high);
|
||||
|
||||
--active_color: var(--primary-very-low);
|
||||
--active-color: var(--active_color);
|
||||
}
|
||||
|
||||
.jexcel {
|
||||
border-bottom: 1px solid var(--jexcel_border_color);
|
||||
border-right: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
table.jexcel > thead > tr > td {
|
||||
border-top: 1px solid transparent;
|
||||
border-left: 1px solid transparent;
|
||||
border-right: 1px solid transparent;
|
||||
border-bottom: 1px solid #000;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
table.jexcel > tbody > tr > td {
|
||||
padding: 8px;
|
||||
border-right: 1px solid transparent;
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
table.jexcel {
|
||||
border-bottom: 1px solid var(--jexcel_border_color);
|
||||
}
|
||||
|
||||
.jcontextmenu.jexcel_contextmenu hr {
|
||||
border-color: var(--jexcel_border_color);
|
||||
}
|
||||
|
||||
.jexcel_container .jcontextmenu > div a {
|
||||
color: var(--jexcel_menu_color);
|
||||
}
|
||||
|
||||
.jexcel_corner {
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td,
|
||||
.jexcel > thead > tr > td {
|
||||
border-top: 1px solid var(--jexcel_border_color);
|
||||
border-left: 1px solid var(--jexcel_border_color);
|
||||
background-color: var(--jexcel_content_background);
|
||||
color: var(--jexcel_content_color);
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td:first-child,
|
||||
.jexcel > thead > tr > td {
|
||||
background-color: var(--jexcel_header_background);
|
||||
color: var(--jexcel_header_color);
|
||||
}
|
||||
|
||||
.jexcel > thead > tr > td.selected,
|
||||
.jexcel > tbody > tr.selected > td:first-child {
|
||||
background-color: var(--jexcel_header_background_highlighted);
|
||||
color: var(--jexcel_header_color_highlighted);
|
||||
}
|
||||
|
||||
table.jexcel > tbody > tr > td:first-child {
|
||||
background-color: var(--jexcel_header_background);
|
||||
}
|
||||
|
||||
table.jexcel > tbody > tr.selected > td:first-child {
|
||||
background-color: var(--jexcel_header_background_highlighted);
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td.jexcel_cursor a {
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.jexcel_pagination > div > div {
|
||||
color: var(--jexcel_header_color);
|
||||
background: var(--jexcel_header_background);
|
||||
border: 1px solid var(--jexcel_border_color);
|
||||
}
|
||||
|
||||
.jexcel_page,
|
||||
.jexcel_container input,
|
||||
.jexcel_container select {
|
||||
color: var(--jexcel_header_color);
|
||||
background: var(--jexcel_header_background);
|
||||
border: 1px solid var(--jexcel_border_color);
|
||||
}
|
||||
|
||||
.jexcel_contextmenu.jcontextmenu {
|
||||
border: 1px solid var(--jexcel_border_color);
|
||||
background: var(--jexcel_menu_background);
|
||||
color: var(--jexcel_menu_color);
|
||||
box-shadow: 0 12px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.jcontextmenu > div a {
|
||||
color: var(--jexcel_menu_color);
|
||||
}
|
||||
|
||||
.jcontextmenu > div:not(.contextmenu-line):hover a {
|
||||
color: var(--jexcel_menu_color_highlighted);
|
||||
}
|
||||
|
||||
.jcontextmenu > div:not(.contextmenu-line):hover {
|
||||
background: var(--jexcel_menu_background_highlighted);
|
||||
}
|
||||
|
||||
.jexcel_dropdown .jdropdown-container,
|
||||
.jexcel_dropdown .jdropdown-content {
|
||||
background-color: var(--jexcel_content_background);
|
||||
color: var(--jexcel_content_color);
|
||||
}
|
||||
|
||||
.jexcel_dropdown .jdropdown-item {
|
||||
color: var(--jexcel_content_color);
|
||||
}
|
||||
|
||||
.jexcel_dropdown .jdropdown-item:hover,
|
||||
.jexcel_dropdown .jdropdown-selected,
|
||||
.jexcel_dropdown .jdropdown-cursor {
|
||||
background-color: var(--jexcel_content_background_highlighted);
|
||||
color: var(--jexcel_content_color_highlighted);
|
||||
}
|
||||
|
||||
.jexcel .jcalendar-content {
|
||||
background-color: var(--jexcel_header_background);
|
||||
color: var(--jexcel_header_color);
|
||||
}
|
||||
|
||||
.jexcel .jcalendar-content > table {
|
||||
background-color: var(--jexcel_content_background);
|
||||
color: var(--jexcel_content_color);
|
||||
}
|
||||
|
||||
.jexcel .jcalendar-weekday {
|
||||
background-color: var(--jexcel_content_background_highlighted);
|
||||
color: var(--jexcel_content_color_highlighted);
|
||||
}
|
||||
|
||||
.jexcel .jcalendar-sunday {
|
||||
color: var(--jexcel_header_color);
|
||||
}
|
||||
|
||||
.jexcel .jcalendar-selected {
|
||||
background-color: var(--jexcel_content_background_highlighted);
|
||||
color: var(--jexcel_content_color_highlighted);
|
||||
}
|
||||
|
||||
.jexcel_toolbar i.jexcel_toolbar_item {
|
||||
color: var(--jexcel_content_color);
|
||||
}
|
||||
|
||||
.jexcel_toolbar i.jexcel_toolbar_item:hover {
|
||||
background: var(--jexcel_content_background_highlighted);
|
||||
color: var(--jexcel_content_color_highlighted);
|
||||
}
|
||||
|
||||
.jexcel_toolbar {
|
||||
background: var(--jexcel_header_background);
|
||||
}
|
||||
|
||||
.jexcel_content::-webkit-scrollbar-track {
|
||||
background: var(--jexcel_background_head);
|
||||
}
|
||||
|
||||
.jexcel_content::-webkit-scrollbar-thumb {
|
||||
background: var(--jexcel_background_head_highlighted);
|
||||
}
|
||||
|
||||
.jexcel_border_main {
|
||||
border: 1px solid #000;
|
||||
border-color: var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
|
||||
.jexcel .highlight {
|
||||
background-color: var(--jexcel_content_background_highlighted);
|
||||
}
|
||||
|
||||
.jexcel .highlight-bottom {
|
||||
border-bottom: 1.5px solid var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel .highlight-right {
|
||||
border-right: 1.5px solid var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel .highlight-left {
|
||||
border-left: 1.5px solid var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel .highlight-top {
|
||||
border-top: 1.5px solid var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel .copying-top {
|
||||
border-top-color: var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel .copying-right {
|
||||
border-right-color: var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel .copying-left {
|
||||
border-left-color: var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel .copying-bottom {
|
||||
border-bottom-color: var(--jexcel_border_color_highlighted);
|
||||
}
|
||||
.jexcel_border_main,
|
||||
.jexcel .highlight-top.highlight-left,
|
||||
.jexcel .highlight-top,
|
||||
.jexcel .highlight-left {
|
||||
-webkit-box-shadow: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
table.jexcel > thead > tr > td {
|
||||
border-top: 1px solid var(--jexcel_border_color);
|
||||
border-right: 1px solid var(--jexcel_border_color);
|
||||
border-bottom: 1px solid var(--jexcel_border_color);
|
||||
background-color: var(--jexcel_header_background);
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid var(--jexcel_border_color);
|
||||
}
|
||||
}
|
||||
|
||||
table.jexcel > thead > tr > td.selected {
|
||||
background-color: var(--jexcel_header_background_highlighted);
|
||||
color: var(--jexcel_header_color_highlighted);
|
||||
}
|
||||
|
||||
table.jexcel > tbody > tr > td {
|
||||
border-right: 1px solid var(--jexcel_border_color);
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid var(--jexcel_border_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Hides about item in context menu
|
||||
.jcontextmenu > div:not(.contextmenu-line):last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jexcel_container {
|
||||
padding: 0.5em;
|
||||
min-width: 100%;
|
||||
.jexcel_content {
|
||||
min-width: 100%;
|
||||
padding: 0;
|
||||
table.jexcel {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.jexcel_container {
|
||||
padding: 0;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
.btn-edit-table {
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-table-wrapper:hover .fullscreen-table-wrapper__buttons button {
|
||||
opacity: 100%;
|
||||
}
|
||||
|
||||
.mobile-view {
|
||||
.btn-edit-table {
|
||||
display: none;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
.fullscreen-table-wrapper {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
table {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.btn-edit-table {
|
||||
display: block;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,699 @@
|
|||
:root {
|
||||
--jexcel-border-color: #000;
|
||||
}
|
||||
|
||||
.jexcel_container {
|
||||
display: inline-block;
|
||||
padding-right: 2px;
|
||||
box-sizing: border-box;
|
||||
overscroll-behavior: contain;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.jexcel_container.fullscreen {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 21;
|
||||
}
|
||||
|
||||
.jexcel_container.fullscreen .jexcel_content {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.jexcel_container.with-toolbar .jexcel > thead > tr > td {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.jexcel_container.fullscreen.with-toolbar {
|
||||
height: calc(100% - 46px);
|
||||
}
|
||||
|
||||
.jexcel_content {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding-right: 3px;
|
||||
padding-bottom: 3px;
|
||||
position: relative;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #666 transparent;
|
||||
}
|
||||
|
||||
@supports (-moz-appearance: none) {
|
||||
.jexcel_content {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.jexcel_content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.jexcel_content::-webkit-scrollbar-track {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.jexcel_content::-webkit-scrollbar-thumb {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.jexcel {
|
||||
border-collapse: separate;
|
||||
table-layout: fixed;
|
||||
white-space: nowrap;
|
||||
empty-cells: show;
|
||||
border: 0px;
|
||||
background-color: #fff;
|
||||
width: 0;
|
||||
|
||||
border-top: 1px solid transparent;
|
||||
border-left: 1px solid transparent;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.jexcel > thead > tr > td {
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
background-color: #f3f3f3;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.jexcel_container.with-toolbar .jexcel > thead > tr > td {
|
||||
top: 42px;
|
||||
}
|
||||
|
||||
.jexcel > thead > tr > td.dragging {
|
||||
background-color: #fff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.jexcel > thead > tr > td.selected {
|
||||
background-color: #dcdcdc;
|
||||
}
|
||||
|
||||
.jexcel > thead > tr > td.arrow-up {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right 5px;
|
||||
background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 14l5-5 5 5H7z' fill='gray'/%3E%3C/svg%3E");
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.jexcel > thead > tr > td.arrow-down {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right 5px;
|
||||
background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E");
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td:first-child {
|
||||
position: relative;
|
||||
background-color: #f3f3f3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jexcel > tbody.resizable > tr > td:first-child::before {
|
||||
content: "\00a0";
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.jexcel > tbody.draggable > tr > td:first-child::after {
|
||||
content: "\00a0";
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr.dragging > td {
|
||||
background-color: #eee;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td {
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
padding: 4px;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.jexcel_overflow > tbody > tr > td {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td:last-child {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td > img {
|
||||
display: inline-block;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td.readonly {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.jexcel > tbody > tr.selected > td:first-child {
|
||||
background-color: #dcdcdc;
|
||||
}
|
||||
.jexcel > tbody > tr > td > select,
|
||||
.jexcel > tbody > tr > td > input,
|
||||
.jexcel > tbody > tr > td > textarea {
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
outline: 0px;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
padding-right: 2px;
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td > textarea {
|
||||
resize: none;
|
||||
padding-top: 6px !important;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td > input[type="checkbox"] {
|
||||
width: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.jexcel > tbody > tr > td > input[type="radio"] {
|
||||
width: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td > select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: 100%;
|
||||
background-position-y: 40%;
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+);
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td.jexcel_dropdown {
|
||||
background-repeat: no-repeat;
|
||||
background-position: top 50% right 5px;
|
||||
background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E");
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td.jexcel_dropdown.jexcel_comments {
|
||||
background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E")
|
||||
top 50% right 5px no-repeat,
|
||||
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=")
|
||||
top right no-repeat;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td > .color {
|
||||
width: 90%;
|
||||
height: 10px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td > a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td.highlight > a {
|
||||
color: blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jexcel > tfoot > tr > td {
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
background-color: #f3f3f3;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.jexcel .highlight {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.jexcel .highlight-top {
|
||||
border-top: 1px solid #000; /* var(--jexcel-border-color);*/
|
||||
box-shadow: 0px -1px #ccc;
|
||||
}
|
||||
|
||||
.jexcel .highlight-left {
|
||||
border-left: 1px solid #000; /* var(--jexcel-border-color);*/
|
||||
box-shadow: -1px 0px #ccc;
|
||||
}
|
||||
|
||||
.jexcel .highlight-right {
|
||||
border-right: 1px solid #000; /* var(--jexcel-border-color);*/
|
||||
}
|
||||
|
||||
.jexcel .highlight-bottom {
|
||||
border-bottom: 1px solid #000; /* var(--jexcel-border-color);*/
|
||||
}
|
||||
|
||||
.jexcel .highlight-top.highlight-left {
|
||||
box-shadow: -1px -1px #ccc;
|
||||
-webkit-box-shadow: -1px -1px #ccc;
|
||||
-moz-box-shadow: -1px -1px #ccc;
|
||||
}
|
||||
|
||||
.jexcel .highlight-selected {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.jexcel .selection {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.jexcel .selection-left {
|
||||
border-left: 1px dotted #000;
|
||||
}
|
||||
.jexcel .selection-right {
|
||||
border-right: 1px dotted #000;
|
||||
}
|
||||
.jexcel .selection-top {
|
||||
border-top: 1px dotted #000;
|
||||
}
|
||||
.jexcel .selection-bottom {
|
||||
border-bottom: 1px dotted #000;
|
||||
}
|
||||
.jexcel_corner {
|
||||
position: absolute;
|
||||
background-color: rgb(0, 0, 0);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
border: 1px solid rgb(255, 255, 255);
|
||||
top: -2000px;
|
||||
left: -2000px;
|
||||
cursor: crosshair;
|
||||
box-sizing: initial;
|
||||
z-index: 20;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.jexcel .editor {
|
||||
outline: 0px solid transparent;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
padding: 0px;
|
||||
box-sizing: border-box;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.jexcel .editor > input {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.jexcel .editor .jupload {
|
||||
position: fixed;
|
||||
top: 100%;
|
||||
z-index: 40;
|
||||
user-select: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 0.875rem;
|
||||
letter-spacing: 0.2px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
width: 300px;
|
||||
min-height: 225px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.jexcel .editor .jupload img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.jexcel .editor .jexcel_richtext {
|
||||
position: fixed;
|
||||
top: 100%;
|
||||
z-index: 40;
|
||||
user-select: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 0.875rem;
|
||||
letter-spacing: 0.2px;
|
||||
-webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
min-width: 280px;
|
||||
max-width: 310px;
|
||||
margin-top: 2px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.jexcel .editor .jclose:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 10px;
|
||||
content: "close";
|
||||
font-family: "Material icons";
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
text-shadow: 0px 0px 5px #fff;
|
||||
}
|
||||
|
||||
.jexcel,
|
||||
.jexcel td,
|
||||
.jexcel_corner {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
-khtml-user-drag: none;
|
||||
-moz-user-drag: none;
|
||||
-o-user-drag: none;
|
||||
user-drag: none;
|
||||
}
|
||||
|
||||
.jexcel_textarea {
|
||||
position: absolute;
|
||||
top: -999px;
|
||||
left: -999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.jexcel .dragline {
|
||||
position: absolute;
|
||||
}
|
||||
.jexcel .dragline div {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
height: 5px;
|
||||
width: 22px;
|
||||
}
|
||||
.jexcel .dragline div:hover {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.jexcel .onDrag {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.jexcel .error {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
.jexcel thead td.resizing {
|
||||
border-right-style: dotted !important;
|
||||
border-right-color: red !important;
|
||||
}
|
||||
|
||||
.jexcel tbody tr.resizing > td {
|
||||
border-bottom-style: dotted !important;
|
||||
border-bottom-color: red !important;
|
||||
}
|
||||
|
||||
.jexcel tbody td.resizing {
|
||||
border-right-style: dotted !important;
|
||||
border-right-color: red !important;
|
||||
}
|
||||
|
||||
.jexcel .jdropdown-header {
|
||||
border: 0px !important;
|
||||
outline: none !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
padding: 0px !important;
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
|
||||
.jexcel .jdropdown-container {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.jexcel .jdropdown-container-header {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.jexcel .jdropdown-picker {
|
||||
border: 0px !important;
|
||||
padding: 0px !important;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.jexcel .jexcel_comments {
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=");
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
}
|
||||
|
||||
.jexcel .sp-replacer {
|
||||
margin: 2px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.jexcel > thead > tr.jexcel_filter > td > input {
|
||||
border: 0px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.jexcel_about {
|
||||
float: right;
|
||||
font-size: 0.7em;
|
||||
padding: 2px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
display: none;
|
||||
}
|
||||
.jexcel_about a {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jexcel_about img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jexcel_filter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.jexcel_filter > div {
|
||||
padding: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.jexcel_pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.jexcel_pagination > div {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.jexcel_pagination > div:last-child {
|
||||
padding-right: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.jexcel_pagination > div > div {
|
||||
text-align: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
line-height: 34px;
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
margin-left: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jexcel_page {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.jexcel_page_selected {
|
||||
font-weight: bold;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
.jexcel_toolbar {
|
||||
display: flex;
|
||||
background-color: #f3f3f3;
|
||||
border: 1px solid #ccc;
|
||||
padding: 4px;
|
||||
margin: 0px 2px 4px 1px;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
z-index: 21;
|
||||
}
|
||||
|
||||
.jexcel_toolbar:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jexcel_toolbar i.jexcel_toolbar_item {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.jexcel_toolbar i.jexcel_toolbar_item:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.jexcel_toolbar select.jexcel_toolbar_item {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
display: inline-block;
|
||||
border: 0px;
|
||||
background-color: transparent;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.jexcel .dragging-left {
|
||||
background-repeat: no-repeat;
|
||||
background-position: top 50% left 0px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M14 7l-5 5 5 5V7z'/%3E%3Cpath fill='none' d='M24 0v24H0V0h24z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.jexcel .dragging-right {
|
||||
background-repeat: no-repeat;
|
||||
background-position: top 50% right 0px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 17l5-5-5-5v10z'/%3E%3Cpath fill='none' d='M0 24V0h24v24H0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.jexcel_tabs .jexcel_tab {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jexcel_tabs .jexcel_tab_link {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
background-color: #f3f3f3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jexcel_tabs .jexcel_tab_link.selected {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.jexcel_hidden_index > tbody > tr > td:first-child,
|
||||
.jexcel_hidden_index > thead > tr > td:first-child,
|
||||
.jexcel_hidden_index > tfoot > tr > td:first-child,
|
||||
.jexcel_hidden_index > colgroup > col:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jexcel .jrating {
|
||||
display: inline-flex;
|
||||
}
|
||||
.jexcel .jrating > div {
|
||||
zoom: 0.55;
|
||||
}
|
||||
|
||||
.jexcel .copying-top {
|
||||
border-top: 1px dashed #000;
|
||||
}
|
||||
|
||||
.jexcel .copying-left {
|
||||
border-left: 1px dashed #000;
|
||||
}
|
||||
|
||||
.jexcel .copying-right {
|
||||
border-right: 1px dashed #000;
|
||||
}
|
||||
|
||||
.jexcel .copying-bottom {
|
||||
border-bottom: 1px dashed #000;
|
||||
}
|
||||
|
||||
.jexcel .jexcel_column_filter {
|
||||
background-repeat: no-repeat;
|
||||
background-position: top 50% right 5px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E");
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding: 0px;
|
||||
padding-left: 6px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.jexcel thead .jexcel_freezed,
|
||||
.jexcel tfoot .jexcel_freezed {
|
||||
left: 0px;
|
||||
z-index: 3 !important;
|
||||
box-shadow: 2px 0px 2px 0.2px #ccc !important;
|
||||
-webkit-box-shadow: 2px 0px 2px 0.2px #ccc !important;
|
||||
-moz-box-shadow: 2px 0px 2px 0.2px #ccc !important;
|
||||
}
|
||||
|
||||
.jexcel tbody .jexcel_freezed {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
box-shadow: 1px 1px 1px 1px #ccc !important;
|
||||
-webkit-box-shadow: 2px 4px 4px 0.1px #ccc !important;
|
||||
-moz-box-shadow: 2px 4px 4px 0.1px #ccc !important;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.jexcel > tbody > tr > td.readonly > input[type="checkbox"],
|
||||
.jexcel > tbody > tr > td.readonly > input[type="radio"] {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -2382,6 +2382,7 @@ en:
|
|||
add_warning: "This is an official warning."
|
||||
toggle_whisper: "Toggle Whisper"
|
||||
toggle_unlisted: "Toggle Unlisted"
|
||||
insert_table: "Insert Table"
|
||||
posting_not_on_topic: "Which topic do you want to reply to?"
|
||||
saved_local_draft_tip: "saved locally"
|
||||
similar_topics: "Your topic is similar to…"
|
||||
|
@ -4645,6 +4646,64 @@ en:
|
|||
patternMismatch: "Please match the requested format."
|
||||
badInput: "Please enter a valid input."
|
||||
|
||||
table_builder:
|
||||
title: "Table Builder"
|
||||
modal:
|
||||
title: "Table Builder"
|
||||
create: "Build Table"
|
||||
help:
|
||||
title: "Using the Spreadsheet Editor"
|
||||
enter_key: "Enter"
|
||||
tab_key: "Tab"
|
||||
new_row: "at the end of a row to insert a new row."
|
||||
new_col: "at the end of a column to insert a new column."
|
||||
options: "Right-click on cells to access more options in a dropdown menu."
|
||||
confirm_close: "Are you sure you want to close the table bulder? Any unsaved changes will be lost."
|
||||
edit:
|
||||
btn_edit: "Edit Table"
|
||||
modal:
|
||||
title: "Edit Table"
|
||||
cancel: "cancel"
|
||||
create: "Save"
|
||||
reason: "why are you editing?"
|
||||
trigger_reason: "Add reason for edit"
|
||||
default_edit_reason: "Update Table with Table Editor"
|
||||
default_header:
|
||||
col_1: "Column 1"
|
||||
col_2: "Column 2"
|
||||
col_3: "Column 3"
|
||||
col_4: "Column 4"
|
||||
spreadsheet:
|
||||
no_records_found: "No records found"
|
||||
show: "Show"
|
||||
entries: "entries"
|
||||
about: "About"
|
||||
prompts:
|
||||
delete_selected_rows: "Are you sure you want to delete the selected rows?"
|
||||
delete_selected_cols: "Are you sure you want to delete the selected columns?"
|
||||
will_destroy_merged_cells: "This action will destroy any existing merged cells. Are you sure?"
|
||||
will_clear_search_results: "This action will destroy any existing merged cells. Are you sure?"
|
||||
conflicts_with_merged_cells: "There is a conflict with another merged cell"
|
||||
invalid_merge_props: "Invalid merged properties"
|
||||
cells_already_merged: "Cell already merged"
|
||||
no_cells_selected: "No cells selected"
|
||||
context_menu:
|
||||
row:
|
||||
before: "Insert a new row before"
|
||||
after: "Insert a new row after"
|
||||
delete: "Delete selected rows"
|
||||
col:
|
||||
before: "Insert a new column before"
|
||||
after: "Insert a new column after"
|
||||
delete: "Delete selected columns"
|
||||
rename: "Rename this column"
|
||||
order:
|
||||
ascending: "Order ascending"
|
||||
descending: "Order descending"
|
||||
copy: "Copy..."
|
||||
paste: "Paste..."
|
||||
save: "Save as..."
|
||||
|
||||
# This section is exported to the javascript for i18n in the admin section
|
||||
admin_js:
|
||||
# This is a text input placeholder, keep the translation short
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,97 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script used for updating depencies for the table builder/editor feature.
|
||||
# Updates the JSpreadsheet and jSuites libraries to the latest available versions.
|
||||
|
||||
# Get the directory of the script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
# Construct paths relative to the script directory
|
||||
SCSS_VENDOR="$SCRIPT_DIR/../app/assets/stylesheets/common/table-builder/vendor/"
|
||||
JSPREADSHEET_VENDOR="$SCRIPT_DIR/../public/javascripts/jspreadsheet/"
|
||||
JSUITES_VENDOR="$SCRIPT_DIR/../public/javascripts/jsuites/"
|
||||
|
||||
JSUITES_JS_URL="https://jsuites.net/v4/jsuites.js"
|
||||
JSPREADSHEET_JS_URL="https://bossanova.uk/jspreadsheet/v4/jexcel.js"
|
||||
|
||||
JSUITES_CSS_URL="https://raw.githubusercontent.com/jsuites/jsuites/master/dist/jsuites.css"
|
||||
JSPREADSHEET_CSS_URL="https://bossanova.uk/jspreadsheet/v4/jexcel.css"
|
||||
|
||||
JSUITES_CSS_FILE=jsuites.css
|
||||
JSUITES_SCSS_FILE=jsuites.scss
|
||||
JSUITES_SCSS_FILE_LOCATION=$SCSS_VENDOR$JSUITES_SCSS_FILE
|
||||
|
||||
JSUITES_JS_FILE=jsuites.js
|
||||
JSUITES_NEW_JS_FILE=jsuites.js
|
||||
JSUITES_JS_FILE_LOCATION=$JSUITES_VENDOR$JSUITES_NEW_JS_FILE
|
||||
|
||||
JSPREADSHEET_CSS_FILE=jexcel.css
|
||||
JSPREADSHEET_SCSS_FILE=jspreadsheet.scss
|
||||
JSPREADSHEET_SCSS_FILE_LOCATION=$SCSS_VENDOR$JSPREADSHEET_SCSS_FILE
|
||||
|
||||
JSPREADSHEET_JS_FILE=jexcel.js
|
||||
JSPREADSHEET_NEW_JS_FILE=jspreadsheet.js
|
||||
JSPREADSHEET_JS_FILE_LOCATION=$JSPREADSHEET_VENDOR$JSPREADSHEET_NEW_JS_FILE
|
||||
|
||||
|
||||
|
||||
|
||||
# Remove all vendor related files:
|
||||
rm -r ${SCSS_VENDOR}*
|
||||
rm -r ${JSUITES_VENDOR}
|
||||
rm -r ${JSPREADSHEET_VENDOR}
|
||||
|
||||
# Recreate vendor directory
|
||||
mkdir $JSUITES_VENDOR
|
||||
mkdir $JSPREADSHEET_VENDOR
|
||||
echo "Old vendor assets have been removed."
|
||||
|
||||
|
||||
# STYLESHEETS:
|
||||
# Add JSuite vendor file
|
||||
if test -f "$JSUITES_CSS_FILE"; then
|
||||
echo "$JSUITES_CSS_FILE already exists."
|
||||
else
|
||||
# Fetch jsuite stylesheet
|
||||
wget $JSUITES_CSS_URL
|
||||
echo "$JSUITES_CSS_FILE has been created in $(pwd)"
|
||||
# Move jsuite stylesheet to vendor as a scss file
|
||||
mv $JSUITES_CSS_FILE $JSUITES_SCSS_FILE_LOCATION
|
||||
echo "$JSUITES_SCSS_FILE has been placed in the scss vendor directory"
|
||||
# Scope styles to jexcel_container class
|
||||
sed -i '' '1s/^/.jexcel_container {\n/' $JSUITES_SCSS_FILE_LOCATION
|
||||
sed -i '' '$a\
|
||||
}' $JSUITES_SCSS_FILE_LOCATION
|
||||
|
||||
# Remove conflicting animation classes
|
||||
# TODO: Improve below code to handle nested code blocks
|
||||
fi
|
||||
|
||||
# Add JSpreadsheet vendor file
|
||||
if test -f "$JSPREADSHEET_CSS_FILE"; then
|
||||
echo "$JSPREADSHEET_CSS_FILE already exists."
|
||||
else
|
||||
# Fetch jspreadsheet stylesheet
|
||||
wget $JSPREADSHEET_CSS_URL
|
||||
echo "$JSPREADSHEET_CSS_FILE has been created in $(pwd)"
|
||||
# Move jspreadsheet stylesheet to vendor as a scss file
|
||||
mv $JSPREADSHEET_CSS_FILE $JSPREADSHEET_SCSS_FILE_LOCATION
|
||||
fi
|
||||
|
||||
# Apply prettier to vendor files
|
||||
yarn prettier --write $SCSS_VENDOR
|
||||
|
||||
# JAVASCRIPTS:
|
||||
if test -f "$JSUITES_JS_FILE"; then
|
||||
echo "$JSUITES_JS_FILE already exists."
|
||||
else
|
||||
wget $JSUITES_JS_URL
|
||||
mv $JSUITES_JS_FILE $JSUITES_JS_FILE_LOCATION
|
||||
fi
|
||||
|
||||
if test -f "$JSPREADSHEET_JS_FILE"; then
|
||||
echo "$JSPREADSHEET_JS_FILE already exists."
|
||||
else
|
||||
wget $JSPREADSHEET_JS_URL
|
||||
mv $JSPREADSHEET_JS_FILE $JSPREADSHEET_JS_FILE_LOCATION
|
||||
fi
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Modals
|
||||
class InsertTable < PageObjects::Modals::Base
|
||||
MODAL_SELECTOR = ".insert-table-modal"
|
||||
SPREADSHEET_TABLE_SELECTOR = "#{MODAL_SELECTOR} .jexcel"
|
||||
|
||||
def click_insert_table
|
||||
find("#{MODAL_SELECTOR} .btn-insert-table").click
|
||||
end
|
||||
|
||||
def cancel
|
||||
find("#{MODAL_SELECTOR} .d-modal-cancel").click
|
||||
end
|
||||
|
||||
def click_edit_reason
|
||||
find("#{MODAL_SELECTOR} .btn-edit-reason").click
|
||||
end
|
||||
|
||||
def type_edit_reason(text)
|
||||
find("#{MODAL_SELECTOR} .edit-reason input").send_keys(text)
|
||||
end
|
||||
|
||||
def find_cell(row, col)
|
||||
find("#{SPREADSHEET_TABLE_SELECTOR} tbody tr[data-y='#{row}'] td[data-x='#{col}']")
|
||||
end
|
||||
|
||||
def select_cell(row, col)
|
||||
find_cell(row, col).double_click
|
||||
end
|
||||
|
||||
def type_in_cell(row, col, text)
|
||||
select_cell(row, col)
|
||||
cell = find_cell(row, col).find("textarea")
|
||||
cell.send_keys(text, :return)
|
||||
end
|
||||
|
||||
def has_content_in_cell?(row, col, content)
|
||||
find_cell(row, col).text == content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,144 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "Table Builder", type: :system do
|
||||
fab!(:user)
|
||||
let(:composer) { PageObjects::Components::Composer.new }
|
||||
let(:insert_table_modal) { PageObjects::Modals::InsertTable.new }
|
||||
fab!(:topic) { Fabricate(:topic, user: user) }
|
||||
fab!(:post1) { create_post(user: user, topic: topic, raw: <<~RAW) }
|
||||
|Make | Model | Year|
|
||||
|-------| ------- | ----|
|
||||
|Toyota | Supra | 1998|
|
||||
|Nissan | Skyline | 1999|
|
||||
|Honda | S2000 | 2001|
|
||||
RAW
|
||||
|
||||
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||
|
||||
before { sign_in(user) }
|
||||
|
||||
def normalize_value(content)
|
||||
content.strip.gsub(/\s+/, " ").gsub(/\r\n/, "\n")
|
||||
end
|
||||
|
||||
context "when creating a new table" do
|
||||
it "should add table items created in spreadsheet to composer input" do
|
||||
visit("/latest")
|
||||
page.find("#create-topic").click
|
||||
page.find(".toolbar-popup-menu-options").click
|
||||
page.find(".select-kit-row[data-name='Insert Table']").click
|
||||
insert_table_modal.type_in_cell(0, 0, "Item 1")
|
||||
insert_table_modal.type_in_cell(0, 1, "Item 2")
|
||||
insert_table_modal.type_in_cell(0, 2, "Item 3")
|
||||
insert_table_modal.type_in_cell(0, 3, "Item 4")
|
||||
insert_table_modal.click_insert_table
|
||||
|
||||
created_table = <<~TABLE
|
||||
|Column 1 | Column 2 | Column 3 | Column 4|
|
||||
|--- | --- | --- | ---|
|
||||
|Item 1 | Item 2 | Item 3 | Item 4|
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | | | |
|
||||
TABLE
|
||||
|
||||
expect(normalize_value(composer.composer_input.value)).to eq(normalize_value(created_table))
|
||||
end
|
||||
|
||||
context "when cancelling table creation" do
|
||||
it "should close the modal if there are no changes made" do
|
||||
visit("/latest")
|
||||
page.find("#create-topic").click
|
||||
page.find(".toolbar-popup-menu-options").click
|
||||
page.find(".select-kit-row[data-name='Insert Table']").click
|
||||
insert_table_modal.cancel
|
||||
expect(page).to have_no_css(".insert-table-modal")
|
||||
end
|
||||
|
||||
it "should show a warning popup if there are unsaved changes" do
|
||||
visit("/latest")
|
||||
page.find("#create-topic").click
|
||||
page.find(".toolbar-popup-menu-options").click
|
||||
page.find(".select-kit-row[data-name='Insert Table']").click
|
||||
insert_table_modal.type_in_cell(0, 0, "Item 1")
|
||||
insert_table_modal.cancel
|
||||
expect(page).to have_css(".dialog-container .dialog-content")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when editing a table" do
|
||||
it "should prefill the spreadsheet with the markdown table items from the post" do
|
||||
topic_page.visit_topic(topic)
|
||||
topic_page.find(".btn-edit-table", visible: :all).click
|
||||
expect(page).to have_selector(".insert-table-modal")
|
||||
|
||||
expected_table_content = [
|
||||
%w[Toyota Supra 1998],
|
||||
%w[Nissan Skyline 1999],
|
||||
%w[Honda S2000 2001],
|
||||
]
|
||||
|
||||
expected_table_content.each_with_index do |row, row_index|
|
||||
row.each_with_index do |content, col_index|
|
||||
expect(insert_table_modal).to have_content_in_cell(row_index, col_index, content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should update the post with the new table content" do
|
||||
topic_page.visit_topic(topic)
|
||||
topic_page.find(".btn-edit-table", visible: :all).click
|
||||
expect(page).to have_selector(".insert-table-modal")
|
||||
insert_table_modal.type_in_cell(1, 1, " GTR")
|
||||
insert_table_modal.click_insert_table
|
||||
|
||||
updated_post = <<~RAW
|
||||
|Make | Model | Year|
|
||||
|-------| ------- | ----|
|
||||
|Toyota | Supra | 1998|
|
||||
|Nissan | Skyline | 1999|
|
||||
|Honda | S2000 | 2001|
|
||||
RAW
|
||||
|
||||
expect(normalize_value(post1.reload.raw)).to eq(normalize_value(updated_post))
|
||||
end
|
||||
|
||||
context "when adding an edit reason" do
|
||||
it "should add the edit reason to the edit history" do
|
||||
edit_reason = "Updated Nissan model"
|
||||
|
||||
topic_page.visit_topic(topic)
|
||||
topic_page.find(".btn-edit-table", visible: :all).click
|
||||
expect(page).to have_selector(".insert-table-modal")
|
||||
insert_table_modal.type_in_cell(1, 1, " GTR")
|
||||
insert_table_modal.click_edit_reason
|
||||
insert_table_modal.type_edit_reason(edit_reason)
|
||||
insert_table_modal.click_insert_table
|
||||
wait_for { post1.reload.edit_reason == edit_reason }
|
||||
expect(post1.reload.edit_reason).to eq(edit_reason)
|
||||
end
|
||||
end
|
||||
|
||||
context "when cancelling table creation" do
|
||||
it "should close the modal if there are no changes made" do
|
||||
topic_page.visit_topic(topic)
|
||||
topic_page.find(".btn-edit-table", visible: :all).click
|
||||
expect(page).to have_selector(".insert-table-modal")
|
||||
insert_table_modal.cancel
|
||||
expect(page).to have_no_css(".insert-table-modal")
|
||||
end
|
||||
|
||||
it "should show a warning popup if there are unsaved changes" do
|
||||
topic_page.visit_topic(topic)
|
||||
topic_page.find(".btn-edit-table", visible: :all).click
|
||||
expect(page).to have_selector(".insert-table-modal")
|
||||
insert_table_modal.type_in_cell(1, 1, " GTR")
|
||||
insert_table_modal.cancel
|
||||
expect(page).to have_css(".dialog-container .dialog-content")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue