mirror of
https://github.com/axllent/mailpit.git
synced 2026-03-07 08:27:01 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea12a1ee56 | ||
|
|
9345ed60c6 | ||
|
|
0a13cf8304 | ||
|
|
4ebbdab7c0 | ||
|
|
cea9518b4b | ||
|
|
a9220277d6 | ||
|
|
bd45d9dffe | ||
|
|
baaf3a3a23 | ||
|
|
2e95a75d32 | ||
|
|
53d2296ff5 | ||
|
|
e8bf803ca0 | ||
|
|
d9dc000e89 | ||
|
|
205611856b | ||
|
|
5d396b9f25 | ||
|
|
4b95c6bda0 | ||
|
|
9982948c81 | ||
|
|
614b63cf28 | ||
|
|
b1027ca844 | ||
|
|
2176ad6ca2 | ||
|
|
971753e576 | ||
|
|
9053651cc1 | ||
|
|
a9593030ab | ||
|
|
75a7c1cfd4 | ||
|
|
699a534632 | ||
|
|
53f8d34961 | ||
|
|
81d09aabd1 | ||
|
|
11eec7db30 |
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm
|
||||
platforms: linux/386,linux/amd64,linux/arm,linux/arm64
|
||||
build-args: |
|
||||
"VERSION=${{ steps.tag.outputs.tag }}"
|
||||
push: true
|
||||
|
||||
11
.github/workflows/release-build.yml
vendored
11
.github/workflows/release-build.yml
vendored
@@ -10,15 +10,20 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, arm64]
|
||||
goarch: ["386", amd64, arm, arm64]
|
||||
exclude:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
- goarch: "386"
|
||||
goos: windows
|
||||
- goarch: arm
|
||||
goos: darwin
|
||||
- goarch: arm
|
||||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# @TODO: replace deprecated action with ${{ github.ref_name }}
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
@@ -26,8 +31,9 @@ jobs:
|
||||
# build the assets
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
- run: echo "Building assets for ${{ github.ref_name }}"
|
||||
- run: npm install
|
||||
- run: npm run package
|
||||
|
||||
@@ -42,4 +48,5 @@ jobs:
|
||||
asset_name: mailpit-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
extra_files: LICENSE README.md
|
||||
md5sum: false
|
||||
overwrite: true
|
||||
ldflags: -w -X "github.com/axllent/mailpit/config.Version=${{ steps.tag.outputs.tag }}"
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
# build the assets
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm run package
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -2,6 +2,21 @@
|
||||
|
||||
Notable changes to Mailpit will be documented in this file.
|
||||
|
||||
## 1.2.4
|
||||
|
||||
### Bugfix
|
||||
- Fix mail download link
|
||||
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### API
|
||||
- Add limit and start parameters to search
|
||||
|
||||
### UI
|
||||
- Prevent double message index request on websocket connect
|
||||
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### API
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
|
||||
**Method** : `GET`
|
||||
|
||||
The search returns up to 200 of the most recent matches, and does not support pagination or limits.
|
||||
The search returns the most recent matches (default 50).
|
||||
Matching messages are returned in the order of latest received to oldest.
|
||||
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|--------------|
|
||||
| query | string | true | Search query |
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|---------|----------|----------------------------|
|
||||
| query | string | true | Search query |
|
||||
| limit | integer | false | Limit results (default 50) |
|
||||
| start | integer | false | Pagination offset |
|
||||
|
||||
|
||||
## Response
|
||||
@@ -60,8 +62,8 @@ Matching messages are returned in the order of latest received to oldest.
|
||||
|
||||
- `total` - Total messages in mailbox (all messages, not search)
|
||||
- `unread` - Total unread messages in mailbox (all messages, not search)
|
||||
- `count` - Number of messages returned in request (up to 200 for search)
|
||||
- `start` - Always 0 (offset in search is unsupported)
|
||||
- `count` - Number of messages returned in request
|
||||
- `start` - The offset (default `0`) for pagination
|
||||
- `From` - Singular Name & Address, or null if none
|
||||
- `To`, `CC`, `BCC` - Array of Name & Address, or null if none
|
||||
- `Size` - Total size of raw email in bytes
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 72 KiB |
288
package-lock.json
generated
288
package-lock.json
generated
@@ -25,9 +25,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz",
|
||||
"integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==",
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.4.tgz",
|
||||
"integrity": "sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -61,36 +61,36 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
|
||||
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz",
|
||||
"integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/shared": "3.2.41",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz",
|
||||
"integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-core": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
|
||||
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz",
|
||||
"integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/reactivity-transform": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/compiler-core": "3.2.41",
|
||||
"@vue/compiler-dom": "3.2.41",
|
||||
"@vue/compiler-ssr": "3.2.41",
|
||||
"@vue/reactivity-transform": "3.2.41",
|
||||
"@vue/shared": "3.2.41",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7",
|
||||
"postcss": "^8.1.10",
|
||||
@@ -98,69 +98,69 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
|
||||
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz",
|
||||
"integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-dom": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz",
|
||||
"integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.41.tgz",
|
||||
"integrity": "sha512-9JvCnlj8uc5xRiQGZ28MKGjuCoPhhTwcoAdv3o31+cfGgonwdPNuvqAXLhlzu4zwqavFEG5tvaoINQEfxz+l6g==",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity-transform": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
|
||||
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz",
|
||||
"integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/compiler-core": "3.2.41",
|
||||
"@vue/shared": "3.2.41",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
|
||||
"integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.41.tgz",
|
||||
"integrity": "sha512-0LBBRwqnI0p4FgIkO9q2aJBBTKDSjzhnxrxHYengkAF6dMOjeAIZFDADAlcf2h3GDALWnblbeprYYpItiulSVQ==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/reactivity": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.41.tgz",
|
||||
"integrity": "sha512-U7zYuR1NVIP8BL6jmOqmapRAHovEFp7CSw4pR2FacqewXNGqZaRfHoNLQsqQvVQ8yuZNZtxSZy0FFyC70YXPpA==",
|
||||
"dependencies": {
|
||||
"@vue/runtime-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/runtime-core": "3.2.41",
|
||||
"@vue/shared": "3.2.41",
|
||||
"csstype": "^2.6.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
|
||||
"integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.41.tgz",
|
||||
"integrity": "sha512-7YHLkfJdTlsZTV0ae5sPwl9Gn/EGr2hrlbcS/8naXm2CDpnKUwC68i1wGlrYAfIgYWL7vUZwk2GkYLQH5CvFig==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-ssr": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.2.40"
|
||||
"vue": "3.2.41"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
|
||||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz",
|
||||
"integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw=="
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.2",
|
||||
@@ -272,13 +272,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
|
||||
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.1"
|
||||
}
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "2.6.21",
|
||||
@@ -911,9 +908,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.17",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
|
||||
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -970,12 +967,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz",
|
||||
@@ -1044,23 +1035,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz",
|
||||
"integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.41.tgz",
|
||||
"integrity": "sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-sfc": "3.2.40",
|
||||
"@vue/runtime-dom": "3.2.40",
|
||||
"@vue/server-renderer": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-dom": "3.2.41",
|
||||
"@vue/compiler-sfc": "3.2.41",
|
||||
"@vue/runtime-dom": "3.2.41",
|
||||
"@vue/server-renderer": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": {
|
||||
"version": "7.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz",
|
||||
"integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ=="
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.4.tgz",
|
||||
"integrity": "sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA=="
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.14.54",
|
||||
@@ -1075,36 +1066,36 @@
|
||||
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
|
||||
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz",
|
||||
"integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/shared": "3.2.41",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-dom": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz",
|
||||
"integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==",
|
||||
"requires": {
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-core": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
|
||||
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz",
|
||||
"integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/reactivity-transform": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/compiler-core": "3.2.41",
|
||||
"@vue/compiler-dom": "3.2.41",
|
||||
"@vue/compiler-ssr": "3.2.41",
|
||||
"@vue/reactivity-transform": "3.2.41",
|
||||
"@vue/shared": "3.2.41",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7",
|
||||
"postcss": "^8.1.10",
|
||||
@@ -1112,66 +1103,66 @@
|
||||
}
|
||||
},
|
||||
"@vue/compiler-ssr": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
|
||||
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz",
|
||||
"integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==",
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-dom": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"@vue/reactivity": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz",
|
||||
"integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.41.tgz",
|
||||
"integrity": "sha512-9JvCnlj8uc5xRiQGZ28MKGjuCoPhhTwcoAdv3o31+cfGgonwdPNuvqAXLhlzu4zwqavFEG5tvaoINQEfxz+l6g==",
|
||||
"requires": {
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"@vue/reactivity-transform": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
|
||||
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz",
|
||||
"integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/compiler-core": "3.2.41",
|
||||
"@vue/shared": "3.2.41",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-core": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
|
||||
"integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.41.tgz",
|
||||
"integrity": "sha512-0LBBRwqnI0p4FgIkO9q2aJBBTKDSjzhnxrxHYengkAF6dMOjeAIZFDADAlcf2h3GDALWnblbeprYYpItiulSVQ==",
|
||||
"requires": {
|
||||
"@vue/reactivity": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/reactivity": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-dom": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.41.tgz",
|
||||
"integrity": "sha512-U7zYuR1NVIP8BL6jmOqmapRAHovEFp7CSw4pR2FacqewXNGqZaRfHoNLQsqQvVQ8yuZNZtxSZy0FFyC70YXPpA==",
|
||||
"requires": {
|
||||
"@vue/runtime-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"@vue/runtime-core": "3.2.41",
|
||||
"@vue/shared": "3.2.41",
|
||||
"csstype": "^2.6.8"
|
||||
}
|
||||
},
|
||||
"@vue/server-renderer": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
|
||||
"integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.41.tgz",
|
||||
"integrity": "sha512-7YHLkfJdTlsZTV0ae5sPwl9Gn/EGr2hrlbcS/8naXm2CDpnKUwC68i1wGlrYAfIgYWL7vUZwk2GkYLQH5CvFig==",
|
||||
"requires": {
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-ssr": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
|
||||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz",
|
||||
"integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw=="
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
@@ -1248,13 +1239,10 @@
|
||||
}
|
||||
},
|
||||
"convert-source-map": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
|
||||
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.1"
|
||||
}
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"dev": true
|
||||
},
|
||||
"csstype": {
|
||||
"version": "2.6.21",
|
||||
@@ -1622,9 +1610,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.17",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
|
||||
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
@@ -1656,12 +1644,6 @@
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz",
|
||||
@@ -1709,15 +1691,15 @@
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz",
|
||||
"integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
|
||||
"version": "3.2.41",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.41.tgz",
|
||||
"integrity": "sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==",
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-sfc": "3.2.40",
|
||||
"@vue/runtime-dom": "3.2.40",
|
||||
"@vue/server-renderer": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
"@vue/compiler-dom": "3.2.41",
|
||||
"@vue/compiler-sfc": "3.2.41",
|
||||
"@vue/runtime-dom": "3.2.41",
|
||||
"@vue/server-renderer": "3.2.41",
|
||||
"@vue/shared": "3.2.41"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,9 @@ func Search(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
messages, err := storage.Search(search)
|
||||
start, limit := getStartLimit(r)
|
||||
|
||||
messages, err := storage.Search(search, start, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
|
||||
@@ -26,6 +26,11 @@ func AppInfo(w http.ResponseWriter, r *http.Request) {
|
||||
info := appVersion{}
|
||||
info.Version = config.Version
|
||||
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
info.Memory = m.Sys - m.HeapReleased
|
||||
|
||||
latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName)
|
||||
if err == nil {
|
||||
info.LatestVersion = latest
|
||||
@@ -40,11 +45,6 @@ func AppInfo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
info.Messages = storage.CountTotal()
|
||||
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
info.Memory = m.Sys - m.HeapReleased
|
||||
|
||||
bytes, _ := json.Marshal(info)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
@@ -181,7 +181,9 @@ func assertSearchEqual(t *testing.T, uri, query string, count int) {
|
||||
t.Logf("Test search: %s", query)
|
||||
m := apiv1.MessagesResult{}
|
||||
|
||||
data, err := clientGet(uri + "?query=" + url.QueryEscape(query))
|
||||
limit := fmt.Sprintf("%d", count)
|
||||
|
||||
data, err := clientGet(uri + "?query=" + url.QueryEscape(query) + "&limit=" + limit)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
|
||||
@@ -6,9 +6,11 @@ import Tinycon from 'tinycon';
|
||||
|
||||
export default {
|
||||
mixins: [commonMixins],
|
||||
|
||||
components: {
|
||||
Message
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
currentPath: window.location.hash,
|
||||
@@ -29,9 +31,11 @@ export default {
|
||||
notificationsEnabled: false,
|
||||
selected: [],
|
||||
tcStatus: 0,
|
||||
appInfo : false,
|
||||
appInfo: false,
|
||||
lastLoaded: false
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentPath(v, old) {
|
||||
if (v && v.match(/^[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+$/)) {
|
||||
@@ -52,21 +56,23 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canPrev: function () {
|
||||
return this.start > 0;
|
||||
},
|
||||
canNext: function () {
|
||||
return this.total > (this.start + this.count);
|
||||
}
|
||||
return this.start > 0;
|
||||
},
|
||||
canNext: function () {
|
||||
return this.total > (this.start + this.count);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.currentPath = window.location.hash.slice(1);
|
||||
window.addEventListener('hashchange', () => {
|
||||
this.currentPath = window.location.hash.slice(1);
|
||||
});
|
||||
|
||||
this.notificationsSupported = 'https:' == document.location.protocol
|
||||
this.notificationsSupported = 'https:' == document.location.protocol
|
||||
&& ("Notification" in window && Notification.permission !== "denied");
|
||||
this.notificationsEnabled = this.notificationsSupported && Notification.permission == "granted";
|
||||
|
||||
@@ -76,38 +82,50 @@ export default {
|
||||
fallback: false
|
||||
});
|
||||
|
||||
this.loadMessages();
|
||||
this.connect();
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadMessages: function () {
|
||||
let self = this;
|
||||
let params = {};
|
||||
let now = Date.now()
|
||||
// prevent double loading when UI loads & websocket connects
|
||||
if (this.lastLoaded && now - this.lastLoaded < 250) {
|
||||
return;
|
||||
}
|
||||
if (this.start == 0) {
|
||||
this.lastLoaded = now;
|
||||
}
|
||||
|
||||
let self = this;
|
||||
let params = {};
|
||||
this.selected = [];
|
||||
|
||||
let uri = 'api/v1/messages';
|
||||
if (self.search) {
|
||||
self.searching = true;
|
||||
let uri = 'api/v1/messages';
|
||||
if (self.search) {
|
||||
self.searching = true;
|
||||
self.items = [];
|
||||
uri = 'api/v1/search'
|
||||
self.start = 0; // search is displayed on one page
|
||||
params['query'] = self.search;
|
||||
} else {
|
||||
uri = 'api/v1/search'
|
||||
self.start = 0; // search is displayed on one page
|
||||
params['query'] = self.search;
|
||||
params['limit'] = 200;
|
||||
} else {
|
||||
self.searching = false;
|
||||
params['limit'] = self.limit;
|
||||
if (self.start > 0) {
|
||||
params['start'] = self.start;
|
||||
}
|
||||
}
|
||||
params['limit'] = self.limit;
|
||||
if (self.start > 0) {
|
||||
params['start'] = self.start;
|
||||
}
|
||||
}
|
||||
|
||||
self.get(uri, params, function(response){
|
||||
self.get(uri, params, function (response) {
|
||||
self.total = response.data.total;
|
||||
self.unread = response.data.unread;
|
||||
self.count = response.data.count;
|
||||
self.start = response.data.start;
|
||||
self.items = response.data.messages;
|
||||
|
||||
if (self.items == 0 && self.start > 0) {
|
||||
// if pagination > 0 && results == 0 reload first page (prune)
|
||||
if (response.data.count == 0 && response.data.start > 0) {
|
||||
self.start = 0;
|
||||
return self.loadMessages();
|
||||
}
|
||||
@@ -119,48 +137,48 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollInPlace = false
|
||||
self.scrollInPlace = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
doSearch: function(e) {
|
||||
doSearch: function (e) {
|
||||
e.preventDefault();
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
resetSearch: function(e) {
|
||||
resetSearch: function (e) {
|
||||
e.preventDefault();
|
||||
this.search = '';
|
||||
this.scrollInPlace = true;
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
reloadMessages: function() {
|
||||
reloadMessages: function () {
|
||||
this.search = "";
|
||||
this.start = 0;
|
||||
this.start = 0;
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
viewNext: function () {
|
||||
this.start = parseInt(this.start, 10) + parseInt(this.limit, 10);
|
||||
this.loadMessages();
|
||||
},
|
||||
this.start = parseInt(this.start, 10) + parseInt(this.limit, 10);
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
viewPrev: function () {
|
||||
let s = this.start - this.limit;
|
||||
if (s < 0) {
|
||||
s = 0;
|
||||
}
|
||||
this.start = s;
|
||||
this.loadMessages();
|
||||
},
|
||||
viewPrev: function () {
|
||||
let s = this.start - this.limit;
|
||||
if (s < 0) {
|
||||
s = 0;
|
||||
}
|
||||
this.start = s;
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
openMessage: function(id) {
|
||||
openMessage: function (id) {
|
||||
let self = this;
|
||||
self.selected = [];
|
||||
|
||||
let uri = 'api/v1/message/' + self.currentPath
|
||||
self.get(uri, false, function(response) {
|
||||
let uri = 'api/v1/message/' + self.currentPath
|
||||
self.get(uri, false, function (response) {
|
||||
for (let i in self.items) {
|
||||
if (self.items[i].ID == self.currentPath) {
|
||||
if (!self.items[i].Read) {
|
||||
@@ -176,15 +194,15 @@ export default {
|
||||
let a = d.Inline[i];
|
||||
if (a.ContentID != '') {
|
||||
d.HTML = d.HTML.replace(
|
||||
new RegExp('cid:'+a.ContentID, 'g'),
|
||||
window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID
|
||||
new RegExp('cid:' + a.ContentID, 'g'),
|
||||
window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID
|
||||
);
|
||||
}
|
||||
if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) {
|
||||
// some old email clients use the filename
|
||||
d.HTML = d.HTML.replace(
|
||||
new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'),
|
||||
'src="'+window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID+'"'
|
||||
new RegExp('src=(\'|")' + a.FileName + '(\'|")', 'g'),
|
||||
'src="' + window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID + '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -195,15 +213,15 @@ export default {
|
||||
let a = d.Attachments[i];
|
||||
if (a.ContentID != '') {
|
||||
d.HTML = d.HTML.replace(
|
||||
new RegExp('cid:'+a.ContentID, 'g'),
|
||||
window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID
|
||||
new RegExp('cid:' + a.ContentID, 'g'),
|
||||
window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID
|
||||
);
|
||||
}
|
||||
if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) {
|
||||
// some old email clients use the filename
|
||||
d.HTML = d.HTML.replace(
|
||||
new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'),
|
||||
'src="'+window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID+'"'
|
||||
new RegExp('src=(\'|")' + a.FileName + '(\'|")', 'g'),
|
||||
'src="' + window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID + '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -228,7 +246,7 @@ export default {
|
||||
},
|
||||
|
||||
// universal handler to delete current or selected messages
|
||||
deleteMessages: function() {
|
||||
deleteMessages: function () {
|
||||
let ids = [];
|
||||
let self = this;
|
||||
if (self.message) {
|
||||
@@ -240,65 +258,65 @@ export default {
|
||||
return false;
|
||||
}
|
||||
let uri = 'api/v1/messages';
|
||||
self.delete(uri, {'ids': ids}, function(response) {
|
||||
self.delete(uri, { 'ids': ids }, function (response) {
|
||||
window.location.hash = "";
|
||||
self.scrollInPlace = true;
|
||||
self.loadMessages();
|
||||
});
|
||||
},
|
||||
|
||||
deleteAll: function() {
|
||||
deleteAll: function () {
|
||||
let self = this;
|
||||
let uri = 'api/v1/messages';
|
||||
self.delete(uri, false, function(response) {
|
||||
self.delete(uri, false, function (response) {
|
||||
window.location.hash = "";
|
||||
self.reloadMessages();
|
||||
});
|
||||
},
|
||||
|
||||
markUnread: function() {
|
||||
markUnread: function () {
|
||||
let self = this;
|
||||
if (!self.message) {
|
||||
return false;
|
||||
}
|
||||
let uri = 'api/v1/messages';
|
||||
self.put(uri, {'read': false, 'ids': [self.message.ID]}, function(response) {
|
||||
self.put(uri, { 'read': false, 'ids': [self.message.ID] }, function (response) {
|
||||
window.location.hash = "";
|
||||
self.scrollInPlace = true;
|
||||
self.loadMessages();
|
||||
});
|
||||
},
|
||||
|
||||
markAllRead: function() {
|
||||
markAllRead: function () {
|
||||
let self = this;
|
||||
let uri = 'api/v1/messages'
|
||||
self.put(uri, {'read': true}, function(response) {
|
||||
self.put(uri, { 'read': true }, function (response) {
|
||||
window.location.hash = "";
|
||||
self.scrollInPlace = true;
|
||||
self.loadMessages();
|
||||
});
|
||||
},
|
||||
|
||||
markSelectedRead: function() {
|
||||
markSelectedRead: function () {
|
||||
let self = this;
|
||||
if (!self.selected.length) {
|
||||
return false;
|
||||
}
|
||||
let uri = 'api/v1/messages';
|
||||
self.put(uri, {'read': true, 'ids': self.selected}, function(response) {
|
||||
self.put(uri, { 'read': true, 'ids': self.selected }, function (response) {
|
||||
window.location.hash = "";
|
||||
self.scrollInPlace = true;
|
||||
self.loadMessages();
|
||||
});
|
||||
},
|
||||
|
||||
markSelectedUnread: function() {
|
||||
markSelectedUnread: function () {
|
||||
let self = this;
|
||||
if (!self.selected.length) {
|
||||
return false;
|
||||
}
|
||||
let uri = 'api/v1/messages';
|
||||
self.put(uri, {'read': false, 'ids': self.selected}, function(response) {
|
||||
self.put(uri, { 'read': false, 'ids': self.selected }, function (response) {
|
||||
window.location.hash = "";
|
||||
self.scrollInPlace = true;
|
||||
self.loadMessages();
|
||||
@@ -306,7 +324,7 @@ export default {
|
||||
},
|
||||
|
||||
// test of any selected emails are unread
|
||||
selectedHasUnread: function() {
|
||||
selectedHasUnread: function () {
|
||||
if (!this.selected.length) {
|
||||
return false;
|
||||
}
|
||||
@@ -317,9 +335,9 @@ export default {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
// test of any selected emails are read
|
||||
selectedHasRead: function() {
|
||||
selectedHasRead: function () {
|
||||
if (!this.selected.length) {
|
||||
return false;
|
||||
}
|
||||
@@ -332,13 +350,13 @@ export default {
|
||||
},
|
||||
|
||||
// websocket connect
|
||||
connect: function () {
|
||||
let wsproto = location.protocol == 'https:' ? 'wss' : 'ws';
|
||||
let ws = new WebSocket(
|
||||
connect: function () {
|
||||
let wsproto = location.protocol == 'https:' ? 'wss' : 'ws';
|
||||
let ws = new WebSocket(
|
||||
wsproto + "://" + document.location.host + document.location.pathname + "api/events"
|
||||
);
|
||||
let self = this;
|
||||
ws.onmessage = function (e) {
|
||||
let self = this;
|
||||
ws.onmessage = function (e) {
|
||||
let response = JSON.parse(e.data);
|
||||
if (!response) {
|
||||
return;
|
||||
@@ -346,7 +364,7 @@ export default {
|
||||
// new messages
|
||||
if (response.Type == "new" && response.Data) {
|
||||
if (!self.searching) {
|
||||
if (self.start < 1) {
|
||||
if (self.start < 1) {
|
||||
self.items.unshift(response.Data);
|
||||
if (self.items.length > self.limit) {
|
||||
self.items.pop();
|
||||
@@ -355,36 +373,36 @@ export default {
|
||||
self.start++;
|
||||
}
|
||||
}
|
||||
self.total++;
|
||||
self.total++;
|
||||
self.unread++;
|
||||
let from = response.Data.From != null ? response.Data.From.Address : '[unknown]';
|
||||
self.browserNotify("New mail from: " + from, response.Data.Subject);
|
||||
} else if (response.Type == "prune") {
|
||||
} else if (response.Type == "prune") {
|
||||
// messages have been deleted, reload messages to adjust
|
||||
self.scrollInPlace = true;
|
||||
self.loadMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ws.onopen = function () {
|
||||
self.isConnected = true;
|
||||
ws.onopen = function () {
|
||||
self.isConnected = true;
|
||||
self.loadMessages();
|
||||
}
|
||||
}
|
||||
|
||||
ws.onclose = function (e) {
|
||||
self.isConnected = false;
|
||||
|
||||
ws.onclose = function (e) {
|
||||
self.isConnected = false;
|
||||
|
||||
setTimeout(function () {
|
||||
self.connect(); // reconnect
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = function (err) {
|
||||
ws.close();
|
||||
}
|
||||
},
|
||||
ws.onerror = function (err) {
|
||||
ws.close();
|
||||
}
|
||||
},
|
||||
|
||||
getPrimaryEmailTo: function(message) {
|
||||
getPrimaryEmailTo: function (message) {
|
||||
for (let i in message.To) {
|
||||
return message.To[i].Address;
|
||||
}
|
||||
@@ -392,12 +410,12 @@ export default {
|
||||
return '[ Undisclosed recipients ]';
|
||||
},
|
||||
|
||||
getRelativeCreated: function(message) {
|
||||
let d = new Date(message.Created)
|
||||
return moment(d).fromNow().toString();
|
||||
},
|
||||
getRelativeCreated: function (message) {
|
||||
let d = new Date(message.Created)
|
||||
return moment(d).fromNow().toString();
|
||||
},
|
||||
|
||||
browserNotify: function(title, message) {
|
||||
browserNotify: function (title, message) {
|
||||
if (!("Notification" in window)) {
|
||||
return;
|
||||
}
|
||||
@@ -412,7 +430,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
requestNotifications: function() {
|
||||
requestNotifications: function () {
|
||||
// check if the browser supports notifications
|
||||
if (!("Notification" in window)) {
|
||||
alert("This browser does not support desktop notification");
|
||||
@@ -431,28 +449,28 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
toggleSelected: function(e, id) {
|
||||
toggleSelected: function (e, id) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
if (this.isSelected(id)) {
|
||||
this.selected = this.selected.filter(function(ele){
|
||||
return ele != id;
|
||||
this.selected = this.selected.filter(function (ele) {
|
||||
return ele != id;
|
||||
});
|
||||
} else {
|
||||
this.selected.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
selectRange: function(e, id) {
|
||||
selectRange: function (e, id) {
|
||||
e.preventDefault();
|
||||
|
||||
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;
|
||||
this.selected = this.selected.filter(function (ele) {
|
||||
return ele != id;
|
||||
});
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSelected === false) {
|
||||
@@ -478,14 +496,14 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
isSelected: function(id) {
|
||||
isSelected: function (id) {
|
||||
return this.selected.indexOf(id) != -1;
|
||||
},
|
||||
|
||||
loadInfo: function(e) {
|
||||
loadInfo: function (e) {
|
||||
e.preventDefault();
|
||||
let self = this;
|
||||
self.get('api/v1/info', false, function(response) {
|
||||
self.get('api/v1/info', false, function (response) {
|
||||
self.appInfo = response.data;
|
||||
self.modal('AppInfoModal').show();
|
||||
});
|
||||
@@ -495,36 +513,39 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navbar navbar-expand-lg navbar-light row flex-shrink-0 bg-light shadow-sm">
|
||||
<div class="navbar navbar-expand-lg navbar-dark row flex-shrink-0 bg-primary text-white">
|
||||
<div class="col-lg-2 col-md-3 d-none d-md-block">
|
||||
<a class="navbar-brand" href="#" v-on:click="reloadMessages">
|
||||
<a class="navbar-brand text-white" href="#" v-on:click="reloadMessages">
|
||||
<img src="mailpit.svg" alt="Mailpit">
|
||||
<span class="ms-2">Mailpit</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col col-md-9 col-lg-10" v-if="message">
|
||||
<a class="btn btn-outline-secondary me-4 px-3" href="#" v-on:click="message=false" title="Return to messages">
|
||||
<a class="btn btn-outline-light me-4 px-3" href="#" v-on:click="message=false" title="Return to messages">
|
||||
<i class="bi bi-arrow-return-left"></i>
|
||||
</a>
|
||||
<button class="btn btn-outline-secondary me-2" title="Mark unread" v-on:click="markUnread">
|
||||
<button class="btn btn-outline-light me-2" title="Mark unread" v-on:click="markUnread">
|
||||
<i class="bi bi-eye-slash"></i> <span class="d-none d-md-inline">Mark unread</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary me-2" title="Delete message" v-on:click="deleteMessages">
|
||||
<button class="btn btn-outline-light me-2" title="Delete message" v-on:click="deleteMessages">
|
||||
<i class="bi bi-trash-fill"></i> <span class="d-none d-md-inline">Delete</span>
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary float-end" :class="messageNext ? '':'disabled'" :href="'#'+messageNext" title="View next message">
|
||||
<a class="btn btn-outline-light float-end" :class="messageNext ? '':'disabled'" :href="'#'+messageNext"
|
||||
title="View next message">
|
||||
<i class="bi bi-caret-right-fill"></i>
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary ms-2 me-1 float-end" :class="messagePrev ? '': 'disabled'" :href="'#'+messagePrev" title="View previous message">
|
||||
<a class="btn btn-outline-light 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/v1/' + message.ID + '/raw?dl=1'" class="btn btn-outline-secondary me-2 float-end" title="Download message">
|
||||
<a :href="'api/v1/message/' + message.ID + '/raw?dl=1'" class="btn btn-outline-light 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>
|
||||
|
||||
<div class="col col-md-9 col-lg-5 LOL" v-if="!message">
|
||||
<div class="col col-md-9 col-lg-5" v-if="!message">
|
||||
<form v-on:submit="doSearch">
|
||||
<div class="input-group">
|
||||
<a class="navbar-brand d-md-none" href="#" v-on:click="reloadMessages">
|
||||
@@ -532,24 +553,29 @@ export default {
|
||||
<span v-if="!total" class="ms-2">Mailpit</span>
|
||||
</a>
|
||||
<div v-if="total" class="d-flex bg-white border rounded-start flex-fill position-relative">
|
||||
<input type="text" class="form-control border-0" v-model.trim="search" placeholder="Search mailbox">
|
||||
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search" v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
||||
<input type="text" class="form-control border-0" v-model.trim="search"
|
||||
placeholder="Search mailbox">
|
||||
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search"
|
||||
v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
||||
</div>
|
||||
<button v-if="total" class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
|
||||
<button v-if="total" class="btn btn-outline-light" type="submit"><i
|
||||
class="bi bi-search"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5 text-end mt-2 mt-lg-0" v-if="!message && total">
|
||||
<button v-if="total" class="btn btn-outline-danger float-start d-md-none me-2" data-bs-toggle="modal" data-bs-target="#DeleteAllModal" title="Delete all messages">
|
||||
<button v-if="total" class="btn btn-danger float-start d-md-none me-2" data-bs-toggle="modal"
|
||||
data-bs-target="#DeleteAllModal" title="Delete all messages">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</button>
|
||||
|
||||
<button v-if="unread" class="btn btn-outline-primary float-start d-md-none" data-bs-toggle="modal" data-bs-target="#MarkAllReadModal" title="Mark all read">
|
||||
<button v-if="unread" class="btn btn-light float-start d-md-none" data-bs-toggle="modal"
|
||||
data-bs-target="#MarkAllReadModal" title="Mark all read">
|
||||
<i class="bi bi-check2-square"></i>
|
||||
</button>
|
||||
|
||||
<select v-model="limit" v-on:change="loadMessages"
|
||||
class="form-select form-select-sm d-inline w-auto me-2" v-if="!searching">
|
||||
<select v-model="limit" v-on:change="loadMessages" class="form-select form-select-sm d-inline w-auto me-2"
|
||||
v-if="!searching">
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
@@ -560,12 +586,15 @@ export default {
|
||||
</span>
|
||||
<span v-else>
|
||||
<small>
|
||||
<b>{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }}</b> of <b>{{ formatNumber(total) }}</b>
|
||||
{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }} <small>of</small> {{
|
||||
formatNumber(total) }}
|
||||
</small>
|
||||
<button class="btn btn-outline-secondary ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev" v-if="!searching" :title="'View previous '+limit+' messages'">
|
||||
<button class="btn btn-outline-light ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev"
|
||||
v-if="!searching" :title="'View previous '+limit+' messages'">
|
||||
<i class="bi bi-caret-left-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" :disabled="!canNext" v-on:click="viewNext" v-if="!searching" :title="'View next '+limit+' messages'">
|
||||
<button class="btn btn-outline-light" :disabled="!canNext" v-on:click="viewNext" v-if="!searching"
|
||||
:title="'View next '+limit+' messages'">
|
||||
<i class="bi bi-caret-right-fill"></i>
|
||||
</button>
|
||||
</span>
|
||||
@@ -574,7 +603,7 @@ export default {
|
||||
<div class="row flex-fill" style="min-height:0">
|
||||
<div class="d-none d-md-block col-lg-2 col-md-3 mh-100 position-relative" style="overflow-y: auto;">
|
||||
<ul class="list-unstyled mt-3 mb-5">
|
||||
<li v-if="isConnected" title="Messages will auto-load" class="mb-3">
|
||||
<li v-if="isConnected" title="Messages will auto-load" class="mb-3 text-muted">
|
||||
<i class="bi bi-power text-success"></i>
|
||||
Connected
|
||||
</li>
|
||||
@@ -586,7 +615,7 @@ export default {
|
||||
<a class="position-relative ps-0" href="#" v-on:click="reloadMessages">
|
||||
<i class="bi bi-envelope me-1" v-if="isConnected"></i>
|
||||
<i class="bi bi-arrow-clockwise me-1" v-else></i>
|
||||
Inbox
|
||||
Inbox
|
||||
<span class="badge rounded-pill text-bg-primary ms-1" title="Unread messages" v-if="unread">
|
||||
{{ formatNumber(unread) }}
|
||||
</span>
|
||||
@@ -607,7 +636,8 @@ export default {
|
||||
|
||||
<li class="my-3" v-if="selected.length > 0">
|
||||
<b class="me-2">Selected {{selected.length}}</b>
|
||||
<button class="btn btn-sm text-muted" v-on:click="selected=[]" title="Unselect messages"><i class="bi bi-x-circle"></i></button>
|
||||
<button class="btn btn-sm text-muted" v-on:click="selected=[]" title="Unselect messages"><i
|
||||
class="bi bi-x-circle"></i></button>
|
||||
</li>
|
||||
<li class="my-3 ms-2" v-if="selected.length > 0 && selectedHasUnread()">
|
||||
<a href="#" v-on:click="markSelectedRead">
|
||||
@@ -629,7 +659,8 @@ export default {
|
||||
</li>
|
||||
|
||||
<li class="my-3" v-if="notificationsSupported && !notificationsEnabled">
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target="#EnableNotificationsModal" title="Enable browser notifications">
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target="#EnableNotificationsModal"
|
||||
title="Enable browser notifications">
|
||||
<i class="bi bi-bell"></i>
|
||||
Enable alerts
|
||||
</a>
|
||||
@@ -645,9 +676,10 @@ export default {
|
||||
|
||||
<div class="col-lg-10 col-md-9 mh-100 pe-0">
|
||||
<div class="mh-100" style="overflow-y: auto;" :class="message ? 'd-none':''" id="message-page">
|
||||
<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)"
|
||||
<div class="list-group my-2" 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 border-start-0 border-end-0"
|
||||
:class="message.Read ? 'read':'', isSelected(message.ID) ? 'selected':''">
|
||||
<div class="col-lg-3">
|
||||
@@ -656,10 +688,12 @@ export default {
|
||||
{{ 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>
|
||||
<span v-if="message.From" :title="message.From.Address">{{ message.From.Name ?
|
||||
message.From.Name : message.From.Address }}</span>
|
||||
</div>
|
||||
<div class="text-truncate d-none d-lg-block privacy">
|
||||
<b v-if="message.From" :title="message.From.Address">{{ message.From.Name ? message.From.Name : message.From.Address }}</b>
|
||||
<b v-if="message.From" :title="message.From.Address">{{ message.From.Name ?
|
||||
message.From.Name : message.From.Address }}</b>
|
||||
</div>
|
||||
<div class="d-none d-lg-block text-truncate text-muted small privacy">
|
||||
{{ getPrimaryEmailTo(message) }}
|
||||
@@ -713,15 +747,17 @@ export default {
|
||||
This will permanently delete {{ formatNumber(total) }} message<span v-if="total > 1">s</span>.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" v-on:click="deleteAll">Delete</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal"
|
||||
v-on:click="deleteAll">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -732,15 +768,17 @@ export default {
|
||||
This will mark {{ formatNumber(unread) }} message<span v-if="unread > 1">s</span> as read.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" v-on:click="markAllRead">Confirm</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-success" data-bs-dismiss="modal"
|
||||
v-on:click="markAllRead">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1" aria-labelledby="EnableNotificationsModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1" aria-labelledby="EnableNotificationsModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -750,13 +788,15 @@ export default {
|
||||
<div class="modal-body">
|
||||
<p class="h4">Get browser notifications when Mailpit receives a new mail?</p>
|
||||
<p>
|
||||
Note that your browser will ask you for confirmation when you click <code>enable notifications</code>,
|
||||
Note that your browser will ask you for confirmation when you click
|
||||
<code>enable notifications</code>,
|
||||
and that you must have Mailpit open in a browser tab to be able to receive the notifications.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" v-on:click="requestNotifications">Enable notifications</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"
|
||||
v-on:click="requestNotifications">Enable notifications</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -788,7 +828,8 @@ export default {
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit/wiki" target="_blank">
|
||||
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit/wiki"
|
||||
target="_blank">
|
||||
Documentation
|
||||
<i class="bi bi-box-arrow-up-right"></i>
|
||||
</a>
|
||||
|
||||
@@ -5,4 +5,4 @@ import "./assets/styles.scss";
|
||||
import "../../node_modules/bootstrap-icons/font/bootstrap-icons.scss";
|
||||
import "bootstrap";
|
||||
|
||||
createApp(App).mount('#app')
|
||||
createApp(App).mount('#app');
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
$link-decoration: none;
|
||||
$primary: #3465b5;
|
||||
$primary: #2c3e50;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
.navbar-brand {
|
||||
color: #2d4a5d;
|
||||
transition: all 0.2s;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
@@ -24,6 +25,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
span {
|
||||
opacity: 0.8;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -76,11 +90,10 @@
|
||||
}
|
||||
|
||||
.message.selected {
|
||||
background: $primary;
|
||||
color: #fff;
|
||||
background: $gray-300;
|
||||
|
||||
.text-muted {
|
||||
color: #fff !important;
|
||||
color: $body-color !important;
|
||||
}
|
||||
|
||||
&.read {
|
||||
|
||||
@@ -12,7 +12,7 @@ FakeModal.prototype.show = function () { alert('open fake modal') }
|
||||
const commonMixins = {
|
||||
data() {
|
||||
return {
|
||||
loading: 0,
|
||||
loading: 0
|
||||
}
|
||||
},
|
||||
|
||||
@@ -211,4 +211,4 @@ const commonMixins = {
|
||||
}
|
||||
|
||||
|
||||
export default commonMixins
|
||||
export default commonMixins;
|
||||
|
||||
@@ -28,14 +28,13 @@ export default {
|
||||
handler(newQuestion) {
|
||||
let self = this;
|
||||
// delay 100ms to select first tab and add HTML highlighting (prev/next)
|
||||
window.setTimeout(function() {
|
||||
window.setTimeout(function () {
|
||||
self.renderUI();
|
||||
}, 100)
|
||||
},
|
||||
// force eager callback execution
|
||||
immediate: true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
@@ -47,20 +46,20 @@ export default {
|
||||
self.srcURI = 'api/v1/message/' + self.message.ID + '/raw';
|
||||
});
|
||||
},
|
||||
|
||||
unmounted: function() {
|
||||
|
||||
unmounted: function () {
|
||||
window.removeEventListener("resize", this.resizeIframes);
|
||||
},
|
||||
|
||||
methods: {
|
||||
renderUI: function() {
|
||||
renderUI: function () {
|
||||
let self = this;
|
||||
// click the first non-disabled tab
|
||||
document.querySelector('#nav-tab button:not([disabled])').click();
|
||||
document.activeElement.blur(); // blur focus
|
||||
document.getElementById('message-view').scrollTop = 0;
|
||||
|
||||
window.setTimeout(function(){
|
||||
window.setTimeout(function () {
|
||||
let p = document.getElementById('preview-html');
|
||||
|
||||
if (p) {
|
||||
@@ -83,13 +82,13 @@ export default {
|
||||
window.Prism.manual = true;
|
||||
Prism.highlightAll();
|
||||
},
|
||||
|
||||
resizeIframe: function(el) {
|
||||
|
||||
resizeIframe: function (el) {
|
||||
let i = el.target;
|
||||
i.style.height = i.contentWindow.document.body.scrollHeight + 50 + 'px';
|
||||
},
|
||||
|
||||
resizeIframes: function() {
|
||||
resizeIframes: function () {
|
||||
let h = document.getElementById('preview-html');
|
||||
if (h) {
|
||||
h.style.height = h.contentWindow.document.body.scrollHeight + 50 + 'px';
|
||||
@@ -101,7 +100,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
messageDate: function(d) {
|
||||
messageDate: function (d) {
|
||||
return moment(d).format('ddd, D MMM YYYY, h:mm a');
|
||||
}
|
||||
}
|
||||
@@ -168,7 +167,8 @@ export default {
|
||||
<div class="col-md-auto text-md-end mt-md-3">
|
||||
<p class="text-muted small d-none d-md-block"><small>{{ messageDate(message.Date) }}</small></p>
|
||||
<div class="dropdown mt-2" v-if="allAttachments(message)">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
Attachment<span v-if="allAttachments(message).length > 1">s</span>
|
||||
({{ allAttachments(message).length }})
|
||||
</button>
|
||||
@@ -188,41 +188,40 @@ export default {
|
||||
|
||||
<nav>
|
||||
<div class="nav nav-tabs my-3" id="nav-tab" role="tablist">
|
||||
<button class="nav-link" id="nav-html-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#nav-html" type="button" role="tab" aria-controls="nav-html"
|
||||
aria-selected="true" v-if="message.HTML">HTML</button>
|
||||
<button class="nav-link" id="nav-html-source-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#nav-html-source" type="button" role="tab" aria-controls="nav-html-source"
|
||||
aria-selected="false" v-if="message.HTML">HTML Source</button>
|
||||
<button class="nav-link" id="nav-plain-text-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#nav-plain-text" type="button" role="tab" aria-controls="nav-plain-text"
|
||||
aria-selected="false" :class="message.HTML == '' ? 'show':''">Text</button>
|
||||
<button class="nav-link" id="nav-raw-tab" data-bs-toggle="tab"
|
||||
data-bs-target="#nav-raw" type="button" role="tab" aria-controls="nav-raw"
|
||||
aria-selected="false">Raw</button>
|
||||
<button class="nav-link" id="nav-html-tab" data-bs-toggle="tab" data-bs-target="#nav-html" type="button"
|
||||
role="tab" aria-controls="nav-html" aria-selected="true" v-if="message.HTML">HTML</button>
|
||||
<button class="nav-link" id="nav-html-source-tab" data-bs-toggle="tab" data-bs-target="#nav-html-source"
|
||||
type="button" role="tab" aria-controls="nav-html-source" aria-selected="false"
|
||||
v-if="message.HTML">HTML Source</button>
|
||||
<button class="nav-link" id="nav-plain-text-tab" data-bs-toggle="tab" data-bs-target="#nav-plain-text"
|
||||
type="button" role="tab" aria-controls="nav-plain-text" aria-selected="false"
|
||||
:class="message.HTML == '' ? 'show':''">Text</button>
|
||||
<button class="nav-link" id="nav-raw-tab" data-bs-toggle="tab" data-bs-target="#nav-raw" type="button"
|
||||
role="tab" aria-controls="nav-raw" aria-selected="false">Raw</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content mb-5" id="nav-tabContent">
|
||||
<div v-if="message.HTML != ''" class="tab-pane fade show" id="nav-html" role="tabpanel"
|
||||
aria-labelledby="nav-html-tab" tabindex="0">
|
||||
<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 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>
|
||||
<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.HTML">
|
||||
<div class="tab-pane fade" id="nav-html-source" role="tabpanel" aria-labelledby="nav-html-source-tab"
|
||||
tabindex="0" v-if="message.HTML">
|
||||
<pre><code class="language-html">{{ message.HTML }}</code></pre>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="nav-plain-text" role="tabpanel"
|
||||
aria-labelledby="nav-plain-text-tab" tabindex="0" :class="message.HTML == '' ? 'show':''">
|
||||
<div class="tab-pane fade" id="nav-plain-text" role="tabpanel" aria-labelledby="nav-plain-text-tab"
|
||||
tabindex="0" :class="message.HTML == '' ? 'show':''">
|
||||
<div class="text-view">{{ message.Text }}</div>
|
||||
<Attachments v-if="allAttachments(message).length" :message="message" :attachments="allAttachments(message)"></Attachments>
|
||||
<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">
|
||||
<iframe v-if="srcURI" :src="srcURI" v-on:load="resizeIframe"
|
||||
seamless frameborder="0" style="width: 100%; height: 300px;" id="message-src"></iframe>
|
||||
<div class="tab-pane fade" id="nav-raw" role="tabpanel" aria-labelledby="nav-raw-tab" tabindex="0">
|
||||
<iframe v-if="srcURI" :src="srcURI" v-on:load="resizeIframe" seamless frameborder="0"
|
||||
style="width: 100%; height: 300px;" id="message-src"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
50
server/ui/favicon.svg
Normal file
50
server/ui/favicon.svg
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="500"
|
||||
height="460"
|
||||
viewBox="0 0 132.292 121.708"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="favicon.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.80851684"
|
||||
inkscape:cx="401.9706"
|
||||
inkscape:cy="327.76064"
|
||||
inkscape:window-width="1554"
|
||||
inkscape:window-height="838"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<path
|
||||
d="M12.321 0l53.861 53.918L120.365 0zM5.155 9.025l60.842 59.673 61.211-59.489-.185 36.835L66.921 70.54l15.164 12.616-8.137 5.986-41.609.184c-4.838-.022-25.877-18.34-27.185-41.255z"
|
||||
fill-opacity=".941"
|
||||
fill="#2d4a5f"
|
||||
id="path2"
|
||||
style="fill:#415066;fill-opacity:1"
|
||||
inkscape:export-filename="/mnt/apache/sandpit/go/mailpit/server/ui/mailpit.png"
|
||||
inkscape:export-xdpi="12.29"
|
||||
inkscape:export-ydpi="12.29" />
|
||||
<path
|
||||
d="M78.385 72.049l53.907-21.679-8.031 57.318-11.845-9.132c-21.727 23.171-45.255 26.289-67.997 20.837S12.281 98.39 5.155 83.8-.67 53.116 2.843 38.769c1.13 10.511-1.313 16.316 6.38 33.612 6.31 11.399 14.413 20.417 25.89 24.956 13.9 6.195 32.247 3.357 41.701-3.039l14.24-12.156z"
|
||||
fill="#00b786"
|
||||
id="path4"
|
||||
inkscape:export-filename="/mnt/apache/sandpit/go/mailpit/server/ui/mailpit.png"
|
||||
inkscape:export-xdpi="12.29"
|
||||
inkscape:export-ydpi="12.29" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="robots" content="noindex, nofollow, noarchive">
|
||||
<link rel="icon" href="mailpit.svg">
|
||||
<link rel="icon" href="favicon.svg">
|
||||
<title>Mailpit</title>
|
||||
<link rel=stylesheet href="dist/app.css">
|
||||
</head>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1 +1,50 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="460" viewBox="0 0 132.292 121.708" xmlns:v="https://vecta.io/nano"><path d="M12.321 0l53.861 53.918L120.365 0zM5.155 9.025l60.842 59.673 61.211-59.489-.185 36.835L66.921 70.54l15.164 12.616-8.137 5.986-41.609.184c-4.838-.022-25.877-18.34-27.185-41.255z" fill-opacity=".941" fill="#2d4a5f"/><path d="M78.385 72.049l53.907-21.679-8.031 57.318-11.845-9.132c-21.727 23.171-45.255 26.289-67.997 20.837S12.281 98.39 5.155 83.8-.67 53.116 2.843 38.769c1.13 10.511-1.313 16.316 6.38 33.612 6.31 11.399 14.413 20.417 25.89 24.956 13.9 6.195 32.247 3.357 41.701-3.039l14.24-12.156z" fill="#00b786"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="500"
|
||||
height="460"
|
||||
viewBox="0 0 132.292 121.708"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="mailpit.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.80851684"
|
||||
inkscape:cx="401.35218"
|
||||
inkscape:cy="327.76064"
|
||||
inkscape:window-width="1554"
|
||||
inkscape:window-height="838"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<path
|
||||
d="M12.321 0l53.861 53.918L120.365 0zM5.155 9.025l60.842 59.673 61.211-59.489-.185 36.835L66.921 70.54l15.164 12.616-8.137 5.986-41.609.184c-4.838-.022-25.877-18.34-27.185-41.255z"
|
||||
fill-opacity=".941"
|
||||
fill="#2d4a5f"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
inkscape:export-filename="/mnt/apache/sandpit/go/mailpit/server/ui/mailpit.png"
|
||||
inkscape:export-xdpi="12.29"
|
||||
inkscape:export-ydpi="12.29" />
|
||||
<path
|
||||
d="M78.385 72.049l53.907-21.679-8.031 57.318-11.845-9.132c-21.727 23.171-45.255 26.289-67.997 20.837S12.281 98.39 5.155 83.8-.67 53.116 2.843 38.769c1.13 10.511-1.313 16.316 6.38 33.612 6.31 11.399 14.413 20.417 25.89 24.956 13.9 6.195 32.247 3.357 41.701-3.039l14.24-12.156z"
|
||||
fill="#00b786"
|
||||
id="path4"
|
||||
inkscape:export-filename="/mnt/apache/sandpit/go/mailpit/server/ui/mailpit.png"
|
||||
inkscape:export-xdpi="12.29"
|
||||
inkscape:export-ydpi="12.29" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 1.9 KiB |
@@ -1,3 +1,4 @@
|
||||
// Package storage handles all database actions
|
||||
package storage
|
||||
|
||||
import (
|
||||
@@ -290,9 +291,9 @@ func List(start, limit int) ([]Summary, error) {
|
||||
// The search is broken up by segments (exact phrases can be quoted), and interprits specific terms such as:
|
||||
// is:read, is:unread, has:attachment, to:<term>, from:<term> & subject:<term>
|
||||
// Negative searches also also included by prefixing the search term with a `-` or `!`
|
||||
func Search(search string) ([]Summary, error) {
|
||||
func Search(search string, start, limit int) ([]Summary, error) {
|
||||
results := []Summary{}
|
||||
start := time.Now()
|
||||
tsStart := time.Now()
|
||||
|
||||
s := strings.ToLower(search)
|
||||
// add another quote if missing closing quote
|
||||
@@ -308,7 +309,7 @@ func Search(search string) ([]Summary, error) {
|
||||
}
|
||||
|
||||
// generate the SQL based on arguments
|
||||
q := searchParser(args)
|
||||
q := searchParser(args, start, limit)
|
||||
|
||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
||||
var id string
|
||||
@@ -336,7 +337,7 @@ func Search(search string) ([]Summary, error) {
|
||||
return results, err
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
elapsed := time.Since(tsStart)
|
||||
|
||||
logger.Log().Debugf("[db] search for \"%s\" in %s", search, elapsed)
|
||||
|
||||
@@ -642,19 +643,18 @@ func DeleteAllMessages() error {
|
||||
dbLastAction = time.Now()
|
||||
dbDataDeleted = false
|
||||
|
||||
websockets.Broadcast("prune", nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// StatsGet returns the total/unread statistics for a mailbox
|
||||
func StatsGet() MailboxStats {
|
||||
var (
|
||||
start = time.Now()
|
||||
total = CountTotal()
|
||||
unread = CountUnread()
|
||||
)
|
||||
|
||||
logger.Log().Debugf("[db] statistics calculated in %s", time.Since(start))
|
||||
|
||||
dbLastAction = time.Now()
|
||||
|
||||
return MailboxStats{
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestSearch(t *testing.T) {
|
||||
search = fmt.Sprintf("\"the email body %d jdsauk dwqmdqw\"", i)
|
||||
}
|
||||
|
||||
summaries, err := Search(search)
|
||||
summaries, err := Search(search, 0, 100)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
@@ -196,7 +196,7 @@ func TestSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
// search something that will return 200 rsults
|
||||
summaries, err := Search("This is the email body")
|
||||
summaries, err := Search("This is the email body", 0, testRuns)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
|
||||
@@ -8,7 +8,11 @@ import (
|
||||
)
|
||||
|
||||
// SearchParser returns the SQL syntax for the database search based on the search arguments
|
||||
func searchParser(args []string) *sqlf.Stmt {
|
||||
func searchParser(args []string, start, limit int) *sqlf.Stmt {
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
q := sqlf.From("mailbox").
|
||||
Select(`ID, Data, read,
|
||||
json_extract(Data, '$.To') as ToJSON,
|
||||
@@ -17,7 +21,12 @@ func searchParser(args []string) *sqlf.Stmt {
|
||||
json_extract(Data, '$.Attachments') as Attachments
|
||||
`).
|
||||
OrderBy("Sort DESC").
|
||||
Limit(200)
|
||||
Limit(limit).
|
||||
Offset(start)
|
||||
|
||||
if limit > 0 {
|
||||
q = q.Limit(limit)
|
||||
}
|
||||
|
||||
for _, w := range args {
|
||||
if cleanString(w) == "" {
|
||||
|
||||
Reference in New Issue
Block a user