Skip to content

Commit 4ff600f

Browse files
authored
Merge pull request #90 from penwern/dev/custom-page-router
Various fixes
2 parents 2e2b002 + e6afc20 commit 4ff600f

File tree

5 files changed

+206
-48
lines changed

5 files changed

+206
-48
lines changed

src/js/core/CurateFunctions/CurateRouter.js

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const CurateRouter = (function () {
1515
let routePrefix = '/custom';
1616
let focusTrap = null;
1717
let isInitialized = false;
18+
let lastNonCustomUrl = '/'; // Track last non-custom URL for close button
1819
let configuration = {
1920
routePrefix: '/custom',
2021
showHeader: true,
@@ -120,6 +121,15 @@ const CurateRouter = (function () {
120121
const path = window.location.pathname;
121122

122123
if (path.startsWith(routePrefix)) {
124+
// Check if user is logged in before rendering custom routes
125+
if (typeof pydio !== 'undefined' && (!pydio.user || pydio.user === null)) {
126+
// User is not logged in, don't render custom page
127+
if (currentPage) {
128+
closePage();
129+
}
130+
return;
131+
}
132+
123133
const routePath = path.substring(routePrefix.length) || '/';
124134

125135
// Check if we're already on this route - prevent duplicate navigation
@@ -137,9 +147,14 @@ const CurateRouter = (function () {
137147
} else {
138148
showErrorPage(`Route not found: ${routePath}`);
139149
}
140-
} else if (currentPage) {
141-
// We're navigating away from custom routes
142-
closePage();
150+
} else {
151+
// Track non-custom URLs for the close button
152+
lastNonCustomUrl = path;
153+
154+
if (currentPage) {
155+
// We're navigating away from custom routes
156+
closePage();
157+
}
143158
}
144159
}
145160

@@ -420,41 +435,64 @@ const CurateRouter = (function () {
420435
header.className = 'curate-router-header';
421436
header.style.cssText = `
422437
display: flex;
438+
justify-content: space-between;
423439
align-items: center;
424-
padding: 16px 20px;
425-
border-bottom: 1px solid #e0e0e0;
426-
background: #f5f5f5;
427-
min-height: 56px;
428-
`;
429-
430-
const backButton = document.createElement('button');
431-
backButton.className = 'curate-router-back-button';
432-
backButton.innerHTML = '← Back';
433-
backButton.style.cssText = `
434-
background: none;
435-
border: none;
436-
font-size: 14px;
437-
cursor: pointer;
438-
padding: 8px 12px;
439-
border-radius: 4px;
440-
margin-right: 16px;
440+
padding: 20px;
441+
margin-bottom: 20px;
442+
padding-bottom: 16px;
443+
border-bottom: 1px solid var(--md-sys-color-outline, #c4c7c5);
444+
background: var(--md-sys-color-surface-variant, #fdfcff);
441445
`;
442-
backButton.addEventListener('click', back);
443446

444447
const titleElement = document.createElement('h1');
445448
titleElement.textContent = title;
446449
titleElement.style.cssText = `
447450
margin: 0;
448-
font-size: 18px;
449-
font-weight: 600;
451+
color: var(--md-sys-color-on-background, #1d1b20);
452+
font-size: 28px;
453+
font-weight: 400;
450454
`;
451455

452-
header.appendChild(backButton);
456+
// Create close button
457+
const closeButton = document.createElement('button');
458+
closeButton.textContent = '✕ Close';
459+
closeButton.addEventListener('click', closeAndReturnToUnderlying);
460+
closeButton.style.cssText = `
461+
background: var(--md-sys-color-primary-container);
462+
color: var(--md-sys-color-on-primary-container);
463+
border: none;
464+
border-radius: 4px;
465+
padding: 6px 12px;
466+
font-size: 13px;
467+
cursor: pointer;
468+
transition: opacity 0.2s;
469+
`;
470+
471+
closeButton.addEventListener('mouseenter', () => {
472+
closeButton.style.opacity = '0.8';
473+
});
474+
closeButton.addEventListener('mouseleave', () => {
475+
closeButton.style.opacity = '1';
476+
});
477+
453478
header.appendChild(titleElement);
479+
header.appendChild(closeButton);
454480

455481
return header;
456482
}
457483

484+
/**
485+
* Close the custom page and return to the underlying Pydio URL
486+
*/
487+
function closeAndReturnToUnderlying() {
488+
if (currentPage) {
489+
// Navigate to the last non-custom URL (or fallback to base path)
490+
const targetUrl = lastNonCustomUrl || '/';
491+
window.history.pushState(null, '', targetUrl);
492+
closePage();
493+
}
494+
}
495+
458496
function createNavigationUtils() {
459497
return {
460498
navigate: (path) => navigate(path),
@@ -598,6 +636,40 @@ const CurateRouter = (function () {
598636
return currentPage !== null;
599637
}
600638

639+
/**
640+
* Check if a given path is a valid registered route
641+
*
642+
* @param {string} path - The path to check (can be full path or just route path)
643+
* @returns {boolean} True if the path matches a registered route, false otherwise
644+
*
645+
* @example
646+
* // Check if current URL is a valid custom route
647+
* if (Curate.router.isValidRoute(window.location.pathname)) {
648+
* console.log('Current URL is a valid custom route');
649+
* }
650+
*
651+
* @example
652+
* // Check a specific route
653+
* if (Curate.router.isValidRoute('/custom/pure-integration')) {
654+
* console.log('Pure integration route exists');
655+
* }
656+
*/
657+
function isValidRoute(path) {
658+
if (!path) return false;
659+
660+
// If path includes the route prefix, extract just the route part
661+
let routePath = path;
662+
if (path.startsWith(routePrefix)) {
663+
routePath = path.substring(routePrefix.length) || '/';
664+
} else if (!path.startsWith('/')) {
665+
routePath = '/' + path;
666+
}
667+
668+
// Check if this route matches any registered routes
669+
const match = findMatchingRoute(routePath);
670+
return match !== null;
671+
}
672+
601673
/**
602674
* Manually check and navigate to current URL route
603675
* Useful for handling page refreshes or manual URL entry
@@ -638,6 +710,7 @@ const CurateRouter = (function () {
638710
back,
639711
getCurrentPage,
640712
isActive,
713+
isValidRoute,
641714
checkRoute
642715
};
643716
})();

src/js/core/RouteProtection.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Early Route Protection
3+
*
4+
* This module MUST be imported first to protect custom routes from Pydio's
5+
* homepage redirect before any other code runs.
6+
*
7+
* The issue: When a custom homepage is set in Pydio, it redirects any unrecognized
8+
* URL (including our custom routes) to the custom homepage on page load.
9+
*
10+
* The solution: Intercept the history API immediately on script load to block
11+
* Pydio from redirecting away from /custom/* routes.
12+
*/
13+
14+
(function() {
15+
'use strict';
16+
17+
const initialPath = window.location.pathname;
18+
19+
// Only set up protection if we're on a custom route
20+
if (!initialPath.startsWith('/custom')) {
21+
return;
22+
}
23+
24+
sessionStorage.setItem('curate_protected_route', initialPath);
25+
26+
// Store original history methods before anyone can override them
27+
const originalReplaceState = history.replaceState.bind(history);
28+
const originalPushState = history.pushState.bind(history);
29+
30+
// Override history.replaceState to block Pydio redirects
31+
history.replaceState = function(state, title, url) {
32+
const currentPath = window.location.pathname;
33+
const protectedRoute = sessionStorage.getItem('curate_protected_route');
34+
35+
// Block redirects away from custom routes
36+
if (protectedRoute && currentPath.startsWith('/custom') && url && !url.startsWith('/custom')) {
37+
return;
38+
}
39+
40+
return originalReplaceState(state, title, url);
41+
};
42+
43+
// Override history.pushState to block Pydio navigation
44+
history.pushState = function(state, title, url) {
45+
const currentPath = window.location.pathname;
46+
const protectedRoute = sessionStorage.getItem('curate_protected_route');
47+
48+
// Block navigation away from custom routes
49+
if (protectedRoute && currentPath.startsWith('/custom') && url && !url.startsWith('/custom')) {
50+
return;
51+
}
52+
53+
return originalPushState(state, title, url);
54+
};
55+
56+
// Monitor URL changes and restore if Pydio manages to redirect anyway
57+
let monitorCount = 0;
58+
const maxMonitorChecks = 50; // Monitor for 5 seconds
59+
60+
const urlMonitor = setInterval(() => {
61+
monitorCount++;
62+
const currentPath = window.location.pathname;
63+
const protectedRoute = sessionStorage.getItem('curate_protected_route');
64+
65+
if (!protectedRoute) {
66+
clearInterval(urlMonitor);
67+
return;
68+
}
69+
70+
// If we've been redirected away from the custom route, restore it
71+
if (currentPath !== protectedRoute && !currentPath.startsWith('/custom')) {
72+
originalReplaceState(null, '', protectedRoute);
73+
74+
// Trigger a custom event that the router can listen for
75+
window.dispatchEvent(new CustomEvent('curate-route-restored', {
76+
detail: { path: protectedRoute }
77+
}));
78+
79+
sessionStorage.removeItem('curate_protected_route');
80+
clearInterval(urlMonitor);
81+
return;
82+
}
83+
84+
// If we've successfully stayed on the custom route for 2 seconds, clear protection
85+
if (currentPath === protectedRoute && monitorCount > 20) {
86+
sessionStorage.removeItem('curate_protected_route');
87+
clearInterval(urlMonitor);
88+
return;
89+
}
90+
91+
// Timeout after 5 seconds
92+
if (monitorCount >= maxMonitorChecks) {
93+
sessionStorage.removeItem('curate_protected_route');
94+
clearInterval(urlMonitor);
95+
}
96+
}, 100);
97+
98+
})();

src/js/custom-pages/router-init.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ function initializeRouter() {
1212
escapeClosesPage: true
1313
});
1414

15+
// Listen for route restoration events from RouteProtection module
16+
window.addEventListener('curate-route-restored', (event) => {
17+
// Give the router a moment to be fully initialized
18+
setTimeout(() => {
19+
Curate.router.checkRoute();
20+
}, 100);
21+
});
22+
1523
console.log('Curate Router initialized successfully');
1624
}
1725

src/js/custom-pages/routes/pure-integration.js

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,6 @@ export function registerPureIntegrationRoute() {
2020
-webkit-overflow-scrolling: touch;
2121
`;
2222

23-
// Create header
24-
const header = document.createElement('div');
25-
header.style.cssText = `
26-
display: flex;
27-
justify-content: space-between;
28-
align-items: center;
29-
margin-bottom: 20px;
30-
padding-bottom: 16px;
31-
border-bottom: 1px solid var(--md-sys-color-outline, #c4c7c5);
32-
`;
33-
34-
const title = document.createElement('h1');
35-
title.textContent = 'Pure Integration';
36-
title.style.cssText = `
37-
margin: 0;
38-
color: var(--md-sys-color-on-background, #1d1b20);
39-
font-size: 28px;
40-
font-weight: 400;
41-
`;
42-
43-
header.appendChild(title);
44-
4523
// Create the Pure Integration UI component
4624
const pureUI = document.createElement('pure-integration-interface');
4725
pureUI.style.cssText = `
@@ -50,7 +28,6 @@ export function registerPureIntegrationRoute() {
5028
`;
5129

5230
// Assemble the page
53-
container.appendChild(header);
5431
container.appendChild(pureUI);
5532

5633
// Return cleanup function to remove the component
@@ -61,6 +38,6 @@ export function registerPureIntegrationRoute() {
6138
};
6239
}, {
6340
title: 'Pure Integration',
64-
showHeader: false
41+
showHeader: true
6542
});
6643
}

src/js/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// IMPORTANT: RouteProtection must be imported FIRST to intercept Pydio redirects
2+
import "../js/core/RouteProtection.js";
13
import "../js/core/CurateFunctions/CurateFunctions.js";
24
import "./custom-pages/index.js";
35
import "../js/core/CustomPreservationConfigs.js";

0 commit comments

Comments
 (0)