Merge branch 'master' into sw_digester

This commit is contained in:
schlagmichdoch 2024-07-14 14:32:35 +02:00
commit fa86212139
41 changed files with 2055 additions and 865 deletions

View file

@ -3,8 +3,8 @@ class Localization {
Localization.$htmlRoot = document.querySelector('html');
Localization.defaultLocale = "en";
Localization.supportedLocales = ["ar", "ca", "de", "en", "es", "fr", "id", "it", "ja", "kn", "nb", "nl", "pt-BR", "ro", "ru", "tr", "zh-CN"];
Localization.supportedLocalesRtl = ["ar"];
Localization.supportedLocales = ["ar", "be", "ca", "da", "de", "en", "es", "fr", "he", "hu", "id", "it", "ja", "kn", "nb", "nl", "pl", "pt-BR", "ro", "ru", "tr", "zh-CN", "zh-TW"];
Localization.supportedLocalesRtl = ["ar", "he"];
Localization.translations = {};
Localization.translationsDefaultLocale = {};

View file

@ -194,31 +194,34 @@ class PeersUI {
}
_onDrop(e) {
e.preventDefault();
if (this.shareMode.active || Dialog.anyDialogShown()) return;
if (!$$('x-peer') || !$$('x-peer').contains(e.target)) {
if (e.dataTransfer.files.length > 0) {
Events.fire('activate-share-mode', {files: e.dataTransfer.files});
} else {
for (let i=0; i<e.dataTransfer.items.length; i++) {
if (e.dataTransfer.items[i].type === "text/plain") {
e.dataTransfer.items[i].getAsString(text => {
Events.fire('activate-share-mode', {text: text});
});
}
}
}
}
e.preventDefault();
this._onDragEnd();
if ($$('x-peer') || !$$('x-peer').contains(e.target)) return; // dropped on peer
const files = e.dataTransfer.files;
const text = e.dataTransfer.getData("text");
if (files.length > 0) {
Events.fire('activate-share-mode', {
files: files
});
}
else if(text.length > 0) {
Events.fire('activate-share-mode', {
text: text
});
}
}
_onDragOver(e) {
e.preventDefault();
if (this.shareMode.active || Dialog.anyDialogShown()) return;
e.preventDefault();
this.$xInstructions.setAttribute('drop-bg', true);
this.$xNoPeers.setAttribute('drop-bg', true);
}
@ -836,25 +839,24 @@ class PeerUI {
e.preventDefault();
if (e.dataTransfer.files.length > 0) {
Events.fire('files-selected', {
files: e.dataTransfer.files,
to: this._peer.id
});
} else {
for (let i=0; i<e.dataTransfer.items.length; i++) {
if (e.dataTransfer.items[i].type === "text/plain") {
e.dataTransfer.items[i].getAsString(text => {
Events.fire('send-text', {
text: text,
to: this._peer.id
});
});
}
}
}
this._onDragEnd();
const peerId = this._peer.id;
const files = e.dataTransfer.files;
const text = e.dataTransfer.getData("text");
if (files.length > 0) {
Events.fire('files-selected', {
files: files,
to: peerId
});
}
else if (text.length > 0) {
Events.fire('send-text', {
text: text,
to: peerId
});
}
}
_onDragOver() {
@ -2265,6 +2267,8 @@ class SendTextDialog extends Dialog {
this.$submit = this.$el.querySelector('button[type="submit"]');
this.$form.addEventListener('submit', e => this._onSubmit(e));
this.$text.addEventListener('input', _ => this._onInput());
this.$text.addEventListener('paste', e => this._onPaste(e));
this.$text.addEventListener('drop', e => this._onDrop(e));
Events.on('text-recipient', e => this._onRecipient(e.detail.peerId, e.detail.deviceName));
Events.on('keydown', e => this._onKeyDown(e));
@ -2283,6 +2287,40 @@ class SendTextDialog extends Dialog {
}
}
async _onDrop(e) {
e.preventDefault()
const text = e.dataTransfer.getData("text");
const selection = window.getSelection();
if (selection.rangeCount) {
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(text));
}
this._onInput();
}
async _onPaste(e) {
e.preventDefault()
const text = (e.clipboardData || window.clipboardData).getData('text');
const selection = window.getSelection();
if (selection.rangeCount) {
selection.deleteFromDocument();
const textNode = document.createTextNode(text);
const range = document.createRange();
range.setStart(textNode, textNode.length);
range.collapse(true);
selection.getRangeAt(0).insertNode(textNode);
selection.removeAllRanges();
selection.addRange(range);
}
this._onInput();
}
_textEmpty() {
return !this.$text.innerText || this.$text.innerText === "\n";
}
@ -2345,6 +2383,7 @@ class ReceiveTextDialog extends Dialog {
this.$displayName = this.$el.querySelector('.display-name');
this._receiveTextQueue = [];
this._hideTimeout = null;
}
selectionEmpty() {
@ -2366,12 +2405,17 @@ class ReceiveTextDialog extends Dialog {
audioPlayer.playBlop();
this._receiveTextQueue.push({text: text, peerId: peerId});
this._setDocumentTitleMessages();
if (this.isShown()) return;
changeFavicon("images/favicon-96x96-notification.png");
if (this.isShown() || this._hideTimeout) return;
this._dequeueRequests();
}
_dequeueRequests() {
if (!this._receiveTextQueue.length) return;
this._setDocumentTitleMessages();
changeFavicon("images/favicon-96x96-notification.png");
let {text, peerId} = this._receiveTextQueue.shift();
this._showReceiveTextDialog(text, peerId);
}
@ -2382,41 +2426,68 @@ class ReceiveTextDialog extends Dialog {
this.$displayName.classList.add($(peerId).ui._badgeClassName());
this.$text.innerText = text;
this.$text.classList.remove('text-center');
// Beautify text if text is short
if (text.length < 2000) {
// replace URLs with actual links
this.$text.innerHTML = this.$text.innerHTML
.replace(/(^|<br>|\s|")((https?:\/\/|www.)(([a-z]|[A-Z]|[0-9]|[\-_~:\/?#\[\]@!$&'()*+,;=%]){2,}\.)(([a-z]|[A-Z]|[0-9]|[\-_~:\/?#\[\]@!$&'()*+,;=%.]){2,}))/g,
(match, whitespace, url) => {
let link = url;
// Beautify text if text is not too long
if (this.$text.innerText.length <= 300000) {
// Hacky workaround to replace URLs with link nodes in all cases
// 1. Use text variable, find all valid URLs via regex and replace URLs with placeholder
// 2. Use html variable, find placeholders with regex and replace them with link nodes
// prefix www.example.com with http protocol to prevent it from being a relative link
if (link.startsWith('www')) {
link = "http://" + link
}
let $textShadow = document.createElement('div');
$textShadow.innerText = text;
// Check if link is valid
if (isUrlValid(link)) {
return `${whitespace}<a href="${link}" target="_blank">${url}</a>`;
}
else {
return match;
let linkNodes = {};
let searchHTML = $textShadow.innerHTML;
const p = "@";
const pRgx = new RegExp(`${p}\\d+`, 'g');
let occP = searchHTML.match(pRgx) || [];
let m = 0;
const allowedDomainChars = "a-zA-Z0-9áàäčçđéèêŋńñóòôöšŧüžæøåëìíîïðùúýþćěłřśţźǎǐǒǔǥǧǩǯəʒâûœÿãõāēīōūăąĉċďĕėęĝğġģĥħĩĭįıĵķĸĺļľņňŏőŕŗŝşťũŭůűųŵŷżאבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ";
const urlRgx = new RegExp(`(^|\\n|\\s|["><\\-_~:\\/?#\\[\\]@!$&'()*+,;=%.])(((https?:\\/\\/)?(?:[${allowedDomainChars}](?:[${allowedDomainChars}-]{0,61}[${allowedDomainChars}])?\\.)+[${allowedDomainChars}][${allowedDomainChars}-]{0,61}[${allowedDomainChars}])(:?\\d*)\\/?([${allowedDomainChars}_\\/\\-#.]*)(\\?([${allowedDomainChars}\\-_~:\\/?#\\[\\]@!$&'()*+,;=%.]*))?)`, 'g');
$textShadow.innerText = text.replace(urlRgx,
(match, whitespaceOrSpecial, url, g3, scheme) => {
let link = url;
// prefix www.example.com with http protocol to prevent it from being a relative link
if (!scheme && link.startsWith('www')) {
link = "http://" + link
}
if (isUrlValid(link)) {
// link is valid -> replace with link node placeholder
// find linkNodePlaceholder that is not yet present in text node
m++;
while (occP.includes(`${p}${m}`)) {
m++;
}
let linkNodePlaceholder = `${p}${m}`;
// add linkNodePlaceholder to text node and save a reference to linkNodes object
linkNodes[linkNodePlaceholder] = `<a href="${link}" target="_blank">${url}</a>`;
return `${whitespaceOrSpecial}${linkNodePlaceholder}`;
}
// link is not valid -> do not replace
return match;
});
this.$text.innerHTML = $textShadow.innerHTML.replace(pRgx,
(m) => {
let urlNode = linkNodes[m];
return urlNode ? urlNode : m;
});
}
this._evaluateOverflowing(this.$text);
this._setDocumentTitleMessages();
changeFavicon("images/favicon-96x96-notification.png");
this.show();
}
_setDocumentTitleMessages() {
document.title = !this._receiveTextQueue.length
document.title = this._receiveTextQueue.length <= 1
? `${ Localization.getTranslation("document-titles.message-received") } - PairDrop`
: `${ Localization.getTranslation("document-titles.message-received-plural", null, {count: this._receiveTextQueue.length + 1}) } - PairDrop`;
}
@ -2436,9 +2507,16 @@ class ReceiveTextDialog extends Dialog {
hide() {
super.hide();
setTimeout(() => {
this._dequeueRequests();
this.$text.innerHTML = "";
// If queue is empty -> clear text field | else -> open next message
this._hideTimeout = setTimeout(() => {
if (!this._receiveTextQueue.length) {
this.$text.innerHTML = "";
}
else {
this._dequeueRequests();
}
this._hideTimeout = null;
}, 500);
}
}