1
0
forked from mirror/misskey

Merge branch 'develop'

This commit is contained in:
syuilo 2019-05-05 20:33:44 +09:00
commit 1092532292
No known key found for this signature in database
GPG Key ID: BDC4C49D06AB9D69
47 changed files with 160 additions and 58 deletions

View File

@ -73,6 +73,18 @@ mongodb:
8. master ブランチに戻す 8. master ブランチに戻す
9. enjoy 9. enjoy
11.11.0 (2019/05/05)
--------------------
### Improvements
* MisskeyPagesにリストから選択関数を追加
* MisskeyPagesに確率を指定できるテキストランダム選択関数を追加
* 外部サービス連携ログインリンクにアイコン追加
### Fixes
* MisskeyPagesでifを入れ子にできなくなっていた問題を修正
* MisskeyPagesで数値入力を作成するとテキスト入力になる問題を修正
* 外部サービス連携に関する問題を修正
11.10.1 (2019/05/04) 11.10.1 (2019/05/04)
-------------------- --------------------
### Fixes ### Fixes

View File

@ -1865,8 +1865,8 @@ pages:
font: "フォント" font: "フォント"
fontSerif: "セリフ" fontSerif: "セリフ"
fontSansSerif: "サンセリフ" fontSansSerif: "サンセリフ"
set-eye-catchig-image: "アイキャッチ画像を設定" set-eye-catching-image: "アイキャッチ画像を設定"
remove-eye-catchig-image: "アイキャッチ画像を削除" remove-eye-catching-image: "アイキャッチ画像を削除"
choose-block: "ブロックを追加" choose-block: "ブロックを追加"
select-type: "種類を選択" select-type: "種類を選択"
enter-variable-name: "変数名を決めてください" enter-variable-name: "変数名を決めてください"
@ -1941,6 +1941,7 @@ pages:
fn: "関数" fn: "関数"
text: "テキスト操作" text: "テキスト操作"
convert: "変換" convert: "変換"
list: "リスト"
blocks: blocks:
text: "テキスト" text: "テキスト"
multiLineText: "テキスト(複数行)" multiLineText: "テキスト(複数行)"
@ -2059,6 +2060,13 @@ pages:
_seedRandomPick: _seedRandomPick:
arg1: "シード" arg1: "シード"
arg2: "リスト" arg2: "リスト"
DRPWPM: "確率付きリストからランダムに選択 (ユーザーごとに日替わり)"
_DRPWPM:
arg1: "テキストのリスト"
pick: "リストから選択"
_pick:
arg1: "リスト"
arg2: "位置"
number: "数値" number: "数値"
stringToNumber: "テキストを数値に" stringToNumber: "テキストを数値に"
_stringToNumber: _stringToNumber:

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "11.10.1", "version": "11.11.0",
"codename": "daybreak", "codename": "daybreak",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -34,7 +34,7 @@ body
.peg .peg
display block display block
position absolute position absolute
right 0px right 0
width 100px width 100px
height 100% height 100%
box-shadow 0 0 10px var(--primary), 0 0 5px var(--primary) box-shadow 0 0 10px var(--primary), 0 0 5px var(--primary)

View File

@ -98,7 +98,7 @@ export default Vue.extend({
margin 0 auto margin 0 auto
text-align center text-align center
background #fff background #fff
box-shadow 0px 4px 16px rgba(#000, 0.2) box-shadow 0 4px 16px rgba(#000, 0.2)
> .fetching > .fetching
margin 0 margin 0

View File

@ -52,7 +52,7 @@ function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
pattern.ctrl == e.ctrlKey && pattern.ctrl == e.ctrlKey &&
pattern.shift == e.shiftKey && pattern.shift == e.shiftKey &&
pattern.alt == e.altKey && pattern.alt == e.altKey &&
e.metaKey == false !e.metaKey
); );
} }

View File

