Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/Controller/ReleasesApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ public function index(): DataResponse {
}
return new DataResponse($results);
}
}
}
43 changes: 34 additions & 9 deletions src/filelist.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,45 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// When securemail content is present, disable files-list internal scrolling
// so only the page scrolls. This prevents nested scroll contexts.
.sendent-has-securemail {
overflow: visible !important;
}

// When there are no real files, collapse the files-list entirely
// (hides table headers, empty state, and whitespace).
.sendent-no-files {
.files-list__table,
.files-list__empty,
.empty-content {
display: none !important;
}
}

#sendent-content {
display: block;
margin: 0 0 180px 0;
margin: 0;
padding: 0 16px 48px;
max-width: 100%;
width: 100%;
position: relative;
z-index: 10;
overflow-x: hidden;
box-sizing: border-box;

.sendent-content__header {
margin: 16px 0 8px;
font-size: 16px;
font-weight: bold;
color: var(--color-main-text);
}

iframe {
margin: 10px 10px 0;
width: calc(100% - 20px);
max-width: calc(100% - 20px);
background-color: white;
border: none;
display: block;
width: 100%;
min-height: 120px;
overflow: hidden;
background-color: var(--color-main-background);
border: 1px solid var(--color-border);
border-radius: 8px;
}
}
73 changes: 60 additions & 13 deletions src/filelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class FooterFile {
private source: string,
) {}

public async appendBelowFiles(version: number): Promise<void> {
public async appendBelowFiles(version: number, hasRealFiles: boolean): Promise<void> {
// Skip if a newer render has already been requested
if (version !== renderVersion) return

Expand All @@ -72,13 +72,24 @@ class FooterFile {
const container = document.createElement('div')
container.id = CONTENT_ID

// Insert after the file list table (try multiple selectors for NC 28-33)
const anchor = document.querySelector('.files-list__table')
|| document.querySelector('.files-filestable')
|| document.querySelector('#filestable')

if (!anchor) return

const filesList = anchor.closest('.files-list') ?? anchor.parentElement

if (!hasRealFiles && filesList) {
// No real files — hide empty state and table headers
filesList.classList.add('sendent-no-files')
}

// Insert inside files-list, after the table. Mark files-list so we can
// disable its internal scrolling and let only the page scroll.
if (filesList) {
filesList.classList.add('sendent-has-securemail')
}
anchor.insertAdjacentElement('afterend', container)

// Show loading spinner
Expand All @@ -98,6 +109,13 @@ class FooterFile {

// Replace loading spinner with iframe
container.innerHTML = ''

// Add a "Message" header so it's clear this is the email body
const header = document.createElement('h3')
header.className = 'sendent-content__header'
header.textContent = 'Message'
container.appendChild(header)

container.appendChild(this.generateIframeElement(content))
} catch (err) {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -127,20 +145,38 @@ class FooterFile {

private generateIframeElement(content: string): HTMLIFrameElement {
const iframe = document.createElement('iframe')
iframe.width = '0'
iframe.height = '0'
iframe.addEventListener('load', () => {
iframe.scrolling = 'no'

const resizeIframe = () => {
const innerHeight = iframe.contentDocument?.documentElement?.scrollHeight
const innerWidth = iframe.contentDocument?.documentElement?.scrollWidth
if (innerHeight) iframe.height = String(innerHeight)
if (innerWidth) iframe.width = String(innerWidth)
if (innerHeight) iframe.style.height = innerHeight + 'px'
}

iframe.addEventListener('load', () => {
resizeIframe()
// Re-measure after images finish loading
const images = iframe.contentDocument?.querySelectorAll('img')
for (const img of Array.from(images ?? [])) {
if (!img.complete) {
img.addEventListener('load', resizeIframe)
img.addEventListener('error', resizeIframe)
}
}
})
iframe.srcdoc = content
return iframe
}

}

/**
* Restores the files-list to its default state.
*/
function restoreFilesList() {
document.querySelector('.sendent-no-files')?.classList.remove('sendent-no-files')
document.querySelector('.sendent-has-securemail')?.classList.remove('sendent-has-securemail')
}

let debounceTimer: ReturnType<typeof setTimeout> | null = null
let renderVersion = 0

Expand All @@ -160,18 +196,29 @@ function processFileListDebounced(files: any[]) {
*/
function processFileList(files: any[]) {
const version = ++renderVersion
let securemailFile: any = null
let realFileCount = 0

for (const file of files ?? []) {
const basename = file.basename || file.name
if (file.type === 'file' && basename === FOOTER_NAME) {
// Extract directory from full path (Node.dirname or manual extraction)
const dirPath = file.dirname
?? (file.path ? file.path.substring(0, file.path.lastIndexOf('/')) || '/' : '/')
new FooterFile(basename, dirPath, file.source ?? '').appendBelowFiles(version)
return
securemailFile = file
} else if (file.type === 'file') {
realFileCount++
}
}

if (securemailFile) {
const basename = securemailFile.basename || securemailFile.name
const dirPath = securemailFile.dirname
?? (securemailFile.path ? securemailFile.path.substring(0, securemailFile.path.lastIndexOf('/')) || '/' : '/')
new FooterFile(basename, dirPath, securemailFile.source ?? '').appendBelowFiles(version, realFileCount > 0)
return
}

// No securemail file in this directory — clean up stale preview
document.getElementById(CONTENT_ID)?.remove()
restoreFilesList()
}

/**
Expand Down
Loading