Compare commits

..

24 Commits

Author SHA1 Message Date
9d29a2e85a 10.38.8 2018-11-05 13:47:57 +09:00
c62a225542 oops 2018-11-05 13:46:46 +09:00
d5d995a3e6 Refactor 2018-11-05 13:38:50 +09:00
cbba03b376 [Client] Fix bug 2018-11-05 13:23:30 +09:00
f84e9c7dc8 絵文字サジェストでスペースを挿入しないように 2018-11-05 12:35:50 +09:00
a22ddb1fb9 ✌️ 2018-11-05 11:58:41 +09:00
0d23ce3d45 Make /api/v1/instance and /api/v1/custom_emojis better (#3118)
* Separate commits

From commit dca110ebaa.

* Re-separate commits

From commit 9719387bee.
2018-11-05 11:57:17 +09:00
712802e682 10.38.7 2018-11-05 11:11:23 +09:00
abe99c3c73 Update locales/ja-JP.yml 2018-11-05 11:10:02 +09:00
d7a3b71028 投稿の最大文字数情報を設定ファイルではなくDBに保存するように 2018-11-05 11:09:05 +09:00
10c434f24a Remove Travis
Closes #3109
2018-11-05 10:52:07 +09:00
fe46c53ea6 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-05 10:48:51 +09:00
cdd123dfd3 [doc] specify node version 2018-11-05 10:48:40 +09:00
a1a3ee44b5 Implement /api/v1/custom_emojis (#3116) 2018-11-05 10:45:57 +09:00
a86c419f95 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-05 10:40:15 +09:00
e3ec0ad97e [Client] Improve admin panel usability 2018-11-05 10:40:01 +09:00
75791981ce Fix #3115 2018-11-05 10:34:53 +09:00
e813fe16b9 [API] Better validation of admin/emoji/add 2018-11-05 10:33:49 +09:00
42ac7b954d Improve admin panel usability 2018-11-05 10:32:45 +09:00
c1bbf5dab6 [Client] Fix error 2018-11-05 10:29:57 +09:00
e16dc2a910 Update README.md (#3112) 2018-11-05 01:57:08 +09:00
e236c05d79 10.38.6 2018-11-05 01:43:31 +09:00
454c1e3faf [API] Fix bug 2018-11-05 01:42:41 +09:00
43daf814df [Client] 絵文字登録フォームに便利情報を表示 2018-11-05 01:33:06 +09:00
26 changed files with 204 additions and 107 deletions

View File

@ -167,6 +167,3 @@ drive:
# external: true
# engine: http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}}
# timeout: 300000
# Max allowed note text length in charactors
maxNoteTextLength: 1000

View File

@ -1,41 +0,0 @@
# travis file
# https://docs.travis-ci.com/user/customizing-the-build
notifications:
email: false
branches:
except:
- l10n_master
language: node_js
node_js:
- 11.0.0
env:
- CXX=g++-4.8 NODE_ENV=production
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
cache:
directories:
- node_modules
services:
- mongodb
- redis-server
before_script:
- npm install
# 設定ファイルを配置
- cp ./.ci/default.yml ./.config
- cp ./.ci/test.yml ./.config
- travis_wait npm run build

View File

@ -23,5 +23,5 @@ Please use [Crowdin](https://crowdin.com/project/misskey) for localization.
* Test codes are located in `/test`.
## Continuous integration
Misskey uses Travis for automated test.
Configuration files are located in `/.travis`.
Misskey uses CircleCI for automated test.
Configuration files are located in `/.circleci`.

View File

@ -4,7 +4,6 @@
================================================================
[![CircleCI](https://circleci.com/gh/syuilo/misskey.svg?style=svg)](https://circleci.com/gh/syuilo/misskey)
[![][travis-badge]][travis-link]
[![][dependencies-badge]][dependencies-link]
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
@ -44,7 +43,7 @@ Easiest way to tell your emotions. Misskey allows you to add various type of rea
<h3 align="left">Interface</h3>
<p align="left">
No UI fits for everyone. Therefore, Misskey has a highly customizable UI for your taste. You can edit layouts of your timeline, place selectable widgets you can easily move and create your unique home as this place will be your home.
Highly customizable UI for your taste. We understand no UI fits for everyone. You can edit layouts of your timeline, place selectable widgets you can easily move and create your unique home as this place will be your home.
</p>
---
@ -124,8 +123,6 @@ Misskey is an open-source software licensed under the [GNU AGPLv3](LICENSE).
[agpl-3.0]: https://www.gnu.org/licenses/agpl-3.0.en.html
[agpl-3.0-badge]: https://img.shields.io/badge/license-AGPL--3.0-444444.svg?style=flat-square
[travis-link]: https://travis-ci.org/syuilo/misskey
[travis-badge]: http://img.shields.io/travis/syuilo/misskey/master.svg?style=flat-square
[dependencies-link]: https://david-dm.org/syuilo/misskey
[dependencies-badge]: https://img.shields.io/david/syuilo/misskey.svg?style=flat-square

View File

@ -22,7 +22,7 @@ adduser --disabled-password --disabled-login misskey
Please install and setup these softwares:
#### Dependencies :package:
* **[Node.js](https://nodejs.org/en/)**
* **[Node.js](https://nodejs.org/en/)** >= 10.0.0
* **[MongoDB](https://www.mongodb.com/)** >= 3.6
##### Optional

View File

@ -22,7 +22,7 @@ adduser --disabled-password --disabled-login misskey
これらのソフトウェアをインストール・設定してください:
#### 依存関係 :package:
* **[Node.js](https://nodejs.org/en/)**
* **[Node.js](https://nodejs.org/en/)** (10.0.0以上)
* **[MongoDB](https://www.mongodb.com/)** (3.6以上)
##### オプション

View File

@ -1077,10 +1077,12 @@ admin/views/instance.vue:
instance-name: "インスタンス名"
instance-description: "インスタンスの紹介"
banner-url: "バナー画像URL"
disableRegistration: "ユーザー登録の受付を停止する"
disableLocalTimeline: "ローカルタイムラインを無効にする"
max-note-text-length: "投稿の最大文字数"
disable-registration: "ユーザー登録の受付を停止する"
disable-local-timeline: "ローカルタイムラインを無効にする"
invite: "招待"
save: "保存"
saved: "保存しました"
admin/views/charts.vue:
title: "チャート"
@ -1131,10 +1133,16 @@ admin/views/emoji.vue:
aliases-desc: "スペースで区切って複数設定できます。"
url: "絵文字画像URL"
add: "追加"
info: "50KB以下のPNG画像をおすすめします。"
added: "絵文字を登録しました"
emojis:
title: "絵文字一覧"
update: "更新"
remove: "削除"
updated: "更新しました"
remove-emoji:
are-you-sure: "「$1」を削除しますか"
removed: "削除しました"
admin/views/announcements.vue:
announcements: "お知らせ"
@ -1143,6 +1151,10 @@ admin/views/announcements.vue:
add: "追加"
title: "タイトル"
text: "内容"
saved: "保存しました"
_remove:
are-you-sure: "「$1」を削除しますか"
removed: "削除しました"
admin/views/hashtags.vue:
hided-tags: "Hidden Tags"

View File

@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.38.5",
"clientVersion": "1.0.11513",
"version": "10.38.8",
"clientVersion": "1.0.11537",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,

View File

@ -10,7 +10,7 @@
<span>%i18n:@text%</span>
</ui-textarea>
<ui-horizon-group>
<ui-button @click="save">%fa:save R% %i18n:@save%</ui-button>
<ui-button @click="save()">%fa:save R% %i18n:@save%</ui-button>
<ui-button @click="remove(i)">%fa:trash-alt R% %i18n:@remove%</ui-button>
</ui-horizon-group>
</section>
@ -46,17 +46,36 @@ export default Vue.extend({
},
remove(i) {
this.announcements = this.announcements.filter((_, j) => j !== i);
this.save();
this.$swal({
type: 'warning',
text: '%i18n:@_remove.are-you-sure%'.replace('$1', this.announcements.find((_, j) => j == i).title),
showCancelButton: true
}).then(res => {
if (!res.value) return;
this.announcements = this.announcements.filter((_, j) => j !== i);
this.save(true);
this.$swal({
type: 'success',
text: '%i18n:@_remove.removed%'
});
});
},
save() {
save(silent) {
(this as any).api('admin/update-meta', {
broadcasts: this.announcements
}).then(() => {
//(this as any).os.apis.dialog({ text: `Saved` });
if (!silent) {
this.$swal({
type: 'success',
text: '%i18n:@saved%'
});
}
}).catch(e => {
//(this as any).os.apis.dialog({ text: `Failed ${e}` });
this.$swal({
type: 'error',
text: e
});
});
}
}

View File

@ -16,6 +16,7 @@
<ui-input v-model="url">
<span>%i18n:@add-emoji.url%</span>
</ui-input>
<ui-info>%i18n:@add-emoji.info%</ui-info>
<ui-button @click="add">%i18n:@add-emoji.add%</ui-button>
</section>
</ui-card>
@ -66,17 +67,24 @@ export default Vue.extend({
(this as any).api('admin/emoji/add', {
name: this.name,
url: this.url,
aliases: this.aliases.split(' ')
aliases: this.aliases.split(' ').filter(x => x.length > 0)
}).then(() => {
//(this as any).os.apis.dialog({ text: `Added` });
this.$swal({
type: 'success',
text: '%i18n:@add-emoji.added%'
});
this.fetchEmojis();
}).catch(e => {
//(this as any).os.apis.dialog({ text: `Failed ${e}` });
this.$swal({
type: 'error',
text: e
});
});
},
fetchEmojis() {
(this as any).api('admin/emoji/list').then(emojis => {
emojis.reverse();
emojis.forEach(e => e.aliases = (e.aliases || []).join(' '));
this.emojis = emojis;
});
@ -87,22 +95,42 @@ export default Vue.extend({
id: emoji.id,
name: emoji.name,
url: emoji.url,
aliases: emoji.aliases.split(' ')
aliases: emoji.aliases.split(' ').filter(x => x.length > 0)
}).then(() => {
//(this as any).os.apis.dialog({ text: `Updated` });
this.$swal({
type: 'success',
text: '%i18n:@updated%'
});
}).catch(e => {
//(this as any).os.apis.dialog({ text: `Failed ${e}` });
this.$swal({
type: 'error',
text: e
});
});
},
removeEmoji(emoji) {
(this as any).api('admin/emoji/remove', {
id: emoji.id
}).then(() => {
//(this as any).os.apis.dialog({ text: `Removed` });
this.fetchEmojis();
}).catch(e => {
//(this as any).os.apis.dialog({ text: `Failed ${e}` });
this.$swal({
type: 'warning',
text: '%i18n:@remove-emoji.are-you-sure%'.replace('$1', emoji.name),
showCancelButton: true
}).then(res => {
if (!res.value) return;
(this as any).api('admin/emoji/remove', {
id: emoji.id
}).then(() => {
this.$swal({
type: 'success',
text: '%i18n:@remove-emoji.removed%'
});
this.fetchEmojis();
}).catch(e => {
this.$swal({
type: 'error',
text: e
});
});
});
}
}

View File

@ -6,6 +6,7 @@
<ui-input v-model="name">%i18n:@instance-name%</ui-input>
<ui-textarea v-model="description">%i18n:@instance-description%</ui-textarea>
<ui-input v-model="bannerUrl">%i18n:@banner-url%</ui-input>
<ui-input v-model="maxNoteTextLength">%i18n:@max-note-text-length%</ui-input>
<ui-button @click="updateMeta">%i18n:@save%</ui-button>
</section>
</ui-card>
@ -39,6 +40,7 @@ export default Vue.extend({
bannerUrl: null,
name: null,
description: null,
maxNoteTextLength: null,
inviteCode: null,
};
},
@ -48,6 +50,7 @@ export default Vue.extend({
this.bannerUrl = meta.bannerUrl;
this.name = meta.name;
this.description = meta.description;
this.maxNoteTextLength = meta.maxNoteTextLength;
});
},
@ -56,7 +59,10 @@ export default Vue.extend({
(this as any).api('admin/invite').then(x => {
this.inviteCode = x.code;
}).catch(e => {
//(this as any).os.apis.dialog({ text: `Failed ${e}` });
this.$swal({
type: 'error',
text: e
});
});
},
@ -66,11 +72,18 @@ export default Vue.extend({
disableLocalTimeline: this.disableLocalTimeline,
bannerUrl: this.bannerUrl,
name: this.name,
description: this.description
description: this.description,
maxNoteTextLength: parseInt(this.maxNoteTextLength, 10)
}).then(() => {
//(this as any).os.apis.dialog({ text: `Saved` });
this.$swal({
type: 'success',
text: '%i18n:@saved%'
});
}).catch(e => {
//(this as any).os.apis.dialog({ text: `Failed ${e}` });
this.$swal({
type: 'error',
text: e
});
});
}
}

View File

@ -12,7 +12,11 @@
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
inject: ['horizonGrouped'],
inject: {
horizonGrouped: {
default: false
}
},
props: {
type: {
type: String,

View File

@ -41,7 +41,11 @@ import Vue from 'vue';
const getPasswordStrength = require('syuilo-password-strength');
export default Vue.extend({
inject: ['horizonGrouped'],
inject: {
horizonGrouped: {
default: false
}
},
props: {
value: {
required: false

View File

@ -222,8 +222,6 @@ class Autocomplete {
const trimmedBefore = before.substring(0, before.lastIndexOf(':'));
const after = source.substr(caret);
if (value.startsWith(':')) value = value + ' ';
// 挿入
this.text = trimmedBefore + value + after;

View File

@ -49,8 +49,6 @@ export default function load() {
if (config.localDriveCapacityMb == null) config.localDriveCapacityMb = 256;
if (config.remoteDriveCapacityMb == null) config.remoteDriveCapacityMb = 8;
if (config.maxNoteTextLength == null) config.maxNoteTextLength = 1000;
return Object.assign(config, mixin);
}

View File

@ -107,8 +107,6 @@ export type Source = {
engine: string;
timeout: number;
};
maxNoteTextLength?: number;
};
/**

View File

@ -43,4 +43,9 @@ export type IMeta = {
disableLocalTimeline?: boolean;
hidedTags?: string[];
bannerUrl?: string;
/**
* Max allowed note text length in charactors
*/
maxNoteTextLength?: number;
};

View File

@ -11,7 +11,6 @@ import Reaction from './note-reaction';
import { packMany as packFileMany, IDriveFile } from './drive-file';
import Favorite from './favorite';
import Following from './following';
import config from '../config';
import Emoji from './emoji';
const Note = db.get<INote>('notes');
@ -27,10 +26,6 @@ Note.createIndex({ createdAt: -1 });
Note.createIndex({ score: -1 }, { sparse: true });
export default Note;
export function isValidText(text: string): boolean {
return length(text.trim()) <= config.maxNoteTextLength && text.trim() != '';
}
export function isValidCw(text: string): boolean {
return length(text.trim()) <= 100;
}

View File

@ -12,15 +12,15 @@ export const meta = {
params: {
name: {
validator: $.str
validator: $.str.min(1)
},
url: {
validator: $.str
validator: $.str.min(1)
},
aliases: {
validator: $.arr($.str).optional,
validator: $.arr($.str.min(1)).optional,
default: [] as string[]
}
}

View File

@ -59,6 +59,13 @@ export const meta = {
'ja-JP': 'インスタンスの紹介文'
}
},
maxNoteTextLength: {
validator: $.num.optional.min(1),
desc: {
'ja-JP': '投稿の最大文字数'
}
}
}
};
@ -93,6 +100,10 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
set.description = ps.description;
}
if (ps.maxNoteTextLength) {
set.maxNoteTextLength = ps.maxNoteTextLength;
}
await Meta.update({}, {
$set: set
}, { upsert: true });

View File

@ -26,7 +26,7 @@ export const meta = {
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const folders = await DriveFolder
.find({
name: name,
name: ps.name,
userId: user._id,
parentId: ps.parentId
});

View File

@ -62,7 +62,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
swPublickey: config.sw ? config.sw.public_key : null,
hidedTags: (me && me.isAdmin) ? met.hidedTags : undefined,
bannerUrl: met.bannerUrl,
maxNoteTextLength: config.maxNoteTextLength,
maxNoteTextLength: met.maxNoteTextLength || 1000,
emojis: emojis,

View File

@ -1,10 +1,20 @@
import $ from 'cafy'; import ID, { transform, transformMany } from '../../../../misc/cafy-id';
const ms = require('ms');
import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
import { length } from 'stringz';
import Note, { INote, isValidCw, pack } from '../../../../models/note';
import User, { IUser } from '../../../../models/user';
import DriveFile, { IDriveFile } from '../../../../models/drive-file';
import create from '../../../../services/note/create';
import define from '../../define';
import Meta from '../../../../models/meta';
let maxNoteTextLength = 1000;
setInterval(() => {
Meta.findOne({}).then(m => {
if (m.maxNoteTextLength) maxNoteTextLength = m.maxNoteTextLength;
});
}, 3000);
export const meta = {
stability: 'stable',
@ -40,7 +50,9 @@ export const meta = {
},
text: {
validator: $.str.optional.nullable.pipe(isValidText),
validator: $.str.optional.nullable.pipe(text =>
length(text.trim()) <= maxNoteTextLength && text.trim() != ''
),
default: null as any,
desc: {
'ja-JP': '投稿内容'

View File

@ -0,0 +1,35 @@
export type IMastodonEmoji = {
shortcode: string,
url: string,
static_url: string,
visible_in_picker: boolean
};
export async function toMastodonEmojis(emoji: any): Promise<IMastodonEmoji[]> {
return [{
shortcode: emoji.name,
url: emoji.url,
static_url: emoji.url, // TODO: Implement ensuring static emoji
visible_in_picker: true
}, ...(emoji.aliases as string[] || []).map(x => ({
shortcode: x,
url: emoji.url,
static_url: emoji.url,
visible_in_picker: true
}))];
}
export function toMisskeyEmojiSync(emoji: IMastodonEmoji) {
return {
name: emoji.shortcode,
url: emoji.url
};
}
export function toMisskeyEmojiWithAliasesSync(emoji: IMastodonEmoji, ...aliases: string[]) {
return {
name: emoji.shortcode,
aliases,
url: emoji.url
};
}

View File

@ -1,15 +1,22 @@
import * as Router from 'koa-router';
import User from '../../models/user';
import User from '../../../models/user';
import { toASCII } from 'punycode';
import config from '../../config';
import Meta from '../../models/meta';
import config from '../../../config';
import Meta from '../../../models/meta';
import { ObjectID } from 'bson';
const pkg = require('../../../package.json');
import Emoji from '../../../models/emoji';
import { toMastodonEmojis } from './emoji';
const pkg = require('../../../../package.json');
// Init router
const router = new Router();
router.get('/v1/custom_emojis', async ctx => ctx.body = {});
router.get('/v1/custom_emojis', async ctx => ctx.body =
(await Emoji.find({ host: null }, {
fields: {
_id: false
}
})).map(x => toMastodonEmojis(x)));
router.get('/v1/instance', async ctx => { // TODO: This is a temporary implementation. Consider creating helper methods!
const meta = await Meta.findOne() || {};
@ -34,6 +41,11 @@ router.get('/v1/instance', async ctx => { // TODO: This is a temporary implement
notesCount: 0
};
const acct = maintainer.host ? `${maintainer.username}@${maintainer.host}` : maintainer.username;
const emojis = (await Emoji.find({ host: null }, {
fields: {
_id: false
}
})).map(toMastodonEmojis);
ctx.body = {
uri: config.hostname,
@ -73,7 +85,7 @@ router.get('/v1/instance', async ctx => { // TODO: This is a temporary implement
followers_count: maintainer.followersCount,
following_count: maintainer.followingCount,
statuses_count: maintainer.notesCount,
emojis: [],
emojis: emojis,
moved: null,
fields: null
}

View File

@ -109,7 +109,7 @@ if (!config.github || !redis) {
}
const params = {
redirect_uri: `${config.url}:8089/api/gh/cb`,
redirect_uri: `${config.url}/api/gh/cb`,
scope: ['read:user'],
state: uuid()
};
@ -122,7 +122,7 @@ if (!config.github || !redis) {
const sessid = uuid();
const params = {
redirect_uri: `${config.url}:8089/api/gh/cb`,
redirect_uri: `${config.url}/api/gh/cb`,
scope: ['read:user'],
state: uuid()
};