@ -11,9 +11,9 @@ export default function(me, settings, note) {
return ( return (
(!isMyNote && note.reply && includesMutedWords(note.reply.text)) || (!isMyNote && note.reply && includesMutedWords(note.reply.text)) ||
(!isMyNote && note.renote && includesMutedWords(note.renote.text)) || (!isMyNote && note.renote && includesMutedWords(note.renote.text)) ||
(settings.showMyRenotes === false && isMyNote && isPureRenote) || (!settings.showMyRenotes && isMyNote && isPureRenote) ||
(settings.showRenotedMyNotes === false && isPureRenote && note.renote.userId == me.id) || (!settings.showRenotedMyNotes && isPureRenote && note.renote.userId == me.id) ||
(settings.showLocalRenotes === false && isPureRenote && note.renote.user.host == null) || (!settings.showLocalRenotes && isPureRenote && note.renote.user.host == null) ||
(!isMyNote && includesMutedWords(note.text)) (!isMyNote && includesMutedWords(note.text))
); );
} }

View File

@ -80,7 +80,7 @@ export default Vue.extend({
ms(): number { ms(): number {
return this.now.getMilliseconds() * this.smooth; return this.now.getMilliseconds() * this.smooth;
} },
s(): number { s(): number {
return this.now.getSeconds(); return this.now.getSeconds();
}, },

View File

@ -202,7 +202,7 @@ export default Vue.extend({
left 0 left 0
z-index 1 z-index 1
width 100% width 100%
box-shadow 0 0px 2px rgba(#000, 0.2) box-shadow 0 0 2px rgba(#000, 0.2)
> .form > .form
background rgba(0, 0, 0, 0.02) background rgba(0, 0, 0, 0.02)

View File

@ -14,7 +14,7 @@ import XImage from './els/page-editor.el.image.vue';
import XButton from './els/page-editor.el.button.vue'; import XButton from './els/page-editor.el.button.vue';
import XTextInput from './els/page-editor.el.text-input.vue'; import XTextInput from './els/page-editor.el.text-input.vue';
import XTextareaInput from './els/page-editor.el.textarea-input.vue'; import XTextareaInput from './els/page-editor.el.textarea-input.vue';
import XNumberInput from './els/page-editor.el.text-input.vue'; import XNumberInput from './els/page-editor.el.number-input.vue';
import XSwitch from './els/page-editor.el.switch.vue'; import XSwitch from './els/page-editor.el.switch.vue';
import XIf from './els/page-editor.el.if.vue'; import XIf from './els/page-editor.el.if.vue';
import XPost from './els/page-editor.el.post.vue'; import XPost from './els/page-editor.el.post.vue';

View File

@ -36,10 +36,10 @@
</ui-select> </ui-select>
<div class="eyeCatch"> <div class="eyeCatch">
<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catchig-image') }}</ui-button> <ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</ui-button>
<div v-else-if="eyeCatchingImage"> <div v-else-if="eyeCatchingImage">
<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/> <img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/>
<ui-button @click="removeEyeCatchingImage()" v-if="!readonly"><fa :icon="faTrashAlt"/> {{ $t('remove-eye-catchig-image') }}</ui-button> <ui-button @click="removeEyeCatchingImage()" v-if="!readonly"><fa :icon="faTrashAlt"/> {{ $t('remove-eye-catching-image') }}</ui-button>
</div> </div>
</div> </div>
</template> </template>
@ -337,7 +337,7 @@ export default Vue.extend({
getScriptBlockList(type: string = null) { getScriptBlockList(type: string = null) {
const list = []; const list = [];
const blocks = blockDefs.filter(block => type === null || block.out === null || block.out === type); const blocks = blockDefs.filter(block => type === null || block.out === null || block.out === type || typeof block.out === 'number');
for (const block of blocks) { for (const block of blocks) {
const category = list.find(x => x.category === block.category); const category = list.find(x => x.category === block.category);

View File

@ -54,7 +54,11 @@ export default Vue.extend({
}, },
mounted() { mounted() {
document.cookie = `i=${this.$store.state.i.token}`; if (!document.cookie.match(/i=(\w+)/)) {
document.cookie = `i=${this.$store.state.i.token}; path=/;` +
` domain=${document.location.hostname}; max-age=31536000;` +
(document.location.protocol.startsWith('https') ? ' secure' : '');
}
this.$watch('$store.state.i', () => { this.$watch('$store.state.i', () => {
if (this.$store.state.i.twitter) { if (this.$store.state.i.twitter) {
if (this.twitterForm) this.twitterForm.close(); if (this.twitterForm) this.twitterForm.close();

View File

@ -273,7 +273,7 @@ export default Vue.extend({
import_() { import_() {
(this.$refs.file as any).click(); (this.$refs.file as any).click();
} },
export_() { export_() {
const blob = new Blob([this.selectedThemeCode], { const blob = new Blob([this.selectedThemeCode], {

View File

@ -15,9 +15,9 @@
<template #prefix><fa icon="gavel"/></template> <template #prefix><fa icon="gavel"/></template>
</ui-input> </ui-input>
<ui-button type="submit" :disabled="signing">{{ signing ? $t('signing-in') : $t('@.signin') }}</ui-button> <ui-button type="submit" :disabled="signing">{{ signing ? $t('signing-in') : $t('@.signin') }}</ui-button>
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`">{{ $t('signin-with-twitter') }}</a></p> <p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`"><fa :icon="['fab', 'twitter']"/> {{ $t('signin-with-twitter') }}</a></p>
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`">{{ $t('signin-with-github') }}</a></p> <p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`"><fa :icon="['fab', 'github']"/> {{ $t('signin-with-github') }}</a></p>
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`">{{ $t('signin-with-discord') /* TODO: Make these layouts better */ }}</a></p> <p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`"><fa :icon="['fab', 'discord']"/> {{ $t('signin-with-discord') /* TODO: Make these layouts better */ }}</a></p>
</form> </form>
</template> </template>

View File

@ -322,7 +322,7 @@ root(fill)
> .value > .value
display block display block
width 0% width 0
height 100% height 100%
background transparent background transparent
border-radius 6px border-radius 6px

View File

@ -166,7 +166,7 @@ export default Vue.extend({
> .follow-button > .follow-button
position absolute position absolute
top 8px top 8px
right 0px right 0
> .more > .more
display block display block

View File

@ -160,7 +160,7 @@ export default Vue.extend({
this.$emit('top'); this.$emit('top');
} }
if (this.$store.state.settings.fetchOnScroll !== false) { if (this.$store.state.settings.fetchOnScroll) {
const current = this.$refs.body.scrollTop + this.$refs.body.clientHeight; const current = this.$refs.body.scrollTop + this.$refs.body.clientHeight;
if (current > this.$refs.body.scrollHeight - 1) this.$emit('bottom'); if (current > this.$refs.body.scrollHeight - 1) this.$emit('bottom');
} }

View File

@ -205,7 +205,7 @@ export default Vue.extend({
top -32px top -32px
left 0 left 0
right 0 right 0
width 0px width 0
margin 0 auto margin 0 auto
border-top solid 16px transparent border-top solid 16px transparent
border-left solid 16px transparent border-left solid 16px transparent

View File

@ -102,7 +102,7 @@ class Autocomplete {
} }
} }
if (isHashtag && opened == false) { if (isHashtag && !opened) {
const hashtag = text.substr(hashtagIndex + 1); const hashtag = text.substr(hashtagIndex + 1);
if (!hashtag.includes(' ')) { if (!hashtag.includes(' ')) {
this.open('hashtag', hashtag); this.open('hashtag', hashtag);
@ -110,7 +110,7 @@ class Autocomplete {
} }
} }
if (isEmoji && opened == false) { if (isEmoji && !opened) {
const emoji = text.substr(emojiIndex + 1); const emoji = text.substr(emojiIndex + 1);
if (!emoji.includes(' ')) { if (!emoji.includes(' ')) {
this.open('emoji', emoji); this.open('emoji', emoji);

View File

@ -47,7 +47,7 @@ class Script {
public interpolate(str: string) { public interpolate(str: string) {
if (str == null) return null; if (str == null) return null;
return str.replace(/\{(.+?)\}/g, match => { return str.replace(/{(.+?)}/g, match => {
const v = this.vars[match.slice(1, -1).trim()]; const v = this.vars[match.slice(1, -1).trim()];
return v == null ? 'NULL' : v.toString(); return v == null ? 'NULL' : v.toString();
}); });

View File

@ -750,12 +750,17 @@ export default Vue.extend({
bottom 0 bottom 0
animation-delay -1.0s animation-delay -1.0s
@keyframes sk-rotate { 100% { transform: rotate(360deg); }} @keyframes sk-rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes sk-bounce { @keyframes sk-bounce {
0%, 100% { 0%, 100% {
transform: scale(0.0); transform: scale(0.0);
} 50% { }
50% {
transform: scale(1.0); transform: scale(1.0);
} }
} }

View File

@ -181,7 +181,7 @@ export default Vue.extend({
this.releaseQueue(); this.releaseQueue();
} }
if (this.$store.state.settings.fetchOnScroll !== false) { if (this.$store.state.settings.fetchOnScroll) {
const current = window.scrollY + window.innerHeight; const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.fetchMore(); if (current > document.body.offsetHeight - 8) this.fetchMore();
} }

View File

@ -377,7 +377,7 @@ export default Vue.extend({
}, err => { }, err => {
this.$root.dialog({ this.$root.dialog({
type: 'error', type: 'error',
title: this.$t('error') title: this.$t('error'),
text: err.message text: err.message
}); });
}, { }, {

View File

@ -480,7 +480,7 @@ export default Vue.extend({
&:focus &:focus
&:not([data-is-modal]) &:not([data-is-modal])
> .body > .body
box-shadow 0 0 0px 1px var(--primaryAlpha05), 0 2px 12px 0 var(--desktopWindowShadow) box-shadow 0 0 0 1px var(--primaryAlpha05), 0 2px 12px 0 var(--desktopWindowShadow)
> .handle > .handle
$size = 8px $size = 8px

View File

@ -352,7 +352,7 @@ export default Vue.extend({
padding 0 16px padding 0 16px
line-height 48px line-height 48px
background var(--faceHeader) background var(--faceHeader)
box-shadow 0 1px 0px rgba(0, 0, 0, 0.1) box-shadow 0 1px 0 rgba(0, 0, 0, 0.1)
& + div & + div
max-height calc(100% - 48px) max-height calc(100% - 48px)

View File

@ -505,7 +505,7 @@ class WindowSystem extends EventEmitter {
function urlBase64ToUint8Array(base64String: string): Uint8Array { function urlBase64ToUint8Array(base64String: string): Uint8Array {
const padding = '='.repeat((4 - base64String.length % 4) % 4); const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding) const base64 = (base64String + padding)
.replace(/\-/g, '+') .replace(/-/g, '+')
.replace(/_/g, '/'); .replace(/_/g, '/');
const rawData = window.atob(base64); const rawData = window.atob(base64);

View File

@ -83,7 +83,7 @@ export default Vue.extend({
hierarchyFolders: [], hierarchyFolders: [],
selectedFiles: [], selectedFiles: [],
info: null, info: null,
connection: null connection: null,
fetching: true, fetching: true,
fetchingMoreFiles: false, fetchingMoreFiles: false,
@ -385,7 +385,7 @@ export default Vue.extend({
createFolder() { createFolder() {
this.$root.dialog({ this.$root.dialog({
title: this.$t('folder-name') title: this.$t('folder-name'),
input: { input: {
default: this.folder.name default: this.folder.name
} }
@ -415,7 +415,7 @@ export default Vue.extend({
return; return;
} }
this.$root.dialog({ this.$root.dialog({
title: this.$t('folder-name') title: this.$t('folder-name'),
input: { input: {
default: this.folder.name default: this.folder.name
} }
@ -597,12 +597,17 @@ export default Vue.extend({
bottom 0 bottom 0
animation-delay -1.0s animation-delay -1.0s
@keyframes sk-rotate { 100% { transform: rotate(360deg); }} @keyframes sk-rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes sk-bounce { @keyframes sk-bounce {
0%, 100% { 0%, 100% {
transform: scale(0.0); transform: scale(0.0);
} 50% { }
50% {
transform: scale(1.0); transform: scale(1.0);
} }
} }

View File

@ -175,7 +175,7 @@ export default Vue.extend({
this.releaseQueue(); this.releaseQueue();
} }
if (this.$store.state.settings.fetchOnScroll !== false) { if (this.$store.state.settings.fetchOnScroll) {
// display none // display none
// https://github.com/syuilo/misskey/issues/1569 // https://github.com/syuilo/misskey/issues/1569
// http://d.hatena.ne.jp/favril/20091105/1257403319 // http://d.hatena.ne.jp/favril/20091105/1257403319

View File

@ -115,7 +115,7 @@ export default Vue.extend({
}, },
onScroll() { onScroll() {
if (this.$store.state.settings.fetchOnScroll !== false) { if (this.$store.state.settings.fetchOnScroll) {
// display none // display none
// https://github.com/syuilo/misskey/issues/1569 // https://github.com/syuilo/misskey/issues/1569
// http://d.hatena.ne.jp/favril/20091105/1257403319 // http://d.hatena.ne.jp/favril/20091105/1257403319

View File

@ -295,7 +295,7 @@ export default Vue.extend({
}, err => { }, err => {
this.$root.dialog({ this.$root.dialog({
type: 'error', type: 'error',
title: this.$t('error') title: this.$t('error'),
text: err.message text: err.message
}); });
}, { }, {
@ -341,7 +341,7 @@ export default Vue.extend({
post() { post() {
this.posting = true; this.posting = true;
const viaMobile = this.$store.state.settings.disableViaMobile !== true; const viaMobile = !this.$store.state.settings.disableViaMobile;
this.$root.api('notes/create', { this.$root.api('notes/create', {
text: this.text == '' ? undefined : this.text, text: this.text == '' ? undefined : this.text,
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,

View File

@ -49,7 +49,7 @@ export default Vue.extend({
padding 0 8px padding 0 8px
&.shadow &.shadow
box-shadow 0 0px 8px rgba(0, 0, 0, 0.25) box-shadow 0 0 8px rgba(0, 0, 0, 0.25)
&, * &, *
user-select none user-select none

View File

@ -1,5 +1,5 @@
input input
min-width 0px min-width 0
input:not([type]) input:not([type])
input[type='text'] input[type='text']

View File

@ -126,7 +126,7 @@ export default (os: MiOS) => new Vuex.Store({
logout(ctx) { logout(ctx) {
ctx.commit('updateI', null); ctx.commit('updateI', null);
document.cookie = 'i=;'; document.cookie = `i=; max-age=0; domain=${document.location.hostname}`;
localStorage.removeItem('i'); localStorage.removeItem('i');
}, },

View File

@ -129,7 +129,7 @@ export const mfmLanguage = P.createLanguage({
mention: () => { mention: () => {
return P((input, i) => { return P((input, i) => {
const text = input.substr(i); const text = input.substr(i);
const match = text.match(/^@\w([\w-]*\w)?(?:@[\w\.\-]+\w)?/); const match = text.match(/^@\w([\w-]*\w)?(?:@[\w.\-]+\w)?/);
if (!match) return P.makeFailure(i, 'not a mention'); if (!match) return P.makeFailure(i, 'not a mention');
if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a mention'); if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a mention');
return P.makeSuccess(i + match[0].length, match[0]); return P.makeSuccess(i + match[0].length, match[0]);
@ -141,7 +141,7 @@ export const mfmLanguage = P.createLanguage({
}, },
hashtag: () => P((input, i) => { hashtag: () => P((input, i) => {
const text = input.substr(i); const text = input.substr(i);
const match = text.match(/^#([^\s\.,!\?'"#:\/\[\]【】]+)/i); const match = text.match(/^#([^\s.,!?'"#:\/\[\]【】]+)/i);
if (!match) return P.makeFailure(i, 'not a hashtag'); if (!match) return P.makeFailure(i, 'not a hashtag');
let hashtag = match[1]; let hashtag = match[1];
hashtag = removeOrphanedBrackets(hashtag); hashtag = removeOrphanedBrackets(hashtag);

View File

@ -36,4 +36,4 @@ export function createTree(type: string, children: MfmForest, props: any): MfmTr
return T.createTree({ type, props }, children); return T.createTree({ type, props }, children);
} }
export const urlRegex = /^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/; export const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;

View File

@ -64,7 +64,7 @@ export class ASEvaluator {
@autobind @autobind
private interpolate(str: string, scope: Scope) { private interpolate(str: string, scope: Scope) {
return str.replace(/\{(.+?)\}/g, match => { return str.replace(/{(.+?)}/g, match => {
const v = scope.getState(match.slice(1, -1).trim()); const v = scope.getState(match.slice(1, -1).trim());
return v == null ? 'NULL' : v.toString(); return v == null ? 'NULL' : v.toString();
}); });
@ -169,6 +169,7 @@ export class ASEvaluator {
stringToNumber: (a: string) => parseInt(a), stringToNumber: (a: string) => parseInt(a),
numberToString: (a: number) => a.toString(), numberToString: (a: number) => a.toString(),
splitStrByLine: (a: string) => a.split('\n'), splitStrByLine: (a: string) => a.split('\n'),
pick: (list: any[], i: number) => list[i - 1],
random: (probability: number) => Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * 100) < probability, random: (probability: number) => Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * 100) < probability,
rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * (max - min + 1)), rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * (max - min + 1)),
randomPick: (list: any[]) => list[Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * list.length)], randomPick: (list: any[]) => list[Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * list.length)],
@ -178,6 +179,27 @@ export class ASEvaluator {
seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability, seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability,
seedRannum: (seed: any, min: number, max: number) => min + Math.floor(seedrandom(seed)() * (max - min + 1)), seedRannum: (seed: any, min: number, max: number) => min + Math.floor(seedrandom(seed)() * (max - min + 1)),
seedRandomPick: (seed: any, list: any[]) => list[Math.floor(seedrandom(seed)() * list.length)], seedRandomPick: (seed: any, list: any[]) => list[Math.floor(seedrandom(seed)() * list.length)],
DRPWPM: (list: string[]) => {
const xs = [];
let totalFactor = 0;
for (const x of list) {
const parts = x.split(' ');
const factor = parseInt(parts.pop()!, 10);
const text = parts.join(' ');
totalFactor += factor;
xs.push({ factor, text });
}
const r = seedrandom(`${day}:${block.id}`)() * totalFactor;
let stackedFactor = 0;
for (const x of xs) {
if (r >= stackedFactor && r <= x.factor) {
return x.text;
} else {
stackedFactor += x.factor;
}
}
return xs[0].text;
},
}; };
const fnName = block.type; const fnName = block.type;

View File

@ -23,6 +23,7 @@ import {
faSortNumericUp, faSortNumericUp,
faExchangeAlt, faExchangeAlt,
faRecycle, faRecycle,
faIndent,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { faFlag } from '@fortawesome/free-regular-svg-icons'; import { faFlag } from '@fortawesome/free-regular-svg-icons';
@ -72,6 +73,7 @@ export const funcDefs: Record<string, { in: any[]; out: any; category: string; i
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: faExchangeAlt, }, stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: faExchangeAlt, },
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: faExchangeAlt, }, numberToString: { in: ['number'], out: 'string', category: 'convert', icon: faExchangeAlt, },
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: faExchangeAlt, }, splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: faExchangeAlt, },
pick: { in: [null], out: null, category: 'list', icon: faIndent, },
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: faDice, }, rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: faDice, },
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: faDice, }, dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: faDice, },
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: faDice, }, seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: faDice, },
@ -81,6 +83,7 @@ export const funcDefs: Record<string, { in: any[]; out: any; category: string; i
randomPick: { in: [0], out: 0, category: 'random', icon: faDice, }, randomPick: { in: [0], out: 0, category: 'random', icon: faDice, },
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: faDice, }, dailyRandomPick: { in: [0], out: 0, category: 'random', icon: faDice, },
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: faDice, }, seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: faDice, },
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: faDice, }, // dailyRandomPickWithProbabilityMapping
}; };
export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = { export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {

View File

@ -1,6 +1,7 @@
import { ulid } from 'ulid'; import { ulid } from 'ulid';
import { genAid } from './id/aid'; import { genAid } from './id/aid';
import { genMeid } from './id/meid'; import { genMeid } from './id/meid';
import { genMeidg } from './id/meidg';
import { genObjectId } from './id/object-id'; import { genObjectId } from './id/object-id';
import config from '../config'; import config from '../config';
@ -12,6 +13,7 @@ export function genId(date?: Date): string {
switch (metohd) { switch (metohd) {
case 'aid': return genAid(date); case 'aid': return genAid(date);
case 'meid': return genMeid(date); case 'meid': return genMeid(date);
case 'meidg': return genMeidg(date);
case 'ulid': return ulid(date.getTime()); case 'ulid': return ulid(date.getTime());
case 'objectid': return genObjectId(date); case 'objectid': return genObjectId(date);
default: throw new Error('unknown id generation method'); default: throw new Error('unknown id generation method');

28
src/misc/id/meidg.ts Normal file
View File

@ -0,0 +1,28 @@
const CHARS = '0123456789abcdef';
// 4bit Fixed hex value 'g'
// 44bit UNIX Time ms in Hex
// 48bit Random value in Hex
function getTime(time: number) {
if (time < 0) time = 0;
if (time === 0) {
return CHARS[0];
}
return time.toString(16).padStart(11, CHARS[0]);
}
function getRandom() {
let str = '';
for (let i = 0; i < 12; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genMeidg(date: Date): string {
return 'g' + getTime(date.getTime()) + getRandom();
}

View File

@ -26,7 +26,7 @@ export class UserProfile {
public birthday: string | null; public birthday: string | null;
@Column('varchar', { @Column('varchar', {
length: 1024, nullable: true, length: 2048, nullable: true,
comment: 'The description (bio) of the User.' comment: 'The description (bio) of the User.'
}) })
public description: string | null; public description: string | null;

View File

@ -128,6 +128,19 @@ export class UserRepository extends Repository<User> {
detail: true detail: true
}), }),
twoFactorEnabled: profile!.twoFactorEnabled, twoFactorEnabled: profile!.twoFactorEnabled,
twitter: profile!.twitter ? {
id: profile!.twitterUserId,
screenName: profile!.twitterScreenName
} : null,
github: profile!.github ? {
id: profile!.githubId,
login: profile!.githubLogin
} : null,
discord: profile!.discord ? {
id: profile!.discordId,
username: profile!.discordUsername,
discriminator: profile!.discordDiscriminator
} : null,
} : {}), } : {}),
...(opts.detail && meId === user.id ? { ...(opts.detail && meId === user.id ? {
@ -217,7 +230,7 @@ export class UserRepository extends Repository<User> {
} }
public isValidBirthday(birthday: string): boolean { public isValidBirthday(birthday: string): boolean {
return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday); return typeof birthday == 'string' && /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.test(birthday);
} }
//#endregion //#endregion
} }

View File

@ -37,7 +37,7 @@ export const meta = {
}, },
type: { type: {
validator: $.optional.str.match(/^[a-zA-Z\/\-\*]+$/) validator: $.optional.str.match(/^[a-zA-Z\/\-*]+$/)
} }
}, },

View File

@ -27,7 +27,7 @@ export const meta = {
}, },
type: { type: {
validator: $.optional.str.match(/^[a-zA-Z\/\-\*]+$/) validator: $.optional.str.match(/^[a-zA-Z\/\-*]+$/)
} }
}, },

View File

@ -52,7 +52,7 @@ for (const endpoint of endpoints) {
} else { } else {
if (endpoint.name.includes('-')) { if (endpoint.name.includes('-')) {
// 後方互換性のため // 後方互換性のため
router.post(`/${endpoint.name.replace(/\-/g, '_')}`, handler.bind(null, endpoint)); router.post(`/${endpoint.name.replace(/-/g, '_')}`, handler.bind(null, endpoint));
} }
router.post(`/${endpoint.name}`, handler.bind(null, endpoint)); router.post(`/${endpoint.name}`, handler.bind(null, endpoint));
} }

View File

@ -18,7 +18,7 @@ export function convertSchemaToOpenApiSchema(schema: Schema) {
const res: any = schema; const res: any = schema;
if (schema.type === 'object' && schema.properties) { if (schema.type === 'object' && schema.properties) {
res.required = Object.entries(schema.properties).filter(([k, v]) => v.optional !== true).map(([k]) => k); res.required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
for (const k of Object.keys(schema.properties)) { for (const k of Object.keys(schema.properties)) {
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]); res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);

View File

@ -34,7 +34,7 @@
"radix": false, "radix": false,
"ban-types": [ "ban-types": [
true, true,
"Object" ["Object", "Use {} instead."]
], ],
"ban": [ "ban": [
true, true,

View File

@ -95,7 +95,7 @@ module.exports = {
loader: 'css-loader' loader: 'css-loader'
}, postcss] }, postcss]
}, { }, {
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/, test: /\.(eot|woff|woff2|svg|ttf)([?]?.*)$/,
loader: 'url-loader' loader: 'url-loader'
}, { }, {
test: /\.json5$/, test: /\.json5$/,