Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fdcc994291 | ||
![]() |
f54363076c | ||
![]() |
ec016e5a95 | ||
![]() |
bbdb2496a4 | ||
![]() |
b515cc90e9 | ||
![]() |
bb92158dff | ||
![]() |
c652add16a | ||
![]() |
b8a7468c4a | ||
![]() |
e220ef3e75 | ||
![]() |
4bb4903ee5 | ||
![]() |
9487840ae3 | ||
![]() |
7e3a8d56e6 | ||
![]() |
e909eac296 | ||
![]() |
8dc7f28744 | ||
![]() |
a4b1e8ca26 | ||
![]() |
79b0cc6785 | ||
![]() |
00b134ce1e | ||
![]() |
b3fc4dc00f | ||
![]() |
d06fbbe3ea | ||
![]() |
28bfb45426 | ||
![]() |
1c60a49c96 | ||
![]() |
3ae8ff083b | ||
![]() |
c12ccb2a15 | ||
![]() |
e3b1d00e4c | ||
![]() |
98795aad9a | ||
![]() |
ca26edbfce | ||
![]() |
3058e8f354 | ||
![]() |
4c9b66b0f0 | ||
![]() |
6eb9ba31bf | ||
![]() |
5bbf4187e6 | ||
![]() |
f2425f71c2 | ||
![]() |
b0e00da2f7 | ||
![]() |
215472cd17 | ||
![]() |
072fd4455e | ||
![]() |
2ed9e26a4f | ||
![]() |
8a3e26cdb8 | ||
![]() |
7301671962 | ||
![]() |
0d0f25818e | ||
![]() |
7850d68dc2 | ||
![]() |
f0f5b32300 | ||
![]() |
1ca0976e85 | ||
![]() |
7fbfd17896 | ||
![]() |
3d04f7db62 | ||
![]() |
e301630c9d | ||
![]() |
afbccaae41 | ||
![]() |
893c01c207 | ||
![]() |
41c80097ce | ||
![]() |
250933fff3 | ||
![]() |
bc9454f67c |
@@ -1,6 +1,14 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
10.80.0
|
||||
----------
|
||||
* サイレンス機能の追加
|
||||
* リプライ/メンションされていれば非フォロワーへのフォロワー限定でも参照可能に
|
||||
* MFMの解析を強化
|
||||
* Misskey以外のインスタンスからMisskeyの投稿を見たときに改行が多い問題を修正
|
||||
* Misskey以外のインスタンスからMisskeyの投稿を見たときにメンションのURLが展開されるのを修正
|
||||
|
||||
10.79.1
|
||||
----------
|
||||
* jump構文の追加
|
||||
|
@@ -93,6 +93,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=prtYqPOiSHBulhM7NU0VzMaWx39-9ntdq25b6kafDNA%3D" alt="negao" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/3?token-time=2145916800&token-hash=c8HeVqLtmdgH-gSBJg8i10gmOcwllM87MDHeznl3el0%3D" alt="Melilot" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=Ch3iF81ZGP0LMo894Y9ajpLisgtE91SnxtZE7fxsgrM%3D" alt="べすれい" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
|
||||
@@ -101,6 +102,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td>
|
||||
<td><a href="https://www.patreon.com/negao">negao</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
|
||||
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
|
||||
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||
@@ -114,7 +116,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/3?token-time=2145916800&token-hash=gMq30aylxu5v3G8pRhWR5jeRBbYWEoRKjGbNeiCQz5g%3D" alt="Acid Chicken" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/2?token-time=2145916800&token-hash=zcwFxb2zopzWwksKVU1YpfAEjsl4yKT02aQ6yiAFRiQ%3D" alt="natalie" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/10789744/97175095d8f04c0f86225ff47cb98d40/1?token-time=2145916800&token-hash=ubVARikVOg3v7NW6LDhtG-ClE1LTU3I2TJ3js2-5xDs%3D" alt="Naoki Hirayama" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
|
||||
@@ -124,7 +125,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/acid_chicken">Acid Chicken</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
|
||||
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
|
||||
<td><a href="https://www.patreon.com/spinlock">Naoki Hirayama</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=Ksk_2l3gjPDbnzMUOCSW1E-hdPJsNs2tSR4_RAakRK8%3D" alt="dansup" width="100"></td>
|
||||
@@ -138,7 +138,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Mon, 21 Jan 2019 06:45:06 UTC
|
||||
**Last updated:** Tue, 29 Jan 2019 04:42:06 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
50
gulpfile.ts
50
gulpfile.ts
@@ -32,14 +32,6 @@ if (isDebug) {
|
||||
console.warn(chalk.yellow.bold(' built script will not be compressed.'));
|
||||
}
|
||||
|
||||
gulp.task('build', [
|
||||
'build:ts',
|
||||
'build:copy',
|
||||
'build:client',
|
||||
'locales',
|
||||
'doc'
|
||||
]);
|
||||
|
||||
gulp.task('build:ts', () => {
|
||||
const tsProject = ts.createProject('./tsconfig.json');
|
||||
|
||||
@@ -47,6 +39,7 @@ gulp.task('build:ts', () => {
|
||||
.src()
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', () => {})
|
||||
.pipe(sourcemaps.write('.', { includeContent: false, sourceRoot: '../built' }))
|
||||
.pipe(gulp.dest('./built/'));
|
||||
});
|
||||
@@ -55,7 +48,7 @@ gulp.task('build:copy:views', () =>
|
||||
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
|
||||
);
|
||||
|
||||
gulp.task('build:copy', ['build:copy:views'], () =>
|
||||
gulp.task('build:copy', gulp.parallel('build:copy:views', () =>
|
||||
gulp.src([
|
||||
'./build/Release/crypto_key.node',
|
||||
'./src/const.json',
|
||||
@@ -63,9 +56,7 @@ gulp.task('build:copy', ['build:copy:views'], () =>
|
||||
'./src/**/assets/**/*',
|
||||
'!./src/client/app/**/assets/**/*'
|
||||
]).pipe(gulp.dest('./built/'))
|
||||
);
|
||||
|
||||
gulp.task('test', ['mocha']);
|
||||
));
|
||||
|
||||
gulp.task('lint', () =>
|
||||
gulp.src('./src/**/*.ts')
|
||||
@@ -92,22 +83,15 @@ gulp.task('mocha', () =>
|
||||
} as any))
|
||||
);
|
||||
|
||||
gulp.task('test', gulp.task('mocha'));
|
||||
|
||||
gulp.task('clean', cb =>
|
||||
rimraf('./built', cb)
|
||||
);
|
||||
|
||||
gulp.task('cleanall', ['clean'], cb =>
|
||||
gulp.task('cleanall', gulp.parallel('clean', cb =>
|
||||
rimraf('./node_modules', cb)
|
||||
);
|
||||
|
||||
gulp.task('default', ['build']);
|
||||
|
||||
gulp.task('build:client', [
|
||||
'build:ts',
|
||||
'build:client:script',
|
||||
'build:client:styles',
|
||||
'copy:client'
|
||||
]);
|
||||
));
|
||||
|
||||
gulp.task('build:client:script', () => {
|
||||
const client = require('./built/client/meta.json');
|
||||
@@ -129,9 +113,7 @@ gulp.task('build:client:styles', () =>
|
||||
.pipe(gulp.dest('./built/client/assets/'))
|
||||
);
|
||||
|
||||
gulp.task('copy:client', [
|
||||
'build:client:script'
|
||||
], () =>
|
||||
gulp.task('copy:client', () =>
|
||||
gulp.src([
|
||||
'./assets/**/*',
|
||||
'./src/client/assets/**/*',
|
||||
@@ -156,3 +138,19 @@ gulp.task('doc', () =>
|
||||
.pipe((cssnano as any)())
|
||||
.pipe(gulp.dest('./built/docs/assets/'))
|
||||
);
|
||||
|
||||
gulp.task('build:client', gulp.parallel(
|
||||
'build:client:script',
|
||||
'build:client:styles',
|
||||
'copy:client'
|
||||
));
|
||||
|
||||
gulp.task('build', gulp.parallel(
|
||||
'build:ts',
|
||||
'build:copy',
|
||||
'build:client',
|
||||
'locales',
|
||||
'doc'
|
||||
));
|
||||
|
||||
gulp.task('default', gulp.task('build'));
|
||||
|
@@ -1274,6 +1274,8 @@ admin/views/users.vue:
|
||||
unsuspend: "凍結の解除"
|
||||
unsuspend-confirm: "凍結を解除しますか?"
|
||||
unsuspended: "凍結を解除しました"
|
||||
make-silence: "サイレンス"
|
||||
unmake-silence: "サイレンスの解除"
|
||||
verify: "公式アカウントにする"
|
||||
verify-confirm: "公式アカウントにしますか?"
|
||||
verified: "公式アカウントにしました"
|
||||
|
@@ -593,7 +593,7 @@ desktop/views/components/calendar.vue:
|
||||
title: "{year}년 {month}월"
|
||||
prev: "이전 달"
|
||||
next: "다음 달"
|
||||
go: "클릭 하 여 시간 회귀"
|
||||
go: "클릭하여 시간역행"
|
||||
desktop/views/components/choose-file-from-drive-window.vue:
|
||||
chosen-files: "{count} 파일 선택중"
|
||||
upload: "PC에서 드라이브에 파일을 업로드"
|
||||
|
@@ -103,14 +103,14 @@ common:
|
||||
f: "等待你的书写..."
|
||||
search: "搜索"
|
||||
delete: "删除"
|
||||
loading: "正在加载, 等着就好啦"
|
||||
loading: "正在加载中"
|
||||
ok: "没问题"
|
||||
update-available-title: "有可用更新"
|
||||
update-available: "新的 Misskey 版本现已发布({newer}。目前版本{current}). 刷新页面以应用更新。"
|
||||
my-token-regenerated: "您的 Token 已被重置, 您将自动登出。"
|
||||
i-like-sushi: "相比于布丁来说, 我更喜欢寿司。"
|
||||
show-reversi-board-labels: "在 Reversi 中显示行和列表签"
|
||||
use-avatar-reversi-stones: "用头像作为 Reversi 中的 “石头”"
|
||||
use-avatar-reversi-stones: "用头像作为黑白棋的棋子"
|
||||
verified-user: "认证用户"
|
||||
disable-animated-mfm: "在帖子中禁用动画文本"
|
||||
suggest-recent-hashtags: "在帖子表单上显示最近流行的主题标签"
|
||||
@@ -873,8 +873,8 @@ desktop/views/components/settings.2fa.vue:
|
||||
failed: "设置失败, 请确保您的密钥是正确的。"
|
||||
info: "从下次登录Misskey时,您的设备上显示的令牌以及密码也是必需的。"
|
||||
common/views/components/media-image.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
sensitive: "阅读注意"
|
||||
click-to-show: "点击查看"
|
||||
common/views/components/api-settings.vue:
|
||||
intro: "要访问API,请将此标记设置为请求参数的关键字“i”。"
|
||||
caution: "请勿将此令牌输入任何应用,也不要将此令牌告诉其他人,否则您的账户可能会受到损害。"
|
||||
@@ -1126,30 +1126,30 @@ admin/views/drive.vue:
|
||||
deleted: "已删除"
|
||||
mark-as-sensitive: "标记为“敏感”"
|
||||
unmark-as-sensitive: "取消标记为“敏感”"
|
||||
marked-as-sensitive: "标记为关注"
|
||||
unmarked-as-sensitive: "解除关注标记"
|
||||
marked-as-sensitive: "标记为“敏感”"
|
||||
unmarked-as-sensitive: "取消标记为“敏感”"
|
||||
admin/views/users.vue:
|
||||
operation: "操作"
|
||||
username-or-userid: "用户名或用户ID"
|
||||
user-not-found: "用户不存在"
|
||||
lookup: "订阅"
|
||||
reset-password: "密码重置"
|
||||
reset-password-confirm: "パスワードをリセットしますか?"
|
||||
reset-password-confirm: "是否重置密码?"
|
||||
password-updated: "密码为「{password}」"
|
||||
suspend: "被冻结"
|
||||
suspend-confirm: "凍結しますか?"
|
||||
suspend-confirm: "是否冻结?"
|
||||
suspended: "成功冻结用户"
|
||||
unsuspend: "已解除冻结"
|
||||
unsuspend-confirm: "凍結を解除しますか?"
|
||||
unsuspend-confirm: "是否解除冻结?"
|
||||
unsuspended: "已成功解除用户冻结"
|
||||
verify: "认证用户"
|
||||
verify-confirm: "公式アカウントにしますか?"
|
||||
verify-confirm: "是否官方账号?"
|
||||
verified: "此账户已被认证"
|
||||
unverify: "解除账户认证"
|
||||
unverify-confirm: "公式アカウントを解除しますか?"
|
||||
unverify-confirm: "是否解除官方账号认证?"
|
||||
unverified: "该帐户未经认证"
|
||||
update-remote-user: "リモートユーザー情報の更新"
|
||||
remote-user-updated: "リモートユーザー情報を更新しました"
|
||||
update-remote-user: "更新远程用户信息"
|
||||
remote-user-updated: "远程用户信息已更新"
|
||||
users:
|
||||
title: "用户"
|
||||
sort:
|
||||
|
16
package.json
16
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.79.1",
|
||||
"clientVersion": "2.0.13871",
|
||||
"version": "10.80.0",
|
||||
"clientVersion": "2.0.13920",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@@ -36,7 +36,7 @@
|
||||
"@types/double-ended-queue": "2.1.0",
|
||||
"@types/elasticsearch": "5.0.30",
|
||||
"@types/file-type": "10.6.0",
|
||||
"@types/gulp": "3.8.36",
|
||||
"@types/gulp": "4.0.5",
|
||||
"@types/gulp-mocha": "0.0.32",
|
||||
"@types/gulp-rename": "0.0.33",
|
||||
"@types/gulp-replace": "0.0.31",
|
||||
@@ -61,7 +61,7 @@
|
||||
"@types/minio": "7.0.1",
|
||||
"@types/mkdirp": "0.5.2",
|
||||
"@types/mocha": "5.2.5",
|
||||
"@types/mongodb": "3.1.18",
|
||||
"@types/mongodb": "3.1.19",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "10.12.18",
|
||||
"@types/nodemailer": "4.6.5",
|
||||
@@ -84,6 +84,7 @@
|
||||
"@types/tinycolor2": "1.4.1",
|
||||
"@types/tmp": "0.0.33",
|
||||
"@types/uuid": "3.4.4",
|
||||
"@types/web-push": "3.3.0",
|
||||
"@types/webpack": "4.4.24",
|
||||
"@types/webpack-stream": "3.2.10",
|
||||
"@types/websocket": "0.0.40",
|
||||
@@ -117,10 +118,9 @@
|
||||
"eslint-plugin-vue": "5.1.0",
|
||||
"eventemitter3": "3.1.0",
|
||||
"feed": "2.0.2",
|
||||
"file-loader": "2.0.0",
|
||||
"file-type": "10.7.0",
|
||||
"fuckadblock": "3.2.1",
|
||||
"gulp": "3.9.1",
|
||||
"gulp": "4.0.0",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-imagemin": "4.1.0",
|
||||
"gulp-mocha": "6.0.0",
|
||||
@@ -236,7 +236,7 @@
|
||||
"vue-router": "3.0.2",
|
||||
"vue-sequential-entrance": "1.1.3",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.2.7",
|
||||
"vue-svg-inline-loader": "1.2.10",
|
||||
"vue-template-compiler": "2.5.17",
|
||||
"vuedraggable": "2.17.0",
|
||||
"vuewordcloud": "18.7.11",
|
||||
@@ -247,7 +247,7 @@
|
||||
"webpack": "4.28.4",
|
||||
"webpack-cli": "3.2.1",
|
||||
"websocket": "1.0.28",
|
||||
"ws": "6.1.2",
|
||||
"ws": "6.1.3",
|
||||
"xev": "2.0.1"
|
||||
}
|
||||
}
|
||||
|
5
src/@types/is-root.d.ts
vendored
Normal file
5
src/@types/is-root.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module 'is-root';
|
||||
|
||||
declare namespace isRoot {
|
||||
export function isRoot(): boolean;
|
||||
}
|
3
src/@types/package.json.d.ts
vendored
Normal file
3
src/@types/package.json.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare module '*/package.json' {
|
||||
const version: string;
|
||||
}
|
@@ -12,6 +12,7 @@
|
||||
<span class="is-admin" v-if="user.isAdmin">admin</span>
|
||||
<span class="is-moderator" v-if="user.isModerator">moderator</span>
|
||||
<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span>
|
||||
<span class="is-silenced" v-if="user.isSilenced" :title="$t('@.silenced-user')"><fa :icon="faMicrophoneSlash"/></span>
|
||||
<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span>
|
||||
</header>
|
||||
<div>
|
||||
@@ -27,6 +28,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import { faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
@@ -34,7 +36,7 @@ export default Vue.extend({
|
||||
props: ['user'],
|
||||
data() {
|
||||
return {
|
||||
faSnowflake
|
||||
faSnowflake, faMicrophoneSlash
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -76,6 +78,7 @@ export default Vue.extend({
|
||||
color var(--noteHeaderAdminFg)
|
||||
|
||||
> .is-verified
|
||||
> .is-silenced
|
||||
> .is-suspended
|
||||
margin 0 0 0 .5em
|
||||
color #4dabf7
|
||||
|
@@ -16,6 +16,10 @@
|
||||
<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button>
|
||||
<ui-button @click="unverifyUser" :disabled="unverifying">{{ $t('unverify') }}</ui-button>
|
||||
</ui-horizon-group>
|
||||
<ui-horizon-group>
|
||||
<ui-button @click="silenceUser"><fa :icon="faMicrophoneSlash"/> {{ $t('make-silence') }}</ui-button>
|
||||
<ui-button @click="unsilenceUser">{{ $t('unmake-silence') }}</ui-button>
|
||||
</ui-horizon-group>
|
||||
<ui-horizon-group>
|
||||
<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button>
|
||||
<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button>
|
||||
@@ -66,7 +70,7 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import parseAcct from "../../../../misc/acct/parse";
|
||||
import { faCertificate, faUsers, faTerminal, faSearch, faKey, faSync } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCertificate, faUsers, faTerminal, faSearch, faKey, faSync, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
|
||||
import XUser from './users.user.vue';
|
||||
|
||||
@@ -90,7 +94,7 @@ export default Vue.extend({
|
||||
offset: 0,
|
||||
users: [],
|
||||
existMore: false,
|
||||
faTerminal, faCertificate, faUsers, faSnowflake, faSearch, faKey, faSync
|
||||
faTerminal, faCertificate, faUsers, faSnowflake, faSearch, faKey, faSync, faMicrophoneSlash
|
||||
};
|
||||
},
|
||||
|
||||
@@ -216,6 +220,44 @@ export default Vue.extend({
|
||||
this.refreshUser();
|
||||
},
|
||||
|
||||
async silenceUser() {
|
||||
const process = async () => {
|
||||
await this.$root.api('admin/silence-user', { userId: this.user._id });
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
splash: true
|
||||
});
|
||||
};
|
||||
|
||||
await process().catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.toString()
|
||||
});
|
||||
});
|
||||
|
||||
this.refreshUser();
|
||||
},
|
||||
|
||||
async unsilenceUser() {
|
||||
const process = async () => {
|
||||
await this.$root.api('admin/unsilence-user', { userId: this.user._id });
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
splash: true
|
||||
});
|
||||
};
|
||||
|
||||
await process().catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.toString()
|
||||
});
|
||||
});
|
||||
|
||||
this.refreshUser();
|
||||
},
|
||||
|
||||
async suspendUser() {
|
||||
if (!await this.getConfirmed(this.$t('suspend-confirm'))) return;
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// スクリプトサイズがデカい
|
||||
//const crypto = require('crypto');
|
||||
//import * as crypto from 'crypto';
|
||||
|
||||
export default (data: ArrayBuffer) => {
|
||||
//const buf = new Buffer(data);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import parse from '../../../../mfm/parse';
|
||||
import { parse } from '../../../../mfm/parse';
|
||||
import { sum, unique } from '../../../../prelude/array';
|
||||
import shouldMuteNote from './should-mute-note';
|
||||
import MkNoteMenu from '../views/components/note-menu.vue';
|
||||
|
@@ -34,7 +34,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
import { parse } from '../../../../../mfm/parse';
|
||||
import { unique } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import Vue, { VNode } from 'vue';
|
||||
import { length } from 'stringz';
|
||||
import { MfmForest } from '../../../../../mfm/parser';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
import { MfmForest } from '../../../../../mfm/types';
|
||||
import { parse, parsePlain } from '../../../../../mfm/parse';
|
||||
import MkUrl from './url.vue';
|
||||
import MkMention from './mention.vue';
|
||||
import { concat, sum } from '../../../../../prelude/array';
|
||||
@@ -46,7 +46,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
render(createElement) {
|
||||
if (this.text == null || this.text == '') return;
|
||||
|
||||
const ast = parse(this.text, this.plainText);
|
||||
const ast = (this.plainText ? parsePlain : parse)(this.text);
|
||||
|
||||
let bigCount = 0;
|
||||
let motionCount = 0;
|
||||
@@ -98,7 +98,11 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
}
|
||||
|
||||
case 'small': {
|
||||
return [createElement('small', genEl(token.children))];
|
||||
return [createElement('small', {
|
||||
attrs: {
|
||||
style: 'opacity: 0.7;'
|
||||
},
|
||||
}, genEl(token.children))];
|
||||
}
|
||||
|
||||
case 'center': {
|
||||
@@ -126,7 +130,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
|
||||
case 'spin': {
|
||||
motionCount++;
|
||||
const isLong = sumTextsLength(token.children) > 5 || countNodesF(token.children) > 3;
|
||||
const isLong = sumTextsLength(token.children) > 10 || countNodesF(token.children) > 5;
|
||||
const isMany = motionCount > 5;
|
||||
const direction =
|
||||
token.node.props.attr == 'left' ? 'reverse' :
|
||||
@@ -144,7 +148,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
|
||||
case 'jump': {
|
||||
motionCount++;
|
||||
const isLong = sumTextsLength(token.children) > 5 || countNodesF(token.children) > 3;
|
||||
const isLong = sumTextsLength(token.children) > 30 || countNodesF(token.children) > 5;
|
||||
const isMany = motionCount > 5;
|
||||
return (createElement as any)('span', {
|
||||
attrs: {
|
||||
|
@@ -68,7 +68,7 @@ import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import * as XDraggable from 'vuedraggable';
|
||||
import getFace from '../../../common/scripts/get-face';
|
||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
import { parse } from '../../../../../mfm/parse';
|
||||
import { host } from '../../../config';
|
||||
import { erase, unique } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
|
@@ -60,7 +60,7 @@ import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import * as XDraggable from 'vuedraggable';
|
||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
||||
import getFace from '../../../common/scripts/get-face';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
import { parse } from '../../../../../mfm/parse';
|
||||
import { host } from '../../../config';
|
||||
import { erase, unique } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
|
@@ -41,7 +41,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parse from '../../../../mfm/parse';
|
||||
import { parse } from '../../../../mfm/parse';
|
||||
import * as JSON5 from 'json5';
|
||||
|
||||
export default Vue.extend({
|
||||
|
@@ -7,7 +7,7 @@ import { URL } from 'url';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { Source, Mixin } from './types';
|
||||
import isUrl = require('is-url');
|
||||
const pkg = require('../../package.json');
|
||||
import * as pkg from '../../package.json';
|
||||
|
||||
/**
|
||||
* Path of configuration directory
|
||||
|
@@ -11,7 +11,7 @@ import * as cluster from 'cluster';
|
||||
import * as debug from 'debug';
|
||||
import chalk from 'chalk';
|
||||
import * as portscanner from 'portscanner';
|
||||
import isRoot = require('is-root');
|
||||
import * as isRoot from 'is-root';
|
||||
import Xev from 'xev';
|
||||
import * as program from 'commander';
|
||||
import * as sysUtils from 'systeminformation';
|
||||
@@ -23,6 +23,7 @@ import notesStats from './daemons/notes-stats';
|
||||
import loadConfig from './config/load';
|
||||
import { Config } from './config/types';
|
||||
import { lessThan } from './prelude/array';
|
||||
import * as pkg from '../package.json';
|
||||
|
||||
const clusterLog = debug('misskey:cluster');
|
||||
const ev = new Xev();
|
||||
@@ -31,8 +32,6 @@ if (process.env.NODE_ENV != 'production' && process.env.DEBUG == null) {
|
||||
debug.enable('misskey');
|
||||
}
|
||||
|
||||
const pkg = require('../package.json');
|
||||
|
||||
//#region Command line argument definitions
|
||||
program
|
||||
.version(pkg.version)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const parse5 = require('parse5');
|
||||
import { URL } from 'url';
|
||||
|
||||
export default function(html: string): string {
|
||||
export function fromHtml(html: string): string {
|
||||
if (html == null) return null;
|
||||
|
||||
const dom = parse5.parseFragment(html);
|
File diff suppressed because one or more lines are too long
31
src/mfm/normalize.ts
Normal file
31
src/mfm/normalize.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as A from '../prelude/array';
|
||||
import * as S from '../prelude/string';
|
||||
import { MfmForest, MfmTree } from './types';
|
||||
import { createTree, createLeaf } from '../prelude/tree';
|
||||
|
||||
function isEmptyTextTree(t: MfmTree): boolean {
|
||||
return t.node.type == 'text' && t.node.props.text === '';
|
||||
}
|
||||
|
||||
function concatTextTrees(ts: MfmForest): MfmTree {
|
||||
return createLeaf({ type: 'text', props: { text: S.concat(ts.map(x => x.node.props.text)) } });
|
||||
}
|
||||
|
||||
function concatIfTextTrees(ts: MfmForest): MfmForest {
|
||||
return ts[0].node.type === 'text' ? [concatTextTrees(ts)] : ts;
|
||||
}
|
||||
|
||||
function concatConsecutiveTextTrees(ts: MfmForest): MfmForest {
|
||||
const us = A.concat(A.groupOn(t => t.node.type, ts).map(concatIfTextTrees));
|
||||
return us.map(t => createTree(t.node, concatConsecutiveTextTrees(t.children)));
|
||||
}
|
||||
|
||||
function removeEmptyTextNodes(ts: MfmForest): MfmForest {
|
||||
return ts
|
||||
.filter(t => !isEmptyTextTree(t))
|
||||
.map(t => createTree(t.node, removeEmptyTextNodes(t.children)));
|
||||
}
|
||||
|
||||
export function normalize(ts: MfmForest): MfmForest {
|
||||
return removeEmptyTextNodes(concatConsecutiveTextTrees(ts));
|
||||
}
|
@@ -1,36 +1,19 @@
|
||||
import parser, { plainParser, MfmForest, MfmTree } from './parser';
|
||||
import * as A from '../prelude/array';
|
||||
import * as S from '../prelude/string';
|
||||
import { createTree, createLeaf } from '../prelude/tree';
|
||||
import { mfmLanguage } from './language';
|
||||
import { MfmForest } from './types';
|
||||
import { normalize } from './normalize';
|
||||
|
||||
function concatTextTrees(ts: MfmForest): MfmTree {
|
||||
return createLeaf({ type: 'text', props: { text: S.concat(ts.map(x => x.node.props.text)) } });
|
||||
}
|
||||
|
||||
function concatIfTextTrees(ts: MfmForest): MfmForest {
|
||||
return ts[0].node.type === 'text' ? [concatTextTrees(ts)] : ts;
|
||||
}
|
||||
|
||||
function concatConsecutiveTextTrees(ts: MfmForest): MfmForest {
|
||||
const us = A.concat(A.groupOn(t => t.node.type, ts).map(concatIfTextTrees));
|
||||
return us.map(t => createTree(t.node, concatConsecutiveTextTrees(t.children)));
|
||||
}
|
||||
|
||||
function isEmptyTextTree(t: MfmTree): boolean {
|
||||
return t.node.type == 'text' && t.node.props.text === '';
|
||||
}
|
||||
|
||||
function removeEmptyTextNodes(ts: MfmForest): MfmForest {
|
||||
return ts
|
||||
.filter(t => !isEmptyTextTree(t))
|
||||
.map(t => createTree(t.node, removeEmptyTextNodes(t.children)));
|
||||
}
|
||||
|
||||
export default (source: string, plainText = false): MfmForest => {
|
||||
export function parse(source: string): MfmForest {
|
||||
if (source == null || source == '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const raw = plainText ? plainParser.root.tryParse(source) : parser.root.tryParse(source) as MfmForest;
|
||||
return removeEmptyTextNodes(concatConsecutiveTextTrees(raw));
|
||||
};
|
||||
return normalize(mfmLanguage.root.tryParse(source));
|
||||
}
|
||||
|
||||
export function parsePlain(source: string): MfmForest {
|
||||
if (source == null || source == '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalize(mfmLanguage.plain.tryParse(source));
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@ const { JSDOM } = jsdom;
|
||||
import config from '../config';
|
||||
import { INote } from '../models/note';
|
||||
import { intersperse } from '../prelude/array';
|
||||
import { MfmForest, MfmTree } from './parser';
|
||||
import { MfmForest, MfmTree } from './types';
|
||||
|
||||
export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) => {
|
||||
export function toHtml(tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) {
|
||||
if (tokens == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -137,6 +137,7 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU
|
||||
default:
|
||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
||||
a.href = remoteUserInfo ? remoteUserInfo.uri : `${config.url}/${acct}`;
|
||||
a.className = 'mention';
|
||||
break;
|
||||
}
|
||||
a.textContent = acct;
|
||||
@@ -157,7 +158,7 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU
|
||||
|
||||
text(token) {
|
||||
const el = doc.createElement('span');
|
||||
const nodes = (token.node.props.text as string).split('\n').map(x => doc.createTextNode(x));
|
||||
const nodes = (token.node.props.text as string).split(/\r\n|\r|\n/).map(x => doc.createTextNode(x));
|
||||
|
||||
for (const x of intersperse('br', nodes)) {
|
||||
el.appendChild(x === 'br' ? doc.createElement('br') : x);
|
||||
@@ -184,4 +185,4 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU
|
||||
appendChildren(tokens, doc.body);
|
||||
|
||||
return `<p>${doc.body.innerHTML}</p>`;
|
||||
};
|
||||
}
|
37
src/mfm/types.ts
Normal file
37
src/mfm/types.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Tree } from '../prelude/tree';
|
||||
import * as T from '../prelude/tree';
|
||||
|
||||
type Node<T, P> = { type: T, props: P };
|
||||
|
||||
export type MentionNode = Node<'mention', {
|
||||
canonical: string,
|
||||
username: string,
|
||||
host: string,
|
||||
acct: string
|
||||
}>;
|
||||
|
||||
export type HashtagNode = Node<'hashtag', {
|
||||
hashtag: string
|
||||
}>;
|
||||
|
||||
export type EmojiNode = Node<'emoji', {
|
||||
name: string
|
||||
}>;
|
||||
|
||||
export type MfmNode =
|
||||
MentionNode |
|
||||
HashtagNode |
|
||||
EmojiNode |
|
||||
Node<string, any>;
|
||||
|
||||
export type MfmTree = Tree<MfmNode>;
|
||||
|
||||
export type MfmForest = MfmTree[];
|
||||
|
||||
export function createLeaf(type: string, props: any): MfmTree {
|
||||
return T.createLeaf({ type, props });
|
||||
}
|
||||
|
||||
export function createTree(type: string, children: MfmForest, props: any): MfmTree {
|
||||
return T.createTree({ type, props }, children);
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { EmojiNode, MfmForest } from '../mfm/parser';
|
||||
import { EmojiNode, MfmForest } from '../mfm/types';
|
||||
import { preorderF } from '../prelude/tree';
|
||||
import { unique } from '../prelude/array';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HashtagNode, MfmForest } from '../mfm/parser';
|
||||
import { HashtagNode, MfmForest } from '../mfm/types';
|
||||
import { preorderF } from '../prelude/tree';
|
||||
import { unique } from '../prelude/array';
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// test is located in test/extract-mentions
|
||||
|
||||
import { MentionNode, MfmForest } from '../mfm/parser';
|
||||
import { MentionNode, MfmForest } from '../mfm/types';
|
||||
import { preorderF } from '../prelude/tree';
|
||||
|
||||
export default function(mfmForest: MfmForest): MentionNode['props'][] {
|
||||
|
@@ -140,6 +140,12 @@ export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => {
|
||||
hide = true;
|
||||
} else if (meId.equals(packedNote.userId)) {
|
||||
hide = false;
|
||||
} else if (packedNote.reply && meId.equals(packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId.equals(id))) {
|
||||
// 自分へのメンション
|
||||
hide = false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
const following = await Following.findOne({
|
||||
|
@@ -54,6 +54,11 @@ type IUserBase = {
|
||||
*/
|
||||
isSuspended: boolean;
|
||||
|
||||
/**
|
||||
* サイレンスされているか否か
|
||||
*/
|
||||
isSilenced: boolean;
|
||||
|
||||
/**
|
||||
* 鍵アカウントか否か
|
||||
*/
|
||||
@@ -306,6 +311,7 @@ export const pack = (
|
||||
delete _user.password;
|
||||
delete _user.token;
|
||||
delete _user.twoFactorTempSecret;
|
||||
delete _user.two_factor_temp_secret; // 後方互換性のため
|
||||
delete _user.twoFactorSecret;
|
||||
if (_user.twitter) {
|
||||
delete _user.twitter.accessToken;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const push = require('web-push');
|
||||
import * as push from 'web-push';
|
||||
import * as mongo from 'mongodb';
|
||||
import Subscription from './models/sw-subscription';
|
||||
import config from './config';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { INote } from '../../../models/note';
|
||||
import toHtml from '../../../mfm/html';
|
||||
import parse from '../../../mfm/parse';
|
||||
import { toHtml } from '../../../mfm/toHtml';
|
||||
import { parse } from '../../../mfm/parse';
|
||||
|
||||
export default function(note: INote) {
|
||||
let html = toHtml(parse(note.text), note.mentionedRemoteUsers);
|
||||
|
@@ -9,7 +9,7 @@ import { INote as INoteActivityStreamsObject, IObject } from '../type';
|
||||
import { resolvePerson, updatePerson } from './person';
|
||||
import { resolveImage } from './image';
|
||||
import { IRemoteUser, IUser } from '../../../models/user';
|
||||
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||
import { fromHtml } from '../../../mfm/fromHtml';
|
||||
import Emoji, { IEmoji } from '../../../models/emoji';
|
||||
import { ITag } from './tag';
|
||||
import { toUnicode } from 'punycode';
|
||||
@@ -110,7 +110,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
||||
const cw = note.summary === '' ? null : note.summary;
|
||||
|
||||
// テキストのパース
|
||||
const text = note._misskey_content ? note._misskey_content : htmlToMFM(note.content);
|
||||
const text = note._misskey_content ? note._misskey_content : fromHtml(note.content);
|
||||
|
||||
// vote
|
||||
if (reply && reply.poll && text != null) {
|
||||
|
@@ -9,7 +9,7 @@ import { resolveImage } from './image';
|
||||
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
|
||||
import { IDriveFile } from '../../../models/drive-file';
|
||||
import Meta from '../../../models/meta';
|
||||
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||
import { fromHtml } from '../../../mfm/fromHtml';
|
||||
import usersChart from '../../../chart/users';
|
||||
import { URL } from 'url';
|
||||
import { resolveNote, extractEmojis } from './note';
|
||||
@@ -150,7 +150,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
||||
bannerId: null,
|
||||
createdAt: Date.parse(person.published) || null,
|
||||
lastFetchedAt: new Date(),
|
||||
description: htmlToMFM(person.summary),
|
||||
description: fromHtml(person.summary),
|
||||
followersCount,
|
||||
followingCount,
|
||||
notesCount,
|
||||
@@ -340,7 +340,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
||||
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||
featured: person.featured,
|
||||
emojis: emojiNames,
|
||||
description: htmlToMFM(person.summary),
|
||||
description: fromHtml(person.summary),
|
||||
followersCount,
|
||||
followingCount,
|
||||
notesCount,
|
||||
@@ -463,7 +463,7 @@ export function analyzeAttachments(attachments: ITag[]) {
|
||||
else
|
||||
fields.push({
|
||||
name: attachment.name,
|
||||
value: htmlToMFM(attachment.value)
|
||||
value: fromHtml(attachment.value)
|
||||
});
|
||||
|
||||
return { fields, services };
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import config from '../../../config';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
export default (x: any) => {
|
||||
export const renderActivity = (x: any) => {
|
||||
if (x == null) return null;
|
||||
|
||||
if (x !== null && typeof x === 'object' && x.id == null) {
|
||||
|
@@ -2,8 +2,8 @@ import renderImage from './image';
|
||||
import renderKey from './key';
|
||||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/user';
|
||||
import toHtml from '../../../mfm/html';
|
||||
import parse from '../../../mfm/parse';
|
||||
import { toHtml } from '../../../mfm/toHtml';
|
||||
import { parse } from '../../../mfm/parse';
|
||||
import DriveFile from '../../../models/drive-file';
|
||||
import { getEmojis } from './note';
|
||||
import renderEmoji from './emoji';
|
||||
|
@@ -2,7 +2,7 @@ import { request } from 'https';
|
||||
const { sign } = require('http-signature');
|
||||
import { URL } from 'url';
|
||||
import * as debug from 'debug';
|
||||
const crypto = require('crypto');
|
||||
import * as crypto from 'crypto';
|
||||
const { lookup } = require('lookup-dns-cache');
|
||||
const promiseAny = require('promise-any');
|
||||
|
||||
|
@@ -4,7 +4,7 @@ const json = require('koa-json-body');
|
||||
const httpSignature = require('http-signature');
|
||||
|
||||
import { createHttpJob } from '../queue';
|
||||
import pack from '../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../remote/activitypub/renderer';
|
||||
import Note from '../models/note';
|
||||
import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
|
||||
import Emoji from '../models/emoji';
|
||||
@@ -83,7 +83,7 @@ router.get('/notes/:note', async (ctx, next) => {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = pack(await renderNote(note, false));
|
||||
ctx.body = renderActivity(await renderNote(note, false));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
});
|
||||
@@ -106,7 +106,7 @@ router.get('/notes/:note/activity', async ctx => {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = pack(await packActivity(note));
|
||||
ctx.body = renderActivity(await packActivity(note));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
});
|
||||
@@ -137,7 +137,7 @@ router.get('/questions/:question', async (ctx, next) => {
|
||||
_id: poll.userId
|
||||
});
|
||||
|
||||
ctx.body = pack(await renderQuestion(user as ILocalUser, poll));
|
||||
ctx.body = renderActivity(await renderQuestion(user as ILocalUser, poll));
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
@@ -173,7 +173,7 @@ router.get('/users/:user/publickey', async ctx => {
|
||||
}
|
||||
|
||||
if (isLocalUser(user)) {
|
||||
ctx.body = pack(renderKey(user));
|
||||
ctx.body = renderActivity(renderKey(user));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
@@ -188,7 +188,7 @@ async function userInfo(ctx: Router.IRouterContext, user: IUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = pack(await renderPerson(user as ILocalUser));
|
||||
ctx.body = renderActivity(await renderPerson(user as ILocalUser));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
}
|
||||
@@ -235,7 +235,7 @@ router.get('/emojis/:emoji', async ctx => {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = pack(await renderEmoji(emoji));
|
||||
ctx.body = renderActivity(await renderEmoji(emoji));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
@@ -2,7 +2,7 @@ import { ObjectID } from 'mongodb';
|
||||
import * as Router from 'koa-router';
|
||||
import config from '../../config';
|
||||
import User from '../../models/user';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
import { setResponseType } from '../activitypub';
|
||||
import Note from '../../models/note';
|
||||
@@ -38,7 +38,7 @@ export default async (ctx: Router.IRouterContext) => {
|
||||
renderedNotes.length, null, null, renderedNotes
|
||||
);
|
||||
|
||||
ctx.body = pack(rendered);
|
||||
ctx.body = renderActivity(rendered);
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
setResponseType(ctx);
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@ import config from '../../config';
|
||||
import $ from 'cafy'; import ID, { transform } from '../../misc/cafy-id';
|
||||
import User from '../../models/user';
|
||||
import Following from '../../models/following';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
|
||||
import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
|
||||
@@ -77,12 +77,12 @@ export default async (ctx: Router.IRouterContext) => {
|
||||
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
|
||||
);
|
||||
|
||||
ctx.body = pack(rendered);
|
||||
ctx.body = renderActivity(rendered);
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
// index page
|
||||
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null);
|
||||
ctx.body = pack(rendered);
|
||||
ctx.body = renderActivity(rendered);
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
setResponseType(ctx);
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import $ from 'cafy';
|
||||
import ID, { transform } from '../../misc/cafy-id';
|
||||
import User from '../../models/user';
|
||||
import Following from '../../models/following';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
|
||||
import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
|
||||
@@ -78,12 +78,12 @@ export default async (ctx: Router.IRouterContext) => {
|
||||
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
|
||||
);
|
||||
|
||||
ctx.body = pack(rendered);
|
||||
ctx.body = renderActivity(rendered);
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
// index page
|
||||
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null);
|
||||
ctx.body = pack(rendered);
|
||||
ctx.body = renderActivity(rendered);
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
setResponseType(ctx);
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import config from '../../config';
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../misc/cafy-id';
|
||||
import User from '../../models/user';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
|
||||
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
|
||||
import { setResponseType } from '../activitypub';
|
||||
@@ -94,7 +94,7 @@ export default async (ctx: Router.IRouterContext) => {
|
||||
notes.length > 0 ? `${partOf}?page=true&until_id=${notes[notes.length - 1]._id}` : null
|
||||
);
|
||||
|
||||
ctx.body = pack(rendered);
|
||||
ctx.body = renderActivity(rendered);
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
setResponseType(ctx);
|
||||
} else {
|
||||
@@ -103,7 +103,7 @@ export default async (ctx: Router.IRouterContext) => {
|
||||
`${partOf}?page=true`,
|
||||
`${partOf}?page=true&since_id=000000000000000000000000`
|
||||
);
|
||||
ctx.body = pack(rendered);
|
||||
ctx.body = renderActivity(rendered);
|
||||
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
setResponseType(ctx);
|
||||
}
|
||||
|
49
src/server/api/endpoints/admin/silence-user.ts
Normal file
49
src/server/api/endpoints/admin/silence-user.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import User from '../../../../models/user';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーをサイレンスにします。',
|
||||
'en-US': 'Make silence a user.'
|
||||
},
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーID',
|
||||
'en-US': 'The user ID which you want to make silence'
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, (ps) => new Promise(async (res, rej) => {
|
||||
const user = await User.findOne({
|
||||
_id: ps.userId
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
return rej('user not found');
|
||||
}
|
||||
|
||||
if (user.isAdmin) {
|
||||
return rej('cannot silence admin');
|
||||
}
|
||||
|
||||
await User.findOneAndUpdate({
|
||||
_id: user._id
|
||||
}, {
|
||||
$set: {
|
||||
isSilenced: true
|
||||
}
|
||||
});
|
||||
|
||||
res();
|
||||
}));
|
45
src/server/api/endpoints/admin/unsilence-user.ts
Normal file
45
src/server/api/endpoints/admin/unsilence-user.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import $ from 'cafy';
|
||||
import ID, { transform } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import User from '../../../../models/user';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーのサイレンスを解除します。',
|
||||
'en-US': 'Unsilence a user.'
|
||||
},
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
transform: transform,
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーID',
|
||||
'en-US': 'The user ID which you want to unsilence'
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, (ps) => new Promise(async (res, rej) => {
|
||||
const user = await User.findOne({
|
||||
_id: ps.userId
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
return rej('user not found');
|
||||
}
|
||||
|
||||
await User.findOneAndUpdate({
|
||||
_id: user._id
|
||||
}, {
|
||||
$set: {
|
||||
isSilenced: false
|
||||
}
|
||||
});
|
||||
|
||||
res();
|
||||
}));
|
@@ -1,5 +1,5 @@
|
||||
import rndstr from 'rndstr';
|
||||
const crypto = require('crypto');
|
||||
import * as crypto from 'crypto';
|
||||
import $ from 'cafy';
|
||||
import App from '../../../../models/app';
|
||||
import AuthSess from '../../../../models/auth-session';
|
||||
|
@@ -6,7 +6,7 @@ import acceptAllFollowRequests from '../../../../services/following/requests/acc
|
||||
import { publishToFollowers } from '../../../../services/i/update';
|
||||
import define from '../../define';
|
||||
import getDriveFileUrl from '../../../../misc/get-drive-file-url';
|
||||
import parse from '../../../../mfm/parse';
|
||||
import { parse, parsePlain } from '../../../../mfm/parse';
|
||||
import extractEmojis from '../../../../misc/extract-emojis';
|
||||
const langmap = require('langmap');
|
||||
|
||||
@@ -206,7 +206,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
|
||||
let emojis = [] as string[];
|
||||
|
||||
if (updates.name != null) {
|
||||
const tokens = parse(updates.name, true);
|
||||
const tokens = parsePlain(updates.name);
|
||||
emojis = emojis.concat(extractEmojis(tokens));
|
||||
}
|
||||
|
||||
|
@@ -4,8 +4,8 @@ import config from '../../../config';
|
||||
import Emoji from '../../../models/emoji';
|
||||
import define from '../define';
|
||||
import fetchMeta from '../../../misc/fetch-meta';
|
||||
import * as pkg from '../../../../package.json';
|
||||
|
||||
const pkg = require('../../../../package.json');
|
||||
const client = require('../../../../built/client/meta.json');
|
||||
|
||||
export const meta = {
|
||||
|
@@ -53,14 +53,23 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||
const visibleQuery = [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}, {
|
||||
// myself (for specified/private)
|
||||
// myself (for followers/specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [ user._id ] }
|
||||
}, {
|
||||
visibility: 'followers',
|
||||
userId: { $in: followings.map(f => f.id) }
|
||||
$or: [{
|
||||
// フォロワーの投稿
|
||||
userId: { $in: followings.map(f => f.id) },
|
||||
}, {
|
||||
// 自分の投稿へのリプライ
|
||||
'_reply.userId': user._id,
|
||||
}, {
|
||||
// 自分へのメンションが含まれている
|
||||
mentions: { $in: [ user._id ] }
|
||||
}]
|
||||
}];
|
||||
|
||||
const query = {
|
||||
|
@@ -51,14 +51,23 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||
}] : [{
|
||||
visibility: { $in: [ 'public', 'home' ] }
|
||||
}, {
|
||||
// myself (for specified/private)
|
||||
// myself (for followers/specified/private)
|
||||
userId: user._id
|
||||
}, {
|
||||
// to me (for specified)
|
||||
visibleUserIds: { $in: [ user._id ] }
|
||||
}, {
|
||||
visibility: 'followers',
|
||||
userId: { $in: followings.map(f => f.id) }
|
||||
$or: [{
|
||||
// フォロワーの投稿
|
||||
userId: { $in: followings.map(f => f.id) },
|
||||
}, {
|
||||
// 自分の投稿へのリプライ
|
||||
'_reply.userId': user._id,
|
||||
}, {
|
||||
// 自分へのメンションが含まれている
|
||||
mentions: { $in: [ user._id ] }
|
||||
}]
|
||||
}];
|
||||
|
||||
const q = {
|
||||
|
@@ -2,7 +2,7 @@ import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id
|
||||
import UserList from '../../../../../models/user-list';
|
||||
import User, { pack as packUser, isRemoteUser, fetchProxyAccount } from '../../../../../models/user';
|
||||
import { publishUserListStream } from '../../../../../stream';
|
||||
import ap from '../../../../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../../../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../../../../remote/activitypub/renderer/follow';
|
||||
import { deliver } from '../../../../../queue';
|
||||
import define from '../../../define';
|
||||
@@ -72,7 +72,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
|
||||
if (isRemoteUser(user)) {
|
||||
const proxy = await fetchProxyAccount();
|
||||
const content = ap(renderFollow(proxy, user));
|
||||
const content = renderActivity(renderFollow(proxy, user));
|
||||
deliver(proxy, content, user.inbox);
|
||||
}
|
||||
}));
|
||||
|
@@ -6,6 +6,7 @@ import { ObjectID } from 'bson';
|
||||
import Emoji from '../../../models/emoji';
|
||||
import { toMastodonEmojis } from './emoji';
|
||||
import fetchMeta from '../../../misc/fetch-meta';
|
||||
import * as pkg from '../../../../package.json';
|
||||
|
||||
// Init router
|
||||
const router = new Router();
|
||||
@@ -48,7 +49,7 @@ router.get('/v1/instance', async ctx => { // TODO: This is a temporary implement
|
||||
title: meta.name || 'Misskey',
|
||||
description: meta.description || '',
|
||||
email: meta.maintainer.email,
|
||||
version: `0.0.0 (compatible; Misskey)`, // TODO: commit hash
|
||||
version: `0.0.0 (compatible; Misskey ${pkg.version})`, // TODO: commit hash
|
||||
thumbnail: meta.bannerUrl,
|
||||
/*
|
||||
urls: {
|
||||
|
@@ -20,7 +20,7 @@ import Note, { pack as packNote } from '../../models/note';
|
||||
import getNoteSummary from '../../misc/get-note-summary';
|
||||
import fetchMeta from '../../misc/fetch-meta';
|
||||
import Emoji from '../../models/emoji';
|
||||
const pkg = require('../../../package.json');
|
||||
import * as pkg from '../../../package.json';
|
||||
|
||||
const client = `${__dirname}/../../client/`;
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import User, { isLocalUser, isRemoteUser, pack as packUser, IUser } from '../../
|
||||
import Following from '../../models/following';
|
||||
import FollowRequest from '../../models/follow-request';
|
||||
import { publishMainStream } from '../../stream';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../remote/activitypub/renderer/follow';
|
||||
import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||
import renderBlock from '../../remote/activitypub/renderer/block';
|
||||
@@ -27,7 +27,7 @@ export default async function(blocker: IUser, blockee: IUser) {
|
||||
});
|
||||
|
||||
if (isLocalUser(blocker) && isRemoteUser(blockee)) {
|
||||
const content = pack(renderBlock(blocker, blockee));
|
||||
const content = renderActivity(renderBlock(blocker, blockee));
|
||||
deliver(blocker, content, blockee.inbox);
|
||||
}
|
||||
}
|
||||
@@ -67,13 +67,13 @@ async function cancelRequest(follower: IUser, followee: IUser) {
|
||||
|
||||
// リモートにフォローリクエストをしていたらUndoFollow送信
|
||||
if (isLocalUser(follower) && isRemoteUser(followee)) {
|
||||
const content = pack(renderUndo(renderFollow(follower, followee), follower));
|
||||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||
deliver(follower, content, followee.inbox);
|
||||
}
|
||||
|
||||
// リモートからフォローリクエストを受けていたらReject送信
|
||||
if (isRemoteUser(follower) && isLocalUser(followee)) {
|
||||
const content = pack(renderReject(renderFollow(follower, followee, request.requestId), followee));
|
||||
const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId), followee));
|
||||
deliver(followee, content, follower.inbox);
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ async function unFollow(follower: IUser, followee: IUser) {
|
||||
|
||||
// リモートにフォローをしていたらUndoFollow送信
|
||||
if (isLocalUser(follower) && isRemoteUser(followee)) {
|
||||
const content = pack(renderUndo(renderFollow(follower, followee), follower));
|
||||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||
deliver(follower, content, followee.inbox);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { isLocalUser, isRemoteUser, IUser } from '../../models/user';
|
||||
import Blocking from '../../models/blocking';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderBlock from '../../remote/activitypub/renderer/block';
|
||||
import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||
import { deliver } from '../../queue';
|
||||
@@ -22,7 +22,7 @@ export default async function(blocker: IUser, blockee: IUser) {
|
||||
|
||||
// deliver if remote bloking
|
||||
if (isLocalUser(blocker) && isRemoteUser(blockee)) {
|
||||
const content = pack(renderUndo(renderBlock(blocker, blockee), blocker));
|
||||
const content = renderActivity(renderUndo(renderBlock(blocker, blockee), blocker));
|
||||
deliver(blocker, content, blockee.inbox);
|
||||
}
|
||||
}
|
||||
|
@@ -322,7 +322,7 @@ export default async function(
|
||||
if (type) {
|
||||
res([type.mime, type.ext]);
|
||||
} else if (isSvg(buffer)) {
|
||||
res(['image/svg+xml', 'svg'])
|
||||
res(['image/svg+xml', 'svg']);
|
||||
} else {
|
||||
// 種類が同定できなかったら application/octet-stream にする
|
||||
res(['application/octet-stream', null]);
|
||||
|
@@ -3,7 +3,7 @@ import Following from '../../models/following';
|
||||
import Blocking from '../../models/blocking';
|
||||
import { publishMainStream } from '../../stream';
|
||||
import notify from '../../notify';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../remote/activitypub/renderer/follow';
|
||||
import renderAccept from '../../remote/activitypub/renderer/accept';
|
||||
import renderReject from '../../remote/activitypub/renderer/reject';
|
||||
@@ -26,7 +26,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
|
||||
|
||||
if (isRemoteUser(follower) && isLocalUser(followee) && blocked) {
|
||||
// リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。
|
||||
const content = pack(renderReject(renderFollow(follower, followee, requestId), followee));
|
||||
const content = renderActivity(renderReject(renderFollow(follower, followee, requestId), followee));
|
||||
deliver(followee , content, follower.inbox);
|
||||
return;
|
||||
} else if (isRemoteUser(follower) && isLocalUser(followee) && blocking) {
|
||||
@@ -115,7 +115,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
|
||||
}
|
||||
|
||||
if (isRemoteUser(follower) && isLocalUser(followee)) {
|
||||
const content = pack(renderAccept(renderFollow(follower, followee, requestId), followee));
|
||||
const content = renderActivity(renderAccept(renderFollow(follower, followee, requestId), followee));
|
||||
deliver(followee, content, follower.inbox);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import User, { isLocalUser, isRemoteUser, pack as packUser, IUser } from '../../models/user';
|
||||
import Following from '../../models/following';
|
||||
import { publishMainStream } from '../../stream';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../remote/activitypub/renderer/follow';
|
||||
import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||
import { deliver } from '../../queue';
|
||||
@@ -48,7 +48,7 @@ export default async function(follower: IUser, followee: IUser) {
|
||||
}
|
||||
|
||||
if (isLocalUser(follower) && isRemoteUser(followee)) {
|
||||
const content = pack(renderUndo(renderFollow(follower, followee), follower));
|
||||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||
deliver(follower, content, followee.inbox);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import User, { IUser, isRemoteUser, ILocalUser, pack as packUser, isLocalUser } from '../../../models/user';
|
||||
import FollowRequest from '../../../models/follow-request';
|
||||
import pack from '../../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../../remote/activitypub/renderer/follow';
|
||||
import renderAccept from '../../../remote/activitypub/renderer/accept';
|
||||
import { deliver } from '../../../queue';
|
||||
@@ -42,7 +42,7 @@ export default async function(followee: IUser, follower: IUser) {
|
||||
followerId: follower._id
|
||||
});
|
||||
|
||||
const content = pack(renderAccept(renderFollow(follower, followee, request.requestId), followee as ILocalUser));
|
||||
const content = renderActivity(renderAccept(renderFollow(follower, followee, request.requestId), followee as ILocalUser));
|
||||
deliver(followee as ILocalUser, content, follower.inbox);
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import User, { IUser, isRemoteUser, ILocalUser, pack as packUser } from '../../../models/user';
|
||||
import FollowRequest from '../../../models/follow-request';
|
||||
import pack from '../../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../../remote/activitypub/renderer/follow';
|
||||
import renderUndo from '../../../remote/activitypub/renderer/undo';
|
||||
import { deliver } from '../../../queue';
|
||||
@@ -8,7 +8,7 @@ import { publishMainStream } from '../../../stream';
|
||||
|
||||
export default async function(followee: IUser, follower: IUser) {
|
||||
if (isRemoteUser(followee)) {
|
||||
const content = pack(renderUndo(renderFollow(follower, followee), follower));
|
||||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||
deliver(follower as ILocalUser, content, followee.inbox);
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import User, { isLocalUser, isRemoteUser, pack as packUser, IUser } from '../../../models/user';
|
||||
import { publishMainStream } from '../../../stream';
|
||||
import notify from '../../../notify';
|
||||
import pack from '../../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../../remote/activitypub/renderer/follow';
|
||||
import { deliver } from '../../../queue';
|
||||
import FollowRequest from '../../../models/follow-request';
|
||||
@@ -61,7 +61,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
|
||||
}
|
||||
|
||||
if (isLocalUser(follower) && isRemoteUser(followee)) {
|
||||
const content = pack(renderFollow(follower, followee));
|
||||
const content = renderActivity(renderFollow(follower, followee));
|
||||
deliver(follower, content, followee.inbox);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import User, { IUser, isRemoteUser, ILocalUser, pack as packUser } from '../../../models/user';
|
||||
import FollowRequest from '../../../models/follow-request';
|
||||
import pack from '../../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import renderFollow from '../../../remote/activitypub/renderer/follow';
|
||||
import renderReject from '../../../remote/activitypub/renderer/reject';
|
||||
import { deliver } from '../../../queue';
|
||||
@@ -13,7 +13,7 @@ export default async function(followee: IUser, follower: IUser) {
|
||||
followerId: follower._id
|
||||
});
|
||||
|
||||
const content = pack(renderReject(renderFollow(follower, followee, request.requestId), followee as ILocalUser));
|
||||
const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId), followee as ILocalUser));
|
||||
deliver(followee as ILocalUser, content, follower.inbox);
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import Note, { packMany } from '../../models/note';
|
||||
import Following from '../../models/following';
|
||||
import renderAdd from '../../remote/activitypub/renderer/add';
|
||||
import renderRemove from '../../remote/activitypub/renderer/remove';
|
||||
import packAp from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import { deliver } from '../../queue';
|
||||
|
||||
/**
|
||||
@@ -100,7 +100,7 @@ export async function deliverPinnedChange(userId: mongo.ObjectID, noteId: mongo.
|
||||
const target = `${config.url}/users/${user._id}/collections/featured`;
|
||||
|
||||
const item = `${config.url}/notes/${noteId}`;
|
||||
const content = packAp(isAddition ? renderAdd(user, target, item) : renderRemove(user, target, item));
|
||||
const content = renderActivity(isAddition ? renderAdd(user, target, item) : renderRemove(user, target, item));
|
||||
for (const inbox of queue) {
|
||||
deliver(user, content, inbox);
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import User, { isLocalUser, isRemoteUser } from '../../models/user';
|
||||
import Following from '../../models/following';
|
||||
import renderPerson from '../../remote/activitypub/renderer/person';
|
||||
import renderUpdate from '../../remote/activitypub/renderer/update';
|
||||
import packAp from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import { deliver } from '../../queue';
|
||||
|
||||
export async function publishToFollowers(userId: mongo.ObjectID) {
|
||||
@@ -29,7 +29,7 @@ export async function publishToFollowers(userId: mongo.ObjectID) {
|
||||
}
|
||||
|
||||
if (queue.length > 0) {
|
||||
const content = packAp(renderUpdate(await renderPerson(user), user));
|
||||
const content = renderActivity(renderUpdate(await renderPerson(user), user));
|
||||
for (const inbox of queue) {
|
||||
deliver(user, content, inbox);
|
||||
}
|
||||
|
@@ -7,13 +7,13 @@ import { deliver } from '../../queue';
|
||||
import renderNote from '../../remote/activitypub/renderer/note';
|
||||
import renderCreate from '../../remote/activitypub/renderer/create';
|
||||
import renderAnnounce from '../../remote/activitypub/renderer/announce';
|
||||
import packAp from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import DriveFile, { IDriveFile } from '../../models/drive-file';
|
||||
import notify from '../../notify';
|
||||
import NoteWatching from '../../models/note-watching';
|
||||
import watch from './watch';
|
||||
import Mute from '../../models/mute';
|
||||
import parse from '../../mfm/parse';
|
||||
import { parse } from '../../mfm/parse';
|
||||
import { IApp } from '../../models/app';
|
||||
import UserList from '../../models/user-list';
|
||||
import resolveUser from '../../remote/resolve-user';
|
||||
@@ -116,6 +116,11 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||
if (data.viaMobile == null) data.viaMobile = false;
|
||||
if (data.localOnly == null) data.localOnly = false;
|
||||
|
||||
// サイレンス
|
||||
if (user.isSilenced && data.visibility == 'public') {
|
||||
data.visibility = 'home';
|
||||
}
|
||||
|
||||
if (data.visibleUsers) {
|
||||
data.visibleUsers = erase(null, data.visibleUsers);
|
||||
}
|
||||
@@ -278,7 +283,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||
|
||||
createMentionedEvents(mentionedUsers, note, nm);
|
||||
|
||||
const noteActivity = await renderActivity(data, note);
|
||||
const noteActivity = await renderNoteOrRenoteActivity(data, note);
|
||||
|
||||
if (isLocalUser(user)) {
|
||||
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
|
||||
@@ -336,14 +341,14 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||
index(note);
|
||||
});
|
||||
|
||||
async function renderActivity(data: Option, note: INote) {
|
||||
async function renderNoteOrRenoteActivity(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);
|
||||
|
||||
return packAp(content);
|
||||
return renderActivity(content);
|
||||
}
|
||||
|
||||
function incRenoteCount(renote: INote) {
|
||||
|
@@ -2,7 +2,7 @@ import Note, { INote } from '../../models/note';
|
||||
import { IUser, isLocalUser } from '../../models/user';
|
||||
import { publishNoteStream } from '../../stream';
|
||||
import renderDelete from '../../remote/activitypub/renderer/delete';
|
||||
import pack from '../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import { deliver } from '../../queue';
|
||||
import Following from '../../models/following';
|
||||
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
||||
@@ -75,7 +75,7 @@ export default async function(user: IUser, note: INote) {
|
||||
|
||||
//#region ローカルの投稿なら削除アクティビティを配送
|
||||
if (isLocalUser(user)) {
|
||||
const content = pack(renderDelete(renderTombstone(`${config.url}/notes/${note._id}`), user));
|
||||
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${note._id}`), user));
|
||||
|
||||
const followings = await Following.find({
|
||||
followeeId: user._id,
|
||||
|
@@ -7,7 +7,7 @@ import NoteWatching from '../../../models/note-watching';
|
||||
import watch from '../watch';
|
||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||
import { deliver } from '../../../queue';
|
||||
import pack from '../../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import perUserReactionsChart from '../../../chart/per-user-reactions';
|
||||
|
||||
export default async (user: IUser, note: INote, reaction: string) => new Promise(async (res, rej) => {
|
||||
@@ -86,7 +86,7 @@ export default async (user: IUser, note: INote, reaction: string) => new Promise
|
||||
//#region 配信
|
||||
// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
|
||||
if (isLocalUser(user) && isRemoteUser(note._user)) {
|
||||
const content = pack(renderLike(user, note, reaction));
|
||||
const content = renderActivity(renderLike(user, note, reaction));
|
||||
deliver(user, content, note._user.inbox);
|
||||
}
|
||||
//#endregion
|
||||
|
@@ -4,7 +4,7 @@ import Reaction from '../../../models/note-reaction';
|
||||
import { publishNoteStream } from '../../../stream';
|
||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||
import renderUndo from '../../../remote/activitypub/renderer/undo';
|
||||
import pack from '../../../remote/activitypub/renderer';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import { deliver } from '../../../queue';
|
||||
|
||||
export default async (user: IUser, note: INote) => new Promise(async (res, rej) => {
|
||||
@@ -42,7 +42,7 @@ export default async (user: IUser, note: INote) => new Promise(async (res, rej)
|
||||
//#region 配信
|
||||
// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
|
||||
if (isLocalUser(user) && isRemoteUser(note._user)) {
|
||||
const content = pack(renderUndo(renderLike(user, note, exist.reaction), user));
|
||||
const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user));
|
||||
deliver(user, content, note._user.inbox);
|
||||
}
|
||||
//#endregion
|
||||
|
572
test/api-visibility.ts
Normal file
572
test/api-visibility.ts
Normal file
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
* Tests of API (visibility)
|
||||
*
|
||||
* How to run the tests:
|
||||
* > mocha test/api-visibility.ts --require ts-node/register
|
||||
*
|
||||
* To specify test:
|
||||
* > mocha test/api-visibility.ts --require ts-node/register -g 'test name'
|
||||
*/
|
||||
import * as http from 'http';
|
||||
import * as assert from 'chai';
|
||||
import { async, _signup, _request, _uploadFile, _post, _react, resetDb } from './utils';
|
||||
|
||||
const expect = assert.expect;
|
||||
|
||||
//#region process
|
||||
Error.stackTraceLimit = Infinity;
|
||||
|
||||
// During the test the env variable is set to test
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
// Display detail of unhandled promise rejection
|
||||
process.on('unhandledRejection', console.dir);
|
||||
//#endregion
|
||||
|
||||
const app = require('../built/server/api').default;
|
||||
const db = require('../built/db/mongodb').default;
|
||||
|
||||
const server = http.createServer(app.callback());
|
||||
|
||||
//#region Utilities
|
||||
const request = _request(server);
|
||||
const signup = _signup(request);
|
||||
const post = _post(request);
|
||||
//#endregion
|
||||
|
||||
describe('API visibility', () => {
|
||||
// Reset database each test
|
||||
before(resetDb(db));
|
||||
|
||||
after(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe('Note visibility', async () => {
|
||||
//#region vars
|
||||
/** ヒロイン */
|
||||
let alice: any;
|
||||
/** フォロワー */
|
||||
let follower: any;
|
||||
/** 非フォロワー */
|
||||
let other: any;
|
||||
/** 非フォロワーでもリプライやメンションをされた人 */
|
||||
let target: any;
|
||||
|
||||
/** public-post */
|
||||
let pub: any;
|
||||
/** home-post */
|
||||
let home: any;
|
||||
/** followers-post */
|
||||
let fol: any;
|
||||
/** specified-post */
|
||||
let spe: any;
|
||||
/** private-post */
|
||||
let pri: any;
|
||||
|
||||
/** public-reply to target's post */
|
||||
let pubR: any;
|
||||
/** home-reply to target's post */
|
||||
let homeR: any;
|
||||
/** followers-reply to target's post */
|
||||
let folR: any;
|
||||
/** specified-reply to target's post */
|
||||
let speR: any;
|
||||
/** private-reply to target's post */
|
||||
let priR: any;
|
||||
|
||||
/** public-mention to target */
|
||||
let pubM: any;
|
||||
/** home-mention to target */
|
||||
let homeM: any;
|
||||
/** followers-mention to target */
|
||||
let folM: any;
|
||||
/** specified-mention to target */
|
||||
let speM: any;
|
||||
/** private-mention to target */
|
||||
let priM: any;
|
||||
|
||||
/** reply target post */
|
||||
let tgt: any;
|
||||
//#endregion
|
||||
|
||||
const show = async (noteId: any, by: any) => {
|
||||
return await request('/notes/show', {
|
||||
noteId
|
||||
}, by);
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
//#region prepare
|
||||
// signup
|
||||
alice = await signup({ username: 'alice' });
|
||||
follower = await signup({ username: 'follower' });
|
||||
other = await signup({ username: 'other' });
|
||||
target = await signup({ username: 'target' });
|
||||
|
||||
// follow alice <= follower
|
||||
await request('/following/create', { userId: alice.id }, follower);
|
||||
|
||||
// normal posts
|
||||
pub = await post(alice, { text: 'x', visibility: 'public' });
|
||||
home = await post(alice, { text: 'x', visibility: 'home' });
|
||||
fol = await post(alice, { text: 'x', visibility: 'followers' });
|
||||
spe = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] });
|
||||
pri = await post(alice, { text: 'x', visibility: 'private' });
|
||||
|
||||
// replies
|
||||
tgt = await post(target, { text: 'y', visibility: 'public' });
|
||||
pubR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' });
|
||||
homeR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'home' });
|
||||
folR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' });
|
||||
speR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' });
|
||||
priR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'private' });
|
||||
|
||||
// mentions
|
||||
pubM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' });
|
||||
homeM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'home' });
|
||||
folM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' });
|
||||
speM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'specified' });
|
||||
priM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'private' });
|
||||
//#endregion
|
||||
});
|
||||
|
||||
//#region show post
|
||||
// public
|
||||
it('[show] public-postを自分が見れる', async(async () => {
|
||||
const res = await show(pub.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] public-postをフォロワーが見れる', async(async () => {
|
||||
const res = await show(pub.id, follower);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] public-postを非フォロワーが見れる', async(async () => {
|
||||
const res = await show(pub.id, other);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] public-postを未認証が見れる', async(async () => {
|
||||
const res = await show(pub.id, null);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home-postを自分が見れる', async(async () => {
|
||||
const res = await show(home.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] home-postをフォロワーが見れる', async(async () => {
|
||||
const res = await show(home.id, follower);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] home-postを非フォロワーが見れる', async(async () => {
|
||||
const res = await show(home.id, other);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] home-postを未認証が見れる', async(async () => {
|
||||
const res = await show(home.id, null);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers-postを自分が見れる', async(async () => {
|
||||
const res = await show(fol.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] followers-postをフォロワーが見れる', async(async () => {
|
||||
const res = await show(fol.id, follower);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] followers-postを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(fol.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] followers-postを未認証が見れない', async(async () => {
|
||||
const res = await show(fol.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified-postを自分が見れる', async(async () => {
|
||||
const res = await show(spe.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] specified-postを指定ユーザーが見れる', async(async () => {
|
||||
const res = await show(spe.id, target);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] specified-postをフォロワーが見れない', async(async () => {
|
||||
const res = await show(spe.id, follower);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] specified-postを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(spe.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] specified-postを未認証が見れない', async(async () => {
|
||||
const res = await show(spe.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
// private
|
||||
it('[show] private-postを自分が見れる', async(async () => {
|
||||
const res = await show(pri.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] private-postをフォロワーが見れない', async(async () => {
|
||||
const res = await show(pri.id, follower);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] private-postを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(pri.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] private-postを未認証が見れない', async(async () => {
|
||||
const res = await show(pri.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region show reply
|
||||
// public
|
||||
it('[show] public-replyを自分が見れる', async(async () => {
|
||||
const res = await show(pubR.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyをされた人が見れる', async(async () => {
|
||||
const res = await show(pubR.id, target);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyをフォロワーが見れる', async(async () => {
|
||||
const res = await show(pubR.id, follower);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyを非フォロワーが見れる', async(async () => {
|
||||
const res = await show(pubR.id, other);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] public-replyを未認証が見れる', async(async () => {
|
||||
const res = await show(pubR.id, null);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home-replyを自分が見れる', async(async () => {
|
||||
const res = await show(homeR.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyをされた人が見れる', async(async () => {
|
||||
const res = await show(homeR.id, target);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyをフォロワーが見れる', async(async () => {
|
||||
const res = await show(homeR.id, follower);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyを非フォロワーが見れる', async(async () => {
|
||||
const res = await show(homeR.id, other);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] home-replyを未認証が見れる', async(async () => {
|
||||
const res = await show(homeR.id, null);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers-replyを自分が見れる', async(async () => {
|
||||
const res = await show(folR.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] followers-replyを非フォロワーでもリプライされていれば見れる', async(async () => {
|
||||
const res = await show(folR.id, target);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] followers-replyをフォロワーが見れる', async(async () => {
|
||||
const res = await show(folR.id, follower);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] followers-replyを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(folR.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] followers-replyを未認証が見れない', async(async () => {
|
||||
const res = await show(folR.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified-replyを自分が見れる', async(async () => {
|
||||
const res = await show(speR.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] specified-replyを指定ユーザーが見れる', async(async () => {
|
||||
const res = await show(speR.id, target);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] specified-replyをされた人が指定されてなくても見れる', async(async () => {
|
||||
const res = await show(speR.id, target);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] specified-replyをフォロワーが見れない', async(async () => {
|
||||
const res = await show(speR.id, follower);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] specified-replyを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(speR.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] specified-replyを未認証が見れない', async(async () => {
|
||||
const res = await show(speR.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
// private
|
||||
it('[show] private-replyを自分が見れる', async(async () => {
|
||||
const res = await show(priR.id, alice);
|
||||
expect(res.body).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[show] private-replyをフォロワーが見れない', async(async () => {
|
||||
const res = await show(priR.id, follower);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] private-replyを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(priR.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] private-replyを未認証が見れない', async(async () => {
|
||||
const res = await show(priR.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region show mention
|
||||
// public
|
||||
it('[show] public-mentionを自分が見れる', async(async () => {
|
||||
const res = await show(pubM.id, alice);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionをされた人が見れる', async(async () => {
|
||||
const res = await show(pubM.id, target);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionをフォロワーが見れる', async(async () => {
|
||||
const res = await show(pubM.id, follower);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionを非フォロワーが見れる', async(async () => {
|
||||
const res = await show(pubM.id, other);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] public-mentionを未認証が見れる', async(async () => {
|
||||
const res = await show(pubM.id, null);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home-mentionを自分が見れる', async(async () => {
|
||||
const res = await show(homeM.id, alice);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionをされた人が見れる', async(async () => {
|
||||
const res = await show(homeM.id, target);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionをフォロワーが見れる', async(async () => {
|
||||
const res = await show(homeM.id, follower);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionを非フォロワーが見れる', async(async () => {
|
||||
const res = await show(homeM.id, other);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] home-mentionを未認証が見れる', async(async () => {
|
||||
const res = await show(homeM.id, null);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers-mentionを自分が見れる', async(async () => {
|
||||
const res = await show(folM.id, alice);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionを非フォロワーでもメンションされていれば見れる', async(async () => {
|
||||
const res = await show(folM.id, target);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionをフォロワーが見れる', async(async () => {
|
||||
const res = await show(folM.id, follower);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(folM.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] followers-mentionを未認証が見れない', async(async () => {
|
||||
const res = await show(folM.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified-mentionを自分が見れる', async(async () => {
|
||||
const res = await show(speM.id, alice);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionを指定ユーザーが見れる', async(async () => {
|
||||
const res = await show(speM.id, target);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionをされた人が指定されてなくても見れる', async(async () => {
|
||||
const res = await show(speM.id, target);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionをフォロワーが見れない', async(async () => {
|
||||
const res = await show(speM.id, follower);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(speM.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] specified-mentionを未認証が見れない', async(async () => {
|
||||
const res = await show(speM.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
// private
|
||||
it('[show] private-mentionを自分が見れる', async(async () => {
|
||||
const res = await show(priM.id, alice);
|
||||
expect(res.body).have.property('text').eql('@target x');
|
||||
}));
|
||||
|
||||
it('[show] private-mentionをフォロワーが見れない', async(async () => {
|
||||
const res = await show(priM.id, follower);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] private-mentionを非フォロワーが見れない', async(async () => {
|
||||
const res = await show(priM.id, other);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
|
||||
it('[show] private-mentionを未認証が見れない', async(async () => {
|
||||
const res = await show(priM.id, null);
|
||||
expect(res.body).have.property('isHidden').eql(true);
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region HTL
|
||||
it('[HTL] public-post が 自分が見れる', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, alice);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
||||
expect(notes[0]).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[HTL] public-post が 非フォロワーから見れない', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, other);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
||||
expect(notes).length(0);
|
||||
}));
|
||||
|
||||
it('[HTL] followers-post が フォロワーから見れる', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, follower);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == fol.id);
|
||||
expect(notes[0]).have.property('text').eql('x');
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region RTL
|
||||
it('[replies] followers-reply が フォロワーから見れる', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, follower);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
expect(notes[0]).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, other);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
expect(notes).length(0);
|
||||
}));
|
||||
|
||||
it('[replies] followers-reply が 非フォロワー (リプライ先である) から見れる', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, target);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
expect(notes[0]).have.property('text').eql('x');
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region MTL
|
||||
it('[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる', async(async () => {
|
||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
||||
expect(notes[0]).have.property('text').eql('x');
|
||||
}));
|
||||
|
||||
it('[mentions] followers-mention が 非フォロワー (メンション先である) から見れる', async(async () => {
|
||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||
expect(res).have.status(200);
|
||||
const notes = res.body.filter((n: any) => n.id == folM.id);
|
||||
expect(notes[0]).have.property('text').eql('@target x');
|
||||
}));
|
||||
//#endregion
|
||||
});
|
||||
});
|
@@ -1,7 +1,7 @@
|
||||
import * as assert from 'assert';
|
||||
|
||||
import extractMentions from '../src/misc/extract-mentions';
|
||||
import parse from '../src/mfm/parse';
|
||||
import { parse } from '../src/mfm/parse';
|
||||
|
||||
describe('Extract mentions', () => {
|
||||
it('simple', () => {
|
||||
|
266
test/mfm.ts
266
test/mfm.ts
@@ -10,9 +10,10 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import analyze from '../src/mfm/parse';
|
||||
import toHtml from '../src/mfm/html';
|
||||
import { createTree as tree, createLeaf as leaf, MfmTree, removeOrphanedBrackets } from '../src/mfm/parser';
|
||||
import { parse, parsePlain } from '../src/mfm/parse';
|
||||
import { toHtml } from '../src/mfm/toHtml';
|
||||
import { createTree as tree, createLeaf as leaf, MfmTree } from '../src/mfm/types';
|
||||
import { removeOrphanedBrackets } from '../src/mfm/language';
|
||||
|
||||
function text(text: string): MfmTree {
|
||||
return leaf('text', { text });
|
||||
@@ -150,7 +151,7 @@ describe('removeOrphanedBrackets', () => {
|
||||
|
||||
describe('MFM', () => {
|
||||
it('can be analyzed', () => {
|
||||
const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
|
||||
const tokens = parse('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('mention', {
|
||||
acct: '@himawari',
|
||||
@@ -175,7 +176,7 @@ describe('MFM', () => {
|
||||
describe('elements', () => {
|
||||
describe('bold', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('**foo**');
|
||||
const tokens = parse('**foo**');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('bold', [
|
||||
text('foo')
|
||||
@@ -184,7 +185,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with other texts', () => {
|
||||
const tokens = analyze('bar**foo**bar');
|
||||
const tokens = parse('bar**foo**bar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('bar'),
|
||||
tree('bold', [
|
||||
@@ -195,7 +196,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with underscores', () => {
|
||||
const tokens = analyze('__foo__');
|
||||
const tokens = parse('__foo__');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('bold', [
|
||||
text('foo')
|
||||
@@ -204,21 +205,21 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with underscores (ensure it allows alphabet only)', () => {
|
||||
const tokens = analyze('(=^・__________・^=)');
|
||||
const tokens = parse('(=^・__________・^=)');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('(=^・__________・^=)')
|
||||
]);
|
||||
});
|
||||
|
||||
it('mixed syntax', () => {
|
||||
const tokens = analyze('**foo__');
|
||||
const tokens = parse('**foo__');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('**foo__'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('mixed syntax', () => {
|
||||
const tokens = analyze('__foo**');
|
||||
const tokens = parse('__foo**');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('__foo**'),
|
||||
]);
|
||||
@@ -226,7 +227,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('big', () => {
|
||||
const tokens = analyze('***Strawberry*** Pasta');
|
||||
const tokens = parse('***Strawberry*** Pasta');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('big', [
|
||||
text('Strawberry')
|
||||
@@ -236,7 +237,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('small', () => {
|
||||
const tokens = analyze('<small>smaller</small>');
|
||||
const tokens = parse('<small>smaller</small>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('small', [
|
||||
text('smaller')
|
||||
@@ -245,7 +246,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('flip', () => {
|
||||
const tokens = analyze('<flip>foo</flip>');
|
||||
const tokens = parse('<flip>foo</flip>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('flip', [
|
||||
text('foo')
|
||||
@@ -255,7 +256,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('spin', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('<spin>:foo:</spin>');
|
||||
const tokens = parse('<spin>:foo:</spin>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('spin', [
|
||||
leaf('emoji', { name: 'foo' })
|
||||
@@ -266,7 +267,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with attr', () => {
|
||||
const tokens = analyze('<spin left>:foo:</spin>');
|
||||
const tokens = parse('<spin left>:foo:</spin>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('spin', [
|
||||
leaf('emoji', { name: 'foo' })
|
||||
@@ -275,10 +276,25 @@ describe('MFM', () => {
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('nested', () => {
|
||||
const tokens = parse('<spin><spin>:foo:</spin></spin>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('spin', [
|
||||
tree('spin', [
|
||||
leaf('emoji', { name: 'foo' })
|
||||
], {
|
||||
attr: null
|
||||
}),
|
||||
], {
|
||||
attr: null
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('jump', () => {
|
||||
const tokens = analyze('<jump>:foo:</jump>');
|
||||
const tokens = parse('<jump>:foo:</jump>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('jump', [
|
||||
leaf('emoji', { name: 'foo' })
|
||||
@@ -288,7 +304,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('motion', () => {
|
||||
it('by triple brackets', () => {
|
||||
const tokens = analyze('(((foo)))');
|
||||
const tokens = parse('(((foo)))');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('motion', [
|
||||
text('foo')
|
||||
@@ -297,7 +313,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('by triple brackets (with other texts)', () => {
|
||||
const tokens = analyze('bar(((foo)))bar');
|
||||
const tokens = parse('bar(((foo)))bar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('bar'),
|
||||
tree('motion', [
|
||||
@@ -308,7 +324,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('by <motion> tag', () => {
|
||||
const tokens = analyze('<motion>foo</motion>');
|
||||
const tokens = parse('<motion>foo</motion>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('motion', [
|
||||
text('foo')
|
||||
@@ -317,7 +333,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('by <motion> tag (with other texts)', () => {
|
||||
const tokens = analyze('bar<motion>foo</motion>bar');
|
||||
const tokens = parse('bar<motion>foo</motion>bar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('bar'),
|
||||
tree('motion', [
|
||||
@@ -330,7 +346,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('mention', () => {
|
||||
it('local', () => {
|
||||
const tokens = analyze('@himawari foo');
|
||||
const tokens = parse('@himawari foo');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('mention', {
|
||||
acct: '@himawari',
|
||||
@@ -343,7 +359,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('remote', () => {
|
||||
const tokens = analyze('@hima_sub@namori.net foo');
|
||||
const tokens = parse('@hima_sub@namori.net foo');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('mention', {
|
||||
acct: '@hima_sub@namori.net',
|
||||
@@ -356,7 +372,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('remote punycode', () => {
|
||||
const tokens = analyze('@hima_sub@xn--q9j5bya.xn--zckzah foo');
|
||||
const tokens = parse('@hima_sub@xn--q9j5bya.xn--zckzah foo');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('mention', {
|
||||
acct: '@hima_sub@xn--q9j5bya.xn--zckzah',
|
||||
@@ -369,12 +385,12 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore', () => {
|
||||
const tokens = analyze('idolm@ster');
|
||||
const tokens = parse('idolm@ster');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('idolm@ster')
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('@a\n@b\n@c');
|
||||
const tokens2 = parse('@a\n@b\n@c');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
leaf('mention', {
|
||||
acct: '@a',
|
||||
@@ -398,7 +414,7 @@ describe('MFM', () => {
|
||||
})
|
||||
]);
|
||||
|
||||
const tokens3 = analyze('**x**@a');
|
||||
const tokens3 = parse('**x**@a');
|
||||
assert.deepStrictEqual(tokens3, [
|
||||
tree('bold', [
|
||||
text('x')
|
||||
@@ -411,7 +427,7 @@ describe('MFM', () => {
|
||||
})
|
||||
]);
|
||||
|
||||
const tokens4 = analyze('@\n@v\n@veryverylongusername');
|
||||
const tokens4 = parse('@\n@v\n@veryverylongusername');
|
||||
assert.deepStrictEqual(tokens4, [
|
||||
text('@\n'),
|
||||
leaf('mention', {
|
||||
@@ -433,14 +449,14 @@ describe('MFM', () => {
|
||||
|
||||
describe('hashtag', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('#alice');
|
||||
const tokens = parse('#alice');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('hashtag', { hashtag: 'alice' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('after line break', () => {
|
||||
const tokens = analyze('foo\n#alice');
|
||||
const tokens = parse('foo\n#alice');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo\n'),
|
||||
leaf('hashtag', { hashtag: 'alice' })
|
||||
@@ -448,7 +464,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with text', () => {
|
||||
const tokens = analyze('Strawberry Pasta #alice');
|
||||
const tokens = parse('Strawberry Pasta #alice');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('Strawberry Pasta '),
|
||||
leaf('hashtag', { hashtag: 'alice' })
|
||||
@@ -456,7 +472,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with text (zenkaku)', () => {
|
||||
const tokens = analyze('こんにちは#世界');
|
||||
const tokens = parse('こんにちは#世界');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('こんにちは'),
|
||||
leaf('hashtag', { hashtag: '世界' })
|
||||
@@ -464,7 +480,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore comma and period', () => {
|
||||
const tokens = analyze('Foo #bar, baz #piyo.');
|
||||
const tokens = parse('Foo #bar, baz #piyo.');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('Foo '),
|
||||
leaf('hashtag', { hashtag: 'bar' }),
|
||||
@@ -475,7 +491,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore exclamation mark', () => {
|
||||
const tokens = analyze('#Foo!');
|
||||
const tokens = parse('#Foo!');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('hashtag', { hashtag: 'Foo' }),
|
||||
text('!'),
|
||||
@@ -483,7 +499,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore colon', () => {
|
||||
const tokens = analyze('#Foo:');
|
||||
const tokens = parse('#Foo:');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('hashtag', { hashtag: 'Foo' }),
|
||||
text(':'),
|
||||
@@ -491,7 +507,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore single quote', () => {
|
||||
const tokens = analyze('#foo\'');
|
||||
const tokens = parse('#foo\'');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('hashtag', { hashtag: 'foo' }),
|
||||
text('\''),
|
||||
@@ -499,7 +515,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore double quote', () => {
|
||||
const tokens = analyze('#foo"');
|
||||
const tokens = parse('#foo"');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('hashtag', { hashtag: 'foo' }),
|
||||
text('"'),
|
||||
@@ -507,21 +523,21 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('allow including number', () => {
|
||||
const tokens = analyze('#foo123');
|
||||
const tokens = parse('#foo123');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('hashtag', { hashtag: 'foo123' }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('with brackets', () => {
|
||||
const tokens1 = analyze('(#foo)');
|
||||
const tokens1 = parse('(#foo)');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
text('('),
|
||||
leaf('hashtag', { hashtag: 'foo' }),
|
||||
text(')'),
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('「#foo」');
|
||||
const tokens2 = parse('「#foo」');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
text('「'),
|
||||
leaf('hashtag', { hashtag: 'foo' }),
|
||||
@@ -530,7 +546,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with mixed brackets', () => {
|
||||
const tokens = analyze('「#foo(bar)」');
|
||||
const tokens = parse('「#foo(bar)」');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('「'),
|
||||
leaf('hashtag', { hashtag: 'foo(bar)' }),
|
||||
@@ -539,14 +555,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with brackets (space before)', () => {
|
||||
const tokens1 = analyze('(bar #foo)');
|
||||
const tokens1 = parse('(bar #foo)');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
text('(bar '),
|
||||
leaf('hashtag', { hashtag: 'foo' }),
|
||||
text(')'),
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('「bar #foo」');
|
||||
const tokens2 = parse('「bar #foo」');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
text('「bar '),
|
||||
leaf('hashtag', { hashtag: 'foo' }),
|
||||
@@ -555,14 +571,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('disallow number only', () => {
|
||||
const tokens = analyze('#123');
|
||||
const tokens = parse('#123');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('#123'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('disallow number only (with brackets)', () => {
|
||||
const tokens = analyze('(#123)');
|
||||
const tokens = parse('(#123)');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('(#123)'),
|
||||
]);
|
||||
@@ -571,14 +587,14 @@ describe('MFM', () => {
|
||||
|
||||
describe('quote', () => {
|
||||
it('basic', () => {
|
||||
const tokens1 = analyze('> foo');
|
||||
const tokens1 = parse('> foo');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
tree('quote', [
|
||||
text('foo')
|
||||
], {})
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('>foo');
|
||||
const tokens2 = parse('>foo');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
tree('quote', [
|
||||
text('foo')
|
||||
@@ -587,7 +603,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('series', () => {
|
||||
const tokens = analyze('> foo\n\n> bar');
|
||||
const tokens = parse('> foo\n\n> bar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('quote', [
|
||||
text('foo')
|
||||
@@ -600,14 +616,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('trailing line break', () => {
|
||||
const tokens1 = analyze('> foo\n');
|
||||
const tokens1 = parse('> foo\n');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
tree('quote', [
|
||||
text('foo')
|
||||
], {}),
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('> foo\n\n');
|
||||
const tokens2 = parse('> foo\n\n');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
tree('quote', [
|
||||
text('foo')
|
||||
@@ -617,14 +633,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('multiline', () => {
|
||||
const tokens1 = analyze('>foo\n>bar');
|
||||
const tokens1 = parse('>foo\n>bar');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
tree('quote', [
|
||||
text('foo\nbar')
|
||||
], {})
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('> foo\n> bar');
|
||||
const tokens2 = parse('> foo\n> bar');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
tree('quote', [
|
||||
text('foo\nbar')
|
||||
@@ -633,14 +649,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('multiline with trailing line break', () => {
|
||||
const tokens1 = analyze('> foo\n> bar\n');
|
||||
const tokens1 = parse('> foo\n> bar\n');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
tree('quote', [
|
||||
text('foo\nbar')
|
||||
], {}),
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('> foo\n> bar\n\n');
|
||||
const tokens2 = parse('> foo\n> bar\n\n');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
tree('quote', [
|
||||
text('foo\nbar')
|
||||
@@ -650,7 +666,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with before and after texts', () => {
|
||||
const tokens = analyze('before\n> foo\nafter');
|
||||
const tokens = parse('before\n> foo\nafter');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('before\n'),
|
||||
tree('quote', [
|
||||
@@ -661,7 +677,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('multiple quotes', () => {
|
||||
const tokens = analyze('> foo\nbar\n\n> foo\nbar\n\n> foo\nbar');
|
||||
const tokens = parse('> foo\nbar\n\n> foo\nbar\n\n> foo\nbar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('quote', [
|
||||
text('foo')
|
||||
@@ -679,14 +695,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('require line break before ">"', () => {
|
||||
const tokens = analyze('foo>bar');
|
||||
const tokens = parse('foo>bar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo>bar'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('nested', () => {
|
||||
const tokens = analyze('>> foo\n> bar');
|
||||
const tokens = parse('>> foo\n> bar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('quote', [
|
||||
tree('quote', [
|
||||
@@ -698,7 +714,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('trim line breaks', () => {
|
||||
const tokens = analyze('foo\n\n>a\n>>b\n>>\n>>>\n>>>c\n>>>\n>d\n\n');
|
||||
const tokens = parse('foo\n\n>a\n>>b\n>>\n>>>\n>>>c\n>>>\n>d\n\n');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo\n\n'),
|
||||
tree('quote', [
|
||||
@@ -718,14 +734,14 @@ describe('MFM', () => {
|
||||
|
||||
describe('url', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('https://example.com');
|
||||
const tokens = parse('https://example.com');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('url', { url: 'https://example.com' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignore trailing period', () => {
|
||||
const tokens = analyze('https://example.com.');
|
||||
const tokens = parse('https://example.com.');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('url', { url: 'https://example.com' }),
|
||||
text('.')
|
||||
@@ -733,14 +749,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with comma', () => {
|
||||
const tokens = analyze('https://example.com/foo?bar=a,b');
|
||||
const tokens = parse('https://example.com/foo?bar=a,b');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('url', { url: 'https://example.com/foo?bar=a,b' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignore trailing comma', () => {
|
||||
const tokens = analyze('https://example.com/foo, bar');
|
||||
const tokens = parse('https://example.com/foo, bar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('url', { url: 'https://example.com/foo' }),
|
||||
text(', bar')
|
||||
@@ -748,14 +764,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with brackets', () => {
|
||||
const tokens = analyze('https://example.com/foo(bar)');
|
||||
const tokens = parse('https://example.com/foo(bar)');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('url', { url: 'https://example.com/foo(bar)' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignore parent brackets', () => {
|
||||
const tokens = analyze('(https://example.com/foo)');
|
||||
const tokens = parse('(https://example.com/foo)');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('('),
|
||||
leaf('url', { url: 'https://example.com/foo' }),
|
||||
@@ -764,7 +780,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore parent brackets 2', () => {
|
||||
const tokens = analyze('(foo https://example.com/foo)');
|
||||
const tokens = parse('(foo https://example.com/foo)');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('(foo '),
|
||||
leaf('url', { url: 'https://example.com/foo' }),
|
||||
@@ -773,7 +789,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore parent brackets with internal brackets', () => {
|
||||
const tokens = analyze('(https://example.com/foo(bar))');
|
||||
const tokens = parse('(https://example.com/foo(bar))');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('('),
|
||||
leaf('url', { url: 'https://example.com/foo(bar)' }),
|
||||
@@ -784,7 +800,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('link', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('[foo](https://example.com)');
|
||||
const tokens = parse('[foo](https://example.com)');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('link', [
|
||||
text('foo')
|
||||
@@ -793,7 +809,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('simple (with silent flag)', () => {
|
||||
const tokens = analyze('?[foo](https://example.com)');
|
||||
const tokens = parse('?[foo](https://example.com)');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('link', [
|
||||
text('foo')
|
||||
@@ -802,7 +818,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('in text', () => {
|
||||
const tokens = analyze('before[foo](https://example.com)after');
|
||||
const tokens = parse('before[foo](https://example.com)after');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('before'),
|
||||
tree('link', [
|
||||
@@ -813,7 +829,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with brackets', () => {
|
||||
const tokens = analyze('[foo](https://example.com/foo(bar))');
|
||||
const tokens = parse('[foo](https://example.com/foo(bar))');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('link', [
|
||||
text('foo')
|
||||
@@ -822,7 +838,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('with parent brackets', () => {
|
||||
const tokens = analyze('([foo](https://example.com/foo(bar)))');
|
||||
const tokens = parse('([foo](https://example.com/foo(bar)))');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('('),
|
||||
tree('link', [
|
||||
@@ -834,19 +850,19 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('emoji', () => {
|
||||
const tokens1 = analyze(':cat:');
|
||||
const tokens1 = parse(':cat:');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
leaf('emoji', { name: 'cat' })
|
||||
]);
|
||||
|
||||
const tokens2 = analyze(':cat::cat::cat:');
|
||||
const tokens2 = parse(':cat::cat::cat:');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
leaf('emoji', { name: 'cat' }),
|
||||
leaf('emoji', { name: 'cat' }),
|
||||
leaf('emoji', { name: 'cat' })
|
||||
]);
|
||||
|
||||
const tokens3 = analyze('🍎');
|
||||
const tokens3 = parse('🍎');
|
||||
assert.deepStrictEqual(tokens3, [
|
||||
leaf('emoji', { emoji: '🍎' })
|
||||
]);
|
||||
@@ -854,21 +870,21 @@ describe('MFM', () => {
|
||||
|
||||
describe('block code', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('```\nvar x = "Strawberry Pasta";\n```');
|
||||
const tokens = parse('```\nvar x = "Strawberry Pasta";\n```');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('blockCode', { code: 'var x = "Strawberry Pasta";', lang: null })
|
||||
]);
|
||||
});
|
||||
|
||||
it('can specify language', () => {
|
||||
const tokens = analyze('``` json\n{ "x": 42 }\n```');
|
||||
const tokens = parse('``` json\n{ "x": 42 }\n```');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('blockCode', { code: '{ "x": 42 }', lang: 'json' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('require line break before "```"', () => {
|
||||
const tokens = analyze('before```\nfoo\n```');
|
||||
const tokens = parse('before```\nfoo\n```');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('before'),
|
||||
leaf('inlineCode', { code: '`' }),
|
||||
@@ -878,7 +894,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('series', () => {
|
||||
const tokens = analyze('```\nfoo\n```\n```\nbar\n```\n```\nbaz\n```');
|
||||
const tokens = parse('```\nfoo\n```\n```\nbar\n```\n```\nbaz\n```');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('blockCode', { code: 'foo', lang: null }),
|
||||
leaf('blockCode', { code: 'bar', lang: null }),
|
||||
@@ -887,14 +903,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore internal marker', () => {
|
||||
const tokens = analyze('```\naaa```bbb\n```');
|
||||
const tokens = parse('```\naaa```bbb\n```');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('blockCode', { code: 'aaa```bbb', lang: null })
|
||||
]);
|
||||
});
|
||||
|
||||
it('trim after line break', () => {
|
||||
const tokens = analyze('```\nfoo\n```\nbar');
|
||||
const tokens = parse('```\nfoo\n```\nbar');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('blockCode', { code: 'foo', lang: null }),
|
||||
text('bar')
|
||||
@@ -904,21 +920,21 @@ describe('MFM', () => {
|
||||
|
||||
describe('inline code', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('`var x = "Strawberry Pasta";`');
|
||||
const tokens = parse('`var x = "Strawberry Pasta";`');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('inlineCode', { code: 'var x = "Strawberry Pasta";' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('disallow line break', () => {
|
||||
const tokens = analyze('`foo\nbar`');
|
||||
const tokens = parse('`foo\nbar`');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('`foo\nbar`')
|
||||
]);
|
||||
});
|
||||
|
||||
it('disallow ´', () => {
|
||||
const tokens = analyze('`foo´bar`');
|
||||
const tokens = parse('`foo´bar`');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('`foo´bar`')
|
||||
]);
|
||||
@@ -928,7 +944,7 @@ describe('MFM', () => {
|
||||
it('mathInline', () => {
|
||||
const fomula = 'x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}';
|
||||
const content = `\\(${fomula}\\)`;
|
||||
const tokens = analyze(content);
|
||||
const tokens = parse(content);
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('mathInline', { formula: fomula })
|
||||
]);
|
||||
@@ -938,7 +954,7 @@ describe('MFM', () => {
|
||||
it('simple', () => {
|
||||
const fomula = 'x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}';
|
||||
const content = `\\[\n${fomula}\n\\]`;
|
||||
const tokens = analyze(content);
|
||||
const tokens = parse(content);
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('mathBlock', { formula: fomula })
|
||||
]);
|
||||
@@ -946,22 +962,22 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('search', () => {
|
||||
const tokens1 = analyze('a b c 検索');
|
||||
const tokens1 = parse('a b c 検索');
|
||||
assert.deepStrictEqual(tokens1, [
|
||||
leaf('search', { content: 'a b c 検索', query: 'a b c' })
|
||||
]);
|
||||
|
||||
const tokens2 = analyze('a b c Search');
|
||||
const tokens2 = parse('a b c Search');
|
||||
assert.deepStrictEqual(tokens2, [
|
||||
leaf('search', { content: 'a b c Search', query: 'a b c' })
|
||||
]);
|
||||
|
||||
const tokens3 = analyze('a b c search');
|
||||
const tokens3 = parse('a b c search');
|
||||
assert.deepStrictEqual(tokens3, [
|
||||
leaf('search', { content: 'a b c search', query: 'a b c' })
|
||||
]);
|
||||
|
||||
const tokens4 = analyze('a b c SEARCH');
|
||||
const tokens4 = parse('a b c SEARCH');
|
||||
assert.deepStrictEqual(tokens4, [
|
||||
leaf('search', { content: 'a b c SEARCH', query: 'a b c' })
|
||||
]);
|
||||
@@ -969,7 +985,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('title', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('【foo】');
|
||||
const tokens = parse('【foo】');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('title', [
|
||||
text('foo')
|
||||
@@ -978,14 +994,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('require line break', () => {
|
||||
const tokens = analyze('a【foo】');
|
||||
const tokens = parse('a【foo】');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('a【foo】')
|
||||
]);
|
||||
});
|
||||
|
||||
it('with before and after texts', () => {
|
||||
const tokens = analyze('before\n【foo】\nafter');
|
||||
const tokens = parse('before\n【foo】\nafter');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('before\n'),
|
||||
tree('title', [
|
||||
@@ -996,14 +1012,14 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('ignore multiple title blocks', () => {
|
||||
const tokens = analyze('【foo】bar【baz】');
|
||||
const tokens = parse('【foo】bar【baz】');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('【foo】bar【baz】')
|
||||
]);
|
||||
});
|
||||
|
||||
it('disallow linebreak in title', () => {
|
||||
const tokens = analyze('【foo\nbar】');
|
||||
const tokens = parse('【foo\nbar】');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('【foo\nbar】')
|
||||
]);
|
||||
@@ -1012,7 +1028,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('center', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('<center>foo</center>');
|
||||
const tokens = parse('<center>foo</center>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('center', [
|
||||
text('foo')
|
||||
@@ -1023,7 +1039,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('strike', () => {
|
||||
it('simple', () => {
|
||||
const tokens = analyze('~~foo~~');
|
||||
const tokens = parse('~~foo~~');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('strike', [
|
||||
text('foo')
|
||||
@@ -1034,7 +1050,7 @@ describe('MFM', () => {
|
||||
|
||||
describe('italic', () => {
|
||||
it('<i>', () => {
|
||||
const tokens = analyze('<i>foo</i>');
|
||||
const tokens = parse('<i>foo</i>');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('italic', [
|
||||
text('foo')
|
||||
@@ -1043,7 +1059,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('underscore', () => {
|
||||
const tokens = analyze('_foo_');
|
||||
const tokens = parse('_foo_');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('italic', [
|
||||
text('foo')
|
||||
@@ -1052,7 +1068,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('simple with asterix', () => {
|
||||
const tokens = analyze('*foo*');
|
||||
const tokens = parse('*foo*');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('italic', [
|
||||
text('foo')
|
||||
@@ -1061,28 +1077,28 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('exlude emotes', () => {
|
||||
const tokens = analyze('*.*');
|
||||
const tokens = parse('*.*');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text("*.*"),
|
||||
]);
|
||||
});
|
||||
|
||||
it('mixed', () => {
|
||||
const tokens = analyze('_foo*');
|
||||
const tokens = parse('_foo*');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('_foo*'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('mixed', () => {
|
||||
const tokens = analyze('*foo_');
|
||||
const tokens = parse('*foo_');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('*foo_'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignore snake_case string', () => {
|
||||
const tokens = analyze('foo_bar_baz');
|
||||
const tokens = parse('foo_bar_baz');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo_bar_baz'),
|
||||
]);
|
||||
@@ -1090,16 +1106,54 @@ describe('MFM', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('plainText', () => {
|
||||
it('text', () => {
|
||||
const tokens = parsePlain('foo');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('emoji', () => {
|
||||
const tokens = parsePlain(':foo:');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('emoji', { name: 'foo' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('emoji in text', () => {
|
||||
const tokens = parsePlain('foo:bar:baz');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo'),
|
||||
leaf('emoji', { name: 'bar' }),
|
||||
text('baz'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('disallow other syntax', () => {
|
||||
const tokens = parsePlain('foo **bar** baz');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
text('foo **bar** baz'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHtml', () => {
|
||||
it('br', () => {
|
||||
const input = 'foo\nbar\nbaz';
|
||||
const output = '<p><span>foo<br>bar<br>baz</span></p>';
|
||||
assert.equal(toHtml(analyze(input)), output);
|
||||
assert.equal(toHtml(parse(input)), output);
|
||||
});
|
||||
|
||||
it('br alt', () => {
|
||||
const input = 'foo\r\nbar\rbaz';
|
||||
const output = '<p><span>foo<br>bar<br>baz</span></p>';
|
||||
assert.equal(toHtml(parse(input)), output);
|
||||
});
|
||||
});
|
||||
|
||||
it('code block with quote', () => {
|
||||
const tokens = analyze('> foo\n```\nbar\n```');
|
||||
const tokens = parse('> foo\n```\nbar\n```');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
tree('quote', [
|
||||
text('foo')
|
||||
@@ -1109,7 +1163,7 @@ describe('MFM', () => {
|
||||
});
|
||||
|
||||
it('quote between two code blocks', () => {
|
||||
const tokens = analyze('```\nbefore\n```\n> foo\n```\nafter\n```');
|
||||
const tokens = parse('```\nbefore\n```\n> foo\n```\nafter\n```');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('blockCode', { code: 'before', lang: null }),
|
||||
tree('quote', [
|
||||
|
@@ -15,7 +15,8 @@
|
||||
"noLib": false,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"experimentalDecorators": true
|
||||
"experimentalDecorators": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"include": [
|
||||
|
Reference in New Issue
Block a user