parent
							
								
									aaaaf2681a
								
							
						
					
					
						commit
						bceb02d760
					
				|  | @ -96,6 +96,9 @@ common: | |||
|     specified: "ダイレクト" | ||||
|     specified-desc: "指定したユーザーにのみ公開" | ||||
|     private: "非公開" | ||||
|     local-public: "公開(ローカルのみ)" | ||||
|     local-home: "ホーム(ローカルのみ)" | ||||
|     local-followers: "フォロワー(ローカルのみ)" | ||||
| 
 | ||||
|   note-placeholders: | ||||
|     a: "今どうしてる?" | ||||
|  | @ -471,6 +474,9 @@ common/views/components/visibility-chooser.vue: | |||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
|   local-public: "公開(ローカルのみ)" | ||||
|   local-home: "ホーム(ローカルのみ)" | ||||
|   local-followers: "フォロワー(ローカルのみ)" | ||||
| 
 | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|  | @ -761,6 +767,7 @@ desktop/views/components/post-form.vue: | |||
|   create-poll: "アンケートを作成" | ||||
|   text-remain: "残り{}文字" | ||||
|   recent-tags: "最近" | ||||
|   local-only-message: "この投稿はローカルにのみ公開されます" | ||||
|   click-to-tagging: "クリックでタグ付け" | ||||
|   visibility: "公開範囲" | ||||
|   geolocation-alert: "お使いの端末は位置情報に対応していません" | ||||
|  |  | |||
|  | @ -19,6 +19,9 @@ | |||
| 			<template v-if="note.visibility == 'specified'"><fa icon="envelope"/></template> | ||||
| 			<template v-if="note.visibility == 'private'"><fa icon="lock"/></template> | ||||
| 		</span> | ||||
| 		<span class="localOnly" v-if="note.localOnly == true"> | ||||
| 			<template><fa icon="heart"/></template> | ||||
| 		</span> | ||||
| 	</div> | ||||
| </header> | ||||
| </template> | ||||
|  | @ -115,4 +118,7 @@ export default Vue.extend({ | |||
| 		> .visibility | ||||
| 			margin-left 8px | ||||
| 
 | ||||
| 		> .localOnly | ||||
| 			margin-left 4px | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -35,6 +35,24 @@ | |||
| 				<span>{{ $t('private') }}</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div @click="choose('local-public')" :class="{ active: v == 'local-public' }"> | ||||
| 			<div><fa icon="globe"/></div> | ||||
| 			<div> | ||||
| 				<span>{{ $t('local-public') }}</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div @click="choose('local-home')" :class="{ active: v == 'local-home' }"> | ||||
| 			<div><fa icon="home"/></div> | ||||
| 			<div> | ||||
| 				<span>{{ $t('local-home') }}</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div @click="choose('local-followers')" :class="{ active: v == 'local-followers' }"> | ||||
| 			<div><fa icon="unlock"/></div> | ||||
| 			<div> | ||||
| 				<span>{{ $t('local-followers') }}</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  |  | |||
|  | @ -20,6 +20,15 @@ | |||
| 		<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> | ||||
| 		<span>{{ this.$t('reposted-by').substr(this.$t('reposted-by').indexOf('}') + 1) }}</span> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 		<span class="visibility" v-if="note.visibility != 'public'"> | ||||
| 			<template v-if="note.visibility == 'home'"><fa icon="home"/></template> | ||||
| 			<template v-if="note.visibility == 'followers'"><fa icon="unlock"/></template> | ||||
| 			<template v-if="note.visibility == 'specified'"><fa icon="envelope"/></template> | ||||
| 			<template v-if="note.visibility == 'private'"><fa icon="lock"/></template> | ||||
| 		</span> | ||||
| 		<span class="localOnly" v-if="note.localOnly == true"> | ||||
| 			<template><fa icon="heart"/></template> | ||||
| 		</span> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<mk-avatar class="avatar" :user="appearNote.user"/> | ||||
|  | @ -199,9 +208,6 @@ export default Vue.extend({ | |||
| 		> span | ||||
| 			flex-shrink 0 | ||||
| 
 | ||||
| 			&:last-of-type | ||||
| 				margin-right 8px | ||||
| 
 | ||||
| 		.name | ||||
| 			overflow hidden | ||||
| 			flex-shrink 1 | ||||
|  | @ -215,6 +221,18 @@ export default Vue.extend({ | |||
| 			flex-shrink 0 | ||||
| 			font-size 0.9em | ||||
| 
 | ||||
| 		> .visibility | ||||
| 			margin-left 8px | ||||
| 
 | ||||
| 			[data-icon] | ||||
| 				margin-right 0 | ||||
| 
 | ||||
| 		> .localOnly | ||||
| 			margin-left 4px | ||||
| 
 | ||||
| 			[data-icon] | ||||
| 				margin-right 0 | ||||
| 
 | ||||
| 		& + article | ||||
| 			padding-top 8px | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
| 			<b>{{ $t('recent-tags') }}:</b> | ||||
| 			<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a> | ||||
| 		</div> | ||||
| 		<div class="local-only" v-if="this.localOnly == true">{{ $t('local-only-message') }}</div> | ||||
| 		<input v-show="useCw" v-model="cw" :placeholder="$t('annotations')"> | ||||
| 		<div class="textarea"> | ||||
| 			<textarea :class="{ with: (files.length != 0 || poll) }" | ||||
|  | @ -112,6 +113,7 @@ export default Vue.extend({ | |||
| 			geo: null, | ||||
| 			visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility, | ||||
| 			visibleUsers: [], | ||||
| 			localOnly: false, | ||||
| 			autocomplete: null, | ||||
| 			draghover: false, | ||||
| 			recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), | ||||
|  | @ -363,7 +365,14 @@ export default Vue.extend({ | |||
| 				source: this.$refs.visibilityButton | ||||
| 			}); | ||||
| 			w.$once('chosen', v => { | ||||
| 				this.visibility = v; | ||||
| 				const m = v.match(/^local-(.+)/); | ||||
| 				if (m) { | ||||
| 					this.localOnly = true; | ||||
| 					this.visibility = m[1]; | ||||
| 				} else { | ||||
| 					this.localOnly = false; | ||||
| 					this.visibility = v; | ||||
| 				} | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
|  | @ -407,6 +416,7 @@ export default Vue.extend({ | |||
| 				cw: this.useCw ? this.cw || '' : undefined, | ||||
| 				visibility: this.visibility, | ||||
| 				visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, | ||||
| 				localOnly: this.localOnly, | ||||
| 				geo: this.geo ? { | ||||
| 					coordinates: [this.geo.longitude, this.geo.latitude], | ||||
| 					altitude: this.geo.altitude, | ||||
|  | @ -640,6 +650,10 @@ export default Vue.extend({ | |||
| 				margin-right 8px | ||||
| 				white-space nowrap | ||||
| 
 | ||||
| 		> .local-only | ||||
| 			margin 0 0 8px 0 | ||||
| 			color var(--primary) | ||||
| 
 | ||||
| 	> .mk-uploader | ||||
| 		margin 8px 0 0 0 | ||||
| 		padding 8px | ||||
|  |  | |||
|  | @ -16,6 +16,15 @@ | |||
| 		<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> | ||||
| 		<span>{{ this.$t('reposted-by').substr(this.$t('reposted-by').indexOf('}') + 1) }}</span> | ||||
| 		<mk-time :time="note.createdAt"/> | ||||
| 		<span class="visibility" v-if="note.visibility != 'public'"> | ||||
| 			<template v-if="note.visibility == 'home'"><fa icon="home"/></template> | ||||
| 			<template v-if="note.visibility == 'followers'"><fa icon="unlock"/></template> | ||||
| 			<template v-if="note.visibility == 'specified'"><fa icon="envelope"/></template> | ||||
| 			<template v-if="note.visibility == 'private'"><fa icon="lock"/></template> | ||||
| 		</span> | ||||
| 		<span class="localOnly" v-if="note.localOnly == true"> | ||||
| 			<template><fa icon="heart"/></template> | ||||
| 		</span> | ||||
| 	</div> | ||||
| 	<article> | ||||
| 		<mk-avatar class="avatar" :user="appearNote.user" v-if="$store.state.device.postStyle != 'smart'"/> | ||||
|  | @ -163,9 +172,6 @@ export default Vue.extend({ | |||
| 		> span | ||||
| 			flex-shrink 0 | ||||
| 
 | ||||
| 			&:last-of-type | ||||
| 				margin-right 8px | ||||
| 
 | ||||
| 		.name | ||||
| 			overflow hidden | ||||
| 			flex-shrink 1 | ||||
|  | @ -179,6 +185,18 @@ export default Vue.extend({ | |||
| 			flex-shrink 0 | ||||
| 			font-size 0.9em | ||||
| 
 | ||||
| 		> .visibility | ||||
| 			margin-left 8px | ||||
| 
 | ||||
| 			[data-icon] | ||||
| 				margin-right 0 | ||||
| 
 | ||||
| 		> .localOnly | ||||
| 			margin-left 4px | ||||
| 
 | ||||
| 			[data-icon] | ||||
| 				margin-right 0 | ||||
| 
 | ||||
| 		& + article | ||||
| 			padding-top 8px | ||||
| 
 | ||||
|  |  | |||
|  | @ -102,6 +102,7 @@ export default Vue.extend({ | |||
| 			geo: null, | ||||
| 			visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility, | ||||
| 			visibleUsers: [], | ||||
| 			localOnly: false, | ||||
| 			useCw: false, | ||||
| 			cw: null, | ||||
| 			recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), | ||||
|  | @ -274,7 +275,14 @@ export default Vue.extend({ | |||
| 				compact: true | ||||
| 			}); | ||||
| 			w.$once('chosen', v => { | ||||
| 				this.visibility = v; | ||||
| 				const m = v.match(/^local-(.+)/); | ||||
| 				if (m) { | ||||
| 					this.localOnly = true; | ||||
| 					this.visibility = m[1]; | ||||
| 				} else { | ||||
| 					this.localOnly = false; | ||||
| 					this.visibility = v; | ||||
| 				} | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
|  | @ -320,6 +328,7 @@ export default Vue.extend({ | |||
| 				} : null, | ||||
| 				visibility: this.visibility, | ||||
| 				visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, | ||||
| 				localOnly: this.localOnly, | ||||
| 				viaMobile: viaMobile | ||||
| 			}).then(data => { | ||||
| 				this.$emit('posted'); | ||||
|  |  | |||
|  | @ -26,6 +26,13 @@ props: | |||
|       ja-JP: "モバイル端末から投稿したか否か(自己申告であることに留意)" | ||||
|       en-US: "Whether this note sent via a mobile device" | ||||
| 
 | ||||
|   localOnly: | ||||
|     type: "boolean" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja-JP: "ローカルのみに公開する投稿か否か" | ||||
|       en-US: "Whether this note is no federation" | ||||
| 
 | ||||
|   text: | ||||
|     type: "string" | ||||
|     optional: true | ||||
|  |  | |||
|  | @ -50,6 +50,7 @@ export type INote = { | |||
| 	userId: mongo.ObjectID; | ||||
| 	appId: mongo.ObjectID; | ||||
| 	viaMobile: boolean; | ||||
| 	localOnly: boolean; | ||||
| 	renoteCount: number; | ||||
| 	repliesCount: number; | ||||
| 	reactionCounts: any; | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ export function createHttpJob(data: any) { | |||
| } | ||||
| 
 | ||||
| export function deliver(user: ILocalUser, content: any, to: any) { | ||||
| 	if (content == null) return; | ||||
| 
 | ||||
| 	createHttpJob({ | ||||
| 		type: 'deliver', | ||||
| 		user, | ||||
|  |  | |||
|  | @ -116,6 +116,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false | |||
| 		cw: note.summary, | ||||
| 		text: text, | ||||
| 		viaMobile: false, | ||||
| 		localOnly: false, | ||||
| 		geo: undefined, | ||||
| 		visibility, | ||||
| 		visibleUsers, | ||||
|  |  | |||
|  | @ -66,7 +66,8 @@ router.get('/notes/:note', async (ctx, next) => { | |||
| 
 | ||||
| 	const note = await Note.findOne({ | ||||
| 		_id: new mongo.ObjectID(ctx.params.note), | ||||
| 		visibility: { $in: ['public', 'home'] } | ||||
| 		visibility: { $in: ['public', 'home'] }, | ||||
| 		localOnly: { $ne: true } | ||||
| 	}); | ||||
| 
 | ||||
| 	if (note === null) { | ||||
|  | @ -83,7 +84,8 @@ router.get('/notes/:note', async (ctx, next) => { | |||
| router.get('/notes/:note/activity', async ctx => { | ||||
| 	const note = await Note.findOne({ | ||||
| 		_id: new mongo.ObjectID(ctx.params.note), | ||||
| 		visibility: { $in: ['public', 'home'] } | ||||
| 		visibility: { $in: ['public', 'home'] }, | ||||
| 		localOnly: { $ne: true } | ||||
| 	}); | ||||
| 
 | ||||
| 	if (note === null) { | ||||
|  |  | |||
|  | @ -55,7 +55,8 @@ export default async (ctx: Router.IRouterContext) => { | |||
| 
 | ||||
| 		const query = { | ||||
| 			userId: user._id, | ||||
| 			visibility: { $in: ['public', 'home'] } | ||||
| 			visibility: { $in: ['public', 'home'] }, | ||||
| 			localOnly: { $ne: true } | ||||
| 		} as any; | ||||
| 
 | ||||
| 		if (sinceId) { | ||||
|  |  | |||
|  | @ -74,6 +74,14 @@ export const meta = { | |||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		localOnly: { | ||||
| 			validator: $.bool.optional, | ||||
| 			default: false, | ||||
| 			desc: { | ||||
| 				'ja-JP': 'ローカルのみに投稿か否か。' | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		geo: { | ||||
| 			validator: $.obj({ | ||||
| 				coordinates: $.arr().length(2) | ||||
|  | @ -226,6 +234,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => { | |||
| 		cw: ps.cw, | ||||
| 		app, | ||||
| 		viaMobile: ps.viaMobile, | ||||
| 		localOnly: ps.localOnly, | ||||
| 		visibility: ps.visibility, | ||||
| 		visibleUsers, | ||||
| 		geo: ps.geo | ||||
|  |  | |||
|  | @ -95,6 +95,7 @@ type Option = { | |||
| 	geo?: any; | ||||
| 	poll?: any; | ||||
| 	viaMobile?: boolean; | ||||
| 	localOnly?: boolean; | ||||
| 	cw?: string; | ||||
| 	visibility?: string; | ||||
| 	visibleUsers?: IUser[]; | ||||
|  | @ -109,6 +110,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< | |||
| 	if (data.createdAt == null) data.createdAt = new Date(); | ||||
| 	if (data.visibility == null) data.visibility = 'public'; | ||||
| 	if (data.viaMobile == null) data.viaMobile = false; | ||||
| 	if (data.localOnly == null) data.localOnly = false; | ||||
| 
 | ||||
| 	if (data.visibleUsers) { | ||||
| 		data.visibleUsers = erase(null, data.visibleUsers); | ||||
|  | @ -139,6 +141,16 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< | |||
| 		return rej('Renote target is private of others'); | ||||
| 	} | ||||
| 
 | ||||
| 	// ローカルのみをRenoteしたらローカルのみにする
 | ||||
| 	if (data.renote && data.renote.localOnly) { | ||||
| 		data.localOnly = true; | ||||
| 	} | ||||
| 
 | ||||
| 	// ローカルのみにリプライしたらローカルのみにする
 | ||||
| 	if (data.reply && data.reply.localOnly) { | ||||
| 		data.localOnly = true; | ||||
| 	} | ||||
| 
 | ||||
| 	if (data.text) { | ||||
| 		data.text = data.text.trim(); | ||||
| 	} | ||||
|  | @ -308,6 +320,8 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< | |||
| }); | ||||
| 
 | ||||
| async function renderActivity(data: Option, note: INote) { | ||||
| 	if (data.localOnly) return null; | ||||
| 
 | ||||
| 	const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length == 0) | ||||
| 		? renderAnnounce(data.renote.uri ? data.renote.uri : `${config.url}/notes/${data.renote._id}`, note) | ||||
| 		: renderCreate(await renderNote(note, false), note); | ||||
|  | @ -389,6 +403,7 @@ async function insertNote(user: IUser, data: Option, tags: string[], emojis: str | |||
| 		emojis, | ||||
| 		userId: user._id, | ||||
| 		viaMobile: data.viaMobile, | ||||
| 		localOnly: data.localOnly, | ||||
| 		geo: data.geo || null, | ||||
| 		appId: data.app ? data.app._id : null, | ||||
| 		visibility: data.visibility, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue