mirror of
https://github.com/vale981/Vulcan
synced 2025-03-04 17:21:37 -05:00
Merge Apollo2 branch
This commit is contained in:
commit
022a72e41b
258 changed files with 4458 additions and 11839 deletions
|
@ -52,7 +52,8 @@
|
||||||
1,
|
1,
|
||||||
"single"
|
"single"
|
||||||
],
|
],
|
||||||
"react/prop-types": 0
|
"react/prop-types": 0,
|
||||||
|
"semi": [1, "always"]
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
|
|
17
.github/stale.yml
vendored
Normal file
17
.github/stale.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 7
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- pinned
|
||||||
|
- security
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
|
@ -1 +1 @@
|
||||||
METEOR@1.7.0.5
|
METEOR@1.8
|
||||||
|
|
|
@ -1,79 +1,82 @@
|
||||||
accounts-base@1.4.2
|
accounts-base@1.4.3
|
||||||
accounts-password@1.5.1
|
accounts-password@1.5.1
|
||||||
allow-deny@1.1.0
|
allow-deny@1.1.0
|
||||||
apollo@3.0.0
|
apollo@3.0.0
|
||||||
autoupdate@1.4.0
|
autoupdate@1.5.0
|
||||||
babel-compiler@7.1.1
|
babel-compiler@7.2.1
|
||||||
babel-runtime@1.2.4
|
babel-runtime@1.3.0
|
||||||
base64@1.0.11
|
base64@1.0.11
|
||||||
binary-heap@1.0.10
|
binary-heap@1.0.11
|
||||||
blaze-tools@1.0.10
|
blaze-tools@1.0.10
|
||||||
boilerplate-generator@1.5.0
|
boilerplate-generator@1.6.0
|
||||||
buffer@0.0.0
|
buffer@0.0.0
|
||||||
caching-compiler@1.1.12
|
caching-compiler@1.2.0
|
||||||
caching-html-compiler@1.1.2
|
caching-html-compiler@1.1.3
|
||||||
callback-hook@1.1.0
|
callback-hook@1.1.0
|
||||||
check@1.3.1
|
check@1.3.1
|
||||||
ddp@1.4.0
|
ddp@1.4.0
|
||||||
ddp-client@2.3.2
|
ddp-client@2.3.3
|
||||||
ddp-common@1.4.0
|
ddp-common@1.4.0
|
||||||
ddp-rate-limiter@1.0.7
|
ddp-rate-limiter@1.0.7
|
||||||
ddp-server@2.2.0
|
ddp-server@2.2.0
|
||||||
deps@1.0.12
|
deps@1.0.12
|
||||||
diff-sequence@1.1.0
|
diff-sequence@1.1.0
|
||||||
dynamic-import@0.4.2
|
dynamic-import@0.5.0
|
||||||
ecmascript@0.11.1
|
ecmascript@0.12.1
|
||||||
ecmascript-runtime@0.7.0
|
ecmascript-runtime@0.7.0
|
||||||
ecmascript-runtime-client@0.7.1
|
ecmascript-runtime-client@0.8.0
|
||||||
ecmascript-runtime-server@0.7.0
|
ecmascript-runtime-server@0.7.1
|
||||||
ejson@1.1.0
|
ejson@1.1.0
|
||||||
email@1.2.3
|
email@1.2.3
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
fourseven:scss@4.5.4
|
fetch@0.1.0
|
||||||
|
fourseven:scss@4.10.0
|
||||||
geojson-utils@1.0.10
|
geojson-utils@1.0.10
|
||||||
hot-code-push@1.0.4
|
hot-code-push@1.0.4
|
||||||
html-tools@1.0.11
|
html-tools@1.0.11
|
||||||
htmljs@1.0.11
|
htmljs@1.0.11
|
||||||
http@1.4.1
|
http@1.4.1
|
||||||
id-map@1.1.0
|
id-map@1.1.0
|
||||||
lmieulet:meteor-coverage@1.1.4
|
inter-process-messaging@0.1.0
|
||||||
|
lmieulet:meteor-coverage@2.0.2
|
||||||
localstorage@1.2.0
|
localstorage@1.2.0
|
||||||
logging@1.1.20
|
logging@1.1.20
|
||||||
meteor@1.9.0
|
meteor@1.9.2
|
||||||
meteorhacks:inject-initial@1.0.4
|
meteorhacks:inject-initial@1.0.4
|
||||||
meteorhacks:picker@1.0.3
|
meteorhacks:picker@1.0.3
|
||||||
meteortesting:browser-tests@1.0.0
|
meteortesting:browser-tests@1.1.0
|
||||||
meteortesting:mocha@1.0.0
|
meteortesting:mocha@1.0.1
|
||||||
minifier-css@1.3.1
|
meteortesting:mocha-core@1.0.1
|
||||||
minifier-js@2.3.5
|
minifier-css@1.4.0
|
||||||
minimongo@1.4.4
|
minifier-js@2.4.0
|
||||||
modern-browsers@0.1.1
|
minimongo@1.4.5
|
||||||
modules@0.12.2
|
modern-browsers@0.1.2
|
||||||
modules-runtime@0.10.0
|
modules@0.13.0
|
||||||
mongo@1.5.0
|
modules-runtime@0.10.3
|
||||||
|
mongo@1.6.0
|
||||||
|
mongo-decimal@0.1.0
|
||||||
mongo-dev-server@1.1.0
|
mongo-dev-server@1.1.0
|
||||||
mongo-id@1.0.7
|
mongo-id@1.0.7
|
||||||
npm-bcrypt@0.9.3
|
npm-bcrypt@0.9.3
|
||||||
npm-mongo@3.0.7
|
npm-mongo@3.1.1
|
||||||
ordered-dict@1.1.0
|
ordered-dict@1.1.0
|
||||||
percolatestudio:synced-cron@1.1.0
|
percolatestudio:synced-cron@1.1.0
|
||||||
practicalmeteor:mocha-core@1.0.1
|
|
||||||
promise@0.11.1
|
promise@0.11.1
|
||||||
random@1.1.0
|
random@1.1.0
|
||||||
rate-limit@1.0.9
|
rate-limit@1.0.9
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
reload@1.2.0
|
reload@1.2.0
|
||||||
retry@1.1.0
|
retry@1.1.0
|
||||||
routepolicy@1.0.13
|
routepolicy@1.1.0
|
||||||
server-render@0.3.1
|
server-render@0.3.1
|
||||||
service-configuration@1.0.11
|
service-configuration@1.0.11
|
||||||
sha@1.0.9
|
sha@1.0.9
|
||||||
shell-server@0.3.1
|
shell-server@0.4.0
|
||||||
socket-stream-client@0.2.2
|
socket-stream-client@0.2.2
|
||||||
spacebars-compiler@1.1.3
|
spacebars-compiler@1.1.3
|
||||||
srp@1.0.10
|
srp@1.0.12
|
||||||
standard-minifier-css@1.4.1
|
standard-minifier-css@1.5.1
|
||||||
standard-minifier-js@2.3.4
|
standard-minifier-js@2.4.0
|
||||||
static-html@1.2.2
|
static-html@1.2.2
|
||||||
templating-tools@1.1.2
|
templating-tools@1.1.2
|
||||||
tracker@1.2.0
|
tracker@1.2.0
|
||||||
|
@ -85,6 +88,7 @@ vulcan:email@1.12.8
|
||||||
vulcan:i18n@1.12.8
|
vulcan:i18n@1.12.8
|
||||||
vulcan:i18n-en-us@1.12.8
|
vulcan:i18n-en-us@1.12.8
|
||||||
vulcan:lib@1.12.8
|
vulcan:lib@1.12.8
|
||||||
|
vulcan:routing@1.12.8
|
||||||
vulcan:users@1.12.8
|
vulcan:users@1.12.8
|
||||||
webapp@1.6.1
|
webapp@1.7.0
|
||||||
webapp-hashing@1.0.9
|
webapp-hashing@1.0.9
|
||||||
|
|
21
.prettierrc.js
Normal file
21
.prettierrc.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {esNextPaths} = require('./.vulcan/shared/pathsByLanguageVersion');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bracketSpacing: false,
|
||||||
|
singleQuote: true,
|
||||||
|
jsxBracketSameLine: true,
|
||||||
|
trailingComma: 'es5',
|
||||||
|
printWidth: 80,
|
||||||
|
parser: 'babylon',
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: esNextPaths,
|
||||||
|
options: {
|
||||||
|
trailingComma: 'all',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
76
.vulcan/prettier/index.js
Normal file
76
.vulcan/prettier/index.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Based on similar script in Jest
|
||||||
|
// https://github.com/facebook/jest/blob/a7acc5ae519613647ff2c253dd21933d6f94b47f/scripts/prettier.js
|
||||||
|
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const glob = require('glob');
|
||||||
|
const prettier = require('prettier');
|
||||||
|
const fs = require('fs');
|
||||||
|
const listChangedFiles = require('../shared/listChangedFiles');
|
||||||
|
const prettierConfigPath = require.resolve('../../.prettierrc');
|
||||||
|
|
||||||
|
const mode = process.argv[2] || 'check';
|
||||||
|
const shouldWrite = mode === 'write' || mode === 'write-changed';
|
||||||
|
const onlyChanged = mode === 'check-changed' || mode === 'write-changed';
|
||||||
|
|
||||||
|
const changedFiles = onlyChanged ? listChangedFiles() : null;
|
||||||
|
let didWarn = false;
|
||||||
|
let didError = false;
|
||||||
|
|
||||||
|
const files = glob
|
||||||
|
.sync('**/*.js', {ignore: '**/node_modules/**'})
|
||||||
|
.filter(f => !onlyChanged || changedFiles.has(f));
|
||||||
|
|
||||||
|
if (!files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
const options = prettier.resolveConfig.sync(file, {
|
||||||
|
config: prettierConfigPath,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const input = fs.readFileSync(file, 'utf8');
|
||||||
|
if (shouldWrite) {
|
||||||
|
const output = prettier.format(input, options);
|
||||||
|
if (output !== input) {
|
||||||
|
fs.writeFileSync(file, output, 'utf8');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!prettier.check(input, options)) {
|
||||||
|
if (!didWarn) {
|
||||||
|
console.log(
|
||||||
|
'\n' +
|
||||||
|
chalk.red(
|
||||||
|
` This project uses prettier to format all JavaScript code.\n`
|
||||||
|
) +
|
||||||
|
chalk.dim(` Please run `) +
|
||||||
|
chalk.reset('yarn prettier-all') +
|
||||||
|
chalk.dim(
|
||||||
|
` and add changes to files listed below to your commit:`
|
||||||
|
) +
|
||||||
|
`\n\n`
|
||||||
|
);
|
||||||
|
didWarn = true;
|
||||||
|
}
|
||||||
|
console.log(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
didError = true;
|
||||||
|
console.log('\n\n' + error.message);
|
||||||
|
console.log(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (didWarn || didError) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
36
.vulcan/shared/listChangedFiles.js
Normal file
36
.vulcan/shared/listChangedFiles.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const execFileSync = require('child_process').execFileSync;
|
||||||
|
|
||||||
|
const exec = (command, args) => {
|
||||||
|
console.log('> ' + [command].concat(args).join(' '));
|
||||||
|
const options = {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: process.env,
|
||||||
|
stdio: 'pipe',
|
||||||
|
encoding: 'utf-8',
|
||||||
|
};
|
||||||
|
return execFileSync(command, args, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
const execGitCmd = args =>
|
||||||
|
exec('git', args)
|
||||||
|
.trim()
|
||||||
|
.toString()
|
||||||
|
.split('\n');
|
||||||
|
|
||||||
|
const listChangedFiles = () => {
|
||||||
|
const mergeBase = execGitCmd(['merge-base', 'HEAD', 'devel']);
|
||||||
|
return new Set([
|
||||||
|
...execGitCmd(['diff', '--name-only', '--diff-filter=ACMRTUB', mergeBase]),
|
||||||
|
...execGitCmd(['ls-files', '--others', '--exclude-standard']),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = listChangedFiles;
|
18
.vulcan/shared/pathsByLanguageVersion.js
Normal file
18
.vulcan/shared/pathsByLanguageVersion.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Files that are transformed and can use ES6/Flow/JSX.
|
||||||
|
const esNextPaths = [
|
||||||
|
// Internal forwarding modules
|
||||||
|
'packages/*/*.js',
|
||||||
|
'packages/*/*.jsx',
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
esNextPaths,
|
||||||
|
};
|
12
README.md
12
README.md
|
@ -50,18 +50,16 @@ You can find the even older, non-React version of Telescope on the [legacy](http
|
||||||
|
|
||||||
This project exists thanks to all the people who contribute.
|
This project exists thanks to all the people who contribute.
|
||||||
|
|
||||||
<a href="graphs/contributors"><img src="https://opencollective.com/vulcan/contributors.svg?width=890&button=false" /></a>
|
<a href="https://github.com/VulcanJS/Vulcan/graphs/contributors"><img src="https://opencollective.com/vulcan/contributors.svg?width=890&button=false" /></a>
|
||||||
|
|
||||||
### Backers
|
### Backers
|
||||||
|
|
||||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/vulcan#backer)]
|
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/vulcan#contribute)]
|
||||||
|
|
||||||
<a href="https://opencollective.com/vulcan#backers" target="_blank"><img src="https://opencollective.com/vulcan/backers.svg?width=890"></a>
|
<a href="https://opencollective.com/vulcan#contributors" target="_blank"><img src="https://opencollective.com/vulcan/backers.svg?width=890"></a>
|
||||||
|
|
||||||
### Sponsors
|
### Sponsors
|
||||||
|
|
||||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/vulcan#sponsor)]
|
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/vulcan#contribute)]
|
||||||
|
|
||||||
<a href="https://opencollective.com/vulcan/sponsor/0/website" target="_blank"><img src="https://opencollective.com/vulcan/sponsor/0/avatar.svg"></a>
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/vulcan#contributors" target="_blank"><img src="https://opencollective.com/vulcan/sponsors.svg?width=890"></a>
|
||||||
|
|
9718
package-lock.json
generated
9718
package-lock.json
generated
File diff suppressed because it is too large
Load diff
27
package.json
27
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Vulcan",
|
"name": "Vulcan",
|
||||||
"version": "1.12.8",
|
"version": "1.12.13",
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": "^3.0"
|
"npm": "^3.0"
|
||||||
},
|
},
|
||||||
|
@ -8,7 +8,15 @@
|
||||||
"start": "meteor --settings settings.json",
|
"start": "meteor --settings settings.json",
|
||||||
"lint": "eslint --cache --ext .jsx,js packages",
|
"lint": "eslint --cache --ext .jsx,js packages",
|
||||||
"test-unit": "TEST_WATCH=1 meteor test-packages ./packages/* --port 3002 --driver-package meteortesting:mocha --raw-logs",
|
"test-unit": "TEST_WATCH=1 meteor test-packages ./packages/* --port 3002 --driver-package meteortesting:mocha --raw-logs",
|
||||||
"test": "npm run test-unit"
|
"test": "npm run test-unit",
|
||||||
|
"prettier": "node ./.vulcan/prettier/index.js write-changed",
|
||||||
|
"prettier-all": "node ./.vulcan/prettier/index.js write"
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "npm run lint",
|
||||||
|
"pre-push": "npm run prettier"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.0.0-beta.55",
|
"@babel/runtime": "7.0.0-beta.55",
|
||||||
|
@ -17,12 +25,13 @@
|
||||||
"apollo-client": "^2.4.2",
|
"apollo-client": "^2.4.2",
|
||||||
"apollo-engine": "^0.5.4",
|
"apollo-engine": "^0.5.4",
|
||||||
"apollo-errors": "^1.4.0",
|
"apollo-errors": "^1.4.0",
|
||||||
|
"apollo-link-error": "^1.1.5",
|
||||||
"apollo-link-schema": "^1.1.1",
|
"apollo-link-schema": "^1.1.1",
|
||||||
"apollo-link-state": "^0.4.2",
|
"apollo-link-state": "^0.4.2",
|
||||||
"apollo-server": "^2.1.0",
|
"apollo-server": "^2.1.0",
|
||||||
"apollo-server-express": "^2.1.0",
|
"apollo-server-express": "^2.1.0",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"bcrypt": "^2.0.1",
|
"bcrypt": "^3.0.2",
|
||||||
"body-parser": "^1.18.2",
|
"body-parser": "^1.18.2",
|
||||||
"chalk": "2.2.0",
|
"chalk": "2.2.0",
|
||||||
"classnames": "^2.2.3",
|
"classnames": "^2.2.3",
|
||||||
|
@ -52,11 +61,10 @@
|
||||||
"import": "0.0.6",
|
"import": "0.0.6",
|
||||||
"intl": "^1.2.4",
|
"intl": "^1.2.4",
|
||||||
"intl-locales-supported": "^1.0.0",
|
"intl-locales-supported": "^1.0.0",
|
||||||
"juice": "^1.11.0",
|
"juice": "^5.1.0",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"mailchimp": "^1.1.6",
|
"mailchimp": "^1.1.6",
|
||||||
"marked": "^0.3.9",
|
"marked": "^0.3.9",
|
||||||
"metascraper": "^1.0.6",
|
|
||||||
"meteor-node-stubs": "^0.2.3",
|
"meteor-node-stubs": "^0.2.3",
|
||||||
"mingo": "^2.2.0",
|
"mingo": "^2.2.0",
|
||||||
"moment": "^2.13.0",
|
"moment": "^2.13.0",
|
||||||
|
@ -64,7 +72,7 @@
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-addons-pure-render-mixin": "^15.4.1",
|
"react-addons-pure-render-mixin": "^15.4.1",
|
||||||
"react-apollo": "^2.2.4",
|
"react-apollo": "^2.2.4",
|
||||||
"react-bootstrap": "^0.32.0",
|
"react-bootstrap": "^1.0.0-beta.3",
|
||||||
"react-bootstrap-datetimepicker": "0.0.22",
|
"react-bootstrap-datetimepicker": "0.0.22",
|
||||||
"react-cookie": "^2.1.4",
|
"react-cookie": "^2.1.4",
|
||||||
"react-datetime": "^2.11.1",
|
"react-datetime": "^2.11.1",
|
||||||
|
@ -85,7 +93,6 @@
|
||||||
"redux": "^3.6.0",
|
"redux": "^3.6.0",
|
||||||
"rss": "^1.2.1",
|
"rss": "^1.2.1",
|
||||||
"sanitize-html": "^1.16.3",
|
"sanitize-html": "^1.16.3",
|
||||||
"sendy-api": "^0.1.0",
|
|
||||||
"simpl-schema": "^1.4.2",
|
"simpl-schema": "^1.4.2",
|
||||||
"speakingurl": "^9.0.0",
|
"speakingurl": "^9.0.0",
|
||||||
"stripe": "^4.23.1",
|
"stripe": "^4.23.1",
|
||||||
|
@ -98,9 +105,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.3.6",
|
"autoprefixer": "^6.3.6",
|
||||||
"babel-eslint": "^7.0.0",
|
"babel-eslint": "^7.0.0",
|
||||||
|
"babylon": "^6.18.0",
|
||||||
"chromedriver": "^2.40.0",
|
"chromedriver": "^2.40.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
"enzyme-adapter-react-16": "^1.1.1",
|
"enzyme-adapter-react-16.3": "^1.4.0",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^3.19.0",
|
||||||
"eslint-config-airbnb": "^13.0.0",
|
"eslint-config-airbnb": "^13.0.0",
|
||||||
"eslint-config-meteor": "0.0.9",
|
"eslint-config-meteor": "0.0.9",
|
||||||
|
@ -113,8 +121,11 @@
|
||||||
"eslint-plugin-prettier": "^2.5.0",
|
"eslint-plugin-prettier": "^2.5.0",
|
||||||
"eslint-plugin-react": "^6.7.1",
|
"eslint-plugin-react": "^6.7.1",
|
||||||
"expect": "^23.4.0",
|
"expect": "^23.4.0",
|
||||||
|
"glob": "^7.1.3",
|
||||||
|
"husky": "^1.2.0",
|
||||||
"jsdom": "^11.11.0",
|
"jsdom": "^11.11.0",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
|
"prettier": "^1.15.2",
|
||||||
"selenium-webdriver": "^4.0.0-alpha.1"
|
"selenium-webdriver": "^4.0.0-alpha.1"
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
summary: "Generates the boilerplate html from program's manifest",
|
summary: 'Generates the boilerplate html from program\'s manifest',
|
||||||
version: '1.5.0',
|
version: '1.6.0',
|
||||||
name: 'boilerplate-generator'
|
name: 'boilerplate-generator'
|
||||||
});
|
});
|
||||||
|
|
||||||
Npm.depends({
|
Npm.depends({
|
||||||
"combined-stream2": "1.1.2"
|
'combined-stream2': '1.1.2'
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(api => {
|
Package.onUse(api => {
|
||||||
|
|
12
packages/vulcan-accounts/imports/emailTemplates.js
Normal file
12
packages/vulcan-accounts/imports/emailTemplates.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import {Accounts} from 'meteor/accounts-base';
|
||||||
|
import {getSetting} from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
// the emailTemplates are made available by accounts-password, which we don't want to depend on
|
||||||
|
if (Package['accounts-password']) {
|
||||||
|
Accounts.emailTemplates.siteName = getSetting('public.title', '');
|
||||||
|
Accounts.emailTemplates.from =
|
||||||
|
getSetting('public.title', '') +
|
||||||
|
' <' +
|
||||||
|
getSetting('defaultEmail', 'no-reply@example.com') +
|
||||||
|
'>';
|
||||||
|
}
|
|
@ -80,7 +80,7 @@ export function validatePassword(password = '', showMessage, clearMessage){
|
||||||
if (password.length >= Accounts.ui._options.minimumPasswordLength) {
|
if (password.length >= Accounts.ui._options.minimumPasswordLength) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
const errMsg = 'accounts.error_minchar'
|
const errMsg = 'accounts.error_minchar';
|
||||||
showMessage(errMsg, 'warning', false, 'password');
|
showMessage(errMsg, 'warning', false, 'password');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class AccountsEnrollAccount extends PureComponent {
|
||||||
|
|
||||||
AccountsEnrollAccount.contextTypes = {
|
AccountsEnrollAccount.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
}
|
};
|
||||||
|
|
||||||
AccountsEnrollAccount.propsTypes = {
|
AccountsEnrollAccount.propsTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
|
|
|
@ -7,7 +7,7 @@ const autocompleteValues = {
|
||||||
'usernameOrEmail': 'email',
|
'usernameOrEmail': 'email',
|
||||||
'email': 'email',
|
'email': 'email',
|
||||||
'password': 'current-password'
|
'password': 'current-password'
|
||||||
}
|
};
|
||||||
|
|
||||||
export class AccountsField extends PureComponent {
|
export class AccountsField extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -75,4 +75,4 @@ AccountsField.propTypes = {
|
||||||
onChange: PropTypes.func
|
onChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
registerComponent('AccountsField', AccountsField)
|
registerComponent('AccountsField', AccountsField);
|
|
@ -35,9 +35,9 @@ export class AccountsLoginFormInner extends TrackerComponent {
|
||||||
return () => {
|
return () => {
|
||||||
props.client.resetStore().then(() => {
|
props.client.resetStore().then(() => {
|
||||||
hook();
|
hook();
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const postLogInAndThen = hook => {
|
const postLogInAndThen = hook => {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -47,9 +47,9 @@ export class AccountsLoginFormInner extends TrackerComponent {
|
||||||
} else { // or else execute the hook
|
} else { // or else execute the hook
|
||||||
hook();
|
hook();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const doNothing = () => {};
|
const doNothing = () => {};
|
||||||
|
|
||||||
|
@ -1008,16 +1008,16 @@ export class AccountsLoginFormInner extends TrackerComponent {
|
||||||
AccountsLoginFormInner.propTypes = {
|
AccountsLoginFormInner.propTypes = {
|
||||||
showSignInLink: PropTypes.bool,
|
showSignInLink: PropTypes.bool,
|
||||||
showSignUpLink: PropTypes.bool,
|
showSignUpLink: PropTypes.bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
AccountsLoginFormInner.defaultProps = {
|
AccountsLoginFormInner.defaultProps = {
|
||||||
showSignInLink: true,
|
showSignInLink: true,
|
||||||
showSignUpLink: true,
|
showSignUpLink: true,
|
||||||
redirect: true,
|
redirect: true,
|
||||||
}
|
};
|
||||||
|
|
||||||
AccountsLoginFormInner.contextTypes = {
|
AccountsLoginFormInner.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
}
|
};
|
||||||
|
|
||||||
registerComponent('AccountsLoginFormInner', AccountsLoginFormInner, withCurrentUser, withApollo);
|
registerComponent('AccountsLoginFormInner', AccountsLoginFormInner, withCurrentUser, withApollo);
|
||||||
|
|
|
@ -29,7 +29,7 @@ class AccountsResetPassword extends PureComponent {
|
||||||
|
|
||||||
AccountsResetPassword.contextTypes = {
|
AccountsResetPassword.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
}
|
};
|
||||||
|
|
||||||
AccountsResetPassword.propsTypes = {
|
AccountsResetPassword.propsTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class AccountsStateSwitcher extends React.Component {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
formState: props.formState
|
formState: props.formState
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
switchToSignUp = (event) => {
|
switchToSignUp = (event) => {
|
||||||
|
@ -92,7 +92,7 @@ export class AccountsStateSwitcher extends React.Component {
|
||||||
switchToSignOut,
|
switchToSignOut,
|
||||||
cancelResetPassword,
|
cancelResetPassword,
|
||||||
switchToProfile,
|
switchToProfile,
|
||||||
}
|
};
|
||||||
return (
|
return (
|
||||||
<Components.AccountsLoginFormInner {...this.props} formState={this.state.formState} handlers={handlers} />
|
<Components.AccountsLoginFormInner {...this.props} formState={this.state.formState} handlers={handlers} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,11 +21,11 @@ class TrackerComponent extends React.Component {
|
||||||
|
|
||||||
autorun(fn) { return this.__comps.push(Tracker.autorun(c => {
|
autorun(fn) { return this.__comps.push(Tracker.autorun(c => {
|
||||||
this.__live = true; fn(c); this.__live = false;
|
this.__live = true; fn(c); this.__live = false;
|
||||||
}))}
|
}));}
|
||||||
|
|
||||||
componentDidUpdate() { !this.__live && this.__comps.forEach(c => {
|
componentDidUpdate() { !this.__live && this.__comps.forEach(c => {
|
||||||
c.invalidated = c.stopped = false; !c.invalidate();
|
c.invalidated = c.stopped = false; !c.invalidate();
|
||||||
})}
|
});}
|
||||||
|
|
||||||
subscriptionsReady() {
|
subscriptionsReady() {
|
||||||
return !Object.keys(this.__subs).some(id => !this.__subs[id].ready());
|
return !Object.keys(this.__subs).some(id => !this.__subs[id].ready());
|
||||||
|
|
|
@ -6,11 +6,11 @@ import { intlShape } from 'meteor/vulcan:i18n';
|
||||||
|
|
||||||
class AccountsVerifyEmail extends PureComponent {
|
class AccountsVerifyEmail extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
pending: true,
|
pending: true,
|
||||||
error: null
|
error: null
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -32,7 +32,7 @@ class AccountsVerifyEmail extends PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(this.state.pending) {
|
if(this.state.pending) {
|
||||||
return <Components.Loading />
|
return <Components.Loading />;
|
||||||
} else if(this.state.error) {
|
} else if(this.state.error) {
|
||||||
return (
|
return (
|
||||||
<div className='password-reset-form'>
|
<div className='password-reset-form'>
|
||||||
|
@ -51,7 +51,7 @@ class AccountsVerifyEmail extends PureComponent {
|
||||||
|
|
||||||
AccountsVerifyEmail.contextTypes = {
|
AccountsVerifyEmail.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
}
|
};
|
||||||
|
|
||||||
AccountsVerifyEmail.propsTypes = {
|
AccountsVerifyEmail.propsTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import './imports/components.js';
|
||||||
import './imports/login_session.js';
|
import './imports/login_session.js';
|
||||||
import './imports/routes.js';
|
import './imports/routes.js';
|
||||||
import './imports/oauth_config.js';
|
import './imports/oauth_config.js';
|
||||||
|
import './imports/emailTemplates.js';
|
||||||
import { redirect, STATES } from './imports/helpers.js';
|
import { redirect, STATES } from './imports/helpers.js';
|
||||||
import './imports/api/server/servicesListPublication.js';
|
import './imports/api/server/servicesListPublication.js';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:accounts',
|
name: 'vulcan:accounts',
|
||||||
version: '1.12.8',
|
version: '1.12.13',
|
||||||
summary: 'Accounts UI for React in Meteor 1.3+',
|
summary: 'Accounts UI for React in Meteor 1.3+',
|
||||||
git: 'https://github.com/studiointeract/accounts-ui',
|
git: 'https://github.com/studiointeract/accounts-ui',
|
||||||
documentation: 'README.md'
|
documentation: 'README.md'
|
||||||
|
@ -9,7 +9,7 @@ Package.describe({
|
||||||
Package.onUse(function(api) {
|
Package.onUse(function(api) {
|
||||||
api.versionsFrom('1.6.1');
|
api.versionsFrom('1.6.1');
|
||||||
|
|
||||||
api.use('vulcan:core@1.12.8');
|
api.use('vulcan:core@1.12.13');
|
||||||
|
|
||||||
api.use('ecmascript');
|
api.use('ecmascript');
|
||||||
api.use('tracker');
|
api.use('tracker');
|
||||||
|
|
|
@ -19,6 +19,6 @@ const AdminHome = ({ currentUser }) =>
|
||||||
showEdit={true}
|
showEdit={true}
|
||||||
/>
|
/>
|
||||||
</Components.ShowIf>
|
</Components.ShowIf>
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
export default withCurrentUser(AdminHome);
|
export default withCurrentUser(AdminHome);
|
|
@ -9,14 +9,14 @@ const AdminUsersActions = ({ document: user, removeMutation }) =>{
|
||||||
if (confirm(`Delete user ${Users.getDisplayName(user)}?`)) {
|
if (confirm(`Delete user ${Users.getDisplayName(user)}?`)) {
|
||||||
removeMutation({documentId: user._id});
|
removeMutation({documentId: user._id});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return <Components.Button variant="primary" onClick={deleteHandler}>Delete</Components.Button>
|
return <Components.Button variant="primary" onClick={deleteHandler}>Delete</Components.Button>;
|
||||||
}
|
};
|
||||||
|
|
||||||
const removeOptions = {
|
const removeOptions = {
|
||||||
collection: Users
|
collection: Users
|
||||||
}
|
};
|
||||||
|
|
||||||
export default withRemove(removeOptions)(AdminUsersActions);
|
export default withRemove(removeOptions)(AdminUsersActions);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,6 @@ import moment from 'moment';
|
||||||
const AdminUsersCreated = ({ document: user }) =>
|
const AdminUsersCreated = ({ document: user }) =>
|
||||||
<div>
|
<div>
|
||||||
{moment(new Date(user.createdAt)).format('MM/DD/YY')}
|
{moment(new Date(user.createdAt)).format('MM/DD/YY')}
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
export default AdminUsersCreated;
|
export default AdminUsersCreated;
|
|
@ -3,6 +3,6 @@ import Users from 'meteor/vulcan:users';
|
||||||
import { Components } from 'meteor/vulcan:core';
|
import { Components } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const AdminUsersEmail = ({ document: user }) =>
|
const AdminUsersEmail = ({ document: user }) =>
|
||||||
<a href={`mailto:${Users.getEmail(user)}`}>{Users.getEmail(user)}</a>
|
<a href={`mailto:${Users.getEmail(user)}`}>{Users.getEmail(user)}</a>;
|
||||||
|
|
||||||
export default AdminUsersEmail;
|
export default AdminUsersEmail;
|
|
@ -13,6 +13,6 @@ const AdminUsersName = ({ document: user, flash }) =>
|
||||||
|
|
||||||
{_.rest(Users.getGroups(user)).map(group => <code key={group}>{group}</code>)}
|
{_.rest(Users.getGroups(user)).map(group => <code key={group}>{group}</code>)}
|
||||||
|
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
export default AdminUsersName;
|
export default AdminUsersName;
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:admin',
|
name: 'vulcan:admin',
|
||||||
summary: 'Vulcan components package',
|
summary: 'Vulcan components package',
|
||||||
version: '1.12.8',
|
version: '1.12.13',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git'
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ Package.onUse(function (api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
|
|
||||||
'fourseven:scss@4.5.0',
|
'fourseven:scss@4.10.0',
|
||||||
'dynamic-import@0.1.1',
|
'dynamic-import@0.1.1',
|
||||||
// Vulcan packages
|
// Vulcan packages
|
||||||
'vulcan:core@1.12.8',
|
'vulcan:core@1.12.13',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export * from '../modules/index.js'
|
export * from '../modules/index.js';
|
||||||
export * from './make_cloudinary.js'
|
export * from './make_cloudinary.js';
|
||||||
|
|
|
@ -2,4 +2,4 @@ import { addCustomFields } from '../modules/index.js';
|
||||||
|
|
||||||
export const makeCloudinary = ({collection, fieldName}) => {
|
export const makeCloudinary = ({collection, fieldName}) => {
|
||||||
addCustomFields(collection);
|
addCustomFields(collection);
|
||||||
}
|
};
|
|
@ -41,7 +41,7 @@ export const addCustomFields = collection => {
|
||||||
type: 'String',
|
type: 'String',
|
||||||
arguments: 'format: String',
|
arguments: 'format: String',
|
||||||
resolver: (document, {format}, context) => {
|
resolver: (document, {format}, context) => {
|
||||||
const image = format ? _.findWhere(document.cloudinaryUrls, {name: format}) : document.cloudinaryUrls[0]
|
const image = format ? _.findWhere(document.cloudinaryUrls, {name: format}) : document.cloudinaryUrls[0];
|
||||||
return image && image.url;
|
return image && image.url;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -50,4 +50,4 @@ export const addCustomFields = collection => {
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
};
|
|
@ -1,3 +1,3 @@
|
||||||
export * from './cloudinary.js';
|
export * from './cloudinary.js';
|
||||||
export * from '../modules/index.js';
|
export * from '../modules/index.js';
|
||||||
export * from './make_cloudinary.js'
|
export * from './make_cloudinary.js';
|
||||||
|
|
|
@ -39,4 +39,4 @@ export const makeCloudinary = ({collection, fieldName}) => {
|
||||||
}
|
}
|
||||||
addCallback(`${collection.options.collectionName.toLowerCase()}.edit.sync`, cacheImageOnEdit);
|
addCallback(`${collection.options.collectionName.toLowerCase()}.edit.sync`, cacheImageOnEdit);
|
||||||
|
|
||||||
}
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:cloudinary',
|
name: 'vulcan:cloudinary',
|
||||||
summary: 'Vulcan file upload package.',
|
summary: 'Vulcan file upload package.',
|
||||||
version: '1.12.8',
|
version: '1.12.13',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git'
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom('1.6.1');
|
api.versionsFrom('1.6.1');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.12.8'
|
'vulcan:core@1.12.13'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule('lib/client/main.js', 'client');
|
api.mainModule('lib/client/main.js', 'client');
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import { addCallback, getActions } from 'meteor/vulcan:lib';
|
import { addCallback, getActions } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Core callbacks
|
Core callbacks
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Clear flash messages marked as seen when the route changes
|
* @summary Clear flash messages marked as seen when the route changes
|
||||||
* @param {Object} Item needed by `runCallbacks` to iterate on, unused here
|
* @param {Object} props
|
||||||
* @param {Object} Redux store reference instantiated on the current connected client
|
* @param {Object} props.client Apollo Client reference instantiated on the current connected client
|
||||||
* @param {Object} Apollo Client reference instantiated on the current connected client
|
|
||||||
*/
|
*/
|
||||||
function RouterClearMessages(unusedItem, nextRoute, store, apolloClient) {
|
function RouterClearMessages(unusedItem, nextRoute, store, apolloClient) {
|
||||||
// TODO Apollo2: clear error messages on route change
|
// TODO Apollo2: clear error messages on route change
|
||||||
|
@ -18,4 +17,4 @@ function RouterClearMessages(unusedItem, nextRoute, store, apolloClient) {
|
||||||
// return unusedItem;
|
// return unusedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCallback('router.onUpdate', RouterClearMessages);
|
addCallback('router.onUpdate.async', RouterClearMessages);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import PropTypes from 'prop-types';
|
||||||
import { IntlProvider, intlShape } from 'meteor/vulcan:i18n';
|
import { IntlProvider, intlShape } from 'meteor/vulcan:i18n';
|
||||||
import withCurrentUser from '../containers/withCurrentUser.js';
|
import withCurrentUser from '../containers/withCurrentUser.js';
|
||||||
import withUpdate from '../containers/withUpdate.js';
|
import withUpdate from '../containers/withUpdate.js';
|
||||||
|
import withSiteData from '../containers/withSiteData.js';
|
||||||
import { withApollo } from 'react-apollo';
|
import { withApollo } from 'react-apollo';
|
||||||
import { withCookies } from 'react-cookie';
|
import { withCookies } from 'react-cookie';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
@ -34,31 +35,47 @@ const RouteWithLayout = ({ layoutName, component, ...rest }) => (
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const DummyErrorCatcher = ({ children }) => children;
|
||||||
|
|
||||||
class App extends PureComponent {
|
class App extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
if (props.currentUser) {
|
if (props.currentUser) {
|
||||||
runCallbacks('events.identify', props.currentUser);
|
runCallbacks('events.identify', props.currentUser);
|
||||||
}
|
}
|
||||||
const locale = this.initLocale();
|
const { locale, localeMethod } = this.initLocale();
|
||||||
this.state = { locale };
|
this.state = { locale, localeMethod };
|
||||||
moment.locale(locale);
|
moment.locale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
runCallbacks('app.mounted', this.props);
|
||||||
|
}
|
||||||
|
|
||||||
initLocale = () => {
|
initLocale = () => {
|
||||||
let userLocale = '';
|
let userLocale = '';
|
||||||
const { currentUser, cookies } = this.props;
|
let localeMethod = '';
|
||||||
|
const { currentUser, cookies, locale } = this.props;
|
||||||
const availableLocales = Object.keys(Strings);
|
const availableLocales = Object.keys(Strings);
|
||||||
|
const detectedLocale = detectLocale();
|
||||||
|
|
||||||
if (currentUser && currentUser.locale) {
|
if (locale) {
|
||||||
// 1. if user is logged in, check for their preferred locale
|
// 1. locale is passed through SSR process
|
||||||
userLocale = currentUser.locale;
|
// TODO: currently SSR locale is passed through cookies as a hack
|
||||||
|
userLocale = locale;
|
||||||
|
localeMethod = 'SSR';
|
||||||
} else if (cookies && cookies.get('locale')) {
|
} else if (cookies && cookies.get('locale')) {
|
||||||
// 2. else, look for a cookie
|
// 2. look for a cookie
|
||||||
userLocale = cookies.get('locale');
|
userLocale = cookies.get('locale');
|
||||||
} else if (detectLocale()) {
|
localeMethod = 'cookie';
|
||||||
// 3. else, check for browser settings
|
} else if (currentUser && currentUser.locale) {
|
||||||
userLocale = detectLocale();
|
// 3. if user is logged in, check for their preferred locale
|
||||||
|
userLocale = currentUser.locale;
|
||||||
|
localeMethod = 'user';
|
||||||
|
} else if (detectedLocale) {
|
||||||
|
// 4. else, check for browser settings
|
||||||
|
userLocale = detectedLocale;
|
||||||
|
localeMethod = 'browser';
|
||||||
}
|
}
|
||||||
// if user locale is available, use it; else compare first two chars
|
// if user locale is available, use it; else compare first two chars
|
||||||
// of user locale with first two chars of available locales
|
// of user locale with first two chars of available locales
|
||||||
|
@ -67,23 +84,29 @@ class App extends PureComponent {
|
||||||
: availableLocales.find(locale => locale.slice(0, 2) === userLocale.slice(0, 2));
|
: availableLocales.find(locale => locale.slice(0, 2) === userLocale.slice(0, 2));
|
||||||
|
|
||||||
// 4. if user-defined locale is available, use it; else default to setting or `en-US`
|
// 4. if user-defined locale is available, use it; else default to setting or `en-US`
|
||||||
return availableLocale ? availableLocale : getSetting('locale', 'en-US');
|
if (availableLocale) {
|
||||||
|
return { locale: availableLocale, localeMethod };
|
||||||
|
} else {
|
||||||
|
return { locale: getSetting('locale', 'en-US'), localeMethod: 'setting' };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getLocale = (truncate = false) => {
|
getLocale = truncate => {
|
||||||
return truncate ? this.state.locale.slice(0, 2) : this.state.locale;
|
return truncate ? this.state.locale.slice(0, 2) : this.state.locale;
|
||||||
};
|
};
|
||||||
|
|
||||||
setLocale = async locale => {
|
setLocale = async locale => {
|
||||||
|
const { cookies, updateUser, client, currentUser } = this.props;
|
||||||
this.setState({ locale });
|
this.setState({ locale });
|
||||||
this.props.cookies.set('locale', locale);
|
cookies.remove('locale', { path: '/' });
|
||||||
|
cookies.set('locale', locale, { path: '/' });
|
||||||
// if user is logged in, change their `locale` profile property
|
// if user is logged in, change their `locale` profile property
|
||||||
if (this.props.currentUser) {
|
if (currentUser) {
|
||||||
await this.props.updateUser({ selector: { documentId: this.props.currentUser._id }, data: { locale } });
|
await updateUser({ selector: { documentId: currentUser._id }, data: { locale } });
|
||||||
}
|
}
|
||||||
moment.locale(locale);
|
moment.locale(locale);
|
||||||
if (hasIntlFields) {
|
if (hasIntlFields) {
|
||||||
this.props.client.resetStore();
|
client.resetStore();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,6 +129,12 @@ class App extends PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const routeNames = Object.keys(Routes);
|
const routeNames = Object.keys(Routes);
|
||||||
|
//const currentRoute = _.last(this.props.routes);
|
||||||
|
//const LayoutComponent = currentRoute.layoutName ? Components[currentRoute.layoutName] : Components.Layout;
|
||||||
|
//
|
||||||
|
// // if defined, use ErrorCatcher component to wrap layout contents
|
||||||
|
// const ErrorCatcher = Components.ErrorCatcher ? Components.ErrorCatcher : DummyErrorCatcher;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IntlProvider locale={this.getLocale()} key={this.getLocale()} messages={Strings[this.getLocale()]}>
|
<IntlProvider locale={this.getLocale()} key={this.getLocale()} messages={Strings[this.getLocale()]}>
|
||||||
<div className={`locale-${this.getLocale()}`}>
|
<div className={`locale-${this.getLocale()}`}>
|
||||||
|
@ -121,11 +150,23 @@ class App extends PureComponent {
|
||||||
// because it is the direct child of Switch
|
// because it is the direct child of Switch
|
||||||
<RouteWithLayout exact key={key} {...Routes[key]} />
|
<RouteWithLayout exact key={key} {...Routes[key]} />
|
||||||
))}
|
))}
|
||||||
<Route component={Components.Error404} /> // TODO Apollo2: figure out why this is not working
|
<Route component={Components.Error404} />
|
||||||
</Switch>
|
</Switch>
|
||||||
) : (
|
) : (
|
||||||
<Components.Welcome />
|
<Components.Welcome />
|
||||||
)}
|
)}
|
||||||
|
{/*
|
||||||
|
<Components.RouterHook currentRoute={currentRoute} />
|
||||||
|
<LayoutComponent {...this.props} currentRoute={currentRoute}>
|
||||||
|
{this.props.currentUserLoading ? (
|
||||||
|
<Components.Loading />
|
||||||
|
) : this.props.children ? (
|
||||||
|
<ErrorCatcher siteData={this.props.siteData}>{this.props.children}</ErrorCatcher>
|
||||||
|
) : (
|
||||||
|
<Components.Welcome />
|
||||||
|
)}
|
||||||
|
</LayoutComponent>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
|
@ -149,6 +190,6 @@ const updateOptions = {
|
||||||
fragmentName: 'UsersCurrent',
|
fragmentName: 'UsersCurrent',
|
||||||
};
|
};
|
||||||
|
|
||||||
registerComponent('App', App, withCurrentUser, [withUpdate, updateOptions], withApollo, withCookies);
|
registerComponent('App', App, withCurrentUser, withSiteData, [withUpdate, updateOptions], withApollo, withCookies);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
@ -10,7 +10,7 @@ const Avatar = ({ className, user, link, fallback }) => {
|
||||||
const avatarClassNames = classNames('avatar', className);
|
const avatarClassNames = classNames('avatar', className);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return <div className={avatarClassNames}>{fallback}</div>
|
return <div className={avatarClassNames}>{fallback}</div>;
|
||||||
}
|
}
|
||||||
const avatarUrl = user.avatarUrl || Users.avatar.getUrl(user);
|
const avatarUrl = user.avatarUrl || Users.avatar.getUrl(user);
|
||||||
|
|
||||||
|
@ -30,18 +30,18 @@ const Avatar = ({ className, user, link, fallback }) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
Avatar.propTypes = {
|
Avatar.propTypes = {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
size: PropTypes.string,
|
size: PropTypes.string,
|
||||||
link: PropTypes.bool
|
link: PropTypes.bool
|
||||||
}
|
};
|
||||||
|
|
||||||
Avatar.defaultProps = {
|
Avatar.defaultProps = {
|
||||||
size: 'medium',
|
size: 'medium',
|
||||||
link: true
|
link: true
|
||||||
}
|
};
|
||||||
|
|
||||||
Avatar.displayName = 'Avatar';
|
Avatar.displayName = 'Avatar';
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ const getLabel = (field, fieldName, collection, intl) => {
|
||||||
} else {
|
} else {
|
||||||
return fieldName;
|
return fieldName;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getTypeName = (field, fieldName, collection) => {
|
const getTypeName = (field, fieldName, collection) => {
|
||||||
const schema = collection && collection.simpleSchema()._schema;
|
const schema = collection && collection.simpleSchema()._schema;
|
||||||
|
@ -26,18 +26,18 @@ const getTypeName = (field, fieldName, collection) => {
|
||||||
} else {
|
} else {
|
||||||
return typeof field;
|
return typeof field;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const parseImageUrl = value => {
|
const parseImageUrl = value => {
|
||||||
const isImage = ['.png', '.jpg', '.gif'].indexOf(value.substr(-4)) !== -1 || ['.webp', '.jpeg'].indexOf(value.substr(-5)) !== -1;
|
const isImage = ['.png', '.jpg', '.gif'].indexOf(value.substr(-4)) !== -1 || ['.webp', '.jpeg'].indexOf(value.substr(-5)) !== -1;
|
||||||
return isImage ?
|
return isImage ?
|
||||||
<img style={{ width: '100%', minWidth: 80, maxWidth: 200, display: 'block' }} src={value} alt={value} /> :
|
<img style={{ width: '100%', minWidth: 80, maxWidth: 200, display: 'block' }} src={value} alt={value} /> :
|
||||||
parseUrl(value);
|
parseUrl(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
const parseUrl = value => {
|
const parseUrl = value => {
|
||||||
return value.slice(0, 4) === 'http' ? <a href={value} target="_blank"><LimitedString string={value} /></a> : <LimitedString string={value} />;
|
return value.slice(0,4) === 'http' ? <a href={value} target="_blank"><LimitedString string={value}/></a> : <LimitedString string={value}/>;
|
||||||
}
|
};
|
||||||
|
|
||||||
const LimitedString = ({ string }) =>
|
const LimitedString = ({ string }) =>
|
||||||
<div>
|
<div>
|
||||||
|
@ -45,12 +45,12 @@ const LimitedString = ({ string }) =>
|
||||||
<span title={string}>{string.substr(0, 30)}…</span> :
|
<span title={string}>{string.substr(0, 30)}…</span> :
|
||||||
<span>{(string)}</span>
|
<span>{(string)}</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
export const getFieldValue = (value, typeName) => {
|
export const getFieldValue = (value, typeName) => {
|
||||||
|
|
||||||
if (typeof value === 'undefined' || value === null) {
|
if (typeof value === 'undefined' || value === null) {
|
||||||
return ''
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSX element
|
// JSX element
|
||||||
|
@ -76,7 +76,7 @@ export const getFieldValue = (value, typeName) => {
|
||||||
return <code>{value.toString()}</code>;
|
return <code>{value.toString()}</code>;
|
||||||
|
|
||||||
case 'Array':
|
case 'Array':
|
||||||
return <ol>{value.map((item, index) => <li key={index}>{getFieldValue(item, typeof item)}</li>)}</ol>
|
return <ol>{value.map((item, index) => <li key={index}>{getFieldValue(item, typeof item)}</li>)}</ol>;
|
||||||
|
|
||||||
case 'Object':
|
case 'Object':
|
||||||
case 'object':
|
case 'object':
|
||||||
|
@ -92,7 +92,7 @@ export const getFieldValue = (value, typeName) => {
|
||||||
default:
|
default:
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getObject = object => {
|
const getObject = object => {
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ const getObject = object => {
|
||||||
<Components.Avatar size="small" user={user} link />
|
<Components.Avatar size="small" user={user} link />
|
||||||
<Link to={user.pageUrl}>{user.displayName}</Link>
|
<Link to={user.pageUrl}>{user.displayName}</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -120,16 +120,16 @@ const getObject = object => {
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const CardItem = ({ label, value, typeName }) =>
|
const CardItem = ({ label, value, typeName }) =>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="datacard-label"><strong>{label}</strong></td>
|
<td className="datacard-label"><strong>{label}</strong></td>
|
||||||
<td className="datacard-value">{getFieldValue(value, typeName)}</td>
|
<td className="datacard-value">{getFieldValue(value, typeName)}</td>
|
||||||
</tr>
|
</tr>;
|
||||||
|
|
||||||
const CardEdit = (props, context) =>
|
const CardEdit = (props, context) =>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -138,7 +138,7 @@ const CardEdit = (props, context) =>
|
||||||
<CardEditForm {...props} />
|
<CardEditForm {...props} />
|
||||||
</Components.ModalTrigger>
|
</Components.ModalTrigger>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>;
|
||||||
|
|
||||||
CardEdit.contextTypes = { intl: intlShape };
|
CardEdit.contextTypes = { intl: intlShape };
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ const CardEditForm = ({ collection, document, closeModal }) =>
|
||||||
successCallback={document => {
|
successCallback={document => {
|
||||||
closeModal();
|
closeModal();
|
||||||
}}
|
}}
|
||||||
/>
|
/>;
|
||||||
|
|
||||||
const Card = ({ title, className, collection, document, currentUser, fields, showEdit = true }, { intl }) => {
|
const Card = ({ title, className, collection, document, currentUser, fields, showEdit = true }, { intl }) => {
|
||||||
|
|
||||||
|
@ -181,10 +181,10 @@ Card.propTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
fields: PropTypes.array,
|
fields: PropTypes.array,
|
||||||
showEdit: PropTypes.bool
|
showEdit: PropTypes.bool
|
||||||
}
|
};
|
||||||
|
|
||||||
Card.contextTypes = {
|
Card.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
}
|
};
|
||||||
|
|
||||||
registerComponent('Card', Card);
|
registerComponent('Card', Card);
|
|
@ -1,10 +1,12 @@
|
||||||
import { registerComponent, Components, getCollection, Utils } from 'meteor/vulcan:lib';
|
import { registerComponent, getCollection, Utils } from 'meteor/vulcan:lib';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import withCurrentUser from '../containers/withCurrentUser.js';
|
import withCurrentUser from '../containers/withCurrentUser.js';
|
||||||
|
import withComponents from '../containers/withComponents';
|
||||||
import withMulti from '../containers/withMulti.js';
|
import withMulti from '../containers/withMulti.js';
|
||||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||||
import { getFieldValue } from './Card.jsx';
|
import { getFieldValue } from './Card.jsx';
|
||||||
|
import _sortBy from 'lodash/sortBy';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@ -21,6 +23,12 @@ const delay = (function(){
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const getColumnName = column => (
|
||||||
|
typeof column === 'string'
|
||||||
|
? column
|
||||||
|
: column.label || column.name
|
||||||
|
);
|
||||||
|
|
||||||
class Datatable extends PureComponent {
|
class Datatable extends PureComponent {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -30,7 +38,7 @@ class Datatable extends PureComponent {
|
||||||
value: '',
|
value: '',
|
||||||
query: '',
|
query: '',
|
||||||
currentSort: {}
|
currentSort: {}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSort = column => {
|
toggleSort = column => {
|
||||||
|
@ -46,7 +54,7 @@ class Datatable extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateQuery(e) {
|
updateQuery(e) {
|
||||||
e.persist()
|
e.persist();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({
|
this.setState({
|
||||||
value: e.target.value
|
value: e.target.value
|
||||||
|
@ -59,10 +67,11 @@ class Datatable extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { Components } = this.props;
|
||||||
|
|
||||||
if (this.props.data) { // static JSON datatable
|
if (this.props.data) { // static JSON datatable
|
||||||
|
|
||||||
return <Components.DatatableContents columns={Object.keys(this.props.data[0])} {...this.props} results={this.props.data} showEdit={false} showNew={false} />;
|
return <Components.DatatableContents Components={Components} columns={Object.keys(this.props.data[0])} {...this.props} results={this.props.data} showEdit={false} showNew={false} />;
|
||||||
|
|
||||||
} else { // dynamic datatable with data loading
|
} else { // dynamic datatable with data loading
|
||||||
|
|
||||||
|
@ -70,18 +79,23 @@ class Datatable extends PureComponent {
|
||||||
const options = {
|
const options = {
|
||||||
collection,
|
collection,
|
||||||
...this.props.options
|
...this.props.options
|
||||||
}
|
};
|
||||||
|
|
||||||
const DatatableWithMulti = withMulti(options)(Components.DatatableContents);
|
const DatatableWithMulti = withMulti(options)(Components.DatatableContents);
|
||||||
|
|
||||||
const canInsert = collection.options && collection.options.mutations && collection.options.mutations.new && collection.options.mutations.new.check(this.props.currentUser);
|
const canInsert = collection.options && collection.options.mutations && collection.options.mutations.new && collection.options.mutations.new.check(this.props.currentUser);
|
||||||
|
|
||||||
|
// add _id to orderBy when we want to sort a column, to avoid breaking the graphql() hoc;
|
||||||
|
// see https://github.com/VulcanJS/Vulcan/issues/2090#issuecomment-433860782
|
||||||
|
// this.state.currentSort !== {} is always false, even when console.log(this.state.currentSort) displays {}. So we test on the length of keys for this object.
|
||||||
|
const orderBy = Object.keys(this.state.currentSort).length == 0 ? {} : { ...this.state.currentSort, _id: -1 };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`datatable datatable-${collection.options.collectionName}`}>
|
<Components.DatatableLayout Components={Components} collectionName={collection.options.collectionName}>
|
||||||
<Components.DatatableAbove {...this.props} collection={collection} canInsert={canInsert} value={this.state.value} updateQuery={this.updateQuery} />
|
<Components.DatatableAbove Components={Components} {...this.props} collection={collection} canInsert={canInsert} value={this.state.value} updateQuery={this.updateQuery} />
|
||||||
<DatatableWithMulti {...this.props} collection={collection} terms={{query: this.state.query, orderBy: this.state.currentSort }} currentUser={this.props.currentUser} toggleSort={this.toggleSort} currentSort={this.state.currentSort}/>
|
<DatatableWithMulti Components={Components} {...this.props} collection={collection} terms={{ query: this.state.query, orderBy: orderBy }} currentUser={this.props.currentUser} toggleSort={this.toggleSort} currentSort={this.state.currentSort}/>
|
||||||
</div>
|
</Components.DatatableLayout>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,41 +112,80 @@ Datatable.propTypes = {
|
||||||
newFormOptions: PropTypes.object,
|
newFormOptions: PropTypes.object,
|
||||||
editFormOptions: PropTypes.object,
|
editFormOptions: PropTypes.object,
|
||||||
emptyState: PropTypes.object,
|
emptyState: PropTypes.object,
|
||||||
}
|
Components: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
Datatable.defaultProps = {
|
Datatable.defaultProps = {
|
||||||
showNew: true,
|
showNew: true,
|
||||||
showEdit: true,
|
showEdit: true,
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
}
|
};
|
||||||
registerComponent('Datatable', Datatable, withCurrentUser);
|
registerComponent({ name: 'Datatable', component: Datatable, hocs: [withCurrentUser, withComponents] });
|
||||||
|
export default Datatable;
|
||||||
|
|
||||||
|
const DatatableLayout = ({ collectionName, children }) => (
|
||||||
|
<div className={`datatable datatable-${collectionName}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableLayout', component: DatatableLayout });
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
DatatableAbove Component
|
DatatableAbove Component
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const DatatableAbove = (props) => {
|
const DatatableAbove = (props, { intl }) => {
|
||||||
const { collection, currentUser, showSearch, showNew, canInsert, value, updateQuery, options, newFormOptions } = props;
|
const { collection, currentUser, showSearch, showNew, canInsert,
|
||||||
|
value, updateQuery, options, newFormOptions, Components } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="datatable-above">
|
<Components.DatatableAboveLayout>
|
||||||
{showSearch && <input className="datatable-search form-control" placeholder="Search…" type="text" name="datatableSearchQuery" value={value} onChange={updateQuery} />}
|
{showSearch && (
|
||||||
|
<Components.DatatableAboveSearchInput
|
||||||
|
className="datatable-search form-control"
|
||||||
|
placeholder={`${intl.formatMessage({ id: 'datatable.search', defaultMessage: 'Search' })}…`}
|
||||||
|
type="text"
|
||||||
|
name="datatableSearchQuery"
|
||||||
|
value={value}
|
||||||
|
onChange={updateQuery}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{showNew && canInsert && <Components.NewButton collection={collection} currentUser={currentUser} mutationFragmentName={options && options.fragmentName} {...newFormOptions}/>}
|
{showNew && canInsert && <Components.NewButton collection={collection} currentUser={currentUser} mutationFragmentName={options && options.fragmentName} {...newFormOptions}/>}
|
||||||
</div>
|
</Components.DatatableAboveLayout>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
DatatableAbove.contextTypes = {
|
||||||
|
intl: intlShape,
|
||||||
|
};
|
||||||
|
DatatableAbove.propTypes = {
|
||||||
|
Components: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
registerComponent('DatatableAbove', DatatableAbove);
|
registerComponent('DatatableAbove', DatatableAbove);
|
||||||
|
|
||||||
|
const DatatableAboveSearchInput = (props) => (
|
||||||
|
<input
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableAboveSearchInput', component: DatatableAboveSearchInput });
|
||||||
|
|
||||||
|
const DatatableAboveLayout = ({ children }) => (
|
||||||
|
<div className="datatable-above">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableAboveLayout', component: DatatableAboveLayout });
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
DatatableHeader Component
|
DatatableHeader Component
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const DatatableHeader = ({ collection, column, toggleSort, currentSort }, { intl }) => {
|
const DatatableHeader = ({ collection, column, toggleSort, currentSort, Components }, { intl }) => {
|
||||||
|
|
||||||
const columnName = typeof column === 'string' ? column : column.label || column.name;
|
const columnName = getColumnName(column);
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const schema = collection.simpleSchema()._schema;
|
const schema = collection.simpleSchema()._schema;
|
||||||
|
@ -151,43 +204,55 @@ const DatatableHeader = ({ collection, column, toggleSort, currentSort }, { intl
|
||||||
|
|
||||||
// if sortable is a string, use it as the name of the property to sort by. If it's just `true`, use column.name
|
// 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;
|
const sortPropertyName = typeof column.sortable === 'string' ? column.sortable : column.name;
|
||||||
return column.sortable ? <Components.DatatableSorter name={sortPropertyName} label={formattedLabel} toggleSort={toggleSort} currentSort={currentSort} sortable={column.sortable}/> : <th>{formattedLabel}</th>;
|
return column.sortable
|
||||||
|
? <Components.DatatableSorter name={sortPropertyName} label={formattedLabel} toggleSort={toggleSort} currentSort={currentSort} sortable={column.sortable}/>
|
||||||
|
: <Components.DatatableHeaderCellLayout>{formattedLabel}</Components.DatatableHeaderCellLayout>;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const formattedLabel = intl.formatMessage({ id: columnName, defaultMessage: columnName });
|
const formattedLabel = intl.formatMessage({ id: columnName, defaultMessage: columnName });
|
||||||
return <th className={`datatable-th-${columnName.toLowerCase().replace(/\s/g,'-')}`}>{formattedLabel}</th>;
|
return (
|
||||||
|
<Components.DatatableHeaderCellLayout
|
||||||
|
className={`datatable-th-${columnName.toLowerCase().replace(/\s/g, '-')}`}
|
||||||
|
>
|
||||||
|
{formattedLabel}
|
||||||
|
</Components.DatatableHeaderCellLayout>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
DatatableHeader.contextTypes = {
|
DatatableHeader.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
};
|
};
|
||||||
|
DatatableHeader.propTypes = {
|
||||||
|
Components: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
registerComponent('DatatableHeader', DatatableHeader);
|
registerComponent('DatatableHeader', DatatableHeader);
|
||||||
|
|
||||||
|
const DatatableHeaderCellLayout = ({ children, ...otherProps }) => (
|
||||||
|
<th {...otherProps}>{children}</th>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableHeaderCellLayout', component: DatatableHeaderCellLayout });
|
||||||
|
|
||||||
const SortNone = () =>
|
const SortNone = () =>
|
||||||
<svg width='16' height='16' viewBox='0 0 438 438' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
<svg width='16' height='16' viewBox='0 0 438 438' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
<path d='M25.7368 247.243H280.263C303.149 247.243 314.592 274.958 298.444 291.116L171.18 418.456C161.128 428.515 144.872 428.515 134.926 418.456L7.55631 291.116C-8.59221 274.958 2.85078 247.243 25.7368 247.243ZM298.444 134.884L171.18 7.54408C161.128 -2.51469 144.872 -2.51469 134.926 7.54408L7.55631 134.884C-8.59221 151.042 2.85078 178.757 25.7368 178.757H280.263C303.149 178.757 314.592 151.042 298.444 134.884Z' transform='translate(66 6)' fill='#000' fillOpacity='0.2' />
|
<path d='M25.7368 247.243H280.263C303.149 247.243 314.592 274.958 298.444 291.116L171.18 418.456C161.128 428.515 144.872 428.515 134.926 418.456L7.55631 291.116C-8.59221 274.958 2.85078 247.243 25.7368 247.243ZM298.444 134.884L171.18 7.54408C161.128 -2.51469 144.872 -2.51469 134.926 7.54408L7.55631 134.884C-8.59221 151.042 2.85078 178.757 25.7368 178.757H280.263C303.149 178.757 314.592 151.042 298.444 134.884Z' transform='translate(66 6)' fill='#000' fillOpacity='0.2' />
|
||||||
</svg>
|
</svg>;
|
||||||
|
|
||||||
const SortDesc = () =>
|
const SortDesc = () =>
|
||||||
<svg width="16" height="16" viewBox="0 0 438 438" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 438 438" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M25.7368 0H280.263C303.149 0 314.592 27.7151 298.444 43.8734L171.18 171.213C161.128 181.272 144.872 181.272 134.926 171.213L7.55631 43.8734C-8.59221 27.7151 2.85078 0 25.7368 0Z" transform="translate(66 253.243)" fill="black" fillOpacity="0.7"/>
|
<path d="M25.7368 0H280.263C303.149 0 314.592 27.7151 298.444 43.8734L171.18 171.213C161.128 181.272 144.872 181.272 134.926 171.213L7.55631 43.8734C-8.59221 27.7151 2.85078 0 25.7368 0Z" transform="translate(66 253.243)" fill="black" fillOpacity="0.7"/>
|
||||||
<path d="M171.18 7.54408L298.444 134.884C314.592 151.042 303.149 178.757 280.263 178.757H25.7368C2.85078 178.757 -8.59221 151.042 7.55631 134.884L134.926 7.54408C144.872 -2.51469 161.128 -2.51469 171.18 7.54408Z" transform="translate(66 6)" fill="black" fillOpacity="0.2"/>
|
<path d="M171.18 7.54408L298.444 134.884C314.592 151.042 303.149 178.757 280.263 178.757H25.7368C2.85078 178.757 -8.59221 151.042 7.55631 134.884L134.926 7.54408C144.872 -2.51469 161.128 -2.51469 171.18 7.54408Z" transform="translate(66 6)" fill="black" fillOpacity="0.2"/>
|
||||||
</svg>
|
</svg>;
|
||||||
|
|
||||||
const SortAsc = () =>
|
const SortAsc = () =>
|
||||||
<svg width="16" height="16" viewBox="0 0 438 438" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 438 438" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M298.444 134.884L171.18 7.54408C161.128 -2.51469 144.872 -2.51469 134.926 7.54408L7.55631 134.884C-8.59221 151.042 2.85078 178.757 25.7368 178.757H280.263C303.149 178.757 314.592 151.042 298.444 134.884Z" transform="translate(66 6)" fill="black" fillOpacity="0.7"/>
|
<path d="M298.444 134.884L171.18 7.54408C161.128 -2.51469 144.872 -2.51469 134.926 7.54408L7.55631 134.884C-8.59221 151.042 2.85078 178.757 25.7368 178.757H280.263C303.149 178.757 314.592 151.042 298.444 134.884Z" transform="translate(66 6)" fill="black" fillOpacity="0.7"/>
|
||||||
<path d="M280.263 0H25.7368C2.85078 0 -8.59221 27.7151 7.55631 43.8734L134.926 171.213C144.872 181.272 161.128 181.272 171.18 171.213L298.444 43.8734C314.592 27.7151 303.149 0 280.263 0Z" transform="translate(66 253.243)" fill="black" fillOpacity="0.2"/>
|
<path d="M280.263 0H25.7368C2.85078 0 -8.59221 27.7151 7.55631 43.8734L134.926 171.213C144.872 181.272 161.128 181.272 171.18 171.213L298.444 43.8734C314.592 27.7151 303.149 0 280.263 0Z" transform="translate(66 253.243)" fill="black" fillOpacity="0.2"/>
|
||||||
</svg>
|
</svg>;
|
||||||
|
|
||||||
const DatatableSorter = ({ name, label, toggleSort, currentSort }) =>
|
const DatatableSorter = ({ name, label, toggleSort, currentSort }) =>
|
||||||
|
|
||||||
<th>
|
<th>
|
||||||
<div className="datatable-sorter" onClick={() => {toggleSort(name)}}>
|
<div className="datatable-sorter" onClick={() => {toggleSort(name);}}>
|
||||||
<span className="datatable-sorter-label">{label}</span>
|
<span className="datatable-sorter-label">{label}</span>
|
||||||
<span className="sort-icon">
|
<span className="sort-icon">
|
||||||
{!currentSort[name] ? (
|
{!currentSort[name] ? (
|
||||||
|
@ -200,7 +265,7 @@ const DatatableSorter = ({ name, label, toggleSort, currentSort }) =>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>;
|
||||||
|
|
||||||
registerComponent('DatatableSorter', DatatableSorter);
|
registerComponent('DatatableSorter', DatatableSorter);
|
||||||
|
|
||||||
|
@ -213,7 +278,10 @@ DatatableContents Component
|
||||||
const DatatableContents = (props) => {
|
const DatatableContents = (props) => {
|
||||||
|
|
||||||
// if no columns are provided, default to using keys of first array item
|
// if no columns are provided, default to using keys of first array item
|
||||||
const { title, collection, results, columns, loading, loadMore, count, totalCount, networkStatus, showEdit, currentUser, emptyState, toggleSort, currentSort } = props;
|
const { title, collection, results, columns, loading, loadMore,
|
||||||
|
count, totalCount, networkStatus, showEdit, currentUser, emptyState,
|
||||||
|
toggleSort, currentSort,
|
||||||
|
Components } = props;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className="datatable-list datatable-list-loading"><Components.Loading /></div>;
|
return <div className="datatable-list datatable-list-loading"><Components.Loading /></div>;
|
||||||
|
@ -223,41 +291,89 @@ const DatatableContents = (props) => {
|
||||||
|
|
||||||
const isLoadingMore = networkStatus === 2;
|
const isLoadingMore = networkStatus === 2;
|
||||||
const hasMore = totalCount > results.length;
|
const hasMore = totalCount > results.length;
|
||||||
|
const sortedColumns = _sortBy(columns, column => column.order);
|
||||||
return (
|
return (
|
||||||
<div className="datatable-list">
|
<Components.DatatableContentsLayout>
|
||||||
{title && <Components.DatatableTitle title={title}/>}
|
{title && <Components.DatatableTitle title={title}/>}
|
||||||
<table className="table">
|
<Components.DatatableContentsInnerLayout>
|
||||||
<thead>
|
<Components.DatatableContentsHeadLayout>
|
||||||
<tr>
|
{
|
||||||
{_.sortBy(columns, column => column.order).map((column, index) => <Components.DatatableHeader key={index} collection={collection} column={column} toggleSort={toggleSort} currentSort={currentSort} />)}
|
sortedColumns
|
||||||
{showEdit ? <th><FormattedMessage id="datatable.edit"/></th> : null}
|
.map((column, index) => (
|
||||||
</tr>
|
<Components.DatatableHeader
|
||||||
</thead>
|
Components={Components}
|
||||||
<tbody>
|
key={index} collection={collection} column={column}
|
||||||
{results.map((document, index) => <Components.DatatableRow {...props} collection={collection} columns={columns} document={document} key={index} showEdit={showEdit} currentUser={currentUser}/>)}
|
toggleSort={toggleSort} currentSort={currentSort} />)
|
||||||
</tbody>
|
)
|
||||||
</table>
|
|
||||||
{hasMore &&
|
|
||||||
<div className="datatable-list-load-more">
|
|
||||||
{isLoadingMore ?
|
|
||||||
<Components.Loading/> :
|
|
||||||
<Components.Button variant="primary" onClick={e => {e.preventDefault(); loadMore();}}>Load More ({count}/{totalCount})</Components.Button>
|
|
||||||
}
|
}
|
||||||
</div>
|
{showEdit ? <th><FormattedMessage id="datatable.edit" /></th> : null}
|
||||||
|
</Components.DatatableContentsHeadLayout>
|
||||||
|
<Components.DatatableContentsBodyLayout>
|
||||||
|
{results.map((document, index) => <Components.DatatableRow {...props} collection={collection} columns={columns} document={document} key={index} showEdit={showEdit} currentUser={currentUser} />)}
|
||||||
|
</Components.DatatableContentsBodyLayout>
|
||||||
|
</Components.DatatableContentsInnerLayout>
|
||||||
|
{hasMore &&
|
||||||
|
<Components.DatatableContentsMoreLayout>
|
||||||
|
{isLoadingMore
|
||||||
|
? <Components.Loading />
|
||||||
|
: (
|
||||||
|
<Components.DatatableLoadMoreButton Components={Components} onClick={e => { e.preventDefault(); loadMore(); }}>
|
||||||
|
Load More ({count}/{totalCount})
|
||||||
|
</Components.DatatableLoadMoreButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Components.DatatableContentsMoreLayout>
|
||||||
}
|
}
|
||||||
</div>
|
</Components.DatatableContentsLayout>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
DatatableContents.propTypes = {
|
||||||
|
Components: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
registerComponent('DatatableContents', DatatableContents);
|
registerComponent('DatatableContents', DatatableContents);
|
||||||
|
|
||||||
|
const DatatableContentsLayout = ({ children }) => (
|
||||||
|
<div className="datatable-list">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableContentsLayout', component: DatatableContentsLayout });
|
||||||
|
const DatatableContentsInnerLayout = ({ children }) => (
|
||||||
|
<table className="table">
|
||||||
|
{children}
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableContentsInnerLayout', component: DatatableContentsInnerLayout });
|
||||||
|
const DatatableContentsHeadLayout = ({ children }) => (
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{children}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableContentsHeadLayout', component: DatatableContentsHeadLayout });
|
||||||
|
const DatatableContentsBodyLayout = ({ children }) => (
|
||||||
|
<tbody>{children}</tbody>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableContentsBodyLayout', component: DatatableContentsBodyLayout });
|
||||||
|
const DatatableContentsMoreLayout = ({ children }) => (
|
||||||
|
<div className="datatable-list-load-more">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableContentsMoreLayout', component: DatatableContentsMoreLayout });
|
||||||
|
const DatatableLoadMoreButton = ({ count, totalCount, Components, children, ...otherProps }) => (
|
||||||
|
<Components.Button variant="primary" {...otherProps}>{children}</Components.Button>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableLoadMoreButton', component: DatatableLoadMoreButton });
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
DatatableTitle Component
|
DatatableTitle Component
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const DatatableTitle = ({ title }) =>
|
const DatatableTitle = ({ title }) =>
|
||||||
<div className="datatable-title">{title}</div>
|
<div className="datatable-title">{title}</div>;
|
||||||
|
|
||||||
registerComponent('DatatableTitle', DatatableTitle);
|
registerComponent('DatatableTitle', DatatableTitle);
|
||||||
|
|
||||||
|
@ -268,52 +384,81 @@ DatatableRow Component
|
||||||
*/
|
*/
|
||||||
const DatatableRow = (props, { intl }) => {
|
const DatatableRow = (props, { intl }) => {
|
||||||
|
|
||||||
const { collection, columns, document, showEdit, currentUser, options, editFormOptions, rowClass } = props;
|
const { collection, columns, document, showEdit,
|
||||||
|
currentUser, options, editFormOptions, rowClass, Components } = props;
|
||||||
const canEdit = collection && collection.options && collection.options.mutations && collection.options.mutations.edit && collection.options.mutations.edit.check(currentUser, document);
|
const canEdit = collection && collection.options && collection.options.mutations && collection.options.mutations.edit && collection.options.mutations.edit.check(currentUser, document);
|
||||||
|
|
||||||
const row = typeof rowClass === 'function' ? rowClass(document) : rowClass || '';
|
const row = typeof rowClass === 'function' ? rowClass(document) : rowClass || '';
|
||||||
const modalProps = { title: <code>{document._id}</code> };
|
const modalProps = { title: <code>{document._id}</code> };
|
||||||
|
const sortedColumns = _sortBy(columns, column => column.order);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={`datatable-item ${row}`}>
|
<Components.DatatableRowLayout className={`datatable-item ${row}`}>
|
||||||
|
{
|
||||||
{_.sortBy(columns, column => column.order).map((column, index) => <Components.DatatableCell key={index} column={column} document={document} currentUser={currentUser} />)}
|
sortedColumns
|
||||||
|
.map((column, index) => (
|
||||||
|
<Components.DatatableCell
|
||||||
|
key={index}
|
||||||
|
Components={Components}
|
||||||
|
column={column} document={document}
|
||||||
|
currentUser={currentUser} />
|
||||||
|
))}
|
||||||
{showEdit && canEdit ?
|
{showEdit && canEdit ?
|
||||||
<td>
|
<Components.DatatableCellLayout>
|
||||||
<Components.EditButton collection={collection} documentId={document._id} currentUser={currentUser} mutationFragmentName={options && options.fragmentName} modalProps={modalProps} {...editFormOptions}/>
|
<Components.EditButton collection={collection} documentId={document._id} currentUser={currentUser} mutationFragmentName={options && options.fragmentName} modalProps={modalProps} {...editFormOptions}/>
|
||||||
</td>
|
</Components.DatatableCellLayout>
|
||||||
: null}
|
: null}
|
||||||
|
</Components.DatatableRowLayout>
|
||||||
</tr>
|
);
|
||||||
)
|
};
|
||||||
}
|
DatatableRow.propTypes = {
|
||||||
|
Components: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
registerComponent('DatatableRow', DatatableRow);
|
registerComponent('DatatableRow', DatatableRow);
|
||||||
|
|
||||||
DatatableRow.contextTypes = {
|
DatatableRow.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape
|
||||||
};
|
};
|
||||||
|
const DatatableRowLayout = ({ children, ...otherProps }) => (
|
||||||
|
<tr {...otherProps}>
|
||||||
|
{children}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableRowLayout', component: DatatableRowLayout });
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
DatatableCell Component
|
DatatableCell Component
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const DatatableCell = ({ column, document, currentUser }) => {
|
const DatatableCell = ({ column, document, currentUser, Components }) => {
|
||||||
const Component = column.component || column.componentName && Components[column.componentName] || Components.DatatableDefaultCell;
|
const Component = column.component
|
||||||
const columnName = column.name || column;
|
|| column.componentName && Components[column.componentName]
|
||||||
|
|| Components.DatatableDefaultCell;
|
||||||
|
const columnName = getColumnName(column);
|
||||||
return (
|
return (
|
||||||
<td className={`datatable-item-${columnName.toLowerCase().replace(/\s/g,'-')}`}><Component column={column} document={document} currentUser={currentUser} /></td>
|
<Components.DatatableCellLayout className={`datatable-item-${columnName.toLowerCase().replace(/\s/g, '-')}`}>
|
||||||
)
|
<Component column={column} document={document} currentUser={currentUser} />
|
||||||
}
|
</Components.DatatableCellLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
DatatableCell.propTypes = {
|
||||||
|
Components: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
registerComponent('DatatableCell', DatatableCell);
|
registerComponent('DatatableCell', DatatableCell);
|
||||||
|
|
||||||
|
const DatatableCellLayout = ({ children, ...otherProps }) => (
|
||||||
|
<td {...otherProps}>{children}</td>
|
||||||
|
);
|
||||||
|
registerComponent({ name: 'DatatableCellLayout', component: DatatableCellLayout });
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
DatatableDefaultCell Component
|
DatatableDefaultCell Component
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const DatatableDefaultCell = ({ column, document }) =>
|
const DatatableDefaultCell = ({ column, document }) =>
|
||||||
<div>{typeof column === 'string' ? getFieldValue(document[column]) : getFieldValue(document[column.name])}</div>
|
<div>{typeof column === 'string' ? getFieldValue(document[column]) : getFieldValue(document[column.name])}</div>;
|
||||||
|
|
||||||
registerComponent('DatatableDefaultCell', DatatableDefaultCell);
|
registerComponent('DatatableDefaultCell', DatatableDefaultCell);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const DynamicLoading = ({ isLoading, pastDelay, error }) => {
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
registerComponent('DynamicLoading', DynamicLoading);
|
registerComponent('DynamicLoading', DynamicLoading);
|
||||||
|
|
||||||
|
|
|
@ -32,15 +32,15 @@ EditForm Component
|
||||||
const EditForm = ({ closeModal, successCallback, removeSuccessCallback, ...props }) => {
|
const EditForm = ({ closeModal, successCallback, removeSuccessCallback, ...props }) => {
|
||||||
|
|
||||||
const success = successCallback
|
const success = successCallback
|
||||||
? () => {
|
? document => {
|
||||||
successCallback();
|
successCallback(document);
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
: closeModal;
|
: closeModal;
|
||||||
|
|
||||||
const remove = removeSuccessCallback
|
const remove = removeSuccessCallback
|
||||||
? () => {
|
? document => {
|
||||||
removeSuccessCallback();
|
removeSuccessCallback(document);
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
: closeModal;
|
: closeModal;
|
||||||
|
|
|
@ -7,8 +7,8 @@ const Error404 = () => {
|
||||||
<div className="error404">
|
<div className="error404">
|
||||||
<h3><FormattedMessage id="app.404"/></h3>
|
<h3><FormattedMessage id="app.404"/></h3>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
Error404.displayName = 'Error404';
|
Error404.displayName = 'Error404';
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Flash extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.markAsSeen(this.props.message._id);
|
this.props.markAsSeen && this.props.markAsSeen(this.props.message._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissFlash(e) {
|
dismissFlash(e) {
|
||||||
|
@ -42,25 +42,20 @@ class Flash extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message, type } = this.getProperties();
|
|
||||||
|
const { message, type = 'danger' } = this.getProperties();
|
||||||
const flashType = type === 'error' ? 'danger' : type; // if flashType is "error", use "danger" instead
|
const flashType = type === 'error' ? 'danger' : type; // if flashType is "error", use "danger" instead
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Components.Alert
|
<Components.Alert className="flash-message" variant={flashType} onDismiss={this.dismissFlash}>
|
||||||
className="flash-message"
|
<span dangerouslySetInnerHTML={{ __html: message }} />
|
||||||
variant={flashType}
|
|
||||||
onDismiss={this.dismissFlash}
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</Components.Alert>
|
</Components.Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Flash.propTypes = {
|
Flash.propTypes = {
|
||||||
message: PropTypes.object.isRequired,
|
message: PropTypes.oneOfType([PropTypes.object.isRequired, PropTypes.string.isRequired])
|
||||||
markAsSeen: PropTypes.func.isRequired,
|
|
||||||
clear: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Flash.contextTypes = {
|
Flash.contextTypes = {
|
||||||
|
@ -69,17 +64,12 @@ Flash.contextTypes = {
|
||||||
|
|
||||||
registerComponent('Flash', Flash);
|
registerComponent('Flash', Flash);
|
||||||
|
|
||||||
const FlashMessages = ({ messages, clear, markAsSeen }) => {
|
const FlashMessages = ({messages, clear, markAsSeen, className}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flash-messages">
|
<div className={`flash-messages ${className}`}>
|
||||||
{messages.filter(message => message.show).map(message => (
|
{messages
|
||||||
<Components.Flash
|
.filter(message => message.show)
|
||||||
key={message._id}
|
.map(message => <Components.Flash key={message._id} message={message} clear={clear} markAsSeen={markAsSeen} />)}
|
||||||
message={message}
|
|
||||||
clear={clear}
|
|
||||||
markAsSeen={markAsSeen}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,7 +75,7 @@ class HeadTags extends PureComponent {
|
||||||
} else {
|
} else {
|
||||||
HeadComponent = componentOrArray;
|
HeadComponent = componentOrArray;
|
||||||
}
|
}
|
||||||
return <HeadComponent key={index} />
|
return <HeadComponent key={index} />;
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,18 +13,18 @@ const wrapper = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}
|
};
|
||||||
|
|
||||||
const header = {
|
const header = {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}
|
};
|
||||||
|
|
||||||
const code = {
|
const code = {
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
padding: '10px 20px',
|
padding: '10px 20px',
|
||||||
background: 'white',
|
background: 'white',
|
||||||
}
|
};
|
||||||
|
|
||||||
function escapeHtml(unsafe) {
|
function escapeHtml(unsafe) {
|
||||||
return unsafe
|
return unsafe
|
||||||
|
@ -73,7 +73,7 @@ addRoute({ name: 'home', path: '/', componentName: 'Home' });
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
HelloWorld.displayName = 'HelloWorld';
|
HelloWorld.displayName = 'HelloWorld';
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ const Icon = ({ name, iconClass, onClick }) => {
|
||||||
iconClass = (typeof iconClass === 'string') ? ' '+iconClass : '';
|
iconClass = (typeof iconClass === 'string') ? ' '+iconClass : '';
|
||||||
const c = 'icon fa fa-fw fa-' + iconCode + ' icon-' + name + iconClass;
|
const c = 'icon fa fa-fw fa-' + iconCode + ' icon-' + name + iconClass;
|
||||||
return <i onClick={onClick} className={c} aria-hidden="true"></i>;
|
return <i onClick={onClick} className={c} aria-hidden="true"></i>;
|
||||||
}
|
};
|
||||||
|
|
||||||
Icon.displayName = 'Icon';
|
Icon.displayName = 'Icon';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Components, registerComponent } from 'meteor/vulcan:lib';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const Layout = ({children}) =>
|
const Layout = ({children}) =>
|
||||||
<div className="wrapper" id="wrapper">{children}</div>
|
<div className="wrapper" id="wrapper">{children}</div>;
|
||||||
|
|
||||||
Layout.displayName = 'Layout';
|
Layout.displayName = 'Layout';
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ const Loading = props => {
|
||||||
<div className="bounce2"></div>
|
<div className="bounce2"></div>
|
||||||
<div className="bounce3"></div>
|
<div className="bounce3"></div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
Loading.displayName = 'Loading';
|
Loading.displayName = 'Loading';
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class MutationButtonInner extends PureComponent {
|
||||||
successCallback(result);
|
successCallback(result);
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
this.setState({ loading: false });
|
||||||
if(errorCallback) {
|
if(errorCallback) {
|
||||||
errorCallback(error);
|
errorCallback(error);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +47,8 @@ class MutationButtonInner extends PureComponent {
|
||||||
delete rest[mutationName];
|
delete rest[mutationName];
|
||||||
delete rest.mutationOptions;
|
delete rest.mutationOptions;
|
||||||
delete rest.mutationArguments;
|
delete rest.mutationArguments;
|
||||||
|
delete rest.successCallback;
|
||||||
|
delete rest.errorCallback;
|
||||||
|
|
||||||
return <Components.LoadingButton loading={loading} onClick={this.handleClick} label={label} {...rest}/>;
|
return <Components.LoadingButton loading={loading} onClick={this.handleClick} label={label} {...rest}/>;
|
||||||
}
|
}
|
||||||
|
@ -80,6 +83,6 @@ const LoadingButton = ({ loading, label, onClick, children, ...rest }) => {
|
||||||
</span>
|
</span>
|
||||||
</Components.Button>
|
</Components.Button>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
registerComponent('LoadingButton', LoadingButton);
|
registerComponent('LoadingButton', LoadingButton);
|
||||||
|
|
|
@ -31,8 +31,8 @@ NewForm Component
|
||||||
const NewForm = ({ closeModal, successCallback, ...props }) => {
|
const NewForm = ({ closeModal, successCallback, ...props }) => {
|
||||||
|
|
||||||
const success = successCallback
|
const success = successCallback
|
||||||
? () => {
|
? document => {
|
||||||
successCallback();
|
successCallback(document);
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
: closeModal;
|
: closeModal;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { registerComponent, runCallbacks } from 'meteor/vulcan:lib';
|
import { registerComponent, runCallbacks, runCallbacksAsync } from 'meteor/vulcan:lib';
|
||||||
import { withApollo } from 'react-apollo';
|
import { withApollo } from 'react-apollo';
|
||||||
|
|
||||||
class RouterHook extends PureComponent {
|
class RouterHook extends PureComponent {
|
||||||
|
@ -8,15 +8,17 @@ class RouterHook extends PureComponent {
|
||||||
this.runOnUpdateCallback(props);
|
this.runOnUpdateCallback(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentDidUpdate(nextProps) {
|
||||||
this.runOnUpdateCallback(nextProps);
|
this.runOnUpdateCallback(this.props, nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
runOnUpdateCallback = props => {
|
runOnUpdateCallback = (props, nextProps = {}) => {
|
||||||
const { currentRoute, client } = props;
|
const { currentRoute, client } = props;
|
||||||
// the first argument is an item to iterate on, needed by vulcan:lib/callbacks
|
// the first argument is an item to iterate on, needed by vulcan:lib/callbacks
|
||||||
// note: this item is not used in this specific callback: router.onUpdate
|
// note: this item is not used in this specific callback: router.onUpdate
|
||||||
runCallbacks('router.onUpdate', {}, currentRoute, client.store, client);
|
runCallbacks('router.onUpdate', {}, currentRoute, client.store, client);
|
||||||
|
|
||||||
|
runCallbacksAsync('router.onUpdate.async', props, nextProps);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -12,18 +12,18 @@ const wrapper = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}
|
};
|
||||||
|
|
||||||
const header = {
|
const header = {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}
|
};
|
||||||
|
|
||||||
const code = {
|
const code = {
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
padding: '10px 20px',
|
padding: '10px 20px',
|
||||||
background: 'white',
|
background: 'white',
|
||||||
}
|
};
|
||||||
|
|
||||||
const Welcome = props =>
|
const Welcome = props =>
|
||||||
<div style={wrapper}>
|
<div style={wrapper}>
|
||||||
|
@ -47,7 +47,7 @@ addRoute({ name: 'home', path: '/', componentName: 'HelloWorld' });
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
Welcome.displayName = 'Welcome';
|
Welcome.displayName = 'Welcome';
|
||||||
|
|
||||||
|
|
|
@ -32,5 +32,5 @@ export default function withAccess (options) {
|
||||||
AccessComponent.displayName = `withAccess(${WrappedComponent.displayName})`;
|
AccessComponent.displayName = `withAccess(${WrappedComponent.displayName})`;
|
||||||
|
|
||||||
return withRouter(withCurrentUser(AccessComponent));
|
return withRouter(withCurrentUser(AccessComponent));
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* This HOC will load the global Components.
|
||||||
|
* If a "components" prop is passed, it will be merged with the global Components.
|
||||||
|
*
|
||||||
|
* This allow local replacement of global components, for example if
|
||||||
|
* you want a specific submit button but only for one specific form.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { mergeWithComponents } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
|
const withComponents = C => {
|
||||||
|
const WrappedComponent = ({ components, formComponents, ...otherProps }) => {
|
||||||
|
//if (formComponents){
|
||||||
|
// console.warn('"formComponents" prop is deprecated, use "components" prop instead (same behaviour)');
|
||||||
|
//}
|
||||||
|
const Components = mergeWithComponents(components || formComponents);
|
||||||
|
return <C Components={Components} {...otherProps} />;
|
||||||
|
};
|
||||||
|
WrappedComponent.displayName = `withComponents(${C.displayName})`;
|
||||||
|
WrappedComponent.propTypes = {
|
||||||
|
formComponents: PropTypes.object,
|
||||||
|
components: PropTypes.object
|
||||||
|
};
|
||||||
|
return WrappedComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withComponents;
|
|
@ -30,7 +30,7 @@ import React, { Component } from 'react';
|
||||||
import { graphql } from 'react-apollo';
|
import { graphql } from 'react-apollo';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { createClientTemplate } from 'meteor/vulcan:core';
|
import { createClientTemplate } from 'meteor/vulcan:core';
|
||||||
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
|
import { extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
const withCreate = options => {
|
const withCreate = options => {
|
||||||
const { collectionName, collection } = extractCollectionInfo(options);
|
const { collectionName, collection } = extractCollectionInfo(options);
|
||||||
|
|
|
@ -26,6 +26,6 @@ const withCurrentUser = component => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)(component);
|
)(component);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default withCurrentUser;
|
export default withCurrentUser;
|
||||||
|
|
|
@ -30,7 +30,7 @@ import React, { Component } from 'react';
|
||||||
import { graphql } from 'react-apollo';
|
import { graphql } from 'react-apollo';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { deleteClientTemplate } from 'meteor/vulcan:core';
|
import { deleteClientTemplate } from 'meteor/vulcan:core';
|
||||||
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
|
import { extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
const withDelete = options => {
|
const withDelete = options => {
|
||||||
const { collectionName, collection } = extractCollectionInfo(options);
|
const { collectionName, collection } = extractCollectionInfo(options);
|
||||||
|
|
|
@ -36,16 +36,16 @@ Terms object can have the following properties:
|
||||||
|
|
||||||
import { withApollo, graphql } from 'react-apollo';
|
import { withApollo, graphql } from 'react-apollo';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { getSetting, Utils, multiClientTemplate } from 'meteor/vulcan:lib';
|
import update from 'immutability-helper';
|
||||||
|
import { getSetting, Utils, multiClientTemplate, extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
|
||||||
|
import Mingo from 'mingo';
|
||||||
import compose from 'recompose/compose';
|
import compose from 'recompose/compose';
|
||||||
import withState from 'recompose/withState';
|
import withState from 'recompose/withState';
|
||||||
|
|
||||||
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
|
|
||||||
|
|
||||||
export default function withMulti(options) {
|
export default function withMulti(options) {
|
||||||
// console.log(options)
|
// console.log(options)
|
||||||
|
|
||||||
const {
|
let {
|
||||||
limit = 10,
|
limit = 10,
|
||||||
pollInterval = getSetting('pollInterval', 20000),
|
pollInterval = getSetting('pollInterval', 20000),
|
||||||
enableTotal = true,
|
enableTotal = true,
|
||||||
|
@ -53,6 +53,10 @@ export default function withMulti(options) {
|
||||||
extraQueries
|
extraQueries
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
|
// if this is the SSR process, set pollInterval to null
|
||||||
|
// see https://github.com/apollographql/apollo-client/issues/1704#issuecomment-322995855
|
||||||
|
pollInterval = typeof window === 'undefined' ? null : pollInterval;
|
||||||
|
|
||||||
const { collectionName, collection } = extractCollectionInfo(options);
|
const { collectionName, collection } = extractCollectionInfo(options);
|
||||||
const { fragmentName, fragment } = extractFragmentInfo(options, collectionName);
|
const { fragmentName, fragment } = extractFragmentInfo(options, collectionName);
|
||||||
|
|
||||||
|
@ -156,8 +160,8 @@ export default function withMulti(options) {
|
||||||
typeof providedTerms === 'undefined'
|
typeof providedTerms === 'undefined'
|
||||||
? {
|
? {
|
||||||
/*...props.ownProps.terms,*/ ...props.ownProps.paginationTerms,
|
/*...props.ownProps.terms,*/ ...props.ownProps.paginationTerms,
|
||||||
limit: results.length + props.ownProps.paginationTerms.itemsPerPage
|
limit: results.length + props.ownProps.paginationTerms.itemsPerPage
|
||||||
}
|
}
|
||||||
: providedTerms;
|
: providedTerms;
|
||||||
|
|
||||||
props.ownProps.setPaginationTerms(newTerms);
|
props.ownProps.setPaginationTerms(newTerms);
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default function withMutation({name, args, fragmentName}) {
|
||||||
fragment = getFragment(fragmentName);
|
fragment = getFragment(fragmentName);
|
||||||
fragmentBlock = `{
|
fragmentBlock = `{
|
||||||
...${fragmentName}
|
...${fragmentName}
|
||||||
}`
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args) {
|
if (args) {
|
||||||
|
@ -33,13 +33,13 @@ export default function withMutation({name, args, fragmentName}) {
|
||||||
mutation ${name}(${args1}) {
|
mutation ${name}(${args1}) {
|
||||||
${name}(${args2})${fragmentBlock}
|
${name}(${args2})${fragmentBlock}
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
} else {
|
} else {
|
||||||
mutation = `
|
mutation = `
|
||||||
mutation ${name} {
|
mutation ${name} {
|
||||||
${name}${fragmentBlock}
|
${name}${fragmentBlock}
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return graphql(gql`${mutation}${fragmentName ? fragment : ''}`, {
|
return graphql(gql`${mutation}${fragmentName ? fragment : ''}`, {
|
||||||
|
|
|
@ -2,12 +2,15 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { graphql } from 'react-apollo';
|
import { graphql } from 'react-apollo';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { getSetting, singleClientTemplate, Utils } from 'meteor/vulcan:lib';
|
import { getSetting, singleClientTemplate, Utils, extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
|
|
||||||
|
|
||||||
export default function withSingle(options) {
|
export default function withSingle(options) {
|
||||||
const { pollInterval = getSetting('pollInterval', 20000), enableCache = false, extraQueries } = options;
|
|
||||||
|
let { pollInterval = getSetting('pollInterval', 20000), enableCache = false, extraQueries } = options;
|
||||||
|
|
||||||
|
// if this is the SSR process, set pollInterval to null
|
||||||
|
// see https://github.com/apollographql/apollo-client/issues/1704#issuecomment-322995855
|
||||||
|
pollInterval = typeof window === 'undefined' ? null : pollInterval;
|
||||||
|
|
||||||
const { collectionName, collection } = extractCollectionInfo(options);
|
const { collectionName, collection } = extractCollectionInfo(options);
|
||||||
const { fragmentName, fragment } = extractFragmentInfo(options, collectionName);
|
const { fragmentName, fragment } = extractFragmentInfo(options, collectionName);
|
||||||
|
@ -46,6 +49,7 @@ export default function withSingle(options) {
|
||||||
const propertyName = options.propertyName || 'document';
|
const propertyName = options.propertyName || 'document';
|
||||||
const props = {
|
const props = {
|
||||||
loading: data.loading,
|
loading: data.loading,
|
||||||
|
refetch: data.refetch,
|
||||||
// document: Utils.convertDates(collection, data[singleResolverName]),
|
// document: Utils.convertDates(collection, data[singleResolverName]),
|
||||||
[propertyName]: data[resolverName] && data[resolverName].result,
|
[propertyName]: data[resolverName] && data[resolverName].result,
|
||||||
fragmentName,
|
fragmentName,
|
||||||
|
|
32
packages/vulcan-core/lib/modules/containers/withSiteData.js
Normal file
32
packages/vulcan-core/lib/modules/containers/withSiteData.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { graphql } from 'react-apollo';
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
const withSiteData = component => {
|
||||||
|
|
||||||
|
return graphql(
|
||||||
|
gql`
|
||||||
|
query getSiteData {
|
||||||
|
SiteData {
|
||||||
|
url
|
||||||
|
title
|
||||||
|
sourceVersion
|
||||||
|
logoUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
alias: 'withSiteData',
|
||||||
|
|
||||||
|
props(props) {
|
||||||
|
const { data } = props;
|
||||||
|
return {
|
||||||
|
siteDataLoading: data.loading,
|
||||||
|
siteData: data.SiteData,
|
||||||
|
siteDataData: data,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)(component);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withSiteData;
|
|
@ -30,11 +30,9 @@ Child Props:
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { graphql } from 'react-apollo';
|
import { graphql } from 'react-apollo';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { updateClientTemplate } from 'meteor/vulcan:lib';
|
import { updateClientTemplate, extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
|
||||||
import clone from 'lodash/clone';
|
import clone from 'lodash/clone';
|
||||||
|
|
||||||
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
|
|
||||||
|
|
||||||
const withUpdate = options => {
|
const withUpdate = options => {
|
||||||
const { collectionName, collection } = extractCollectionInfo(options);
|
const { collectionName, collection } = extractCollectionInfo(options);
|
||||||
const { fragmentName, fragment } = extractFragmentInfo(options, collectionName);
|
const { fragmentName, fragment } = extractFragmentInfo(options, collectionName);
|
||||||
|
|
|
@ -33,7 +33,7 @@ import gql from 'graphql-tag';
|
||||||
import { upsertClientTemplate } from 'meteor/vulcan:core';
|
import { upsertClientTemplate } from 'meteor/vulcan:core';
|
||||||
import clone from 'lodash/clone';
|
import clone from 'lodash/clone';
|
||||||
|
|
||||||
import { extractCollectionInfo, extractFragmentInfo } from './handleOptions';
|
import { extractCollectionInfo, extractFragmentInfo } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
const withUpsert = options => {
|
const withUpsert = options => {
|
||||||
const { collectionName, collection } = extractCollectionInfo(options);
|
const { collectionName, collection } = extractCollectionInfo(options);
|
||||||
|
|
|
@ -73,7 +73,7 @@ export function getDefaultMutations(options) {
|
||||||
const collection = context[collectionName];
|
const collection = context[collectionName];
|
||||||
|
|
||||||
// check if current user can pass check function; else throw error
|
// check if current user can pass check function; else throw error
|
||||||
Utils.performCheck(this.check, context.currentUser, data);
|
Utils.performCheck(this.check, context.currentUser, data, '', `${typeName}.create`, collectionName);
|
||||||
|
|
||||||
// pass document to boilerplate newMutator function
|
// pass document to boilerplate newMutator function
|
||||||
return await createMutator({
|
return await createMutator({
|
||||||
|
@ -167,7 +167,7 @@ export function getDefaultMutations(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if user can perform operation; if not throw error
|
// check if user can perform operation; if not throw error
|
||||||
Utils.performCheck(this.check, context.currentUser, document);
|
Utils.performCheck(this.check, context.currentUser, document, document._id, `${typeName}.update`, collectionName);
|
||||||
|
|
||||||
// call editMutator boilerplate function
|
// call editMutator boilerplate function
|
||||||
return await updateMutator({
|
return await updateMutator({
|
||||||
|
@ -282,7 +282,7 @@ export function getDefaultMutations(options) {
|
||||||
throw new Error(`Could not find document to delete for selector: ${JSON.stringify(selector)}`);
|
throw new Error(`Could not find document to delete for selector: ${JSON.stringify(selector)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.performCheck(this.check, context.currentUser, document, context);
|
Utils.performCheck(this.check, context.currentUser, document, context, document._id, `${typeName}.delete`, collectionName);
|
||||||
|
|
||||||
return await deleteMutator({
|
return await deleteMutator({
|
||||||
collection,
|
collection,
|
||||||
|
|
|
@ -4,8 +4,7 @@ Default list, single, and total resolvers
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Utils, debug, debugGroup, debugGroupEnd, Connectors, getTypeName, getCollectionName } from 'meteor/vulcan:lib';
|
import { Utils, debug, debugGroup, debugGroupEnd, Connectors, getTypeName, getCollectionName, throwError } from 'meteor/vulcan:lib';
|
||||||
import { createError } from 'apollo-errors';
|
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
cacheMaxAge: 300
|
cacheMaxAge: 300
|
||||||
|
@ -117,15 +116,22 @@ export function getDefaultResolvers(options) {
|
||||||
if (allowNull) {
|
if (allowNull) {
|
||||||
return { result: null };
|
return { result: null };
|
||||||
} else {
|
} else {
|
||||||
const MissingDocumentError = createError('app.missing_document', { message: 'app.missing_document' });
|
throwError({ id: 'app.missing_document', data: {documentId, selector} });
|
||||||
throw new MissingDocumentError({ data: { documentId, selector } });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if collection has a checkAccess function defined, use it to perform a check on the current document
|
// if collection has a checkAccess function defined, use it to perform a check on the current document
|
||||||
// (will throw an error if check doesn't pass)
|
// (will throw an error if check doesn't pass)
|
||||||
if (collection.checkAccess) {
|
if (collection.checkAccess) {
|
||||||
Utils.performCheck(collection.checkAccess, currentUser, doc, collection, documentId);
|
Utils.performCheck(
|
||||||
|
collection.checkAccess,
|
||||||
|
currentUser,
|
||||||
|
doc,
|
||||||
|
collection,
|
||||||
|
documentId,
|
||||||
|
`${typeName}.read.single`,
|
||||||
|
collectionName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const restrictedDoc = Users.restrictViewableFields(currentUser, collection, doc);
|
const restrictedDoc = Users.restrictViewableFields(currentUser, collection, doc);
|
||||||
|
|
|
@ -36,6 +36,7 @@ export { default as withCurrentUser } from './containers/withCurrentUser.js';
|
||||||
export { default as withMutation } from './containers/withMutation.js';
|
export { default as withMutation } from './containers/withMutation.js';
|
||||||
export { default as withUpsert } from './containers/withUpsert.js';
|
export { default as withUpsert } from './containers/withUpsert.js';
|
||||||
|
|
||||||
|
export { default as withComponents } from './containers/withComponents';
|
||||||
|
|
||||||
// OpenCRUD backwards compatibility
|
// OpenCRUD backwards compatibility
|
||||||
export { default as withNew } from './containers/withCreate.js';
|
export { default as withNew } from './containers/withCreate.js';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:core',
|
name: 'vulcan:core',
|
||||||
summary: 'Vulcan core package',
|
summary: 'Vulcan core package',
|
||||||
version: '1.12.8',
|
version: '1.12.13',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git'
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,19 +9,20 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom('1.6.1');
|
api.versionsFrom('1.6.1');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:lib@1.12.8',
|
'vulcan:lib@1.12.13',
|
||||||
'vulcan:i18n@1.12.8',
|
'vulcan:i18n@1.12.13',
|
||||||
'vulcan:users@1.12.8',
|
'vulcan:users@1.12.13',
|
||||||
'vulcan:debug@1.12.8'
|
'vulcan:routing@1.12.13',
|
||||||
|
'vulcan:debug@1.12.13'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.imply(['vulcan:lib@1.12.8']);
|
api.imply(['vulcan:lib@1.12.13']);
|
||||||
|
|
||||||
api.mainModule('lib/server/main.js', 'server');
|
api.mainModule('lib/server/main.js', 'server');
|
||||||
api.mainModule('lib/client/main.js', 'client');
|
api.mainModule('lib/client/main.js', 'client');
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onTest(function (api) {
|
Package.onTest(function (api) {
|
||||||
api.use(['ecmascript', 'meteortesting:mocha', 'vulcan:core']);
|
api.use(['ecmascript', 'meteortesting:mocha', 'vulcan:test', 'vulcan:core']);
|
||||||
api.mainModule('./test/index.js');
|
api.mainModule('./test/index.js');
|
||||||
});
|
});
|
||||||
|
|
74
packages/vulcan-core/test/components.test.js
Normal file
74
packages/vulcan-core/test/components.test.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// setup JSDOM server side for testing (necessary for Enzyme to mount)
|
||||||
|
import 'jsdom-global/register';
|
||||||
|
import React from 'react';
|
||||||
|
import expect from 'expect';
|
||||||
|
import { mount, shallow } from 'enzyme';
|
||||||
|
import { Components } from 'meteor/vulcan:core';
|
||||||
|
import { initComponentTest } from 'meteor/vulcan:test';
|
||||||
|
|
||||||
|
|
||||||
|
// we must import all the other components, so that "registerComponent" is called
|
||||||
|
import '../lib/modules';
|
||||||
|
import Datatable from '../lib/modules/components/Datatable';
|
||||||
|
// stub collection
|
||||||
|
import { createCollection, getDefaultResolvers, getDefaultMutations, registerFragment } from 'meteor/vulcan:core';
|
||||||
|
const createDummyCollection = (typeName, schema) => {
|
||||||
|
return createCollection({
|
||||||
|
collectionName: typeName + 's',
|
||||||
|
typeName,
|
||||||
|
schema,
|
||||||
|
resolvers: getDefaultResolvers(typeName + 's'),
|
||||||
|
mutations: getDefaultMutations(typeName + 's')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const Articles = createDummyCollection('Article', {
|
||||||
|
name: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registerFragment(`
|
||||||
|
fragment ArticlesDefaultFragment on Article {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
// setup Vulcan (load components, initialize fragments)
|
||||||
|
initComponentTest();
|
||||||
|
|
||||||
|
|
||||||
|
describe('vulcan-core/components', function () {
|
||||||
|
describe('DataTable', function () {
|
||||||
|
it('shallow renders DataTable', function () {
|
||||||
|
const wrapper = shallow(<Datatable
|
||||||
|
Components={Components}
|
||||||
|
collection={Articles} />);
|
||||||
|
expect(wrapper).toBeDefined();
|
||||||
|
});
|
||||||
|
it('render a static version', function () {
|
||||||
|
const wrapper = shallow(<Datatable
|
||||||
|
Components={Components}
|
||||||
|
data={[{ name: 'foo' }, { name: 'bar' }]} />);
|
||||||
|
const content = wrapper.find('DatatableContents').first();
|
||||||
|
expect(content).toBeDefined();
|
||||||
|
});
|
||||||
|
const context = {
|
||||||
|
intl: {
|
||||||
|
formatMessage: () => { },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
it('mounts a static version', function () {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Datatable
|
||||||
|
Components={Components}
|
||||||
|
data={[{ name: 'foo' }, { name: 'bar' }]}
|
||||||
|
/>
|
||||||
|
, {
|
||||||
|
context,
|
||||||
|
childContextTypes: context
|
||||||
|
});
|
||||||
|
expect(wrapper).toBeDefined();
|
||||||
|
//const content = wrapper.find('DatatableContents').first();
|
||||||
|
//expect(content).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,18 +1,45 @@
|
||||||
import { extractCollectionInfo, extractFragmentInfo } from '../lib/modules/containers/handleOptions';
|
// setup JSDOM server side for testing (necessary for Enzyme to mount)
|
||||||
|
import 'jsdom-global/register';
|
||||||
|
import React from 'react';
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { Components } from 'meteor/vulcan:core';
|
||||||
|
import { initComponentTest } from 'meteor/vulcan:test';
|
||||||
|
import { withComponents } from '../lib/modules';
|
||||||
|
|
||||||
describe('vulcan-core/containers', function() {
|
|
||||||
describe('handleOptions', function() {
|
// we must import all the other components, so that "registerComponent" is called
|
||||||
|
import '../lib/modules';
|
||||||
|
// setup Vulcan (load components, initialize fragments)
|
||||||
|
initComponentTest();
|
||||||
|
|
||||||
|
|
||||||
|
describe('vulcan-core/containers', function () {
|
||||||
|
describe('withComponents', function () {
|
||||||
|
it('should override components', function () {
|
||||||
|
// replace any component for testing purpose
|
||||||
|
const firstComponentName = Components[Object.keys(Components)[0]];
|
||||||
|
const FooComponent = () => 'FOO';
|
||||||
|
const components = { [firstComponentName]: FooComponent };
|
||||||
|
const MyComponent = withComponents(({ Components }) => Components[firstComponentName]());
|
||||||
|
const wrapper = shallow(<MyComponent components={components} />);
|
||||||
|
expect(wrapper.prop('Components')).toBeDefined();
|
||||||
|
expect(wrapper.prop('Components')[firstComponentName]).toEqual(FooComponent);
|
||||||
|
expect(wrapper.html()).toEqual('FOO');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('handleOptions', function () {
|
||||||
const expectedCollectionName = 'COLLECTION_NAME';
|
const expectedCollectionName = 'COLLECTION_NAME';
|
||||||
const collectionNameOptions = { collectionName: expectedCollectionName };
|
const collectionNameOptions = { collectionName: expectedCollectionName };
|
||||||
const expectedCollection = { options: collectionNameOptions };
|
const expectedCollection = { options: collectionNameOptions };
|
||||||
it('get collectionName from collection', function() {
|
it('get collectionName from collection', function () {
|
||||||
const options = { collection: expectedCollection };
|
const options = { collection: expectedCollection };
|
||||||
const { collection, collectionName } = extractCollectionInfo(options);
|
const { collection, collectionName } = extractCollectionInfo(options);
|
||||||
expect(collection).toEqual(expectedCollection);
|
expect(collection).toEqual(expectedCollection);
|
||||||
expect(collectionName).toEqual(expectedCollectionName);
|
expect(collectionName).toEqual(expectedCollectionName);
|
||||||
});
|
});
|
||||||
it('get collection from collectioName', function() {
|
it('get collection from collectioName', function () {
|
||||||
// MOCK getCollection
|
// MOCK getCollection
|
||||||
const { collection, collectionName } = extractCollectionInfo(collectionNameOptions);
|
const { collection, collectionName } = extractCollectionInfo(collectionNameOptions);
|
||||||
expect(collection).toEqual(expectedCollection);
|
expect(collection).toEqual(expectedCollection);
|
||||||
|
@ -20,20 +47,20 @@ describe('vulcan-core/containers', function() {
|
||||||
});
|
});
|
||||||
const expectedFragmentName = 'FRAGMENT_NAME';
|
const expectedFragmentName = 'FRAGMENT_NAME';
|
||||||
const expectedFragment = { definitions: [{ name: { value: expectedFragmentName } }] };
|
const expectedFragment = { definitions: [{ name: { value: expectedFragmentName } }] };
|
||||||
it('get fragment from fragmentName', function() {
|
it('get fragment from fragmentName', function () {
|
||||||
// MOCK getCollection
|
// MOCK getCollection
|
||||||
const options = { fragmentName: expectedFragmentName };
|
const options = { fragmentName: expectedFragmentName };
|
||||||
const { fragment, fragmentName } = extractFragmentInfo(options);
|
const { fragment, fragmentName } = extractFragmentInfo(options);
|
||||||
expect(fragment).toEqual(expectedFragment);
|
expect(fragment).toEqual(expectedFragment);
|
||||||
expect(fragmentName).toEqual(expectedFragmentName);
|
expect(fragmentName).toEqual(expectedFragmentName);
|
||||||
});
|
});
|
||||||
it('get fragmentName from fragment', function() {
|
it('get fragmentName from fragment', function () {
|
||||||
const options = { fragment: expectedFragment };
|
const options = { fragment: expectedFragment };
|
||||||
const { fragment, fragmentName } = extractFragmentInfo(options);
|
const { fragment, fragmentName } = extractFragmentInfo(options);
|
||||||
expect(fragment).toEqual(expectedFragment);
|
expect(fragment).toEqual(expectedFragment);
|
||||||
expect(fragmentName).toEqual(expectedFragmentName);
|
expect(fragmentName).toEqual(expectedFragmentName);
|
||||||
});
|
});
|
||||||
it('get fragmentName and fragment from collectionName', function() {
|
it('get fragmentName and fragment from collectionName', function () {
|
||||||
// if options does not contain fragment, we get the collection default fragment based on its name
|
// if options does not contain fragment, we get the collection default fragment based on its name
|
||||||
const options = {};
|
const options = {};
|
||||||
const { fragment, fragmentName } = extractFragmentInfo(options, expectedCollectionName);
|
const { fragment, fragmentName } = extractFragmentInfo(options, expectedCollectionName);
|
||||||
|
@ -42,13 +69,13 @@ describe('vulcan-core/containers', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('withMessages', function() {
|
describe('withMessages', function () {
|
||||||
const WrappedComponent = props => <div />;
|
const WrappedComponent = props => <div />;
|
||||||
const apolloClient = null; // TODO: init an apolloClient, that must be available in the context
|
const apolloClient = null; // TODO: init an apolloClient, that must be available in the context
|
||||||
it.skip('pass messages', function() {});
|
it.skip('pass messages', function () { });
|
||||||
it.skip('add a flash message', function() {});
|
it.skip('add a flash message', function () { });
|
||||||
it.skip('mark a flash message as seen', function() {});
|
it.skip('mark a flash message as seen', function () { });
|
||||||
it.skip('hide a flash message as seen', function() {});
|
it.skip('hide a flash message as seen', function () { });
|
||||||
it.skip('clear seen', function() {});
|
it.skip('clear seen', function () { });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
import './containers.test.js';
|
import './resolvers.test';
|
||||||
|
import './components.test';
|
||||||
|
import './containers.test';
|
||||||
|
|
84
packages/vulcan-core/test/resolvers.test.js
Normal file
84
packages/vulcan-core/test/resolvers.test.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import { getDefaultResolvers } from '../lib/modules/default_resolvers';
|
||||||
|
|
||||||
|
describe('vulcan:core/default_resolvers', function() {
|
||||||
|
const resolversOptions = {
|
||||||
|
typeName: 'Dummy',
|
||||||
|
collectionName: 'Dummies',
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
describe('single', function() {
|
||||||
|
it('defines the correct fields', function() {
|
||||||
|
const { single } = getDefaultResolvers(resolversOptions);
|
||||||
|
const { description, resolver } = single;
|
||||||
|
expect(description).toBeDefined();
|
||||||
|
expect(resolver).toBeDefined();
|
||||||
|
expect(resolver).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
|
const buildContext = ({ load = () => null, currentUser = null }) => ({
|
||||||
|
Dummies: {
|
||||||
|
options: { collectionName: 'Dummies' },
|
||||||
|
loader: { load }
|
||||||
|
//findOne() {
|
||||||
|
// console.log('FINDE_ONE');
|
||||||
|
//}
|
||||||
|
}, //TODO fake collection
|
||||||
|
Users: {
|
||||||
|
restrictViewableFields: (currentUser, collection, doc) => doc
|
||||||
|
},
|
||||||
|
currentUser
|
||||||
|
});
|
||||||
|
// TODO: what's the name of this argument? handles cache
|
||||||
|
const lastArg = { cacheControl: {} };
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const loggedInUser = { _id: 'foobar', groups: [], isAdmin: false };
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const adminUser = { _id: 'foobar', groups: [], isAdmin: true };
|
||||||
|
const getSingleResolver = () => getDefaultResolvers(resolversOptions).single.resolver;
|
||||||
|
|
||||||
|
// TODO: the current behaviour is not consistent, could be improved
|
||||||
|
// @see https://github.com/VulcanJS/Vulcan/issues/2118
|
||||||
|
it.skip('return null if documentId is undefined', function() {
|
||||||
|
const resolver = getSingleResolver();
|
||||||
|
// no documentId
|
||||||
|
const input = { selector: {} };
|
||||||
|
// non empty db
|
||||||
|
const context = buildContext({ load: () => ({ _id: 'my-document' }) });
|
||||||
|
const res = resolver(null, { input }, context, lastArg);
|
||||||
|
return expect(res).resolves.toEqual({ result: null });
|
||||||
|
});
|
||||||
|
it('return document in case of success', function() {
|
||||||
|
const resolver = getSingleResolver();
|
||||||
|
const documentId = 'my-document';
|
||||||
|
const document = { _id: documentId };
|
||||||
|
const input = { selector: { documentId } };
|
||||||
|
// non empty db
|
||||||
|
const context = buildContext({
|
||||||
|
load: () => {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const res = resolver(null, { input }, context, lastArg);
|
||||||
|
return expect(res).resolves.toEqual({ result: document });
|
||||||
|
});
|
||||||
|
it('return null if failure to find doc but allowNull is true', function() {
|
||||||
|
const resolver = getSingleResolver();
|
||||||
|
const documentId = 'bad-document';
|
||||||
|
const input = { selector: { documentId }, allowNull: true };
|
||||||
|
// empty db
|
||||||
|
const context = buildContext({ load: () => null });
|
||||||
|
const res = resolver(null, { input }, context, lastArg);
|
||||||
|
return expect(res).resolves.toEqual({ result: null });
|
||||||
|
});
|
||||||
|
it('throws if documentId is defined but does not match any document', function() {
|
||||||
|
const resolver = getSingleResolver();
|
||||||
|
const documentId = 'bad-document';
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const document = { _id: documentId };
|
||||||
|
const input = { selector: { documentId } };
|
||||||
|
// empty db
|
||||||
|
const context = buildContext({ load: () => null });
|
||||||
|
return expect(resolver(null, { input }, context, lastArg)).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,8 +3,8 @@ import { Components, registerComponent } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
const adminStyles = {
|
const adminStyles = {
|
||||||
padding: '20px'
|
padding: '20px'
|
||||||
}
|
};
|
||||||
|
|
||||||
const AdminLayout = props => <div className="admin-layout" style={adminStyles}>{props.children}</div>
|
const AdminLayout = props => <div className="admin-layout" style={adminStyles}>{props.children}</div>;
|
||||||
|
|
||||||
registerComponent('AdminLayout', AdminLayout);
|
registerComponent('AdminLayout', AdminLayout);
|
|
@ -3,7 +3,7 @@ import { registerComponent, Components } from 'meteor/vulcan:lib';
|
||||||
import Callbacks from '../modules/callbacks/collection.js';
|
import Callbacks from '../modules/callbacks/collection.js';
|
||||||
|
|
||||||
const CallbacksName = ({ document }) =>
|
const CallbacksName = ({ document }) =>
|
||||||
<strong>{document.name}</strong>
|
<strong>{document.name}</strong>;
|
||||||
|
|
||||||
const CallbacksDashboard = props =>
|
const CallbacksDashboard = props =>
|
||||||
<div className="settings">
|
<div className="settings">
|
||||||
|
@ -23,7 +23,7 @@ const CallbacksDashboard = props =>
|
||||||
'hooks',
|
'hooks',
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
registerComponent('Callbacks', CallbacksDashboard);
|
registerComponent('Callbacks', CallbacksDashboard);
|
||||||
|
|
||||||
|
|
36
packages/vulcan-debug/lib/components/Dashboard.jsx
Normal file
36
packages/vulcan-debug/lib/components/Dashboard.jsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { registerComponent } from 'meteor/vulcan:lib';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
|
function Dashboard() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Debug Dashboard</h3>
|
||||||
|
<ul>
|
||||||
|
<li key="Callbacks">
|
||||||
|
<Link to="/debug/callbacks">Callbacks</Link>
|
||||||
|
</li>
|
||||||
|
<li key="Components">
|
||||||
|
<Link to="/debug/components">Components</Link>
|
||||||
|
</li>
|
||||||
|
<li key="Emails">
|
||||||
|
<Link to="/debug/emails">Emails</Link>
|
||||||
|
</li>
|
||||||
|
<li key="Groups">
|
||||||
|
<Link to="/debug/groups">Groups</Link>
|
||||||
|
</li>
|
||||||
|
<li key="I18n">
|
||||||
|
<Link to="/debug/i18n">I18n</Link>
|
||||||
|
</li>
|
||||||
|
<li key="Routes">
|
||||||
|
<Link to="/debug/routes">Routes</Link>
|
||||||
|
</li>
|
||||||
|
<li key="Settings">
|
||||||
|
<Link to="/debug/settings">Settings</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerComponent({ name: 'DebugDashboard', component: Dashboard, hocs: [] });
|
|
@ -10,7 +10,7 @@ class Email extends PureComponent {
|
||||||
this.sendTest = this.sendTest.bind(this);
|
this.sendTest = this.sendTest.bind(this);
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: false
|
loading: false
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTest() {
|
sendTest() {
|
||||||
|
@ -44,7 +44,7 @@ class Email extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ const Emails = (/* props*/) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerComponent('Emails', Emails);
|
registerComponent('Emails', Emails);
|
||||||
|
|
|
@ -8,8 +8,8 @@ const Group = ({name, actions}) => {
|
||||||
<td>{name}</td>
|
<td>{name}</td>
|
||||||
<td><ul>{actions.map((action, index) => <li key={index}><code>{action}</code></li>)}</ul></td>
|
<td><ul>{actions.map((action, index) => <li key={index}><code>{action}</code></li>)}</ul></td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const Groups = props => {
|
const Groups = props => {
|
||||||
return (
|
return (
|
||||||
|
@ -33,8 +33,8 @@ const Groups = props => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
registerComponent('Groups', Groups);
|
registerComponent('Groups', Groups);
|
||||||
|
|
||||||
|
|
70
packages/vulcan-debug/lib/components/I18n.jsx
Normal file
70
packages/vulcan-debug/lib/components/I18n.jsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { registerComponent, Components, Strings, Locales } from 'meteor/vulcan:lib';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import sortedUniq from 'lodash/sortedUniq';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internationalization debugging page
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
function LocaleSwitcher(props, context) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>Switch locales :</span>
|
||||||
|
{Locales.map(localeObj => (
|
||||||
|
<Components.Button key={localeObj.id} onClick={() => context.setLocale(localeObj.id)}>
|
||||||
|
{localeObj.label}
|
||||||
|
</Components.Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LocaleSwitcher.contextTypes = {
|
||||||
|
getLocale: PropTypes.func,
|
||||||
|
setLocale: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const I18n = (props, context) => {
|
||||||
|
// translations holds all the translations ids
|
||||||
|
let translations = [];
|
||||||
|
let columns = [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
component: function({ document }) {
|
||||||
|
return document;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// reunite all the ids in a single array (translations) and create the columns for each language
|
||||||
|
Object.keys(Strings).forEach(language => {
|
||||||
|
translations.push(...Object.keys(Strings[language]));
|
||||||
|
columns.push({
|
||||||
|
name: language,
|
||||||
|
component: function({ document }) {
|
||||||
|
return Strings[language][document] || null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//sort the array
|
||||||
|
translations.sort();
|
||||||
|
//remove duplicates
|
||||||
|
let translationsUniq = sortedUniq(translations);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{'Your current locale: ' + context.getLocale()}</h3>
|
||||||
|
<LocaleSwitcher />
|
||||||
|
<Components.Datatable showSearch={false} showNew={false} showEdit={false} data={translationsUniq} columns={columns} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
I18n.contextTypes = {
|
||||||
|
getLocale: PropTypes.func,
|
||||||
|
setLocale: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
registerComponent({ name: 'I18n', component: I18n, hocs: [] });
|
|
@ -3,7 +3,7 @@ import { registerComponent, Components, Routes } from 'meteor/vulcan:lib';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const RoutePath = ({ document }) =>
|
const RoutePath = ({ document }) =>
|
||||||
<Link to={document.path}>{document.path}</Link>
|
<Link to={document.path}>{document.path}</Link>;
|
||||||
|
|
||||||
const RoutesDashboard = props =>
|
const RoutesDashboard = props =>
|
||||||
<div className="routes">
|
<div className="routes">
|
||||||
|
@ -21,6 +21,6 @@ const RoutesDashboard = props =>
|
||||||
'componentName',
|
'componentName',
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
registerComponent('Routes', RoutesDashboard);
|
registerComponent('Routes', RoutesDashboard);
|
|
@ -3,7 +3,7 @@ import { registerComponent, Components } from 'meteor/vulcan:lib';
|
||||||
import Settings from '../modules/settings/collection.js';
|
import Settings from '../modules/settings/collection.js';
|
||||||
|
|
||||||
const SettingName = ({ document }) =>
|
const SettingName = ({ document }) =>
|
||||||
<strong>{document.name}</strong>
|
<strong>{document.name}</strong>;
|
||||||
|
|
||||||
const SettingsDashboard = props =>
|
const SettingsDashboard = props =>
|
||||||
<div className="settings">
|
<div className="settings">
|
||||||
|
@ -20,7 +20,7 @@ const SettingsDashboard = props =>
|
||||||
'serverOnly'
|
'serverOnly'
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
registerComponent('Settings', SettingsDashboard);
|
registerComponent('Settings', SettingsDashboard);
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,5 @@ import '../components/Settings.jsx';
|
||||||
import '../components/Callbacks.jsx';
|
import '../components/Callbacks.jsx';
|
||||||
import '../components/Routes.jsx';
|
import '../components/Routes.jsx';
|
||||||
import '../components/Components.jsx';
|
import '../components/Components.jsx';
|
||||||
|
import '../components/I18n.jsx';
|
||||||
|
import '../components/Dashboard.jsx';
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { addRoute, getDynamicComponent } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
addRoute([
|
addRoute([
|
||||||
// {name: 'cheatsheet', path: '/cheatsheet', component: import('./components/Cheatsheet.jsx')},
|
// {name: 'cheatsheet', path: '/cheatsheet', component: import('./components/Cheatsheet.jsx')},
|
||||||
{name: 'groups', path: '/groups', component: () => getDynamicComponent(import('../components/Groups.jsx')), layoutName: 'AdminLayout'},
|
{ name: 'debug', path: '/debug', componentName: 'DebugDashboard', layoutName: 'AdminLayout' },
|
||||||
{name: 'settings', path: '/settings', componentName: 'Settings', layoutName: 'AdminLayout'},
|
{ name: 'debugGroups', path: '/debug/groups', component: () => getDynamicComponent(import('../components/Groups.jsx')), layoutName: 'AdminLayout' },
|
||||||
{name: 'callbacks', path: '/callbacks', componentName: 'Callbacks', layoutName: 'AdminLayout'},
|
{ name: 'debugSettings', path: '/debug/settings', componentName: 'Settings', layoutName: 'AdminLayout' },
|
||||||
|
{ name: 'debugCallbacks', path: '/debug/callbacks', componentName: 'Callbacks', layoutName: 'AdminLayout' },
|
||||||
// {name: 'emails', path: '/emails', component: () => getDynamicComponent(import('./components/Emails.jsx'))},
|
// {name: 'emails', path: '/emails', component: () => getDynamicComponent(import('./components/Emails.jsx'))},
|
||||||
{name: 'emails', path: '/emails', componentName: 'Emails', layoutName: 'AdminLayout'},
|
{ name: 'debugEmails', path: '/debug/emails', componentName: 'Emails', layoutName: 'AdminLayout' },
|
||||||
{name: 'routes', path: '/routes', componentName: 'Routes', layoutName: 'AdminLayout'},
|
{ name: 'debugRoutes', path: '/debug/routes', componentName: 'Routes', layoutName: 'AdminLayout' },
|
||||||
{name: 'components', path: '/components', componentName: 'Components', layoutName: 'AdminLayout'},
|
{ name: 'debugComponents', path: '/debug/components', componentName: 'Components', layoutName: 'AdminLayout' },
|
||||||
]);
|
{ name: 'debugI18n', path: '/debug/i18n', componentName: 'I18n', layoutName: 'AdminLayout' },
|
||||||
|
]);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:debug',
|
name: 'vulcan:debug',
|
||||||
summary: 'Vulcan debug package',
|
summary: 'Vulcan debug package',
|
||||||
version: '1.12.8',
|
version: '1.12.13',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git',
|
git: 'https://github.com/VulcanJS/Vulcan.git',
|
||||||
debugOnly: true
|
debugOnly: true
|
||||||
});
|
});
|
||||||
|
@ -12,13 +12,13 @@ Package.onUse(function (api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
|
|
||||||
'fourseven:scss@4.5.0',
|
'fourseven:scss@4.10.0',
|
||||||
'dynamic-import@0.1.1',
|
'dynamic-import@0.1.1',
|
||||||
|
|
||||||
// Vulcan packages
|
// Vulcan packages
|
||||||
|
|
||||||
'vulcan:lib@1.12.8',
|
'vulcan:lib@1.12.13',
|
||||||
'vulcan:email@1.12.8',
|
'vulcan:email@1.12.13',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,27 @@ import VulcanEmail from '../namespace.js';
|
||||||
import Juice from 'juice';
|
import Juice from 'juice';
|
||||||
import htmlToText from 'html-to-text';
|
import htmlToText from 'html-to-text';
|
||||||
import Handlebars from 'handlebars';
|
import Handlebars from 'handlebars';
|
||||||
import { Utils, getSetting, registerSetting, runQuery, Strings } from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core is not loaded yet
|
import { Utils, getSetting, registerSetting, runQuery, Strings, getString } from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core is not loaded yet
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Get intl string. Usage: {{__ "posts.create"}}
|
||||||
|
|
||||||
|
*/
|
||||||
|
Handlebars.registerHelper('__', function(id, context) {
|
||||||
|
const s = getString({ id, locale: context.data.root.locale });
|
||||||
|
return new Handlebars.SafeString(s);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Get intl string, accepts a second variables argument. Usage: {{__ "posts.create" postVariables}}
|
||||||
|
|
||||||
|
*/
|
||||||
|
Handlebars.registerHelper('___', function(id, variables, context) {
|
||||||
|
const s = getString({ id, variables, locale: context.data.root.locale });
|
||||||
|
return new Handlebars.SafeString(s);
|
||||||
|
});
|
||||||
|
|
||||||
registerSetting('secondaryColor', '#444444');
|
registerSetting('secondaryColor', '#444444');
|
||||||
registerSetting('accentColor', '#DD3416');
|
registerSetting('accentColor', '#DD3416');
|
||||||
|
@ -23,8 +43,12 @@ VulcanEmail.addTemplates = templates => {
|
||||||
_.extend(VulcanEmail.templates, templates);
|
_.extend(VulcanEmail.templates, templates);
|
||||||
};
|
};
|
||||||
|
|
||||||
VulcanEmail.getTemplate = templateName =>
|
VulcanEmail.getTemplate = templateName => {
|
||||||
Handlebars.compile(VulcanEmail.templates[templateName], { noEscape: true, strict: true });
|
if (!VulcanEmail.templates[templateName]) {
|
||||||
|
throw new Error(`Couldn't find email template named “${templateName}”`);
|
||||||
|
}
|
||||||
|
return Handlebars.compile(VulcanEmail.templates[templateName], { noEscape: true, strict: true });
|
||||||
|
};
|
||||||
|
|
||||||
VulcanEmail.buildTemplate = (htmlContent, data = {}, locale) => {
|
VulcanEmail.buildTemplate = (htmlContent, data = {}, locale) => {
|
||||||
const emailProperties = {
|
const emailProperties = {
|
||||||
|
@ -58,18 +82,18 @@ VulcanEmail.generateTextVersion = html => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo) => {
|
VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo, headers) => {
|
||||||
// TODO: limit who can send emails
|
// TODO: limit who can send emails
|
||||||
// TODO: fix this error: Error: getaddrinfo ENOTFOUND
|
// TODO: fix this error: Error: getaddrinfo ENOTFOUND
|
||||||
|
|
||||||
if (typeof to === 'object') {
|
if (typeof to === 'object') {
|
||||||
// eslint-disable-next-line no-redeclare
|
// eslint-disable-next-line no-redeclare
|
||||||
var { to, cc, bcc, replyTo, subject, html, text, throwErrors } = to;
|
var { to, cc, bcc, replyTo, subject, html, text, throwErrors, headers } = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
const from = getSetting('defaultEmail', 'noreply@example.com');
|
const from = getSetting('defaultEmail', 'noreply@example.com');
|
||||||
const siteName = getSetting('title', 'Vulcan');
|
const siteName = getSetting('title', 'Vulcan');
|
||||||
subject = '[' + siteName + '] ' + subject;
|
subject = subject || '[' + siteName + ']';
|
||||||
|
|
||||||
if (typeof text === 'undefined') {
|
if (typeof text === 'undefined') {
|
||||||
// Auto-generate text version if it doesn't exist. Has bugs, but should be good enough.
|
// Auto-generate text version if it doesn't exist. Has bugs, but should be good enough.
|
||||||
|
@ -83,19 +107,22 @@ VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo) => {
|
||||||
bcc: bcc,
|
bcc: bcc,
|
||||||
replyTo: replyTo,
|
replyTo: replyTo,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
|
headers: headers,
|
||||||
text: text,
|
text: text,
|
||||||
html: html,
|
html: html,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production' || getSetting('enableDevelopmentEmails', false)) {
|
const shouldSendEmail = process.env.NODE_ENV === 'production' || getSetting('enableDevelopmentEmails', false);
|
||||||
console.log('//////// sending email…'); // eslint-disable-line
|
|
||||||
console.log('from: ' + from); // eslint-disable-line
|
|
||||||
console.log('cc: ' + cc); // eslint-disable-line
|
|
||||||
console.log('bcc: ' + bcc); // eslint-disable-line
|
|
||||||
console.log('replyTo: ' + replyTo); // eslint-disable-line
|
|
||||||
// console.log('html: '+html);
|
|
||||||
// console.log('text: '+text);
|
|
||||||
|
|
||||||
|
console.log(`//////// sending email${shouldSendEmail ? '' : ' (simulation)'}…`); // eslint-disable-line
|
||||||
|
console.log('from: ' + from); // eslint-disable-line
|
||||||
|
console.log('to: ' + to); // eslint-disable-line
|
||||||
|
console.log('cc: ' + cc); // eslint-disable-line
|
||||||
|
console.log('bcc: ' + bcc); // eslint-disable-line
|
||||||
|
console.log('replyTo: ' + replyTo); // eslint-disable-line
|
||||||
|
console.log('headers: ' + JSON.stringify(headers)); // eslint-disable-line
|
||||||
|
|
||||||
|
if (shouldSendEmail) {
|
||||||
try {
|
try {
|
||||||
Email.send(email);
|
Email.send(email);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -103,13 +130,6 @@ VulcanEmail.send = (to, subject, html, text, throwErrors, cc, bcc, replyTo) => {
|
||||||
console.log(error); // eslint-disable-line
|
console.log(error); // eslint-disable-line
|
||||||
if (throwErrors) throw error;
|
if (throwErrors) throw error;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.log('//////// sending email (simulation)…'); // eslint-disable-line
|
|
||||||
console.log('from: ' + from); // eslint-disable-line
|
|
||||||
console.log('to: ' + to); // eslint-disable-line
|
|
||||||
console.log('cc: ' + cc); // eslint-disable-line
|
|
||||||
console.log('bcc: ' + bcc); // eslint-disable-line
|
|
||||||
console.log('replyTo: ' + replyTo); // eslint-disable-line
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return email;
|
return email;
|
||||||
|
@ -121,20 +141,21 @@ VulcanEmail.build = async ({ emailName, variables, locale }) => {
|
||||||
const result = email.query ? await runQuery(email.query, variables, { locale }) : { data: {} };
|
const result = email.query ? await runQuery(email.query, variables, { locale }) : { data: {} };
|
||||||
|
|
||||||
// if email has a data() function, merge its return value with results from the query
|
// if email has a data() function, merge its return value with results from the query
|
||||||
const data = email.data ? { ...result.data, ...email.data(variables) } : result.data;
|
const data = email.data ? { ...result.data, ...email.data({ data: result.data, variables, locale }) } : result.data;
|
||||||
|
|
||||||
const subject = typeof email.subject === 'function' ? email.subject(data) : email.subject;
|
const subject = typeof email.subject === 'function' ? email.subject({ data, variables, locale }) : email.subject;
|
||||||
|
|
||||||
data.__ = Strings[locale];
|
data.__ = Strings[locale];
|
||||||
|
data.locale = locale;
|
||||||
|
|
||||||
const html = VulcanEmail.buildTemplate(VulcanEmail.getTemplate(email.template)(data), data, locale);
|
const html = VulcanEmail.buildTemplate(VulcanEmail.getTemplate(email.template)(data), data, locale);
|
||||||
|
|
||||||
return { data, subject, html };
|
return { data, subject, html };
|
||||||
};
|
};
|
||||||
|
|
||||||
VulcanEmail.buildAndSend = async ({ to, cc, bcc, replyTo, emailName, variables, locale = getSetting('locale') }) => {
|
VulcanEmail.buildAndSend = async ({ to, cc, bcc, replyTo, emailName, variables, locale = getSetting('locale'), headers }) => {
|
||||||
const email = await VulcanEmail.build({ to, emailName, variables, locale });
|
const email = await VulcanEmail.build({ to, emailName, variables, locale });
|
||||||
return VulcanEmail.send({ to, cc, bcc, replyTo, subject: email.subject, html: email.html });
|
return VulcanEmail.send({ to, cc, bcc, replyTo, subject: email.subject, html: email.html, headers });
|
||||||
};
|
};
|
||||||
|
|
||||||
VulcanEmail.buildAndSendHTML = (to, subject, html) => VulcanEmail.send(to, subject, VulcanEmail.buildTemplate(html));
|
VulcanEmail.buildAndSendHTML = (to, subject, html) => VulcanEmail.send(to, subject, VulcanEmail.buildTemplate(html));
|
||||||
|
|
|
@ -15,7 +15,7 @@ Meteor.startup(function() {
|
||||||
|
|
||||||
// else get test object (sample post, comment, user, etc.)
|
// else get test object (sample post, comment, user, etc.)
|
||||||
const testVariables =
|
const testVariables =
|
||||||
(typeof email.testVariables === 'function' ? email.testVariables() : email.testVariables) || {};
|
(typeof email.testVariables === 'function' ? email.testVariables(params) : email.testVariables) || {};
|
||||||
// delete params.query so we don't pass it to GraphQL query
|
// delete params.query so we don't pass it to GraphQL query
|
||||||
delete params.query;
|
delete params.query;
|
||||||
// merge test variables with params from URL
|
// merge test variables with params from URL
|
||||||
|
@ -33,6 +33,11 @@ Meteor.startup(function() {
|
||||||
html = `
|
html = `
|
||||||
${builtHtml}
|
${builtHtml}
|
||||||
<h4 style="margin: 20px;"><code>Subject: ${subject}</code></h4>
|
<h4 style="margin: 20px;"><code>Subject: ${subject}</code></h4>
|
||||||
|
<h5 style="margin: 20px;">Variables:</h5>
|
||||||
|
<div style="border: 1px solid #999; padding: 10px 20px; margin: 20px;">
|
||||||
|
<pre><code>${JSON.stringify(variables, null, 2)}</code></pre>
|
||||||
|
</div>
|
||||||
|
<h5 style="margin: 20px;">Data:</h5>
|
||||||
<div style="border: 1px solid #999; padding: 10px 20px; margin: 20px;">
|
<div style="border: 1px solid #999; padding: 10px 20px; margin: 20px;">
|
||||||
<pre><code>${JSON.stringify(emailTestData, null, 2)}</code></pre>
|
<pre><code>${JSON.stringify(emailTestData, null, 2)}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:email',
|
name: 'vulcan:email',
|
||||||
summary: 'Vulcan email package',
|
summary: 'Vulcan email package',
|
||||||
version: '1.12.8',
|
version: '1.12.13',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git'
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom('1.6.1');
|
api.versionsFrom('1.6.1');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:lib@1.12.8'
|
'vulcan:lib@1.12.13'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule('lib/server.js', 'server');
|
api.mainModule('lib/server.js', 'server');
|
||||||
|
|
|
@ -27,10 +27,10 @@ Embed.builtin = {
|
||||||
title: metadata.title,
|
title: metadata.title,
|
||||||
description: metadata.description,
|
description: metadata.description,
|
||||||
thumbnailUrl: metadata.image,
|
thumbnailUrl: metadata.image,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
// -------------- //
|
// -------------- //
|
||||||
// adapted from https://github.com/acemtp/meteor-meta-extractor/blob/master/meta-extractor.js
|
// adapted from https://github.com/acemtp/meteor-meta-extractor/blob/master/meta-extractor.js
|
||||||
|
|
|
@ -40,7 +40,7 @@ if (settings) {
|
||||||
const embedData = {
|
const embedData = {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description
|
description: data.description
|
||||||
}
|
};
|
||||||
|
|
||||||
if (data.pics && data.pics.length > 0) {
|
if (data.pics && data.pics.length > 0) {
|
||||||
embedData.thumbnailUrl = data.pics[0];
|
embedData.thumbnailUrl = data.pics[0];
|
||||||
|
@ -63,7 +63,7 @@ if (settings) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ if (settings) {
|
||||||
|
|
||||||
|
|
||||||
if (data.images && data.images.length > 0) // there may not always be an image
|
if (data.images && data.images.length > 0) // there may not always be an image
|
||||||
data.thumbnailUrl = data.images[0].url.replace('http:','') // add thumbnailUrl as its own property
|
data.thumbnailUrl = data.images[0].url.replace('http:',''); // add thumbnailUrl as its own property
|
||||||
|
|
||||||
if (data.authors && data.authors.length > 0) {
|
if (data.authors && data.authors.length > 0) {
|
||||||
data.sourceName = data.authors[0].name;
|
data.sourceName = data.authors[0].name;
|
||||||
|
@ -60,7 +60,7 @@ if (settings) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export * from '../modules/index.js';
|
export * from '../modules/index.js';
|
||||||
|
|
||||||
import './integrations/builtin.js'
|
import './integrations/builtin.js';
|
||||||
import './integrations/embedly.js'
|
import './integrations/embedly.js';
|
||||||
import './integrations/embedapi.js'
|
import './integrations/embedapi.js';
|
||||||
|
|
||||||
import './mutations.js';
|
import './mutations.js';
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:embed',
|
name: 'vulcan:embed',
|
||||||
summary: 'Vulcan Embed package',
|
summary: 'Vulcan Embed package',
|
||||||
version: '1.12.8',
|
version: '1.12.13',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git'
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ Package.onUse( function(api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'http',
|
'http',
|
||||||
'vulcan:core@1.12.8',
|
'vulcan:core@1.12.13',
|
||||||
'fourseven:scss@4.5.0'
|
'fourseven:scss@4.10.0'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
1
packages/vulcan-errors-sentry/README.md
Normal file
1
packages/vulcan-errors-sentry/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Vulcan error tracking adapter for Sentry.
|
2
packages/vulcan-errors-sentry/lib/client/main.js
Normal file
2
packages/vulcan-errors-sentry/lib/client/main.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from '../modules/index';
|
||||||
|
import './sentry-client.js';
|
52
packages/vulcan-errors-sentry/lib/client/sentry-client.js
Normal file
52
packages/vulcan-errors-sentry/lib/client/sentry-client.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { getSetting } from 'meteor/vulcan:core';
|
||||||
|
import { addInitFunction, addLogFunction, addUserFunction } from 'meteor/vulcan:errors';
|
||||||
|
import Sentry from '@sentry/browser';
|
||||||
|
import { clientDSNSetting } from '../modules/settings';
|
||||||
|
import { getUserObject } from '../modules/sentry';
|
||||||
|
|
||||||
|
const clientDSN = getSetting(clientDSNSetting);
|
||||||
|
const environment = getSetting('environment');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Initialize Sentry
|
||||||
|
|
||||||
|
*/
|
||||||
|
function initSentryForClient(props = {}) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: clientDSN,
|
||||||
|
environment,
|
||||||
|
release: props.siteData && props.siteData.sourceVersion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addInitFunction(initSentryForClient);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Log an error, and optionally set current user as well
|
||||||
|
|
||||||
|
*/
|
||||||
|
function logToSentry({ error, details, currentUser }) {
|
||||||
|
Sentry.withScope(scope => {
|
||||||
|
if (currentUser) {
|
||||||
|
scope.setUser(getUserObject(currentUser));
|
||||||
|
}
|
||||||
|
Object.keys(details).forEach(key => {
|
||||||
|
scope.setExtra(key, details[key]);
|
||||||
|
});
|
||||||
|
Sentry.captureException(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addLogFunction(logToSentry);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Set the current user
|
||||||
|
|
||||||
|
*/
|
||||||
|
function setSentryUser(currentUser) {
|
||||||
|
Sentry.configureScope(scope => {
|
||||||
|
scope.setUser(getUserObject(currentUser));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addUserFunction(setSentryUser);
|
2
packages/vulcan-errors-sentry/lib/modules/index.js
Normal file
2
packages/vulcan-errors-sentry/lib/modules/index.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './settings';
|
||||||
|
// import './logToRollbar';
|
5
packages/vulcan-errors-sentry/lib/modules/sentry.js
Normal file
5
packages/vulcan-errors-sentry/lib/modules/sentry.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const getUserObject = currentUser => ({
|
||||||
|
id: currentUser._id,
|
||||||
|
username: currentUser.displayName,
|
||||||
|
email: currentUser.email,
|
||||||
|
});
|
8
packages/vulcan-errors-sentry/lib/modules/settings.js
Normal file
8
packages/vulcan-errors-sentry/lib/modules/settings.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { registerSetting } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
export const clientDSNSetting = 'sentry.clientDSN';
|
||||||
|
export const serverDSNSetting = 'sentry.serverDSN';
|
||||||
|
export const tokensUrl = 'https://sentry.io/onboarding/{account}/{project}/configure/node';
|
||||||
|
|
||||||
|
registerSetting(clientDSNSetting, null, `Sentry client DSN access token (from ${tokensUrl})`);
|
||||||
|
registerSetting(serverDSNSetting, null, `Sentry client DSN access token (from ${tokensUrl})`);
|
2
packages/vulcan-errors-sentry/lib/server/main.js
Normal file
2
packages/vulcan-errors-sentry/lib/server/main.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import './sentry-server.js';
|
||||||
|
export * from '../modules/index';
|
53
packages/vulcan-errors-sentry/lib/server/sentry-server.js
Normal file
53
packages/vulcan-errors-sentry/lib/server/sentry-server.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { getSetting, getSourceVersion } from 'meteor/vulcan:core';
|
||||||
|
import { addInitFunction, addLogFunction, addUserFunction } from 'meteor/vulcan:errors';
|
||||||
|
import { serverDSNSetting } from '../modules/settings';
|
||||||
|
import Sentry from '@sentry/node';
|
||||||
|
import { getUserObject } from '../modules/sentry';
|
||||||
|
|
||||||
|
const serverDSN = getSetting(serverDSNSetting);
|
||||||
|
const environment = getSetting('environment');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Initialize Sentry
|
||||||
|
|
||||||
|
*/
|
||||||
|
function initSentryForServer() {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: serverDSN,
|
||||||
|
environment,
|
||||||
|
// see https://github.com/zodern/meteor-up/issues/807#issuecomment-346915622
|
||||||
|
release: getSourceVersion(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addInitFunction(initSentryForServer);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Log an error, and optionally set current user as well
|
||||||
|
|
||||||
|
*/
|
||||||
|
function logToSentry({ error, details, currentUser }) {
|
||||||
|
Sentry.withScope(scope => {
|
||||||
|
if (currentUser) {
|
||||||
|
scope.setUser(getUserObject(currentUser));
|
||||||
|
}
|
||||||
|
Object.keys(details).forEach(key => {
|
||||||
|
scope.setExtra(key, details[key]);
|
||||||
|
});
|
||||||
|
Sentry.captureException(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addLogFunction(logToSentry);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Set the current user
|
||||||
|
|
||||||
|
*/
|
||||||
|
function setSentryUser(currentUser) {
|
||||||
|
Sentry.configureScope(scope => {
|
||||||
|
scope.setUser(getUserObject(currentUser));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addUserFunction(setSentryUser);
|
22
packages/vulcan-errors-sentry/package.js
Executable file
22
packages/vulcan-errors-sentry/package.js
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
Package.describe({
|
||||||
|
name: 'vulcan:errors-sentry',
|
||||||
|
summary: 'Vulcan Sentry error tracking package',
|
||||||
|
version: '1.12.13',
|
||||||
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Package.onUse(function(api) {
|
||||||
|
api.versionsFrom('1.6.1');
|
||||||
|
|
||||||
|
api.use([
|
||||||
|
'ecmascript',
|
||||||
|
'vulcan:core@1.12.13',
|
||||||
|
'vulcan:users@1.12.13',
|
||||||
|
'vulcan:errors@1.12.13',
|
||||||
|
]);
|
||||||
|
|
||||||
|
api.mainModule('lib/server/main.js', 'server');
|
||||||
|
api.mainModule('lib/client/main.js', 'client');
|
||||||
|
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue