Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve paste uploading Resolve #3023 #4542

Merged
merged 12 commits into from
Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ common:
add-visible-user: "ユーザーを追加"
cw-placeholder: "内容への注釈 (オプション)"
username-prompt: "ユーザー名を入力してください"
enter-file-name: "ファイル名を編集"

weekday-short:
sunday: "日"
Expand Down Expand Up @@ -201,6 +202,11 @@ common:
remember-note-visibility: "投稿の公開範囲を記憶する"
web-search-engine: "ウェブ検索エンジン"
web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
paste: "ペースト"
pasted-file-name: "ペーストされたファイル名のテンプレート"
pasted-file-name-desc: "例: \"yyyy-MM-dd HH-mm-ss [{{number}}]\" → \"2018-03-20 21-30-24 1\""
paste-dialog: "ペースト時にファイル名を編集"
paste-dialog-desc: "ペースト時にファイル名を編集するダイアログを表示するようにします。"
keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
Expand Down
24 changes: 19 additions & 5 deletions src/client/app/common/scripts/post-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { host, url } from '../../config';
import i18n from '../../i18n';
import { erase, unique } from '../../../../prelude/array';
import extractMentions from '../../../../misc/extract-mentions';
import { formatTimeString } from '../../../../misc/format-time-string';

export default (opts) => ({
i18n: i18n(),
Expand Down Expand Up @@ -244,8 +245,8 @@ export default (opts) => ({
for (const x of Array.from((this.$refs.file as any).files)) this.upload(x);
},

upload(file) {
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder);
upload(file: File, name?: string) {
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder, name);
},

onChangeUploadings(uploads) {
Expand Down Expand Up @@ -334,10 +335,23 @@ export default (opts) => ({
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
},

async onPaste(e) {
for (const item of Array.from(e.clipboardData.items)) {
async onPaste(e: ClipboardEvent) {
for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) {
if (item.kind == 'file') {
this.upload(item.getAsFile());
const file = item.getAsFile();
const lio = file.name.lastIndexOf('.');
const ext = lio >= 0 ? file.name.slice(lio) : '';
const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.settings.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
const name = this.$store.state.settings.pasteDialog
? await this.$root.dialog({
title: this.$t('@.post-form.enter-file-name'),
input: {
default: formatted
},
allowEmpty: false
}).then(({ canceled, result }) => canceled ? false : result)
: formatted;
if (name) this.upload(file, name);
}
}

Expand Down
22 changes: 18 additions & 4 deletions src/client/app/common/views/components/messaging-room.form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import * as autosize from 'autosize';
import { formatTimeString } from '../../../../../misc/format-time-string';

export default Vue.extend({
i18n: i18n('common/views/components/messaging-room.form.vue'),
Expand Down Expand Up @@ -84,13 +85,26 @@ export default Vue.extend({
}
},
methods: {
onPaste(e) {
async onPaste(e: ClipboardEvent) {
const data = e.clipboardData;
const items = data.items;

if (items.length == 1) {
if (items[0].kind == 'file') {
this.upload(items[0].getAsFile());
const file = items[0].getAsFile();
const lio = file.name.lastIndexOf('.');
const ext = lio >= 0 ? file.name.slice(lio) : '';
const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.settings.pastedFileName).replace(/{{number}}/g, '1')}${ext}`;
const name = this.$store.state.settings.pasteDialog
? await this.$root.dialog({
title: this.$t('@.post-form.enter-file-name'),
input: {
default: formatted
},
allowEmpty: false
}).then(({ canceled, result }) => canceled ? false : result)
: formatted;
if (name) this.upload(file, name);
}
} else {
if (items[0].kind == 'file') {
Expand Down Expand Up @@ -157,8 +171,8 @@ export default Vue.extend({
this.upload((this.$refs.file as any).files[0]);
},

upload(file) {
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder);
upload(file: File, name?: string) {
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder, name);
},

onUploaded(file) {
Expand Down
24 changes: 23 additions & 1 deletion src/client/app/common/views/components/settings/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,19 @@

<section>
<header>{{ $t('@._settings.web-search-engine') }}</header>
<ui-input v-model="webSearchEngine">{{ $t('@._settings.web-search-engine') }}<template #desc>{{ $t('@._settings.web-search-engine-desc') }}</template></ui-input>
<ui-input v-model="webSearchEngine">{{ $t('@._settings.web-search-engine') }}
<template #desc>{{ $t('@._settings.web-search-engine-desc') }}</template>
</ui-input>
</section>

<section v-if="!$root.isMobile">
<header>{{ $t('@._settings.paste') }}</header>
<ui-input v-model="pastedFileName">{{ $t('@._settings.pasted-file-name') }}
<template #desc>{{ $t('@._settings.pasted-file-name-desc') }}</template>
</ui-input>
<ui-switch v-model="pasteDialog">{{ $t('@._settings.paste-dialog') }}
<template #desc>{{ $t('@._settings.paste-dialog-desc') }}</template>
</ui-switch>
</section>
</ui-card>

Expand Down Expand Up @@ -412,6 +424,16 @@ export default Vue.extend({
set(value) { this.$store.dispatch('settings/set', { key: 'webSearchEngine', value }); }
},

pastedFileName: {
get() { return this.$store.state.settings.pastedFileName; },
set(value) { this.$store.dispatch('settings/set', { key: 'pastedFileName', value }); }
},

pasteDialog: {
get() { return this.$store.state.settings.pasteDialog; },
set(value) { this.$store.dispatch('settings/set', { key: 'pasteDialog', value }); }
},

showReplyTarget: {
get() { return this.$store.state.settings.showReplyTarget; },
set(value) { this.$store.dispatch('settings/set', { key: 'showReplyTarget', value }); }
Expand Down
5 changes: 3 additions & 2 deletions src/client/app/common/views/components/uploader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default Vue.extend({
});
},

upload(file: File, folder: any) {
upload(file: File, folder: any, name?: string) {
if (folder && typeof folder == 'object') folder = folder.id;

const id = Math.random();
Expand All @@ -61,7 +61,7 @@ export default Vue.extend({

const ctx = {
id: id,
name: file.name || 'untitled',
name: name || file.name || 'untitled',
progress: undefined,
img: window.URL.createObjectURL(file)
};
Expand All @@ -75,6 +75,7 @@ export default Vue.extend({
data.append('file', file);

if (folder) data.append('folderId', folder);
if (name) data.append('name', name);

const xhr = new XMLHttpRequest();
xhr.open('POST', apiUrl + '/drive/files/create', true);
Expand Down
24 changes: 19 additions & 5 deletions src/client/app/common/views/widgets/post-form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import define from '../../../common/define-widget';
import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor';
import { formatTimeString } from '../../../../../misc/format-time-string';

export default define({
name: 'post-form',
Expand Down Expand Up @@ -109,10 +110,23 @@ export default define({
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && !this.posting && this.text) this.post();
},

onPaste(e) {
for (const item of Array.from(e.clipboardData.items)) {
async onPaste(e: ClipboardEvent) {
for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) {
if (item.kind == 'file') {
this.upload(item.getAsFile());
const file = item.getAsFile();
const lio = file.name.lastIndexOf('.');
const ext = lio >= 0 ? file.name.slice(lio) : '';
const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.settings.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
const name = this.$store.state.settings.pasteDialog
? await this.$root.dialog({
title: this.$t('@.post-form.enter-file-name'),
input: {
default: formatted
},
allowEmpty: false
}).then(({ canceled, result }) => canceled ? false : result)
: formatted;
if (name) this.upload(file, name);
}
}
},
Expand All @@ -121,8 +135,8 @@ export default define({
for (const x of Array.from((this.$refs.file as any).files)) this.upload(x);
},

upload(file) {
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder);
upload(file: File, name?: string) {
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder, name);
},

onDragover(e) {
Expand Down
2 changes: 2 additions & 0 deletions src/client/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const defaultSettings = {
mobileHomeProfiles: {},
deckProfiles: {},
uploadFolder: null,
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
pasteDialog: false,
};

const defaultDeviceSettings = {
Expand Down
50 changes: 50 additions & 0 deletions src/misc/format-time-string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const defaultLocaleStringFormats: {[index: string]: string} = {
'weekday': 'narrow',
'era': 'narrow',
'year': 'numeric',
'month': 'numeric',
'day': 'numeric',
'hour': 'numeric',
'minute': 'numeric',
'second': 'numeric',
'timeZoneName': 'short'
};

function formatLocaleString(date: Date, format: string): string {
return format.replace(/\{\{(\w+)(:(\w+))?\}\}/g, (match: string, kind: string, unused?, option?: string) => {
if (['weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName'].includes(kind)) {
return date.toLocaleString(window.navigator.language, {[kind]: option ? option : defaultLocaleStringFormats[kind]});
} else {
return match;
}
});
}

function formatDateTimeString(date: Date, format: string): string {
return format
.replace(/yyyy/g, date.getFullYear().toString())
.replace(/yy/g, date.getFullYear().toString().slice(-2))
.replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long'}))
.replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short'}))
.replace(/MM/g, (`0${date.getMonth() + 1}`).slice(-2))
.replace(/M/g, (date.getMonth() + 1).toString())
.replace(/dd/g, (`0${date.getDate()}`).slice(-2))
.replace(/d/g, date.getDate().toString())
.replace(/HH/g, (`0${date.getHours()}`).slice(-2))
.replace(/H/g, date.getHours().toString())
.replace(/hh/g, (`0${(date.getHours() % 12) || 12}`).slice(-2))
.replace(/h/g, ((date.getHours() % 12) || 12).toString())
.replace(/mm/g, (`0${date.getMinutes()}`).slice(-2))
.replace(/m/g, date.getMinutes().toString())
.replace(/ss/g, (`0${date.getSeconds()}`).slice(-2))
.replace(/s/g, date.getSeconds().toString())
.replace(/tt/g, date.getHours() >= 12 ? 'PM' : 'AM');
}

export function formatTimeString(date: Date, format: string): string {
return format.replace(/\[(([^\[]|\[\])*)\]|([yMdHhmst]{1,4})/g, (match: string, localeformat?: string, unused?, datetimeformat?: string) => {
if (localeformat) return formatLocaleString(date, localeformat);
if (datetimeformat) return formatDateTimeString(date, datetimeformat);
return match;
});
}
10 changes: 9 additions & 1 deletion src/server/api/endpoints/drive/files/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export const meta = {
}
},

name: {
validator: $.optional.nullable.str,
default: null as any,
desc: {
'ja-JP': 'ファイル名(拡張子があるなら含めて)'
}
},

isSensitive: {
validator: $.optional.either($.bool, $.str),
default: false,
Expand Down Expand Up @@ -72,7 +80,7 @@ export const meta = {

export default define(meta, async (ps, user, app, file, cleanup) => {
// Get 'name' parameter
let name = file.originalname;
let name = ps.name || file.originalname;
if (name !== undefined && name !== null) {
name = name.trim();
if (name.length === 0) {
Expand Down
14 changes: 14 additions & 0 deletions test/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,20 @@ describe('API', () => {
assert.strictEqual(res.body.name, 'Lenna.png');
}));

it('ファイルに名前を付けられる', async(async () => {
const alice = await signup({ username: 'alice' });

const res = await assert.request(server)
.post('/drive/files/create')
.field('i', alice.token)
.field('name', 'Belmond.png')
.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');

expect(res).have.status(200);
expect(res.body).be.a('object');
expect(res.body).have.property('name').eql('Belmond.png');
}));

it('ファイル無しで怒られる', async(async () => {
const alice = await signup({ username: 'alice' });

Expand Down