apostrophe 2.225.0 → 2.226.0
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.
- package/CHANGELOG.md +10 -0
- package/lib/modules/apostrophe-schemas/index.js +49 -24
- package/lib/modules/apostrophe-schemas/public/js/user.js +59 -41
- package/lib/modules/apostrophe-schemas/views/macros.html +1 -0
- package/lib/modules/apostrophe-ui/public/css/components/fields.less +15 -0
- package/lib/modules/apostrophe-ui/views/components/fields.html +1 -0
- package/package.json +2 -2
- package/test/schemas.js +106 -0
- package/test/package.json +0 -73
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.226.0 (2023-03-06)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Add `readOnlyFields` option. Exactly like `showFields`, a boolean/select/checkboxes field in the document can control whether other fields are read-only or not (as opposed to visible or not).
|
|
8
|
+
|
|
9
|
+
### Security
|
|
10
|
+
|
|
11
|
+
* Upgrades passport to the latest version in order to ensure session regeneration when logging in or out. This adds additional security to logins by mitigating any risks due to XSS attacks. Apostrophe is already robust against XSS attacks. For passport methods that are internally used by Apostrophe everything is still working. For projects that are accessing the passport instance directly through `self.apos.login.passport`, some verifications may be necessary to avoid any compatibility issue. The internally used methods are `authenticate`, `use`, `serializeUser`, `deserializeUser`, `initialize`, `session`.
|
|
12
|
+
|
|
3
13
|
## 2.225.0 (2023-02-17)
|
|
4
14
|
|
|
5
15
|
### Adds
|
|
@@ -485,7 +485,7 @@ module.exports = {
|
|
|
485
485
|
}
|
|
486
486
|
var errors = {};
|
|
487
487
|
return async.eachSeries(schema, function(field, callback) {
|
|
488
|
-
if (field.readOnly) {
|
|
488
|
+
if (field.readOnly || self.isReadOnly(schema, object, field.name)) {
|
|
489
489
|
return setImmediate(callback);
|
|
490
490
|
}
|
|
491
491
|
// Fields that are contextual are edited in the context of a
|
|
@@ -545,41 +545,66 @@ module.exports = {
|
|
|
545
545
|
// based on showFields options of all fields
|
|
546
546
|
|
|
547
547
|
self.isVisible = function(schema, object, name) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
548
|
+
const transform = value => !value;
|
|
549
|
+
const buildWarningMessage = name => `⚠️ showFields misconfigured, attempts to show/hide ${name} which does not exist`;
|
|
550
|
+
|
|
551
|
+
return self.doesMatchConditionalChoices(schema, object, name, 'showFields', transform, buildWarningMessage);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// Determine whether the given field is read-only
|
|
555
|
+
// based on readOnlyFields options of all fields
|
|
556
|
+
|
|
557
|
+
self.isReadOnly = function(schema, object, name) {
|
|
558
|
+
const transform = value => value;
|
|
559
|
+
const buildWarningMessage = name => `⚠️ readOnlyFields misconfigured, attempts to set ${name} which does not exist as read only`;
|
|
560
|
+
|
|
561
|
+
return self.doesMatchConditionalChoices(schema, object, name, 'readOnlyFields', transform, buildWarningMessage);
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
self.doesMatchConditionalChoices = function(schema, object, name, actionName, transform, buildWarningMessage) {
|
|
565
|
+
var memo = {};
|
|
566
|
+
|
|
567
|
+
_.each(schema, field => {
|
|
568
|
+
if (!_.find(field.choices || [], choice => choice[actionName])) {
|
|
553
569
|
return;
|
|
554
570
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
571
|
+
|
|
572
|
+
_.each(field.choices, choice => {
|
|
573
|
+
if (!choice[actionName]) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (field.type === 'checkboxes') {
|
|
578
|
+
if (object[field.name] && transform(object[field.name].includes(choice.value))) {
|
|
579
|
+
_.each(choice[actionName], action);
|
|
563
580
|
}
|
|
581
|
+
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (transform(object[field.name] === choice.value)) {
|
|
586
|
+
_.each(choice[actionName], action);
|
|
564
587
|
}
|
|
565
588
|
});
|
|
566
589
|
});
|
|
567
|
-
return !hidden[name];
|
|
568
590
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
591
|
+
return transform(memo[name]);
|
|
592
|
+
|
|
593
|
+
function action(name) {
|
|
594
|
+
memo[name] = true;
|
|
595
|
+
|
|
596
|
+
var field = _.find(schema, { name });
|
|
573
597
|
if (!field) {
|
|
574
598
|
// Do not crash. The linter at startup also catches this,
|
|
575
599
|
// but is a nonfatal warning for bc, so we should also catch it
|
|
576
|
-
self.apos.utils.warnDev(
|
|
600
|
+
self.apos.utils.warnDev(buildWarningMessage(name));
|
|
601
|
+
|
|
577
602
|
return;
|
|
578
603
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
604
|
+
|
|
605
|
+
_.each(field.choices || [], choice => {
|
|
606
|
+
// Cope with nested showFields/readOnlyFields
|
|
607
|
+
_.each(choice[actionName] || [], action);
|
|
583
608
|
});
|
|
584
609
|
}
|
|
585
610
|
};
|
|
@@ -670,94 +670,109 @@ apos.define('apostrophe-schemas', {
|
|
|
670
670
|
};
|
|
671
671
|
|
|
672
672
|
self.enableShowFields = function(data, name, $field, $el, field) {
|
|
673
|
+
self.enableConditionalFields(data, name, $field, $el, field, 'showFields', function ($fieldset, match) {
|
|
674
|
+
$fieldset.toggleClass('apos-hidden', !match);
|
|
675
|
+
});
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
self.enableReadOnlyFields = function(data, name, $field, $el, field) {
|
|
679
|
+
self.enableConditionalFields(data, name, $field, $el, field, 'readOnlyFields', function ($fieldset, match) {
|
|
680
|
+
// Disable inputs, and color picker via the spectrum API
|
|
681
|
+
$fieldset
|
|
682
|
+
.find('input')
|
|
683
|
+
.attr('disabled', match)
|
|
684
|
+
.filter('[data-apos-color]')
|
|
685
|
+
.spectrum(match ? 'disable' : 'enable');
|
|
686
|
+
|
|
687
|
+
// Disable select element
|
|
688
|
+
$fieldset
|
|
689
|
+
.find('select')
|
|
690
|
+
.attr('disabled', match);
|
|
691
|
+
|
|
692
|
+
// Add a visual overlay to show that the field is read-only
|
|
693
|
+
$fieldset
|
|
694
|
+
.find('.apos-field-readonly-overlay')
|
|
695
|
+
.toggleClass('apos-field-readonly-overlay--active', match);
|
|
696
|
+
});
|
|
697
|
+
};
|
|
673
698
|
|
|
699
|
+
self.enableConditionalFields = function(data, name, $field, $el, field, actionName, callback) {
|
|
700
|
+
var eventName = 'apos' + apos.utils.capitalizeFirst(actionName);
|
|
674
701
|
var $fieldset = self.findFieldset($el, name);
|
|
675
702
|
|
|
676
|
-
// afterChange shows and hides other fieldsets based on
|
|
677
|
-
// the current value of this field
|
|
703
|
+
// afterChange shows and hides or disables other fieldsets based on
|
|
704
|
+
// the current value of this field..
|
|
678
705
|
// We do this in three situations: at startup, when the
|
|
679
|
-
// user changes the value, and when the visibility of this
|
|
706
|
+
// user changes the value, and when the visibility/disability of this
|
|
680
707
|
// field has been affected by another field with the
|
|
681
|
-
// showFields option.
|
|
682
|
-
// work properly. -Tom
|
|
708
|
+
// showFields/readOnlyFields option.
|
|
709
|
+
// This allows nested showFields/readOnlyFields to work properly. -Tom
|
|
683
710
|
|
|
684
711
|
afterChange();
|
|
685
712
|
|
|
686
713
|
$field.on('change', afterChange);
|
|
687
|
-
$fieldset.on(
|
|
688
|
-
function afterChange() {
|
|
689
|
-
// Implement showFields
|
|
714
|
+
$fieldset.on(eventName, afterChange);
|
|
690
715
|
|
|
716
|
+
function afterChange() {
|
|
691
717
|
if (!_.find(field.choices || [], function(choice) {
|
|
692
|
-
return choice
|
|
718
|
+
return choice[actionName];
|
|
693
719
|
})) {
|
|
694
|
-
// showFields is not in use for this select
|
|
695
720
|
return;
|
|
696
721
|
}
|
|
697
722
|
|
|
698
|
-
var val
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
} else {
|
|
702
|
-
val = $field.val();
|
|
703
|
-
}
|
|
723
|
+
var val = field.checkbox
|
|
724
|
+
? $field.is(':checked')
|
|
725
|
+
: $field.val();
|
|
704
726
|
|
|
705
|
-
// Recall if another choice currently active already chose to show each field.
|
|
706
|
-
// That way, if two choices show the same field, the fact that the second one
|
|
707
|
-
// is not currently selected does not hide the field the first one just showed
|
|
708
|
-
var
|
|
727
|
+
// Recall if another choice currently active already chose to show/disable each field.
|
|
728
|
+
// That way, if two choices show/disable the same field, the fact that the second one
|
|
729
|
+
// is not currently selected does not hide/enable the field the first one just showed/disabled
|
|
730
|
+
var memo = {};
|
|
709
731
|
|
|
710
732
|
_.each(field.choices || [], function(choice) {
|
|
733
|
+
var match;
|
|
711
734
|
|
|
712
|
-
// Show the fields for this value if it is the current value
|
|
735
|
+
// Show/disable the fields for this value if it is the current value
|
|
713
736
|
// *and* the select field itself is currently visible
|
|
714
|
-
|
|
715
|
-
var show;
|
|
716
|
-
|
|
717
737
|
if ($fieldset.hasClass('apos-hidden')) {
|
|
718
|
-
|
|
738
|
+
match = false;
|
|
719
739
|
} else if (field.type === 'boolean') {
|
|
720
740
|
// Comparing boolean values is hard because
|
|
721
741
|
// the string '0' must be considered falsy in
|
|
722
742
|
// order to permit use of select elements. -Tom
|
|
723
743
|
if (val === choice.value) {
|
|
724
|
-
|
|
744
|
+
match = true;
|
|
725
745
|
} else if (!choice.value) {
|
|
726
746
|
if ((!val) || (val === '0')) {
|
|
727
|
-
|
|
747
|
+
match = true;
|
|
728
748
|
}
|
|
729
749
|
} else {
|
|
730
750
|
if (val && (val !== '0')) {
|
|
731
|
-
|
|
751
|
+
match = true;
|
|
732
752
|
}
|
|
733
753
|
}
|
|
734
754
|
} else if (field.type === 'checkboxes') {
|
|
735
755
|
_.each($field || [], function(checkbox) {
|
|
736
|
-
if (checkbox.checked && checkbox.value === choice.value && choice
|
|
737
|
-
|
|
756
|
+
if (checkbox.checked && checkbox.value === choice.value && choice[actionName]) {
|
|
757
|
+
match = true;
|
|
738
758
|
}
|
|
739
759
|
});
|
|
740
760
|
} else {
|
|
741
761
|
// type select
|
|
742
762
|
if (val === choice.value.toString()) {
|
|
743
|
-
|
|
763
|
+
match = true;
|
|
744
764
|
}
|
|
745
765
|
}
|
|
746
766
|
|
|
747
|
-
_.each(choice
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
} else {
|
|
751
|
-
shown[fieldName] = false;
|
|
752
|
-
}
|
|
767
|
+
_.each(choice[actionName] || [], function(fieldName) {
|
|
768
|
+
memo[fieldName] = match || !!memo[fieldName];
|
|
769
|
+
|
|
753
770
|
var $fieldset = self.findFieldset($el, fieldName);
|
|
754
771
|
|
|
755
|
-
$fieldset
|
|
756
|
-
$fieldset.trigger(
|
|
772
|
+
callback($fieldset, memo[fieldName]);
|
|
773
|
+
$fieldset.trigger(eventName);
|
|
757
774
|
});
|
|
758
|
-
|
|
759
775
|
});
|
|
760
|
-
|
|
761
776
|
}
|
|
762
777
|
};
|
|
763
778
|
|
|
@@ -1277,6 +1292,7 @@ apos.define('apostrophe-schemas', {
|
|
|
1277
1292
|
$field.val('0');
|
|
1278
1293
|
}
|
|
1279
1294
|
self.enableShowFields(data, name, $field, $el, field);
|
|
1295
|
+
self.enableReadOnlyFields(data, name, $field, $el, field);
|
|
1280
1296
|
return setImmediate(callback);
|
|
1281
1297
|
},
|
|
1282
1298
|
convert: function(data, name, $field, $el, field, callback) {
|
|
@@ -1340,6 +1356,7 @@ apos.define('apostrophe-schemas', {
|
|
|
1340
1356
|
self.findSafe($fieldset, 'input[name="' + name + '"][value="' + data[name][c] + '"]', '.apos-field').prop('checked', true);
|
|
1341
1357
|
}
|
|
1342
1358
|
self.enableShowFields(data, name, $field, $el, field);
|
|
1359
|
+
self.enableReadOnlyFields(data, name, $field, $el, field);
|
|
1343
1360
|
return setImmediate(callback);
|
|
1344
1361
|
}
|
|
1345
1362
|
},
|
|
@@ -1448,6 +1465,7 @@ apos.define('apostrophe-schemas', {
|
|
|
1448
1465
|
}
|
|
1449
1466
|
$field.val(value);
|
|
1450
1467
|
self.enableShowFields(data, name, $field, $el, field);
|
|
1468
|
+
self.enableReadOnlyFields(data, name, $field, $el, field);
|
|
1451
1469
|
return setImmediate(callback);
|
|
1452
1470
|
}
|
|
1453
1471
|
},
|
|
@@ -137,6 +137,7 @@
|
|
|
137
137
|
{%- endmacro -%}
|
|
138
138
|
|
|
139
139
|
{%- macro checkboxesBody(field) -%}
|
|
140
|
+
<div class="apos-field-readonly-overlay"></div>
|
|
140
141
|
{%- for choice in field.choices -%}
|
|
141
142
|
<div class="apos-form-checkbox">
|
|
142
143
|
<label class="apos-form-checkbox-label apos-text-small">
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
.apos-field
|
|
8
8
|
{
|
|
9
|
+
position: relative;
|
|
9
10
|
margin-bottom: @apos-margin-4;
|
|
10
11
|
width: 100%;
|
|
11
12
|
max-width: 540px;
|
|
@@ -300,6 +301,20 @@
|
|
|
300
301
|
display: inline-block;
|
|
301
302
|
}
|
|
302
303
|
|
|
304
|
+
// Field readonly overlay ===================================
|
|
305
|
+
.apos-field-readonly-overlay {
|
|
306
|
+
display: none;
|
|
307
|
+
position: absolute;
|
|
308
|
+
z-index: @apos-z-index-2;
|
|
309
|
+
width: 100%;
|
|
310
|
+
height: 100%;
|
|
311
|
+
background-color: rgba(255, 255, 255, 0.75);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.apos-field-readonly-overlay--active {
|
|
315
|
+
display: block;
|
|
316
|
+
}
|
|
317
|
+
|
|
303
318
|
// Browse and autocomplete combo
|
|
304
319
|
.apos-browse-and-autocomplete {
|
|
305
320
|
display: flex;
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
{% macro color(name, placeholder, value, readOnly, options) -%}
|
|
32
32
|
<label>
|
|
33
|
+
<div class="apos-field-readonly-overlay"></div>
|
|
33
34
|
<div class="apos-field-input-color-preview" data-apos-color-preview></div>
|
|
34
35
|
<input id="{{ options.id }}" name="{{ name }}" data-apos-color-empty-label="{{ __ns('apostrophe', "None selected") }}" class="apos-field-input apos-field-input-color{% if options.fieldClasses %} {{ options.fieldClasses }}{% endif %}" data-apos-color type="text" value="{{__ns('apostrophe', value | d(''))}}"{% if readOnly %} disabled{% endif %}{% if options.fieldAttributes %} {{ options.fieldAttributes }}{% endif %}>
|
|
35
36
|
<span class="apos-field-input-colorpicker-value" data-apos-color-value></span>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.226.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"moog-require": "^1.1.0",
|
|
61
61
|
"nodemailer": "^6.6.2",
|
|
62
62
|
"oembetter": "^1.0.1",
|
|
63
|
-
"passport": "^0.
|
|
63
|
+
"passport": "^0.6.0",
|
|
64
64
|
"passport-local": "^1.0.0",
|
|
65
65
|
"passport-totp": "0.0.2",
|
|
66
66
|
"path-to-regexp": "^1.7.0",
|
package/test/schemas.js
CHANGED
|
@@ -2129,4 +2129,110 @@ describe('Schemas', function() {
|
|
|
2129
2129
|
});
|
|
2130
2130
|
});
|
|
2131
2131
|
|
|
2132
|
+
it('should disregard read-only fields set by a boolean field', function(done) {
|
|
2133
|
+
var req = apos.tasks.getReq();
|
|
2134
|
+
var schema = apos.schemas.compose({
|
|
2135
|
+
addFields: [
|
|
2136
|
+
{
|
|
2137
|
+
name: 'age',
|
|
2138
|
+
type: 'integer'
|
|
2139
|
+
},
|
|
2140
|
+
{
|
|
2141
|
+
name: 'canEditAge',
|
|
2142
|
+
type: 'boolean',
|
|
2143
|
+
choices: [
|
|
2144
|
+
{
|
|
2145
|
+
value: true
|
|
2146
|
+
},
|
|
2147
|
+
{
|
|
2148
|
+
value: false,
|
|
2149
|
+
readOnlyFields: [ 'age' ]
|
|
2150
|
+
}
|
|
2151
|
+
]
|
|
2152
|
+
}
|
|
2153
|
+
]
|
|
2154
|
+
});
|
|
2155
|
+
var object = {
|
|
2156
|
+
age: 20,
|
|
2157
|
+
canEditAge: false
|
|
2158
|
+
};
|
|
2159
|
+
apos.schemas.convert(req, schema, 'form', { age: '30' }, object, function(err) {
|
|
2160
|
+
assert(!err);
|
|
2161
|
+
assert(object.age === 20);
|
|
2162
|
+
done();
|
|
2163
|
+
});
|
|
2164
|
+
});
|
|
2165
|
+
|
|
2166
|
+
it('should disregard read-only fields set by a select field', function(done) {
|
|
2167
|
+
var req = apos.tasks.getReq();
|
|
2168
|
+
var schema = apos.schemas.compose({
|
|
2169
|
+
addFields: [
|
|
2170
|
+
{
|
|
2171
|
+
name: 'age',
|
|
2172
|
+
type: 'integer'
|
|
2173
|
+
},
|
|
2174
|
+
{
|
|
2175
|
+
name: 'edit',
|
|
2176
|
+
type: 'select',
|
|
2177
|
+
choices: [
|
|
2178
|
+
{
|
|
2179
|
+
label: 'Can edit anything',
|
|
2180
|
+
value: 'canEditAnything'
|
|
2181
|
+
},
|
|
2182
|
+
{
|
|
2183
|
+
label: 'Cannot edit age',
|
|
2184
|
+
value: 'cannotEditAge',
|
|
2185
|
+
readOnlyFields: [ 'age' ]
|
|
2186
|
+
}
|
|
2187
|
+
]
|
|
2188
|
+
}
|
|
2189
|
+
]
|
|
2190
|
+
});
|
|
2191
|
+
var object = {
|
|
2192
|
+
age: 20,
|
|
2193
|
+
edit: 'cannotEditAge'
|
|
2194
|
+
};
|
|
2195
|
+
apos.schemas.convert(req, schema, 'form', { age: '30' }, object, function(err) {
|
|
2196
|
+
assert(!err);
|
|
2197
|
+
assert(object.age === 20);
|
|
2198
|
+
done();
|
|
2199
|
+
});
|
|
2200
|
+
});
|
|
2201
|
+
|
|
2202
|
+
it('should disregard read-only fields set by a checkboxes field', function(done) {
|
|
2203
|
+
var req = apos.tasks.getReq();
|
|
2204
|
+
var schema = apos.schemas.compose({
|
|
2205
|
+
addFields: [
|
|
2206
|
+
{
|
|
2207
|
+
name: 'age',
|
|
2208
|
+
type: 'integer'
|
|
2209
|
+
},
|
|
2210
|
+
{
|
|
2211
|
+
name: 'edit',
|
|
2212
|
+
type: 'checkboxes',
|
|
2213
|
+
choices: [
|
|
2214
|
+
{
|
|
2215
|
+
label: 'Can edit anything',
|
|
2216
|
+
value: 'canEditAnything'
|
|
2217
|
+
},
|
|
2218
|
+
{
|
|
2219
|
+
label: 'Cannot edit age',
|
|
2220
|
+
value: 'cannotEditAge',
|
|
2221
|
+
readOnlyFields: [ 'age' ]
|
|
2222
|
+
}
|
|
2223
|
+
]
|
|
2224
|
+
}
|
|
2225
|
+
]
|
|
2226
|
+
});
|
|
2227
|
+
var object = {
|
|
2228
|
+
age: 20,
|
|
2229
|
+
edit: 'cannotEditAge'
|
|
2230
|
+
};
|
|
2231
|
+
apos.schemas.convert(req, schema, 'form', { age: '30' }, object, function(err) {
|
|
2232
|
+
assert(!err);
|
|
2233
|
+
assert(object.age === 20);
|
|
2234
|
+
done();
|
|
2235
|
+
});
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2132
2238
|
});
|
package/test/package.json
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"//": "Automatically generated to satisfy moog-require, do not edit",
|
|
3
|
-
"dependencies": {
|
|
4
|
-
"@apostrophecms/nunjucks": "^2.5.4",
|
|
5
|
-
"@sailshq/lodash": "^3.10.4",
|
|
6
|
-
"async": "^1.5.2",
|
|
7
|
-
"bless": "^3.0.3",
|
|
8
|
-
"bluebird": "^3.7.1",
|
|
9
|
-
"body-parser": "^1.19.0",
|
|
10
|
-
"cheerio": "^1.0.0-rc.10",
|
|
11
|
-
"chokidar": "^3.5.1",
|
|
12
|
-
"connect-flash": "^0.1.1",
|
|
13
|
-
"connect-multiparty": "^2.2.0",
|
|
14
|
-
"cookie-parser": "^1.4.5",
|
|
15
|
-
"credentials": "^3.0.2",
|
|
16
|
-
"cuid": "^1.3.8",
|
|
17
|
-
"diff": "^4.0.1",
|
|
18
|
-
"emulate-mongo-2-driver": "^1.2.3",
|
|
19
|
-
"express": "^4.17.1",
|
|
20
|
-
"express-session": "^1.17.0",
|
|
21
|
-
"glob": "^5.0.15",
|
|
22
|
-
"he": "^0.5.0",
|
|
23
|
-
"heic-to-jpeg-middleware": "^2.0.0",
|
|
24
|
-
"html-to-plaintext": "^0.1.1",
|
|
25
|
-
"html-to-text": "^5.1.1",
|
|
26
|
-
"i18n": "^0.8.6",
|
|
27
|
-
"is-wsl": "^2.2.0",
|
|
28
|
-
"joinr": "^1.0.2",
|
|
29
|
-
"jpeg-exif": "^1.1.4",
|
|
30
|
-
"launder": "^1.5.0",
|
|
31
|
-
"less": "^3.13.1",
|
|
32
|
-
"less-middleware": "^3.1.0",
|
|
33
|
-
"minimatch": "^3.0.4",
|
|
34
|
-
"mkdirp": "^1.0.3",
|
|
35
|
-
"moment": "^2.29.1",
|
|
36
|
-
"moog-require": "^1.1.0",
|
|
37
|
-
"nodemailer": "^6.6.2",
|
|
38
|
-
"oembetter": "^1.0.1",
|
|
39
|
-
"passport": "^0.3.2",
|
|
40
|
-
"passport-local": "^1.0.0",
|
|
41
|
-
"passport-totp": "0.0.2",
|
|
42
|
-
"path-to-regexp": "^1.7.0",
|
|
43
|
-
"performance-now": "^2.1.0",
|
|
44
|
-
"qs": "^6.9.6",
|
|
45
|
-
"regexp-quote": "0.0.0",
|
|
46
|
-
"request": "^2.88.2",
|
|
47
|
-
"request-promise": "^4.2.4",
|
|
48
|
-
"resolve": "^1.20.0",
|
|
49
|
-
"rimraf": "^2.7.1",
|
|
50
|
-
"sanitize-html": "^2.7.1",
|
|
51
|
-
"server-destroy": "^1.0.1",
|
|
52
|
-
"sluggo": "^0.2.0",
|
|
53
|
-
"syntax-error": "^1.3.0",
|
|
54
|
-
"thirty-two": "^1.0.2",
|
|
55
|
-
"tinycolor2": "^1.4.1",
|
|
56
|
-
"uglify-js": "^2.8.29",
|
|
57
|
-
"underscore.string": "^3.3.5",
|
|
58
|
-
"uploadfs": "^1.18.4",
|
|
59
|
-
"xregexp": "^2.0.0",
|
|
60
|
-
"yargs": "^3.32.0",
|
|
61
|
-
"apostrophe": "^2.0.0"
|
|
62
|
-
},
|
|
63
|
-
"devDependencies": {
|
|
64
|
-
"eslint": "^6.5.1",
|
|
65
|
-
"eslint-config-apostrophe": "^2.0.2",
|
|
66
|
-
"eslint-config-standard": "^11.0.0",
|
|
67
|
-
"eslint-plugin-import": "^2.18.2",
|
|
68
|
-
"eslint-plugin-node": "^6.0.1",
|
|
69
|
-
"eslint-plugin-promise": "^3.8.0",
|
|
70
|
-
"eslint-plugin-standard": "^3.1.0",
|
|
71
|
-
"mocha": "^7.0.0"
|
|
72
|
-
}
|
|
73
|
-
}
|