Merge tag '13.8.1' into io
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
|
||||
import { utils, values } from '@syuilo/aiscript';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { ref, Ref } from 'vue';
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { defineAsyncComponent, Ref, inject } from 'vue';
|
||||
import { defineAsyncComponent, Ref } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { pleaseLogin } from './please-login';
|
||||
import { claimAchievement } from './achievements';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -9,8 +8,8 @@ import * as os from '@/os';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import { url } from '@/config';
|
||||
import { noteActions } from '@/store';
|
||||
import { notePage } from '@/filters/note';
|
||||
import { miLocalStorage } from '@/local-storage';
|
||||
import { getUserMenu } from '@/scripts/get-user-menu';
|
||||
|
||||
export function getNoteMenu(props: {
|
||||
note: misskey.entities.Note;
|
||||
@@ -101,66 +100,6 @@ export function getNoteMenu(props: {
|
||||
});
|
||||
}
|
||||
|
||||
async function clip(): Promise<void> {
|
||||
const clips = await os.api('clips/list');
|
||||
os.popupMenu([{
|
||||
icon: 'ti ti-plus',
|
||||
text: i18n.ts.createNew,
|
||||
action: async () => {
|
||||
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
|
||||
name: {
|
||||
type: 'string',
|
||||
label: i18n.ts.name,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
multiline: true,
|
||||
label: i18n.ts.description,
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
label: i18n.ts.public,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const clip = await os.apiWithDialog('clips/create', result);
|
||||
|
||||
claimAchievement('noteClipped1');
|
||||
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
|
||||
},
|
||||
}, null, ...clips.map(clip => ({
|
||||
text: clip.name,
|
||||
action: () => {
|
||||
claimAchievement('noteClipped1');
|
||||
os.promiseDialog(
|
||||
os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
|
||||
null,
|
||||
async (err) => {
|
||||
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
|
||||
});
|
||||
if (!confirm.canceled) {
|
||||
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
|
||||
if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
|
||||
}
|
||||
} else {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.message + '\n' + err.id,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
}))], props.menuButton.value, {
|
||||
}).then(focus);
|
||||
}
|
||||
|
||||
async function unclip(): Promise<void> {
|
||||
os.apiWithDialog('clips/remove-note', { clipId: props.currentClipPage.value.id, noteId: appearNote.id });
|
||||
props.isDeleted.value = true;
|
||||
@@ -202,7 +141,7 @@ export function getNoteMenu(props: {
|
||||
props.translating.value = true;
|
||||
const res = await os.api('notes/translate', {
|
||||
noteId: appearNote.id,
|
||||
targetLang: miLocalStorage.getItem('lang') || navigator.language,
|
||||
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
|
||||
});
|
||||
props.translating.value = false;
|
||||
props.translation.value = res;
|
||||
@@ -242,7 +181,7 @@ export function getNoteMenu(props: {
|
||||
icon: 'ti ti-external-link',
|
||||
text: i18n.ts.showOnRemote,
|
||||
action: () => {
|
||||
window.open(appearNote.url || appearNote.uri, '_blank');
|
||||
window.open(appearNote.url ?? appearNote.uri, '_blank');
|
||||
},
|
||||
} : undefined,
|
||||
{
|
||||
@@ -266,9 +205,67 @@ export function getNoteMenu(props: {
|
||||
action: () => toggleFavorite(true),
|
||||
}),
|
||||
{
|
||||
type: 'parent',
|
||||
icon: 'ti ti-paperclip',
|
||||
text: i18n.ts.clip,
|
||||
action: () => clip(),
|
||||
children: async () => {
|
||||
const clips = await os.api('clips/list');
|
||||
return [{
|
||||
icon: 'ti ti-plus',
|
||||
text: i18n.ts.createNew,
|
||||
action: async () => {
|
||||
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
|
||||
name: {
|
||||
type: 'string',
|
||||
label: i18n.ts.name,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
multiline: true,
|
||||
label: i18n.ts.description,
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
label: i18n.ts.public,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const clip = await os.apiWithDialog('clips/create', result);
|
||||
|
||||
claimAchievement('noteClipped1');
|
||||
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
|
||||
},
|
||||
}, null, ...clips.map(clip => ({
|
||||
text: clip.name,
|
||||
action: () => {
|
||||
claimAchievement('noteClipped1');
|
||||
os.promiseDialog(
|
||||
os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
|
||||
null,
|
||||
async (err) => {
|
||||
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
|
||||
});
|
||||
if (!confirm.canceled) {
|
||||
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
|
||||
if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
|
||||
}
|
||||
} else {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.message + '\n' + err.id,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
}))];
|
||||
},
|
||||
},
|
||||
statePromise.then(state => state.isMutedThread ? {
|
||||
icon: 'ti ti-message-off',
|
||||
@@ -288,11 +285,20 @@ export function getNoteMenu(props: {
|
||||
text: i18n.ts.pin,
|
||||
action: () => togglePin(true),
|
||||
} : undefined,
|
||||
appearNote.userId !== $i.id ? {
|
||||
type: 'parent',
|
||||
icon: 'ti ti-user',
|
||||
text: i18n.ts.user,
|
||||
children: async () => {
|
||||
const user = await os.api('users/show', { userId: appearNote.userId });
|
||||
return getUserMenu(user);
|
||||
},
|
||||
} : undefined,
|
||||
/*
|
||||
...($i.isModerator || $i.isAdmin ? [
|
||||
null,
|
||||
{
|
||||
icon: 'fas fa-bullhorn',
|
||||
icon: 'ti ti-speakerphone',
|
||||
text: i18n.ts.promote,
|
||||
action: promote
|
||||
}]
|
||||
@@ -304,7 +310,7 @@ export function getNoteMenu(props: {
|
||||
icon: 'ti ti-exclamation-circle',
|
||||
text: i18n.ts.reportAbuse,
|
||||
action: () => {
|
||||
const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`;
|
||||
const u = appearNote.url ?? appearNote.uri ?? `${url}/notes/${appearNote.id}`;
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
|
||||
user: appearNote.user,
|
||||
initialComment: `Note: ${u}\n-----\n`,
|
||||
@@ -346,7 +352,7 @@ export function getNoteMenu(props: {
|
||||
icon: 'ti ti-external-link',
|
||||
text: i18n.ts.showOnRemote,
|
||||
action: () => {
|
||||
window.open(appearNote.url || appearNote.uri, '_blank');
|
||||
window.open(appearNote.url ?? appearNote.uri, '_blank');
|
||||
},
|
||||
} : undefined]
|
||||
.filter(x => x !== undefined);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as Acct from 'misskey-js/built/acct';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { i18n } from '@/i18n';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import { host } from '@/config';
|
||||
@@ -9,54 +9,9 @@ import { $i, iAmModerator } from '@/account';
|
||||
import { mainRouter } from '@/router';
|
||||
import { Router } from '@/nirax';
|
||||
|
||||
export function getUserMenu(user, router: Router = mainRouter) {
|
||||
export function getUserMenu(user: misskey.entities.UserDetailed, router: Router = mainRouter) {
|
||||
const meId = $i ? $i.id : null;
|
||||
|
||||
async function pushList() {
|
||||
const t = i18n.ts.selectList; // なぜか後で参照すると null になるので最初にメモリに確保しておく
|
||||
const lists = await os.api('users/lists/list');
|
||||
if (lists.length === 0) {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts.youHaveNoLists,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { canceled, result: listId } = await os.select({
|
||||
title: t,
|
||||
items: lists.map(list => ({
|
||||
value: list.id, text: list.name,
|
||||
})),
|
||||
});
|
||||
if (canceled) return;
|
||||
os.apiWithDialog('users/lists/push', {
|
||||
listId: listId,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function inviteGroup() {
|
||||
const groups = await os.api('users/groups/owned');
|
||||
if (groups.length === 0) {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts.youHaveNoGroups,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { canceled, result: groupId } = await os.select({
|
||||
title: i18n.ts.group,
|
||||
items: groups.map(group => ({
|
||||
value: group.id, text: group.name,
|
||||
})),
|
||||
});
|
||||
if (canceled) return;
|
||||
os.apiWithDialog('users/groups/invite', {
|
||||
groupId: groupId,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleMute() {
|
||||
if (user.isMuted) {
|
||||
os.apiWithDialog('mute/delete', {
|
||||
@@ -125,6 +80,8 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||
}
|
||||
|
||||
async function invalidateFollow() {
|
||||
if (!await getConfirmed(i18n.ts.breakFollowConfirm)) return;
|
||||
|
||||
os.apiWithDialog('following/invalidate', {
|
||||
userId: user.id,
|
||||
}).then(() => {
|
||||
@@ -136,7 +93,7 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||
icon: 'ti ti-at',
|
||||
text: i18n.ts.copyUsername,
|
||||
action: () => {
|
||||
copyToClipboard(`@${user.username}@${user.host || host}`);
|
||||
copyToClipboard(`@${user.username}@${user.host ?? host}`);
|
||||
},
|
||||
}, {
|
||||
icon: 'ti ti-info-circle',
|
||||
@@ -156,22 +113,44 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||
action: () => {
|
||||
os.post({ specified: user });
|
||||
},
|
||||
}, meId !== user.id ? {
|
||||
type: 'link',
|
||||
icon: 'ti ti-messages',
|
||||
text: i18n.ts.startMessaging,
|
||||
to: '/my/messaging/' + Acct.toString(user),
|
||||
} : undefined, null, {
|
||||
}, null, {
|
||||
type: 'parent',
|
||||
icon: 'ti ti-list',
|
||||
text: i18n.ts.addToList,
|
||||
action: pushList,
|
||||
}, meId !== user.id ? {
|
||||
icon: 'ti ti-users',
|
||||
text: i18n.ts.inviteToGroup,
|
||||
action: inviteGroup,
|
||||
} : undefined] as any;
|
||||
children: async () => {
|
||||
const lists = await os.api('users/lists/list');
|
||||
|
||||
return lists.map(list => ({
|
||||
text: list.name,
|
||||
action: () => {
|
||||
os.apiWithDialog('users/lists/push', {
|
||||
listId: list.id,
|
||||
userId: user.id,
|
||||
});
|
||||
},
|
||||
}));
|
||||
},
|
||||
}] as any;
|
||||
|
||||
if ($i && meId !== user.id) {
|
||||
if (iAmModerator) {
|
||||
menu = menu.concat([{
|
||||
type: 'parent',
|
||||
icon: 'ti ti-badges',
|
||||
text: i18n.ts.roles,
|
||||
children: async () => {
|
||||
const roles = await os.api('admin/roles/list');
|
||||
|
||||
return roles.filter(r => r.target === 'manual').map(r => ({
|
||||
text: r.name,
|
||||
action: () => {
|
||||
os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id });
|
||||
},
|
||||
}));
|
||||
},
|
||||
}]);
|
||||
}
|
||||
|
||||
menu = menu.concat([null, {
|
||||
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
|
||||
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
|
||||
@@ -195,30 +174,6 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||
text: i18n.ts.reportAbuse,
|
||||
action: reportAbuse,
|
||||
}]);
|
||||
|
||||
if (iAmModerator) {
|
||||
menu = menu.concat([null, {
|
||||
icon: 'ti ti-user-exclamation',
|
||||
text: i18n.ts.moderation,
|
||||
action: () => {
|
||||
router.push('/user-info/' + user.id + '#moderation');
|
||||
},
|
||||
}, {
|
||||
icon: 'ti ti-badges',
|
||||
text: i18n.ts.roles,
|
||||
action: async () => {
|
||||
const roles = await os.api('admin/roles/list');
|
||||
|
||||
const { canceled, result: roleId } = await os.select({
|
||||
title: i18n.ts._role.chooseRoleToAssign,
|
||||
items: roles.map(r => ({ text: r.name, value: r.id })),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.id });
|
||||
},
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($i && meId === user.id) {
|
||||
|
@@ -1,3 +1,3 @@
|
||||
export default function(user: { name?: string | null, username: string }): string {
|
||||
return user.name || user.username;
|
||||
return user.name === '' ? user.username : user.name ?? user.username;
|
||||
}
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import { markRaw, ref, Ref, unref } from 'vue';
|
||||
import { ref, Ref, unref } from 'vue';
|
||||
import { collectPageVars } from '../collect-page-vars';
|
||||
import { initHpmlLib, initAiLib } from './lib';
|
||||
import { initHpmlLib } from './lib';
|
||||
import { Expr, isLiteralValue, Variable } from './expr';
|
||||
import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.';
|
||||
import { version } from '@/config';
|
||||
import * as os from '@/os';
|
||||
|
||||
/**
|
||||
* Hpml evaluator
|
||||
|
@@ -15,12 +15,12 @@ export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
|
||||
|
||||
export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
|
||||
text: { out: 'string', category: 'value', icon: 'ti ti-quote' },
|
||||
multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left' },
|
||||
textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list' },
|
||||
number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up' },
|
||||
ref: { out: null, category: 'value', icon: 'fas fa-magic' },
|
||||
aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic' },
|
||||
fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt' },
|
||||
multiLineText: { out: 'string', category: 'value', icon: 'ti ti-align-left' },
|
||||
textList: { out: 'stringArray', category: 'value', icon: 'ti ti-list' },
|
||||
number: { out: 'number', category: 'value', icon: 'ti ti-list-numbers' },
|
||||
ref: { out: null, category: 'value', icon: 'ti ti-wand' },
|
||||
aiScriptVar: { out: null, category: 'value', icon: 'ti ti-wand' },
|
||||
fn: { out: 'function', category: 'value', icon: 'ti ti-math-function' },
|
||||
};
|
||||
|
||||
export const blockDefs = [
|
||||
@@ -58,7 +58,7 @@ export class HpmlScope {
|
||||
|
||||
constructor(layerdStates: HpmlScope['layerdStates'], name?: HpmlScope['name']) {
|
||||
this.layerdStates = layerdStates;
|
||||
this.name = name || 'anonymous';
|
||||
this.name = name ?? 'anonymous';
|
||||
}
|
||||
|
||||
@autobind
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
import seedrandom from 'seedrandom';
|
||||
import { Hpml } from './evaluator';
|
||||
import { Expr } from './expr';
|
||||
@@ -130,42 +129,42 @@ export function initAiLib(hpml: Hpml) {
|
||||
|
||||
export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
|
||||
if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'ti ti-share' },
|
||||
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle' },
|
||||
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
|
||||
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
|
||||
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
|
||||
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'ti ti-recycle' },
|
||||
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
|
||||
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
|
||||
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
|
||||
add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-plus' },
|
||||
subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-minus' },
|
||||
multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-x' },
|
||||
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
|
||||
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
|
||||
round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator' },
|
||||
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals' },
|
||||
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal' },
|
||||
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than' },
|
||||
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than' },
|
||||
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal' },
|
||||
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal' },
|
||||
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' },
|
||||
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' },
|
||||
round: { in: ['number'], out: 'number', category: 'operation', icon: 'ti ti-calculator' },
|
||||
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal' },
|
||||
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal-not' },
|
||||
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-greater' },
|
||||
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-lower' },
|
||||
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-greater' },
|
||||
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-lower' },
|
||||
strLen: { in: ['string'], out: 'number', category: 'text', icon: 'ti ti-quote' },
|
||||
strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'ti ti-quote' },
|
||||
strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
|
||||
strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
|
||||
join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
|
||||
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt' },
|
||||
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt' },
|
||||
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt' },
|
||||
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent' },
|
||||
listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent' },
|
||||
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
|
||||
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
|
||||
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
|
||||
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
|
||||
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
|
||||
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
|
||||
randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
|
||||
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
|
||||
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice' },
|
||||
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice' }, // dailyRandomPickWithProbabilityMapping
|
||||
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'ti ti-arrows-right-left' },
|
||||
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'ti ti-arrows-right-left' },
|
||||
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'ti ti-arrows-right-left' },
|
||||
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'ti ti-indent-increase' },
|
||||
listLen: { in: [null], out: 'number', category: 'list', icon: 'ti ti-indent-increase' },
|
||||
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
|
||||
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
|
||||
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
|
||||
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
|
||||
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
|
||||
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
|
||||
randomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' },
|
||||
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' },
|
||||
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'ti ti-dice' },
|
||||
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'ti ti-dice' }, // dailyRandomPickWithProbabilityMapping
|
||||
};
|
||||
|
||||
export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) {
|
||||
|
@@ -63,7 +63,7 @@ export class HpmlTypeChecker {
|
||||
|
||||
@autobind
|
||||
public getExpectedType(v: Expr, slot: number): Type {
|
||||
const def = funcDefs[v.type || ''];
|
||||
const def = funcDefs[v.type ?? ''];
|
||||
if (def == null) {
|
||||
throw new Error('Unknown type: ' + v.type);
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export class HpmlTypeChecker {
|
||||
return pageVar.type;
|
||||
}
|
||||
|
||||
const envVar = envVarsDef[v.value || ''];
|
||||
const envVar = envVarsDef[v.value ?? ''];
|
||||
if (envVar !== undefined) {
|
||||
return envVar;
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { Ref } from 'vue';
|
||||
|
||||
export function calcPopupPosition(el: HTMLElement, props: {
|
||||
anchorElement: HTMLElement | null;
|
||||
|
@@ -10,7 +10,7 @@ export function getScrollContainer(el: HTMLElement | null): HTMLElement | null {
|
||||
}
|
||||
}
|
||||
|
||||
export function getStickyTop(el: HTMLElement, container: HTMLElement | null = null, top: number = 0) {
|
||||
export function getStickyTop(el: HTMLElement, container: HTMLElement | null = null, top = 0) {
|
||||
if (!el.parentElement) return top;
|
||||
const data = el.dataset.stickyContainerHeaderHeight;
|
||||
const newTop = data ? Number(data) + top : top;
|
||||
@@ -23,14 +23,14 @@ export function getScrollPosition(el: HTMLElement | null): number {
|
||||
return container == null ? window.scrollY : container.scrollTop;
|
||||
}
|
||||
|
||||
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) {
|
||||
export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
|
||||
// とりあえず評価してみる
|
||||
if (isTopVisible(el)) {
|
||||
cb();
|
||||
if (once) return null;
|
||||
}
|
||||
|
||||
const container = getScrollContainer(el) || window;
|
||||
const container = getScrollContainer(el) ?? window;
|
||||
|
||||
const onScroll = ev => {
|
||||
if (!document.body.contains(el)) return;
|
||||
@@ -45,7 +45,7 @@ export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance: numbe
|
||||
return removeListener;
|
||||
}
|
||||
|
||||
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) {
|
||||
export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
|
||||
const container = getScrollContainer(el);
|
||||
|
||||
// とりあえず評価してみる
|
||||
@@ -54,7 +54,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance: nu
|
||||
if (once) return null;
|
||||
}
|
||||
|
||||
const containerOrWindow = container || window;
|
||||
const containerOrWindow = container ?? window;
|
||||
const onScroll = ev => {
|
||||
if (!document.body.contains(el)) return;
|
||||
if (isBottomVisible(el, 1, container)) {
|
||||
@@ -104,12 +104,12 @@ export function scrollToBottom(
|
||||
} else {
|
||||
window.scroll({
|
||||
top: (el.scrollHeight - window.innerHeight + getStickyTop(el, container) + (window.innerWidth <= 500 ? 96 : 0)) || 0,
|
||||
...options
|
||||
...options,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function isTopVisible(el: HTMLElement, tolerance: number = 1): boolean {
|
||||
export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
|
||||
const scrollTop = getScrollPosition(el);
|
||||
return scrollTop <= tolerance;
|
||||
}
|
||||
@@ -124,6 +124,6 @@ export function getBodyScrollHeight() {
|
||||
return Math.max(
|
||||
document.body.scrollHeight, document.documentElement.scrollHeight,
|
||||
document.body.offsetHeight, document.documentElement.offsetHeight,
|
||||
document.body.clientHeight, document.documentElement.clientHeight
|
||||
document.body.clientHeight, document.documentElement.clientHeight,
|
||||
);
|
||||
}
|
||||
|
@@ -1,63 +0,0 @@
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { mainRouter } from '@/router';
|
||||
|
||||
export async function search() {
|
||||
const { canceled, result: query } = await os.inputText({
|
||||
title: i18n.ts.search,
|
||||
});
|
||||
if (canceled || query == null || query === '') return;
|
||||
|
||||
const q = query.trim();
|
||||
|
||||
if (q.startsWith('@') && !q.includes(' ')) {
|
||||
mainRouter.push(`/${q}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (q.startsWith('#')) {
|
||||
mainRouter.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// like 2018/03/12
|
||||
if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) {
|
||||
const date = new Date(q.replace(/-/g, '/'));
|
||||
|
||||
// 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは
|
||||
// 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので
|
||||
// 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の
|
||||
// 結果になってしまい、2018/03/12 のコンテンツは含まれない)
|
||||
if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) {
|
||||
date.setHours(23, 59, 59, 999);
|
||||
}
|
||||
|
||||
// TODO
|
||||
//v.$root.$emit('warp', date);
|
||||
os.alert({
|
||||
icon: 'fas fa-history',
|
||||
iconOnly: true, autoClose: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (q.startsWith('https://')) {
|
||||
const promise = os.api('ap/show', {
|
||||
uri: q,
|
||||
});
|
||||
|
||||
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
||||
|
||||
const res = await promise;
|
||||
|
||||
if (res.type === 'User') {
|
||||
mainRouter.push(`/@${res.object.username}@${res.object.host}`);
|
||||
} else if (res.type === 'Note') {
|
||||
mainRouter.push(`/notes/${res.object.id}`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mainRouter.push(`/search?q=${encodeURIComponent(q)}`);
|
||||
}
|
19
packages/frontend/src/scripts/use-document-visibility.ts
Normal file
19
packages/frontend/src/scripts/use-document-visibility.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { onMounted, onUnmounted, ref, Ref } from 'vue';
|
||||
|
||||
export function useDocumentVisibility(): Ref<DocumentVisibilityState> {
|
||||
const visibility = ref(document.visibilityState);
|
||||
|
||||
const onChange = (): void => {
|
||||
visibility.value = document.visibilityState;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('visibilitychange', onChange);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('visibilitychange', onChange);
|
||||
});
|
||||
|
||||
return visibility;
|
||||
}
|
@@ -1,6 +1,4 @@
|
||||
import { inject, onUnmounted, Ref } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import * as os from '@/os';
|
||||
import { Ref } from 'vue';
|
||||
|
||||
export function useLeaveGuard(enabled: Ref<boolean>) {
|
||||
/* TODO
|
||||
|
Reference in New Issue
Block a user