mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-06-20 01:48:01 +02:00
Et2LinkTo: Fix several bugs
- Linked entries had no thumbnail - Linking to VFS before initial save lost links - Missing icon in VFS clipboard paste
This commit is contained in:
parent
a84dbd4c25
commit
e31470c58f
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {css, html, TemplateResult} from "lit";
|
import {css, html, nothing, TemplateResult} from "lit";
|
||||||
import {repeat} from "lit/directives/repeat.js";
|
import {repeat} from "lit/directives/repeat.js";
|
||||||
import {LinkInfo} from "./Et2Link";
|
import {LinkInfo} from "./Et2Link";
|
||||||
import {egw} from "../../jsapi/egw_global";
|
import {egw} from "../../jsapi/egw_global";
|
||||||
@ -71,31 +71,42 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
|
|
||||||
/* CSS for child elements */
|
/* CSS for child elements */
|
||||||
|
|
||||||
::slotted(*):after {
|
et2-link::part(title):after {
|
||||||
/* Reset from Et2LinkString */
|
/* Reset from Et2LinkString */
|
||||||
content: initial;
|
content: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(*)::part(icon) {
|
et2-link::part(icon) {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(et2-link) {
|
et2-link {
|
||||||
|
display: block;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(.remark) {
|
et2-link:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
et2-link::part(base) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(.delete_button) {
|
div et2-image[part=delete-button] {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
order: 5;
|
order: 5;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
div:hover ::slotted(.delete_button) {
|
div:hover et2-image[part=delete-button] {
|
||||||
visibility: initial;
|
visibility: initial;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -164,7 +175,35 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
${repeat(this._link_list,
|
${repeat(this._link_list,
|
||||||
(link) => link.app + ":" + link.id,
|
(link) => link.app + ":" + link.id,
|
||||||
(link) => this._rowTemplate(link))
|
(link) => this._rowTemplate(link))
|
||||||
}`;
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async moreResultsTemplate()
|
||||||
|
{
|
||||||
|
if(this._totalResults <= 0 || !this._loadingPromise)
|
||||||
|
{
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
return this._loadingPromise.then(() =>
|
||||||
|
{
|
||||||
|
const moreCount = this._totalResults - this._link_list.length;
|
||||||
|
const more = this.egw().lang("%1 more...", moreCount);
|
||||||
|
return html`${moreCount > 0 ? html`
|
||||||
|
<et2-button image="box-arrow-down" label="${more}" noSubmit="true"
|
||||||
|
._parent=${this}
|
||||||
|
@click="${(e) =>
|
||||||
|
{
|
||||||
|
// Change icon for some feedback
|
||||||
|
e.target.querySelectorAll("[slot=prefix]").forEach(n => n.remove());
|
||||||
|
e.target.append(Object.assign(document.createElement("sl-spinner"), {slot: "prefix"}));
|
||||||
|
|
||||||
|
// Get the next batch
|
||||||
|
const start = this._link_list.filter(l => l.app !== "file").length;
|
||||||
|
this.get_links([], start);
|
||||||
|
}}"
|
||||||
|
></et2-button>` : nothing}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,7 +232,7 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
{
|
{
|
||||||
const id = typeof link.id === "string" ? link.id : link.link_id;
|
const id = typeof link.id === "string" ? link.id : link.link_id;
|
||||||
return html`
|
return html`
|
||||||
<et2-link slot="${this._get_row_id(link)}" app="${link.app}" entryId="${id}" statustext="${link.title}"
|
<et2-link app="${link.app}" entryId="${id}" statustext="${link.title}"
|
||||||
._parent=${this}
|
._parent=${this}
|
||||||
.value=${link}></et2-link>
|
.value=${link}></et2-link>
|
||||||
${this._deleteButtonTemplate(link)}
|
${this._deleteButtonTemplate(link)}
|
||||||
@ -214,7 +253,7 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
return html`
|
return html`
|
||||||
<div id="${this._get_row_id(link)}"
|
<div id="${this._get_row_id(link)}"
|
||||||
@contextmenu=${this._handleRowContext}>
|
@contextmenu=${this._handleRowContext}>
|
||||||
<slot name="${this._get_row_id(link)}"></slot>
|
${this._linkTemplate(link)}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +281,24 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
{
|
{
|
||||||
if(_ev && typeof _ev.currentTarget)
|
if(_ev && typeof _ev.currentTarget)
|
||||||
{
|
{
|
||||||
this.get_links(_ev.detail || []);
|
// Add in new links from LinkTo
|
||||||
|
for(let link of <LinkInfo[]>Object.values(_ev.detail || []))
|
||||||
|
{
|
||||||
|
if(!this._link_list.some(l => l.app == link.app && l.id == link.id))
|
||||||
|
{
|
||||||
|
this._link_list.unshift(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No need to ask server if we got it in the event
|
||||||
|
if(_ev.detail.length)
|
||||||
|
{
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Event didn't have it, need to ask
|
||||||
|
this.get_links();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,22 +346,27 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
*/
|
*/
|
||||||
protected _delete_link(link : LinkInfo)
|
protected _delete_link(link : LinkInfo)
|
||||||
{
|
{
|
||||||
let link_element = <HTMLElement>this.querySelector("et2-link[slot='" + this._get_row_id(link) + "']");
|
let link_element = <HTMLElement>this.shadowRoot.querySelector("[id='" + this._get_row_id(link) + "']");
|
||||||
link_element.classList.add("loading");
|
link_element.classList.add("loading");
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent("et2-before-delete", {detail: link}));
|
this.dispatchEvent(new CustomEvent("et2-before-delete", {detail: link}));
|
||||||
|
|
||||||
let removeLink = () =>
|
let removeLink = () =>
|
||||||
{
|
{
|
||||||
this.querySelectorAll("[slot='" + this._get_row_id(link) + "']").forEach(e => e.remove());
|
this.shadowRoot.querySelectorAll("[id='" + this._get_row_id(link) + "']").forEach(e => e.remove());
|
||||||
if(this._link_list.indexOf(link) != -1)
|
if(this._link_list.indexOf(link) != -1)
|
||||||
{
|
{
|
||||||
this._link_list.splice(this._link_list.indexOf(link), 1);
|
this._link_list.splice(this._link_list.indexOf(link), 1);
|
||||||
|
this._totalResults--;
|
||||||
}
|
}
|
||||||
|
this.requestUpdate();
|
||||||
|
this.updateComplete.then(() =>
|
||||||
|
{
|
||||||
this.dispatchEvent(new CustomEvent("et2-delete", {bubbles: true, detail: link}));
|
this.dispatchEvent(new CustomEvent("et2-delete", {bubbles: true, detail: link}));
|
||||||
let change = new Event("change", {bubbles: true});
|
let change = new Event("change", {bubbles: true});
|
||||||
change['data'] = link;
|
change['data'] = link;
|
||||||
this.dispatchEvent(change);
|
this.dispatchEvent(change);
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unsaved entry, had no ID yet
|
// Unsaved entry, had no ID yet
|
||||||
@ -534,7 +595,7 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
this._createContextMenu();
|
this._createContextMenu();
|
||||||
}
|
}
|
||||||
// Find the link
|
// Find the link
|
||||||
let link = this.querySelector("et2-link[slot='" + _ev.currentTarget.id + "']");
|
let link = _ev.currentTarget.querySelector("et2-link");
|
||||||
|
|
||||||
let _link_data = Object.assign({app: link.app, id: link.entryId}, link.dataset);
|
let _link_data = Object.assign({app: link.app, id: link.entryId}, link.dataset);
|
||||||
// Comment only available if link_id is there and not readonly
|
// Comment only available if link_id is there and not readonly
|
||||||
@ -555,7 +616,7 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
|
|
||||||
protected _set_comment(link, comment)
|
protected _set_comment(link, comment)
|
||||||
{
|
{
|
||||||
let remark = this.querySelector("et2-link[slot='" + this._get_row_id(link) + "']");
|
let remark = this.shadowRoot.querySelector("#" + this._get_row_id(link) + " et2-link");
|
||||||
if(!remark)
|
if(!remark)
|
||||||
{
|
{
|
||||||
console.warn("Could not find link to comment on", link);
|
console.warn("Could not find link to comment on", link);
|
||||||
|
@ -24,6 +24,7 @@ import {Et2VfsSelectButton} from "../Et2Vfs/Et2VfsSelectButton";
|
|||||||
import {Et2LinkPasteDialog, getClipboardFiles} from "./Et2LinkPasteDialog";
|
import {Et2LinkPasteDialog, getClipboardFiles} from "./Et2LinkPasteDialog";
|
||||||
import {waitForEvent} from "../Et2Widget/event";
|
import {waitForEvent} from "../Et2Widget/event";
|
||||||
import {classMap} from "lit/directives/class-map.js";
|
import {classMap} from "lit/directives/class-map.js";
|
||||||
|
import {Et2VfsSelectDialog} from "../Et2Vfs/Et2VfsSelectDialog";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choose an existing entry, VFS file or local file, and link it to the current entry.
|
* Choose an existing entry, VFS file or local file, and link it to the current entry.
|
||||||
@ -100,7 +101,9 @@ export class Et2LinkTo extends Et2InputWidget(LitElement)
|
|||||||
|
|
||||||
private get pasteButton() : Et2VfsSelectButton { return this.shadowRoot?.querySelector("#paste"); }
|
private get pasteButton() : Et2VfsSelectButton { return this.shadowRoot?.querySelector("#paste"); }
|
||||||
|
|
||||||
private get pasteDialog() { return this.pasteButton?.querySelector("et2-link-paste-dialog"); }
|
private get pasteDialog() : Et2LinkPasteDialog { return <Et2LinkPasteDialog><unknown>this.pasteButton?.querySelector("et2-link-paste-dialog"); }
|
||||||
|
|
||||||
|
private get vfsDialog() : Et2VfsSelectDialog { return <Et2VfsSelectDialog><unknown>this.shadowRoot.querySelector("#link")?.shadowRoot.querySelector("et2-vfs-select-dialog")}
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
@ -150,7 +153,10 @@ export class Et2LinkTo extends Et2InputWidget(LitElement)
|
|||||||
|
|
||||||
getClipboardFiles().then((files) =>
|
getClipboardFiles().then((files) =>
|
||||||
{
|
{
|
||||||
this.pasteButton.disabled = files.length == 0;
|
if(files.length > 0)
|
||||||
|
{
|
||||||
|
this.pasteButton.removeAttribute("disabled");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,17 +172,17 @@ export class Et2LinkTo extends Et2InputWidget(LitElement)
|
|||||||
.buttonLabel=${this.egw().lang('Link')}
|
.buttonLabel=${this.egw().lang('Link')}
|
||||||
@change=${async() =>
|
@change=${async() =>
|
||||||
{
|
{
|
||||||
this.handleVfsSelected(await this.shadowRoot.getElementById("link")._dialog.getComplete());
|
this.handleVfsSelected(await this.vfsDialog.getComplete());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<et2-button slot="footer" image="copy" id="copy" style="order:3" noSubmit="true"
|
<et2-button slot="footer" image="copy" id="copy" style="order:3" noSubmit="true"
|
||||||
label=${this.egw().lang("copy")}></et2-button>
|
label=${this.egw().lang("copy")}></et2-button>
|
||||||
<et2-button slot="footer" image="move" id="move" style="order:3" noSubmit="true"
|
<et2-button slot="footer" image="move" id="move" style="order:3" noSubmit="true" ?disabled=${!method_id}
|
||||||
label=${this.egw().lang("move")}></et2-button>
|
label=${this.egw().lang("move")}></et2-button>
|
||||||
</et2-vfs-select>
|
</et2-vfs-select>
|
||||||
<et2-vfs-select
|
<et2-vfs-select
|
||||||
id="paste"
|
id="paste"
|
||||||
image="linkpaste" aria-label=${this.egw().lang("clipboard contents")} noSubmit="true"
|
image="clipboard-data" aria-label=${this.egw().lang("clipboard contents")} noSubmit="true"
|
||||||
title=${this.egw().lang("Clipboard contents")}
|
title=${this.egw().lang("Clipboard contents")}
|
||||||
?readonly=${this.readonly}
|
?readonly=${this.readonly}
|
||||||
disabled
|
disabled
|
||||||
@ -192,7 +198,7 @@ export class Et2LinkTo extends Et2InputWidget(LitElement)
|
|||||||
|
|
||||||
waitForEvent(e.target._dialog, "sl-after-show").then(async() =>
|
waitForEvent(e.target._dialog, "sl-after-show").then(async() =>
|
||||||
{
|
{
|
||||||
this.handleVfsSelected(await this.pasteButton._dialog.getComplete());
|
this.handleFilePaste(await this.pasteDialog.getComplete());
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -491,30 +497,39 @@ export class Et2LinkTo extends Et2InputWidget(LitElement)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFilePaste([button, files])
|
handleFilePaste([button, selected])
|
||||||
{
|
{
|
||||||
if(!button)
|
let fileInfo = []
|
||||||
|
selected.forEach(file =>
|
||||||
{
|
{
|
||||||
return;
|
fileInfo.push({...this.pasteDialog.fileInfo(file), app: "link"});
|
||||||
}
|
})
|
||||||
let values = {};
|
this.handleVfsFile(button, fileInfo);
|
||||||
for(var i = 0; i < files.length; i++)
|
this.pasteButton.value = [];
|
||||||
{
|
|
||||||
values['link:' + files[i]] = {
|
|
||||||
app: 'link',
|
|
||||||
id: files[i],
|
|
||||||
type: 'unknown',
|
|
||||||
icon: 'link',
|
|
||||||
remark: '',
|
|
||||||
title: files[i]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this._link_result(values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleVfsSelected([button, selected])
|
handleVfsSelected([button, selected])
|
||||||
{
|
{
|
||||||
if(!button || !selected?.length)
|
let fileInfo = []
|
||||||
|
selected.forEach(file =>
|
||||||
|
{
|
||||||
|
let info = {
|
||||||
|
...this.vfsDialog.fileInfo(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.value.to_id || typeof this.value.to_id == 'object')
|
||||||
|
{
|
||||||
|
info['app'] = button == "copy" ? "file" : "link";
|
||||||
|
info['path'] = button == "copy" ? "vfs://default" + info.path : info.path;
|
||||||
|
}
|
||||||
|
fileInfo.push(info);
|
||||||
|
})
|
||||||
|
this.handleVfsFile(button, fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleVfsFile(button, selectedFileInfo)
|
||||||
|
{
|
||||||
|
if(!button)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -523,31 +538,30 @@ export class Et2LinkTo extends Et2InputWidget(LitElement)
|
|||||||
if(!this.value.to_id || typeof this.value.to_id == 'object')
|
if(!this.value.to_id || typeof this.value.to_id == 'object')
|
||||||
{
|
{
|
||||||
values = this.value.to_id || {};
|
values = this.value.to_id || {};
|
||||||
for(let i = 0; i < selected.length; i++)
|
selectedFileInfo.forEach(info =>
|
||||||
{
|
{
|
||||||
const info = this.pasteDialog.fileInfo(selected[i]);
|
debugger;
|
||||||
values['link:' + selected[i]] = {
|
values['link:' + info.path] = {
|
||||||
app: info?.app == "filemanager" ? "link" : info?.app,
|
app: info?.app,
|
||||||
id: info?.app == "filemanager" ? selected[i] : info?.id,
|
id: info.path ?? info.id,
|
||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
icon: 'link',
|
icon: 'link',
|
||||||
remark: '',
|
remark: '',
|
||||||
title: selected[i]
|
title: info.path
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Send to server to link
|
// Send to server to link
|
||||||
const files = [];
|
const files = [];
|
||||||
const links = [];
|
const links = [];
|
||||||
selected.forEach(id =>
|
selectedFileInfo.forEach(info =>
|
||||||
{
|
{
|
||||||
const info = this.pasteDialog.fileInfo(id);
|
|
||||||
switch(info?.app)
|
switch(info?.app)
|
||||||
{
|
{
|
||||||
case "filemanager":
|
case "filemanager":
|
||||||
files.push(id);
|
files.push(info.path);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
links.push({app: info.app, id: info.id});
|
links.push({app: info.app, id: info.id});
|
||||||
@ -567,7 +581,6 @@ export class Et2LinkTo extends Et2InputWidget(LitElement)
|
|||||||
this.createLink(links);
|
this.createLink(links);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.pasteButton.value = [];
|
|
||||||
this._link_result(values);
|
this._link_result(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,8 +201,8 @@ class Link extends Etemplate\Widget
|
|||||||
$app = $value['to_app'];
|
$app = $value['to_app'];
|
||||||
$id = $value['to_id'];
|
$id = $value['to_id'];
|
||||||
|
|
||||||
$links = Api\Link::get_links($app, $id, $value['only_app'] ?? '', 'link_lastmod DESC', true, $value['show_deleted'], $value['limit'] ?? null);
|
$links = Api\Link::get_links($app, $id, $value['only_app'], 'link_lastmod DESC, link_id DESC', true, $value['show_deleted'], $value['limit']);
|
||||||
$limit_exceeded = !empty($value['limit']) && Api\Link::$limit_exceeded;
|
|
||||||
$only_links = [];
|
$only_links = [];
|
||||||
if($value['only_app'])
|
if($value['only_app'])
|
||||||
{
|
{
|
||||||
@ -244,19 +244,12 @@ class Link extends Etemplate\Widget
|
|||||||
$link['help'] = lang('Remove this link (not the entry itself)');
|
$link['help'] = lang('Remove this link (not the entry itself)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($limit_exceeded)
|
|
||||||
{
|
|
||||||
$links[] = [
|
|
||||||
'app' => 'exceeded',
|
|
||||||
'id' => 'exceeded',
|
|
||||||
'title' => lang('Load more links ...'),
|
|
||||||
'icon' => 'box-arrow-down',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = Api\Json\Response::get();
|
$response = Api\Json\Response::get();
|
||||||
// Strip keys, unneeded and cause index problems on the client side
|
// Strip keys, unneeded and cause index problems on the client side
|
||||||
$response->data(array_values($links));
|
$result = array_values($links);
|
||||||
|
$result['total'] = Api\Link\Storage::$row_count;
|
||||||
|
$response->data($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -320,6 +313,13 @@ class Link extends Etemplate\Widget
|
|||||||
Api\Vfs::move_files($files, Api\Link::vfs_path($app, $id, '', true), $errs, $moved);
|
Api\Vfs::move_files($files, Api\Link::vfs_path($app, $id, '', true), $errs, $moved);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if(!str_ends_with($dest_file, '/') && count($files) == 1 && is_int($id))
|
||||||
|
{
|
||||||
|
// 1 file to a specific filename
|
||||||
|
Api\Vfs::symlink($files[0], Api\Link::vfs_path($app, $id));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
foreach($files as $target)
|
foreach($files as $target)
|
||||||
{
|
{
|
||||||
@ -327,6 +327,7 @@ class Link extends Etemplate\Widget
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy a linked file to somewhere else in the VFS
|
* Copy a linked file to somewhere else in the VFS
|
||||||
|
Loading…
x
Reference in New Issue
Block a user