mirror of
https://github.com/strapi/strapi.git
synced 2025-12-25 14:14:10 +00:00
Update to add dynamic settings to email plugin
Email plugin now functions more like the upload plugin. Users can go to the plugins page, click on the settings cog for the email, and switch between providers or change settings. The default provider is strapi-email-sendmail. Extra providers can be added by installing strapi-email-mailgun or strapi-email-sendgrid.
This commit is contained in:
parent
cbb97f22eb
commit
ddc01eb27b
@ -32,14 +32,15 @@ class Row extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const uploadPath = `/plugins/upload/configurations/${this.context.currentEnvironment}`;
|
||||
const icons = this.props.name === 'upload' ? [
|
||||
// const uploadPath = `/plugins/upload/configurations/${this.context.currentEnvironment}`;
|
||||
const settingsPath = `/plugins/${this.props.name}/configurations/${this.context.currentEnvironment}`;
|
||||
const icons = this.props.name === 'upload' || this.props.name === 'email' ? [
|
||||
{
|
||||
icoType: 'cog',
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.context.router.history.push(uploadPath);
|
||||
this.context.router.history.push(settingsPath);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
16
packages/strapi-email-mailgun/.editorconfig
Normal file
16
packages/strapi-email-mailgun/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{package.json,*.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
95
packages/strapi-email-mailgun/.gitignore
vendored
Normal file
95
packages/strapi-email-mailgun/.gitignore
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
.idea
|
||||
nbproject
|
||||
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
.node_history
|
||||
package-lock.json
|
||||
104
packages/strapi-email-mailgun/.npmignore
Normal file
104
packages/strapi-email-mailgun/.npmignore
Normal file
@ -0,0 +1,104 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
ssl
|
||||
.editorconfig
|
||||
.gitattributes
|
||||
.idea
|
||||
nbproject
|
||||
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
.node_history
|
||||
|
||||
|
||||
############################
|
||||
# Tests
|
||||
############################
|
||||
|
||||
test
|
||||
7
packages/strapi-email-mailgun/LICENSE.md
Normal file
7
packages/strapi-email-mailgun/LICENSE.md
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2015-2018 Strapi Solutions.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
11
packages/strapi-email-mailgun/README.md
Normal file
11
packages/strapi-email-mailgun/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# strapi-email-sendmail
|
||||
|
||||
## Resources
|
||||
|
||||
- [MIT License](LICENSE.md)
|
||||
|
||||
## Links
|
||||
|
||||
- [Strapi website](http://strapi.io/)
|
||||
- [Strapi community on Slack](http://slack.strapi.io)
|
||||
- [Strapi news on Twitter](https://twitter.com/strapijs)
|
||||
70
packages/strapi-email-mailgun/lib/index.js
Normal file
70
packages/strapi-email-mailgun/lib/index.js
Normal file
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
const mailgunFactory = require('mailgun-js');
|
||||
/* eslint-disable no-unused-vars */
|
||||
module.exports = {
|
||||
provider: 'mailgun',
|
||||
name: 'Mailgun',
|
||||
auth: {
|
||||
mailgun_default_from: {
|
||||
label: 'Mailgun Default From',
|
||||
type: 'text'
|
||||
},
|
||||
mailgun_default_replyto: {
|
||||
label: 'Mailgun Default Reply-To',
|
||||
type: 'text'
|
||||
},
|
||||
mailgun_api_key: {
|
||||
label: 'Mailgun API Key',
|
||||
type: 'text'
|
||||
},
|
||||
mailgun_domain: {
|
||||
label: 'Mailgun Domain',
|
||||
type: 'text'
|
||||
}
|
||||
},
|
||||
init: (config) => {
|
||||
|
||||
const mailgun = mailgunFactory({
|
||||
apiKey: config.mailgun_api_key,
|
||||
domain: config.mailgun_domain,
|
||||
mute: false
|
||||
});
|
||||
|
||||
return {
|
||||
send: (options, cb) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Default values.
|
||||
options = _.isObject(options) ? options : {};
|
||||
options.from = options.from || config.mailgun_default_from;
|
||||
options.replyTo = options.replyTo || config.mailgun_default_replyto;
|
||||
options.text = options.text || options.html;
|
||||
options.html = options.html || options.text;
|
||||
|
||||
let msg = {
|
||||
from: options.from,
|
||||
to: options.to,
|
||||
subject: options.subject,
|
||||
text: options.text,
|
||||
html: options.html
|
||||
};
|
||||
msg['h:Reply-To'] = options.replyTo;
|
||||
|
||||
mailgun.messages().send(msg, function (err) {
|
||||
if (err) {
|
||||
reject([{ messages: [{ id: 'Auth.form.error.email.invalid' }] }]);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
45
packages/strapi-email-mailgun/package.json
Normal file
45
packages/strapi-email-mailgun/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "strapi-email-mailgun",
|
||||
"version": "3.0.0-alpha.12.2",
|
||||
"description": "Mailgun provider for strapi email plugin",
|
||||
"homepage": "http://strapi.io",
|
||||
"keywords": [
|
||||
"email",
|
||||
"strapi",
|
||||
"mailgun"
|
||||
],
|
||||
"directories": {
|
||||
"lib": "./lib"
|
||||
},
|
||||
"main": "./lib",
|
||||
"dependencies": {
|
||||
"mailgun-js": "0.18.0"
|
||||
},
|
||||
"strapi": {
|
||||
"isProvider": true
|
||||
},
|
||||
"author": {
|
||||
"email": "hi@strapi.io",
|
||||
"name": "Strapi team",
|
||||
"url": "http://strapi.io"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Strapi team",
|
||||
"email": "hi@strapi.io",
|
||||
"url": "http://strapi.io"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/strapi/strapi.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/strapi/strapi/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 9.0.0",
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
16
packages/strapi-email-sendgrid/.editorconfig
Normal file
16
packages/strapi-email-sendgrid/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{package.json,*.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
95
packages/strapi-email-sendgrid/.gitignore
vendored
Normal file
95
packages/strapi-email-sendgrid/.gitignore
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
.idea
|
||||
nbproject
|
||||
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
.node_history
|
||||
package-lock.json
|
||||
104
packages/strapi-email-sendgrid/.npmignore
Normal file
104
packages/strapi-email-sendgrid/.npmignore
Normal file
@ -0,0 +1,104 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
ssl
|
||||
.editorconfig
|
||||
.gitattributes
|
||||
.idea
|
||||
nbproject
|
||||
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
.node_history
|
||||
|
||||
|
||||
############################
|
||||
# Tests
|
||||
############################
|
||||
|
||||
test
|
||||
7
packages/strapi-email-sendgrid/LICENSE.md
Normal file
7
packages/strapi-email-sendgrid/LICENSE.md
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2015-2018 Strapi Solutions.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
11
packages/strapi-email-sendgrid/README.md
Normal file
11
packages/strapi-email-sendgrid/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# strapi-email-sendmail
|
||||
|
||||
## Resources
|
||||
|
||||
- [MIT License](LICENSE.md)
|
||||
|
||||
## Links
|
||||
|
||||
- [Strapi website](http://strapi.io/)
|
||||
- [Strapi community on Slack](http://slack.strapi.io)
|
||||
- [Strapi news on Twitter](https://twitter.com/strapijs)
|
||||
62
packages/strapi-email-sendgrid/lib/index.js
Normal file
62
packages/strapi-email-sendgrid/lib/index.js
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
const sendgrid = require('@sendgrid/mail');
|
||||
/* eslint-disable no-unused-vars */
|
||||
module.exports = {
|
||||
provider: 'sendgrid',
|
||||
name: 'Sendgrid',
|
||||
auth: {
|
||||
sendgrid_default_from: {
|
||||
label: 'Sendgrid Default From',
|
||||
type: 'text'
|
||||
},
|
||||
sendgrid_default_replyto: {
|
||||
label: 'Sendgrid Default Reply-To',
|
||||
type: 'text'
|
||||
},
|
||||
sendgrid_api_key: {
|
||||
label: 'Sendgrid API Key',
|
||||
type: 'text'
|
||||
}
|
||||
},
|
||||
init: (config) => {
|
||||
|
||||
sendgrid.setApiKey(config.sendgrid_api_key);
|
||||
|
||||
return {
|
||||
send: (options, cb) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Default values.
|
||||
options = _.isObject(options) ? options : {};
|
||||
options.from = options.from || config.sendgrid_default_from;
|
||||
options.replyTo = options.replyTo || config.sendgrid_default_replyto;
|
||||
options.text = options.text || options.html;
|
||||
options.html = options.html || options.text;
|
||||
|
||||
let msg = {
|
||||
from: options.from,
|
||||
to: options.to,
|
||||
reply_to: options.replyTo,
|
||||
subject: options.subject,
|
||||
text: options.text,
|
||||
html: options.html
|
||||
};
|
||||
|
||||
sendgrid.send(msg, function (err) {
|
||||
if (err) {
|
||||
reject([{ messages: [{ id: 'Auth.form.error.email.invalid' }] }]);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
45
packages/strapi-email-sendgrid/package.json
Normal file
45
packages/strapi-email-sendgrid/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "strapi-email-sendgrid",
|
||||
"version": "3.0.0-alpha.12.2",
|
||||
"description": "Sendgrid provider for strapi email",
|
||||
"homepage": "http://strapi.io",
|
||||
"keywords": [
|
||||
"email",
|
||||
"strapi",
|
||||
"sendgrid"
|
||||
],
|
||||
"directories": {
|
||||
"lib": "./lib"
|
||||
},
|
||||
"main": "./lib",
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "6.2.1"
|
||||
},
|
||||
"strapi": {
|
||||
"isProvider": true
|
||||
},
|
||||
"author": {
|
||||
"email": "hi@strapi.io",
|
||||
"name": "Strapi team",
|
||||
"url": "http://strapi.io"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Strapi team",
|
||||
"email": "hi@strapi.io",
|
||||
"url": "http://strapi.io"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/strapi/strapi.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/strapi/strapi/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 9.0.0",
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
16
packages/strapi-email-sendmail/.editorconfig
Normal file
16
packages/strapi-email-sendmail/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{package.json,*.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
95
packages/strapi-email-sendmail/.gitignore
vendored
Normal file
95
packages/strapi-email-sendmail/.gitignore
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
.idea
|
||||
nbproject
|
||||
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
.node_history
|
||||
package-lock.json
|
||||
104
packages/strapi-email-sendmail/.npmignore
Normal file
104
packages/strapi-email-sendmail/.npmignore
Normal file
@ -0,0 +1,104 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
ssl
|
||||
.editorconfig
|
||||
.gitattributes
|
||||
.idea
|
||||
nbproject
|
||||
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
.node_history
|
||||
|
||||
|
||||
############################
|
||||
# Tests
|
||||
############################
|
||||
|
||||
test
|
||||
7
packages/strapi-email-sendmail/LICENSE.md
Normal file
7
packages/strapi-email-sendmail/LICENSE.md
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2015-2018 Strapi Solutions.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
11
packages/strapi-email-sendmail/README.md
Normal file
11
packages/strapi-email-sendmail/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# strapi-email-sendmail
|
||||
|
||||
## Resources
|
||||
|
||||
- [MIT License](LICENSE.md)
|
||||
|
||||
## Links
|
||||
|
||||
- [Strapi website](http://strapi.io/)
|
||||
- [Strapi community on Slack](http://slack.strapi.io)
|
||||
- [Strapi news on Twitter](https://twitter.com/strapijs)
|
||||
56
packages/strapi-email-sendmail/lib/index.js
Normal file
56
packages/strapi-email-sendmail/lib/index.js
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
const sendmail = require('sendmail')({
|
||||
silent: true
|
||||
});
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
module.exports = {
|
||||
provider: 'sendmail',
|
||||
name: 'Sendmail',
|
||||
auth: {
|
||||
sendmail_default_from: {
|
||||
label: 'Sendmail Default From',
|
||||
type: 'text'
|
||||
},
|
||||
sendmail_default_replyto: {
|
||||
label: 'Sendmail Default Reply-To',
|
||||
type: 'text'
|
||||
}
|
||||
},
|
||||
init: (config) => {
|
||||
return {
|
||||
send: (options, cb) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Default values.
|
||||
options = _.isObject(options) ? options : {};
|
||||
options.from = options.from || config.sendmail_default_from;
|
||||
options.replyTo = options.replyTo || config.sendmail_default_replyto;
|
||||
options.text = options.text || options.html;
|
||||
options.html = options.html || options.text;
|
||||
|
||||
sendmail({
|
||||
from: options.from,
|
||||
to: options.to,
|
||||
replyTo: options.replyTo,
|
||||
subject: options.subject,
|
||||
text: options.text,
|
||||
html: options.html
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
reject([{ messages: [{ id: 'Auth.form.error.email.invalid' }] }]);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
44
packages/strapi-email-sendmail/package.json
Normal file
44
packages/strapi-email-sendmail/package.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "strapi-email-sendmail",
|
||||
"version": "3.0.0-alpha.12.2",
|
||||
"description": "Sendmail provider for strapi email",
|
||||
"homepage": "http://strapi.io",
|
||||
"keywords": [
|
||||
"email",
|
||||
"strapi"
|
||||
],
|
||||
"directories": {
|
||||
"lib": "./lib"
|
||||
},
|
||||
"main": "./lib",
|
||||
"dependencies": {
|
||||
"sendmail": "^1.2.0"
|
||||
},
|
||||
"strapi": {
|
||||
"isProvider": true
|
||||
},
|
||||
"author": {
|
||||
"email": "hi@strapi.io",
|
||||
"name": "Strapi team",
|
||||
"url": "http://strapi.io"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Strapi team",
|
||||
"email": "hi@strapi.io",
|
||||
"url": "http://strapi.io"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/strapi/strapi.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/strapi/strapi/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 9.0.0",
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
/**
|
||||
*
|
||||
* EditForm
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { findIndex, get, isEmpty, map } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
// You can find these components in either
|
||||
// ./node_modules/strapi-helper-plugin/lib/src
|
||||
// or strapi/packages/strapi-helper-plugin/lib/src
|
||||
import Input from 'components/InputsIndex';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class EditForm extends React.Component {
|
||||
getProviderForm = () => get(this.props.settings, ['providers', this.props.selectedProviderIndex, 'auth'], {});
|
||||
|
||||
generateSelectOptions = () => (
|
||||
Object.keys(get(this.props.settings, 'providers', {})).reduce((acc, current) => {
|
||||
const option = {
|
||||
id: get(this.props.settings, ['providers', current, 'name']),
|
||||
value: get(this.props.settings, ['providers', current, 'provider']),
|
||||
};
|
||||
acc.push(option);
|
||||
return acc;
|
||||
}, [])
|
||||
)
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.editForm}>
|
||||
<div className="row">
|
||||
<Input
|
||||
customBootstrapClass="col-md-6"
|
||||
inputDescription={{ id: 'email.EditForm.Input.select.inputDescription' }}
|
||||
inputClassName={styles.inputStyle}
|
||||
label={{ id: 'email.EditForm.Input.select.label' }}
|
||||
name="provider"
|
||||
onChange={this.props.onChange}
|
||||
selectOptions={this.generateSelectOptions()}
|
||||
type="select"
|
||||
value={get(this.props.modifiedData, 'provider')}
|
||||
/>
|
||||
</div>
|
||||
{!isEmpty(this.getProviderForm()) && (
|
||||
<div className={styles.subFormWrapper}>
|
||||
<div className="row">
|
||||
{map(this.getProviderForm(), (value, key) => (
|
||||
<Input
|
||||
didCheckErrors={this.props.didCheckErrors}
|
||||
errors={get(this.props.formErrors, [findIndex(this.props.formErrors, ['name', key]), 'errors'])}
|
||||
key={key}
|
||||
label={{ id: value.label }}
|
||||
name={key}
|
||||
onChange={this.props.onChange}
|
||||
selectOptions={value.values}
|
||||
type={value.type === 'enum' ? 'select' : value.type}
|
||||
validations={{ required: true }}
|
||||
value={get(this.props.modifiedData, key, '')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditForm.defaultProps = {
|
||||
settings: {
|
||||
providers: [],
|
||||
},
|
||||
};
|
||||
|
||||
EditForm.propTypes = {
|
||||
didCheckErrors: PropTypes.bool.isRequired,
|
||||
formErrors: PropTypes.array.isRequired,
|
||||
modifiedData: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
selectedProviderIndex: PropTypes.number.isRequired,
|
||||
settings: PropTypes.object,
|
||||
};
|
||||
|
||||
export default EditForm;
|
||||
@ -0,0 +1,24 @@
|
||||
.editForm {
|
||||
background: #ffffff;
|
||||
padding: 45px 30px 22px 30px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 4px #E3E9F3;
|
||||
}
|
||||
|
||||
.inputStyle {
|
||||
max-width: 358px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
margin-bottom: 22px;
|
||||
background: #F6F6F6;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subFormWrapper {
|
||||
margin-bottom: 14px;
|
||||
padding: 23px 30px 0 30px;
|
||||
background-color: #FAFAFB;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
*
|
||||
* EntriesNumber
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function EntriesNumber({ number }) {
|
||||
const id = number > 1 ? 'number.plural' : 'number';
|
||||
|
||||
return (
|
||||
<div className={styles.entriesNumberContainer}>
|
||||
<FormattedMessage id={`upload.EntriesNumber.${id}`} values={{ number }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EntriesNumber.defaultProps = {
|
||||
number: 0,
|
||||
};
|
||||
|
||||
EntriesNumber.propTypes = {
|
||||
number: PropTypes.number,
|
||||
};
|
||||
|
||||
export default EntriesNumber;
|
||||
@ -0,0 +1,4 @@
|
||||
.entriesNumberContainer {
|
||||
color: #787E8F;
|
||||
font-size: 13px;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
{
|
||||
"archive": ["rar", "zip"],
|
||||
"code": ["js", "json", "rb", "erb", "txt", "css", "scss", "html", "jsx", "svg"],
|
||||
"img": ["jpg", "jpeg", "png", "gif", "ico"],
|
||||
"pdf": ["pdf"],
|
||||
"powerpoint": ["ppt", "key", "xls"],
|
||||
"video": ["mov", "avi", "mpg", "mp4", "m4v"],
|
||||
"word": ["doc", "pages"]
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* FileIcon
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { trim } from 'lodash';
|
||||
|
||||
import ext from './extensions.json';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function FileIcon({ fileType }) {
|
||||
const iconType = (() => {
|
||||
switch (true) {
|
||||
case ext.archive.includes(trim(fileType, '.')):
|
||||
return 'file-archive-o';
|
||||
case ext.code.includes(trim(fileType, '.')):
|
||||
return 'file-code-o';
|
||||
case ext.img.includes(trim(fileType, '.')):
|
||||
return 'file-image-o';
|
||||
case ext.pdf.includes(trim(fileType, '.')):
|
||||
return 'file-pdf-o';
|
||||
case ext.powerpoint.includes(trim(fileType, '.')):
|
||||
return 'file-powerpoint-o';
|
||||
case ext.video.includes(trim(fileType, '.')):
|
||||
return 'file-video-o';
|
||||
case ext.word.includes(trim(fileType, '.')):
|
||||
return 'file-word-o';
|
||||
default:
|
||||
return 'file';
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={(cn(
|
||||
styles.fileIconContainer,
|
||||
iconType === 'file-pdf-o' && styles.pdf,
|
||||
iconType === 'file-archive-o' && styles.zip,
|
||||
iconType === 'file-image-o' && styles.image,
|
||||
iconType === 'file-video-o' && styles.video,
|
||||
iconType === 'file-code-o' && styles.code,
|
||||
))}
|
||||
>
|
||||
<i className={`fa fa-${iconType}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
FileIcon.defaultProps = {
|
||||
fileType: 'zip',
|
||||
};
|
||||
|
||||
FileIcon.propTypes = {
|
||||
fileType: PropTypes.string,
|
||||
};
|
||||
|
||||
export default FileIcon;
|
||||
@ -0,0 +1,24 @@
|
||||
.fileIconContainer {
|
||||
font-size: 20px;
|
||||
color: #BDBFC2;
|
||||
}
|
||||
|
||||
.image {
|
||||
color: #8AA066;
|
||||
}
|
||||
|
||||
.pdf {
|
||||
color: #E26D6D;
|
||||
}
|
||||
|
||||
.video {
|
||||
color: #77C69E;
|
||||
}
|
||||
|
||||
.zip {
|
||||
color: #715A31;
|
||||
}
|
||||
|
||||
.code {
|
||||
color: #515A6D;
|
||||
}
|
||||
167
packages/strapi-plugin-email/admin/src/components/Li/index.js
Normal file
167
packages/strapi-plugin-email/admin/src/components/Li/index.js
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
*
|
||||
* Li
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import cn from 'classnames';
|
||||
import moment from 'moment';
|
||||
|
||||
import FileIcon from 'components/FileIcon';
|
||||
import IcoContainer from 'components/IcoContainer';
|
||||
import PopUpWarning from 'components/PopUpWarning';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
/* eslint-disable react/no-string-refs */
|
||||
class Li extends React.Component {
|
||||
state = { isOpen: false, copied: false };
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.copied !== this.state.copied && this.state.copied) {
|
||||
setTimeout(() => {
|
||||
this.setState({ copied: false });
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
getUnit = (value) => {
|
||||
let unit;
|
||||
let divider;
|
||||
|
||||
switch (true) {
|
||||
case value > 10000:
|
||||
unit = 'GB';
|
||||
divider = 1000;
|
||||
break;
|
||||
case value < 1:
|
||||
unit = 'B';
|
||||
divider = 1;
|
||||
break;
|
||||
case value > 1000:
|
||||
unit = 'MB';
|
||||
divider = 1000;
|
||||
break;
|
||||
default:
|
||||
unit = 'KB';
|
||||
divider = 1;
|
||||
}
|
||||
|
||||
return { divider, unit };
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
const aTag = document.getElementById('aTag');
|
||||
aTag.click();
|
||||
}
|
||||
|
||||
handleDelete = (e) => {
|
||||
e.preventDefault();
|
||||
this.context.deleteData(this.props.item);
|
||||
}
|
||||
|
||||
renderLiCopied = () => (
|
||||
<li className={cn(styles.liWrapper, styles.copied)}>
|
||||
<div>
|
||||
<div className={styles.checked}>
|
||||
<div />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage id="upload.Li.linkCopied" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
|
||||
if (this.state.copied) {
|
||||
return this.renderLiCopied();
|
||||
}
|
||||
|
||||
const icons = [
|
||||
// {
|
||||
// icoType: item.private ? 'lock' : 'unlock',
|
||||
// onClick: () => {},
|
||||
// },
|
||||
{
|
||||
icoType: 'eye',
|
||||
onClick: this.handleClick,
|
||||
},
|
||||
{
|
||||
icoType: 'trash',
|
||||
onClick: () => this.setState({ isOpen: true }),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<CopyToClipboard text={item.url} onCopy={() => this.setState({copied: true})}>
|
||||
<li className={styles.liWrapper}>
|
||||
<a href={item.url} target="_blank" style={{ display: 'none' }} id="aTag">nothing</a>
|
||||
<div className={styles.liContainer}>
|
||||
<div>
|
||||
<div />
|
||||
<FileIcon fileType={item.ext} />
|
||||
</div>
|
||||
{['hash', 'name', 'updatedAt', 'size', 'relatedTo', ''].map((value, key) => {
|
||||
if (value === 'updatedAt') {
|
||||
return (
|
||||
<div key={key} className={styles.truncate}>{moment(item[value]).format('YYYY/MM/DD - HH:mm')}</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (value === 'size') {
|
||||
const { divider, unit } = this.getUnit(item[value]);
|
||||
const size = item[value]/divider;
|
||||
|
||||
return (
|
||||
<div key={key} className={styles.truncate}>{Math.round(size * 100) / 100 } {unit}</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (value !== '') {
|
||||
return (
|
||||
<div key={key} className={styles.truncate}>{item[value]}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <IcoContainer key={key} icons={icons} />;
|
||||
})}
|
||||
</div>
|
||||
<PopUpWarning
|
||||
isOpen={this.state.isOpen}
|
||||
onConfirm={this.handleDelete}
|
||||
toggleModal={() => this.setState({ isOpen: false })}
|
||||
/>
|
||||
</li>
|
||||
</CopyToClipboard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Li.contextTypes = {
|
||||
deleteData: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Li.defaultProps = {
|
||||
item: {
|
||||
type: 'pdf',
|
||||
hash: '1234',
|
||||
name: 'avatar.pdf',
|
||||
updated: '20/11/2017 19:29:54',
|
||||
size: '24 B',
|
||||
relatedTo: 'John Doe',
|
||||
},
|
||||
};
|
||||
|
||||
Li.propTypes = {
|
||||
item: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Li;
|
||||
108
packages/strapi-plugin-email/admin/src/components/Li/styles.scss
Normal file
108
packages/strapi-plugin-email/admin/src/components/Li/styles.scss
Normal file
@ -0,0 +1,108 @@
|
||||
.liWrapper {
|
||||
height: 54px;
|
||||
background-color: #fff;
|
||||
padding-top: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.liContainer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
line-height: 48px;
|
||||
border-bottom: 1px solid rgba(14,22,34,0.04);
|
||||
justify-content: space-between;
|
||||
|
||||
> div:first-child {
|
||||
display: flex;
|
||||
width: 133px;
|
||||
> div:first-child {
|
||||
width: 51px;
|
||||
}
|
||||
> div:last-child {
|
||||
width: 82px;
|
||||
}
|
||||
}
|
||||
|
||||
> div:nth-child(2) {
|
||||
width: calc(100% - 696px);
|
||||
padding-right: 20px;
|
||||
}
|
||||
> div:nth-child(3) {
|
||||
width: calc(100% - 596px);
|
||||
}
|
||||
> div:nth-child(4) {
|
||||
width: 184px;
|
||||
flex-shrink: 0;
|
||||
> span {
|
||||
&:after {
|
||||
content: '\f0d8';
|
||||
margin-left: 10px;
|
||||
font-family: 'FontAwesome';
|
||||
}
|
||||
}
|
||||
}
|
||||
> div:nth-child(5) {
|
||||
flex-shrink: 0;
|
||||
width: 100px;
|
||||
}
|
||||
> div:nth-child(6) {
|
||||
width: 147px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
> div:nth-child(7) {
|
||||
width: 116px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.copied {
|
||||
background-color: #FAFAFB;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding-top: 1px;
|
||||
text-align: center;
|
||||
color: #868FA1;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 54px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .05rem;
|
||||
}
|
||||
}
|
||||
|
||||
.checked {
|
||||
padding-top: 20px;
|
||||
line-height: 54px;
|
||||
position: relative;
|
||||
> div {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
margin-right: 10px;
|
||||
background-color: #2DD210;
|
||||
border: 1px solid rgba(16,22,34,0.10);
|
||||
border-radius: 3px;
|
||||
&:after {
|
||||
content: '\f00c';
|
||||
position: absolute;
|
||||
top: 0; left: 2px;
|
||||
font-size: 10px;
|
||||
font-family: 'FontAwesome';
|
||||
font-weight: 100;
|
||||
color: #fff;
|
||||
transition: all .2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
/**
|
||||
*
|
||||
* List
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import cn from 'classnames';
|
||||
|
||||
import Li from 'components/Li';
|
||||
import ListHeader from 'components/ListHeader';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const EmptyLi = () => (
|
||||
<li className={styles.emptyLiWrapper}>
|
||||
<div>
|
||||
<FormattedMessage id="upload.EmptyLi.message" />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
function List(props) {
|
||||
return (
|
||||
<div className={cn('container-fluid', styles.listWrapper)}>
|
||||
<div className="row">
|
||||
<ul className={styles.ulList}>
|
||||
<ListHeader changeSort={props.changeSort} sort={props.sort} />
|
||||
{props.data.map((item, key) => (
|
||||
<Li
|
||||
key={item.hash || key}
|
||||
item={item}
|
||||
/>
|
||||
))}
|
||||
{props.data.length === 0 && <EmptyLi />}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
List.defaultProps = {
|
||||
sort: 'id',
|
||||
};
|
||||
|
||||
List.propTypes = {
|
||||
changeSort: PropTypes.func.isRequired,
|
||||
data: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sort: PropTypes.string,
|
||||
};
|
||||
|
||||
export default List;
|
||||
@ -0,0 +1,48 @@
|
||||
.listWrapper {
|
||||
margin-top: 9px;
|
||||
padding: 0;
|
||||
|
||||
> div:first-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ulList {
|
||||
width: 100%;
|
||||
padding: 0 !important;
|
||||
list-style: none;
|
||||
> li:nth-child(2) {
|
||||
height: 57px;
|
||||
> div {
|
||||
line-height: 54px !important;
|
||||
}
|
||||
}
|
||||
> li {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> li:last-child {
|
||||
> div {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
box-shadow: 0 2px 4px #E3E9F3;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyLiWrapper {
|
||||
height: 54px;
|
||||
background-color: #fff;
|
||||
padding-top: 5px;
|
||||
cursor: pointer;
|
||||
> div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding-top: 1px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 54px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
/**
|
||||
*
|
||||
* ListHeader
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import cn from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// import InputCheckBox from 'components/InputCheckbox';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function ListHeader({ changeSort, sort }) {
|
||||
const titles = [
|
||||
'hash',
|
||||
'name',
|
||||
'updated',
|
||||
'size',
|
||||
// 'related',
|
||||
'',
|
||||
'',
|
||||
];
|
||||
|
||||
const handleChangeSort = (name) => {
|
||||
if (sort === name) {
|
||||
changeSort(`-${name}`);
|
||||
} else if (sort === `-${name}`) {
|
||||
changeSort('hash');
|
||||
} else if (name === 'updated' || name === 'related') {
|
||||
changeSort('hash');
|
||||
} else {
|
||||
changeSort(name);
|
||||
}
|
||||
};
|
||||
|
||||
const shouldDisplaySort = (title) => sort === title && styles.icon || sort === `-${title}` && styles.iconDesc || '';
|
||||
|
||||
return (
|
||||
<li className={styles.listheaderWrapper}>
|
||||
<div className={cn(styles.listHeader)}>
|
||||
<div>
|
||||
<div />
|
||||
<div className={shouldDisplaySort('type')} onClick={() => handleChangeSort('type')}>
|
||||
<FormattedMessage id="upload.ListHeader.type" />
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
{titles.map((title, key) => {
|
||||
if (title !== '') {
|
||||
return (
|
||||
<div key={key} className={shouldDisplaySort(title)} onClick={() => handleChangeSort(title)}>
|
||||
<FormattedMessage id={`upload.ListHeader.${title}`} />
|
||||
<span />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div key={key} />;
|
||||
})}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
ListHeader.defaultProps = {
|
||||
changeSort: () => {},
|
||||
};
|
||||
|
||||
ListHeader.propTypes = {
|
||||
changeSort: PropTypes.func,
|
||||
sort: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ListHeader;
|
||||
@ -0,0 +1,72 @@
|
||||
.listheaderWrapper {
|
||||
height: 30px;
|
||||
background-color: #F3F3F4;
|
||||
color: #333740;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.listHeader {
|
||||
display: flex;
|
||||
margin-left: 32px;
|
||||
margin-right: 32px;
|
||||
justify-content: space-between;
|
||||
span {
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
> div:first-child {
|
||||
display: flex;
|
||||
width: 133px;
|
||||
> div:first-child {
|
||||
width: 51px;
|
||||
}
|
||||
> div:last-child {
|
||||
width: 82px;
|
||||
}
|
||||
}
|
||||
> div:nth-child(2) {
|
||||
width: calc(100% - 496px - 221px);
|
||||
}
|
||||
> div:nth-child(3) {
|
||||
width: calc(100% - 496px - 148px);
|
||||
}
|
||||
> div:nth-child(4) {
|
||||
width: 184px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
> div:nth-child(5) {
|
||||
width: 100px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
> div:nth-child(6) {
|
||||
width: 147px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
> div:nth-child(7) {
|
||||
width: 116px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
> span:last-child {
|
||||
&:after {
|
||||
content: '\f0d8';
|
||||
margin-left: 10px;
|
||||
font-family: 'FontAwesome';
|
||||
}
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.iconDesc {
|
||||
> span:last-child {
|
||||
&:after {
|
||||
content: '\f0d7';
|
||||
margin-left: 10px;
|
||||
font-family: 'FontAwesome';
|
||||
}
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
/**
|
||||
*
|
||||
* PluginInputFile
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import cn from 'classnames';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
/* eslint-disable react/no-string-refs */
|
||||
/* eslint-disable jsx-a11y/label-has-for */
|
||||
/* eslint-disable react/jsx-tag-spacing */
|
||||
class PluginInputFile extends React.PureComponent {
|
||||
state = { isDraging: false };
|
||||
|
||||
handleChange = (e) => {
|
||||
const dataTransfer = e.target;
|
||||
this.props.onDrop({ dataTransfer });
|
||||
}
|
||||
|
||||
handleDragEnter = () => this.setState({ isDraging: true });
|
||||
|
||||
handleDragLeave = () => this.setState({ isDraging: false });
|
||||
|
||||
handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ isDraging: false });
|
||||
this.props.onDrop(e);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
showLoader,
|
||||
} = this.props;
|
||||
const { isDraging } = this.state;
|
||||
const link = (
|
||||
<FormattedMessage id="upload.PluginInputFile.link">
|
||||
{(message) => <span className={styles.underline}>{message}</span>}
|
||||
</FormattedMessage>
|
||||
);
|
||||
|
||||
return (
|
||||
<label
|
||||
className={cn(styles.pluginInputFile, isDraging && styles.pluginInputFileHover, showLoader && styles.quadrat)}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onDrop={this.handleDrop}
|
||||
>
|
||||
<svg className={styles.icon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104.40317 83.13328"><g><rect x="5.02914" y="8.63138" width="77.33334" height="62.29167" rx="4" ry="4" transform="translate(-7.45722 9.32921) rotate(-12)" fill="#fafafb"/><rect x="5.52914" y="9.13138" width="76.33334" height="61.29167" rx="4" ry="4" transform="translate(-7.45722 9.32921) rotate(-12)" fill="none" stroke="#979797"/><path d="M74.25543,36.05041l3.94166,18.54405L20.81242,66.79194l-1.68928-7.94745,10.2265-16.01791,7.92872,5.2368,16.3624-25.62865ZM71.974,6.07811,6.76414,19.93889a1.27175,1.27175,0,0,0-.83343.58815,1.31145,1.31145,0,0,0-.18922,1.01364L16.44028,71.87453a1.31145,1.31145,0,0,0,.58515.849,1.27176,1.27176,0,0,0,1.0006.19831L83.23586,59.06111a1.27177,1.27177,0,0,0,.83343-.58815,1.31146,1.31146,0,0,0,.18922-1.01364L73.55972,7.12547a1.31146,1.31146,0,0,0-.58514-.849A1.27177,1.27177,0,0,0,71.974,6.07811Zm6.80253-.0615L89.4753,56.35046A6.5712,6.5712,0,0,1,88.554,61.435a6.37055,6.37055,0,0,1-4.19192,2.92439L19.15221,78.22019a6.37056,6.37056,0,0,1-5.019-.96655,6.57121,6.57121,0,0,1-2.90975-4.27024L.5247,22.64955A6.57121,6.57121,0,0,1,1.446,17.565a6.37056,6.37056,0,0,1,4.19192-2.92439L70.84779.77981a6.37055,6.37055,0,0,1,5.019.96655A6.5712,6.5712,0,0,1,78.77651,6.01661Z" transform="translate(-0.14193 -0.62489)" fill="#333740"/><rect x="26.56627" y="4.48824" width="62.29167" height="77.33333" rx="4" ry="4" transform="translate(0.94874 87.10632) rotate(-75)" fill="#fafafb"/><rect x="27.06627" y="4.98824" width="61.29167" height="76.33333" rx="4" ry="4" transform="translate(0.94874 87.10632) rotate(-75)" fill="none" stroke="#979797"/><path d="M49.62583,26.96884A7.89786,7.89786,0,0,1,45.88245,31.924a7.96,7.96,0,0,1-10.94716-2.93328,7.89786,7.89786,0,0,1-.76427-6.163,7.89787,7.89787,0,0,1,3.74338-4.95519,7.96,7.96,0,0,1,10.94716,2.93328A7.89787,7.89787,0,0,1,49.62583,26.96884Zm37.007,26.73924L81.72608,72.02042,25.05843,56.83637l2.1029-7.84815L43.54519,39.3589l4.68708,8.26558L74.44644,32.21756ZM98.20721,25.96681,33.81216,8.71221a1.27175,1.27175,0,0,0-1.00961.14568,1.31145,1.31145,0,0,0-.62878.81726L18.85537,59.38007a1.31145,1.31145,0,0,0,.13591,1.02215,1.27176,1.27176,0,0,0,.80151.631l64.39506,17.2546a1.27177,1.27177,0,0,0,1.0096-.14567,1.31146,1.31146,0,0,0,.62877-.81726l13.3184-49.70493a1.31146,1.31146,0,0,0-.13591-1.02215A1.27177,1.27177,0,0,0,98.20721,25.96681Zm6.089,3.03348L90.97784,78.70523a6.5712,6.5712,0,0,1-3.12925,4.1121,6.37055,6.37055,0,0,1-5.06267.70256L18.39086,66.26529a6.37056,6.37056,0,0,1-4.03313-3.13977,6.57121,6.57121,0,0,1-.654-5.12581L27.02217,8.29477a6.57121,6.57121,0,0,1,3.12925-4.11211,6.37056,6.37056,0,0,1,5.06267-.70255l64.39506,17.2546a6.37055,6.37055,0,0,1,4.03312,3.13977A6.5712,6.5712,0,0,1,104.29623,29.0003Z" transform="translate(-0.14193 -0.62489)" fill="#333740"/></g></svg>
|
||||
<p className={styles.textWrapper}>
|
||||
{!showLoader && <FormattedMessage id="upload.PluginInputFile.text" values={{ link }} /> }
|
||||
{showLoader && <FormattedMessage id="upload.PluginInputFile.loading" />}
|
||||
</p>
|
||||
<div
|
||||
onDragLeave={this.handleDragLeave}
|
||||
className={cn(isDraging && styles.isDraging)}
|
||||
/>
|
||||
<input
|
||||
multiple
|
||||
name={name}
|
||||
onChange={this.handleChange}
|
||||
type="file"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PluginInputFile.defaultProps = {};
|
||||
|
||||
PluginInputFile.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
onDrop: PropTypes.func.isRequired,
|
||||
showLoader: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default PluginInputFile;
|
||||
@ -0,0 +1,102 @@
|
||||
.isDraging {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.pluginInputFile {
|
||||
position: relative;
|
||||
height: 146px;
|
||||
width: 100%;
|
||||
padding-top: 28px;
|
||||
border: 2px dashed #E3E9F3;
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
|
||||
> input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon{
|
||||
width: 82px;
|
||||
path{
|
||||
fill: #CCD0DA;
|
||||
transition: fill .3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.textWrapper {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #9EA7B8;
|
||||
|
||||
u {
|
||||
color: #1C5DE7;
|
||||
}
|
||||
}
|
||||
|
||||
.pluginInputFileHover {
|
||||
background-color: rgba(28,93,231,0.01) !important;
|
||||
border: 2px dashed rgba(28,93,231,0.10) !important;
|
||||
}
|
||||
|
||||
.underline {
|
||||
color: #1C5DE7;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@mixin smoothBlink($firstColor, $secondColor) {
|
||||
@-webkit-keyframes blink {
|
||||
0% {
|
||||
fill: $firstColor;
|
||||
background-color: $firstColor;
|
||||
}
|
||||
26% {
|
||||
fill: $secondColor;
|
||||
background-color: $secondColor;
|
||||
}
|
||||
76% {
|
||||
fill: $firstColor;
|
||||
background-color: $firstColor;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
fill: $firstColor;
|
||||
background-color: $firstColor;
|
||||
}
|
||||
26% {
|
||||
fill: $secondColor;
|
||||
background-color: $secondColor;
|
||||
}
|
||||
76% {
|
||||
fill: $firstColor;
|
||||
background-color: $firstColor;
|
||||
}
|
||||
}
|
||||
|
||||
-webkit-animation: blink 2s linear infinite;
|
||||
-moz-animation: blink 2s linear infinite;
|
||||
-o-animation: blink 2s linear infinite;
|
||||
animation: blink 2s linear infinite;
|
||||
}
|
||||
|
||||
.quadrat {
|
||||
.icon{
|
||||
path {
|
||||
fill: #729BEF;
|
||||
}
|
||||
}
|
||||
|
||||
@include smoothBlink(transparent, rgba(28, 93, 231, 0.05));
|
||||
}
|
||||
31
packages/strapi-plugin-email/admin/src/containers/App/index.js
Executable file
31
packages/strapi-plugin-email/admin/src/containers/App/index.js
Executable file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
*
|
||||
* This component is the skeleton around the actual pages, and should only
|
||||
* contain code that should be seen on all pages. (e.g. navigation bar)
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
|
||||
// Utils
|
||||
import { pluginId } from 'app';
|
||||
|
||||
// Containers
|
||||
import ConfigPage from 'containers/ConfigPage';
|
||||
import NotFoundPage from 'containers/NotFoundPage';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className={pluginId}>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}/configurations/:env`} component={ConfigPage} exact />
|
||||
<Route path={`/plugins/${pluginId}/configurations/`} component={ConfigPage} exact />
|
||||
<Route path={`/plugins/${pluginId}`} component={ConfigPage} exact />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
18
packages/strapi-plugin-email/admin/src/containers/App/reducer.js
Executable file
18
packages/strapi-plugin-email/admin/src/containers/App/reducer.js
Executable file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
*
|
||||
* App reducer
|
||||
*
|
||||
*/
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
const initialState = fromJS({});
|
||||
|
||||
function appReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default appReducer;
|
||||
9
packages/strapi-plugin-email/admin/src/containers/App/selectors.js
Executable file
9
packages/strapi-plugin-email/admin/src/containers/App/selectors.js
Executable file
@ -0,0 +1,9 @@
|
||||
// import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Direct selector to the list state domain
|
||||
*/
|
||||
|
||||
// const selectGlobalDomain = () => state => state.get('global');
|
||||
|
||||
export {};
|
||||
@ -0,0 +1,77 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* ConfigPage actions
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
GET_SETTINGS,
|
||||
GET_SETTINGS_SUCCEEDED,
|
||||
ON_CANCEL,
|
||||
ON_CHANGE,
|
||||
SET_ERRORS,
|
||||
SUBMIT,
|
||||
SUBMIT_ERROR,
|
||||
SUBMIT_SUCCEEDED,
|
||||
} from './constants';
|
||||
|
||||
export function getSettings(env) {
|
||||
return {
|
||||
type: GET_SETTINGS,
|
||||
env,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSettingsSucceeded(settings, appEnvironments) {
|
||||
return {
|
||||
type: GET_SETTINGS_SUCCEEDED,
|
||||
appEnvironments,
|
||||
settings,
|
||||
initialData: settings.config,
|
||||
};
|
||||
}
|
||||
|
||||
export function onCancel() {
|
||||
return {
|
||||
type: ON_CANCEL,
|
||||
};
|
||||
}
|
||||
|
||||
export function onChange({ target }) {
|
||||
const keys = ['modifiedData'].concat(target.name.split('.'));
|
||||
const value = target.name === 'sizeLimit' ? parseInt(target.value, 10) * 1000 : target.value;
|
||||
|
||||
return {
|
||||
type: ON_CHANGE,
|
||||
keys,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function setErrors(errors) {
|
||||
return {
|
||||
type: SET_ERRORS,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
export function submit() {
|
||||
return {
|
||||
type: SUBMIT,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitError(errors) {
|
||||
return {
|
||||
type: SUBMIT_ERROR,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitSucceeded(data) {
|
||||
return {
|
||||
type: SUBMIT_SUCCEEDED,
|
||||
data,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
/**
|
||||
*
|
||||
* ConfigPage constants
|
||||
*
|
||||
*/
|
||||
|
||||
export const GET_ENV = 'Email/ConfigPage/GET_ENV';
|
||||
export const GET_ENV_SUCCEEDED = 'Email/ConfigPage/GET_ENV_SUCCEEDED';
|
||||
export const GET_SETTINGS = 'Email/ConfigPage/GET_SETTINGS';
|
||||
export const GET_SETTINGS_SUCCEEDED = 'Email/ConfigPage/GET_SETTINGS_SUCCEEDED';
|
||||
export const ON_CANCEL = 'Email/ConfigPage/ON_CANCEL';
|
||||
export const ON_CHANGE = 'Email/ConfigPage/ON_CHANGE';
|
||||
export const SET_ERRORS = 'Email/ConfigPage/SET_ERRORS';
|
||||
export const SUBMIT = 'Email/ConfigPage/SUBMIT';
|
||||
export const SUBMIT_ERROR = 'Email/ConfigPage/SUBMIT_ERROR';
|
||||
export const SUBMIT_SUCCEEDED = 'Email/ConfigPage/SUBMIT_SUCCEEDED';
|
||||
@ -0,0 +1,190 @@
|
||||
/**
|
||||
*
|
||||
* ConfigPage
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { findIndex, get, isEmpty } from 'lodash';
|
||||
|
||||
// You can find these components in either
|
||||
// ./node_modules/strapi-helper-plugin/lib/src
|
||||
// or strapi/packages/strapi-helper-plugin/lib/src
|
||||
import ContainerFluid from 'components/ContainerFluid';
|
||||
import HeaderNav from 'components/HeaderNav';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
|
||||
// Plugin's components
|
||||
import EditForm from 'components/EditForm';
|
||||
|
||||
// You can find these utils in either
|
||||
// ./node_modules/strapi-helper-plugin/lib/src
|
||||
// or strapi/packages/strapi-helper-plugin/lib/src
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
|
||||
import {
|
||||
getSettings,
|
||||
onCancel,
|
||||
onChange,
|
||||
setErrors,
|
||||
submit,
|
||||
} from './actions';
|
||||
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
import selectConfigPage from './selectors';
|
||||
|
||||
class ConfigPage extends React.Component {
|
||||
componentDidMount() {
|
||||
this.getSettings(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Get new settings on navigation change
|
||||
if (nextProps.match.params.env !== this.props.match.params.env) {
|
||||
this.getSettings(nextProps);
|
||||
}
|
||||
|
||||
// Redirect the user to the email list after modifying is provider
|
||||
if (nextProps.submitSuccess !== this.props.submitSuccess) {
|
||||
this.props.history.push(`/plugins/email/configurations/${this.props.match.params.env}`);
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedProviderIndex = () => findIndex(this.props.settings.providers, ['provider', get(this.props.modifiedData, 'provider')]);
|
||||
|
||||
/**
|
||||
* Get Settings depending on the props
|
||||
* @param {Object} props
|
||||
* @return {Func} calls the saga that gets the current settings
|
||||
*/
|
||||
getSettings = (props) => {
|
||||
const { match: { params: { env} } } = props;
|
||||
this.props.getSettings(env);
|
||||
}
|
||||
|
||||
generateLinks = () => {
|
||||
const headerNavLinks = this.props.appEnvironments.reduce((acc, current) => {
|
||||
const link = Object.assign(current, { to: `/plugins/email/configurations/${current.name}` });
|
||||
acc.push(link);
|
||||
return acc;
|
||||
}, []).sort(link => link.name === 'production');
|
||||
|
||||
return headerNavLinks;
|
||||
}
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const formErrors = Object.keys(get(this.props.settings, ['providers', this.getSelectedProviderIndex(), 'auth'], {})).reduce((acc, current) => {
|
||||
if (isEmpty(get(this.props.modifiedData, current, ''))) {
|
||||
acc.push({
|
||||
name: current,
|
||||
errors: [{ id: 'components.Input.error.validation.required' }],
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (!isEmpty(formErrors)) {
|
||||
return this.props.setErrors(formErrors);
|
||||
}
|
||||
|
||||
return this.props.submit();
|
||||
}
|
||||
|
||||
pluginHeaderActions = [
|
||||
{
|
||||
kind: 'secondary',
|
||||
label: 'app.components.Button.cancel',
|
||||
onClick: this.props.onCancel,
|
||||
type: 'button',
|
||||
},
|
||||
{
|
||||
kind: 'primary',
|
||||
label: 'app.components.Button.save',
|
||||
onClick: this.handleSubmit,
|
||||
type: 'submit',
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<ContainerFluid>
|
||||
<PluginHeader
|
||||
actions={this.pluginHeaderActions}
|
||||
description={{ id: 'email.ConfigPage.description' }}
|
||||
title={{ id: 'email.ConfigPage.title'}}
|
||||
/>
|
||||
<HeaderNav links={this.generateLinks()} />
|
||||
<EditForm
|
||||
didCheckErrors={this.props.didCheckErrors}
|
||||
formErrors={this.props.formErrors}
|
||||
modifiedData={this.props.modifiedData}
|
||||
onChange={this.props.onChange}
|
||||
selectedProviderIndex={this.getSelectedProviderIndex()}
|
||||
settings={this.props.settings}
|
||||
/>
|
||||
</ContainerFluid>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigPage.contextTypes = {};
|
||||
|
||||
ConfigPage.defaultProps = {
|
||||
appEnvironments: [],
|
||||
formErrors: [],
|
||||
settings: {
|
||||
providers: [],
|
||||
},
|
||||
};
|
||||
|
||||
ConfigPage.propTypes = {
|
||||
appEnvironments: PropTypes.array,
|
||||
didCheckErrors: PropTypes.bool.isRequired,
|
||||
formErrors: PropTypes.array,
|
||||
getSettings: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
modifiedData: PropTypes.object.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
setErrors: PropTypes.func.isRequired,
|
||||
settings: PropTypes.object,
|
||||
submit: PropTypes.func.isRequired,
|
||||
submitSuccess: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
getSettings,
|
||||
onCancel,
|
||||
onChange,
|
||||
setErrors,
|
||||
submit,
|
||||
},
|
||||
dispatch,
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = selectConfigPage();
|
||||
|
||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
const withReducer = injectReducer({ key: 'configPage', reducer });
|
||||
const withSaga = injectSaga({ key: 'configPage', saga });
|
||||
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect,
|
||||
)(ConfigPage);
|
||||
@ -0,0 +1,67 @@
|
||||
/**
|
||||
*
|
||||
* ConfigPage reducer
|
||||
*
|
||||
*/
|
||||
|
||||
import { fromJS, List, Map } from 'immutable';
|
||||
|
||||
import {
|
||||
GET_SETTINGS,
|
||||
GET_SETTINGS_SUCCEEDED,
|
||||
ON_CANCEL,
|
||||
ON_CHANGE,
|
||||
SET_ERRORS,
|
||||
SUBMIT_ERROR,
|
||||
SUBMIT_SUCCEEDED,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
appEnvironments: List([]),
|
||||
didCheckErrors: false,
|
||||
env: '',
|
||||
formErrors: List([]),
|
||||
initialData: Map({}),
|
||||
modifiedData: Map({}),
|
||||
settings: {},
|
||||
submitSuccess: false,
|
||||
});
|
||||
|
||||
function configPageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GET_SETTINGS:
|
||||
return state.update('env', () => action.env);
|
||||
case GET_SETTINGS_SUCCEEDED:
|
||||
return state
|
||||
.update('appEnvironments', () => List(action.appEnvironments))
|
||||
.update('didCheckErrors', (v) => v = !v)
|
||||
.update('formErrors', () => List([]))
|
||||
.update('initialData', () => Map(action.initialData))
|
||||
.update('modifiedData', () => Map(action.initialData))
|
||||
.update('settings', () => action.settings);
|
||||
case ON_CANCEL:
|
||||
return state
|
||||
.update('didCheckErrors', (v) => v = !v)
|
||||
.update('formErrors', () => List([]))
|
||||
.update('modifiedData', () => state.get('initialData'));
|
||||
case ON_CHANGE:
|
||||
return state
|
||||
.updateIn(action.keys, () => action.value);
|
||||
case SET_ERRORS:
|
||||
case SUBMIT_ERROR:
|
||||
return state
|
||||
.update('didCheckErrors', (v) => v = !v)
|
||||
.update('formErrors', () => List(action.errors));
|
||||
case SUBMIT_SUCCEEDED:
|
||||
return state
|
||||
.update('didCheckErrors', (v) => v = !v)
|
||||
.update('formErrors', () => List([]))
|
||||
.update('initialData', () => Map(action.data))
|
||||
.update('modifiedData', () => Map(action.data))
|
||||
.update('submitSuccess', (v) => v = !v);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default configPageReducer;
|
||||
@ -0,0 +1,61 @@
|
||||
// import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import { call, fork, put, select, takeLatest } from 'redux-saga/effects';
|
||||
import request from 'utils/request';
|
||||
|
||||
import {
|
||||
getSettingsSucceeded,
|
||||
submitSucceeded,
|
||||
} from './actions';
|
||||
import {
|
||||
GET_SETTINGS,
|
||||
SUBMIT,
|
||||
} from './constants';
|
||||
import {
|
||||
makeSelectEnv,
|
||||
makeSelectModifiedData,
|
||||
} from './selectors';
|
||||
|
||||
export function* settingsGet(action) {
|
||||
try {
|
||||
const requestURL = `/email/settings/${action.env}`;
|
||||
const response = yield [
|
||||
call(request, requestURL, { method: 'GET' }),
|
||||
call(request, '/email/environments', { method: 'GET' }),
|
||||
];
|
||||
|
||||
yield put(getSettingsSucceeded(response[0], response[1].environments));
|
||||
} catch(err) {
|
||||
strapi.notification.error('notification.error');
|
||||
}
|
||||
}
|
||||
|
||||
export function* submit() {
|
||||
try {
|
||||
const env = yield select(makeSelectEnv());
|
||||
let body = yield select(makeSelectModifiedData());
|
||||
|
||||
if (body.provider === 'local') {
|
||||
body = {
|
||||
enabled: body.enabled,
|
||||
provider: 'local',
|
||||
sizeLimit: body.sizeLimit,
|
||||
};
|
||||
}
|
||||
const requestURL = `/email/settings/${env}`;
|
||||
yield call(request, requestURL, { method: 'PUT', body });
|
||||
|
||||
// Update reducer with optimisticResponse
|
||||
strapi.notification.success('email.notification.config.success');
|
||||
yield put(submitSucceeded(body));
|
||||
} catch(err) {
|
||||
strapi.notification.error('notification.error');
|
||||
// TODO handle error PUT
|
||||
}
|
||||
}
|
||||
|
||||
function* defaultSaga() {
|
||||
yield fork(takeLatest, GET_SETTINGS, settingsGet);
|
||||
yield fork(takeLatest, SUBMIT, submit);
|
||||
}
|
||||
|
||||
export default defaultSaga;
|
||||
@ -0,0 +1,31 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Direct selector to the configPage state domain
|
||||
*/
|
||||
const selectConfigPageDomain = () => state => state.get('configPage');
|
||||
|
||||
/**
|
||||
* Default selector used by ConfigPage
|
||||
*/
|
||||
|
||||
const selectConfigPage = () => createSelector(
|
||||
selectConfigPageDomain(),
|
||||
(substate) => substate.toJS(),
|
||||
);
|
||||
|
||||
const makeSelectEnv = () => createSelector(
|
||||
selectConfigPageDomain(),
|
||||
(substate) => substate.get('env'),
|
||||
);
|
||||
|
||||
const makeSelectModifiedData = () => createSelector(
|
||||
selectConfigPageDomain(),
|
||||
(substate) => substate.get('modifiedData').toJS(),
|
||||
);
|
||||
|
||||
export default selectConfigPage;
|
||||
export {
|
||||
makeSelectEnv,
|
||||
makeSelectModifiedData,
|
||||
};
|
||||
20
packages/strapi-plugin-email/admin/src/containers/NotFoundPage/index.js
Executable file
20
packages/strapi-plugin-email/admin/src/containers/NotFoundPage/index.js
Executable file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* NotFoundPage
|
||||
*
|
||||
* This is the page we show when the user visits a url that doesn't have a route
|
||||
*
|
||||
* NOTE: while this component should technically be a stateless functional
|
||||
* component (SFC), hot reloading does not currently support SFCs. If hot
|
||||
* reloading is not a neccessity for you then you can refactor it and remove
|
||||
* the linting exception.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import NotFound from 'components/NotFound';
|
||||
|
||||
export default class NotFoundPage extends React.Component {
|
||||
render() {
|
||||
return <NotFound {...this.props} />;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,16 @@
|
||||
{
|
||||
"ConfigPage.title": "Email - Settings",
|
||||
"ConfigPage.description": "Configure the email plugin",
|
||||
|
||||
|
||||
"EditForm.Input.number.label": "Maximum size allowed (in MB)",
|
||||
"EditForm.Input.select.label": "Providers",
|
||||
"EditForm.Input.select.inputDescription": "Emails can be sent with the default provider (Sendmail) or an external provider",
|
||||
"EditForm.Input.toggle.label": "Enable email send",
|
||||
|
||||
"plugin.description.short": "Send emails.",
|
||||
"plugin.description.long": "Send emails."
|
||||
"plugin.description.long": "Send emails.",
|
||||
|
||||
|
||||
"notification.config.success": "The settings has been updated"
|
||||
}
|
||||
|
||||
68
packages/strapi-plugin-email/config/functions/bootstrap.js
vendored
Normal file
68
packages/strapi-plugin-email/config/functions/bootstrap.js
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* An asynchronous bootstrap function that runs before
|
||||
* your application gets started.
|
||||
*
|
||||
* This gives you an opportunity to set up your data model,
|
||||
* run jobs, or perform some special logic.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = async cb => {
|
||||
// set plugin store
|
||||
const pluginStore = strapi.store({
|
||||
environment: strapi.config.environment,
|
||||
type: 'plugin',
|
||||
name: 'email'
|
||||
});
|
||||
|
||||
strapi.plugins.email.config.providers = [];
|
||||
|
||||
const loadProviders = (basePath, cb) => {
|
||||
fs.readdir(path.join(basePath, 'node_modules'), async (err, node_modules) => {
|
||||
// get all email providers
|
||||
const emails = _.filter(node_modules, (node_module) => {
|
||||
return _.startsWith(node_module, ('strapi-email'));
|
||||
});
|
||||
|
||||
// mount all providers to get configs
|
||||
_.forEach(emails, (node_module) => {
|
||||
strapi.plugins.email.config.providers.push(
|
||||
require(path.join(`${basePath}/node_modules/${node_module}`))
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
// if provider config not exist set one by default
|
||||
const config = await pluginStore.get({key: 'provider'});
|
||||
|
||||
if (!config) {
|
||||
const provider = _.find(strapi.plugins.email.config.providers, {provider: 'sendmail'});
|
||||
|
||||
const value = _.assign({}, provider, {
|
||||
// TODO: set other default settings here
|
||||
});
|
||||
|
||||
await pluginStore.set({key: 'provider', value});
|
||||
}
|
||||
} catch (err) {
|
||||
strapi.log.error(`Can't load ${config.provider} email provider.`);
|
||||
strapi.log.warn(`Please install strapi-email-${config.provider} --save in ${path.join(strapi.config.appPath, 'plugins', 'email')} folder.`);
|
||||
strapi.stop();
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
// Load providers from the plugins' node_modules.
|
||||
loadProviders(path.join(strapi.config.appPath, 'plugins', 'email'), () => {
|
||||
// Load providers from the root node_modules.
|
||||
loadProviders(path.join(strapi.config.appPath), cb);
|
||||
});
|
||||
|
||||
};
|
||||
134
packages/strapi-plugin-email/config/queries/bookshelf.js
Normal file
134
packages/strapi-plugin-email/config/queries/bookshelf.js
Normal file
@ -0,0 +1,134 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
find: async function (params = {}, populate) {
|
||||
const records = await this.query(function(qb) {
|
||||
_.forEach(params.where, (where, key) => {
|
||||
qb.where(key, where[0].symbol, where[0].value);
|
||||
});
|
||||
|
||||
if (params.sort) {
|
||||
qb.orderBy(params.sort.key, params.sort.order);
|
||||
}
|
||||
|
||||
if (params.start) {
|
||||
qb.offset(params.start);
|
||||
}
|
||||
|
||||
if (params.limit) {
|
||||
qb.limit(params.limit);
|
||||
}
|
||||
}).fetchAll({
|
||||
withRelated: populate || _.keys(_.groupBy(_.reject(this.associations, { autoPopulate: false }), 'alias'))
|
||||
});
|
||||
|
||||
return records ? records.toJSON() : records;
|
||||
},
|
||||
|
||||
count: async function (params = {}) {
|
||||
return await this
|
||||
.where(params)
|
||||
.count();
|
||||
},
|
||||
|
||||
findOne: async function (params, populate) {
|
||||
const primaryKey = params[this.primaryKey] || params.id;
|
||||
|
||||
if (primaryKey) {
|
||||
params = {
|
||||
[this.primaryKey]: primaryKey
|
||||
};
|
||||
}
|
||||
|
||||
const record = await this
|
||||
.forge(params)
|
||||
.fetch({
|
||||
withRelated: populate || this.associations.map(x => x.alias)
|
||||
});
|
||||
|
||||
return record ? record.toJSON() : record;
|
||||
},
|
||||
|
||||
create: async function (params) {
|
||||
return this
|
||||
.forge()
|
||||
.save(Object.keys(params).reduce((acc, current) => {
|
||||
if (_.get(this._attributes, [current, 'type']) || _.get(this._attributes, [current, 'model'])) {
|
||||
acc[current] = params[current];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {}))
|
||||
.catch((err) => {
|
||||
if (err.detail) {
|
||||
const field = _.last(_.words(err.detail.split('=')[0]));
|
||||
err = { message: `This ${field} is already taken`, field };
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
update: async function (search, params = {}) {
|
||||
if (_.isEmpty(params)) {
|
||||
params = search;
|
||||
}
|
||||
|
||||
const primaryKey = search[this.primaryKey] || search.id;
|
||||
|
||||
if (primaryKey) {
|
||||
search = {
|
||||
[this.primaryKey]: primaryKey
|
||||
};
|
||||
} else {
|
||||
const entry = await module.exports.findOne.call(this, search);
|
||||
|
||||
search = {
|
||||
[this.primaryKey]: entry[this.primaryKey] || entry.id
|
||||
};
|
||||
}
|
||||
|
||||
return this.forge(search)
|
||||
.save(params, {
|
||||
patch: true
|
||||
})
|
||||
.catch((err) => {
|
||||
const field = _.last(_.words(err.detail.split('=')[0]));
|
||||
const error = { message: `This ${field} is already taken`, field };
|
||||
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
|
||||
delete: async function (params) {
|
||||
return await this
|
||||
.forge({
|
||||
[this.primaryKey]: params[this.primaryKey] || params.id
|
||||
})
|
||||
.destroy();
|
||||
},
|
||||
|
||||
search: async function (params) {
|
||||
return this
|
||||
.query(function(qb) {
|
||||
qb
|
||||
.whereRaw(`LOWER(hash) LIKE ?`, [`%${params.id}%`])
|
||||
.orWhereRaw(`LOWER(name) LIKE ?`, [`%${params.id}%`]);
|
||||
})
|
||||
.fetchAll();
|
||||
},
|
||||
|
||||
addPermission: async function (params) {
|
||||
return this
|
||||
.forge(params)
|
||||
.save();
|
||||
},
|
||||
|
||||
removePermission: async function (params) {
|
||||
return this
|
||||
.forge({
|
||||
[this.primaryKey]: params[this.primaryKey] || params.id
|
||||
})
|
||||
.destroy();
|
||||
}
|
||||
};
|
||||
111
packages/strapi-plugin-email/config/queries/mongoose.js
Normal file
111
packages/strapi-plugin-email/config/queries/mongoose.js
Normal file
@ -0,0 +1,111 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
find: async function (params = {}, populate) {
|
||||
return this
|
||||
.find(params.where)
|
||||
.limit(Number(params.limit))
|
||||
.sort(params.sort)
|
||||
.skip(Number(params.start))
|
||||
.populate(populate || this.associations.map(x => x.alias).join(' '))
|
||||
.lean();
|
||||
},
|
||||
|
||||
count: async function (params = {}) {
|
||||
return Number(await this
|
||||
.count(params));
|
||||
},
|
||||
|
||||
findOne: async function (params, populate) {
|
||||
const primaryKey = params[this.primaryKey] || params.id;
|
||||
|
||||
if (primaryKey) {
|
||||
params = {
|
||||
[this.primaryKey]: primaryKey
|
||||
};
|
||||
}
|
||||
|
||||
return this
|
||||
.findOne(params)
|
||||
.populate(populate || this.associations.map(x => x.alias).join(' '))
|
||||
.lean();
|
||||
},
|
||||
|
||||
create: async function (params) {
|
||||
// Exclude relationships.
|
||||
const values = Object.keys(params).reduce((acc, current) => {
|
||||
if (_.get(this._attributes, [current, 'type']) || _.get(this._attributes, [current, 'model'])) {
|
||||
acc[current] = params[current];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return this.create(values)
|
||||
.catch((err) => {
|
||||
if (err.message.indexOf('index:') !== -1) {
|
||||
const message = err.message.split('index:');
|
||||
const field = _.words(_.last(message).split('_')[0]);
|
||||
const error = { message: `This ${field} is already taken`, field };
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
update: async function (search, params = {}) {
|
||||
if (_.isEmpty(params)) {
|
||||
params = search;
|
||||
}
|
||||
|
||||
const primaryKey = search[this.primaryKey] || search.id;
|
||||
|
||||
if (primaryKey) {
|
||||
search = {
|
||||
[this.primaryKey]: primaryKey
|
||||
};
|
||||
}
|
||||
|
||||
return this.update(search, params, {
|
||||
strict: false
|
||||
})
|
||||
.catch((error) => {
|
||||
const field = _.last(_.words(error.message.split('_')[0]));
|
||||
const err = { message: `This ${field} is already taken`, field };
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
delete: async function (params) {
|
||||
// Delete entry.
|
||||
return this
|
||||
.remove({
|
||||
[this.primaryKey]: params[this.primaryKey] || params.id
|
||||
});
|
||||
},
|
||||
|
||||
search: async function (params) {
|
||||
const re = new RegExp(params.id, 'i');
|
||||
|
||||
return this
|
||||
.find({
|
||||
'$or': [
|
||||
{ hash: re },
|
||||
{ name: re }
|
||||
]
|
||||
});
|
||||
},
|
||||
|
||||
addPermission: async function (params) {
|
||||
return this
|
||||
.create(params);
|
||||
},
|
||||
|
||||
removePermission: async function (params) {
|
||||
return this
|
||||
.remove(params);
|
||||
}
|
||||
};
|
||||
@ -1,3 +1,36 @@
|
||||
{
|
||||
"routes": []
|
||||
"routes": [
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/",
|
||||
"handler": "Email.send",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/environments",
|
||||
"handler": "Email.getEnvironments",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/settings/:environment",
|
||||
"handler": "Email.getSettings",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/settings/:environment",
|
||||
"handler": "Email.updateSettings",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"EMAIL_METHOD": "",
|
||||
"MAILGUN_API_KEY": "",
|
||||
"MAILGUN_DOMAIN": "",
|
||||
"SENDGRID_API_KEY": ""
|
||||
}
|
||||
68
packages/strapi-plugin-email/controllers/Email.js
Normal file
68
packages/strapi-plugin-email/controllers/Email.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Email.js controller
|
||||
*
|
||||
* @description: A set of functions called "actions" of the `email` plugin.
|
||||
*/
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
send: async (ctx) => {
|
||||
// Retrieve provider configuration.
|
||||
const config = await strapi.store({
|
||||
environment: strapi.config.environment,
|
||||
type: 'plugin',
|
||||
name: 'email'
|
||||
}).get({ key: 'provider' });
|
||||
|
||||
// Verify if the file email is enable.
|
||||
if (config.enabled === false) {
|
||||
strapi.log.error('Email is disabled');
|
||||
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Email.status.disabled' }] }] : 'Emailis disabled');
|
||||
}
|
||||
|
||||
// Something is wrong
|
||||
if (ctx.status === 400) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = ctx.request.body;
|
||||
|
||||
await strapi.plugins.email.services.send(options, config);
|
||||
|
||||
// Send 200 `ok`
|
||||
ctx.send({});
|
||||
},
|
||||
|
||||
getEnvironments: async (ctx) => {
|
||||
const environments = _.map(_.keys(strapi.config.environments), environment => {
|
||||
return {
|
||||
name: environment,
|
||||
active: (strapi.config.environment === environment)
|
||||
};
|
||||
});
|
||||
|
||||
ctx.send({ environments });
|
||||
},
|
||||
|
||||
getSettings: async (ctx) => {
|
||||
let config = await strapi.plugins.email.services.email.getProviderConfig(ctx.params.environment);
|
||||
|
||||
ctx.send({
|
||||
providers: strapi.plugins.email.config.providers,
|
||||
config
|
||||
});
|
||||
},
|
||||
|
||||
updateSettings: async (ctx) => {
|
||||
await strapi.store({
|
||||
environment: ctx.params.environment,
|
||||
type: 'plugin',
|
||||
name: 'email'
|
||||
}).set({key: 'provider', value: ctx.request.body});
|
||||
|
||||
ctx.send({ok: true});
|
||||
},
|
||||
};
|
||||
@ -21,11 +21,10 @@
|
||||
"test": "echo \"Error: no test specified\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "6.2.1",
|
||||
"mailgun-js": "0.18.0",
|
||||
"sendmail": "^1.2.0"
|
||||
"strapi-email-sendmail": "3.0.0-alpha.12.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-copy-to-clipboard": "5.0.1",
|
||||
"strapi-helper-plugin": "3.0.0-alpha.12.2"
|
||||
},
|
||||
"author": {
|
||||
|
||||
@ -7,68 +7,56 @@
|
||||
*/
|
||||
|
||||
const _ = require('lodash');
|
||||
const config = require('../config/settings.json');
|
||||
|
||||
let mailer;
|
||||
|
||||
if(config.EMAIL_METHOD === 'mailgun') {
|
||||
const mailgun = require('mailgun-js')({
|
||||
apiKey: config.MAILGUN_API_KEY,
|
||||
domain: config.MAILGUN_DOMAIN,
|
||||
mute: false
|
||||
const createDefaultEnvConfig = async (env) => {
|
||||
const pluginStore = strapi.store({
|
||||
environment: env,
|
||||
type: 'plugin',
|
||||
name: 'email'
|
||||
});
|
||||
|
||||
mailer = (msg, mailerCallback) => {
|
||||
// change reply to format for Mailgun
|
||||
msg['h:Reply-To'] = msg.replyTo;
|
||||
mailgun.messages().send(msg, mailerCallback);
|
||||
};
|
||||
}
|
||||
else if(config.EMAIL_METHOD === 'sendgrid') {
|
||||
const sendgrid = require('@sendgrid/mail');
|
||||
sendgrid.setApiKey(config.SENDGRID_API_KEY);
|
||||
const provider = _.find(strapi.plugins.email.config.providers, {provider: 'sendmail'});
|
||||
const value = _.assign({}, provider, {});
|
||||
|
||||
await pluginStore.set({key: 'provider', value});
|
||||
return await strapi.store({
|
||||
environment: env,
|
||||
type: 'plugin',
|
||||
name: 'email'
|
||||
}).get({key: 'provider'});
|
||||
};
|
||||
|
||||
mailer = (msg, mailerCallback) => {
|
||||
// change capitalization for SendGrid
|
||||
msg.reply_to = msg.replyTo;
|
||||
sendgrid.send(msg, mailerCallback);
|
||||
};
|
||||
}
|
||||
else {
|
||||
// Fallback to default email method
|
||||
const sendmail = require('sendmail')({
|
||||
silent: true
|
||||
});
|
||||
const getProviderConfig = async (env) => {
|
||||
let config = await strapi.store({
|
||||
environment: env,
|
||||
type: 'plugin',
|
||||
name: 'email'
|
||||
}).get({key: 'provider'});
|
||||
|
||||
mailer = (msg, mailerCallback) => {
|
||||
sendmail(msg, mailerCallback);
|
||||
};
|
||||
}
|
||||
if(!config) {
|
||||
config = await createDefaultEnvConfig(env);
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
send: (options, cb) => { // eslint-disable-line no-unused-vars
|
||||
return new Promise((resolve, reject) => {
|
||||
// Default values.
|
||||
options = _.isObject(options) ? options : {};
|
||||
options.from = options.from || '"Administration Panel" <no-reply@strapi.io>';
|
||||
options.replyTo = options.replyTo || '"Administration Panel" <no-reply@strapi.io>';
|
||||
options.text = options.text || options.html;
|
||||
options.html = options.html || options.text;
|
||||
getProviderConfig,
|
||||
send: async (options, config, cb) => {
|
||||
// Get email provider settings to configure the provider to use.
|
||||
if(!config) {
|
||||
config = await getProviderConfig(strapi.config.environment);
|
||||
}
|
||||
|
||||
mailer({
|
||||
from: options.from,
|
||||
to: options.to,
|
||||
replyTo: options.replyTo,
|
||||
subject: options.subject,
|
||||
text: options.text,
|
||||
html: options.html
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
reject([{ messages: [{ id: 'Auth.form.error.email.invalid' }] }]);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
const provider = _.find(strapi.plugins.email.config.providers, { provider: config.provider });
|
||||
|
||||
if (!provider) {
|
||||
throw new Error(`The provider package isn't installed. Please run \`npm install strapi-email-${config.provider}\``);
|
||||
}
|
||||
|
||||
const actions = provider.init(config);
|
||||
|
||||
// Execute email function of the provider for all files.
|
||||
return actions.send(options, cb);
|
||||
}
|
||||
};
|
||||
|
||||
@ -24,6 +24,7 @@ module.exports = function() {
|
||||
'./node_modules/strapi-helper-*',
|
||||
'./node_modules/strapi-middleware-*',
|
||||
'./node_modules/strapi-upload-*',
|
||||
'./node_modules/strapi-email-*',
|
||||
'./node_modules/strapi-lint'
|
||||
]
|
||||
}, (err, files) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user