Merge branch 'develop' into better-8176
This commit is contained in:
commit
e87e97fce4
102 changed files with 1475 additions and 1295 deletions
|
@ -18,6 +18,7 @@ module.exports = {
|
|||
// data の禁止理由: 抽象的すぎるため
|
||||
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
|
||||
"id-denylist": ["error", "window", "data", "e"],
|
||||
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
|
||||
"vue/attributes-order": ["error", {
|
||||
"alphabetical": false
|
||||
}],
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
"dependencies": {
|
||||
"@discordapp/twemoji": "13.1.0",
|
||||
"@syuilo/aiscript": "0.11.1",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/glob": "7.2.0",
|
||||
"@types/gulp": "4.0.9",
|
||||
|
|
51
packages/client/src/components/chart-tooltip.vue
Normal file
51
packages/client/src/components/chart-tooltip.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :max-width="340" @closed="emit('closed')">
|
||||
<div v-if="title" class="qpcyisrl">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div v-for="x in series" class="series">
|
||||
<span class="color" :style="{ background: x.backgroundColor, borderColor: x.borderColor }"></span>
|
||||
<span>{{ x.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</MkTooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkTooltip from './ui/tooltip.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
showing: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
title: string;
|
||||
series: {
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
text: string;
|
||||
}[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.qpcyisrl {
|
||||
> .title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
> .series {
|
||||
> .color {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -8,7 +8,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, watch, PropType } from 'vue';
|
||||
import { defineComponent, onMounted, ref, watch, PropType, onUnmounted, shallowRef } from 'vue';
|
||||
import {
|
||||
Chart,
|
||||
ArcElement,
|
||||
|
@ -31,6 +31,7 @@ import { enUS } from 'date-fns/locale';
|
|||
import zoomPlugin from 'chartjs-plugin-zoom';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
import MkChartTooltip from '@/components/chart-tooltip.vue';
|
||||
|
||||
Chart.register(
|
||||
ArcElement,
|
||||
|
@ -94,6 +95,11 @@ export default defineComponent({
|
|||
required: false,
|
||||
default: false
|
||||
},
|
||||
bar: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
aspectRatio: {
|
||||
type: Number,
|
||||
required: false,
|
||||
|
@ -137,6 +143,43 @@ export default defineComponent({
|
|||
}));
|
||||
};
|
||||
|
||||
const tooltipShowing = ref(false);
|
||||
const tooltipX = ref(0);
|
||||
const tooltipY = ref(0);
|
||||
const tooltipTitle = ref(null);
|
||||
const tooltipSeries = ref(null);
|
||||
let disposeTooltipComponent;
|
||||
|
||||
os.popup(MkChartTooltip, {
|
||||
showing: tooltipShowing,
|
||||
x: tooltipX,
|
||||
y: tooltipY,
|
||||
title: tooltipTitle,
|
||||
series: tooltipSeries,
|
||||
}, {}).then(({ dispose }) => {
|
||||
disposeTooltipComponent = dispose;
|
||||
});
|
||||
|
||||
function externalTooltipHandler(context) {
|
||||
if (context.tooltip.opacity === 0) {
|
||||
tooltipShowing.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tooltipTitle.value = context.tooltip.title[0];
|
||||
tooltipSeries.value = context.tooltip.body.map((b, i) => ({
|
||||
backgroundColor: context.tooltip.labelColors[i].backgroundColor,
|
||||
borderColor: context.tooltip.labelColors[i].borderColor,
|
||||
text: b.lines[0],
|
||||
}));
|
||||
|
||||
const rect = context.chart.canvas.getBoundingClientRect();
|
||||
|
||||
tooltipShowing.value = true;
|
||||
tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX;
|
||||
tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY;
|
||||
}
|
||||
|
||||
const render = () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
|
@ -149,7 +192,7 @@ export default defineComponent({
|
|||
Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
|
||||
|
||||
chartInstance = new Chart(chartEl.value, {
|
||||
type: 'line',
|
||||
type: props.bar ? 'bar' : 'line',
|
||||
data: {
|
||||
labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(),
|
||||
datasets: data.series.map((x, i) => ({
|
||||
|
@ -157,12 +200,13 @@ export default defineComponent({
|
|||
label: x.name,
|
||||
data: x.data.slice().reverse(),
|
||||
pointRadius: 0,
|
||||
tension: 0,
|
||||
borderWidth: 2,
|
||||
borderColor: x.color ? x.color : getColor(i),
|
||||
borderDash: x.borderDash || [],
|
||||
borderJoinStyle: 'round',
|
||||
backgroundColor: alpha(x.color ? x.color : getColor(i), 0.1),
|
||||
barPercentage: 0.9,
|
||||
categoryPercentage: 0.9,
|
||||
fill: x.type === 'area',
|
||||
hidden: !!x.hidden,
|
||||
})),
|
||||
|
@ -180,6 +224,7 @@ export default defineComponent({
|
|||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
stacked: props.stacked,
|
||||
time: {
|
||||
stepSize: 1,
|
||||
unit: props.span === 'day' ? 'month' : 'day',
|
||||
|
@ -212,7 +257,15 @@ export default defineComponent({
|
|||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
hoverRadius: 5,
|
||||
hoverBorderWidth: 2,
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: props.detailed,
|
||||
|
@ -222,10 +275,12 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
mode: 'index',
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
external: externalTooltipHandler,
|
||||
},
|
||||
zoom: {
|
||||
pan: {
|
||||
|
@ -640,6 +695,21 @@ export default defineComponent({
|
|||
};
|
||||
};
|
||||
|
||||
const fetchPerUserDriveChart = async (): Promise<typeof data> => {
|
||||
const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span });
|
||||
return {
|
||||
series: [{
|
||||
name: 'Inc',
|
||||
type: 'area',
|
||||
data: format(raw.incSize),
|
||||
}, {
|
||||
name: 'Dec',
|
||||
type: 'area',
|
||||
data: format(raw.decSize),
|
||||
}],
|
||||
};
|
||||
};
|
||||
|
||||
const fetchAndRender = async () => {
|
||||
const fetchData = () => {
|
||||
switch (props.src) {
|
||||
|
@ -670,6 +740,7 @@ export default defineComponent({
|
|||
case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true);
|
||||
|
||||
case 'per-user-notes': return fetchPerUserNotesChart();
|
||||
case 'per-user-drive': return fetchPerUserDriveChart();
|
||||
}
|
||||
};
|
||||
fetching.value = true;
|
||||
|
@ -684,6 +755,10 @@ export default defineComponent({
|
|||
fetchAndRender();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (disposeTooltipComponent) disposeTooltipComponent();
|
||||
});
|
||||
|
||||
return {
|
||||
chartEl,
|
||||
fetching,
|
||||
|
|
|
@ -117,7 +117,7 @@ export default defineComponent({
|
|||
text: computed(() => {
|
||||
return props.textConverter(finalValue.value);
|
||||
}),
|
||||
source: thumbEl,
|
||||
targetElement: thumbEl,
|
||||
}, {}, 'closed');
|
||||
|
||||
const style = document.createElement('style');
|
||||
|
|
|
@ -23,8 +23,9 @@ const props = withDefaults(defineProps<{
|
|||
behavior: null,
|
||||
});
|
||||
|
||||
const navHook = inject('navHook', null);
|
||||
const sideViewHook = inject('sideViewHook', null);
|
||||
type Navigate = (path: string, record?: boolean) => void;
|
||||
const navHook = inject<null | Navigate>('navHook', null);
|
||||
const sideViewHook = inject<null | Navigate>('sideViewHook', null);
|
||||
|
||||
const active = $computed(() => {
|
||||
if (props.activeClass == null) return false;
|
||||
|
|
|
@ -153,7 +153,7 @@ export default defineComponent({
|
|||
showing,
|
||||
reaction: props.notification.reaction ? props.notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : props.notification.reaction,
|
||||
emojis: props.notification.note.emojis,
|
||||
source: reactionRef.value.$el,
|
||||
targetElement: reactionRef.value.$el,
|
||||
}, {}, 'closed');
|
||||
});
|
||||
|
||||
|
|
|
@ -136,7 +136,10 @@ let showPreview = $ref(false);
|
|||
let cw = $ref<string | null>(null);
|
||||
let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
|
||||
let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]);
|
||||
let visibleUsers = $ref(props.initialVisibleUsers ?? []);
|
||||
let visibleUsers = $ref([]);
|
||||
if (props.initialVisibleUsers) {
|
||||
props.initialVisibleUsers.forEach(pushVisibleUser);
|
||||
}
|
||||
let autocomplete = $ref(null);
|
||||
let draghover = $ref(false);
|
||||
let quoteId = $ref(null);
|
||||
|
@ -263,12 +266,12 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
|
|||
os.api('users/show', {
|
||||
userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId)
|
||||
}).then(users => {
|
||||
visibleUsers.push(...users);
|
||||
users.forEach(pushVisibleUser);
|
||||
});
|
||||
|
||||
if (props.reply.userId !== $i.id) {
|
||||
os.api('users/show', { userId: props.reply.userId }).then(user => {
|
||||
visibleUsers.push(user);
|
||||
pushVisibleUser(user);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -276,7 +279,7 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
|
|||
|
||||
if (props.specified) {
|
||||
visibility = 'specified';
|
||||
visibleUsers.push(props.specified);
|
||||
pushVisibleUser(props.specified);
|
||||
}
|
||||
|
||||
// keep cw when reply
|
||||
|
@ -398,9 +401,15 @@ function setVisibility() {
|
|||
}, 'closed');
|
||||
}
|
||||
|
||||
function pushVisibleUser(user) {
|
||||
if (!visibleUsers.some(u => u.username === user.username && u.host === user.host)) {
|
||||
visibleUsers.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
function addVisibleUser() {
|
||||
os.selectUser().then(user => {
|
||||
visibleUsers.push(user);
|
||||
pushVisibleUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')">
|
||||
<MkTooltip ref="tooltip" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
||||
<div class="beeadbfb">
|
||||
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/>
|
||||
<div class="name">{{ reaction.replace('@.', '') }}</div>
|
||||
|
@ -15,11 +15,11 @@ import XReactionIcon from './reaction-icon.vue';
|
|||
const props = defineProps<{
|
||||
reaction: string;
|
||||
emojis: any[]; // TODO
|
||||
source: any; // TODO
|
||||
targetElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')">
|
||||
<MkTooltip ref="tooltip" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
||||
<div class="bqxuuuey">
|
||||
<div class="reaction">
|
||||
<XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/>
|
||||
|
@ -26,11 +26,11 @@ const props = defineProps<{
|
|||
users: any[]; // TODO
|
||||
count: number;
|
||||
emojis: any[]; // TODO
|
||||
source: any; // TODO
|
||||
targetElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ export default defineComponent({
|
|||
emojis: props.note.emojis,
|
||||
users,
|
||||
count: props.count,
|
||||
source: buttonRef.value
|
||||
targetElement: buttonRef.value,
|
||||
}, {}, 'closed');
|
||||
});
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ export default defineComponent({
|
|||
showing,
|
||||
users,
|
||||
count: props.count,
|
||||
source: buttonRef.value
|
||||
targetElement: buttonRef.value
|
||||
}, {}, 'closed');
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MkTooltip ref="tooltip" :source="source" :max-width="250" @closed="emit('closed')">
|
||||
<MkTooltip ref="tooltip" :target-element="targetElement" :max-width="250" @closed="emit('closed')">
|
||||
<div class="beaffaef">
|
||||
<div v-for="u in users" :key="u.id" class="user">
|
||||
<MkAvatar class="avatar" :user="u"/>
|
||||
|
@ -17,11 +17,11 @@ import MkTooltip from './ui/tooltip.vue';
|
|||
const props = defineProps<{
|
||||
users: any[]; // TODO
|
||||
count: number;
|
||||
source: any; // TODO
|
||||
targetElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
|
|
@ -12,9 +12,11 @@ import * as os from '@/os';
|
|||
|
||||
const props = withDefaults(defineProps<{
|
||||
showing: boolean;
|
||||
source: HTMLElement;
|
||||
targetElement?: HTMLElement;
|
||||
x?: number;
|
||||
y?: number;
|
||||
text?: string;
|
||||
maxWidth?; number;
|
||||
maxWidth?: number;
|
||||
}>(), {
|
||||
maxWidth: 250,
|
||||
});
|
||||
|
@ -29,13 +31,25 @@ const zIndex = os.claimZIndex('high');
|
|||
const setPosition = () => {
|
||||
if (el.value == null) return;
|
||||
|
||||
const rect = props.source.getBoundingClientRect();
|
||||
|
||||
const contentWidth = el.value.offsetWidth;
|
||||
const contentHeight = el.value.offsetHeight;
|
||||
|
||||
let left = rect.left + window.pageXOffset + (props.source.offsetWidth / 2);
|
||||
let top = rect.top + window.pageYOffset - contentHeight;
|
||||
let left: number;
|
||||
let top: number;
|
||||
|
||||
let rect: DOMRect;
|
||||
|
||||
if (props.targetElement) {
|
||||
rect = props.targetElement.getBoundingClientRect();
|
||||
|
||||
left = rect.left + window.pageXOffset + (props.targetElement.offsetWidth / 2);
|
||||
top = rect.top + window.pageYOffset - contentHeight;
|
||||
|
||||
el.value.style.transformOrigin = 'center bottom';
|
||||
} else {
|
||||
left = props.x;
|
||||
top = props.y - contentHeight;
|
||||
}
|
||||
|
||||
left -= (el.value.offsetWidth / 2);
|
||||
|
||||
|
@ -43,9 +57,14 @@ const setPosition = () => {
|
|||
left = window.innerWidth - contentWidth + window.pageXOffset - 1;
|
||||
}
|
||||
|
||||
// ツールチップを上に向かって表示するスペースがなければ下に向かって出す
|
||||
if (top - window.pageYOffset < 0) {
|
||||
top = rect.top + window.pageYOffset + props.source.offsetHeight;
|
||||
el.value.style.transformOrigin = 'center top';
|
||||
if (props.targetElement) {
|
||||
top = rect.top + window.pageYOffset + props.targetElement.offsetHeight;
|
||||
el.value.style.transformOrigin = 'center top';
|
||||
} else {
|
||||
top = props.y;
|
||||
}
|
||||
}
|
||||
|
||||
el.value.style.left = left + 'px';
|
||||
|
@ -54,11 +73,6 @@ const setPosition = () => {
|
|||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (props.source == null) {
|
||||
emit('closed');
|
||||
return;
|
||||
}
|
||||
|
||||
setPosition();
|
||||
|
||||
let loopHandler;
|
||||
|
@ -101,6 +115,6 @@ onMounted(() => {
|
|||
border-radius: 4px;
|
||||
border: solid 0.5px var(--divider);
|
||||
pointer-events: none;
|
||||
transform-origin: center bottom;
|
||||
transform-origin: center center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -48,7 +48,7 @@ export default {
|
|||
popup(import('@/components/ui/tooltip.vue'), {
|
||||
showing,
|
||||
text: self.text,
|
||||
source: el
|
||||
targetElement: el,
|
||||
}, {}, 'closed');
|
||||
|
||||
self._close = () => {
|
||||
|
@ -56,8 +56,8 @@ export default {
|
|||
};
|
||||
};
|
||||
|
||||
el.addEventListener('selectstart', e => {
|
||||
e.preventDefault();
|
||||
el.addEventListener('selectstart', ev => {
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
el.addEventListener(start, () => {
|
||||
|
|
|
@ -14,7 +14,7 @@ if (localStorage.getItem('accounts') != null) {
|
|||
//#endregion
|
||||
|
||||
import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue';
|
||||
import * as compareVersions from 'compare-versions';
|
||||
import compareVersions from 'compare-versions';
|
||||
|
||||
import widgets from '@/widgets';
|
||||
import directives from '@/directives';
|
||||
|
|
|
@ -115,7 +115,7 @@ const pagination = {
|
|||
offsetMode: true,
|
||||
params: computed(() => ({
|
||||
sort: sort,
|
||||
host: host != '' ? host : null,
|
||||
host: host !== '' ? host : null,
|
||||
...(
|
||||
state === 'federating' ? { federating: true } :
|
||||
state === 'subscribing' ? { subscribing: true } :
|
||||
|
@ -157,11 +157,10 @@ defineExpose({
|
|||
|
||||
> .instance {
|
||||
padding: 16px;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 6px;
|
||||
background: var(--panel);
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
border: solid 1px var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<template #label>Moderation</template>
|
||||
<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.stopActivityDelivery }}</FormSwitch>
|
||||
<FormSwitch v-model="isBlocked" class="_formBlock" @update:modelValue="toggleBlock">{{ $ts.blockThisInstance }}</FormSwitch>
|
||||
<MkButton @click="refreshMetadata">Refresh metadata</MkButton>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
|
@ -111,6 +112,7 @@ import MkChart from '@/components/chart.vue';
|
|||
import MkObjectView from '@/components/object-view.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkLink from '@/components/link.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import MkSelect from '@/components/form/select.vue';
|
||||
|
@ -155,6 +157,15 @@ async function toggleSuspend(v) {
|
|||
});
|
||||
}
|
||||
|
||||
function refreshMetadata() {
|
||||
os.api('admin/federation/refresh-remote-instance-metadata', {
|
||||
host: instance.host,
|
||||
});
|
||||
os.alert({
|
||||
text: 'Refresh requested',
|
||||
});
|
||||
}
|
||||
|
||||
fetch();
|
||||
|
||||
defineExpose({
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<FormSection>
|
||||
<template #label>{{ $ts.statistics }}</template>
|
||||
<div ref="chart"></div>
|
||||
<MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="6"/>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
|
@ -45,8 +45,7 @@ import * as os from '@/os';
|
|||
import bytes from '@/filters/bytes';
|
||||
import * as symbols from '@/symbols';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
// TODO: render chart
|
||||
import MkChart from '@/components/chart.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -55,6 +54,7 @@ export default defineComponent({
|
|||
FormSection,
|
||||
MkKeyValue,
|
||||
FormSplit,
|
||||
MkChart,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
|
|
@ -46,8 +46,10 @@ const keymap = {
|
|||
const tlComponent = $ref<InstanceType<typeof XTimeline>>();
|
||||
const rootEl = $ref<HTMLElement>();
|
||||
|
||||
let src = $ref<'home' | 'local' | 'social' | 'global'>(defaultStore.state.tl.src);
|
||||
let queue = $ref(0);
|
||||
const src = $computed(() => defaultStore.reactiveState.tl.value.src);
|
||||
|
||||
watch ($$(src), () => queue = 0);
|
||||
|
||||
function queueUpdated(q: number): void {
|
||||
queue = q;
|
||||
|
@ -60,7 +62,7 @@ function top(): void {
|
|||
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||
const lists = await os.api('users/lists/list');
|
||||
const items = lists.map(list => ({
|
||||
type: 'link',
|
||||
type: 'link' as const,
|
||||
text: list.name,
|
||||
to: `/timeline/list/${list.id}`,
|
||||
}));
|
||||
|
@ -70,7 +72,7 @@ async function chooseList(ev: MouseEvent): Promise<void> {
|
|||
async function chooseAntenna(ev: MouseEvent): Promise<void> {
|
||||
const antennas = await os.api('antennas/list');
|
||||
const items = antennas.map(antenna => ({
|
||||
type: 'link',
|
||||
type: 'link' as const,
|
||||
text: antenna.name,
|
||||
indicate: antenna.hasUnreadNote,
|
||||
to: `/timeline/antenna/${antenna.id}`,
|
||||
|
@ -81,7 +83,7 @@ async function chooseAntenna(ev: MouseEvent): Promise<void> {
|
|||
async function chooseChannel(ev: MouseEvent): Promise<void> {
|
||||
const channels = await os.api('channels/followed');
|
||||
const items = channels.map(channel => ({
|
||||
type: 'link',
|
||||
type: 'link' as const,
|
||||
text: channel.name,
|
||||
indicate: channel.hasUnreadNote,
|
||||
to: `/channels/${channel.id}`,
|
||||
|
@ -89,9 +91,10 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
|
|||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function saveSrc(): void {
|
||||
function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global'): void {
|
||||
defaultStore.set('tl', {
|
||||
src: src,
|
||||
...defaultStore.state.tl,
|
||||
src: newSrc,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -135,25 +138,25 @@ defineExpose({
|
|||
title: i18n.ts._timelines.home,
|
||||
icon: 'fas fa-home',
|
||||
iconOnly: true,
|
||||
onClick: () => { src = 'home'; saveSrc(); },
|
||||
onClick: () => { saveSrc('home'); },
|
||||
}, ...(isLocalTimelineAvailable ? [{
|
||||
active: src === 'local',
|
||||
title: i18n.ts._timelines.local,
|
||||
icon: 'fas fa-comments',
|
||||
iconOnly: true,
|
||||
onClick: () => { src = 'local'; saveSrc(); },
|
||||
onClick: () => { saveSrc('local'); },
|
||||
}, {
|
||||
active: src === 'social',
|
||||
title: i18n.ts._timelines.social,
|
||||
icon: 'fas fa-share-alt',
|
||||
iconOnly: true,
|
||||
onClick: () => { src = 'social'; saveSrc(); },
|
||||
onClick: () => { saveSrc('social'); },
|
||||
}] : []), ...(isGlobalTimelineAvailable ? [{
|
||||
active: src === 'global',
|
||||
title: i18n.ts._timelines.global,
|
||||
icon: 'fas fa-globe',
|
||||
iconOnly: true,
|
||||
onClick: () => { src = 'global'; saveSrc(); },
|
||||
onClick: () => { saveSrc('global'); },
|
||||
}] : [])],
|
||||
})),
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #header><i class="fas fa-chart-bar" style="margin-right: 0.5em;"></i>{{ $ts.activity }}</template>
|
||||
|
||||
<div style="padding: 8px;">
|
||||
<MkChart src="per-user-notes" :args="{ user, withoutAll: true }" span="day" :limit="limit" :stacked="true" :detailed="false" :aspect-ratio="6"/>
|
||||
<MkChart src="per-user-notes" :args="{ user, withoutAll: true }" span="day" :limit="limit" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="5"/>
|
||||
</div>
|
||||
</MkContainer>
|
||||
</template>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<header class="header" @contextmenu.prevent.stop="onContextmenu">
|
||||
<button v-if="history.length > 0" class="_button" @click="back()"><i class="fas fa-chevron-left"></i></button>
|
||||
<button v-else class="_button" style="pointer-events: none;"><!-- マージンのバランスを取るためのダミー --></button>
|
||||
<span class="title">{{ pageInfo.title }}</span>
|
||||
<span class="title" v-text="pageInfo?.title" />
|
||||
<button class="_button" @click="close()"><i class="fas fa-times"></i></button>
|
||||
</header>
|
||||
<MkHeader class="pageHeader" :info="pageInfo"/>
|
||||
|
@ -13,99 +13,89 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { provide } from 'vue';
|
||||
import * as os from '@/os';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import { resolve } from '@/router';
|
||||
import { url } from '@/config';
|
||||
import { resolve, router } from '@/router';
|
||||
import { url as root } from '@/config';
|
||||
import * as symbols from '@/symbols';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
provide() {
|
||||
return {
|
||||
navHook: (path) => {
|
||||
this.navigate(path);
|
||||
}
|
||||
};
|
||||
},
|
||||
provide('navHook', navigate);
|
||||
|
||||
data() {
|
||||
return {
|
||||
path: null,
|
||||
component: null,
|
||||
props: {},
|
||||
pageInfo: null,
|
||||
history: [],
|
||||
};
|
||||
},
|
||||
let path: string | null = $ref(null);
|
||||
let component: ReturnType<typeof resolve>['component'] | null = $ref(null);
|
||||
let props: any | null = $ref(null);
|
||||
let pageInfo: any | null = $ref(null);
|
||||
let history: string[] = $ref([]);
|
||||
|
||||
computed: {
|
||||
url(): string {
|
||||
return url + this.path;
|
||||
}
|
||||
},
|
||||
let url = $computed(() => `${root}${path}`);
|
||||
|
||||
methods: {
|
||||
changePage(page) {
|
||||
if (page == null) return;
|
||||
if (page[symbols.PAGE_INFO]) {
|
||||
this.pageInfo = page[symbols.PAGE_INFO];
|
||||
}
|
||||
},
|
||||
|
||||
navigate(path, record = true) {
|
||||
if (record && this.path) this.history.push(this.path);
|
||||
this.path = path;
|
||||
const { component, props } = resolve(path);
|
||||
this.component = component;
|
||||
this.props = props;
|
||||
},
|
||||
|
||||
back() {
|
||||
this.navigate(this.history.pop(), false);
|
||||
},
|
||||
|
||||
close() {
|
||||
this.path = null;
|
||||
this.component = null;
|
||||
this.props = {};
|
||||
},
|
||||
|
||||
onContextmenu(ev: MouseEvent) {
|
||||
os.contextMenu([{
|
||||
type: 'label',
|
||||
text: this.path,
|
||||
}, {
|
||||
icon: 'fas fa-expand-alt',
|
||||
text: this.$ts.showInPage,
|
||||
action: () => {
|
||||
this.$router.push(this.path);
|
||||
this.close();
|
||||
}
|
||||
}, {
|
||||
icon: 'fas fa-window-maximize',
|
||||
text: this.$ts.openInWindow,
|
||||
action: () => {
|
||||
os.pageWindow(this.path);
|
||||
this.close();
|
||||
}
|
||||
}, null, {
|
||||
icon: 'fas fa-external-link-alt',
|
||||
text: this.$ts.openInNewTab,
|
||||
action: () => {
|
||||
window.open(this.url, '_blank');
|
||||
this.close();
|
||||
}
|
||||
}, {
|
||||
icon: 'fas fa-link',
|
||||
text: this.$ts.copyLink,
|
||||
action: () => {
|
||||
copyToClipboard(this.url);
|
||||
}
|
||||
}], ev);
|
||||
}
|
||||
function changePage(page) {
|
||||
if (page == null) return;
|
||||
if (page[symbols.PAGE_INFO]) {
|
||||
pageInfo = page[symbols.PAGE_INFO];
|
||||
}
|
||||
}
|
||||
|
||||
function navigate(_path: string, record = true) {
|
||||
if (record && path) history.push($$(path).value);
|
||||
path = _path;
|
||||
const resolved = resolve(path);
|
||||
component = resolved.component;
|
||||
props = resolved.props;
|
||||
}
|
||||
|
||||
function back() {
|
||||
const prev = history.pop();
|
||||
if (prev) navigate(prev, false);
|
||||
}
|
||||
|
||||
function close() {
|
||||
path = null;
|
||||
component = null;
|
||||
props = {};
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent) {
|
||||
os.contextMenu([{
|
||||
type: 'label',
|
||||
text: path || '',
|
||||
}, {
|
||||
icon: 'fas fa-expand-alt',
|
||||
text: i18n.ts.showInPage,
|
||||
action: () => {
|
||||
if (path) router.push(path);
|
||||
close();
|
||||
}
|
||||
}, {
|
||||
icon: 'fas fa-window-maximize',
|
||||
text: i18n.ts.openInWindow,
|
||||
action: () => {
|
||||
if (path) os.pageWindow(path);
|
||||
close();
|
||||
}
|
||||
}, null, {
|
||||
icon: 'fas fa-external-link-alt',
|
||||
text: i18n.ts.openInNewTab,
|
||||
action: () => {
|
||||
window.open(url, '_blank');
|
||||
close();
|
||||
}
|
||||
}, {
|
||||
icon: 'fas fa-link',
|
||||
text: i18n.ts.copyLink,
|
||||
action: () => {
|
||||
copyToClipboard(url);
|
||||
}
|
||||
}], ev);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
navigate,
|
||||
back,
|
||||
close,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</main>
|
||||
</div>
|
||||
|
||||
<XSideView v-if="isDesktop" ref="side" class="side"/>
|
||||
<XSideView v-if="isDesktop" ref="sideEl" class="side"/>
|
||||
|
||||
<div v-if="isDesktop" ref="widgetsEl" class="widgets">
|
||||
<XWidgets @mounted="attachSticky"/>
|
||||
|
@ -31,9 +31,9 @@
|
|||
<div v-if="isMobile" class="buttons">
|
||||
<button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
|
||||
<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button>
|
||||
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
|
||||
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
|
||||
<button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
|
||||
<button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button>
|
||||
<button class="button post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button>
|
||||
</div>
|
||||
|
||||
<transition :name="$store.state.animation ? 'menuDrawer-back' : ''">
|
||||
|
@ -64,155 +64,133 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, defineAsyncComponent, provide, onMounted, computed, ref, watch } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, provide, onMounted, computed, ref, watch } from 'vue';
|
||||
import { instanceName } from '@/config';
|
||||
import { StickySidebar } from '@/scripts/sticky-sidebar';
|
||||
import XSidebar from '@/ui/_common_/sidebar.vue';
|
||||
import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue';
|
||||
import XCommon from './_common_/common.vue';
|
||||
import XSideView from './classic.side.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { defaultStore } from '@/store';
|
||||
import * as EventEmitter from 'eventemitter3';
|
||||
import { menuDef } from '@/menu';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { i18n } from '@/i18n';
|
||||
import { $i } from '@/account';
|
||||
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
|
||||
const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/sidebar.vue'));
|
||||
|
||||
const DESKTOP_THRESHOLD = 1100;
|
||||
const MOBILE_THRESHOLD = 500;
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XCommon,
|
||||
XSidebar,
|
||||
XDrawerMenu,
|
||||
XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')),
|
||||
XSideView, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
|
||||
},
|
||||
|
||||
setup() {
|
||||
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
|
||||
const isMobile = ref(window.innerWidth <= MOBILE_THRESHOLD);
|
||||
window.addEventListener('resize', () => {
|
||||
isMobile.value = window.innerWidth <= MOBILE_THRESHOLD;
|
||||
});
|
||||
|
||||
const pageInfo = ref();
|
||||
const widgetsEl = ref<HTMLElement>();
|
||||
const widgetsShowing = ref(false);
|
||||
|
||||
const sideViewController = new EventEmitter();
|
||||
|
||||
provide('sideViewHook', isDesktop.value ? (url) => {
|
||||
sideViewController.emit('navigate', url);
|
||||
} : null);
|
||||
|
||||
const menuIndicated = computed(() => {
|
||||
for (const def in menuDef) {
|
||||
if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから
|
||||
if (menuDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const drawerMenuShowing = ref(false);
|
||||
|
||||
const route = useRoute();
|
||||
watch(route, () => {
|
||||
drawerMenuShowing.value = false;
|
||||
});
|
||||
|
||||
document.documentElement.style.overflowY = 'scroll';
|
||||
|
||||
if (defaultStore.state.widgets.length === 0) {
|
||||
defaultStore.set('widgets', [{
|
||||
name: 'calendar',
|
||||
id: 'a', place: 'right', data: {}
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: 'b', place: 'right', data: {}
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', place: 'right', data: {}
|
||||
}]);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!isDesktop.value) {
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
|
||||
}, { passive: true });
|
||||
}
|
||||
});
|
||||
|
||||
const changePage = (page) => {
|
||||
if (page == null) return;
|
||||
if (page[symbols.PAGE_INFO]) {
|
||||
pageInfo.value = page[symbols.PAGE_INFO];
|
||||
document.title = `${pageInfo.value.title} | ${instanceName}`;
|
||||
}
|
||||
};
|
||||
|
||||
const onContextmenu = (ev) => {
|
||||
const isLink = (el: HTMLElement) => {
|
||||
if (el.tagName === 'A') return true;
|
||||
if (el.parentElement) {
|
||||
return isLink(el.parentElement);
|
||||
}
|
||||
};
|
||||
if (isLink(ev.target)) return;
|
||||
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
|
||||
if (window.getSelection().toString() !== '') return;
|
||||
const path = route.path;
|
||||
os.contextMenu([{
|
||||
type: 'label',
|
||||
text: path,
|
||||
}, {
|
||||
icon: 'fas fa-columns',
|
||||
text: i18n.ts.openInSideView,
|
||||
action: () => {
|
||||
this.$refs.side.navigate(path);
|
||||
}
|
||||
}, {
|
||||
icon: 'fas fa-window-maximize',
|
||||
text: i18n.ts.openInWindow,
|
||||
action: () => {
|
||||
os.pageWindow(path);
|
||||
}
|
||||
}], ev);
|
||||
};
|
||||
|
||||
const attachSticky = (el) => {
|
||||
const sticky = new StickySidebar(widgetsEl.value);
|
||||
window.addEventListener('scroll', () => {
|
||||
sticky.calc(window.scrollY);
|
||||
}, { passive: true });
|
||||
};
|
||||
|
||||
return {
|
||||
pageInfo,
|
||||
isDesktop,
|
||||
isMobile,
|
||||
widgetsEl,
|
||||
widgetsShowing,
|
||||
drawerMenuShowing,
|
||||
menuIndicated,
|
||||
wallpaper: localStorage.getItem('wallpaper') != null,
|
||||
changePage,
|
||||
top: () => {
|
||||
window.scroll({ top: 0, behavior: 'smooth' });
|
||||
},
|
||||
onTransition: () => {
|
||||
if (window._scroll) window._scroll();
|
||||
},
|
||||
post: os.post,
|
||||
onContextmenu,
|
||||
attachSticky,
|
||||
};
|
||||
},
|
||||
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
|
||||
const isMobile = ref(window.innerWidth <= MOBILE_THRESHOLD);
|
||||
window.addEventListener('resize', () => {
|
||||
isMobile.value = window.innerWidth <= MOBILE_THRESHOLD;
|
||||
});
|
||||
|
||||
const pageInfo = ref();
|
||||
const widgetsEl = $ref<HTMLElement>();
|
||||
const widgetsShowing = ref(false);
|
||||
|
||||
let sideEl = $ref<InstanceType<typeof XSideView>>();
|
||||
|
||||
provide('sideViewHook', isDesktop.value ? (url) => {
|
||||
sideEl.navigate(url);
|
||||
} : null);
|
||||
|
||||
const menuIndicated = computed(() => {
|
||||
for (const def in menuDef) {
|
||||
if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから
|
||||
if (menuDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const drawerMenuShowing = ref(false);
|
||||
|
||||
const route = useRoute();
|
||||
watch(route, () => {
|
||||
drawerMenuShowing.value = false;
|
||||
});
|
||||
|
||||
document.documentElement.style.overflowY = 'scroll';
|
||||
|
||||
if (defaultStore.state.widgets.length === 0) {
|
||||
defaultStore.set('widgets', [{
|
||||
name: 'calendar',
|
||||
id: 'a', place: 'right', data: {}
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: 'b', place: 'right', data: {}
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', place: 'right', data: {}
|
||||
}]);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!isDesktop.value) {
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
|
||||
}, { passive: true });
|
||||
}
|
||||
});
|
||||
|
||||
const changePage = (page) => {
|
||||
if (page == null) return;
|
||||
if (page[symbols.PAGE_INFO]) {
|
||||
pageInfo.value = page[symbols.PAGE_INFO];
|
||||
document.title = `${pageInfo.value.title} | ${instanceName}`;
|
||||
}
|
||||
};
|
||||
|
||||
const onContextmenu = (ev) => {
|
||||
const isLink = (el: HTMLElement) => {
|
||||
if (el.tagName === 'A') return true;
|
||||
if (el.parentElement) {
|
||||
return isLink(el.parentElement);
|
||||
}
|
||||
};
|
||||
if (isLink(ev.target)) return;
|
||||
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
|
||||
if (window.getSelection()?.toString() !== '') return;
|
||||
const path = route.path;
|
||||
os.contextMenu([{
|
||||
type: 'label',
|
||||
text: path,
|
||||
}, {
|
||||
icon: 'fas fa-columns',
|
||||
text: i18n.ts.openInSideView,
|
||||
action: () => {
|
||||
sideEl.navigate(path);
|
||||
}
|
||||
}, {
|
||||
icon: 'fas fa-window-maximize',
|
||||
text: i18n.ts.openInWindow,
|
||||
action: () => {
|
||||
os.pageWindow(path);
|
||||
}
|
||||
}], ev);
|
||||
};
|
||||
|
||||
const attachSticky = (el) => {
|
||||
const sticky = new StickySidebar(widgetsEl);
|
||||
window.addEventListener('scroll', () => {
|
||||
sticky.calc(window.scrollY);
|
||||
}, { passive: true });
|
||||
};
|
||||
|
||||
function top() {
|
||||
window.scroll({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function onTransition() {
|
||||
if (window._scroll) window._scroll();
|
||||
}
|
||||
|
||||
const wallpaper = localStorage.getItem('wallpaper') != null;
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -58,7 +58,7 @@ const fetch = async () => {
|
|||
sort: '+lastCommunicatedAt',
|
||||
limit: 5
|
||||
});
|
||||
const fetchedCharts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
|
||||
const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
|
||||
instances.value = fetchedInstances;
|
||||
charts.value = fetchedCharts;
|
||||
fetching.value = false;
|
||||
|
|
|
@ -266,11 +266,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/dateformat@3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc"
|
||||
integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==
|
||||
|
||||
"@types/escape-regexp@0.0.1":
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue