-
+
{{ '%i18n:@matching.waiting-for%'.split('{}')[0] }}{{ matching | userName }}{{ '%i18n:@matching.waiting-for%'.split('{}')[1] }}
@@ -34,6 +34,11 @@ export default Vue.extend({
gameId: {
type: String,
required: false
+ },
+ selfNav: {
+ type: Boolean,
+ require: false,
+ default: true
}
},
@@ -95,18 +100,24 @@ export default Vue.extend({
(this as any).api('games/reversi/games/show', {
gameId: this.gameId
}).then(game => {
- this.nav(game, true);
+ this.game = game;
Progress.done();
});
}
},
- nav(game, silent) {
- this.matching = null;
- this.game = game;
+ async nav(game, actualNav = true) {
+ if (this.selfNav) {
+ // 受け取ったゲーム情報が省略されたものなら完全な情報を取得する
+ if (game != null && (game.settings == null || game.settings.map == null)) {
+ game = await (this as any).api('games/reversi/games/show', {
+ gameId: game.id
+ });
+ }
- if (!silent) {
- this.$emit('nav', this.game);
+ this.game = game;
+ } else {
+ this.$emit('nav', game, actualNav);
}
},
@@ -125,7 +136,8 @@ export default Vue.extend({
}).then(game => {
if (game) {
this.matching = null;
- this.game = game;
+
+ this.nav(game);
}
});
},
@@ -133,6 +145,11 @@ export default Vue.extend({
onMatched(game) {
this.matching = null;
this.game = game;
+ this.nav(game, false);
+ },
+
+ goIndex() {
+ this.nav(null);
}
}
});
diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.ts b/src/client/app/common/views/components/misskey-flavored-markdown.ts
index f9c97bd35a..e97da4302c 100644
--- a/src/client/app/common/views/components/misskey-flavored-markdown.ts
+++ b/src/client/app/common/views/components/misskey-flavored-markdown.ts
@@ -1,5 +1,6 @@
import Vue from 'vue';
import * as emojilib from 'emojilib';
+import { length } from 'stringz';
import parse from '../../../../../mfm/parse';
import getAcct from '../../../../../misc/acct/render';
import { url } from '../../../config';
@@ -40,10 +41,13 @@ export default Vue.component('misskey-flavored-markdown', {
ast = this.ast;
}
+ let bigCount = 0;
+ let motionCount = 0;
+
// Parse ast to DOM
const els = flatten(ast.map(token => {
switch (token.type) {
- case 'text':
+ case 'text': {
const text = token.content.replace(/(\r\n|\n|\r)/g, '\n');
if (this.shouldBreak) {
@@ -54,30 +58,52 @@ export default Vue.component('misskey-flavored-markdown', {
} else {
return createElement('span', text.replace(/\n/g, ' '));
}
+ }
- case 'bold':
+ case 'bold': {
return createElement('b', token.bold);
+ }
- case 'big':
+ case 'big': {
+ bigCount++;
+ const isLong = length(token.big) > 10;
+ const isMany = bigCount > 3;
return (createElement as any)('strong', {
attrs: {
- style: 'display: inline-block; font-size: 200%;'
+ style: `display: inline-block; font-size: ${ isMany ? '100%' : '150%' };`
},
- directives: [this.$store.state.settings.disableAnimatedMfm ? {} : {
+ directives: [this.$store.state.settings.disableAnimatedMfm || isLong || isMany ? {} : {
name: 'animate-css',
value: { classes: 'tada', iteration: 'infinite' }
}]
}, token.big);
+ }
- case 'url':
+ case 'motion': {
+ motionCount++;
+ const isLong = length(token.motion) > 10;
+ const isMany = motionCount > 3;
+ return (createElement as any)('span', {
+ attrs: {
+ style: 'display: inline-block;'
+ },
+ directives: [this.$store.state.settings.disableAnimatedMfm || isLong || isMany ? {} : {
+ name: 'animate-css',
+ value: { classes: 'rubberBand', iteration: 'infinite' }
+ }]
+ }, token.motion);
+ }
+
+ case 'url': {
return createElement(MkUrl, {
props: {
url: token.content,
target: '_blank'
}
});
+ }
- case 'link':
+ case 'link': {
return createElement('a', {
attrs: {
class: 'link',
@@ -86,8 +112,9 @@ export default Vue.component('misskey-flavored-markdown', {
title: token.url
}
}, token.title);
+ }
- case 'mention':
+ case 'mention': {
return (createElement as any)('a', {
attrs: {
href: `${url}/@${getAcct(token)}`,
@@ -99,16 +126,18 @@ export default Vue.component('misskey-flavored-markdown', {
value: token.content
}]
}, token.content);
+ }
- case 'hashtag':
+ case 'hashtag': {
return createElement('a', {
attrs: {
href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
target: '_blank'
}
}, token.content);
+ }
- case 'code':
+ case 'code': {
return createElement('pre', {
class: 'code'
}, [
@@ -118,15 +147,17 @@ export default Vue.component('misskey-flavored-markdown', {
}
})
]);
+ }
- case 'inline-code':
+ case 'inline-code': {
return createElement('code', {
domProps: {
innerHTML: token.html
}
});
+ }
- case 'quote':
+ case 'quote': {
const text2 = token.quote.replace(/(\r\n|\n|\r)/g, '\n');
if (this.shouldBreak) {
@@ -145,27 +176,32 @@ export default Vue.component('misskey-flavored-markdown', {
}
}, text2.replace(/\n/g, ' '));
}
+ }
- case 'title':
+ case 'title': {
return createElement('div', {
attrs: {
class: 'title'
}
}, token.title);
+ }
- case 'emoji':
+ case 'emoji': {
const emoji = emojilib.lib[token.emoji];
return createElement('span', emoji ? emoji.char : token.content);
+ }
- case 'search':
+ case 'search': {
return createElement(MkGoogle, {
props: {
q: token.query
}
});
+ }
- default:
+ default: {
console.log('unknown ast type:', token.type);
+ }
}
}));
diff --git a/src/client/app/common/views/components/ui/form/button.vue b/src/client/app/common/views/components/ui/form/button.vue
index 6e1475bc38..9c37b3118b 100644
--- a/src/client/app/common/views/components/ui/form/button.vue
+++ b/src/client/app/common/views/components/ui/form/button.vue
@@ -45,6 +45,9 @@ root(isDark)
color isDark ? #fff : #606266
transition 0.1s
+ *
+ pointer-events none
+
&:hover
&:focus
color $theme-color
diff --git a/src/client/app/desktop/views/pages/games/reversi.vue b/src/client/app/desktop/views/pages/games/reversi.vue
index 590bda2d86..ce9b42c65f 100644
--- a/src/client/app/desktop/views/pages/games/reversi.vue
+++ b/src/client/app/desktop/views/pages/games/reversi.vue
@@ -1,6 +1,6 @@
-
+
@@ -14,9 +14,14 @@ export default Vue.extend({
}
},
methods: {
- nav(game) {
- history.pushState(null, null, '/reversi/' + game.id);
- },
+ nav(game, actualNav) {
+ if (actualNav) {
+ this.$router.push('/reversi/' + game.id);
+ } else {
+ // TODO: https://github.com/vuejs/vue-router/issues/703
+ this.$router.push('/reversi/' + game.id);
+ }
+ }
}
});
diff --git a/src/client/app/mobile/views/pages/games/reversi.vue b/src/client/app/mobile/views/pages/games/reversi.vue
index 7118644ef3..e6e6325f8b 100644
--- a/src/client/app/mobile/views/pages/games/reversi.vue
+++ b/src/client/app/mobile/views/pages/games/reversi.vue
@@ -1,7 +1,7 @@
%fa:gamepad%%i18n:@reversi%
-
+
@@ -14,8 +14,13 @@ export default Vue.extend({
document.documentElement.style.background = '#fff';
},
methods: {
- nav(game) {
- history.pushState(null, null, '/reversi/' + game.id);
+ nav(game, actualNav) {
+ if (actualNav) {
+ this.$router.push('/reversi/' + game.id);
+ } else {
+ // TODO: https://github.com/vuejs/vue-router/issues/703
+ this.$router.push('/reversi/' + game.id);
+ }
}
}
});
diff --git a/src/mfm/html.ts b/src/mfm/html.ts
index dfe291b3a5..c11bd55cf4 100644
--- a/src/mfm/html.ts
+++ b/src/mfm/html.ts
@@ -18,6 +18,12 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
document.body.appendChild(b);
},
+ motion({ document }, { big }) {
+ const b = document.createElement('strong');
+ b.textContent = big;
+ document.body.appendChild(b);
+ },
+
code({ document }, { code }) {
const pre = document.createElement('pre');
const inner = document.createElement('code');
diff --git a/src/mfm/parse/elements/big.ts b/src/mfm/parse/elements/big.ts
index ca798986e9..8e39c75a55 100644
--- a/src/mfm/parse/elements/big.ts
+++ b/src/mfm/parse/elements/big.ts
@@ -1,5 +1,5 @@
/**
- * Bold
+ * Big
*/
export type TextElementBig = {
diff --git a/src/mfm/parse/elements/motion.ts b/src/mfm/parse/elements/motion.ts
new file mode 100644
index 0000000000..9e7370e071
--- /dev/null
+++ b/src/mfm/parse/elements/motion.ts
@@ -0,0 +1,20 @@
+/**
+ * Motion
+ */
+
+export type TextElementMotion = {
+ type: 'motion'
+ content: string
+ motion: string
+};
+
+export default function(text: string) {
+ const match = text.match(/^\(\(\((.+?)\)\)\)/) || text.match(/^(.+?)<\/motion>/);
+ if (!match) return null;
+ const motion = match[0];
+ return {
+ type: 'motion',
+ content: motion,
+ motion: match[1]
+ } as TextElementMotion;
+}
diff --git a/src/mfm/parse/index.ts b/src/mfm/parse/index.ts
index 066c062559..99c00ae649 100644
--- a/src/mfm/parse/index.ts
+++ b/src/mfm/parse/index.ts
@@ -14,6 +14,7 @@ import { TextElementQuote } from './elements/quote';
import { TextElementSearch } from './elements/search';
import { TextElementTitle } from './elements/title';
import { TextElementUrl } from './elements/url';
+import { TextElementMotion } from './elements/motion';
const elements = [
require('./elements/big'),
@@ -27,7 +28,8 @@ const elements = [
require('./elements/inline-code'),
require('./elements/quote'),
require('./elements/emoji'),
- require('./elements/search')
+ require('./elements/search'),
+ require('./elements/motion')
].map(element => element.default as TextElementProcessor);
export type TextElement = { type: 'text', content: string }
@@ -42,7 +44,8 @@ export type TextElement = { type: 'text', content: string }
| TextElementQuote
| TextElementSearch
| TextElementTitle
- | TextElementUrl;
+ | TextElementUrl
+ | TextElementMotion;
export type TextElementProcessor = (text: string, i: number) => TextElement | TextElement[];
export default (source: string): TextElement[] => {
diff --git a/src/server/api/endpoints/games/reversi/games/surrender.ts b/src/server/api/endpoints/games/reversi/games/surrender.ts
index dc9908aef1..49821650ed 100644
--- a/src/server/api/endpoints/games/reversi/games/surrender.ts
+++ b/src/server/api/endpoints/games/reversi/games/surrender.ts
@@ -12,7 +12,7 @@ export const meta = {
requireCredential: true,
params: {
- gameId: $.type(ID).optional.note({
+ gameId: $.type(ID).note({
desc: {
ja: '投了したい対局'
}
diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts
index 9024740a96..c8d588eaaf 100644
--- a/src/server/api/service/github.ts
+++ b/src/server/api/service/github.ts
@@ -11,7 +11,7 @@ const handler = new EventEmitter();
let bot: IUser;
-const post = async (text: string) => {
+const post = async (text: string, home = true) => {
if (bot == null) {
const account = await User.findOne({
usernameLower: config.github_bot.username.toLowerCase()
@@ -25,7 +25,7 @@ const post = async (text: string) => {
}
}
- createNote(bot, { text, visibility: 'home' });
+ createNote(bot, { text, visibility: home ? 'home' : 'public' });
};
// Init router
@@ -130,7 +130,7 @@ handler.on('issue_comment', event => {
handler.on('watch', event => {
const sender = event.sender;
- post(`⭐️ Starred by **${sender.login}** ⭐️`);
+ post(`(((⭐️))) Starred by **${sender.login}** (((⭐️)))`, false);
});
handler.on('fork', event => {
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index 64556c160a..4f90a19f2c 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -103,6 +103,24 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
data.visibleUsers = data.visibleUsers.filter(x => x != null);
}
+ if (data.reply && data.reply.deletedAt != null) {
+ return rej();
+ }
+
+ if (data.renote && data.renote.deletedAt != null) {
+ return rej();
+ }
+
+ // リプライ先が自分以外の非公開の投稿なら禁止
+ if (data.reply && data.reply.visibility == 'private' && !data.reply.userId.equals(user._id)) {
+ return rej();
+ }
+
+ // Renote先が自分以外の非公開の投稿なら禁止
+ if (data.renote && data.renote.visibility == 'private' && !data.renote.userId.equals(user._id)) {
+ return rej();
+ }
+
if (data.text) {
data.text = data.text.trim();
}
diff --git a/test/mfm.ts b/test/mfm.ts
index 1a22e1491b..706c4c549a 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -34,11 +34,25 @@ describe('Text', () => {
it('big', () => {
const tokens = analyze('***Strawberry*** Pasta');
assert.deepEqual([
- { type: 'big', content: '***Strawberry***', bold: 'Strawberry' },
+ { type: 'big', content: '***Strawberry***', big: 'Strawberry' },
{ type: 'text', content: ' Pasta' }
], tokens);
});
+ it('motion', () => {
+ const tokens1 = analyze('(((Strawberry))) Pasta');
+ assert.deepEqual([
+ { type: 'motion', content: '(((Strawberry)))', motion: 'Strawberry' },
+ { type: 'text', content: ' Pasta' }
+ ], tokens1);
+
+ const tokens2 = analyze('Strawberry Pasta');
+ assert.deepEqual([
+ { type: 'motion', content: 'Strawberry', motion: 'Strawberry' },
+ { type: 'text', content: ' Pasta' }
+ ], tokens2);
+ });
+
it('mention', () => {
const tokens = analyze('@himawari お腹ペコい');
assert.deepEqual([