Skip to content
107 changes: 107 additions & 0 deletions css/apps/files.scss
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,17 @@ table.files-filestable {
.files-list__header {
margin-block: 15px;
margin-inline: 3rem 1.5rem;
display: flex;
align-items: center;

// NC33 renders "New" as a direct header child; push it to the right.
> div:has(> .files-list__header-upload-button) {
order: 2;
margin-left: auto;
}

.files-list__header-grid-button {
order: 3;
border: 1px solid var(--color-main-text);
border-radius: 50%;
width: 40px !important;
Expand Down Expand Up @@ -576,9 +585,107 @@ table.files-filestable {
padding-inline-end: 1rem;
}

// Chip styling for the NC33 filters; nmcfiles.ts relocates them here.
[data-test-id="files-list-filters"] {
display: flex;
align-items: center;
gap: 0.5rem;

// Hide the People filter (absent in the legacy layout).
> div:nth-child(3) {
display: none;
}

button.button-vue {
background-color: var(--nmc-ods-blue-primary);
color: var(--nmc-color-text-and-icon-black);
border: none;
border-radius: var(--border-radius-small);
height: 28px;
min-height: 28px;
min-width: unset;
padding: 0 10px;
font-weight: normal;

&:hover {
background-color: var(--nmc-ods-blue-hover);
}

&:active,
&[aria-expanded="true"] {
background-color: var(--nmc-ods-blue-active);
}

// Hide the leading icon; replace with a trailing caret like the chips.
.button-vue__icon {
display: none;
}

.button-vue__text {
color: var(--nmc-color-text-and-icon-black);
}

.button-vue__wrapper::after {
content: '';
display: inline-block;
width: 6px;
height: 6px;
border-left: 1.5px solid var(--nmc-color-text-and-icon-black);
border-bottom: 1.5px solid var(--nmc-color-text-and-icon-black);
transform: rotate(-45deg);
margin-left: 8px;
margin-bottom: 2px;
transition: transform 0.2s ease;
}

&[aria-expanded="true"] .button-vue__wrapper::after {
transform: rotate(135deg);
margin-bottom: -2px;
}
}
}

.files-list__filters {
padding-block: 1rem;
padding-inline: 4rem 1rem;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;

// NC33 renders the toggles after the chips; show them first (left) and
// cap their container so it can't stretch and push the chips off-screen.
> [data-test-id="files-list-filters"] {
order: -1;
flex: 0 0 auto;
width: max-content;
max-width: max-content;
}

> ul[aria-label="Active filters"] {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 0.5rem;
list-style: none;
margin: 0;
padding: 0;
}

// Divider anchored as the toggles' last flex item so it sits a fixed gap
// after "Modified"; only shown when filters are active.
&:has(> ul[aria-label="Active filters"] li) {
gap: 0.25rem;

> [data-test-id="files-list-filters"]::after {
content: "";
flex: 0 0 auto;
width: 1px;
height: 1.5rem;
margin-inline-start: -0.25rem;
background-color: var(--color-border);
}
}

.file-list-filters {
display: flex;
Expand Down
75 changes: 75 additions & 0 deletions css/components/ncactions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,81 @@
padding: 1rem;
}

// NC33 file-type filter uses NcButton items (no ul/li); reshape the
// popover to match the legacy action menu below.
files-file-list-filter-type .icon-vue svg path {
fill: var(--color-main-text);
}

&:has(files-file-list-filter-type) {
width: fit-content;
min-width: 213px;
max-height: min(312px, 60vh);
overflow-y: auto;

> div,
> div > div,
files-file-list-filter-type,
files-file-list-filter-type > div,
.button-vue {
min-width: 0;
}

> div,
> div > div {
padding: 0;
}

files-file-list-filter-type > div {
display: flex;
flex-direction: column;
gap: 0;
padding: 0;
}

.button-vue {
margin: 0;
border-radius: var(--border-radius);
padding-inline: 4px 16px;

// Grey on hover only, not the first item's focus-on-open.
&:hover,
&:focus-visible {
background-color: var(--color-background-hover);
}

.button-vue__text {
font-weight: normal;
}

// Selected: strip NcButton's magenta pressed fill, keep a dark
// label and add a checkmark like the legacy menu.
&[aria-pressed="true"] {
color: var(--color-main-text);

&:not(:hover):not(:focus-visible) {
background-color: transparent;
}

.button-vue__text {
color: var(--color-main-text);
}

.button-vue__wrapper::after {
content: "";
display: inline-block;
flex: 0 0 auto;
width: 16px;
height: 16px;
margin-inline-start: auto;
background-color: currentColor;
-webkit-mask: var(--icon-check-dark) no-repeat center / contain;
mask: var(--icon-check-dark) no-repeat center / contain;
}
}
}
}

ul {
display: flex;
flex-direction: column;
Expand Down
5 changes: 5 additions & 0 deletions css/components/ncappnavigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
// default navbar link styling
#app-navigation-vue .app-navigation-entry-wrapper {

// Cancel the framework's margin-top:auto so "Deleted files" isn't pinned to the bottom.
&.app-navigation-entry--pinned {
margin-top: 0;
}

.app-navigation-entry {
position: relative;
border-radius: var(--border-radius-large);
Expand Down
18 changes: 17 additions & 1 deletion css/components/ncusermenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
order: 1;
}

li#profile {
li:has(a#logout) {
order: 3;
}

li:has(a#profile) {
display: none;
}

Expand Down Expand Up @@ -150,6 +154,18 @@
background-image: var(--icon-logout-dark);
}

> a#admin_settings::after {
background-image: var(--icon-admin-dark);
}

> a#core_apps::after {
background-image: var(--icon-apps-dark);
}

> a#core_users::after {
background-image: var(--icon-users-dark);
}

> a#nmc_welcome_popup-about {

.loading-icon {
Expand Down
16 changes: 10 additions & 6 deletions lib/JSResourceLocatorExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use OC\Template\JSResourceLocator;
use OCP\IConfig;
use OCP\App\IAppManager;
use OCP\IConfig;
use Psr\Log\LoggerInterface;

class JSResourceLocatorExtension extends JSResourceLocator {
Expand All @@ -29,14 +30,17 @@ class JSResourceLocatorExtension extends JSResourceLocator {
* so we try to handle different backports in this constructor.
*
*/
public function __construct(
LoggerInterface $logger,
IConfig $config,
protected JSCombiner $jsCombiner,
protected IAppManager $appManager,
) {
public function __construct(LoggerInterface $logger, IConfig $config, JSCombiner $jsCombiner, IAppManager $appManager) {
$this->ownAppManager = $appManager;

// V33
try {
parent::__construct($logger, $config, $jsCombiner, $appManager);
return;
} catch (\Throwable $eWrongConstructNC33) {
// ignore the exception, try another constructor
}

// later
try {
parent::__construct($logger, $config, $jsCombiner, $appManager);
Expand Down
57 changes: 39 additions & 18 deletions src/js/filessettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,51 @@ const View = new StorageQuotaView({
pinia,
})

window.addEventListener('DOMContentLoaded', () => {
// Select the <li> that should be moved
const settingsButton = document.querySelector(
'li[data-cy-files-navigation-settings-button]'
)
let isMounted = false

// Target Files navigation list
const filesNavList = document.querySelector('.files-navigation__list')

if (!settingsButton || !filesNavList) {
return
const setupStorageQuota = () => {
if (isMounted) {
return true
}

// Move the settings button into the Files navigation list
filesNavList.appendChild(settingsButton)

// Create the mount anchor
// Rendered by Vue after DOMContentLoaded; retry via the observer until present.
const entrySettings = document.querySelector('.app-navigation-entry__settings')
if (!entrySettings) return
if (!entrySettings) {
return false
}

// Best-effort: lift the "Files settings" button into the main nav list.
const settingsButton = document.querySelector('li[data-cy-files-navigation-settings-button]')
const filesNavList = document.querySelector('.files-navigation__list, .app-navigation__list')
if (settingsButton && filesNavList) {
filesNavList.appendChild(settingsButton)
}

const anchor = document.createElement('div')
anchor.id = 'storage-quota-app'
entrySettings.appendChild(anchor)

// Mount the vue component
View.$mount('#storage-quota-app')
})

isMounted = true
return true
}

const waitForNavigation = () => {
if (setupStorageQuota()) {
return
}

const observer = new MutationObserver(() => {
if (setupStorageQuota()) {
observer.disconnect()
}
})
observer.observe(document.body, { childList: true, subtree: true })
}

// Handle the bundle loading after DOMContentLoaded has already fired.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', waitForNavigation)
} else {
waitForNavigation()
}
31 changes: 31 additions & 0 deletions src/nmcfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,37 @@ if (document.readyState === 'loading') {
setupGridViewScrollFix()
}

/**
* NC33 renders the Type/Modified filters inline in the header; move them into the
* .files-list__filters row below. Re-runs on Vue re-renders that re-insert them.
*/
function setupFilterRelocation(): void {
const relocate = (): void => {
const target = document.querySelector<HTMLElement>('.files-list__filters')
if (!target) return

const headerFilters = document.querySelector<HTMLElement>(
'.files-list__header [data-test-id="files-list-filters"]',
)
if (!headerFilters) return

// Drop any stale copy before moving, in case Vue recreated the element.
target.querySelectorAll('[data-test-id="files-list-filters"]').forEach(el => el.remove())
target.appendChild(headerFilters)
}

relocate()

const observer = new MutationObserver(() => relocate())
observer.observe(document.body, { childList: true, subtree: true })
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupFilterRelocation)
} else {
setupFilterRelocation()
}

window.addEventListener('DOMContentLoaded', function() {
const breadcrumb = document.querySelector('.breadcrumb')
const empty = document.querySelector('.files-list__empty')
Expand Down
Loading