Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ documents.label.spaceHomeDocuments=Documents
documents.label.access=Access
documents.label.parentFolders=Parent folders
documents.drawer.tree=Folder tree
documents.drawer.tree.searchDrives=Search drives
documents.label.extension.visibility=Access
documents.label.visibility.specific.collaborator=Only hosts and designated collaborators can view and edit
documents.label.visibility.all=All space member can edit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ attachments.drawer.templates.empty.canCreate=No templates yet. Create a Template
attachments.drawer.templates.createFolder=Create a Templates folder
attachments.drawer.templates.createFolder.success=Templates folder created. Add documents to it to offer them as templates.
attachments.drawer.templates.createFolder.error=Couldn’t create the Templates folder.
attachments.drawer.templates.search=Search templates
attachments.drawer.templates.noMatch=No template matches your filter
documents.attach.image.alt=Preview of {0}
attachments.drawer.done=Done
attachments.drawer.removeAll=Remove all
Expand Down
3 changes: 3 additions & 0 deletions documents-webapp/src/main/webapp/WEB-INF/gatein-resources.xml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@
<depends>
<module>documentsPreviewExtension</module>
</depends>
<depends>
<module>categoryVueComponents</module>
</depends>
</module>
</portlet>

Expand Down
45 changes: 41 additions & 4 deletions documents-webapp/src/main/webapp/skin/less/attachmentsApp.less
Original file line number Diff line number Diff line change
Expand Up @@ -255,18 +255,34 @@
color: @greyColorLighten1Default;
}

/* Full-width, stacked template cards (each nearly the whole drawer width).
The editable name field appears right under the selected card. */
/* Category chip row (search lives in the title bar, standard drawer pattern). */
.attachmentsTemplatesFilters {
.attachmentsTemplateCategories {
min-width: 0;
}
}

/* Responsive template grid: one full-width card per row in the normal (narrow)
drawer, flowing to several columns when the drawer is expanded — driven purely
by the container width via CSS grid auto-fill (no expand-state needed). The
editable name field appears right under the selected card. */
.attachmentsTemplatesGrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
align-items: start;

.attachmentsTemplateItem {
width: 100%;
min-width: 0;
}

.attachmentsTemplateCard {
width: 100%;
max-width: 100%;
outline: none;
border-radius: 8px;
/* Match the reused card's own 4px radius so the selected ring hugs its
corners instead of bowing out. */
border-radius: 4px;
transition: box-shadow .2s ease;

/* Selected template = a clear primary ring around the card. */
Expand Down Expand Up @@ -298,6 +314,27 @@
background-color: transparent !important;
}

/* Templates show the reused card's title band permanently (show-details) with the
file icon + name; also keep the info button visible without hovering (the card
otherwise only reveals it on hover). */
.attachmentsTemplateCard #attachment-info {
display: inline-flex !important;
}

/* ...but suppress the docs card-view selection checkbox and 3-dots actions menu —
neither makes sense when picking a template. */
.attachmentsTemplateCard .v-simple-checkbox,
.attachmentsTemplateCard .v-btn:has(.mdi-dots-vertical) {
display: none !important;
}

/* Expanded (full-screen) drawer: centre the content with side margins — like the
notification / favorite drawers — instead of spanning edge to edge. */
.createDocumentDrawerContent--expanded {
max-width: 1200px;
margin-inline: auto;
}

