Repository navigation
Mobile Friendly Files
Scope: the desktop page (
files/index.php) and the viewer it opens. The guide and Files -> Settings are already answered by the shared help and settings layers. Layers:mobile.cssLAYERS 45a-45d. Nomobile.jslayer - the phone list is drawn byfiles-desktop.jsitself. Parent: Mobile-Friendly Β· Techniques: Β§3, Β§8, Β§13, Β§28, Β§30
Files is a fake Windows desktop: fixed-position windows that drag and resize, a taskbar with hover previews, a start menu, right-click menus, rubber-band selection and drag and drop. None of that is a phone control. A desktop metaphor at 390px is a worse file manager than a list, so on a phone there is no desktop at all.
The page links mobile.css after files.css (Β§9), mobile.js last, and data-mobile-module="files" / data-mobile-page="files-desktop" on the <body>.
| Desktop | Phone |
|---|---|
| Desktop icons, windows, taskbar, start menu, previews | Hidden (45a) |
| Documents (an Explorer window) |
.fd-mobile - a list of the same folders, files and shortcuts (45b) |
| The viewer in a window | The same viewer, full screen (45d) |
| The person's navbar setting (on / auto-hide / off) | Ignored - the header always shows |
| Fields at 13px | 16px module-wide (45c) |
On the desktop files.css gives body.fd-body height: 100vh; overflow: hidden - the desktop is the page, and windows scroll inside themselves. On a phone that is the wrong shape twice: 100vh runs under Safari's address bar, and there is nothing inside to scroll.
45a puts LAYER 2's shell back: the body is a 100dvh column, the header's wrapper (.fd-navwrap) is static and displayed, and .fd-mobile is the one scroller - flex: 1 1 auto; min-height: 0; overflow-y: auto. The min-height: 0 is the part that matters (Β§28).
The desktop, taskbar, start menu, preview and the auto-hide sensor are all display: none !important.
The Files desktop lets each person hide the top bar or have it appear when the mouse reaches the top. Both are mouse settings: on a phone there is no mouse to reach the top with, and the header is how you leave the module. So 45a overrides whatever the person chose:
body[data-mobile-page="files-desktop"] .fd-navwrap {
position: static !important; transform: none !important; display: block !important;
flex-shrink: 0; box-shadow: none;
}!important is needed because the navbar modes in files.css are written against body[data-navbar], which is just as specific.
files-desktop.js checks matchMedia('(max-width: 768px)') at start. On a phone it loads list.php?folder=0 into #fdMobile instead of opening an Explorer window - the same endpoint the desktop uses, so the same permissions, the same "Documents" top, and the same audit rows.
-
The bar (
.fdm-bar) is sticky, the first child of the scroller, one line: Back (an arrow, when inside a folder), the folder's name (or "Documents"), then Upload (inside a folder you can upload to) and New folder (where you can create one - inside a folder with Upload, or at the top with the Folders permission). Each button is 44px square. -
The rows (
.fdm-row) are full-width 56px buttons: folders first, then files, then shortcuts. A folder seen out of context (shared with you without its parents) shows its path underneath; a file shows its size and date; a shortcut shows "Shortcut to" and the target's path. - Tapping a folder opens it, a file opens the viewer, and a shortcut opens its target - a folder in the list, a file in the viewer.
- An empty folder says so, and an empty "Documents" says "Nothing has been shared with you yet."
Upload uses the same Uploads.pick() as the desktop, so the same 4 MB pieces, conflict question and size limit. A finished upload refreshes the list.
On a phone viewFile() passes phone: true, and files-viewer.js builds .fdm-viewer on <body> instead of a window: its own bar with Back and the file's name, and the same renderInto() underneath. So PDFs, Word documents, spreadsheets, images, media, text and zips all work the same way, and View only and the watermark are exactly the desktop's - the token, the missing Download button, the tiled stamp.
It sits at z-index: 300000:
body[data-mobile-page="files-desktop"] .fdm-viewer {
/* Above the header too (LAYER 1 lifts it): the viewer is full screen and
carries its own Back, so nothing may sit on top of that bar. */
position: fixed; inset: 0; z-index: 300000; display: flex; flex-direction: column; background: var(--surface);
}LAYER 1 lifts the header above page content, and files.css already puts .fd-navwrap at 200000 for the desktop's auto-hide mode. Anything lower and the header sits on top of the viewer's own Back button - the one way out. The bar also pads for env(safe-area-inset-top).
Inside the overlay the viewer's toolbar may wrap, its buttons are at least 40px, and the Word page and PDF stage lose most of their desktop padding.
Every field in the module at 16px so iOS does not zoom in on focus (Β§3) - on this page that is the New folder name dialog. The dialog itself is the screen width less 24px, and its buttons are 44px tall.
Deliberately desktop-only, because each is a window or a gesture the list does not have: Recent, Search, desktop shortcuts, Permissions, Properties, Rename, Delete, Cut/Copy/Paste and drag and drop. The help page's On a phone section says what the phone can do.
Checked at 390px through an explicit-width iframe (Β§8, Β§30): the list in place of the desktop, into a folder and back, and a file opening full screen in the viewer. The things worth re-checking after any change here: the list's last row can be scrolled to under the sticky bar (Β§13), the header shows whatever the person's navbar setting is, and the viewer's Back sits above the header and works.
- A real device pass. Everything here is headless; iOS gesture handling, the real address bar and the safe-area insets are not.
-
A stale comment. LAYER 45's header in
mobile.cssstill says tapping a file downloads it, and lists only 45a-45c. Tapping a file opens the viewer (45d).
See also: Files Β· Files - Developer Guide Β· Mobile-Friendly Β· Techniques
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: Projects
- β³ ποΈ Mobile: Files
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ π§βπΌ REST API: Analysts
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ π Attachment size limits and large emails
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
- π Projects
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Leases
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- ποΈ Files
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π Projects: adding a phase did nothing on Save
- β³ π Leavers could still sign in
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96