diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b7ef578c52..390bfc9f31 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -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: "お使いの端末は位置情報に対応していません"
diff --git a/src/client/app/common/views/components/note-header.vue b/src/client/app/common/views/components/note-header.vue
index 2c7ae0194c..012b678ab3 100644
--- a/src/client/app/common/views/components/note-header.vue
+++ b/src/client/app/common/views/components/note-header.vue
@@ -19,6 +19,9 @@
+
+
+
@@ -115,4 +118,7 @@ export default Vue.extend({
> .visibility
margin-left 8px
+ > .localOnly
+ margin-left 4px
+
diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue
index 896be039b3..0335fba0ee 100644
--- a/src/client/app/common/views/components/visibility-chooser.vue
+++ b/src/client/app/common/views/components/visibility-chooser.vue
@@ -35,6 +35,24 @@
{{ $t('private') }}
+
+
+
+ {{ $t('local-public') }}
+
+
+
+
+
+ {{ $t('local-home') }}
+
+
+
+
+
+ {{ $t('local-followers') }}
+
+
diff --git a/src/client/app/desktop/views/components/note.vue b/src/client/app/desktop/views/components/note.vue
index e2b67c150f..6bd4674269 100644
--- a/src/client/app/desktop/views/components/note.vue
+++ b/src/client/app/desktop/views/components/note.vue
@@ -20,6 +20,15 @@
{{ note.user | userName }}
{{ this.$t('reposted-by').substr(this.$t('reposted-by').indexOf('}') + 1) }}
+
+
+
+
+
+
+
+
+
@@ -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
diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue
index e05fab168c..02478b4eb3 100644
--- a/src/client/app/desktop/views/components/post-form.vue
+++ b/src/client/app/desktop/views/components/post-form.vue
@@ -14,6 +14,7 @@
{{ $t('recent-tags') }}:
#{{ tag }}
+ {{ $t('local-only-message') }}
@@ -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
diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue
index df7a5c5a04..f941c59d9f 100644
--- a/src/client/app/mobile/views/components/post-form.vue
+++ b/src/client/app/mobile/views/components/post-form.vue
@@ -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');
diff --git a/src/docs/api/entities/note.yaml b/src/docs/api/entities/note.yaml
index 6654be2b02..89846a56c7 100644
--- a/src/docs/api/entities/note.yaml
+++ b/src/docs/api/entities/note.yaml
@@ -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
diff --git a/src/models/note.ts b/src/models/note.ts
index 516045225c..717960bb23 100644
--- a/src/models/note.ts
+++ b/src/models/note.ts
@@ -50,6 +50,7 @@ export type INote = {
userId: mongo.ObjectID;
appId: mongo.ObjectID;
viaMobile: boolean;
+ localOnly: boolean;
renoteCount: number;
repliesCount: number;
reactionCounts: any;
diff --git a/src/queue/index.ts b/src/queue/index.ts
index 5a48dbe648..8683bcd1df 100644
--- a/src/queue/index.ts
+++ b/src/queue/index.ts
@@ -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,
diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts
index 7501bf1a89..48a02e79bd 100644
--- a/src/remote/activitypub/models/note.ts
+++ b/src/remote/activitypub/models/note.ts
@@ -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,
diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts
index 8da933a0f6..888feb08ce 100644
--- a/src/server/activitypub.ts
+++ b/src/server/activitypub.ts
@@ -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) {
diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts
index 24d4e3730e..6b917ef843 100644
--- a/src/server/activitypub/outbox.ts
+++ b/src/server/activitypub/outbox.ts
@@ -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) {
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index a7050e2ec2..4f8d6a4f4f 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -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
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index 53d51036b3..0fd983d6c2 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -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,