Merge branch 'devel-apollo1' of https://github.com/VulcanJS/Vulcan into devel-apollo1

This commit is contained in:
SachaG 2019-02-16 18:01:33 +09:00
commit 0de16c4a67
47 changed files with 744 additions and 500 deletions

View file

@ -51,7 +51,8 @@
"no-useless-escape": 0,
"quotes": [
1,
"single"
"single",
"avoid-escape"
],
"react/prop-types": 0,
"semi": [1, "always"]

View file

@ -3,12 +3,12 @@ Package.describe({
version: '1.12.16',
summary: 'Accounts UI for React in Meteor 1.3+',
git: 'https://github.com/studiointeract/accounts-ui',
documentation: 'README.md'
documentation: 'README.md',
});
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use('vulcan:core@1.12.16');
api.use('ecmascript');
@ -23,8 +23,8 @@ Package.onUse(function(api) {
api.imply('accounts-base');
api.use('accounts-oauth', {weak: true});
api.use('accounts-password', {weak: true});
api.use('accounts-oauth', { weak: true });
api.use('accounts-password', { weak: true });
api.mainModule('main_client.js', 'client');
api.mainModule('main_server.js', 'server');

View file

@ -2,27 +2,21 @@ Package.describe({
name: 'vulcan:admin',
summary: 'Vulcan components package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'fourseven:scss@4.10.0',
'dynamic-import@0.1.1',
// Vulcan packages
'vulcan:core@1.12.16',
]);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
api.addFiles([
'lib/stylesheets/style.scss'
], ['client']);
api.addFiles(['lib/stylesheets/style.scss'], ['client']);
});

View file

@ -2,18 +2,14 @@ Package.describe({
name: 'vulcan:cloudinary',
summary: 'Vulcan file upload package.',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16'
]);
api.use(['vulcan:core@1.12.16']);
api.mainModule('lib/client/main.js', 'client');
api.mainModule('lib/server/main.js', 'server');
});

View file

@ -8,12 +8,7 @@ import { Link } from 'react-router';
const getLabel = (field, fieldName, collection, intl) => {
const schema = collection && collection.simpleSchema()._schema;
const fieldSchema = schema && schema[fieldName];
if (fieldSchema) {
return intl.formatMessage({id: `${collection._name}.${fieldName}`, defaultMessage: fieldSchema.label});
} else {
return fieldName;
}
return intl.formatLabel({ fieldName: fieldName, collectionName: collection._name, schema: schema });
};
const getTypeName = (field, fieldName, collection) => {

View file

@ -1,4 +1,4 @@
import { registerComponent, getCollection, Utils } from 'meteor/vulcan:lib';
import { registerComponent, getCollection } from 'meteor/vulcan:lib';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import withCurrentUser from '../containers/withCurrentUser.js';
@ -194,13 +194,12 @@ const DatatableHeader = ({ collection, column, toggleSort, currentSort, Componen
use either:
1. the column name translation
1. the column name translation : collectionName.columnName, global.columnName, columnName
2. the column name label in the schema (if the column name matches a schema field)
3. the raw column name.
*/
const defaultMessage = schema[columnName] ? schema[columnName].label : Utils.camelToSpaces(columnName);
const formattedLabel = intl.formatMessage({ id: `${collection._name}.${columnName}`, defaultMessage });
const formattedLabel = intl.formatLabel({fieldName: columnName, collectionName: collection._name, schema: schema});
// if sortable is a string, use it as the name of the property to sort by. If it's just `true`, use column.name
const sortPropertyName = typeof column.sortable === 'string' ? column.sortable : column.name;
@ -461,4 +460,3 @@ const DatatableDefaultCell = ({ column, document }) =>
<div>{typeof column === 'string' ? getFieldValue(document[column]) : getFieldValue(document[column.name])}</div>;
registerComponent('DatatableDefaultCell', DatatableDefaultCell);

View file

@ -2,10 +2,10 @@ Package.describe({
name: 'vulcan:core',
summary: 'Vulcan core package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
@ -13,7 +13,7 @@ Package.onUse(function (api) {
'vulcan:i18n@1.12.16',
'vulcan:users@1.12.16',
'vulcan:routing@1.12.16',
'vulcan:debug@1.12.16'
'vulcan:debug@1.12.16',
]);
api.imply(['vulcan:lib@1.12.16']);
@ -22,7 +22,7 @@ Package.onUse(function (api) {
api.mainModule('lib/client/main.js', 'client');
});
Package.onTest(function (api) {
Package.onTest(function(api) {
api.use(['ecmascript', 'meteortesting:mocha', 'vulcan:test', 'vulcan:core']);
api.mainModule('./test/index.js');
});

View file

@ -3,15 +3,13 @@ Package.describe({
summary: 'Vulcan debug package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git',
debugOnly: true
debugOnly: true,
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'fourseven:scss@4.10.0',
'dynamic-import@0.1.1',
@ -19,14 +17,10 @@ Package.onUse(function (api) {
'vulcan:lib@1.12.16',
'vulcan:email@1.12.16',
]);
api.addFiles([
'lib/stylesheets/debug.scss'
], ['client']);
api.addFiles(['lib/stylesheets/debug.scss'], ['client']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,18 +2,14 @@ Package.describe({
name: 'vulcan:email',
summary: 'Vulcan email package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:lib@1.12.16'
]);
api.use(['vulcan:lib@1.12.16']);
api.mainModule('lib/server.js', 'server');
api.mainModule('lib/client.js', 'client');
});

View file

@ -2,25 +2,16 @@ Package.describe({
name: 'vulcan:embed',
summary: 'Vulcan Embed package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse( function(api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'http',
'vulcan:core@1.12.16',
'fourseven:scss@4.10.0'
]);
api.use(['http', 'vulcan:core@1.12.16', 'fourseven:scss@4.10.0']);
api.addFiles([
'lib/stylesheets/embedly.scss'
], ['client']);
api.addFiles(['lib/stylesheets/embedly.scss'], ['client']);
api.mainModule('lib/client/main.js', 'client');
api.mainModule('lib/server/main.js', 'server');
});

View file

@ -2,21 +2,14 @@ Package.describe({
name: 'vulcan:errors-sentry',
summary: 'Vulcan Sentry error tracking package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'ecmascript',
'vulcan:core@1.12.16',
'vulcan:users@1.12.16',
'vulcan:errors@1.12.16',
]);
api.use(['ecmascript', 'vulcan:core@1.12.16', 'vulcan:users@1.12.16', 'vulcan:errors@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,19 +2,14 @@ Package.describe({
name: 'vulcan:errors',
summary: 'Vulcan error tracking package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'ecmascript',
'vulcan:core@1.12.16',
]);
api.use(['ecmascript', 'vulcan:core@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,19 +2,14 @@ Package.describe({
name: 'vulcan:events-ga',
summary: 'Vulcan Google Analytics event tracking package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
'vulcan:events@1.12.16',
]);
api.use(['vulcan:core@1.12.16', 'vulcan:events@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,19 +2,14 @@ Package.describe({
name: 'vulcan:events-intercom',
summary: 'Vulcan Intercom integration package.',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
'vulcan:events@1.12.16'
]);
api.use(['vulcan:core@1.12.16', 'vulcan:events@1.12.16']);
api.mainModule('lib/client/main.js', 'client');
api.mainModule('lib/server/main.js', 'server');
});

View file

@ -2,19 +2,14 @@ Package.describe({
name: 'vulcan:events-internal',
summary: 'Vulcan internal event tracking package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
'vulcan:events@1.12.16',
]);
api.use(['vulcan:core@1.12.16', 'vulcan:events@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,19 +2,14 @@ Package.describe({
name: 'vulcan:events-segment',
summary: 'Vulcan Segment',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
'vulcan:events@1.12.16',
]);
api.use(['vulcan:core@1.12.16', 'vulcan:events@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,18 +2,14 @@ Package.describe({
name: 'vulcan:events',
summary: 'Vulcan event tracking package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
]);
api.use(['vulcan:core@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,18 +2,13 @@ Package.describe({
name: 'vulcan:forms-tags',
summary: 'Vulcan tag input package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse( function(api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
'vulcan:forms@1.12.16'
]);
api.use(['vulcan:core@1.12.16', 'vulcan:forms@1.12.16']);
api.mainModule('lib/export.js', ['client', 'server']);
});

View file

@ -2,23 +2,15 @@ Package.describe({
name: 'vulcan:forms-upload',
summary: 'Vulcan package extending vulcan:forms to upload images to Cloudinary from a drop zone.',
version: '1.12.16',
git: 'https://github.com/xavcz/nova-forms-upload.git'
git: 'https://github.com/xavcz/nova-forms-upload.git',
});
Package.onUse( function(api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
'vulcan:forms@1.12.16',
'fourseven:scss@4.10.0'
]);
api.use(['vulcan:core@1.12.16', 'vulcan:forms@1.12.16', 'fourseven:scss@4.10.0']);
api.addFiles([
'lib/Upload.scss'
], 'client');
api.addFiles(['lib/Upload.scss'], 'client');
api.mainModule('lib/modules.js', ['client', 'server']);
});

