Merge branch 'develop' into sw-notification-action
This commit is contained in:
@@ -9,6 +9,8 @@ import { unisonReload, reloadChannel } from '@client/scripts/unison-reload';
|
||||
type Account = {
|
||||
id: string;
|
||||
token: string;
|
||||
isModerator: boolean;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
|
||||
const data = localStorage.getItem('account');
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="hoawjimk">
|
||||
<div class="mk-media-list">
|
||||
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :media="media" :key="media.id"/>
|
||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container">
|
||||
<div :data-count="mediaList.filter(media => previewable(media)).length">
|
||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container" ref="gridOuter">
|
||||
<div :data-count="mediaList.filter(media => previewable(media)).length" :style="gridInnerStyle">
|
||||
<template v-for="media in mediaList">
|
||||
<XVideo :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
|
||||
<XImage :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
|
||||
@@ -33,16 +33,57 @@ export default defineComponent({
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gridInnerStyle: {},
|
||||
sizeWaiting: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.size();
|
||||
window.addEventListener('resize', this.size);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('resize', this.size);
|
||||
},
|
||||
activated() {
|
||||
this.size();
|
||||
},
|
||||
methods: {
|
||||
previewable(file) {
|
||||
return file.type.startsWith('video') || file.type.startsWith('image');
|
||||
},
|
||||
size() {
|
||||
// for Safari bug
|
||||
if (this.sizeWaiting) return;
|
||||
|
||||
this.sizeWaiting = true;
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
this.sizeWaiting = false;
|
||||
|
||||
if (this.$refs.gridOuter) {
|
||||
let height = 287;
|
||||
const parent = this.$parent.$el;
|
||||
|
||||
if (this.$refs.gridOuter.clientHeight) {
|
||||
height = this.$refs.gridOuter.clientHeight;
|
||||
} else if (parent) {
|
||||
height = parent.getBoundingClientRect().width * 9 / 16;
|
||||
}
|
||||
|
||||
this.gridInnerStyle = { height: `${height}px` };
|
||||
} else {
|
||||
this.gridInnerStyle = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hoawjimk {
|
||||
.mk-media-list {
|
||||
> .gird-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
@@ -61,7 +61,7 @@ import * as mfm from 'mfm-js';
|
||||
import { host, url } from '@client/config';
|
||||
import { erase, unique } from '../../prelude/array';
|
||||
import { extractMentions } from '@/misc/extract-mentions';
|
||||
import getAcct from '@/misc/acct/render';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
import { formatTimeString } from '@/misc/format-time-string';
|
||||
import { Autocomplete } from '@client/scripts/autocomplete';
|
||||
import { noteVisibilities } from '../../types';
|
||||
|
@@ -29,7 +29,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import MkFollowButton from './follow-button.vue';
|
||||
import { userPage } from '../filters/user';
|
||||
|
||||
|
@@ -33,7 +33,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import MkFollowButton from './follow-button.vue';
|
||||
import { userPage } from '../filters/user';
|
||||
import * as os from '@client/os';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import getAcct from '@/misc/acct/render';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
import getUserName from '@/misc/get-user-name';
|
||||
import { url } from '@client/config';
|
||||
|
||||
|
@@ -77,18 +77,6 @@ console.info(`Misskey v${version}`);
|
||||
window.onerror = null;
|
||||
window.onunhandledrejection = null;
|
||||
|
||||
// 後方互換性のため。
|
||||
// TODO: そのうち消す
|
||||
if ((typeof ColdDeviceStorage.get('lightTheme') === 'string') || (typeof ColdDeviceStorage.get('darkTheme') === 'string')) {
|
||||
ColdDeviceStorage.set('lightTheme', require('@client/themes/l-light.json5'));
|
||||
ColdDeviceStorage.set('darkTheme', require('@client/themes/d-dark.json5'));
|
||||
}
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://use.fontawesome.com/releases/v5.15.3/css/all.css';
|
||||
document.head.appendChild(link);
|
||||
// TODOここまで
|
||||
|
||||
if (_DEV_) {
|
||||
console.warn('Development mode!!!');
|
||||
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as os from '@client/os';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
|
||||
export default defineComponent({
|
||||
created() {
|
||||
|
@@ -63,7 +63,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import MkInput from '@client/components/ui/input.vue';
|
||||
import MkSelect from '@client/components/ui/select.vue';
|
||||
|
@@ -38,7 +38,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
import getAcct from '@/misc/acct/render';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import { acct } from '../../filters/user';
|
||||
import * as os from '@client/os';
|
||||
|
@@ -40,7 +40,7 @@ import { computed, defineComponent } from 'vue';
|
||||
import XList from '@client/components/date-separated-list.vue';
|
||||
import XMessage from './messaging-room.message.vue';
|
||||
import XForm from './messaging-room.form.vue';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { isBottom, onScrollBottom, scroll } from '@client/scripts/scroll';
|
||||
import * as os from '@client/os';
|
||||
import { popout } from '@client/scripts/popout';
|
||||
|
@@ -52,7 +52,7 @@ import MkInput from '@client/components/ui/input.vue';
|
||||
import MkTextarea from '@client/components/ui/textarea.vue';
|
||||
import MkSelect from '@client/components/ui/select.vue';
|
||||
import MkSwitch from '@client/components/ui/switch.vue';
|
||||
import getAcct from '@/misc/acct/render';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
import * as os from '@client/os';
|
||||
|
||||
export default defineComponent({
|
||||
|
@@ -52,7 +52,7 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { Room } from '@client/scripts/room/room';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import XPreview from './preview.vue';
|
||||
const storeItems = require('@client/scripts/room/furnitures.json5');
|
||||
import { query as urlQuery } from '../../../prelude/url';
|
||||
|
72
src/client/pages/settings/custom-css.vue
Normal file
72
src/client/pages/settings/custom-css.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<FormBase>
|
||||
<FormInfo warn>{{ $ts.customCssWarn }}</FormInfo>
|
||||
|
||||
<FormTextarea v-model:value="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;">
|
||||
<span>{{ $ts.local }}</span>
|
||||
</FormTextarea>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import FormTextarea from '@client/components/form/textarea.vue';
|
||||
import FormSelect from '@client/components/form/select.vue';
|
||||
import FormRadios from '@client/components/form/radios.vue';
|
||||
import FormBase from '@client/components/form/base.vue';
|
||||
import FormGroup from '@client/components/form/group.vue';
|
||||
import FormLink from '@client/components/form/link.vue';
|
||||
import FormButton from '@client/components/form/button.vue';
|
||||
import FormInfo from '@client/components/form/info.vue';
|
||||
import * as os from '@client/os';
|
||||
import { ColdDeviceStorage } from '@client/store';
|
||||
import { unisonReload } from '@client/scripts/unison-reload';
|
||||
import * as symbols from '@client/symbols';
|
||||
import { defaultStore } from '@client/store';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormTextarea,
|
||||
FormSelect,
|
||||
FormRadios,
|
||||
FormBase,
|
||||
FormGroup,
|
||||
FormLink,
|
||||
FormButton,
|
||||
FormInfo,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.customCss,
|
||||
icon: 'fas fa-code'
|
||||
},
|
||||
localCustomCss: localStorage.getItem('customCss')
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emit('info', this[symbols.PAGE_INFO]);
|
||||
|
||||
this.$watch('localCustomCss', this.apply);
|
||||
},
|
||||
|
||||
methods: {
|
||||
async apply() {
|
||||
localStorage.setItem('customCss', this.localCustomCss);
|
||||
|
||||
const { canceled } = await os.dialog({
|
||||
type: 'info',
|
||||
text: this.$ts.reloadToApplySetting,
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
unisonReload();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@@ -78,6 +78,8 @@
|
||||
</FormSelect>
|
||||
|
||||
<FormLink to="/settings/deck">{{ $ts.deck }}</FormLink>
|
||||
|
||||
<FormLink to="/settings/custom-css"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
|
@@ -123,6 +123,7 @@ export default defineComponent({
|
||||
case 'theme/manage': return defineAsyncComponent(() => import('./theme.manage.vue'));
|
||||
case 'sidebar': return defineAsyncComponent(() => import('./sidebar.vue'));
|
||||
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
|
||||
case 'custom-css': return defineAsyncComponent(() => import('./custom-css.vue'));
|
||||
case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
|
||||
case 'plugin': return defineAsyncComponent(() => import('./plugin.vue'));
|
||||
case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue'));
|
||||
|
@@ -29,6 +29,7 @@ import * as os from '@client/os';
|
||||
import { sidebarDef } from '@client/sidebar';
|
||||
import { defaultStore } from '@client/store';
|
||||
import * as symbols from '@client/symbols';
|
||||
import { unisonReload } from '@client/scripts/unison-reload';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -106,7 +107,7 @@ export default defineComponent({
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
location.reload();
|
||||
unisonReload();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@@ -234,7 +234,7 @@ import MkRemoteCaution from '@client/components/remote-caution.vue';
|
||||
import MkTab from '@client/components/tab.vue';
|
||||
import MkInfo from '@client/components/ui/info.vue';
|
||||
import Progress from '@client/scripts/loading';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { getScrollPosition } from '@client/scripts/scroll';
|
||||
import { getUserMenu } from '@client/scripts/get-user-menu';
|
||||
import number from '../../filters/number';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { host as localHost } from '@client/config';
|
||||
|
||||
export async function genSearchQuery(v: any, q: string) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { i18n } from '@client/i18n';
|
||||
import copyToClipboard from '@client/scripts/copy-to-clipboard';
|
||||
import { host } from '@client/config';
|
||||
import getAcct from '@/misc/acct/render';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
import * as os from '@client/os';
|
||||
import { userActions } from '@client/store';
|
||||
import { router } from '@client/router';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { i18n } from '@client/i18n';
|
||||
import * as os from '@client/os';
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import * as os from '@client/os';
|
||||
import { i18n } from '@client/i18n';
|
||||
import { defaultStore } from '@client/store';
|
||||
|
||||
export function selectFile(src: any, label: string | null, multiple = false) {
|
||||
return new Promise((res, rej) => {
|
||||
@@ -8,7 +9,7 @@ export function selectFile(src: any, label: string | null, multiple = false) {
|
||||
input.type = 'file';
|
||||
input.multiple = multiple;
|
||||
input.onchange = () => {
|
||||
const promises = Array.from(input.files).map(file => os.upload(file));
|
||||
const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder));
|
||||
|
||||
Promise.all(promises).then(driveFiles => {
|
||||
res(multiple ? driveFiles : driveFiles[0]);
|
||||
@@ -57,6 +58,7 @@ export function selectFile(src: any, label: string | null, multiple = false) {
|
||||
|
||||
os.api('drive/files/upload-from-url', {
|
||||
url: url,
|
||||
folderId: defaultStore.state.uploadFolder,
|
||||
marker
|
||||
});
|
||||
|
||||
|
@@ -37,7 +37,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||
},
|
||||
uploadFolder: {
|
||||
where: 'account',
|
||||
default: null
|
||||
default: null as string | null
|
||||
},
|
||||
pastedFileName: {
|
||||
where: 'account',
|
||||
|
@@ -55,7 +55,7 @@ import * as mfm from 'mfm-js';
|
||||
import { host, url } from '@client/config';
|
||||
import { erase, unique } from '../../../prelude/array';
|
||||
import { extractMentions } from '@/misc/extract-mentions';
|
||||
import getAcct from '@/misc/acct/render';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
import { formatTimeString } from '@/misc/format-time-string';
|
||||
import { Autocomplete } from '@client/scripts/autocomplete';
|
||||
import * as os from '@client/os';
|
||||
|
14
src/misc/acct.ts
Normal file
14
src/misc/acct.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type Acct = {
|
||||
username: string;
|
||||
host: string | null;
|
||||
};
|
||||
|
||||
export const getAcct = (user: Acct) => {
|
||||
return user.host == null ? user.username : `${user.username}@${user.host}`;
|
||||
};
|
||||
|
||||
export const parseAcct = (acct: string): Acct => {
|
||||
if (acct.startsWith('@')) acct = acct.substr(1);
|
||||
const split = acct.split('@', 2);
|
||||
return { username: split[0], host: split[1] || null };
|
||||
};
|
@@ -1,7 +0,0 @@
|
||||
import Acct from './type';
|
||||
|
||||
export default (acct: string): Acct => {
|
||||
if (acct.startsWith('@')) acct = acct.substr(1);
|
||||
const split = acct.split('@', 2);
|
||||
return { username: split[0], host: split[1] || null };
|
||||
};
|
@@ -1,5 +0,0 @@
|
||||
import Acct from './type';
|
||||
|
||||
export default (user: Acct) => {
|
||||
return user.host == null ? user.username : `${user.username}@${user.host}`;
|
||||
};
|
@@ -1,6 +0,0 @@
|
||||
type Acct = {
|
||||
username: string;
|
||||
host: string | null;
|
||||
};
|
||||
|
||||
export default Acct;
|
@@ -2,9 +2,9 @@ import { Antenna } from '../models/entities/antenna';
|
||||
import { Note } from '../models/entities/note';
|
||||
import { User } from '../models/entities/user';
|
||||
import { UserListJoinings, UserGroupJoinings } from '../models';
|
||||
import parseAcct from './acct/parse';
|
||||
import { getFullApAccount } from './convert-host';
|
||||
import { PackedNote } from '../models/repositories/note';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
|
||||
/**
|
||||
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
|
||||
|
@@ -2,7 +2,7 @@ import * as Bull from 'bull';
|
||||
|
||||
import { queueLogger } from '../../logger';
|
||||
import follow from '../../../services/following/create';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { resolveUser } from '../../../remote/resolve-user';
|
||||
import { downloadTextFile } from '@/misc/download-text-file';
|
||||
import { isSelfHost, toPuny } from '@/misc/convert-host';
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import * as Bull from 'bull';
|
||||
|
||||
import { queueLogger } from '../../logger';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { resolveUser } from '../../../remote/resolve-user';
|
||||
import { pushUserToUserList } from '../../../services/user-list/push';
|
||||
import { downloadTextFile } from '@/misc/download-text-file';
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import define from '../define';
|
||||
import { Users } from '../../../models';
|
||||
import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { User } from '../../../models/entities/user';
|
||||
|
||||
export const meta = {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import * as Limiter from 'ratelimiter';
|
||||
import { redisClient } from '../../db/redis';
|
||||
import { IEndpoint } from './endpoints';
|
||||
import getAcct from '@/misc/acct/render';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
import { User } from '../../models/entities/user';
|
||||
import Logger from '../../services/logger';
|
||||
|
||||
|
@@ -107,6 +107,13 @@
|
||||
document.documentElement.style.backgroundImage = `url(${wallpaper})`;
|
||||
}
|
||||
|
||||
const customCss = localStorage.getItem('customCss');
|
||||
if (customCss && customCss.length > 0) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function renderError(code, details) {
|
||||
document.documentElement.innerHTML = `
|
||||
|
@@ -18,7 +18,7 @@ import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import { genOpenapiSpec } from '../api/openapi/gen-spec';
|
||||
import config from '@/config';
|
||||
import { Users, Notes, Emojis, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '../../models';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
import { getNoteSummary } from '@/misc/get-note-summary';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { redisClient } from '../../db/redis';
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import * as Router from '@koa/router';
|
||||
|
||||
import config from '@/config';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import Acct from '@/misc/acct/type';
|
||||
import { parseAcct, Acct } from '@/misc/acct';
|
||||
import { links } from './nodeinfo';
|
||||
import { escapeAttribute, escapeValue } from '../prelude/xml';
|
||||
import { Users } from '../models';
|
||||
|
@@ -3,15 +3,17 @@ import { User } from '../models/entities/user';
|
||||
import { sendEmail } from './send-email';
|
||||
import * as locales from '../../locales/';
|
||||
import { I18n } from '@/misc/i18n';
|
||||
import { getAcct } from '@/misc/acct';
|
||||
|
||||
// TODO: locale ファイルをクライアント用とサーバー用で分けたい
|
||||
|
||||
async function follow(userId: User['id'], args: {}) {
|
||||
async function follow(userId: User['id'], follower: User) {
|
||||
const userProfile = await UserProfiles.findOneOrFail({ userId: userId });
|
||||
if (!userProfile.email || !userProfile.emailNotificationTypes.includes('follow')) return;
|
||||
const locale = locales[userProfile.lang || 'ja-JP'];
|
||||
const i18n = new I18n(locale);
|
||||
sendEmail(userProfile.email, i18n.t('_email._follow.title'), 'test', 'test');
|
||||
// TODO: render user information html
|
||||
sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${getAcct(follower)})`, `${follower.name} (@${getAcct(follower)})`);
|
||||
}
|
||||
|
||||
async function receiveFollowRequest(userId: User['id'], args: {}) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { initDb } from '@/db/postgre';
|
||||
import parseAcct from '@/misc/acct/parse';
|
||||
import { parseAcct } from '@/misc/acct';
|
||||
|
||||
async function main(acct: string): Promise<any> {
|
||||
await initDb();
|
||||
|
Reference in New Issue
Block a user