mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-02-10 18:05:07 +08:00
[feat] Add jwt for forgotten; Fix styles; Refactor
This commit is contained in:
@ -187,12 +187,12 @@ export const resetConfiguration = async () => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateDocServerToken = async (document, editorConfig) => {
|
export const generateDocServerToken = async (document, editorConfig, command) => {
|
||||||
const response = await safeFetch(`${BACKEND_URL}${API_BASE_PATH}/generate-docserver-token`, {
|
const response = await safeFetch(`${BACKEND_URL}${API_BASE_PATH}/generate-docserver-token`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: JSON.stringify({document, editorConfig})
|
body: JSON.stringify({document, editorConfig, command})
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to generate Document Server token');
|
throw new Error('Failed to generate Document Server token');
|
||||||
@ -200,53 +200,50 @@ export const generateDocServerToken = async (document, editorConfig) => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getForgottenList = async () => {
|
const callDocumentServer = async (command, key = null) => {
|
||||||
// Call Document Server directly
|
const {token} = await generateDocServerToken(
|
||||||
const docServiceUrl = process.env.REACT_APP_DOCSERVICE_URL || 'http://localhost:8000';
|
{key: key || 'forgotten-list', fileType: 'docx', title: 'Document', url: ''},
|
||||||
const response = await safeFetch(`${docServiceUrl}/coauthoring/CommandService.ashx`, {
|
{user: {id: 'admin', name: 'admin'}, lang: 'en', mode: 'view'},
|
||||||
|
command
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await safeFetch(`${process.env.REACT_APP_DOCSERVICE_URL || window.location.origin}/coauthoring/CommandService.ashx`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
c: 'getForgottenList'
|
c: command,
|
||||||
|
...(key && {key}),
|
||||||
|
token: token
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch forgotten files list');
|
if (response.status === 404) throw new Error('File not found');
|
||||||
|
throw new Error(`Failed to execute ${command}`);
|
||||||
}
|
}
|
||||||
const result = await response.json();
|
|
||||||
// Format the response to match our component expectations
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getForgottenList = async () => {
|
||||||
|
const result = await callDocumentServer('getForgottenList');
|
||||||
const files = result.keys || [];
|
const files = result.keys || [];
|
||||||
return files.map(fileKey => {
|
return files.map(fileKey => {
|
||||||
const fileName = fileKey.split('/').pop() || fileKey;
|
const fileName = fileKey.split('/').pop() || fileKey;
|
||||||
return {
|
return {
|
||||||
key: fileKey,
|
key: fileKey,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
size: null, // Size not available from getForgottenList
|
size: null,
|
||||||
modified: null // Modified date not available from getForgottenList
|
modified: null
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getForgotten = async (docId) => {
|
export const getForgotten = async (docId) => {
|
||||||
// Call Document Server directly
|
const result = await callDocumentServer('getForgotten', docId);
|
||||||
const docServiceUrl = process.env.REACT_APP_DOCSERVICE_URL || 'http://localhost:8000';
|
|
||||||
const response = await safeFetch(`${docServiceUrl}/coauthoring/CommandService.ashx`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
c: 'getForgotten',
|
|
||||||
key: docId
|
|
||||||
})
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 404) throw new Error('File not found');
|
|
||||||
throw new Error('Failed to fetch forgotten file');
|
|
||||||
}
|
|
||||||
const result = await response.json();
|
|
||||||
return {
|
return {
|
||||||
docId: docId,
|
docId: docId,
|
||||||
url: result.url,
|
url: result.url,
|
||||||
|
|||||||
4
AdminPanel/client/src/assets/Download.svg
Normal file
4
AdminPanel/client/src/assets/Download.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 16V13H7.2V11H5C4.44772 11 4 11.4477 4 12V17C4 17.5523 4.44772 18 5 18H19C19.5523 18 20 17.5523 20 17V12C20 11.4477 19.5523 11 19 11H16.8V13H18V16H6Z" fill="#444444"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.9996 8.5L15.2025 8.5C15.7911 8.5 16 8.49063 16 8.75503C16 8.94763 15.8986 9.29489 15.6987 9.4874L12.6667 12.6478C12.179 13.1174 11.821 13.1174 11.3333 12.6478L8.30133 9.4874C8.10142 9.29489 8 8.94763 8 8.75503C8 8.49056 8.20889 8.5 8.79749 8.5C8.79749 8.5 9.4909 8.5 9.99708 8.5C9.99708 8.28638 9.99708 5 9.99708 5L13.9996 5C13.9996 5 13.9996 8.29916 13.9996 8.5Z" fill="#444444"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 757 B |
@ -1,5 +1,7 @@
|
|||||||
import React, {useState, useEffect} from 'react';
|
import React, {useState, useEffect} from 'react';
|
||||||
import {getForgottenList, getForgotten} from '../../api';
|
import {getForgottenList, getForgotten} from '../../api';
|
||||||
|
import DownloadIcon from '../../assets/Download.svg';
|
||||||
|
import styles from './Forgotten.module.scss';
|
||||||
|
|
||||||
const Forgotten = () => {
|
const Forgotten = () => {
|
||||||
const [forgottenFiles, setForgottenFiles] = useState([]);
|
const [forgottenFiles, setForgottenFiles] = useState([]);
|
||||||
@ -41,16 +43,13 @@ const Forgotten = () => {
|
|||||||
try {
|
try {
|
||||||
console.log('Downloading file:', file.name);
|
console.log('Downloading file:', file.name);
|
||||||
|
|
||||||
// Add file to downloading set
|
|
||||||
setDownloadingFiles(prev => new Set(prev).add(file.key));
|
setDownloadingFiles(prev => new Set(prev).add(file.key));
|
||||||
|
|
||||||
const result = await getForgotten(file.key);
|
const result = await getForgotten(file.key);
|
||||||
|
|
||||||
if (result.url) {
|
if (result.url) {
|
||||||
// Create a temporary link element and trigger download
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = result.url;
|
link.href = result.url;
|
||||||
// Use "output" as the filename with the proper extension
|
|
||||||
const fileExtension = file.name.split('.').pop() || 'docx';
|
const fileExtension = file.name.split('.').pop() || 'docx';
|
||||||
link.download = 'output.docx';
|
link.download = 'output.docx';
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
@ -67,7 +66,6 @@ const Forgotten = () => {
|
|||||||
console.error('Error downloading file:', err);
|
console.error('Error downloading file:', err);
|
||||||
setError(`Failed to download ${file.name}: ${err.message}`);
|
setError(`Failed to download ${file.name}: ${err.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
// Remove file from downloading set
|
|
||||||
setDownloadingFiles(prev => {
|
setDownloadingFiles(prev => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
newSet.delete(file.key);
|
newSet.delete(file.key);
|
||||||
@ -76,79 +74,48 @@ const Forgotten = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="forgotten-page">
|
|
||||||
<div className="page-header">
|
|
||||||
<h1>Forgotten Files</h1>
|
|
||||||
</div>
|
|
||||||
<div className="loading">
|
|
||||||
<div className="spinner"></div>
|
|
||||||
<p>Loading forgotten files...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="forgotten-page">
|
<div className={styles.forgottenPage}>
|
||||||
<div className="page-header">
|
<div className={styles.pageHeader}>
|
||||||
<h1>Forgotten Files</h1>
|
<h1>Forgotten Files</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="error">
|
Failed to load forgotten files
|
||||||
<p>{error}</p>
|
|
||||||
<button onClick={loadForgottenFiles} className="retry-btn">
|
|
||||||
Retry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="forgotten-page">
|
<div className={styles.forgottenPage}>
|
||||||
<div className="page-header">
|
<div className={styles.pageHeader}>
|
||||||
<h1>Forgotten Files</h1>
|
<h1>Forgotten Files</h1>
|
||||||
<button onClick={loadForgottenFiles} className="refresh-btn">
|
|
||||||
Refresh
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="forgotten-content">
|
<div className={styles.forgottenContent}>
|
||||||
{forgottenFiles.length === 0 ? (
|
{forgottenFiles.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className={styles.emptyState}>
|
||||||
<p>No forgotten files found.</p>
|
<p>No forgotten files found.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="files-list">
|
<div className={styles.filesList}>
|
||||||
<div className="files-header">
|
|
||||||
<span>File Name</span>
|
|
||||||
<span>Size</span>
|
|
||||||
<span>Modified</span>
|
|
||||||
<span>Actions</span>
|
|
||||||
</div>
|
|
||||||
{forgottenFiles.map((file, index) => (
|
{forgottenFiles.map((file, index) => (
|
||||||
<div key={index} className="file-item">
|
<div key={index} className={styles.fileRow}>
|
||||||
<span className="file-name" title={file.name}>
|
<span className={styles.fileName} title={file.name}>
|
||||||
{file.name}
|
{file.name}
|
||||||
</span>
|
</span>
|
||||||
<span className="file-size">
|
<button
|
||||||
{file.size ? formatFileSize(file.size) : '-'}
|
className={styles.downloadBtn}
|
||||||
</span>
|
onClick={() => handleDownload(file)}
|
||||||
<span className="file-date">
|
disabled={downloadingFiles.has(file.key)}
|
||||||
{file.modified ? formatDate(file.modified) : '-'}
|
title="Download file"
|
||||||
</span>
|
>
|
||||||
<div className="file-actions">
|
<img
|
||||||
<button
|
src={DownloadIcon}
|
||||||
className="download-btn"
|
alt="Download"
|
||||||
onClick={() => handleDownload(file)}
|
className={styles.downloadIcon}
|
||||||
disabled={downloadingFiles.has(file.key)}
|
style={{opacity: downloadingFiles.has(file.key) ? 0.5 : 1}}
|
||||||
title="Download file"
|
/>
|
||||||
>
|
</button>
|
||||||
{downloadingFiles.has(file.key) ? 'Downloading...' : 'Download'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
165
AdminPanel/client/src/pages/Forgotten/Forgotten.module.scss
Normal file
165
AdminPanel/client/src/pages/Forgotten/Forgotten.module.scss
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
.forgottenPage {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.pageHeader {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #007bff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #dc3545;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retryBtn {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyState {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filesList {
|
||||||
|
.fileRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 0;
|
||||||
|
border-bottom: 1px solid rgb(226, 226, 226);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileName {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #333;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadBtn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadIcon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive design
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.forgottenPage {
|
||||||
|
// padding: 15px;
|
||||||
|
|
||||||
|
.pageHeader {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filesList {
|
||||||
|
.fileRow {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.fileName {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadBtn {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,225 +0,0 @@
|
|||||||
.forgotten-page {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
border-bottom: 2px solid #e0e0e0;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: 4px solid #f3f3f3;
|
|
||||||
border-top: 4px solid #007bff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 0 auto 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #666;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #dc3545;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn {
|
|
||||||
background: #dc3545;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #c82333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #666;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-list {
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.files-header {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2fr 1fr 1.5fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 15px 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2fr 1fr 1.5fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
align-items: center;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-size {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-date {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
background: #28a745;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #218838;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background: #6c757d;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsive design
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.forgotten-page {
|
|
||||||
padding: 15px;
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-list {
|
|
||||||
.files-header,
|
|
||||||
.file-item {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-size,
|
|
||||||
.file-date {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-actions {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -236,7 +236,7 @@ router.post('/generate-docserver-token', requireAuth, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
ctx.initFromRequest(req);
|
ctx.initFromRequest(req);
|
||||||
|
|
||||||
const {document, editorConfig} = req.body;
|
const {document, editorConfig, command} = req.body;
|
||||||
|
|
||||||
if (!document || !editorConfig) {
|
if (!document || !editorConfig) {
|
||||||
return res.status(400).json({error: 'Document and editorConfig are required'});
|
return res.status(400).json({error: 'Document and editorConfig are required'});
|
||||||
@ -275,6 +275,15 @@ router.post('/generate-docserver-token', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add command parameter if provided (required for forgotten files operations)
|
||||||
|
if (command) {
|
||||||
|
payload.c = command;
|
||||||
|
// For forgotten files operations, also add the key at root level
|
||||||
|
if (command === 'getForgotten' || command === 'getForgottenList') {
|
||||||
|
payload.key = document.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', 'HS256');
|
const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', 'HS256');
|
||||||
const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', '5m');
|
const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', '5m');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user