mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2025-12-16 06:46:33 +01:00
Compare commits
4 commits
dd6a901ca1
...
f68dfe0da2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f68dfe0da2 | ||
|
|
b95b9b3db1 | ||
|
|
0eda44ea1c | ||
|
|
b348fcca93 |
11 changed files with 347 additions and 267 deletions
|
|
@ -18,6 +18,8 @@ IMAP_PORT=993 # IMAP port (def
|
||||||
IMAP_TLS=true # Use secure TLS connection (true/false)
|
IMAP_TLS=true # Use secure TLS connection (true/false)
|
||||||
IMAP_AUTH_TIMEOUT=3000 # Authentication timeout in ms
|
IMAP_AUTH_TIMEOUT=3000 # Authentication timeout in ms
|
||||||
IMAP_REFRESH_INTERVAL_SECONDS=60 # Refresh interval for checking new emails
|
IMAP_REFRESH_INTERVAL_SECONDS=60 # Refresh interval for checking new emails
|
||||||
|
IMAP_FETCH_CHUNK=200 # Number of UIDs per fetch chunk during initial load
|
||||||
|
IMAP_CONCURRENCY=6 # Number of concurrent fetch workers during initial load
|
||||||
|
|
||||||
# --- HTTP / WEB CONFIGURATION ---
|
# --- HTTP / WEB CONFIGURATION ---
|
||||||
HTTP_PORT=3000 # Port
|
HTTP_PORT=3000 # Port
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,6 +2,5 @@
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
copilot-instructions.md
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@ const config = {
|
||||||
port: Number(process.env.IMAP_PORT),
|
port: Number(process.env.IMAP_PORT),
|
||||||
tls: parseBool(process.env.IMAP_TLS),
|
tls: parseBool(process.env.IMAP_TLS),
|
||||||
authTimeout: Number(process.env.IMAP_AUTH_TIMEOUT),
|
authTimeout: Number(process.env.IMAP_AUTH_TIMEOUT),
|
||||||
refreshIntervalSeconds: Number(process.env.IMAP_REFRESH_INTERVAL_SECONDS)
|
refreshIntervalSeconds: Number(process.env.IMAP_REFRESH_INTERVAL_SECONDS),
|
||||||
|
fetchChunkSize: Number(process.env.IMAP_FETCH_CHUNK) || 100,
|
||||||
|
fetchConcurrency: Number(process.env.IMAP_CONCURRENCY) || 6
|
||||||
},
|
},
|
||||||
|
|
||||||
http: {
|
http: {
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ class ImapService extends EventEmitter {
|
||||||
this.loadedUids = new Set()
|
this.loadedUids = new Set()
|
||||||
this.connection = null
|
this.connection = null
|
||||||
this.initialLoadDone = false
|
this.initialLoadDone = false
|
||||||
|
this.loadingInProgress = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectAndLoadMessages() {
|
async connectAndLoadMessages() {
|
||||||
|
|
@ -167,28 +168,58 @@ class ImapService extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadMailSummariesAndEmitAsEvents() {
|
async _loadMailSummariesAndEmitAsEvents() {
|
||||||
// UID: Unique id of a message.
|
// Prevent overlapping loads which can inflate counts
|
||||||
|
if (this.loadingInProgress) {
|
||||||
|
debug('Load skipped: another load already in progress')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loadingInProgress = true
|
||||||
|
debug('Starting load of mail summaries')
|
||||||
const uids = await this._getAllUids()
|
const uids = await this._getAllUids()
|
||||||
const newUids = uids.filter(uid => !this.loadedUids.has(uid))
|
const newUids = uids.filter(uid => !this.loadedUids.has(uid))
|
||||||
|
debug(`UIDs on server: ${uids.length}, new UIDs to fetch: ${newUids.length}, already loaded: ${this.loadedUids.size}`)
|
||||||
|
|
||||||
// Optimize by fetching several messages (but not all) with one 'search' call.
|
// Tuneable chunk size & concurrency for faster initial loads
|
||||||
// fetching all at once might be more efficient, but then it takes long until we see any messages
|
const chunkSize = this.config.imap.fetchChunkSize
|
||||||
// in the frontend. With a small chunk size we ensure that we see the newest emails after a few seconds after
|
const concurrency = this.config.imap.fetchConcurrency
|
||||||
// restart.
|
|
||||||
const uidChunks = _.chunk(newUids, 20)
|
|
||||||
|
|
||||||
// Creates an array of functions. We do not start the search now, we just create the function.
|
// Chunk newest-first UIDs to balance speed and first-paint
|
||||||
const fetchFunctions = uidChunks.map(uidChunk => () =>
|
const uidChunks = _.chunk(newUids, chunkSize)
|
||||||
this._getMailHeadersAndEmitAsEvents(uidChunk)
|
debug(`Chunk size: ${chunkSize}, concurrency: ${concurrency}, chunks to process: ${uidChunks.length}`)
|
||||||
)
|
|
||||||
|
|
||||||
await pSeries(fetchFunctions)
|
// Limited-concurrency worker
|
||||||
|
const pool = []
|
||||||
|
let workerId = 0
|
||||||
|
const runNext = async() => {
|
||||||
|
if (workerId >= uidChunks.length) return
|
||||||
|
const chunkId = workerId++
|
||||||
|
const chunk = uidChunks[chunkId]
|
||||||
|
try {
|
||||||
|
debug(`Worker processing chunk ${chunkId + 1}/${uidChunks.length} (size: ${chunk.length})`)
|
||||||
|
await this._getMailHeadersAndEmitAsEvents(chunk)
|
||||||
|
debug(`Completed chunk ${chunkId + 1}/${uidChunks.length}; loadedUids size now: ${this.loadedUids.size}`)
|
||||||
|
} finally {
|
||||||
|
await runNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start workers
|
||||||
|
const workers = Math.min(concurrency, uidChunks.length)
|
||||||
|
for (let i = 0; i < workers; i++) {
|
||||||
|
pool.push(runNext())
|
||||||
|
}
|
||||||
|
await Promise.all(pool)
|
||||||
|
debug(`All chunks processed. Final loadedUids size: ${this.loadedUids.size}`)
|
||||||
|
|
||||||
|
// Mark initial load done only after all chunks complete to avoid double-runs
|
||||||
if (!this.initialLoadDone) {
|
if (!this.initialLoadDone) {
|
||||||
this.initialLoadDone = true
|
this.initialLoadDone = true
|
||||||
this.emit(ImapService.EVENT_INITIAL_LOAD_DONE)
|
this.emit(ImapService.EVENT_INITIAL_LOAD_DONE)
|
||||||
|
debug('Emitted initial load done')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.loadingInProgress = false
|
||||||
|
debug('Load finished')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -341,6 +372,7 @@ class ImapService extends EventEmitter {
|
||||||
async _getMailHeadersAndEmitAsEvents(uids) {
|
async _getMailHeadersAndEmitAsEvents(uids) {
|
||||||
try {
|
try {
|
||||||
const mails = await this._getMailHeaders(uids)
|
const mails = await this._getMailHeaders(uids)
|
||||||
|
debug(`Fetched headers for ${uids.length} UIDs; server returned ${mails.length} messages`)
|
||||||
mails.forEach(mail => {
|
mails.forEach(mail => {
|
||||||
this.loadedUids.add(mail.attributes.uid)
|
this.loadedUids.add(mail.attributes.uid)
|
||||||
// Some broadcast messages have no TO field. We have to ignore those messages.
|
// Some broadcast messages have no TO field. We have to ignore those messages.
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,40 @@
|
||||||
/*!
|
/*!
|
||||||
* Milligram v1.3.0
|
* Milligram v1.4.1
|
||||||
* https://milligram.github.io
|
* https://milligram.io
|
||||||
*
|
*
|
||||||
* Copyright (c) 2017 CJ Patoilo
|
* Copyright (c) 2020 CJ Patoilo
|
||||||
* Licensed under the MIT license
|
* Licensed under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*:after,
|
*:after,
|
||||||
*:before {
|
*:before {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-size: 62.5%;
|
font-size: 62.5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: #606c76;
|
color: #606c76;
|
||||||
font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
||||||
font-size: 1.6em;
|
font-size: 1.6em;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
letter-spacing: .01em;
|
letter-spacing: .01em;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 0.3rem solid #d1d1d1;
|
border-left: 0.3rem solid #d1d1d1;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote *:last-child {
|
blockquote *:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button,
|
.button,
|
||||||
|
|
@ -42,25 +42,26 @@ button,
|
||||||
input[type='button'],
|
input[type='button'],
|
||||||
input[type='reset'],
|
input[type='reset'],
|
||||||
input[type='submit'] {
|
input[type='submit'] {
|
||||||
background-color: #9b4dca;
|
background-color: #9b4dca;
|
||||||
border: 0.1rem solid #9b4dca;
|
border: 0.1rem solid #9b4dca;
|
||||||
border-radius: .4rem;
|
border-radius: .4rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
height: 3.8rem;
|
height: 3.8rem;
|
||||||
letter-spacing: .1rem;
|
letter-spacing: .1rem;
|
||||||
line-height: 3.8rem;
|
line-height: 3.8rem;
|
||||||
padding: 0 3.0rem;
|
padding: 0 3.0rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:focus, .button:hover,
|
.button:focus,
|
||||||
|
.button:hover,
|
||||||
button:focus,
|
button:focus,
|
||||||
button:hover,
|
button:hover,
|
||||||
input[type='button']:focus,
|
input[type='button']:focus,
|
||||||
|
|
@ -69,10 +70,10 @@ input[type='reset']:focus,
|
||||||
input[type='reset']:hover,
|
input[type='reset']:hover,
|
||||||
input[type='submit']:focus,
|
input[type='submit']:focus,
|
||||||
input[type='submit']:hover {
|
input[type='submit']:hover {
|
||||||
background-color: #606c76;
|
background-color: #606c76;
|
||||||
border-color: #606c76;
|
border-color: #606c76;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button[disabled],
|
.button[disabled],
|
||||||
|
|
@ -80,11 +81,12 @@ button[disabled],
|
||||||
input[type='button'][disabled],
|
input[type='button'][disabled],
|
||||||
input[type='reset'][disabled],
|
input[type='reset'][disabled],
|
||||||
input[type='submit'][disabled] {
|
input[type='submit'][disabled] {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button[disabled]:focus, .button[disabled]:hover,
|
.button[disabled]:focus,
|
||||||
|
.button[disabled]:hover,
|
||||||
button[disabled]:focus,
|
button[disabled]:focus,
|
||||||
button[disabled]:hover,
|
button[disabled]:hover,
|
||||||
input[type='button'][disabled]:focus,
|
input[type='button'][disabled]:focus,
|
||||||
|
|
@ -93,8 +95,8 @@ input[type='reset'][disabled]:focus,
|
||||||
input[type='reset'][disabled]:hover,
|
input[type='reset'][disabled]:hover,
|
||||||
input[type='submit'][disabled]:focus,
|
input[type='submit'][disabled]:focus,
|
||||||
input[type='submit'][disabled]:hover {
|
input[type='submit'][disabled]:hover {
|
||||||
background-color: #9b4dca;
|
background-color: #9b4dca;
|
||||||
border-color: #9b4dca;
|
border-color: #9b4dca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.button-outline,
|
.button.button-outline,
|
||||||
|
|
@ -102,11 +104,12 @@ button.button-outline,
|
||||||
input[type='button'].button-outline,
|
input[type='button'].button-outline,
|
||||||
input[type='reset'].button-outline,
|
input[type='reset'].button-outline,
|
||||||
input[type='submit'].button-outline {
|
input[type='submit'].button-outline {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #9b4dca;
|
color: #9b4dca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.button-outline:focus, .button.button-outline:hover,
|
.button.button-outline:focus,
|
||||||
|
.button.button-outline:hover,
|
||||||
button.button-outline:focus,
|
button.button-outline:focus,
|
||||||
button.button-outline:hover,
|
button.button-outline:hover,
|
||||||
input[type='button'].button-outline:focus,
|
input[type='button'].button-outline:focus,
|
||||||
|
|
@ -115,12 +118,13 @@ input[type='reset'].button-outline:focus,
|
||||||
input[type='reset'].button-outline:hover,
|
input[type='reset'].button-outline:hover,
|
||||||
input[type='submit'].button-outline:focus,
|
input[type='submit'].button-outline:focus,
|
||||||
input[type='submit'].button-outline:hover {
|
input[type='submit'].button-outline:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: #606c76;
|
border-color: #606c76;
|
||||||
color: #606c76;
|
color: #606c76;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover,
|
.button.button-outline[disabled]:focus,
|
||||||
|
.button.button-outline[disabled]:hover,
|
||||||
button.button-outline[disabled]:focus,
|
button.button-outline[disabled]:focus,
|
||||||
button.button-outline[disabled]:hover,
|
button.button-outline[disabled]:hover,
|
||||||
input[type='button'].button-outline[disabled]:focus,
|
input[type='button'].button-outline[disabled]:focus,
|
||||||
|
|
@ -129,8 +133,8 @@ input[type='reset'].button-outline[disabled]:focus,
|
||||||
input[type='reset'].button-outline[disabled]:hover,
|
input[type='reset'].button-outline[disabled]:hover,
|
||||||
input[type='submit'].button-outline[disabled]:focus,
|
input[type='submit'].button-outline[disabled]:focus,
|
||||||
input[type='submit'].button-outline[disabled]:hover {
|
input[type='submit'].button-outline[disabled]:hover {
|
||||||
border-color: inherit;
|
border-color: inherit;
|
||||||
color: #9b4dca;
|
color: #9b4dca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.button-clear,
|
.button.button-clear,
|
||||||
|
|
@ -138,12 +142,13 @@ button.button-clear,
|
||||||
input[type='button'].button-clear,
|
input[type='button'].button-clear,
|
||||||
input[type='reset'].button-clear,
|
input[type='reset'].button-clear,
|
||||||
input[type='submit'].button-clear {
|
input[type='submit'].button-clear {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
color: #9b4dca;
|
color: #9b4dca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.button-clear:focus, .button.button-clear:hover,
|
.button.button-clear:focus,
|
||||||
|
.button.button-clear:hover,
|
||||||
button.button-clear:focus,
|
button.button-clear:focus,
|
||||||
button.button-clear:hover,
|
button.button-clear:hover,
|
||||||
input[type='button'].button-clear:focus,
|
input[type='button'].button-clear:focus,
|
||||||
|
|
@ -152,12 +157,13 @@ input[type='reset'].button-clear:focus,
|
||||||
input[type='reset'].button-clear:hover,
|
input[type='reset'].button-clear:hover,
|
||||||
input[type='submit'].button-clear:focus,
|
input[type='submit'].button-clear:focus,
|
||||||
input[type='submit'].button-clear:hover {
|
input[type='submit'].button-clear:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
color: #606c76;
|
color: #606c76;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover,
|
.button.button-clear[disabled]:focus,
|
||||||
|
.button.button-clear[disabled]:hover,
|
||||||
button.button-clear[disabled]:focus,
|
button.button-clear[disabled]:focus,
|
||||||
button.button-clear[disabled]:hover,
|
button.button-clear[disabled]:hover,
|
||||||
input[type='button'].button-clear[disabled]:focus,
|
input[type='button'].button-clear[disabled]:focus,
|
||||||
|
|
@ -166,295 +172,324 @@ input[type='reset'].button-clear[disabled]:focus,
|
||||||
input[type='reset'].button-clear[disabled]:hover,
|
input[type='reset'].button-clear[disabled]:hover,
|
||||||
input[type='submit'].button-clear[disabled]:focus,
|
input[type='submit'].button-clear[disabled]:focus,
|
||||||
input[type='submit'].button-clear[disabled]:hover {
|
input[type='submit'].button-clear[disabled]:hover {
|
||||||
color: #9b4dca;
|
color: #9b4dca;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background: #f4f5f6;
|
background: #f4f5f6;
|
||||||
border-radius: .4rem;
|
border-radius: .4rem;
|
||||||
font-size: 86%;
|
font-size: 86%;
|
||||||
margin: 0 .2rem;
|
margin: 0 .2rem;
|
||||||
padding: .2rem .5rem;
|
padding: .2rem .5rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
background: #f4f5f6;
|
background: #f4f5f6;
|
||||||
border-left: 0.3rem solid #9b4dca;
|
border-left: 0.3rem solid #9b4dca;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre > code {
|
pre>code {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-top: 0.1rem solid #f4f5f6;
|
border-top: 0.1rem solid #f4f5f6;
|
||||||
margin: 3.0rem 0;
|
margin: 3.0rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type='color'],
|
||||||
|
input[type='date'],
|
||||||
|
input[type='datetime'],
|
||||||
|
input[type='datetime-local'],
|
||||||
input[type='email'],
|
input[type='email'],
|
||||||
|
input[type='month'],
|
||||||
input[type='number'],
|
input[type='number'],
|
||||||
input[type='password'],
|
input[type='password'],
|
||||||
input[type='search'],
|
input[type='search'],
|
||||||
input[type='tel'],
|
input[type='tel'],
|
||||||
input[type='text'],
|
input[type='text'],
|
||||||
input[type='url'],
|
input[type='url'],
|
||||||
|
input[type='week'],
|
||||||
|
input:not([type]),
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
background-color: transparent;
|
||||||
appearance: none;
|
border: 0.1rem solid #d1d1d1;
|
||||||
background-color: transparent;
|
border-radius: .4rem;
|
||||||
border: 0.1rem solid #d1d1d1;
|
box-shadow: none;
|
||||||
border-radius: .4rem;
|
box-sizing: inherit;
|
||||||
box-shadow: none;
|
height: 3.8rem;
|
||||||
box-sizing: inherit;
|
padding: .6rem 1.0rem .7rem;
|
||||||
height: 3.8rem;
|
width: 100%;
|
||||||
padding: .6rem 1.0rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type='color']:focus,
|
||||||
|
input[type='date']:focus,
|
||||||
|
input[type='datetime']:focus,
|
||||||
|
input[type='datetime-local']:focus,
|
||||||
input[type='email']:focus,
|
input[type='email']:focus,
|
||||||
|
input[type='month']:focus,
|
||||||
input[type='number']:focus,
|
input[type='number']:focus,
|
||||||
input[type='password']:focus,
|
input[type='password']:focus,
|
||||||
input[type='search']:focus,
|
input[type='search']:focus,
|
||||||
input[type='tel']:focus,
|
input[type='tel']:focus,
|
||||||
input[type='text']:focus,
|
input[type='text']:focus,
|
||||||
input[type='url']:focus,
|
input[type='url']:focus,
|
||||||
|
input[type='week']:focus,
|
||||||
|
input:not([type]):focus,
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
select:focus {
|
select:focus {
|
||||||
border-color: #9b4dca;
|
border-color: #9b4dca;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 29 14" width="29"><path fill="#d1d1d1" d="M9.37727 3.625l5.08154 6.93523L19.54036 3.625"/></svg>') center right no-repeat;
|
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%23d1d1d1" d="M0,0l6,8l6-8"/></svg>') center right no-repeat;
|
||||||
padding-right: 3.0rem;
|
padding-right: 3.0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
select:focus {
|
select:focus {
|
||||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 29 14" width="29"><path fill="#9b4dca" d="M9.37727 3.625l5.08154 6.93523L19.54036 3.625"/></svg>');
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%239b4dca" d="M0,0l6,8l6-8"/></svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
select[multiple] {
|
||||||
|
background: none;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
min-height: 6.5rem;
|
min-height: 6.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
label,
|
label,
|
||||||
legend {
|
legend {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='checkbox'],
|
input[type='checkbox'],
|
||||||
input[type='radio'] {
|
input[type='radio'] {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-inline {
|
.label-inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 112.0rem;
|
max-width: 112.0rem;
|
||||||
padding: 0 2.0rem;
|
padding: 0 2.0rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-no-padding {
|
.row.row-no-padding {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-no-padding > .column {
|
.row.row-no-padding>.column {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-wrap {
|
.row.row-wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-top {
|
.row.row-top {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-bottom {
|
.row.row-bottom {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-center {
|
.row.row-center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-stretch {
|
.row.row-stretch {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.row-baseline {
|
.row.row-baseline {
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column {
|
.row .column {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-10 {
|
.row .column.column-offset-10 {
|
||||||
margin-left: 10%;
|
margin-left: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-20 {
|
.row .column.column-offset-20 {
|
||||||
margin-left: 20%;
|
margin-left: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-25 {
|
.row .column.column-offset-25 {
|
||||||
margin-left: 25%;
|
margin-left: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-33, .row .column.column-offset-34 {
|
.row .column.column-offset-33,
|
||||||
margin-left: 33.3333%;
|
.row .column.column-offset-34 {
|
||||||
|
margin-left: 33.3333%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row .column.column-offset-40 {
|
||||||
|
margin-left: 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-50 {
|
.row .column.column-offset-50 {
|
||||||
margin-left: 50%;
|
margin-left: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-66, .row .column.column-offset-67 {
|
.row .column.column-offset-60 {
|
||||||
margin-left: 66.6666%;
|
margin-left: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row .column.column-offset-66,
|
||||||
|
.row .column.column-offset-67 {
|
||||||
|
margin-left: 66.6666%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-75 {
|
.row .column.column-offset-75 {
|
||||||
margin-left: 75%;
|
margin-left: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-80 {
|
.row .column.column-offset-80 {
|
||||||
margin-left: 80%;
|
margin-left: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-offset-90 {
|
.row .column.column-offset-90 {
|
||||||
margin-left: 90%;
|
margin-left: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-10 {
|
.row .column.column-10 {
|
||||||
flex: 0 0 10%;
|
flex: 0 0 10%;
|
||||||
max-width: 10%;
|
max-width: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-20 {
|
.row .column.column-20 {
|
||||||
flex: 0 0 20%;
|
flex: 0 0 20%;
|
||||||
max-width: 20%;
|
max-width: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-25 {
|
.row .column.column-25 {
|
||||||
flex: 0 0 25%;
|
flex: 0 0 25%;
|
||||||
max-width: 25%;
|
max-width: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-33, .row .column.column-34 {
|
.row .column.column-33,
|
||||||
flex: 0 0 33.3333%;
|
.row .column.column-34 {
|
||||||
max-width: 33.3333%;
|
flex: 0 0 33.3333%;
|
||||||
|
max-width: 33.3333%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-40 {
|
.row .column.column-40 {
|
||||||
flex: 0 0 40%;
|
flex: 0 0 40%;
|
||||||
max-width: 40%;
|
max-width: 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-50 {
|
.row .column.column-50 {
|
||||||
flex: 0 0 50%;
|
flex: 0 0 50%;
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-60 {
|
.row .column.column-60 {
|
||||||
flex: 0 0 60%;
|
flex: 0 0 60%;
|
||||||
max-width: 60%;
|
max-width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-66, .row .column.column-67 {
|
.row .column.column-66,
|
||||||
flex: 0 0 66.6666%;
|
.row .column.column-67 {
|
||||||
max-width: 66.6666%;
|
flex: 0 0 66.6666%;
|
||||||
|
max-width: 66.6666%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-75 {
|
.row .column.column-75 {
|
||||||
flex: 0 0 75%;
|
flex: 0 0 75%;
|
||||||
max-width: 75%;
|
max-width: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-80 {
|
.row .column.column-80 {
|
||||||
flex: 0 0 80%;
|
flex: 0 0 80%;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column.column-90 {
|
.row .column.column-90 {
|
||||||
flex: 0 0 90%;
|
flex: 0 0 90%;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column .column-top {
|
.row .column .column-top {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column .column-bottom {
|
.row .column .column-bottom {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .column .column-center {
|
.row .column .column-center {
|
||||||
-ms-grid-row-align: center;
|
align-self: center;
|
||||||
align-self: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 40rem) {
|
@media (min-width: 40rem) {
|
||||||
.row {
|
.row {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-left: -1.0rem;
|
margin-left: -1.0rem;
|
||||||
width: calc(100% + 2.0rem);
|
width: calc(100% + 2.0rem);
|
||||||
}
|
}
|
||||||
.row .column {
|
.row .column {
|
||||||
margin-bottom: inherit;
|
margin-bottom: inherit;
|
||||||
padding: 0 1.0rem;
|
padding: 0 1.0rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #9b4dca;
|
color: #9b4dca;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:focus, a:hover {
|
a:focus,
|
||||||
color: #606c76;
|
a:hover {
|
||||||
|
color: #606c76;
|
||||||
}
|
}
|
||||||
|
|
||||||
dl,
|
dl,
|
||||||
ol,
|
ol,
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
dl dl,
|
dl dl,
|
||||||
|
|
@ -466,16 +501,16 @@ ol ul,
|
||||||
ul dl,
|
ul dl,
|
||||||
ul ol,
|
ul ol,
|
||||||
ul ul {
|
ul ul {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
margin: 1.5rem 0 1.5rem 3.0rem;
|
margin: 1.5rem 0 1.5rem 3.0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
list-style: decimal inside;
|
list-style: decimal inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: circle inside;
|
list-style: circle inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button,
|
.button,
|
||||||
|
|
@ -483,14 +518,14 @@ button,
|
||||||
dd,
|
dd,
|
||||||
dt,
|
dt,
|
||||||
li {
|
li {
|
||||||
margin-bottom: 1.0rem;
|
margin-bottom: 1.0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset,
|
fieldset,
|
||||||
input,
|
input,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote,
|
blockquote,
|
||||||
|
|
@ -502,38 +537,47 @@ p,
|
||||||
pre,
|
pre,
|
||||||
table,
|
table,
|
||||||
ul {
|
ul {
|
||||||
margin-bottom: 2.5rem;
|
margin-bottom: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
width: 100%;
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
border-bottom: 0.1rem solid #e1e1e1;
|
border-bottom: 0.1rem solid #e1e1e1;
|
||||||
padding: 1.2rem 1.5rem;
|
padding: 1.2rem 1.5rem;
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td:first-child,
|
td:first-child,
|
||||||
th:first-child {
|
th:first-child {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:last-child,
|
td:last-child,
|
||||||
th:last-child {
|
th:last-child {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 40rem) {
|
||||||
|
table {
|
||||||
|
display: table;
|
||||||
|
overflow-x: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
|
@ -542,61 +586,62 @@ h3,
|
||||||
h4,
|
h4,
|
||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
letter-spacing: -.1rem;
|
letter-spacing: -.1rem;
|
||||||
margin-bottom: 2.0rem;
|
margin-bottom: 2.0rem;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 4.6rem;
|
font-size: 4.6rem;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 3.6rem;
|
font-size: 3.6rem;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 2.8rem;
|
font-size: 2.8rem;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 2.2rem;
|
font-size: 2.2rem;
|
||||||
letter-spacing: -.08rem;
|
letter-spacing: -.08rem;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
letter-spacing: -.05rem;
|
letter-spacing: -.05rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
h6 {
|
h6 {
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clearfix:after {
|
.clearfix:after {
|
||||||
clear: both;
|
clear: both;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-left {
|
.float-left {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-right {
|
.float-right {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*# sourceMappingURL=milligram.css.map */
|
/*# sourceMappingURL=milligram.css.map */
|
||||||
1
infrastructure/web/public/dependencies/milligram.css.map
Normal file
1
infrastructure/web/public/dependencies/milligram.css.map
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -69,6 +69,15 @@ text-muted {
|
||||||
margin-top: -2rem
|
margin-top: -2rem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-links {
|
||||||
|
float: right;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-links a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Reset apple form styles */
|
/* Reset apple form styles */
|
||||||
|
|
||||||
|
|
@ -98,6 +107,10 @@ select:hover {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login h4 {
|
||||||
|
margin-bottom: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
#login fieldset {
|
#login fieldset {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
{% extends 'layout.twig' %}
|
{% extends 'layout.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div style="float: right; text-align: end;">
|
<div class="action-links">
|
||||||
<a href="/inbox/{{ address }}">
|
<a href="/inbox/{{ address }}">← Return to inbox</a>
|
||||||
← Return to inbox</a>
|
<a href="/logout">Logout</a>
|
||||||
<br>
|
|
||||||
<a href="/logout">
|
|
||||||
Logout</a>
|
|
||||||
</div>
|
</div>
|
||||||
<h1>{{message}}</h1>
|
<h1>{{message}}</h1>
|
||||||
<h2>{{error.status}}</h2>
|
<h2>{{error.status}}</h2>
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div style="float: right; text-align: end;">
|
<div class="action-links">
|
||||||
<a href="/inbox/{{ address }}/delete-all">
|
<a href="/inbox/{{ address }}/delete-all">Wipe Inbox</a>
|
||||||
Wipe Inbox</a><br>
|
<a href="/logout">Logout</a>
|
||||||
<a href="/logout">
|
|
||||||
Logout</a>
|
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ address }}</h1>
|
<h1>{{ address }}</h1>
|
||||||
{% for mail in mailSummaries %}
|
{% for mail in mailSummaries %}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div style="float: right; text-align: end;">
|
<div class="action-links">
|
||||||
<a href="/inbox/{{ example }}">Example Inbox</a>
|
<a href="/inbox/{{ example }}">Example Inbox</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="login">
|
<div id="login">
|
||||||
<h1>Welcome!</h1>
|
<h1>Welcome!</h1>
|
||||||
<h4>Here you can either create a new Inbox, or access your old one</h4>
|
<h4>Here you can either create a new Inbox, or access your old one</h4>
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
{% if userInputError %}
|
{% if userInputError %}
|
||||||
<blockquote class="warning">
|
<blockquote class="warning">
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,11 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div style="float: right; text-align: end;">
|
<div class="action-links">
|
||||||
<a href="/inbox/{{ address }}">
|
<a href="/inbox/{{ address }}">← Return to inbox</a>
|
||||||
← Return to inbox</a>
|
<a href="/inbox/{{ address }}/{{ uid }}/delete">Delete Email</a>
|
||||||
<br>
|
<a href="/inbox/{{ address }}/{{ uid }}/raw" target="_blank">View Raw</a>
|
||||||
<a href="/inbox/{{ address }}/{{ uid }}/delete">
|
<a href="/logout">Logout</a>
|
||||||
Delete Email</a>
|
|
||||||
<br>
|
|
||||||
<a href="/inbox/{{ address }}/{{ uid }}/raw" target="_blank">
|
|
||||||
View Raw</a>
|
|
||||||
<br>
|
|
||||||
<a href="/logout">
|
|
||||||
Logout</a>
|
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="mail_body" style="padding-left:10%;">
|
<div class="mail_body" style="padding-left:10%;">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue