mirror of
https://github.com/discourse/discourse.git
synced 2025-03-26 15:58:11 +08:00
DEV: Port discourse-table-builder
theme component to core (#24441)
This commit is contained in:
parent
bcca1692c6
commit
d2b53ccac2
app/assets
javascripts/discourse
app
components/modal
instance-initializers
lib
services
tests
acceptance
fixtures
unit/lib
stylesheets
config/locales
public/javascripts
script
spec/system
@ -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 { schedule } from "@ember/runloop";
|
||||||
import { create } from "virtual-dom";
|
import { create } from "virtual-dom";
|
||||||
import FullscreenTableModal from "discourse/components/modal/fullscreen-table";
|
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 Columns from "discourse/lib/columns";
|
||||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||||
import { nativeLazyLoading } from "discourse/lib/lazy-load-images";
|
import { nativeLazyLoading } from "discourse/lib/lazy-load-images";
|
||||||
import lightbox from "discourse/lib/lightbox";
|
import lightbox from "discourse/lib/lightbox";
|
||||||
import { SELECTORS } from "discourse/lib/lightbox/constants";
|
import { SELECTORS } from "discourse/lib/lightbox/constants";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
import { parseAsync } from "discourse/lib/text";
|
||||||
import { setTextDirections } from "discourse/lib/text-direction";
|
import { setTextDirections } from "discourse/lib/text-direction";
|
||||||
|
import { tokenRange } from "discourse/lib/utilities";
|
||||||
import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
|
import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
@ -106,21 +111,33 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function _createButton() {
|
function _createButton(props) {
|
||||||
const openPopupBtn = document.createElement("button");
|
const openPopupBtn = document.createElement("button");
|
||||||
openPopupBtn.classList.add(
|
const defaultClasses = [
|
||||||
"open-popup-link",
|
"open-popup-link",
|
||||||
"btn-default",
|
"btn-default",
|
||||||
"btn",
|
"btn",
|
||||||
"btn-icon",
|
"btn-icon",
|
||||||
"btn-expand-table",
|
"no-text",
|
||||||
"no-text"
|
];
|
||||||
);
|
|
||||||
const expandIcon = create(
|
openPopupBtn.classList.add(...defaultClasses);
|
||||||
iconNode("discourse-expand", { class: "expand-table-icon" })
|
|
||||||
);
|
if (props.classes) {
|
||||||
openPopupBtn.title = I18n.t("fullscreen_table.expand_btn");
|
openPopupBtn.classList.add(...props.classes);
|
||||||
openPopupBtn.append(expandIcon);
|
}
|
||||||
|
|
||||||
|
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;
|
return openPopupBtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,14 +145,64 @@ export default {
|
|||||||
return scrollWidth > clientWidth;
|
return scrollWidth > clientWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateModal(event) {
|
function generateFullScreenTableModal(event) {
|
||||||
const table = event.currentTarget.parentElement.nextElementSibling;
|
const table = event.currentTarget.parentElement.nextElementSibling;
|
||||||
const tempTable = table.cloneNode(true);
|
const tempTable = table.cloneNode(true);
|
||||||
modal.show(FullscreenTableModal, { model: { tableHtml: tempTable } });
|
modal.show(FullscreenTableModal, { model: { tableHtml: tempTable } });
|
||||||
}
|
}
|
||||||
|
|
||||||
function generatePopups(tables) {
|
function generateSpreadsheetModal() {
|
||||||
tables.forEach((table) => {
|
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)) {
|
if (!isOverflown(table.parentNode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -144,28 +211,50 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const popupBtn = _createButton();
|
const expandTableBtn = _createButton({
|
||||||
table.parentNode.classList.add("fullscreen-table-wrapper");
|
classes: ["btn-expand-table"],
|
||||||
// Create a button wrapper for case of multiple buttons (i.e. table builder extension)
|
title: "fullscreen_table.expand_btn",
|
||||||
const buttonWrapper = document.createElement("div");
|
icon: { name: "discourse-expand", class: "expand-table-icon" },
|
||||||
buttonWrapper.classList.add("fullscreen-table-wrapper--buttons");
|
});
|
||||||
buttonWrapper.append(popupBtn);
|
buttonWrapper.append(expandTableBtn);
|
||||||
popupBtn.addEventListener("click", generateModal, false);
|
expandTableBtn.addEventListener(
|
||||||
|
"click",
|
||||||
|
generateFullScreenTableModal,
|
||||||
|
false
|
||||||
|
);
|
||||||
table.parentNode.insertBefore(buttonWrapper, table);
|
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(
|
api.decorateCookedElement(
|
||||||
(post) => {
|
(post, helper) => {
|
||||||
schedule("afterRender", () => {
|
schedule("afterRender", () => {
|
||||||
const tables = post.querySelectorAll("table");
|
const tables = post.querySelectorAll(".md-table table");
|
||||||
generatePopups(tables);
|
generatePopups(tables, helper.widget.attrs);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onlyStream: true,
|
onlyStream: true,
|
||||||
|
id: "table-wrapper",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
api.cleanupStream(cleanupPopupBtns);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -613,3 +613,79 @@ export function getCaretPosition(element, options) {
|
|||||||
|
|
||||||
return adjustedPosition;
|
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 { Promise } from "rsvp";
|
||||||
import DiscardDraftModal from "discourse/components/modal/discard-draft";
|
import DiscardDraftModal from "discourse/components/modal/discard-draft";
|
||||||
import PostEnqueuedModal from "discourse/components/modal/post-enqueued";
|
import PostEnqueuedModal from "discourse/components/modal/post-enqueued";
|
||||||
|
import SpreadsheetEditor from "discourse/components/modal/spreadsheet-editor";
|
||||||
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
||||||
import {
|
import {
|
||||||
cannotPostAgain,
|
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(
|
return options.concat(
|
||||||
customPopupMenuOptions
|
customPopupMenuOptions
|
||||||
.map((option) => this._setupPopupMenuOption(option))
|
.map((option) => this._setupPopupMenuOption(option))
|
||||||
@ -723,6 +732,16 @@ export default class ComposerService extends Service {
|
|||||||
this.toggleProperty("model.whisper");
|
this.toggleProperty("model.whisper");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleSpreadsheet() {
|
||||||
|
this.modal.show(SpreadsheetEditor, {
|
||||||
|
model: {
|
||||||
|
toolbarEvent: this.toolbarEvent,
|
||||||
|
tableTokens: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
toggleInvisible() {
|
toggleInvisible() {
|
||||||
this.toggleProperty("model.unlistTopic");
|
this.toggleProperty("model.unlistTopic");
|
||||||
|
@ -17,13 +17,13 @@ acceptance("Post Table Wrapper Test", function () {
|
|||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(
|
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"
|
"buttons for the table wrapper appear inside a separate div"
|
||||||
);
|
);
|
||||||
|
|
||||||
const fullscreenButtonWrapper = query(
|
const fullscreenButtonWrapper = query(
|
||||||
`${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper--buttons`
|
`${postWithLargeTable} .fullscreen-table-wrapper .fullscreen-table-wrapper__buttons`
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(
|
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");
|
||||||
|
});
|
||||||
|
});
|
3
app/assets/javascripts/discourse/tests/fixtures/md-table.js
vendored
Normal file
3
app/assets/javascripts/discourse/tests/fixtures/md-table.js
vendored
Normal file
@ -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 { module, test } from "qunit";
|
||||||
import sinon from "sinon";
|
import sinon from "sinon";
|
||||||
import {
|
import {
|
||||||
|
arrayToTable,
|
||||||
caretRowCol,
|
caretRowCol,
|
||||||
clipboardCopyAsync,
|
clipboardCopyAsync,
|
||||||
defaultHomepage,
|
defaultHomepage,
|
||||||
@ -13,6 +14,7 @@ import {
|
|||||||
escapeExpression,
|
escapeExpression,
|
||||||
extractDomainFromUrl,
|
extractDomainFromUrl,
|
||||||
fillMissingDates,
|
fillMissingDates,
|
||||||
|
findTableRegex,
|
||||||
inCodeBlock,
|
inCodeBlock,
|
||||||
initializeDefaultHomepage,
|
initializeDefaultHomepage,
|
||||||
mergeSortedLists,
|
mergeSortedLists,
|
||||||
@ -22,6 +24,11 @@ import {
|
|||||||
slugify,
|
slugify,
|
||||||
toAsciiPrintable,
|
toAsciiPrintable,
|
||||||
} from "discourse/lib/utilities";
|
} from "discourse/lib/utilities";
|
||||||
|
import {
|
||||||
|
mdTable,
|
||||||
|
mdTableNonUniqueHeadings,
|
||||||
|
mdTableSpecialChars,
|
||||||
|
} from "discourse/tests/fixtures/md-table";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { chromeTest } from "discourse/tests/helpers/qunit-helpers";
|
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]
|
||||||
|

|
||||||
|
[/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/loading-slider";
|
||||||
@import "common/float-kit/_index";
|
@import "common/float-kit/_index";
|
||||||
@import "common/login/_index";
|
@import "common/login/_index";
|
||||||
|
@import "common/table-builder/_index";
|
||||||
|
@ -1648,10 +1648,13 @@ iframe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.open-popup-link {
|
.open-popup-link {
|
||||||
|
display: inline;
|
||||||
|
margin-inline: 0.25em;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 1rem;
|
left: 1rem;
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
transition: 0.25s ease-in-out opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-table-wrapper {
|
.fullscreen-table-wrapper {
|
||||||
@ -1659,7 +1662,7 @@ iframe {
|
|||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&--buttons {
|
&__buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
|
5
app/assets/stylesheets/common/table-builder/_index.scss
Normal file
5
app/assets/stylesheets/common/table-builder/_index.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
699
app/assets/stylesheets/common/table-builder/vendor/jspreadsheet.scss
vendored
Normal file
699
app/assets/stylesheets/common/table-builder/vendor/jspreadsheet.scss
vendored
Normal file
@ -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;
|
||||||
|
}
|
3038
app/assets/stylesheets/common/table-builder/vendor/jsuites.scss
vendored
Normal file
3038
app/assets/stylesheets/common/table-builder/vendor/jsuites.scss
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -2382,6 +2382,7 @@ en:
|
|||||||
add_warning: "This is an official warning."
|
add_warning: "This is an official warning."
|
||||||
toggle_whisper: "Toggle Whisper"
|
toggle_whisper: "Toggle Whisper"
|
||||||
toggle_unlisted: "Toggle Unlisted"
|
toggle_unlisted: "Toggle Unlisted"
|
||||||
|
insert_table: "Insert Table"
|
||||||
posting_not_on_topic: "Which topic do you want to reply to?"
|
posting_not_on_topic: "Which topic do you want to reply to?"
|
||||||
saved_local_draft_tip: "saved locally"
|
saved_local_draft_tip: "saved locally"
|
||||||
similar_topics: "Your topic is similar to…"
|
similar_topics: "Your topic is similar to…"
|
||||||
@ -4645,6 +4646,64 @@ en:
|
|||||||
patternMismatch: "Please match the requested format."
|
patternMismatch: "Please match the requested format."
|
||||||
badInput: "Please enter a valid input."
|
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
|
# This section is exported to the javascript for i18n in the admin section
|
||||||
admin_js:
|
admin_js:
|
||||||
# This is a text input placeholder, keep the translation short
|
# This is a text input placeholder, keep the translation short
|
||||||
|
16905
public/javascripts/jspreadsheet/jspreadsheet.js
Normal file
16905
public/javascripts/jspreadsheet/jspreadsheet.js
Normal file
File diff suppressed because it is too large
Load Diff
12414
public/javascripts/jsuites/jsuites.js
Normal file
12414
public/javascripts/jsuites/jsuites.js
Normal file
File diff suppressed because it is too large
Load Diff
97
script/table_builder_libraries.bash
Executable file
97
script/table_builder_libraries.bash
Executable file
@ -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
|
44
spec/system/page_objects/modals/insert_table.rb
Normal file
44
spec/system/page_objects/modals/insert_table.rb
Normal file
@ -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
|
144
spec/system/table_builder_spec.rb
Normal file
144
spec/system/table_builder_spec.rb
Normal file
@ -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…
x
Reference in New Issue
Block a user