feat: multi-vues workspace + preview panel (tree/list/details/title/content)
- Sélecteur de vue avec 5 modes: Tree, List, Details, Title, Content - Vue List: compacte (Name, Type, Size, Modified) - Vue Details: table complète (Path, Author, Tags) - Vue Title: grille de cartes avec icônes - Vue Content: liste avec aperçu inline - Preview Panel: panneau latéral avec rendu contenu + métadonnées + actions - Animation slide-in/out pour le preview panel - Tri par date (Oldest/Newest)
This commit is contained in:
@@ -1,5 +1,22 @@
|
||||
# Changelog — FlowDeck
|
||||
|
||||
## v2.3.0 (2026-07-12) — Multi-Vues Workspace & Preview Panel
|
||||
|
||||
### Added
|
||||
- **Sélecteur de vue**: 🌳 Tree, 📋 List, 📊 Details, 🏷️ Title, 📝 Content — 5 modes d'affichage
|
||||
- **Vue List**: liste compacte avec Name, Type, Size, Modified
|
||||
- **Vue Details**: table complète avec Path, Author, Tags
|
||||
- **Vue Title**: grille de cartes avec icônes et métadonnées
|
||||
- **Vue Content**: liste avec aperçu inline (taille ou nombre d'items)
|
||||
- **Preview Panel**: panneau latéral droit avec contenu du fichier sélectionné
|
||||
- Header avec icône + titre + bouton fermer
|
||||
- Métadonnées (type, taille, date)
|
||||
- Tags affichés en chips
|
||||
- Corps avec rendu du contenu (blocks JSON → HTML, texte brut)
|
||||
- Actions: Open, Rename, Delete
|
||||
- Animation slide-in/out
|
||||
- **Tri par date**: options Oldest/Newest dans le selecteur de tri
|
||||
|
||||
## v2.2.0 (2026-07-12) — Tags & Recherche Avancée
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ async def lifespan(_app: FastAPI):
|
||||
|
||||
app = FastAPI(
|
||||
title="FlowDeck",
|
||||
version="2.2.0",
|
||||
version="2.3.0",
|
||||
docs_url="/docs" if settings.log_level == "DEBUG" else None,
|
||||
redoc_url=None,
|
||||
lifespan=lifespan,
|
||||
|
||||
@@ -221,6 +221,55 @@
|
||||
.ws-table-compact th{text-align:left;padding:6px 10px;color:var(--text-tertiary);font-weight:500;border-bottom:2px solid var(--border);font-size:11px;text-transform:uppercase;}
|
||||
.ws-table-compact td{padding:5px 10px;border-bottom:1px solid var(--border);}
|
||||
.ws-table-compact tr:hover td{background:var(--bg-hover);}
|
||||
|
||||
/* View selector */
|
||||
.view-selector{display:flex;gap:2px;background:var(--bg-tertiary);border-radius:8px;padding:2px;}
|
||||
.view-btn{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;background:none;border:none;border-radius:6px;cursor:pointer;font-size:14px;color:var(--text-tertiary);transition:all 150ms;}
|
||||
.view-btn:hover{background:var(--bg-hover);color:var(--text);}
|
||||
.view-btn.active{background:var(--accent);color:#fff;}
|
||||
|
||||
/* Title grid */
|
||||
.title-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px;margin-top:8px;}
|
||||
.title-card{display:flex;flex-direction:column;align-items:center;padding:16px 8px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all 150ms;min-height:100px;justify-content:center;}
|
||||
.title-card:hover{background:var(--bg-hover);border-color:var(--accent);}
|
||||
.title-card.selected-card{background:rgba(35,131,226,.1);border-color:var(--accent);}
|
||||
.title-card-icon{font-size:28px;margin-bottom:8px;}
|
||||
.title-card-name{font-size:13px;font-weight:500;text-align:center;word-break:break-word;max-width:100%;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}
|
||||
.title-card-meta{display:flex;gap:8px;margin-top:6px;font-size:11px;color:var(--text-tertiary);}
|
||||
|
||||
/* Content list view */
|
||||
.content-list{display:flex;flex-direction:column;gap:4px;margin-top:8px;}
|
||||
.content-item{padding:10px 12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:all 150ms;}
|
||||
.content-item:hover{background:var(--bg-hover);border-color:var(--accent);}
|
||||
.content-item.selected-row{background:rgba(35,131,226,.08);border-color:var(--accent);}
|
||||
.content-item-header{display:flex;align-items:center;gap:8px;font-size:14px;}
|
||||
.content-item-preview{font-size:12px;color:var(--text-secondary);margin-top:6px;padding-left:24px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:600px;}
|
||||
.content-item-tags{display:flex;gap:4px;margin-top:6px;padding-left:24px;}
|
||||
|
||||
/* Selected row highlight */
|
||||
.selected-row{background:rgba(35,131,226,.08)!important;}
|
||||
|
||||
/* Preview panel */
|
||||
.preview-panel{position:fixed;top:44px;right:0;width:380px;max-width:90vw;height:calc(100vh - 44px);background:var(--bg);border-left:1px solid var(--border-strong);box-shadow:-4px 0 20px rgba(0,0,0,.3);z-index:200;display:flex;flex-direction:column;overflow:hidden;}
|
||||
@media(max-width:768px){.preview-panel{width:100vw;max-width:100vw;}}
|
||||
.preview-panel-header{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--border);flex-shrink:0;}
|
||||
.preview-panel-title{font-size:15px;font-weight:600;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.preview-close{background:none;border:none;color:var(--text-tertiary);font-size:20px;cursor:pointer;padding:2px 6px;border-radius:4px;line-height:1;}
|
||||
.preview-close:hover{color:var(--text);background:var(--bg-hover);}
|
||||
.preview-panel-meta{display:flex;gap:12px;padding:8px 16px;font-size:12px;color:var(--text-tertiary);border-bottom:1px solid var(--border);flex-shrink:0;}
|
||||
.preview-panel-tags{display:flex;gap:4px;flex-wrap:wrap;padding:8px 16px;border-bottom:1px solid var(--border);flex-shrink:0;}
|
||||
.preview-panel-body{flex:1;overflow:auto;padding:16px;font-size:13px;line-height:1.6;color:var(--text);}
|
||||
.preview-panel-body pre{background:var(--bg-tertiary);border-radius:8px;padding:12px;overflow:auto;font-size:12px;font-family:'Fira Code',monospace;white-space:pre-wrap;word-wrap:break-word;}
|
||||
.preview-panel-body img{max-width:100%;border-radius:8px;}
|
||||
.preview-panel-actions{display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--border);flex-shrink:0;}
|
||||
|
||||
/* Transitions */
|
||||
.preview-enter{transition:transform .2s ease,opacity .2s ease;}
|
||||
.preview-enter-start{transform:translateX(100%);opacity:0;}
|
||||
.preview-enter-end{transform:translateX(0);opacity:1;}
|
||||
.preview-leave{transition:transform .15s ease,opacity .15s ease;}
|
||||
.preview-leave-start{transform:translateX(0);opacity:1;}
|
||||
.preview-leave-end{transform:translateX(100%);opacity:0;}
|
||||
</style>
|
||||
|
||||
<div class="ws-layout" x-data="wsInit()"
|
||||
@@ -292,17 +341,23 @@
|
||||
<div class="ws-toolbar">
|
||||
<button class="btn-icon" @click="goBack()" title="Back" :disabled="!canGoBack">←</button>
|
||||
<button class="btn-icon" @click="goForward()" title="Forward" :disabled="!canGoForward">→</button>
|
||||
<button class="btn-icon" @click="viewMode = (viewMode==='tree'?'table':'tree')" :title="viewMode==='tree'?'Table view':'Tree view'">
|
||||
<span x-text="viewMode==='tree'?'📋':'🌳'"></span>
|
||||
</button>
|
||||
<div class="view-selector">
|
||||
<button class="view-btn" :class="{ active: viewMode==='tree' }" @click="viewMode='tree'; previewItem=null" title="Tree view">🌳</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='list' }" @click="viewMode='list'; previewItem=null" title="List view">📋</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='details' }" @click="viewMode='details'; previewItem=null" title="Details view">📊</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='title' }" @click="viewMode='title'; previewItem=null" title="Title cards">🏷️</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='content' }" @click="viewMode='content'; previewItem=null" title="Content preview">📝</button>
|
||||
</div>
|
||||
<select class="sort-select" x-model="sortBy" @change="doSort()">
|
||||
<option value="">Sort by...</option>
|
||||
<option value="name-asc">Name A→Z</option>
|
||||
<option value="name-desc">Name Z→A</option>
|
||||
<option value="type">Type</option>
|
||||
<option value="date-asc">Oldest</option>
|
||||
<option value="date-desc">Newest</option>
|
||||
</select>
|
||||
<button class="btn-icon" @click="expandAll()" title="Expand all">⊞</button>
|
||||
<button class="btn-icon" @click="collapseAll()" title="Collapse all">⊟</button>
|
||||
<button class="btn-icon" @click="expandAll()" title="Expand all" x-show="viewMode==='tree'">⊞</button>
|
||||
<button class="btn-icon" @click="collapseAll()" title="Collapse all" x-show="viewMode==='tree'">⊟</button>
|
||||
<button class="btn-icon" @click="openModal('file')" title="New file">📄</button>
|
||||
<button class="btn-icon" @click="openModal('folder')" title="New folder">📁</button>
|
||||
</div>
|
||||
@@ -316,6 +371,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Tree -->
|
||||
<div x-show="viewMode==='tree'" style="display:contents;">
|
||||
<!-- Search bar -->
|
||||
<div class="search-bar" x-show="tree.length>0 || searchQuery">
|
||||
<span class="search-icon">🔍</span>
|
||||
@@ -408,6 +464,7 @@
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tag filter bar -->
|
||||
<div class="tag-filter-bar" x-show="workspaceTags && workspaceTags.length > 0">
|
||||
@@ -476,27 +533,106 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Regular table view (toggle) -->
|
||||
<table class="ws-table" x-show="viewMode==='table' && !searchQuery && !filterType && !tagFilter && displayTree.length>0">
|
||||
<!-- List view — compact flat list -->
|
||||
<table class="ws-table-compact" x-show="viewMode==='list' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<thead><tr>
|
||||
<th style="width:30px"><input type="checkbox" @click="selectAll($event)"></th>
|
||||
<th>Name</th><th>Type</th><th style="width:100px">Size</th>
|
||||
<th style="width:24px"></th>
|
||||
<th>Name</th>
|
||||
<th style="width:80px">Type</th>
|
||||
<th style="width:80px">Size</th>
|
||||
<th style="width:100px">Modified</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<template x-for="node in flatTree" :key="'t'+node.id">
|
||||
<tr :class="{ selected: !!selectedIds[node.id] }">
|
||||
<td><input type="checkbox" :checked="!!selectedIds[node.id]" @click.stop="toggleSelect(node, $event)"></td>
|
||||
<td>
|
||||
<span class="table-icon" x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span>
|
||||
<span class="table-name" @click="node.is_folder ? navigateToFolder(node.id) : openPage(node.id)" x-text="node.name"></span>
|
||||
</td>
|
||||
<td x-text="node.is_folder ? 'Folder' : 'File'"></td>
|
||||
<td class="table-meta" x-text="node.is_folder ? (node.child_count ? node.child_count+' items' : 'Empty') : (node.size_display || '—')"></td>
|
||||
<template x-for="node in flatTree" :key="'l'+node.id">
|
||||
<tr @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" style="cursor:pointer">
|
||||
<td><span x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span></td>
|
||||
<td class="col-name" x-text="node.name"></td>
|
||||
<td style="font-size:12px;color:var(--text-tertiary)" x-text="node.is_folder ? '📁 Folder' : _fileTypeLabel(node.name, node.content_format)"></td>
|
||||
<td style="font-size:12px;color:var(--text-secondary)" x-text="node.is_folder ? (node.child_count||0)+' items' : (node.size_display||'—')"></td>
|
||||
<td style="font-size:12px;color:var(--text-tertiary)" x-text="_formatDate(node.updated_at)"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Details view — full table with all columns -->
|
||||
<table class="ws-table-enhanced" x-show="viewMode==='details' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<thead><tr>
|
||||
<th style="width:24px"></th>
|
||||
<th>Name</th>
|
||||
<th>Path</th>
|
||||
<th>Size</th>
|
||||
<th>Modified</th>
|
||||
<th>Type</th>
|
||||
<th>Author</th>
|
||||
<th>Tags</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<template x-for="node in flatTree" :key="'d'+node.id">
|
||||
<tr @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" style="cursor:pointer"
|
||||
:class="{ 'selected-row': previewItem && previewItem.id === node.id }">
|
||||
<td><span class="table-icon" x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span></td>
|
||||
<td class="col-name" x-text="node.name"></td>
|
||||
<td class="col-path" x-text="node._path || '—'"></td>
|
||||
<td class="col-size" x-text="node.size_display || '—'"></td>
|
||||
<td class="col-date" x-text="_formatDate(node.updated_at)"></td>
|
||||
<td class="col-type" x-text="node.is_folder ? '📁 Folder' : _fileTypeLabel(node.name, node.content_format)"></td>
|
||||
<td class="col-author" x-text="node.author || '—'"></td>
|
||||
<td class="col-tags">
|
||||
<span style="display:flex;align-items:center;gap:2px;flex-wrap:wrap;">
|
||||
<template x-for="t in (node.tags||[])" :key="'dtag'+t.id">
|
||||
<span class="tag-chip"><span x-text="t.name"></span></span>
|
||||
</template>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Title view — card grid -->
|
||||
<div class="title-grid" x-show="viewMode==='title' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<template x-for="node in flatTree" :key="'g'+node.id">
|
||||
<div class="title-card" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)"
|
||||
:class="{ 'selected-card': previewItem && previewItem.id === node.id }">
|
||||
<div class="title-card-icon" x-text="node.is_folder ? '📁' : _fileIcon(node.name, node.is_folder, node.content_format)"></div>
|
||||
<div class="title-card-name" x-text="node.name"></div>
|
||||
<div class="title-card-meta">
|
||||
<span x-text="_formatDate(node.updated_at)"></span>
|
||||
<span x-text="node.size_display || '—'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Content view — list with inline preview -->
|
||||
<div class="content-list" x-show="viewMode==='content' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<template x-for="node in flatTree" :key="'c'+node.id">
|
||||
<div class="content-item" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)"
|
||||
:class="{ 'selected-row': previewItem && previewItem.id === node.id }">
|
||||
<div class="content-item-header">
|
||||
<span x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span>
|
||||
<span class="col-name" x-text="node.name"></span>
|
||||
<span style="font-size:11px;color:var(--text-tertiary);margin-left:auto" x-text="_formatDate(node.updated_at)"></span>
|
||||
</div>
|
||||
<div class="content-item-preview" x-text="node.is_folder ? (node.child_count||0)+' items' : (node.size_display||'—')"></div>
|
||||
<div class="content-item-tags" x-show="(node.tags||[]).length > 0">
|
||||
<template x-for="t in (node.tags||[])" :key="'ctag'+t.id">
|
||||
<span class="tag-chip"><span x-text="t.name"></span></span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Old table view kept for backward compat, hidden -->
|
||||
<table class="ws-table" x-show="false" style="display:none">
|
||||
<thead><tr><th>Name</th><th>Type</th><th>Size</th></tr></thead>
|
||||
<tbody><template x-for="node in flatTree" :key="'t'+node.id">
|
||||
<tr><td x-text="node.name"></td></tr>
|
||||
</template></tbody>
|
||||
</table>
|
||||
|
||||
<!-- Empty -->
|
||||
<div class="empty-state" x-show="tree.length===0&&loaded">
|
||||
<div class="empty-icon">📁</div>
|
||||
@@ -563,6 +699,31 @@
|
||||
<div class="file-preview-body" x-html="previewContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Panel — right side panel for file preview -->
|
||||
<div class="preview-panel" x-show="previewItem && (viewMode==='list' || viewMode==='details' || viewMode==='title' || viewMode==='content')" x-transition:enter="preview-enter" x-transition:leave="preview-leave">
|
||||
<div class="preview-panel-header">
|
||||
<span x-text="_fileIcon(previewItem.name, previewItem.is_folder, previewItem.content_format)"></span>
|
||||
<span class="preview-panel-title" x-text="previewItem.name"></span>
|
||||
<button class="preview-close" @click="previewItem=null" title="Close preview">×</button>
|
||||
</div>
|
||||
<div class="preview-panel-meta">
|
||||
<span x-text="previewItem.is_folder ? '📁 Folder' : _fileTypeLabel(previewItem.name, previewItem.content_format)"></span>
|
||||
<span x-text="previewItem.size_display || '—'"></span>
|
||||
<span x-text="_formatDate(previewItem.updated_at)"></span>
|
||||
</div>
|
||||
<div class="preview-panel-tags" x-show="(previewItem.tags||[]).length > 0">
|
||||
<template x-for="t in (previewItem.tags||[])" :key="'ptag'+t.id">
|
||||
<span class="tag-chip"><span x-text="t.name"></span></span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="preview-panel-body" x-html="previewPanelContent || '<div style=text-align:center;padding:40px;color:var(--text-tertiary)>Loading preview...</div>'"></div>
|
||||
<div class="preview-panel-actions">
|
||||
<button class="btn btn-secondary" @click="openPage(previewItem.id)">📄 Open</button>
|
||||
<button class="btn btn-secondary" @click="startRename(previewItem)">✏️ Rename</button>
|
||||
<button class="btn btn-danger" @click="deleteWithUndo(previewItem)">🗑 Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -608,6 +769,10 @@ document.addEventListener('alpine:init', function() {
|
||||
tagFilter: '',
|
||||
tagInputId: null,
|
||||
|
||||
// Preview panel
|
||||
previewItem: null,
|
||||
previewPanelContent: '',
|
||||
|
||||
// Multi-select
|
||||
selectedIds: {},
|
||||
selectedIdsCount: 0,
|
||||
@@ -830,11 +995,66 @@ document.addEventListener('alpine:init', function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
// ── Preview panel ──
|
||||
selectForPreview(node) {
|
||||
this.previewItem = node;
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:40px;color:var(--text-tertiary)">Loading...</div>';
|
||||
this._loadPreviewContent(node);
|
||||
},
|
||||
|
||||
async _loadPreviewContent(node) {
|
||||
if (node.is_folder) {
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:40px;color:var(--text-secondary)">'
|
||||
+ '<span style="font-size:48px;display:block;margin-bottom:12px">📁</span>'
|
||||
+ '<div style="font-size:14px">' + (node.child_count||0) + ' item(s)</div>'
|
||||
+ '<button class="btn btn-secondary" style="margin-top:16px" onclick="window.location=\'/local-workspace?folder=' + node.id + '\'">Open folder</button>'
|
||||
+ '</div>';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var r = await this._fetchRetry('/api/local-workspace/page-content/' + node.id, true);
|
||||
var content = r.content || '';
|
||||
var format = r.format || '';
|
||||
var preview = '';
|
||||
if (format === 'file') {
|
||||
preview = '<div style="text-align:center;padding:20px;color:var(--text-secondary)">'
|
||||
+ '<span style="font-size:48px;display:block;margin-bottom:8px">📄</span>'
|
||||
+ 'Uploaded file — open to view</div>';
|
||||
} else if (format === 'blocks') {
|
||||
try {
|
||||
var blocks = typeof content === 'string' ? JSON.parse(content) : content;
|
||||
if (blocks && blocks.blocks) {
|
||||
preview = blocks.blocks.slice(0, 30).map(function(b) {
|
||||
var txt = b.content || b.text || (typeof b === 'string' ? b : '');
|
||||
return '<div style="margin-bottom:6px;line-height:1.5">' + this._escHtml(txt) + '</div>';
|
||||
}, this).join('');
|
||||
}
|
||||
} catch(e) { preview = '<pre>' + this._escHtml(String(content).substring(0, 2000)) + '</pre>'; }
|
||||
} else {
|
||||
preview = '<pre>' + this._escHtml(String(content).substring(0, 2000)) + '</pre>';
|
||||
}
|
||||
this.previewPanelContent = preview || '<div style="color:var(--text-tertiary);text-align:center;padding:20px">(empty)</div>';
|
||||
} catch(e) {
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:20px;color:var(--text-tertiary)">Preview not available</div>';
|
||||
}
|
||||
},
|
||||
|
||||
_escHtml(s) {
|
||||
return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
},
|
||||
|
||||
// ── Content snippet for content view ──
|
||||
get contentSnippetMap() {
|
||||
// Preload snippets? Too expensive. Return empty, load on hover.
|
||||
return {};
|
||||
},
|
||||
|
||||
_markTree(nodes, depth) {
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
var n = nodes[i];
|
||||
n.depth = depth;
|
||||
n.db_id = n.id;
|
||||
n._path = '/' + n.name;
|
||||
if (n.children && n.children.length > 0) {
|
||||
this._markTree(n.children, depth + 1);
|
||||
}
|
||||
@@ -1060,6 +1280,8 @@ document.addEventListener('alpine:init', function() {
|
||||
var sorted = this.tree.slice();
|
||||
if (s === 'name-asc') sorted.sort(function(a,b) { return (a.name||'').localeCompare(b.name||''); });
|
||||
else if (s === 'name-desc') sorted.sort(function(a,b) { return (b.name||'').localeCompare(a.name||''); });
|
||||
else if (s === 'date-asc') sorted.sort(function(a,b) { return (a.updated_at||'').localeCompare(b.updated_at||''); });
|
||||
else if (s === 'date-desc') sorted.sort(function(a,b) { return (b.updated_at||'').localeCompare(a.updated_at||''); });
|
||||
else if (s === 'type') sorted.sort(function(a,b) { return (a.is_folder===b.is_folder) ? 0 : a.is_folder ? -1 : 1; });
|
||||
this.displayTree = sorted;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user