mirror of
https://github.com/axllent/mailpit.git
synced 2026-03-05 10:37:01 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f05b97947 | ||
|
|
8bdd0cc635 | ||
|
|
a372e8150e | ||
|
|
2bc2660ad5 | ||
|
|
5d6aa7c48a | ||
|
|
997e041042 | ||
|
|
5c362c1430 | ||
|
|
9219b2d411 | ||
|
|
86abc7ea68 | ||
|
|
867dbf41d5 | ||
|
|
51e458ad57 | ||
|
|
d29a7d6218 | ||
|
|
f6a8de3215 | ||
|
|
4e2e59ec87 | ||
|
|
6aeebb9824 | ||
|
|
a426f64795 | ||
|
|
b228c9477e | ||
|
|
d70f2fd196 | ||
|
|
0da89d91dd | ||
|
|
edab9e1b6b | ||
|
|
66aead387e | ||
|
|
efe1ac732e | ||
|
|
33dcd489eb | ||
|
|
6b2e5b2e41 | ||
|
|
812c9b99d1 | ||
|
|
8202c94a43 | ||
|
|
c1d4a73440 | ||
|
|
8e100ff21b | ||
|
|
088b772de5 | ||
|
|
faf8bd4a08 |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -2,6 +2,47 @@
|
||||
|
||||
Notable changes to Mailpit will be documented in this file.
|
||||
|
||||
## 1.1.5
|
||||
|
||||
### Build
|
||||
- Switch to esbuild-sass-plugin
|
||||
|
||||
### UI
|
||||
- Support for inline images using filenames instead of cid
|
||||
|
||||
|
||||
## 1.1.4
|
||||
|
||||
### Feature
|
||||
- Add --quiet flag to display only errors
|
||||
|
||||
### Security
|
||||
- Add restrictive HTTP Content-Security-Policy
|
||||
|
||||
### UI
|
||||
- Minor UI color change & unread count position adjustment
|
||||
- Add favicon unread message counter
|
||||
- Remove left & right borders (message list)
|
||||
|
||||
|
||||
## 1.1.3
|
||||
|
||||
### Fix
|
||||
- Update message download link
|
||||
|
||||
|
||||
## 1.1.2
|
||||
|
||||
### UI
|
||||
- Allow reverse proxy subdirectories
|
||||
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### UI
|
||||
- Attachment icons and image thumbnails
|
||||
|
||||
|
||||
## 1.1.0
|
||||
|
||||
### UI
|
||||
|
||||
15
README.md
15
README.md
@@ -19,7 +19,8 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster.
|
||||
|
||||
- Runs entirely from a single binary, no installation required
|
||||
- SMTP server (default `0.0.0.0:1025`)
|
||||
- Web UI to view emails (HTML format, text, source and MIME attachments, default `0.0.0.0:8025`)
|
||||
- Web UI to view emails (formatted HTML, highlighted HTML source, text, raw source and MIME attachments including image thumbnails)
|
||||
- Advanced mail search ([see wiki](https://github.com/axllent/mailpit/wiki/Mail-search))
|
||||
- Real-time web UI updates using web sockets for new mail
|
||||
- Optional browser notifications for new mail (HTTPS only)
|
||||
- Configurable automatic email pruning (default keeps the most recent 500 emails)
|
||||
@@ -34,10 +35,18 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster.
|
||||
|
||||
## Installation
|
||||
|
||||
Download a pre-built binary in the [releases](https://github.com/axllent/mailpit/releases/latest). The `mailpit` can be placed in your `$PATH`, or simply run as `./mailpit`. See `mailpit -h` for options, or see [the wiki](https://github.com/axllent/mailpit/wiki/Runtime-options) for additional information.
|
||||
Linux & Mac users can install it directly to `/usr/local/bin/mailpit` with:
|
||||
|
||||
```bash
|
||||
sudo bash < <(curl -sL https://raw.githubusercontent.com/axllent/mailpit/develop/install.sh)
|
||||
```
|
||||
|
||||
Or download a pre-built binary in the [releases](https://github.com/axllent/mailpit/releases/latest). The `mailpit` can be placed in your `$PATH`, or simply run as `./mailpit`. See `mailpit -h` for options, or see [the wiki](https://github.com/axllent/mailpit/wiki/Runtime-options) for additional information.
|
||||
|
||||
To build Mailpit from source see [building from source](https://github.com/axllent/mailpit/wiki/Building-from-source).
|
||||
|
||||
The Mailpit web UI listens by default on `http://0.0.0.0:8025`, and the SMTP port on `0.0.0.0:1025`.
|
||||
|
||||
|
||||
### Configuring sendmail
|
||||
|
||||
@@ -59,6 +68,6 @@ You can build a Mailpit-specific sendmail binary from source (see [building from
|
||||
|
||||
I had been using MailHog for a few years to intercept and test emails generated from several projects. MailHog has a number of severe performance issues, many of the modules are horribly out of date, and other than a few accepted MRs, it is not actively developed.
|
||||
|
||||
Initially I started trying to upgrade a fork of MailHog (both the UI as well as the HTTP server & API), but soon discovered that it is (with all due respect) very poorly designed. It is over-engineered (split over 9 separate projects), has too many unnecessary features for my purpose, and performs exceptionally poorly when dealing with large lumbers of emails or processing any email with an attachment (a single email with a 3MB attachment can take over a minute). The API transmits a lot of duplicate and unnecessary data on every message request for all web calls, and there is no HTTP compression.
|
||||
Initially I started trying to upgrade a fork of MailHog (both the UI as well as the HTTP server & API), but soon discovered that it is (with all due respect) very poorly designed. It is over-engineered (split over 9 separate projects) and has too many unnecessary features for my purpose. It performs exceptionally poorly when dealing with large amounts of emails or processing any email with an attachment (a single email with a 3MB attachment can take over a minute to ingest). The API also transmits a lot of duplicate and unnecessary data on every message request for all web calls, and there is no HTTP compression.
|
||||
|
||||
In order to improve it I felt it needed to be completely rewritten, and so Mailpit was born.
|
||||
|
||||
@@ -136,6 +136,7 @@ func init() {
|
||||
rootCmd.Flags().StringVar(&config.SMTPSSLCert, "smtp-ssl-cert", config.SMTPSSLCert, "SSL certificate for SMTP - requires smtp-ssl-key")
|
||||
rootCmd.Flags().StringVar(&config.SMTPSSLKey, "smtp-ssl-key", config.SMTPSSLKey, "SSL key for SMTP - requires smtp-ssl-cert")
|
||||
|
||||
rootCmd.Flags().BoolVarP(&config.QuietLogging, "quiet", "q", false, "Quiet logging (errors only)")
|
||||
rootCmd.Flags().BoolVarP(&config.VerboseLogging, "verbose", "v", false, "Verbose logging")
|
||||
|
||||
// deprecated 2022/08/06
|
||||
|
||||
@@ -26,6 +26,9 @@ var (
|
||||
// VerboseLogging for console output
|
||||
VerboseLogging = false
|
||||
|
||||
// QuietLogging for console output (errors only)
|
||||
QuietLogging = false
|
||||
|
||||
// NoLogging for tests
|
||||
NoLogging = false
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { build } = require('esbuild')
|
||||
const pluginVue = require('esbuild-plugin-vue-next')
|
||||
const sassPlugin = require("esbuild-plugin-sass");
|
||||
const { sassPlugin } = require('esbuild-sass-plugin');
|
||||
|
||||
const doWatch = process.env.WATCH == 'true' ? true : false;
|
||||
const doMinify = process.env.MINIFY == 'true' ? true : false;
|
||||
|
||||
2
go.mod
2
go.mod
@@ -5,6 +5,7 @@ go 1.18
|
||||
require (
|
||||
github.com/GuiaBolso/darwin v0.0.0-20191218124601-fd6d2aa3d244
|
||||
github.com/axllent/semver v0.0.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jhillyerd/enmime v0.10.0
|
||||
@@ -44,6 +45,7 @@ require (
|
||||
github.com/stretchr/testify v1.7.2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -33,6 +33,8 @@ github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKX
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
|
||||
@@ -136,6 +138,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
|
||||
98
install.sh
Normal file
98
install.sh
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
GH_REPO="axllent/mailpit"
|
||||
TIMEOUT=90
|
||||
|
||||
set -e
|
||||
|
||||
VERSION=$(curl --silent --location --max-time "${TIMEOUT}" "https://api.github.com/repos/${GH_REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -ne "\nThere was an error trying to check what is the latest version of ssbak.\nPlease try again later.\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# detect the platform
|
||||
OS="$(uname)"
|
||||
case $OS in
|
||||
Linux)
|
||||
OS='linux'
|
||||
;;
|
||||
FreeBSD)
|
||||
OS='freebsd'
|
||||
echo 'OS not supported'
|
||||
exit 2
|
||||
;;
|
||||
NetBSD)
|
||||
OS='netbsd'
|
||||
echo 'OS not supported'
|
||||
exit 2
|
||||
;;
|
||||
OpenBSD)
|
||||
OS='openbsd'
|
||||
echo 'OS not supported'
|
||||
exit 2
|
||||
;;
|
||||
Darwin)
|
||||
OS='darwin'
|
||||
;;
|
||||
SunOS)
|
||||
OS='solaris'
|
||||
echo 'OS not supported'
|
||||
exit 2
|
||||
;;
|
||||
*)
|
||||
echo 'OS not supported'
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
# detect the arch
|
||||
OS_type="$(uname -m)"
|
||||
case "$OS_type" in
|
||||
x86_64 | amd64)
|
||||
OS_type='amd64'
|
||||
;;
|
||||
i?86 | x86)
|
||||
OS_type='386'
|
||||
;;
|
||||
aarch64 | arm64)
|
||||
OS_type='arm64'
|
||||
;;
|
||||
*)
|
||||
echo 'OS type not supported'
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
GH_REPO_BIN="mailpit-${OS}-${OS_type}.tar.gz"
|
||||
|
||||
#create tmp directory and move to it with macOS compatibility fallback
|
||||
tmp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mailpit-install.XXXXXXXXXX')
|
||||
cd "$tmp_dir"
|
||||
|
||||
echo "Downloading Mailpit $VERSION"
|
||||
LINK="https://github.com/${GH_REPO}/releases/download/${VERSION}/${GH_REPO_BIN}"
|
||||
|
||||
curl --silent --location --max-time "${TIMEOUT}" "${LINK}" | tar zxf - || {
|
||||
echo "Error downloading"
|
||||
exit 2
|
||||
}
|
||||
|
||||
mkdir -p /usr/local/bin || exit 2
|
||||
cp mailpit /usr/local/bin/ || exit 2
|
||||
chmod 755 /usr/local/bin/mailpit || exit 2
|
||||
case "$OS" in
|
||||
'linux')
|
||||
chown root:root /usr/local/bin/mailpit || exit 2
|
||||
;;
|
||||
'freebsd' | 'openbsd' | 'netbsd' | 'darwin')
|
||||
chown root:wheel /usr/local/bin/mailpit || exit 2
|
||||
;;
|
||||
*)
|
||||
echo 'OS not supported'
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -rf "$tmp_dir"
|
||||
echo "Installed successfully to /usr/local/bin/mailpit"
|
||||
@@ -19,9 +19,13 @@ func Log() *logrus.Logger {
|
||||
log = logrus.New()
|
||||
log.SetLevel(logrus.InfoLevel)
|
||||
if config.VerboseLogging {
|
||||
// verbose logging (debug)
|
||||
log.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
if config.NoLogging {
|
||||
} else if config.QuietLogging {
|
||||
// show errors only
|
||||
log.SetLevel(logrus.ErrorLevel)
|
||||
} else if config.NoLogging {
|
||||
// disable all logging (tests)
|
||||
log.SetLevel(logrus.PanicLevel)
|
||||
}
|
||||
|
||||
|
||||
528
package-lock.json
generated
528
package-lock.json
generated
@@ -13,14 +13,15 @@
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"moment": "^2.29.4",
|
||||
"prismjs": "^1.29.0",
|
||||
"tinycon": "^0.6.8",
|
||||
"vue": "^3.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"@vue/compiler-sfc": "^3.2.37",
|
||||
"esbuild": "^0.14.50",
|
||||
"esbuild-plugin-sass": "^1.0.1",
|
||||
"esbuild-plugin-vue-next": "^0.1.4"
|
||||
"esbuild-plugin-vue-next": "^0.1.4",
|
||||
"esbuild-sass-plugin": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
@@ -188,12 +189,6 @@
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -226,16 +221,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.9.1.tgz",
|
||||
"integrity": "sha512-d4ZkO30MIkAhQ2nNRJqKXJVEQorALGbLWTuRxyCTJF96lRIV6imcgMehWGJUiJMJhglN0o2tqLIeDnMdiQEE9g=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
@@ -286,12 +271,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
|
||||
@@ -301,19 +280,6 @@
|
||||
"safe-buffer": "~5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||
"integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.14",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "2.6.20",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
|
||||
@@ -619,21 +585,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-plugin-sass": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-plugin-sass/-/esbuild-plugin-sass-1.0.1.tgz",
|
||||
"integrity": "sha512-YFxjzD9Z1vz92QCJcAmCO15WVCUiOobw9ypdVeMsW+xa6S+zqryLUIh8d3fe/UkRHRO5PODZz/3xDAQuEXZwmQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"css-tree": "1.1.3",
|
||||
"fs-extra": "10.0.0",
|
||||
"sass": "1.47.0",
|
||||
"tmp": "0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild": ">=0.11.14"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-plugin-vue-next": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-plugin-vue-next/-/esbuild-plugin-vue-next-0.1.4.tgz",
|
||||
@@ -647,6 +598,34 @@
|
||||
"@vue/compiler-sfc": ">=3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sass-plugin": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.3.2.tgz",
|
||||
"integrity": "sha512-bNIV241S0vpy+F9U9oMbmlAD+GDzKLJp2+Z9rSRP8Rq8Nwmxh9roI0s3iB9d6Eii1A5WYgCK7HZeWPokw2rhSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.14.13",
|
||||
"resolve": "^1.22.1",
|
||||
"sass": "^1.49.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sass-plugin/node_modules/sass": {
|
||||
"version": "1.54.9",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.9.tgz",
|
||||
"integrity": "sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
|
||||
@@ -760,26 +739,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
|
||||
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
@@ -794,25 +753,11 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
@@ -826,11 +771,17 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.10",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
|
||||
"dev": true
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hash-sum": {
|
||||
"version": "2.0.0",
|
||||
@@ -844,22 +795,6 @@
|
||||
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -872,6 +807,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
|
||||
"integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -902,18 +849,6 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||
@@ -922,12 +857,6 @@
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@@ -947,18 +876,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
@@ -987,23 +904,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
@@ -1065,19 +970,21 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
"is-core-module": "^2.9.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
@@ -1086,23 +993,6 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.47.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.47.0.tgz",
|
||||
"integrity": "sha512-GtXwvwgD7/6MLUZPnlA5/8cdRgC9SzT5kAnnJMRmEZQFRE3J56Foswig4NyyyQGsnmNvg6EUM/FP0Pe9Y2zywQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -1124,18 +1014,23 @@
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
|
||||
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"rimraf": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.17.0"
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tinycon": {
|
||||
"version": "0.6.8",
|
||||
"resolved": "https://registry.npmjs.org/tinycon/-/tinycon-0.6.8.tgz",
|
||||
"integrity": "sha512-bF8Lxm4JUXF6Cw0XlZdugJ44GV575OinZ0Pt8vQPr8ooNqd2yyNkoFdCHzmdpHlgoqfSLfcyk4HDP1EyllT+ug=="
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -1148,15 +1043,6 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.38.tgz",
|
||||
@@ -1168,12 +1054,6 @@
|
||||
"@vue/server-renderer": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -1317,12 +1197,6 @@
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -1340,16 +1214,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.9.1.tgz",
|
||||
"integrity": "sha512-d4ZkO30MIkAhQ2nNRJqKXJVEQorALGbLWTuRxyCTJF96lRIV6imcgMehWGJUiJMJhglN0o2tqLIeDnMdiQEE9g=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
@@ -1383,12 +1247,6 @@
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"convert-source-map": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
|
||||
@@ -1398,16 +1256,6 @@
|
||||
"safe-buffer": "~5.1.1"
|
||||
}
|
||||
},
|
||||
"css-tree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||
"integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mdn-data": "2.0.14",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "2.6.20",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
|
||||
@@ -1559,18 +1407,6 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-plugin-sass": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-plugin-sass/-/esbuild-plugin-sass-1.0.1.tgz",
|
||||
"integrity": "sha512-YFxjzD9Z1vz92QCJcAmCO15WVCUiOobw9ypdVeMsW+xa6S+zqryLUIh8d3fe/UkRHRO5PODZz/3xDAQuEXZwmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css-tree": "1.1.3",
|
||||
"fs-extra": "10.0.0",
|
||||
"sass": "1.47.0",
|
||||
"tmp": "0.2.1"
|
||||
}
|
||||
},
|
||||
"esbuild-plugin-vue-next": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-plugin-vue-next/-/esbuild-plugin-vue-next-0.1.4.tgz",
|
||||
@@ -1581,6 +1417,30 @@
|
||||
"hash-sum": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"esbuild-sass-plugin": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.3.2.tgz",
|
||||
"integrity": "sha512-bNIV241S0vpy+F9U9oMbmlAD+GDzKLJp2+Z9rSRP8Rq8Nwmxh9roI0s3iB9d6Eii1A5WYgCK7HZeWPokw2rhSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.14.13",
|
||||
"resolve": "^1.22.1",
|
||||
"sass": "^1.49.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"sass": {
|
||||
"version": "1.54.9",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.9.tgz",
|
||||
"integrity": "sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
|
||||
@@ -1638,23 +1498,6 @@
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
|
||||
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
@@ -1662,19 +1505,11 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
@@ -1685,11 +1520,14 @@
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.10",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
|
||||
"dev": true
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"hash-sum": {
|
||||
"version": "2.0.0",
|
||||
@@ -1703,22 +1541,6 @@
|
||||
"integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -1728,6 +1550,15 @@
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
|
||||
"integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -1749,16 +1580,6 @@
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||
@@ -1767,12 +1588,6 @@
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@@ -1786,15 +1601,6 @@
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
@@ -1811,19 +1617,10 @@
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"picocolors": {
|
||||
@@ -1861,13 +1658,15 @@
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
"is-core-module": "^2.9.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
@@ -1876,17 +1675,6 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.47.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.47.0.tgz",
|
||||
"integrity": "sha512-GtXwvwgD7/6MLUZPnlA5/8cdRgC9SzT5kAnnJMRmEZQFRE3J56Foswig4NyyyQGsnmNvg6EUM/FP0Pe9Y2zywQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -1902,14 +1690,16 @@
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
|
||||
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"rimraf": "^3.0.0"
|
||||
}
|
||||
"supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true
|
||||
},
|
||||
"tinycon": {
|
||||
"version": "0.6.8",
|
||||
"resolved": "https://registry.npmjs.org/tinycon/-/tinycon-0.6.8.tgz",
|
||||
"integrity": "sha512-bF8Lxm4JUXF6Cw0XlZdugJ44GV575OinZ0Pt8vQPr8ooNqd2yyNkoFdCHzmdpHlgoqfSLfcyk4HDP1EyllT+ug=="
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
@@ -1920,12 +1710,6 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"dev": true
|
||||
},
|
||||
"vue": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.38.tgz",
|
||||
@@ -1937,12 +1721,6 @@
|
||||
"@vue/server-renderer": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
"bootstrap-icons": "^1.9.1",
|
||||
"moment": "^2.29.4",
|
||||
"prismjs": "^1.29.0",
|
||||
"tinycon": "^0.6.8",
|
||||
"vue": "^3.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"@vue/compiler-sfc": "^3.2.37",
|
||||
"esbuild": "^0.14.50",
|
||||
"esbuild-plugin-sass": "^1.0.1",
|
||||
"esbuild-plugin-vue-next": "^0.1.4"
|
||||
"esbuild-plugin-vue-next": "^0.1.4",
|
||||
"esbuild-sass-plugin": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ func apiOpenMessage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
httpError(w, "Message not found")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
//go:embed ui
|
||||
var embeddedFS embed.FS
|
||||
|
||||
var contentSecurityPolicy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'self'; img-src * data: blob:; font-src 'self' data:; media-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self';"
|
||||
|
||||
// Listen will start the httpd
|
||||
func Listen() {
|
||||
serverRoot, err := fs.Sub(embeddedFS, "ui")
|
||||
@@ -45,6 +47,7 @@ func Listen() {
|
||||
r.HandleFunc("/api/unread", apiMarkSelectedUnread).Methods("POST")
|
||||
r.HandleFunc("/api/{id}/raw", middleWareFunc(apiDownloadRaw)).Methods("GET")
|
||||
r.HandleFunc("/api/{id}/part/{partID}", middleWareFunc(apiDownloadAttachment)).Methods("GET")
|
||||
r.HandleFunc("/api/{id}/part/{partID}/thumb", middleWareFunc(apiAttachmentThumbnail)).Methods("GET")
|
||||
r.HandleFunc("/api/{id}/delete", middleWareFunc(apiDeleteOne)).Methods("GET")
|
||||
r.HandleFunc("/api/{id}/unread", middleWareFunc(apiUnreadOne)).Methods("GET")
|
||||
r.HandleFunc("/api/{id}", middleWareFunc(apiOpenMessage)).Methods("GET")
|
||||
@@ -84,6 +87,9 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
// and gzip compression.
|
||||
func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("Content-Security-Policy", contentSecurityPolicy)
|
||||
|
||||
if config.UIAuthFile != "" {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
|
||||
@@ -114,6 +120,8 @@ func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
|
||||
// and gzip compression
|
||||
func middlewareHandler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("Content-Security-Policy", contentSecurityPolicy)
|
||||
|
||||
if config.UIAuthFile != "" {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
@@ -142,6 +150,8 @@ func middlewareHandler(h http.Handler) http.Handler {
|
||||
|
||||
// FourOFour returns a basic 404 message
|
||||
func fourOFour(w http.ResponseWriter) {
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("Content-Security-Policy", contentSecurityPolicy)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
fmt.Fprint(w, "404 page not found")
|
||||
@@ -149,6 +159,8 @@ func fourOFour(w http.ResponseWriter) {
|
||||
|
||||
// HTTPError returns a basic error message (400 response)
|
||||
func httpError(w http.ResponseWriter, msg string) {
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("Content-Security-Policy", contentSecurityPolicy)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
fmt.Fprint(w, msg)
|
||||
|
||||
107
server/thumbnails.go
Normal file
107
server/thumbnails.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/jpeg"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/logger"
|
||||
"github.com/axllent/mailpit/storage"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jhillyerd/enmime"
|
||||
)
|
||||
|
||||
var (
|
||||
thumbWidth = 180
|
||||
thumbHeight = 120
|
||||
)
|
||||
|
||||
// Attachment thumbnail (images only)
|
||||
func apiAttachmentThumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
partID := vars["partID"]
|
||||
|
||||
a, err := storage.GetAttachmentPart(id, partID)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fileName := a.FileName
|
||||
if fileName == "" {
|
||||
fileName = a.ContentID
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(a.ContentType, "image/") {
|
||||
blankImage(a, w)
|
||||
return
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(a.Content)
|
||||
|
||||
img, err := imaging.Decode(buf)
|
||||
if err != nil {
|
||||
// it's not an image, return default
|
||||
logger.Log().Warning(err)
|
||||
blankImage(a, w)
|
||||
return
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
foo := bufio.NewWriter(&b)
|
||||
|
||||
var dstImageFill *image.NRGBA
|
||||
|
||||
if img.Bounds().Dx() < thumbWidth || img.Bounds().Dy() < thumbHeight {
|
||||
dstImageFill = imaging.Fit(img, thumbWidth, thumbHeight, imaging.Lanczos)
|
||||
} else {
|
||||
dstImageFill = imaging.Fill(img, thumbWidth, thumbHeight, imaging.Center, imaging.Lanczos)
|
||||
}
|
||||
// create white image and paste image over the top
|
||||
// preventing black backgrounds for transparent GIF/PNG images
|
||||
dst := imaging.New(thumbWidth, thumbHeight, color.White)
|
||||
// paste the original over the top
|
||||
dst = imaging.OverlayCenter(dst, dstImageFill, 1.0)
|
||||
|
||||
if err := jpeg.Encode(foo, dst, &jpeg.Options{Quality: 70}); err != nil {
|
||||
logger.Log().Warning(err)
|
||||
blankImage(a, w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "image/jpeg")
|
||||
w.Header().Set("Content-Disposition", "filename=\""+fileName+"\"")
|
||||
_, _ = w.Write(b.Bytes())
|
||||
}
|
||||
|
||||
// Return a blank image instead of an error when file or image not supported
|
||||
func blankImage(a *enmime.Part, w http.ResponseWriter) {
|
||||
rect := image.Rect(0, 0, thumbWidth, thumbHeight)
|
||||
img := image.NewRGBA(rect)
|
||||
background := color.RGBA{255, 255, 255, 255}
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{background}, image.ZP, draw.Src)
|
||||
var b bytes.Buffer
|
||||
foo := bufio.NewWriter(&b)
|
||||
dstImageFill := imaging.Fill(img, thumbWidth, thumbHeight, imaging.Center, imaging.Lanczos)
|
||||
|
||||
if err := jpeg.Encode(foo, dstImageFill, &jpeg.Options{Quality: 70}); err != nil {
|
||||
logger.Log().Warning(err)
|
||||
}
|
||||
|
||||
fileName := a.FileName
|
||||
if fileName == "" {
|
||||
fileName = a.ContentID
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "image/jpeg")
|
||||
w.Header().Set("Content-Disposition", "filename=\""+fileName+"\"")
|
||||
_, _ = w.Write(b.Bytes())
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<script>
|
||||
import commonMixins from './mixins.js'
|
||||
import commonMixins from './mixins.js';
|
||||
import Message from './templates/Message.vue';
|
||||
import moment from 'moment'
|
||||
import moment from 'moment';
|
||||
import Tinycon from 'tinycon';
|
||||
|
||||
export default {
|
||||
mixins: [commonMixins],
|
||||
@@ -26,7 +27,8 @@ export default {
|
||||
messageNext: false,
|
||||
notificationsSupported: false,
|
||||
notificationsEnabled: false,
|
||||
selected: []
|
||||
selected: [],
|
||||
tcStatus: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -36,6 +38,17 @@ export default {
|
||||
} else {
|
||||
this.message = false;
|
||||
}
|
||||
},
|
||||
unread(v, old) {
|
||||
if (v == this.tcStatus) {
|
||||
return;
|
||||
}
|
||||
this.tcStatus = v;
|
||||
if (v == 0) {
|
||||
Tinycon.reset();
|
||||
} else {
|
||||
Tinycon.setBubble(v);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -56,6 +69,12 @@ export default {
|
||||
&& ("Notification" in window && Notification.permission !== "denied");
|
||||
this.notificationsEnabled = this.notificationsSupported && Notification.permission == "granted";
|
||||
|
||||
Tinycon.setOptions({
|
||||
height: 11,
|
||||
background: '#dd0000',
|
||||
fallback: false
|
||||
});
|
||||
|
||||
this.connect();
|
||||
this.loadMessages();
|
||||
},
|
||||
@@ -154,6 +173,12 @@ export default {
|
||||
new RegExp('cid:'+a.ContentID, 'g'),
|
||||
window.location.origin+'/api/'+d.ID+'/part/'+a.PartID
|
||||
);
|
||||
} else if (a.FileName.match(/^[a-zA-Z\_\-\.]+$/)) {
|
||||
// some old email clients use the filename
|
||||
d.HTML = d.HTML.replace(
|
||||
new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'),
|
||||
'src="'+window.location.origin+'/api/'+d.ID+'/part/'+a.PartID+'"'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +191,12 @@ export default {
|
||||
new RegExp('cid:'+a.ContentID, 'g'),
|
||||
window.location.origin+'/api/'+d.ID+'/part/'+a.PartID
|
||||
);
|
||||
} else if (a.FileName.match(/^[a-zA-Z\_\-\.]+$/)) {
|
||||
// some old email clients use the filename
|
||||
d.HTML = d.HTML.replace(
|
||||
new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'),
|
||||
'src="'+window.location.origin+'/api/'+d.ID+'/part/'+a.PartID+'"'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,6 +223,7 @@ export default {
|
||||
let self = this;
|
||||
let uri = 'api/delete'
|
||||
self.get(uri, false, function(response) {
|
||||
window.location.hash = "";
|
||||
self.reloadMessages();
|
||||
});
|
||||
},
|
||||
@@ -206,7 +238,6 @@ export default {
|
||||
window.location.hash = "";
|
||||
self.scrollInPlace = true;
|
||||
self.loadMessages();
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
@@ -275,7 +306,9 @@ export default {
|
||||
// websocket connect
|
||||
connect: function () {
|
||||
let wsproto = location.protocol == 'https:' ? 'wss' : 'ws';
|
||||
let ws = new WebSocket(wsproto + "://" + document.location.host + "/api/events");
|
||||
let ws = new WebSocket(
|
||||
wsproto + "://" + document.location.host + document.location.pathname + "api/events"
|
||||
);
|
||||
let self = this;
|
||||
ws.onmessage = function (e) {
|
||||
let response = JSON.parse(e.data);
|
||||
@@ -386,6 +419,12 @@ export default {
|
||||
|
||||
let selecting = false;
|
||||
let lastSelected = this.selected.length > 0 && this.selected[this.selected.length - 1];
|
||||
if (lastSelected == id) {
|
||||
this.selected = this.selected.filter(function(ele){
|
||||
return ele != id;
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
if (lastSelected === false) {
|
||||
this.selected.push(id);
|
||||
@@ -394,13 +433,17 @@ export default {
|
||||
|
||||
for (let d of this.items) {
|
||||
if (selecting) {
|
||||
this.selected.push(d.ID);
|
||||
if (!this.isSelected(d.ID)) {
|
||||
this.selected.push(d.ID);
|
||||
}
|
||||
if (d.ID == lastSelected || d.ID == id) {
|
||||
// reached backwards select
|
||||
break;
|
||||
}
|
||||
} else if (d.ID == id || d.ID == lastSelected) {
|
||||
this.selected.push(d.ID);
|
||||
if (!this.isSelected(d.ID)) {
|
||||
this.selected.push(d.ID);
|
||||
}
|
||||
selecting = true;
|
||||
}
|
||||
}
|
||||
@@ -438,7 +481,7 @@ export default {
|
||||
<a class="btn btn-outline-secondary ms-2 me-1 float-end" :class="messagePrev ? '': 'disabled'" :href="'#'+messagePrev" title="View previous message">
|
||||
<i class="bi bi-caret-left-fill"></i>
|
||||
</a>
|
||||
<a :href="'api/' + message.ID + '/source?dl=1'" class="btn btn-outline-secondary me-2 float-end" title="Download message">
|
||||
<a :href="'api/' + message.ID + '/raw?dl=1'" class="btn btn-outline-secondary me-2 float-end" title="Download message">
|
||||
<i class="bi bi-file-arrow-down-fill"></i> <span class="d-none d-md-inline">Download</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -506,7 +549,7 @@ export default {
|
||||
<i class="bi bi-envelope me-1" v-if="isConnected"></i>
|
||||
<i class="bi bi-arrow-clockwise me-1" v-else></i>
|
||||
Inbox
|
||||
<span style="margin-top: -5px; margin-left: 5px;" class="position-absolute badge rounded-pill text-bg-secondary" title="Unread messages" v-if="unread">
|
||||
<span class="badge rounded-pill text-bg-primary ms-1" title="Unread messages" v-if="unread">
|
||||
{{ formatNumber(unread) }}
|
||||
</span>
|
||||
</a>
|
||||
@@ -567,14 +610,13 @@ export default {
|
||||
<div class="list-group" v-if="items.length">
|
||||
<a v-for="message in items" :href="'#'+message.ID"
|
||||
v-on:click.ctrl="toggleSelected($event, message.ID)" v-on:click.shift="selectRange($event, message.ID)"
|
||||
class="row message d-flex small list-group-item list-group-item-action"
|
||||
class="row message d-flex small list-group-item list-group-item-action border-start-0 border-end-0"
|
||||
:class="message.Read ? 'read':'', isSelected(message.ID) ? 'selected':''">
|
||||
<div class="col-lg-3">
|
||||
<div class="d-lg-none float-end text-muted text-nowrap small">
|
||||
<i class="bi bi-paperclip h6 me-1" v-if="message.Attachments"></i>
|
||||
{{ getRelativeCreated(message) }}
|
||||
</div>
|
||||
|
||||
<div class="text-truncate d-lg-none privacy">
|
||||
<span v-if="message.From" :title="message.From.Address">{{ message.From.Name ? message.From.Name : message.From.Address }}</span>
|
||||
</div>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
$link-decoration: none;
|
||||
$primary: #3465b5;
|
||||
|
||||
@@ -38,14 +38,14 @@
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
#nav-plain-text,
|
||||
#nav-plain-text .text-view,
|
||||
#nav-source {
|
||||
white-space: pre;
|
||||
font-family: Courier New, Courier, System, fixed-width;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
#nav-plain-text {
|
||||
#nav-plain-text .text-view {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -96,6 +96,46 @@ body.blur {
|
||||
}
|
||||
}
|
||||
|
||||
.card.attachment {
|
||||
color: $gray-800;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-size: 3.5rem;
|
||||
text-align: center;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
background: $gray-300;
|
||||
|
||||
.bi {
|
||||
font-size: 1.3em;
|
||||
margin-left: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.card-body {
|
||||
opacity: 1;
|
||||
background: $gray-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* PrismJS 1.29.0 - modified!
|
||||
https://prismjs.com/download.html#themes=prism-coy&languages=markup+css */
|
||||
code[class*="language-"],
|
||||
|
||||
@@ -8,127 +8,183 @@ FakeModal.prototype.show = function () { alert('open fake modal') }
|
||||
|
||||
/* Common mixin functions used in apps */
|
||||
const commonMixins = {
|
||||
data() {
|
||||
return {
|
||||
loading: 0,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: 0,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getFileSize: function (bytes) {
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, i)).toFixed(1) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
|
||||
},
|
||||
methods: {
|
||||
getFileSize: function (bytes) {
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, i)).toFixed(1) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
|
||||
},
|
||||
|
||||
formatNumber: function (nr) {
|
||||
return new Intl.NumberFormat().format(nr);
|
||||
},
|
||||
formatNumber: function (nr) {
|
||||
return new Intl.NumberFormat().format(nr);
|
||||
},
|
||||
|
||||
// Ajax error message
|
||||
handleError: function (error) {
|
||||
// handle error
|
||||
if (error.response && error.response.data) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
if (error.response.data.Error) {
|
||||
alert(error.response.data.Error)
|
||||
} else {
|
||||
alert(error.response.data);
|
||||
}
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
alert('Error sending data to the server. Please try again.');
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
alert(error.message);
|
||||
}
|
||||
},
|
||||
// Ajax error message
|
||||
handleError: function (error) {
|
||||
// handle error
|
||||
if (error.response && error.response.data) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
if (error.response.data.Error) {
|
||||
alert(error.response.data.Error)
|
||||
} else {
|
||||
alert(error.response.data);
|
||||
}
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
alert('Error sending data to the server. Please try again.');
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
alert(error.message);
|
||||
}
|
||||
},
|
||||
|
||||
// generic modal get/set function
|
||||
modal: function (id) {
|
||||
let e = document.getElementById(id);
|
||||
if (e) {
|
||||
return bootstrap.Modal.getOrCreateInstance(e);
|
||||
}
|
||||
// in case there are open/close actions
|
||||
return new FakeModal();
|
||||
},
|
||||
// generic modal get/set function
|
||||
modal: function (id) {
|
||||
let e = document.getElementById(id);
|
||||
if (e) {
|
||||
return bootstrap.Modal.getOrCreateInstance(e);
|
||||
}
|
||||
// in case there are open/close actions
|
||||
return new FakeModal();
|
||||
},
|
||||
|
||||
// generic modal get/set function
|
||||
offcanvas: function (id) {
|
||||
var e = document.getElementById(id);
|
||||
if (e) {
|
||||
return bootstrap.Offcanvas.getOrCreateInstance(e);
|
||||
}
|
||||
// in case there are open/close actions
|
||||
return new FakeModal();
|
||||
},
|
||||
// generic modal get/set function
|
||||
offcanvas: function (id) {
|
||||
var e = document.getElementById(id);
|
||||
if (e) {
|
||||
return bootstrap.Offcanvas.getOrCreateInstance(e);
|
||||
}
|
||||
// in case there are open/close actions
|
||||
return new FakeModal();
|
||||
},
|
||||
|
||||
/**
|
||||
* Axios GET request
|
||||
*
|
||||
* @params string url
|
||||
* @params array array parameters Object/array
|
||||
* @params function callback function
|
||||
*/
|
||||
get: function (url, values, callback) {
|
||||
let self = this;
|
||||
self.loading++;
|
||||
axios.get(url, { params: values })
|
||||
.then(callback)
|
||||
.catch(self.handleError)
|
||||
.then(function () {
|
||||
// always executed
|
||||
if (self.loading > 0) {
|
||||
self.loading--;
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Axios GET request
|
||||
*
|
||||
* @params string url
|
||||
* @params array array parameters Object/array
|
||||
* @params function callback function
|
||||
*/
|
||||
get: function (url, values, callback) {
|
||||
let self = this;
|
||||
self.loading++;
|
||||
axios.get(url, { params: values })
|
||||
.then(callback)
|
||||
.catch(self.handleError)
|
||||
.then(function () {
|
||||
// always executed
|
||||
if (self.loading > 0) {
|
||||
self.loading--;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Axios Post request
|
||||
*
|
||||
* @params string url
|
||||
* @params array array parameters Object/array
|
||||
* @params function callback function
|
||||
*/
|
||||
post: function (url, values, callback) {
|
||||
let self = this;
|
||||
self.loading++;
|
||||
axios.post(url, values)
|
||||
.then(callback)
|
||||
.catch(self.handleError)
|
||||
.then(function () {
|
||||
// always executed
|
||||
if (self.loading > 0) {
|
||||
self.loading--;
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Axios Post request
|
||||
*
|
||||
* @params string url
|
||||
* @params array array parameters Object/array
|
||||
* @params function callback function
|
||||
*/
|
||||
post: function (url, values, callback) {
|
||||
let self = this;
|
||||
self.loading++;
|
||||
axios.post(url, values)
|
||||
.then(callback)
|
||||
.catch(self.handleError)
|
||||
.then(function () {
|
||||
// always executed
|
||||
if (self.loading > 0) {
|
||||
self.loading--;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Axios DELETE request (REST only)
|
||||
*
|
||||
* @params string url
|
||||
* @params array array parameters Object/array
|
||||
* @params function callback function
|
||||
*/
|
||||
delete: function (url, values, callback) {
|
||||
let self = this;
|
||||
self.loading++;
|
||||
axios.delete(url, { data: values })
|
||||
.then(callback)
|
||||
.catch(self.handleError)
|
||||
.then(function () {
|
||||
// always executed
|
||||
if (self.loading > 0) {
|
||||
self.loading--;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Axios DELETE request (REST only)
|
||||
*
|
||||
* @params string url
|
||||
* @params array array parameters Object/array
|
||||
* @params function callback function
|
||||
*/
|
||||
delete: function (url, values, callback) {
|
||||
let self = this;
|
||||
self.loading++;
|
||||
axios.delete(url, { data: values })
|
||||
.then(callback)
|
||||
.catch(self.handleError)
|
||||
.then(function () {
|
||||
// always executed
|
||||
if (self.loading > 0) {
|
||||
self.loading--;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
allAttachments: function (message) {
|
||||
let a = [];
|
||||
for (let i in message.Attachments) {
|
||||
a.push(message.Attachments[i]);
|
||||
}
|
||||
for (let i in message.OtherParts) {
|
||||
a.push(message.OtherParts[i]);
|
||||
}
|
||||
for (let i in message.Inline) {
|
||||
a.push(message.Inline[i]);
|
||||
}
|
||||
|
||||
return a.length ? a : false;
|
||||
},
|
||||
|
||||
isImage(a) {
|
||||
return a.ContentType.match(/^image\//);
|
||||
},
|
||||
|
||||
attachmentIcon: function (a) {
|
||||
let ext = a.FileName.split('.').pop().toLowerCase();
|
||||
|
||||
if (a.ContentType.match(/^image\//)) {
|
||||
return 'bi-file-image-fill';
|
||||
}
|
||||
if (a.ContentType.match(/\/pdf$/) || ext == 'pdf') {
|
||||
return 'bi-file-pdf-fill';
|
||||
}
|
||||
if (['doc', 'docx', 'odt', 'rtf'].includes(ext)) {
|
||||
return 'bi-file-word-fill';
|
||||
}
|
||||
if (['xls', 'xlsx', 'ods'].includes(ext)) {
|
||||
return 'bi-file-spreadsheet-fill';
|
||||
}
|
||||
if (['ppt', 'pptx', 'key', 'ppt', 'odp'].includes(ext)) {
|
||||
return 'bi-file-slides-fill';
|
||||
}
|
||||
if (['zip', 'tar', 'rar', 'bz2', 'gz', 'xz'].includes(ext)) {
|
||||
return 'bi-file-zip-fill';
|
||||
}
|
||||
if (a.ContentType.match(/^audio\//)) {
|
||||
return 'bi-file-music-fill';
|
||||
}
|
||||
if (a.ContentType.match(/^video\//)) {
|
||||
return 'bi-file-play-fill';
|
||||
}
|
||||
if (a.ContentType.match(/\/calendar$/)) {
|
||||
return 'bi-file-check-fill';
|
||||
}
|
||||
if (a.ContentType.match(/^text\//) || ['txt', 'sh', 'log'].includes(ext)) {
|
||||
return 'bi-file-text-fill';
|
||||
}
|
||||
|
||||
return 'bi-file-arrow-down-fill';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
37
server/ui-src/templates/Attachments.vue
Normal file
37
server/ui-src/templates/Attachments.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
<script>
|
||||
import commonMixins from '../mixins.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object,
|
||||
attachments: Object
|
||||
},
|
||||
|
||||
mixins: [commonMixins]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-4 border-top pt-4">
|
||||
<a v-for="part in attachments" :href="'api/'+message.ID+'/part/'+part.PartID" class="card attachment float-start me-3 mb-3" target="_blank" style="width: 180px">
|
||||
<img v-if="isImage(part)" :src="'api/'+message.ID+'/part/'+part.PartID+'/thumb'" class="card-img-top" alt="">
|
||||
<img v-else src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAB4AQMAAABhKUq+AAAAA1BMVEX///+nxBvIAAAAGUlEQVQYGe3BgQAAAADDoPtTT+EA1QAAgFsLQAAB12s2WgAAAABJRU5ErkJggg==" class="card-img-top" alt="">
|
||||
<div class="icon" v-if="!isImage(part)">
|
||||
<i class="bi" :class="attachmentIcon(part)"></i>
|
||||
</div>
|
||||
<div class="card-body border-0">
|
||||
<p class="mb-1 text-muted">
|
||||
<i class="bi me-1" :class="attachmentIcon(part)"></i>
|
||||
<small>{{ getFileSize(part.Size) }}</small>
|
||||
</p>
|
||||
<p class="card-text mb-0 small">
|
||||
{{ part.FileName != '' ? part.FileName : '[ unknown ]' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer small border-0 text-center text-truncate">
|
||||
{{ part.FileName != '' ? part.FileName : '[ unknown ]' }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
@@ -3,12 +3,17 @@
|
||||
import commonMixins from '../mixins.js';
|
||||
import moment from 'moment';
|
||||
import Prism from "prismjs";
|
||||
import Attachments from './Attachments.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: Object
|
||||
},
|
||||
|
||||
components: {
|
||||
Attachments
|
||||
},
|
||||
|
||||
mixins: [commonMixins],
|
||||
|
||||
data() {
|
||||
@@ -96,21 +101,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
allAttachments: function(message){
|
||||
let a = [];
|
||||
for (let i in message.Attachments) {
|
||||
a.push(message.Attachments[i]);
|
||||
}
|
||||
for (let i in message.OtherParts) {
|
||||
a.push(message.OtherParts[i]);
|
||||
}
|
||||
for (let i in message.Inline) {
|
||||
a.push(message.Inline[i]);
|
||||
}
|
||||
|
||||
return a.length ? a : false;
|
||||
},
|
||||
|
||||
messageDate: function(d) {
|
||||
return moment(d).format('ddd, D MMM YYYY, h:mm a');
|
||||
}
|
||||
@@ -186,7 +176,7 @@ export default {
|
||||
<li v-for="part in allAttachments(message)">
|
||||
<a :href="'api/'+message.ID+'/part/'+part.PartID" type="button"
|
||||
class="dropdown-item" target="_blank">
|
||||
<i class="bi bi-file-arrow-down-fill"></i>
|
||||
<i class="bi" :class="attachmentIcon(part)"></i>
|
||||
{{ part.FileName != '' ? part.FileName : '[ unknown ]' }}
|
||||
<small class="text-muted ms-2">{{ getFileSize(part.Size) }}</small>
|
||||
</a>
|
||||
@@ -218,6 +208,7 @@ export default {
|
||||
<iframe target-blank="" class="tab-pane" id="preview-html" :srcdoc="message.HTML" v-on:load="resizeIframe"
|
||||
seamless frameborder="0" style="width: 100%; height: 100%;">
|
||||
</iframe>
|
||||
<Attachments v-if="allAttachments(message).length" :message="message" :attachments="allAttachments(message)"></Attachments>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="nav-html-source" role="tabpanel"
|
||||
aria-labelledby="nav-html-source-tab" tabindex="0" v-if="message.HTMLSource">
|
||||
@@ -225,7 +216,8 @@ export default {
|
||||
</div>
|
||||
<div class="tab-pane fade" id="nav-plain-text" role="tabpanel"
|
||||
aria-labelledby="nav-plain-text-tab" tabindex="0" :class="message.HTML == '' ? 'show':''">
|
||||
{{ message.Text }}
|
||||
<div class="text-view">{{ message.Text }}</div>
|
||||
<Attachments v-if="allAttachments(message).length" :message="message" :attachments="allAttachments(message)"></Attachments>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="nav-raw" role="tabpanel" aria-labelledby="nav-raw-tab"
|
||||
tabindex="0">
|
||||
|
||||
Reference in New Issue
Block a user