.driveExplorerDrawer {
.serverFiles {
.contentHeader {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,17 @@
<exo-drawer
ref="createDocumentDrawer"
class="createDocumentDrawer"
right>
right
allow-expand
:use-filter="showTemplateFilters"
:filter-placeholder="$t('attachments.drawer.templates.search')"
@filter-updated="onTemplateFilter"
@expand-updated="drawerExpanded = $event">
<template slot="title">
<!-- Back arrow + title. The filter icon (→ inline header search) and the
expand/full-screen icon are provided by exo-drawer itself (use-filter +
allow-expand), so they sit at the platform-standard location next to the
close button. -->
<div class="d-flex align-center">
<v-btn
icon
Expand All @@ -43,7 +52,9 @@
</div>
</template>
<template slot="content">
<div class="createDocumentDrawerContent pt-4 pa-4">
<div
class="createDocumentDrawerContent pt-4 pa-4"
:class="{ 'createDocumentDrawerContent--expanded': drawerExpanded }">
<!-- Section 1: create a blank document. Doc types are selectable cards
(hover + selected state, keyboard-focusable). Picking one reveals
the inline title input; the primary "Create" action lives in the
Expand Down Expand Up @@ -97,49 +108,71 @@
</div>
<div
v-else-if="templates.length"
class="attachmentsTemplatesGrid d-flex flex-column">
class="attachmentsTemplatesSection">
<!-- Category chip bar — only in the expanded (full-screen) view, where
there's room for it (self-hiding when templates aren't categorized).
Free-text search lives in the drawer title bar (filter icon). -->
<div
v-if="drawerExpanded"
class="attachmentsTemplatesFilters mb-3">
<categories-filter
v-model="selectedTemplateCategoryId"
:space-id="templatesOwnerId"
object-type="document"
hide-on-empty
class="attachmentsTemplateCategories" />
</div>
<!-- Full-width reused Documents-app cards, stacked. click.capture keeps
the card's hover info icon opening the info drawer, while a plain
click SELECTS the template and reveals an editable name field right
under that card (option B: name-first, pre-filled with the
template's name). The footer "Create" then creates it with that
name. -->
<div
v-for="template in templates"
:key="template.id"
class="attachmentsTemplateItem mb-3">
<div class="attachmentsTemplatesGrid">
<div
class="attachmentsTemplateCard clickable"
:class="{ 'attachmentsTemplateCard--selected': selectedTemplate && selectedTemplate.id === template.id }"
role="button"
tabindex="0"
:aria-pressed="selectedTemplate && selectedTemplate.id === template.id ? 'true' : 'false'"
:aria-label="template.name"
@click.capture="onTemplateCardClick($event, template)"
@keydown.enter.prevent="selectTemplate(template)"
@keydown.space.prevent="selectTemplate(template)">
<documents-item-card
:file="template"
:files="templates"
:selected-documents="[]"
:show-details="false"
width="100%"
height="150px"
max-height="150px" />
v-for="template in filteredTemplates"
:key="template.id"
class="attachmentsTemplateItem">
<div
class="attachmentsTemplateCard clickable"
:class="{ 'attachmentsTemplateCard--selected': selectedTemplate && selectedTemplate.id === template.id }"
role="button"
tabindex="0"
:aria-pressed="selectedTemplate && selectedTemplate.id === template.id ? 'true' : 'false'"
:aria-label="template.name"
@click.capture="onTemplateCardClick($event, template)"
@keydown.enter.prevent="selectTemplate(template)"
@keydown.space.prevent="selectTemplate(template)">
<documents-item-card
:file="template"
:files="filteredTemplates"
:selected-documents="[]"
:show-details="true"
width="100%"
height="150px"
max-height="150px" />
</div>
<v-text-field
v-if="selectedTemplate && selectedTemplate.id === template.id"
ref="templateNameInput"
v-model="newDocTitleInput"
:rules="documentTitleRules"
:placeholder="$t('attachment.untitledDocument')"
class="attachmentsCreateDocumentInput mt-2"
outlined
dense
autofocus
@keyup.enter="submitCreate()">
<span slot="append" class="text-color mt-1">{{ activeExtension }}</span>
</v-text-field>
</div>
<v-text-field
v-if="selectedTemplate && selectedTemplate.id === template.id"
ref="templateNameInput"
v-model="newDocTitleInput"
:rules="documentTitleRules"
:placeholder="$t('attachment.untitledDocument')"
class="attachmentsCreateDocumentInput mt-2"
outlined
dense
autofocus
@keyup.enter="submitCreate()">
<span slot="append" class="text-color mt-1">{{ activeExtension }}</span>
</v-text-field>
</div>
<!-- The active category/search filter matched no template. -->
<div
v-if="!filteredTemplates.length"
class="attachmentsTemplatesEmpty d-flex flex-column align-center justify-center text-center py-6">
<v-icon size="36" color="grey">far fa-clone</v-icon>
<span class="text-sub-title mt-3">{{ $t('attachments.drawer.templates.noMatch') }}</span>
</div>
</div>
<div
Expand Down Expand Up @@ -224,6 +257,15 @@ export default {
templates: [],
templatesLoading: false,
templateCreating: false,
// Filters over the loaded templates (both applied client-side): the selected
// category chip (id) + the free-text query fed by exo-drawer's built-in
// header filter (use-filter → @filter-updated).
selectedTemplateCategoryId: null,
templateSearch: '',
// True when the drawer is in its expanded (full-screen) state — drives the
// centered side margins and shows the category chip bar. Fed by exo-drawer's
// own @expand-updated event (same pattern as App Center's launcher drawer).
drawerExpanded: false,
// Bootstrap state: whether the drive's "Templates" folder exists, its parent
// (drive root) id, and whether the current user may create a folder here.
templatesFolderExists: false,
Expand Down Expand Up @@ -307,6 +349,29 @@ export default {
? this.$t('attachments.drawer.templates.empty.space')
: this.$t('attachments.drawer.templates.empty.personal');
},
// Templates after the active category + free-text filters (both client-side).
// Category ids are compared as strings — the chip component may emit the id as
// a string while the item's categoryIds are numbers (or vice-versa).
filteredTemplates() {
let list = this.templates;
// `!= null` catches both null and undefined — the category bar emits
// `undefined` when you navigate back to root, which must mean "no filter".
if (this.selectedTemplateCategoryId != null && this.selectedTemplateCategoryId !== '') {
const selected = String(this.selectedTemplateCategoryId);
list = list.filter(template => (template.categoryIds || []).some(id => String(id) === selected));
}
const query = (this.templateSearch || '').trim().toLowerCase();
if (query) {
list = list.filter(template => (template.name || '').toLowerCase().includes(query));
}
return list;
},
// Only surface the filter row once there are enough templates to warrant it —
// small sets are fully visible, and the category chip bar self-hides anyway
// when the templates aren't categorized.
showTemplateFilters() {
return this.templates.length > 3;
},
// Drive-root-relative path of the current target folder (blank-create target).
// '' targets the drive root. Defensive against a path passed as an object.
destinationRelativePath() {
Expand Down Expand Up @@ -335,6 +400,7 @@ export default {
this.$refs.createDocumentDrawer.open();
},
close() {
this.drawerExpanded = false;
this.$refs.createDocumentDrawer.close();
},
refreshNewDocumentsActions() {
Expand Down Expand Up @@ -425,6 +491,11 @@ export default {
this.createNewDoc();
}
},
// exo-drawer built-in header filter (use-filter) → the typed query, applied
// client-side over the loaded templates.
onTemplateFilter(text) {
this.templateSearch = text || '';
},
resetNewDocInput() {
this.NewDocInputHidden = true;
this.newDocTitleInput = '';
Expand Down Expand Up @@ -469,6 +540,8 @@ export default {
this.templatesFolderExists = false;
this.rootFolderId = null;
this.canCreateTemplatesFolder = false;
this.selectedTemplateCategoryId = null;
this.templateSearch = '';
const ownerId = this.templatesOwnerId;
if (!ownerId) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,35 @@
*
-->
<template>
<exo-drawer
<exo-drawer
ref="treeViewDrawer"
class="treeViewDrawer"
:use-filter="true"
:filter-placeholder="$t('documents.drawer.tree.searchDrives')"
@filter-updated="onFilterDrives"
@closed="close"
right>
<template slot="title">
{{ $t('documents.drawer.tree') }}
<!-- Back arrow (returns to the underlying list/explorer) + title. The filter
icon is provided by exo-drawer's built-in use-filter, wired to the tree's
existing drive search. -->
<div class="d-flex align-center">
<v-btn
icon
small
class="ms-n2 me-1 flex-shrink-0"
:aria-label="$t('documents.label.button.back')"
:title="$t('documents.label.button.back')"
@click="close()">
<v-icon size="20">fa-arrow-left</v-icon>
</v-btn>
<span class="text-truncate">{{ $t('documents.drawer.tree') }}</span>
</div>
</template>
<template slot="content">
<document-tree-view
:items="items"
:search="treeSearch"
:folder-path="folderPath" />
</template>
</exo-drawer>
Expand All @@ -47,6 +65,7 @@ export default {
pageSize: 20,
drivesLimit: 20,
drivesOffset: 0,
treeSearch: '',
}),
created(){
this.$root.$on('openTreeFolderDrawer', this.open);
Expand Down Expand Up @@ -74,6 +93,11 @@ export default {
close() {
this.$refs.treeViewDrawer?.close();
},
// exo-drawer's built-in header filter → v-treeview's built-in search, which
// filters the loaded tree (drives + expanded folders) by name client-side.
onFilterDrives(query) {
this.treeSearch = query || '';
},
async retrieveDocumentTree() {
this.items = [];
this.loading = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
:open.sync="openNodes"
:active.sync="activeNodes"
:items="items"
:search="search"
:load-children="fetchChildren"
class="treeView-item my-2"
item-key="id"
Expand Down Expand Up @@ -92,6 +93,12 @@ export default {
showHidden: {
type: Boolean,
default: false
},
// Free-text filter fed to v-treeview's built-in search (matches node names of
// the loaded tree — drives and any expanded folders).
search: {
type: String,
default: ''
}
},

Expand Down