mirror of
https://github.com/vale981/Vulcan
synced 2025-03-04 17:21:37 -05:00
Merge branch 'devel'
This commit is contained in:
commit
867495b8ec
138 changed files with 3053 additions and 666 deletions
|
@ -10,7 +10,7 @@ indent_brace_style = 1TBS
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
max_line_length = 80
|
max_line_length = 120
|
||||||
quote_type = auto
|
quote_type = auto
|
||||||
spaces_around_operators = true
|
spaces_around_operators = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
|
@ -50,10 +50,15 @@
|
||||||
"meteor": true,
|
"meteor": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"modules": true,
|
||||||
|
"jsx": true
|
||||||
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"babel",
|
"babel",
|
||||||
"meteor",
|
"meteor",
|
||||||
"react"
|
"react",
|
||||||
|
"prettier"
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": "meteor"
|
"import/resolver": "meteor"
|
||||||
|
|
7
.meteor/.id
Normal file
7
.meteor/.id
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# This file contains a token that is unique to your project.
|
||||||
|
# Check it into your repository along with the rest of this directory.
|
||||||
|
# It can be used for purposes such as:
|
||||||
|
# - ensuring you don't accidentally deploy one app on top of another
|
||||||
|
# - providing package authors with aggregated statistics
|
||||||
|
|
||||||
|
1txv9r51kxht481ysl8bb
|
|
@ -1 +1 @@
|
||||||
METEOR@1.5.2.2
|
METEOR@1.6
|
||||||
|
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost:8080",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
549
package-lock.json
generated
549
package-lock.json
generated
|
@ -1,9 +1,269 @@
|
||||||
{
|
{
|
||||||
"name": "Vulcan",
|
"name": "Vulcan",
|
||||||
"version": "1.2.0",
|
"version": "1.8.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"accepts": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz",
|
||||||
|
"integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=",
|
||||||
|
"requires": {
|
||||||
|
"mime-types": "2.1.17",
|
||||||
|
"negotiator": "0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ajv": {
|
||||||
|
"version": "5.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.4.0.tgz",
|
||||||
|
"integrity": "sha1-MtHPCNvIDEMvQm8S4QslEfa0ZHQ=",
|
||||||
|
"requires": {
|
||||||
|
"co": "4.6.0",
|
||||||
|
"fast-deep-equal": "1.0.0",
|
||||||
|
"fast-json-stable-stringify": "2.0.0",
|
||||||
|
"json-schema-traverse": "0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-cache-control": {
|
||||||
|
"version": "0.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.0.7.tgz",
|
||||||
|
"integrity": "sha512-DoMTr3uTC5Cx9ukSO63wlzHD15C37FwZuoOZEu+m/UTzVFKQ4PnlBKzwZ0H2+iIwcdSulV0xte6Z3wBe9lHAOA==",
|
||||||
|
"requires": {
|
||||||
|
"graphql-extensions": "0.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-engine": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-engine/-/apollo-engine-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-91yqiM1fB33fjvcsIBICy8BUHh2cG9FAIrteCh9QaL7UwJ6aQMV5DSfjNhgP95DEZcPMggKQGLhbW156A7G0mg==",
|
||||||
|
"requires": {
|
||||||
|
"apollo-engine-binary-darwin": "0.2017.11-84-gb299b9188",
|
||||||
|
"apollo-engine-binary-linux": "0.2017.11-84-gb299b9188",
|
||||||
|
"apollo-engine-binary-windows": "0.2017.11-84-gb299b9188",
|
||||||
|
"request": "2.83.0",
|
||||||
|
"stream-line-wrapper": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-engine-binary-darwin": {
|
||||||
|
"version": "0.2017.11-84-gb299b9188",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-engine-binary-darwin/-/apollo-engine-binary-darwin-0.2017.11-84-gb299b9188.tgz",
|
||||||
|
"integrity": "sha512-8zKIFo6ldSwT1npHU4gjHMDEJQuN/CG3MCnx5xY5MGSSkqlqNZZ8njYgXe4qLEjewLMwRTXapcnCw7E2+H1RYQ==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"apollo-engine-binary-linux": {
|
||||||
|
"version": "0.2017.11-84-gb299b9188",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-engine-binary-linux/-/apollo-engine-binary-linux-0.2017.11-84-gb299b9188.tgz",
|
||||||
|
"integrity": "sha512-Y+DYYoR24yi73+Kt03Nr7IXNoMJw6faEgdUpysMdnkIdmqaFfcKj3KH0auzVBhPyVcJo+iRTKqXdnMzjnQxrsg==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"apollo-engine-binary-windows": {
|
||||||
|
"version": "0.2017.11-84-gb299b9188",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-engine-binary-windows/-/apollo-engine-binary-windows-0.2017.11-84-gb299b9188.tgz",
|
||||||
|
"integrity": "sha512-ecpP1HrlP+eb5mNQuz7ObzMWtGJA78UrPlzGRes1KiKJ/c8e1UrrAWI/wuI0Ry7fIKYA6dUzxJ4fHR5TEnMAVA==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"apollo-server-core": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-6FHEdESZG2+J+IUpI3B2uD4B6O4=",
|
||||||
|
"requires": {
|
||||||
|
"apollo-cache-control": "0.0.7",
|
||||||
|
"apollo-tracing": "0.1.1",
|
||||||
|
"graphql-extensions": "0.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-server-express": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-AmsStFO47KxgRLIFtqhf5Zb7X54=",
|
||||||
|
"requires": {
|
||||||
|
"apollo-server-core": "1.2.0",
|
||||||
|
"apollo-server-module-graphiql": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-server-module-graphiql": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-iZ2E87dHeV27/INUqlFiLvA4FRw="
|
||||||
|
},
|
||||||
|
"apollo-tracing": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-OrL0SYpmwNs6R339y7Is6PppOkyooMB1iLSN+HAp1FdBycQ88SqVV5Dqjxb4Du+TrMyyJLHfR5BAENZSFQyWGQ==",
|
||||||
|
"requires": {
|
||||||
|
"graphql-extensions": "0.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"asn1": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
|
||||||
|
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
|
||||||
|
},
|
||||||
|
"assert-plus": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||||
|
},
|
||||||
|
"async": {
|
||||||
|
"version": "0.2.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
||||||
|
"integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E="
|
||||||
|
},
|
||||||
|
"asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||||
|
},
|
||||||
|
"aws-sign2": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||||
|
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
|
||||||
|
},
|
||||||
|
"aws4": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
|
||||||
|
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
|
||||||
|
},
|
||||||
|
"bcrypt-pbkdf": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"tweetnacl": "0.14.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boom": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
|
||||||
|
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
|
||||||
|
"requires": {
|
||||||
|
"hoek": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bytes": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||||
|
},
|
||||||
|
"caseless": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||||
|
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||||
|
},
|
||||||
|
"co": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
||||||
|
},
|
||||||
|
"combined-stream": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
|
||||||
|
"requires": {
|
||||||
|
"delayed-stream": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compressible": {
|
||||||
|
"version": "2.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.12.tgz",
|
||||||
|
"integrity": "sha1-xZpcmdt2dn6YdlAOJx72OzSTvWY=",
|
||||||
|
"requires": {
|
||||||
|
"mime-db": "1.30.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compression": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.1.tgz",
|
||||||
|
"integrity": "sha1-7/JgPvwuIs+G810uuTWJ+YdTc9s=",
|
||||||
|
"requires": {
|
||||||
|
"accepts": "1.3.4",
|
||||||
|
"bytes": "3.0.0",
|
||||||
|
"compressible": "2.0.12",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"on-headers": "1.0.1",
|
||||||
|
"safe-buffer": "5.1.1",
|
||||||
|
"vary": "1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"core-js": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
|
||||||
|
"integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs="
|
||||||
|
},
|
||||||
|
"core-util-is": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
|
},
|
||||||
|
"cryptiles": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
|
||||||
|
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
|
||||||
|
"requires": {
|
||||||
|
"boom": "5.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"boom": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
|
||||||
|
"requires": {
|
||||||
|
"hoek": "4.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dashdash": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
|
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||||
|
},
|
||||||
|
"ecc-jsbn": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"jsbn": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extend": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
|
||||||
|
},
|
||||||
|
"extsprintf": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
|
||||||
|
},
|
||||||
|
"fast-deep-equal": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
|
||||||
|
},
|
||||||
|
"fast-json-stable-stringify": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
|
||||||
|
},
|
||||||
"flat": {
|
"flat": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz",
|
||||||
|
@ -12,10 +272,297 @@
|
||||||
"is-buffer": "1.1.5"
|
"is-buffer": "1.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"forever-agent": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
||||||
|
},
|
||||||
|
"form-data": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
|
||||||
|
"integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "0.4.0",
|
||||||
|
"combined-stream": "1.0.5",
|
||||||
|
"mime-types": "2.1.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"getpass": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"graphql-extensions": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-IbgYhKIyI60Nio/uJjkkiXaOZ2fI8ynAyzcA/okD0iuKzBdWX4Tn6tidMLgd16Bf2v3TtNnyXnN0F2BJDs6e4A==",
|
||||||
|
"requires": {
|
||||||
|
"core-js": "2.5.1",
|
||||||
|
"source-map-support": "0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"har-schema": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
|
||||||
|
},
|
||||||
|
"har-validator": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
||||||
|
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
||||||
|
"requires": {
|
||||||
|
"ajv": "5.4.0",
|
||||||
|
"har-schema": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hawk": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
|
||||||
|
"requires": {
|
||||||
|
"boom": "4.3.1",
|
||||||
|
"cryptiles": "3.1.2",
|
||||||
|
"hoek": "4.2.0",
|
||||||
|
"sntp": "2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hoek": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ=="
|
||||||
|
},
|
||||||
|
"http-signature": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"jsprim": "1.4.1",
|
||||||
|
"sshpk": "1.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"is-buffer": {
|
"is-buffer": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
|
||||||
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
|
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
|
||||||
|
},
|
||||||
|
"is-typedarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||||
|
},
|
||||||
|
"isstream": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||||
|
},
|
||||||
|
"jsbn": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"json-schema": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||||
|
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
||||||
|
},
|
||||||
|
"json-schema-traverse": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
|
||||||
|
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
|
||||||
|
},
|
||||||
|
"json-stringify-safe": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||||
|
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||||
|
},
|
||||||
|
"jsprim": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"extsprintf": "1.3.0",
|
||||||
|
"json-schema": "0.2.3",
|
||||||
|
"verror": "1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mime-db": {
|
||||||
|
"version": "1.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
|
||||||
|
"integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
|
||||||
|
},
|
||||||
|
"mime-types": {
|
||||||
|
"version": "2.1.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
|
||||||
|
"integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
|
||||||
|
"requires": {
|
||||||
|
"mime-db": "1.30.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
|
},
|
||||||
|
"negotiator": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
||||||
|
},
|
||||||
|
"oauth-sign": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||||
|
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
|
||||||
|
},
|
||||||
|
"on-headers": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c="
|
||||||
|
},
|
||||||
|
"performance-now": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||||
|
},
|
||||||
|
"punycode": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||||
|
},
|
||||||
|
"qs": {
|
||||||
|
"version": "6.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
|
||||||
|
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"version": "2.83.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
|
||||||
|
"integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
|
||||||
|
"requires": {
|
||||||
|
"aws-sign2": "0.7.0",
|
||||||
|
"aws4": "1.6.0",
|
||||||
|
"caseless": "0.12.0",
|
||||||
|
"combined-stream": "1.0.5",
|
||||||
|
"extend": "3.0.1",
|
||||||
|
"forever-agent": "0.6.1",
|
||||||
|
"form-data": "2.3.1",
|
||||||
|
"har-validator": "5.0.3",
|
||||||
|
"hawk": "6.0.2",
|
||||||
|
"http-signature": "1.2.0",
|
||||||
|
"is-typedarray": "1.0.0",
|
||||||
|
"isstream": "0.1.2",
|
||||||
|
"json-stringify-safe": "5.0.1",
|
||||||
|
"mime-types": "2.1.17",
|
||||||
|
"oauth-sign": "0.8.2",
|
||||||
|
"performance-now": "2.1.0",
|
||||||
|
"qs": "6.5.1",
|
||||||
|
"safe-buffer": "5.1.1",
|
||||||
|
"stringstream": "0.0.5",
|
||||||
|
"tough-cookie": "2.3.3",
|
||||||
|
"tunnel-agent": "0.6.0",
|
||||||
|
"uuid": "3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
|
||||||
|
},
|
||||||
|
"sntp": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
|
||||||
|
"requires": {
|
||||||
|
"hoek": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
|
},
|
||||||
|
"source-map-support": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-vUoN3I7fHQe0R/SJLKRdKYuEdRGogsviXFkHHo17AWaTGv17VLnxw+CFXvqy+y4ORZ3doWLQcxRYfwKrsd/H7Q==",
|
||||||
|
"requires": {
|
||||||
|
"source-map": "0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sshpk": {
|
||||||
|
"version": "1.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
|
||||||
|
"integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
|
||||||
|
"requires": {
|
||||||
|
"asn1": "0.2.3",
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"bcrypt-pbkdf": "1.0.1",
|
||||||
|
"dashdash": "1.14.1",
|
||||||
|
"ecc-jsbn": "0.1.1",
|
||||||
|
"getpass": "0.1.7",
|
||||||
|
"jsbn": "0.1.1",
|
||||||
|
"tweetnacl": "0.14.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stream-line-wrapper": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-line-wrapper/-/stream-line-wrapper-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-Pivh02jGNW+Qru9keGaD8+7j7qc=",
|
||||||
|
"requires": {
|
||||||
|
"async": "0.2.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stringstream": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
||||||
|
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
|
||||||
|
},
|
||||||
|
"tough-cookie": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
||||||
|
"integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
|
||||||
|
"requires": {
|
||||||
|
"punycode": "1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tweetnacl": {
|
||||||
|
"version": "0.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
|
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
|
||||||
|
},
|
||||||
|
"vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||||
|
},
|
||||||
|
"verror": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"core-util-is": "1.0.2",
|
||||||
|
"extsprintf": "1.3.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
package.json
13
package.json
|
@ -1,22 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "Vulcan",
|
"name": "Vulcan",
|
||||||
"version": "1.8.0",
|
"version": "1.8.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": "^3.0"
|
"npm": "^3.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prestart": "sh prestart_vulcan.sh",
|
"prestart": "node prestart_vulcan.js",
|
||||||
"start": "meteor --settings settings.json",
|
"start": "meteor --settings settings.json",
|
||||||
"lint": "eslint --cache --ext .jsx,js packages"
|
"lint": "eslint --cache --ext .jsx,js packages"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"analytics-node": "^2.1.1",
|
"analytics-node": "^2.1.1",
|
||||||
"apollo-client": "^1.2.2",
|
"apollo-client": "^1.2.2",
|
||||||
|
"apollo-engine": "^0.5.4",
|
||||||
"apollo-errors": "^1.4.0",
|
"apollo-errors": "^1.4.0",
|
||||||
|
"apollo-server-express": "^1.2.0",
|
||||||
"babel-runtime": "^6.18.0",
|
"babel-runtime": "^6.18.0",
|
||||||
"bcrypt": "^0.8.7",
|
"bcrypt": "^0.8.7",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.18.2",
|
||||||
|
"chalk": "2.2.0",
|
||||||
"classnames": "^2.2.3",
|
"classnames": "^2.2.3",
|
||||||
|
"compression": "^1.7.1",
|
||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
"cross-fetch": "^0.0.8",
|
"cross-fetch": "^0.0.8",
|
||||||
"crypto-js": "^3.1.9-1",
|
"crypto-js": "^3.1.9-1",
|
||||||
|
@ -30,7 +34,6 @@
|
||||||
"graphql": "^0.9.6",
|
"graphql": "^0.9.6",
|
||||||
"graphql-anywhere": "^3.0.1",
|
"graphql-anywhere": "^3.0.1",
|
||||||
"graphql-date": "^1.0.2",
|
"graphql-date": "^1.0.2",
|
||||||
"graphql-server-express": "^0.6.0",
|
|
||||||
"graphql-tag": "^2.0.0",
|
"graphql-tag": "^2.0.0",
|
||||||
"graphql-tools": "^0.10.1",
|
"graphql-tools": "^0.10.1",
|
||||||
"graphql-type-json": "^0.1.4",
|
"graphql-type-json": "^0.1.4",
|
||||||
|
@ -54,7 +57,7 @@
|
||||||
"react": "^15.6.1",
|
"react": "^15.6.1",
|
||||||
"react-addons-pure-render-mixin": "^15.4.1",
|
"react-addons-pure-render-mixin": "^15.4.1",
|
||||||
"react-apollo": "^1.1.1",
|
"react-apollo": "^1.1.1",
|
||||||
"react-bootstrap": "^0.30.7",
|
"react-bootstrap": "^0.31.3",
|
||||||
"react-bootstrap-datetimepicker": "0.0.22",
|
"react-bootstrap-datetimepicker": "0.0.22",
|
||||||
"react-cookie": "^0.4.6",
|
"react-cookie": "^0.4.6",
|
||||||
"react-datetime": "^2.3.2",
|
"react-datetime": "^2.3.2",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "boilerplate-generator",
|
name: "boilerplate-generator",
|
||||||
summary: "Generates the boilerplate html from program's manifest",
|
summary: "Generates the boilerplate html from program's manifest",
|
||||||
version: '1.2.0'
|
version: '1.3.0'
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(api => {
|
Package.onUse(api => {
|
||||||
|
@ -11,4 +11,4 @@ Package.onUse(api => {
|
||||||
], 'server');
|
], 'server');
|
||||||
api.mainModule('generator.js', 'server');
|
api.mainModule('generator.js', 'server');
|
||||||
api.export('Boilerplate', 'server');
|
api.export('Boilerplate', 'server');
|
||||||
});
|
});
|
|
@ -25,6 +25,12 @@ registerFragment(`
|
||||||
...UsersMinimumInfo
|
...UsersMinimumInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# vulcan:voting
|
# voting
|
||||||
|
currentUserVotes{
|
||||||
|
...VoteFragment
|
||||||
|
}
|
||||||
|
baseScore
|
||||||
|
score
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
|
@ -198,10 +198,15 @@ const schema = {
|
||||||
insertableBy: ['admins'],
|
insertableBy: ['admins'],
|
||||||
editableBy: ['admins'],
|
editableBy: ['admins'],
|
||||||
control: 'select',
|
control: 'select',
|
||||||
onInsert: document => {
|
onInsert: (document, currentUser) => {
|
||||||
if (document.userId && !document.status) {
|
if (!document.status) {
|
||||||
const user = Users.findOne(document.userId);
|
return Posts.getDefaultStatus(currentUser);
|
||||||
return Posts.getDefaultStatus(user);
|
}
|
||||||
|
},
|
||||||
|
onEdit: (modifier, document, currentUser) => {
|
||||||
|
// if for some reason post status has been removed, give it default status
|
||||||
|
if (modifier.$unset && modifier.$unset.status) {
|
||||||
|
return Posts.getDefaultStatus(currentUser);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
@ -367,8 +372,8 @@ const schema = {
|
||||||
optional: true,
|
optional: true,
|
||||||
resolveAs: {
|
resolveAs: {
|
||||||
type: 'String',
|
type: 'String',
|
||||||
resolver: (booking, args, context) => {
|
resolver: (post, args, context) => {
|
||||||
return moment(booking.endAt).format('dddd, MMMM Do YYYY');
|
return moment(post.postedAt).format('dddd, MMMM Do YYYY');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -403,6 +408,39 @@ const schema = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
emailShareUrl: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
resolveAs: {
|
||||||
|
type: 'String',
|
||||||
|
resolver: (post) => {
|
||||||
|
return Posts.getEmailShareUrl(post);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
twitterShareUrl: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
resolveAs: {
|
||||||
|
type: 'String',
|
||||||
|
resolver: (post) => {
|
||||||
|
return Posts.getTwitterShareUrl(post);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
facebookShareUrl: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
resolveAs: {
|
||||||
|
type: 'String',
|
||||||
|
resolver: (post) => {
|
||||||
|
return Posts.getFacebookShareUrl(post);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default schema;
|
export default schema;
|
||||||
|
|
|
@ -9,7 +9,8 @@ import { performVoteServer } from 'meteor/vulcan:voting';
|
||||||
*/
|
*/
|
||||||
function CommentsNewUpvoteOwnComment(comment) {
|
function CommentsNewUpvoteOwnComment(comment) {
|
||||||
var commentAuthor = Users.findOne(comment.userId);
|
var commentAuthor = Users.findOne(comment.userId);
|
||||||
return {...comment, ...performVoteServer({ document: comment, voteType: 'upvote', collection: Comments, user: commentAuthor })};
|
const votedComent = performVoteServer({ document: comment, voteType: 'upvote', collection: Comments, user: commentAuthor })
|
||||||
|
return {...comment, ...votedComent};
|
||||||
}
|
}
|
||||||
|
|
||||||
addCallback('comments.new.async', CommentsNewUpvoteOwnComment);
|
addCallback('comments.new.after', CommentsNewUpvoteOwnComment);
|
|
@ -96,7 +96,7 @@ Posts.increaseClicks = (post, ip) => {
|
||||||
const existingClickEvent = Events.findOne({name: 'click', 'properties.postId': post._id, 'properties.ip': ip});
|
const existingClickEvent = Events.findOne({name: 'click', 'properties.postId': post._id, 'properties.ip': ip});
|
||||||
|
|
||||||
if(!existingClickEvent) {
|
if(!existingClickEvent) {
|
||||||
Events.log(clickEvent);
|
// Events.log(clickEvent); // Sidebar only: don't log event
|
||||||
return Posts.update(post._id, { $inc: { clickCount: 1 }});
|
return Posts.update(post._id, { $inc: { clickCount: 1 }});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,4 +112,14 @@ function PostsClickTracking(post, ip) {
|
||||||
// note: this event is not sent to segment cause we cannot access the current user
|
// note: this event is not sent to segment cause we cannot access the current user
|
||||||
// in our server-side route /out -> sending an event would create a new anonymous
|
// in our server-side route /out -> sending an event would create a new anonymous
|
||||||
// user: the free limit of 1,000 unique users per month would be reached quickly
|
// user: the free limit of 1,000 unique users per month would be reached quickly
|
||||||
addCallback('posts.click.async', PostsClickTracking);
|
addCallback('posts.click.async', PostsClickTracking);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
// posts.approve.sync //
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function PostsApprovedSetPostedAt (modifier, post) {
|
||||||
|
modifier.postedAt = new Date();
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
addCallback('posts.approve.sync', PostsApprovedSetPostedAt);
|
||||||
|
|
|
@ -17,4 +17,4 @@ function PostsNewUpvoteOwnPost(post) {
|
||||||
return {...post, ...performVoteServer({ document: post, voteType: 'upvote', collection: Posts, user: postAuthor })};
|
return {...post, ...performVoteServer({ document: post, voteType: 'upvote', collection: Posts, user: postAuthor })};
|
||||||
}
|
}
|
||||||
|
|
||||||
addCallback('posts.new.async', PostsNewUpvoteOwnPost);
|
addCallback('posts.new.after', PostsNewUpvoteOwnPost);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "example-forum",
|
name: "example-forum",
|
||||||
summary: "Vulcan forum package",
|
summary: "Vulcan forum package",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git"
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,11 +14,11 @@ Package.onUse(function (api) {
|
||||||
'fourseven:scss@4.5.0',
|
'fourseven:scss@4.5.0',
|
||||||
|
|
||||||
// vulcan core
|
// vulcan core
|
||||||
'vulcan:core@1.8.0',
|
'vulcan:core@1.8.1',
|
||||||
|
|
||||||
// vulcan packages
|
// vulcan packages
|
||||||
'vulcan:voting@1.8.0',
|
'vulcan:voting@1.8.1',
|
||||||
'vulcan:accounts@1.8.0',
|
'vulcan:accounts@1.8.1',
|
||||||
'vulcan:email',
|
'vulcan:email',
|
||||||
'vulcan:forms',
|
'vulcan:forms',
|
||||||
'vulcan:newsletter',
|
'vulcan:newsletter',
|
||||||
|
|
|
@ -11,7 +11,7 @@ component (if the "component" prop is specified).
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { registerComponent, Components, withCurrentUser } from 'meteor/vulcan:core';
|
import { registerComponent, Components, withCurrentUser } from 'meteor/vulcan:core';
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
import PicsNewForm from '../pics/PicsNewForm';
|
// import PicsNewForm from '../pics/PicsNewForm';
|
||||||
|
|
||||||
// navigation bar component when the user is logged in
|
// navigation bar component when the user is logged in
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ const NavLoggedIn = ({currentUser}) =>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Components.ModalTrigger label="Upload">
|
<Components.ModalTrigger label="Upload">
|
||||||
<PicsNewForm />
|
<Components.PicsNewForm />
|
||||||
</Components.ModalTrigger>
|
</Components.ModalTrigger>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,4 +71,4 @@ const Header = ({currentUser}) =>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
registerComponent('Header', Header, withCurrentUser);
|
registerComponent('Header', Header, withCurrentUser);
|
||||||
|
|
|
@ -11,7 +11,7 @@ component (if the "component" prop is specified).
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { registerComponent, Components, withCurrentUser } from 'meteor/vulcan:core';
|
import { registerComponent, Components, withCurrentUser } from 'meteor/vulcan:core';
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
import PicsNewForm from '../pics/PicsNewForm';
|
// import PicsNewForm from '../pics/PicsNewForm';
|
||||||
|
|
||||||
// navigation bar component when the user is logged in
|
// navigation bar component when the user is logged in
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ const NavLoggedIn = ({currentUser}) =>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Components.ModalTrigger label="Upload">
|
<Components.ModalTrigger label="Upload">
|
||||||
<PicsNewForm />
|
<Components.PicsNewForm />
|
||||||
</Components.ModalTrigger>
|
</Components.ModalTrigger>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,4 +71,4 @@ const Header = ({currentUser}) =>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
registerComponent('Header', Header, withCurrentUser);
|
registerComponent('Header', Header, withCurrentUser);
|
||||||
|
|
|
@ -11,7 +11,7 @@ component (if the "component" prop is specified).
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { registerComponent, Components, withCurrentUser } from 'meteor/vulcan:core';
|
import { registerComponent, Components, withCurrentUser } from 'meteor/vulcan:core';
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
import PicsNewForm from '../pics/PicsNewForm';
|
// import PicsNewForm from '../pics/PicsNewForm';
|
||||||
|
|
||||||
// navigation bar component when the user is logged in
|
// navigation bar component when the user is logged in
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ const NavLoggedIn = ({currentUser}) =>
|
||||||
|
|
||||||
{Users.canDo(currentUser, 'pics.new') ?
|
{Users.canDo(currentUser, 'pics.new') ?
|
||||||
<Components.ModalTrigger label="Upload">
|
<Components.ModalTrigger label="Upload">
|
||||||
<PicsNewForm />
|
<Components.PicsNewForm />
|
||||||
</Components.ModalTrigger>
|
</Components.ModalTrigger>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,14 @@ Accounts.ui._options = {
|
||||||
passwordSignupFields: 'USERNAME_AND_EMAIL',
|
passwordSignupFields: 'USERNAME_AND_EMAIL',
|
||||||
minimumPasswordLength: 7,
|
minimumPasswordLength: 7,
|
||||||
loginPath: '/',
|
loginPath: '/',
|
||||||
signUpPath: null,
|
signUpPath: '/',
|
||||||
resetPasswordPath: null,
|
resetPasswordPath: null,
|
||||||
profilePath: '/',
|
profilePath: '/',
|
||||||
changePasswordPath: null,
|
changePasswordPath: null,
|
||||||
homeRoutePath: '/',
|
homeRoutePath: '/',
|
||||||
onSubmitHook: () => {},
|
onSubmitHook: () => {},
|
||||||
onPreSignUpHook: () => new Promise(resolve => resolve()),
|
onPreSignUpHook: () => new Promise(resolve => resolve()),
|
||||||
onPostSignUpHook: () => {},
|
onPostSignUpHook: () => redirect(`${Accounts.ui._options.signUpPath}`),
|
||||||
onEnrollAccountHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
onEnrollAccountHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
||||||
onResetPasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
onResetPasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
||||||
onVerifyEmailHook: () => redirect(`${Accounts.ui._options.profilePath}`),
|
onVerifyEmailHook: () => redirect(`${Accounts.ui._options.profilePath}`),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:accounts',
|
name: 'vulcan:accounts',
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
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.3');
|
api.versionsFrom('1.3');
|
||||||
|
|
||||||
api.use('vulcan:core@1.8.0');
|
api.use('vulcan:core@1.8.1');
|
||||||
|
|
||||||
api.use('ecmascript');
|
api.use('ecmascript');
|
||||||
api.use('tracker');
|
api.use('tracker');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:admin",
|
name: "vulcan:admin",
|
||||||
summary: "Vulcan components package",
|
summary: "Vulcan components package",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git"
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ Package.onUse(function (api) {
|
||||||
'fourseven:scss@4.5.0',
|
'fourseven:scss@4.5.0',
|
||||||
'dynamic-import@0.1.1',
|
'dynamic-import@0.1.1',
|
||||||
// Vulcan packages
|
// Vulcan packages
|
||||||
'vulcan:core@1.8.0',
|
'vulcan:core@1.8.1',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -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.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git"
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.8.0'
|
'vulcan:core@1.8.1'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule("lib/client/main.js", "client");
|
api.mainModule("lib/client/main.js", "client");
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { addCallback, getActions } from 'meteor/vulcan:lib';
|
||||||
* @param {Object} Redux store reference instantiated on the current connected client
|
* @param {Object} Redux store reference instantiated on the current connected client
|
||||||
* @param {Object} Apollo Client reference instantiated on the current connected client
|
* @param {Object} Apollo Client reference instantiated on the current connected client
|
||||||
*/
|
*/
|
||||||
function RouterClearMessages(unusedItem, store, apolloClient) {
|
function RouterClearMessages(unusedItem, nextRoute, store, apolloClient) {
|
||||||
store.dispatch(getActions().messages.clearSeen());
|
store.dispatch(getActions().messages.clearSeen());
|
||||||
|
|
||||||
return unusedItem;
|
return unusedItem;
|
||||||
|
|
|
@ -1,36 +1,68 @@
|
||||||
import { Components, registerComponent, registerSetting, getSetting, Strings } from 'meteor/vulcan:lib';
|
import {
|
||||||
|
Components,
|
||||||
|
registerComponent,
|
||||||
|
registerSetting,
|
||||||
|
getSetting,
|
||||||
|
Strings,
|
||||||
|
runCallbacks,
|
||||||
|
} 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 { 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';
|
||||||
|
|
||||||
class App extends PureComponent {
|
class App extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
if (props.currentUser) {
|
||||||
|
runCallbacks('events.identify', props.currentUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getLocale() {
|
getLocale() {
|
||||||
return getSetting('locale', 'en');
|
return getSetting('locale', 'en');
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildContext() {
|
getChildContext() {
|
||||||
|
|
||||||
const messages = Strings[this.getLocale()] || {};
|
const messages = Strings[this.getLocale()] || {};
|
||||||
const intlProvider = new IntlProvider({locale: this.getLocale()}, messages);
|
const intlProvider = new IntlProvider(
|
||||||
|
{ locale: this.getLocale() },
|
||||||
|
messages
|
||||||
|
);
|
||||||
const { intl } = intlProvider.getChildContext();
|
const { intl } = intlProvider.getChildContext();
|
||||||
return {
|
return {
|
||||||
intl: intl
|
intl: intl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps) {
|
||||||
|
if (!this.props.currentUser && nextProps.currentUser) {
|
||||||
|
runCallbacks('events.identify', nextProps.currentUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const currentRoute = _.last(this.props.routes);
|
const currentRoute = _.last(this.props.routes);
|
||||||
const LayoutComponent = currentRoute.layoutName ? Components[currentRoute.layoutName] : Components.Layout;
|
const LayoutComponent = currentRoute.layoutName
|
||||||
|
? Components[currentRoute.layoutName]
|
||||||
|
: Components.Layout;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IntlProvider locale={this.getLocale()} messages={Strings[this.getLocale()]}>
|
<IntlProvider
|
||||||
|
locale={this.getLocale()}
|
||||||
|
messages={Strings[this.getLocale()]}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<Components.HeadTags />
|
<Components.HeadTags />
|
||||||
|
<Components.RouterHook currentRoute={currentRoute} />
|
||||||
<LayoutComponent {...this.props} currentRoute={currentRoute}>
|
<LayoutComponent {...this.props} currentRoute={currentRoute}>
|
||||||
{ this.props.currentUserLoading ? <Components.Loading /> : (this.props.children ? this.props.children : <Components.Welcome />) }
|
{this.props.currentUserLoading ? (
|
||||||
|
<Components.Loading />
|
||||||
|
) : this.props.children ? (
|
||||||
|
this.props.children
|
||||||
|
) : (
|
||||||
|
<Components.Welcome />
|
||||||
|
)}
|
||||||
</LayoutComponent>
|
</LayoutComponent>
|
||||||
</div>
|
</div>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
|
@ -40,11 +72,11 @@ class App extends PureComponent {
|
||||||
|
|
||||||
App.propTypes = {
|
App.propTypes = {
|
||||||
currentUserLoading: PropTypes.bool,
|
currentUserLoading: PropTypes.bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
App.childContextTypes = {
|
App.childContextTypes = {
|
||||||
intl: intlShape,
|
intl: intlShape,
|
||||||
}
|
};
|
||||||
|
|
||||||
App.displayName = 'App';
|
App.displayName = 'App';
|
||||||
|
|
||||||
|
|
|
@ -112,10 +112,12 @@ DatatableContents Component
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const DatatableContents = (props) => {
|
const DatatableContents = (props) => {
|
||||||
const {collection, columns, results, loading, loadMore, count, totalCount, networkStatus, showEdit, currentUser} = props;
|
const {collection, columns, results, loading, loadMore, count, totalCount, networkStatus, showEdit, currentUser, emptyState} = props;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Components.Loading />;
|
return <Components.Loading />;
|
||||||
|
} else if (!results.length) {
|
||||||
|
return emptyState || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoadingMore = networkStatus === 2;
|
const isLoadingMore = networkStatus === 2;
|
||||||
|
@ -123,26 +125,26 @@ const DatatableContents = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="datatable-list">
|
<div className="datatable-list">
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{_.sortBy(columns, column => column.order).map((column, index) => <Components.DatatableHeader key={index} collection={collection} column={column}/>)}
|
{_.sortBy(columns, column => column.order).map((column, index) => <Components.DatatableHeader key={index} collection={collection} column={column}/>)}
|
||||||
{showEdit ? <th><FormattedMessage id="datatable.edit"/></th> : null}
|
{showEdit ? <th><FormattedMessage id="datatable.edit"/></th> : null}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{results.map((document, index) => <Components.DatatableRow collection={collection} columns={columns} document={document} key={index} showEdit={showEdit} currentUser={currentUser}/>)}
|
{results.map((document, index) => <Components.DatatableRow collection={collection} columns={columns} document={document} key={index} showEdit={showEdit} currentUser={currentUser}/>)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="admin-users-load-more">
|
<div className="admin-users-load-more">
|
||||||
{hasMore ?
|
{hasMore ?
|
||||||
isLoadingMore ?
|
isLoadingMore ?
|
||||||
<Components.Loading/>
|
<Components.Loading/>
|
||||||
: <Button bsStyle="primary" onClick={e => {e.preventDefault(); loadMore();}}>Load More ({count}/{totalCount})</Button>
|
: <Button bsStyle="primary" onClick={e => {e.preventDefault(); loadMore();}}>Load More ({count}/{totalCount})</Button>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
registerComponent('DatatableContents', DatatableContents);
|
registerComponent('DatatableContents', DatatableContents);
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Components, registerComponent } from 'meteor/vulcan:lib';
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'react-bootstrap/lib/Button';
|
||||||
|
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
||||||
|
|
||||||
|
const EditButton = ({ collection, document, bsStyle = 'primary' }, {intl}) =>
|
||||||
|
<Components.ModalTrigger
|
||||||
|
label={intl.formatMessage({id: 'datatable.edit'})}
|
||||||
|
component={<Button bsStyle={bsStyle}><FormattedMessage id="datatable.edit" /></Button>}
|
||||||
|
>
|
||||||
|
<Components.DatatableEditForm collection={collection} document={document} />
|
||||||
|
</Components.ModalTrigger>
|
||||||
|
|
||||||
|
EditButton.contextTypes = {
|
||||||
|
intl: intlShape
|
||||||
|
};
|
||||||
|
|
||||||
|
EditButton.displayName = 'EditButton';
|
||||||
|
|
||||||
|
registerComponent('EditButton', EditButton);
|
|
@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { registerComponent, Utils, getSetting, registerSetting, Head } from 'meteor/vulcan:lib';
|
import { registerComponent, Utils, getSetting, registerSetting, Head } from 'meteor/vulcan:lib';
|
||||||
|
import { compose } from 'react-apollo';
|
||||||
|
|
||||||
registerSetting('logoUrl', null, 'Absolute URL for the logo image');
|
registerSetting('logoUrl', null, 'Absolute URL for the logo image');
|
||||||
registerSetting('title', 'My App', 'App title');
|
registerSetting('title', 'My App', 'App title');
|
||||||
|
@ -13,9 +14,9 @@ registerSetting('faviconUrl', '/img/favicon.ico', 'Favicon absolute URL');
|
||||||
class HeadTags extends PureComponent {
|
class HeadTags extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const url = !!this.props.url ? this.props.url : Utils.getSiteUrl();
|
const url = this.props.url || Utils.getSiteUrl();
|
||||||
const title = !!this.props.title ? this.props.title : getSetting('title', 'My App');
|
const title = this.props.title || getSetting('title', 'My App');
|
||||||
const description = !!this.props.description ? this.props.description : getSetting('tagline') || getSetting('description');
|
const description = this.props.description || getSetting('tagline') || getSetting('description');
|
||||||
|
|
||||||
// default image meta: logo url, else site image defined in settings
|
// default image meta: logo url, else site image defined in settings
|
||||||
let image = !!getSetting('siteImage') ? getSetting('siteImage'): getSetting('logoUrl');
|
let image = !!getSetting('siteImage') ? getSetting('siteImage'): getSetting('logoUrl');
|
||||||
|
@ -27,6 +28,10 @@ class HeadTags extends PureComponent {
|
||||||
|
|
||||||
// add site url base if the image is stored locally
|
// add site url base if the image is stored locally
|
||||||
if (!!image && image.indexOf('//') === -1) {
|
if (!!image && image.indexOf('//') === -1) {
|
||||||
|
// remove starting slash from image path if needed
|
||||||
|
if (image.charAt(0) === '/') {
|
||||||
|
image = image.slice(1);
|
||||||
|
}
|
||||||
image = Utils.getSiteUrl() + image;
|
image = Utils.getSiteUrl() + image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +63,21 @@ class HeadTags extends PureComponent {
|
||||||
|
|
||||||
{Head.meta.map((tag, index) => <meta key={index} {...tag}/>)}
|
{Head.meta.map((tag, index) => <meta key={index} {...tag}/>)}
|
||||||
{Head.link.map((tag, index) => <link key={index} {...tag}/>)}
|
{Head.link.map((tag, index) => <link key={index} {...tag}/>)}
|
||||||
{Head.script.map((tag, index) => <script key={index} {...tag}/>)}
|
{Head.script.map((tag, index) => <script key={index} {...tag}>{contents}</script>)}
|
||||||
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
|
{Head.components.map((componentOrArray, index) => {
|
||||||
|
let HeadComponent;
|
||||||
|
if (Array.isArray(componentOrArray)) {
|
||||||
|
const [component, ...hocs] = componentOrArray;
|
||||||
|
HeadComponent = compose(...hocs)(component);
|
||||||
|
} else {
|
||||||
|
HeadComponent = componentOrArray;
|
||||||
|
}
|
||||||
|
return <HeadComponent key={index} />
|
||||||
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
26
packages/vulcan-core/lib/modules/components/RouterHook.jsx
Normal file
26
packages/vulcan-core/lib/modules/components/RouterHook.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { registerComponent, runCallbacks } from 'meteor/vulcan:lib';
|
||||||
|
import { withApollo } from 'react-apollo';
|
||||||
|
|
||||||
|
class RouterHook extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.runOnUpdateCallback(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.runOnUpdateCallback(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
runOnUpdateCallback = props => {
|
||||||
|
const { currentRoute, client } = props;
|
||||||
|
// 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
|
||||||
|
runCallbacks('router.onUpdate', {}, currentRoute, client.store, client);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerComponent('RouterHook', RouterHook, withApollo);
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
import { graphql } from 'react-apollo';
|
import { graphql } from 'react-apollo';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { getFragment, getFragmentName } from 'meteor/vulcan:core';
|
import { getSetting, getFragment, getFragmentName } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
export default function withDocument (options) {
|
export default function withDocument (options) {
|
||||||
|
|
||||||
const { collection, pollInterval = 20000 } = options,
|
const { collection, pollInterval = getSetting('pollInterval', 20000), enableCache = false } = options,
|
||||||
queryName = options.queryName || `${collection.options.collectionName}SingleQuery`,
|
queryName = options.queryName || `${collection.options.collectionName}SingleQuery`,
|
||||||
singleResolverName = collection.options.resolvers.single && collection.options.resolvers.single.name;
|
singleResolverName = collection.options.resolvers.single && collection.options.resolvers.single.name;
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ export default function withDocument (options) {
|
||||||
const fragmentName = getFragmentName(fragment);
|
const fragmentName = getFragmentName(fragment);
|
||||||
|
|
||||||
return graphql(gql`
|
return graphql(gql`
|
||||||
query ${queryName}($documentId: String, $slug: String) {
|
query ${queryName}($documentId: String, $slug: String, $enableCache: Boolean) {
|
||||||
${singleResolverName}(documentId: $documentId, slug: $slug) {
|
${singleResolverName}(documentId: $documentId, slug: $slug, enableCache: $enableCache) {
|
||||||
__typename
|
__typename
|
||||||
...${fragmentName}
|
...${fragmentName}
|
||||||
}
|
}
|
||||||
|
@ -33,17 +33,24 @@ export default function withDocument (options) {
|
||||||
alias: 'withDocument',
|
alias: 'withDocument',
|
||||||
|
|
||||||
options(ownProps) {
|
options(ownProps) {
|
||||||
return {
|
const graphQLOptions = {
|
||||||
variables: { documentId: ownProps.documentId, slug: ownProps.slug },
|
variables: { documentId: ownProps.documentId, slug: ownProps.slug, enableCache },
|
||||||
pollInterval, // note: pollInterval can be set to 0 to disable polling (20s by default)
|
pollInterval, // note: pollInterval can be set to 0 to disable polling (20s by default)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (options.fetchPolicy) {
|
||||||
|
graphQLOptions.fetchPolicy = options.fetchPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return graphQLOptions;
|
||||||
},
|
},
|
||||||
props: returnedProps => {
|
props: returnedProps => {
|
||||||
const { ownProps, data } = returnedProps;
|
const { ownProps, data } = returnedProps;
|
||||||
|
const propertyName = options.propertyName || 'document';
|
||||||
return {
|
return {
|
||||||
loading: data.loading,
|
loading: data.loading,
|
||||||
// document: Utils.convertDates(collection, data[singleResolverName]),
|
// document: Utils.convertDates(collection, data[singleResolverName]),
|
||||||
document: data[singleResolverName],
|
[ propertyName ]: data[singleResolverName],
|
||||||
fragmentName,
|
fragmentName,
|
||||||
fragment,
|
fragment,
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,7 +38,7 @@ import React, { PropTypes, Component } from 'react';
|
||||||
import { withApollo, graphql } from 'react-apollo';
|
import { withApollo, graphql } from 'react-apollo';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import update from 'immutability-helper';
|
import update from 'immutability-helper';
|
||||||
import { getFragment, getFragmentName } from 'meteor/vulcan:core';
|
import { getSetting, getFragment, getFragmentName } from 'meteor/vulcan:core';
|
||||||
import Mingo from 'mingo';
|
import Mingo from 'mingo';
|
||||||
import compose from 'recompose/compose';
|
import compose from 'recompose/compose';
|
||||||
import withState from 'recompose/withState';
|
import withState from 'recompose/withState';
|
||||||
|
@ -47,7 +47,7 @@ const withList = (options) => {
|
||||||
|
|
||||||
// console.log(options)
|
// console.log(options)
|
||||||
|
|
||||||
const { collection, limit = 10, pollInterval = 20000, totalResolver = true } = options,
|
const { collection, limit = 10, pollInterval = getSetting('pollInterval', 20000), totalResolver = true, enableCache = false } = options,
|
||||||
queryName = options.queryName || `${collection.options.collectionName}ListQuery`,
|
queryName = options.queryName || `${collection.options.collectionName}ListQuery`,
|
||||||
listResolverName = collection.options.resolvers.list && collection.options.resolvers.list.name,
|
listResolverName = collection.options.resolvers.list && collection.options.resolvers.list.name,
|
||||||
totalResolverName = collection.options.resolvers.total && collection.options.resolvers.total.name;
|
totalResolverName = collection.options.resolvers.total && collection.options.resolvers.total.name;
|
||||||
|
@ -66,9 +66,9 @@ const withList = (options) => {
|
||||||
|
|
||||||
// build graphql query from options
|
// build graphql query from options
|
||||||
const query = gql`
|
const query = gql`
|
||||||
query ${queryName}($terms: JSON) {
|
query ${queryName}($terms: JSON, $enableCache: Boolean) {
|
||||||
${totalResolver ? `${totalResolverName}(terms: $terms)` : ``}
|
${totalResolver ? `${totalResolverName}(terms: $terms, enableCache: $enableCache)` : ``}
|
||||||
${listResolverName}(terms: $terms) {
|
${listResolverName}(terms: $terms, enableCache: $enableCache) {
|
||||||
__typename
|
__typename
|
||||||
...${fragmentName}
|
...${fragmentName}
|
||||||
}
|
}
|
||||||
|
@ -106,9 +106,11 @@ const withList = (options) => {
|
||||||
options({terms, paginationTerms, client: apolloClient}) {
|
options({terms, paginationTerms, client: apolloClient}) {
|
||||||
// get terms from options, then props, then pagination
|
// get terms from options, then props, then pagination
|
||||||
const mergedTerms = {...options.terms, ...terms, ...paginationTerms};
|
const mergedTerms = {...options.terms, ...terms, ...paginationTerms};
|
||||||
return {
|
|
||||||
|
const graphQLOptions = {
|
||||||
variables: {
|
variables: {
|
||||||
terms: mergedTerms,
|
terms: mergedTerms,
|
||||||
|
enableCache,
|
||||||
},
|
},
|
||||||
// note: pollInterval can be set to 0 to disable polling (20s by default)
|
// note: pollInterval can be set to 0 to disable polling (20s by default)
|
||||||
pollInterval,
|
pollInterval,
|
||||||
|
@ -119,18 +121,27 @@ const withList = (options) => {
|
||||||
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (options.fetchPolicy) {
|
||||||
|
graphQLOptions.fetchPolicy = options.fetchPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
return graphQLOptions;
|
||||||
},
|
},
|
||||||
|
|
||||||
// define props returned by graphql HoC
|
// define props returned by graphql HoC
|
||||||
props(props) {
|
props(props) {
|
||||||
|
|
||||||
|
// see https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/networkStatus.ts
|
||||||
const refetch = props.data.refetch,
|
const refetch = props.data.refetch,
|
||||||
// results = Utils.convertDates(collection, props.data[listResolverName]),
|
// results = Utils.convertDates(collection, props.data[listResolverName]),
|
||||||
results = props.data[listResolverName],
|
results = props.data[listResolverName],
|
||||||
totalCount = props.data[totalResolverName],
|
totalCount = props.data[totalResolverName],
|
||||||
networkStatus = props.data.networkStatus,
|
networkStatus = props.data.networkStatus,
|
||||||
loading = props.data.loading,
|
loading = props.data.networkStatus === 1,
|
||||||
error = props.data.error;
|
loadingMore = props.data.networkStatus === 2,
|
||||||
|
error = props.data.error,
|
||||||
|
propertyName = options.propertyName || 'results';
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -139,8 +150,9 @@ const withList = (options) => {
|
||||||
return {
|
return {
|
||||||
// see https://github.com/apollostack/apollo-client/blob/master/src/queries/store.ts#L28-L36
|
// see https://github.com/apollostack/apollo-client/blob/master/src/queries/store.ts#L28-L36
|
||||||
// note: loading will propably change soon https://github.com/apollostack/apollo-client/issues/831
|
// note: loading will propably change soon https://github.com/apollostack/apollo-client/issues/831
|
||||||
loading: networkStatus === 1,
|
loading,
|
||||||
results,
|
loadingMore,
|
||||||
|
[ propertyName ]: results,
|
||||||
totalCount,
|
totalCount,
|
||||||
refetch,
|
refetch,
|
||||||
networkStatus,
|
networkStatus,
|
||||||
|
|
|
@ -4,121 +4,213 @@ Default mutations
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { newMutation, editMutation, removeMutation, Utils } from 'meteor/vulcan:lib';
|
import { registerCallback, newMutation, editMutation, removeMutation, Utils } from 'meteor/vulcan:lib';
|
||||||
import Users from 'meteor/vulcan:users';
|
import Users from 'meteor/vulcan:users';
|
||||||
|
|
||||||
export const getDefaultMutations = (collectionName, options = {}) => ({
|
export const getDefaultMutations = (collectionName, options = {}) => {
|
||||||
|
|
||||||
// mutation for inserting a new document
|
// register callbacks for documentation purposes
|
||||||
|
registerCollectionCallbacks(collectionName);
|
||||||
|
|
||||||
new: {
|
return {
|
||||||
|
|
||||||
name: `${collectionName}New`,
|
// mutation for inserting a new document
|
||||||
|
|
||||||
// check function called on a user to see if they can perform the operation
|
new: {
|
||||||
check(user, document) {
|
|
||||||
if (options.newCheck) {
|
|
||||||
return options.newCheck(user, document);
|
|
||||||
}
|
|
||||||
// if user is not logged in, disallow operation
|
|
||||||
if (!user) return false;
|
|
||||||
// else, check if they can perform "foo.new" operation (e.g. "movies.new")
|
|
||||||
return Users.canDo(user, `${collectionName.toLowerCase()}.new`);
|
|
||||||
},
|
|
||||||
|
|
||||||
async mutation(root, {document}, context) {
|
|
||||||
|
|
||||||
const collection = context[collectionName];
|
name: `${collectionName}New`,
|
||||||
|
|
||||||
// check if current user can pass check function; else throw error
|
|
||||||
Utils.performCheck(this.check, context.currentUser, document);
|
|
||||||
|
|
||||||
// pass document to boilerplate newMutation function
|
|
||||||
return await newMutation({
|
|
||||||
collection,
|
|
||||||
document: document,
|
|
||||||
currentUser: context.currentUser,
|
|
||||||
validate: true,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// mutation for editing a specific document
|
|
||||||
|
|
||||||
edit: {
|
|
||||||
|
|
||||||
name: `${collectionName}Edit`,
|
|
||||||
|
|
||||||
// check function called on a user and document to see if they can perform the operation
|
|
||||||
check(user, document) {
|
|
||||||
if (options.editCheck) {
|
|
||||||
return options.editCheck(user, document);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user || !document) return false;
|
|
||||||
// check if user owns the document being edited.
|
|
||||||
// if they do, check if they can perform "foo.edit.own" action
|
|
||||||
// if they don't, check if they can perform "foo.edit.all" action
|
|
||||||
return Users.owns(user, document) ? Users.canDo(user, `${collectionName.toLowerCase()}.edit.own`) : Users.canDo(user, `${collectionName.toLowerCase()}.edit.all`);
|
|
||||||
},
|
|
||||||
|
|
||||||
async mutation(root, {documentId, set, unset}, context) {
|
|
||||||
|
|
||||||
const collection = context[collectionName];
|
|
||||||
|
|
||||||
// get entire unmodified document from database
|
|
||||||
const document = collection.findOne(documentId);
|
|
||||||
|
|
||||||
// check if user can perform operation; if not throw error
|
|
||||||
Utils.performCheck(this.check, context.currentUser, document);
|
|
||||||
|
|
||||||
// call editMutation boilerplate function
|
|
||||||
return await editMutation({
|
|
||||||
collection,
|
|
||||||
documentId: documentId,
|
|
||||||
set: set,
|
|
||||||
unset: unset,
|
|
||||||
currentUser: context.currentUser,
|
|
||||||
validate: true,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// mutation for removing a specific document (same checks as edit mutation)
|
|
||||||
|
|
||||||
remove: {
|
|
||||||
|
|
||||||
name: `${collectionName}Remove`,
|
|
||||||
|
|
||||||
check(user, document) {
|
|
||||||
if (options.removeCheck) {
|
|
||||||
return options.removeCheck(user, document);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user || !document) return false;
|
// check function called on a user to see if they can perform the operation
|
||||||
return Users.owns(user, document) ? Users.canDo(user, `${collectionName.toLowerCase()}.remove.own`) : Users.canDo(user, `${collectionName.toLowerCase()}.remove.all`);
|
check(user, document) {
|
||||||
|
if (options.newCheck) {
|
||||||
|
return options.newCheck(user, document);
|
||||||
|
}
|
||||||
|
// if user is not logged in, disallow operation
|
||||||
|
if (!user) return false;
|
||||||
|
// else, check if they can perform "foo.new" operation (e.g. "movies.new")
|
||||||
|
return Users.canDo(user, `${collectionName.toLowerCase()}.new`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async mutation(root, {document}, context) {
|
||||||
|
|
||||||
|
const collection = context[collectionName];
|
||||||
|
|
||||||
|
// check if current user can pass check function; else throw error
|
||||||
|
Utils.performCheck(this.check, context.currentUser, document);
|
||||||
|
|
||||||
|
// pass document to boilerplate newMutation function
|
||||||
|
return await newMutation({
|
||||||
|
collection,
|
||||||
|
document: document,
|
||||||
|
currentUser: context.currentUser,
|
||||||
|
validate: true,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// mutation for editing a specific document
|
||||||
|
|
||||||
|
edit: {
|
||||||
|
|
||||||
|
name: `${collectionName}Edit`,
|
||||||
|
|
||||||
|
// check function called on a user and document to see if they can perform the operation
|
||||||
|
check(user, document) {
|
||||||
|
if (options.editCheck) {
|
||||||
|
return options.editCheck(user, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user || !document) return false;
|
||||||
|
// check if user owns the document being edited.
|
||||||
|
// if they do, check if they can perform "foo.edit.own" action
|
||||||
|
// if they don't, check if they can perform "foo.edit.all" action
|
||||||
|
return Users.owns(user, document) ? Users.canDo(user, `${collectionName.toLowerCase()}.edit.own`) : Users.canDo(user, `${collectionName.toLowerCase()}.edit.all`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async mutation(root, {documentId, set, unset}, context) {
|
||||||
|
|
||||||
|
const collection = context[collectionName];
|
||||||
|
|
||||||
|
// get entire unmodified document from database
|
||||||
|
const document = collection.findOne(documentId);
|
||||||
|
|
||||||
|
// check if user can perform operation; if not throw error
|
||||||
|
Utils.performCheck(this.check, context.currentUser, document);
|
||||||
|
|
||||||
|
// call editMutation boilerplate function
|
||||||
|
return await editMutation({
|
||||||
|
collection,
|
||||||
|
documentId: documentId,
|
||||||
|
set: set,
|
||||||
|
unset: unset,
|
||||||
|
currentUser: context.currentUser,
|
||||||
|
validate: true,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async mutation(root, {documentId}, context) {
|
// mutation for removing a specific document (same checks as edit mutation)
|
||||||
|
|
||||||
const collection = context[collectionName];
|
remove: {
|
||||||
|
|
||||||
const document = collection.findOne(documentId);
|
name: `${collectionName}Remove`,
|
||||||
Utils.performCheck(this.check, context.currentUser, document, context);
|
|
||||||
|
check(user, document) {
|
||||||
|
if (options.removeCheck) {
|
||||||
|
return options.removeCheck(user, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user || !document) return false;
|
||||||
|
return Users.owns(user, document) ? Users.canDo(user, `${collectionName.toLowerCase()}.remove.own`) : Users.canDo(user, `${collectionName.toLowerCase()}.remove.all`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async mutation(root, {documentId}, context) {
|
||||||
|
|
||||||
|
const collection = context[collectionName];
|
||||||
|
|
||||||
|
const document = collection.findOne(documentId);
|
||||||
|
Utils.performCheck(this.check, context.currentUser, document, context);
|
||||||
|
|
||||||
|
return await removeMutation({
|
||||||
|
collection,
|
||||||
|
documentId: documentId,
|
||||||
|
currentUser: context.currentUser,
|
||||||
|
validate: true,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
return await removeMutation({
|
|
||||||
collection,
|
|
||||||
documentId: documentId,
|
|
||||||
currentUser: context.currentUser,
|
|
||||||
validate: true,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
},
|
};
|
||||||
|
|
||||||
});
|
|
||||||
|
const registerCollectionCallbacks = collectionName => {
|
||||||
|
|
||||||
|
collectionName = collectionName.toLowerCase();
|
||||||
|
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.new.validate`,
|
||||||
|
arguments: [{document: 'The document being inserted'}, {currentUser: 'The current user'}, {validationErrors: 'An object that can be used to accumulate validation errors'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: 'document',
|
||||||
|
description: `Validate a document before insertion (can be skipped when inserting directly on server).`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.new.before`,
|
||||||
|
arguments: [{document: 'The document being inserted'}, {currentUser: 'The current user'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: 'document',
|
||||||
|
description: `Perform operations on a new document before it's inserted in the database.`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.new.after`,
|
||||||
|
arguments: [{document: 'The document being inserted'}, {currentUser: 'The current user'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: 'document',
|
||||||
|
description: `Perform operations on a new document after it's inserted in the database but *before* the mutation returns it.`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.new.async`,
|
||||||
|
arguments: [{document: 'The document being inserted'}, {currentUser: 'The current user'}, {collection: 'The collection the document belongs to'}],
|
||||||
|
runs: 'async',
|
||||||
|
returns: null,
|
||||||
|
description: `Perform operations on a new document after it's inserted in the database asynchronously.`
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.edit.validate`,
|
||||||
|
arguments: [{modifier: 'The MongoDB modifier'}, {document: 'The document being edited'}, {currentUser: 'The current user'}, {validationErrors: 'An object that can be used to accumulate validation errors'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: 'modifier',
|
||||||
|
description: `Validate a document before update (can be skipped when updating directly on server).`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.edit.before`,
|
||||||
|
arguments: [{modifier: 'The MongoDB modifier'}, {document: 'The document being edited'}, {currentUser: 'The current user'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: 'modifier',
|
||||||
|
description: `Perform operations on a document before it's updated in the database.`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.edit.after`,
|
||||||
|
arguments: [{modifier: 'The MongoDB modifier'}, {document: 'The document being edited'}, {currentUser: 'The current user'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: 'document',
|
||||||
|
description: `Perform operations on a document after it's updated in the database but *before* the mutation returns it.`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.edit.async`,
|
||||||
|
arguments: [{newDocument: 'The document after the edit'}, {document: 'The document before the edit'}, {currentUser: 'The current user'}, {collection: 'The collection the document belongs to'}],
|
||||||
|
runs: 'async',
|
||||||
|
returns: null,
|
||||||
|
description: `Perform operations on a document after it's updated in the database asynchronously.`
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.remove.validate`,
|
||||||
|
arguments: [{document: 'The document being removed'}, {currentUser: 'The current user'}, {validationErrors: 'An object that can be used to accumulate validation errors'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: 'document',
|
||||||
|
description: `Validate a document before removal (can be skipped when removing directly on server).`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.remove.before`,
|
||||||
|
arguments: [{document: 'The document being removed'}, {currentUser: 'The current user'}],
|
||||||
|
runs: 'sync',
|
||||||
|
returns: null,
|
||||||
|
description: `Perform operations on a document before it's removed from the database.`
|
||||||
|
});
|
||||||
|
registerCallback({
|
||||||
|
name: `${collectionName}.remove.async`,
|
||||||
|
arguments: [{document: 'The document being removed'}, {currentUser: 'The current user'}, {collection: 'The collection the document belongs to'}],
|
||||||
|
runs: 'async',
|
||||||
|
returns: null,
|
||||||
|
description: `Perform operations on a document after it's removed from the database asynchronously.`
|
||||||
|
});
|
||||||
|
}
|
|
@ -6,97 +6,123 @@ Default list, single, and total resolvers
|
||||||
|
|
||||||
import { Utils, debug } from 'meteor/vulcan:core';
|
import { Utils, debug } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
export const getDefaultResolvers = collectionName => ({
|
const defaultOptions = {
|
||||||
|
cacheMaxAge: 300
|
||||||
|
}
|
||||||
|
|
||||||
// resolver for returning a list of documents based on a set of query terms
|
export const getDefaultResolvers = (collectionName, resolverOptions = defaultOptions) => {
|
||||||
|
|
||||||
list: {
|
return {
|
||||||
|
|
||||||
name: `${collectionName}List`,
|
// resolver for returning a list of documents based on a set of query terms
|
||||||
|
|
||||||
async resolver(root, {terms = {}}, context, info) {
|
list: {
|
||||||
|
|
||||||
debug(`//--------------- start ${collectionName} list resolver ---------------//`);
|
name: `${collectionName}List`,
|
||||||
debug(terms);
|
|
||||||
|
|
||||||
// get currentUser and Users collection from context
|
async resolver(root, {terms = {}, enableCache = false}, context, { cacheControl }) {
|
||||||
const { currentUser, Users } = context;
|
|
||||||
|
|
||||||
// get collection based on collectionName argument
|
debug(`//--------------- start ${collectionName} list resolver ---------------//`);
|
||||||
const collection = context[collectionName];
|
debug(resolverOptions);
|
||||||
|
debug(terms);
|
||||||
|
|
||||||
// get selector and options from terms and perform Mongo query
|
if (cacheControl && enableCache) {
|
||||||
let {selector, options} = await collection.getParameters(terms, {}, context);
|
const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
|
||||||
options.skip = terms.offset;
|
cacheControl.setCacheHint({ maxAge });
|
||||||
|
}
|
||||||
|
|
||||||
debug({ selector, options });
|
// get currentUser and Users collection from context
|
||||||
|
const { currentUser, Users } = context;
|
||||||
|
|
||||||
const docs = collection.find(selector, options).fetch();
|
// get collection based on collectionName argument
|
||||||
|
const collection = context[collectionName];
|
||||||
|
|
||||||
// if collection has a checkAccess function defined, remove any documents that doesn't pass the check
|
// get selector and options from terms and perform Mongo query
|
||||||
const viewableDocs = collection.checkAccess ? _.filter(docs, doc => collection.checkAccess(currentUser, doc)) : docs;
|
let {selector, options} = await collection.getParameters(terms, {}, context);
|
||||||
|
options.skip = terms.offset;
|
||||||
|
|
||||||
|
debug({ selector, options });
|
||||||
|
|
||||||
|
const docs = collection.find(selector, options).fetch();
|
||||||
|
|
||||||
|
// if collection has a checkAccess function defined, remove any documents that doesn't pass the check
|
||||||
|
const viewableDocs = collection.checkAccess ? _.filter(docs, doc => collection.checkAccess(currentUser, doc)) : docs;
|
||||||
|
|
||||||
|
// take the remaining documents and remove any fields that shouldn't be accessible
|
||||||
|
const restrictedDocs = Users.restrictViewableFields(currentUser, collection, viewableDocs);
|
||||||
|
|
||||||
|
// prime the cache
|
||||||
|
restrictedDocs.forEach(doc => collection.loader.prime(doc._id, doc));
|
||||||
|
|
||||||
|
debug(`// ${restrictedDocs.length} documents returned`);
|
||||||
|
debug(`//--------------- end ${collectionName} list resolver ---------------//`);
|
||||||
|
|
||||||
|
// return results
|
||||||
|
return restrictedDocs;
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// resolver for returning a single document queried based on id or slug
|
||||||
|
|
||||||
|
single: {
|
||||||
|
|
||||||
// take the remaining documents and remove any fields that shouldn't be accessible
|
name: `${collectionName}Single`,
|
||||||
const restrictedDocs = Users.restrictViewableFields(currentUser, collection, viewableDocs);
|
|
||||||
|
|
||||||
// prime the cache
|
async resolver(root, {documentId, slug, enableCache = false}, context, { cacheControl }) {
|
||||||
restrictedDocs.forEach(doc => collection.loader.prime(doc._id, doc));
|
|
||||||
|
|
||||||
debug(`// ${restrictedDocs.length} documents returned`);
|
debug(`//--------------- start ${collectionName} single resolver ---------------//`);
|
||||||
debug(`//--------------- end ${collectionName} list resolver ---------------//`);
|
debug(resolverOptions);
|
||||||
|
debug(documentId);
|
||||||
|
|
||||||
// return results
|
if (cacheControl && enableCache) {
|
||||||
return restrictedDocs;
|
const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
|
||||||
|
cacheControl.setCacheHint({ maxAge });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { currentUser, Users } = context;
|
||||||
|
const collection = context[collectionName];
|
||||||
|
|
||||||
|
// don't use Dataloader if doc is selected by slug
|
||||||
|
const doc = documentId ? await collection.loader.load(documentId) : (slug ? collection.findOne({slug}) : collection.findOne());
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if (collection.checkAccess) {
|
||||||
|
Utils.performCheck(collection.checkAccess, currentUser, doc, collection, documentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const restrictedDoc = Users.restrictViewableFields(currentUser, collection, doc);
|
||||||
|
|
||||||
|
debug(`//--------------- end ${collectionName} single resolver ---------------//`);
|
||||||
|
|
||||||
|
// filter out disallowed properties and return resulting document
|
||||||
|
return restrictedDoc;
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
// resolver for returning the total number of documents matching a set of query terms
|
||||||
|
|
||||||
// resolver for returning a single document queried based on id or slug
|
total: {
|
||||||
|
|
||||||
single: {
|
|
||||||
|
|
||||||
name: `${collectionName}Single`,
|
|
||||||
|
|
||||||
async resolver(root, {documentId, slug}, context) {
|
|
||||||
|
|
||||||
debug(`//--------------- start ${collectionName} single resolver ---------------//`);
|
|
||||||
debug(documentId);
|
|
||||||
|
|
||||||
const { currentUser, Users } = context;
|
|
||||||
const collection = context[collectionName];
|
|
||||||
|
|
||||||
// don't use Dataloader if doc is selected by slug
|
|
||||||
const doc = documentId ? await collection.loader.load(documentId) : (slug ? collection.findOne({slug}) : collection.findOne());
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
if (collection.checkAccess) {
|
|
||||||
Utils.performCheck(collection.checkAccess, currentUser, doc, collection, documentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(`//--------------- end ${collectionName} single resolver ---------------//`);
|
|
||||||
|
|
||||||
|
|
||||||
// filter out disallowed properties and return resulting document
|
|
||||||
return Users.restrictViewableFields(currentUser, collection, doc);
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// resolver for returning the total number of documents matching a set of query terms
|
|
||||||
|
|
||||||
total: {
|
|
||||||
|
|
||||||
name: `${collectionName}Total`,
|
|
||||||
|
|
||||||
async resolver(root, {terms}, context) {
|
|
||||||
|
|
||||||
const collection = context[collectionName];
|
name: `${collectionName}Total`,
|
||||||
|
|
||||||
|
async resolver(root, {terms, enableCache}, context, { cacheControl }) {
|
||||||
|
|
||||||
|
if (cacheControl && enableCache) {
|
||||||
|
const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
|
||||||
|
cacheControl.setCacheHint({ maxAge });
|
||||||
|
}
|
||||||
|
|
||||||
const {selector} = await collection.getParameters(terms, {}, context);
|
const collection = context[collectionName];
|
||||||
|
|
||||||
return collection.find(selector).count();
|
const {selector} = await collection.getParameters(terms, {}, context);
|
||||||
},
|
|
||||||
|
return collection.find(selector).count();
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
};
|
||||||
|
|
|
@ -12,6 +12,7 @@ export { default as Icon } from "./components/Icon.jsx";
|
||||||
export { default as Loading } from "./components/Loading.jsx";
|
export { default as Loading } from "./components/Loading.jsx";
|
||||||
export { default as ShowIf } from "./components/ShowIf.jsx";
|
export { default as ShowIf } from "./components/ShowIf.jsx";
|
||||||
export { default as ModalTrigger } from './components/ModalTrigger.jsx';
|
export { default as ModalTrigger } from './components/ModalTrigger.jsx';
|
||||||
|
export { default as EditButton } from './components/EditButton.jsx';
|
||||||
export { default as Error404 } from './components/Error404.jsx';
|
export { default as Error404 } from './components/Error404.jsx';
|
||||||
export { default as DynamicLoading } from './components/DynamicLoading.jsx';
|
export { default as DynamicLoading } from './components/DynamicLoading.jsx';
|
||||||
export { default as HeadTags } from './components/HeadTags.jsx';
|
export { default as HeadTags } from './components/HeadTags.jsx';
|
||||||
|
@ -21,6 +22,7 @@ export { default as Datatable } from './components/Datatable.jsx';
|
||||||
export { default as Flash } from './components/Flash.jsx';
|
export { default as Flash } from './components/Flash.jsx';
|
||||||
export { default as HelloWorld } from './components/HelloWorld.jsx';
|
export { default as HelloWorld } from './components/HelloWorld.jsx';
|
||||||
export { default as Welcome } from './components/Welcome.jsx';
|
export { default as Welcome } from './components/Welcome.jsx';
|
||||||
|
export { default as RouterHook } from './components/RouterHook.jsx';
|
||||||
|
|
||||||
export { default as withMessages } from "./containers/withMessages.js";
|
export { default as withMessages } from "./containers/withMessages.js";
|
||||||
export { default as withList } from './containers/withList.js';
|
export { default as withList } from './containers/withList.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.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git"
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,15 +10,15 @@ Package.onUse(function(api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:lib@1.8.0',
|
'vulcan:lib@1.8.1',
|
||||||
'vulcan:i18n@1.8.0',
|
'vulcan:i18n@1.8.1',
|
||||||
'vulcan:users@1.8.0',
|
'vulcan:users@1.8.1',
|
||||||
'vulcan:routing@1.8.0',
|
'vulcan:routing@1.8.1',
|
||||||
'vulcan:debug@1.8.0',
|
'vulcan:debug@1.8.1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.imply([
|
api.imply([
|
||||||
'vulcan:lib@1.8.0'
|
'vulcan:lib@1.8.1'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule('lib/server/main.js', 'server');
|
api.mainModule('lib/server/main.js', 'server');
|
||||||
|
|
31
packages/vulcan-debug/lib/components/Callbacks.jsx
Normal file
31
packages/vulcan-debug/lib/components/Callbacks.jsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||||
|
import { registerComponent, Components } from 'meteor/vulcan:lib';
|
||||||
|
import Callbacks from '../modules/callbacks/collection.js';
|
||||||
|
|
||||||
|
const CallbacksName = ({ document }) =>
|
||||||
|
<strong>{document.name}</strong>
|
||||||
|
|
||||||
|
const CallbacksDashboard = props =>
|
||||||
|
<div className="settings">
|
||||||
|
<Components.Datatable
|
||||||
|
showSearch={false}
|
||||||
|
showEdit={false}
|
||||||
|
collection={Callbacks}
|
||||||
|
options={{
|
||||||
|
fragmentName: 'CallbacksFragment'
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{ name: 'name', component: CallbacksName },
|
||||||
|
'arguments',
|
||||||
|
'returns',
|
||||||
|
'runs',
|
||||||
|
'description',
|
||||||
|
'hooks',
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
registerComponent('Callbacks', CallbacksDashboard);
|
||||||
|
|
||||||
|
export default Callbacks;
|
|
@ -37,7 +37,7 @@ class Email extends PureComponent {
|
||||||
<td>{name}</td>
|
<td>{name}</td>
|
||||||
<td><a href={"/email/template/"+email.template} target="_blank">{email.template}</a></td>
|
<td><a href={"/email/template/"+email.template} target="_blank">{email.template}</a></td>
|
||||||
<td>{typeof email.subject === 'function' ? email.subject({}) : email.subject}</td>
|
<td>{typeof email.subject === 'function' ? email.subject({}) : email.subject}</td>
|
||||||
<td><a href={email.path.replace(':_id?', '')} target="_blank">{email.path}</a></td>
|
<td><a href={email.path.replace(':_id?', '').replace(':documentId?', '')} target="_blank">{email.path}</a></td>
|
||||||
<td>
|
<td>
|
||||||
<div className={this.state.loading ? "test-email loading" : "test-email"}>
|
<div className={this.state.loading ? "test-email loading" : "test-email"}>
|
||||||
<Button disabled={this.state.loading} onClick={this.sendTest} bsStyle="primary">Send Test</Button>
|
<Button disabled={this.state.loading} onClick={this.sendTest} bsStyle="primary">Send Test</Button>
|
||||||
|
|
19
packages/vulcan-debug/lib/modules/callbacks/collection.js
Normal file
19
packages/vulcan-debug/lib/modules/callbacks/collection.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { createCollection } from 'meteor/vulcan:lib';
|
||||||
|
import schema from './schema.js';
|
||||||
|
import resolvers from './resolvers.js';
|
||||||
|
import './fragments.js';
|
||||||
|
|
||||||
|
const Callbacks = createCollection({
|
||||||
|
|
||||||
|
collectionName: 'Callbacks',
|
||||||
|
|
||||||
|
typeName: 'Callback',
|
||||||
|
|
||||||
|
schema,
|
||||||
|
|
||||||
|
resolvers,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default Callbacks;
|
12
packages/vulcan-debug/lib/modules/callbacks/fragments.js
Normal file
12
packages/vulcan-debug/lib/modules/callbacks/fragments.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { registerFragment } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
|
registerFragment(`
|
||||||
|
fragment CallbacksFragment on Callback {
|
||||||
|
name
|
||||||
|
arguments
|
||||||
|
runs
|
||||||
|
returns
|
||||||
|
description
|
||||||
|
hooks
|
||||||
|
}
|
||||||
|
`);
|
26
packages/vulcan-debug/lib/modules/callbacks/resolvers.js
Normal file
26
packages/vulcan-debug/lib/modules/callbacks/resolvers.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { CallbackHooks } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
|
||||||
|
list: {
|
||||||
|
|
||||||
|
name: 'CallbacksList',
|
||||||
|
|
||||||
|
resolver(root, {terms = {}}, context, info) {
|
||||||
|
return CallbackHooks;
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
total: {
|
||||||
|
|
||||||
|
name: 'CallbacksTotal',
|
||||||
|
|
||||||
|
resolver(root, {terms = {}}, context) {
|
||||||
|
return CallbackHooks.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resolvers;
|
57
packages/vulcan-debug/lib/modules/callbacks/schema.js
Normal file
57
packages/vulcan-debug/lib/modules/callbacks/schema.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { Callbacks } from 'meteor/vulcan:lib';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
name: {
|
||||||
|
label: 'Name',
|
||||||
|
type: String,
|
||||||
|
viewableBy: ['admins'],
|
||||||
|
},
|
||||||
|
|
||||||
|
arguments: {
|
||||||
|
label: 'Arguments',
|
||||||
|
type: Array,
|
||||||
|
viewableBy: ['admins'],
|
||||||
|
},
|
||||||
|
|
||||||
|
'arguments.$': {
|
||||||
|
type: Object,
|
||||||
|
viewableBy: ['admins'],
|
||||||
|
},
|
||||||
|
|
||||||
|
runs: {
|
||||||
|
label: 'Runs',
|
||||||
|
type: String,
|
||||||
|
viewableBy: ['admins'],
|
||||||
|
},
|
||||||
|
|
||||||
|
returns: {
|
||||||
|
label: 'Should Return',
|
||||||
|
type: String,
|
||||||
|
viewableBy: ['admins'],
|
||||||
|
},
|
||||||
|
|
||||||
|
description: {
|
||||||
|
label: 'Description',
|
||||||
|
type: String,
|
||||||
|
viewableBy: ['admins'],
|
||||||
|
},
|
||||||
|
|
||||||
|
hooks: {
|
||||||
|
label: 'Hooks',
|
||||||
|
type: Array,
|
||||||
|
viewableBy: ['admins'],
|
||||||
|
resolveAs: {
|
||||||
|
type: '[String]',
|
||||||
|
resolver: callback => {
|
||||||
|
if (Callbacks[callback.name]) {
|
||||||
|
const callbacks = Callbacks[callback.name].map(f => f.name);
|
||||||
|
return callbacks;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default schema;
|
|
@ -2,3 +2,4 @@
|
||||||
import '../components/Emails.jsx';
|
import '../components/Emails.jsx';
|
||||||
import '../components/Groups.jsx';
|
import '../components/Groups.jsx';
|
||||||
import '../components/Settings.jsx';
|
import '../components/Settings.jsx';
|
||||||
|
import '../components/Callbacks.jsx';
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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'))},
|
{name: 'groups', path: '/groups', component: () => getDynamicComponent(import('../components/Groups.jsx'))},
|
||||||
{name: 'settings', path: '/settings', componentName: 'Settings'},
|
{name: 'settings', path: '/settings', componentName: 'Settings'},
|
||||||
|
{name: 'callbacks', path: '/callbacks', componentName: 'Callbacks'},
|
||||||
// {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'},
|
{name: 'emails', path: '/emails', componentName: 'Emails'},
|
||||||
]);
|
]);
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:debug",
|
name: "vulcan:debug",
|
||||||
summary: "Vulcan debug package",
|
summary: "Vulcan debug package",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git",
|
git: "https://github.com/VulcanJS/Vulcan.git",
|
||||||
debugOnly: true
|
debugOnly: true
|
||||||
});
|
});
|
||||||
|
@ -17,8 +17,8 @@ Package.onUse(function (api) {
|
||||||
|
|
||||||
// Vulcan packages
|
// Vulcan packages
|
||||||
|
|
||||||
'vulcan:lib@1.8.0',
|
'vulcan:lib@1.8.1',
|
||||||
'vulcan:email@1.8.0',
|
'vulcan:email@1.8.1',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ VulcanEmail.addTemplates = templates => {
|
||||||
|
|
||||||
VulcanEmail.getTemplate = templateName => Handlebars.compile(
|
VulcanEmail.getTemplate = templateName => Handlebars.compile(
|
||||||
VulcanEmail.templates[templateName],
|
VulcanEmail.templates[templateName],
|
||||||
{ noEscape: true}
|
{ noEscape: true, strict: true}
|
||||||
);
|
);
|
||||||
|
|
||||||
VulcanEmail.buildTemplate = (htmlContent, optionalProperties = {}) => {
|
VulcanEmail.buildTemplate = (htmlContent, optionalProperties = {}) => {
|
||||||
|
@ -46,9 +46,7 @@ VulcanEmail.buildTemplate = (htmlContent, optionalProperties = {}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const emailHTML = VulcanEmail.getTemplate("wrapper")(emailProperties);
|
const emailHTML = VulcanEmail.getTemplate("wrapper")(emailProperties);
|
||||||
|
|
||||||
const inlinedHTML = Juice(emailHTML, {preserveMediaQueries: true});
|
const inlinedHTML = Juice(emailHTML, {preserveMediaQueries: true});
|
||||||
|
|
||||||
const doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
const doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
||||||
|
|
||||||
return doctype+inlinedHTML;
|
return doctype+inlinedHTML;
|
||||||
|
@ -122,7 +120,7 @@ VulcanEmail.build = async ({ emailName, variables }) => {
|
||||||
|
|
||||||
const subject = typeof email.subject === 'function' ? email.subject(data) : email.subject;
|
const subject = typeof email.subject === 'function' ? email.subject(data) : email.subject;
|
||||||
|
|
||||||
const html = VulcanEmail.buildTemplate(VulcanEmail.getTemplate(email.template)(data));
|
const html = VulcanEmail.buildTemplate(VulcanEmail.getTemplate(email.template)(data), data);
|
||||||
|
|
||||||
return { data, subject, html };
|
return { data, subject, html };
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ Meteor.startup(function () {
|
||||||
Picker.route(email.path, async (params, req, res) => {
|
Picker.route(email.path, async (params, req, res) => {
|
||||||
|
|
||||||
let html;
|
let html;
|
||||||
|
|
||||||
// if email has a custom way of generating test HTML, use it
|
// if email has a custom way of generating test HTML, use it
|
||||||
if (typeof email.getTestHTML !== "undefined") {
|
if (typeof email.getTestHTML !== "undefined") {
|
||||||
|
|
||||||
|
@ -20,14 +19,20 @@ Meteor.startup(function () {
|
||||||
|
|
||||||
// else get test object (sample post, comment, user, etc.)
|
// else get test object (sample post, comment, user, etc.)
|
||||||
const testVariables = (typeof email.testVariables === 'function' ? email.testVariables() : email.testVariables) || {};
|
const testVariables = (typeof email.testVariables === 'function' ? email.testVariables() : email.testVariables) || {};
|
||||||
const result = email.query ? await runQuery(email.query, testVariables) : {data: {}};
|
// merge test variables with params from URL
|
||||||
|
const variables = {...testVariables, ...params};
|
||||||
|
|
||||||
|
const result = email.query ? await runQuery(email.query, variables) : {data: {}};
|
||||||
|
|
||||||
// if email has a data() function, merge it with results of query
|
// if email has a data() function, merge it with results of query
|
||||||
const emailTestData = email.data ? {...result.data, ...email.data(testVariables)} : result.data;
|
const emailTestData = email.data ? {...result.data, ...email.data(variables)} : result.data;
|
||||||
const subject = typeof email.subject === 'function' ? email.subject(emailTestData) : email.subject;
|
const subject = typeof email.subject === 'function' ? email.subject(emailTestData) : email.subject;
|
||||||
|
|
||||||
|
const template = VulcanEmail.getTemplate(email.template);
|
||||||
|
const htmlContent = template(emailTestData)
|
||||||
|
|
||||||
// then apply email template to properties, and wrap it with buildTemplate
|
// then apply email template to properties, and wrap it with buildTemplate
|
||||||
html = VulcanEmail.buildTemplate(VulcanEmail.getTemplate(email.template)(emailTestData));
|
html = VulcanEmail.buildTemplate(htmlContent, emailTestData);
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<h4 style="margin: 20px;"><code>Subject: ${subject}</code></h4>
|
<h4 style="margin: 20px;"><code>Subject: ${subject}</code></h4>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:email",
|
name: "vulcan:email",
|
||||||
summary: "Vulcan email package",
|
summary: "Vulcan email package",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git"
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:lib@1.8.0'
|
'vulcan:lib@1.8.1'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule("lib/server.js", "server");
|
api.mainModule("lib/server.js", "server");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:embed",
|
name: "vulcan:embed",
|
||||||
summary: "Vulcan Embed package",
|
summary: "Vulcan Embed package",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git'
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Package.onUse( function(api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.8.0',
|
'vulcan:core@1.8.1',
|
||||||
'fourseven:scss@4.5.0'
|
'fourseven:scss@4.5.0'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
1
packages/vulcan-events-ga/README.md
Normal file
1
packages/vulcan-events-ga/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Vulcan events package, used internally.
|
62
packages/vulcan-events-ga/lib/client/ga.js
Normal file
62
packages/vulcan-events-ga/lib/client/ga.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { getSetting } from 'meteor/vulcan:core';
|
||||||
|
import { addPageFunction, addInitFunction } from 'meteor/vulcan:events';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
We provide a special support for Google Analytics.
|
||||||
|
|
||||||
|
If you want to enable GA page viewing / tracking, go to
|
||||||
|
your settings file and update the 'public > googleAnalytics > apiKey'
|
||||||
|
field with your GA unique identifier (UA-xxx...).
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
function googleAnaticsTrackPage() {
|
||||||
|
if (window && window.ga) {
|
||||||
|
window.ga('send', 'pageview', {
|
||||||
|
page: window.location.pathname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// add client-side callback: log a ga request on page view
|
||||||
|
addPageFunction(googleAnaticsTrackPage);
|
||||||
|
|
||||||
|
function googleAnalyticsInit() {
|
||||||
|
// get the google analytics id from the settings
|
||||||
|
const googleAnalyticsId = getSetting('googleAnalytics.apiKey');
|
||||||
|
|
||||||
|
// the google analytics id exists & isn't the placeholder from sample_settings.json
|
||||||
|
if (googleAnalyticsId && googleAnalyticsId !== 'foo123') {
|
||||||
|
(function(i, s, o, g, r, a, m) {
|
||||||
|
i['GoogleAnalyticsObject'] = r;
|
||||||
|
(i[r] =
|
||||||
|
i[r] ||
|
||||||
|
function() {
|
||||||
|
(i[r].q = i[r].q || []).push(arguments);
|
||||||
|
}),
|
||||||
|
(i[r].l = 1 * new Date());
|
||||||
|
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
|
||||||
|
a.async = 1;
|
||||||
|
a.src = g;
|
||||||
|
m.parentNode.insertBefore(a, m);
|
||||||
|
})(
|
||||||
|
window,
|
||||||
|
document,
|
||||||
|
'script',
|
||||||
|
'//www.google-analytics.com/analytics.js',
|
||||||
|
'ga'
|
||||||
|
);
|
||||||
|
|
||||||
|
const cookieDomain = document.domain === 'localhost' ? 'none' : 'auto';
|
||||||
|
|
||||||
|
window.ga('create', googleAnalyticsId, cookieDomain);
|
||||||
|
|
||||||
|
// trigger first request once analytics are initialized
|
||||||
|
googleAnaticsTrackPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// init google analytics on the client module
|
||||||
|
addInitFunction(googleAnalyticsInit);
|
2
packages/vulcan-events-ga/lib/client/main.js
Normal file
2
packages/vulcan-events-ga/lib/client/main.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import './ga.js';
|
||||||
|
export * from '../modules/index.js';
|
3
packages/vulcan-events-ga/lib/modules/index.js
Normal file
3
packages/vulcan-events-ga/lib/modules/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { registerSetting } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
registerSetting('googleAnalytics.apiKey', null, 'Google Analytics ID');
|
1
packages/vulcan-events-ga/lib/server/main.js
Normal file
1
packages/vulcan-events-ga/lib/server/main.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from '../modules/index.js';
|
20
packages/vulcan-events-ga/package.js
Normal file
20
packages/vulcan-events-ga/package.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Package.describe({
|
||||||
|
name: "vulcan:events-ga",
|
||||||
|
summary: "Vulcan Google Analytics event tracking package",
|
||||||
|
version: '1.8.1',
|
||||||
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
|
});
|
||||||
|
|
||||||
|
Package.onUse(function(api) {
|
||||||
|
|
||||||
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
|
api.use([
|
||||||
|
'vulcan:core@1.8.1',
|
||||||
|
'vulcan:events@1.8.1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
api.mainModule("lib/server/main.js", "server");
|
||||||
|
api.mainModule('lib/client/main.js', 'client');
|
||||||
|
|
||||||
|
});
|
21
packages/vulcan-events-intercom/README.md
Normal file
21
packages/vulcan-events-intercom/README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
Intercom package.
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"public": {
|
||||||
|
|
||||||
|
"intercom": {
|
||||||
|
"appId": "123foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"intercom": {
|
||||||
|
"accessToken": "456bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires installing the [react-intercom](https://github.com/nhagen/react-intercom) package (`npm install --save react-intercom`).
|
103
packages/vulcan-events-intercom/lib/client/intercom-client.js
Normal file
103
packages/vulcan-events-intercom/lib/client/intercom-client.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { getSetting, addCallback, Utils } from 'meteor/vulcan:core';
|
||||||
|
import { addPageFunction, addInitFunction, addIdentifyFunction, addTrackFunction } from 'meteor/vulcan:events';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Identify User
|
||||||
|
|
||||||
|
*/
|
||||||
|
function intercomIdentify(currentUser) {
|
||||||
|
intercomSettings = {
|
||||||
|
app_id: getSetting('intercom.appId'),
|
||||||
|
name: currentUser.displayName,
|
||||||
|
email: currentUser.email,
|
||||||
|
created_at: currentUser.createdAt,
|
||||||
|
_id: currentUser._id,
|
||||||
|
pageUrl: currentUser.pageUrl,
|
||||||
|
};
|
||||||
|
(function() {
|
||||||
|
var w = window;
|
||||||
|
var ic = w.Intercom;
|
||||||
|
if (typeof ic === 'function') {
|
||||||
|
ic('reattach_activator');
|
||||||
|
ic('update', intercomSettings);
|
||||||
|
} else {
|
||||||
|
var d = document;
|
||||||
|
var i = function() {
|
||||||
|
i.c(arguments);
|
||||||
|
};
|
||||||
|
i.q = [];
|
||||||
|
i.c = function(args) {
|
||||||
|
i.q.push(args);
|
||||||
|
};
|
||||||
|
w.Intercom = i;
|
||||||
|
function l() {
|
||||||
|
var s = d.createElement('script');
|
||||||
|
s.type = 'text/javascript';
|
||||||
|
s.async = true;
|
||||||
|
s.src = 'https://widget.intercom.io/widget/icygo7se';
|
||||||
|
var x = d.getElementsByTagName('script')[0];
|
||||||
|
x.parentNode.insertBefore(s, x);
|
||||||
|
}
|
||||||
|
if (w.attachEvent) {
|
||||||
|
w.attachEvent('onload', l);
|
||||||
|
} else {
|
||||||
|
w.addEventListener('load', l, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
addIdentifyFunction(intercomIdentify);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Track Event
|
||||||
|
|
||||||
|
*/
|
||||||
|
// function segmentTrack(eventName, eventProperties) {
|
||||||
|
// analytics.track(eventName, eventProperties);
|
||||||
|
// }
|
||||||
|
// addTrackFunction(segmentTrack);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Init Snippet
|
||||||
|
|
||||||
|
*/
|
||||||
|
function intercomInit() {
|
||||||
|
window.intercomSettings = {
|
||||||
|
app_id: getSetting('intercom.appId'),
|
||||||
|
};
|
||||||
|
(function() {
|
||||||
|
var w = window;
|
||||||
|
var ic = w.Intercom;
|
||||||
|
if (typeof ic === 'function') {
|
||||||
|
ic('reattach_activator');
|
||||||
|
ic('update', intercomSettings);
|
||||||
|
} else {
|
||||||
|
var d = document;
|
||||||
|
var i = function() {
|
||||||
|
i.c(arguments);
|
||||||
|
};
|
||||||
|
i.q = [];
|
||||||
|
i.c = function(args) {
|
||||||
|
i.q.push(args);
|
||||||
|
};
|
||||||
|
w.Intercom = i;
|
||||||
|
function l() {
|
||||||
|
var s = d.createElement('script');
|
||||||
|
s.type = 'text/javascript';
|
||||||
|
s.async = true;
|
||||||
|
s.src = 'https://widget.intercom.io/widget/icygo7se';
|
||||||
|
var x = d.getElementsByTagName('script')[0];
|
||||||
|
x.parentNode.insertBefore(s, x);
|
||||||
|
}
|
||||||
|
if (w.attachEvent) {
|
||||||
|
w.attachEvent('onload', l);
|
||||||
|
} else {
|
||||||
|
w.addEventListener('load', l, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
addInitFunction(intercomInit);
|
3
packages/vulcan-events-intercom/lib/client/main.js
Normal file
3
packages/vulcan-events-intercom/lib/client/main.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import './intercom-client.js';
|
||||||
|
|
||||||
|
export * from '../modules/index.js';
|
0
packages/vulcan-events-intercom/lib/modules/index.js
Normal file
0
packages/vulcan-events-intercom/lib/modules/index.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import Intercom from 'intercom-client';
|
||||||
|
import { getSetting, addCallback, Utils } from 'meteor/vulcan:core';
|
||||||
|
import { addPageFunction, addUserFunction, addInitFunction, addIdentifyFunction, addTrackFunction } from 'meteor/vulcan:events';
|
||||||
|
|
||||||
|
const token = getSetting('intercom.accessToken');
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('Please add your Intercom access token in settings.json');
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const intercomClient = new Intercom.Client({ token });
|
||||||
|
|
||||||
|
const getDate = () => new Date().valueOf().toString().substr(0,10);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
New User
|
||||||
|
|
||||||
|
*/
|
||||||
|
function intercomNewUser(user) {
|
||||||
|
intercomClient.users.create({
|
||||||
|
email: user.email,
|
||||||
|
custom_attributes: {
|
||||||
|
name: user.displayName,
|
||||||
|
profileUrl: Users.getProfileUrl(user, true),
|
||||||
|
_id: user._id,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addUserFunction(intercomNewUser);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Track Event
|
||||||
|
|
||||||
|
*/
|
||||||
|
function intercomTrackServer(eventName, eventProperties, currentUser) {
|
||||||
|
intercomClient.events.create({
|
||||||
|
event_name: eventName,
|
||||||
|
created_at: getDate(),
|
||||||
|
email: currentUser.email,
|
||||||
|
metadata: {
|
||||||
|
...eventProperties
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addTrackFunction(intercomTrackServer);
|
||||||
|
|
||||||
|
}
|
1
packages/vulcan-events-intercom/lib/server/main.js
Normal file
1
packages/vulcan-events-intercom/lib/server/main.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from '../modules/index.js';
|
20
packages/vulcan-events-intercom/package.js
Normal file
20
packages/vulcan-events-intercom/package.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Package.describe({
|
||||||
|
name: 'vulcan:events-intercom',
|
||||||
|
summary: 'Vulcan Intercom integration package.',
|
||||||
|
version: '1.8.1',
|
||||||
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
|
});
|
||||||
|
|
||||||
|
Package.onUse(function (api) {
|
||||||
|
|
||||||
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
|
api.use([
|
||||||
|
'vulcan:core@1.8.1',
|
||||||
|
'vulcan:events@1.8.1'
|
||||||
|
]);
|
||||||
|
|
||||||
|
api.mainModule("lib/client/main.js", "client");
|
||||||
|
api.mainModule("lib/server/main.js", "server");
|
||||||
|
|
||||||
|
});
|
1
packages/vulcan-events-internal/README.md
Normal file
1
packages/vulcan-events-internal/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Vulcan events package, used internally.
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { addTrackFunction } from 'meteor/vulcan:events';
|
||||||
|
import { ApolloClient } from 'apollo-client';
|
||||||
|
import { getRenderContext } from 'meteor/vulcan:lib';
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
function trackInternal(eventName, eventProperties) {
|
||||||
|
const { apolloClient, store } = getRenderContext();
|
||||||
|
const mutation = gql`
|
||||||
|
mutation EventsNew($document: EventsInput) {
|
||||||
|
EventsNew(document: $document) {
|
||||||
|
name
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const variables = {
|
||||||
|
document: {
|
||||||
|
name: eventName,
|
||||||
|
properties: eventProperties,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
apolloClient.mutate({ mutation, variables });
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrackFunction(trackInternal);
|
3
packages/vulcan-events-internal/lib/client/main.js
Normal file
3
packages/vulcan-events-internal/lib/client/main.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from '../modules/index.js';
|
||||||
|
|
||||||
|
import './internal-client.js';
|
27
packages/vulcan-events-internal/lib/modules/collection.js
Normal file
27
packages/vulcan-events-internal/lib/modules/collection.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core';
|
||||||
|
import schema from './schema.js';
|
||||||
|
import Users from 'meteor/vulcan:users';
|
||||||
|
|
||||||
|
const Events = createCollection({
|
||||||
|
|
||||||
|
collectionName: 'Events',
|
||||||
|
|
||||||
|
typeName: 'Event',
|
||||||
|
|
||||||
|
schema,
|
||||||
|
|
||||||
|
resolvers: getDefaultResolvers('Events'),
|
||||||
|
|
||||||
|
mutations: getDefaultMutations('Events', {
|
||||||
|
newCheck: () => true,
|
||||||
|
editCheck: () => false,
|
||||||
|
removeCheck: () => false
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Events.checkAccess = (currentUser, doc) => {
|
||||||
|
return Users.isAdmin(currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Events;
|
1
packages/vulcan-events-internal/lib/modules/index.js
Normal file
1
packages/vulcan-events-internal/lib/modules/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './collection.js';
|
38
packages/vulcan-events-internal/lib/modules/schema.js
Normal file
38
packages/vulcan-events-internal/lib/modules/schema.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const schema = {
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
onInsert: () => {
|
||||||
|
return new Date()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
insertableBy: ['guests'],
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
unique: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
important: {
|
||||||
|
// marking an event as important means it should never be erased
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
type: Object,
|
||||||
|
optional: true,
|
||||||
|
blackbox: true,
|
||||||
|
insertableBy: ['guests'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default schema;
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { addTrackFunction } from 'meteor/vulcan:events';
|
||||||
|
import { newMutation } from 'meteor/vulcan:lib';
|
||||||
|
import Events from '../modules/collection';
|
||||||
|
|
||||||
|
async function trackInternalServer(eventName, eventProperties, currentUser) {
|
||||||
|
const document = {
|
||||||
|
name: eventName,
|
||||||
|
properties: eventProperties,
|
||||||
|
};
|
||||||
|
return await newMutation({
|
||||||
|
collection: Events,
|
||||||
|
document,
|
||||||
|
currentUser,
|
||||||
|
validate: false,
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrackFunction(trackInternalServer);
|
3
packages/vulcan-events-internal/lib/server/main.js
Normal file
3
packages/vulcan-events-internal/lib/server/main.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from '../modules/index.js';
|
||||||
|
|
||||||
|
import './internal-server';
|
20
packages/vulcan-events-internal/package.js
Normal file
20
packages/vulcan-events-internal/package.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Package.describe({
|
||||||
|
name: "vulcan:events-internal",
|
||||||
|
summary: "Vulcan internal event tracking package",
|
||||||
|
version: '1.8.1',
|
||||||
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
|
});
|
||||||
|
|
||||||
|
Package.onUse(function(api) {
|
||||||
|
|
||||||
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
|
api.use([
|
||||||
|
'vulcan:core@1.8.1',
|
||||||
|
'vulcan:events@1.8.1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
api.mainModule("lib/server/main.js", "server");
|
||||||
|
api.mainModule('lib/client/main.js', 'client');
|
||||||
|
|
||||||
|
});
|
3
packages/vulcan-events-segment/lib/client/main.js
Normal file
3
packages/vulcan-events-segment/lib/client/main.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from '../modules/index';
|
||||||
|
|
||||||
|
import './segment-client.js';
|
110
packages/vulcan-events-segment/lib/client/segment-client.js
Normal file
110
packages/vulcan-events-segment/lib/client/segment-client.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import { getSetting, addCallback, Utils } from 'meteor/vulcan:core';
|
||||||
|
import {
|
||||||
|
addPageFunction,
|
||||||
|
addInitFunction,
|
||||||
|
addIdentifyFunction,
|
||||||
|
addTrackFunction,
|
||||||
|
} from 'meteor/vulcan:events';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Track Page
|
||||||
|
|
||||||
|
*/
|
||||||
|
function segmentTrackPage(route) {
|
||||||
|
const { name, path } = route;
|
||||||
|
const properties = {
|
||||||
|
url: Utils.getSiteUrl().slice(0, -1) + path,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
window.analytics.page(null, name, properties);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
addPageFunction(segmentTrackPage);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Identify User
|
||||||
|
|
||||||
|
*/
|
||||||
|
function segmentIdentify(currentUser) {
|
||||||
|
window.analytics.identify(currentUser._id, {
|
||||||
|
email: currentUser.email,
|
||||||
|
pageUrl: currentUser.pageUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addIdentifyFunction(segmentIdentify);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Track Event
|
||||||
|
|
||||||
|
*/
|
||||||
|
function segmentTrack(eventName, eventProperties) {
|
||||||
|
analytics.track(eventName, eventProperties);
|
||||||
|
}
|
||||||
|
addTrackFunction(segmentTrack);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Init Snippet
|
||||||
|
|
||||||
|
*/
|
||||||
|
function segmentInit() {
|
||||||
|
!(function() {
|
||||||
|
var analytics = (window.analytics = window.analytics || []);
|
||||||
|
if (!analytics.initialize)
|
||||||
|
if (analytics.invoked)
|
||||||
|
window.console &&
|
||||||
|
console.error &&
|
||||||
|
console.error('Segment snippet included twice.');
|
||||||
|
else {
|
||||||
|
analytics.invoked = !0;
|
||||||
|
analytics.methods = [
|
||||||
|
'trackSubmit',
|
||||||
|
'trackClick',
|
||||||
|
'trackLink',
|
||||||
|
'trackForm',
|
||||||
|
'pageview',
|
||||||
|
'identify',
|
||||||
|
'reset',
|
||||||
|
'group',
|
||||||
|
'track',
|
||||||
|
'ready',
|
||||||
|
'alias',
|
||||||
|
'debug',
|
||||||
|
'page',
|
||||||
|
'once',
|
||||||
|
'off',
|
||||||
|
'on',
|
||||||
|
];
|
||||||
|
analytics.factory = function(t) {
|
||||||
|
return function() {
|
||||||
|
var e = Array.prototype.slice.call(arguments);
|
||||||
|
e.unshift(t);
|
||||||
|
analytics.push(e);
|
||||||
|
return analytics;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
for (var t = 0; t < analytics.methods.length; t++) {
|
||||||
|
var e = analytics.methods[t];
|
||||||
|
analytics[e] = analytics.factory(e);
|
||||||
|
}
|
||||||
|
analytics.load = function(t) {
|
||||||
|
var e = document.createElement('script');
|
||||||
|
e.type = 'text/javascript';
|
||||||
|
e.async = !0;
|
||||||
|
e.src =
|
||||||
|
('https:' === document.location.protocol ? 'https://' : 'http://') +
|
||||||
|
'cdn.segment.com/analytics.js/v1/' +
|
||||||
|
t +
|
||||||
|
'/analytics.min.js';
|
||||||
|
var n = document.getElementsByTagName('script')[0];
|
||||||
|
n.parentNode.insertBefore(e, n);
|
||||||
|
};
|
||||||
|
analytics.SNIPPET_VERSION = '4.0.0';
|
||||||
|
analytics.load(getSetting('segment.clientKey'));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
addInitFunction(segmentInit);
|
4
packages/vulcan-events-segment/lib/modules/index.js
Normal file
4
packages/vulcan-events-segment/lib/modules/index.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { registerSetting } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
registerSetting('segment.clientKey', null, 'Segment client-side API key');
|
||||||
|
registerSetting('segment.serverKey', null, 'Segment server-side API key');
|
3
packages/vulcan-events-segment/lib/server/main.js
Normal file
3
packages/vulcan-events-segment/lib/server/main.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// export * from '../modules/index';
|
||||||
|
|
||||||
|
export * from './segment-server.js';
|
40
packages/vulcan-events-segment/lib/server/segment-server.js
Normal file
40
packages/vulcan-events-segment/lib/server/segment-server.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import Analytics from 'analytics-node';
|
||||||
|
import { getSetting, addCallback, Utils } from 'meteor/vulcan:core';
|
||||||
|
import { addPageFunction, addInitFunction, addIdentifyFunction, addTrackFunction } from 'meteor/vulcan:events';
|
||||||
|
|
||||||
|
const segmentWriteKey = getSetting('segment.serverKey');
|
||||||
|
|
||||||
|
if (segmentWriteKey) {
|
||||||
|
|
||||||
|
const analytics = new Analytics(segmentWriteKey);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Identify User
|
||||||
|
|
||||||
|
*/
|
||||||
|
function segmentIdentifyServer(currentUser) {
|
||||||
|
analytics.identify({
|
||||||
|
userId: currentUser._id,
|
||||||
|
traits: {
|
||||||
|
email: currentUser.email,
|
||||||
|
pageUrl: currentUser.pageUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addIdentifyFunction(segmentIdentifyServer);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Track Event
|
||||||
|
|
||||||
|
*/
|
||||||
|
function segmentTrackServer(eventName, eventProperties, currentUser) {
|
||||||
|
analytics.track({
|
||||||
|
event: eventName,
|
||||||
|
properties: eventProperties,
|
||||||
|
userId: currentUser && currentUser._id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addTrackFunction(segmentTrackServer);
|
||||||
|
}
|
20
packages/vulcan-events-segment/package.js
Normal file
20
packages/vulcan-events-segment/package.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Package.describe({
|
||||||
|
name: "vulcan:events-segment",
|
||||||
|
summary: "Vulcan Segment",
|
||||||
|
version: '1.8.1',
|
||||||
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
|
});
|
||||||
|
|
||||||
|
Package.onUse(function (api) {
|
||||||
|
|
||||||
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
|
api.use([
|
||||||
|
'vulcan:core@1.8.1',
|
||||||
|
'vulcan:events@1.8.1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
api.mainModule('lib/server/main.js', 'server');
|
||||||
|
api.mainModule('lib/client/main.js', 'client');
|
||||||
|
|
||||||
|
});
|
|
@ -1,5 +0,0 @@
|
||||||
import { addCallback } from 'meteor/vulcan:core';
|
|
||||||
import { sendGoogleAnalyticsRequest } from './helpers';
|
|
||||||
|
|
||||||
// add client-side callback: log a ga request on page view
|
|
||||||
addCallback('router.onUpdate', sendGoogleAnalyticsRequest);
|
|
|
@ -1,8 +0,0 @@
|
||||||
import Events from './collection.js';
|
|
||||||
import { initGoogleAnalytics } from './helpers.js';
|
|
||||||
import './callbacks.js';
|
|
||||||
|
|
||||||
// init google analytics on the client module
|
|
||||||
initGoogleAnalytics();
|
|
||||||
|
|
||||||
export default Events;
|
|
1
packages/vulcan-events/lib/client/main.js
Normal file
1
packages/vulcan-events/lib/client/main.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from '../modules/index.js';
|
|
@ -1,33 +0,0 @@
|
||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
|
|
||||||
const Events = new Mongo.Collection('events');
|
|
||||||
|
|
||||||
Events.schema = new SimpleSchema({
|
|
||||||
createdAt: {
|
|
||||||
type: Date
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
unique: {
|
|
||||||
type: Boolean,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
important: { // marking an event as important means it should never be erased
|
|
||||||
type: Boolean,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
type: Object,
|
|
||||||
optional: true,
|
|
||||||
blackbox: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Events.attachSchema(Events.schema);
|
|
||||||
|
|
||||||
export default Events;
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { getSetting, registerSetting } from 'meteor/vulcan:core';
|
|
||||||
import Events from './collection.js';
|
|
||||||
|
|
||||||
registerSetting('googleAnalyticsId', null, 'Google Analytics ID');
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
We provide a special support for Google Analytics.
|
|
||||||
|
|
||||||
If you want to enable GA page viewing / tracking, go to
|
|
||||||
your settings file and update the 'public > googleAnalyticsId'
|
|
||||||
field with your GA unique identifier (UA-xxx...).
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function sendGoogleAnalyticsRequest () {
|
|
||||||
if (window && window.ga) {
|
|
||||||
window.ga('send', 'pageview', {
|
|
||||||
'page': window.location.pathname
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initGoogleAnalytics = () => {
|
|
||||||
|
|
||||||
// get the google analytics id from the settings
|
|
||||||
const googleAnalyticsId = getSetting('googleAnalyticsId');
|
|
||||||
|
|
||||||
// the google analytics id exists & isn't the placeholder from sample_settings.json
|
|
||||||
if (googleAnalyticsId && googleAnalyticsId !== 'foo123') {
|
|
||||||
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
const cookieDomain = document.domain === 'localhost' ? 'none' : 'auto';
|
|
||||||
|
|
||||||
window.ga('create', googleAnalyticsId, cookieDomain);
|
|
||||||
|
|
||||||
// trigger first request once analytics are initialized
|
|
||||||
sendGoogleAnalyticsRequest();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// collection based logging
|
|
||||||
Events.log = function (event) {
|
|
||||||
|
|
||||||
// if event is supposed to be unique, check if it has already been logged
|
|
||||||
if (!!event.unique && !!Events.findOne({name: event.name})) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.createdAt = new Date();
|
|
||||||
|
|
||||||
Events.insert(event);
|
|
||||||
|
|
||||||
};
|
|
41
packages/vulcan-events/lib/modules/events.js
Normal file
41
packages/vulcan-events/lib/modules/events.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { addCallback } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
export const initFunctions = [];
|
||||||
|
|
||||||
|
export const trackFunctions = [];
|
||||||
|
|
||||||
|
export const addInitFunction = f => {
|
||||||
|
initFunctions.push(f);
|
||||||
|
// execute init function as soon as possible
|
||||||
|
f();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addTrackFunction = f => {
|
||||||
|
trackFunctions.push(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const track = async (eventName, eventProperties, currentUser) => {
|
||||||
|
for (let f of trackFunctions) {
|
||||||
|
await f(eventName, eventProperties, currentUser);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addUserFunction = f => {
|
||||||
|
addCallback('users.new.async', f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addIdentifyFunction = f => {
|
||||||
|
addCallback('events.identify', f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addPageFunction = f => {
|
||||||
|
const f2 = (empty, route) => f(route);
|
||||||
|
|
||||||
|
// rename f2 to same name as f
|
||||||
|
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
|
||||||
|
const descriptor = Object.create(null); // no inherited properties
|
||||||
|
descriptor.value = f.name;
|
||||||
|
Object.defineProperty(f2, 'name', descriptor);
|
||||||
|
|
||||||
|
addCallback('router.onUpdate', f2);
|
||||||
|
};
|
1
packages/vulcan-events/lib/modules/index.js
Normal file
1
packages/vulcan-events/lib/modules/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './events';
|
|
@ -1,18 +0,0 @@
|
||||||
// import { GraphQLSchema } from 'meteor/vulcan:core';
|
|
||||||
// // import Events from './collection.js';
|
|
||||||
// import { requestAnalyticsAsync } from './helpers.js';
|
|
||||||
|
|
||||||
// GraphQLSchema.addMutation('eventTrack(eventName: String, properties: JSON): JSON');
|
|
||||||
|
|
||||||
// const resolvers = {
|
|
||||||
// Mutation: {
|
|
||||||
// eventTrack: (root, { eventName, properties }, context) => {
|
|
||||||
// const user = context.currentUser || {_id: 'anonymous'};
|
|
||||||
|
|
||||||
|
|
||||||
// return properties;
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// GraphQLSchema.addResolvers(resolvers);
|
|
|
@ -1,5 +0,0 @@
|
||||||
import Events from './collection.js';
|
|
||||||
import './callbacks.js';
|
|
||||||
// import './mutations.js';
|
|
||||||
|
|
||||||
export default Events;
|
|
1
packages/vulcan-events/lib/server/main.js
Normal file
1
packages/vulcan-events/lib/server/main.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from '../modules/index.js';
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:events",
|
name: "vulcan:events",
|
||||||
summary: "Vulcan event tracking package",
|
summary: "Vulcan event tracking package",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git"
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@ Package.onUse(function(api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.8.0',
|
'vulcan:core@1.8.1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule("lib/server.js", "server");
|
api.mainModule("lib/server/main.js", "server");
|
||||||
api.mainModule("lib/client.js", "client");
|
api.mainModule('lib/client/main.js', 'client');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:forms-tags",
|
name: "vulcan:forms-tags",
|
||||||
summary: "Vulcan tag input package",
|
summary: "Vulcan tag input package",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: 'https://github.com/VulcanJS/Vulcan.git'
|
git: 'https://github.com/VulcanJS/Vulcan.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ Package.onUse( function(api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.8.0',
|
'vulcan:core@1.8.1',
|
||||||
'vulcan:forms@1.8.0'
|
'vulcan:forms@1.8.1'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule("lib/export.js", ["client", "server"]);
|
api.mainModule("lib/export.js", ["client", "server"]);
|
||||||
|
|
|
@ -169,15 +169,15 @@ class Upload extends PureComponent {
|
||||||
const newValue = this.enableMultiple() ? removeNthItem(this.state.value, index): '';
|
const newValue = this.enableMultiple() ? removeNthItem(this.state.value, index): '';
|
||||||
this.context.addToAutofilledValues({[this.props.name]: newValue});
|
this.context.addToAutofilledValues({[this.props.name]: newValue});
|
||||||
this.setState({
|
this.setState({
|
||||||
preview: newValue,
|
preview: null,
|
||||||
value: newValue,
|
value: newValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { uploading, preview, value } = this.state;
|
const { uploading, preview, value } = this.state;
|
||||||
|
|
||||||
// show the actual uploaded image or the preview
|
// show the actual uploaded image or the preview
|
||||||
|
|
||||||
const imageData = this.enableMultiple() ? (preview ? value.concat(preview) : value) : value || preview;
|
const imageData = this.enableMultiple() ? (preview ? value.concat(preview) : value) : value || preview;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:forms-upload",
|
name: "vulcan:forms-upload",
|
||||||
summary: "Vulcan package extending vulcan:forms to upload images to Cloudinary from a drop zone.",
|
summary: "Vulcan package extending vulcan:forms to upload images to Cloudinary from a drop zone.",
|
||||||
version: "1.8.0",
|
version: "1.8.1",
|
||||||
git: 'https://github.com/xavcz/nova-forms-upload.git'
|
git: 'https://github.com/xavcz/nova-forms-upload.git'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ Package.onUse( function(api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.8.0',
|
'vulcan:core@1.8.1',
|
||||||
'vulcan:forms@1.8.0',
|
'vulcan:forms@1.8.1',
|
||||||
'fourseven:scss@4.5.0'
|
'fourseven:scss@4.5.0'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Alert from 'react-bootstrap/lib/Alert'
|
import Alert from 'react-bootstrap/lib/Alert'
|
||||||
|
import { registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
const Flash = ({message, type}) => {
|
const Flash = ({message, type}) => {
|
||||||
|
|
||||||
|
@ -24,4 +25,4 @@ Flash.propTypes = {
|
||||||
message: PropTypes.oneOfType([PropTypes.object.isRequired, PropTypes.array.isRequired])
|
message: PropTypes.oneOfType([PropTypes.object.isRequired, PropTypes.array.isRequired])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Flash;
|
registerComponent('FormFlash', Flash);
|
|
@ -25,12 +25,9 @@ This component expects:
|
||||||
import { Components, Utils, runCallbacks } from 'meteor/vulcan:core';
|
import { Components, Utils, runCallbacks } from 'meteor/vulcan:core';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
|
import { intlShape } from 'meteor/vulcan:i18n';
|
||||||
import Formsy from 'formsy-react';
|
import Formsy from 'formsy-react';
|
||||||
import Button from 'react-bootstrap/lib/Button';
|
import { getEditableFields, getInsertableFields } from '../modules/utils.js';
|
||||||
import Flash from "./Flash.jsx";
|
|
||||||
import FormGroup from "./FormGroup.jsx";
|
|
||||||
import { flatten, deepValue, getEditableFields, getInsertableFields } from '../modules/utils.js';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@ -156,21 +153,32 @@ class Form extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace empty value, which has not been prefilled, by the default value from the schema
|
// replace empty value, which has not been prefilled, by the default value from the schema
|
||||||
if (fieldSchema.defaultValue && field.value === "") {
|
// keep defaultValue for backwards compatibility even though it doesn't actually work
|
||||||
|
if (fieldSchema.defaultValue && (typeof field.value === 'undefined' || field.value === '')) {
|
||||||
field.value = fieldSchema.defaultValue;
|
field.value = fieldSchema.defaultValue;
|
||||||
}
|
}
|
||||||
|
if (fieldSchema.default && (typeof field.value === 'undefined' || field.value === '')) {
|
||||||
|
field.value = fieldSchema.default;
|
||||||
|
}
|
||||||
|
|
||||||
// add options if they exist
|
// add options if they exist
|
||||||
if (fieldSchema.form && fieldSchema.form.options) {
|
if (fieldSchema.form && fieldSchema.form.options) {
|
||||||
field.options = typeof fieldSchema.form.options === "function" ? fieldSchema.form.options.call(fieldSchema, this.props) : fieldSchema.form.options;
|
field.options = typeof fieldSchema.form.options === "function" ? fieldSchema.form.options.call(fieldSchema, this.props) : fieldSchema.form.options;
|
||||||
|
|
||||||
|
// in case of checkbox groups, check "checked" option to populate value
|
||||||
|
if (!field.value) {
|
||||||
|
field.value = _.where(field.options, {checked: true}).map(option => option.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldSchema.form && fieldSchema.form.disabled) {
|
if (fieldSchema.form) {
|
||||||
field.disabled = typeof fieldSchema.form.disabled === "function" ? fieldSchema.form.disabled.call(fieldSchema) : fieldSchema.form.disabled;
|
for (const prop in fieldSchema.form) {
|
||||||
}
|
if (prop !== 'prefill' && prop !== 'options' && fieldSchema.form.hasOwnProperty(prop)) {
|
||||||
|
field[prop] = typeof fieldSchema.form[prop] === "function" ?
|
||||||
if (fieldSchema.form && fieldSchema.form.help) {
|
fieldSchema.form[prop].call(fieldSchema) :
|
||||||
field.help = typeof fieldSchema.form.help === "function" ? fieldSchema.form.help.call(fieldSchema) : fieldSchema.form.help;
|
fieldSchema.form[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add limit
|
// add limit
|
||||||
|
@ -353,7 +361,8 @@ class Form extends Component {
|
||||||
|
|
||||||
message = error.data.errors.map(error => {
|
message = error.data.errors.map(error => {
|
||||||
return {
|
return {
|
||||||
content: this.getErrorMessage(error)
|
content: this.getErrorMessage(error),
|
||||||
|
data: error.data,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -362,8 +371,8 @@ class Form extends Component {
|
||||||
message = {content: error.message || this.context.intl.formatMessage({id: error.id, defaultMessage: error.id}, error.data)}
|
message = {content: error.message || this.context.intl.formatMessage({id: error.id, defaultMessage: error.id}, error.data)}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Flash key={index} message={message} type="error"/>
|
return <Components.FormFlash key={index} message={message} type="error"/>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -613,26 +622,25 @@ class Form extends Component {
|
||||||
disabled={this.state.disabled}
|
disabled={this.state.disabled}
|
||||||
ref="form"
|
ref="form"
|
||||||
>
|
>
|
||||||
{this.renderErrors()}
|
|
||||||
{fieldGroups.map(group => <FormGroup key={group.name} {...group} updateCurrentValues={this.updateCurrentValues} />)}
|
|
||||||
|
|
||||||
<div className="form-submit">
|
{this.renderErrors()}
|
||||||
<Button type="submit" bsStyle="primary">{this.props.submitLabel ? this.props.submitLabel : <FormattedMessage id="forms.submit"/>}</Button>
|
|
||||||
{this.props.cancelCallback ? <a className="form-cancel" onClick={(e) => {e.preventDefault(); this.props.cancelCallback(this.getDocument())}}>{this.props.cancelLabel ? this.props.cancelLabel : <FormattedMessage id="forms.cancel"/>}</a> : null}
|
{fieldGroups.map(group => <Components.FormGroup key={group.name} {...group} updateCurrentValues={this.updateCurrentValues} />)}
|
||||||
</div>
|
|
||||||
|
{this.props.repeatErrors && this.renderErrors()}
|
||||||
|
|
||||||
|
<Components.FormSubmit submitLabel={this.props.submitLabel}
|
||||||
|
cancelLabel={this.props.cancelLabel}
|
||||||
|
cancelCallback={this.props.cancelCallback}
|
||||||
|
document={this.getDocument()}
|
||||||
|
deleteDocument={(this.props.formType === 'edit'
|
||||||
|
&& this.props.showRemove
|
||||||
|
&& this.deleteDocument)
|
||||||
|
|| null}
|
||||||
|
collectionName={collectionName}
|
||||||
|
/>
|
||||||
|
|
||||||
</Formsy.Form>
|
</Formsy.Form>
|
||||||
|
|
||||||
{
|
|
||||||
this.props.formType === 'edit' && this.props.showRemove
|
|
||||||
? <div>
|
|
||||||
<hr/>
|
|
||||||
<a href="javascript:void()" onClick={this.deleteDocument} className={`delete-link ${collectionName}-delete-link`}>
|
|
||||||
<Components.Icon name="close"/> <FormattedMessage id="forms.delete"/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -660,6 +668,7 @@ Form.propTypes = {
|
||||||
showRemove: PropTypes.bool,
|
showRemove: PropTypes.bool,
|
||||||
submitLabel: PropTypes.string,
|
submitLabel: PropTypes.string,
|
||||||
cancelLabel: PropTypes.string,
|
cancelLabel: PropTypes.string,
|
||||||
|
repeatErrors: PropTypes.bool,
|
||||||
|
|
||||||
// callbacks
|
// callbacks
|
||||||
submitCallback: PropTypes.func,
|
submitCallback: PropTypes.func,
|
||||||
|
@ -673,7 +682,8 @@ Form.propTypes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
Form.defaultProps = {
|
Form.defaultProps = {
|
||||||
layout: "horizontal",
|
layout: 'horizontal',
|
||||||
|
repeatErrors: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
Form.contextTypes = {
|
Form.contextTypes = {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import { intlShape } from 'meteor/vulcan:i18n';
|
import { intlShape } from 'meteor/vulcan:i18n';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Components } from 'meteor/vulcan:core';
|
import { Components } from 'meteor/vulcan:core';
|
||||||
|
import { registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
class FormComponent extends PureComponent {
|
class FormComponent extends PureComponent {
|
||||||
|
|
||||||
|
@ -97,6 +98,9 @@ class FormComponent extends PureComponent {
|
||||||
case 'datetime':
|
case 'datetime':
|
||||||
return <Components.FormComponentDateTime {...properties} />;
|
return <Components.FormComponentDateTime {...properties} />;
|
||||||
|
|
||||||
|
case 'time':
|
||||||
|
return <Components.FormComponentTime {...properties} />;
|
||||||
|
|
||||||
case 'text':
|
case 'text':
|
||||||
return <Components.FormComponentDefault {...properties}/>;
|
return <Components.FormComponentDefault {...properties}/>;
|
||||||
|
|
||||||
|
@ -120,16 +124,37 @@ class FormComponent extends PureComponent {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showClear = () => {
|
||||||
|
return ['datetime', 'select', 'radiogroup'].includes(this.props.control);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearField = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(this.props)
|
||||||
|
const fieldName = this.props.name;
|
||||||
|
// clear value
|
||||||
|
this.props.updateCurrentValues({[fieldName]: null});
|
||||||
|
// add it to unset
|
||||||
|
this.context.addToDeletedValues(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderClear() {
|
||||||
|
return (
|
||||||
|
<a href="javascript:void(0)" className="form-component-clear" title={this.context.intl.formatMessage({id: 'forms.clear_field'})} onClick={this.clearField}><span>✕</span></a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const hasErrors = this.props.errors && this.props.errors.length;
|
const hasErrors = this.props.errors && this.props.errors.length;
|
||||||
const inputClass = classNames('form-input', `input-${this.props.name}`, {'input-error': hasErrors});
|
const inputClass = classNames('form-input', `input-${this.props.name}`, `form-component-${this.props.control || 'default'}`,{'input-error': hasErrors});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={inputClass}>
|
<div className={inputClass}>
|
||||||
{this.props.beforeComponent ? this.props.beforeComponent : null}
|
{this.props.beforeComponent ? this.props.beforeComponent : null}
|
||||||
{this.renderComponent()}
|
{this.renderComponent()}
|
||||||
{hasErrors ? this.renderErrors() : null}
|
{hasErrors ? this.renderErrors() : null}
|
||||||
|
{this.showClear() ? this.renderClear() : null}
|
||||||
{this.props.limit ? <div className={classNames('form-control-limit', {danger: this.state.limit < 10})}>{this.state.limit}</div> : null}
|
{this.props.limit ? <div className={classNames('form-control-limit', {danger: this.state.limit < 10})}>{this.state.limit}</div> : null}
|
||||||
{this.props.afterComponent ? this.props.afterComponent : null}
|
{this.props.afterComponent ? this.props.afterComponent : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -153,7 +178,8 @@ FormComponent.propTypes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
FormComponent.contextTypes = {
|
FormComponent.contextTypes = {
|
||||||
intl: intlShape
|
intl: intlShape,
|
||||||
|
addToDeletedValues: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FormComponent;
|
registerComponent('FormComponent', FormComponent);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import FormComponent from './FormComponent.jsx';
|
|
||||||
import { Components } from 'meteor/vulcan:core';
|
import { Components } from 'meteor/vulcan:core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
class FormGroup extends PureComponent {
|
class FormGroup extends PureComponent {
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class FormGroup extends PureComponent {
|
||||||
<div className="form-section">
|
<div className="form-section">
|
||||||
{this.props.name === 'default' ? null : this.renderHeading()}
|
{this.props.name === 'default' ? null : this.renderHeading()}
|
||||||
<div className={classNames({'form-section-collapsed': this.state.collapsed && !hasErrors})}>
|
<div className={classNames({'form-section-collapsed': this.state.collapsed && !hasErrors})}>
|
||||||
{this.props.fields.map(field => <FormComponent key={field.name} {...field} updateCurrentValues={this.props.updateCurrentValues} />)}
|
{this.props.fields.map(field => <Components.FormComponent key={field.name} {...field} updateCurrentValues={this.props.updateCurrentValues} />)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -55,4 +55,4 @@ FormGroup.propTypes = {
|
||||||
updateCurrentValues: PropTypes.func
|
updateCurrentValues: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FormGroup;
|
registerComponent('FormGroup', FormGroup);
|
65
packages/vulcan-forms/lib/components/FormSubmit.jsx
Normal file
65
packages/vulcan-forms/lib/components/FormSubmit.jsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Components } from 'meteor/vulcan:core';
|
||||||
|
import { registerComponent } from 'meteor/vulcan:core';
|
||||||
|
import Button from 'react-bootstrap/lib/Button';
|
||||||
|
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||||
|
|
||||||
|
|
||||||
|
const FormSubmit = ({
|
||||||
|
submitLabel,
|
||||||
|
cancelLabel,
|
||||||
|
cancelCallback,
|
||||||
|
document,
|
||||||
|
deleteDocument,
|
||||||
|
collectionName,
|
||||||
|
classes
|
||||||
|
}) => (
|
||||||
|
<div className="form-submit">
|
||||||
|
|
||||||
|
<Button type="submit" bsStyle="primary">
|
||||||
|
{submitLabel ? submitLabel : <FormattedMessage id="forms.submit"/>}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{
|
||||||
|
cancelCallback
|
||||||
|
?
|
||||||
|
<a className="form-cancel" onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
cancelCallback(document);
|
||||||
|
}}>{cancelLabel ? cancelLabel :
|
||||||
|
<FormattedMessage id="forms.cancel"/>}</a>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
deleteDocument
|
||||||
|
?
|
||||||
|
<div>
|
||||||
|
<hr/>
|
||||||
|
<a href="javascript:void()" onClick={deleteDocument}
|
||||||
|
className={`delete-link ${collectionName}-delete-link`}>
|
||||||
|
<Components.Icon name="close"/> <FormattedMessage id="forms.delete"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
FormSubmit.propTypes = {
|
||||||
|
submitLabel: PropTypes.string,
|
||||||
|
cancelLabel: PropTypes.string,
|
||||||
|
cancelCallback: PropTypes.func,
|
||||||
|
document: PropTypes.object,
|
||||||
|
deleteDocument: PropTypes.func,
|
||||||
|
collectionName: PropTypes.string,
|
||||||
|
classes: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
registerComponent('FormSubmit', FormSubmit);
|
|
@ -35,6 +35,13 @@ import { withDocument } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
class FormWrapper extends PureComponent {
|
class FormWrapper extends PureComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
// instantiate the wrapped component in constructor, not in render
|
||||||
|
// see https://reactjs.org/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method
|
||||||
|
this.FormComponent = this.getComponent();
|
||||||
|
}
|
||||||
|
|
||||||
// return the current schema based on either the schema or collection prop
|
// return the current schema based on either the schema or collection prop
|
||||||
getSchema() {
|
getSchema() {
|
||||||
return this.props.schema ? this.props.schema : Utils.stripTelescopeNamespace(this.props.collection.simpleSchema()._schema);
|
return this.props.schema ? this.props.schema : Utils.stripTelescopeNamespace(this.props.collection.simpleSchema()._schema);
|
||||||
|
@ -69,7 +76,7 @@ class FormWrapper extends PureComponent {
|
||||||
mutationFields = _.intersection(mutationFields, fields);
|
mutationFields = _.intersection(mutationFields, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve any array field with resolveAs as fieldName{_id}
|
// resolve any array field with resolveAs as fieldName{_id} -> why?
|
||||||
/*
|
/*
|
||||||
- string field with no resolver -> fieldName
|
- string field with no resolver -> fieldName
|
||||||
- string field with a resolver -> fieldName
|
- string field with a resolver -> fieldName
|
||||||
|
@ -77,9 +84,9 @@ class FormWrapper extends PureComponent {
|
||||||
- array field with a resolver -> fieldName{_id}
|
- array field with a resolver -> fieldName{_id}
|
||||||
*/
|
*/
|
||||||
const mapFieldNameToField = fieldName => {
|
const mapFieldNameToField = fieldName => {
|
||||||
const field = this.getSchema()[fieldName]
|
const field = this.getSchema()[fieldName];
|
||||||
return field.resolveAs && field.type.definitions[0].type === Array
|
return field.resolveAs && field.type.definitions[0].type === Array
|
||||||
? `${fieldName}{_id}` // if it's a custom resolver, add a basic query to its _id
|
? `${fieldName}` // if it's a custom resolver, add a basic query to its _id
|
||||||
: fieldName; // else just ask for the field name
|
: fieldName; // else just ask for the field name
|
||||||
}
|
}
|
||||||
queryFields = queryFields.map(mapFieldNameToField);
|
queryFields = queryFields.map(mapFieldNameToField);
|
||||||
|
@ -108,13 +115,7 @@ class FormWrapper extends PureComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
getComponent() {
|
||||||
// prevent extra re-renderings for unknown reasons
|
|
||||||
// re-render only if the document selector changes
|
|
||||||
return nextProps.slug !== this.props.slug || nextProps.documentId !== this.props.documentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
// console.log(this)
|
// console.log(this)
|
||||||
|
|
||||||
|
@ -136,6 +137,8 @@ class FormWrapper extends PureComponent {
|
||||||
queryName: `${prefix}FormQuery`,
|
queryName: `${prefix}FormQuery`,
|
||||||
collection: this.props.collection,
|
collection: this.props.collection,
|
||||||
fragment: this.getFragments().queryFragment,
|
fragment: this.getFragments().queryFragment,
|
||||||
|
fetchPolicy: 'network-only', // we always want to load a fresh copy of the document
|
||||||
|
enableCache: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// options for withNew, withEdit, and withRemove HoCs
|
// options for withNew, withEdit, and withRemove HoCs
|
||||||
|
@ -180,6 +183,16 @@ class FormWrapper extends PureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
// prevent extra re-renderings for unknown reasons
|
||||||
|
// re-render only if the document selector changes
|
||||||
|
return nextProps.slug !== this.props.slug || nextProps.documentId !== this.props.documentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.FormComponent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormWrapper.propTypes = {
|
FormWrapper.propTypes = {
|
||||||
|
|
73
packages/vulcan-forms/lib/components/bootstrap/Time.jsx
Normal file
73
packages/vulcan-forms/lib/components/bootstrap/Time.jsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import DateTimePicker from 'react-datetime';
|
||||||
|
import { registerComponent } from 'meteor/vulcan:core';
|
||||||
|
|
||||||
|
class Time extends PureComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.updateDate = this.updateDate.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when the datetime picker has mounted, SmartForm will catch the date value (no formsy mixin in this component)
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.value) {
|
||||||
|
this.context.updateCurrentValues({[this.props.name]: this.props.value});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDate(mDate) {
|
||||||
|
// if this is a properly formatted moment date, update time
|
||||||
|
if (typeof mDate === 'object') {
|
||||||
|
this.context.updateCurrentValues({[this.props.name]: mDate.format('HH:mm')});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
const date = new Date();
|
||||||
|
|
||||||
|
// transform time string into date object to work inside datetimepicker
|
||||||
|
const time = this.props.value;
|
||||||
|
if (time) {
|
||||||
|
date.setHours(parseInt(time.substr(0,2)), parseInt(time.substr(3,5)));
|
||||||
|
} else {
|
||||||
|
date.setHours(0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-group row">
|
||||||
|
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||||
|
<div className="col-sm-9">
|
||||||
|
<DateTimePicker
|
||||||
|
value={date}
|
||||||
|
viewMode="time"
|
||||||
|
dateFormat={false}
|
||||||
|
timeFormat="HH:mm"
|
||||||
|
// newDate argument is a Moment object given by react-datetime
|
||||||
|
onChange={newDate => this.updateDate(newDate)}
|
||||||
|
inputProps={{name: this.props.name}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Time.propTypes = {
|
||||||
|
control: PropTypes.any,
|
||||||
|
datatype: PropTypes.any,
|
||||||
|
group: PropTypes.any,
|
||||||
|
label: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
value: PropTypes.any,
|
||||||
|
};
|
||||||
|
|
||||||
|
Time.contextTypes = {
|
||||||
|
updateCurrentValues: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Time;
|
||||||
|
|
||||||
|
registerComponent('FormComponentTime', Time);
|
|
@ -7,6 +7,11 @@ import '../components/bootstrap/Number.jsx';
|
||||||
import '../components/bootstrap/Radiogroup.jsx';
|
import '../components/bootstrap/Radiogroup.jsx';
|
||||||
import '../components/bootstrap/Select.jsx';
|
import '../components/bootstrap/Select.jsx';
|
||||||
import '../components/bootstrap/Textarea.jsx';
|
import '../components/bootstrap/Textarea.jsx';
|
||||||
|
import '../components/bootstrap/Time.jsx';
|
||||||
import '../components/bootstrap/Url.jsx';
|
import '../components/bootstrap/Url.jsx';
|
||||||
|
|
||||||
|
import '../components/Flash.jsx';
|
||||||
|
import '../components/FormComponent.jsx';
|
||||||
|
import '../components/FormGroup.jsx';
|
||||||
|
import '../components/FormSubmit.jsx';
|
||||||
import '../components/FormWrapper.jsx';
|
import '../components/FormWrapper.jsx';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
$light-grey: #ddd;
|
$light-grey: #ddd;
|
||||||
|
$medium-grey: #bbb;
|
||||||
$vmargin: 15px;
|
$vmargin: 15px;
|
||||||
$light-border: $light-grey;
|
$light-border: $light-grey;
|
||||||
|
|
||||||
|
@ -181,4 +182,35 @@ div.ReactTags__suggestions mark{
|
||||||
li{
|
li{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-component-select, .form-component-datetime{
|
||||||
|
.col-sm-9{
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-component-clear{
|
||||||
|
position: absolute;
|
||||||
|
top: 11px;
|
||||||
|
right: 0px;
|
||||||
|
background: $light-grey;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
border: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
span{
|
||||||
|
font-size: 8px;
|
||||||
|
display: block;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
text-decoration: none;
|
||||||
|
background: $medium-grey;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:forms",
|
name: "vulcan:forms",
|
||||||
summary: "Form containers for React",
|
summary: "Form containers for React",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/meteor-utilities/react-form-containers.git"
|
git: "https://github.com/meteor-utilities/react-form-containers.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Package.onUse(function(api) {
|
||||||
api.versionsFrom("METEOR@1.3");
|
api.versionsFrom("METEOR@1.3");
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.8.0',
|
'vulcan:core@1.8.1',
|
||||||
|
|
||||||
'fourseven:scss@4.5.0'
|
'fourseven:scss@4.5.0'
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: "vulcan:i18n-en-us",
|
name: "vulcan:i18n-en-us",
|
||||||
summary: "Vulcan i18n package (en_US)",
|
summary: "Vulcan i18n package (en_US)",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan.git"
|
git: "https://github.com/VulcanJS/Vulcan.git"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Package.onUse(function (api) {
|
||||||
api.versionsFrom('METEOR@1.5.2');
|
api.versionsFrom('METEOR@1.5.2');
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:core@1.8.0'
|
'vulcan:core@1.8.1'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.addFiles([
|
api.addFiles([
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'vulcan:i18n',
|
name: 'vulcan:i18n',
|
||||||
summary: "i18n client polyfill",
|
summary: "i18n client polyfill",
|
||||||
version: '1.8.0',
|
version: '1.8.1',
|
||||||
git: "https://github.com/VulcanJS/Vulcan"
|
git: "https://github.com/VulcanJS/Vulcan"
|
||||||
});
|
});
|
||||||
|
|
||||||
Package.onUse(function (api) {
|
Package.onUse(function (api) {
|
||||||
|
|
||||||
api.use([
|
api.use([
|
||||||
'vulcan:lib@1.8.0',
|
'vulcan:lib@1.8.1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
api.mainModule('lib/server/main.js', 'server');
|
api.mainModule('lib/server/main.js', 'server');
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue