This commit is contained in:
syuilo 2018-07-20 02:40:37 +09:00
parent 85bf76dd98
commit ec2b1ec3f0
15 changed files with 178 additions and 44 deletions

View file

@ -330,6 +330,8 @@ desktop/views/components/drive.file.vue:
banner: "バナー" banner: "バナー"
contextmenu: contextmenu:
rename: "名前を変更" rename: "名前を変更"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
copy-url: "URLをコピー" copy-url: "URLをコピー"
download: "ダウンロード" download: "ダウンロード"
else-files: "その他..." else-files: "その他..."
@ -377,6 +379,10 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード" upload: "ファイルをアップロード"
url-upload: "URLからアップロード" url-upload: "URLからアップロード"
desktop/views/components/media-image.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
desktop/views/components/follow-button.vue: desktop/views/components/follow-button.vue:
following: "フォロー中" following: "フォロー中"
follow: "フォロー" follow: "フォロー"
@ -853,6 +859,10 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ (md5)" hash: "ハッシュ (md5)"
exif: "EXIF" exif: "EXIF"
mobile/views/components/media-image.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
mobile/views/components/follow-button.vue: mobile/views/components/follow-button.vue:
following: "フォロー中" following: "フォロー中"
follow: "フォロー" follow: "フォロー"

View file

