mirror of
https://gitlab.com/timvisee/send.git
synced 2025-12-06 22:20:55 +03:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bf8481fd0 | ||
|
|
ae0758ac14 | ||
|
|
e9405f49ee | ||
|
|
a1d0eef8a5 | ||
|
|
254b806fb4 | ||
|
|
a7aee1450f | ||
|
|
fa5573a5ff | ||
|
|
9714bb0a0a | ||
|
|
ad82d30dd9 | ||
|
|
757ac14d1a | ||
|
|
3e066258c4 | ||
|
|
127f73b4fe | ||
|
|
ae5009e1e3 | ||
|
|
0ab8ddc894 | ||
|
|
b429841534 | ||
|
|
279f6df6f4 | ||
|
|
634e6b2834 | ||
|
|
856b2cdc60 | ||
|
|
41351f877c | ||
|
|
afbb89fbe8 | ||
|
|
b40b45273d | ||
|
|
f1fb877c7f | ||
|
|
9d7ad06b1a | ||
|
|
c6a4b089d9 | ||
|
|
24917f8aa5 | ||
|
|
eada94b262 | ||
|
|
43fa551a64 | ||
|
|
e1137db946 | ||
|
|
c1878649b3 | ||
|
|
30b86b14ed | ||
|
|
e91b341f8a | ||
|
|
70148232c6 | ||
|
|
56e3d5766c | ||
|
|
350e31ae4a | ||
|
|
0794bcc458 | ||
|
|
a6aee8ad62 | ||
|
|
8305d13dab | ||
|
|
441a520765 | ||
|
|
ba84e59f39 | ||
|
|
22a316ab58 | ||
|
|
6b9502d252 | ||
|
|
cdc3a5340d | ||
|
|
f03f7a0286 | ||
|
|
d8a5789701 | ||
|
|
8d26e0e742 | ||
|
|
e142d76cb4 | ||
|
|
c488c1d724 | ||
|
|
bed57af6c5 | ||
|
|
7500bd8326 | ||
|
|
0250924961 | ||
|
|
70813556ad | ||
|
|
2bb9af1943 | ||
|
|
55bd44a8f5 | ||
|
|
d83900f272 | ||
|
|
fa4f9299b2 | ||
|
|
0f7b19c385 | ||
|
|
a9c1dd0180 | ||
|
|
c468e2f34e | ||
|
|
718f42897f | ||
|
|
fb468bd1bc | ||
|
|
dafe00cabb | ||
|
|
98aebb7f70 | ||
|
|
a990d78bc0 | ||
|
|
9b4069be3e | ||
|
|
ff3bc0dd62 | ||
|
|
b39b131928 | ||
|
|
2646fb9b3c | ||
|
|
c2b84650e2 | ||
|
|
fecf938ae7 | ||
|
|
8abf631430 | ||
|
|
d69c535dda | ||
|
|
082ca6c57b | ||
|
|
b263231068 | ||
|
|
947a6d9992 | ||
|
|
1ad7edf5a9 | ||
|
|
0c26204ea1 | ||
|
|
1e3bbee7f1 | ||
|
|
ec80e8e622 | ||
|
|
61e2c0d85b | ||
|
|
80db74fc3a | ||
|
|
30936eb2fa | ||
|
|
31e29d58b9 | ||
|
|
702134b3b1 | ||
|
|
11ae7f857c | ||
|
|
8827556974 | ||
|
|
21b7f16b1e | ||
|
|
314ab237ec | ||
|
|
0fa0416c3f | ||
|
|
09faedf059 | ||
|
|
16aa7983ed | ||
|
|
493bf8dc89 | ||
|
|
46432b9649 | ||
|
|
193664a8e8 | ||
|
|
626e578acb | ||
|
|
51bffe11a8 | ||
|
|
08e2c6c112 | ||
|
|
c38d91db98 | ||
|
|
c13839a522 | ||
|
|
4894d5162f | ||
|
|
77b6fb138f | ||
|
|
9dab74891d | ||
|
|
393d2a0052 | ||
|
|
44ac783f6a | ||
|
|
7ea8712538 | ||
|
|
fb92a793e4 | ||
|
|
87eaba6337 | ||
|
|
7e13f2ab32 | ||
|
|
3214d293ca | ||
|
|
1437116cf3 | ||
|
|
740001ddde | ||
|
|
24af3207e9 | ||
|
|
38746078ed | ||
|
|
fcea981127 |
@@ -6,3 +6,7 @@ test
|
||||
scripts
|
||||
docs
|
||||
firefox
|
||||
public
|
||||
views
|
||||
webpack
|
||||
frontend
|
||||
|
||||
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js,html,yml,json,handlebars}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.toml]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,10 +1,6 @@
|
||||
.DS_Store
|
||||
dist
|
||||
node_modules
|
||||
public/upload.js
|
||||
public/download.js
|
||||
public/version.json
|
||||
public/l20n.min.js
|
||||
public/polyfill.min.js
|
||||
static/*
|
||||
!static/info.txt
|
||||
test/frontend/bundle.js
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
extends: stylelint-config-standard
|
||||
|
||||
plugins:
|
||||
- stylelint-no-unsupported-browser-features
|
||||
|
||||
rules:
|
||||
plugin/no-unsupported-browser-features: [true, {severity: warning}]
|
||||
|
||||
color-hex-case: lower
|
||||
declaration-colon-newline-after: null
|
||||
selector-list-comma-newline-after: null
|
||||
|
||||
5
browserslist
Normal file
5
browserslist
Normal file
@@ -0,0 +1,5 @@
|
||||
last 2 chrome versions
|
||||
last 2 firefox versions
|
||||
firefox esr
|
||||
ie >= 9
|
||||
safari >= 9
|
||||
@@ -16,7 +16,6 @@ deployment:
|
||||
latest:
|
||||
branch: master
|
||||
commands:
|
||||
- npm run build
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- docker build -t mozilla/send:latest .
|
||||
- docker push mozilla/send:latest
|
||||
@@ -24,14 +23,13 @@ deployment:
|
||||
tag: /.*/
|
||||
owner: mozilla
|
||||
commands:
|
||||
- npm run build
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- docker build -t mozilla/send:$CIRCLE_TAG .
|
||||
- docker push mozilla/send:$CIRCLE_TAG
|
||||
|
||||
test:
|
||||
override:
|
||||
- npm run build:version
|
||||
- npm run build
|
||||
- npm run lint
|
||||
- npm test
|
||||
- nsp check
|
||||
|
||||
@@ -8,5 +8,6 @@ services:
|
||||
- "1443:1443"
|
||||
environment:
|
||||
- REDIS_HOST=redis
|
||||
- NODE_ENV=production
|
||||
redis:
|
||||
image: redis:alpine
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
env:
|
||||
browser: true
|
||||
jquery: true
|
||||
node: false
|
||||
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
|
||||
rules:
|
||||
node/no-unsupported-features: off
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Raven = require('raven-js');
|
||||
const { unsupported } = require('./metrics');
|
||||
import Raven from 'raven-js';
|
||||
import { unsupported } from './metrics';
|
||||
|
||||
if (navigator.doNotTrack !== '1' && window.RAVEN_CONFIG) {
|
||||
Raven.config(window.SENTRY_ID, window.RAVEN_CONFIG).install();
|
||||
@@ -17,6 +17,4 @@ if (
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Raven
|
||||
};
|
||||
export { Raven };
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
const { Raven } = require('./common');
|
||||
const FileReceiver = require('./fileReceiver');
|
||||
const { bytes, notify, gcmCompliant } = require('./utils');
|
||||
const Storage = require('./storage');
|
||||
const storage = new Storage(localStorage);
|
||||
const links = require('./links');
|
||||
const metrics = require('./metrics');
|
||||
const progress = require('./progress');
|
||||
const $ = require('jquery');
|
||||
import { Raven } from './common';
|
||||
import FileReceiver from './fileReceiver';
|
||||
import { bytes, notify, gcmCompliant } from './utils';
|
||||
import Storage from './storage';
|
||||
import * as links from './links';
|
||||
import * as metrics from './metrics';
|
||||
import * as progress from './progress';
|
||||
|
||||
const storage = new Storage();
|
||||
function onUnload(size) {
|
||||
metrics.cancelledDownload({ size });
|
||||
}
|
||||
|
||||
function download() {
|
||||
const $downloadBtn = $('#download-btn');
|
||||
const $title = $('.title');
|
||||
const $file = $('#dl-file');
|
||||
const size = Number($file.attr('data-size'));
|
||||
const ttl = Number($file.attr('data-ttl'));
|
||||
async function download() {
|
||||
const downloadBtn = document.getElementById('download-btn');
|
||||
const downloadPanel = document.getElementById('download-page-one');
|
||||
const progressPanel = document.getElementById('download-progress');
|
||||
const file = document.getElementById('dl-file');
|
||||
const size = Number(file.getAttribute('data-size'));
|
||||
const ttl = Number(file.getAttribute('data-ttl'));
|
||||
const unloadHandler = onUnload.bind(null, size);
|
||||
const startTime = Date.now();
|
||||
const fileReceiver = new FileReceiver();
|
||||
const fileReceiver = new FileReceiver(
|
||||
'/assets' + location.pathname.slice(0, -1),
|
||||
location.hash.slice(1)
|
||||
);
|
||||
|
||||
$downloadBtn.attr('disabled', 'disabled');
|
||||
$('#download-page-one').attr('hidden', true);
|
||||
$('#download-progress').removeAttr('hidden');
|
||||
downloadBtn.disabled = true;
|
||||
downloadPanel.hidden = true;
|
||||
progressPanel.hidden = false;
|
||||
metrics.startedDownload({ size, ttl });
|
||||
links.setOpenInNewTab(true);
|
||||
window.addEventListener('unload', unloadHandler);
|
||||
@@ -41,74 +44,72 @@ function download() {
|
||||
document.l10n.formatValue('decryptingFile').then(progress.setText);
|
||||
});
|
||||
|
||||
fileReceiver
|
||||
.download()
|
||||
.catch(err => {
|
||||
metrics.stoppedDownload({ size, err });
|
||||
try {
|
||||
const file = await fileReceiver.download();
|
||||
const endTime = Date.now();
|
||||
const time = endTime - startTime;
|
||||
const downloadTime = endTime - downloadEnd;
|
||||
const speed = size / (downloadTime / 1000);
|
||||
|
||||
if (err.message === 'notfound') {
|
||||
location.reload();
|
||||
} else {
|
||||
document.l10n.formatValue('errorPageHeader').then(translated => {
|
||||
$title.text(translated);
|
||||
});
|
||||
$downloadBtn.attr('hidden', true);
|
||||
$('#expired-img').removeAttr('hidden');
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
.then(([decrypted, fname]) => {
|
||||
const endTime = Date.now();
|
||||
const time = endTime - startTime;
|
||||
const downloadTime = endTime - downloadEnd;
|
||||
const speed = size / (downloadTime / 1000);
|
||||
storage.totalDownloads += 1;
|
||||
metrics.completedDownload({ size, time, speed });
|
||||
progress.setText(' ');
|
||||
document.l10n
|
||||
.formatValues('downloadNotification', 'downloadFinish')
|
||||
.then(translated => {
|
||||
notify(translated[0]);
|
||||
$title.text(translated[1]);
|
||||
});
|
||||
links.setOpenInNewTab(false);
|
||||
storage.totalDownloads += 1;
|
||||
metrics.completedDownload({ size, time, speed });
|
||||
progress.setText(' ');
|
||||
document.l10n
|
||||
.formatValues('downloadNotification', 'downloadFinish')
|
||||
.then(translated => {
|
||||
notify(translated[0]);
|
||||
document.getElementById('dl-title').textContent = translated[1];
|
||||
document.querySelector('#download-progress .description').textContent =
|
||||
' ';
|
||||
});
|
||||
const dataView = new DataView(file.plaintext);
|
||||
const blob = new Blob([dataView], { type: file.type });
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
|
||||
const dataView = new DataView(decrypted);
|
||||
const blob = new Blob([dataView]);
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
if (window.navigator.msSaveBlob) {
|
||||
window.navigator.msSaveBlob(blob, file.name);
|
||||
return;
|
||||
}
|
||||
a.download = file.name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
} catch (err) {
|
||||
metrics.stoppedDownload({ size, err });
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
if (window.navigator.msSaveBlob) {
|
||||
// if we are in microsoft edge or IE
|
||||
window.navigator.msSaveBlob(blob, fname);
|
||||
return;
|
||||
}
|
||||
a.download = fname;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
})
|
||||
.catch(err => {
|
||||
Raven.captureException(err);
|
||||
return Promise.reject(err);
|
||||
})
|
||||
.then(() => links.setOpenInNewTab(false));
|
||||
if (err.message === 'notfound') {
|
||||
location.reload();
|
||||
} else {
|
||||
progressPanel.hidden = true;
|
||||
downloadPanel.hidden = true;
|
||||
document.getElementById('upload-error').hidden = false;
|
||||
}
|
||||
Raven.captureException(err);
|
||||
}
|
||||
}
|
||||
|
||||
$(() => {
|
||||
const $file = $('#dl-file');
|
||||
const filename = $file.attr('data-filename');
|
||||
const b = Number($file.attr('data-size'));
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const file = document.getElementById('dl-file');
|
||||
const filename = file.getAttribute('data-filename');
|
||||
const b = Number(file.getAttribute('data-size'));
|
||||
const size = bytes(b);
|
||||
document.l10n
|
||||
.formatValue('downloadFileSize', { size })
|
||||
.then(str => $('#dl-filesize').text(str));
|
||||
document.l10n.formatValue('downloadFileSize', { size }).then(str => {
|
||||
document.getElementById('dl-filesize').textContent = str;
|
||||
});
|
||||
document.l10n
|
||||
.formatValue('downloadingPageProgress', { filename, size })
|
||||
.then(str => $('#dl-title').text(str));
|
||||
.then(str => {
|
||||
document.getElementById('dl-title').textContent = str;
|
||||
});
|
||||
|
||||
gcmCompliant()
|
||||
.then(() => {
|
||||
$('#download-btn').on('click', download);
|
||||
document
|
||||
.getElementById('download-btn')
|
||||
.addEventListener('click', download);
|
||||
})
|
||||
.catch(err => {
|
||||
metrics.unsupported({ err }).then(() => {
|
||||
|
||||
163
frontend/src/fileList.js
Normal file
163
frontend/src/fileList.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import FileSender from './fileSender';
|
||||
import Storage from './storage';
|
||||
import * as metrics from './metrics';
|
||||
import { allowedCopy, copyToClipboard, ONE_DAY_IN_MS } from './utils';
|
||||
import bel from 'bel';
|
||||
import copyImg from '../../public/resources/copy-16.svg';
|
||||
import closeImg from '../../public/resources/close-16.svg';
|
||||
|
||||
const HOUR = 1000 * 60 * 60;
|
||||
const storage = new Storage();
|
||||
let fileList = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
fileList = document.getElementById('file-list');
|
||||
toggleHeader();
|
||||
Promise.all(
|
||||
storage.files.map(file => {
|
||||
const id = file.fileId;
|
||||
return checkExistence(id).then(exists => {
|
||||
if (exists) {
|
||||
addFile(storage.getFileById(id));
|
||||
} else {
|
||||
storage.remove(id);
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
.catch(err => console.error(err))
|
||||
.then(toggleHeader);
|
||||
});
|
||||
|
||||
function toggleHeader() {
|
||||
fileList.hidden = storage.files.length === 0;
|
||||
}
|
||||
|
||||
function timeLeft(milliseconds) {
|
||||
const minutes = Math.floor(milliseconds / 1000 / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const seconds = Math.floor(milliseconds / 1000 % 60);
|
||||
if (hours >= 1) {
|
||||
return `${hours}h ${minutes % 60}m`;
|
||||
} else if (hours === 0) {
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
return 'Expired';
|
||||
}
|
||||
|
||||
function addFile(file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
file.creationDate = new Date(file.creationDate);
|
||||
const url = `${file.url}#${file.secretKey}`;
|
||||
const future = new Date();
|
||||
future.setTime(file.creationDate.getTime() + file.expiry);
|
||||
const countdown = future.getTime() - Date.now();
|
||||
|
||||
const row = bel`
|
||||
<tr>
|
||||
<td>${file.name}</td>
|
||||
<td>
|
||||
<span class="icon-docs" data-l10n-id="copyUrlHover"></span>
|
||||
<img onclick=${copyClick} src="${copyImg}" class="icon-copy" data-l10n-id="copyUrlHover">
|
||||
<span data-l10n-id="copiedUrl" class="text-copied" hidden="true"></span>
|
||||
</td>
|
||||
<td>${timeLeft(countdown)}</td>
|
||||
<td>
|
||||
<span class="icon-cancel-1" data-l10n-id="deleteButtonHover" title="Delete"></span>
|
||||
<img onclick=${showPopup} src="${closeImg}" class="icon-delete" data-l10n-id="deleteButtonHover" title="Delete">
|
||||
<div class="popup">
|
||||
<div class="popuptext" onclick=${stopProp} onblur=${cancel} tabindex="-1">
|
||||
<div class="popup-message" data-l10n-id="deletePopupText"></div>
|
||||
<div class="popup-action">
|
||||
<span class="popup-no" onclick=${cancel} data-l10n-id="deletePopupCancel"></span>
|
||||
<span class="popup-yes" onclick=${deleteFile} data-l10n-id="deletePopupYes"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
const popup = row.querySelector('.popuptext');
|
||||
const timeCol = row.querySelectorAll('td')[2];
|
||||
if (!allowedCopy()) {
|
||||
row.querySelector('.icon-copy').disabled = true;
|
||||
}
|
||||
|
||||
fileList.querySelector('tbody').appendChild(row);
|
||||
toggleHeader();
|
||||
poll();
|
||||
|
||||
function copyClick(e) {
|
||||
metrics.copiedLink({ location: 'upload-list' });
|
||||
copyToClipboard(url);
|
||||
const icon = e.target;
|
||||
const text = e.target.nextSibling;
|
||||
icon.hidden = true;
|
||||
text.hidden = false;
|
||||
setTimeout(() => {
|
||||
icon.hidden = false;
|
||||
text.hidden = true;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function poll() {
|
||||
const countdown = future.getTime() - Date.now();
|
||||
if (countdown <= 0) {
|
||||
storage.remove(file.fileId);
|
||||
row.parentNode.removeChild(row);
|
||||
toggleHeader();
|
||||
}
|
||||
timeCol.textContent = timeLeft(countdown);
|
||||
setTimeout(poll, countdown >= HOUR ? 60000 : 1000);
|
||||
}
|
||||
|
||||
function deleteFile() {
|
||||
FileSender.delete(file.fileId, file.deleteToken);
|
||||
const ttl = ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
|
||||
metrics.deletedUpload({
|
||||
size: file.size,
|
||||
time: file.totalTime,
|
||||
speed: file.uploadSpeed,
|
||||
type: file.typeOfUpload,
|
||||
location: 'upload-list',
|
||||
ttl
|
||||
});
|
||||
row.parentNode.removeChild(row);
|
||||
storage.remove(file.fileId);
|
||||
toggleHeader();
|
||||
}
|
||||
|
||||
function showPopup() {
|
||||
popup.classList.add('show');
|
||||
popup.focus();
|
||||
}
|
||||
|
||||
function cancel(e) {
|
||||
e.stopPropagation();
|
||||
popup.classList.remove('show');
|
||||
}
|
||||
|
||||
function stopProp(e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
async function checkExistence(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
|
||||
resolve(xhr.status === 200);
|
||||
}
|
||||
};
|
||||
xhr.onerror = reject;
|
||||
xhr.ontimeout = reject;
|
||||
xhr.open('get', '/exists/' + id);
|
||||
xhr.timeout = 2000;
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
export { addFile };
|
||||
@@ -1,85 +1,80 @@
|
||||
const EventEmitter = require('events');
|
||||
const { hexToArray } = require('./utils');
|
||||
import EventEmitter from 'events';
|
||||
import { hexToArray } from './utils';
|
||||
|
||||
class FileReceiver extends EventEmitter {
|
||||
constructor() {
|
||||
export default class FileReceiver extends EventEmitter {
|
||||
constructor(url, k) {
|
||||
super();
|
||||
this.key = window.crypto.subtle.importKey(
|
||||
'jwk',
|
||||
{
|
||||
k,
|
||||
kty: 'oct',
|
||||
alg: 'A128GCM',
|
||||
ext: true
|
||||
},
|
||||
{
|
||||
name: 'AES-GCM'
|
||||
},
|
||||
false,
|
||||
['decrypt']
|
||||
);
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
download() {
|
||||
return window.crypto.subtle
|
||||
.importKey(
|
||||
'jwk',
|
||||
{
|
||||
kty: 'oct',
|
||||
k: location.hash.slice(1),
|
||||
alg: 'A128GCM',
|
||||
ext: true
|
||||
},
|
||||
{
|
||||
name: 'AES-GCM'
|
||||
},
|
||||
true,
|
||||
['encrypt', 'decrypt']
|
||||
)
|
||||
.then(key => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
downloadFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onprogress = event => {
|
||||
if (event.lengthComputable && event.target.status !== 404) {
|
||||
this.emit('progress', [event.loaded, event.total]);
|
||||
}
|
||||
};
|
||||
xhr.onprogress = event => {
|
||||
if (event.lengthComputable && event.target.status !== 404) {
|
||||
this.emit('progress', [event.loaded, event.total]);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function(event) {
|
||||
if (xhr.status === 404) {
|
||||
reject(new Error('notfound'));
|
||||
return;
|
||||
}
|
||||
xhr.onload = function(event) {
|
||||
if (xhr.status === 404) {
|
||||
reject(new Error('notfound'));
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = new Blob([this.response]);
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function() {
|
||||
const meta = JSON.parse(xhr.getResponseHeader('X-File-Metadata'));
|
||||
resolve([
|
||||
{
|
||||
data: this.result,
|
||||
filename: meta.filename,
|
||||
iv: meta.id
|
||||
},
|
||||
key
|
||||
]);
|
||||
};
|
||||
const blob = new Blob([this.response]);
|
||||
const meta = JSON.parse(xhr.getResponseHeader('X-File-Metadata'));
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function() {
|
||||
resolve({
|
||||
data: this.result,
|
||||
name: meta.filename,
|
||||
type: meta.mimeType,
|
||||
iv: meta.id
|
||||
});
|
||||
};
|
||||
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
};
|
||||
|
||||
xhr.open('get', '/assets' + location.pathname.slice(0, -1), true);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.send();
|
||||
});
|
||||
})
|
||||
.then(([fdata, key]) => {
|
||||
this.emit('decrypting');
|
||||
return Promise.all([
|
||||
window.crypto.subtle
|
||||
.decrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: hexToArray(fdata.iv),
|
||||
tagLength: 128
|
||||
},
|
||||
key,
|
||||
fdata.data
|
||||
)
|
||||
.then(decrypted => {
|
||||
return Promise.resolve(decrypted);
|
||||
}),
|
||||
decodeURIComponent(fdata.filename)
|
||||
]);
|
||||
});
|
||||
xhr.open('get', this.url);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
async download() {
|
||||
const key = await this.key;
|
||||
const file = await this.downloadFile();
|
||||
this.emit('decrypting');
|
||||
const plaintext = await window.crypto.subtle.decrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: hexToArray(file.iv),
|
||||
tagLength: 128
|
||||
},
|
||||
key,
|
||||
file.data
|
||||
);
|
||||
return {
|
||||
plaintext,
|
||||
name: decodeURIComponent(file.name),
|
||||
type: file.type
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileReceiver;
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
const EventEmitter = require('events');
|
||||
const { arrayToHex } = require('./utils');
|
||||
import EventEmitter from 'events';
|
||||
import { arrayToHex } from './utils';
|
||||
|
||||
class FileSender extends EventEmitter {
|
||||
export default class FileSender extends EventEmitter {
|
||||
constructor(file) {
|
||||
super();
|
||||
this.file = file;
|
||||
this.iv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||
this.uploadXHR = new XMLHttpRequest();
|
||||
this.key = window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
length: 128
|
||||
},
|
||||
true,
|
||||
['encrypt']
|
||||
);
|
||||
}
|
||||
|
||||
static delete(fileId, token) {
|
||||
@@ -32,89 +40,79 @@ class FileSender extends EventEmitter {
|
||||
this.uploadXHR.abort();
|
||||
}
|
||||
|
||||
upload() {
|
||||
const self = this;
|
||||
self.emit('loading');
|
||||
return Promise.all([
|
||||
window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
length: 128
|
||||
},
|
||||
true,
|
||||
['encrypt', 'decrypt']
|
||||
),
|
||||
new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(this.file);
|
||||
reader.onload = function(event) {
|
||||
const plaintext = new Uint8Array(this.result);
|
||||
resolve(plaintext);
|
||||
};
|
||||
reader.onerror = function(err) {
|
||||
reject(err);
|
||||
};
|
||||
})
|
||||
])
|
||||
.then(([secretKey, plaintext]) => {
|
||||
self.emit('encrypting');
|
||||
return Promise.all([
|
||||
window.crypto.subtle.encrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv,
|
||||
tagLength: 128
|
||||
},
|
||||
secretKey,
|
||||
plaintext
|
||||
),
|
||||
window.crypto.subtle.exportKey('jwk', secretKey)
|
||||
]);
|
||||
})
|
||||
.then(([encrypted, keydata]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = this.file;
|
||||
const fileId = arrayToHex(this.iv);
|
||||
const dataView = new DataView(encrypted);
|
||||
const blob = new Blob([dataView], { type: file.type });
|
||||
const fd = new FormData();
|
||||
fd.append('data', blob, file.name);
|
||||
readFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(this.file);
|
||||
reader.onload = function(event) {
|
||||
const plaintext = new Uint8Array(this.result);
|
||||
resolve(plaintext);
|
||||
};
|
||||
reader.onerror = function(err) {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const xhr = self.uploadXHR;
|
||||
uploadFile(encrypted, keydata) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = this.file;
|
||||
const fileId = arrayToHex(this.iv);
|
||||
const dataView = new DataView(encrypted);
|
||||
const blob = new Blob([dataView], { type: file.type });
|
||||
const fd = new FormData();
|
||||
fd.append('data', blob, file.name);
|
||||
|
||||
xhr.upload.addEventListener('progress', e => {
|
||||
if (e.lengthComputable) {
|
||||
self.emit('progress', [e.loaded, e.total]);
|
||||
}
|
||||
});
|
||||
const xhr = this.uploadXHR;
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
const responseObj = JSON.parse(xhr.responseText);
|
||||
return resolve({
|
||||
url: responseObj.url,
|
||||
fileId: responseObj.id,
|
||||
secretKey: keydata.k,
|
||||
deleteToken: responseObj.delete
|
||||
});
|
||||
}
|
||||
reject(xhr.status);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open('post', '/upload', true);
|
||||
xhr.setRequestHeader(
|
||||
'X-File-Metadata',
|
||||
JSON.stringify({
|
||||
id: fileId,
|
||||
filename: encodeURIComponent(file.name)
|
||||
})
|
||||
);
|
||||
xhr.send(fd);
|
||||
});
|
||||
xhr.upload.addEventListener('progress', e => {
|
||||
if (e.lengthComputable) {
|
||||
this.emit('progress', [e.loaded, e.total]);
|
||||
}
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
const responseObj = JSON.parse(xhr.responseText);
|
||||
return resolve({
|
||||
url: responseObj.url,
|
||||
fileId: responseObj.id,
|
||||
secretKey: keydata.k,
|
||||
deleteToken: responseObj.delete
|
||||
});
|
||||
}
|
||||
reject(xhr.status);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open('post', '/upload', true);
|
||||
xhr.setRequestHeader(
|
||||
'X-File-Metadata',
|
||||
JSON.stringify({
|
||||
id: fileId,
|
||||
filename: encodeURIComponent(file.name)
|
||||
})
|
||||
);
|
||||
xhr.send(fd);
|
||||
});
|
||||
}
|
||||
|
||||
async upload() {
|
||||
this.emit('loading');
|
||||
const key = await this.key;
|
||||
const plaintext = await this.readFile();
|
||||
this.emit('encrypting');
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv,
|
||||
tagLength: 128
|
||||
},
|
||||
key,
|
||||
plaintext
|
||||
);
|
||||
const keydata = await window.crypto.subtle.exportKey('jwk', key);
|
||||
return this.uploadFile(encrypted, keydata);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileSender;
|
||||
|
||||
@@ -18,6 +18,4 @@ function setOpenInNewTab(bool) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setOpenInNewTab
|
||||
};
|
||||
export { setOpenInNewTab };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*** index.html ***/
|
||||
html {
|
||||
background: url('resources/send_bg.svg');
|
||||
background: url('../../public/resources/send_bg.svg');
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'segoe ui',
|
||||
'helvetica neue', helvetica, ubuntu, roboto, noto, arial, sans-serif;
|
||||
font-weight: 200;
|
||||
@@ -22,6 +22,16 @@ body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#progress circle {
|
||||
stroke: #eee;
|
||||
stroke-width: 0.75em;
|
||||
}
|
||||
|
||||
#progress #bar {
|
||||
transition: stroke-dashoffset 300ms linear;
|
||||
stroke: #3b9dff;
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
@@ -79,7 +89,7 @@ body {
|
||||
|
||||
.feedback {
|
||||
background-color: #0297f8;
|
||||
background-image: url('resources/feedback.svg');
|
||||
background-image: url('../../public/resources/feedback.svg');
|
||||
background-position: 2px 4px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 18px;
|
||||
@@ -138,6 +148,10 @@ a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/** page-one **/
|
||||
|
||||
.title {
|
||||
@@ -208,12 +222,13 @@ a {
|
||||
border-radius: 5px;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
width: 240px;
|
||||
min-width: 240px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#browse:hover {
|
||||
@@ -295,6 +310,10 @@ tbody {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.text-copied {
|
||||
color: #0a8dff;
|
||||
}
|
||||
|
||||
/* Popup container */
|
||||
.popup {
|
||||
position: absolute;
|
||||
@@ -415,7 +434,7 @@ tbody {
|
||||
position: absolute;
|
||||
letter-spacing: -0.78px;
|
||||
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
|
||||
top: 53px;
|
||||
top: 58px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
-moz-user-select: none;
|
||||
@@ -644,7 +663,6 @@ tbody {
|
||||
background: #0297f8;
|
||||
border: 1px solid #0297f8;
|
||||
border-radius: 5px;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
const testPilotGA = require('testpilot-ga/src/TestPilotGA');
|
||||
const Storage = require('./storage');
|
||||
const storage = new Storage(localStorage);
|
||||
import testPilotGA from 'testpilot-ga/src/TestPilotGA';
|
||||
import Storage from './storage';
|
||||
const storage = new Storage();
|
||||
|
||||
let hasLocalStorage = false;
|
||||
try {
|
||||
hasLocalStorage = !!localStorage;
|
||||
} catch (e) {
|
||||
// don't care
|
||||
}
|
||||
|
||||
const analytics = new testPilotGA({
|
||||
an: 'Firefox Send',
|
||||
@@ -18,7 +25,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
function sendEvent() {
|
||||
return analytics.sendEvent.apply(analytics, arguments).catch(() => 0);
|
||||
return (
|
||||
hasLocalStorage &&
|
||||
analytics.sendEvent.apply(analytics, arguments).catch(() => 0)
|
||||
);
|
||||
}
|
||||
|
||||
function urlToMetric(url) {
|
||||
@@ -41,6 +51,11 @@ function urlToMetric(url) {
|
||||
return 'twitter';
|
||||
case 'https://www.mozilla.org/firefox/new/?scene=2':
|
||||
return 'download-firefox';
|
||||
case 'https://qsurvey.mozilla.com/s3/txp-firefox-send':
|
||||
return 'survey';
|
||||
case 'https://testpilot.firefox.com/':
|
||||
case 'https://testpilot.firefox.com/experiments/send':
|
||||
return 'testpilot';
|
||||
default:
|
||||
return 'other';
|
||||
}
|
||||
@@ -198,7 +213,7 @@ function exitEvent(target) {
|
||||
function addExitHandlers() {
|
||||
const links = Array.from(document.querySelectorAll('a'));
|
||||
links.forEach(l => {
|
||||
if (/^http/.test(l.href)) {
|
||||
if (/^http/.test(l.getAttribute('href'))) {
|
||||
l.addEventListener('click', exitEvent);
|
||||
}
|
||||
});
|
||||
@@ -219,7 +234,7 @@ function addRestartHandlers() {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
copiedLink,
|
||||
startedUpload,
|
||||
cancelledUpload,
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
const { bytes } = require('./utils');
|
||||
const $ = require('jquery');
|
||||
require('jquery-circle-progress');
|
||||
import { bytes, percent } from './utils';
|
||||
|
||||
let $progress = null;
|
||||
let $percent = null;
|
||||
let $text = null;
|
||||
let percentText = null;
|
||||
let text = null;
|
||||
let title = null;
|
||||
let bar = null;
|
||||
let updateTitle = false;
|
||||
|
||||
const radius = 73;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
$percent = $('.percent-number');
|
||||
$text = $('.progress-text');
|
||||
$progress = $('.progress-bar');
|
||||
$progress.circleProgress({
|
||||
value: 0.0,
|
||||
startAngle: -Math.PI / 2,
|
||||
fill: '#3B9DFF',
|
||||
size: 158,
|
||||
animation: { duration: 300 }
|
||||
});
|
||||
percentText = document.querySelector('.percent-number');
|
||||
text = document.querySelector('.progress-text');
|
||||
bar = document.getElementById('bar');
|
||||
title = document.querySelector('title');
|
||||
});
|
||||
|
||||
document.addEventListener('blur', function() {
|
||||
updateTitle = true;
|
||||
});
|
||||
|
||||
document.addEventListener('focus', function() {
|
||||
updateTitle = false;
|
||||
return title && (title.textContent = 'Firefox Send');
|
||||
});
|
||||
|
||||
function setProgress(params) {
|
||||
const percent = params.complete / params.total;
|
||||
$progress.circleProgress('value', percent);
|
||||
$percent.text(`${Math.floor(percent * 100)}`);
|
||||
const ratio = params.complete / params.total;
|
||||
bar.setAttribute('stroke-dashoffset', (1 - ratio) * circumference);
|
||||
percentText.textContent = Math.floor(ratio * 100);
|
||||
if (updateTitle) {
|
||||
title.textContent = percent(ratio);
|
||||
}
|
||||
document.l10n
|
||||
.formatValue('fileSizeProgress', {
|
||||
partialSize: bytes(params.complete),
|
||||
@@ -32,10 +41,7 @@ function setProgress(params) {
|
||||
}
|
||||
|
||||
function setText(str) {
|
||||
$text.text(str);
|
||||
text.textContent = str;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setProgress,
|
||||
setText
|
||||
};
|
||||
export { setProgress, setText };
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
const { isFile } = require('./utils');
|
||||
import { isFile } from './utils';
|
||||
|
||||
class Storage {
|
||||
constructor(engine) {
|
||||
this.engine = engine;
|
||||
class Mem {
|
||||
constructor() {
|
||||
this.items = new Map();
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.items.size;
|
||||
}
|
||||
|
||||
getItem(key) {
|
||||
return this.items.get(key);
|
||||
}
|
||||
|
||||
setItem(key, value) {
|
||||
return this.items.set(key, value);
|
||||
}
|
||||
|
||||
removeItem(key) {
|
||||
return this.items.delete(key);
|
||||
}
|
||||
|
||||
key(i) {
|
||||
return this.items.keys()[i];
|
||||
}
|
||||
}
|
||||
|
||||
export default class Storage {
|
||||
constructor() {
|
||||
try {
|
||||
this.engine = localStorage || new Mem();
|
||||
} catch (e) {
|
||||
this.engine = new Mem();
|
||||
}
|
||||
}
|
||||
|
||||
get totalDownloads() {
|
||||
@@ -56,11 +86,11 @@ class Storage {
|
||||
}
|
||||
|
||||
getFileById(id) {
|
||||
return this.engine.getItem(id);
|
||||
}
|
||||
|
||||
has(property) {
|
||||
return this.engine.hasOwnProperty(property);
|
||||
try {
|
||||
return JSON.parse(this.engine.getItem(id));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
remove(property) {
|
||||
@@ -71,5 +101,3 @@ class Storage {
|
||||
this.engine.setItem(id, JSON.stringify(file));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Storage;
|
||||
|
||||
@@ -1,472 +1,253 @@
|
||||
/* global MAXFILESIZE EXPIRE_SECONDS */
|
||||
const { Raven } = require('./common');
|
||||
const FileSender = require('./fileSender');
|
||||
const {
|
||||
import { Raven } from './common';
|
||||
import FileSender from './fileSender';
|
||||
import {
|
||||
allowedCopy,
|
||||
bytes,
|
||||
copyToClipboard,
|
||||
notify,
|
||||
gcmCompliant,
|
||||
ONE_DAY_IN_MS
|
||||
} = require('./utils');
|
||||
const Storage = require('./storage');
|
||||
const storage = new Storage(localStorage);
|
||||
const metrics = require('./metrics');
|
||||
const progress = require('./progress');
|
||||
} from './utils';
|
||||
import Storage from './storage';
|
||||
import * as metrics from './metrics';
|
||||
import * as progress from './progress';
|
||||
import * as fileList from './fileList';
|
||||
import checkImg from '../../public/resources/check-16.svg';
|
||||
|
||||
const $ = require('jquery');
|
||||
const storage = new Storage();
|
||||
|
||||
const allowedCopy = () => {
|
||||
const support = !!document.queryCommandSupported;
|
||||
return support ? document.queryCommandSupported('copy') : false;
|
||||
};
|
||||
async function upload(event) {
|
||||
event.preventDefault();
|
||||
const pageOne = document.getElementById('page-one');
|
||||
const link = document.getElementById('link');
|
||||
const uploadWindow = document.querySelector('.upload-window');
|
||||
const uploadError = document.getElementById('upload-error');
|
||||
const uploadProgress = document.getElementById('upload-progress');
|
||||
const clickOrDrop = event.type === 'drop' ? 'drop' : 'click';
|
||||
|
||||
$(() => {
|
||||
// don't allow upload if not on upload page
|
||||
if (pageOne.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
storage.totalUploads += 1;
|
||||
|
||||
let file = '';
|
||||
if (clickOrDrop === 'drop') {
|
||||
if (!event.dataTransfer.files[0]) {
|
||||
uploadWindow.classList.remove('ondrag');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
event.dataTransfer.files.length > 1 ||
|
||||
event.dataTransfer.files[0].size === 0
|
||||
) {
|
||||
uploadWindow.classList.remove('ondrag');
|
||||
document.l10n.formatValue('uploadPageMultipleFilesAlert').then(str => {
|
||||
alert(str);
|
||||
});
|
||||
return;
|
||||
}
|
||||
file = event.dataTransfer.files[0];
|
||||
} else {
|
||||
file = event.target.files[0];
|
||||
}
|
||||
|
||||
if (file.size > MAXFILESIZE) {
|
||||
return document.l10n
|
||||
.formatValue('fileTooBig', { size: bytes(MAXFILESIZE) })
|
||||
.then(alert);
|
||||
}
|
||||
|
||||
pageOne.hidden = true;
|
||||
uploadError.hidden = true;
|
||||
uploadProgress.hidden = false;
|
||||
document.l10n
|
||||
.formatValue('uploadingPageProgress', {
|
||||
size: bytes(file.size),
|
||||
filename: file.name
|
||||
})
|
||||
.then(str => {
|
||||
document.getElementById('upload-filename').textContent = str;
|
||||
});
|
||||
document.l10n.formatValue('importingFile').then(progress.setText);
|
||||
//don't allow drag and drop when not on page-one
|
||||
document.body.removeEventListener('drop', upload);
|
||||
|
||||
const fileSender = new FileSender(file);
|
||||
document.getElementById('cancel-upload').addEventListener('click', () => {
|
||||
fileSender.cancel();
|
||||
metrics.cancelledUpload({
|
||||
size: file.size,
|
||||
type: clickOrDrop
|
||||
});
|
||||
location.reload();
|
||||
});
|
||||
|
||||
let uploadStart;
|
||||
fileSender.on('progress', data => {
|
||||
uploadStart = uploadStart || Date.now();
|
||||
progress.setProgress({
|
||||
complete: data[0],
|
||||
total: data[1]
|
||||
});
|
||||
});
|
||||
|
||||
fileSender.on('encrypting', () => {
|
||||
document.l10n.formatValue('encryptingFile').then(progress.setText);
|
||||
});
|
||||
|
||||
let t;
|
||||
const startTime = Date.now();
|
||||
metrics.startedUpload({
|
||||
size: file.size,
|
||||
type: clickOrDrop
|
||||
});
|
||||
// For large files we need to give the ui a tick to breathe and update
|
||||
// before we kick off the FileSender
|
||||
setTimeout(() => {
|
||||
fileSender
|
||||
.upload()
|
||||
.then(info => {
|
||||
const endTime = Date.now();
|
||||
const time = endTime - startTime;
|
||||
const uploadTime = endTime - uploadStart;
|
||||
const speed = file.size / (uploadTime / 1000);
|
||||
const expiration = EXPIRE_SECONDS * 1000;
|
||||
|
||||
link.setAttribute('value', `${info.url}#${info.secretKey}`);
|
||||
|
||||
const copyText = document.getElementById('copy-text');
|
||||
copyText.setAttribute(
|
||||
'data-l10n-args',
|
||||
JSON.stringify({ filename: file.name })
|
||||
);
|
||||
copyText.setAttribute('data-l10n-id', 'copyUrlFormLabelWithName');
|
||||
|
||||
metrics.completedUpload({
|
||||
size: file.size,
|
||||
time,
|
||||
speed,
|
||||
type: clickOrDrop
|
||||
});
|
||||
|
||||
const fileData = {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
fileId: info.fileId,
|
||||
url: info.url,
|
||||
secretKey: info.secretKey,
|
||||
deleteToken: info.deleteToken,
|
||||
creationDate: new Date(),
|
||||
expiry: expiration,
|
||||
totalTime: time,
|
||||
typeOfUpload: clickOrDrop,
|
||||
uploadSpeed: speed
|
||||
};
|
||||
|
||||
document.getElementById('delete-file').addEventListener('click', () => {
|
||||
FileSender.delete(fileData.fileId, fileData.deleteToken).then(() => {
|
||||
const ttl =
|
||||
ONE_DAY_IN_MS - (Date.now() - fileData.creationDate.getTime());
|
||||
metrics
|
||||
.deletedUpload({
|
||||
size: fileData.size,
|
||||
time: fileData.totalTime,
|
||||
speed: fileData.uploadSpeed,
|
||||
type: fileData.typeOfUpload,
|
||||
location: 'success-screen',
|
||||
ttl
|
||||
})
|
||||
.then(() => {
|
||||
storage.remove(fileData.fileId);
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
storage.addFile(info.fileId, fileData);
|
||||
|
||||
pageOne.hidden = true;
|
||||
uploadProgress.hidden = true;
|
||||
uploadError.hidden = true;
|
||||
document.getElementById('share-link').hidden = false;
|
||||
|
||||
fileList.addFile(fileData);
|
||||
document.l10n.formatValue('notifyUploadDone').then(str => {
|
||||
notify(str);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
// err is 0 when coming from a cancel upload event
|
||||
if (err === 0) {
|
||||
return;
|
||||
}
|
||||
// only show error page when the error is anything other than user cancelling the upload
|
||||
Raven.captureException(err);
|
||||
pageOne.hidden = true;
|
||||
uploadProgress.hidden = true;
|
||||
uploadError.hidden = false;
|
||||
window.clearTimeout(t);
|
||||
|
||||
metrics.stoppedUpload({
|
||||
size: file.size,
|
||||
type: clickOrDrop,
|
||||
err
|
||||
});
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
gcmCompliant()
|
||||
.then(function() {
|
||||
const $pageOne = $('#page-one');
|
||||
const $copyBtn = $('#copy-btn');
|
||||
const $link = $('#link');
|
||||
const $uploadWindow = $('.upload-window');
|
||||
const $uploadError = $('#upload-error');
|
||||
const $uploadProgress = $('#upload-progress');
|
||||
const $fileList = $('#file-list');
|
||||
const pageOne = document.getElementById('page-one');
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
const link = document.getElementById('link');
|
||||
const uploadWindow = document.querySelector('.upload-window');
|
||||
|
||||
$pageOne.removeAttr('hidden');
|
||||
$('#file-upload').on('change', onUpload);
|
||||
pageOne.hidden = false;
|
||||
document.getElementById('file-upload').addEventListener('change', upload);
|
||||
|
||||
$(document.body).on('dragover', allowDrop).on('drop', onUpload);
|
||||
document.body.addEventListener('dragover', allowDrop);
|
||||
document.body.addEventListener('drop', upload);
|
||||
|
||||
// reset copy button
|
||||
$copyBtn.attr({
|
||||
disabled: !allowedCopy(),
|
||||
'data-l10n-id': 'copyUrlFormButton'
|
||||
});
|
||||
copyBtn.disabled = !allowedCopy();
|
||||
copyBtn.setAttribute('data-l10n-id', 'copyUrlFormButton');
|
||||
|
||||
$link.attr('disabled', false);
|
||||
|
||||
const toggleHeader = () => {
|
||||
//hide table header if empty list
|
||||
if (document.querySelector('tbody').childNodes.length === 1) {
|
||||
$fileList.attr('hidden', true);
|
||||
} else {
|
||||
$fileList.removeAttr('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const files = storage.files;
|
||||
if (files.length === 0) {
|
||||
toggleHeader();
|
||||
} else {
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let index in files) {
|
||||
const id = files[index].fileId;
|
||||
//check if file still exists before adding to list
|
||||
checkExistence(id, files[index], true);
|
||||
}
|
||||
}
|
||||
link.disabled = false;
|
||||
|
||||
// copy link to clipboard
|
||||
$copyBtn.on('click', () => {
|
||||
if (allowedCopy() && copyToClipboard($link.attr('value'))) {
|
||||
copyBtn.addEventListener('click', () => {
|
||||
if (allowedCopy() && copyToClipboard(link.getAttribute('value'))) {
|
||||
metrics.copiedLink({ location: 'success-screen' });
|
||||
|
||||
//disable button for 3s
|
||||
$copyBtn.attr('disabled', true);
|
||||
$link.attr('disabled', true);
|
||||
$copyBtn.html(
|
||||
'<img src="/resources/check-16.svg" class="icon-check"></img>'
|
||||
);
|
||||
copyBtn.disabled = true;
|
||||
link.disabled = true;
|
||||
copyBtn.innerHTML = `<img src="${checkImg}" class="icon-check"></img>`;
|
||||
setTimeout(() => {
|
||||
$copyBtn.attr({
|
||||
disabled: false,
|
||||
'data-l10n-id': 'copyUrlFormButton'
|
||||
});
|
||||
$link.attr('disabled', false);
|
||||
copyBtn.disabled = !allowedCopy();
|
||||
copyBtn.setAttribute('data-l10n-id', 'copyUrlFormButton');
|
||||
link.disabled = false;
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
|
||||
$uploadWindow
|
||||
.on('dragover', () => {
|
||||
$uploadWindow.addClass('ondrag');
|
||||
})
|
||||
.on('dragleave', () => {
|
||||
$uploadWindow.removeClass('ondrag');
|
||||
});
|
||||
uploadWindow.addEventListener('dragover', () =>
|
||||
uploadWindow.classList.add('ondrag')
|
||||
);
|
||||
uploadWindow.addEventListener('dragleave', () =>
|
||||
uploadWindow.classList.remove('ondrag')
|
||||
);
|
||||
|
||||
// on file upload by browse or drag & drop
|
||||
function onUpload(event) {
|
||||
event.preventDefault();
|
||||
const clickOrDrop = event.type === 'drop' ? 'drop' : 'click';
|
||||
|
||||
// don't allow upload if not on upload page
|
||||
if ($pageOne.attr('hidden')) {
|
||||
return;
|
||||
}
|
||||
|
||||
storage.totalUploads += 1;
|
||||
|
||||
let file = '';
|
||||
if (clickOrDrop === 'drop') {
|
||||
if (!event.originalEvent.dataTransfer.files[0]) {
|
||||
$uploadWindow.removeClass('ondrag');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
event.originalEvent.dataTransfer.files.length > 1 ||
|
||||
event.originalEvent.dataTransfer.files[0].size === 0
|
||||
) {
|
||||
$uploadWindow.removeClass('ondrag');
|
||||
document.l10n
|
||||
.formatValue('uploadPageMultipleFilesAlert')
|
||||
.then(str => {
|
||||
alert(str);
|
||||
});
|
||||
return;
|
||||
}
|
||||
file = event.originalEvent.dataTransfer.files[0];
|
||||
} else {
|
||||
file = event.target.files[0];
|
||||
}
|
||||
|
||||
if (file.size > MAXFILESIZE) {
|
||||
return document.l10n
|
||||
.formatValue('fileTooBig', { size: bytes(MAXFILESIZE) })
|
||||
.then(alert);
|
||||
}
|
||||
|
||||
$pageOne.attr('hidden', true);
|
||||
$uploadError.attr('hidden', true);
|
||||
$uploadProgress.removeAttr('hidden');
|
||||
document.l10n
|
||||
.formatValue('uploadingPageProgress', {
|
||||
size: bytes(file.size),
|
||||
filename: file.name
|
||||
})
|
||||
.then(str => {
|
||||
$('#upload-filename').text(str);
|
||||
});
|
||||
document.l10n.formatValue('importingFile').then(progress.setText);
|
||||
//don't allow drag and drop when not on page-one
|
||||
$(document.body).off('drop', onUpload);
|
||||
|
||||
const fileSender = new FileSender(file);
|
||||
$('#cancel-upload').on('click', () => {
|
||||
fileSender.cancel();
|
||||
metrics.cancelledUpload({
|
||||
size: file.size,
|
||||
type: clickOrDrop
|
||||
});
|
||||
location.reload();
|
||||
});
|
||||
|
||||
let uploadStart;
|
||||
fileSender.on('progress', data => {
|
||||
uploadStart = uploadStart || Date.now();
|
||||
progress.setProgress({
|
||||
complete: data[0],
|
||||
total: data[1]
|
||||
});
|
||||
});
|
||||
|
||||
fileSender.on('encrypting', () => {
|
||||
document.l10n.formatValue('encryptingFile').then(progress.setText);
|
||||
});
|
||||
|
||||
let t;
|
||||
const startTime = Date.now();
|
||||
metrics.startedUpload({
|
||||
size: file.size,
|
||||
type: clickOrDrop
|
||||
});
|
||||
// For large files we need to give the ui a tick to breathe and update
|
||||
// before we kick off the FileSender
|
||||
setTimeout(() => {
|
||||
fileSender
|
||||
.upload()
|
||||
.then(info => {
|
||||
const endTime = Date.now();
|
||||
const time = endTime - startTime;
|
||||
const uploadTime = endTime - uploadStart;
|
||||
const speed = file.size / (uploadTime / 1000);
|
||||
const expiration = EXPIRE_SECONDS * 1000;
|
||||
|
||||
metrics.completedUpload({
|
||||
size: file.size,
|
||||
time,
|
||||
speed,
|
||||
type: clickOrDrop
|
||||
});
|
||||
|
||||
const fileData = {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
fileId: info.fileId,
|
||||
url: info.url,
|
||||
secretKey: info.secretKey,
|
||||
deleteToken: info.deleteToken,
|
||||
creationDate: new Date(),
|
||||
expiry: expiration,
|
||||
totalTime: time,
|
||||
typeOfUpload: clickOrDrop,
|
||||
uploadSpeed: speed
|
||||
};
|
||||
|
||||
$('#delete-file').on('click', () => {
|
||||
FileSender.delete(
|
||||
fileData.fileId,
|
||||
fileData.deleteToken
|
||||
).then(() => {
|
||||
const ttl =
|
||||
ONE_DAY_IN_MS -
|
||||
(Date.now() - fileData.creationDate.getTime());
|
||||
metrics
|
||||
.deletedUpload({
|
||||
size: fileData.size,
|
||||
time: fileData.totalTime,
|
||||
speed: fileData.uploadSpeed,
|
||||
type: fileData.typeOfUpload,
|
||||
location: 'success-screen',
|
||||
ttl
|
||||
})
|
||||
.then(() => {
|
||||
storage.remove(fileData.fileId);
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
storage.addFile(info.fileId, fileData);
|
||||
|
||||
$pageOne.attr('hidden', true);
|
||||
$uploadProgress.attr('hidden', true);
|
||||
$uploadError.attr('hidden', true);
|
||||
$('#share-link').removeAttr('hidden');
|
||||
|
||||
populateFileList(fileData);
|
||||
document.l10n.formatValue('notifyUploadDone').then(str => {
|
||||
notify(str);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
// err is 0 when coming from a cancel upload event
|
||||
if (err === 0) {
|
||||
return;
|
||||
}
|
||||
// only show error page when the error is anything other than user cancelling the upload
|
||||
Raven.captureException(err);
|
||||
$pageOne.attr('hidden', true);
|
||||
$uploadProgress.attr('hidden', true);
|
||||
$uploadError.removeAttr('hidden');
|
||||
window.clearTimeout(t);
|
||||
|
||||
metrics.stoppedUpload({
|
||||
size: file.size,
|
||||
type: clickOrDrop,
|
||||
err
|
||||
});
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function allowDrop(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function checkExistence(id, file, populate) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
if (populate) {
|
||||
populateFileList(file);
|
||||
}
|
||||
} else if (xhr.status === 404) {
|
||||
storage.remove(id);
|
||||
if (storage.numFiles === 0) {
|
||||
toggleHeader();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open('get', '/exists/' + id, true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
//update file table with current files in storage
|
||||
const populateFileList = file => {
|
||||
const row = document.createElement('tr');
|
||||
const name = document.createElement('td');
|
||||
const link = document.createElement('td');
|
||||
const $copyIcon = $('<img>', {
|
||||
src: '/resources/copy-16.svg',
|
||||
class: 'icon-copy',
|
||||
'data-l10n-id': 'copyUrlHover',
|
||||
disabled: !allowedCopy()
|
||||
});
|
||||
const expiry = document.createElement('td');
|
||||
const del = document.createElement('td');
|
||||
const $delIcon = $('<img>', {
|
||||
src: '/resources/close-16.svg',
|
||||
class: 'icon-delete',
|
||||
'data-l10n-id': 'deleteButtonHover'
|
||||
});
|
||||
const popupDiv = document.createElement('div');
|
||||
const $popupText = $('<div>', { class: 'popuptext' });
|
||||
const cellText = document.createTextNode(file.name);
|
||||
|
||||
const url = file.url.trim() + `#${file.secretKey}`.trim();
|
||||
|
||||
$link.attr('value', url);
|
||||
$('#copy-text')
|
||||
.attr('data-l10n-args', JSON.stringify({ filename: file.name }))
|
||||
.attr('data-l10n-id', 'copyUrlFormLabelWithName');
|
||||
|
||||
$popupText.attr('tabindex', '-1');
|
||||
|
||||
name.appendChild(cellText);
|
||||
|
||||
// create delete button
|
||||
|
||||
const delSpan = document.createElement('span');
|
||||
$(delSpan)
|
||||
.addClass('icon-cancel-1')
|
||||
.attr('data-l10n-id', 'deleteButtonHover');
|
||||
del.appendChild(delSpan);
|
||||
|
||||
const linkSpan = document.createElement('span');
|
||||
$(linkSpan).addClass('icon-docs').attr('data-l10n-id', 'copyUrlHover');
|
||||
|
||||
link.appendChild(linkSpan);
|
||||
link.style.color = '#0A8DFF';
|
||||
|
||||
//copy link to clipboard when icon clicked
|
||||
$copyIcon.on('click', () => {
|
||||
// record copied event from upload list
|
||||
metrics.copiedLink({ location: 'upload-list' });
|
||||
copyToClipboard(url);
|
||||
document.l10n.formatValue('copiedUrl').then(translated => {
|
||||
link.innerHTML = translated;
|
||||
});
|
||||
setTimeout(() => {
|
||||
const linkImg = document.createElement('img');
|
||||
$(linkImg)
|
||||
.addClass('icon-copy')
|
||||
.attr('data-l10n-id', 'copyUrlHover')
|
||||
.attr('src', '/resources/copy-16.svg');
|
||||
|
||||
$(link).html(linkImg);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
file.creationDate = new Date(file.creationDate);
|
||||
|
||||
const future = new Date();
|
||||
future.setTime(file.creationDate.getTime() + file.expiry);
|
||||
|
||||
let countdown = 0;
|
||||
countdown = future.getTime() - Date.now();
|
||||
let minutes = Math.floor(countdown / 1000 / 60);
|
||||
let hours = Math.floor(minutes / 60);
|
||||
let seconds = Math.floor(countdown / 1000 % 60);
|
||||
|
||||
const poll = () => {
|
||||
countdown = future.getTime() - Date.now();
|
||||
minutes = Math.floor(countdown / 1000 / 60);
|
||||
hours = Math.floor(minutes / 60);
|
||||
seconds = Math.floor(countdown / 1000 % 60);
|
||||
let t;
|
||||
|
||||
if (hours >= 1) {
|
||||
expiry.innerHTML = hours + 'h ' + minutes % 60 + 'm';
|
||||
t = setTimeout(() => {
|
||||
poll();
|
||||
}, 60000);
|
||||
} else if (hours === 0) {
|
||||
expiry.innerHTML = minutes + 'm ' + seconds + 's';
|
||||
t = window.setTimeout(() => {
|
||||
poll();
|
||||
}, 1000);
|
||||
}
|
||||
//remove from list when expired
|
||||
if (countdown <= 0) {
|
||||
storage.remove(file.fileId);
|
||||
$(expiry).parents('tr').remove();
|
||||
window.clearTimeout(t);
|
||||
toggleHeader();
|
||||
}
|
||||
};
|
||||
|
||||
poll();
|
||||
|
||||
// create popup
|
||||
popupDiv.classList.add('popup');
|
||||
const $popupMessage = $('<div>', { class: 'popup-message' });
|
||||
$popupMessage.attr('data-l10n-id', 'deletePopupText');
|
||||
const $popupAction = $('<div>', { class: 'popup-action' });
|
||||
const $popupNvmSpan = $('<span>', { class: 'popup-no' });
|
||||
$popupNvmSpan.attr('data-l10n-id', 'deletePopupCancel');
|
||||
const $popupDelSpan = $('<span>', { class: 'popup-yes' });
|
||||
$popupDelSpan.attr('data-l10n-id', 'deletePopupYes');
|
||||
|
||||
$popupText.html([$popupMessage, $popupAction]);
|
||||
$popupAction.html([$popupNvmSpan, $popupDelSpan]);
|
||||
|
||||
// add data cells to table row
|
||||
row.appendChild(name);
|
||||
$(link).append($copyIcon);
|
||||
row.appendChild(link);
|
||||
row.appendChild(expiry);
|
||||
$(popupDiv).append($popupText);
|
||||
$(del).append($delIcon);
|
||||
del.appendChild(popupDiv);
|
||||
row.appendChild(del);
|
||||
$('tbody').append(row); //add row to table
|
||||
|
||||
// delete file
|
||||
$popupText.find('.popup-yes').on('click', e => {
|
||||
FileSender.delete(file.fileId, file.deleteToken).then(() => {
|
||||
$(e.target).parents('tr').remove();
|
||||
const ttl =
|
||||
ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
|
||||
metrics
|
||||
.deletedUpload({
|
||||
size: file.size,
|
||||
time: file.totalTime,
|
||||
speed: file.uploadSpeed,
|
||||
type: file.typeOfUpload,
|
||||
location: 'upload-list',
|
||||
ttl
|
||||
})
|
||||
.then(() => {
|
||||
storage.remove(file.fileId);
|
||||
});
|
||||
toggleHeader();
|
||||
});
|
||||
});
|
||||
|
||||
// show popup
|
||||
$delIcon.on('click', () => {
|
||||
$popupText.addClass('show').focus();
|
||||
});
|
||||
|
||||
// hide popup
|
||||
$popupText.find('.popup-no').on('click', e => {
|
||||
e.stopPropagation();
|
||||
$popupText.removeClass('show');
|
||||
});
|
||||
|
||||
$popupText.on('click', e => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
//close when popup loses focus
|
||||
$popupText.on('blur', () => {
|
||||
$popupText.removeClass('show');
|
||||
});
|
||||
|
||||
toggleHeader();
|
||||
};
|
||||
})
|
||||
.catch(err => {
|
||||
metrics.unsupported({ err }).then(() => {
|
||||
|
||||
@@ -123,10 +123,23 @@ function bytes(num) {
|
||||
return `${nStr}${UNITS[exponent]}`;
|
||||
}
|
||||
|
||||
function percent(ratio) {
|
||||
return LOCALIZE_NUMBERS
|
||||
? ratio.toLocaleString(navigator.languages, { style: 'percent' })
|
||||
: `${Math.floor(ratio * 100)}%`;
|
||||
}
|
||||
|
||||
function allowedCopy() {
|
||||
const support = !!document.queryCommandSupported;
|
||||
return support ? document.queryCommandSupported('copy') : false;
|
||||
}
|
||||
|
||||
const ONE_DAY_IN_MS = 86400000;
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
allowedCopy,
|
||||
bytes,
|
||||
percent,
|
||||
copyToClipboard,
|
||||
arrayToHex,
|
||||
hexToArray,
|
||||
|
||||
6100
package-lock.json
generated
6100
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
55
package.json
55
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "firefox-send",
|
||||
"description": "File Sharing Experiment",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.2",
|
||||
"author": "Mozilla (https://mozilla.org)",
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.89.0",
|
||||
"aws-sdk": "^2.98.0",
|
||||
"body-parser": "^1.17.2",
|
||||
"connect-busboy": "0.0.2",
|
||||
"convict": "^3.0.0",
|
||||
@@ -13,38 +13,54 @@
|
||||
"helmet": "^3.8.0",
|
||||
"mozlog": "^2.1.1",
|
||||
"raven": "^2.1.0",
|
||||
"redis": "^2.7.1"
|
||||
"redis": "^2.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"asmcrypto.js": "0.0.11",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.25.0",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"bel": "^5.0.3",
|
||||
"browserify": "^14.4.0",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"cross-env": "^5.0.5",
|
||||
"css-loader": "^0.28.4",
|
||||
"css-mqpacker": "^6.0.1",
|
||||
"cssnano": "^3.10.0",
|
||||
"eslint": "^4.3.0",
|
||||
"eslint-plugin-mocha": "^4.11.0",
|
||||
"eslint-plugin-node": "^5.1.1",
|
||||
"eslint-plugin-security": "^1.4.0",
|
||||
"extract-loader": "^1.0.0",
|
||||
"file-loader": "^0.11.2",
|
||||
"git-rev-sync": "^1.9.1",
|
||||
"jquery": "^3.2.1",
|
||||
"jquery-circle-progress": "^1.2.2",
|
||||
"html-loader": "^0.5.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"husky": "^0.14.3",
|
||||
"l20n": "^5.0.0",
|
||||
"lint-staged": "^4.0.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mocha": "^3.4.2",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"postcss-cli": "^4.1.0",
|
||||
"postcss-loader": "^2.0.6",
|
||||
"prettier": "^1.5.3",
|
||||
"proxyquire": "^1.8.0",
|
||||
"raven-js": "^3.17.0",
|
||||
"rimraf": "^2.6.1",
|
||||
"selenium-webdriver": "^3.5.0",
|
||||
"sinon": "^2.3.8",
|
||||
"stylelint": "^8.0.0",
|
||||
"stylelint-config-standard": "^17.0.0",
|
||||
"stylelint-no-unsupported-browser-features": "^1.0.0",
|
||||
"supertest": "^3.0.0",
|
||||
"testpilot-ga": "^0.3.0",
|
||||
"webcrypto-liner": "^0.1.25",
|
||||
"webpack": "^3.4.1"
|
||||
"webpack": "^3.5.4",
|
||||
"webpack-dev-middleware": "^1.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.2.0"
|
||||
@@ -53,6 +69,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"repository": "mozilla/send",
|
||||
"availableLanguages": [
|
||||
"ast",
|
||||
"az",
|
||||
"ca",
|
||||
"cs",
|
||||
@@ -61,6 +78,7 @@
|
||||
"dsb",
|
||||
"el",
|
||||
"en-US",
|
||||
"es-AR",
|
||||
"es-CL",
|
||||
"es-ES",
|
||||
"es-MX",
|
||||
@@ -91,25 +109,38 @@
|
||||
"zh-TW"
|
||||
],
|
||||
"scripts": {
|
||||
"precommit": "lint-staged",
|
||||
"clean": "rimraf dist",
|
||||
"build": "npm-run-all build:*",
|
||||
"build:js": "webpack -p",
|
||||
"build:version": "node scripts/version",
|
||||
"build:vendor": "cp node_modules/l20n/dist/web/l20n.min.js node_modules/babel-polyfill/dist/polyfill.min.js public",
|
||||
"contributors": "git shortlog -s | awk -F\\t '{print $2}' > CONTRIBUTORS",
|
||||
"dev": "npm run build && npm start",
|
||||
"format": "prettier '{frontend/src/,scripts/,server/,test/**/!(bundle)}*.js' 'public/*.css' --single-quote --write",
|
||||
"dev": "npm run clean && npm run build && npm start",
|
||||
"format": "prettier '{,frontend/src/,scripts/,server/,test/**/!(bundle)}*.{js,css}' --single-quote --write",
|
||||
"get-prod-locales": "node scripts/get-prod-locales",
|
||||
"get-prod-locales:write": "npm run get-prod-locales -- --write",
|
||||
"lint": "npm-run-all lint:*",
|
||||
"lint:css": "stylelint 'public/*.css'",
|
||||
"lint:css": "stylelint 'frontend/src/*.css'",
|
||||
"lint:js": "eslint .",
|
||||
"lint-locales": "node scripts/lint-locales",
|
||||
"lint-locales:dev": "npm run lint-locales",
|
||||
"lint-locales:prod": "npm run lint-locales -- --production",
|
||||
"start": "node server/server",
|
||||
"test": "npm-run-all test:*",
|
||||
"test": "cross-env NODE_ENV=test npm-run-all test:*",
|
||||
"test:unit": "mocha test/unit",
|
||||
"test:server": "mocha test/server",
|
||||
"test--browser": "browserify test/frontend/frontend.bundle.js -o test/frontend/bundle.js -d && node test/frontend/driver.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"prettier --single-quote --write",
|
||||
"eslint",
|
||||
"git add"
|
||||
],
|
||||
"*.css": [
|
||||
"prettier --single-quote --write",
|
||||
"stylelint",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
15
postcss.config.js
Normal file
15
postcss.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const cssnano = require('cssnano');
|
||||
const mqpacker = require('css-mqpacker');
|
||||
|
||||
const conf = require('./server/config');
|
||||
|
||||
const options = {
|
||||
plugins: [autoprefixer, mqpacker, cssnano]
|
||||
};
|
||||
|
||||
if (conf.env === 'development') {
|
||||
options.map = { inline: true };
|
||||
}
|
||||
|
||||
module.exports = options;
|
||||
99
public/locales/ast/send.ftl
Normal file
99
public/locales/ast/send.ftl
Normal file
@@ -0,0 +1,99 @@
|
||||
// Firefox Send is a brand name and should not be localized.
|
||||
title = Firefox Send
|
||||
siteSubtitle = esperimentu web
|
||||
siteFeedback = Feedback
|
||||
uploadPageHeader = Compartición privada y cifrada de ficheros
|
||||
uploadPageExplainer = Unvia ficheros pente un enllaz seguru, priváu y cifráu que caduca automáticamente p'asegurar que les tos coses nun queden siempres na rede.
|
||||
uploadPageLearnMore = Deprendi más
|
||||
uploadPageDropMessage = Suelta equí'l to ficheru p'aniciar la xuba
|
||||
uploadPageSizeMessage = Pal meyor funcionamientu, lo meyor ye que'l to ficheru seya menor de 1GB
|
||||
uploadPageBrowseButton = Esbilla un ficheru nel to ordenador
|
||||
.title = Esbilla un ficheru nel to ordenador
|
||||
uploadPageBrowseButton1 = Esbilla un ficheru pa unviar
|
||||
.title = Esbilla un ficheru pa unviar
|
||||
uploadPageMultipleFilesAlert = Anguaño nun se sofita la xuba múltiple de ficheros o carpetes.
|
||||
uploadPageBrowseButtonTitle = Xubir ficheru
|
||||
uploadingPageProgress = Xubiendo { $filename } ({ $size })
|
||||
importingFile = Importando...
|
||||
verifyingFile = Verificando...
|
||||
encryptingFile = Cifrando...
|
||||
decryptingFile = Descifrando...
|
||||
notifyUploadDone = Finó la to xuba.
|
||||
uploadingPageMessage = Namái que'l ficheru xuba, sedrás a afitar les opciones de caducidá.
|
||||
uploadingPageCancel = Encaboxar xuba
|
||||
.title = Encaboxar xuba
|
||||
uploadCancelNotification = Encaboxóse la to xuba.
|
||||
uploadingPageLargeFileMessage = Esti ficheru ye grande y pue entardar daqué en xubir. ¡Paciencia!
|
||||
uploadingFileNotification = Avísame cuando se complete la xuba.
|
||||
uploadSuccessConfirmHeader = Preparáu pa unviar
|
||||
uploadSvgAlt
|
||||
.alt = Xubir
|
||||
uploadSuccessTimingHeader = L'enllaz del to ficheru caducará dempués d'una descarga o en 24 hores.
|
||||
copyUrlFormLabelWithName = Copia y comparti l'enllaz pa unviar el to ficheru: { $filename }
|
||||
// Note: Title text for button should be the same.
|
||||
copyUrlFormButton = Copiar al cartafueyu
|
||||
.title = Copiar al cartafueyu
|
||||
copiedUrl = ¡Copióse!
|
||||
// Note: Title text for button should be the same.
|
||||
deleteFileButton = Desaniciar ficheru
|
||||
.title = Desaniciar ficheru
|
||||
// Note: Title text for button should be the same.
|
||||
sendAnotherFileLink = Unviar otru ficheru
|
||||
.title = Unviar otru ficheru
|
||||
// Alternative text used on the download link/button (indicates an action).
|
||||
downloadAltText
|
||||
.alt = Baxar
|
||||
downloadFileName = Baxar { $filename }
|
||||
downloadFileSize = ({ $size })
|
||||
// Firefox Send is a brand name and should not be localized.
|
||||
downloadMessage = El to collaciu unvióte un ficheru usando Firefox Send, un serviciu que te permite compartir ficheros con un enllaz seguru, priváu y cifráu que caduca automáticamente p'asegurar que les to coses nun queden siempres na rede.
|
||||
// Text and title used on the download link/button (indicates an action).
|
||||
downloadButtonLabel = Baxar
|
||||
.title = Baxar
|
||||
downloadNotification = Completóse la to descarga.
|
||||
downloadFinish = Descarga completada
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Prueba Firefox Send
|
||||
.title = Prueba Firefox Send
|
||||
downloadingPageProgress = Baxando { $filename } ({ $size })
|
||||
downloadingPageMessage = Dexa esta llingüeta abierta entrín vamos en cata del to ficheru y lu desciframos, por favor.
|
||||
errorAltText
|
||||
.alt = Fallu de xuba
|
||||
errorPageHeader = ¡Daqué foi mal!
|
||||
errorPageMessage = Hebo un fallu xubiendo'l ficheru.
|
||||
errorPageLink = Unviar otru ficheru
|
||||
fileTooBig = Esti ficheru ye mui grande como pa xubilu. Debería tener menos de { $size }.
|
||||
linkExpiredAlt
|
||||
.alt = Enllaz caducáu
|
||||
expiredPageHeader = ¡Esti enllaz caducó o enxamás nun esistó!
|
||||
notSupportedHeader = El to restolador nun ta sofitáu.
|
||||
// Firefox Send is a brand name and should not be localized.
|
||||
notSupportedDetail = Desafortunadamente esti restolador nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'usar otru restolador. ¡Aconseyámoste Firefox!
|
||||
notSupportedLink = ¿Por qué'l mio restolador nun ta sofitáu?
|
||||
notSupportedOutdatedDetail = Desafortunadamente esta versión de Firefox nun sofita la teunoloxía web qu'usa Firefox Send. Precisarás d'anovar Firefox.
|
||||
updateFirefox = Anovar Firefox
|
||||
downloadFirefoxButtonSub = Descarga de baldre
|
||||
uploadedFile = Ficheru
|
||||
copyFileList = Copiar URL
|
||||
// expiryFileList is used as a column header
|
||||
expiryFileList = Caduca en
|
||||
deleteFileList = Desaniciar
|
||||
nevermindButton = Nun m'importa
|
||||
legalHeader = Términos y privacidá
|
||||
legalNoticeTestPilot = Anguaño Firefox Send ye un esperimentu de Test Pilot y ta suxetu a los <a>Términos de serviciu</a> y l'<a>Avisu de privacidá</a> de Test Pilot. <a>Equí</a> pues deprender más tocante a esti esperimentu y la so recoyida de datos.
|
||||
legalNoticeMozilla = L'usu de Firefox Send tamién ta suxetu al <a>Avisu de privacidá</a> y a los <a>Términos d'usu de la páxina web</a> de Mozilla.
|
||||
deletePopupText = ¿Desaniciar esti ficheru?
|
||||
deletePopupYes = Sí
|
||||
deletePopupCancel = Encaboxar
|
||||
deleteButtonHover
|
||||
.title = Desaniciar
|
||||
copyUrlHover
|
||||
.title = Copiar URL
|
||||
footerLinkLegal = Llegal
|
||||
// Test Pilot is a proper name and should not be localized.
|
||||
footerLinkAbout = Tocante a Test Pilot
|
||||
footerLinkPrivacy = Privacidá
|
||||
footerLinkTerms = Términos
|
||||
footerLinkCookies = Cookies
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Yükləmək üçün faylınızı buraya daşıyın
|
||||
uploadPageSizeMessage = Xidmətin daha yaxşı işləməsi üçün faylınız 1 GB-dan az olmalıdır
|
||||
uploadPageBrowseButton = Kompüterinizdən fayl seçin
|
||||
.title = Kompüterinizdən fayl seçin
|
||||
uploadPageBrowseButton1 = Yüklənəcək faylı seçin
|
||||
.title = Yüklənəcək faylı seçin
|
||||
uploadPageMultipleFilesAlert = Birdən çox fayl və ya qovluq yükləmə hələlik dəstəklənmir.
|
||||
uploadPageBrowseButtonTitle = Fayl yüklə
|
||||
uploadingPageHeader = Faylınız yüklənir
|
||||
uploadingPageProgress = { $filename } ({ $size }) yüklənir
|
||||
importingFile = İdxal edilir…
|
||||
verifyingFile = Təsdiqlənir…
|
||||
encryptingFile = Şifrələnir...
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Endir
|
||||
.title = Endir
|
||||
downloadNotification = Endirməniz tamamlandı.
|
||||
downloadFinish = Endirmə Tamamlandı
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } / { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Firefox Send Yoxla
|
||||
.title = Firefox Send Yoxla
|
||||
|
||||
65
public/locales/bn-BD/send.ftl
Normal file
65
public/locales/bn-BD/send.ftl
Normal file
@@ -0,0 +1,65 @@
|
||||
// Firefox Send is a brand name and should not be localized.
|
||||
title = Firefox Send
|
||||
siteSubtitle = ওয়েব গবেষণা
|
||||
siteFeedback = প্রতিক্রিয়া
|
||||
uploadPageLearnMore = আরও জানুন
|
||||
uploadPageBrowseButton = আপনার কম্পিউটারে ফাইল নির্বাচন করুন
|
||||
.title = আপনার কম্পিউটারে ফাইল নির্বাচন করুন
|
||||
uploadPageBrowseButtonTitle = ফাইল আপলোড
|
||||
importingFile = ইম্পোর্ট হচ্ছে...
|
||||
verifyingFile = যাচাই হচ্ছে...
|
||||
encryptingFile = ইনক্রিপট হচ্ছে...
|
||||
decryptingFile = ডিক্রিপট হচ্ছে...
|
||||
notifyUploadDone = আপনার আপলোড সম্পন্ন হয়েছে।
|
||||
uploadingPageCancel = আপলোড বাতিল করুন
|
||||
.title = আপলোড বাতিল করুন
|
||||
uploadCancelNotification = আপনার অাপলোড বাতিল করা হয়েছে।
|
||||
uploadSuccessConfirmHeader = পাঠানোর জন্য প্রস্তুত
|
||||
uploadSvgAlt
|
||||
.alt = আপলোড
|
||||
// Note: Title text for button should be the same.
|
||||
copyUrlFormButton = ক্লিপবোর্ডে কপি করুন
|
||||
.title = ক্লিপবোর্ডে কপি করুন
|
||||
copiedUrl = কপি করা হয়েছে!
|
||||
// Note: Title text for button should be the same.
|
||||
deleteFileButton = ফাইল মুছুন
|
||||
.title = ফাইল মুছুন
|
||||
// Note: Title text for button should be the same.
|
||||
sendAnotherFileLink = আরেকটি ফাইল পাঠান
|
||||
.title = আরেকটি ফাইল পাঠান
|
||||
// Alternative text used on the download link/button (indicates an action).
|
||||
downloadAltText
|
||||
.alt = ডাউনলোড
|
||||
downloadFileName = ডাউনলোড { $filename }
|
||||
downloadFileSize = ({ $size })
|
||||
// Text and title used on the download link/button (indicates an action).
|
||||
downloadButtonLabel = ডাউনলোড
|
||||
.title = ডাউনলোড
|
||||
downloadNotification = আপনার ডাউনলোড সম্পন্ন হয়েছে।
|
||||
downloadFinish = ডাউনলোড সম্পন্ন
|
||||
errorAltText
|
||||
.alt = আপালোডে ত্রুটি
|
||||
errorPageHeader = কোন সমস্যা হয়েছে!
|
||||
errorPageLink = আরেকটি ফাইল পাঠান
|
||||
updateFirefox = Firefox হালনাগাদ করুন
|
||||
downloadFirefoxButtonSub = বিনামূল্যে ডাউনলোড
|
||||
uploadedFile = ফাইল
|
||||
copyFileList = URL অনুলিপি করুন
|
||||
// expiryFileList is used as a column header
|
||||
expiryFileList = মেয়াদোত্তীর্ণ তারিখ
|
||||
deleteFileList = মুছে ফেলুন
|
||||
nevermindButton = কিছু মনে করবেন না
|
||||
legalHeader = শর্তাবলী এবং গোপনীয়তা
|
||||
deletePopupText = ফাইলটি মুছতে চান?
|
||||
deletePopupYes = হ্যাঁ
|
||||
deletePopupCancel = বাতিল
|
||||
deleteButtonHover
|
||||
.title = মুছে ফেলুন
|
||||
copyUrlHover
|
||||
.title = URL অনুলিপি করুন
|
||||
footerLinkLegal = আইনগত
|
||||
// Test Pilot is a proper name and should not be localized.
|
||||
footerLinkAbout = Test Pilot পরিচিতি
|
||||
footerLinkPrivacy = গোপনীয়তা
|
||||
footerLinkTerms = শর্তাবলী
|
||||
footerLinkCookies = কুকি
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Arrossegueu el fitxer aquí per començar a pujar-lo
|
||||
uploadPageSizeMessage = Funciona millor quan els fitxers tenen menys d'1 GB
|
||||
uploadPageBrowseButton = Trieu un fitxer de l'ordinador
|
||||
.title = Trieu un fitxer de l'ordinador
|
||||
uploadPageBrowseButton1 = Seleccioneu el fitxer que voleu pujar
|
||||
.title = Seleccioneu el fitxer que voleu pujar
|
||||
uploadPageMultipleFilesAlert = Actualment no es permet pujar diversos fitxers ni una carpeta.
|
||||
uploadPageBrowseButtonTitle = Puja el fitxer
|
||||
uploadingPageHeader = S'està pujant el fitxer
|
||||
uploadingPageProgress = S'està pujant { $filename } ({ $size })
|
||||
importingFile = S'està important…
|
||||
verifyingFile = S'està verificant…
|
||||
encryptingFile = S'està xifrant…
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Baixa
|
||||
.title = Baixa
|
||||
downloadNotification = La baixada ha acabat.
|
||||
downloadFinish = Ha acabat la baixada
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Proveu el Firefox Send
|
||||
.title = Proveu el Firefox Send
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Přesunutím souboru sem spustíte jeho nahrávání
|
||||
uploadPageSizeMessage = Nahrávání funguje nejlépe pro soubory do velikosti 1 GB.
|
||||
uploadPageBrowseButton = Vybrat soubor z počítače
|
||||
.title = Výběr souboru z počítače
|
||||
uploadPageBrowseButton1 = Zvolte soubor k nahrání
|
||||
.title = Zvolte soubor k nahrání
|
||||
uploadPageMultipleFilesAlert = Nahrávání více souborů najednou nebo celých složek zatím není podporováno.
|
||||
uploadPageBrowseButtonTitle = Nahrát soubor
|
||||
uploadingPageHeader = Nahrávání vašeho souboru
|
||||
uploadingPageProgress = Nahrávání souboru { $filename } ({ $size })
|
||||
importingFile = Probíhá import…
|
||||
verifyingFile = Probíhá ověřování…
|
||||
encryptingFile = Probíhá šifrování…
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Stáhnout
|
||||
.title = Stáhnout
|
||||
downloadNotification = Stahování bylo dokončeno.
|
||||
downloadFinish = Stahování dokončeno
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } z { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Vyzkoušejte Firefox Send
|
||||
.title = Vyzkoušejte Firefox Send
|
||||
@@ -67,6 +71,7 @@ expiredPageHeader = Platnost tohoto odkazu buď vypršela, nebo vůbec nikdy nee
|
||||
notSupportedHeader = Váš prohlížeč není podporován.
|
||||
// Firefox Send is a brand name and should not be localized.
|
||||
notSupportedDetail = Bohužel tento prohlížeč nepodporuje technologii, kterou Firefox Send používá. Zkuste prosím jiný prohlížeč, doporučujeme Firefox!
|
||||
notSupportedLink = Proč není můj prohlížeč podporovaný?
|
||||
notSupportedOutdatedDetail = Tato verze Firefoxu bohužel nepodporuje webovou technologii, která pohání Firefox Send. Musíte aktualizovat svůj prohlížeč.
|
||||
updateFirefox = Aktualizovat Firefox
|
||||
downloadFirefoxButtonSub = Stáhnout zdarma
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Gollyngwch eich ffeiliau yma i gychwyn llwytho i fyny
|
||||
uploadPageSizeMessage = Mae'n well cadw maint y ffeiliau o dan 1GB er mwyn iddo weithio ar ei orau.
|
||||
uploadPageBrowseButton = Dewiswch ffeil ar eich cyfrifiadur
|
||||
.title = Dewiswch ffeil ar eich cyfrifiadur
|
||||
uploadPageBrowseButton1 = Dewiswch ffeil i'w llwytho i fyny
|
||||
.title = Dewiswch ffeil i'w llwytho i fyny
|
||||
uploadPageMultipleFilesAlert = Nid yw llwytho nifer lluosog o ffeilia neu ffolder yn cael ei gynnal ar hyn o bryd.
|
||||
uploadPageBrowseButtonTitle = Llwytho ffeil i fyny
|
||||
uploadingPageHeader = Llwytho eich Ffeiliau i Fyny
|
||||
uploadingPageProgress = Llwytho $filename} i fyny ({ $size })
|
||||
importingFile = Mewnforio…
|
||||
verifyingFile = Wrthi'n gwirio…
|
||||
encryptingFile = Wrthi'n amgryptio…
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Llwytho i Lawr
|
||||
.title = Llwytho i Lawr
|
||||
downloadNotification = Mae eich llwytho wedi gorffen
|
||||
downloadFinish = Llwytho wedi Gorffen
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } o { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Profwch Firefox Send
|
||||
.title = Profwch Firefox Send
|
||||
@@ -67,6 +71,7 @@ expiredPageHeader = Mae'r ddolen wedi dod i ben neu nad yw wedi bodoli erioed!
|
||||
notSupportedHeader = Nid yw eich porwr yn cael ei gynnal.
|
||||
// Firefox Send is a brand name and should not be localized.
|
||||
notSupportedDetail = Yn anffodus nid yw'r porwr hwn yn cynnal y technoleg gwe sy'n cynnal Firefox Send. Bydd angen i chi ddefnyddio porwr arall. Rydym ni'n argymell Firefox!
|
||||
notSupportedLink = Pam nad yw fy mhorwr yn cael ei gynnal?
|
||||
notSupportedOutdatedDetail = Yn anffodus nid yw'r fersiwn yma o Firefox yn cynnal y technoleg gwe sy'n gyrru Firefox Send. Bydd angen i chi ddiweddaru eich porwr.
|
||||
updateFirefox = Diweddaru Firefox
|
||||
downloadFirefoxButtonSub = Llwytho i Lawr am Ddim
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Ziehen Sie eine Datei zum Hochladen hierher
|
||||
uploadPageSizeMessage = Dateien unter 1 GB sorgen für erhöhte Zuverlässigkeit des Betriebs
|
||||
uploadPageBrowseButton = Wählen Sie eine Datei auf Ihrem Computer aus
|
||||
.title = Wählen Sie eine Datei auf Ihrem Computer aus
|
||||
uploadPageBrowseButton1 = Datei zum Hochladen auswählen
|
||||
.title = Datei zum Hochladen auswählen
|
||||
uploadPageMultipleFilesAlert = Hochladen mehrerer Dateien oder eines Ordners wird derzeit nicht unterstützt.
|
||||
uploadPageBrowseButtonTitle = Datei hochladen
|
||||
uploadingPageProgress = { $filename } ({ $size }) wird hochgeladen
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Śěgniśo swóju dataju sem, aby ju nagrał
|
||||
uploadPageSizeMessage = Wužywajśo nejlěpje dataje, kótarež su mjeńše ako 1 GB za lěpšu spušćobnosć.
|
||||
uploadPageBrowseButton = Wubjeŕśo dataju na swójom licadle
|
||||
.title = Wubjeŕśo dataju na swójom licadle
|
||||
uploadPageBrowseButton1 = Wubjeŕśo dataju za nagraśe
|
||||
.title = Wubjeŕśo dataju za nagraśe
|
||||
uploadPageMultipleFilesAlert = Nagrawanje někotarych datajow abo zarědnika se tuchylu njepódpěra.
|
||||
uploadPageBrowseButtonTitle = Dataju nagraś
|
||||
uploadingPageProgress = { $filename } ({ $size }) se nagrawa
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Εναποθέστε το αρχείο σας εδώ γ
|
||||
uploadPageSizeMessage = Για περισσότερο αξιόπιστη λειτουργία, προτείνεται να διατηρήσετε το αρχείο κάτω από 1GB
|
||||
uploadPageBrowseButton = Επιλέξτε αρχείο από τον υπολογιστή σας
|
||||
.title = Επιλέξτε αρχείο από τον υπολογιστή σας
|
||||
uploadPageBrowseButton1 = Επιλέξτε ένα αρχείο για μεταφόρτωση
|
||||
.title = Επιλέξτε ένα αρχείο για μεταφόρτωση
|
||||
uploadPageMultipleFilesAlert = Η μεταφόρτωση πολλαπλών αρχείων ή φακέλου δεν υποστηρίζεται αυτή τη στιγμή.
|
||||
uploadPageBrowseButtonTitle = Μεταφόρτωση αρχείου
|
||||
uploadingPageHeader = Γίνετε μεταφόρτωση του αρχείου σας
|
||||
uploadingPageProgress = Μεταφόρτωση του { $filename } ({ $size })
|
||||
importingFile = Εισαγωγή…
|
||||
verifyingFile = Επαλήθευση...
|
||||
encryptingFile = Κρυπτογράφηση…
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Λήψη
|
||||
.title = Λήψη
|
||||
downloadNotification = Η λήψη σας ολοκληρώθηκε.
|
||||
downloadFinish = Η λήψη ολοκληρώθηκε
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } από { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Δοκιμάστε το Firefox Send
|
||||
.title = Δοκιμάστε το Firefox Send
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Drop your file here to start uploading
|
||||
uploadPageSizeMessage = For the most reliable operation, it’s best to keep your file under 1GB
|
||||
uploadPageBrowseButton = Select a file on your computer
|
||||
.title = Select a file on your computer
|
||||
uploadPageBrowseButton1 = Select a file to upload
|
||||
.title = Select a file to upload
|
||||
uploadPageMultipleFilesAlert = Uploading multiple files or a folder is currently not supported.
|
||||
uploadPageBrowseButtonTitle = Upload file
|
||||
uploadingPageProgress = Uploading { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Arrastrá el archivo hasta acá para empezar a subir
|
||||
uploadPageSizeMessage = Para una operación más confiable, es mejor que el archivo tenga menos de 1GB
|
||||
uploadPageBrowseButton = Seleccioná un archivo en tu computadora
|
||||
.title = Seleccioná un archivo en tu computadora
|
||||
uploadPageBrowseButton1 = Seleccioná un archivo para subir
|
||||
.title = Seleccioná un archivo para subir
|
||||
uploadPageMultipleFilesAlert = Cargar múltiples archivos o una carpeta todavía no está soportado.
|
||||
uploadPageBrowseButtonTitle = Subir archivo
|
||||
uploadingPageProgress = Subiendo { $filename } ({ $size })
|
||||
@@ -78,6 +80,7 @@ copyFileList = Copiar URL
|
||||
// expiryFileList is used as a column header
|
||||
expiryFileList = Expira en
|
||||
deleteFileList = Borrar
|
||||
nevermindButton = No importa
|
||||
legalHeader = Términos y privacidad
|
||||
legalNoticeTestPilot = Firefox Send es actualmente un experimento de Test Pilot y está sujeto a los <a>términos de servicio</a> y la <a>nota de privacidad</a> de Test Pilot. Podés conocer más sobre este experimento y su recolección de datos <a>aquí</a>.
|
||||
legalNoticeMozilla = El uso del sitio web de Firefox Send también está sujeto a la <a>nota de privacidad de sitios web</a> y los <a>términos de uso de sitios web</a> de Mozilla.
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Suelta tu archivo aquí para empezar a subirlo
|
||||
uploadPageSizeMessage = Para una operación más confiable, es mejor mantener el tamaño del archivo bajo 1 GB
|
||||
uploadPageBrowseButton = Selecciona un archivo en tu computador
|
||||
.title = Selecciona un archivo en tu computador
|
||||
uploadPageBrowseButton1 = Selecciona un archivo a subir
|
||||
.title = Selecciona un archivo a subir
|
||||
uploadPageMultipleFilesAlert = Subir múltiples archivos o una carpeta actualmente no es posible.
|
||||
uploadPageBrowseButtonTitle = Subir archivo
|
||||
uploadingPageHeader = Subiendo tu archivo
|
||||
uploadingPageProgress = Subiendo { $filename } ({ $size })
|
||||
importingFile = Importando…
|
||||
verifyingFile = Verificando…
|
||||
encryptingFile = Cifrando…
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Descargar
|
||||
.title = Descargar
|
||||
downloadNotification = Tu descarga se completó.
|
||||
downloadFinish = Descarga completa
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Probar Firefox Send
|
||||
.title = Probar Firefox Send
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Suelta aquí tu archivo para empezar a subirlo
|
||||
uploadPageSizeMessage = Para que la operación sea más segura, el archivo debería ocupar menos de 1GB
|
||||
uploadPageBrowseButton = Seleccionar un archivo en el equipo
|
||||
.title = Seleccionar un archivo en el equipo
|
||||
uploadPageBrowseButton1 = Seleccionar un archivo para subir
|
||||
.title = Seleccionar un archivo para subir
|
||||
uploadPageMultipleFilesAlert = Aún no se pueden subir varios archivos o una carpeta.
|
||||
uploadPageBrowseButtonTitle = Subir archivo
|
||||
uploadingPageHeader = Subiendo archivo
|
||||
uploadingPageProgress = Subiendo { $filename } ({ $size })
|
||||
importingFile = Imporando...
|
||||
verifyingFile = Comprobando...
|
||||
encryptingFile = Encriptando...
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Descargar
|
||||
.title = Descargar
|
||||
downloadNotification = Se completó la descarga.
|
||||
downloadFinish = Descarga completa
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Prueba Firefox Send
|
||||
.title = Prueba Firefox Send
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Suelta aquí tu archivo para empezar a subirlo
|
||||
uploadPageSizeMessage = Para que la operación sea más segura, el archivo debería ocupar menos de 1GB
|
||||
uploadPageBrowseButton = Selecciona un archivo de tu computadora
|
||||
.title = Selecciona un archivo de tu computadora
|
||||
uploadPageBrowseButton1 = Seleccionar un archivo para subir
|
||||
.title = Seleccionar un archivo para subir
|
||||
uploadPageMultipleFilesAlert = Aún no se pueden subir varios archivos o una carpeta.
|
||||
uploadPageBrowseButtonTitle = Subir archivo
|
||||
uploadingPageHeader = Subiendo tu archivo
|
||||
uploadingPageProgress = Subiendo { $filename } ({ $size })
|
||||
importingFile = Importando...
|
||||
verifyingFile = Verificando...
|
||||
encryptingFile = Encriptando...
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Descargar
|
||||
.title = Descargar
|
||||
downloadNotification = Tu descarga se ha completado
|
||||
downloadFinish = Descarga completa
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } de { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Prueba Firefox Send
|
||||
.title = Prueba Firefox Send
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Déposez votre fichier ici pour l’envoyer
|
||||
uploadPageSizeMessage = Pour un résultat fiable, il est conseillé d’utiliser des fichiers de taille inférieure à 1 Go
|
||||
uploadPageBrowseButton = Sélectionner un fichier sur l’ordinateur
|
||||
.title = Sélectionner un fichier sur l’ordinateur
|
||||
uploadPageBrowseButton1 = Choisir un fichier à envoyer
|
||||
.title = Choisir un fichier à envoyer
|
||||
uploadPageMultipleFilesAlert = L’envoi de plusieurs fichiers ou de dossiers n’est pas pris en charge pour le moment.
|
||||
uploadPageBrowseButtonTitle = Envoyer le fichier
|
||||
uploadingPageProgress = Envoi en cours de { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Ćehńće swoju dataju sem, zo byšće ju nahrał
|
||||
uploadPageSizeMessage = Wužiwajće najlěpje dataje, kotrež su mjeńše hač 1 GB za lěpšu spušćomnosć.
|
||||
uploadPageBrowseButton = Wubjerće dataju na swojim ličaku
|
||||
.title = Wubjerće dataju na swojim ličaku
|
||||
uploadPageBrowseButton1 = Wubjerće dataju za nahraće
|
||||
.title = Wubjerće dataju za nahraće
|
||||
uploadPageMultipleFilesAlert = Nahrawanje wjacorych datajow abo rjadowaka so tuchwilu njepodpěruje.
|
||||
uploadPageBrowseButtonTitle = Dataju nahrać
|
||||
uploadingPageProgress = { $filename } ({ $size }) so nahrawa
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Dobja ide a fájljait, és kezdjen feltölteni
|
||||
uploadPageSizeMessage = A megbízható működés érdekében a legjobb, ha a fájlok 1 GB-nál kisebbek maradnak
|
||||
uploadPageBrowseButton = Válasszon egy fájlt a számítógépén
|
||||
.title = Válasszon egy fájlt a számítógépén
|
||||
uploadPageBrowseButton1 = Válassza ki a feltöltendő fájlt
|
||||
.title = Válassza ki a feltöltendő fájlt
|
||||
uploadPageMultipleFilesAlert = Több fájl vagy mappa feltöltése pillanatnyilag nem támogatott.
|
||||
uploadPageBrowseButtonTitle = Fájl feltöltése
|
||||
uploadingPageProgress = { $filename } ({ $size }) feltöltése
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Trascina qui un file per caricarlo
|
||||
uploadPageSizeMessage = Per evitare problemi è consigliabile caricare file di dimensione inferiore a 1 GB
|
||||
uploadPageBrowseButton = Seleziona un file sul computer
|
||||
.title = Seleziona un file sul computer
|
||||
uploadPageBrowseButton1 = Seleziona un file da caricare
|
||||
.title = Seleziona un file da caricare
|
||||
uploadPageMultipleFilesAlert = Il caricamento di più file o cartelle non è attualmente supportato.
|
||||
uploadPageBrowseButtonTitle = Carica file
|
||||
uploadingPageProgress = Caricamento { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = ここにファイルをドロップしてアップロ
|
||||
uploadPageSizeMessage = 確実に処理できるよう、ファイルサイズは 1 GB 以下にすることを推奨します。
|
||||
uploadPageBrowseButton = コンピューター上のファイルを選択
|
||||
.title = コンピューター上のファイルを選択
|
||||
uploadPageBrowseButton1 = アップロードするファイルを選択
|
||||
.title = アップロードするファイルを選択
|
||||
uploadPageMultipleFilesAlert = 今のところ複数ファイルやフォルダーのアップロードには対応していません。
|
||||
uploadPageBrowseButtonTitle = ファイルをアップロード
|
||||
uploadingPageProgress = { $filename } ({ $size }) をアップロード中
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Zuɣeṛ afaylu-ik ar dagi akken ad tebduḍ asali
|
||||
uploadPageSizeMessage = I ugmuḍ ufrin, yelha ad tesqedceḍ ifuyla daw n 1 GAṬ
|
||||
uploadPageBrowseButton = Fren afaylu sef uselkim-ik
|
||||
.title = Fren afaylu seg uselkim-ik
|
||||
uploadPageBrowseButton1 = Fren afaylu ad tazneḍ
|
||||
.title = Fren afaylu ad tazneḍ
|
||||
uploadPageMultipleFilesAlert = Asali n ddeqs n ifuyla neɣ ikaramen ur ittusefrak ara yakan.
|
||||
uploadPageBrowseButtonTitle = Sali ifuyla
|
||||
uploadingPageHeader = Asali n ufaylu-ik
|
||||
uploadingPageProgress = Tuzna n { $filename } ({ $size })
|
||||
importingFile = Akter...
|
||||
verifyingFile = Asenqed...
|
||||
encryptingFile = Awgelhen...
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Sider
|
||||
.title = Sider
|
||||
downloadNotification = Asider-ik yemmed.
|
||||
downloadFinish = Asider yemmed
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } seg { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Ɛreḍ Firefox Send
|
||||
.title = Ɛreḍ Firefox Send
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Letakkan fail anda di sini untuk mulakan memuat naik
|
||||
uploadPageSizeMessage = Untuk operasi yang paling selamat, lebih baik pastikan fail anda itu kurang 1GB
|
||||
uploadPageBrowseButton = Pilih fail dalam komputer anda
|
||||
.title = Pilih fail dalam komputer anda
|
||||
uploadPageBrowseButton1 = Pilih fail untuk dimuat naik
|
||||
.title = Pilih fail untuk dimuat naik
|
||||
uploadPageMultipleFilesAlert = Memuat naik pelbagai fail atau satu folder masih belum disokong.
|
||||
uploadPageBrowseButtonTitle = Muat naik fail
|
||||
uploadingPageProgress = Memuat naik { $filename } ({ $size })
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Slipp din fil her for å starte opplastingen
|
||||
uploadPageSizeMessage = For den mest problemfrie bruken, er det best å holde filen under 1 GB
|
||||
uploadPageBrowseButton = Velg en fil på din datamaskin
|
||||
.title = Velg en fil på din datamaskin
|
||||
uploadPageBrowseButton1 = Velg en fil til å laste opp
|
||||
.title = Velg en fil til å laste opp
|
||||
uploadPageMultipleFilesAlert = Opplasting av flere filer eller en mappe støttes ikke for øyeblikket.
|
||||
uploadPageBrowseButtonTitle = Last opp fil
|
||||
uploadingPageHeader = Laster opp din fil
|
||||
uploadingPageProgress = Laster opp { $filename } ({ $size })
|
||||
importingFile = Importerer…
|
||||
verifyingFile = Verifiserer...
|
||||
encryptingFile = Krypterer...
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Last ned
|
||||
.title = Last ned
|
||||
downloadNotification = Nedlastingen er fullført.
|
||||
downloadFinish = Nedlastingen er fullført.
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } av { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Prøv Firefox Send
|
||||
.title = Prøv Firefox Send
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Sleep uw bestand hiernaartoe om het te uploaden
|
||||
uploadPageSizeMessage = Voor de meest betrouwbare werking kunt u uw bestand het beste onder de 1 GB houden
|
||||
uploadPageBrowseButton = Selecteer een bestand op uw computer
|
||||
.title = Selecteer een bestand op uw computer
|
||||
uploadPageBrowseButton1 = Selecteer een bestand om te uploaden
|
||||
.title = Selecteer een bestand om te uploaden
|
||||
uploadPageMultipleFilesAlert = Het uploaden van meerdere bestanden of een map wordt momenteel niet ondersteund.
|
||||
uploadPageBrowseButtonTitle = bestand uploaden
|
||||
uploadingPageProgress = { $filename } ({ $size }) wordt geüpload
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Slepp fila di her for å starte opplastinga
|
||||
uploadPageSizeMessage = For mest problemfrie bruk, er det best å halde fila under 1 GB
|
||||
uploadPageBrowseButton = Vel ei fil på datamaskina di
|
||||
.title = Vel ei fil på datamaskina di
|
||||
uploadPageBrowseButton1 = Vel ei fil å laste opp
|
||||
.title = Vel ei fil å laste opp
|
||||
uploadPageMultipleFilesAlert = Opplasting av fleire filer eller ei mappe er for tida ikkje støtta.
|
||||
uploadPageBrowseButtonTitle = Last opp fil
|
||||
uploadingPageProgress = Lastar opp { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Arraste o arquivo para cá para iniciar o envio
|
||||
uploadPageSizeMessage = Para uma operação mais confiável, é melhor manter seu arquivo menor que 1GB
|
||||
uploadPageBrowseButton = Selecione um arquivo em seu computador
|
||||
.title = Selecione um arquivo em seu computador
|
||||
uploadPageBrowseButton1 = Selecione um arquivo para carregar
|
||||
.title = Selecione um arquivo para carregar
|
||||
uploadPageMultipleFilesAlert = Enviar múltiplos arquivos ou uma pasta ainda não é suportado.
|
||||
uploadPageBrowseButtonTitle = Enviar arquivo
|
||||
uploadingPageProgress = Enviando { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Largue o seu ficheiro aqui para começar a carregar
|
||||
uploadPageSizeMessage = Para uma operação mais confiável, é melhor manter o seu ficheiro abaixo de 1GB
|
||||
uploadPageBrowseButton = Selecionar um ficheiro no seu computador
|
||||
.title = Selecionar um ficheiro no seu computador
|
||||
uploadPageBrowseButton1 = Selecione um ficheiro a enviar
|
||||
.title = Selecione um ficheiro a enviar
|
||||
uploadPageMultipleFilesAlert = Carregar múltiplos ficheiros ou uma pasta não é atualmente suportado.
|
||||
uploadPageBrowseButtonTitle = Carregar ficheiro
|
||||
uploadingPageProgress = A carregar { $filename } ({ $size })
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Перетащите свой файл сюда, что
|
||||
uploadPageSizeMessage = Для более надёжной работы сервиса, размер вашего файла не должен превышать 1ГБ.
|
||||
uploadPageBrowseButton = Выбрать файл с моего компьютера
|
||||
.title = Выбрать файл с моего компьютера
|
||||
uploadPageBrowseButton1 = Выбрать файл для загрузки
|
||||
.title = Выбрать файл для загрузки
|
||||
uploadPageMultipleFilesAlert = Загрузка нескольких файлов или папок в настоящее время не поддерживается.
|
||||
uploadPageBrowseButtonTitle = Загрузить файл
|
||||
uploadingPageHeader = Загрузка вашего файла
|
||||
uploadingPageProgress = Загружаю { $filename } ({ $size })
|
||||
importingFile = Импортирование...
|
||||
verifyingFile = Проверка...
|
||||
encryptingFile = Шифрование...
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Скачать
|
||||
.title = Скачать
|
||||
downloadNotification = Ваша загрузка завершена.
|
||||
downloadFinish = Загрузка завершена
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } из { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Попробовать Firefox Send
|
||||
.title = Попробовать Firefox Send
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Presunutím súboru sem začnete nahrávanie
|
||||
uploadPageSizeMessage = Pre zaistenie čo najväčšej spoľahlivosti vám odporúčame nahrávať súbory menšie než 1GB.
|
||||
uploadPageBrowseButton = Vyberte súbor vo vašom počítači
|
||||
.title = Vyberte súbor vo vašom počítači
|
||||
uploadPageBrowseButton1 = Vyberte súbor na nahratie
|
||||
.title = Vyberte súbor na nahratie
|
||||
uploadPageMultipleFilesAlert = Nahrávanie viacerých súborov alebo priečinkov momentálne nie je podporované.
|
||||
uploadPageBrowseButtonTitle = Nahrať súbor
|
||||
uploadingPageProgress = Nahrávanie súboru { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Tukaj spustite datoteko za začetek nalaganja
|
||||
uploadPageSizeMessage = Za zanesljivo delovanje je najbolje, da datoteka ne presega 1 GB
|
||||
uploadPageBrowseButton = Izberite datoteko na računalniku
|
||||
.title = Izberite datoteko na računalniku
|
||||
uploadPageBrowseButton1 = Izberite datoteko za nalaganje
|
||||
.title = Izberite datoteko za nalaganje
|
||||
uploadPageMultipleFilesAlert = Nalaganje več datotek ali map trenutno ni podprto.
|
||||
uploadPageBrowseButtonTitle = Naloži datoteko
|
||||
uploadingPageProgress = Nalaganje { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Превуците ваше датотеке овде д
|
||||
uploadPageSizeMessage = За бољи рад предлажемо да датотека не буде већа од 1GB
|
||||
uploadPageBrowseButton = Изаберите датотеку на рачунару
|
||||
.title = Изаберите датотеку на рачунару
|
||||
uploadPageBrowseButton1 = Изаберите датотеку за отпремање
|
||||
.title = Изаберите датотеку за отпремање
|
||||
uploadPageMultipleFilesAlert = Отпремање фасцикли или више датотека тренутно није подржано.
|
||||
uploadPageBrowseButtonTitle = Отпреми датотеку
|
||||
uploadingPageProgress = Отпремам { $filename } ({ $size })
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Släpp filen här för att börja ladda upp
|
||||
uploadPageSizeMessage = För den mest tillförlitliga driften är det bäst att hålla din fil under 1 GB
|
||||
uploadPageBrowseButton = Välj en fil på din dator
|
||||
.title = Välj en fil på din dator
|
||||
uploadPageBrowseButton1 = Välj en fil att ladda upp
|
||||
.title = Välj en fil att ladda upp
|
||||
uploadPageMultipleFilesAlert = Överföring av flera filer eller en mapp stöds för närvarande inte.
|
||||
uploadPageBrowseButtonTitle = Ladda upp fil
|
||||
uploadingPageHeader = Överför din fil
|
||||
uploadingPageProgress = Laddar upp { $filename } ({ $size })
|
||||
importingFile = Importerar…
|
||||
verifyingFile = Verifierar…
|
||||
encryptingFile = Krypterar…
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = Ladda ner
|
||||
.title = Ladda ner
|
||||
downloadNotification = Din nedladdning har slutförts.
|
||||
downloadFinish = Nedladdning klar
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } av { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Testa Firefox Send
|
||||
.title = Testa Firefox Send
|
||||
@@ -67,7 +71,7 @@ expiredPageHeader = Den här länken har upphört eller har aldrig existerat i f
|
||||
notSupportedHeader = Din webbläsare stöds inte.
|
||||
// Firefox Send is a brand name and should not be localized.
|
||||
notSupportedDetail = Tyvärr stöder inte webbläsaren den webbteknologi som används av Firefox Send. Du måste försöka med en annan webbläsare. Vi rekommenderar Firefox!
|
||||
notSupportedLink = Varför stödjs inte min webbläsare?
|
||||
notSupportedLink = Varför stöds inte min webbläsare?
|
||||
notSupportedOutdatedDetail = Tyvärr stödjer den här versionen av Firefox inte webbtekniken som driver Firefox Send. Du måste uppdatera din webbläsare.
|
||||
updateFirefox = Uppdatera Firefox
|
||||
downloadFirefoxButtonSub = Gratis nedladdning
|
||||
|
||||
@@ -7,8 +7,10 @@ uploadPageDropMessage = ఎగుమతిని ప్రారంభించ
|
||||
uploadPageSizeMessage = అత్యంత నమ్మకమైన కార్యం కోసం, మీ ఫైలును 1GB కంటే తక్కువగా ఉంచడం ఉత్తమం
|
||||
uploadPageBrowseButton = మీ కంప్యూటర్లో ఒక ఫైలును ఎంచుకోండి
|
||||
.title = మీ కంప్యూటర్లో ఒక ఫైలును ఎంచుకోండి
|
||||
uploadPageBrowseButton1 = ఎక్కించటానికి ఒక ఫైలును ఎంచుకోండి
|
||||
.title = ఎక్కించటానికి ఒక ఫైలును ఎంచుకోండి
|
||||
uploadPageBrowseButtonTitle = ఫైలును ఎగుమతి చేయండి
|
||||
uploadingPageHeader = మీ ఫైలు ఎగుమతి అవుతుంది
|
||||
uploadingPageProgress = { $filename } ({ $size }) ఎక్కుతోంది
|
||||
importingFile = దిగుమతవుతోంది...
|
||||
verifyingFile = పరిశీలిస్తున్నది…
|
||||
encryptingFile = గుప్తీకరిస్తోంది...
|
||||
@@ -45,6 +47,8 @@ downloadButtonLabel = దిగుమతి
|
||||
.title = దిగుమతి
|
||||
downloadNotification = మీ దిగుమతి పూర్తయ్యింది.
|
||||
downloadFinish = దిగుమతి పూర్తయింది
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = { $totalSize }) యొక్క ({ $partialSize }
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Firefox sendను ప్రయత్నించండి
|
||||
.title = Firefox sendను ప్రయత్నించండి
|
||||
|
||||
@@ -9,9 +9,11 @@ uploadPageDropMessage = Yüklemeyi başlatmak için dosyanızı buraya bırakın
|
||||
uploadPageSizeMessage = Sorun yaşamamak adına dosyanızın 1 GB’den küçük olmasını öneririz
|
||||
uploadPageBrowseButton = Bilgisayarınızdan bir dosya seçin
|
||||
.title = Bilgisayarınızdan bir dosya seçin
|
||||
uploadPageBrowseButton1 = Yüklenecek dosyayı seçin
|
||||
.title = Yüklenecek dosyayı seçin
|
||||
uploadPageMultipleFilesAlert = Birden fazla dosya veya klasör yükleme şimdilik desteklenmiyor.
|
||||
uploadPageBrowseButtonTitle = Dosyayı yükle
|
||||
uploadingPageHeader = Dosyanız yükleniyor
|
||||
uploadingPageProgress = { $filename } yükleniyor ({ $size })
|
||||
importingFile = İçe aktarılıyor…
|
||||
verifyingFile = Doğrulanıyor…
|
||||
encryptingFile = Şifreleniyor…
|
||||
@@ -50,6 +52,8 @@ downloadButtonLabel = İndir
|
||||
.title = İndir
|
||||
downloadNotification = İndirme tamamlandı.
|
||||
downloadFinish = İndirme tamamlandı
|
||||
// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)".
|
||||
fileSizeProgress = ({ $partialSize } / { $totalSize })
|
||||
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
|
||||
sendYourFilesLink = Firefox Send’i deneyin
|
||||
.title = Firefox Send’i deneyin
|
||||
|
||||
@@ -3,12 +3,14 @@ title = Firefox Send
|
||||
siteSubtitle = веб-експеримент
|
||||
siteFeedback = Відгуки
|
||||
uploadPageHeader = Приватний, зашифрований обмін файлами
|
||||
uploadPageExplainer = Надсилайте файли, використовуючи безпечні, приватні та зашифровані посилання, термін дії яких автоматично закінчується, щоб ваші файли не лишився в Інтернеті назавжди.
|
||||
uploadPageExplainer = Надсилайте файли, використовуючи безпечні, приватні та зашифровані посилання, термін дії яких автоматично закінчується, щоб ваші файли не лишилися в Інтернеті назавжди.
|
||||
uploadPageLearnMore = Докладніше
|
||||
uploadPageDropMessage = Перетягніть свій файл сюди, щоб почати вивантаження
|
||||
uploadPageSizeMessage = Для більш надійної роботи сервісу, розмір вашого файлу не має перевищувати 1ГБ.
|
||||
uploadPageBrowseButton = Виберіть файл на комп'ютері
|
||||
.title = Виберіть файл на комп'ютері
|
||||
uploadPageBrowseButton1 = Виберіть файл для вивантаження
|
||||
.title = Виберіть файл для вивантаження
|
||||
uploadPageMultipleFilesAlert = Вивантаження кількох файлів чи тек на даний момент не підтримується.
|
||||
uploadPageBrowseButtonTitle = Вивантажити файл
|
||||
uploadingPageProgress = Вивантажуємо { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = Kéo thả tập tin của bạn vào đây và bắt đ
|
||||
uploadPageSizeMessage = Để có thể hoạt động tốt nhất, hãy giữ tập tin của bạn dưới 1GB.
|
||||
uploadPageBrowseButton = Chọn một tập tin từ máy tính
|
||||
.title = Chọn một tập tin từ máy tính
|
||||
uploadPageBrowseButton1 = Chọn tập tin để tải lên
|
||||
.title = Chọn tập tin để tải lên
|
||||
uploadPageMultipleFilesAlert = Tải lên nhiều tập tin một lúc hoặc tải lên một thư mục chưa được hỗ trợ.
|
||||
uploadPageBrowseButtonTitle = Tải tập tin lên
|
||||
uploadingPageProgress = Đang tải lên { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = 拖放您的文件到此处以开始上传
|
||||
uploadPageSizeMessage = 为保证运行稳定,建议文件大小不超过 1GB
|
||||
uploadPageBrowseButton = 选择一个您电脑上的文件
|
||||
.title = 选择一个您电脑上的文件
|
||||
uploadPageBrowseButton1 = 选择一个要上传的文件
|
||||
.title = 选择一个要上传的文件
|
||||
uploadPageMultipleFilesAlert = 目前不支持上传多个文件或上传文件夹。
|
||||
uploadPageBrowseButtonTitle = 上传文件
|
||||
uploadingPageProgress = 正在上传 { $filename } ({ $size })
|
||||
|
||||
@@ -9,6 +9,8 @@ uploadPageDropMessage = 將檔案放到此處開始上傳
|
||||
uploadPageSizeMessage = 為了讓系統能最穩定地執行,請盡量將檔案控制在 1GB 以下。
|
||||
uploadPageBrowseButton = 選擇您電腦上的檔案
|
||||
.title = 選擇您電腦上的檔案
|
||||
uploadPageBrowseButton1 = 選擇要上傳的檔案
|
||||
.title = 選擇要上傳的檔案
|
||||
uploadPageMultipleFilesAlert = 目前暫不支援上傳多個檔案或資料夾。
|
||||
uploadPageBrowseButtonTitle = 上傳檔案
|
||||
uploadingPageProgress = 正在上傳 { $filename }({ $size })
|
||||
|
||||
BIN
public/resources/send-fb.jpg
Normal file
BIN
public/resources/send-fb.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 KiB |
BIN
public/resources/send-twitter.jpg
Normal file
BIN
public/resources/send-twitter.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
@@ -1,6 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const pkg = require('../package.json');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
let commit;
|
||||
|
||||
@@ -10,11 +11,11 @@ try {
|
||||
// Whatever...
|
||||
}
|
||||
|
||||
const filename = path.join(__dirname, '..', 'public', 'version.json');
|
||||
const filename = path.join(__dirname, '..', 'dist', 'public', 'version.json');
|
||||
const filedata = {
|
||||
commit,
|
||||
source: pkg.homepage,
|
||||
version: process.env.CIRCLE_TAG || `v${pkg.version}`
|
||||
};
|
||||
|
||||
mkdirp.sync(path.dirname(filename));
|
||||
fs.writeFileSync(filename, JSON.stringify(filedata, null, 2) + '\n');
|
||||
|
||||
@@ -51,6 +51,11 @@ const conf = convict({
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'L10N_DEV'
|
||||
},
|
||||
base_url: {
|
||||
format: 'url',
|
||||
default: 'https://send.firefox.com',
|
||||
env: 'BASE_URL'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const storage = require('./storage.js');
|
||||
const Raven = require('raven');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const version = require('../public/version.json');
|
||||
const version = require('../dist/public/version.json');
|
||||
|
||||
if (conf.sentry_dsn) {
|
||||
Raven.config(conf.sentry_dsn).install();
|
||||
@@ -19,7 +19,7 @@ const mozlog = require('./log.js');
|
||||
|
||||
const log = mozlog('send.server');
|
||||
|
||||
const STATIC_PATH = path.join(__dirname, '../public');
|
||||
const STATIC_PATH = path.join(__dirname, '../dist/public');
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -38,14 +38,32 @@ function prodLangs() {
|
||||
|
||||
const availableLanguages = conf.l10n_dev ? allLangs() : prodLangs();
|
||||
|
||||
// dev middleware is broken at the moment because of how webpack builds the
|
||||
// handlebars templates. Leaving the commented code here as a mark of shame.
|
||||
|
||||
// if (conf.env === 'development') {
|
||||
// const webpack = require('webpack');
|
||||
// const webpackDevMiddleware = require('webpack-dev-middleware');
|
||||
// const config = require('../webpack.config.js');
|
||||
// config.devtool = 'inline-source-map';
|
||||
// const compiler = webpack(config);
|
||||
// const wdm = webpackDevMiddleware(compiler, {
|
||||
// publicPath: config.output.publicPath
|
||||
// });
|
||||
// app.use(wdm);
|
||||
// }
|
||||
app.set('views', 'dist/views/');
|
||||
app.engine(
|
||||
'handlebars',
|
||||
exphbs({
|
||||
defaultLayout: 'main',
|
||||
partialsDir: 'views/partials/',
|
||||
layoutsDir: 'dist/views/layouts',
|
||||
helpers: {
|
||||
availableLanguages,
|
||||
l10nDev: conf.l10n_dev
|
||||
baseUrl: conf.base_url,
|
||||
title: 'Firefox Send',
|
||||
description:
|
||||
'Encrypt and send files with a link that automatically expires to ensure your important documents don’t stay online forever.'
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -86,6 +104,14 @@ app.use(
|
||||
})
|
||||
);
|
||||
app.use(bodyParser.json());
|
||||
app.use(
|
||||
'/resources',
|
||||
express.static(path.join(STATIC_PATH, 'resources'), {
|
||||
setHeaders: function(res) {
|
||||
res.set('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
}
|
||||
})
|
||||
);
|
||||
app.use(express.static(STATIC_PATH));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
@@ -135,7 +161,7 @@ app.get('/exists/:id', async (req, res) => {
|
||||
app.get('/download/:id', async (req, res) => {
|
||||
const id = req.params.id;
|
||||
if (!validateID(id)) {
|
||||
res.sendStatus(404);
|
||||
res.status(404).render('notfound');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -236,24 +262,27 @@ app.post('/upload', (req, res, next) => {
|
||||
meta.delete = crypto.randomBytes(10).toString('hex');
|
||||
req.pipe(req.busboy);
|
||||
|
||||
req.busboy.on('file', async (fieldname, file, filename) => {
|
||||
try {
|
||||
await storage.set(newId, file, filename, meta);
|
||||
|
||||
const protocol = conf.env === 'production' ? 'https' : req.protocol;
|
||||
const url = `${protocol}://${req.get('host')}/download/${newId}/`;
|
||||
res.json({
|
||||
url,
|
||||
delete: meta.delete,
|
||||
id: newId
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.message === 'limit') {
|
||||
return res.sendStatus(413);
|
||||
req.busboy.on(
|
||||
'file',
|
||||
async (fieldname, file, filename, encoding, mimeType) => {
|
||||
try {
|
||||
meta.mimeType = mimeType || 'application/octet-stream';
|
||||
await storage.set(newId, file, filename, meta);
|
||||
const protocol = conf.env === 'production' ? 'https' : req.protocol;
|
||||
const url = `${protocol}://${req.get('host')}/download/${newId}/`;
|
||||
res.json({
|
||||
url,
|
||||
delete: meta.delete,
|
||||
id: newId
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.message === 'limit') {
|
||||
return res.sendStatus(413);
|
||||
}
|
||||
res.sendStatus(500);
|
||||
}
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
req.on('close', async err => {
|
||||
try {
|
||||
|
||||
@@ -1,22 +1,124 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlPlugin = require('html-webpack-plugin');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
vendor: ['babel-polyfill', 'raven-js'],
|
||||
upload: ['./frontend/src/upload.js'],
|
||||
download: ['./frontend/src/download.js']
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, 'public')
|
||||
filename: 'resources/[name].[chunkhash].js',
|
||||
path: path.resolve(__dirname, 'dist/public'),
|
||||
publicPath: '/'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loaders: 'babel-loader',
|
||||
include: [path.resolve(__dirname, 'frontend'), path.resolve(__dirname, 'node_modules/testpilot-ga/src')],
|
||||
query: { babelrc: false, presets: ['es2015', 'stage-2'], plugins: ['add-module-exports'] }
|
||||
loader: 'babel-loader',
|
||||
include: [
|
||||
path.resolve(__dirname, 'frontend'),
|
||||
path.resolve(__dirname, 'node_modules/testpilot-ga/src')
|
||||
],
|
||||
options: {
|
||||
babelrc: false,
|
||||
presets: [['es2015', { modules: false }], 'stage-2']
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(svg|png|jpg)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'resources/[name].[hash].[ext]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'resources/[name].[hash].[ext]'
|
||||
}
|
||||
},
|
||||
'extract-loader',
|
||||
{ loader: 'css-loader', options: { importLoaders: 1 } },
|
||||
'postcss-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.hbs$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
interpolate: 'require',
|
||||
minimize: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin([
|
||||
{
|
||||
context: 'public',
|
||||
from: 'locales/**/*.ftl'
|
||||
},
|
||||
{
|
||||
context: 'public',
|
||||
from: '*.*'
|
||||
},
|
||||
{
|
||||
from: 'views/**',
|
||||
to: '../'
|
||||
},
|
||||
{
|
||||
context: 'node_modules/l20n/dist/web',
|
||||
from: 'l20n.min.js'
|
||||
}
|
||||
]),
|
||||
new HtmlPlugin({
|
||||
filename: '../views/index.handlebars',
|
||||
template: 'webpack/upload.hbs',
|
||||
chunks: ['upload']
|
||||
}),
|
||||
new HtmlPlugin({
|
||||
filename: '../views/download.handlebars',
|
||||
template: 'webpack/download.hbs',
|
||||
chunks: ['download']
|
||||
}),
|
||||
new HtmlPlugin({
|
||||
filename: '../views/legal.handlebars',
|
||||
template: 'webpack/legal.hbs',
|
||||
inject: false
|
||||
}),
|
||||
new HtmlPlugin({
|
||||
filename: '../views/notfound.handlebars',
|
||||
template: 'webpack/notfound.hbs',
|
||||
inject: false
|
||||
}),
|
||||
new HtmlPlugin({
|
||||
filename: '../views/layouts/main.handlebars',
|
||||
template: 'webpack/layout.hbs',
|
||||
inject: 'head',
|
||||
excludeChunks: ['upload', 'download']
|
||||
}),
|
||||
new HtmlPlugin({
|
||||
filename: '../views/unsupported.handlebars',
|
||||
template: 'webpack/unsupported.hbs',
|
||||
inject: false
|
||||
}),
|
||||
new webpack.HashedModuleIdsPlugin(),
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor'
|
||||
}),
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'runtime'
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<div id="download">
|
||||
<script src="/download.js"></script>
|
||||
<div id="download-page-one">
|
||||
<div class="title">
|
||||
<span id="dl-file"
|
||||
@@ -11,17 +10,20 @@
|
||||
<span id="dl-filesize"></span>
|
||||
</div>
|
||||
<div class="description" data-l10n-id="downloadMessage"></div>
|
||||
<img src="/resources/illustration_download.svg" id="download-img" data-l10n-id="downloadAltText"/>
|
||||
<img src="../public/resources/illustration_download.svg" id="download-img" data-l10n-id="downloadAltText"/>
|
||||
<div>
|
||||
<button id="download-btn" data-l10n-id="downloadButtonLabel"></button>
|
||||
<button id="download-btn" class="btn" data-l10n-id="downloadButtonLabel"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="download-progress" hidden="true">
|
||||
<div id="dl-title" class="title"></div>
|
||||
<div class="description" data-l10n-id="downloadingPageMessage"></div>
|
||||
<!-- progress bar here -->
|
||||
<div class="progress-bar" id="dl-progress">
|
||||
<div class="progress-bar">
|
||||
<svg id="progress" width="166" height="166" viewPort="0 0 166 166" version="1.1">
|
||||
<circle r="73" cx="83" cy="83" fill="transparent"/>
|
||||
<circle id="bar" r="73" cx="83" cy="83" fill="transparent" transform="rotate(-90 83 83)" stroke-dasharray="458.67" stroke-dashoffset="458.67"/>
|
||||
</svg>
|
||||
<div class="percentage">
|
||||
<span class="percent-number"></span>
|
||||
<span class="percent-sign">%</span>
|
||||
@@ -32,5 +34,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-error" hidden="true">
|
||||
<div class="title" data-l10n-id="errorPageHeader"></div>
|
||||
<img id="upload-error-img" data-l10n-id="errorAltText" src="../public/resources/illustration_error.svg"/>
|
||||
</div>
|
||||
|
||||
<a class="send-new" data-state="completed" data-l10n-id="sendYourFilesLink" href="/"></a>
|
||||
</div>
|
||||
@@ -7,25 +7,34 @@
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="{{availableLanguages}}">
|
||||
|
||||
<title>Firefox Send</title>
|
||||
<meta property="og:title" content="{{title}}"/>
|
||||
<meta name="twitter:title" content="{{title}}"/>
|
||||
<meta name="description" content="{{description}}"/>
|
||||
<meta property="og:description" content="{{description}}"/>
|
||||
<meta name="twitter:description" content="{{description}}"/>
|
||||
<meta name="twitter:card" content="summary"/>
|
||||
<meta property="og:image" content="{{baseUrl}}${require('../public/resources/send-fb.jpg')}"/>
|
||||
<meta name="twitter:image" content="{{baseUrl}}${require('../public/resources/send-twitter.jpg')}"/>
|
||||
<meta property="og:url" content="{{baseUrl}}"/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/main.css" />
|
||||
<title>{{title}}</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="${require('../frontend/src/main.css')}" />
|
||||
{{#if fira}}
|
||||
<link rel="stylesheet" type="text/css" href="https://code.cdn.mozilla.net/fonts/fira.css" />
|
||||
{{/if}}
|
||||
|
||||
<link rel="icon" type="image/png" href="/resources/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="${require('../public/resources/favicon-32x32.png')}" sizes="32x32" />
|
||||
<link rel="localization" href="/locales/{locale}/send.ftl">
|
||||
|
||||
<script src="/jsconfig.js"></script>
|
||||
<script src="/polyfill.min.js"></script>
|
||||
<script defer src="/l20n.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="send-logo">
|
||||
<a href="/">
|
||||
<img src="/resources/send_logo.svg" alt="Send"/><h1 class="site-title">Send</h1>
|
||||
<img src="../public/resources/send_logo.svg" alt="Send"/><h1 class="site-title">Send</h1>
|
||||
</a>
|
||||
<div class="site-subtitle">
|
||||
<a href="https://testpilot.firefox.com">Firefox Test Pilot</a>
|
||||
@@ -44,7 +53,7 @@
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="legal-links">
|
||||
<a href="https://www.mozilla.org"><img class="mozilla-logo" src="/resources/mozilla-logo.svg"/></a>
|
||||
<a href="https://www.mozilla.org" role="presentation"><img class="mozilla-logo" src="../public/resources/mozilla-logo.svg" alt="mozilla"/></a>
|
||||
<a href="https://www.mozilla.org/about/legal" data-l10n-id="footerLinkLegal">Legal</a>
|
||||
<a href="https://testpilot.firefox.com/about" data-l10n-id="footerLinkAbout">About Test Pilot</a>
|
||||
<a href="/legal" data-l10n-id="footerLinkPrivacy">Privacy</a>
|
||||
@@ -52,8 +61,8 @@
|
||||
<a href="https://www.mozilla.org/privacy/websites/#cookies" data-l10n-id="footerLinkCookies">Cookies</a>
|
||||
</div>
|
||||
<div class="social-links">
|
||||
<a href="https://github.com/mozilla/send"><img class="github" src="/resources/github-icon.svg"/></a>
|
||||
<a href="https://twitter.com/FxTestPilot"><img class="twitter" src="/resources/twitter-icon.svg"/></a>
|
||||
<a href="https://github.com/mozilla/send" role="presentation"><img class="github" src="../public/resources/github-icon.svg" alt="github"/></a>
|
||||
<a href="https://twitter.com/FxTestPilot" role="presentation"><img class="twitter" src="../public/resources/twitter-icon.svg" alt="twitter"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -1,7 +1,7 @@
|
||||
<div id="download">
|
||||
<div class="title" data-l10n-id="expiredPageHeader"></div>
|
||||
<div class="share-window">
|
||||
<img src="/resources/illustration_expired.svg" id="expired-img" data-l10n-id="linkExpiredAlt"/>
|
||||
<img src="../public/resources/illustration_expired.svg" id="expired-img" data-l10n-id="linkExpiredAlt"/>
|
||||
</div>
|
||||
<div class="expired-description" data-l10n-id="uploadPageExplainer"></div>
|
||||
<a class="send-new" href="/" data-state="notfound" data-l10n-id="sendYourFilesLink"></a>
|
||||
@@ -3,14 +3,14 @@
|
||||
{{#if outdated}}
|
||||
<div class="description" data-l10n-id="notSupportedOutdatedDetail">Unfortunately this version of Firefox does not support the web technology that powers Firefox Send. You’ll need to update your browser.</div>
|
||||
<a id="update-firefox" href="https://support.mozilla.org/kb/update-firefox-latest-version">
|
||||
<img src="/resources/firefox_logo-only.svg" class="firefox-logo" alt="Firefox"/>
|
||||
<img src="../public/resources/firefox_logo-only.svg" class="firefox-logo" alt="Firefox"/>
|
||||
<div class="unsupported-button-text" data-l10n-id="updateFirefox">Update Firefox</div>
|
||||
</a>
|
||||
{{else}}
|
||||
<div class="description" data-l10n-id="notSupportedDetail">Unfortunately this browser does not support the web technology that powers Firefox Send. You’ll need to try another browser. We recommend Firefox!</div>
|
||||
<div class="description"><a href="https://github.com/mozilla/send/blob/master/docs/faq.md#why-is-my-browser-not-supported" data-l10n-id="notSupportedLink">Why is my browser not supported?</a></div>
|
||||
<a id="dl-firefox" href="https://www.mozilla.org/firefox/new/?scene=2">
|
||||
<img src="/resources/firefox_logo-only.svg" class="firefox-logo" alt="Firefox"/>
|
||||
<img src="../public/resources/firefox_logo-only.svg" class="firefox-logo" alt="Firefox"/>
|
||||
<div class="unsupported-button-text">Firefox<br>
|
||||
<span data-l10n-id="downloadFirefoxButtonSub">Free Download</span>
|
||||
</div>
|
||||
@@ -1,17 +1,16 @@
|
||||
<div id="page-one" hidden>
|
||||
<script src="/upload.js"></script>
|
||||
<div class="title" data-l10n-id="uploadPageHeader"></div>
|
||||
<div class="description">
|
||||
<div data-l10n-id="uploadPageExplainer"></div>
|
||||
<a href="https://testpilot.firefox.com/experiments/send" class="link" data-l10n-id="uploadPageLearnMore"></a>
|
||||
</div>
|
||||
<div class="upload-window" >
|
||||
<div id="upload-img"><img data-l10n-id="uploadSvgAlt" src="/resources/upload.svg"/></div>
|
||||
<div id="upload-img"><img data-l10n-id="uploadSvgAlt" src="../public/resources/upload.svg"/></div>
|
||||
<div id="upload-text" data-l10n-id="uploadPageDropMessage"></div>
|
||||
<span id="file-size-msg"><em data-l10n-id="uploadPageSizeMessage"></em></span>
|
||||
<form method="post" action="upload" enctype="multipart/form-data">
|
||||
<label for="file-upload" id="browse"
|
||||
data-l10n-id="uploadPageBrowseButton"></label>
|
||||
data-l10n-id="uploadPageBrowseButton1" class="btn"></label>
|
||||
<input id="file-upload" type="file" name="fileUploaded" />
|
||||
</form>
|
||||
</div>
|
||||
@@ -37,8 +36,11 @@
|
||||
<div id="upload-progress" hidden="true">
|
||||
<div class="title" id="upload-filename"></div>
|
||||
<div class="description"></div>
|
||||
<!-- progress bar here -->
|
||||
<div class="progress-bar" id="ul-progress">
|
||||
<div class="progress-bar">
|
||||
<svg id="progress" width="166" height="166" viewPort="0 0 166 166" version="1.1">
|
||||
<circle r="73" cx="83" cy="83" fill="transparent"/>
|
||||
<circle id="bar" r="73" cx="83" cy="83" fill="transparent" transform="rotate(-90 83 83)" stroke-dasharray="458.67" stroke-dashoffset="458.67"/>
|
||||
</svg>
|
||||
<div class="percentage">
|
||||
<span class="percent-number">0</span>
|
||||
<span class="percent-sign">%</span>
|
||||
@@ -58,9 +60,9 @@
|
||||
<div id="copy-text"></div>
|
||||
<div id="copy">
|
||||
<input id="link" type="url" value="" readonly/>
|
||||
<button id="copy-btn" data-l10n-id="copyUrlFormButton"></button>
|
||||
<button id="copy-btn" class="btn" data-l10n-id="copyUrlFormButton"></button>
|
||||
</div>
|
||||
<button id="delete-file" data-l10n-id="deleteFileButton"></button>
|
||||
<button id="delete-file" class="btn" data-l10n-id="deleteFileButton"></button>
|
||||
<a class="send-new" data-state="completed" data-l10n-id="sendAnotherFileLink" href="/"></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,6 +70,6 @@
|
||||
<div id="upload-error" hidden="true">
|
||||
<div class="title" data-l10n-id="errorPageHeader"></div>
|
||||
<div class="expired-description" data-l10n-id="errorPageMessage"></div>
|
||||
<img id="upload-error-img" data-l10n-id="errorAltText" src="/resources/illustration_error.svg"/>
|
||||
<a class="send-new" data-state="errored" data-l10n-id="sendAnotherFileLink"></a>
|
||||
<img id="upload-error-img" data-l10n-id="errorAltText" src="../public/resources/illustration_error.svg"/>
|
||||
<a class="send-new" href="/" data-state="errored" data-l10n-id="sendAnotherFileLink"></a>
|
||||
</div>
|
||||
Reference in New Issue
Block a user