(core) Comments

Summary:
First iteration for comments system for Grist.
- Comments are stored in a generic metatable `_grist_Cells`
- Each comment is connected to a particular cell (hence the generic name of the table)
- Access level works naturally for records stored in this table
-- User can add/read comments for cells he can see
-- User can't update/remove comments that he doesn't own, but he can delete them by removing cells (rows/columns)
-- Anonymous users can't see comments at all.
- Each comment can have replies (but replies can't have more replies)

Comments are hidden by default, they can be enabled by COMMENTS=true env variable.
Some things for follow-up
- Avatars, currently the user's profile image is not shown or retrieved from the server
- Virtual rendering for comments list in creator panel. Currently, there is a limit of 200 comments.

Test Plan: New and existing tests

Reviewers: georgegevoian, paulfitz

Reviewed By: georgegevoian

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D3509
This commit is contained in:
Jarosław Sadziński
2022-10-17 11:47:16 +02:00
parent 8be920dd25
commit bfd7243fe2
41 changed files with 2621 additions and 77 deletions

View File

@@ -25,6 +25,8 @@ const {setTestState} = require('app/client/lib/testState');
const {ExtraRows} = require('app/client/models/DataTableModelWithDiff');
const {createFilterMenu} = require('app/client/ui/ColumnFilterMenu');
const {closeRegisteredMenu} = require('app/client/ui2018/menus');
const {COMMENTS} = require('app/client/models/features');
/**
* BaseView forms the basis for ViewSection classes.
@@ -85,6 +87,8 @@ function BaseView(gristDoc, viewSectionModel, options) {
this._filteredRowSource.updateFilter(filterFunc);
}));
this.rowSource = this._filteredRowSource;
// Sorted collection of all rows to show in this view.
this.sortedRows = rowset.SortedRowSet.create(this, null, this.tableModel.tableData);
@@ -238,7 +242,8 @@ BaseView.commonCommands = {
showRawData: function() { this.showRawData().catch(reportError); },
filterByThisCellValue: function() { this.filterByThisCellValue(); },
duplicateRows: function() { this._duplicateRows().catch(reportError); }
duplicateRows: function() { this._duplicateRows().catch(reportError); },
openDiscussion: function() { this.openDiscussionAtCursor(); },
};
/**
@@ -288,6 +293,30 @@ BaseView.prototype.activateEditorAtCursor = function(options) {
builder.buildEditorDom(this.editRowModel, lazyRow, options || {});
};
/**
* Opens discussion panel at the cursor position. Returns true if discussion panel was opened.
*/
BaseView.prototype.openDiscussionAtCursor = function(id) {
if (!COMMENTS().get()) { return false; }
var builder = this.activeFieldBuilder();
if (builder.isEditorActive()) {
return false;
}
var rowId = this.viewData.getRowId(this.cursor.rowIndex());
// LazyArrayModel row model which is also used to build the cell dom. Needed since
// it may be used as a key to retrieve the cell dom, which is useful for editor placement.
var lazyRow = this.getRenderedRowModel(rowId);
if (!lazyRow) {
// TODO scroll into view. For now, just don't start discussion.
return false;
}
this.editRowModel.assign(rowId);
builder.buildDiscussionPopup(this.editRowModel, lazyRow, id);
return true;
};
/**
* Move the floating RowModel for editing to the current cursor position, and return it.
*