@ -46,33 +46,45 @@ export default Vue.extend({
display grid display grid
grid-gap 4px grid-gap 4px
> *
overflow hidden
border-radius 4px
&[data-count="1"] &[data-count="1"]
grid-template-rows 1fr grid-template-rows 1fr
&[data-count="2"] &[data-count="2"]
grid-template-columns 1fr 1fr grid-template-columns 1fr 1fr
grid-template-rows 1fr grid-template-rows 1fr
&[data-count="3"] &[data-count="3"]
grid-template-columns 1fr 0.5fr grid-template-columns 1fr 0.5fr
grid-template-rows 1fr 1fr grid-template-rows 1fr 1fr
:nth-child(1)
> *:nth-child(1)
grid-row 1 / 3 grid-row 1 / 3
:nth-child(3)
> *:nth-child(3)
grid-column 2 / 3 grid-column 2 / 3
grid-row 2 / 3 grid-row 2 / 3
&[data-count="4"] &[data-count="4"]
grid-template-columns 1fr 1fr grid-template-columns 1fr 1fr
grid-template-rows 1fr 1fr grid-template-rows 1fr 1fr
:nth-child(1) > *:nth-child(1)
grid-column 1 / 2 grid-column 1 / 2
grid-row 1 / 2 grid-row 1 / 2
:nth-child(2)
> *:nth-child(2)
grid-column 2 / 3 grid-column 2 / 3
grid-row 1 / 2 grid-row 1 / 2
:nth-child(3)
> *:nth-child(3)
grid-column 1 / 2 grid-column 1 / 2
grid-row 2 / 3 grid-row 2 / 3
:nth-child(4)
> *:nth-child(4)
grid-column 2 / 3 grid-column 2 / 3
grid-row 2 / 3 grid-row 2 / 3

View file

@ -68,6 +68,11 @@ export default Vue.extend({
icon: '%fa:i-cursor%', icon: '%fa:i-cursor%',
action: this.rename action: this.rename
}, { }, {
type: 'item',
text: this.file.isSensitive ? '%i18n:@contextmenu.unmark-as-sensitive%' : '%i18n:@contextmenu.mark-as-sensitive%',
icon: this.file.isSensitive ? '%fa:R eye%' : '%fa:R eye-slash%',
action: this.toggleSensitive
}, null, {
type: 'item', type: 'item',
text: '%i18n:@contextmenu.copy-url%', text: '%i18n:@contextmenu.copy-url%',
icon: '%fa:link%', icon: '%fa:link%',
@ -149,6 +154,13 @@ export default Vue.extend({
}); });
}, },
toggleSensitive() {
(this as any).api('drive/files/update', {
fileId: this.file.id,
isSensitive: !this.file.isSensitive
});
},
copyUrl() { copyUrl() {
copyToClipboard(this.file.url); copyToClipboard(this.file.url);
(this as any).apis.dialog({ (this as any).apis.dialog({

View file

@ -1,5 +1,11 @@
<template> <template>
<a class="mk-media-image" <div class="ldwbgwstjsdgcjruamauqdrffetqudry" v-if="image.isSensitive && hide" @click="hide = false">
<div>
<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
<span>%i18n:@click-to-show%</span>
</div>
</div>
<a class="lcjomzwbohoelkxsnuqjiaccdbdfiazy" v-else
:href="image.url" :href="image.url"
@mousemove="onMousemove" @mousemove="onMousemove"
@mouseleave="onMouseleave" @mouseleave="onMouseleave"
@ -21,6 +27,10 @@ export default Vue.extend({
}, },
raw: { raw: {
default: false default: false
},
hide: {
type: Boolean,
default: true
} }
}, },
computed: { computed: {
@ -56,16 +66,30 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-media-image .lcjomzwbohoelkxsnuqjiaccdbdfiazy
display block display block
cursor zoom-in cursor zoom-in
overflow hidden overflow hidden
width 100% width 100%
height 100% height 100%
background-position center background-position center
border-radius 4px
&:not(:hover) &:not(:hover)
background-size cover background-size cover
.ldwbgwstjsdgcjruamauqdrffetqudry
display flex
justify-content center
align-items center
background #111
color #fff
> div
display table-cell
text-align center
font-size 12px
> b
display block
</style> </style>

View file

@ -1,5 +1,11 @@
<template> <template>
<a class="mk-media-image" :href="image.url" target="_blank" :style="style" :title="image.name"></a> <div class="qjewsnkgzzxlxtzncydssfbgjibiehcy" v-if="image.isSensitive && hide" @click="hide = false">
<div>
<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
<span>%i18n:@click-to-show%</span>
</div>
</div>
<a class="gqnyydlzavusgskkfvwvjiattxdzsqlf" v-else :href="image.url" target="_blank" :style="style" :title="image.name"></a>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -13,6 +19,10 @@ export default Vue.extend({
}, },
raw: { raw: {
default: false default: false
},
hide: {
type: Boolean,
default: true
} }
}, },
computed: { computed: {
@ -35,13 +45,27 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-media-image .gqnyydlzavusgskkfvwvjiattxdzsqlf
display block display block
overflow hidden overflow hidden
width 100% width 100%
height 100% height 100%
background-position center background-position center
background-size cover background-size cover
border-radius 4px
.qjewsnkgzzxlxtzncydssfbgjibiehcy
display flex
justify-content center
align-items center
background #111
color #fff
> div
display table-cell
text-align center
font-size 12px
> b
display block
</style> </style>

View file

@ -81,3 +81,10 @@ props:
desc: desc:
ja: "フォルダ" ja: "フォルダ"
en: "The folder of this file" en: "The folder of this file"
sensitive:
type: "boolean"
optional: true
desc:
ja: "このメディアが「閲覧注意」(NSFW)かどうか"
en: "Whether this media is NSFW"

View file

@ -33,6 +33,7 @@ export type IMetadata = {
url?: string; url?: string;
deletedAt?: Date; deletedAt?: Date;
isMetaOnly?: boolean; isMetaOnly?: boolean;
isSensitive?: boolean;
}; };
export type IDriveFile = { export type IDriveFile = {

View file

@ -16,7 +16,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
return null; return null;
} }
const image = await new Resolver().resolve(value); const image = await new Resolver().resolve(value) as any;
if (image.url == null) { if (image.url == null) {
throw new Error('invalid image: url not privided'); throw new Error('invalid image: url not privided');
@ -24,7 +24,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
log(`Creating the Image: ${image.url}`); log(`Creating the Image: ${image.url}`);
return await uploadFromUrl(image.url, actor, null, image.url); return await uploadFromUrl(image.url, actor, null, image.url, image.sensitive);
} }
/** /**

View file

@ -1,7 +1,8 @@
import config from '../../../config'; import config from '../../../config';
import { IDriveFile } from '../../../models/drive-file'; import { IDriveFile } from '../../../models/drive-file';
export default (fileId: IDriveFile['_id']) => ({ export default (file: IDriveFile) => ({
type: 'Image', type: 'Image',
url: `${config.drive_url}/${fileId}` url: `${config.drive_url}/${file._id}`,
sensitive: file.metadata.isSensitive
}); });

View file

@ -4,10 +4,16 @@ import config from '../../../config';
import { ILocalUser } from '../../../models/user'; import { ILocalUser } from '../../../models/user';
import toHtml from '../../../mfm/html'; import toHtml from '../../../mfm/html';
import parse from '../../../mfm/parse'; import parse from '../../../mfm/parse';
import DriveFile from '../../../models/drive-file';
export default (user: ILocalUser) => { export default async (user: ILocalUser) => {
const id = `${config.url}/users/${user._id}`; const id = `${config.url}/users/${user._id}`;
const [avatar, banner] = await Promise.all([
DriveFile.findOne({ _id: user.avatarId }),
DriveFile.findOne({ _id: user.bannerId })
]);
return { return {
type: user.isBot ? 'Service' : 'Person', type: user.isBot ? 'Service' : 'Person',
id, id,
@ -18,8 +24,8 @@ export default (user: ILocalUser) => {
preferredUsername: user.username, preferredUsername: user.username,
name: user.name, name: user.name,
summary: toHtml(parse(user.description)), summary: toHtml(parse(user.description)),
icon: user.avatarId && renderImage(user.avatarId), icon: user.avatarId && renderImage(avatar),
image: user.bannerId && renderImage(user.bannerId), image: user.bannerId && renderImage(banner),
manuallyApprovesFollowers: user.isLocked, manuallyApprovesFollowers: user.isLocked,
publicKey: renderKey(user) publicKey: renderKey(user)
}; };

View file

@ -111,13 +111,13 @@ router.get('/users/:user/publickey', async ctx => {
}); });
// user // user
function userInfo(ctx: Router.IRouterContext, user: IUser) { async function userInfo(ctx: Router.IRouterContext, user: IUser) {
if (user === null) { if (user === null) {
ctx.status = 404; ctx.status = 404;
return; return;
} }
ctx.body = pack(renderPerson(user as ILocalUser)); ctx.body = pack(await renderPerson(user as ILocalUser));
} }
router.get('/users/:user', async ctx => { router.get('/users/:user', async ctx => {
@ -128,7 +128,7 @@ router.get('/users/:user', async ctx => {
host: null host: null
}); });
userInfo(ctx, user); await userInfo(ctx, user);
}); });
router.get('/@:user', async (ctx, next) => { router.get('/@:user', async (ctx, next) => {
@ -139,7 +139,7 @@ router.get('/@:user', async (ctx, next) => {
host: null host: null
}); });
userInfo(ctx, user); await userInfo(ctx, user);
}); });
//#endregion //#endregion

View file

@ -29,6 +29,14 @@ export const meta = {
desc: { desc: {
ja: 'フォルダID' ja: 'フォルダID'
} }
}),
isSensitive: $.bool.optional.note({
default: false,
desc: {
ja: 'このメディアが「閲覧注意」(NSFW)かどうか',
en: 'Whether this media is NSFW'
}
}) })
} }
}; };
@ -68,7 +76,7 @@ export default async (file: any, params: any, user: ILocalUser): Promise<any> =>
try { try {
// Create file // Create file
const driveFile = await create(user, file.path, name, null, ps.folderId); const driveFile = await create(user, file.path, name, null, ps.folderId, false, false, null, null, ps.isSensitive);
cleanup(); cleanup();

View file

@ -3,6 +3,7 @@ import DriveFolder from '../../../../../models/drive-folder';
import DriveFile, { validateFileName, pack } from '../../../../../models/drive-file'; import DriveFile, { validateFileName, pack } from '../../../../../models/drive-file';
import { publishDriveStream } from '../../../../../stream'; import { publishDriveStream } from '../../../../../stream';
import { ILocalUser } from '../../../../../models/user'; import { ILocalUser } from '../../../../../models/user';
import getParams from '../../../get-params';
export const meta = { export const meta = {
desc: { desc: {
@ -12,18 +13,48 @@ export const meta = {
requireCredential: true, requireCredential: true,
kind: 'drive-write' kind: 'drive-write',
params: {
fileId: $.type(ID).note({
desc: {
ja: '対象のファイルID'
}
}),
folderId: $.type(ID).optional.nullable.note({
default: undefined,
desc: {
ja: 'フォルダID'
}
}),
name: $.str.optional.pipe(validateFileName).note({
default: undefined,
desc: {
ja: 'ファイル名',
en: 'Name of the file'
}
}),
isSensitive: $.bool.optional.note({
default: undefined,
desc: {
ja: 'このメディアが「閲覧注意」(NSFW)かどうか',
en: 'Whether this media is NSFW'
}
})
}
}; };
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
// Get 'fileId' parameter const [ps, psErr] = getParams(meta, params);
const [fileId, fileIdErr] = $.type(ID).get(params.fileId); if (psErr) return rej(psErr);
if (fileIdErr) return rej('invalid fileId param');
// Fetch file // Fetch file
const file = await DriveFile const file = await DriveFile
.findOne({ .findOne({
_id: fileId, _id: ps.fileId,
'metadata.userId': user._id 'metadata.userId': user._id
}); });
@ -31,23 +62,18 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
return rej('file-not-found'); return rej('file-not-found');
} }
// Get 'name' parameter if (ps.name) file.filename = ps.name;
const [name, nameErr] = $.str.optional.pipe(validateFileName).get(params.name);
if (nameErr) return rej('invalid name param');
if (name) file.filename = name;
// Get 'folderId' parameter if (ps.isSensitive) file.metadata.isSensitive = ps.isSensitive;
const [folderId, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
if (folderIdErr) return rej('invalid folderId param');
if (folderId !== undefined) { if (ps.folderId !== undefined) {
if (folderId === null) { if (ps.folderId === null) {
file.metadata.folderId = null; file.metadata.folderId = null;
} else { } else {
// Fetch folder // Fetch folder
const folder = await DriveFolder const folder = await DriveFolder
.findOne({ .findOne({
_id: folderId, _id: ps.folderId,
userId: user._id userId: user._id
}); });
@ -62,7 +88,8 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
await DriveFile.update(file._id, { await DriveFile.update(file._id, {
$set: { $set: {
filename: file.filename, filename: file.filename,
'metadata.folderId': file.metadata.folderId 'metadata.folderId': file.metadata.folderId,
'metadata.isSensitive': file.metadata.isSensitive
} }
}); });

View file

@ -83,7 +83,8 @@ export default async function(
force: boolean = false, force: boolean = false,
metaOnly: boolean = false, metaOnly: boolean = false,
url: string = null, url: string = null,
uri: string = null uri: string = null,
sensitive = false
): Promise<IDriveFile> { ): Promise<IDriveFile> {
// Calc md5 hash // Calc md5 hash
const calcHash = new Promise<string>((res, rej) => { const calcHash = new Promise<string>((res, rej) => {
@ -258,7 +259,8 @@ export default async function(
folderId: folder !== null ? folder._id : null, folderId: folder !== null ? folder._id : null,
comment: comment, comment: comment,
properties: properties, properties: properties,
isMetaOnly: metaOnly isMetaOnly: metaOnly,
isSensitive: sensitive
} as IMetadata; } as IMetadata;
if (url !== null) { if (url !== null) {

View file

@ -13,7 +13,7 @@ import * as mongodb from 'mongodb';
const log = debug('misskey:drive:upload-from-url'); const log = debug('misskey:drive:upload-from-url');
export default async (url: string, user: IUser, folderId: mongodb.ObjectID = null, uri: string = null): Promise<IDriveFile> => { export default async (url: string, user: IUser, folderId: mongodb.ObjectID = null, uri: string = null, sensitive = false): Promise<IDriveFile> => {
log(`REQUESTED: ${url}`); log(`REQUESTED: ${url}`);
let name = URL.parse(url).pathname.split('/').pop(); let name = URL.parse(url).pathname.split('/').pop();
@ -48,7 +48,7 @@ export default async (url: string, user: IUser, folderId: mongodb.ObjectID = nul
let error; let error;
try { try {
driveFile = await create(user, path, name, null, folderId, false, config.preventCacheRemoteFiles, url, uri); driveFile = await create(user, path, name, null, folderId, false, config.preventCacheRemoteFiles, url, uri, sensitive);
log(`got: ${driveFile._id}`); log(`got: ${driveFile._id}`);
} catch (e) { } catch (e) {
error = e; error = e;