@saltcorn/history-control 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +1 -1
  2. package/package.json +1 -1
  3. package/rowdiffview.js +216 -0
package/index.js CHANGED
@@ -96,5 +96,5 @@ module.exports = {
96
96
  sc_plugin_api_version: 1,
97
97
  actions: features?.table_undo ? actions : undefined,
98
98
  table_providers: require("./table-provider.js"),
99
- viewtemplates: [require("./diffview")],
99
+ viewtemplates: [require("./diffview"), require("./rowdiffview")],
100
100
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/history-control",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Allow users to interact with row history",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/rowdiffview.js ADDED
@@ -0,0 +1,216 @@
1
+ const Field = require("@saltcorn/data/models/field");
2
+ const Table = require("@saltcorn/data/models/table");
3
+ const Form = require("@saltcorn/data/models/form");
4
+ const User = require("@saltcorn/data/models/user");
5
+ const View = require("@saltcorn/data/models/view");
6
+ const { hashState } = require("@saltcorn/data/utils");
7
+ const Workflow = require("@saltcorn/data/models/workflow");
8
+ const HtmlDiff = require("htmldiff-js");
9
+ const {
10
+ text,
11
+ div,
12
+ h3,
13
+ style,
14
+ a,
15
+ script,
16
+ pre,
17
+ domReady,
18
+ p,
19
+ i,
20
+ select,
21
+ option,
22
+ h2,
23
+ button,
24
+ } = require("@saltcorn/markup/tags");
25
+ const { radio_group, checkbox_group } = require("@saltcorn/markup/helpers");
26
+ const moment = require("moment");
27
+
28
+ const get_state_fields = () => [
29
+ {
30
+ name: "id",
31
+ type: "Integer",
32
+ required: true,
33
+ primary_key: true,
34
+ },
35
+ ];
36
+
37
+ const configuration_workflow = (req) =>
38
+ new Workflow({
39
+ steps: [
40
+ {
41
+ name: "Difference View",
42
+ form: async (context) => {
43
+ const table = await Table.findOne({ id: context.table_id });
44
+ const show_views = await View.find_table_views_where(
45
+ context.table_id,
46
+ ({ state_fields, viewtemplate, viewrow }) =>
47
+ viewtemplate.runMany &&
48
+ viewrow.name !== context.viewname &&
49
+ state_fields.some((sf) => sf.name === "id")
50
+ );
51
+ const show_view_opts = show_views.map((v) => v.select_option);
52
+ let blurb;
53
+ if (table.provider_name)
54
+ blurb = [
55
+ div(
56
+ { class: "alert alert-danger fst-normal" },
57
+ `Use this view on a standard database table, not a provided table`
58
+ ),
59
+ ];
60
+ else if (!table.versioned)
61
+ blurb = [
62
+ div(
63
+ { class: "alert alert-danger fst-normal" },
64
+ `Version history is not enabled for this table`
65
+ ),
66
+ ];
67
+ return new Form({
68
+ blurb,
69
+ fields: [
70
+ {
71
+ name: "show_view",
72
+ label: req.__("Single item view"),
73
+ type: "String",
74
+ sublabel:
75
+ req.__("The underlying individual view of each table row") +
76
+ ". " +
77
+ a(
78
+ {
79
+ "data-dyn-href": `\`/viewedit/config/\${show_view}\``,
80
+ target: "_blank",
81
+ },
82
+ req.__("Configure")
83
+ ),
84
+ required: true,
85
+ attributes: {
86
+ options: show_view_opts,
87
+ },
88
+ },
89
+ {
90
+ name: "comparison",
91
+ label: "Side-by-side comparison",
92
+ type: "Bool",
93
+ },
94
+ {
95
+ name: "min_interval_secs",
96
+ label: "Minimum interval (s)",
97
+ type: "Integer",
98
+ },
99
+ {
100
+ name: "date_format",
101
+ label: "Date format",
102
+ type: "String",
103
+ sublabel: "moment.js format specifier",
104
+ },
105
+ ],
106
+ });
107
+ },
108
+ },
109
+ ],
110
+ });
111
+
112
+ const run = async (
113
+ table_id,
114
+ viewname,
115
+ { show_view, min_interval_secs, date_format, comparison },
116
+ state,
117
+ extraArgs
118
+ ) => {
119
+ const table = await Table.findOne({ id: table_id });
120
+ const stateHash = hashState(state, show_view);
121
+
122
+ const id = state[table.pk_name];
123
+
124
+ if (!id) return `Need id`;
125
+
126
+ let hist = await table.get_history(id);
127
+
128
+ let last = 0;
129
+ let last_changed_by = undefined;
130
+ hist = hist.filter((row) => {
131
+ const myEpoch = Math.round(new Date(row._time).getTime() / 1000);
132
+ if (
133
+ myEpoch - last > min_interval_secs ||
134
+ (row._userid && row._userid !== last_changed_by)
135
+ ) {
136
+ //include
137
+ last = myEpoch;
138
+ last_changed_by = row._userid;
139
+ return true;
140
+ } else return false;
141
+ });
142
+
143
+ if (!hist || !hist.length) return "No versions recorded";
144
+ hist = hist.reverse();
145
+
146
+ const userIds = new Set(hist.map((h) => h._userid));
147
+ const users = await User.find({ id: { in: [...userIds] } });
148
+ const emails = {};
149
+ users.forEach((u) => (emails[u.id] = u.email));
150
+ const view = View.findOne({ name: show_view });
151
+ const rendered = await view.viewtemplateObj.renderRows(
152
+ table,
153
+ view.name,
154
+ view.configuration,
155
+ extraArgs,
156
+ hist,
157
+ state
158
+ );
159
+ return div(
160
+ {
161
+ class: ["accordion"],
162
+ id: `top${stateHash}`,
163
+ },
164
+ rendered.map((html, ix) => {
165
+ const row = hist[ix];
166
+ return div(
167
+ { class: "accordion-item" },
168
+ h2(
169
+ { class: "accordion-header", id: `a${stateHash}head${ix}` },
170
+ button(
171
+ {
172
+ class: ["accordion-button", "collapsed"],
173
+ type: "button",
174
+ "data-bs-toggle": "collapse",
175
+ "data-bs-target": `#a${stateHash}tab${ix}`,
176
+ "aria-expanded": "false",
177
+ "aria-controls": `a${stateHash}tab${ix}`,
178
+ },
179
+ date_format
180
+ ? moment(row._time).format(date_format)
181
+ : row._time.toString(),
182
+ " - ",
183
+ emails[row._userid]
184
+ )
185
+ ),
186
+ div(
187
+ {
188
+ class: ["accordion-collapse", "collapse"],
189
+ id: `a${stateHash}tab${ix}`,
190
+ "aria-labelledby": `a${stateHash}head${ix}`,
191
+ "data-bs-parent": `#top${stateHash}`,
192
+ },
193
+ div(
194
+ { class: ["accordion-body"] },
195
+ comparison && ix < rendered.length - 1
196
+ ? div(
197
+ { class: "d-flex align-middle" },
198
+ div({ class: "border p-1 m-1" }, rendered[ix + 1]),
199
+ i({ class: "m-2 fas fa-arrow-right" }),
200
+ div({ class: "border p-1 m-1" }, html)
201
+ )
202
+ : html
203
+ )
204
+ )
205
+ );
206
+ })
207
+ );
208
+ };
209
+
210
+ module.exports = {
211
+ name: "History Row Difference",
212
+ display_state_form: false,
213
+ get_state_fields,
214
+ configuration_workflow,
215
+ run,
216
+ };