luci-app-filemanager: upgrades

-use the new lstat based file listing which is symlink aware
-convert spaces to tabs
-title action buttons
-fixed formatted size prefixes
-ES6 conversion
-CSS cleanup
-code reduction
-permissions column

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
Paul Donald
2025-12-03 01:12:14 +01:00
parent bf8fe5e6ea
commit a62518d1c6
4 changed files with 1181 additions and 1399 deletions

View File

@@ -575,7 +575,7 @@ class HexEditor {
pattern = document.getElementById('hexedit-search-regex').value.trim();
break;
default:
console.warn(`Unknown search type: ${searchType}`);
// console.warn(`Unknown search type: ${searchType}`);
pattern = '';
break;
}
@@ -588,7 +588,7 @@ class HexEditor {
if (!pattern) {
// Update status field to 0/0
this.updateSearchStatus(searchType, 0, 0);
console.log('No search pattern entered.');
// console.log('No search pattern entered.');
return;
}
@@ -602,7 +602,7 @@ class HexEditor {
this.searchRegex(pattern);
}
} catch (error) {
console.log(`Error during search: ${error.message}`);
// console.log(`Error during search: ${error.message}`);
// Update status field to 0/0 on error
this.updateSearchStatus(searchType, 0, 0);
return;
@@ -615,11 +615,11 @@ class HexEditor {
this.navigateToMatch(this.currentMatchIndex);
// Update status field with actual match count
this.updateSearchStatus(searchType, this.currentMatchIndex + 1, this.matches.length);
console.log(`Found ${this.matches.length} matches.`);
// console.log(`Found ${this.matches.length} matches.`);
} else {
// Update status field to 0/0 if no matches found
this.updateSearchStatus(searchType, 0, 0);
console.log('No matches found.');
// console.log('No matches found.');
}
}
@@ -654,13 +654,13 @@ class HexEditor {
if (this.matches.length === 0) {
// Update status field to 0/0 if no matches
this.updateSearchStatus(this.currentSearchType, 0, 0);
console.log('No matches to navigate.');
// console.log('No matches to navigate.');
return;
}
// Ensure matchIndex is within bounds
if (matchIndex < 0 || matchIndex >= this.matches.length) {
console.log('navigateToMatch: matchIndex out of bounds.');
// console.log('navigateToMatch: matchIndex out of bounds.');
return;
}
@@ -672,7 +672,7 @@ class HexEditor {
// Set selected index to the match start
this.setSelectedIndex(match.index);
console.log(`Navigated to match ${matchIndex + 1} at offset ${match.index.toString(16)}`);
// console.log(`Navigated to match ${matchIndex + 1} at offset ${match.index.toString(16)}`);
// Update status field
this.updateSearchStatus(this.currentSearchType, this.currentMatchIndex + 1, this.matches.length);
@@ -697,7 +697,7 @@ class HexEditor {
regex.lastIndex++;
}
}
console.log(`searchASCII: Found ${this.matches.length} matches.`);
// console.log(`searchASCII: Found ${this.matches.length} matches.`);
}
/**
@@ -733,7 +733,7 @@ class HexEditor {
});
}
}
console.log(`searchHEX: Found ${this.matches.length} matches.`);
// console.log(`searchHEX: Found ${this.matches.length} matches.`);
}
/**
@@ -757,7 +757,7 @@ class HexEditor {
regex.lastIndex++;
}
}
console.log(`searchRegex: Found ${this.matches.length} matches.`);
// console.log(`searchRegex: Found ${this.matches.length} matches.`);
}
/**
@@ -772,9 +772,9 @@ class HexEditor {
// Calculate new scroll position to ensure the matched line is visible
const newScrollTop = Math.max(0, (lineNumber * lineHeight) - ((this.visibleRows / 2) * lineHeight));
console.log(`scrollToMatch called with index: ${index}`);
console.log(`lineNumber: ${lineNumber}`);
console.log(`newScrollTop: ${newScrollTop}`);
// console.log(`scrollToMatch called with index: ${index}`);
// console.log(`lineNumber: ${lineNumber}`);
// console.log(`newScrollTop: ${newScrollTop}`);
// Update the scrollTop property to trigger handleScroll
this.hexeditContent.scrollTop = newScrollTop;
@@ -809,7 +809,7 @@ class HexEditor {
const containerHeight = this.hexeditContent.clientHeight;
this.visibleRows = Math.floor(containerHeight / lineHeight);
this.visibleByteCount = this.bytesPerRow * this.visibleRows;
console.log(`calculateVisibleRows: visibleRows=${this.visibleRows}, visibleByteCount=${this.visibleByteCount}`);
// console.log(`calculateVisibleRows: visibleRows=${this.visibleRows}, visibleByteCount=${this.visibleByteCount}`);
this.renderDom(); // Re-render to apply the new rows
}
@@ -821,7 +821,7 @@ class HexEditor {
setData(data) {
this.data = data;
this.totalRows = Math.ceil(this.data.length / this.bytesPerRow);
console.log(`setData: data length=${this.data.length}, totalRows=${this.totalRows}`);
// console.log(`setData: data length=${this.data.length}, totalRows=${this.totalRows}`);
this.calculateVisibleRows(); // Ensure visibleRows are calculated before rendering
}
@@ -845,13 +845,13 @@ class HexEditor {
const firstVisibleLine = Math.floor(scrollTop / lineHeight);
const newStartIndex = firstVisibleLine * this.bytesPerRow;
console.log(`handleScroll: scrollTop=${scrollTop}, firstVisibleLine=${firstVisibleLine}, newStartIndex=${newStartIndex}`);
// console.log(`handleScroll: scrollTop=${scrollTop}, firstVisibleLine=${firstVisibleLine}, newStartIndex=${newStartIndex}`);
// Update startIndex and re-render the DOM if necessary
if (newStartIndex !== this.startIndex) {
this.startIndex = newStartIndex;
this.renderDom(); // Re-render visible data
console.log(`handleScroll: Updated startIndex and rendered DOM.`);
// console.log(`handleScroll: Updated startIndex and rendered DOM.`);
}
}
@@ -1163,7 +1163,7 @@ class HexEditor {
*/
setSelectedIndex(index) {
this.selectedIndex = index;
console.log(`setSelectedIndex called with index: ${index}`);
// console.log(`setSelectedIndex called with index: ${index}`);
if (index !== null) {
// Calculate the line number of the selected index
@@ -1175,13 +1175,13 @@ class HexEditor {
const visibleStartLine = Math.floor(this.hexeditContent.scrollTop / lineHeight);
const visibleEndLine = visibleStartLine + this.visibleRows;
console.log(`setSelectedIndex: lineNumber=${lineNumber}, visibleStartLine=${visibleStartLine}, visibleEndLine=${visibleEndLine}`);
// console.log(`setSelectedIndex: lineNumber=${lineNumber}, visibleStartLine=${visibleStartLine}, visibleEndLine=${visibleEndLine}`);
// If the selected line is out of the visible range, update scrollTop
if (lineNumber < visibleStartLine || lineNumber >= visibleEndLine) {
const newScrollTop = Math.max(0, (lineNumber * lineHeight) - ((this.visibleRows / 2) * lineHeight));
this.hexeditContent.scrollTop = newScrollTop;
console.log(`setSelectedIndex: Updated scrollTop to ${this.hexeditContent.scrollTop}`);
// console.log(`setSelectedIndex: Updated scrollTop to ${this.hexeditContent.scrollTop}`);
}
}
@@ -1298,8 +1298,8 @@ return L.Class.extend({
/**
* Initializing new instance of HexEditor.
*
* @param {HTMLElement} hexeditDomObject - DOM элемент для HexEditor.
* @returns {HexEditor} - Новый экземпляр HexEditor.
* @param {HTMLElement} hexeditDomObject - DOM element for HexEditor.
* @returns {HexEditor} - new instance of HexEditor.
*/
initialize: function(hexeditDomObject) {
return new HexEditor(hexeditDomObject);

View File

@@ -13,87 +13,105 @@
* @returns {string} - The resulting HTML string.
*/
function parseMarkdown(markdown) {
// Split the input into lines
const lines = markdown.split('\n');
const html = [];
let inList = false;
let listType = ''; // 'ul' or 'ol'
lines.forEach((line) => {
let trimmedLine = line.trim();
// Stack of open lists: [{ type: "ul"|"ol", indent: number }]
const listStack = [];
if (trimmedLine === '') {
// Empty line signifies a new paragraph
if (inList) {
html.push(`</${listType}>`);
inList = false;
listType = '';
function closeListsToIndent(indent) {
while (listStack.length > 0 && listStack[listStack.length - 1].indent >= indent) {
const last = listStack.pop();
html.push(`</${last.type}>`);
}
return; // Skip adding empty lines to HTML
}
// Check for headings
if (/^###\s+(.*)/.test(trimmedLine)) {
const content = trimmedLine.replace(/^###\s+/, '');
html.push(`<h3>${escapeHtml(content)}</h3>`);
return;
} else if (/^##\s+(.*)/.test(trimmedLine)) {
const content = trimmedLine.replace(/^##\s+/, '');
html.push(`<h2>${escapeHtml(content)}</h2>`);
return;
} else if (/^#\s+(.*)/.test(trimmedLine)) {
const content = trimmedLine.replace(/^#\s+/, '');
html.push(`<h1>${escapeHtml(content)}</h1>`);
function openList(type, indent, startNumber = null) {
listStack.push({ type, indent });
if (type === "ol" && startNumber != null && startNumber !== 1)
html.push(`<ol start="${startNumber}">`);
else
html.push(`<${type}>`);
}
lines.forEach(line => {
// Detect indentation level (2 spaces = one indent)
const indentSpaces = line.match(/^ */)[0].length;
const indent = Math.floor(indentSpaces / 2);
const trimmed = line.trim();
if (trimmed === "") {
// Close all lists for blank lines, do NOT output <p>
closeListsToIndent(0);
return;
}
// Check for ordered lists
let orderedMatch = trimmedLine.match(/^(\d+)\.\s+(.*)/);
if (orderedMatch) {
const [, number, content] = orderedMatch;
if (!inList || listType !== 'ol') {
if (inList) {
html.push(`</${listType}>`);
// --------
// Headings
// --------
if (/^###\s+/.test(trimmed)) {
closeListsToIndent(0);
html.push(`<h3>${escapeHtml(trimmed.replace(/^###\s+/, ''))}</h3>`);
return;
}
html.push('<ol>');
inList = true;
listType = 'ol';
if (/^##\s+/.test(trimmed)) {
closeListsToIndent(0);
html.push(`<h2>${escapeHtml(trimmed.replace(/^##\s+/, ''))}</h2>`);
return;
}
if (/^#\s+/.test(trimmed)) {
closeListsToIndent(0);
html.push(`<h1>${escapeHtml(trimmed.replace(/^#\s+/, ''))}</h1>`);
return;
}
// ------------------------
// Ordered lists: "N. text"
// ------------------------
let mOrdered = trimmed.match(/^(\d+)\.\s+(.*)/);
if (mOrdered) {
const num = parseInt(mOrdered[1], 10);
const content = mOrdered[2];
const last = listStack[listStack.length - 1];
if (!last || last.indent < indent || last.type !== "ol") {
// NEW ordered list
closeListsToIndent(indent);
openList("ol", indent, num);
}
// ELSE: same indent, same list → continue existing OL without closing/opening
html.push(`<li>${parseInlineMarkdown(escapeHtml(content))}</li>`);
return;
}
// Check for unordered lists
let unorderedMatch = trimmedLine.match(/^[-*]\s+(.*)/);
if (unorderedMatch) {
const content = unorderedMatch[1];
if (!inList || listType !== 'ul') {
if (inList) {
html.push(`</${listType}>`);
}
html.push('<ul>');
inList = true;
listType = 'ul';
// -------------------------------------
// Unordered lists: "- text" or "* text"
// -------------------------------------
let mUnordered = trimmed.match(/^[-*]\s+(.*)/);
if (mUnordered) {
const content = mUnordered[1];
const last = listStack[listStack.length - 1];
if (!last || last.indent < indent || last.type !== "ul") {
closeListsToIndent(indent);
openList("ul", indent);
}
html.push(`<li>${parseInlineMarkdown(escapeHtml(content))}</li>`);
return;
}
// If currently inside a list but the line doesn't match a list item, close the list
if (inList) {
html.push(`</${listType}>`);
inList = false;
listType = '';
}
// Regular paragraph
html.push(`<p>${parseInlineMarkdown(escapeHtml(trimmedLine))}</p>`);
// ---------
// Paragraph
// ---------
closeListsToIndent(0);
html.push(`<p>${parseInlineMarkdown(escapeHtml(trimmed))}</p>`);
});
// Close any open list tags at the end
if (inList) {
html.push(`</${listType}>`);
}
// Close all remaining lists
closeListsToIndent(0);
return html.join('\n');
}

View File

@@ -17,7 +17,7 @@ The **LuCI OpenWrt File Manager** is a tool to navigate directories, manage file
2. **File Management**
- **View Files and Directories**: Display a list of files and folders within the current directory.
- **Navigate Directories**: Move into subdirectories or return to parent directories.
- **Resizable Columns**: Adjust the width of table columns to enhance readability and organization.
- **Resizeable Columns**: Adjust the width of table columns to enhance readability and organization.
- **Drag-and-Drop Uploads**: Upload files by simply dragging them into the designated area.
- **Upload via File Selector**: Use the "Upload File" button to select and upload files from your local machine.
- **Create New Files and Folders**:
@@ -118,7 +118,7 @@ The **LuCI OpenWrt File Manager** is a tool to navigate directories, manage file
## Additional Functionalities
- **Resizable Columns and Windows**: Enhance the interface's flexibility by resizing table columns and editor windows to match your workflow. The Help window starts at **650x600** pixels and can be adjusted as needed.
- **Resizeable Columns and Windows**: Enhance the interface's flexibility by resizing table columns and editor windows to match your workflow. The Help window starts at **650x600** pixels and can be adjusted as needed.
- **Responsive Design**: The application adapts to different screen sizes, ensuring usability across various devices.
- **Error Handling and Notifications**: Receive immediate feedback on actions, helping you stay informed about the status of your file management tasks.
- **Line Number Toggle**: Easily show or hide line numbers in the text editor to assist with content navigation.