View file

@ -517,26 +517,15 @@ class SmartForm extends Component {
*/
getLabel = (fieldName, fieldLocale) => {
const collectionName = this.props.collectionName.toLowerCase();
const defaultMessage = '|*|*|';
let id = `${collectionName}.${fieldName}`;
let intlLabel;
intlLabel = this.context.intl.formatMessage({ id, defaultMessage });
if (intlLabel === defaultMessage) {
id = `global.${fieldName}`;
intlLabel = this.context.intl.formatMessage({ id });
if (intlLabel === defaultMessage) {
id = fieldName;
intlLabel = this.context.intl.formatMessage({ id });
}
}
const schemaLabel =
this.state.flatSchema[fieldName] &&
this.state.flatSchema[fieldName].label;
const label = intlLabel || schemaLabel || fieldName;
const label = this.context.intl.formatLabel({
fieldName: fieldName,
collectionName: collectionName,
schema: this.state.flatSchema,
});
if (fieldLocale) {
const intlFieldLocale = this.context.intl.formatMessage({
id: `locales.${fieldLocale}`,
defaultMessage: fieldLocale
defaultMessage: fieldLocale,
});
return `${label} (${intlFieldLocale})`;
} else {

View file

@ -2,10 +2,10 @@ Package.describe({
name: 'vulcan:forms',
summary: 'Form containers for React',
version: '1.12.16',
git: 'https://github.com/meteor-utilities/react-form-containers.git'
git: 'https://github.com/meteor-utilities/react-form-containers.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use(['vulcan:core@1.12.16']);
@ -14,7 +14,7 @@ Package.onUse(function (api) {
api.mainModule('lib/server/main.js', ['server']);
});
Package.onTest(function (api) {
Package.onTest(function(api) {
api.use(['ecmascript', 'meteortesting:mocha', 'vulcan:test', 'vulcan:forms']);
api.mainModule('./test/index.js');
});

View file

@ -22,39 +22,39 @@ import SimpleSchema from 'simpl-schema';
const addressGroup = {
name: 'addresses',
label: 'Addresses',
order: 10
order: 10,
};
const permissions = {
canRead: ['guests'],
canUpdate: ['quests'],
canCreate: ['guests']
canCreate: ['guests'],
};
// just 1 input for state testing
const fooSchema = {
foo: {
type: String,
...permissions
}
...permissions,
},
};
//
const addressSchema = {
street: {
type: String,
optional: true,
...permissions
}
...permissions,
},
};
// [{street, city,...}, {street, city, ...}]
const arrayOfObjectSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
...permissions,
},
'addresses.$': {
type: new SimpleSchema(addressSchema)
}
type: new SimpleSchema(addressSchema),
},
};
// example with custom inputs for the children
// ["http://maps/XYZ", "http://maps/foobar"]
@ -62,12 +62,12 @@ const arrayOfUrlSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
...permissions,
},
'addresses.$': {
type: String,
input: 'url'
}
input: 'url',
},
};
// example with array and custom input
const CustomObjectInput = () => 'OBJECT INPUT';
@ -75,12 +75,12 @@ const arrayOfCustomObjectSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
...permissions,
},
'addresses.$': {
type: new SimpleSchema(addressSchema),
input: CustomObjectInput
}
input: CustomObjectInput,
},
};
// example with a fully custom input for both the array and its children
const ArrayInput = () => 'ARRAY INPUT';
@ -89,12 +89,12 @@ const arrayFullCustomSchema = {
type: Array,
group: addressGroup,
...permissions,
input: ArrayInput
input: ArrayInput,
},
'addresses.$': {
type: String,
input: 'url'
}
input: 'url',
},
};
// example with a native type
// ["20 rue du Moulin PARIS", "16 rue de la poste PARIS"]
@ -104,26 +104,26 @@ const arrayOfStringSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
...permissions,
},
'addresses.$': {
type: String
}
type: String,
},
};
// object (not in an array): {street, city}
const objectSchema = {
addresses: {
type: new SimpleSchema(addressSchema),
...permissions
}
...permissions,
},
};
// without calling SimpleSchema
// eslint-disable-next-line no-unused-vars
const bareObjectSchema = {
addresses: {
type: addressSchema,
...permissions
}
...permissions,
},
};
// stub collection
@ -134,13 +134,16 @@ const createDummyCollection = (typeName, schema) =>
typeName,
schema,
resolvers: getDefaultResolvers(typeName + 's'),
mutations: getDefaultMutations(typeName + 's')
mutations: getDefaultMutations(typeName + 's'),
});
const Foos = createDummyCollection('Foo', fooSchema);
const ArrayOfObjects = createDummyCollection('ArrayOfObject', arrayOfObjectSchema);
const Objects = createDummyCollection('Object', objectSchema);
const ArrayOfUrls = createDummyCollection('ArrayOfUrl', arrayOfUrlSchema);
const ArrayOfCustomObjects = createDummyCollection('ArrayOfCustomObject', arrayOfCustomObjectSchema);
const ArrayOfCustomObjects = createDummyCollection(
'ArrayOfCustomObject',
arrayOfCustomObjectSchema
);
const ArrayFullCustom = createDummyCollection('ArrayFullCustom', arrayFullCustomSchema);
// eslint-disable-next-line no-unused-vars
const ArrayOfStrings = createDummyCollection('ArrayOfString', arrayOfStringSchema);
@ -150,7 +153,7 @@ const Addresses = createCollection({
typeName: 'Address',
schema: addressSchema,
resolvers: getDefaultResolvers('Addresses'),
mutations: getDefaultMutations('Addresses')
mutations: getDefaultMutations('Addresses'),
});
// helpers
@ -158,6 +161,7 @@ const Addresses = createCollection({
describe('vulcan-forms/components', function() {
const context = {
intl: {
formatLabel: () => '',
formatMessage: () => '',
formatDate: () => '',
formatTime: () => '',
@ -165,24 +169,24 @@ describe('vulcan-forms/components', function() {
formatNumber: () => '',
formatPlural: () => '',
formatHTMLMessage: () => '',
now: () => ''
}
now: () => '',
},
};
// eslint-disable-next-line no-unused-vars
const mountWithContext = C =>
mount(C, {
context
context,
});
const shallowWithContext = C =>
shallow(C, {
context
context,
});
describe('Form collectionName="" (handle fields computation)', function() {
// since some props are now handled by HOC we need to provide them manually
const defaultProps = {
collectionName: '',
typeName: ''
typeName: '',
};
describe('Form generation', function() {
@ -224,17 +228,23 @@ describe('vulcan-forms/components', function() {
});
describe('array of objects', function() {
it('shallow render', () => {
const wrapper = shallowWithContext(<Form collectionName="" collection={ArrayOfObjects} />);
const wrapper = shallowWithContext(
<Form collectionName="" collection={ArrayOfObjects} />
);
expect(wrapper).toBeDefined();
});
it('render a FormGroup for addresses', function() {
const wrapper = shallowWithContext(<Form collectionName="" collection={ArrayOfObjects} />);
const wrapper = shallowWithContext(
<Form collectionName="" collection={ArrayOfObjects} />
);
const formGroup = wrapper.find('FormGroup').find({ name: 'addresses' });
expect(formGroup).toBeDefined();
expect(formGroup).toHaveLength(1);
});
it('passes down the array child fields', function() {
const wrapper = shallowWithContext(<Form collectionName="" collection={ArrayOfObjects} />);
const wrapper = shallowWithContext(
<Form collectionName="" collection={ArrayOfObjects} />
);
const formGroup = getArrayFormGroup(wrapper);
const fields = getFields(formGroup);
const arrayField = fields[0];
@ -257,12 +267,16 @@ describe('vulcan-forms/components', function() {
});
describe('array of objects with custom children inputs', function() {
it('shallow render', function() {
const wrapper = shallowWithContext(<Form collectionName="" collection={ArrayOfCustomObjects} />);
const wrapper = shallowWithContext(
<Form collectionName="" collection={ArrayOfCustomObjects} />
);
expect(wrapper).toBeDefined();
});
// TODO: does not work, schema_utils needs an update
it.skip('passes down the custom input', function() {
const wrapper = shallowWithContext(<Form collectionName="" collection={ArrayOfCustomObjects} />);
const wrapper = shallowWithContext(
<Form collectionName="" collection={ArrayOfCustomObjects} />
);
const formGroup = getArrayFormGroup(wrapper);
const fields = getFields(formGroup);
const arrayField = fields[0];
@ -271,11 +285,15 @@ describe('vulcan-forms/components', function() {
});
describe('array with a fully custom input (array itself and children)', function() {
it('shallow render', function() {
const wrapper = shallowWithContext(<Form collectionName="" collection={ArrayFullCustom} />);
const wrapper = shallowWithContext(
<Form collectionName="" collection={ArrayFullCustom} />
);
expect(wrapper).toBeDefined();
});
it('passes down the custom input', function() {
const wrapper = shallowWithContext(<Form collectionName="" collection={ArrayFullCustom} />);
const wrapper = shallowWithContext(
<Form collectionName="" collection={ArrayFullCustom} />
);
const formGroup = getArrayFormGroup(wrapper);
const fields = getFields(formGroup);
const arrayField = fields[0];
@ -293,29 +311,38 @@ describe('vulcan-forms/components', function() {
wrapper
.find('input')
.first()
.simulate('change', { target:{value:'bar'} });
.simulate('change', { target: { value: 'bar' } });
// eslint-disable-next-line no-console
console.log(wrapper.find('input').first().html());
console.log(
wrapper
.find('input')
.first()
.html()
);
// eslint-disable-next-line no-console
console.log(wrapper.state());
expect(wrapper.state().currentValues).toEqual({foo:'bar'});
expect(wrapper.state().currentValues).toEqual({ foo: 'bar' });
});
it('reset state when relevant props change', function() {
const wrapper = shallowWithContext(<Form {...defaultProps} collectionName="Foos" collection={Foos} />);
const wrapper = shallowWithContext(
<Form {...defaultProps} collectionName="Foos" collection={Foos} />
);
wrapper.setState({ currentValues: { foo: 'bar' } });
expect(wrapper.state('currentValues')).toEqual({foo:'bar'});
expect(wrapper.state('currentValues')).toEqual({ foo: 'bar' });
wrapper.setProps({ collectionName: 'Bars' });
expect(wrapper.state('currentValues')).toEqual({});
});
it('does not reset state when external prop change', function(){
it('does not reset state when external prop change', function() {
//const prefilledProps = { bar: 'foo' } // TODO
const changeCallback= () => 'CHANGE';
const wrapper = shallowWithContext(<Form {...defaultProps} collection={Foos} changeCallback={changeCallback} />);
const changeCallback = () => 'CHANGE';
const wrapper = shallowWithContext(
<Form {...defaultProps} collection={Foos} changeCallback={changeCallback} />
);
wrapper.setState({ currentValues: { foo: 'bar' } });
expect(wrapper.state('currentValues')).toEqual({foo:'bar'});
expect(wrapper.state('currentValues')).toEqual({ foo: 'bar' });
const newChangeCallback = () => 'NEW';
wrapper.setProps({ changeCallback: newChangeCallback });
expect(wrapper.state('currentValues')).toEqual({ foo:'bar'});
expect(wrapper.state('currentValues')).toEqual({ foo: 'bar' });
});
});
});
@ -324,8 +351,8 @@ describe('vulcan-forms/components', function() {
const shallowWithContext = C =>
shallow(C, {
context: {
getDocument: () => {}
}
getDocument: () => {},
},
});
const defaultProps = {
disabled: false,
@ -342,7 +369,7 @@ describe('vulcan-forms/components', function() {
throwError: () => {},
updateCurrentValues: () => {},
errors: [],
clearFieldErrors: () => {}
clearFieldErrors: () => {},
};
it('shallow render', function() {
const wrapper = shallowWithContext(<FormComponent {...defaultProps} />);
@ -355,11 +382,11 @@ describe('vulcan-forms/components', function() {
nestedSchema: {
street: {},
country: {},
zipCode: {}
zipCode: {},
},
nestedInput: true,
nestedFields: [{}, {}, {}],
currentValues: {}
currentValues: {},
};
it('render a FormNestedArray', function() {
const wrapper = shallowWithContext(<FormComponent {...props} />);
@ -374,11 +401,11 @@ describe('vulcan-forms/components', function() {
nestedSchema: {
street: {},
country: {},
zipCode: {}
zipCode: {},
},
nestedInput: true,
nestedFields: [{}, {}, {}],
currentValues: {}
currentValues: {},
};
it('shallow render', function() {
const wrapper = shallowWithContext(<FormComponent {...props} />);
@ -400,7 +427,7 @@ describe('vulcan-forms/components', function() {
errors: [],
deletedValues: [],
path: 'foobar',
formComponents: Components
formComponents: Components,
};
it('shallow render', function() {
const wrapper = shallow(<Components.FormNestedArray {...defaultProps} currentValues={{}} />);
@ -413,12 +440,16 @@ describe('vulcan-forms/components', function() {
expect(addButton).toHaveLength(1);
});
it.skip('shows 3 items', function() {
const wrapper = mount(<Components.FormNestedArray {...defaultProps} currentValues={{}} value={[1, 2, 3]} />);
const wrapper = mount(
<Components.FormNestedArray {...defaultProps} currentValues={{}} value={[1, 2, 3]} />
);
const nestedItem = wrapper.find('FormNestedItem');
expect(nestedItem).toHaveLength(3);
});
it.skip('pass the correct path and itemIndex to each form', function() {
const wrapper = mount(<Components.FormNestedArray {...defaultProps} currentValues={{}} value={[1, 2]} />);
const wrapper = mount(
<Components.FormNestedArray {...defaultProps} currentValues={{}} value={[1, 2]} />
);
const nestedItem = wrapper.find('FormNestedItem');
const item0 = nestedItem.at(0);
const item1 = nestedItem.at(1);
@ -432,7 +463,7 @@ describe('vulcan-forms/components', function() {
const defaultProps = {
errors: [],
path: 'foobar',
formComponents: Components
formComponents: Components,
};
it('shallow render', function() {
const wrapper = shallow(<Components.FormNestedObject {...defaultProps} currentValues={{}} />);

View file

@ -1,7 +1,6 @@
import { addStrings } from 'meteor/vulcan:core';
addStrings('en', {
'accounts.error_incorrect_password': 'Incorrect password',
'accounts.error_email_required': 'Email required',
'accounts.error_email_already_exists': 'Email already exists',
@ -79,8 +78,8 @@ addStrings('en', {
'users.please_sign_up_log_in': 'Please sign up or log in',
'users.cannot_post': 'Sorry, you do not have permission to post at this time',
'users.cannot_comment': 'Sorry, you do not have permission to comment at this time',
'users.subscribe': 'Subscribe to this user\'s posts',
'users.unsubscribe': 'Unsubscribe to this user\'s posts',
'users.subscribe': "Subscribe to this user's posts",
'users.unsubscribe': "Unsubscribe to this user's posts",
'users.subscribed': 'You have subscribed to “{name}” posts.',
'users.unsubscribed': 'You have unsubscribed from “{name}” posts.',
'users.subscribers': 'Subscribers',
@ -88,8 +87,9 @@ addStrings('en', {
'users.delete_confirm': 'Delete this user?',
'users.email_already_taken': 'This email is already taken: {value}',
'settings': 'Settings',
'settings.json_message': 'Note: settings already provided in your <code>settings.json</code> file will be disabled.',
settings: 'Settings',
'settings.json_message':
'Note: settings already provided in your <code>settings.json</code> file will be disabled.',
'settings.edit': 'Edit Settings',
'settings.edited': 'Settings edited (please reload).',
'settings.title': 'Title',
@ -120,31 +120,35 @@ addStrings('en', {
'settings.scoreUpdateInterval': 'Score Update Interval',
'app.loading': 'Loading…',
'app.404': 'Sorry, we couldn\'t find what you were looking for.',
'app.missing_document': 'Sorry, we couldn\'t find the document you were looking for.',
'app.404': "Sorry, we couldn't find what you were looking for.",
'app.missing_document': "Sorry, we couldn't find the document you were looking for.",
'app.powered_by': 'Built with Vulcan.js',
'app.or': 'Or',
'app.noPermission': 'Sorry, you do not have the permission to do this at this time.',
'app.operation_not_allowed': 'Sorry, you don\'t have the rights to perform the operation "{value}"',
'app.operation_not_allowed':
'Sorry, you don\'t have the rights to perform the operation "{value}"',
'app.document_not_found': 'Document not found (id: {value})',
'app.disallowed_property_detected': 'Disallowed property detected: {value}',
'app.something_bad_happened': 'Something bad happened...',
'app.embedly_not_authorized': 'Invalid Embedly API key provided in the settings file. To find your key, log into https://app.embed.ly -> API',
'app.embedly_not_authorized':
'Invalid Embedly API key provided in the settings file. To find your key, log into https://app.embed.ly -> API',
'cards.edit': 'Edit',
'datatable.new': 'New',
'datatable.edit': 'Edit',
'datatable.search': 'Search',
'admin': 'Admin',
'notifications': 'Notifications',
admin: 'Admin',
notifications: 'Notifications',
'errors.expectedType': 'Expected type {dataType} for field “{label}”, received “{value}” instead.',
'errors.expectedType':
'Expected type {dataType} for field “{label}”, received “{value}” instead.',
'errors.required': 'Field “{label}” is required.',
'errors.minString': 'Field “{label}” needs to have at least {min} characters',
'errors.maxString': 'Field “{label}” is limited to {max} characters.',
'errors.generic': 'Sorry, something went wrong: <code>{errorMessage}</code>.',
'errors.generic_report': 'Sorry, something went wrong: <code>{errorMessage}</code>. <br/>An error report has been generated.',
'errors.generic_report':
'Sorry, something went wrong: <code>{errorMessage}</code>. <br/>An error report has been generated.',
'errors.minNumber': 'Field “{label}” must be higher than {min}. ',
'errors.maxNumber': 'Field “{label}” must be lower than {max}. ',
'errors.minCount': 'There needs to be at least {count} in field “{label}”.',
@ -156,6 +160,5 @@ addStrings('en', {
//TODO other simple schema errors
'errors.minNumberExclusive': '',
'errors.maxNumberExclusive': '',
'errors.keyNotInSchema': ''
});
'errors.keyNotInSchema': '',
});

View file

@ -2,18 +2,13 @@ Package.describe({
name: 'vulcan:i18n-en-us',
summary: 'Vulcan i18n package (en_US)',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16'
]);
api.use(['vulcan:core@1.12.16']);
api.addFiles([
'lib/en_US.js'
], ['client', 'server']);
api.addFiles(['lib/en_US.js'], ['client', 'server']);
});

View file

@ -2,18 +2,13 @@ Package.describe({
name: 'vulcan:i18n-es-es',
summary: 'Vulcan i18n package (es_ES)',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16'
]);
api.use(['vulcan:core@1.12.16']);
api.addFiles([
'lib/es_ES.js'
], ['client', 'server']);
api.addFiles(['lib/es_ES.js'], ['client', 'server']);
});

View file

@ -2,18 +2,13 @@ Package.describe({
name: 'vulcan:i18n-fr-fr',
summary: 'Vulcan i18n package (fr_FR)',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16'
]);
api.use(['vulcan:core@1.12.16']);
api.addFiles([
'lib/fr_FR.js'
], ['client', 'server']);
api.addFiles(['lib/fr_FR.js'], ['client', 'server']);
});

View file

@ -1,16 +1,56 @@
import React, { Component } from 'react';
import { getString } from 'meteor/vulcan:lib';
import { getString, Utils } from 'meteor/vulcan:lib';
import { intlShape } from './shape.js';
export default class IntlProvider extends Component{
export default class IntlProvider extends Component {
formatMessage = ({ id, defaultMessage }, values) => {
return getString({ id, defaultMessage, values, locale: this.props.locale });
}
};
formatStuff = (something) => {
/**
* formatLabel - Get a label for a field, for a given collection, in the current language. The evaluation is as follows : i18n(collectionName.fieldName) > i18n(global.fieldName) > i18n(fieldName) > schema.fieldName.label > fieldName
*
* @param {object} params
* @param {string} params.fieldName The name of the field to evaluate (required)
* @param {string} params.collectionName The name of the collection the field belongs to
* @param {object} params.schema The schema of the collection
* @param {object} values The values to pass to format the i18n string
* @return {string} The translated label
*/
formatLabel = ({ fieldName, collectionName, schema }, values) => {
if (!fieldName) {
throw new Error('fieldName option passed to formatLabel cannot be empty or undefined');
}
const defaultMessage = '|*|*|';
// Get the intl label
let intlLabel = defaultMessage;
// try collectionName.fieldName as intl id
if (collectionName) {
intlLabel = this.formatMessage(
{ id: `${collectionName.toLowerCase()}.${fieldName}`, defaultMessage },
values
);
}
// try global.fieldName then just fieldName as intl id
if (intlLabel === defaultMessage) {
intlLabel = this.formatMessage({ id: `global.${fieldName}`, defaultMessage }, values);
if (intlLabel === defaultMessage) {
intlLabel = this.formatMessage({ id: fieldName }, values);
}
}
if (intlLabel) {
return intlLabel;
}
// define the schemaLabel. If the schema has been initialized with SimpleSchema, the label should be here even if it has not been declared https://github.com/aldeed/simple-schema-js#label
let schemaLabel = schema && schema[fieldName] ? schema[fieldName].label : null;
return schemaLabel || Utils.camelToSpaces(fieldName);
};
formatStuff = something => {
return something;
}
};
getChildContext() {
return {
@ -19,20 +59,20 @@ export default class IntlProvider extends Component{
formatTime: this.formatStuff,
formatRelative: this.formatStuff,
formatNumber: this.formatStuff,
formatPlural: this.formatStuff,
formatPlural: this.formatStuff,
formatMessage: this.formatMessage,
formatLabel: this.formatLabel,
formatHTMLMessage: this.formatStuff,
now: this.formatStuff,
}
},
};
}
render(){
render() {
return this.props.children;
}
}
IntlProvider.childContextTypes = {
intl: intlShape
intl: intlShape,
};

View file

@ -1,34 +1,35 @@
/*
* Copyright 2015, Yahoo Inc.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
* Copyright 2015, Yahoo Inc.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
import PropTypes from 'prop-types';
const {bool, number, string, func, object, oneOf, shape, any} = PropTypes;
const { bool, number, string, func, object, oneOf, shape, any } = PropTypes;
const localeMatcher = oneOf(['best fit', 'lookup']);
const narrowShortLong = oneOf(['narrow', 'short', 'long']);
const numeric2digit = oneOf(['numeric', '2-digit']);
const funcReq = func.isRequired;
export const intlConfigPropTypes = {
locale : string,
formats : object,
messages : object,
locale: string,
formats: object,
messages: object,
textComponent: any,
defaultLocale : string,
defaultLocale: string,
defaultFormats: object,
};
export const intlFormatPropTypes = {
formatDate : funcReq,
formatTime : funcReq,
formatRelative : funcReq,
formatNumber : funcReq,
formatPlural : funcReq,
formatMessage : funcReq,
formatDate: funcReq,
formatTime: funcReq,
formatRelative: funcReq,
formatNumber: funcReq,
formatPlural: funcReq,
formatMessage: funcReq,
formatLabel: funcReq,
formatHTMLMessage: funcReq,
};
@ -40,8 +41,8 @@ export const intlShape = shape({
});
export const messageDescriptorPropTypes = {
id : string.isRequired,
description : string,
id: string.isRequired,
description: string,
defaultMessage: string,
};
@ -50,30 +51,30 @@ export const dateTimeFormatPropTypes = {
formatMatcher: oneOf(['basic', 'best fit']),
timeZone: string,
hour12 : bool,
hour12: bool,
weekday : narrowShortLong,
era : narrowShortLong,
year : numeric2digit,
month : oneOf(['numeric', '2-digit', 'narrow', 'short', 'long']),
day : numeric2digit,
hour : numeric2digit,
minute : numeric2digit,
second : numeric2digit,
weekday: narrowShortLong,
era: narrowShortLong,
year: numeric2digit,
month: oneOf(['numeric', '2-digit', 'narrow', 'short', 'long']),
day: numeric2digit,
hour: numeric2digit,
minute: numeric2digit,
second: numeric2digit,
timeZoneName: oneOf(['short', 'long']),
};
export const numberFormatPropTypes = {
localeMatcher,
style : oneOf(['decimal', 'currency', 'percent']),
currency : string,
style: oneOf(['decimal', 'currency', 'percent']),
currency: string,
currencyDisplay: oneOf(['symbol', 'code', 'name']),
useGrouping : bool,
useGrouping: bool,
minimumIntegerDigits : number,
minimumFractionDigits : number,
maximumFractionDigits : number,
minimumIntegerDigits: number,
minimumFractionDigits: number,
maximumFractionDigits: number,
minimumSignificantDigits: number,
maximumSignificantDigits: number,
};

View file

@ -2,18 +2,19 @@ Package.describe({
name: 'vulcan:i18n',
summary: 'i18n client polyfill',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan'
git: 'https://github.com/VulcanJS/Vulcan',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:lib@1.12.16',
]);
api.use(['vulcan:lib@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});
Package.onTest(function(api) {
api.use(['ecmascript', 'meteortesting:mocha', 'vulcan:test', 'vulcan:i18n']);
api.mainModule('./test/index.js');
});

View file

@ -0,0 +1 @@
import './provider.test.js';

View file

@ -0,0 +1,127 @@
import IntlProvider from '../lib/modules/provider';
import React from 'react';
import expect from 'expect';
import { shallow } from 'enzyme';
import { addStrings, Utils } from 'meteor/vulcan:core';
// constants for formatMessage
const defaultMessage = 'default';
const stringId = 'test_string';
const ENTestString = 'English test string';
const FRTestString = 'Phrase test en Français';
const valueStringId = 'valueStringId';
const valueStringValue = 'Vulcan';
const valueTestStringStatic = 'the value is ';
const valueTestStringDynamic = 'testValue';
const valueTestString = `${valueTestStringStatic}{${valueTestStringDynamic}}`;
// constants for formatLabel
const fieldName = 'testFieldName';
const fieldNameForSchema = 'fieldNameForSchema';
const fieldNameForGlobal = 'testFieldNameGlobal';
const fieldNameForCollection = 'testFieldNameCollection';
const unknownFieldName = 'unknownFieldName';
const collectionName = 'Tests';
const labelFromCollection = 'label from collection';
const labelFromGlobal = 'label from global';
const labelFromSchema = 'label from schema';
const labelFromFieldName = 'label from fieldName';
// add the schema entries for all fields to test respect of the order too
const schema = {
[fieldName]: {
label: labelFromSchema,
},
[fieldNameForSchema]: {
label: labelFromSchema,
},
[fieldNameForGlobal]: {
label: labelFromSchema,
},
[fieldNameForCollection]: {
label: labelFromSchema,
},
};
// add the strings for formatMessage
addStrings('en', {
[stringId]: ENTestString,
[valueStringId]: valueTestString,
});
addStrings('fr', {
[stringId]: FRTestString,
});
// add the strings for formatLabel
addStrings('en', {
// fieldName only
[fieldName]: labelFromFieldName,
// fieldName + global - we expect labelFromGlobal
[fieldNameForGlobal]: labelFromFieldName,
[`global.${fieldNameForGlobal}`]: labelFromGlobal,
// fieldName + global + collectionName - we expect labelFromCollection
[fieldNameForCollection]: labelFromFieldName,
[`global.${fieldNameForCollection}`]: labelFromGlobal,
[`${collectionName.toLowerCase()}.${fieldNameForCollection}`]: labelFromCollection,
});
describe('vulcan:i18n/IntlProvider', function() {
it('shallow render', function() {
const wrapper = shallow(<IntlProvider />);
expect(wrapper).toBeDefined();
});
describe('formatMessage', function() {
it('format a message according to locale', function() {
const wrapper = shallow(<IntlProvider locale="en" />);
const ENString = wrapper.instance().formatMessage({ id: stringId });
expect(ENString).toEqual(ENTestString);
wrapper.setProps({ locale: 'fr' });
const FRString = wrapper.instance().formatMessage({ id: stringId });
expect(FRString).toEqual(FRTestString);
});
it('format a message according to a value', function() {
const wrapper = shallow(<IntlProvider locale="en" />);
const dynamicString = wrapper
.instance()
.formatMessage({ id: valueStringId }, { [valueTestStringDynamic]: valueStringValue });
expect(dynamicString).toEqual(valueTestStringStatic + valueStringValue);
});
it('return a default message when no string is found', function() {
const wrapper = shallow(<IntlProvider locale="en" />);
const ENString = wrapper.instance().formatMessage({
id: 'unknownStringId',
defaultMessage: defaultMessage,
});
expect(ENString).toEqual(defaultMessage);
});
});
describe('formatLabel', function() {
const wrapper = shallow(<IntlProvider locale="en" />);
it('return the fieldName when there is no matching string or label', function() {
const ENString = wrapper
.instance()
.formatLabel({ fieldName: unknownFieldName, schema, collectionName });
expect(ENString).toEqual(Utils.camelToSpaces(unknownFieldName));
});
it('return the matching schema label when there is no matching string', function() {
const ENString = wrapper
.instance()
.formatLabel({ fieldName: fieldNameForSchema, schema, collectionName });
expect(ENString).toEqual(schema[fieldName].label);
});
it('return the label from a matched `fieldName`', function() {
const ENString = wrapper.instance().formatLabel({ fieldName, schema, collectionName });
expect(ENString).toEqual(labelFromFieldName);
});
it('return the label from a matched `global.fieldName`', function() {
const ENString = wrapper
.instance()
.formatLabel({ fieldName: fieldNameForGlobal, schema, collectionName });
expect(ENString).toEqual(labelFromGlobal);
});
it('return the label from a matched `collectionName.fieldName`', function() {
const ENString = wrapper
.instance()
.formatLabel({ fieldName: fieldNameForCollection, schema, collectionName });
expect(ENString).toEqual(labelFromCollection);
});
});
});

View file

@ -20,7 +20,8 @@ export const Collections = [];
export const getCollection = name =>
Collections.find(
({ options: { collectionName } }) => name === collectionName || name === collectionName.toLowerCase()
({ options: { collectionName } }) =>
name === collectionName || name === collectionName.toLowerCase()
);
// TODO: find more reliable way to get collection name from type name?
@ -105,7 +106,9 @@ Mongo.Collection.prototype.helpers = function(helpers) {
var self = this;
if (self._transform && !self._helpers)
throw new Meteor.Error('Can\'t apply helpers to \'' + self._name + '\' a transform function already exists!');
throw new Meteor.Error(
"Can't apply helpers to '" + self._name + "' a transform function already exists!"
);
if (!self._helpers) {
self._helpers = function Document(doc) {
@ -126,7 +129,7 @@ export const createCollection = options => {
typeName,
collectionName = getCollectionName(typeName),
generateGraphQLSchema = true,
dbCollectionName
dbCollectionName,
} = options;
let { schema } = options;
@ -155,7 +158,7 @@ export const createCollection = options => {
//register individual collection callback
registerCollectionCallback(typeName.toLowerCase());
// if schema has at least one intl field, add intl callback just before
// if schema has at least one intl field, add intl callback just before
// `${collectionName}.collection` callbacks run to make sure it always runs last
if (schemaHasIntlFields(schema)) {
hasIntlFields = true; // we have at least one intl field
@ -163,8 +166,12 @@ export const createCollection = options => {
}
//run schema callbacks and run general callbacks last
schema = runCallbacks({ name: `${typeName.toLowerCase()}.collection`, iterator: schema, properties: { options }});
schema = runCallbacks({ name: '*.collection', iterator: schema, properties: { options }});
schema = runCallbacks({
name: `${typeName.toLowerCase()}.collection`,
iterator: schema,
properties: { options },
});
schema = runCallbacks({ name: '*.collection', iterator: schema, properties: { options } });
if (schema) {
// attach schema to collection
@ -196,11 +203,15 @@ export const createCollection = options => {
let parameters = {
selector: {},
options: {}
options: {},
};
if (collection.defaultView) {
parameters = Utils.deepExtend(true, parameters, collection.defaultView(terms, apolloClient, context));
parameters = Utils.deepExtend(
true,
parameters,
collection.defaultView(terms, apolloClient, context)
);
}
// handle view option
@ -208,8 +219,13 @@ export const createCollection = options => {
const viewFn = collection.views[terms.view];
const view = viewFn(terms, apolloClient, context);
let mergedParameters = Utils.deepExtend(true, parameters, view);
if (mergedParameters.options && mergedParameters.options.sort && view.options && view.options.sort) {
if (
mergedParameters.options &&
mergedParameters.options.sort &&
view.options &&
view.options.sort
) {
// If both the default view and the selected view have sort options,
// don't merge them together; take the selected view's sort. (Otherwise
// they merge in the wrong order, so that the default-view's sort takes
@ -254,7 +270,12 @@ export const createCollection = options => {
// note: check that context exists to avoid calling this from withList during SSR
if (Meteor.isServer && context) {
parameters = runCallbacks(`${typeName.toLowerCase()}.parameters.server`, parameters, _.clone(terms), context);
parameters = runCallbacks(
`${typeName.toLowerCase()}.parameters.server`,
parameters,
_.clone(terms),
context
);
// OpenCRUD backwards compatibility
parameters = runCallbacks(
`${collectionName.toLowerCase()}.parameters.server`,
@ -293,12 +314,17 @@ export const createCollection = options => {
if (terms.query) {
const query = escapeStringRegexp(terms.query);
const currentSchema = collection.simpleSchema()._schema;
const searchableFieldNames = _.filter(_.keys(currentSchema), fieldName => currentSchema[fieldName].searchable);
const searchableFieldNames = _.filter(
_.keys(currentSchema),
fieldName => currentSchema[fieldName].searchable
);
if (searchableFieldNames.length) {
parameters = Utils.deepExtend(true, parameters, {
selector: {
$or: searchableFieldNames.map(fieldName => ({ [fieldName]: { $regex: query, $options: 'i' } }))
}
$or: searchableFieldNames.map(fieldName => ({
[fieldName]: { $regex: query, $options: 'i' },
})),
},
});
} else {
// eslint-disable-next-line no-console
@ -332,11 +358,11 @@ function registerCollectionCallback(typeName) {
iterator: { schema: 'the schema of the collection' },
properties: [
{ schema: 'The schema of the collection' },
{ validationErrors: 'An Object that can be used to accumulate validation errors' }
{ validationErrors: 'An Object that can be used to accumulate validation errors' },
],
runs: 'sync',
returns: 'schema',
description: 'Modifies schemas on collection creation'
description: 'Modifies schemas on collection creation',
});
}
@ -346,7 +372,7 @@ registerCallback({
iterator: { schema: 'the schema of the collection' },
properties: [
{ schema: 'The schema of the collection' },
{ validationErrors: 'An object that can be used to accumulate validation errors' }
{ validationErrors: 'An object that can be used to accumulate validation errors' },
],
runs: 'sync',
returns: 'schema',
@ -358,7 +384,6 @@ function addIntlFields(schema) {
Object.keys(schema).forEach(fieldName => {
const fieldSchema = schema[fieldName];
if (isIntlField(fieldSchema)) {
// remove `intl` to avoid treating new _intl field as a field to internationalize
// eslint-disable-next-line no-unused-vars
const { intl, ...propertiesToCopy } = schema[fieldName];
@ -367,13 +392,13 @@ function addIntlFields(schema) {
...propertiesToCopy, // copy properties from regular field
hidden: true,
type: Array,
isIntlData: true
isIntlData: true,
};
delete schema[`${fieldName}_intl`].intl;
schema[`${fieldName}_intl.$`] = {
type: getIntlString()
type: getIntlString(),
};
// if original field is required, enable custom validation function instead of `optional` property

View file

@ -42,9 +42,9 @@ export const coreComponents = [
* @param {...Function} hocs The HOCs to compose with the raw component.
*
* Note: when a component is registered without higher order component, `hocs` will be
* an empty array, and it's ok!
* an empty array, and it's ok!
* See https://github.com/reactjs/redux/blob/master/src/compose.js#L13-L15
*
*
* @returns Structure of a component in the list:
*
* ComponentsTable.Foo = {
@ -112,10 +112,9 @@ export const populateComponentsApp = () => {
// loop over each component in the list
registeredComponents.map(name => {
// populate an entry in the lookup table
Components[name] = getComponent(name);
// uncomment for debug
// console.log('init component:', name);
});
@ -124,9 +123,12 @@ export const populateComponentsApp = () => {
if (missingComponents.length) {
// eslint-disable-next-line no-console
console.warn(`Found the following missing core components: ${missingComponents.join(', ')}. Include a UI package such as vulcan:ui-bootstrap to add them.`);
console.warn(
`Found the following missing core components: ${missingComponents.join(
', '
)}. Include a UI package such as vulcan:ui-bootstrap to add them.`
);
}
};
/**
@ -136,12 +138,12 @@ export const populateComponentsApp = () => {
* @param {String} name The name of the component to get.
* @returns {Function|React Component} An interchangeable/extendable React component
*/
export const getRawComponent = (name) => {
export const getRawComponent = name => {
return ComponentsTable[name].rawComponent;
};
/**
* Replace a Vulcan component with the same name with a new component or
* Replace a Vulcan component with the same name with a new component or
* an extension of the raw component and one or more optional higher order components.
* This function keeps track of the previous HOCs and wrap the new HOCs around previous ones
*
@ -151,11 +153,10 @@ export const populateComponentsApp = () => {
* @returns {Function|React Component} A component callable with Components[name]
*
* Note: when a component is registered without higher order component, `hocs` will be
* an empty array, and it's ok!
* an empty array, and it's ok!
* See https://github.com/reactjs/redux/blob/master/src/compose.js#L13-L15
*/
export function replaceComponent(name, newComponent, ...newHocs) {
export function replaceComponent(name, newComponent, ...newHocs) {
// support single argument syntax
if (typeof arguments[0] === 'object') {
// eslint-disable-next-line no-redeclare
@ -165,20 +166,20 @@ export const populateComponentsApp = () => {
}
const previousComponent = ComponentsTable[name];
const previousHocs = previousComponent && previousComponent.hocs || [];
const previousHocs = (previousComponent && previousComponent.hocs) || [];
if (!previousComponent) {
// eslint-disable-next-line no-console
console.warn(
`Trying to replace non-registered component ${name}. The component is ` +
'being registered. If you were trying to replace a component defined by ' +
'another package, make sure that you haven\'t misspelled the name. Check ' +
'also if the original component is still being registered or that it ' +
'hasn\'t been renamed.',
'being registered. If you were trying to replace a component defined by ' +
"another package, make sure that you haven't misspelled the name. Check " +
'also if the original component is still being registered or that it ' +
"hasn't been renamed."
);
}
return registerComponent(name, newComponent, ...newHocs, ...previousHocs);
return registerComponent(name, newComponent, ...newHocs, ...previousHocs);
}
export const copyHoCs = (sourceComponent, targetComponent) => {
@ -196,10 +197,14 @@ export const instantiateComponent = (component, props) => {
return null;
} else if (typeof component === 'string') {
const Component = getComponent(component);
return <Component {...props}/>;
} else if (typeof component === 'function' && component.prototype && component.prototype.isReactComponent) {
return <Component {...props} />;
} else if (
typeof component === 'function' &&
component.prototype &&
component.prototype.isReactComponent
) {
const Component = component;
return <Component {...props}/>;
return <Component {...props} />;
} else if (typeof component === 'function') {
return component(props);
} else {
@ -237,7 +242,6 @@ export const delayedComponent = name => {
};
};
// Example with Proxy (might be unstable/hard to reason about)
//const mergeWithComponents = (myComponents = {}) => {
// const handler = {
@ -248,4 +252,5 @@ export const delayedComponent = name => {
// const proxy = new Proxy(myComponents, handler);
// return proxy;
//};
export const mergeWithComponents = myComponents => (myComponents ? { ...Components, ...myComponents } : Components);
export const mergeWithComponents = myComponents =>
myComponents ? { ...Components, ...myComponents } : Components;

View file

@ -14,7 +14,7 @@ Vulcan.VERSION = '1.12.16';
// ------------------------------------- Schemas -------------------------------- //
SimpleSchema.extendOptions([
'hidden', // hidden: true means the field is never shown in a form no matter what
'hidden', // hidden: true means the field is never shown in a form no matter what
'mustComplete', // mustComplete: true means the field is required to have a complete profile
'form', // extra form properties
'inputProperties', // extra form properties
@ -22,7 +22,7 @@ SimpleSchema.extendOptions([
'control', // SmartForm control (String or React component) (legacy)
'order', // position in the form
'group', // form fieldset group
'onCreate', // field insert callback
'onInsert', // field insert callback (OpenCRUD backwards compatibility)

View file

@ -29,13 +29,12 @@ import { delayedComponent } from './components';
* @return {React.Component}
* Component that will load the dynamic import on mount
*/
export const dynamicLoader = importComponent => loadable({
loader: isFunction(importComponent)
? importComponent
: () => importComponent, // backwards compatibility,
// use delayedComponent, as this function can be used when Components is not populated yet
loading: delayedComponent('DynamicLoading'),
});
export const dynamicLoader = importComponent =>
loadable({
loader: isFunction(importComponent) ? importComponent : () => importComponent, // backwards compatibility,
// use delayedComponent, as this function can be used when Components is not populated yet
loading: delayedComponent('DynamicLoading'),
});
/**
* Renders a dynamic component with the given props.
@ -51,7 +50,7 @@ export const getDynamicComponent = componentImport => {
console.warn(
'getDynamicComponent is deprecated, use renderDynamicComponent instead.',
'If you want to retrieve the component instead that of just rendering it,',
'use dynamicLoader. See this issue to know how to do it: https://github.com/VulcanJS/Vulcan/issues/1997',
'use dynamicLoader. See this issue to know how to do it: https://github.com/VulcanJS/Vulcan/issues/1997'
);
return renderDynamicComponent(componentImport);
};

View file

@ -32,7 +32,12 @@ to the client.
*/
import { runCallbacks, runCallbacksAsync } from '../modules/index.js';
import { validateDocument, validateData, dataToModifier, modifierToData } from '../modules/validation.js';
import {
validateDocument,
validateData,
dataToModifier,
modifierToData,
} from '../modules/validation.js';
import { registerSetting } from '../modules/settings.js';
import { debug, debugGroup, debugGroupEnd } from '../modules/debug.js';
import { throwError } from '../modules/errors.js';
@ -48,8 +53,14 @@ registerSetting('database', 'mongo', 'Which database to use for your back-end');
Create
*/
export const createMutator = async ({ collection, document, data, currentUser, validate, context }) => {
export const createMutator = async ({
collection,
document,
data,
currentUser,
validate,
context,
}) => {
// OpenCRUD backwards compatibility: accept either data or document
// we don't want to modify the original document
document = data || document;
@ -77,10 +88,23 @@ export const createMutator = async ({ collection, document, data, currentUser, v
let validationErrors = [];
validationErrors = validationErrors.concat(validateDocument(document, collection, context));
// run validation callbacks
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.create.validate`, iterator: validationErrors, properties });
validationErrors = await runCallbacks({ name: '*.create.validate', iterator: validationErrors, properties });
validationErrors = await runCallbacks({
name: `${typeName.toLowerCase()}.create.validate`,
iterator: validationErrors,
properties,
});
validationErrors = await runCallbacks({
name: '*.create.validate',
iterator: validationErrors,
properties,
});
// OpenCRUD backwards compatibility
document = await runCallbacks(`${collectionName.toLowerCase()}.new.validate`, document, currentUser, validationErrors);
document = await runCallbacks(
`${collectionName.toLowerCase()}.new.validate`,
document,
currentUser,
validationErrors
);
if (validationErrors.length) {
console.log(validationErrors); // eslint-disable-line no-console
throwError({ id: 'app.validation_error', data: { break: true, errors: validationErrors } });
@ -134,10 +158,18 @@ export const createMutator = async ({ collection, document, data, currentUser, v
Before
*/
document = await runCallbacks({ name: `${typeName.toLowerCase()}.create.before`, iterator: document, properties });
document = await runCallbacks({
name: `${typeName.toLowerCase()}.create.before`,
iterator: document,
properties,
});
document = await runCallbacks({ name: '*.create.before', iterator: document, properties });
// OpenCRUD backwards compatibility
document = await runCallbacks(`${collectionName.toLowerCase()}.new.before`, document, currentUser);
document = await runCallbacks(
`${collectionName.toLowerCase()}.new.before`,
document,
currentUser
);
document = await runCallbacks(`${collectionName.toLowerCase()}.new.sync`, document, currentUser);
/*
@ -153,7 +185,11 @@ export const createMutator = async ({ collection, document, data, currentUser, v
*/
// run any post-operation sync callbacks
document = await runCallbacks({ name: `${typeName.toLowerCase()}.create.after`, iterator: document, properties });
document = await runCallbacks({
name: `${typeName.toLowerCase()}.create.after`,
iterator: document,
properties,
});
document = await runCallbacks({ name: '*.create.after', iterator: document, properties });
// OpenCRUD backwards compatibility
document = await runCallbacks(`${collectionName.toLowerCase()}.new.after`, document, currentUser);
@ -167,10 +203,18 @@ export const createMutator = async ({ collection, document, data, currentUser, v
*/
// note: make sure properties.document is up to date
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.create.async`, properties: { ...properties, document: document } });
await runCallbacksAsync({
name: `${typeName.toLowerCase()}.create.async`,
properties: { ...properties, document: document },
});
await runCallbacksAsync({ name: '*.create.async', properties });
// OpenCRUD backwards compatibility
await runCallbacksAsync(`${collectionName.toLowerCase()}.new.async`, document, currentUser, collection);
await runCallbacksAsync(
`${collectionName.toLowerCase()}.new.async`,
document,
currentUser,
collection
);
endDebugMutator(collectionName, 'Create', { document });
@ -182,8 +226,18 @@ export const createMutator = async ({ collection, document, data, currentUser, v
Update
*/
export const updateMutator = async ({ collection, documentId, selector, data, set = {}, unset = {}, currentUser, validate, context, document: oldDocument }) => {
export const updateMutator = async ({
collection,
documentId,
selector,
data,
set = {},
unset = {},
currentUser,
validate,
context,
document: oldDocument,
}) => {
const { collectionName, typeName } = collection.options;
const schema = collection.simpleSchema()._schema;
@ -225,10 +279,26 @@ export const updateMutator = async ({ collection, documentId, selector, data, se
validationErrors = validationErrors.concat(validateData(data, document, collection, context));
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.update.validate`, iterator: validationErrors, properties });
validationErrors = await runCallbacks({ name: '*.update.validate', iterator: validationErrors, properties });
validationErrors = await runCallbacks({
name: `${typeName.toLowerCase()}.update.validate`,
iterator: validationErrors,
properties,
});
validationErrors = await runCallbacks({
name: '*.update.validate',
iterator: validationErrors,
properties,
});
// OpenCRUD backwards compatibility
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.validate`, dataToModifier(data), document, currentUser, validationErrors));
data = modifierToData(
await runCallbacks(
`${collectionName.toLowerCase()}.edit.validate`,
dataToModifier(data),
document,
currentUser,
validationErrors
)
);
if (validationErrors.length) {
console.log(validationErrors); // eslint-disable-line no-console
@ -247,7 +317,13 @@ export const updateMutator = async ({ collection, documentId, selector, data, se
autoValue = await schema[fieldName].onUpdate(properties); // eslint-disable-line no-await-in-loop
} else if (schema[fieldName].onEdit) {
// OpenCRUD backwards compatibility
autoValue = await schema[fieldName].onEdit(dataToModifier(clone(data)), document, currentUser, document); // eslint-disable-line no-await-in-loop
// eslint-disable-next-line no-await-in-loop
autoValue = await schema[fieldName].onEdit(
dataToModifier(clone(data)),
document,
currentUser,
document
);
}
if (typeof autoValue !== 'undefined') {
data[fieldName] = autoValue;
@ -259,11 +335,31 @@ export const updateMutator = async ({ collection, documentId, selector, data, se
Before
*/
data = await runCallbacks({ name: `${typeName.toLowerCase()}.update.before`, iterator: data, properties });
data = await runCallbacks({
name: `${typeName.toLowerCase()}.update.before`,
iterator: data,
properties,
});
data = await runCallbacks({ name: '*.update.before', iterator: data, properties });
// OpenCRUD backwards compatibility
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.before`, dataToModifier(data), document, currentUser, document));
data = modifierToData(await runCallbacks(`${collectionName.toLowerCase()}.edit.sync`, dataToModifier(data), document, currentUser, document));
data = modifierToData(
await runCallbacks(
`${collectionName.toLowerCase()}.edit.before`,
dataToModifier(data),
document,
currentUser,
document
)
);
data = modifierToData(
await runCallbacks(
`${collectionName.toLowerCase()}.edit.sync`,
dataToModifier(data),
document,
currentUser,
document
)
);
// update connector requires a modifier, so get it from data
const modifier = dataToModifier(data);
@ -301,10 +397,19 @@ export const updateMutator = async ({ collection, documentId, selector, data, se
After
*/
document = await runCallbacks({ name: `${typeName.toLowerCase()}.update.after`, iterator: document, properties });
document = await runCallbacks({
name: `${typeName.toLowerCase()}.update.after`,
iterator: document,
properties,
});
document = await runCallbacks({ name: '*.update.after', iterator: document, properties });
// OpenCRUD backwards compatibility
document = await runCallbacks(`${collectionName.toLowerCase()}.edit.after`, document, oldDocument, currentUser);
document = await runCallbacks(
`${collectionName.toLowerCase()}.edit.after`,
document,
oldDocument,
currentUser
);
/*
@ -315,7 +420,13 @@ export const updateMutator = async ({ collection, documentId, selector, data, se
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.update.async`, properties });
await runCallbacksAsync({ name: '*.update.async', properties });
// OpenCRUD backwards compatibility
await runCallbacksAsync(`${collectionName.toLowerCase()}.edit.async`, document, oldDocument, currentUser, collection);
await runCallbacksAsync(
`${collectionName.toLowerCase()}.edit.async`,
document,
oldDocument,
currentUser,
collection
);
endDebugMutator(collectionName, 'Update', { modifier });
@ -327,7 +438,15 @@ export const updateMutator = async ({ collection, documentId, selector, data, se
Delete
*/
export const deleteMutator = async ({ collection, documentId, selector, currentUser, validate, context, document }) => {
export const deleteMutator = async ({
collection,
documentId,
selector,
currentUser,
validate,
context,
document,
}) => {
const { collectionName, typeName } = collection.options;
const schema = collection.simpleSchema()._schema;
// OpenCRUD backwards compatibility
@ -358,10 +477,22 @@ export const deleteMutator = async ({ collection, documentId, selector, currentU
if (validate) {
let validationErrors = [];
validationErrors = await runCallbacks({ name: `${typeName.toLowerCase()}.delete.validate`, iterator: validationErrors, properties });
validationErrors = await runCallbacks({ name: '*.delete.validate', iterator: validationErrors, properties });
validationErrors = await runCallbacks({
name: `${typeName.toLowerCase()}.delete.validate`,
iterator: validationErrors,
properties,
});
validationErrors = await runCallbacks({
name: '*.delete.validate',
iterator: validationErrors,
properties,
});
// OpenCRUD backwards compatibility
document = await runCallbacks(`${collectionName.toLowerCase()}.remove.validate`, document, currentUser);
document = await runCallbacks(
`${collectionName.toLowerCase()}.remove.validate`,
document,
currentUser
);
if (validationErrors.length) {
console.log(validationErrors); // eslint-disable-line no-console
@ -388,7 +519,11 @@ export const deleteMutator = async ({ collection, documentId, selector, currentU
Before
*/
await runCallbacks({ name: `${typeName.toLowerCase()}.delete.before`, iterator: document, properties });
await runCallbacks({
name: `${typeName.toLowerCase()}.delete.before`,
iterator: document,
properties,
});
await runCallbacks({ name: '*.delete.before', iterator: document, properties });
// OpenCRUD backwards compatibility
await runCallbacks(`${collectionName.toLowerCase()}.remove.before`, document, currentUser);
@ -415,7 +550,12 @@ export const deleteMutator = async ({ collection, documentId, selector, currentU
await runCallbacksAsync({ name: `${typeName.toLowerCase()}.delete.async`, properties });
await runCallbacksAsync({ name: '*.delete.async', properties });
// OpenCRUD backwards compatibility
await runCallbacksAsync(`${collectionName.toLowerCase()}.remove.async`, document, currentUser, collection);
await runCallbacksAsync(
`${collectionName.toLowerCase()}.remove.async`,
document,
currentUser,
collection
);
endDebugMutator(collectionName, 'Delete');

View file

@ -2,7 +2,7 @@ Package.describe({
name: 'vulcan:lib',
summary: 'Vulcan libraries.',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function(api) {
@ -46,7 +46,7 @@ Package.onUse(function(api) {
// 'aldeed:collection2-core@2.0.0',
'meteorhacks:picker@1.0.3',
'percolatestudio:synced-cron@1.1.0',
'meteorhacks:inject-initial@1.0.4'
'meteorhacks:inject-initial@1.0.4',
];
api.use(packages);

View file

@ -2,19 +2,14 @@ Package.describe({
name: 'vulcan:newsletter',
summary: 'Vulcan email newsletter package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
'vulcan:email@1.12.16'
]);
api.use(['vulcan:core@1.12.16', 'vulcan:email@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,25 +2,16 @@ Package.describe({
name: 'vulcan:payments',
summary: 'Vulcan payments package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'promise',
'vulcan:core@1.12.16',
'fourseven:scss@4.5.4',
]);
api.use(['promise', 'vulcan:core@1.12.16', 'fourseven:scss@4.5.4']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
api.addFiles([
'lib/stylesheets/style.scss',
]);
api.addFiles(['lib/stylesheets/style.scss']);
});

View file

@ -2,18 +2,14 @@ Package.describe({
name: 'vulcan:routing',
summary: 'Vulcan router package',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:lib@1.12.16',
]);
api.use(['vulcan:lib@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,26 +2,21 @@ Package.describe({
name: 'vulcan:subscribe',
summary: 'Subscribe to posts, users, etc. to be notified of new activity',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:core@1.12.16',
// dependencies on posts, categories are done with nested imports to reduce explicit dependencies
]);
api.use([
'vulcan:posts@1.12.16',
'vulcan:comments@1.12.16',
'vulcan:categories@1.12.16',
], {weak: true});
api.use(['vulcan:posts@1.12.16', 'vulcan:comments@1.12.16', 'vulcan:categories@1.12.16'], {
weak: true,
});
api.mainModule('lib/modules.js', ['client']);
api.mainModule('lib/modules.js', ['server']);
});

View file

@ -16,7 +16,7 @@ import '../components/forms/Url.jsx';
import '../components/forms/StaticText.jsx';
import '../components/forms/FormComponentInner.jsx';
import '../components/forms/FormControl.jsx'; // note: only used by old accounts package, remove soon?
import '../components/forms/FormItem.jsx';
import '../components/forms/FormItem.jsx';
import '../components/ui/Button.jsx';
import '../components/ui/Alert.jsx';

View file

@ -2,24 +2,16 @@ Package.describe({
name: 'vulcan:ui-bootstrap',
summary: 'Vulcan Bootstrap UI components.',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:lib@1.12.16',
'fourseven:scss@4.10.0',
]);
api.addFiles([
'lib/stylesheets/style.scss',
'lib/stylesheets/datetime.scss'
], 'client');
api.use(['vulcan:lib@1.12.16', 'fourseven:scss@4.10.0']);
api.addFiles(['lib/stylesheets/style.scss', 'lib/stylesheets/datetime.scss'], 'client');
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});

View file

@ -2,11 +2,11 @@ import SimpleSchema from 'simpl-schema';
import { Utils, getCollection, Connectors, Locales } from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core isn't loaded yet
///////////////////////////////////////
// Order for the Schema is as follows. Change as you see fit:
// 00.
// Order for the Schema is as follows. Change as you see fit:
// 00.
// 10. Display Name
// 20. Email
// 30. Bio
// 30. Bio
// 40. Slug
// 50. Website
// 60. Twitter username
@ -23,13 +23,13 @@ const createDisplayName = user => {
const linkedinFirstName = Utils.getNestedProperty(user, 'services.linkedin.firstName');
if (profileName) return profileName;
if (twitterName) return twitterName;
if (linkedinFirstName) return `${linkedinFirstName} ${Utils.getNestedProperty(user, 'services.linkedin.lastName')}`;
if (linkedinFirstName)
return `${linkedinFirstName} ${Utils.getNestedProperty(user, 'services.linkedin.lastName')}`;
if (user.username) return user.username;
if (user.email) return user.email.slice(0, user.email.indexOf('@'));
return undefined;
};
const adminGroup = {
name: 'admin',
order: 100,
@ -56,11 +56,16 @@ const schema = {
canUpdate: ['admins'],
canCreate: ['members'],
onCreate: ({ document: user }) => {
if ((!user.username) && user.services && user.services.twitter && user.services.twitter.screenName) {
if (
!user.username &&
user.services &&
user.services.twitter &&
user.services.twitter.screenName
) {
return user.services.twitter.screenName;
}
},
searchable: true
searchable: true,
},
emails: {
type: Array,
@ -85,7 +90,7 @@ const schema = {
canRead: ['admins'],
onCreate: () => {
return new Date();
}
},
},
isAdmin: {
type: Boolean,
@ -140,7 +145,7 @@ const schema = {
onCreate: ({ document: user }) => {
return createDisplayName(user);
},
searchable: true
searchable: true,
},
/**
The user's email. Modifiable.
@ -170,7 +175,7 @@ const schema = {
if (linkedinEmail) return linkedinEmail;
return undefined;
},
searchable: true
searchable: true,
// unique: true // note: find a way to fix duplicate accounts before enabling this
},
/**
@ -184,27 +189,27 @@ const schema = {
if (user.email) {
return getCollection('Users').avatar.hash(user.email);
}
}
},
},
avatarUrl: {
type: String,
optional: true,
canRead: ['guests'],
onCreate: ({ document: user }) => {
const twitterAvatar = Utils.getNestedProperty(user, 'services.twitter.profile_image_url_https');
const twitterAvatar = Utils.getNestedProperty(
user,
'services.twitter.profile_image_url_https'
);
const facebookId = Utils.getNestedProperty(user, 'services.facebook.id');
if (twitterAvatar) return twitterAvatar;
if (facebookId) return `https://graph.facebook.com/${facebookId}/picture?type=large`;
return undefined;
},
resolveAs: {
fieldName: 'avatarUrl',
type: 'String',
resolver: async (user, args, { Users }) => {
if (_.isEmpty(user)) return null;
if (user.avatarUrl) {
@ -215,9 +220,8 @@ const schema = {
const fullUser = await Users.loader.load(user._id);
return Users.avatar.getUrl(fullUser);
}
}
}
},
},
},
/**
The user's profile URL slug // TODO: change this when displayName changes
@ -256,7 +260,7 @@ const schema = {
if (user.services && user.services.twitter && user.services.twitter.screenName) {
return user.services.twitter.screenName;
}
}
},
},
/**
Groups
@ -270,15 +274,22 @@ const schema = {
canRead: ['guests'],
group: adminGroup,
form: {
options: function () {
const groups = _.without(_.keys(getCollection('Users').groups), 'guests', 'members', 'admins');
return groups.map(group => { return { value: group, label: group }; });
}
options: function() {
const groups = _.without(
_.keys(getCollection('Users').groups),
'guests',
'members',
'admins'
);
return groups.map(group => {
return { value: group, label: group };
});
},
},
},
'groups.$': {
type: String,
optional: true
optional: true,
},
// GraphQL only fields
@ -292,7 +303,7 @@ const schema = {
resolver: (user, args, { Users }) => {
return Users.getProfileUrl(user, true);
},
}
},
},
editUrl: {
@ -304,9 +315,8 @@ const schema = {
resolver: (user, args, { Users }) => {
return Users.getEditUrl(user, true);
},
}
}
},
},
};
export default schema;

View file

@ -1,5 +1,12 @@
import Users from '../modules/index.js';
import { runCallbacks, runCallbacksAsync, Utils, debug, debugGroup, debugGroupEnd } from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core isn't loaded yet
import {
runCallbacks,
runCallbacksAsync,
Utils,
debug,
debugGroup,
debugGroupEnd,
} from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core isn't loaded yet
import clone from 'lodash/clone';
// TODO: the following should use async/await, but async/await doesn't seem to work with Accounts.onCreateUser
@ -25,7 +32,9 @@ function onCreateUserCallback(options, user) {
_.keys(options).forEach(fieldName => {
var field = schema[fieldName];
if (!field || !Users.canCreateField(user, field)) {
throw new Error(Utils.encodeIntlError({ id: 'app.disallowed_property_detected', value: fieldName }));
throw new Error(
Utils.encodeIntlError({ id: 'app.disallowed_property_detected', value: fieldName })
);
}
});

View file

@ -2,20 +2,16 @@ Package.describe({
name: 'vulcan:users',
summary: 'Vulcan permissions.',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'vulcan:lib@1.12.16'
]);
api.use(['vulcan:lib@1.12.16']);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});
Package.onTest(function(api) {
api.use('vulcan:users');

View file

@ -2,20 +2,17 @@ Package.describe({
name: 'vulcan:voting',
summary: 'Vulcan scoring package.',
version: '1.12.16',
git: 'https://github.com/VulcanJS/Vulcan.git'
git: 'https://github.com/VulcanJS/Vulcan.git',
});
Package.onUse(function (api) {
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'fourseven:scss@4.10.0',
'vulcan:core@1.12.16',
'vulcan:i18n@1.12.16',
], ['client', 'server']);
api.use(
['fourseven:scss@4.10.0', 'vulcan:core@1.12.16', 'vulcan:i18n@1.12.16'],
['client', 'server'],
);
api.mainModule('lib/server/main.js', 'server');
api.mainModule('lib/client/main.js', 'client');
});