diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-16 19:55:44 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-16 19:55:44 +0900 |
| commit | 8a1f3a4c0b5732d0f08f0788d93c5934de8960c8 (patch) | |
| tree | be6fbcf3a1bbd78306d91e19ef6f3e7023f41561 /src/client | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.92.0 (diff) | |
| download | misskey-8a1f3a4c0b5732d0f08f0788d93c5934de8960c8.tar.gz misskey-8a1f3a4c0b5732d0f08f0788d93c5934de8960c8.tar.bz2 misskey-8a1f3a4c0b5732d0f08f0788d93c5934de8960c8.zip | |
Merge branch 'develop'
Diffstat (limited to 'src/client')
259 files changed, 5261 insertions, 3999 deletions
diff --git a/src/client/account.ts b/src/client/account.ts index 6e26ac1f7d..a3fe082a22 100644 --- a/src/client/account.ts +++ b/src/client/account.ts @@ -1,9 +1,10 @@ import { del, get, set } from '@client/scripts/idb-proxy'; import { reactive } from 'vue'; import { apiUrl } from '@client/config'; -import { waiting } from '@client/os'; +import { waiting, api, popup, popupMenu, success } from '@client/os'; import { unisonReload, reloadChannel } from '@client/scripts/unison-reload'; import { showSuspendedDialog } from './scripts/show-suspended-dialog'; +import { i18n } from './i18n'; // TODO: 他のタブと永続化されたstateを同期 @@ -129,6 +130,77 @@ export async function login(token: Account['token'], redirect?: string) { unisonReload(); } +export async function openAccountMenu(ev: MouseEvent) { + function showSigninDialog() { + popup(import('@client/components/signin-dialog.vue'), {}, { + done: res => { + addAccount(res.id, res.i); + success(); + }, + }, 'closed'); + } + + function createAccount() { + popup(import('@client/components/signup-dialog.vue'), {}, { + done: res => { + addAccount(res.id, res.i); + switchAccountWithToken(res.i); + }, + }, 'closed'); + } + + async function switchAccount(account: any) { + const storedAccounts = await getAccounts(); + const token = storedAccounts.find(x => x.id === account.id).token; + switchAccountWithToken(token); + } + + function switchAccountWithToken(token: string) { + login(token); + } + + const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id)); + const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) }); + + const accountItemPromises = storedAccounts.map(a => new Promise(res => { + accountsPromise.then(accounts => { + const account = accounts.find(x => x.id === a.id); + if (account == null) return res(null); + res({ + type: 'user', + user: account, + action: () => { switchAccount(account); } + }); + }); + })); + + popupMenu([...[{ + type: 'link', + text: i18n.locale.profile, + to: `/@${ $i.username }`, + avatar: $i, + }, null, ...accountItemPromises, { + icon: 'fas fa-plus', + text: i18n.locale.addAccount, + action: () => { + popupMenu([{ + text: i18n.locale.existingAccount, + action: () => { showSigninDialog(); }, + }, { + text: i18n.locale.createAccount, + action: () => { createAccount(); }, + }], ev.currentTarget || ev.target); + }, + }, { + type: 'link', + icon: 'fas fa-users', + text: i18n.locale.manageAccounts, + to: `/settings/accounts`, + }]], ev.currentTarget || ev.target, { + align: 'left' + }); +} + // このファイルに書きたくないけどここに書かないと何故かVeturが認識しない declare module '@vue/runtime-core' { interface ComponentCustomProperties { diff --git a/src/client/components/abuse-report-window.vue b/src/client/components/abuse-report-window.vue index 266c0d566f..21a19385ae 100644 --- a/src/client/components/abuse-report-window.vue +++ b/src/client/components/abuse-report-window.vue @@ -25,7 +25,7 @@ <script lang="ts"> import { defineComponent, markRaw } from 'vue'; import XWindow from '@client/components/ui/window.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; import MkButton from '@client/components/ui/button.vue'; import * as os from '@client/os'; diff --git a/src/client/components/autocomplete.vue b/src/client/components/autocomplete.vue index 065ee6de2e..e621b26229 100644 --- a/src/client/components/autocomplete.vue +++ b/src/client/components/autocomplete.vue @@ -10,12 +10,12 @@ </li> <li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $ts.selectUser }}</li> </ol> - <ol class="hashtags" ref="suggests" v-if="hashtags.length > 0"> + <ol class="hashtags" ref="suggests" v-else-if="hashtags.length > 0"> <li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1"> <span class="name">{{ hashtag }}</span> </li> </ol> - <ol class="emojis" ref="suggests" v-if="emojis.length > 0"> + <ol class="emojis" ref="suggests" v-else-if="emojis.length > 0"> <li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> <span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> <span class="emoji" v-else-if="!$store.state.useOsNativeEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span> @@ -24,6 +24,11 @@ <span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span> </li> </ol> + <ol class="mfmTags" ref="suggests" v-else-if="mfmTags.length > 0"> + <li v-for="tag in mfmTags" @click="complete(type, tag)" @keydown="onKeydown" tabindex="-1"> + <span class="tag">{{ tag }}</span> + </li> + </ol> </div> </template> @@ -106,6 +111,8 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length); const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); //#endregion +const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle']; + export default defineComponent({ props: { type: { @@ -137,11 +144,6 @@ export default defineComponent({ type: Number, required: true, }, - - showing: { - type: Boolean, - required: true - }, }, emits: ['done', 'closed'], @@ -154,18 +156,11 @@ export default defineComponent({ hashtags: [], emojis: [], items: [], + mfmTags: [], select: -1, } }, - watch: { - showing() { - if (!this.showing) { - this.$emit('closed'); - } - } - }, - updated() { this.setPosition(); this.items = (this.$refs.suggests as Element | undefined)?.children || []; @@ -236,7 +231,7 @@ export default defineComponent({ } } - if (this.type == 'user') { + if (this.type === 'user') { if (this.q == null) { this.users = []; this.fetching = false; @@ -262,7 +257,7 @@ export default defineComponent({ sessionStorage.setItem(cacheKey, JSON.stringify(users)); }); } - } else if (this.type == 'hashtag') { + } else if (this.type === 'hashtag') { if (this.q == null || this.q == '') { this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); this.fetching = false; @@ -286,7 +281,7 @@ export default defineComponent({ }); } } - } else if (this.type == 'emoji') { + } else if (this.type === 'emoji') { if (this.q == null || this.q == '') { // 最近使った絵文字をサジェスト this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null); @@ -314,6 +309,13 @@ export default defineComponent({ } this.emojis = matched; + } else if (this.type === 'mfmTag') { + if (this.q == null || this.q == '') { + this.mfmTags = MFM_TAGS; + return; + } + + this.mfmTags = MFM_TAGS.filter(tag => tag.startsWith(this.q)); } }, @@ -490,5 +492,11 @@ export default defineComponent({ margin: 0 0 0 8px; } } + + > .mfmTags > li { + + .name { + } + } } </style> diff --git a/src/client/components/captcha.vue b/src/client/components/captcha.vue index 5da8ede3b9..baa922506e 100644 --- a/src/client/components/captcha.vue +++ b/src/client/components/captcha.vue @@ -39,7 +39,7 @@ export default defineComponent({ type: String, required: true, }, - value: { + modelValue: { type: String, }, }, @@ -116,7 +116,7 @@ export default defineComponent({ } }, callback(response?: string) { - this.$emit('update:value', typeof response == 'string' ? response : null); + this.$emit('update:modelValue', typeof response == 'string' ? response : null); }, }, }); diff --git a/src/client/components/channel-follow-button.vue b/src/client/components/channel-follow-button.vue index 6f9405b97f..bd8627f6e8 100644 --- a/src/client/components/channel-follow-button.vue +++ b/src/client/components/channel-follow-button.vue @@ -91,7 +91,7 @@ export default defineComponent({ width: 31px; } - &:focus { + &:focus-visible { &:after { content: ""; pointer-events: none; diff --git a/src/client/components/cw-button.vue b/src/client/components/cw-button.vue index d2336085ad..3a172f5d5e 100644 --- a/src/client/components/cw-button.vue +++ b/src/client/components/cw-button.vue @@ -1,7 +1,7 @@ <template> <button class="nrvgflfu _button" @click="toggle"> - <b>{{ value ? $ts._cw.hide : $ts._cw.show }}</b> - <span v-if="!value">{{ label }}</span> + <b>{{ modelValue ? $ts._cw.hide : $ts._cw.show }}</b> + <span v-if="!modelValue">{{ label }}</span> </button> </template> @@ -12,7 +12,7 @@ import { concat } from '../../prelude/array'; export default defineComponent({ props: { - value: { + modelValue: { type: Boolean, required: true }, @@ -36,7 +36,7 @@ export default defineComponent({ length, toggle() { - this.$emit('update:value', !this.value); + this.$emit('update:modelValue', !this.modelValue); } } }); diff --git a/src/client/components/form/base.vue b/src/client/components/debobigego/base.vue index 132942d527..f551a3478b 100644 --- a/src/client/components/form/base.vue +++ b/src/client/components/debobigego/base.vue @@ -21,39 +21,39 @@ export default defineComponent({ <style lang="scss" scoped> .rbusrurv { // 他のCSSからも参照されるので消さないように - --formXPadding: 32px; - --formYPadding: 32px; + --debobigegoXPadding: 32px; + --debobigegoYPadding: 32px; - --formContentHMargin: 16px; + --debobigegoContentHMargin: 16px; font-size: 95%; line-height: 1.3em; background: var(--bg); - padding: var(--formYPadding) var(--formXPadding); + padding: var(--debobigegoYPadding) var(--debobigegoXPadding); max-width: 750px; margin: 0 auto; &:not(.wide).max-width_400px { - --formXPadding: 0px; + --debobigegoXPadding: 0px; > ::v-deep(*) { - ._formPanel { + ._debobigegoPanel { border: solid 0.5px var(--divider); border-radius: 0; border-left: none; border-right: none; } - ._form_group { - > *:not(._formNoConcat) { - &:not(:last-child):not(._formNoConcatPrev) { - &._formPanel, ._formPanel { + ._debobigego_group { + > *:not(._debobigegoNoConcat) { + &:not(:last-child):not(._debobigegoNoConcatPrev) { + &._debobigegoPanel, ._debobigegoPanel { border-bottom: solid 0.5px var(--divider); } } - &:not(:first-child):not(._formNoConcatNext) { - &._formPanel, ._formPanel { + &:not(:first-child):not(._debobigegoNoConcatNext) { + &._debobigegoPanel, ._debobigegoPanel { border-top: none; } } diff --git a/src/client/components/form/button.vue b/src/client/components/debobigego/button.vue index b4f0890945..b883e817a4 100644 --- a/src/client/components/form/button.vue +++ b/src/client/components/debobigego/button.vue @@ -1,7 +1,7 @@ <template> -<div class="yzpgjkxe _formItem"> - <div class="_formLabel"><slot name="label"></slot></div> - <button class="main _button _formPanel _formClickable" :class="{ center, primary, danger }"> +<div class="yzpgjkxe _debobigegoItem"> + <div class="_debobigegoLabel"><slot name="label"></slot></div> + <button class="main _button _debobigegoPanel _debobigegoClickable" :class="{ center, primary, danger }"> <slot></slot> <div class="suffix"> <slot name="suffix"></slot> @@ -10,13 +10,13 @@ </div> </div> </button> - <div class="_formCaption"><slot name="desc"></slot></div> + <div class="_debobigegoCaption"><slot name="desc"></slot></div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import './form.scss'; +import './debobigego.scss'; export default defineComponent({ props: { diff --git a/src/client/components/form/form.scss b/src/client/components/debobigego/debobigego.scss index 00f40df9b1..833b656b66 100644 --- a/src/client/components/form/form.scss +++ b/src/client/components/debobigego/debobigego.scss @@ -1,9 +1,9 @@ -._formPanel { +._debobigegoPanel { background: var(--panel); border-radius: var(--radius); transition: background 0.2s ease; - &._formClickable { + &._debobigegoClickable { &:hover { //background: var(--panelHighlight); } @@ -15,8 +15,8 @@ } } -._formLabel, -._formCaption { +._debobigegoLabel, +._debobigegoCaption { font-size: 80%; color: var(--fgTransparentWeak); @@ -25,28 +25,28 @@ } } -._formLabel { +._debobigegoLabel { position: sticky; top: var(--stickyTop, 0px); z-index: 2; - margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1); - padding: 8px calc(var(--formContentHMargin) + var(--formXPadding)) 8px calc(var(--formContentHMargin) + var(--formXPadding)); + margin: -8px calc(var(--debobigegoXPadding) * -1) 0 calc(var(--debobigegoXPadding) * -1); + padding: 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)) 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)); background: var(--X17); -webkit-backdrop-filter: var(--blur, blur(10px)); backdrop-filter: var(--blur, blur(10px)); } -._themeChanging_ ._formLabel { +._themeChanging_ ._debobigegoLabel { transition: none !important; background: transparent; } -._formCaption { - padding: 8px var(--formContentHMargin) 0 var(--formContentHMargin); +._debobigegoCaption { + padding: 8px var(--debobigegoContentHMargin) 0 var(--debobigegoContentHMargin); } -._formItem { - & + ._formItem { +._debobigegoItem { + & + ._debobigegoItem { margin-top: 24px; } } diff --git a/src/client/components/form/group.vue b/src/client/components/debobigego/group.vue index 34ccaeff07..cba2c6ec94 100644 --- a/src/client/components/form/group.vue +++ b/src/client/components/debobigego/group.vue @@ -1,10 +1,10 @@ <template> -<div class="vrtktovg _formItem _formNoConcat" v-size="{ max: [500] }" v-sticky-container> - <div class="_formLabel"><slot name="label"></slot></div> - <div class="main _form_group" ref="child"> +<div class="vrtktovg _debobigegoItem _debobigegoNoConcat" v-size="{ max: [500] }" v-sticky-container> + <div class="_debobigegoLabel"><slot name="label"></slot></div> + <div class="main _debobigego_group" ref="child"> <slot></slot> </div> - <div class="_formCaption"><slot name="caption"></slot></div> + <div class="_debobigegoCaption"><slot name="caption"></slot></div> </div> </template> @@ -20,9 +20,9 @@ export default defineComponent({ const els = Array.from(child.value.children); for (let i = 0; i < els.length; i++) { const el = els[i]; - if (el.classList.contains('_formNoConcat')) { - if (els[i - 1]) els[i - 1].classList.add('_formNoConcatPrev'); - if (els[i + 1]) els[i + 1].classList.add('_formNoConcatNext'); + if (el.classList.contains('_debobigegoNoConcat')) { + if (els[i - 1]) els[i - 1].classList.add('_debobigegoNoConcatPrev'); + if (els[i + 1]) els[i + 1].classList.add('_debobigegoNoConcatNext'); } } }; @@ -52,21 +52,21 @@ export default defineComponent({ <style lang="scss" scoped> .vrtktovg { > .main { - > ::v-deep(*):not(._formNoConcat) { - &:not(._formNoConcatNext) { + > ::v-deep(*):not(._debobigegoNoConcat) { + &:not(._debobigegoNoConcatNext) { margin: 0; } - &:not(:last-child):not(._formNoConcatPrev) { - &._formPanel, ._formPanel { + &:not(:last-child):not(._debobigegoNoConcatPrev) { + &._debobigegoPanel, ._debobigegoPanel { border-bottom: solid 0.5px var(--divider); border-bottom-left-radius: 0; border-bottom-right-radius: 0; } } - &:not(:first-child):not(._formNoConcatNext) { - &._formPanel, ._formPanel { + &:not(:first-child):not(._debobigegoNoConcatNext) { + &._debobigegoPanel, ._debobigegoPanel { border-top: none; border-top-left-radius: 0; border-top-right-radius: 0; diff --git a/src/client/components/form/info.vue b/src/client/components/debobigego/info.vue index 9fdcbdca62..41afb03304 100644 --- a/src/client/components/form/info.vue +++ b/src/client/components/debobigego/info.vue @@ -1,6 +1,6 @@ <template> -<div class="fzenkabp _formItem"> - <div class="_formPanel" :class="{ warn }"> +<div class="fzenkabp _debobigegoItem"> + <div class="_debobigegoPanel" :class="{ warn }"> <i v-if="warn" class="fas fa-exclamation-triangle"></i> <i v-else class="fas fa-info-circle"></i> <slot></slot> diff --git a/src/client/components/ui/input.vue b/src/client/components/debobigego/input.vue index a916a0b035..d113f04d27 100644 --- a/src/client/components/ui/input.vue +++ b/src/client/components/debobigego/input.vue @@ -1,49 +1,53 @@ <template> -<div class="matxzzsk"> - <div class="label" @click="focus"><slot name="label"></slot></div> - <div class="input" :class="{ inline, disabled, focused }"> - <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> - <input ref="inputEl" - :type="type" - v-model="v" - :disabled="disabled" - :required="required" - :readonly="readonly" - :placeholder="placeholder" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="spellcheck" - :step="step" - @focus="focused = true" - @blur="focused = false" - @keydown="onKeydown($event)" - @input="onInput" - :list="id" - > - <datalist :id="id" v-if="datalist"> - <option v-for="data in datalist" :value="data"/> - </datalist> - <div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div> +<FormGroup class="_debobigegoItem"> + <template #label><slot></slot></template> + <div class="ztzhwixg _debobigegoItem" :class="{ inline, disabled }"> + <div class="icon" ref="icon"><slot name="icon"></slot></div> + <div class="input _debobigegoPanel"> + <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> + <input ref="inputEl" + :type="type" + v-model="v" + :disabled="disabled" + :required="required" + :readonly="readonly" + :placeholder="placeholder" + :pattern="pattern" + :autocomplete="autocomplete" + :spellcheck="spellcheck" + :step="step" + @focus="focused = true" + @blur="focused = false" + @keydown="onKeydown($event)" + @input="onInput" + :list="id" + > + <datalist :id="id" v-if="datalist"> + <option v-for="data in datalist" :value="data"/> + </datalist> + <div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div> + </div> </div> - <div class="caption"><slot name="caption"></slot></div> + <template #caption><slot name="desc"></slot></template> - <MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> -</div> + <FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> +</FormGroup> </template> <script lang="ts"> import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; -import MkButton from './button.vue'; -import { debounce } from 'throttle-debounce'; +import './debobigego.scss'; +import FormButton from './button.vue'; +import FormGroup from './group.vue'; export default defineComponent({ components: { - MkButton, + FormGroup, + FormButton, }, - props: { modelValue: { - required: true + required: false }, type: { type: String, @@ -92,20 +96,13 @@ export default defineComponent({ required: false, default: false }, - debounce: { - type: Boolean, - required: false, - default: false - }, manualSave: { type: Boolean, required: false, default: false }, }, - emits: ['change', 'keydown', 'enter', 'update:modelValue'], - setup(props, context) { const { modelValue, type, autofocus } = toRefs(props); const v = ref(modelValue.value); @@ -140,19 +137,13 @@ export default defineComponent({ } }; - const debouncedUpdated = debounce(1000, updated); - - watch(modelValue, newValue => { + watch(modelValue.value, newValue => { v.value = newValue; }); watch(v, newValue => { if (!props.manualSave) { - if (props.debounce) { - debouncedUpdated(); - } else { - updated(); - } + updated(); } invalid.value = inputEl.value.validity.badInput; @@ -205,68 +196,59 @@ export default defineComponent({ </script> <style lang="scss" scoped> -.matxzzsk { - margin: 1.5em 0; +.ztzhwixg { + position: relative; - > .label { - font-size: 0.85em; - padding: 0 0 8px 12px; - user-select: none; + > .icon { + position: absolute; + top: 0; + left: 0; + width: 24px; + text-align: center; + line-height: 32px; - &:empty { - display: none; - } - } - - > .caption { - font-size: 0.8em; - padding: 8px 0 0 12px; - color: var(--fgTransparentWeak); - - &:empty { - display: none; + &:not(:empty) + .input { + margin-left: 28px; } } > .input { - $height: 42px; + $height: 48px; position: relative; > input { - appearance: none; - -webkit-appearance: none; display: block; height: $height; width: 100%; margin: 0; - padding: 0 12px; + padding: 0 16px; font: inherit; font-weight: normal; font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 0.5px var(--inputBorder); - border-radius: 6px; + line-height: $height; + color: var(--inputText); + background: transparent; + border: none; + border-radius: 0; outline: none; box-shadow: none; box-sizing: border-box; - transition: border-color 0.1s ease-out; - &:hover { - border-color: var(--inputBorderHover); + &[type='file'] { + display: none; } } > .prefix, > .suffix { - display: flex; - align-items: center; + display: block; position: absolute; z-index: 1; top: 0; - padding: 0 12px; + padding: 0 16px; font-size: 1em; - height: $height; + line-height: $height; + color: var(--inputLabel); pointer-events: none; &:empty { @@ -285,32 +267,25 @@ export default defineComponent({ > .prefix { left: 0; - padding-right: 6px; + padding-right: 8px; } > .suffix { right: 0; - padding-left: 6px; - } - - &.inline { - display: inline-block; - margin: 0; + padding-left: 8px; } + } - &.focused { - > input { - border-color: var(--accent); - //box-shadow: 0 0 0 4px var(--focus); - } - } + &.inline { + display: inline-block; + margin: 0; + } - &.disabled { - opacity: 0.7; + &.disabled { + opacity: 0.7; - &, * { - cursor: not-allowed !important; - } + &, * { + cursor: not-allowed !important; } } } diff --git a/src/client/components/form/key-value-view.vue b/src/client/components/debobigego/key-value-view.vue index ca4c09867f..0e034a2d54 100644 --- a/src/client/components/form/key-value-view.vue +++ b/src/client/components/debobigego/key-value-view.vue @@ -1,6 +1,6 @@ <template> -<div class="_formItem"> - <div class="_formPanel anocepby"> +<div class="_debobigegoItem"> + <div class="_debobigegoPanel anocepby"> <span class="key"><slot name="key"></slot></span> <span class="value"><slot name="value"></slot></span> </div> @@ -9,7 +9,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import './form.scss'; +import './debobigego.scss'; export default defineComponent({ @@ -20,7 +20,7 @@ export default defineComponent({ .anocepby { display: flex; align-items: center; - padding: 14px var(--formContentHMargin); + padding: 14px var(--debobigegoContentHMargin); > .key { margin-right: 12px; diff --git a/src/client/components/form/link.vue b/src/client/components/debobigego/link.vue index e1d13c6431..885579eadf 100644 --- a/src/client/components/form/link.vue +++ b/src/client/components/debobigego/link.vue @@ -1,6 +1,6 @@ <template> -<div class="qmfkfnzi _formItem"> - <a class="main _button _formPanel _formClickable" :href="to" target="_blank" v-if="external"> +<div class="qmfkfnzi _debobigegoItem"> + <a class="main _button _debobigegoPanel _debobigegoClickable" :href="to" target="_blank" v-if="external"> <span class="icon"><slot name="icon"></slot></span> <span class="text"><slot></slot></span> <span class="right"> @@ -8,7 +8,7 @@ <i class="fas fa-external-link-alt icon"></i> </span> </a> - <MkA class="main _button _formPanel _formClickable" :class="{ active }" :to="to" :behavior="behavior" v-else> + <MkA class="main _button _debobigegoPanel _debobigegoClickable" :class="{ active }" :to="to" :behavior="behavior" v-else> <span class="icon"><slot name="icon"></slot></span> <span class="text"><slot></slot></span> <span class="right"> @@ -21,7 +21,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import './form.scss'; +import './debobigego.scss'; export default defineComponent({ props: { diff --git a/src/client/components/form/object-view.vue b/src/client/components/debobigego/object-view.vue index 59fb62b5e6..ea79daa915 100644 --- a/src/client/components/form/object-view.vue +++ b/src/client/components/debobigego/object-view.vue @@ -1,8 +1,8 @@ <template> -<FormGroup class="_formItem"> +<FormGroup class="_debobigegoItem"> <template #label><slot></slot></template> - <div class="drooglns _formItem" :class="{ tall }"> - <div class="input _formPanel"> + <div class="drooglns _debobigegoItem" :class="{ tall }"> + <div class="input _debobigegoPanel"> <textarea class="_monospace" v-model="v" readonly @@ -17,7 +17,7 @@ <script lang="ts"> import { defineComponent, ref, toRefs, watch } from 'vue'; import * as JSON5 from 'json5'; -import './form.scss'; +import './debobigego.scss'; import FormGroup from './group.vue'; export default defineComponent({ @@ -75,7 +75,7 @@ export default defineComponent({ max-width: 100%; min-height: 130px; margin: 0; - padding: 16px var(--formContentHMargin); + padding: 16px var(--debobigegoContentHMargin); box-sizing: border-box; font: inherit; font-weight: normal; diff --git a/src/client/components/form/pagination.vue b/src/client/components/debobigego/pagination.vue index 0a2f1ff0e1..2166f5065f 100644 --- a/src/client/components/form/pagination.vue +++ b/src/client/components/debobigego/pagination.vue @@ -1,5 +1,5 @@ <template> -<FormGroup class="uljviswt _formItem"> +<FormGroup class="uljviswt _debobigegoItem"> <template #label><slot name="label"></slot></template> <slot :items="items"></slot> <div class="empty" v-if="empty" key="_empty_"> diff --git a/src/client/components/debobigego/radios.vue b/src/client/components/debobigego/radios.vue new file mode 100644 index 0000000000..071c013afb --- /dev/null +++ b/src/client/components/debobigego/radios.vue @@ -0,0 +1,112 @@ +<script lang="ts"> +import { defineComponent, h } from 'vue'; +import MkRadio from '@client/components/form/radio.vue'; +import './debobigego.scss'; + +export default defineComponent({ + components: { + MkRadio + }, + props: { + modelValue: { + required: false + }, + }, + data() { + return { + value: this.modelValue, + } + }, + watch: { + modelValue() { + this.value = this.modelValue; + }, + value() { + this.$emit('update:modelValue', this.value); + } + }, + render() { + const label = this.$slots.desc(); + let options = this.$slots.default(); + + // なぜかFragmentになることがあるため + if (options.length === 1 && options[0].props == null) options = options[0].children; + + return h('div', { + class: 'cnklmpwm _debobigegoItem' + }, [ + h('div', { + class: '_debobigegoLabel', + }, label), + ...options.map(option => h('button', { + class: '_button _debobigegoPanel _debobigegoClickable', + key: option.key, + onClick: () => this.value = option.props.value, + }, [h('span', { + class: ['check', { checked: this.value === option.props.value }], + }), option.children])) + ]); + } +}); +</script> + +<style lang="scss"> +.cnklmpwm { + > button { + display: block; + width: 100%; + box-sizing: border-box; + padding: 14px 18px; + text-align: left; + + &:not(:first-of-type) { + border-top: none !important; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + &:not(:last-of-type) { + border-bottom: solid 0.5px var(--divider); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + > .check { + display: inline-block; + vertical-align: bottom; + position: relative; + width: 16px; + height: 16px; + margin-right: 8px; + background: none; + border: 2px solid var(--inputBorder); + border-radius: 100%; + transition: inherit; + + &:after { + content: ""; + display: block; + position: absolute; + top: 3px; + right: 3px; + bottom: 3px; + left: 3px; + border-radius: 100%; + opacity: 0; + transform: scale(0); + transition: .4s cubic-bezier(.25,.8,.25,1); + } + + &.checked { + border-color: var(--accent); + + &:after { + background-color: var(--accent); + transform: scale(1); + opacity: 1; + } + } + } + } +} +</style> diff --git a/src/client/components/debobigego/range.vue b/src/client/components/debobigego/range.vue new file mode 100644 index 0000000000..26fb0f37c6 --- /dev/null +++ b/src/client/components/debobigego/range.vue @@ -0,0 +1,122 @@ +<template> +<div class="ifitouly _debobigegoItem" :class="{ focused, disabled }"> + <div class="_debobigegoLabel"><slot name="label"></slot></div> + <div class="_debobigegoPanel main"> + <input + type="range" + ref="input" + v-model="v" + :disabled="disabled" + :min="min" + :max="max" + :step="step" + @focus="focused = true" + @blur="focused = false" + @input="$emit('update:value', $event.target.value)" + /> + </div> + <div class="_debobigegoCaption"><slot name="caption"></slot></div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; + +export default defineComponent({ + props: { + value: { + type: Number, + required: false, + default: 0 + }, + disabled: { + type: Boolean, + required: false, + default: false + }, + min: { + type: Number, + required: false, + default: 0 + }, + max: { + type: Number, + required: false, + default: 100 + }, + step: { + type: Number, + required: false, + default: 1 + }, + }, + data() { + return { + v: this.value, + focused: false + }; + }, + watch: { + value(v) { + this.v = parseFloat(v); + } + }, +}); +</script> + +<style lang="scss" scoped> +.ifitouly { + position: relative; + + > .main { + padding: 22px 16px; + + > input { + display: block; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: var(--X10); + height: 4px; + width: 100%; + box-sizing: border-box; + margin: 0; + outline: 0; + border: 0; + border-radius: 7px; + + &.disabled { + opacity: 0.6; + cursor: not-allowed; + } + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + cursor: pointer; + width: 20px; + height: 20px; + display: block; + border-radius: 50%; + border: none; + background: var(--accent); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); + box-sizing: content-box; + } + + &::-moz-range-thumb { + -moz-appearance: none; + appearance: none; + cursor: pointer; + width: 20px; + height: 20px; + display: block; + border-radius: 50%; + border: none; + background: var(--accent); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); + } + } + } +} +</style> diff --git a/src/client/components/debobigego/select.vue b/src/client/components/debobigego/select.vue new file mode 100644 index 0000000000..7a31371afc --- /dev/null +++ b/src/client/components/debobigego/select.vue @@ -0,0 +1,145 @@ +<template> +<div class="yrtfrpux _debobigegoItem" :class="{ disabled, inline }"> + <div class="_debobigegoLabel"><slot name="label"></slot></div> + <div class="icon" ref="icon"><slot name="icon"></slot></div> + <div class="input _debobigegoPanel _debobigegoClickable" @click="focus"> + <div class="prefix" ref="prefix"><slot name="prefix"></slot></div> + <select ref="input" + v-model="v" + :required="required" + :disabled="disabled" + @focus="focused = true" + @blur="focused = false" + > + <slot></slot> + </select> + <div class="suffix"> + <i class="fas fa-chevron-down"></i> + </div> + </div> + <div class="_debobigegoCaption"><slot name="caption"></slot></div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import './debobigego.scss'; + +export default defineComponent({ + props: { + modelValue: { + required: false + }, + required: { + type: Boolean, + required: false + }, + disabled: { + type: Boolean, + required: false + }, + inline: { + type: Boolean, + required: false, + default: false + }, + }, + data() { + return { + }; + }, + computed: { + v: { + get() { + return this.modelValue; + }, + set(v) { + this.$emit('update:modelValue', v); + } + }, + }, + methods: { + focus() { + this.$refs.input.focus(); + } + } +}); +</script> + +<style lang="scss" scoped> +.yrtfrpux { + position: relative; + + > .icon { + position: absolute; + top: 0; + left: 0; + width: 24px; + text-align: center; + line-height: 32px; + + &:not(:empty) + .input { + margin-left: 28px; + } + } + + > .input { + display: flex; + position: relative; + + > select { + display: block; + flex: 1; + width: 100%; + padding: 0 16px; + font: inherit; + font-weight: normal; + font-size: 1em; + height: 48px; + background: none; + border: none; + border-radius: 0; + outline: none; + box-shadow: none; + appearance: none; + -webkit-appearance: none; + color: var(--fg); + + option, + optgroup { + color: var(--fg); + background: var(--bg); + } + } + + > .prefix, + > .suffix { + display: block; + align-self: center; + justify-self: center; + font-size: 1em; + line-height: 32px; + color: var(--inputLabel); + pointer-events: none; + + &:empty { + display: none; + } + + > * { + display: block; + min-width: 16px; + } + } + + > .prefix { + padding-right: 4px; + } + + > .suffix { + padding: 0 16px 0 0; + opacity: 0.7; + } + } +} +</style> diff --git a/src/client/components/form/suspense.vue b/src/client/components/debobigego/suspense.vue index d04dc07624..e59e0ba12d 100644 --- a/src/client/components/form/suspense.vue +++ b/src/client/components/debobigego/suspense.vue @@ -1,15 +1,15 @@ <template> <transition name="fade" mode="out-in"> - <div class="_formItem" v-if="pending"> - <div class="_formPanel"> + <div class="_debobigegoItem" v-if="pending"> + <div class="_debobigegoPanel"> <MkLoading/> </div> </div> - <div v-else-if="resolved" class="_formItem"> + <div v-else-if="resolved" class="_debobigegoItem"> <slot :result="result"></slot> </div> - <div class="_formItem" v-else> - <div class="_formPanel eiurkvay"> + <div class="_debobigegoItem" v-else> + <div class="_debobigegoPanel eiurkvay"> <div><i class="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</div> <MkButton inline @click="retry" class="retry"><i class="fas fa-redo-alt"></i> {{ $ts.retry }}</MkButton> </div> @@ -19,7 +19,7 @@ <script lang="ts"> import { defineComponent, PropType, ref, watch } from 'vue'; -import './form.scss'; +import './debobigego.scss'; import MkButton from '@client/components/ui/button.vue'; export default defineComponent({ diff --git a/src/client/components/debobigego/switch.vue b/src/client/components/debobigego/switch.vue new file mode 100644 index 0000000000..9a69e18302 --- /dev/null +++ b/src/client/components/debobigego/switch.vue @@ -0,0 +1,132 @@ +<template> +<div class="ijnpvmgr _debobigegoItem"> + <div class="main _debobigegoPanel _debobigegoClickable" + :class="{ disabled, checked }" + :aria-checked="checked" + :aria-disabled="disabled" + @click.prevent="toggle" + > + <input + type="checkbox" + ref="input" + :disabled="disabled" + @keydown.enter="toggle" + > + <span class="button" v-tooltip="checked ? $ts.itsOn : $ts.itsOff"> + <span class="handle"></span> + </span> + <span class="label"> + <span><slot></slot></span> + </span> + </div> + <div class="_debobigegoCaption"><slot name="desc"></slot></div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import './debobigego.scss'; + +export default defineComponent({ + props: { + modelValue: { + type: Boolean, + default: false + }, + disabled: { + type: Boolean, + default: false + } + }, + computed: { + checked(): boolean { + return this.modelValue; + } + }, + methods: { + toggle() { + if (this.disabled) return; + this.$emit('update:modelValue', !this.checked); + } + } +}); +</script> + +<style lang="scss" scoped> +.ijnpvmgr { + > .main { + position: relative; + display: flex; + padding: 14px 16px; + cursor: pointer; + + > * { + user-select: none; + } + + > input { + position: absolute; + width: 0; + height: 0; + opacity: 0; + margin: 0; + } + + > .button { + position: relative; + display: inline-block; + flex-shrink: 0; + margin: 0; + width: 34px; + height: 22px; + background: var(--switchBg); + outline: none; + border-radius: 999px; + transition: all 0.3s; + cursor: pointer; + + > .handle { + position: absolute; + top: 0; + left: 3px; + bottom: 0; + margin: auto 0; + border-radius: 100%; + transition: background-color 0.3s, transform 0.3s; + width: 16px; + height: 16px; + background-color: #fff; + pointer-events: none; + } + } + + > .label { + margin-left: 12px; + display: block; + transition: inherit; + color: var(--fg); + + > span { + display: block; + line-height: 20px; + transition: inherit; + } + } + + &.disabled { + opacity: 0.6; + cursor: not-allowed; + } + + &.checked { + > .button { + background-color: var(--accent); + + > .handle { + transform: translateX(12px); + } + } + } + } +} +</style> diff --git a/src/client/components/debobigego/textarea.vue b/src/client/components/debobigego/textarea.vue new file mode 100644 index 0000000000..64e8d47126 --- /dev/null +++ b/src/client/components/debobigego/textarea.vue @@ -0,0 +1,161 @@ +<template> +<FormGroup class="_debobigegoItem"> + <template #label><slot></slot></template> + <div class="rivhosbp _debobigegoItem" :class="{ tall, pre }"> + <div class="input _debobigegoPanel"> + <textarea ref="input" :class="{ code, _monospace: code }" + v-model="v" + :required="required" + :readonly="readonly" + :pattern="pattern" + :autocomplete="autocomplete" + :spellcheck="!code" + @input="onInput" + @focus="focused = true" + @blur="focused = false" + ></textarea> + </div> + </div> + <template #caption><slot name="desc"></slot></template> + + <FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> +</FormGroup> +</template> + +<script lang="ts"> +import { defineComponent, ref, toRefs, watch } from 'vue'; +import './debobigego.scss'; +import FormButton from './button.vue'; +import FormGroup from './group.vue'; + +export default defineComponent({ + components: { + FormGroup, + FormButton, + }, + props: { + modelValue: { + required: false + }, + required: { + type: Boolean, + required: false + }, + readonly: { + type: Boolean, + required: false + }, + pattern: { + type: String, + required: false + }, + autocomplete: { + type: String, + required: false + }, + code: { + type: Boolean, + required: false + }, + tall: { + type: Boolean, + required: false, + default: false + }, + pre: { + type: Boolean, + required: false, + default: false + }, + manualSave: { + type: Boolean, + required: false, + default: false + }, + }, + setup(props, context) { + const { modelValue } = toRefs(props); + const v = ref(modelValue.value); + const changed = ref(false); + const inputEl = ref(null); + const focus = () => inputEl.value.focus(); + const onInput = (ev) => { + changed.value = true; + context.emit('change', ev); + }; + + const updated = () => { + changed.value = false; + context.emit('update:modelValue', v.value); + }; + + watch(modelValue.value, newValue => { + v.value = newValue; + }); + + watch(v, newValue => { + if (!props.manualSave) { + updated(); + } + }); + + return { + v, + updated, + changed, + focus, + onInput, + }; + } +}); +</script> + +<style lang="scss" scoped> +.rivhosbp { + position: relative; + + > .input { + position: relative; + + > textarea { + display: block; + width: 100%; + min-width: 100%; + max-width: 100%; + min-height: 130px; + margin: 0; + padding: 16px; + box-sizing: border-box; + font: inherit; + font-weight: normal; + font-size: 1em; + background: transparent; + border: none; + border-radius: 0; + outline: none; + box-shadow: none; + color: var(--fg); + + &.code { + tab-size: 2; + } + } + } + + &.tall { + > .input { + > textarea { + min-height: 200px; + } + } + } + + &.pre { + > .input { + > textarea { + white-space: pre; + } + } + } +} +</style> diff --git a/src/client/components/form/tuple.vue b/src/client/components/debobigego/tuple.vue index 6c8a22d189..8a4599fd64 100644 --- a/src/client/components/form/tuple.vue +++ b/src/client/components/debobigego/tuple.vue @@ -1,5 +1,5 @@ <template> -<div class="wthhikgt _formItem" v-size="{ max: [500] }"> +<div class="wthhikgt _debobigegoItem" v-size="{ max: [500] }"> <slot></slot> </div> </template> diff --git a/src/client/components/dialog.vue b/src/client/components/dialog.vue index f3611f050e..dd4932f61f 100644 --- a/src/client/components/dialog.vue +++ b/src/client/components/dialog.vue @@ -40,8 +40,8 @@ import { defineComponent } from 'vue'; import MkModal from '@client/components/ui/modal.vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSelect from '@client/components/form/select.vue'; export default defineComponent({ components: { diff --git a/src/client/components/emoji-picker-window.vue b/src/client/components/emoji-picker-window.vue index 53b6ae6b32..b7b884565b 100644 --- a/src/client/components/emoji-picker-window.vue +++ b/src/client/components/emoji-picker-window.vue @@ -153,7 +153,7 @@ export default defineComponent({ height: var(--eachSize); border-radius: 4px; - &:focus { + &:focus-visible { outline: solid 2px var(--focus); z-index: 1; } diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue index d8703202c7..85a12a08e6 100644 --- a/src/client/components/emoji-picker.vue +++ b/src/client/components/emoji-picker.vue @@ -465,7 +465,7 @@ export default defineComponent({ height: var(--eachSize); border-radius: 4px; - &:focus { + &:focus-visible { outline: solid 2px var(--focus); z-index: 1; } diff --git a/src/client/components/follow-button.vue b/src/client/components/follow-button.vue index 5685b86a51..5eba9b1f6b 100644 --- a/src/client/components/follow-button.vue +++ b/src/client/components/follow-button.vue @@ -161,7 +161,7 @@ export default defineComponent({ width: 31px; } - &:focus { + &:focus-visible { &:after { content: ""; pointer-events: none; diff --git a/src/client/components/forgot-password.vue b/src/client/components/forgot-password.vue index 3b5ad6d6ba..cb2380f483 100644 --- a/src/client/components/forgot-password.vue +++ b/src/client/components/forgot-password.vue @@ -35,7 +35,7 @@ import { defineComponent } from 'vue'; import XModalWindow from '@client/components/ui/modal-window.vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/components/form-dialog.vue b/src/client/components/form-dialog.vue index e13592b488..6353b7287e 100644 --- a/src/client/components/form-dialog.vue +++ b/src/client/components/form-dialog.vue @@ -14,23 +14,23 @@ </template> <FormBase class="xkpnjxcv"> <template v-for="item in Object.keys(form).filter(item => !form[item].hidden)"> - <FormInput v-if="form[item].type === 'number'" v-model:value="values[item]" type="number" :step="form[item].step || 1"> + <FormInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1"> <span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span> <template v-if="form[item].description" #desc>{{ form[item].description }}</template> </FormInput> - <FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model:value="values[item]" type="text"> + <FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text"> <span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span> <template v-if="form[item].description" #desc>{{ form[item].description }}</template> </FormInput> - <FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model:value="values[item]"> + <FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]"> <span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span> <template v-if="form[item].description" #desc>{{ form[item].description }}</template> </FormTextarea> - <FormSwitch v-else-if="form[item].type === 'boolean'" v-model:value="values[item]"> + <FormSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]"> <span v-text="form[item].label || item"></span> <template v-if="form[item].description" #desc>{{ form[item].description }}</template> </FormSwitch> - <FormSelect v-else-if="form[item].type === 'enum'" v-model:value="values[item]"> + <FormSelect v-else-if="form[item].type === 'enum'" v-model="values[item]"> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <option v-for="item in form[item].enum" :value="item.value" :key="item.value">{{ item.label }}</option> </FormSelect> @@ -38,7 +38,7 @@ <template #desc><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <option v-for="item in form[item].options" :value="item.value" :key="item.value">{{ item.label }}</option> </FormRadios> - <FormRange v-else-if="form[item].type === 'range'" v-model:value="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step"> + <FormRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step"> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template v-if="form[item].description" #desc>{{ form[item].description }}</template> </FormRange> @@ -53,14 +53,14 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XModalWindow from '@client/components/ui/modal-window.vue'; -import FormBase from './form/base.vue'; -import FormInput from './form/input.vue'; -import FormTextarea from './form/textarea.vue'; -import FormSwitch from './form/switch.vue'; -import FormSelect from './form/select.vue'; -import FormRange from './form/range.vue'; -import FormButton from './form/button.vue'; -import FormRadios from './form/radios.vue'; +import FormBase from './debobigego/base.vue'; +import FormInput from './debobigego/input.vue'; +import FormTextarea from './debobigego/textarea.vue'; +import FormSwitch from './debobigego/switch.vue'; +import FormSelect from './debobigego/select.vue'; +import FormRange from './debobigego/range.vue'; +import FormButton from './debobigego/button.vue'; +import FormRadios from './debobigego/radios.vue'; export default defineComponent({ components: { diff --git a/src/client/components/form/input.vue b/src/client/components/form/input.vue index 942ac4dfd2..d7b6f77519 100644 --- a/src/client/components/form/input.vue +++ b/src/client/components/form/input.vue @@ -1,53 +1,49 @@ <template> -<FormGroup class="_formItem"> - <template #label><slot></slot></template> - <div class="ztzhwixg _formItem" :class="{ inline, disabled }"> - <div class="icon" ref="icon"><slot name="icon"></slot></div> - <div class="input _formPanel"> - <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> - <input ref="inputEl" - :type="type" - v-model="v" - :disabled="disabled" - :required="required" - :readonly="readonly" - :placeholder="placeholder" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="spellcheck" - :step="step" - @focus="focused = true" - @blur="focused = false" - @keydown="onKeydown($event)" - @input="onInput" - :list="id" - > - <datalist :id="id" v-if="datalist"> - <option v-for="data in datalist" :value="data"/> - </datalist> - <div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div> - </div> +<div class="matxzzsk"> + <div class="label" @click="focus"><slot name="label"></slot></div> + <div class="input" :class="{ inline, disabled, focused }"> + <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> + <input ref="inputEl" + :type="type" + v-model="v" + :disabled="disabled" + :required="required" + :readonly="readonly" + :placeholder="placeholder" + :pattern="pattern" + :autocomplete="autocomplete" + :spellcheck="spellcheck" + :step="step" + @focus="focused = true" + @blur="focused = false" + @keydown="onKeydown($event)" + @input="onInput" + :list="id" + > + <datalist :id="id" v-if="datalist"> + <option v-for="data in datalist" :value="data"/> + </datalist> + <div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div> </div> - <template #caption><slot name="desc"></slot></template> + <div class="caption"><slot name="caption"></slot></div> - <FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> -</FormGroup> + <MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> +</div> </template> <script lang="ts"> import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; -import './form.scss'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; +import MkButton from '../ui/button.vue'; +import { debounce } from 'throttle-debounce'; export default defineComponent({ components: { - FormGroup, - FormButton, + MkButton, }, + props: { - value: { - required: false + modelValue: { + required: true }, type: { type: String, @@ -96,16 +92,23 @@ export default defineComponent({ required: false, default: false }, + debounce: { + type: Boolean, + required: false, + default: false + }, manualSave: { type: Boolean, required: false, default: false }, }, - emits: ['change', 'keydown', 'enter'], + + emits: ['change', 'keydown', 'enter', 'update:modelValue'], + setup(props, context) { - const { value, type, autofocus } = toRefs(props); - const v = ref(value.value); + const { modelValue, type, autofocus } = toRefs(props); + const v = ref(modelValue.value); const id = Math.random().toString(); // TODO: uuid? const focused = ref(false); const changed = ref(false); @@ -131,19 +134,25 @@ export default defineComponent({ const updated = () => { changed.value = false; if (type?.value === 'number') { - context.emit('update:value', parseFloat(v.value)); + context.emit('update:modelValue', parseFloat(v.value)); } else { - context.emit('update:value', v.value); + context.emit('update:modelValue', v.value); } }; - watch(value, newValue => { + const debouncedUpdated = debounce(1000, updated); + + watch(modelValue, newValue => { v.value = newValue; }); watch(v, newValue => { if (!props.manualSave) { - updated(); + if (props.debounce) { + debouncedUpdated(); + } else { + updated(); + } } invalid.value = inputEl.value.validity.badInput; @@ -196,59 +205,66 @@ export default defineComponent({ </script> <style lang="scss" scoped> -.ztzhwixg { - position: relative; +.matxzzsk { + > .label { + font-size: 0.85em; + padding: 0 0 8px 12px; + user-select: none; + + &:empty { + display: none; + } + } - > .icon { - position: absolute; - top: 0; - left: 0; - width: 24px; - text-align: center; - line-height: 32px; + > .caption { + font-size: 0.8em; + padding: 8px 0 0 12px; + color: var(--fgTransparentWeak); - &:not(:empty) + .input { - margin-left: 28px; + &:empty { + display: none; } } > .input { - $height: 48px; + $height: 42px; position: relative; > input { + appearance: none; + -webkit-appearance: none; display: block; height: $height; width: 100%; margin: 0; - padding: 0 16px; + padding: 0 12px; font: inherit; font-weight: normal; font-size: 1em; - line-height: $height; - color: var(--inputText); - background: transparent; - border: none; - border-radius: 0; + color: var(--fg); + background: var(--panel); + border: solid 0.5px var(--inputBorder); + border-radius: 6px; outline: none; box-shadow: none; box-sizing: border-box; + transition: border-color 0.1s ease-out; - &[type='file'] { - display: none; + &:hover { + border-color: var(--inputBorderHover); } } > .prefix, > .suffix { - display: block; + display: flex; + align-items: center; position: absolute; z-index: 1; top: 0; - padding: 0 16px; + padding: 0 12px; font-size: 1em; - line-height: $height; - color: var(--inputLabel); + height: $height; pointer-events: none; &:empty { @@ -267,25 +283,32 @@ export default defineComponent({ > .prefix { left: 0; - padding-right: 8px; + padding-right: 6px; } > .suffix { right: 0; - padding-left: 8px; + padding-left: 6px; } - } - &.inline { - display: inline-block; - margin: 0; - } + &.inline { + display: inline-block; + margin: 0; + } + + &.focused { + > input { + border-color: var(--accent); + //box-shadow: 0 0 0 4px var(--focus); + } + } - &.disabled { - opacity: 0.7; + &.disabled { + opacity: 0.7; - &, * { - cursor: not-allowed !important; + &, * { + cursor: not-allowed !important; + } } } } diff --git a/src/client/components/ui/radio.vue b/src/client/components/form/radio.vue index 0f31d8fa0a..0f31d8fa0a 100644 --- a/src/client/components/ui/radio.vue +++ b/src/client/components/form/radio.vue diff --git a/src/client/components/form/radios.vue b/src/client/components/form/radios.vue index b660c37ace..1d3d80172a 100644 --- a/src/client/components/form/radios.vue +++ b/src/client/components/form/radios.vue @@ -1,7 +1,6 @@ <script lang="ts"> import { defineComponent, h } from 'vue'; -import MkRadio from '@client/components/ui/radio.vue'; -import './form.scss'; +import MkRadio from './radio.vue'; export default defineComponent({ components: { @@ -18,9 +17,6 @@ export default defineComponent({ } }, watch: { - modelValue() { - this.value = this.modelValue; - }, value() { this.$emit('update:modelValue', this.value); } @@ -33,80 +29,38 @@ export default defineComponent({ if (options.length === 1 && options[0].props == null) options = options[0].children; return h('div', { - class: 'cnklmpwm _formItem' + class: 'novjtcto' }, [ - h('div', { - class: '_formLabel', - }, label), - ...options.map(option => h('button', { - class: '_button _formPanel _formClickable', + h('div', { class: 'label' }, label), + ...options.map(option => h(MkRadio, { key: option.key, - onClick: () => this.value = option.props.value, - }, [h('span', { - class: ['check', { checked: this.value === option.props.value }], - }), option.children])) + value: option.props.value, + modelValue: this.value, + 'onUpdate:modelValue': value => this.value = value, + }, option.children)) ]); } }); </script> <style lang="scss"> -.cnklmpwm { - > button { - display: block; - width: 100%; - box-sizing: border-box; - padding: 14px 18px; - text-align: left; - - &:not(:first-of-type) { - border-top: none !important; - border-top-left-radius: 0; - border-top-right-radius: 0; - } +.novjtcto { + > .label { + font-size: 0.85em; + padding: 0 0 8px 12px; + user-select: none; - &:not(:last-of-type) { - border-bottom: solid 0.5px var(--divider); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + &:empty { + display: none; } + } - > .check { - display: inline-block; - vertical-align: bottom; - position: relative; - width: 16px; - height: 16px; - margin-right: 8px; - background: none; - border: 2px solid var(--inputBorder); - border-radius: 100%; - transition: inherit; - - &:after { - content: ""; - display: block; - position: absolute; - top: 3px; - right: 3px; - bottom: 3px; - left: 3px; - border-radius: 100%; - opacity: 0; - transform: scale(0); - transition: .4s cubic-bezier(.25,.8,.25,1); - } - - &.checked { - border-color: var(--accent); + &:first-child { + margin-top: 0; + } - &:after { - background-color: var(--accent); - transform: scale(1); - opacity: 1; - } - } - } + &:last-child { + margin-bottom: 0; } } </style> diff --git a/src/client/components/form/range.vue b/src/client/components/form/range.vue index 65d665c70a..4cfe66a8fc 100644 --- a/src/client/components/form/range.vue +++ b/src/client/components/form/range.vue @@ -1,21 +1,20 @@ <template> -<div class="ifitouly _formItem" :class="{ focused, disabled }"> - <div class="_formLabel"><slot name="label"></slot></div> - <div class="_formPanel main"> - <input - type="range" - ref="input" - v-model="v" - :disabled="disabled" - :min="min" - :max="max" - :step="step" - @focus="focused = true" - @blur="focused = false" - @input="$emit('update:value', $event.target.value)" - /> - </div> - <div class="_formCaption"><slot name="caption"></slot></div> +<div class="timctyfi" :class="{ focused, disabled }"> + <div class="icon"><slot name="icon"></slot></div> + <span class="label"><slot name="label"></slot></span> + <input + type="range" + ref="input" + v-model="v" + :disabled="disabled" + :min="min" + :max="max" + :step="step" + :autofocus="autofocus" + @focus="focused = true" + @blur="focused = false" + @input="$emit('update:value', $event.target.value)" + /> </div> </template> @@ -49,6 +48,10 @@ export default defineComponent({ required: false, default: 1 }, + autofocus: { + type: Boolean, + required: false + } }, data() { return { @@ -61,61 +64,75 @@ export default defineComponent({ this.v = parseFloat(v); } }, + mounted() { + if (this.autofocus) { + this.$nextTick(() => { + this.$refs.input.focus(); + }); + } + } }); </script> <style lang="scss" scoped> -.ifitouly { +.timctyfi { position: relative; + margin: 8px; - > .main { - padding: 22px 16px; + > .icon { + display: inline-block; + width: 24px; + text-align: center; + } - > input { - display: block; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: var(--X10); - height: 4px; - width: 100%; - box-sizing: border-box; - margin: 0; - outline: 0; - border: 0; - border-radius: 7px; + > .title { + pointer-events: none; + font-size: 16px; + color: var(--inputLabel); + overflow: hidden; + } - &.disabled { - opacity: 0.6; - cursor: not-allowed; - } + > input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: var(--X10); + height: 7px; + margin: 0 8px; + outline: 0; + border: 0; + border-radius: 7px; - &::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - box-sizing: content-box; - } + &.disabled { + opacity: 0.6; + cursor: not-allowed; + } - &::-moz-range-thumb { - -moz-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - } + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + cursor: pointer; + width: 20px; + height: 20px; + display: block; + border-radius: 50%; + border: none; + background: var(--accent); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); + box-sizing: content-box; + } + + &::-moz-range-thumb { + -moz-appearance: none; + appearance: none; + cursor: pointer; + width: 20px; + height: 20px; + display: block; + border-radius: 50%; + border: none; + background: var(--accent); + box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); } } } diff --git a/src/client/components/form/section.vue b/src/client/components/form/section.vue new file mode 100644 index 0000000000..8eac40a0db --- /dev/null +++ b/src/client/components/form/section.vue @@ -0,0 +1,31 @@ +<template> +<div class="vrtktovh" v-size="{ max: [500] }" v-sticky-container> + <div class="label"><slot name="label"></slot></div> + <div class="main"> + <slot></slot> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; + +export default defineComponent({ + +}); +</script> + +<style lang="scss" scoped> +.vrtktovh { + border-top: solid 0.5px var(--divider); + + > .label { + font-weight: bold; + padding: 24px 0 16px 0; + } + + > .main { + margin-bottom: 32px; + } +} +</style> diff --git a/src/client/components/form/select.vue b/src/client/components/form/select.vue index 1c5a473451..257e2cc990 100644 --- a/src/client/components/form/select.vue +++ b/src/client/components/form/select.vue @@ -1,125 +1,216 @@ <template> -<div class="yrtfrpux _formItem" :class="{ disabled, inline }"> - <div class="_formLabel"><slot name="label"></slot></div> - <div class="icon" ref="icon"><slot name="icon"></slot></div> - <div class="input _formPanel _formClickable" @click="focus"> - <div class="prefix" ref="prefix"><slot name="prefix"></slot></div> - <select ref="input" +<div class="vblkjoeq"> + <div class="label" @click="focus"><slot name="label"></slot></div> + <div class="input" :class="{ inline, disabled, focused }"> + <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> + <select ref="inputEl" v-model="v" - :required="required" :disabled="disabled" + :required="required" + :readonly="readonly" + :placeholder="placeholder" @focus="focused = true" @blur="focused = false" + @input="onInput" > <slot></slot> </select> - <div class="suffix"> - <i class="fas fa-chevron-down"></i> - </div> + <div class="suffix" ref="suffixEl"><i class="fas fa-chevron-down"></i></div> </div> - <div class="_formCaption"><slot name="caption"></slot></div> + <div class="caption"><slot name="caption"></slot></div> + + <MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> </div> </template> <script lang="ts"> -import { defineComponent } from 'vue'; -import './form.scss'; +import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; +import MkButton from '../ui/button.vue'; export default defineComponent({ + components: { + MkButton, + }, + props: { - value: { - required: false + modelValue: { + required: true }, required: { type: Boolean, required: false }, + readonly: { + type: Boolean, + required: false + }, disabled: { type: Boolean, required: false }, + placeholder: { + type: String, + required: false + }, + autofocus: { + type: Boolean, + required: false, + default: false + }, inline: { type: Boolean, required: false, default: false }, + manualSave: { + type: Boolean, + required: false, + default: false + }, }, - data() { - return { + + emits: ['change', 'update:modelValue'], + + setup(props, context) { + const { modelValue, autofocus } = toRefs(props); + const v = ref(modelValue.value); + const focused = ref(false); + const changed = ref(false); + const invalid = ref(false); + const filled = computed(() => v.value !== '' && v.value != null); + const inputEl = ref(null); + const prefixEl = ref(null); + const suffixEl = ref(null); + + const focus = () => inputEl.value.focus(); + const onInput = (ev) => { + changed.value = true; + context.emit('change', ev); }; - }, - computed: { - v: { - get() { - return this.value; - }, - set(v) { - this.$emit('update:value', v); + + const updated = () => { + changed.value = false; + context.emit('update:modelValue', v.value); + }; + + watch(modelValue, newValue => { + v.value = newValue; + }); + + watch(v, newValue => { + if (!props.manualSave) { + updated(); } - }, + + invalid.value = inputEl.value.validity.badInput; + }); + + onMounted(() => { + nextTick(() => { + if (autofocus.value) { + focus(); + } + + // このコンポーネントが作成された時、非表示状態である場合がある + // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する + const clock = setInterval(() => { + if (prefixEl.value) { + if (prefixEl.value.offsetWidth) { + inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; + } + } + if (suffixEl.value) { + if (suffixEl.value.offsetWidth) { + inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; + } + } + }, 100); + + onUnmounted(() => { + clearInterval(clock); + }); + }); + }); + + return { + v, + focused, + invalid, + changed, + filled, + inputEl, + prefixEl, + suffixEl, + focus, + onInput, + updated, + }; }, - methods: { - focus() { - this.$refs.input.focus(); - } - } }); </script> <style lang="scss" scoped> -.yrtfrpux { - position: relative; +.vblkjoeq { + > .label { + font-size: 0.85em; + padding: 0 0 8px 12px; + user-select: none; - > .icon { - position: absolute; - top: 0; - left: 0; - width: 24px; - text-align: center; - line-height: 32px; + &:empty { + display: none; + } + } - &:not(:empty) + .input { - margin-left: 28px; + > .caption { + font-size: 0.8em; + padding: 8px 0 0 12px; + color: var(--fgTransparentWeak); + + &:empty { + display: none; } } > .input { - display: flex; + $height: 42px; position: relative; > select { + appearance: none; + -webkit-appearance: none; display: block; - flex: 1; + height: $height; width: 100%; - padding: 0 16px; + margin: 0; + padding: 0 12px; font: inherit; font-weight: normal; font-size: 1em; - height: 48px; - background: none; - border: none; - border-radius: 0; + color: var(--fg); + background: var(--panel); + border: solid 1px var(--inputBorder); + border-radius: 6px; outline: none; box-shadow: none; - appearance: none; - -webkit-appearance: none; - color: var(--fg); + box-sizing: border-box; + cursor: pointer; + transition: border-color 0.1s ease-out; - option, - optgroup { - color: var(--fg); - background: var(--bg); + &:hover { + border-color: var(--inputBorderHover); } } > .prefix, > .suffix { - display: block; - align-self: center; - justify-self: center; + display: flex; + align-items: center; + position: absolute; + z-index: 1; + top: 0; + padding: 0 12px; font-size: 1em; - line-height: 32px; - color: var(--inputLabel); + height: $height; pointer-events: none; &:empty { @@ -127,18 +218,42 @@ export default defineComponent({ } > * { - display: block; + display: inline-block; min-width: 16px; + max-width: 150px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } } > .prefix { - padding-right: 4px; + left: 0; + padding-right: 6px; } > .suffix { - padding: 0 16px 0 0; + right: 0; + padding-left: 6px; + } + + &.inline { + display: inline-block; + margin: 0; + } + + &.focused { + > select { + border-color: var(--accent); + } + } + + &.disabled { opacity: 0.7; + + &, * { + cursor: not-allowed !important; + } } } } diff --git a/src/client/components/form/slot.vue b/src/client/components/form/slot.vue new file mode 100644 index 0000000000..8580c1307d --- /dev/null +++ b/src/client/components/form/slot.vue @@ -0,0 +1,50 @@ +<template> +<div class="adhpbeou"> + <div class="label" @click="focus"><slot name="label"></slot></div> + <div class="content"> + <slot></slot> + </div> + <div class="caption"><slot name="caption"></slot></div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; + +export default defineComponent({ + +}); +</script> + +<style lang="scss" scoped> +.adhpbeou { + margin: 1.5em 0; + + > .label { + font-size: 0.85em; + padding: 0 0 8px 12px; + user-select: none; + + &:empty { + display: none; + } + } + + > .caption { + font-size: 0.8em; + padding: 8px 0 0 12px; + color: var(--fgTransparentWeak); + + &:empty { + display: none; + } + } + + > .content { + position: relative; + background: var(--panel); + border: solid 0.5px var(--inputBorder); + border-radius: 6px; + } +} +</style> diff --git a/src/client/components/form/switch.vue b/src/client/components/form/switch.vue index e7ef714c49..85f8b7c870 100644 --- a/src/client/components/form/switch.vue +++ b/src/client/components/form/switch.vue @@ -1,35 +1,34 @@ <template> -<div class="ijnpvmgr _formItem"> - <div class="main _formPanel _formClickable" - :class="{ disabled, checked }" - :aria-checked="checked" - :aria-disabled="disabled" - @click.prevent="toggle" +<div + class="ziffeoms" + :class="{ disabled, checked }" + role="switch" + :aria-checked="checked" + :aria-disabled="disabled" + @click.prevent="toggle" +> + <input + type="checkbox" + ref="input" + :disabled="disabled" + @keydown.enter="toggle" > - <input - type="checkbox" - ref="input" - :disabled="disabled" - @keydown.enter="toggle" - > - <span class="button"> - <span></span> - </span> - <span class="label"> - <span><slot></slot></span> - </span> - </div> - <div class="_formCaption"><slot name="desc"></slot></div> + <span class="button" v-tooltip="checked ? $ts.itsOn : $ts.itsOff"> + <span class="handle"></span> + </span> + <span class="label"> + <span><slot></slot></span> + <p><slot name="caption"></slot></p> + </span> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import './form.scss'; export default defineComponent({ props: { - value: { + modelValue: { type: Boolean, default: false }, @@ -40,91 +39,110 @@ export default defineComponent({ }, computed: { checked(): boolean { - return this.value; + return this.modelValue; } }, methods: { toggle() { if (this.disabled) return; - this.$emit('update:value', !this.checked); + this.$emit('update:modelValue', !this.checked); } } }); </script> <style lang="scss" scoped> -.ijnpvmgr { - > .main { - position: relative; - display: flex; - padding: 14px 16px; - cursor: pointer; +.ziffeoms { + position: relative; + display: flex; + cursor: pointer; + transition: all 0.3s; - > * { - user-select: none; - } + &:first-child { + margin-top: 0; + } + + &:last-child { + margin-bottom: 0; + } + + > * { + user-select: none; + } + + > input { + position: absolute; + width: 0; + height: 0; + opacity: 0; + margin: 0; + } - &.disabled { - opacity: 0.6; - cursor: not-allowed; + > .button { + position: relative; + display: inline-block; + flex-shrink: 0; + margin: 0; + width: 36px; + height: 26px; + background: var(--switchBg); + outline: none; + border-radius: 999px; + transition: inherit; + + > .handle { + position: absolute; + top: 0; + bottom: 0; + left: 5px; + margin: auto 0; + border-radius: 100%; + transition: background-color 0.3s, transform 0.3s; + width: 16px; + height: 16px; + background-color: #fff; } + } - &.checked { - > .button { - background-color: var(--X10); - border-color: var(--X10); + > .label { + margin-left: 16px; + margin-top: 2px; + display: block; + cursor: pointer; + transition: inherit; + color: var(--fg); - > * { - background-color: var(--accent); - transform: translateX(14px); - } - } + > span { + display: block; + line-height: 20px; + transition: inherit; } - > input { - position: absolute; - width: 0; - height: 0; - opacity: 0; + > p { margin: 0; + color: var(--fgTransparentWeak); + font-size: 90%; } + } + &:hover { > .button { - position: relative; - display: inline-block; - flex-shrink: 0; - margin: 3px 0 0 0; - width: 34px; - height: 14px; - background: var(--X6); - outline: none; - border-radius: 14px; - transition: all 0.3s; - cursor: pointer; - - > * { - position: absolute; - top: -3px; - left: 0; - border-radius: 100%; - transition: background-color 0.3s, transform 0.3s; - width: 20px; - height: 20px; - background-color: #fff; - box-shadow: 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12); - } + background-color: var(--accentedBg); } + } - > .label { - margin-left: 12px; - display: block; - transition: inherit; - color: var(--fg); + &.disabled { + opacity: 0.6; + cursor: not-allowed; + } + + &.checked { + > .button { + background-color: var(--accent); + border-color: var(--accent); - > span { - display: block; - line-height: 20px; - transition: inherit; + > .handle { + transform: translateX(10px); } } } diff --git a/src/client/components/form/textarea.vue b/src/client/components/form/textarea.vue index 8f42581a9b..50be69f930 100644 --- a/src/client/components/form/textarea.vue +++ b/src/client/components/form/textarea.vue @@ -1,40 +1,45 @@ <template> -<FormGroup class="_formItem"> - <template #label><slot></slot></template> - <div class="rivhosbp _formItem" :class="{ tall, pre }"> - <div class="input _formPanel"> - <textarea ref="input" :class="{ code, _monospace: code }" - v-model="v" - :required="required" - :readonly="readonly" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="!code" - @input="onInput" - @focus="focused = true" - @blur="focused = false" - ></textarea> - </div> +<div class="adhpbeos"> + <div class="label" @click="focus"><slot name="label"></slot></div> + <div class="input" :class="{ disabled, focused, tall, pre }"> + <textarea ref="inputEl" + :class="{ code, _monospace: code }" + v-model="v" + :disabled="disabled" + :required="required" + :readonly="readonly" + :placeholder="placeholder" + :pattern="pattern" + :autocomplete="autocomplete" + :spellcheck="spellcheck" + @focus="focused = true" + @blur="focused = false" + @keydown="onKeydown($event)" + @input="onInput" + ></textarea> </div> - <template #caption><slot name="desc"></slot></template> + <div class="caption"><slot name="caption"></slot></div> - <FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> -</FormGroup> + <MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> +</div> </template> <script lang="ts"> -import { defineComponent, ref, toRefs, watch } from 'vue'; -import './form.scss'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; +import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; +import MkButton from '../ui/button.vue'; +import { debounce } from 'throttle-debounce'; export default defineComponent({ components: { - FormGroup, - FormButton, + MkButton, }, + props: { - value: { + modelValue: { + required: true + }, + type: { + type: String, required: false }, required: { @@ -45,14 +50,29 @@ export default defineComponent({ type: Boolean, required: false }, + disabled: { + type: Boolean, + required: false + }, pattern: { type: String, required: false }, - autocomplete: { + placeholder: { type: String, required: false }, + autofocus: { + type: Boolean, + required: false, + default: false + }, + autocomplete: { + required: false + }, + spellcheck: { + required: false + }, code: { type: Boolean, required: false @@ -67,91 +87,162 @@ export default defineComponent({ required: false, default: false }, + debounce: { + type: Boolean, + required: false, + default: false + }, manualSave: { type: Boolean, required: false, default: false }, }, + + emits: ['change', 'keydown', 'enter', 'update:modelValue'], + setup(props, context) { - const { value } = toRefs(props); - const v = ref(value.value); + const { modelValue, autofocus } = toRefs(props); + const v = ref(modelValue.value); + const focused = ref(false); const changed = ref(false); + const invalid = ref(false); + const filled = computed(() => v.value !== '' && v.value != null); const inputEl = ref(null); + const focus = () => inputEl.value.focus(); const onInput = (ev) => { changed.value = true; context.emit('change', ev); }; + const onKeydown = (ev: KeyboardEvent) => { + context.emit('keydown', ev); + + if (ev.code === 'Enter') { + context.emit('enter'); + } + }; const updated = () => { changed.value = false; - context.emit('update:value', v.value); + context.emit('update:modelValue', v.value); }; - watch(value, newValue => { + const debouncedUpdated = debounce(1000, updated); + + watch(modelValue, newValue => { v.value = newValue; }); watch(v, newValue => { if (!props.manualSave) { - updated(); + if (props.debounce) { + debouncedUpdated(); + } else { + updated(); + } } + + invalid.value = inputEl.value.validity.badInput; }); - + + onMounted(() => { + nextTick(() => { + if (autofocus.value) { + focus(); + } + }); + }); + return { v, - updated, + focused, + invalid, changed, + filled, + inputEl, focus, onInput, + onKeydown, + updated, }; - } + }, }); </script> <style lang="scss" scoped> -.rivhosbp { - position: relative; +.adhpbeos { + > .label { + font-size: 0.85em; + padding: 0 0 8px 12px; + user-select: none; + + &:empty { + display: none; + } + } + + > .caption { + font-size: 0.8em; + padding: 8px 0 0 12px; + color: var(--fgTransparentWeak); + + &:empty { + display: none; + } + } > .input { position: relative; - + > textarea { + appearance: none; + -webkit-appearance: none; display: block; width: 100%; min-width: 100%; max-width: 100%; min-height: 130px; margin: 0; - padding: 16px; - box-sizing: border-box; + padding: 12px; font: inherit; font-weight: normal; font-size: 1em; - background: transparent; - border: none; - border-radius: 0; + color: var(--fg); + background: var(--panel); + border: solid 0.5px var(--inputBorder); + border-radius: 6px; outline: none; box-shadow: none; - color: var(--fg); + box-sizing: border-box; + transition: border-color 0.1s ease-out; - &.code { - tab-size: 2; + &:hover { + border-color: var(--inputBorderHover); + } + } + + &.focused { + > textarea { + border-color: var(--accent); } } - } - &.tall { - > .input { + &.disabled { + opacity: 0.7; + + &, * { + cursor: not-allowed !important; + } + } + + &.tall { > textarea { min-height: 200px; } } - } - &.pre { - > .input { + &.pre { > textarea { white-space: pre; } diff --git a/src/client/components/global/header.vue b/src/client/components/global/header.vue new file mode 100644 index 0000000000..a4466da498 --- /dev/null +++ b/src/client/components/global/header.vue @@ -0,0 +1,359 @@ +<template> +<div class="fdidabkb" :class="{ slim: narrow, thin: thin_ }" :style="{ background: bg }" @click="onClick" ref="el"> + <template v-if="info"> + <div class="titleContainer" @click="showTabsPopup" v-if="!hideTitle"> + <i v-if="info.icon" class="icon" :class="info.icon"></i> + <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/> + + <div class="title"> + <MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/> + <div v-else-if="info.title" class="title">{{ info.title }}</div> + <div class="subtitle" v-if="!narrow && info.subtitle"> + {{ info.subtitle }} + </div> + <div class="subtitle activeTab" v-if="narrow && hasTabs"> + {{ info.tabs.find(tab => tab.active)?.title }} + <i class="chevron fas fa-chevron-down"></i> + </div> + </div> + </div> + <div class="tabs" v-if="!narrow || hideTitle"> + <button class="tab _button" v-for="tab in info.tabs" :class="{ active: tab.active }" @click="tab.onClick" v-tooltip="tab.title"> + <i v-if="tab.icon" class="icon" :class="tab.icon"></i> + <span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span> + </button> + </div> + </template> + <div class="buttons right"> + <template v-if="info && info.actions && !narrow"> + <template v-for="action in info.actions"> + <MkButton class="fullButton" v-if="action.asFullButton" @click.stop="action.handler" primary><i :class="action.icon" style="margin-right: 6px;"></i>{{ action.text }}</MkButton> + <button v-else class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button> + </template> + </template> + <button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button> + </div> +</div> +</template> + +<script lang="ts"> +import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, inject } from 'vue'; +import * as tinycolor from 'tinycolor2'; +import { popupMenu } from '@client/os'; +import { url } from '@client/config'; +import { scrollToTop } from '@client/scripts/scroll'; +import MkButton from '@client/components/ui/button.vue'; +import { i18n } from '@client/i18n'; +import { globalEvents } from '@client/events'; + +export default defineComponent({ + components: { + MkButton + }, + + props: { + info: { + type: Object as PropType<{ + actions?: {}[]; + tabs?: {}[]; + }>, + required: true + }, + menu: { + required: false + }, + thin: { + required: false, + default: false + }, + }, + + setup(props) { + const el = ref<HTMLElement>(null); + const bg = ref(null); + const narrow = ref(false); + const height = ref(0); + const hasTabs = computed(() => { + return props.info.tabs && props.info.tabs.length > 0; + }); + const shouldShowMenu = computed(() => { + if (props.info == null) return false; + if (props.info.actions != null && narrow.value) return true; + if (props.info.menu != null) return true; + if (props.info.share != null) return true; + if (props.menu != null) return true; + return false; + }); + + const share = () => { + navigator.share({ + url: url + props.info.path, + ...props.info.share, + }); + }; + + const showMenu = (ev: MouseEvent) => { + let menu = props.info.menu ? props.info.menu() : []; + if (narrow.value && props.info.actions) { + menu = [...props.info.actions.map(x => ({ + text: x.text, + icon: x.icon, + action: x.handler + })), menu.length > 0 ? null : undefined, ...menu]; + } + if (props.info.share) { + if (menu.length > 0) menu.push(null); + menu.push({ + text: i18n.locale.share, + icon: 'fas fa-share-alt', + action: share + }); + } + if (props.menu) { + if (menu.length > 0) menu.push(null); + menu = menu.concat(props.menu); + } + popupMenu(menu, ev.currentTarget || ev.target); + }; + + const showTabsPopup = (ev: MouseEvent) => { + if (!hasTabs.value) return; + if (!narrow.value) return; + ev.preventDefault(); + ev.stopPropagation(); + const menu = props.info.tabs.map(tab => ({ + text: tab.title, + icon: tab.icon, + action: tab.onClick, + })); + popupMenu(menu, ev.currentTarget || ev.target); + }; + + const preventDrag = (ev: TouchEvent) => { + ev.stopPropagation(); + }; + + const onClick = () => { + scrollToTop(el.value, { behavior: 'smooth' }); + }; + + const calcBg = () => { + const rawBg = props.info?.bg || 'var(--bg)'; + const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); + tinyBg.setAlpha(0.85); + bg.value = tinyBg.toRgbString(); + }; + + onMounted(() => { + calcBg(); + globalEvents.on('themeChanged', calcBg); + onUnmounted(() => { + globalEvents.off('themeChanged', calcBg); + }); + + if (el.value.parentElement) { + narrow.value = el.value.parentElement.offsetWidth < 500; + const ro = new ResizeObserver((entries, observer) => { + if (el.value) { + narrow.value = el.value.parentElement.offsetWidth < 500; + } + }); + ro.observe(el.value.parentElement); + onUnmounted(() => { + ro.disconnect(); + }); + setTimeout(() => { + const currentStickyTop = getComputedStyle(el.value.parentElement).getPropertyValue('--stickyTop') || '0px'; + el.value.style.setProperty('--stickyTop', currentStickyTop); + el.value.parentElement.style.setProperty('--stickyTop', `calc(${currentStickyTop} + ${el.value.offsetHeight}px)`); + }, 100); // レンダリング順序の関係で親のstickyTopの設定が少し遅れることがあるため + } + }); + + return { + el, + bg, + narrow, + height, + hasTabs, + shouldShowMenu, + share, + showMenu, + showTabsPopup, + preventDrag, + onClick, + hideTitle: inject('shouldOmitHeaderTitle', false), + thin_: props.thin || inject('shouldHeaderThin', false) + }; + }, +}); +</script> + +<style lang="scss" scoped> +.fdidabkb { + --height: 60px; + display: flex; + position: sticky; + top: var(--stickyTop, 0); + z-index: 1000; + width: 100%; + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); + border-bottom: solid 0.5px var(--divider); + + &.thin { + --height: 50px; + } + + &.slim { + text-align: center; + + > .titleContainer { + flex: 1; + margin: 0 auto; + margin-left: var(--height); + + > *:first-child { + margin-left: auto; + } + + > *:last-child { + margin-right: auto; + } + } + } + + > .buttons { + --margin: 8px; + display: flex; + align-items: center; + height: var(--height); + margin: 0 var(--margin); + + &.right { + margin-left: auto; + } + + &:empty { + width: var(--height); + } + + > .button { + display: flex; + align-items: center; + justify-content: center; + height: calc(var(--height) - (var(--margin) * 2)); + width: calc(var(--height) - (var(--margin) * 2)); + box-sizing: border-box; + position: relative; + border-radius: 5px; + + &:hover { + background: rgba(0, 0, 0, 0.05); + } + + &.highlighted { + color: var(--accent); + } + } + + > .fullButton { + & + .fullButton { + margin-left: 12px; + } + } + } + + > .titleContainer { + display: flex; + align-items: center; + overflow: auto; + white-space: nowrap; + text-align: left; + font-weight: bold; + flex-shrink: 0; + margin-left: 24px; + + > .avatar { + $size: 32px; + display: inline-block; + width: $size; + height: $size; + vertical-align: bottom; + margin: 0 8px; + pointer-events: none; + } + + > .icon { + margin-right: 8px; + } + + > .title { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.1; + + > .subtitle { + opacity: 0.6; + font-size: 0.8em; + font-weight: normal; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &.activeTab { + text-align: center; + + > .chevron { + display: inline-block; + margin-left: 6px; + } + } + } + } + } + + > .tabs { + margin-left: 16px; + font-size: 0.8em; + overflow: auto; + white-space: nowrap; + + > .tab { + display: inline-block; + position: relative; + padding: 0 10px; + height: 100%; + font-weight: normal; + opacity: 0.7; + + &:hover { + opacity: 1; + } + + &.active { + opacity: 1; + + &:after { + content: ""; + display: block; + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 auto; + width: 100%; + height: 3px; + background: var(--accent); + } + } + + > .icon + .title { + margin-left: 8px; + } + } + } +} +</style> diff --git a/src/client/components/global/spacer.vue b/src/client/components/global/spacer.vue new file mode 100644 index 0000000000..1129d54c71 --- /dev/null +++ b/src/client/components/global/spacer.vue @@ -0,0 +1,76 @@ +<template> +<div ref="root" :class="$style.root" :style="{ padding: margin + 'px' }"> + <div ref="content" :class="$style.content"> + <slot></slot> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent, onMounted, onUnmounted, ref } from 'vue'; + +export default defineComponent({ + props: { + contentMax: { + type: Number, + required: false, + default: null, + } + }, + + setup(props, context) { + let ro: ResizeObserver; + const root = ref<HTMLElement>(null); + const content = ref<HTMLElement>(null); + const margin = ref(0); + const adjust = (rect: { width: number; height: number; }) => { + if (rect.width > (props.contentMax || 500)) { + margin.value = 32; + } else { + margin.value = 12; + } + }; + + onMounted(() => { + ro = new ResizeObserver((entries) => { + /* iOSが対応していない + adjust({ + width: entries[0].borderBoxSize[0].inlineSize, + height: entries[0].borderBoxSize[0].blockSize, + }); + */ + adjust({ + width: root.value.offsetWidth, + height: root.value.offsetHeight, + }); + }); + ro.observe(root.value); + + if (props.contentMax) { + content.value.style.maxWidth = `${props.contentMax}px`; + } + }); + + onUnmounted(() => { + ro.disconnect(); + }); + + return { + root, + content, + margin, + }; + }, +}); +</script> + +<style lang="scss" module> +.root { + box-sizing: border-box; + width: 100%; +} + +.content { + margin: 0 auto; +} +</style> diff --git a/src/client/components/index.ts b/src/client/components/index.ts index 8b914c5eec..ecf66ea0e8 100644 --- a/src/client/components/index.ts +++ b/src/client/components/index.ts @@ -13,6 +13,8 @@ import i18n from './global/i18n'; import loading from './global/loading.vue'; import error from './global/error.vue'; import ad from './global/ad.vue'; +import header from './global/header.vue'; +import spacer from './global/spacer.vue'; export default function(app: App) { app.component('I18n', i18n); @@ -28,4 +30,6 @@ export default function(app: App) { app.component('MkLoading', loading); app.component('MkError', error); app.component('MkAd', ad); + app.component('MkHeader', header); + app.component('MkSpacer', spacer); } diff --git a/src/client/components/instance-stats.vue b/src/client/components/instance-stats.vue index 78044f0b16..5e7c71ea65 100644 --- a/src/client/components/instance-stats.vue +++ b/src/client/components/instance-stats.vue @@ -36,7 +36,7 @@ <script lang="ts"> import { defineComponent, markRaw } from 'vue'; import Chart from 'chart.js'; -import MkSelect from './ui/select.vue'; +import MkSelect from './form/select.vue'; import number from '@client/filters/number'; const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); diff --git a/src/client/components/media-caption.vue b/src/client/components/media-caption.vue index 690927d4c5..b35b101d06 100644 --- a/src/client/components/media-caption.vue +++ b/src/client/components/media-caption.vue @@ -3,10 +3,13 @@ <div class="container"> <div class="fullwidth top-caption"> <div class="mk-dialog"> - <header v-if="title"><Mfm :text="title"/></header> + <header> + <Mfm v-if="title" class="title" :text="title"/> + <span class="text-count" :class="{ over: remainingLength < 0 }">{{ remainingLength }}</span> + </header> <textarea autofocus v-model="inputValue" :placeholder="input.placeholder" @keydown="onInputKeydown"></textarea> <div class="buttons" v-if="(showOkButton || showCancelButton)"> - <MkButton inline @click="ok" primary>{{ $ts.ok }}</MkButton> + <MkButton inline @click="ok" primary :disabled="remainingLength < 0">{{ $ts.ok }}</MkButton> <MkButton inline @click="cancel" >{{ $ts.cancel }}</MkButton> </div> </div> @@ -26,10 +29,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; +import { length } from 'stringz'; import MkModal from '@client/components/ui/modal.vue'; import MkButton from '@client/components/ui/button.vue'; import bytes from '@client/filters/bytes'; import number from '@client/filters/number'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export default defineComponent({ components: { @@ -79,6 +84,13 @@ export default defineComponent({ document.removeEventListener('keydown', this.onKeydown); }, + computed: { + remainingLength(): number { + if (typeof this.inputValue != "string") return DB_MAX_IMAGE_COMMENT_LENGTH; + return DB_MAX_IMAGE_COMMENT_LENGTH - length(this.inputValue); + } + }, + methods: { bytes, number, @@ -156,8 +168,18 @@ export default defineComponent({ > header { margin: 0 0 8px 0; - font-weight: bold; - font-size: 20px; + position: relative; + + > .title { + font-weight: bold; + font-size: 20px; + } + + > .text-count { + opacity: 0.7; + position: absolute; + right: 0; + } } > .buttons { @@ -184,7 +206,7 @@ export default defineComponent({ min-width: 100%; min-height: 90px; - &:focus { + &:focus-visible { outline: none; } diff --git a/src/client/components/mfm.ts b/src/client/components/mfm.ts index a228ca4b8d..2bdd7d46ee 100644 --- a/src/client/components/mfm.ts +++ b/src/client/components/mfm.ts @@ -185,7 +185,7 @@ export default defineComponent({ } } if (style == null) { - return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']); + return h('span', {}, ['[', token.props.name, ' ', ...genEl(token.children), ']']); } else { return h('span', { style: 'display: inline-block;' + style, diff --git a/src/client/components/modal-page-window.vue b/src/client/components/modal-page-window.vue index e7d96f7a6f..cb81a974f5 100644 --- a/src/client/components/modal-page-window.vue +++ b/src/client/components/modal-page-window.vue @@ -2,11 +2,15 @@ <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> <div class="hrmcaedk _window _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> <div class="header" @contextmenu="onContextmenu"> - <span class="title"> - <XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="$refs.modal.close()"/> + <button v-if="history.length > 0" class="_button" @click="back()" v-tooltip="$ts.goBack"><i class="fas fa-arrow-left"></i></button> + <span v-else style="display: inline-block; width: 20px"></span> + <span v-if="pageInfo" class="title"> + <i v-if="pageInfo.icon" class="icon" :class="pageInfo.icon"></i> + <span>{{ pageInfo.title }}</span> </span> + <button class="_button" @click="$refs.modal.close()"><i class="fas fa-times"></i></button> </div> - <div class="body _flat_"> + <div class="body _fitSide_"> <keep-alive> <component :is="component" v-bind="props" :ref="changePage"/> </keep-alive> @@ -18,7 +22,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkModal from '@client/components/ui/modal.vue'; -import XHeader from '@client/ui/_common_/header.vue'; import { popout } from '@client/scripts/popout'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { resolve } from '@client/router'; @@ -29,7 +32,6 @@ import * as os from '@client/os'; export default defineComponent({ components: { MkModal, - XHeader, }, inject: { @@ -42,7 +44,8 @@ export default defineComponent({ return { navHook: (path) => { this.navigate(path); - } + }, + shouldHeaderThin: true, }; }, @@ -172,19 +175,39 @@ export default defineComponent({ $height-narrow: 42px; display: flex; flex-shrink: 0; + height: $height; + line-height: $height; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; box-shadow: 0px 1px var(--divider); - > .title { - flex: 1; + > button { height: $height; - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + width: $height; + + &:hover { + color: var(--fgHighlighted); + } + } + + @media (max-width: 500px) { + height: $height-narrow; + line-height: $height-narrow; + padding-left: 16px; - @media (max-width: 500px) { + > button { height: $height-narrow; - padding-left: 16px; + width: $height-narrow; + } + } + + > .title { + flex: 1; + + > .icon { + margin-right: 0.5em; } } } diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue index e7f116d1fd..40b0a68c58 100644 --- a/src/client/components/note-detailed.vue +++ b/src/client/components/note-detailed.vue @@ -59,7 +59,7 @@ <div class="body"> <p v-if="appearNote.cw != null" class="cw"> <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <XCwButton v-model:value="showContent" :note="appearNote"/> + <XCwButton v-model="showContent" :note="appearNote"/> </p> <div class="content" v-show="appearNote.cw == null || showContent"> <div class="text"> @@ -80,7 +80,7 @@ </div> <XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/> <MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="true" class="url-preview"/> - <div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div> + <div class="renote" v-if="appearNote.renote"><XNoteSimple :note="appearNote.renote"/></div> </div> <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> </div> @@ -132,7 +132,7 @@ import * as mfm from 'mfm-js'; import { sum } from '../../prelude/array'; import XSub from './note.sub.vue'; import XNoteHeader from './note-header.vue'; -import XNotePreview from './note-preview.vue'; +import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; @@ -153,7 +153,7 @@ export default defineComponent({ components: { XSub, XNoteHeader, - XNotePreview, + XNoteSimple, XReactionsViewer, XMediaList, XCwButton, diff --git a/src/client/components/note-preview.vue b/src/client/components/note-preview.vue index 4248c2bb1d..a474a01341 100644 --- a/src/client/components/note-preview.vue +++ b/src/client/components/note-preview.vue @@ -1,15 +1,13 @@ <template> -<div class="yohlumlk" v-size="{ min: [350, 500] }"> - <MkAvatar class="avatar" :user="note.user"/> +<div class="fefdfafb" v-size="{ min: [350, 500] }"> + <MkAvatar class="avatar" :user="$i"/> <div class="main"> - <XNoteHeader class="header" :note="note" :mini="true"/> + <div class="header"> + <MkUserName :user="$i"/> + </div> <div class="body"> - <p v-if="note.cw != null" class="cw"> - <span class="text" v-if="note.cw != ''">{{ note.cw }}</span> - <XCwButton v-model:value="showContent" :note="note"/> - </p> - <div class="content" v-show="note.cw == null || showContent"> - <XSubNote-content class="text" :note="note"/> + <div class="content"> + <Mfm :text="text" :author="$i" :i="$i"/> </div> </div> </div> @@ -18,35 +16,22 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; -import XCwButton from './cw-button.vue'; -import * as os from '@client/os'; export default defineComponent({ components: { - XNoteHeader, - XSubNoteContent, - XCwButton, }, props: { - note: { - type: Object, + text: { + type: String, required: true } }, - - data() { - return { - showContent: false - }; - } }); </script> <style lang="scss" scoped> -.yohlumlk { +.fefdfafb { display: flex; margin: 0; padding: 0; diff --git a/src/client/components/note-simple.vue b/src/client/components/note-simple.vue new file mode 100644 index 0000000000..406a475cd9 --- /dev/null +++ b/src/client/components/note-simple.vue @@ -0,0 +1,113 @@ +<template> +<div class="yohlumlk" v-size="{ min: [350, 500] }"> + <MkAvatar class="avatar" :user="note.user"/> + <div class="main"> + <XNoteHeader class="header" :note="note" :mini="true"/> + <div class="body"> + <p v-if="note.cw != null" class="cw"> + <span class="text" v-if="note.cw != ''">{{ note.cw }}</span> + <XCwButton v-model="showContent" :note="note"/> + </p> + <div class="content" v-show="note.cw == null || showContent"> + <XSubNote-content class="text" :note="note"/> + </div> + </div> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import XNoteHeader from './note-header.vue'; +import XSubNoteContent from './sub-note-content.vue'; +import XCwButton from './cw-button.vue'; +import * as os from '@client/os'; + +export default defineComponent({ + components: { + XNoteHeader, + XSubNoteContent, + XCwButton, + }, + + props: { + note: { + type: Object, + required: true + } + }, + + data() { + return { + showContent: false + }; + } +}); +</script> + +<style lang="scss" scoped> +.yohlumlk { + display: flex; + margin: 0; + padding: 0; + overflow: clip; + font-size: 0.95em; + + &.min-width_350px { + > .avatar { + margin: 0 10px 0 0; + width: 44px; + height: 44px; + } + } + + &.min-width_500px { + > .avatar { + margin: 0 12px 0 0; + width: 48px; + height: 48px; + } + } + + > .avatar { + flex-shrink: 0; + display: block; + margin: 0 10px 0 0; + width: 40px; + height: 40px; + border-radius: 8px; + } + + > .main { + flex: 1; + min-width: 0; + + > .header { + margin-bottom: 2px; + } + + > .body { + + > .cw { + cursor: default; + display: block; + margin: 0; + padding: 0; + overflow-wrap: break-word; + + > .text { + margin-right: 8px; + } + } + + > .content { + > .text { + cursor: default; + margin: 0; + padding: 0; + } + } + } + } +} +</style> diff --git a/src/client/components/note.sub.vue b/src/client/components/note.sub.vue index 899c4b2f16..157b65ec5c 100644 --- a/src/client/components/note.sub.vue +++ b/src/client/components/note.sub.vue @@ -7,7 +7,7 @@ <div class="body"> <p v-if="note.cw != null" class="cw"> <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis" /> - <XCwButton v-model:value="showContent" :note="note"/> + <XCwButton v-model="showContent" :note="note"/> </p> <div class="content" v-show="note.cw == null || showContent"> <XSubNote-content class="text" :note="note"/> diff --git a/src/client/components/note.vue b/src/client/components/note.vue index 38b529dd91..91a3e3b87d 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -43,7 +43,7 @@ <div class="body"> <p v-if="appearNote.cw != null" class="cw"> <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <XCwButton v-model:value="showContent" :note="appearNote"/> + <XCwButton v-model="showContent" :note="appearNote"/> </p> <div class="content" :class="{ collapsed }" v-show="appearNote.cw == null || showContent"> <div class="text"> @@ -64,7 +64,7 @@ </div> <XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/> <MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="false" class="url-preview"/> - <div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div> + <div class="renote" v-if="appearNote.renote"><XNoteSimple :note="appearNote.renote"/></div> <button v-if="collapsed" class="fade _button" @click="collapsed = false"> <span>{{ $ts.showMore }}</span> </button> @@ -114,7 +114,7 @@ import * as mfm from 'mfm-js'; import { sum } from '../../prelude/array'; import XSub from './note.sub.vue'; import XNoteHeader from './note-header.vue'; -import XNotePreview from './note-preview.vue'; +import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; @@ -134,7 +134,7 @@ export default defineComponent({ components: { XSub, XNoteHeader, - XNotePreview, + XNoteSimple, XReactionsViewer, XMediaList, XCwButton, @@ -888,7 +888,7 @@ export default defineComponent({ //content-visibility: auto; //contain-intrinsic-size: 0 128px; - &:focus { + &:focus-visible { outline: none; &:after { diff --git a/src/client/components/notification-setting-window.vue b/src/client/components/notification-setting-window.vue index c33106ae15..14e0b76cc6 100644 --- a/src/client/components/notification-setting-window.vue +++ b/src/client/components/notification-setting-window.vue @@ -29,10 +29,10 @@ <script lang="ts"> import { defineComponent, PropType } from 'vue'; import XModalWindow from '@client/components/ui/modal-window.vue'; -import MkSwitch from './ui/switch.vue'; +import MkSwitch from './form/switch.vue'; import MkInfo from './ui/info.vue'; import MkButton from './ui/button.vue'; -import { notificationTypes } from '../../types'; +import { notificationTypes } from '@/types'; export default defineComponent({ components: { diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue index e91f18a693..78c1cce0c7 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -26,7 +26,7 @@ import paging from '@client/scripts/paging'; import XNotification from './notification.vue'; import XList from './date-separated-list.vue'; import XNote from './note.vue'; -import { notificationTypes } from '../../types'; +import { notificationTypes } from '@/types'; import * as os from '@client/os'; import MkButton from '@client/components/ui/button.vue'; @@ -48,6 +48,11 @@ export default defineComponent({ required: false, default: null, }, + unreadOnly: { + type: Boolean, + required: false, + default: false, + }, }, data() { @@ -58,6 +63,7 @@ export default defineComponent({ limit: 10, params: () => ({ includeTypes: this.allIncludeTypes || undefined, + unreadOnly: this.unreadOnly, }) }, }; @@ -76,6 +82,11 @@ export default defineComponent({ }, deep: true }, + unreadOnly: { + handler() { + this.reload(); + }, + }, // TODO: vue/vuexのバグか仕様かは不明なものの、プロフィール更新するなどして $i が更新されると、 // mutingNotificationTypes に変化が無くてもこのハンドラーが呼び出され無駄なリロードが発生するのを直す '$i.mutingNotificationTypes': { diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue index fbc9f0b7fd..7d15c75d62 100644 --- a/src/client/components/page-window.vue +++ b/src/client/components/page-window.vue @@ -3,14 +3,20 @@ :initial-width="500" :initial-height="500" :can-resize="true" - :close-button="false" + :close-button="true" :contextmenu="contextmenu" @closed="$emit('closed')" > <template #header> - <XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="close()" :title-only="true"/> + <template v-if="pageInfo"> + <i v-if="pageInfo.icon" class="icon" :class="pageInfo.icon" style="margin-right: 0.5em;"></i> + <span>{{ pageInfo.title }}</span> + </template> </template> - <div class="yrolvcoq _flat_"> + <template #headerLeft> + <button v-if="history.length > 0" class="_button" @click="back()" v-tooltip="$ts.goBack"><i class="fas fa-arrow-left"></i></button> + </template> + <div class="yrolvcoq _fitSide_"> <component :is="component" v-bind="props" :ref="changePage"/> </div> </XWindow> @@ -19,7 +25,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XWindow from '@client/components/ui/window.vue'; -import XHeader from '@client/ui/_common_/header.vue'; import { popout } from '@client/scripts/popout'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { resolve } from '@client/router'; @@ -29,7 +34,6 @@ import * as symbols from '@client/symbols'; export default defineComponent({ components: { XWindow, - XHeader, }, inject: { @@ -42,7 +46,8 @@ export default defineComponent({ return { navHook: (path) => { this.navigate(path); - } + }, + shouldHeaderThin: true, }; }, diff --git a/src/client/components/page/page.number-input.vue b/src/client/components/page/page.number-input.vue index 9c4a537e15..5d9168f130 100644 --- a/src/client/components/page/page.number-input.vue +++ b/src/client/components/page/page.number-input.vue @@ -8,7 +8,7 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; -import MkInput from '../ui/input.vue'; +import MkInput from '../form/input.vue'; import * as os from '@client/os'; import { Hpml } from '@client/scripts/hpml/evaluator'; import { NumberInputVarBlock } from '@client/scripts/hpml/block'; diff --git a/src/client/components/page/page.post.vue b/src/client/components/page/page.post.vue index 7b061d8cda..c20d7cade1 100644 --- a/src/client/components/page/page.post.vue +++ b/src/client/components/page/page.post.vue @@ -10,7 +10,7 @@ <script lang="ts"> import { defineComponent, PropType } from 'vue'; -import MkTextarea from '../ui/textarea.vue'; +import MkTextarea from '../form/textarea.vue'; import MkButton from '../ui/button.vue'; import { apiUrl } from '@client/config'; import * as os from '@client/os'; diff --git a/src/client/components/page/page.radio-button.vue b/src/client/components/page/page.radio-button.vue index f6f146b52f..590e59d706 100644 --- a/src/client/components/page/page.radio-button.vue +++ b/src/client/components/page/page.radio-button.vue @@ -7,7 +7,7 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; -import MkRadio from '../ui/radio.vue'; +import MkRadio from '../form/radio.vue'; import * as os from '@client/os'; import { Hpml } from '@client/scripts/hpml/evaluator'; import { RadioButtonVarBlock } from '@client/scripts/hpml/block'; diff --git a/src/client/components/page/page.switch.vue b/src/client/components/page/page.switch.vue index 8818e6cbcf..4d74e5df39 100644 --- a/src/client/components/page/page.switch.vue +++ b/src/client/components/page/page.switch.vue @@ -6,7 +6,7 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; -import MkSwitch from '../ui/switch.vue'; +import MkSwitch from '../form/switch.vue'; import * as os from '@client/os'; import { Hpml } from '@client/scripts/hpml/evaluator'; import { SwitchVarBlock } from '@client/scripts/hpml/block'; diff --git a/src/client/components/page/page.text-input.vue b/src/client/components/page/page.text-input.vue index 752d3d7257..6e9ac0b543 100644 --- a/src/client/components/page/page.text-input.vue +++ b/src/client/components/page/page.text-input.vue @@ -8,7 +8,7 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; -import MkInput from '../ui/input.vue'; +import MkInput from '../form/input.vue'; import * as os from '@client/os'; import { Hpml } from '@client/scripts/hpml/evaluator'; import { TextInputVarBlock } from '@client/scripts/hpml/block'; diff --git a/src/client/components/page/page.textarea-input.vue b/src/client/components/page/page.textarea-input.vue index e6cf5117f9..dfcb398937 100644 --- a/src/client/components/page/page.textarea-input.vue +++ b/src/client/components/page/page.textarea-input.vue @@ -8,7 +8,7 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; -import MkTextarea from '../ui/textarea.vue'; +import MkTextarea from '../form/textarea.vue'; import * as os from '@client/os'; import { Hpml } from '@client/scripts/hpml/evaluator'; import { HpmlTextInput } from '@client/scripts/hpml'; diff --git a/src/client/components/page/page.textarea.vue b/src/client/components/page/page.textarea.vue index 974c7f2c57..cf953bf041 100644 --- a/src/client/components/page/page.textarea.vue +++ b/src/client/components/page/page.textarea.vue @@ -6,7 +6,7 @@ import { TextBlock } from '@client/scripts/hpml/block'; import { Hpml } from '@client/scripts/hpml/evaluator'; import { defineComponent, PropType } from 'vue'; -import MkTextarea from '../ui/textarea.vue'; +import MkTextarea from '../form/textarea.vue'; export default defineComponent({ components: { diff --git a/src/client/components/poll-editor.vue b/src/client/components/poll-editor.vue index 0a9a1c6a03..b28a1c8baa 100644 --- a/src/client/components/poll-editor.vue +++ b/src/client/components/poll-editor.vue @@ -51,9 +51,9 @@ import { defineComponent } from 'vue'; import { addTime } from '../../prelude/time'; import { formatDateTimeString } from '@/misc/format-time-string'; -import MkInput from './ui/input.vue'; -import MkSelect from './ui/select.vue'; -import MkSwitch from './ui/switch.vue'; +import MkInput from './form/input.vue'; +import MkSelect from './form/select.vue'; +import MkSwitch from './form/switch.vue'; import MkButton from './ui/button.vue'; export default defineComponent({ diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 657053cc93..a1d89d2a2e 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -1,6 +1,6 @@ <template> <div class="gafaadew" :class="{ modal, _popup: modal }" - v-size="{ max: [500] }" + v-size="{ max: [310, 500] }" @dragover.stop="onDragover" @dragenter="onDragenter" @dragleave="onDragleave" @@ -17,12 +17,13 @@ <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> </button> - <button class="submit _buttonPrimary" :disabled="!canPost" @click="post" data-cy-open-post-form-submit>{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> + <button class="_button preview" @click="showPreview = !showPreview" :class="{ active: showPreview }" v-tooltip="$ts.previewNoteText"><i class="fas fa-file-code"></i></button> + <button class="submit _buttonGradate" :disabled="!canPost" @click="post" data-cy-open-post-form-submit>{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> </div> </header> <div class="form" :class="{ fixed }"> - <XNotePreview class="preview" v-if="reply" :note="reply"/> - <XNotePreview class="preview" v-if="renote" :note="renote"/> + <XNoteSimple class="preview" v-if="reply" :note="reply"/> + <XNoteSimple class="preview" v-if="renote" :note="renote"/> <div class="with-quote" v-if="quoteId"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> <div v-if="visibility === 'specified'" class="to-specified"> <span style="margin-right: 8px;">{{ $ts.recipient }}</span> @@ -40,6 +41,7 @@ <input v-show="withHashtags" ref="hashtags" class="hashtags" v-model="hashtags" :placeholder="$ts.hashtags" list="hashtags"> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> + <XNotePreview class="preview" v-if="showPreview" :text="text"/> <footer> <button class="_button" @click="chooseFileFrom" v-tooltip="$ts.attachFile"><i class="fas fa-photo-video"></i></button> <button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button> @@ -61,6 +63,7 @@ import { defineComponent, defineAsyncComponent } from 'vue'; import insertTextAtCursor from 'insert-text-at-cursor'; import { length } from 'stringz'; import { toASCII } from 'punycode/'; +import XNoteSimple from './note-simple.vue'; import XNotePreview from './note-preview.vue'; import * as mfm from 'mfm-js'; import { host, url } from '@client/config'; @@ -80,6 +83,7 @@ import { defaultStore } from '@client/store'; export default defineComponent({ components: { + XNoteSimple, XNotePreview, XPostFormAttaches: defineAsyncComponent(() => import('./post-form-attaches.vue')), XPollEditor: defineAsyncComponent(() => import('./poll-editor.vue')), @@ -143,6 +147,7 @@ export default defineComponent({ files: [], poll: null, useCw: false, + showPreview: false, cw: null, localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly, visibility: this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility, @@ -717,7 +722,7 @@ export default defineComponent({ > .visibility { height: 34px; width: 34px; - margin: 0 8px; + margin: 0 0 0 8px; & + .localOnly { margin-left: 0 !important; @@ -729,6 +734,24 @@ export default defineComponent({ opacity: 0.7; } + > .preview { + display: inline-block; + padding: 0; + margin: 0 8px 0 0; + font-size: 16px; + width: 34px; + height: 34px; + border-radius: 6px; + + &:hover { + background: var(--X5); + } + + &.active { + color: var(--accent); + } + } + > .submit { margin: 16px 16px 16px 0; padding: 0 12px; @@ -736,6 +759,7 @@ export default defineComponent({ font-weight: bold; vertical-align: bottom; border-radius: 4px; + font-size: 0.9em; &:disabled { opacity: 0.7; @@ -819,7 +843,7 @@ export default defineComponent({ color: var(--fg); font-family: inherit; - &:focus { + &:focus-visible { outline: none; } @@ -914,5 +938,17 @@ export default defineComponent({ } } } + + &.max-width_310px { + > .form { + > footer { + > button { + font-size: 14px; + width: 44px; + height: 44px; + } + } + } + } } </style> diff --git a/src/client/components/sample.vue b/src/client/components/sample.vue index bce02466f6..c8b46a80e7 100644 --- a/src/client/components/sample.vue +++ b/src/client/components/sample.vue @@ -30,10 +30,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkRadio from '@client/components/ui/radio.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSwitch from '@client/components/form/switch.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkRadio from '@client/components/form/radio.vue'; import * as os from '@client/os'; import * as config from '@client/config'; diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue index 69f527b7d6..d6e1ee8b68 100755 --- a/src/client/components/signin.vue +++ b/src/client/components/signin.vue @@ -1,17 +1,17 @@ <template> <form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> - <div class="auth _section"> + <div class="auth _section _formRoot"> <div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div> <div class="normal-signin" v-if="!totpLogin"> - <MkInput v-model="username" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:modelValue="onUsernameChange" data-cy-signin-username> + <MkInput class="_formBlock" v-model="username" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:modelValue="onUsernameChange" data-cy-signin-username> <template #prefix>@</template> <template #suffix>@{{ host }}</template> </MkInput> - <MkInput v-model="password" :placeholder="$ts.password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required data-cy-signin-password> + <MkInput class="_formBlock" v-model="password" :placeholder="$ts.password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required data-cy-signin-password> <template #prefix><i class="fas fa-lock"></i></template> <template #caption><button class="_textButton" @click="resetPassword" type="button">{{ $ts.forgotPassword }}</button></template> </MkInput> - <MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> + <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> </div> <div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }"> <div v-if="user && user.securityKeys" class="twofa-group tap-group"> @@ -49,7 +49,7 @@ import { defineComponent } from 'vue'; import { toUnicode } from 'punycode/'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import { apiUrl, host } from '@client/config'; import { byteify, hexify } from '@client/scripts/2fa'; import * as os from '@client/os'; diff --git a/src/client/components/signup-dialog.vue b/src/client/components/signup-dialog.vue index df1a525055..9741e8c73b 100644 --- a/src/client/components/signup-dialog.vue +++ b/src/client/components/signup-dialog.vue @@ -9,7 +9,7 @@ <div class="_monolithic_"> <div class="_section"> - <XSignup :auto-set="autoSet" @signup="onSignup"/> + <XSignup :auto-set="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/> </div> </div> </XModalWindow> @@ -40,6 +40,10 @@ export default defineComponent({ onSignup(res) { this.$emit('done', res); this.$refs.dialog.close(); + }, + + onSignupEmailPending() { + this.$refs.dialog.close(); } } }); diff --git a/src/client/components/signup.vue b/src/client/components/signup.vue index d332274111..cb25eadf06 100644 --- a/src/client/components/signup.vue +++ b/src/client/components/signup.vue @@ -1,25 +1,35 @@ <template> -<form class="qlvuhzng" @submit.prevent="onSubmit" :autocomplete="Math.random()"> +<form class="qlvuhzng _formRoot" @submit.prevent="onSubmit" :autocomplete="Math.random()"> <template v-if="meta"> - <MkInput class="_inputNoTopMargin" v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required> + <MkInput class="_formBlock" v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required> <template #label>{{ $ts.invitationCode }}</template> <template #prefix><i class="fas fa-key"></i></template> </MkInput> - <MkInput class="_inputNoTopMargin" v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeUsername" data-cy-signup-username> + <MkInput class="_formBlock" v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeUsername" data-cy-signup-username> <template #label>{{ $ts.username }} <div class="_button _help" v-tooltip:dialog="$ts.usernameInfo"><i class="far fa-question-circle"></i></div></template> <template #prefix>@</template> <template #suffix>@{{ host }}</template> <template #caption> - <span v-if="usernameState == 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> - <span v-if="usernameState == 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> - <span v-if="usernameState == 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> - <span v-if="usernameState == 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> - <span v-if="usernameState == 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span> - <span v-if="usernameState == 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span> - <span v-if="usernameState == 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> + <span v-if="usernameState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> + <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> + <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> + <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> + <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span> + <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span> + <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> </template> </MkInput> - <MkInput v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword" data-cy-signup-password> + <MkInput v-if="meta.emailRequiredForSignup" class="_formBlock" v-model="email" type="email" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeEmail" data-cy-signup-email> + <template #label>{{ $ts.emailAddress }} <div class="_button _help" v-tooltip:dialog="$ts._signup.emailAddressInfo"><i class="far fa-question-circle"></i></div></template> + <template #prefix><i class="fas fa-envelope"></i></template> + <template #caption> + <span v-if="emailState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> + <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> + <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> + <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> + </template> + </MkInput> + <MkInput class="_formBlock" v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword" data-cy-signup-password> <template #label>{{ $ts.password }}</template> <template #prefix><i class="fas fa-lock"></i></template> <template #caption> @@ -28,7 +38,7 @@ <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span> </template> </MkInput> - <MkInput v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePasswordRetype" data-cy-signup-password-retype> + <MkInput class="_formBlock" v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePasswordRetype" data-cy-signup-password-retype> <template #label>{{ $ts.password }} ({{ $ts.retype }})</template> <template #prefix><i class="fas fa-lock"></i></template> <template #caption> @@ -36,7 +46,7 @@ <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span> </template> </MkInput> - <label v-if="meta.tosUrl" class="tou"> + <label v-if="meta.tosUrl" class="_formBlock tou"> <input type="checkbox" v-model="ToSAgreement"> <I18n :src="$ts.agreeTo"> <template #0> @@ -44,9 +54,9 @@ </template> </I18n> </label> - <captcha v-if="meta.enableHcaptcha" class="captcha" provider="hcaptcha" ref="hcaptcha" v-model:value="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/> - <captcha v-if="meta.enableRecaptcha" class="captcha" provider="recaptcha" ref="recaptcha" v-model:value="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/> - <MkButton type="submit" :disabled="shouldDisableSubmitting" primary data-cy-signup-submit>{{ $ts.start }}</MkButton> + <captcha v-if="meta.enableHcaptcha" class="_formBlock captcha" provider="hcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/> + <captcha v-if="meta.enableRecaptcha" class="_formBlock captcha" provider="recaptcha" ref="recaptcha" v-model="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/> + <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> </template> </form> </template> @@ -57,8 +67,8 @@ const getPasswordStrength = require('syuilo-password-strength'); import { toUnicode } from 'punycode/'; import { host, url } from '@client/config'; import MkButton from './ui/button.vue'; -import MkInput from './ui/input.vue'; -import MkSwitch from './ui/switch.vue'; +import MkInput from './form/input.vue'; +import MkSwitch from './form/switch.vue'; import * as os from '@client/os'; import { login } from '@client/account'; @@ -87,8 +97,10 @@ export default defineComponent({ password: '', retypedPassword: '', invitationCode: '', + email: '', url, usernameState: null, + emailState: null, passwordStrength: '', passwordRetypeState: null, submitting: false, @@ -148,6 +160,23 @@ export default defineComponent({ }); }, + onChangeEmail() { + if (this.email == '') { + this.emailState = null; + return; + } + + this.emailState = 'wait'; + + os.api('email-address/available', { + emailAddress: this.email + }).then(result => { + this.emailState = result.available ? 'ok' : 'unavailable'; + }).catch(err => { + this.emailState = 'error'; + }); + }, + onChangePassword() { if (this.password == '') { this.passwordStrength = ''; @@ -174,20 +203,30 @@ export default defineComponent({ os.api('signup', { username: this.username, password: this.password, + emailAddress: this.email, invitationCode: this.invitationCode, 'hcaptcha-response': this.hCaptchaResponse, 'g-recaptcha-response': this.reCaptchaResponse, }).then(() => { - return os.api('signin', { - username: this.username, - password: this.password - }).then(res => { - this.$emit('signup', res); + if (this.meta.emailRequiredForSignup) { + os.dialog({ + type: 'success', + title: this.$ts._signup.almostThere, + text: this.$t('_signup.emailSent', { email: this.email }), + }); + this.$emit('signupEmailPending'); + } else { + os.api('signin', { + username: this.username, + password: this.password + }).then(res => { + this.$emit('signup', res); - if (this.autoSet) { - return login(res.i); - } - }); + if (this.autoSet) { + login(res.i); + } + }); + } }).catch(() => { this.submitting = false; this.$refs.hcaptcha?.reset?.(); diff --git a/src/client/components/tab.vue b/src/client/components/tab.vue index 3902b7f98f..ce86af8f95 100644 --- a/src/client/components/tab.vue +++ b/src/client/components/tab.vue @@ -3,7 +3,7 @@ import { defineComponent, h, resolveDirective, withDirectives } from 'vue'; export default defineComponent({ props: { - value: { + modelValue: { required: true, }, }, @@ -13,11 +13,11 @@ export default defineComponent({ return withDirectives(h('div', { class: 'pxhvhrfw', }, options.map(option => withDirectives(h('button', { - class: ['_button', { active: this.value === option.props.value }], + class: ['_button', { active: this.modelValue === option.props.value }], key: option.key, - disabled: this.value === option.props.value, + disabled: this.modelValue === option.props.value, onClick: () => { - this.$emit('update:value', option.props.value); + this.$emit('update:modelValue', option.props.value); } }, option.children), [ [resolveDirective('click-anime')] @@ -35,8 +35,8 @@ export default defineComponent({ > button { flex: 1; - padding: 15px 12px 12px 12px; - border-bottom: solid 3px transparent; + padding: 10px 8px; + border-radius: 6px; &:disabled { opacity: 1 !important; @@ -45,11 +45,16 @@ export default defineComponent({ &.active { color: var(--accent); - border-bottom-color: var(--accent); + background: var(--accentedBg); } &:not(.active):hover { color: var(--fgHighlighted); + background: var(--panelHighlight); + } + + &:not(:first-child) { + margin-left: 8px; } > .icon { @@ -61,7 +66,7 @@ export default defineComponent({ font-size: 80%; > button { - padding: 11px 8px 8px 8px; + padding: 11px 8px; } } } diff --git a/src/client/components/taskmanager.api-window.vue b/src/client/components/taskmanager.api-window.vue index c9b2c43413..807e4a0075 100644 --- a/src/client/components/taskmanager.api-window.vue +++ b/src/client/components/taskmanager.api-window.vue @@ -9,7 +9,7 @@ <template #header>Req Viewer</template> <div class="rlkneywz"> - <MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);"> + <MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);"> <option value="req">Request</option> <option value="res">Response</option> </MkTab> diff --git a/src/client/components/taskmanager.vue b/src/client/components/taskmanager.vue index cb8cb78748..6f3d1b0354 100644 --- a/src/client/components/taskmanager.vue +++ b/src/client/components/taskmanager.vue @@ -4,7 +4,7 @@ <i class="fas fa-terminal" style="margin-right: 0.5em;"></i>Task Manager </template> <div class="qljqmnzj _monospace"> - <MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);"> + <MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);"> <option value="windows">Windows</option> <option value="stream">Stream</option> <option value="streamPool">Stream (Pool)</option> diff --git a/src/client/components/token-generate-window.vue b/src/client/components/token-generate-window.vue index fe61f61efa..86312564cc 100644 --- a/src/client/components/token-generate-window.vue +++ b/src/client/components/token-generate-window.vue @@ -31,9 +31,9 @@ import { defineComponent } from 'vue'; import { kinds } from '@/misc/api-permissions'; import XModalWindow from '@client/components/ui/modal-window.vue'; -import MkInput from './ui/input.vue'; -import MkTextarea from './ui/textarea.vue'; -import MkSwitch from './ui/switch.vue'; +import MkInput from './form/input.vue'; +import MkTextarea from './form/textarea.vue'; +import MkSwitch from './form/switch.vue'; import MkButton from './ui/button.vue'; import MkInfo from './ui/info.vue'; diff --git a/src/client/components/ui/button.vue b/src/client/components/ui/button.vue index d6ac42994f..b5f4547c84 100644 --- a/src/client/components/ui/button.vue +++ b/src/client/components/ui/button.vue @@ -1,7 +1,6 @@ <template> -<component class="bghgjjyj _button" - :is="link ? 'MkA' : 'button'" - :class="{ inline, primary, danger, full }" +<button v-if="!link" class="bghgjjyj _button" + :class="{ inline, primary, gradate, danger, rounded, full }" :type="type" @click="$emit('click', $event)" @mousedown="onMousedown" @@ -10,7 +9,17 @@ <div class="content"> <slot></slot> </div> -</component> +</button> +<MkA v-else class="bghgjjyj _button" + :class="{ inline, primary, gradate, danger, rounded, full }" + :to="to" + @mousedown="onMousedown" +> + <div ref="ripples" class="ripples"></div> + <div class="content"> + <slot></slot> + </div> +</MkA> </template> <script lang="ts"> @@ -27,6 +36,16 @@ export default defineComponent({ required: false, default: false }, + gradate: { + type: Boolean, + required: false, + default: false + }, + rounded: { + type: Boolean, + required: false, + default: false + }, inline: { type: Boolean, required: false, @@ -37,6 +56,10 @@ export default defineComponent({ required: false, default: false }, + to: { + type: String, + required: false + }, autofocus: { type: Boolean, required: false, @@ -119,13 +142,13 @@ export default defineComponent({ padding: 8px 14px; text-align: center; font-weight: normal; - font-size: 0.9em; - line-height: 24px; + font-size: 0.8em; + line-height: 22px; box-shadow: none; text-decoration: none; background: var(--buttonBg); - border-radius: 999px; - overflow: hidden; + border-radius: 4px; + overflow: clip; box-sizing: border-box; transition: background 0.1s ease; @@ -141,6 +164,10 @@ export default defineComponent({ width: 100%; } + &.rounded { + border-radius: 999px; + } + &.primary { font-weight: bold; color: var(--fgOnAccent) !important; @@ -155,6 +182,20 @@ export default defineComponent({ } } + &.gradate { + font-weight: bold; + color: var(--fgOnAccent) !important; + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + + &:not(:disabled):hover { + background: linear-gradient(90deg, var(--X8), var(--X8)); + } + + &:not(:disabled):active { + background: linear-gradient(90deg, var(--X8), var(--X8)); + } + } + &.danger { color: #ff2a2a; @@ -176,19 +217,11 @@ export default defineComponent({ opacity: 0.7; } - &:focus { + &:focus-visible { outline: solid 2px var(--focus); outline-offset: 2px; } - &.inline + .bghgjjyj { - margin-left: 12px; - } - - &:not(.inline) + .bghgjjyj { - margin-top: 16px; - } - &.inline { display: inline-block; width: auto; diff --git a/src/client/components/ui/folder.vue b/src/client/components/ui/folder.vue index eecf1d8be1..d0616a57c1 100644 --- a/src/client/components/ui/folder.vue +++ b/src/client/components/ui/folder.vue @@ -1,6 +1,6 @@ <template> <div class="ssazuxis" v-size="{ max: [500] }"> - <header @click="showBody = !showBody" class="_button"> + <header @click="showBody = !showBody" class="_button" :style="{ background: bg }"> <div class="title"><slot name="header"></slot></div> <div class="divider"></div> <button class="_button"> @@ -23,6 +23,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; +import * as tinycolor from 'tinycolor2'; const localStoragePrefix = 'ui:folder:'; @@ -41,6 +42,7 @@ export default defineComponent({ }, data() { return { + bg: null, showBody: (this.persistKey && localStorage.getItem(localStoragePrefix + this.persistKey)) ? localStorage.getItem(localStoragePrefix + this.persistKey) === 't' : this.expanded, }; }, @@ -51,6 +53,21 @@ export default defineComponent({ } } }, + mounted() { + function getParentBg(el: Element | null): string { + if (el == null || el.tagName === 'BODY') return 'var(--bg)'; + const bg = el.style.background || el.style.backgroundColor; + if (bg) { + return bg; + } else { + return getParentBg(el.parentElement); + } + } + const rawBg = getParentBg(this.$el); + const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); + bg.setAlpha(0.85); + this.bg = bg.toRgbString(); + }, methods: { toggleContent(show: boolean) { this.showBody = show; @@ -100,12 +117,8 @@ export default defineComponent({ position: sticky; top: var(--stickyTop, 0px); padding: var(--x-padding); - background: var(--x-header, var(--panel)); - /* TODO panelの半透明バージョンをプログラマティックに作りたい - background: var(--X17); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(20px)); - */ > .title { margin: 0; @@ -141,7 +154,7 @@ export default defineComponent({ } } -._flat_ .ssazuxis { +._fitSide_ .ssazuxis { > header { padding: 0 16px; } diff --git a/src/client/components/ui/info.vue b/src/client/components/ui/info.vue index 513682ef55..e16f2736f1 100644 --- a/src/client/components/ui/info.vue +++ b/src/client/components/ui/info.vue @@ -27,7 +27,6 @@ export default defineComponent({ <style lang="scss" scoped> .fpezltsf { - margin: 16px 0; padding: 16px; font-size: 90%; background: var(--infoBg); @@ -39,20 +38,12 @@ export default defineComponent({ color: var(--infoWarnFg); } - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - > i { margin-right: 4px; } } -._flat_ .fpezltsf { +._fitSide_ .fpezltsf { border-radius: 0; } </style> diff --git a/src/client/components/ui/menu.vue b/src/client/components/ui/menu.vue index 26b4b04b11..da24d90170 100644 --- a/src/client/components/ui/menu.vue +++ b/src/client/components/ui/menu.vue @@ -1,5 +1,5 @@ <template> -<div class="rrevdjwt" :class="{ left: align === 'left', pointer: point === 'top' }" +<div class="rrevdjwt" :class="{ center: align === 'center' }" ref="items" @contextmenu.self="e => e.preventDefault()" v-hotkey="keymap" @@ -27,7 +27,7 @@ <MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> </button> - <button v-else @click="clicked(item.action, $event)" :tabindex="i" class="_button item" :class="{ danger: item.danger }"> + <button v-else @click="clicked(item.action, $event)" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active"> <i v-if="item.icon" class="fa-fw" :class="item.icon"></i> <MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> <span>{{ item.text }}</span> @@ -59,10 +59,6 @@ export default defineComponent({ type: String, requried: false }, - point: { - type: String, - requried: false - }, }, emits: ['close'], data() { @@ -145,68 +141,83 @@ export default defineComponent({ <style lang="scss" scoped> .rrevdjwt { padding: 8px 0; + min-width: 200px; - &.pointer { - &:before { - --size: 8px; - content: ''; - display: block; - position: absolute; - top: calc(0px - (var(--size) * 2)); - left: 0; - right: 0; - width: 0; - margin: auto; - border: solid var(--size) transparent; - border-bottom-color: var(--popup); - } - } - - &.left { + &.center { > .item { - text-align: left; + text-align: center; } } > .item { display: block; position: relative; - padding: 8px 16px; + padding: 8px 18px; width: 100%; box-sizing: border-box; white-space: nowrap; font-size: 0.9em; line-height: 20px; - text-align: center; + text-align: left; overflow: hidden; text-overflow: ellipsis; + &:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + margin: auto; + width: calc(100% - 16px); + height: 100%; + border-radius: 6px; + } + + > * { + position: relative; + } + &.danger { color: #ff2a2a; &:hover { color: #fff; - background: #ff4242; + + &:before { + background: #ff4242; + } } &:active { color: #fff; - background: #d42e2e; + + &:before { + background: #d42e2e; + } } } - &:hover { + &.active { color: var(--fgOnAccent); - background: var(--accent); - text-decoration: none; + opacity: 1; + + &:before { + background: var(--accent); + } } - &:active { - color: var(--fgOnAccent); - background: var(--accentDarken); + &:not(:disabled):hover { + color: var(--accent); + text-decoration: none; + + &:before { + background: var(--accentedBg); + } } - &:not(:active):focus { + &:not(:active):focus-visible { box-shadow: 0 0 0 2px var(--focus) inset; } @@ -231,12 +242,12 @@ export default defineComponent({ } > i { - margin-right: 4px; + margin-right: 5px; width: 20px; } > .avatar { - margin-right: 4px; + margin-right: 5px; width: 20px; height: 20px; } diff --git a/src/client/components/ui/popup-menu.vue b/src/client/components/ui/popup-menu.vue index 3590426172..23f7c89f3b 100644 --- a/src/client/components/ui/popup-menu.vue +++ b/src/client/components/ui/popup-menu.vue @@ -1,6 +1,6 @@ <template> -<MkPopup ref="popup" :src="src" @closed="$emit('closed')" #default="{point}"> - <MkMenu :items="items" :align="align" :point="point" @close="$refs.popup.close()" class="_popup _shadow"/> +<MkPopup ref="popup" :src="src" @closed="$emit('closed')"> + <MkMenu :items="items" :align="align" @close="$refs.popup.close()" class="_popup _shadow"/> </MkPopup> </template> diff --git a/src/client/components/ui/popup.vue b/src/client/components/ui/popup.vue index 8497eedecb..0fb1780cc5 100644 --- a/src/client/components/ui/popup.vue +++ b/src/client/components/ui/popup.vue @@ -1,7 +1,7 @@ <template> <transition :name="$store.state.animation ? 'popup-menu' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered"> <div v-show="manualShowing != null ? manualShowing : showing" class="ccczpooj" :class="{ front, fixed, top: position === 'top' }" ref="content" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> - <slot :point="point"></slot> + <slot></slot> </div> </transition> </template> @@ -52,7 +52,6 @@ export default defineComponent({ fixed: false, transformOrigin: 'center', contentClicking: false, - point: null, }; }, @@ -136,10 +135,8 @@ export default defineComponent({ } if (top > rect.top + (this.fixed ? 0 : window.pageYOffset)) { - this.point = 'top'; this.transformOrigin = 'center top'; } else { - this.point = null; this.transformOrigin = 'center'; } diff --git a/src/client/components/ui/radios.vue b/src/client/components/ui/radios.vue deleted file mode 100644 index 8a62b87683..0000000000 --- a/src/client/components/ui/radios.vue +++ /dev/null @@ -1,58 +0,0 @@ -<script lang="ts"> -import { defineComponent, h } from 'vue'; -import MkRadio from '@client/components/ui/radio.vue'; - -export default defineComponent({ - components: { - MkRadio - }, - props: { - modelValue: { - required: false - }, - }, - data() { - return { - value: this.modelValue, - } - }, - watch: { - value() { - this.$emit('update:modelValue', this.value); - } - }, - render() { - const label = this.$slots.desc(); - let options = this.$slots.default(); - - // なぜかFragmentになることがあるため - if (options.length === 1 && options[0].props == null) options = options[0].children; - - return h('div', { - class: 'novjtcto' - }, [ - h('div', label), - ...options.map(option => h(MkRadio, { - key: option.key, - value: option.props.value, - modelValue: this.value, - 'onUpdate:modelValue': value => this.value = value, - }, option.children)) - ]); - } -}); -</script> - -<style lang="scss"> -.novjtcto { - margin: 32px 0; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } -} -</style> diff --git a/src/client/components/ui/range.vue b/src/client/components/ui/range.vue deleted file mode 100644 index 4cfe66a8fc..0000000000 --- a/src/client/components/ui/range.vue +++ /dev/null @@ -1,139 +0,0 @@ -<template> -<div class="timctyfi" :class="{ focused, disabled }"> - <div class="icon"><slot name="icon"></slot></div> - <span class="label"><slot name="label"></slot></span> - <input - type="range" - ref="input" - v-model="v" - :disabled="disabled" - :min="min" - :max="max" - :step="step" - :autofocus="autofocus" - @focus="focused = true" - @blur="focused = false" - @input="$emit('update:value', $event.target.value)" - /> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - value: { - type: Number, - required: false, - default: 0 - }, - disabled: { - type: Boolean, - required: false, - default: false - }, - min: { - type: Number, - required: false, - default: 0 - }, - max: { - type: Number, - required: false, - default: 100 - }, - step: { - type: Number, - required: false, - default: 1 - }, - autofocus: { - type: Boolean, - required: false - } - }, - data() { - return { - v: this.value, - focused: false - }; - }, - watch: { - value(v) { - this.v = parseFloat(v); - } - }, - mounted() { - if (this.autofocus) { - this.$nextTick(() => { - this.$refs.input.focus(); - }); - } - } -}); -</script> - -<style lang="scss" scoped> -.timctyfi { - position: relative; - margin: 8px; - - > .icon { - display: inline-block; - width: 24px; - text-align: center; - } - - > .title { - pointer-events: none; - font-size: 16px; - color: var(--inputLabel); - overflow: hidden; - } - - > input { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: var(--X10); - height: 7px; - margin: 0 8px; - outline: 0; - border: 0; - border-radius: 7px; - - &.disabled { - opacity: 0.6; - cursor: not-allowed; - } - - &::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - box-sizing: content-box; - } - - &::-moz-range-thumb { - -moz-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - } - } -} -</style> diff --git a/src/client/components/ui/select.vue b/src/client/components/ui/select.vue deleted file mode 100644 index e9d43d8a64..0000000000 --- a/src/client/components/ui/select.vue +++ /dev/null @@ -1,262 +0,0 @@ -<template> -<div class="vblkjoeq"> - <div class="label" @click="focus"><slot name="label"></slot></div> - <div class="input" :class="{ inline, disabled, focused }"> - <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> - <select ref="inputEl" - v-model="v" - :disabled="disabled" - :required="required" - :readonly="readonly" - :placeholder="placeholder" - @focus="focused = true" - @blur="focused = false" - @input="onInput" - > - <slot></slot> - </select> - <div class="suffix" ref="suffixEl"><i class="fas fa-chevron-down"></i></div> - </div> - <div class="caption"><slot name="caption"></slot></div> - - <MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> -</div> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; -import MkButton from './button.vue'; - -export default defineComponent({ - components: { - MkButton, - }, - - props: { - modelValue: { - required: true - }, - required: { - type: Boolean, - required: false - }, - readonly: { - type: Boolean, - required: false - }, - disabled: { - type: Boolean, - required: false - }, - placeholder: { - type: String, - required: false - }, - autofocus: { - type: Boolean, - required: false, - default: false - }, - inline: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['change', 'update:modelValue'], - - setup(props, context) { - const { modelValue, autofocus } = toRefs(props); - const v = ref(modelValue.value); - const focused = ref(false); - const changed = ref(false); - const invalid = ref(false); - const filled = computed(() => v.value !== '' && v.value != null); - const inputEl = ref(null); - const prefixEl = ref(null); - const suffixEl = ref(null); - - const focus = () => inputEl.value.focus(); - const onInput = (ev) => { - changed.value = true; - context.emit('change', ev); - }; - - const updated = () => { - changed.value = false; - context.emit('update:modelValue', v.value); - }; - - watch(modelValue, newValue => { - v.value = newValue; - }); - - watch(v, newValue => { - if (!props.manualSave) { - updated(); - } - - invalid.value = inputEl.value.validity.badInput; - }); - - onMounted(() => { - nextTick(() => { - if (autofocus.value) { - focus(); - } - - // このコンポーネントが作成された時、非表示状態である場合がある - // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = setInterval(() => { - if (prefixEl.value) { - if (prefixEl.value.offsetWidth) { - inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; - } - } - if (suffixEl.value) { - if (suffixEl.value.offsetWidth) { - inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; - } - } - }, 100); - - onUnmounted(() => { - clearInterval(clock); - }); - }); - }); - - return { - v, - focused, - invalid, - changed, - filled, - inputEl, - prefixEl, - suffixEl, - focus, - onInput, - updated, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.vblkjoeq { - margin: 1.5em 0; - - > .label { - font-size: 0.85em; - padding: 0 0 8px 12px; - user-select: none; - - &:empty { - display: none; - } - } - - > .caption { - font-size: 0.8em; - padding: 8px 0 0 12px; - color: var(--fgTransparentWeak); - - &:empty { - display: none; - } - } - - > .input { - $height: 42px; - position: relative; - - > select { - appearance: none; - -webkit-appearance: none; - display: block; - height: $height; - width: 100%; - margin: 0; - padding: 0 12px; - font: inherit; - font-weight: normal; - font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--inputBorder); - border-radius: 6px; - outline: none; - box-shadow: none; - box-sizing: border-box; - cursor: pointer; - transition: border-color 0.1s ease-out; - - &:hover { - border-color: var(--inputBorderHover); - } - } - - > .prefix, - > .suffix { - display: flex; - align-items: center; - position: absolute; - z-index: 1; - top: 0; - padding: 0 12px; - font-size: 1em; - height: $height; - pointer-events: none; - - &:empty { - display: none; - } - - > * { - display: inline-block; - min-width: 16px; - max-width: 150px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } - - > .prefix { - left: 0; - padding-right: 6px; - } - - > .suffix { - right: 0; - padding-left: 6px; - } - - &.inline { - display: inline-block; - margin: 0; - } - - &.focused { - > select { - border-color: var(--accent); - } - } - - &.disabled { - opacity: 0.7; - - &, * { - cursor: not-allowed !important; - } - } - } -} -</style> diff --git a/src/client/components/ui/super-menu.vue b/src/client/components/ui/super-menu.vue new file mode 100644 index 0000000000..35fc81550d --- /dev/null +++ b/src/client/components/ui/super-menu.vue @@ -0,0 +1,151 @@ +<template> +<div class="rrevdjwu" :class="{ grid }"> + <div class="group" v-for="group in def"> + <div class="title" v-if="group.title">{{ group.title }}</div> + + <div class="items"> + <template v-for="(item, i) in group.items"> + <a v-if="item.type === 'a'" :href="item.href" :target="item.target" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }"> + <i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i> + <span class="text">{{ item.text }}</span> + </a> + <button v-else-if="item.type === 'button'" @click="ev => item.action(ev)" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active"> + <i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i> + <span class="text">{{ item.text }}</span> + </button> + <MkA v-else :to="item.to" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }"> + <i v-if="item.icon" class="icon fa-fw" :class="item.icon"></i> + <span class="text">{{ item.text }}</span> + </MkA> + </template> + </div> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent, ref, unref } from 'vue'; + +export default defineComponent({ + props: { + def: { + type: Array, + required: true + }, + grid: { + type: Boolean, + required: false, + default: false, + }, + }, +}); +</script> + +<style lang="scss" scoped> +.rrevdjwu { + > .group { + & + .group { + margin-top: 16px; + padding-top: 16px; + border-top: solid 0.5px var(--divider); + } + + margin-left: 16px; + margin-right: 16px; + + > .title { + font-size: 0.9em; + opacity: 0.7; + margin: 0 0 8px 12px; + } + + > .items { + > .item { + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + padding: 10px 16px 10px 8px; + border-radius: 9px; + font-size: 0.9em; + + &:hover { + text-decoration: none; + background: var(--panelHighlight); + } + + &.active { + color: var(--accent); + background: var(--accentedBg); + } + + &.danger { + color: var(--error); + } + + > .icon { + width: 32px; + margin-right: 2px; + flex-shrink: 0; + text-align: center; + opacity: 0.8; + } + + > .text { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 12px; + } + + } + } + } + + &.grid { + > .group { + & + .group { + padding-top: 0; + border-top: none; + } + + margin-left: 0; + margin-right: 0; + + > .title { + font-size: 1em; + opacity: 0.7; + margin: 0 0 8px 16px; + } + + > .items { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + grid-gap: 8px; + padding: 0 16px; + + > .item { + flex-direction: column; + padding: 18px 16px 16px 16px; + background: var(--panel); + border-radius: 8px; + text-align: center; + + > .icon { + display: block; + margin-right: 0; + margin-bottom: 12px; + font-size: 1.5em; + } + + > .text { + padding-right: 0; + width: 100%; + font-size: 0.8em; + } + } + } + } + } +} +</style> diff --git a/src/client/components/ui/switch.vue b/src/client/components/ui/switch.vue deleted file mode 100644 index 7aa9c0619d..0000000000 --- a/src/client/components/ui/switch.vue +++ /dev/null @@ -1,144 +0,0 @@ -<template> -<div - class="ziffeoms" - :class="{ disabled, checked }" - role="switch" - :aria-checked="checked" - :aria-disabled="disabled" - @click.prevent="toggle" -> - <input - type="checkbox" - ref="input" - :disabled="disabled" - @keydown.enter="toggle" - > - <span class="button"> - <span></span> - </span> - <span class="label"> - <span><slot></slot></span> - <p><slot name="caption"></slot></p> - </span> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - modelValue: { - type: Boolean, - default: false - }, - disabled: { - type: Boolean, - default: false - } - }, - computed: { - checked(): boolean { - return this.modelValue; - } - }, - methods: { - toggle() { - if (this.disabled) return; - this.$emit('update:modelValue', !this.checked); - } - } -}); -</script> - -<style lang="scss" scoped> -.ziffeoms { - position: relative; - display: flex; - margin: 32px 0; - cursor: pointer; - transition: all 0.3s; - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - - > * { - user-select: none; - } - - &.disabled { - opacity: 0.6; - cursor: not-allowed; - } - - &.checked { - > .button { - background-color: var(--X10); - border-color: var(--X10); - - > * { - background-color: var(--accent); - transform: translateX(14px); - } - } - } - - > input { - position: absolute; - width: 0; - height: 0; - opacity: 0; - margin: 0; - } - - > .button { - position: relative; - display: inline-block; - flex-shrink: 0; - margin: 3px 0 0 0; - width: 34px; - height: 14px; - background: var(--X6); - outline: none; - border-radius: 14px; - transition: inherit; - - > * { - position: absolute; - top: -3px; - left: 0; - border-radius: 100%; - transition: background-color 0.3s, transform 0.3s; - width: 20px; - height: 20px; - background-color: #fff; - box-shadow: 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12); - } - } - - > .label { - margin-left: 8px; - display: block; - cursor: pointer; - transition: inherit; - color: var(--fg); - - > span { - display: block; - line-height: 20px; - transition: inherit; - } - - > p { - margin: 0; - color: var(--fgTransparentWeak); - font-size: 90%; - } - } -} -</style> diff --git a/src/client/components/ui/textarea.vue b/src/client/components/ui/textarea.vue deleted file mode 100644 index 08ac3182a9..0000000000 --- a/src/client/components/ui/textarea.vue +++ /dev/null @@ -1,254 +0,0 @@ -<template> -<div class="adhpbeos"> - <div class="label" @click="focus"><slot name="label"></slot></div> - <div class="input" :class="{ disabled, focused, tall, pre }"> - <textarea ref="inputEl" - :class="{ code, _monospace: code }" - v-model="v" - :disabled="disabled" - :required="required" - :readonly="readonly" - :placeholder="placeholder" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="spellcheck" - @focus="focused = true" - @blur="focused = false" - @keydown="onKeydown($event)" - @input="onInput" - ></textarea> - </div> - <div class="caption"><slot name="caption"></slot></div> - - <MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> -</div> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; -import MkButton from './button.vue'; -import { debounce } from 'throttle-debounce'; - -export default defineComponent({ - components: { - MkButton, - }, - - props: { - modelValue: { - required: true - }, - type: { - type: String, - required: false - }, - required: { - type: Boolean, - required: false - }, - readonly: { - type: Boolean, - required: false - }, - disabled: { - type: Boolean, - required: false - }, - pattern: { - type: String, - required: false - }, - placeholder: { - type: String, - required: false - }, - autofocus: { - type: Boolean, - required: false, - default: false - }, - autocomplete: { - required: false - }, - spellcheck: { - required: false - }, - code: { - type: Boolean, - required: false - }, - tall: { - type: Boolean, - required: false, - default: false - }, - pre: { - type: Boolean, - required: false, - default: false - }, - debounce: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['change', 'keydown', 'enter', 'update:modelValue'], - - setup(props, context) { - const { modelValue, autofocus } = toRefs(props); - const v = ref(modelValue.value); - const focused = ref(false); - const changed = ref(false); - const invalid = ref(false); - const filled = computed(() => v.value !== '' && v.value != null); - const inputEl = ref(null); - - const focus = () => inputEl.value.focus(); - const onInput = (ev) => { - changed.value = true; - context.emit('change', ev); - }; - const onKeydown = (ev: KeyboardEvent) => { - context.emit('keydown', ev); - - if (ev.code === 'Enter') { - context.emit('enter'); - } - }; - - const updated = () => { - changed.value = false; - context.emit('update:modelValue', v.value); - }; - - const debouncedUpdated = debounce(1000, updated); - - watch(modelValue, newValue => { - v.value = newValue; - }); - - watch(v, newValue => { - if (!props.manualSave) { - if (props.debounce) { - debouncedUpdated(); - } else { - updated(); - } - } - - invalid.value = inputEl.value.validity.badInput; - }); - - onMounted(() => { - nextTick(() => { - if (autofocus.value) { - focus(); - } - }); - }); - - return { - v, - focused, - invalid, - changed, - filled, - inputEl, - focus, - onInput, - onKeydown, - updated, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.adhpbeos { - margin: 1.5em 0; - - > .label { - font-size: 0.85em; - padding: 0 0 8px 12px; - user-select: none; - - &:empty { - display: none; - } - } - - > .caption { - font-size: 0.8em; - padding: 8px 0 0 12px; - color: var(--fgTransparentWeak); - - &:empty { - display: none; - } - } - - > .input { - position: relative; - - > textarea { - appearance: none; - -webkit-appearance: none; - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 130px; - margin: 0; - padding: 12px; - font: inherit; - font-weight: normal; - font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 0.5px var(--inputBorder); - border-radius: 6px; - outline: none; - box-shadow: none; - box-sizing: border-box; - transition: border-color 0.1s ease-out; - - &:hover { - border-color: var(--inputBorderHover); - } - } - - &.focused { - > textarea { - border-color: var(--accent); - } - } - - &.disabled { - opacity: 0.7; - - &, * { - cursor: not-allowed !important; - } - } - - &.tall { - > textarea { - min-height: 200px; - } - } - - &.pre { - > textarea { - white-space: pre; - } - } - } -} -</style> diff --git a/src/client/components/ui/window.vue b/src/client/components/ui/window.vue index 773c3b9b13..00284b0467 100644 --- a/src/client/components/ui/window.vue +++ b/src/client/components/ui/window.vue @@ -3,11 +3,16 @@ <div class="ebkgocck" :class="{ front }" v-if="showing"> <div class="body _window _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown"> <div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu"> - <button v-if="closeButton" class="_button" @click="close()"><i class="fas fa-times"></i></button> - + <span class="left"> + <slot name="headerLeft"></slot> + </span> <span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown"> <slot name="header"></slot> </span> + <span class="right"> + <slot name="headerRight"></slot> + <button v-if="closeButton" class="_button" @click="close()"><i class="fas fa-times"></i></button> + </span> </div> <div class="body" v-if="padding"> <div class="_section"> @@ -377,7 +382,7 @@ export default defineComponent({ <style lang="scss" scoped> .window-enter-active, .window-leave-active { - transition: opacity 0.3s, transform 0.3s !important; + transition: opacity 0.2s, transform 0.2s !important; } .window-enter-from, .window-leave-to { pointer-events: none; @@ -418,12 +423,14 @@ export default defineComponent({ height: var(--height); border-bottom: solid 1px var(--divider); - > ::v-deep(button) { - height: var(--height); - width: var(--height); + > .left, > .right { + > ::v-deep(button) { + height: var(--height); + width: var(--height); - &:hover { - color: var(--fgHighlighted); + &:hover { + color: var(--fgHighlighted); + } } } diff --git a/src/client/components/user-select-dialog.vue b/src/client/components/user-select-dialog.vue index 87c32dab25..0f3ee2a126 100644 --- a/src/client/components/user-select-dialog.vue +++ b/src/client/components/user-select-dialog.vue @@ -10,7 +10,7 @@ <template #header>{{ $ts.selectUser }}</template> <div class="tbhwbxda _monolithic_"> <div class="_section"> - <div class="_inputSplit _inputNoTopMargin _inputNoBottomMargin"> + <div class="_inputSplit"> <MkInput v-model="username" class="input" @update:modelValue="search" ref="username"> <template #label>{{ $ts.username }}</template> <template #prefix>@</template> @@ -52,7 +52,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import MkInput from './ui/input.vue'; +import MkInput from './form/input.vue'; import XModalWindow from '@client/components/ui/modal-window.vue'; import * as os from '@client/os'; diff --git a/src/client/components/widgets.vue b/src/client/components/widgets.vue index 150d61c027..aef5de453c 100644 --- a/src/client/components/widgets.vue +++ b/src/client/components/widgets.vue @@ -30,7 +30,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; import { v4 as uuid } from 'uuid'; -import MkSelect from '@client/components/ui/select.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkButton from '@client/components/ui/button.vue'; import { widgets as widgetDefs } from '@client/widgets'; diff --git a/src/client/directives/click-anime.ts b/src/client/directives/click-anime.ts index 9fd583d6dd..0d5a6da94e 100644 --- a/src/client/directives/click-anime.ts +++ b/src/client/directives/click-anime.ts @@ -1,7 +1,10 @@ import { Directive } from 'vue'; +import { defaultStore } from '@client/store'; export default { mounted(el, binding, vn) { + if (!defaultStore.state.animation) return; + el.classList.add('_anime_bounce_standBy'); el.addEventListener('mousedown', () => { diff --git a/src/client/directives/get-size.ts b/src/client/directives/get-size.ts new file mode 100644 index 0000000000..e3b5dea0f3 --- /dev/null +++ b/src/client/directives/get-size.ts @@ -0,0 +1,34 @@ +import { Directive } from 'vue'; + +export default { + mounted(src, binding, vn) { + const calc = () => { + const height = src.clientHeight; + const width = src.clientWidth; + + // 要素が(一時的に)DOMに存在しないときは計算スキップ + if (height === 0) return; + + binding.value(width, height); + }; + + calc(); + + // Vue3では使えなくなった + // 無くても大丈夫か...? + // TODO: ↑大丈夫じゃなかったので解決策を探す + //vn.context.$on('hook:activated', calc); + + const ro = new ResizeObserver((entries, observer) => { + calc(); + }); + ro.observe(src); + + src._get_size_ro_ = ro; + }, + + unmounted(src, binding, vn) { + binding.value(0, 0); + src._get_size_ro_.unobserve(src); + } +} as Directive; diff --git a/src/client/directives/index.ts b/src/client/directives/index.ts index f0a0123771..cd71bc26d3 100644 --- a/src/client/directives/index.ts +++ b/src/client/directives/index.ts @@ -2,6 +2,7 @@ import { App } from 'vue'; import userPreview from './user-preview'; import size from './size'; +import getSize from './get-size'; import particle from './particle'; import tooltip from './tooltip'; import hotkey from './hotkey'; @@ -14,6 +15,7 @@ export default function(app: App) { app.directive('userPreview', userPreview); app.directive('user-preview', userPreview); app.directive('size', size); + app.directive('get-size', getSize); app.directive('particle', particle); app.directive('tooltip', tooltip); app.directive('hotkey', hotkey); diff --git a/src/client/directives/tooltip.ts b/src/client/directives/tooltip.ts index ee690558af..32d137b2e2 100644 --- a/src/client/directives/tooltip.ts +++ b/src/client/directives/tooltip.ts @@ -36,7 +36,7 @@ export default { }); } - const show = e => { + self.show = () => { if (!document.body.contains(el)) return; if (self._close) return; if (self.text == null) return; @@ -60,7 +60,7 @@ export default { el.addEventListener(start, () => { clearTimeout(self.showTimer); clearTimeout(self.hideTimer); - self.showTimer = setTimeout(show, delay); + self.showTimer = setTimeout(self.show, delay); }, { passive: true }); el.addEventListener(end, () => { @@ -75,6 +75,11 @@ export default { }); }, + updated(el, binding) { + const self = el._tooltipDirective_; + self.text = binding.value as string; + }, + unmounted(el, binding, vn) { const self = el._tooltipDirective_; clearInterval(self.checkTimer); diff --git a/src/client/events.ts b/src/client/events.ts new file mode 100644 index 0000000000..dbbd908b8f --- /dev/null +++ b/src/client/events.ts @@ -0,0 +1,4 @@ +import { EventEmitter } from 'eventemitter3'; + +// TODO: 型付け +export const globalEvents = new EventEmitter(); diff --git a/src/client/pages/_error_.vue b/src/client/pages/_error_.vue index 1d67d9b14d..d1cefad8dd 100644 --- a/src/client/pages/_error_.vue +++ b/src/client/pages/_error_.vue @@ -1,9 +1,16 @@ <template> +<MkLoading v-if="!loaded" /> <transition :name="$store.state.animation ? 'zoom' : ''" appear> - <div class="mjndxjch"> + <div class="mjndxjch" v-show="loaded"> <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> <p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p> - <p>{{ $ts.pageLoadErrorDescription }}</p> + <p v-if="version === meta.version">{{ $ts.pageLoadErrorDescription }}</p> + <p v-else-if="serverIsDead">{{ $ts.serverIsDead }}</p> + <template v-else> + <p>{{ $ts.newVersionOfClientAvailable }}</p> + <p>{{ $ts.youShouldUpgradeClient }}</p> + <MkButton @click="reload" class="button primary">{{ $ts.reload }}</MkButton> + </template> <p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p> <p v-if="error" class="error">ERROR: {{ error }}</p> </div> @@ -14,6 +21,9 @@ import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; import * as symbols from '@client/symbols'; +import { version } from '@client/config'; +import * as os from '@client/os'; +import { unisonReload } from '@client/scripts/unison-reload'; export default defineComponent({ components: { @@ -30,8 +40,30 @@ export default defineComponent({ title: this.$ts.error, icon: 'fas fa-exclamation-triangle' }, + loaded: false, + serverIsDead: false, + meta: {} as any, + version, }; }, + created() { + os.api('meta', { + detail: false + }).then(meta => { + this.loaded = true; + this.serverIsDead = false; + this.meta = meta; + localStorage.setItem('v', meta.version); + }, () => { + this.loaded = true; + this.serverIsDead = true; + }); + }, + methods: { + reload() { + unisonReload(); + }, + }, }); </script> @@ -45,7 +77,7 @@ export default defineComponent({ } > .button { - margin: 0 auto; + margin: 8px auto; } > img { diff --git a/src/client/pages/about-misskey.vue b/src/client/pages/about-misskey.vue index 384c7e8ccb..d2c0ec0550 100644 --- a/src/client/pages/about-misskey.vue +++ b/src/client/pages/about-misskey.vue @@ -2,15 +2,15 @@ <div style="overflow: clip;"> <FormBase class="znqjceqz"> <div id="debug"></div> - <section class="_formItem about"> - <div class="_formPanel panel" :class="{ playing: easterEggEngine != null }" ref="about"> + <section class="_debobigegoItem about"> + <div class="_debobigegoPanel panel" :class="{ playing: easterEggEngine != null }" ref="about"> <img src="/static-assets/client/about-icon.png" alt="" class="icon" @load="iconLoaded" draggable="false" @click="gravity"/> <div class="misskey">Misskey</div> <div class="version">v{{ version }}</div> <span class="emoji" v-for="emoji in easterEggEmojis" :key="emoji.id" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> </div> </section> - <section class="_formItem" style="text-align: center; padding: 0 16px;"> + <section class="_debobigegoItem" style="text-align: center; padding: 0 16px;"> {{ $ts._aboutMisskey.about }}<br><MkA class="_link" to="/docs/general/misskey">{{ $ts.learnMore }}</MkA> </section> <FormGroup> @@ -55,10 +55,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; import { version } from '@client/config'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; import MkLink from '@client/components/link.vue'; import { physics } from '@client/scripts/physics'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/about.vue b/src/client/pages/about.vue index bdd4c78827..2c580c293a 100644 --- a/src/client/pages/about.vue +++ b/src/client/pages/about.vue @@ -1,7 +1,7 @@ <template> <FormBase> - <div class="_formItem"> - <div class="_formPanel fwhjspax"> + <div class="_debobigegoItem"> + <div class="_debobigegoPanel fwhjspax"> <img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> <span class="name">{{ $instance.name || host }}</span> </div> @@ -59,12 +59,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; import { version, instanceName } from '@client/config'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import number from '@client/filters/number'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/advanced-theme-editor.vue b/src/client/pages/advanced-theme-editor.vue index c03d88b82d..8a63d74887 100644 --- a/src/client/pages/advanced-theme-editor.vue +++ b/src/client/pages/advanced-theme-editor.vue @@ -4,7 +4,7 @@ <div class="_content"> <details> <summary>{{ $ts.import }}</summary> - <MkTextarea v-model:value="themeToImport"> + <MkTextarea v-model="themeToImport"> {{ $ts._theme.importInfo }} </MkTextarea> <MkButton :disabled="!themeToImport.trim()" @click="importTheme">{{ $ts.import }}</MkButton> @@ -14,9 +14,9 @@ <section class="_section"> <div class="_content _card _gap"> <div class="_content"> - <MkInput v-model:value="name" required><span>{{ $ts.name }}</span></MkInput> - <MkInput v-model:value="author" required><span>{{ $ts.author }}</span></MkInput> - <MkTextarea v-model:value="description"><span>{{ $ts.description }}</span></MkTextarea> + <MkInput v-model="name" required><span>{{ $ts.name }}</span></MkInput> + <MkInput v-model="author" required><span>{{ $ts.author }}</span></MkInput> + <MkTextarea v-model="description"><span>{{ $ts.description }}</span></MkTextarea> <div class="_inputs"> <div v-text="$ts._theme.base" /> <MkRadio v-model="baseTheme" value="light">{{ $ts.light }}</MkRadio> @@ -41,31 +41,31 @@ <!-- color --> <div v-else-if="typeof v === 'string'" class="color"> <input type="color" :value="v" @input="colorChanged($event.target.value, i)"/> - <MkInput class="select" :value="v" @update:value="colorChanged($event, i)"/> + <MkInput class="select" :value="v" @update:modelValue="colorChanged($event, i)"/> </div> <!-- ref const --> - <MkInput v-else-if="v.type === 'refConst'" v-model:value="v.key"> + <MkInput v-else-if="v.type === 'refConst'" v-model="v.key"> <template #prefix>$</template> <span>{{ $ts.name }}</span> </MkInput> <!-- ref props --> - <MkSelect class="select" v-else-if="v.type === 'refProp'" v-model:value="v.key"> + <MkSelect class="select" v-else-if="v.type === 'refProp'" v-model="v.key"> <option v-for="key in themeProps" :value="key" :key="key">{{ $t('_theme.keys.' + key) }}</option> </MkSelect> <!-- func --> <template v-else-if="v.type === 'func'"> - <MkSelect class="select" v-model:value="v.name"> + <MkSelect class="select" v-model="v.name"> <template #label>{{ $ts._theme.funcKind }}</template> <option v-for="n in ['alpha', 'darken', 'lighten']" :value="n" :key="n">{{ $t('_theme.' + n) }}</option> </MkSelect> - <MkInput type="number" v-model:value="v.arg"><span>{{ $ts._theme.argument }}</span></MkInput> - <MkSelect class="select" v-model:value="v.value"> + <MkInput type="number" v-model="v.arg"><span>{{ $ts._theme.argument }}</span></MkInput> + <MkSelect class="select" v-model="v.value"> <template #label>{{ $ts._theme.basedProp }}</template> <option v-for="key in themeProps" :value="key" :key="key">{{ $t('_theme.keys.' + key) }}</option> </MkSelect> </template> <!-- CSS --> - <MkInput v-else-if="v.type === 'css'" v-model:value="v.value"> + <MkInput v-else-if="v.type === 'css'" v-model="v.value"> <span>CSS</span> </MkInput> </div> @@ -95,11 +95,11 @@ import { defineComponent } from 'vue'; import * as JSON5 from 'json5'; import { toUnicode } from 'punycode/'; -import MkRadio from '@client/components/ui/radio.vue'; +import MkRadio from '@client/components/form/radio.vue'; import MkButton from '@client/components/ui/button.vue'; -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 MkInput from '@client/components/form/input.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkSample from '@client/components/sample.vue'; import { convertToMisskeyTheme, ThemeValue, convertToViewModel, ThemeViewModel } from '@client/scripts/theme-editor'; diff --git a/src/client/pages/announcements.vue b/src/client/pages/announcements.vue index a7ccb03588..6a0cbd67ba 100644 --- a/src/client/pages/announcements.vue +++ b/src/client/pages/announcements.vue @@ -1,17 +1,20 @@ <template> -<div class="_section"> - <MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content"> - <section class="_card announcement _gap" v-for="(announcement, i) in items" :key="announcement.id"> - <div class="_title"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div> - <div class="_content"> - <Mfm :text="announcement.text"/> - <img v-if="announcement.imageUrl" :src="announcement.imageUrl"/> - </div> - <div class="_footer" v-if="$i && !announcement.isRead"> - <MkButton @click="read(items, announcement, i)" primary><i class="fas fa-check"></i> {{ $ts.gotIt }}</MkButton> - </div> - </section> - </MkPagination> +<div> + <MkHeader :info="header"/> + <div class="_section"> + <MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content"> + <section class="_card announcement _gap" v-for="(announcement, i) in items" :key="announcement.id"> + <div class="_title"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div> + <div class="_content"> + <Mfm :text="announcement.text"/> + <img v-if="announcement.imageUrl" :src="announcement.imageUrl"/> + </div> + <div class="_footer" v-if="$i && !announcement.isRead"> + <MkButton @click="read(items, announcement, i)" primary><i class="fas fa-check"></i> {{ $ts.gotIt }}</MkButton> + </div> + </section> + </MkPagination> + </div> </div> </template> @@ -32,7 +35,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.announcements, - icon: 'fas fa-broadcast-tower' + icon: 'fas fa-broadcast-tower', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.announcements, + icon: 'fas fa-broadcast-tower', + bg: 'var(--bg)', }, pagination: { endpoint: 'announcements', diff --git a/src/client/pages/antenna-timeline.vue b/src/client/pages/antenna-timeline.vue index 425bec6987..c99124dbdc 100644 --- a/src/client/pages/antenna-timeline.vue +++ b/src/client/pages/antenna-timeline.vue @@ -89,7 +89,7 @@ export default defineComponent({ }, top() { - scroll(this.$el, 0); + scroll(this.$el, { top: 0 }); }, async timetravel() { diff --git a/src/client/pages/api-console.vue b/src/client/pages/api-console.vue index c6d459fd6d..9aa7d4ea4d 100644 --- a/src/client/pages/api-console.vue +++ b/src/client/pages/api-console.vue @@ -1,7 +1,7 @@ <template> <div class="_root"> <div class="_block" style="padding: 24px;"> - <MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()" class="_inputNoTopMargin"> + <MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()" class=""> <template #label>Endpoint</template> </MkInput> <MkTextarea v-model="body" code> @@ -27,9 +27,9 @@ import { defineComponent } from 'vue'; import * as JSON5 from 'json5'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkSwitch from '@client/components/form/switch.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/channel-editor.vue b/src/client/pages/channel-editor.vue index eeea0b70aa..67e27896ce 100644 --- a/src/client/pages/channel-editor.vue +++ b/src/client/pages/channel-editor.vue @@ -27,9 +27,9 @@ <script lang="ts"> import { computed, defineComponent } from 'vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import { selectFile } from '@client/scripts/select-file'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/channels.vue b/src/client/pages/channels.vue index 7e3302959b..fd1408c253 100644 --- a/src/client/pages/channels.vue +++ b/src/client/pages/channels.vue @@ -1,7 +1,7 @@ <template> <div> <div class="_section" style="padding: 0;" v-if="$i"> - <MkTab class="_content" v-model:value="tab"> + <MkTab class="_content" v-model="tab"> <option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._channel.featured }}</option> <option value="following"><i class="fas fa-heart"></i> {{ $ts._channel.following }}</option> <option value="owned"><i class="fas fa-edit"></i> {{ $ts._channel.owned }}</option> diff --git a/src/client/pages/docs.vue b/src/client/pages/docs.vue index be4d4255db..629dc2be53 100644 --- a/src/client/pages/docs.vue +++ b/src/client/pages/docs.vue @@ -2,7 +2,7 @@ <div class="vtaihdtm"> <div class="body"> <div class="search"> - <MkInput v-model="query" :debounce="true" type="search" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search"> + <MkInput v-model="query" :debounce="true" type="search" class="" :placeholder="$ts.search"> <template #prefix><i class="fas fa-search"></i></template> </MkInput> </div> @@ -57,7 +57,7 @@ import { defineComponent } from 'vue'; import { url, lang } from '@client/config'; import * as symbols from '@client/symbols'; import MkFolder from '@client/components/ui/folder.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; export default defineComponent({ components: { diff --git a/src/client/pages/emojis.category.vue b/src/client/pages/emojis.category.vue index 091c3f20a9..e725bcb31f 100644 --- a/src/client/pages/emojis.category.vue +++ b/src/client/pages/emojis.category.vue @@ -1,13 +1,15 @@ <template> <div class="driuhtrh"> <div class="query"> - <MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search"> + <MkInput v-model="q" class="" :placeholder="$ts.search"> <template #prefix><i class="fas fa-search"></i></template> </MkInput> + <!-- たくさんあると邪魔 <div class="tags"> <span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span> </div> + --> </div> <MkFolder class="emojis" v-if="searchEmojis"> @@ -29,8 +31,8 @@ <script lang="ts"> import { defineComponent, computed } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkFolder from '@client/components/ui/folder.vue'; import MkTab from '@client/components/tab.vue'; import * as os from '@client/os'; @@ -120,7 +122,6 @@ export default defineComponent({ } > .emojis { - --x-header: var(--bg); --x-padding: 0 16px; .zuvgdzyt { diff --git a/src/client/pages/emojis.emoji.vue b/src/client/pages/emojis.emoji.vue index 3c9bb4debe..ca0ef2dbb7 100644 --- a/src/client/pages/emojis.emoji.vue +++ b/src/client/pages/emojis.emoji.vue @@ -23,12 +23,14 @@ export default defineComponent({ }, mounted() { - VanillaTilt.init(this.$el, { - reverse: true, - gyroscope: false, - scale: 1.1, - speed: 500, - }); + if (this.$store.animation) { + VanillaTilt.init(this.$el, { + reverse: true, + gyroscope: false, + scale: 1.1, + speed: 500, + }); + } }, methods: { diff --git a/src/client/pages/emojis.vue b/src/client/pages/emojis.vue index 8918de2338..d61fd25d3c 100644 --- a/src/client/pages/emojis.vue +++ b/src/client/pages/emojis.vue @@ -1,6 +1,9 @@ <template> -<div :class="$style.root"> - <XCategory v-if="tab === 'category'"/> +<div> + <MkHeader :info="header"/> + <div :class="$style.root"> + <XCategory v-if="tab === 'category'"/> + </div> </div> </template> @@ -22,6 +25,11 @@ export default defineComponent({ icon: 'fas fa-laugh', bg: 'var(--bg)', })), + header: computed(() => ({ + title: this.$ts.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + })), tab: 'category', } }, diff --git a/src/client/pages/explore.vue b/src/client/pages/explore.vue index 7054940a1a..2ca0668611 100644 --- a/src/client/pages/explore.vue +++ b/src/client/pages/explore.vue @@ -1,73 +1,80 @@ <template> -<div class="lznhrdub _root"> - <div> - <div class="_isolated"> - <MkInput v-model="query" :debounce="true" type="search"> - <template #prefix><i class="fas fa-search"></i></template> - <template #label>{{ $ts.searchUser }}</template> - </MkInput> - </div> +<div> + <MkHeader :info="header"/> - <XUserList v-if="query" class="_gap" :pagination="searchPagination" ref="search"/> + <MkSpacer :content-max="1200"> + <div class="lznhrdub"> + <div v-if="tab === 'local'"> + <div class="localfedi7 _block _isolated" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }"> + <header><span>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</span></header> + <div><span>{{ $t('exploreUsersCount', { count: num(stats.originalUsersCount) }) }}</span></div> + </div> - <div class="localfedi7 _block _isolated" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }"> - <header><span>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</span></header> - <div><span>{{ $t('exploreUsersCount', { count: num(stats.originalUsersCount) }) }}</span></div> - </div> + <template v-if="tag == null"> + <MkFolder class="_gap" persist-key="explore-pinned-users"> + <template #header><i class="fas fa-bookmark fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.pinnedUsers }}</template> + <XUserList :pagination="pinnedUsers"/> + </MkFolder> + <MkFolder class="_gap" persist-key="explore-popular-users"> + <template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template> + <XUserList :pagination="popularUsers"/> + </MkFolder> + <MkFolder class="_gap" persist-key="explore-recently-updated-users"> + <template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template> + <XUserList :pagination="recentlyUpdatedUsers"/> + </MkFolder> + <MkFolder class="_gap" persist-key="explore-recently-registered-users"> + <template #header><i class="fas fa-plus fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyRegisteredUsers }}</template> + <XUserList :pagination="recentlyRegisteredUsers"/> + </MkFolder> + </template> + </div> + <div v-else-if="tab === 'remote'"> + <div class="localfedi7 _block _isolated" v-if="tag == null" :style="{ backgroundImage: `url(/static-assets/client/fedi.jpg)` }"> + <header><span>{{ $ts.exploreFediverse }}</span></header> + </div> - <template v-if="tag == null"> - <MkFolder class="_gap" persist-key="explore-pinned-users"> - <template #header><i class="fas fa-bookmark fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.pinnedUsers }}</template> - <XUserList :pagination="pinnedUsers"/> - </MkFolder> - <MkFolder class="_gap" persist-key="explore-popular-users"> - <template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template> - <XUserList :pagination="popularUsers"/> - </MkFolder> - <MkFolder class="_gap" persist-key="explore-recently-updated-users"> - <template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template> - <XUserList :pagination="recentlyUpdatedUsers"/> - </MkFolder> - <MkFolder class="_gap" persist-key="explore-recently-registered-users"> - <template #header><i class="fas fa-plus fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyRegisteredUsers }}</template> - <XUserList :pagination="recentlyRegisteredUsers"/> - </MkFolder> - </template> - </div> - <div> - <div class="localfedi7 _block _isolated" v-if="tag == null" :style="{ backgroundImage: `url(/static-assets/client/fedi.jpg)` }"> - <header><span>{{ $ts.exploreFediverse }}</span></header> - </div> + <MkFolder :foldable="true" :expanded="false" ref="tags" class="_gap"> + <template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularTags }}</template> - <MkFolder :foldable="true" :expanded="false" ref="tags" class="_gap"> - <template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularTags }}</template> + <div class="vxjfqztj"> + <MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA> + <MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA> + </div> + </MkFolder> - <div class="vxjfqztj"> - <MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA> - <MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA> - </div> - </MkFolder> + <MkFolder v-if="tag != null" :key="`${tag}`" class="_gap"> + <template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template> + <XUserList :pagination="tagUsers"/> + </MkFolder> - <MkFolder v-if="tag != null" :key="`${tag}`" class="_gap"> - <template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template> - <XUserList :pagination="tagUsers"/> - </MkFolder> + <template v-if="tag == null"> + <MkFolder class="_gap"> + <template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template> + <XUserList :pagination="popularUsersF"/> + </MkFolder> + <MkFolder class="_gap"> + <template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template> + <XUserList :pagination="recentlyUpdatedUsersF"/> + </MkFolder> + <MkFolder class="_gap"> + <template #header><i class="fas fa-rocket fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyDiscoveredUsers }}</template> + <XUserList :pagination="recentlyRegisteredUsersF"/> + </MkFolder> + </template> + </div> + <div v-else-if="tab === 'search'"> + <div class="_isolated"> + <MkInput v-model="query" :debounce="true" type="search"> + <template #prefix><i class="fas fa-search"></i></template> + <template #label>{{ $ts.searchUser }}</template> + </MkInput> + </div> - <template v-if="tag == null"> - <MkFolder class="_gap"> - <template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template> - <XUserList :pagination="popularUsersF"/> - </MkFolder> - <MkFolder class="_gap"> - <template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template> - <XUserList :pagination="recentlyUpdatedUsersF"/> - </MkFolder> - <MkFolder class="_gap"> - <template #header><i class="fas fa-rocket fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyDiscoveredUsers }}</template> - <XUserList :pagination="recentlyRegisteredUsersF"/> - </MkFolder> - </template> - </div> + <XUserList v-if="query" class="_gap" :pagination="searchPagination" ref="search"/> + </div> + </div> + </MkSpacer> </div> </template> @@ -75,7 +82,7 @@ import { computed, defineComponent } from 'vue'; import XUserList from '@client/components/user-list.vue'; import MkFolder from '@client/components/ui/folder.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import number from '@client/filters/number'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -98,8 +105,28 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.explore, - icon: 'fas fa-hashtag' + icon: 'fas fa-hashtag', + bg: 'var(--bg)', }, + tab: 'local', + header: computed(() => ({ + title: this.$ts.explore, + icon: 'fas fa-hashtag', + bg: 'var(--bg)', + tabs: [{ + active: this.tab === 'local', + title: this.$ts.local, + onClick: () => { this.tab = 'local'; }, + }, { + active: this.tab === 'remote', + title: this.$ts.remote, + onClick: () => { this.tab = 'remote'; }, + }, { + active: this.tab === 'search', + title: this.$ts.search, + onClick: () => { this.tab = 'search'; }, + },] + })), pinnedUsers: { endpoint: 'pinned-users' }, popularUsers: { endpoint: 'users', limit: 10, noPaging: true, params: { state: 'alive', @@ -189,11 +216,6 @@ export default defineComponent({ </script> <style lang="scss" scoped> -.lznhrdub { - max-width: 1400px; - margin: 0 auto; -} - .localfedi7 { color: #fff; padding: 16px; diff --git a/src/client/pages/favorites.vue b/src/client/pages/favorites.vue index f13723c2d1..bed78d1dbe 100644 --- a/src/client/pages/favorites.vue +++ b/src/client/pages/favorites.vue @@ -1,7 +1,10 @@ <template> -<div class="jmelgwjh"> - <div class="body"> - <XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'" @before="before()" @after="after()"/> +<div> + <MkHeader :info="header"/> + <div class="jmelgwjh"> + <div class="body"> + <XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'" @before="before()" @after="after()"/> + </div> </div> </div> </template> @@ -25,6 +28,11 @@ export default defineComponent({ icon: 'fas fa-star', bg: 'var(--bg)', }, + header: { + title: this.$ts.favorites, + icon: 'fas fa-star', + bg: 'var(--bg)', + }, pagination: { endpoint: 'i/favorites', limit: 10, diff --git a/src/client/pages/featured.vue b/src/client/pages/featured.vue index 21818ba617..5d8da54541 100644 --- a/src/client/pages/featured.vue +++ b/src/client/pages/featured.vue @@ -1,6 +1,9 @@ <template> -<div class="_section"> - <XNotes class="_content" ref="notes" :pagination="pagination" @before="before" @after="after"/> +<div> + <MkHeader :info="header"/> + <div class="_section"> + <XNotes class="_content" ref="notes" :pagination="pagination" @before="before" @after="after"/> + </div> </div> </template> @@ -19,12 +22,18 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.featured, - icon: 'fas fa-fire-alt' + icon: 'fas fa-fire-alt', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.featured, + icon: 'fas fa-fire-alt', + bg: 'var(--bg)', }, pagination: { endpoint: 'notes/featured', limit: 10, - offsetMode: true + offsetMode: true, }, }; }, diff --git a/src/client/pages/federation.vue b/src/client/pages/federation.vue index 2afe70eea6..ae0aed4cc7 100644 --- a/src/client/pages/federation.vue +++ b/src/client/pages/federation.vue @@ -1,103 +1,106 @@ <template> -<div class="taeiyria"> - <div class="query"> - <MkInput v-model="host" :debounce="true" class="_inputNoTopMargin"> - <template #prefix><i class="fas fa-search"></i></template> - <template #label>{{ $ts.host }}</template> - </MkInput> - <div class="_inputSplit _inputNoBottomMargin"> - <MkSelect v-model="state"> - <template #label>{{ $ts.state }}</template> - <option value="all">{{ $ts.all }}</option> - <option value="federating">{{ $ts.federating }}</option> - <option value="subscribing">{{ $ts.subscribing }}</option> - <option value="publishing">{{ $ts.publishing }}</option> - <option value="suspended">{{ $ts.suspended }}</option> - <option value="blocked">{{ $ts.blocked }}</option> - <option value="notResponding">{{ $ts.notResponding }}</option> - </MkSelect> - <MkSelect v-model="sort"> - <template #label>{{ $ts.sort }}</template> - <option value="+pubSub">{{ $ts.pubSub }} ({{ $ts.descendingOrder }})</option> - <option value="-pubSub">{{ $ts.pubSub }} ({{ $ts.ascendingOrder }})</option> - <option value="+notes">{{ $ts.notes }} ({{ $ts.descendingOrder }})</option> - <option value="-notes">{{ $ts.notes }} ({{ $ts.ascendingOrder }})</option> - <option value="+users">{{ $ts.users }} ({{ $ts.descendingOrder }})</option> - <option value="-users">{{ $ts.users }} ({{ $ts.ascendingOrder }})</option> - <option value="+following">{{ $ts.following }} ({{ $ts.descendingOrder }})</option> - <option value="-following">{{ $ts.following }} ({{ $ts.ascendingOrder }})</option> - <option value="+followers">{{ $ts.followers }} ({{ $ts.descendingOrder }})</option> - <option value="-followers">{{ $ts.followers }} ({{ $ts.ascendingOrder }})</option> - <option value="+caughtAt">{{ $ts.registeredAt }} ({{ $ts.descendingOrder }})</option> - <option value="-caughtAt">{{ $ts.registeredAt }} ({{ $ts.ascendingOrder }})</option> - <option value="+lastCommunicatedAt">{{ $ts.lastCommunication }} ({{ $ts.descendingOrder }})</option> - <option value="-lastCommunicatedAt">{{ $ts.lastCommunication }} ({{ $ts.ascendingOrder }})</option> - <option value="+driveUsage">{{ $ts.driveUsage }} ({{ $ts.descendingOrder }})</option> - <option value="-driveUsage">{{ $ts.driveUsage }} ({{ $ts.ascendingOrder }})</option> - <option value="+driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.descendingOrder }})</option> - <option value="-driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.ascendingOrder }})</option> - </MkSelect> +<div> + <MkHeader :info="header"/> + <div class="taeiyria"> + <div class="query"> + <MkInput v-model="host" :debounce="true" class=""> + <template #prefix><i class="fas fa-search"></i></template> + <template #label>{{ $ts.host }}</template> + </MkInput> + <div class="_inputSplit"> + <MkSelect v-model="state"> + <template #label>{{ $ts.state }}</template> + <option value="all">{{ $ts.all }}</option> + <option value="federating">{{ $ts.federating }}</option> + <option value="subscribing">{{ $ts.subscribing }}</option> + <option value="publishing">{{ $ts.publishing }}</option> + <option value="suspended">{{ $ts.suspended }}</option> + <option value="blocked">{{ $ts.blocked }}</option> + <option value="notResponding">{{ $ts.notResponding }}</option> + </MkSelect> + <MkSelect v-model="sort"> + <template #label>{{ $ts.sort }}</template> + <option value="+pubSub">{{ $ts.pubSub }} ({{ $ts.descendingOrder }})</option> + <option value="-pubSub">{{ $ts.pubSub }} ({{ $ts.ascendingOrder }})</option> + <option value="+notes">{{ $ts.notes }} ({{ $ts.descendingOrder }})</option> + <option value="-notes">{{ $ts.notes }} ({{ $ts.ascendingOrder }})</option> + <option value="+users">{{ $ts.users }} ({{ $ts.descendingOrder }})</option> + <option value="-users">{{ $ts.users }} ({{ $ts.ascendingOrder }})</option> + <option value="+following">{{ $ts.following }} ({{ $ts.descendingOrder }})</option> + <option value="-following">{{ $ts.following }} ({{ $ts.ascendingOrder }})</option> + <option value="+followers">{{ $ts.followers }} ({{ $ts.descendingOrder }})</option> + <option value="-followers">{{ $ts.followers }} ({{ $ts.ascendingOrder }})</option> + <option value="+caughtAt">{{ $ts.registeredAt }} ({{ $ts.descendingOrder }})</option> + <option value="-caughtAt">{{ $ts.registeredAt }} ({{ $ts.ascendingOrder }})</option> + <option value="+lastCommunicatedAt">{{ $ts.lastCommunication }} ({{ $ts.descendingOrder }})</option> + <option value="-lastCommunicatedAt">{{ $ts.lastCommunication }} ({{ $ts.ascendingOrder }})</option> + <option value="+driveUsage">{{ $ts.driveUsage }} ({{ $ts.descendingOrder }})</option> + <option value="-driveUsage">{{ $ts.driveUsage }} ({{ $ts.ascendingOrder }})</option> + <option value="+driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.descendingOrder }})</option> + <option value="-driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.ascendingOrder }})</option> + </MkSelect> + </div> </div> - </div> - <MkPagination :pagination="pagination" #default="{items}" ref="instances" :key="host + state"> - <div class="dqokceoi"> - <MkA class="instance" v-for="instance in items" :key="instance.id" :to="`/instance-info/${instance.host}`"> - <div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div> - <div class="table"> - <div class="cell"> - <div class="key">{{ $ts.registeredAt }}</div> - <div class="value"><MkTime :time="instance.caughtAt"/></div> - </div> - <div class="cell"> - <div class="key">{{ $ts.software }}</div> - <div class="value">{{ instance.softwareName || `(${$ts.unknown})` }}</div> - </div> - <div class="cell"> - <div class="key">{{ $ts.version }}</div> - <div class="value">{{ instance.softwareVersion || `(${$ts.unknown})` }}</div> - </div> - <div class="cell"> - <div class="key">{{ $ts.users }}</div> - <div class="value">{{ instance.usersCount }}</div> - </div> - <div class="cell"> - <div class="key">{{ $ts.notes }}</div> - <div class="value">{{ instance.notesCount }}</div> + <MkPagination :pagination="pagination" #default="{items}" ref="instances" :key="host + state"> + <div class="dqokceoi"> + <MkA class="instance" v-for="instance in items" :key="instance.id" :to="`/instance-info/${instance.host}`"> + <div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div> + <div class="table"> + <div class="cell"> + <div class="key">{{ $ts.registeredAt }}</div> + <div class="value"><MkTime :time="instance.caughtAt"/></div> + </div> + <div class="cell"> + <div class="key">{{ $ts.software }}</div> + <div class="value">{{ instance.softwareName || `(${$ts.unknown})` }}</div> + </div> + <div class="cell"> + <div class="key">{{ $ts.version }}</div> + <div class="value">{{ instance.softwareVersion || `(${$ts.unknown})` }}</div> + </div> + <div class="cell"> + <div class="key">{{ $ts.users }}</div> + <div class="value">{{ instance.usersCount }}</div> + </div> + <div class="cell"> + <div class="key">{{ $ts.notes }}</div> + <div class="value">{{ instance.notesCount }}</div> + </div> + <div class="cell"> + <div class="key">{{ $ts.sent }}</div> + <div class="value"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div> + </div> + <div class="cell"> + <div class="key">{{ $ts.received }}</div> + <div class="value"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div> + </div> </div> - <div class="cell"> - <div class="key">{{ $ts.sent }}</div> - <div class="value"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div> + <div class="footer"> + <span class="status" :class="getStatus(instance)">{{ getStatus(instance) }}</span> + <span class="pubSub"> + <span class="sub" v-if="instance.followersCount > 0"><i class="fas fa-caret-down icon"></i>Sub</span> + <span class="sub" v-else><i class="fas fa-caret-down icon"></i>-</span> + <span class="pub" v-if="instance.followingCount > 0"><i class="fas fa-caret-up icon"></i>Pub</span> + <span class="pub" v-else><i class="fas fa-caret-up icon"></i>-</span> + </span> + <span class="right"> + <span class="latestStatus">{{ instance.latestStatus || '-' }}</span> + <span class="lastCommunicatedAt"><MkTime :time="instance.lastCommunicatedAt"/></span> + </span> </div> - <div class="cell"> - <div class="key">{{ $ts.received }}</div> - <div class="value"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div> - </div> - </div> - <div class="footer"> - <span class="status" :class="getStatus(instance)">{{ getStatus(instance) }}</span> - <span class="pubSub"> - <span class="sub" v-if="instance.followersCount > 0"><i class="fas fa-caret-down icon"></i>Sub</span> - <span class="sub" v-else><i class="fas fa-caret-down icon"></i>-</span> - <span class="pub" v-if="instance.followingCount > 0"><i class="fas fa-caret-up icon"></i>Pub</span> - <span class="pub" v-else><i class="fas fa-caret-up icon"></i>-</span> - </span> - <span class="right"> - <span class="latestStatus">{{ instance.latestStatus || '-' }}</span> - <span class="lastCommunicatedAt"><MkTime :time="instance.lastCommunicatedAt"/></span> - </span> - </div> - </MkA> - </div> - </MkPagination> + </MkA> + </div> + </MkPagination> + </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkPagination from '@client/components/ui/pagination.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -116,7 +119,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.federation, - icon: 'fas fa-globe' + icon: 'fas fa-globe', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.federation, + icon: 'fas fa-globe', + bg: 'var(--bg)', }, host: '', state: 'federating', diff --git a/src/client/pages/gallery/edit.vue b/src/client/pages/gallery/edit.vue index cd6a0defdd..8e74b068ef 100644 --- a/src/client/pages/gallery/edit.vue +++ b/src/client/pages/gallery/edit.vue @@ -1,23 +1,23 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormInput v-model:value="title"> + <FormInput v-model="title"> <span>{{ $ts.title }}</span> </FormInput> - <FormTextarea v-model:value="description" :max="500"> + <FormTextarea v-model="description" :max="500"> <span>{{ $ts.description }}</span> </FormTextarea> <FormGroup> - <div v-for="file in files" :key="file.id" class="_formItem _formPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> + <div v-for="file in files" :key="file.id" class="_debobigegoItem _debobigegoPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> <div class="name">{{ file.name }}</div> <button class="remove _button" @click="remove(file)" v-tooltip="$ts.remove"><i class="fas fa-times"></i></button> </div> <FormButton @click="selectFile" primary><i class="fas fa-plus"></i> {{ $ts.attachFile }}</FormButton> </FormGroup> - <FormSwitch v-model:value="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch> + <FormSwitch v-model="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch> <FormButton v-if="postId" @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> <FormButton v-else @click="save" primary><i class="fas fa-save"></i> {{ $ts.publish }}</FormButton> @@ -29,14 +29,14 @@ <script lang="ts"> import { computed, defineComponent } from 'vue'; -import FormButton from '@client/components/form/button.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormTuple from '@client/components/form/tuple.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormTuple from '@client/components/debobigego/tuple.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import { selectFile } from '@client/scripts/select-file'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/gallery/index.vue b/src/client/pages/gallery/index.vue index 9e726e70f2..ffc599513e 100644 --- a/src/client/pages/gallery/index.vue +++ b/src/client/pages/gallery/index.vue @@ -1,6 +1,6 @@ <template> <div class="xprsixdl _root"> - <MkTab v-model:value="tab" v-if="$i"> + <MkTab v-model="tab" v-if="$i"> <option value="explore"><i class="fas fa-icons"></i> {{ $ts.gallery }}</option> <option value="liked"><i class="fas fa-heart"></i> {{ $ts._gallery.liked }}</option> <option value="my"><i class="fas fa-edit"></i> {{ $ts._gallery.my }}</option> @@ -46,7 +46,7 @@ import { computed, defineComponent } from 'vue'; import XUserList from '@client/components/user-list.vue'; import MkFolder from '@client/components/ui/folder.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import MkButton from '@client/components/ui/button.vue'; import MkTab from '@client/components/tab.vue'; import MkPagination from '@client/components/ui/pagination.vue'; diff --git a/src/client/pages/instance-info.vue b/src/client/pages/instance-info.vue index 7d03c0847d..4fbf104f0c 100644 --- a/src/client/pages/instance-info.vue +++ b/src/client/pages/instance-info.vue @@ -3,8 +3,8 @@ <FormGroup v-if="instance"> <template #label>{{ instance.host }}</template> <FormGroup> - <div class="_formItem"> - <div class="_formPanel fnfelxur"> + <div class="_debobigegoItem"> + <div class="_debobigegoPanel fnfelxur"> <img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> </div> </div> @@ -60,9 +60,9 @@ <template #value>{{ instance.openRegistrations ? $ts.yes : $ts.no }}</template> </FormKeyValueView> </FormGroup> - <div class="_formItem"> - <div class="_formLabel">{{ $ts.statistics }}</div> - <div class="_formPanel cmhjzshl"> + <div class="_debobigegoItem"> + <div class="_debobigegoLabel">{{ $ts.statistics }}</div> + <div class="_debobigegoPanel cmhjzshl"> <div class="selects"> <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> <option value="requests">{{ $ts._instanceCharts.requests }}</option> @@ -136,15 +136,15 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; import Chart from 'chart.js'; -import FormObjectView from '@client/components/form/object-view.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import FormObjectView from '@client/components/debobigego/object-view.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; +import MkSelect from '@client/components/form/select.vue'; import * as os from '@client/os'; import number from '@client/filters/number'; import bytes from '@client/filters/bytes'; diff --git a/src/client/pages/instance/abuses.vue b/src/client/pages/instance/abuses.vue index ac20ebabe5..29da8cc2c5 100644 --- a/src/client/pages/instance/abuses.vue +++ b/src/client/pages/instance/abuses.vue @@ -24,10 +24,10 @@ </div> <!-- TODO <div class="inputs" style="display: flex; padding-top: 1.2em;"> - <MkInput v-model:value="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:value="$refs.reports.reload()"> + <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()"> <span>{{ $ts.username }}</span> </MkInput> - <MkInput v-model:value="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:value="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'"> <span>{{ $ts.host }}</span> </MkInput> </div> @@ -65,8 +65,8 @@ import { defineComponent } from 'vue'; 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'; +import MkInput from '@client/components/form/input.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkPagination from '@client/components/ui/pagination.vue'; import { acct } from '@client/filters/user'; import * as os from '@client/os'; @@ -86,7 +86,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.abuseReports, - icon: 'fas fa-exclamation-circle' + icon: 'fas fa-exclamation-circle', + bg: 'var(--bg)', }, searchUsername: '', searchHost: '', diff --git a/src/client/pages/instance/ads.vue b/src/client/pages/instance/ads.vue index 50c8c29cbf..e776f99a4c 100644 --- a/src/client/pages/instance/ads.vue +++ b/src/client/pages/instance/ads.vue @@ -1,52 +1,54 @@ <template> -<div class="uqshojas"> - <MkButton @click="add()" primary style="margin: 0 auto 16px auto;"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> - <section class="_card _gap ads" v-for="ad in ads"> - <div class="_content ad"> - <MkAd v-if="ad.url" :specify="ad"/> - <MkInput v-model="ad.url" type="url"> - <template #label>URL</template> - </MkInput> - <MkInput v-model="ad.imageUrl"> - <template #label>{{ $ts.imageUrl }}</template> - </MkInput> - <div style="margin: 32px 0;"> - <MkRadio v-model="ad.place" value="square">square</MkRadio> - <MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio> - <MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio> +<div> + <MkHeader :info="header"/> + <div class="uqshojas"> + <section class="_card _gap ads" v-for="ad in ads"> + <div class="_content ad"> + <MkAd v-if="ad.url" :specify="ad"/> + <MkInput v-model="ad.url" type="url"> + <template #label>URL</template> + </MkInput> + <MkInput v-model="ad.imageUrl"> + <template #label>{{ $ts.imageUrl }}</template> + </MkInput> + <div style="margin: 32px 0;"> + <MkRadio v-model="ad.place" value="square">square</MkRadio> + <MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio> + <MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio> + </div> + <!-- + <div style="margin: 32px 0;"> + {{ $ts.priority }} + <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> + <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio> + <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> + </div> + --> + <MkInput v-model="ad.ratio" type="number"> + <template #label>{{ $ts.ratio }}</template> + </MkInput> + <MkInput v-model="ad.expiresAt" type="date"> + <template #label>{{ $ts.expiration }}</template> + </MkInput> + <MkTextarea v-model="ad.memo"> + <template #label>{{ $ts.memo }}</template> + </MkTextarea> + <div class="buttons"> + <MkButton class="button" inline @click="save(ad)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton class="button" inline @click="remove(ad)" danger><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + </div> </div> - <!-- - <div style="margin: 32px 0;"> - {{ $ts.priority }} - <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> - <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio> - <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> - </div> - --> - <MkInput v-model="ad.ratio" type="number"> - <template #label>{{ $ts.ratio }}</template> - </MkInput> - <MkInput v-model="ad.expiresAt" type="date"> - <template #label>{{ $ts.expiration }}</template> - </MkInput> - <MkTextarea v-model="ad.memo"> - <template #label>{{ $ts.memo }}</template> - </MkTextarea> - <div class="buttons"> - <MkButton class="button" inline @click="save(ad)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline @click="remove(ad)" danger><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> - </div> - </div> - </section> + </section> + </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkRadio from '@client/components/ui/radio.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkRadio from '@client/components/form/radio.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -64,7 +66,19 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.ads, - icon: 'fas fa-audio-description' + icon: 'fas fa-audio-description', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.ads, + icon: 'fas fa-audio-description', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: this.$ts.add, + handler: this.add, + }], }, ads: [], } diff --git a/src/client/pages/instance/announcements.vue b/src/client/pages/instance/announcements.vue index d48e3737ad..78637c095a 100644 --- a/src/client/pages/instance/announcements.vue +++ b/src/client/pages/instance/announcements.vue @@ -1,32 +1,35 @@ <template> -<div class="ztgjmzrw"> - <MkButton @click="add()" primary style="margin: 0 auto 16px auto;"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> - <section class="_card _gap announcements" v-for="announcement in announcements"> - <div class="_content announcement"> - <MkInput v-model="announcement.title"> - <template #label>{{ $ts.title }}</template> - </MkInput> - <MkTextarea v-model="announcement.text"> - <template #label>{{ $ts.text }}</template> - </MkTextarea> - <MkInput v-model="announcement.imageUrl"> - <template #label>{{ $ts.imageUrl }}</template> - </MkInput> - <p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p> - <div class="buttons"> - <MkButton class="button" inline @click="save(announcement)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> +<div> + <MkHeader :info="header"/> + + <div class="ztgjmzrw"> + <section class="_card _gap announcements" v-for="announcement in announcements"> + <div class="_content announcement"> + <MkInput v-model="announcement.title"> + <template #label>{{ $ts.title }}</template> + </MkInput> + <MkTextarea v-model="announcement.text"> + <template #label>{{ $ts.text }}</template> + </MkTextarea> + <MkInput v-model="announcement.imageUrl"> + <template #label>{{ $ts.imageUrl }}</template> + </MkInput> + <p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p> + <div class="buttons"> + <MkButton class="button" inline @click="save(announcement)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + </div> </div> - </div> - </section> + </section> + </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -43,7 +46,19 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.announcements, - icon: 'fas fa-broadcast-tower' + icon: 'fas fa-broadcast-tower', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.announcements, + icon: 'fas fa-broadcast-tower', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: this.$ts.add, + handler: this.add, + }], }, announcements: [], } diff --git a/src/client/pages/instance/bot-protection.vue b/src/client/pages/instance/bot-protection.vue index 449b8a233d..731f114cc2 100644 --- a/src/client/pages/instance/bot-protection.vue +++ b/src/client/pages/instance/bot-protection.vue @@ -9,43 +9,43 @@ </FormRadios> <template v-if="provider === 'hcaptcha'"> - <div class="_formItem _formNoConcat" v-sticky-container> - <div class="_formLabel">hCaptcha</div> + <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container> + <div class="_debobigegoLabel">hCaptcha</div> <div class="main"> - <FormInput v-model:value="hcaptchaSiteKey"> + <FormInput v-model="hcaptchaSiteKey"> <template #prefix><i class="fas fa-key"></i></template> <span>{{ $ts.hcaptchaSiteKey }}</span> </FormInput> - <FormInput v-model:value="hcaptchaSecretKey"> + <FormInput v-model="hcaptchaSecretKey"> <template #prefix><i class="fas fa-key"></i></template> <span>{{ $ts.hcaptchaSecretKey }}</span> </FormInput> </div> </div> - <div class="_formItem _formNoConcat" v-sticky-container> - <div class="_formLabel">{{ $ts.preview }}</div> - <div class="_formPanel" style="padding: var(--formContentHMargin);"> + <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container> + <div class="_debobigegoLabel">{{ $ts.preview }}</div> + <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> <MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/> </div> </div> </template> <template v-else-if="provider === 'recaptcha'"> - <div class="_formItem _formNoConcat" v-sticky-container> - <div class="_formLabel">reCAPTCHA</div> + <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container> + <div class="_debobigegoLabel">reCAPTCHA</div> <div class="main"> - <FormInput v-model:value="recaptchaSiteKey"> + <FormInput v-model="recaptchaSiteKey"> <template #prefix><i class="fas fa-key"></i></template> <span>{{ $ts.recaptchaSiteKey }}</span> </FormInput> - <FormInput v-model:value="recaptchaSecretKey"> + <FormInput v-model="recaptchaSecretKey"> <template #prefix><i class="fas fa-key"></i></template> <span>{{ $ts.recaptchaSecretKey }}</span> </FormInput> </div> </div> - <div v-if="recaptchaSiteKey" class="_formItem _formNoConcat" v-sticky-container> - <div class="_formLabel">{{ $ts.preview }}</div> - <div class="_formPanel" style="padding: var(--formContentHMargin);"> + <div v-if="recaptchaSiteKey" class="_debobigegoItem _debobigegoNoConcat" v-sticky-container> + <div class="_debobigegoLabel">{{ $ts.preview }}</div> + <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> <MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/> </div> </div> @@ -58,13 +58,13 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormRadios from '@client/components/form/radios.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormRadios from '@client/components/debobigego/radios.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; diff --git a/src/client/pages/instance/database.vue b/src/client/pages/instance/database.vue index a41d61ce2b..ffbeed8b30 100644 --- a/src/client/pages/instance/database.vue +++ b/src/client/pages/instance/database.vue @@ -18,11 +18,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSuspense from '@client/components/form/suspense.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import bytes from '@client/filters/bytes'; @@ -43,7 +43,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.database, - icon: 'fas fa-database' + icon: 'fas fa-database', + bg: 'var(--bg)', }, databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)), } diff --git a/src/client/pages/instance/email-settings.vue b/src/client/pages/instance/email-settings.vue index 9965a1420f..ebf724fcdd 100644 --- a/src/client/pages/instance/email-settings.vue +++ b/src/client/pages/instance/email-settings.vue @@ -1,30 +1,30 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormSwitch v-model:value="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch> + <FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch> <template v-if="enableEmail"> - <FormInput v-model:value="email" type="email"> + <FormInput v-model="email" type="email"> <span>{{ $ts.emailAddress }}</span> </FormInput> - <div class="_formItem _formNoConcat" v-sticky-container> - <div class="_formLabel">{{ $ts.smtpConfig }}</div> + <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container> + <div class="_debobigegoLabel">{{ $ts.smtpConfig }}</div> <div class="main"> - <FormInput v-model:value="smtpHost"> + <FormInput v-model="smtpHost"> <span>{{ $ts.smtpHost }}</span> </FormInput> - <FormInput v-model:value="smtpPort" type="number"> + <FormInput v-model="smtpPort" type="number"> <span>{{ $ts.smtpPort }}</span> </FormInput> - <FormInput v-model:value="smtpUser"> + <FormInput v-model="smtpUser"> <span>{{ $ts.smtpUser }}</span> </FormInput> - <FormInput v-model:value="smtpPass" type="password"> + <FormInput v-model="smtpPass" type="password"> <span>{{ $ts.smtpPass }}</span> </FormInput> <FormInfo>{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> - <FormSwitch v-model:value="smtpSecure">{{ $ts.smtpSecure }}<template #desc>{{ $ts.smtpSecureInfo }}</template></FormSwitch> + <FormSwitch v-model="smtpSecure">{{ $ts.smtpSecure }}<template #desc>{{ $ts.smtpSecureInfo }}</template></FormSwitch> </div> </div> @@ -38,13 +38,13 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -66,7 +66,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.emailServer, - icon: 'fas fa-envelope' + icon: 'fas fa-envelope', + bg: 'var(--bg)', }, enableEmail: false, email: null, diff --git a/src/client/pages/instance/emoji-edit-dialog.vue b/src/client/pages/instance/emoji-edit-dialog.vue index 7e9bdc80dd..4854c69884 100644 --- a/src/client/pages/instance/emoji-edit-dialog.vue +++ b/src/client/pages/instance/emoji-edit-dialog.vue @@ -11,13 +11,13 @@ <div class="_monolithic_"> <div class="yigymqpb _section"> <img :src="emoji.url" class="img"/> - <MkInput v-model="name"> + <MkInput class="_formBlock" v-model="name"> <template #label>{{ $ts.name }}</template> </MkInput> - <MkInput v-model="category" :datalist="categories"> + <MkInput class="_formBlock" v-model="category" :datalist="categories"> <template #label>{{ $ts.category }}</template> </MkInput> - <MkInput v-model="aliases"> + <MkInput class="_formBlock" v-model="aliases"> <template #label>{{ $ts.tags }}</template> <template #caption>{{ $ts.setMultipleBySeparatingWithSpace }}</template> </MkInput> @@ -31,7 +31,7 @@ import { defineComponent } from 'vue'; import XModalWindow from '@client/components/ui/modal-window.vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; import { unique } from '../../../prelude/array'; diff --git a/src/client/pages/instance/emojis.vue b/src/client/pages/instance/emojis.vue index 7badc9da02..4cd34b046d 100644 --- a/src/client/pages/instance/emojis.vue +++ b/src/client/pages/instance/emojis.vue @@ -1,12 +1,8 @@ <template> <div class="ogwlenmc"> - <MkTab v-model:value="tab"> - <option value="local">{{ $ts.local }}</option> - <option value="remote">{{ $ts.remote }}</option> - </MkTab> + <MkHeader :info="header"/> <div class="local" v-if="tab === 'local'"> - <MkButton primary @click="add" style="margin: var(--margin) auto;"><i class="fas fa-plus"></i> {{ $ts.addEmoji }}</MkButton> <MkInput v-model="query" :debounce="true" type="search" style="margin: var(--margin);"> <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> @@ -56,7 +52,7 @@ <script lang="ts"> import { computed, defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import MkPagination from '@client/components/ui/pagination.vue'; import MkTab from '@client/components/tab.vue'; import { selectFile } from '@client/scripts/select-file'; @@ -78,11 +74,28 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.customEmojis, icon: 'fas fa-laugh', - action: { - icon: 'fas fa-plus', - handler: this.add - } + bg: 'var(--bg)', }, + header: computed(() => ({ + title: this.$ts.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: this.$ts.addEmoji, + handler: this.add, + }], + tabs: [{ + active: this.tab === 'local', + title: this.$ts.local, + onClick: () => { this.tab = 'local'; }, + }, { + active: this.tab === 'remote', + title: this.$ts.remote, + onClick: () => { this.tab = 'remote'; }, + },] + })), tab: 'local', query: null, queryRemote: null, diff --git a/src/client/pages/instance/file-dialog.vue b/src/client/pages/instance/file-dialog.vue index 8a03a11de7..02d83e5022 100644 --- a/src/client/pages/instance/file-dialog.vue +++ b/src/client/pages/instance/file-dialog.vue @@ -37,7 +37,7 @@ <script lang="ts"> import { computed, defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; +import MkSwitch from '@client/components/form/switch.vue'; import XModalWindow from '@client/components/ui/modal-window.vue'; import MkDriveFileThumbnail from '@client/components/drive-file-thumbnail.vue'; import Progress from '@client/scripts/loading'; diff --git a/src/client/pages/instance/files-settings.vue b/src/client/pages/instance/files-settings.vue index 614c7d4dbb..8aefa9e90d 100644 --- a/src/client/pages/instance/files-settings.vue +++ b/src/client/pages/instance/files-settings.vue @@ -1,23 +1,23 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormSwitch v-model:value="cacheRemoteFiles"> + <FormSwitch v-model="cacheRemoteFiles"> {{ $ts.cacheRemoteFiles }} <template #desc>{{ $ts.cacheRemoteFilesDescription }}</template> </FormSwitch> - <FormSwitch v-model:value="proxyRemoteFiles"> + <FormSwitch v-model="proxyRemoteFiles"> {{ $ts.proxyRemoteFiles }} <template #desc>{{ $ts.proxyRemoteFilesDescription }}</template> </FormSwitch> - <FormInput v-model:value="localDriveCapacityMb" type="number"> + <FormInput v-model="localDriveCapacityMb" type="number"> <span>{{ $ts.driveCapacityPerLocalAccount }}</span> <template #suffix>MB</template> <template #desc>{{ $ts.inMb }}</template> </FormInput> - <FormInput v-model:value="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles"> + <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles"> <span>{{ $ts.driveCapacityPerRemoteAccount }}</span> <template #suffix>MB</template> <template #desc>{{ $ts.inMb }}</template> @@ -30,12 +30,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -56,7 +56,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.files, - icon: 'fas fa-cloud' + icon: 'fas fa-cloud', + bg: 'var(--bg)', }, cacheRemoteFiles: false, proxyRemoteFiles: false, diff --git a/src/client/pages/instance/files.vue b/src/client/pages/instance/files.vue index b7f472b7c8..55189cfd84 100644 --- a/src/client/pages/instance/files.vue +++ b/src/client/pages/instance/files.vue @@ -1,20 +1,14 @@ <template> <div class="xrmjdkdw"> - <div class="_section"> - <div class="_content"> - <MkButton primary @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearCachedFiles }}</MkButton> - </div> - </div> - - <div class="_section lookup"> - <div class="_title"><i class="fas fa-search"></i> {{ $ts.lookup }}</div> - <div class="_content"> - <MkInput class="target" v-model="q" type="text" @enter="find()"> + <MkContainer :foldable="true" class="lookup"> + <template #header><i class="fas fa-search"></i> {{ $ts.lookup }}</template> + <div class="xrmjdkdw-lookup"> + <MkInput class="item" v-model="q" type="text" @enter="find()"> <template #label>{{ $ts.fileIdOrUrl }}</template> </MkInput> <MkButton @click="find()" primary><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton> </div> - </div> + </MkContainer> <div class="_section"> <div class="_content"> @@ -31,7 +25,7 @@ </div> <div class="inputs" style="display: flex; padding-top: 1.2em;"> <MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;"> - <template #label>{{ $ts.type }}</template> + <template #label>MIME type</template> </MkInput> </div> <MkPagination :pagination="pagination" #default="{items}" class="urempief" ref="files"> @@ -63,9 +57,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkPagination from '@client/components/ui/pagination.vue'; +import MkContainer from '@client/components/ui/container.vue'; import MkDriveFileThumbnail from '@client/components/drive-file-thumbnail.vue'; import bytes from '@client/filters/bytes'; import * as os from '@client/os'; @@ -77,6 +72,7 @@ export default defineComponent({ MkInput, MkSelect, MkPagination, + MkContainer, MkDriveFileThumbnail, }, @@ -86,7 +82,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.files, - icon: 'fas fa-cloud' + icon: 'fas fa-cloud', + bg: 'var(--bg)', + actions: [{ + text: this.$ts.clearCachedFiles, + icon: 'fas fa-trash-alt', + handler: this.clear + }] }, q: null, origin: 'local', @@ -161,6 +163,10 @@ export default defineComponent({ .xrmjdkdw { margin: var(--margin); + > .lookup { + margin-bottom: 16px; + } + .urempief { margin-top: var(--margin); @@ -192,4 +198,12 @@ export default defineComponent({ } } } + +.xrmjdkdw-lookup { + padding: 16px; + + > .item { + margin-bottom: 16px; + } +} </style> diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue index 612bfa762a..7b07bf2dde 100644 --- a/src/client/pages/instance/index.vue +++ b/src/client/pages/instance/index.vue @@ -1,51 +1,16 @@ <template> <div class="hiyeyicy" :class="{ wide: !narrow }" ref="el"> <div class="nav" v-if="!narrow || page == null"> - <FormBase> - <FormGroup> - <div class="_formItem"> - <div class="_formPanel lxpfedzu"> - <img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/> - </div> - </div> - <FormLink :active="page === 'overview'" replace to="/instance/overview"><template #icon><i class="fas fa-tachometer-alt"></i></template>{{ $ts.overview }}</FormLink> - </FormGroup> - <FormGroup> - <template #label>{{ $ts.quickAction }}</template> - <FormButton @click="lookup"><i class="fas fa-search"></i> {{ $ts.lookup }}</FormButton> - <FormButton v-if="$instance.disableRegistration" @click="invite"><i class="fas fa-user"></i> {{ $ts.invite }}</FormButton> - </FormGroup> - <FormGroup> - <template #label>{{ $ts.administration }}</template> - <FormLink :active="page === 'users'" replace to="/instance/users"><template #icon><i class="fas fa-users"></i></template>{{ $ts.users }}</FormLink> - <FormLink :active="page === 'emojis'" replace to="/instance/emojis"><template #icon><i class="fas fa-laugh"></i></template>{{ $ts.customEmojis }}</FormLink> - <FormLink :active="page === 'federation'" replace to="/instance/federation"><template #icon><i class="fas fa-globe"></i></template>{{ $ts.federation }}</FormLink> - <FormLink :active="page === 'queue'" replace to="/instance/queue"><template #icon><i class="fas fa-clipboard-list"></i></template>{{ $ts.jobQueue }}</FormLink> - <FormLink :active="page === 'files'" replace to="/instance/files"><template #icon><i class="fas fa-cloud"></i></template>{{ $ts.files }}</FormLink> - <FormLink :active="page === 'announcements'" replace to="/instance/announcements"><template #icon><i class="fas fa-broadcast-tower"></i></template>{{ $ts.announcements }}</FormLink> - <FormLink :active="page === 'ads'" replace to="/instance/ads"><template #icon><i class="fas fa-audio-description"></i></template>{{ $ts.ads }}</FormLink> - <FormLink :active="page === 'abuses'" replace to="/instance/abuses"><template #icon><i class="fas fa-exclamation-circle"></i></template>{{ $ts.abuseReports }}</FormLink> - </FormGroup> - <FormGroup> - <template #label>{{ $ts.settings }}</template> - <FormLink :active="page === 'settings'" replace to="/instance/settings"><template #icon><i class="fas fa-cog"></i></template>{{ $ts.general }}</FormLink> - <FormLink :active="page === 'files-settings'" replace to="/instance/files-settings"><template #icon><i class="fas fa-cloud"></i></template>{{ $ts.files }}</FormLink> - <FormLink :active="page === 'email-settings'" replace to="/instance/email-settings"><template #icon><i class="fas fa-envelope"></i></template>{{ $ts.emailServer }}</FormLink> - <FormLink :active="page === 'object-storage'" replace to="/instance/object-storage"><template #icon><i class="fas fa-cloud"></i></template>{{ $ts.objectStorage }}</FormLink> - <FormLink :active="page === 'security'" replace to="/instance/security"><template #icon><i class="fas fa-lock"></i></template>{{ $ts.security }}</FormLink> - <FormLink :active="page === 'service-worker'" replace to="/instance/service-worker"><template #icon><i class="fas fa-bolt"></i></template>ServiceWorker</FormLink> - <FormLink :active="page === 'relays'" replace to="/instance/relays"><template #icon><i class="fas fa-globe"></i></template>{{ $ts.relays }}</FormLink> - <FormLink :active="page === 'integrations'" replace to="/instance/integrations"><template #icon><i class="fas fa-share-alt"></i></template>{{ $ts.integration }}</FormLink> - <FormLink :active="page === 'instance-block'" replace to="/instance/instance-block"><template #icon><i class="fas fa-ban"></i></template>{{ $ts.instanceBlocking }}</FormLink> - <FormLink :active="page === 'proxy-account'" replace to="/instance/proxy-account"><template #icon><i class="fas fa-ghost"></i></template>{{ $ts.proxyAccount }}</FormLink> - <FormLink :active="page === 'other-settings'" replace to="/instance/other-settings"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.other }}</FormLink> - </FormGroup> - <FormGroup> - <template #label>{{ $ts.info }}</template> - <FormLink :active="page === 'database'" replace to="/instance/database"><template #icon><i class="fas fa-database"></i></template>{{ $ts.database }}</FormLink> - <FormLink :active="page === 'logs'" replace to="/instance/logs"><template #icon><i class="fas fa-stream"></i></template>{{ $ts.logs }}</FormLink> - </FormGroup> - </FormBase> + <MkHeader :info="header"></MkHeader> + + <div class="lxpfedzu"> + <img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/> + </div> + + <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/instance/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo> + <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/instance/bot-protection" class="_link">{{ $ts.configure }}</MkA></MkInfo> + + <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> </div> <div class="main"> <component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/> @@ -56,11 +21,13 @@ <script lang="ts"> import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { i18n } from '@client/i18n'; -import FormLink from '@client/components/form/link.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormButton from '@client/components/form/button.vue'; +import MkSuperMenu from '@client/components/ui/super-menu.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import MkInfo from '@client/components/ui/info.vue'; import { scroll } from '@client/scripts/scroll'; +import { instance } from '@client/instance'; import * as symbols from '@client/symbols'; import * as os from '@client/os'; import { lookupUser } from '@client/scripts/lookup-user'; @@ -68,9 +35,10 @@ import { lookupUser } from '@client/scripts/lookup-user'; export default defineComponent({ components: { FormBase, - FormLink, + MkSuperMenu, FormGroup, FormButton, + MkInfo, }, props: { @@ -83,7 +51,8 @@ export default defineComponent({ setup(props, context) { const indexInfo = { title: i18n.locale.instance, - icon: 'fas fa-cog' + icon: 'fas fa-cog', + bg: 'var(--bg)', }; const INFO = ref(indexInfo); const page = ref(props.initialPage); @@ -94,6 +63,151 @@ export default defineComponent({ INFO.value = viewInfo; }; const pageProps = ref({}); + + const isEmpty = (x: any) => x == null || x == ''; + + const noMaintainerInformation = ref(false); + const noBotProtection = ref(false); + + os.api('meta', { detail: true }).then(meta => { + // TODO: 設定が完了しても残ったままになるので、ストリーミングでmeta更新イベントを受け取ってよしなに更新する + noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail); + noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha; + }); + + const menuDef = computed(() => [{ + title: i18n.locale.quickAction, + items: [{ + type: 'button', + icon: 'fas fa-search', + text: i18n.locale.lookup, + action: lookup, + }, ...(instance.disableRegistration ? [{ + type: 'button', + icon: 'fas fa-user', + text: i18n.locale.invite, + action: invite, + }] : [])], + }, { + title: i18n.locale.administration, + items: [{ + icon: 'fas fa-tachometer-alt', + text: i18n.locale.dashboard, + to: '/instance/overview', + active: page.value === 'overview', + }, { + icon: 'fas fa-users', + text: i18n.locale.users, + to: '/instance/users', + active: page.value === 'users', + }, { + icon: 'fas fa-laugh', + text: i18n.locale.customEmojis, + to: '/instance/emojis', + active: page.value === 'emojis', + }, { + icon: 'fas fa-globe', + text: i18n.locale.federation, + to: '/instance/federation', + active: page.value === 'federation', + }, { + icon: 'fas fa-clipboard-list', + text: i18n.locale.jobQueue, + to: '/instance/queue', + active: page.value === 'queue', + }, { + icon: 'fas fa-cloud', + text: i18n.locale.files, + to: '/instance/files', + active: page.value === 'files', + }, { + icon: 'fas fa-broadcast-tower', + text: i18n.locale.announcements, + to: '/instance/announcements', + active: page.value === 'announcements', + }, { + icon: 'fas fa-audio-description', + text: i18n.locale.ads, + to: '/instance/ads', + active: page.value === 'ads', + }, { + icon: 'fas fa-exclamation-circle', + text: i18n.locale.abuseReports, + to: '/instance/abuses', + active: page.value === 'abuses', + }], + }, { + title: i18n.locale.settings, + items: [{ + icon: 'fas fa-cog', + text: i18n.locale.general, + to: '/instance/settings', + active: page.value === 'settings', + }, { + icon: 'fas fa-cloud', + text: i18n.locale.files, + to: '/instance/files-settings', + active: page.value === 'files-settings', + }, { + icon: 'fas fa-envelope', + text: i18n.locale.emailServer, + to: '/instance/email-settings', + active: page.value === 'email-settings', + }, { + icon: 'fas fa-cloud', + text: i18n.locale.objectStorage, + to: '/instance/object-storage', + active: page.value === 'object-storage', + }, { + icon: 'fas fa-lock', + text: i18n.locale.security, + to: '/instance/security', + active: page.value === 'security', + }, { + icon: 'fas fa-bolt', + text: 'ServiceWorker', + to: '/instance/service-worker', + active: page.value === 'service-worker', + }, { + icon: 'fas fa-globe', + text: i18n.locale.relays, + to: '/instance/relays', + active: page.value === 'relays', + }, { + icon: 'fas fa-share-alt', + text: i18n.locale.integration, + to: '/instance/integrations', + active: page.value === 'integrations', + }, { + icon: 'fas fa-ban', + text: i18n.locale.instanceBlocking, + to: '/instance/instance-block', + active: page.value === 'instance-block', + }, { + icon: 'fas fa-ghost', + text: i18n.locale.proxyAccount, + to: '/instance/proxy-account', + active: page.value === 'proxy-account', + }, { + icon: 'fas fa-cogs', + text: i18n.locale.other, + to: '/instance/other-settings', + active: page.value === 'other-settings', + }], + }, { + title: i18n.locale.info, + items: [{ + icon: 'fas fa-database', + text: i18n.locale.database, + to: '/instance/database', + active: page.value === 'database', + }, { + icon: 'fas fa-stream', + text: i18n.locale.logs, + to: '/instance/logs', + active: page.value === 'logs', + }], + }]); const component = computed(() => { if (page.value == null) return null; switch (page.value) { @@ -130,7 +244,7 @@ export default defineComponent({ pageProps.value = {}; nextTick(() => { - scroll(el.value, 0); + scroll(el.value, { top: 0 }); }); }, { immediate: true }); @@ -196,6 +310,12 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: INFO, + menuDef, + header: { + title: i18n.locale.controllPanel, + }, + noMaintainerInformation, + noBotProtection, page, narrow, view, @@ -214,28 +334,34 @@ export default defineComponent({ .hiyeyicy { &.wide { display: flex; - max-width: 1100px; margin: 0 auto; height: 100%; > .nav { width: 32%; + max-width: 280px; box-sizing: border-box; border-right: solid 0.5px var(--divider); overflow: auto; + height: 100%; } > .main { flex: 1; min-width: 0; - overflow: auto; --baseContentWidth: 100%; } } + + > .nav { + > .info { + margin: 16px; + } + } } .lxpfedzu { - padding: 16px; + margin: 16px; > .icon { display: block; diff --git a/src/client/pages/instance/instance-block.vue b/src/client/pages/instance/instance-block.vue index ed5740f339..105cdb4941 100644 --- a/src/client/pages/instance/instance-block.vue +++ b/src/client/pages/instance/instance-block.vue @@ -1,7 +1,7 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormTextarea v-model:value="blockedHosts"> + <FormTextarea v-model="blockedHosts"> <span>{{ $ts.blockedInstances }}</span> <template #desc>{{ $ts.blockedInstancesDescription }}</template> </FormTextarea> @@ -13,14 +13,14 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -43,7 +43,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.instanceBlocking, - icon: 'fas fa-ban' + icon: 'fas fa-ban', + bg: 'var(--bg)', }, blockedHosts: '', } diff --git a/src/client/pages/instance/instance.vue b/src/client/pages/instance/instance.vue index c39f0d1ecb..6117f090de 100644 --- a/src/client/pages/instance/instance.vue +++ b/src/client/pages/instance/instance.vue @@ -127,9 +127,9 @@ import { defineComponent, markRaw } from 'vue'; import Chart from 'chart.js'; import XModalWindow from '@client/components/ui/modal-window.vue'; import MkUsersDialog from '@client/components/users-dialog.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkButton from '@client/components/ui/button.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; +import MkSwitch from '@client/components/form/switch.vue'; import MkInfo from '@client/components/ui/info.vue'; import bytes from '@client/filters/bytes'; import number from '@client/filters/number'; diff --git a/src/client/pages/instance/integrations-discord.vue b/src/client/pages/instance/integrations-discord.vue index c7508918f8..c33b24f17f 100644 --- a/src/client/pages/instance/integrations-discord.vue +++ b/src/client/pages/instance/integrations-discord.vue @@ -1,19 +1,19 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormSwitch v-model:value="enableDiscordIntegration"> + <FormSwitch v-model="enableDiscordIntegration"> {{ $ts.enable }} </FormSwitch> <template v-if="enableDiscordIntegration"> <FormInfo>Callback URL: {{ `${url}/api/dc/cb` }}</FormInfo> - <FormInput v-model:value="discordClientId"> + <FormInput v-model="discordClientId"> <template #prefix><i class="fas fa-key"></i></template> Client ID </FormInput> - <FormInput v-model:value="discordClientSecret"> + <FormInput v-model="discordClientSecret"> <template #prefix><i class="fas fa-key"></i></template> Client Secret </FormInput> @@ -26,12 +26,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; diff --git a/src/client/pages/instance/integrations-github.vue b/src/client/pages/instance/integrations-github.vue index 16586b15b4..cdf85868ff 100644 --- a/src/client/pages/instance/integrations-github.vue +++ b/src/client/pages/instance/integrations-github.vue @@ -1,19 +1,19 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormSwitch v-model:value="enableGithubIntegration"> + <FormSwitch v-model="enableGithubIntegration"> {{ $ts.enable }} </FormSwitch> <template v-if="enableGithubIntegration"> <FormInfo>Callback URL: {{ `${url}/api/gh/cb` }}</FormInfo> - <FormInput v-model:value="githubClientId"> + <FormInput v-model="githubClientId"> <template #prefix><i class="fas fa-key"></i></template> Client ID </FormInput> - <FormInput v-model:value="githubClientSecret"> + <FormInput v-model="githubClientSecret"> <template #prefix><i class="fas fa-key"></i></template> Client Secret </FormInput> @@ -26,12 +26,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; diff --git a/src/client/pages/instance/integrations-twitter.vue b/src/client/pages/instance/integrations-twitter.vue index b08b7f40a5..ed7d097d0a 100644 --- a/src/client/pages/instance/integrations-twitter.vue +++ b/src/client/pages/instance/integrations-twitter.vue @@ -1,19 +1,19 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormSwitch v-model:value="enableTwitterIntegration"> + <FormSwitch v-model="enableTwitterIntegration"> {{ $ts.enable }} </FormSwitch> <template v-if="enableTwitterIntegration"> <FormInfo>Callback URL: {{ `${url}/api/tw/cb` }}</FormInfo> - <FormInput v-model:value="twitterConsumerKey"> + <FormInput v-model="twitterConsumerKey"> <template #prefix><i class="fas fa-key"></i></template> Consumer Key </FormInput> - <FormInput v-model:value="twitterConsumerSecret"> + <FormInput v-model="twitterConsumerSecret"> <template #prefix><i class="fas fa-key"></i></template> Consumer Secret </FormInput> @@ -26,12 +26,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; diff --git a/src/client/pages/instance/integrations.vue b/src/client/pages/instance/integrations.vue index 7debedc367..6964ae5704 100644 --- a/src/client/pages/instance/integrations.vue +++ b/src/client/pages/instance/integrations.vue @@ -19,14 +19,14 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormLink from '@client/components/form/link.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -49,7 +49,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.integration, - icon: 'fas fa-share-alt' + icon: 'fas fa-share-alt', + bg: 'var(--bg)', }, enableTwitterIntegration: false, enableGithubIntegration: false, diff --git a/src/client/pages/instance/logs.vue b/src/client/pages/instance/logs.vue index 4eee816f96..74aea0fc45 100644 --- a/src/client/pages/instance/logs.vue +++ b/src/client/pages/instance/logs.vue @@ -31,9 +31,9 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSelect from '@client/components/ui/select.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSelect from '@client/components/form/select.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/instance/metrics.vue b/src/client/pages/instance/metrics.vue index 283b5939f0..1606063aee 100644 --- a/src/client/pages/instance/metrics.vue +++ b/src/client/pages/instance/metrics.vue @@ -1,7 +1,7 @@ <template> -<div class="_formItem"> - <div class="_formLabel"><i class="fas fa-microchip"></i> {{ $ts.cpuAndMemory }}</div> - <div class="_formPanel xhexznfu"> +<div class="_debobigegoItem"> + <div class="_debobigegoLabel"><i class="fas fa-microchip"></i> {{ $ts.cpuAndMemory }}</div> + <div class="_debobigegoPanel xhexznfu"> <div> <canvas :ref="cpumem"></canvas> </div> @@ -16,9 +16,9 @@ </div> </div> </div> -<div class="_formItem"> - <div class="_formLabel"><i class="fas fa-hdd"></i> {{ $ts.disk }}</div> - <div class="_formPanel xhexznfu"> +<div class="_debobigegoItem"> + <div class="_debobigegoLabel"><i class="fas fa-hdd"></i> {{ $ts.disk }}</div> + <div class="_debobigegoPanel xhexznfu"> <div> <canvas :ref="disk"></canvas> </div> @@ -33,9 +33,9 @@ </div> </div> </div> -<div class="_formItem"> - <div class="_formLabel"><i class="fas fa-exchange-alt"></i> {{ $ts.network }}</div> - <div class="_formPanel xhexznfu"> +<div class="_debobigegoItem"> + <div class="_debobigegoLabel"><i class="fas fa-exchange-alt"></i> {{ $ts.network }}</div> + <div class="_debobigegoPanel xhexznfu"> <div> <canvas :ref="net"></canvas> </div> @@ -54,8 +54,8 @@ import { defineComponent, markRaw } from 'vue'; import Chart from 'chart.js'; import MkButton from '@client/components/ui/button.vue'; -import MkSelect from '@client/components/ui/select.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkSelect from '@client/components/form/select.vue'; +import MkInput from '@client/components/form/input.vue'; import MkContainer from '@client/components/ui/container.vue'; import MkFolder from '@client/components/ui/folder.vue'; import MkwFederation from '../../widgets/federation.vue'; diff --git a/src/client/pages/instance/object-storage.vue b/src/client/pages/instance/object-storage.vue index 814aeb6e48..2d765270e6 100644 --- a/src/client/pages/instance/object-storage.vue +++ b/src/client/pages/instance/object-storage.vue @@ -1,59 +1,59 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormSwitch v-model:value="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch> + <FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch> <template v-if="useObjectStorage"> - <FormInput v-model:value="objectStorageBaseUrl"> + <FormInput v-model="objectStorageBaseUrl"> <span>{{ $ts.objectStorageBaseUrl }}</span> <template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template> </FormInput> - <FormInput v-model:value="objectStorageBucket"> + <FormInput v-model="objectStorageBucket"> <span>{{ $ts.objectStorageBucket }}</span> <template #desc>{{ $ts.objectStorageBucketDesc }}</template> </FormInput> - <FormInput v-model:value="objectStoragePrefix"> + <FormInput v-model="objectStoragePrefix"> <span>{{ $ts.objectStoragePrefix }}</span> <template #desc>{{ $ts.objectStoragePrefixDesc }}</template> </FormInput> - <FormInput v-model:value="objectStorageEndpoint"> + <FormInput v-model="objectStorageEndpoint"> <span>{{ $ts.objectStorageEndpoint }}</span> <template #desc>{{ $ts.objectStorageEndpointDesc }}</template> </FormInput> - <FormInput v-model:value="objectStorageRegion"> + <FormInput v-model="objectStorageRegion"> <span>{{ $ts.objectStorageRegion }}</span> <template #desc>{{ $ts.objectStorageRegionDesc }}</template> </FormInput> - <FormInput v-model:value="objectStorageAccessKey"> + <FormInput v-model="objectStorageAccessKey"> <template #prefix><i class="fas fa-key"></i></template> <span>Access key</span> </FormInput> - <FormInput v-model:value="objectStorageSecretKey"> + <FormInput v-model="objectStorageSecretKey"> <template #prefix><i class="fas fa-key"></i></template> <span>Secret key</span> </FormInput> - <FormSwitch v-model:value="objectStorageUseSSL"> + <FormSwitch v-model="objectStorageUseSSL"> {{ $ts.objectStorageUseSSL }} <template #desc>{{ $ts.objectStorageUseSSLDesc }}</template> </FormSwitch> - <FormSwitch v-model:value="objectStorageUseProxy"> + <FormSwitch v-model="objectStorageUseProxy"> {{ $ts.objectStorageUseProxy }} <template #desc>{{ $ts.objectStorageUseProxyDesc }}</template> </FormSwitch> - <FormSwitch v-model:value="objectStorageSetPublicRead"> + <FormSwitch v-model="objectStorageSetPublicRead"> {{ $ts.objectStorageSetPublicRead }} </FormSwitch> - <FormSwitch v-model:value="objectStorageS3ForcePathStyle"> + <FormSwitch v-model="objectStorageS3ForcePathStyle"> s3ForcePathStyle </FormSwitch> </template> @@ -65,12 +65,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -91,7 +91,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.objectStorage, - icon: 'fas fa-cloud' + icon: 'fas fa-cloud', + bg: 'var(--bg)', }, useObjectStorage: false, objectStorageBaseUrl: null, diff --git a/src/client/pages/instance/other-settings.vue b/src/client/pages/instance/other-settings.vue index 4fa80b2b2c..4e55df41fb 100644 --- a/src/client/pages/instance/other-settings.vue +++ b/src/client/pages/instance/other-settings.vue @@ -2,17 +2,17 @@ <FormBase> <FormSuspense :p="init"> <FormGroup> - <FormInput v-model:value="summalyProxy"> + <FormInput v-model="summalyProxy"> <template #prefix><i class="fas fa-link"></i></template> Summaly Proxy URL </FormInput> </FormGroup> <FormGroup> - <FormInput v-model:value="deeplAuthKey"> + <FormInput v-model="deeplAuthKey"> <template #prefix><i class="fas fa-key"></i></template> DeepL Auth Key </FormInput> - <FormSwitch v-model:value="deeplIsPro"> + <FormSwitch v-model="deeplIsPro"> Pro account </FormSwitch> </FormGroup> @@ -23,12 +23,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -49,7 +49,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.other, - icon: 'fas fa-cogs' + icon: 'fas fa-cogs', + bg: 'var(--bg)', }, summalyProxy: '', deeplAuthKey: '', diff --git a/src/client/pages/instance/overview.vue b/src/client/pages/instance/overview.vue index 0d7a5d1501..c6db9d0c04 100644 --- a/src/client/pages/instance/overview.vue +++ b/src/client/pages/instance/overview.vue @@ -1,9 +1,6 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormInfo v-if="noMaintainerInformation" warn>{{ $ts.noMaintainerInformationWarning }} <MkA to="/instance/settings" class="_link">{{ $ts.configure }}</MkA></FormInfo> - <FormInfo v-if="noBotProtection" warn>{{ $ts.noBotProtectionWarning }} <MkA to="/instance/bot-protection" class="_link">{{ $ts.configure }}</MkA></FormInfo> - <FormSuspense :p="fetchStats" v-slot="{ result: stats }"> <FormGroup> <FormKeyValueView> @@ -17,8 +14,8 @@ </FormGroup> </FormSuspense> - <div class="_formItem"> - <div class="_formPanel"> + <div class="_debobigegoItem"> + <div class="_debobigegoPanel"> <MkInstanceStats :chart-limit="300" :detailed="true"/> </div> </div> @@ -47,18 +44,18 @@ <script lang="ts"> import { computed, defineComponent, markRaw } from 'vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import MkInstanceStats from '@client/components/instance-stats.vue'; import MkButton from '@client/components/ui/button.vue'; -import MkSelect from '@client/components/ui/select.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkSelect from '@client/components/form/select.vue'; +import MkInput from '@client/components/form/input.vue'; import MkContainer from '@client/components/ui/container.vue'; import MkFolder from '@client/components/ui/folder.vue'; import { version, url } from '@client/config'; @@ -86,7 +83,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.overview, - icon: 'fas fa-tachometer-alt' + icon: 'fas fa-tachometer-alt', + bg: 'var(--bg)', }, page: 'index', version, @@ -97,8 +95,6 @@ export default defineComponent({ fetchServerInfo: () => os.api('admin/server-info', {}), fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), fetchModLogs: () => os.api('admin/show-moderation-logs', {}), - noMaintainerInformation: false, - noBotProtection: false, } }, @@ -109,11 +105,6 @@ export default defineComponent({ methods: { async init() { this.meta = await os.api('meta', { detail: true }); - - const isEmpty = (x: any) => x == null || x == ''; - - this.noMaintainerInformation = isEmpty(this.meta.maintainerName) || isEmpty(this.meta.maintainerEmail); - this.noBotProtection = !this.meta.enableHcaptcha && !this.meta.enableRecaptcha; }, async showInstanceInfo(q) { diff --git a/src/client/pages/instance/proxy-account.vue b/src/client/pages/instance/proxy-account.vue index 3e2df8dcb4..b1ece19710 100644 --- a/src/client/pages/instance/proxy-account.vue +++ b/src/client/pages/instance/proxy-account.vue @@ -16,14 +16,14 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -46,7 +46,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.proxyAccount, - icon: 'fas fa-ghost' + icon: 'fas fa-ghost', + bg: 'var(--bg)', }, proxyAccount: null, proxyAccountId: null, diff --git a/src/client/pages/instance/queue.chart.vue b/src/client/pages/instance/queue.chart.vue index 53d790598a..887fe9a574 100644 --- a/src/client/pages/instance/queue.chart.vue +++ b/src/client/pages/instance/queue.chart.vue @@ -1,7 +1,7 @@ <template> -<div class="_formItem"> - <div class="_formLabel"><slot name="title"></slot></div> - <div class="_formPanel pumxzjhg"> +<div class="_debobigegoItem"> + <div class="_debobigegoLabel"><slot name="title"></slot></div> + <div class="_debobigegoPanel pumxzjhg"> <div class="_table status"> <div class="_row"> <div class="_cell"><div class="_label">Process</div>{{ number(activeSincePrevTick) }}</div> diff --git a/src/client/pages/instance/queue.vue b/src/client/pages/instance/queue.vue index e8ec0bc97d..f88825eb19 100644 --- a/src/client/pages/instance/queue.vue +++ b/src/client/pages/instance/queue.vue @@ -14,8 +14,8 @@ import { defineComponent, markRaw } from 'vue'; import MkButton from '@client/components/ui/button.vue'; import XQueue from './queue.chart.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -34,6 +34,7 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.jobQueue, icon: 'fas fa-clipboard-list', + bg: 'var(--bg)', }, connection: markRaw(os.stream.useChannel('queueStats')), } diff --git a/src/client/pages/instance/relays.vue b/src/client/pages/instance/relays.vue index a3e4e7d1da..7d7888eaa8 100644 --- a/src/client/pages/instance/relays.vue +++ b/src/client/pages/instance/relays.vue @@ -2,8 +2,8 @@ <FormBase class="relaycxt"> <FormButton @click="addRelay" primary><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton> - <div class="_formItem" v-for="relay in relays" :key="relay.inbox"> - <div class="_formPanel" style="padding: 16px;"> + <div class="_debobigegoItem" v-for="relay in relays" :key="relay.inbox"> + <div class="_debobigegoPanel" style="padding: 16px;"> <div>{{ relay.inbox }}</div> <div>{{ $t(`_relayStatus.${relay.status}`) }}</div> <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> @@ -15,9 +15,9 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormButton from '@client/components/form/button.vue'; +import MkInput from '@client/components/form/input.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -36,6 +36,7 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.relays, icon: 'fas fa-globe', + bg: 'var(--bg)', }, relays: [], inbox: '', diff --git a/src/client/pages/instance/security.vue b/src/client/pages/instance/security.vue index e3397a113b..a854b6dbd0 100644 --- a/src/client/pages/instance/security.vue +++ b/src/client/pages/instance/security.vue @@ -8,7 +8,9 @@ <template #suffix v-else>{{ $ts.none }} ({{ $ts.notRecommended }})</template> </FormLink> - <FormSwitch v-model:value="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch> + <FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch> + + <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch> <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> </FormSuspense> @@ -17,13 +19,13 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormLink from '@client/components/form/link.vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -45,11 +47,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.security, - icon: 'fas fa-lock' + icon: 'fas fa-lock', + bg: 'var(--bg)', }, enableHcaptcha: false, enableRecaptcha: false, enableRegistration: false, + emailRequiredForSignup: false, } }, @@ -63,11 +67,13 @@ export default defineComponent({ this.enableHcaptcha = meta.enableHcaptcha; this.enableRecaptcha = meta.enableRecaptcha; this.enableRegistration = !meta.disableRegistration; + this.emailRequiredForSignup = meta.emailRequiredForSignup; }, save() { os.apiWithDialog('admin/update-meta', { disableRegistration: !this.enableRegistration, + emailRequiredForSignup: this.emailRequiredForSignup, }).then(() => { fetchInstance(); }); diff --git a/src/client/pages/instance/service-worker.vue b/src/client/pages/instance/service-worker.vue index a52932bb75..430e02ad2e 100644 --- a/src/client/pages/instance/service-worker.vue +++ b/src/client/pages/instance/service-worker.vue @@ -1,18 +1,18 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormSwitch v-model:value="enableServiceWorker"> + <FormSwitch v-model="enableServiceWorker"> {{ $ts.enableServiceworker }} <template #desc>{{ $ts.serviceworkerInfo }}</template> </FormSwitch> <template v-if="enableServiceWorker"> - <FormInput v-model:value="swPublicKey"> + <FormInput v-model="swPublicKey"> <template #prefix><i class="fas fa-key"></i></template> Public key </FormInput> - <FormInput v-model:value="swPrivateKey"> + <FormInput v-model="swPrivateKey"> <template #prefix><i class="fas fa-key"></i></template> Private key </FormInput> @@ -25,12 +25,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -51,7 +51,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: 'ServiceWorker', - icon: 'fas fa-bolt' + icon: 'fas fa-bolt', + bg: 'var(--bg)', }, enableServiceWorker: false, swPublicKey: null, diff --git a/src/client/pages/instance/settings.vue b/src/client/pages/instance/settings.vue index b68d784897..7bd363e5f3 100644 --- a/src/client/pages/instance/settings.vue +++ b/src/client/pages/instance/settings.vue @@ -1,50 +1,55 @@ <template> <FormBase> <FormSuspense :p="init"> - <FormInput v-model:value="name"> + <FormInput v-model="name"> <span>{{ $ts.instanceName }}</span> </FormInput> - <FormTextarea v-model:value="description"> + <FormTextarea v-model="description"> <span>{{ $ts.instanceDescription }}</span> </FormTextarea> - <FormInput v-model:value="iconUrl"> + <FormInput v-model="iconUrl"> <template #prefix><i class="fas fa-link"></i></template> <span>{{ $ts.iconUrl }}</span> </FormInput> - <FormInput v-model:value="bannerUrl"> + <FormInput v-model="bannerUrl"> <template #prefix><i class="fas fa-link"></i></template> <span>{{ $ts.bannerUrl }}</span> </FormInput> - <FormInput v-model:value="backgroundImageUrl"> + <FormInput v-model="backgroundImageUrl"> <template #prefix><i class="fas fa-link"></i></template> <span>{{ $ts.backgroundImageUrl }}</span> </FormInput> - <FormInput v-model:value="tosUrl"> + <FormInput v-model="tosUrl"> <template #prefix><i class="fas fa-link"></i></template> <span>{{ $ts.tosUrl }}</span> </FormInput> - <FormInput v-model:value="maintainerName"> + <FormInput v-model="maintainerName"> <span>{{ $ts.maintainerName }}</span> </FormInput> - <FormInput v-model:value="maintainerEmail" type="email"> + <FormInput v-model="maintainerEmail" type="email"> <template #prefix><i class="fas fa-envelope"></i></template> <span>{{ $ts.maintainerEmail }}</span> </FormInput> - <FormInput v-model:value="maxNoteTextLength" type="number"> + <FormTextarea v-model="pinnedUsers"> + <span>{{ $ts.pinnedUsers }}</span> + <template #desc>{{ $ts.pinnedUsersDescription }}</template> + </FormTextarea> + + <FormInput v-model="maxNoteTextLength" type="number"> <template #prefix><i class="fas fa-pencil-alt"></i></template> <span>{{ $ts.maxNoteTextLength }}</span> </FormInput> - <FormSwitch v-model:value="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch> - <FormSwitch v-model:value="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch> + <FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch> + <FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch> <FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo> <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> @@ -54,14 +59,14 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { fetchInstance } from '@client/instance'; @@ -84,7 +89,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.general, - icon: 'fas fa-cog' + icon: 'fas fa-cog', + bg: 'var(--bg)', }, name: null, description: null, @@ -97,6 +103,7 @@ export default defineComponent({ maxNoteTextLength: 0, enableLocalTimeline: false, enableGlobalTimeline: false, + pinnedUsers: '', } }, @@ -118,6 +125,7 @@ export default defineComponent({ this.maxNoteTextLength = meta.maxNoteTextLength; this.enableLocalTimeline = !meta.disableLocalTimeline; this.enableGlobalTimeline = !meta.disableGlobalTimeline; + this.pinnedUsers = meta.pinnedUsers.join('\n'); }, save() { @@ -133,6 +141,7 @@ export default defineComponent({ maxNoteTextLength: this.maxNoteTextLength, disableLocalTimeline: !this.enableLocalTimeline, disableGlobalTimeline: !this.enableGlobalTimeline, + pinnedUsers: this.pinnedUsers.split('\n'), }).then(() => { fetchInstance(); }); diff --git a/src/client/pages/instance/users.vue b/src/client/pages/instance/users.vue index 8db62683ba..f7f9306b70 100644 --- a/src/client/pages/instance/users.vue +++ b/src/client/pages/instance/users.vue @@ -1,20 +1,17 @@ <template> <div class="lknzcolw"> - <div class="actions"> - <MkButton inline primary @click="addUser()"><i class="fas fa-plus"></i> {{ $ts.addUser }}</MkButton> - <MkButton inline primary @click="lookupUser()"><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton> - </div> + <MkHeader :info="header"/> <div class="users"> - <div class="inputs" style="display: flex;"> - <MkSelect v-model="sort" style="margin: 0; flex: 1;"> + <div class="inputs"> + <MkSelect v-model="sort" style="flex: 1;"> <template #label>{{ $ts.sort }}</template> <option value="-createdAt">{{ $ts.registeredDate }} ({{ $ts.ascendingOrder }})</option> <option value="+createdAt">{{ $ts.registeredDate }} ({{ $ts.descendingOrder }})</option> <option value="-updatedAt">{{ $ts.lastUsed }} ({{ $ts.ascendingOrder }})</option> <option value="+updatedAt">{{ $ts.lastUsed }} ({{ $ts.descendingOrder }})</option> </MkSelect> - <MkSelect v-model="state" style="margin: 0; flex: 1;"> + <MkSelect v-model="state" style="flex: 1;"> <template #label>{{ $ts.state }}</template> <option value="all">{{ $ts.all }}</option> <option value="available">{{ $ts.normal }}</option> @@ -23,18 +20,20 @@ <option value="silenced">{{ $ts.silence }}</option> <option value="suspended">{{ $ts.suspend }}</option> </MkSelect> - <MkSelect v-model="origin" style="margin: 0; flex: 1;"> + <MkSelect v-model="origin" style="flex: 1;"> <template #label>{{ $ts.instance }}</template> <option value="combined">{{ $ts.all }}</option> <option value="local">{{ $ts.local }}</option> <option value="remote">{{ $ts.remote }}</option> </MkSelect> </div> - <div class="inputs" style="display: flex; padding-top: 1.2em;"> - <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()"> + <div class="inputs"> + <MkInput v-model="searchUsername" style="flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()"> + <template #prefix>@</template> <template #label>{{ $ts.username }}</template> </MkInput> - <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()" :disabled="pagination.params().origin === 'local'"> + <template #prefix>@</template> <template #label>{{ $ts.host }}</template> </MkInput> </div> @@ -67,8 +66,8 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSelect from '@client/components/form/select.vue'; import MkPagination from '@client/components/ui/pagination.vue'; import { acct } from '@client/filters/user'; import * as os from '@client/os'; @@ -90,10 +89,27 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.users, icon: 'fas fa-users', - action: { + bg: 'var(--bg)', + }, + header: { + title: this.$ts.users, + icon: 'fas fa-users', + bg: 'var(--bg)', + actions: [{ icon: 'fas fa-search', + text: this.$ts.search, handler: this.searchUser - } + }, { + asFullButton: true, + icon: 'fas fa-plus', + text: this.$ts.addUser, + handler: this.addUser + }, { + asFullButton: true, + icon: 'fas fa-search', + text: this.$ts.lookup, + handler: this.lookupUser + }] }, sort: '+createdAt', state: 'all', @@ -172,12 +188,21 @@ export default defineComponent({ <style lang="scss" scoped> .lknzcolw { - > .actions { - margin: var(--margin); - } - > .users { margin: var(--margin); + + > .inputs { + display: flex; + margin-bottom: 16px; + + > * { + margin-right: 16px; + + &:last-child { + margin-right: 0; + } + } + } > .users { margin-top: var(--margin); diff --git a/src/client/pages/mentions.vue b/src/client/pages/mentions.vue index 798d3e342d..e1d2f096e1 100644 --- a/src/client/pages/mentions.vue +++ b/src/client/pages/mentions.vue @@ -1,6 +1,9 @@ <template> -<div class="_section"> - <XNotes class="_content" :pagination="pagination" @before="before()" @after="after()"/> +<div> + <MkHeader :info="header"/> + <div class="_section"> + <XNotes class="_content" :pagination="pagination" @before="before()" @after="after()"/> + </div> </div> </template> @@ -19,7 +22,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.mentions, - icon: 'fas fa-at' + icon: 'fas fa-at', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.mentions, + icon: 'fas fa-at', + bg: 'var(--bg)', }, pagination: { endpoint: 'notes/mentions', diff --git a/src/client/pages/messages.vue b/src/client/pages/messages.vue index 6ac9746d4e..f4c68daab9 100644 --- a/src/client/pages/messages.vue +++ b/src/client/pages/messages.vue @@ -1,6 +1,9 @@ <template> <div> - <XNotes :pagination="pagination" @before="before()" @after="after()"/> + <MkHeader :info="header"/> + <div> + <XNotes :pagination="pagination" @before="before()" @after="after()"/> + </div> </div> </template> @@ -19,7 +22,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.directNotes, - icon: 'fas fa-envelope' + icon: 'fas fa-envelope', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.directNotes, + icon: 'fas fa-envelope', + bg: 'var(--bg)', }, pagination: { endpoint: 'notes/mentions', diff --git a/src/client/pages/messaging/index.vue b/src/client/pages/messaging/index.vue index 1e0d4dc64c..fef3b76e10 100644 --- a/src/client/pages/messaging/index.vue +++ b/src/client/pages/messaging/index.vue @@ -1,38 +1,42 @@ <template> -<div class="yweeujhr _root" v-size="{ max: [400] }"> - <MkButton @click="start" primary class="start"><i class="fas fa-plus"></i> {{ $ts.startMessaging }}</MkButton> +<div> + <MkHeader :info="header"/> - <div class="history" v-if="messages.length > 0"> - <MkA v-for="(message, i) in messages" - class="message _block" - :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }" - :to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`" - :data-index="i" - :key="message.id" - v-anim="i" - > - <div> - <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" :show-indicator="true"/> - <header v-if="message.groupId"> - <span class="name">{{ message.group.name }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <header v-else> - <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span> - <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <div class="body"> - <p class="text"><span class="me" v-if="isMe(message)">{{ $ts.you }}:</span>{{ message.text }}</p> + <div class="yweeujhr _root" v-size="{ max: [400] }"> + <MkButton @click="start" primary class="start"><i class="fas fa-plus"></i> {{ $ts.startMessaging }}</MkButton> + + <div class="history" v-if="messages.length > 0"> + <MkA v-for="(message, i) in messages" + class="message _block" + :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }" + :to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`" + :data-index="i" + :key="message.id" + v-anim="i" + > + <div> + <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" :show-indicator="true"/> + <header v-if="message.groupId"> + <span class="name">{{ message.group.name }}</span> + <MkTime :time="message.createdAt" class="time"/> + </header> + <header v-else> + <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span> + <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span> + <MkTime :time="message.createdAt" class="time"/> + </header> + <div class="body"> + <p class="text"><span class="me" v-if="isMe(message)">{{ $ts.you }}:</span>{{ message.text }}</p> + </div> </div> - </div> - </MkA> - </div> - <div class="_fullinfo" v-if="!fetching && messages.length == 0"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noHistory }}</div> + </MkA> + </div> + <div class="_fullinfo" v-if="!fetching && messages.length == 0"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noHistory }}</div> + </div> + <MkLoading v-if="fetching"/> </div> - <MkLoading v-if="fetching"/> </div> </template> @@ -53,7 +57,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.messaging, - icon: 'fas fa-comments' + icon: 'fas fa-comments', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.messaging, + icon: 'fas fa-comments', + bg: 'var(--bg)', }, fetching: true, moreFetching: false, diff --git a/src/client/pages/messaging/messaging-room.message.vue b/src/client/pages/messaging/messaging-room.message.vue index dfac83ad6a..a2740c0bdc 100644 --- a/src/client/pages/messaging/messaging-room.message.vue +++ b/src/client/pages/messaging/messaging-room.message.vue @@ -302,7 +302,7 @@ export default defineComponent({ > .text { &, ::v-deep(*) { - color: #fff !important; + color: var(--fgOnAccent) !important; } } } diff --git a/src/client/pages/messaging/messaging-room.vue b/src/client/pages/messaging/messaging-room.vue index b6a2fbd3d4..76e58d5bc9 100644 --- a/src/client/pages/messaging/messaging-room.vue +++ b/src/client/pages/messaging/messaging-room.vue @@ -284,7 +284,7 @@ const Component = defineComponent({ }, scrollToBottom() { - scroll(this.$el, this.$el.offsetHeight); + scroll(this.$el, { top: this.$el.offsetHeight }); }, onIndicatorClick() { diff --git a/src/client/pages/mfm-cheat-sheet.vue b/src/client/pages/mfm-cheat-sheet.vue index 314b5e2a5f..5ff4317627 100644 --- a/src/client/pages/mfm-cheat-sheet.vue +++ b/src/client/pages/mfm-cheat-sheet.vue @@ -286,7 +286,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; import * as symbols from '@client/symbols'; export default defineComponent({ diff --git a/src/client/pages/my-antennas/editor.vue b/src/client/pages/my-antennas/editor.vue index 882d48e643..93ab640030 100644 --- a/src/client/pages/my-antennas/editor.vue +++ b/src/client/pages/my-antennas/editor.vue @@ -1,10 +1,10 @@ <template> <div class="shaynizk"> <div class="form"> - <MkInput v-model="name" class="_inputNoTopMargin"> + <MkInput v-model="name" class="_formBlock"> <template #label>{{ $ts.name }}</template> </MkInput> - <MkSelect v-model="src"> + <MkSelect v-model="src" class="_formBlock"> <template #label>{{ $ts.antennaSource }}</template> <option value="all">{{ $ts._antennaSources.all }}</option> <option value="home">{{ $ts._antennaSources.homeTimeline }}</option> @@ -12,30 +12,30 @@ <option value="list">{{ $ts._antennaSources.userList }}</option> <option value="group">{{ $ts._antennaSources.userGroup }}</option> </MkSelect> - <MkSelect v-model="userListId" v-if="src === 'list'"> + <MkSelect v-model="userListId" v-if="src === 'list'" class="_formBlock"> <template #label>{{ $ts.userList }}</template> <option v-for="list in userLists" :value="list.id" :key="list.id">{{ list.name }}</option> </MkSelect> - <MkSelect v-model="userGroupId" v-else-if="src === 'group'"> + <MkSelect v-model="userGroupId" v-else-if="src === 'group'" class="_formBlock"> <template #label>{{ $ts.userGroup }}</template> <option v-for="group in userGroups" :value="group.id" :key="group.id">{{ group.name }}</option> </MkSelect> - <MkTextarea v-model="users" v-else-if="src === 'users'"> + <MkTextarea v-model="users" v-else-if="src === 'users'" class="_formBlock"> <template #label>{{ $ts.users }}</template> <template #caption>{{ $ts.antennaUsersDescription }} <button class="_textButton" @click="addUser">{{ $ts.addUser }}</button></template> </MkTextarea> - <MkSwitch v-model="withReplies">{{ $ts.withReplies }}</MkSwitch> - <MkTextarea v-model="keywords"> + <MkSwitch v-model="withReplies" class="_formBlock">{{ $ts.withReplies }}</MkSwitch> + <MkTextarea v-model="keywords" class="_formBlock"> <template #label>{{ $ts.antennaKeywords }}</template> <template #caption>{{ $ts.antennaKeywordsDescription }}</template> </MkTextarea> - <MkTextarea v-model="excludeKeywords"> + <MkTextarea v-model="excludeKeywords" class="_formBlock"> <template #label>{{ $ts.antennaExcludeKeywords }}</template> <template #caption>{{ $ts.antennaKeywordsDescription }}</template> </MkTextarea> - <MkSwitch v-model="caseSensitive">{{ $ts.caseSensitive }}</MkSwitch> - <MkSwitch v-model="withFile">{{ $ts.withFileAntenna }}</MkSwitch> - <MkSwitch v-model="notify">{{ $ts.notifyAntenna }}</MkSwitch> + <MkSwitch v-model="caseSensitive" class="_formBlock">{{ $ts.caseSensitive }}</MkSwitch> + <MkSwitch v-model="withFile" class="_formBlock">{{ $ts.withFileAntenna }}</MkSwitch> + <MkSwitch v-model="notify" class="_formBlock">{{ $ts.notifyAntenna }}</MkSwitch> </div> <div class="actions"> <MkButton inline @click="saveAntenna()" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> @@ -47,10 +47,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -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 MkInput from '@client/components/form/input.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkSelect from '@client/components/form/select.vue'; +import MkSwitch from '@client/components/form/switch.vue'; import { getAcct } from '@/misc/acct'; import * as os from '@client/os'; diff --git a/src/client/pages/my-groups/index.vue b/src/client/pages/my-groups/index.vue index 9f153ff9cc..34f82f8a71 100644 --- a/src/client/pages/my-groups/index.vue +++ b/src/client/pages/my-groups/index.vue @@ -1,7 +1,7 @@ <template> <div class=""> <div class="_section" style="padding: 0;"> - <MkTab v-model:value="tab"> + <MkTab v-model="tab"> <option value="owned">{{ $ts.ownedGroups }}</option> <option value="joined">{{ $ts.joinedGroups }}</option> <option value="invites"><i class="fas fa-envelope-open-text"></i> {{ $ts.invites }}</option> diff --git a/src/client/pages/my-lists/index.vue b/src/client/pages/my-lists/index.vue index 7de31bb308..b0e9bf9d54 100644 --- a/src/client/pages/my-lists/index.vue +++ b/src/client/pages/my-lists/index.vue @@ -1,13 +1,16 @@ <template> -<div class="qkcjvfiv"> - <MkButton @click="create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton> +<div> + <MkHeader :info="header"/> + <div class="qkcjvfiv"> + <MkButton @click="create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton> - <MkPagination :pagination="pagination" #default="{items}" class="lists _content" ref="list"> - <MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`"> - <div class="name">{{ list.name }}</div> - <MkAvatars :user-ids="list.userIds"/> - </MkA> - </MkPagination> + <MkPagination :pagination="pagination" #default="{items}" class="lists _content" ref="list"> + <MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`"> + <div class="name">{{ list.name }}</div> + <MkAvatars :user-ids="list.userIds"/> + </MkA> + </MkPagination> + </div> </div> </template> @@ -31,6 +34,12 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.manageLists, icon: 'fas fa-list-ul', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.manageLists, + icon: 'fas fa-list-ul', + bg: 'var(--bg)', action: { icon: 'fas fa-plus', handler: this.create diff --git a/src/client/pages/my-lists/list.vue b/src/client/pages/my-lists/list.vue index 049d370b4e..27c979bc88 100644 --- a/src/client/pages/my-lists/list.vue +++ b/src/client/pages/my-lists/list.vue @@ -1,34 +1,37 @@ <template> -<div class="mk-list-page"> - <transition name="zoom" mode="out-in"> - <div v-if="list" class="_section"> - <div class="_content"> - <MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton> - <MkButton inline @click="renameList()">{{ $ts.rename }}</MkButton> - <MkButton inline @click="deleteList()">{{ $ts.delete }}</MkButton> +<div> + <MkHeader v-if="header" :info="header"/> + <div class="mk-list-page"> + <transition name="zoom" mode="out-in"> + <div v-if="list" class="_section"> + <div class="_content"> + <MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton> + <MkButton inline @click="renameList()">{{ $ts.rename }}</MkButton> + <MkButton inline @click="deleteList()">{{ $ts.delete }}</MkButton> + </div> </div> - </div> - </transition> + </transition> - <transition name="zoom" mode="out-in"> - <div v-if="list" class="_section members _gap"> - <div class="_title">{{ $ts.members }}</div> - <div class="_content"> - <div class="users"> - <div class="user _panel" v-for="user in users" :key="user.id"> - <MkAvatar :user="user" class="avatar" :show-indicator="true"/> - <div class="body"> - <MkUserName :user="user" class="name"/> - <MkAcct :user="user" class="acct"/> - </div> - <div class="action"> - <button class="_button" @click="removeUser(user)"><i class="fas fa-times"></i></button> + <transition name="zoom" mode="out-in"> + <div v-if="list" class="_section members _gap"> + <div class="_title">{{ $ts.members }}</div> + <div class="_content"> + <div class="users"> + <div class="user _panel" v-for="user in users" :key="user.id"> + <MkAvatar :user="user" class="avatar" :show-indicator="true"/> + <div class="body"> + <MkUserName :user="user" class="name"/> + <MkAcct :user="user" class="acct"/> + </div> + <div class="action"> + <button class="_button" @click="removeUser(user)"><i class="fas fa-times"></i></button> + </div> </div> </div> </div> </div> - </div> - </transition> + </transition> + </div> </div> </template> @@ -50,6 +53,10 @@ export default defineComponent({ title: this.list.name, icon: 'fas fa-list-ul', } : null), + header: computed(() => this.list ? { + title: this.list.name, + icon: 'fas fa-list-ul', + } : null), list: null, users: [], }; diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue index 06f8ad3cba..049d057d02 100644 --- a/src/client/pages/notifications.vue +++ b/src/client/pages/notifications.vue @@ -1,15 +1,21 @@ <template> -<div class="clupoqwt" v-size="{ min: [800] }"> - <XNotifications class="notifications" @before="before" @after="after" page/> +<div> + <MkHeader :info="header"/> + <MkSpacer :content-max="800"> + <div class="clupoqwt"> + <XNotifications class="notifications" @before="before" @after="after" :include-types="includeTypes" :unread-only="tab === 'unread'"/> + </div> + </MkSpacer> </div> </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import Progress from '@client/scripts/loading'; import XNotifications from '@client/components/notifications.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; +import { notificationTypes } from '@/types'; export default defineComponent({ components: { @@ -22,14 +28,35 @@ export default defineComponent({ title: this.$ts.notifications, icon: 'fas fa-bell', bg: 'var(--bg)', + }, + tab: 'all', + includeTypes: null, + header: computed(() => ({ + title: this.$ts.notifications, + icon: 'fas fa-bell', + bg: 'var(--bg)', actions: [{ + text: this.$ts.filter, + icon: 'fas fa-filter', + highlighted: this.includeTypes != null, + handler: this.setFilter, + }, { text: this.$ts.markAllAsRead, icon: 'fas fa-check', handler: () => { os.apiWithDialog('notifications/mark-all-as-read'); - } - }] - }, + }, + }], + tabs: [{ + active: this.tab === 'all', + title: this.$ts.all, + onClick: () => { this.tab = 'all'; }, + }, { + active: this.tab === 'unread', + title: this.$ts.unread, + onClick: () => { this.tab = 'unread'; }, + },] + })), }; }, @@ -40,6 +67,24 @@ export default defineComponent({ after() { Progress.done(); + }, + + setFilter(ev) { + const typeItems = notificationTypes.map(t => ({ + text: this.$t(`_notification._types.${t}`), + active: this.includeTypes && this.includeTypes.includes(t), + action: () => { + this.includeTypes = [t]; + } + })); + const items = this.includeTypes != null ? [{ + icon: 'fas fa-times', + text: this.$ts.clear, + action: () => { + this.includeTypes = null; + } + }, null, ...typeItems] : typeItems; + os.popupMenu(items, ev.currentTarget || ev.target); } } }); @@ -47,14 +92,5 @@ export default defineComponent({ <style lang="scss" scoped> .clupoqwt { - &.min-width_800px { - background: var(--bg); - padding: 32px 0; - - > .notifications { - max-width: 800px; - margin: 0 auto; - } - } } </style> diff --git a/src/client/pages/page-editor/els/page-editor.el.button.vue b/src/client/pages/page-editor/els/page-editor.el.button.vue index 3a43817cf6..85e9d7e711 100644 --- a/src/client/pages/page-editor/els/page-editor.el.button.vue +++ b/src/client/pages/page-editor/els/page-editor.el.button.vue @@ -40,9 +40,9 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkSelect from '@client/components/ui/select.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; +import MkSelect from '@client/components/form/select.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSwitch from '@client/components/form/switch.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.canvas.vue b/src/client/pages/page-editor/els/page-editor.el.canvas.vue index d8d5b990ca..c40d69a7c1 100644 --- a/src/client/pages/page-editor/els/page-editor.el.canvas.vue +++ b/src/client/pages/page-editor/els/page-editor.el.canvas.vue @@ -22,7 +22,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.counter.vue b/src/client/pages/page-editor/els/page-editor.el.counter.vue index 973de50fc2..de7994e3ba 100644 --- a/src/client/pages/page-editor/els/page-editor.el.counter.vue +++ b/src/client/pages/page-editor/els/page-editor.el.counter.vue @@ -20,7 +20,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.if.vue b/src/client/pages/page-editor/els/page-editor.el.if.vue index 6eb0c7709f..52f4dac22e 100644 --- a/src/client/pages/page-editor/els/page-editor.el.if.vue +++ b/src/client/pages/page-editor/els/page-editor.el.if.vue @@ -19,7 +19,7 @@ </optgroup> </MkSelect> - <XBlocks class="children" v-model:value="value.children" :hpml="hpml"/> + <XBlocks class="children" v-model="value.children" :hpml="hpml"/> </section> </XContainer> </template> @@ -28,7 +28,7 @@ import { defineComponent, defineAsyncComponent } from 'vue'; import { v4 as uuid } from 'uuid'; import XContainer from '../page-editor.container.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkSelect from '@client/components/form/select.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.note.vue b/src/client/pages/page-editor/els/page-editor.el.note.vue index 5766564c1a..9feec395b7 100644 --- a/src/client/pages/page-editor/els/page-editor.el.note.vue +++ b/src/client/pages/page-editor/els/page-editor.el.note.vue @@ -18,8 +18,8 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSwitch from '@client/components/form/switch.vue'; import XNote from '@client/components/note.vue'; import XNoteDetailed from '@client/components/note-detailed.vue'; import * as os from '@client/os'; diff --git a/src/client/pages/page-editor/els/page-editor.el.number-input.vue b/src/client/pages/page-editor/els/page-editor.el.number-input.vue index 892e7e1caa..57b1397824 100644 --- a/src/client/pages/page-editor/els/page-editor.el.number-input.vue +++ b/src/client/pages/page-editor/els/page-editor.el.number-input.vue @@ -20,7 +20,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.post.vue b/src/client/pages/page-editor/els/page-editor.el.post.vue index 4215b159d3..e21ccfd345 100644 --- a/src/client/pages/page-editor/els/page-editor.el.post.vue +++ b/src/client/pages/page-editor/els/page-editor.el.post.vue @@ -13,9 +13,9 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSwitch from '@client/components/form/switch.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.radio-button.vue b/src/client/pages/page-editor/els/page-editor.el.radio-button.vue index 88be96f35d..62fb231f79 100644 --- a/src/client/pages/page-editor/els/page-editor.el.radio-button.vue +++ b/src/client/pages/page-editor/els/page-editor.el.radio-button.vue @@ -14,8 +14,8 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.section.vue b/src/client/pages/page-editor/els/page-editor.el.section.vue index 16ef2598f9..75bdf120c0 100644 --- a/src/client/pages/page-editor/els/page-editor.el.section.vue +++ b/src/client/pages/page-editor/els/page-editor.el.section.vue @@ -11,7 +11,7 @@ </template> <section class="ilrvjyvi"> - <XBlocks class="children" v-model:value="value.children" :hpml="hpml"/> + <XBlocks class="children" v-model="value.children" :hpml="hpml"/> </section> </XContainer> </template> diff --git a/src/client/pages/page-editor/els/page-editor.el.switch.vue b/src/client/pages/page-editor/els/page-editor.el.switch.vue index ade1291410..cf15f58c82 100644 --- a/src/client/pages/page-editor/els/page-editor.el.switch.vue +++ b/src/client/pages/page-editor/els/page-editor.el.switch.vue @@ -13,8 +13,8 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkSwitch from '@client/components/form/switch.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.text-input.vue b/src/client/pages/page-editor/els/page-editor.el.text-input.vue index 3c8fcc04af..210199befd 100644 --- a/src/client/pages/page-editor/els/page-editor.el.text-input.vue +++ b/src/client/pages/page-editor/els/page-editor.el.text-input.vue @@ -13,7 +13,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue b/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue index a4fbb08ffe..14f36db2a1 100644 --- a/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue +++ b/src/client/pages/page-editor/els/page-editor.el.textarea-input.vue @@ -13,8 +13,8 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XContainer from '../page-editor.container.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkInput from '@client/components/form/input.vue'; import * as os from '@client/os'; export default defineComponent({ diff --git a/src/client/pages/page-editor/page-editor.blocks.vue b/src/client/pages/page-editor/page-editor.blocks.vue index 0065b16c8c..c27162a26e 100644 --- a/src/client/pages/page-editor/page-editor.blocks.vue +++ b/src/client/pages/page-editor/page-editor.blocks.vue @@ -32,7 +32,7 @@ export default defineComponent({ }, props: { - value: { + modelValue: { type: Array, required: true }, @@ -41,15 +41,15 @@ export default defineComponent({ }, }, - emits: ['update:value'], + emits: ['update:modelValue'], computed: { blocks: { get() { - return this.value; + return this.modelValue; }, set(value) { - this.$emit('update:value', value); + this.$emit('update:modelValue', value); } } }, @@ -62,17 +62,16 @@ export default defineComponent({ v, ...this.blocks.slice(i + 1) ]; - this.$emit('update:value', newValue); + this.$emit('update:modelValue', newValue); }, removeItem(el) { - console.log(el); const i = this.blocks.findIndex(x => x.id === el.id); const newValue = [ ...this.blocks.slice(0, i), ...this.blocks.slice(i + 1) ]; - this.$emit('update:value', newValue); + this.$emit('update:modelValue', newValue); }, } }); diff --git a/src/client/pages/page-editor/page-editor.script-block.vue b/src/client/pages/page-editor/page-editor.script-block.vue index fedcd7b317..3313fc1ba9 100644 --- a/src/client/pages/page-editor/page-editor.script-block.vue +++ b/src/client/pages/page-editor/page-editor.script-block.vue @@ -7,23 +7,23 @@ </button> </template> - <section v-if="value.type === null" class="pbglfege" @click="changeType()"> + <section v-if="modelValue.type === null" class="pbglfege" @click="changeType()"> {{ $ts._pages.script.emptySlot }} </section> - <section v-else-if="value.type === 'text'" class="tbwccoaw"> - <input v-model="value.value"/> + <section v-else-if="modelValue.type === 'text'" class="tbwccoaw"> + <input v-model="modelValue.value"/> </section> - <section v-else-if="value.type === 'multiLineText'" class="tbwccoaw"> - <textarea v-model="value.value"></textarea> + <section v-else-if="modelValue.type === 'multiLineText'" class="tbwccoaw"> + <textarea v-model="modelValue.value"></textarea> </section> - <section v-else-if="value.type === 'textList'" class="tbwccoaw"> - <textarea v-model="value.value" :placeholder="$ts._pages.script.blocks._textList.info"></textarea> + <section v-else-if="modelValue.type === 'textList'" class="tbwccoaw"> + <textarea v-model="modelValue.value" :placeholder="$ts._pages.script.blocks._textList.info"></textarea> </section> - <section v-else-if="value.type === 'number'" class="tbwccoaw"> - <input v-model="value.value" type="number"/> + <section v-else-if="modelValue.type === 'number'" class="tbwccoaw"> + <input v-model="modelValue.value" type="number"/> </section> - <section v-else-if="value.type === 'ref'" class="hpdwcrvs"> - <select v-model="value.value"> + <section v-else-if="modelValue.type === 'ref'" class="hpdwcrvs"> + <select v-model="modelValue.value"> <option v-for="v in hpml.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option> <optgroup :label="$ts._pages.script.argVariables"> <option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option> @@ -36,21 +36,21 @@ </optgroup> </select> </section> - <section v-else-if="value.type === 'aiScriptVar'" class="tbwccoaw"> - <input v-model="value.value"/> + <section v-else-if="modelValue.type === 'aiScriptVar'" class="tbwccoaw"> + <input v-model="modelValue.value"/> </section> - <section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;"> + <section v-else-if="modelValue.type === 'fn'" class="" style="padding:0 16px 16px 16px;"> <MkTextarea v-model="slots"> <template #label>{{ $ts._pages.script.blocks._fn.slots }}</template> <template #caption>{{ $t('_pages.script.blocks._fn.slots-info') }}</template> </MkTextarea> - <XV v-if="value.value.expression" v-model:value="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="value.value.slots" :name="name"/> + <XV v-if="modelValue.value.expression" v-model="modelValue.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="value.value.slots" :name="name"/> </section> - <section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;"> - <XV v-for="(x, i) in value.args" v-model:value="value.args[i]" :title="hpml.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name" :key="i"/> + <section v-else-if="modelValue.type.startsWith('fn:')" class="" style="padding:16px;"> + <XV v-for="(x, i) in modelValue.args" v-model="value.args[i]" :title="hpml.getVarByName(modelValue.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name" :key="i"/> </section> <section v-else class="" style="padding:16px;"> - <XV v-for="(x, i) in value.args" v-model:value="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots" :key="i"/> + <XV v-for="(x, i) in modelValue.args" v-model="modelValue.args[i]" :title="$t(`_pages.script.blocks._${modelValue.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots" :key="i"/> </section> </XContainer> </template> @@ -59,7 +59,7 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import { v4 as uuid } from 'uuid'; import XContainer from './page-editor.container.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; import { blockDefs } from '@client/scripts/hpml/index'; import * as os from '@client/os'; import { isLiteralValue } from '@client/scripts/hpml/expr'; @@ -78,7 +78,7 @@ export default defineComponent({ required: false, default: null }, - value: { + modelValue: { required: true }, title: { @@ -113,21 +113,21 @@ export default defineComponent({ computed: { icon(): any { - if (this.value.type === null) return null; - if (this.value.type.startsWith('fn:')) return 'fas fa-plug'; - return blockDefs.find(x => x.type === this.value.type).icon; + if (this.modelValue.type === null) return null; + if (this.modelValue.type.startsWith('fn:')) return 'fas fa-plug'; + return blockDefs.find(x => x.type === this.modelValue.type).icon; }, typeText(): any { - if (this.value.type === null) return null; - if (this.value.type.startsWith('fn:')) return this.value.type.split(':')[1]; - return this.$t(`_pages.script.blocks.${this.value.type}`); + if (this.modelValue.type === null) return null; + if (this.modelValue.type.startsWith('fn:')) return this.modelValue.type.split(':')[1]; + return this.$t(`_pages.script.blocks.${this.modelValue.type}`); }, }, watch: { slots: { handler() { - this.value.value.slots = this.slots.split('\n').map(x => ({ + this.modelValue.value.slots = this.slots.split('\n').map(x => ({ name: x, type: null })); @@ -137,24 +137,24 @@ export default defineComponent({ }, created() { - if (this.value.value == null) this.value.value = null; + if (this.modelValue.value == null) this.modelValue.value = null; - if (this.value.value && this.value.value.slots) this.slots = this.value.value.slots.map(x => x.name).join('\n'); + if (this.modelValue.value && this.modelValue.value.slots) this.slots = this.modelValue.value.slots.map(x => x.name).join('\n'); - this.$watch(() => this.value.type, (t) => { + this.$watch(() => this.modelValue.type, (t) => { this.warn = null; - if (this.value.type === 'fn') { + if (this.modelValue.type === 'fn') { const id = uuid(); - this.value.value = { + this.modelValue.value = { slots: [], expression: { id, type: null } }; return; } - if (this.value.type && this.value.type.startsWith('fn:')) { - const fnName = this.value.type.split(':')[1]; + if (this.modelValue.type && this.modelValue.type.startsWith('fn:')) { + const fnName = this.modelValue.type.split(':')[1]; const fn = this.hpml.getVarByName(fnName); const empties = []; @@ -162,29 +162,29 @@ export default defineComponent({ const id = uuid(); empties.push({ id, type: null }); } - this.value.args = empties; + this.modelValue.args = empties; return; } - if (isLiteralValue(this.value)) return; + if (isLiteralValue(this.modelValue)) return; const empties = []; - for (let i = 0; i < funcDefs[this.value.type].in.length; i++) { + for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) { const id = uuid(); empties.push({ id, type: null }); } - this.value.args = empties; + this.modelValue.args = empties; - for (let i = 0; i < funcDefs[this.value.type].in.length; i++) { - const inType = funcDefs[this.value.type].in[i]; + for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) { + const inType = funcDefs[this.modelValue.type].in[i]; if (typeof inType !== 'number') { - if (inType === 'number') this.value.args[i].type = 'number'; - if (inType === 'string') this.value.args[i].type = 'text'; + if (inType === 'number') this.modelValue.args[i].type = 'number'; + if (inType === 'string') this.modelValue.args[i].type = 'text'; } } }); - this.$watch(() => this.value.args, (args) => { + this.$watch(() => this.modelValue.args, (args) => { if (args == null) { this.warn = null; return; @@ -202,8 +202,8 @@ export default defineComponent({ }); this.$watch(() => this.hpml.variables, () => { - if (this.type != null && this.value) { - this.error = this.hpml.typeCheck(this.value); + if (this.type != null && this.modelValue) { + this.error = this.hpml.typeCheck(this.modelValue); } }, { deep: true @@ -221,11 +221,11 @@ export default defineComponent({ showCancelButton: true }); if (canceled) return; - this.value.type = type; + this.modelValue.type = type; }, _getExpectedType(slot: number) { - return this.hpml.getExpectedType(this.value, slot); + return this.hpml.getExpectedType(this.modelValue, slot); } } }); diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue index dc6896ba12..e04039e634 100644 --- a/src/client/pages/page-editor/page-editor.vue +++ b/src/client/pages/page-editor/page-editor.vue @@ -1,85 +1,84 @@ <template> -<div class="_root"> - <MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkA> +<div> + <MkHeader :info="header"/> - <div class="buttons" style="margin: 16px;"> - <MkButton inline @click="save" primary class="save" v-if="!readonly"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton inline @click="duplicate" class="duplicate" v-if="pageId"><i class="fas fa-copy"></i> {{ $ts.duplicate }}</MkButton> - <MkButton inline @click="del" class="delete" v-if="pageId && !readonly"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton> - </div> + <div class="_root"> + <div class="jqqmcavi" style="margin: 16px;"> + <MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkButton> + <MkButton inline @click="save" primary class="button" v-if="!readonly"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton inline @click="duplicate" class="button" v-if="pageId"><i class="fas fa-copy"></i> {{ $ts.duplicate }}</MkButton> + <MkButton inline @click="del" class="button" v-if="pageId && !readonly" danger><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton> + </div> - <MkContainer :foldable="true" :expanded="true" class="_gap"> - <template #header><i class="fas fa-cog"></i> {{ $ts._pages.pageSetting }}</template> - <div style="padding: 16px;"> - <MkInput v-model="title"> - <template #label>{{ $ts._pages.title }}</template> - </MkInput> + <div v-if="tab === 'settings'"> + <div style="padding: 16px;" class="_formRoot"> + <MkInput v-model="title" class="_formBlock"> + <template #label>{{ $ts._pages.title }}</template> + </MkInput> - <MkInput v-model="summary"> - <template #label>{{ $ts._pages.summary }}</template> - </MkInput> + <MkInput v-model="summary" class="_formBlock"> + <template #label>{{ $ts._pages.summary }}</template> + </MkInput> - <MkInput v-model="name"> - <template #prefix>{{ url }}/@{{ author.username }}/pages/</template> - <template #label>{{ $ts._pages.url }}</template> - </MkInput> + <MkInput v-model="name" class="_formBlock"> + <template #prefix>{{ url }}/@{{ author.username }}/pages/</template> + <template #label>{{ $ts._pages.url }}</template> + </MkInput> - <MkSwitch v-model="alignCenter">{{ $ts._pages.alignCenter }}</MkSwitch> + <MkSwitch v-model="alignCenter" class="_formBlock">{{ $ts._pages.alignCenter }}</MkSwitch> - <MkSelect v-model="font"> - <template #label>{{ $ts._pages.font }}</template> - <option value="serif">{{ $ts._pages.fontSerif }}</option> - <option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option> - </MkSelect> + <MkSelect v-model="font" class="_formBlock"> + <template #label>{{ $ts._pages.font }}</template> + <option value="serif">{{ $ts._pages.fontSerif }}</option> + <option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option> + </MkSelect> - <MkSwitch v-model="hideTitleWhenPinned">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch> + <MkSwitch v-model="hideTitleWhenPinned" class="_formBlock">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch> - <div class="eyeCatch"> - <MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><i class="fas fa-plus"></i> {{ $ts._pages.eyeCatchingImageSet }}</MkButton> - <div v-else-if="eyeCatchingImage"> - <img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/> - <MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><i class="fas fa-trash-alt"></i> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton> + <div class="eyeCatch"> + <MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><i class="fas fa-plus"></i> {{ $ts._pages.eyeCatchingImageSet }}</MkButton> + <div v-else-if="eyeCatchingImage"> + <img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/> + <MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><i class="fas fa-trash-alt"></i> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton> + </div> </div> </div> </div> - </MkContainer> - <MkContainer :foldable="true" :expanded="true" class="_gap"> - <template #header><i class="fas fa-sticky-note"></i> {{ $ts._pages.contents }}</template> - <div style="padding: 16px;"> - <XBlocks class="content" v-model:value="content" :hpml="hpml"/> + <div v-else-if="tab === 'contents'"> + <div style="padding: 16px;"> + <XBlocks class="content" v-model="content" :hpml="hpml"/> - <MkButton @click="add()" v-if="!readonly"><i class="fas fa-plus"></i></MkButton> + <MkButton @click="add()" v-if="!readonly"><i class="fas fa-plus"></i></MkButton> + </div> </div> - </MkContainer> - <MkContainer :foldable="true" class="_gap"> - <template #header><i class="fas fa-magic"></i> {{ $ts._pages.variables }}</template> - <div class="qmuvgica"> - <XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> - <template #item="{element}"> - <XVariable - :value="element" - :removable="true" - @remove="() => removeVariable(element)" - :hpml="hpml" - :name="element.name" - :title="element.name" - :draggable="true" - /> - </template> - </XDraggable> + <div v-else-if="tab === 'variables'"> + <div class="qmuvgica"> + <XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5"> + <template #item="{element}"> + <XVariable + :modelValue="element" + :removable="true" + @remove="() => removeVariable(element)" + :hpml="hpml" + :name="element.name" + :title="element.name" + :draggable="true" + /> + </template> + </XDraggable> - <MkButton @click="addVariable()" class="add" v-if="!readonly"><i class="fas fa-plus"></i></MkButton> + <MkButton @click="addVariable()" class="add" v-if="!readonly"><i class="fas fa-plus"></i></MkButton> + </div> </div> - </MkContainer> - <MkContainer :foldable="true" :expanded="true" class="_gap"> - <template #header><i class="fas fa-code"></i> {{ $ts.script }}</template> - <div> - <MkTextarea class="_code" v-model="script"/> + <div v-else-if="tab === 'script'"> + <div> + <MkTextarea class="_code" v-model="script"/> + </div> </div> - </MkContainer> + </div> </div> </template> @@ -94,12 +93,12 @@ import 'vue-prism-editor/dist/prismeditor.min.css'; import { v4 as uuid } from 'uuid'; import XVariable from './page-editor.script-block.vue'; import XBlocks from './page-editor.blocks.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; import MkContainer from '@client/components/ui/container.vue'; import MkButton from '@client/components/ui/button.vue'; -import MkSelect from '@client/components/ui/select.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkSelect from '@client/components/form/select.vue'; +import MkSwitch from '@client/components/form/switch.vue'; +import MkInput from '@client/components/form/input.vue'; import { blockDefs } from '@client/scripts/hpml/index'; import { HpmlTypeChecker } from '@client/scripts/hpml/type-checker'; import { url } from '@client/config'; @@ -142,6 +141,43 @@ export default defineComponent({ return { title: title, icon: 'fas fa-pencil-alt', + bg: 'var(--bg)', + }; + }), + tab: 'settings', + header: computed(() => { + let title = this.$ts._pages.newPage; + if (this.initPageId) { + title = this.$ts._pages.editPage; + } + else if (this.initPageName && this.initUser) { + title = this.$ts._pages.readPage; + } + return { + title: title, + icon: 'fas fa-pencil-alt', + bg: 'var(--bg)', + tabs: [{ + active: this.tab === 'settings', + title: this.$ts._pages.pageSetting, + icon: 'fas fa-cog', + onClick: () => { this.tab = 'settings'; }, + }, { + active: this.tab === 'contents', + title: this.$ts._pages.contents, + icon: 'fas fa-sticky-note', + onClick: () => { this.tab = 'contents'; }, + }, { + active: this.tab === 'variables', + title: this.$ts._pages.variables, + icon: 'fas fa-magic', + onClick: () => { this.tab = 'variables'; }, + }, { + active: this.tab === 'script', + title: this.$ts.script, + icon: 'fas fa-code', + onClick: () => { this.tab = 'script'; }, + }] }; }), author: this.$i, @@ -455,6 +491,14 @@ export default defineComponent({ </script> <style lang="scss" scoped> +.jqqmcavi { + > .button { + & + .button { + margin-left: 8px; + } + } +} + .gwbmwxkm { position: relative; @@ -522,11 +566,7 @@ export default defineComponent({ } .qmuvgica { - padding: 32px; - - @media (max-width: 500px) { - padding: 16px; - } + padding: 16px; > .variables { margin-bottom: 16px; diff --git a/src/client/pages/page.vue b/src/client/pages/page.vue index 47a458df9c..b8d7507363 100644 --- a/src/client/pages/page.vue +++ b/src/client/pages/page.vue @@ -1,61 +1,65 @@ <template> -<div class="_root"> - <transition name="fade" mode="out-in"> - <div v-if="page" class="xcukqgmh" :key="page.id" v-size="{ max: [450] }"> - <div class="_block main"> - <!-- - <div class="header"> - <h1>{{ page.title }}</h1> - </div> - --> - <div class="banner"> - <img :src="page.eyeCatchingImage.url" v-if="page.eyeCatchingImageId"/> - </div> - <div class="content"> - <XPage :page="page"/> - </div> - <div class="actions"> - <div class="like"> - <MkButton class="button" @click="unlike()" v-if="page.isLiked" v-tooltip="$ts._pages.unlike" primary><i class="fas fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton> - <MkButton class="button" @click="like()" v-else v-tooltip="$ts._pages.like"><i class="far fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton> +<div> + <MkHeader :info="header"/> + + <div class="_root"> + <transition name="fade" mode="out-in"> + <div v-if="page" class="xcukqgmh" :key="page.id" v-size="{ max: [450] }"> + <div class="_block main"> + <!-- + <div class="header"> + <h1>{{ page.title }}</h1> </div> - <div class="other"> - <button class="_button" @click="shareWithNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button> - <button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button> + --> + <div class="banner"> + <img :src="page.eyeCatchingImage.url" v-if="page.eyeCatchingImageId"/> </div> - </div> - <div class="user"> - <MkAvatar :user="page.user" class="avatar"/> - <div class="name"> - <MkUserName :user="page.user" style="display: block;"/> - <MkAcct :user="page.user"/> + <div class="content"> + <XPage :page="page"/> + </div> + <div class="actions"> + <div class="like"> + <MkButton class="button" @click="unlike()" v-if="page.isLiked" v-tooltip="$ts._pages.unlike" primary><i class="fas fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton> + <MkButton class="button" @click="like()" v-else v-tooltip="$ts._pages.like"><i class="far fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton> + </div> + <div class="other"> + <button class="_button" @click="shareWithNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button> + <button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button> + </div> + </div> + <div class="user"> + <MkAvatar :user="page.user" class="avatar"/> + <div class="name"> + <MkUserName :user="page.user" style="display: block;"/> + <MkAcct :user="page.user"/> + </div> + <MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> + </div> + <div class="links"> + <MkA :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ $ts._pages.viewSource }}</MkA> + <template v-if="$i && $i.id === page.userId"> + <MkA :to="`/pages/edit/${page.id}`" class="link">{{ $ts._pages.editThisPage }}</MkA> + <button v-if="$i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $ts.unpin }}</button> + <button v-else @click="pin(true)" class="link _textButton">{{ $ts.pin }}</button> + </template> </div> - <MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> </div> - <div class="links"> - <MkA :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ $ts._pages.viewSource }}</MkA> - <template v-if="$i && $i.id === page.userId"> - <MkA :to="`/pages/edit/${page.id}`" class="link">{{ $ts._pages.editThisPage }}</MkA> - <button v-if="$i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $ts.unpin }}</button> - <button v-else @click="pin(true)" class="link _textButton">{{ $ts.pin }}</button> - </template> + <div class="footer"> + <div><i class="far fa-clock"></i> {{ $ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div> + <div v-if="page.createdAt != page.updatedAt"><i class="far fa-clock"></i> {{ $ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div> </div> + <MkAd :prefer="['horizontal', 'horizontal-big']"/> + <MkContainer :max-height="300" :foldable="true" class="other"> + <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template> + <MkPagination :pagination="otherPostsPagination" #default="{items}"> + <MkPagePreview v-for="page in items" :page="page" :key="page.id" class="_gap"/> + </MkPagination> + </MkContainer> </div> - <div class="footer"> - <div><i class="far fa-clock"></i> {{ $ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div> - <div v-if="page.createdAt != page.updatedAt"><i class="far fa-clock"></i> {{ $ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div> - </div> - <MkAd :prefer="['horizontal', 'horizontal-big']"/> - <MkContainer :max-height="300" :foldable="true" class="other"> - <template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template> - <MkPagination :pagination="otherPostsPagination" #default="{items}"> - <MkPagePreview v-for="page in items" :page="page" :key="page.id" class="_gap"/> - </MkPagination> - </MkContainer> - </div> - <MkError v-else-if="error" @retry="fetch()"/> - <MkLoading v-else/> - </transition> + <MkError v-else-if="error" @retry="fetch()"/> + <MkLoading v-else/> + </transition> + </div> </div> </template> @@ -97,6 +101,10 @@ export default defineComponent({ [symbols.PAGE_INFO]: computed(() => this.page ? { title: computed(() => this.page.title || this.page.name), avatar: this.page.user, + } : null), + header: computed(() => this.page ? { + title: computed(() => this.page.title || this.page.name), + avatar: this.page.user, path: `/@${this.page.user.username}/pages/${this.page.name}`, share: { title: this.page.title || this.page.name, diff --git a/src/client/pages/pages.vue b/src/client/pages/pages.vue index 52a860be13..8300e8a6e4 100644 --- a/src/client/pages/pages.vue +++ b/src/client/pages/pages.vue @@ -1,31 +1,36 @@ <template> <div> - <MkTab v-model:value="tab" v-if="$i"> - <option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._pages.featured }}</option> - <option value="my"><i class="fas fa-edit"></i> {{ $ts._pages.my }}</option> - <option value="liked"><i class="fas fa-heart"></i> {{ $ts._pages.liked }}</option> - </MkTab> + <MkHeader :info="header"/> - <div class="_section"> - <div class="rknalgpo _content" v-if="tab === 'featured'"> - <MkPagination :pagination="featuredPagesPagination" #default="{items}"> - <MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/> - </MkPagination> - </div> + <MkSpacer> + <!-- TODO: MkHeaderに統合 --> + <MkTab v-model="tab" v-if="$i"> + <option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._pages.featured }}</option> + <option value="my"><i class="fas fa-edit"></i> {{ $ts._pages.my }}</option> + <option value="liked"><i class="fas fa-heart"></i> {{ $ts._pages.liked }}</option> + </MkTab> - <div class="rknalgpo _content my" v-if="tab === 'my'"> - <MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton> - <MkPagination :pagination="myPagesPagination" #default="{items}"> - <MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/> - </MkPagination> - </div> + <div class="_section"> + <div class="rknalgpo _content" v-if="tab === 'featured'"> + <MkPagination :pagination="featuredPagesPagination" #default="{items}"> + <MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/> + </MkPagination> + </div> + + <div class="rknalgpo _content my" v-if="tab === 'my'"> + <MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton> + <MkPagination :pagination="myPagesPagination" #default="{items}"> + <MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/> + </MkPagination> + </div> - <div class="rknalgpo _content" v-if="tab === 'liked'"> - <MkPagination :pagination="likedPagesPagination" #default="{items}"> - <MkPagePreview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/> - </MkPagination> + <div class="rknalgpo _content" v-if="tab === 'liked'"> + <MkPagination :pagination="likedPagesPagination" #default="{items}"> + <MkPagePreview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/> + </MkPagination> + </div> </div> - </div> + </MkSpacer> </div> </template> @@ -46,11 +51,17 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.pages, icon: 'fas fa-sticky-note', + bg: 'var(--bg)', + }, + header: { + title: this.$ts.pages, + icon: 'fas fa-sticky-note', + bg: 'var(--bg)', actions: [{ icon: 'fas fa-plus', text: this.$ts.create, - handler: this.create - }] + handler: this.create, + }], }, tab: 'featured', featuredPagesPagination: { diff --git a/src/client/pages/reset-password.vue b/src/client/pages/reset-password.vue index c331382132..6dd9f24259 100644 --- a/src/client/pages/reset-password.vue +++ b/src/client/pages/reset-password.vue @@ -1,6 +1,6 @@ <template> <FormBase v-if="token"> - <FormInput v-model:value="password" type="password"> + <FormInput v-model="password" type="password"> <template #prefix><i class="fas fa-lock"></i></template> <span>{{ $ts.newPassword }}</span> </FormInput> @@ -11,11 +11,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/reversi/game.setting.vue b/src/client/pages/reversi/game.setting.vue index 1cc623b790..eb6f24e4ab 100644 --- a/src/client/pages/reversi/game.setting.vue +++ b/src/client/pages/reversi/game.setting.vue @@ -127,8 +127,8 @@ import { defineComponent } from 'vue'; import * as maps from '../../../games/reversi/maps'; import MkButton from '@client/components/ui/button.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; -import MkRadio from '@client/components/ui/radio.vue'; +import MkSwitch from '@client/components/form/switch.vue'; +import MkRadio from '@client/components/form/radio.vue'; export default defineComponent({ components: { @@ -303,7 +303,7 @@ export default defineComponent({ -moz-appearance: none; appearance: none; - &:focus, + &:focus-visible, &:active { border-color: var(--accent); } diff --git a/src/client/pages/room/room.vue b/src/client/pages/room/room.vue index 365ed5b803..671dca3577 100644 --- a/src/client/pages/room/room.vue +++ b/src/client/pages/room/room.vue @@ -57,7 +57,7 @@ import XPreview from './preview.vue'; const storeItems = require('@client/scripts/room/furnitures.json5'); import { query as urlQuery } from '../../../prelude/url'; import MkButton from '@client/components/ui/button.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkSelect from '@client/components/form/select.vue'; import { selectFile } from '@client/scripts/select-file'; import * as os from '@client/os'; import { ColdDeviceStorage } from '@client/store'; diff --git a/src/client/pages/search.vue b/src/client/pages/search.vue index bf228576be..fec138726f 100644 --- a/src/client/pages/search.vue +++ b/src/client/pages/search.vue @@ -1,7 +1,10 @@ <template> -<div class="_section"> - <div class="_content"> - <XNotes ref="notes" :pagination="pagination" @before="before" @after="after"/> +<div> + <MkHeader :info="header"/> + <div class="_section"> + <div class="_content"> + <XNotes ref="notes" :pagination="pagination" @before="before" @after="after"/> + </div> </div> </div> </template> @@ -21,7 +24,11 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: computed(() => this.$t('searchWith', { q: this.$route.query.q })), - icon: 'fas fa-search' + icon: 'fas fa-search', + }, + header: { + title: computed(() => this.$t('searchWith', { q: this.$route.query.q })), + icon: 'fas fa-search', }, pagination: { endpoint: 'notes/search', diff --git a/src/client/pages/settings/2fa.vue b/src/client/pages/settings/2fa.vue index 48b06eaa24..386e7c635a 100644 --- a/src/client/pages/settings/2fa.vue +++ b/src/client/pages/settings/2fa.vue @@ -72,11 +72,11 @@ import { hostname } from '@client/config'; import { byteify, hexify, stringify } from '@client/scripts/2fa'; import MkButton from '@client/components/ui/button.vue'; import MkInfo from '@client/components/ui/info.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSwitch from '@client/components/form/switch.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/settings/account-info.vue b/src/client/pages/settings/account-info.vue index 4d851b7b12..16ce91b12f 100644 --- a/src/client/pages/settings/account-info.vue +++ b/src/client/pages/settings/account-info.vue @@ -134,11 +134,11 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; import * as os from '@client/os'; import number from '@client/filters/number'; import bytes from '@client/filters/bytes'; diff --git a/src/client/pages/settings/accounts.vue b/src/client/pages/settings/accounts.vue index ca6f53776a..d2966cc216 100644 --- a/src/client/pages/settings/accounts.vue +++ b/src/client/pages/settings/accounts.vue @@ -3,8 +3,8 @@ <FormSuspense :p="init"> <FormButton @click="addAccount" primary><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton> - <div class="_formItem _button" v-for="account in accounts" :key="account.id" @click="menu(account, $event)"> - <div class="_formPanel lcjjdxlm"> + <div class="_debobigegoItem _button" v-for="account in accounts" :key="account.id" @click="menu(account, $event)"> + <div class="_debobigegoPanel lcjjdxlm"> <div class="avatar"> <MkAvatar :user="account" class="avatar"/> </div> @@ -24,11 +24,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSuspense from '@client/components/form/suspense.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import { getAccounts, addAccount, login } from '@client/account'; @@ -47,6 +47,7 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.accounts, icon: 'fas fa-users', + bg: 'var(--bg)', }, storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)), accounts: null, diff --git a/src/client/pages/settings/api.vue b/src/client/pages/settings/api.vue index 396d4405c3..5c7496e2f9 100644 --- a/src/client/pages/settings/api.vue +++ b/src/client/pages/settings/api.vue @@ -10,10 +10,10 @@ import { defineComponent } from 'vue'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -30,7 +30,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: 'API', - icon: 'fas fa-key' + icon: 'fas fa-key', + bg: 'var(--bg)', }, isDesktop: window.innerWidth >= 1100, }; diff --git a/src/client/pages/settings/apps.vue b/src/client/pages/settings/apps.vue index c864920ce1..da4f672adf 100644 --- a/src/client/pages/settings/apps.vue +++ b/src/client/pages/settings/apps.vue @@ -8,7 +8,7 @@ </div> </template> <template #default="{items}"> - <div class="_formPanel bfomjevm" v-for="token in items" :key="token.id"> + <div class="_debobigegoPanel bfomjevm" v-for="token in items" :key="token.id"> <img class="icon" :src="token.iconUrl" alt="" v-if="token.iconUrl"/> <div class="body"> <div class="name">{{ token.name }}</div> @@ -39,12 +39,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormPagination from '@client/components/form/pagination.vue'; +import FormPagination from '@client/components/debobigego/pagination.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -61,6 +61,7 @@ export default defineComponent({ [symbols.PAGE_INFO]: { title: this.$ts.installedApps, icon: 'fas fa-plug', + bg: 'var(--bg)', }, pagination: { endpoint: 'i/apps', diff --git a/src/client/pages/settings/custom-css.vue b/src/client/pages/settings/custom-css.vue index 0781eeebd7..fd473a11fa 100644 --- a/src/client/pages/settings/custom-css.vue +++ b/src/client/pages/settings/custom-css.vue @@ -2,7 +2,7 @@ <FormBase> <FormInfo warn>{{ $ts.customCssWarn }}</FormInfo> - <FormTextarea v-model:value="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;"> + <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;"> <span>{{ $ts.local }}</span> </FormTextarea> </FormBase> @@ -13,11 +13,11 @@ 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 FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; import * as os from '@client/os'; import { ColdDeviceStorage } from '@client/store'; import { unisonReload } from '@client/scripts/unison-reload'; @@ -42,7 +42,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.customCss, - icon: 'fas fa-code' + icon: 'fas fa-code', + bg: 'var(--bg)', }, localCustomCss: localStorage.getItem('customCss') } diff --git a/src/client/pages/settings/deck.vue b/src/client/pages/settings/deck.vue index 05f3061ca1..e4b5c697c4 100644 --- a/src/client/pages/settings/deck.vue +++ b/src/client/pages/settings/deck.vue @@ -2,10 +2,10 @@ <FormBase> <FormGroup> <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model:value="navWindow">{{ $ts.openInWindow }}</FormSwitch> + <FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch> </FormGroup> - <FormSwitch v-model:value="alwaysShowMainColumn">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> + <FormSwitch v-model="alwaysShowMainColumn">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> <FormRadios v-model="columnAlign"> <template #desc>{{ $ts._deck.columnAlign }}</template> @@ -20,7 +20,7 @@ <option :value="48">{{ $ts.wide }}</option> </FormRadios> - <FormInput v-model:value="columnMargin" type="number"> + <FormInput v-model="columnMargin" type="number"> <span>{{ $ts._deck.columnMargin }}</span> <template #suffix>px</template> </FormInput> @@ -31,12 +31,12 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormRadios from '@client/components/form/radios.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormRadios from '@client/components/debobigego/radios.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import { deckStore } from '@client/ui/deck/deck-store'; import * as os from '@client/os'; import { unisonReload } from '@client/scripts/unison-reload'; @@ -58,7 +58,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.deck, - icon: 'fas fa-columns' + icon: 'fas fa-columns', + bg: 'var(--bg)', }, } }, diff --git a/src/client/pages/settings/delete-account.vue b/src/client/pages/settings/delete-account.vue index 3af1879857..6bac214e04 100644 --- a/src/client/pages/settings/delete-account.vue +++ b/src/client/pages/settings/delete-account.vue @@ -9,10 +9,10 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import { debug } from '@client/config'; import { signout } from '@client/account'; @@ -32,7 +32,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts._accountDelete.accountDelete, - icon: 'fas fa-exclamation-triangle' + icon: 'fas fa-exclamation-triangle', + bg: 'var(--bg)', }, debug, } diff --git a/src/client/pages/settings/drive.vue b/src/client/pages/settings/drive.vue index 83068a8335..177bf058f3 100644 --- a/src/client/pages/settings/drive.vue +++ b/src/client/pages/settings/drive.vue @@ -2,8 +2,8 @@ <FormBase class=""> <FormGroup v-if="!fetching"> <template #label>{{ $ts.usageAmount }}</template> - <div class="_formItem uawsfosz"> - <div class="_formPanel"> + <div class="_debobigegoItem uawsfosz"> + <div class="_debobigegoPanel"> <div class="meter"><div :style="meterStyle"></div></div> </div> </div> @@ -17,9 +17,9 @@ </FormKeyValueView> </FormGroup> - <div class="_formItem"> - <div class="_formLabel">{{ $ts.statistics }}</div> - <div class="_formPanel"> + <div class="_debobigegoItem"> + <div class="_debobigegoLabel">{{ $ts.statistics }}</div> + <div class="_debobigegoPanel"> <div ref="chart"></div> </div> </div> @@ -36,10 +36,10 @@ import { defineComponent } from 'vue'; import * as tinycolor from 'tinycolor2'; import ApexCharts from 'apexcharts'; -import FormButton from '@client/components/form/button.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormBase from '@client/components/form/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormBase from '@client/components/debobigego/base.vue'; import * as os from '@client/os'; import bytes from '@client/filters/bytes'; import * as symbols from '@client/symbols'; @@ -58,7 +58,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.drive, - icon: 'fas fa-cloud' + icon: 'fas fa-cloud', + bg: 'var(--bg)', }, fetching: true, usage: null, diff --git a/src/client/pages/settings/email-address.vue b/src/client/pages/settings/email-address.vue index 28eeeb6b73..f98b22ada7 100644 --- a/src/client/pages/settings/email-address.vue +++ b/src/client/pages/settings/email-address.vue @@ -1,7 +1,7 @@ <template> <FormBase> <FormGroup> - <FormInput v-model:value="emailAddress" type="email"> + <FormInput v-model="emailAddress" type="email"> {{ $ts.emailAddress }} <template #desc v-if="$i.email && !$i.emailVerified">{{ $ts.verificationEmailSent }}</template> <template #desc v-else-if="emailAddress === $i.email && $i.emailVerified">{{ $ts.emailVerified }}</template> @@ -13,10 +13,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormButton from '@client/components/form/button.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import FormInput from '@client/components/form/input.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -34,7 +34,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.emailAddress, - icon: 'fas fa-envelope' + icon: 'fas fa-envelope', + bg: 'var(--bg)', }, emailAddress: null, code: null, diff --git a/src/client/pages/settings/email-notification.vue b/src/client/pages/settings/email-notification.vue index ac3402568a..1b78621c3f 100644 --- a/src/client/pages/settings/email-notification.vue +++ b/src/client/pages/settings/email-notification.vue @@ -1,22 +1,22 @@ <template> <FormBase> <FormGroup> - <FormSwitch v-model:value="mention"> + <FormSwitch v-model="mention"> {{ $ts._notification._types.mention }} </FormSwitch> - <FormSwitch v-model:value="reply"> + <FormSwitch v-model="reply"> {{ $ts._notification._types.reply }} </FormSwitch> - <FormSwitch v-model:value="quote"> + <FormSwitch v-model="quote"> {{ $ts._notification._types.quote }} </FormSwitch> - <FormSwitch v-model:value="follow"> + <FormSwitch v-model="follow"> {{ $ts._notification._types.follow }} </FormSwitch> - <FormSwitch v-model:value="receiveFollowRequest"> + <FormSwitch v-model="receiveFollowRequest"> {{ $ts._notification._types.receiveFollowRequest }} </FormSwitch> - <FormSwitch v-model:value="groupInvited"> + <FormSwitch v-model="groupInvited"> {{ $ts._notification._types.groupInvited }} </FormSwitch> </FormGroup> @@ -25,10 +25,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormButton from '@client/components/form/button.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import FormSwitch from '@client/components/form/switch.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; import * as symbols from '@client/symbols'; @@ -47,7 +47,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.emailNotification, - icon: 'fas fa-envelope' + icon: 'fas fa-envelope', + bg: 'var(--bg)', }, mention: this.$i.emailNotificationTypes.includes('mention'), diff --git a/src/client/pages/settings/email.vue b/src/client/pages/settings/email.vue index aa20d9d94e..adc62133ac 100644 --- a/src/client/pages/settings/email.vue +++ b/src/client/pages/settings/email.vue @@ -14,7 +14,7 @@ {{ $ts.emailNotification }} </FormLink> - <FormSwitch :value="$i.receiveAnnouncementEmail" @update:value="onChangeReceiveAnnouncementEmail"> + <FormSwitch :value="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail"> {{ $ts.receiveAnnouncementFromInstance }} </FormSwitch> </FormBase> @@ -22,11 +22,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormButton from '@client/components/form/button.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormSwitch from '@client/components/form/switch.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -45,7 +45,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.email, - icon: 'fas fa-envelope' + icon: 'fas fa-envelope', + bg: 'var(--bg)', }, } }, diff --git a/src/client/pages/settings/experimental-features.vue b/src/client/pages/settings/experimental-features.vue index f8d5e419e9..971c45a628 100644 --- a/src/client/pages/settings/experimental-features.vue +++ b/src/client/pages/settings/experimental-features.vue @@ -8,11 +8,11 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/settings/general.vue b/src/client/pages/settings/general.vue index f8e8e6b24b..59dd251948 100644 --- a/src/client/pages/settings/general.vue +++ b/src/client/pages/settings/general.vue @@ -1,8 +1,8 @@ <template> <FormBase> - <FormSwitch v-model:value="showFixedPostForm">{{ $ts.showFixedPostForm }}</FormSwitch> + <FormSwitch v-model="showFixedPostForm">{{ $ts.showFixedPostForm }}</FormSwitch> - <FormSelect v-model:value="lang"> + <FormSelect v-model="lang"> <template #label>{{ $ts.uiLanguage }}</template> <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option> <template #caption> @@ -16,13 +16,13 @@ <FormGroup> <template #label>{{ $ts.behavior }}</template> - <FormSwitch v-model:value="imageNewTab">{{ $ts.openImageInNewTab }}</FormSwitch> - <FormSwitch v-model:value="enableInfiniteScroll">{{ $ts.enableInfiniteScroll }}</FormSwitch> - <FormSwitch v-model:value="useReactionPickerForContextMenu">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch> - <FormSwitch v-model:value="disablePagesScript">{{ $ts.disablePagesScript }}</FormSwitch> + <FormSwitch v-model="imageNewTab">{{ $ts.openImageInNewTab }}</FormSwitch> + <FormSwitch v-model="enableInfiniteScroll">{{ $ts.enableInfiniteScroll }}</FormSwitch> + <FormSwitch v-model="useReactionPickerForContextMenu">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch> + <FormSwitch v-model="disablePagesScript">{{ $ts.disablePagesScript }}</FormSwitch> </FormGroup> - <FormSelect v-model:value="serverDisconnectedBehavior"> + <FormSelect v-model="serverDisconnectedBehavior"> <template #label>{{ $ts.whenServerDisconnected }}</template> <option value="reload">{{ $ts._serverDisconnectedBehavior.reload }}</option> <option value="dialog">{{ $ts._serverDisconnectedBehavior.dialog }}</option> @@ -31,22 +31,22 @@ <FormGroup> <template #label>{{ $ts.appearance }}</template> - <FormSwitch v-model:value="disableAnimatedMfm">{{ $ts.disableAnimatedMfm }}</FormSwitch> - <FormSwitch v-model:value="reduceAnimation">{{ $ts.reduceUiAnimation }}</FormSwitch> - <FormSwitch v-model:value="useBlurEffect">{{ $ts.useBlurEffect }}</FormSwitch> - <FormSwitch v-model:value="useBlurEffectForModal">{{ $ts.useBlurEffectForModal }}</FormSwitch> - <FormSwitch v-model:value="showGapBetweenNotesInTimeline">{{ $ts.showGapBetweenNotesInTimeline }}</FormSwitch> - <FormSwitch v-model:value="loadRawImages">{{ $ts.loadRawImages }}</FormSwitch> - <FormSwitch v-model:value="disableShowingAnimatedImages">{{ $ts.disableShowingAnimatedImages }}</FormSwitch> - <FormSwitch v-model:value="squareAvatars">{{ $ts.squareAvatars }}</FormSwitch> - <FormSwitch v-model:value="useSystemFont">{{ $ts.useSystemFont }}</FormSwitch> - <FormSwitch v-model:value="useOsNativeEmojis">{{ $ts.useOsNativeEmojis }} + <FormSwitch v-model="disableAnimatedMfm">{{ $ts.disableAnimatedMfm }}</FormSwitch> + <FormSwitch v-model="reduceAnimation">{{ $ts.reduceUiAnimation }}</FormSwitch> + <FormSwitch v-model="useBlurEffect">{{ $ts.useBlurEffect }}</FormSwitch> + <FormSwitch v-model="useBlurEffectForModal">{{ $ts.useBlurEffectForModal }}</FormSwitch> + <FormSwitch v-model="showGapBetweenNotesInTimeline">{{ $ts.showGapBetweenNotesInTimeline }}</FormSwitch> + <FormSwitch v-model="loadRawImages">{{ $ts.loadRawImages }}</FormSwitch> + <FormSwitch v-model="disableShowingAnimatedImages">{{ $ts.disableShowingAnimatedImages }}</FormSwitch> + <FormSwitch v-model="squareAvatars">{{ $ts.squareAvatars }}</FormSwitch> + <FormSwitch v-model="useSystemFont">{{ $ts.useSystemFont }}</FormSwitch> + <FormSwitch v-model="useOsNativeEmojis">{{ $ts.useOsNativeEmojis }} <div><Mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪" :key="useOsNativeEmojis"/></div> </FormSwitch> </FormGroup> <FormGroup> - <FormSwitch v-model:value="aiChanMode">{{ $ts.aiChanMode }}</FormSwitch> + <FormSwitch v-model="aiChanMode">{{ $ts.aiChanMode }}</FormSwitch> </FormGroup> <FormRadios v-model="fontSize"> @@ -57,14 +57,14 @@ <option value="veryLarge"><span style="font-size: 20px;">Aa</span></option> </FormRadios> - <FormSelect v-model:value="instanceTicker"> + <FormSelect v-model="instanceTicker"> <template #label>{{ $ts.instanceTicker }}</template> <option value="none">{{ $ts._instanceTicker.none }}</option> <option value="remote">{{ $ts._instanceTicker.remote }}</option> <option value="always">{{ $ts._instanceTicker.always }}</option> </FormSelect> - <FormSelect v-model:value="nsfw"> + <FormSelect v-model="nsfw"> <template #label>{{ $ts.nsfw }}</template> <option value="respect">{{ $ts._nsfw.respect }}</option> <option value="ignore">{{ $ts._nsfw.ignore }}</option> @@ -73,10 +73,10 @@ <FormGroup> <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model:value="defaultSideView">{{ $ts.openInSideView }}</FormSwitch> + <FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch> </FormGroup> - <FormSelect v-model:value="chatOpenBehavior"> + <FormSelect v-model="chatOpenBehavior"> <template #label>{{ $ts.chatOpenBehavior }}</template> <option value="page">{{ $ts.showInPage }}</option> <option value="window">{{ $ts.openInWindow }}</option> @@ -91,13 +91,13 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.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 FormSwitch from '@client/components/debobigego/switch.vue'; +import FormSelect from '@client/components/debobigego/select.vue'; +import FormRadios from '@client/components/debobigego/radios.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import MkLink from '@client/components/link.vue'; import { langs } from '@client/config'; import { defaultStore } from '@client/store'; @@ -124,7 +124,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.general, - icon: 'fas fa-cogs' + icon: 'fas fa-cogs', + bg: 'var(--bg)' }, langs, lang: localStorage.getItem('lang'), diff --git a/src/client/pages/settings/import-export.vue b/src/client/pages/settings/import-export.vue index e77efb4429..2b49996dda 100644 --- a/src/client/pages/settings/import-export.vue +++ b/src/client/pages/settings/import-export.vue @@ -1,45 +1,42 @@ <template> -<FormBase> - <FormGroup> +<div style="margin: 16px;"> + <FormSection> <template #label>{{ $ts._exportOrImport.allNotes }}</template> - <FormButton @click="doExport('notes')"><i class="fas fa-download"></i> {{ $ts.export }}</FormButton> - </FormGroup> - <FormGroup> + <MkButton :class="$style.button" inline @click="doExport('notes')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton> + </FormSection> + <FormSection> <template #label>{{ $ts._exportOrImport.followingList }}</template> - <FormButton @click="doExport('following')"><i class="fas fa-download"></i> {{ $ts.export }}</FormButton> - <FormButton @click="doImport('following', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</FormButton> - </FormGroup> - <FormGroup> + <MkButton :class="$style.button" inline @click="doExport('following')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton> + <MkButton :class="$style.button" inline @click="doImport('following', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton> + </FormSection> + <FormSection> <template #label>{{ $ts._exportOrImport.userLists }}</template> - <FormButton @click="doExport('user-lists')"><i class="fas fa-download"></i> {{ $ts.export }}</FormButton> - <FormButton @click="doImport('user-lists', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</FormButton> - </FormGroup> - <FormGroup> + <MkButton :class="$style.button" inline @click="doExport('user-lists')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton> + <MkButton :class="$style.button" inline @click="doImport('user-lists', $event)"><i class="fas fa-upload"></i> {{ $ts.import }}</MkButton> + </FormSection> + <FormSection> <template #label>{{ $ts._exportOrImport.muteList }}</template> - <FormButton @click="doExport('mute')"><i class="fas fa-download"></i> {{ $ts.export }}</FormButton> - </FormGroup> - <FormGroup> + <MkButton :class="$style.button" inline @click="doExport('mute')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton> + </FormSection> + <FormSection> <template #label>{{ $ts._exportOrImport.blockingList }}</template> - <FormButton @click="doExport('blocking')"><i class="fas fa-download"></i> {{ $ts.export }}</FormButton> - </FormGroup> -</FormBase> + <MkButton :class="$style.button" inline @click="doExport('blocking')"><i class="fas fa-download"></i> {{ $ts.export }}</MkButton> + </FormSection> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSelect from '@client/components/form/select.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import MkButton from '@client/components/ui/button.vue'; +import FormSection from '@client/components/form/section.vue'; import * as os from '@client/os'; import { selectFile } from '@client/scripts/select-file'; import * as symbols from '@client/symbols'; export default defineComponent({ components: { - FormBase, - FormGroup, - FormButton, + FormSection, + MkButton, }, emits: ['info'], @@ -48,7 +45,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.importAndExport, - icon: 'fas fa-boxes' + icon: 'fas fa-boxes', + bg: 'var(--bg)', }, } }, @@ -102,3 +100,9 @@ export default defineComponent({ } }); </script> + +<style module> +.button { + margin-right: 16px; +} +</style> diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue index 3fb5f5f1e6..9da3031a41 100644 --- a/src/client/pages/settings/index.vue +++ b/src/client/pages/settings/index.vue @@ -1,53 +1,12 @@ <template> <div class="vvcocwet" :class="{ wide: !narrow }" ref="el"> <div class="nav" v-if="!narrow || page == null"> - <FormBase> - <FormGroup> - <div class="_formItem"> - <div class="_formPanel lwjxoukj"> - <MkAvatar :user="$i" class="avatar"/> - </div> - </div> - <FormLink :active="page === 'accounts'" replace to="/settings/accounts"><template #icon><i class="fas fa-users"></i></template>{{ $ts.accounts }}</FormLink> - </FormGroup> - <FormInfo v-if="emailNotConfigured" warn>{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></FormInfo> - <FormGroup> - <template #label>{{ $ts.basicSettings }}</template> - <FormLink :active="page === 'profile'" replace to="/settings/profile"><template #icon><i class="fas fa-user"></i></template>{{ $ts.profile }}</FormLink> - <FormLink :active="page === 'privacy'" replace to="/settings/privacy"><template #icon><i class="fas fa-lock-open"></i></template>{{ $ts.privacy }}</FormLink> - <FormLink :active="page === 'reaction'" replace to="/settings/reaction"><template #icon><i class="fas fa-laugh"></i></template>{{ $ts.reaction }}</FormLink> - <FormLink :active="page === 'drive'" replace to="/settings/drive"><template #icon><i class="fas fa-cloud"></i></template>{{ $ts.drive }}</FormLink> - <FormLink :active="page === 'notifications'" replace to="/settings/notifications"><template #icon><i class="fas fa-bell"></i></template>{{ $ts.notifications }}</FormLink> - <FormLink :active="page === 'email'" replace to="/settings/email"><template #icon><i class="fas fa-envelope"></i></template>{{ $ts.email }}</FormLink> - <FormLink :active="page === 'integration'" replace to="/settings/integration"><template #icon><i class="fas fa-share-alt"></i></template>{{ $ts.integration }}</FormLink> - <FormLink :active="page === 'security'" replace to="/settings/security"><template #icon><i class="fas fa-lock"></i></template>{{ $ts.security }}</FormLink> - </FormGroup> - <FormGroup> - <template #label>{{ $ts.clientSettings }}</template> - <FormLink :active="page === 'general'" replace to="/settings/general"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.general }}</FormLink> - <FormLink :active="page === 'theme'" replace to="/settings/theme"><template #icon><i class="fas fa-palette"></i></template>{{ $ts.theme }}</FormLink> - <FormLink :active="page === 'menu'" replace to="/settings/menu"><template #icon><i class="fas fa-list-ul"></i></template>{{ $ts.menu }}</FormLink> - <FormLink :active="page === 'sounds'" replace to="/settings/sounds"><template #icon><i class="fas fa-music"></i></template>{{ $ts.sounds }}</FormLink> - <FormLink :active="page === 'plugin'" replace to="/settings/plugin"><template #icon><i class="fas fa-plug"></i></template>{{ $ts.plugins }}</FormLink> - </FormGroup> - <FormGroup> - <template #label>{{ $ts.otherSettings }}</template> - <FormLink :active="page === 'import-export'" replace to="/settings/import-export"><template #icon><i class="fas fa-boxes"></i></template>{{ $ts.importAndExport }}</FormLink> - <FormLink :active="page === 'mute-block'" replace to="/settings/mute-block"><template #icon><i class="fas fa-ban"></i></template>{{ $ts.muteAndBlock }}</FormLink> - <FormLink :active="page === 'word-mute'" replace to="/settings/word-mute"><template #icon><i class="fas fa-comment-slash"></i></template>{{ $ts.wordMute }}</FormLink> - <FormLink :active="page === 'api'" replace to="/settings/api"><template #icon><i class="fas fa-key"></i></template>API</FormLink> - <FormLink :active="page === 'other'" replace to="/settings/other"><template #icon><i class="fas fa-ellipsis-h"></i></template>{{ $ts.other }}</FormLink> - </FormGroup> - <FormGroup> - <FormButton @click="clear">{{ $ts.clearCache }}</FormButton> - </FormGroup> - <FormGroup> - <FormButton @click="logout" danger>{{ $ts.logout }}</FormButton> - </FormGroup> - </FormBase> + <div class="title">{{ $ts.settings }}</div> + <MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo> + <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> </div> <div class="main"> - <component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/> + <component :is="component" :key="page" v-bind="pageProps"/> </div> </div> </template> @@ -55,11 +14,8 @@ <script lang="ts"> import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { i18n } from '@client/i18n'; -import FormLink from '@client/components/form/link.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormInfo from '@client/components/form/info.vue'; +import MkInfo from '@client/components/ui/info.vue'; +import MkSuperMenu from '@client/components/ui/super-menu.vue'; import { scroll } from '@client/scripts/scroll'; import { signout } from '@client/account'; import { unisonReload } from '@client/scripts/unison-reload'; @@ -69,11 +25,8 @@ import { $i } from '@client/account'; export default defineComponent({ components: { - FormBase, - FormLink, - FormGroup, - FormButton, - FormInfo, + MkInfo, + MkSuperMenu, }, props: { @@ -94,9 +47,126 @@ export default defineComponent({ const narrow = ref(false); const view = ref(null); const el = ref(null); - const onInfo = (viewInfo) => { - INFO.value = viewInfo; - }; + const menuDef = computed(() => [{ + title: i18n.locale.basicSettings, + items: [{ + icon: 'fas fa-user', + text: i18n.locale.profile, + to: '/settings/profile', + active: page.value === 'profile', + }, { + icon: 'fas fa-lock-open', + text: i18n.locale.privacy, + to: '/settings/privacy', + active: page.value === 'privacy', + }, { + icon: 'fas fa-laugh', + text: i18n.locale.reaction, + to: '/settings/reaction', + active: page.value === 'reaction', + }, { + icon: 'fas fa-cloud', + text: i18n.locale.drive, + to: '/settings/drive', + active: page.value === 'drive', + }, { + icon: 'fas fa-bell', + text: i18n.locale.notifications, + to: '/settings/notifications', + active: page.value === 'notifications', + }, { + icon: 'fas fa-envelope', + text: i18n.locale.email, + to: '/settings/email', + active: page.value === 'email', + }, { + icon: 'fas fa-share-alt', + text: i18n.locale.integration, + to: '/settings/integration', + active: page.value === 'integration', + }, { + icon: 'fas fa-lock', + text: i18n.locale.security, + to: '/settings/security', + active: page.value === 'security', + }], + }, { + title: i18n.locale.clientSettings, + items: [{ + icon: 'fas fa-cogs', + text: i18n.locale.general, + to: '/settings/general', + active: page.value === 'general', + }, { + icon: 'fas fa-palette', + text: i18n.locale.theme, + to: '/settings/theme', + active: page.value === 'theme', + }, { + icon: 'fas fa-list-ul', + text: i18n.locale.menu, + to: '/settings/menu', + active: page.value === 'menu', + }, { + icon: 'fas fa-music', + text: i18n.locale.sounds, + to: '/settings/sounds', + active: page.value === 'sounds', + }, { + icon: 'fas fa-plug', + text: i18n.locale.plugins, + to: '/settings/plugin', + active: page.value === 'plugin', + }], + }, { + title: i18n.locale.otherSettings, + items: [{ + icon: 'fas fa-boxes', + text: i18n.locale.importAndExport, + to: '/settings/import-export', + active: page.value === 'import-export', + }, { + icon: 'fas fa-ban', + text: i18n.locale.muteAndBlock, + to: '/settings/mute-block', + active: page.value === 'mute-block', + }, { + icon: 'fas fa-comment-slash', + text: i18n.locale.wordMute, + to: '/settings/word-mute', + active: page.value === 'word-mute', + }, { + icon: 'fas fa-key', + text: 'API', + to: '/settings/api', + active: page.value === 'api', + }, { + icon: 'fas fa-ellipsis-h', + text: i18n.locale.other, + to: '/settings/other', + active: page.value === 'other', + }], + }, { + items: [{ + type: 'button', + icon: 'fas fa-trash', + text: i18n.locale.clearCache, + action: () => { + localStorage.removeItem('locale'); + localStorage.removeItem('theme'); + unisonReload(); + }, + }, { + type: 'button', + icon: 'fas fa-sign-in-alt fa-flip-horizontal', + text: i18n.locale.logout, + action: () => { + signout(); + }, + danger: true, + },], + }]); + const pageProps = ref({}); const component = computed(() => { if (page.value == null) return null; @@ -159,7 +229,7 @@ export default defineComponent({ } nextTick(() => { - scroll(el.value, 0); + scroll(el.value, { top: 0 }); }); }, { immediate: true }); @@ -186,21 +256,13 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: INFO, page, + menuDef, narrow, view, el, - onInfo, pageProps, component, emailNotConfigured, - logout: () => { - signout(); - }, - clear: () => { - localStorage.removeItem('locale'); - localStorage.removeItem('theme'); - unisonReload(); - }, }; }, }); @@ -208,17 +270,41 @@ export default defineComponent({ <style lang="scss" scoped> .vvcocwet { + > .nav { + > .title { + margin: 16px; + font-size: 1.5em; + font-weight: bold; + } + + > .info { + margin: 0 16px; + } + + > .accounts { + > .avatar { + display: block; + width: 50px; + height: 50px; + margin: 8px auto 16px auto; + } + } + } + &.wide { display: flex; - max-width: 1100px; + max-width: 1000px; margin: 0 auto; height: 100%; > .nav { width: 32%; box-sizing: border-box; - border-right: solid 0.5px var(--divider); overflow: auto; + + > .title { + margin: 24px; + } } > .main { @@ -229,15 +315,4 @@ export default defineComponent({ } } } - -.lwjxoukj { - padding: 16px; - - > .avatar { - display: block; - margin: auto; - width: 42px; - height: 42px; - } -} </style> diff --git a/src/client/pages/settings/integration.vue b/src/client/pages/settings/integration.vue index f1c0a88afc..7f398dde9d 100644 --- a/src/client/pages/settings/integration.vue +++ b/src/client/pages/settings/integration.vue @@ -1,26 +1,26 @@ <template> <FormBase> - <div class="_formItem" v-if="enableTwitterIntegration"> - <div class="_formLabel"><i class="fab fa-twitter"></i> Twitter</div> - <div class="_formPanel" style="padding: 16px;"> + <div class="_debobigegoItem" v-if="enableTwitterIntegration"> + <div class="_debobigegoLabel"><i class="fab fa-twitter"></i> Twitter</div> + <div class="_debobigegoPanel" style="padding: 16px;"> <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> <MkButton v-if="integrations.twitter" @click="disconnectTwitter" danger>{{ $ts.disconnectService }}</MkButton> <MkButton v-else @click="connectTwitter" primary>{{ $ts.connectService }}</MkButton> </div> </div> - <div class="_formItem" v-if="enableDiscordIntegration"> - <div class="_formLabel"><i class="fab fa-discord"></i> Discord</div> - <div class="_formPanel" style="padding: 16px;"> + <div class="_debobigegoItem" v-if="enableDiscordIntegration"> + <div class="_debobigegoLabel"><i class="fab fa-discord"></i> Discord</div> + <div class="_debobigegoPanel" style="padding: 16px;"> <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> <MkButton v-if="integrations.discord" @click="disconnectDiscord" danger>{{ $ts.disconnectService }}</MkButton> <MkButton v-else @click="connectDiscord" primary>{{ $ts.connectService }}</MkButton> </div> </div> - <div class="_formItem" v-if="enableGithubIntegration"> - <div class="_formLabel"><i class="fab fa-github"></i> GitHub</div> - <div class="_formPanel" style="padding: 16px;"> + <div class="_debobigegoItem" v-if="enableGithubIntegration"> + <div class="_debobigegoLabel"><i class="fab fa-github"></i> GitHub</div> + <div class="_debobigegoPanel" style="padding: 16px;"> <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> <MkButton v-if="integrations.github" @click="disconnectGithub" danger>{{ $ts.disconnectService }}</MkButton> <MkButton v-else @click="connectGithub" primary>{{ $ts.connectService }}</MkButton> @@ -32,7 +32,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; import { apiUrl } from '@client/config'; -import FormBase from '@client/components/form/base.vue'; +import FormBase from '@client/components/debobigego/base.vue'; import MkButton from '@client/components/ui/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -49,7 +49,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.integration, - icon: 'fas fa-share-alt' + icon: 'fas fa-share-alt', + bg: 'var(--bg)', }, apiUrl, twitterForm: null, diff --git a/src/client/pages/settings/menu.vue b/src/client/pages/settings/menu.vue index 4b315145e1..31472eb0c1 100644 --- a/src/client/pages/settings/menu.vue +++ b/src/client/pages/settings/menu.vue @@ -1,6 +1,6 @@ <template> <FormBase> - <FormTextarea v-model:value="items" tall manual-save> + <FormTextarea v-model="items" tall manual-save> <span>{{ $ts.menu }}</span> <template #desc><button class="_textButton" @click="addItem">{{ $ts.addItem }}</button></template> </FormTextarea> @@ -19,12 +19,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormTextarea from '@client/components/form/textarea.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 FormButton from '@client/components/form/button.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormRadios from '@client/components/debobigego/radios.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import { menuDef } from '@client/menu'; import { defaultStore } from '@client/store'; @@ -45,7 +43,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.menu, - icon: 'fas fa-list-ul' + icon: 'fas fa-list-ul', + bg: 'var(--bg)', }, menuDef: menuDef, items: defaultStore.state.menu.join('\n'), diff --git a/src/client/pages/settings/mute-block.vue b/src/client/pages/settings/mute-block.vue index dde0199e18..18b2fc0af4 100644 --- a/src/client/pages/settings/mute-block.vue +++ b/src/client/pages/settings/mute-block.vue @@ -1,6 +1,6 @@ <template> <FormBase> - <MkTab v-model:value="tab" style="margin-bottom: var(--margin);"> + <MkTab v-model="tab" style="margin-bottom: var(--margin);"> <option value="mute">{{ $ts.mutedUsers }}</option> <option value="block">{{ $ts.blockedUsers }}</option> </MkTab> @@ -35,10 +35,10 @@ import { defineComponent } from 'vue'; import MkPagination from '@client/components/ui/pagination.vue'; import MkTab from '@client/components/tab.vue'; -import FormInfo from '@client/components/form/info.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import { userPage } from '@client/filters/user'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -59,7 +59,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.muteAndBlock, - icon: 'fas fa-ban' + icon: 'fas fa-ban', + bg: 'var(--bg)', }, tab: 'mute', mutingPagination: { diff --git a/src/client/pages/settings/notifications.vue b/src/client/pages/settings/notifications.vue index ec95452ba2..5f84349474 100644 --- a/src/client/pages/settings/notifications.vue +++ b/src/client/pages/settings/notifications.vue @@ -11,11 +11,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormButton from '@client/components/form/button.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import { notificationTypes } from '../../../types'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import { notificationTypes } from '@/types'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -33,7 +33,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.notifications, - icon: 'fas fa-bell' + icon: 'fas fa-bell', + bg: 'var(--bg)', }, } }, diff --git a/src/client/pages/settings/other.vue b/src/client/pages/settings/other.vue index 21b5439041..2eb922453f 100644 --- a/src/client/pages/settings/other.vue +++ b/src/client/pages/settings/other.vue @@ -2,18 +2,18 @@ <FormBase> <FormLink to="/settings/update">Misskey Update</FormLink> - <FormSwitch :value="$i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote"> + <FormSwitch :value="$i.injectFeaturedNote" @update:modelValue="onChangeInjectFeaturedNote"> {{ $ts.showFeaturedNotesInTimeline }} </FormSwitch> - <FormSwitch v-model:value="reportError">{{ $ts.sendErrorReports }}<template #desc>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> + <FormSwitch v-model="reportError">{{ $ts.sendErrorReports }}<template #desc>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> <FormLink to="/settings/account-info">{{ $ts.accountInfo }}</FormLink> <FormLink to="/settings/experimental-features">{{ $ts.experimentalFeatures }}</FormLink> <FormGroup> <template #label>{{ $ts.developer }}</template> - <FormSwitch v-model:value="debug" @update:value="changeDebug"> + <FormSwitch v-model="debug" @update:modelValue="changeDebug"> DEBUG MODE </FormSwitch> <template v-if="debug"> @@ -34,10 +34,10 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import { debug } from '@client/config'; import { defaultStore } from '@client/store'; @@ -60,7 +60,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.other, - icon: 'fas fa-ellipsis-h' + icon: 'fas fa-ellipsis-h', + bg: 'var(--bg)', }, debug, } diff --git a/src/client/pages/settings/plugin.install.vue b/src/client/pages/settings/plugin.install.vue index 30cbf58ad7..709ef11abb 100644 --- a/src/client/pages/settings/plugin.install.vue +++ b/src/client/pages/settings/plugin.install.vue @@ -3,7 +3,7 @@ <FormInfo warn>{{ $ts._plugin.installWarn }}</FormInfo> <FormGroup> - <FormTextarea v-model:value="code" tall> + <FormTextarea v-model="code" tall> <span>{{ $ts.code }}</span> </FormTextarea> </FormGroup> @@ -20,11 +20,11 @@ import { v4 as uuid } from 'uuid'; 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 FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; import * as os from '@client/os'; import { ColdDeviceStorage } from '@client/store'; import { unisonReload } from '@client/scripts/unison-reload'; @@ -48,7 +48,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts._plugin.install, - icon: 'fas fa-download' + icon: 'fas fa-download', + bg: 'var(--bg)', }, code: null, } diff --git a/src/client/pages/settings/plugin.manage.vue b/src/client/pages/settings/plugin.manage.vue index 3df87ca084..f1c27f1e3c 100644 --- a/src/client/pages/settings/plugin.manage.vue +++ b/src/client/pages/settings/plugin.manage.vue @@ -3,9 +3,9 @@ <FormGroup v-for="plugin in plugins" :key="plugin.id"> <template #label><span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span></template> - <FormSwitch :value="plugin.active" @update:value="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> - <div class="_formItem"> - <div class="_formPanel" style="padding: 16px;"> + <FormSwitch :value="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> + <div class="_debobigegoItem"> + <div class="_debobigegoPanel" style="padding: 16px;"> <div class="_keyValue"> <div>{{ $ts.author }}:</div> <div>{{ plugin.author }}</div> @@ -20,8 +20,8 @@ </div> </div> </div> - <div class="_formItem"> - <div class="_formPanel" style="padding: 16px;"> + <div class="_debobigegoItem"> + <div class="_debobigegoPanel" style="padding: 16px;"> <MkButton @click="config(plugin)" inline v-if="plugin.config"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> <MkButton @click="uninstall(plugin)" inline danger><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> </div> @@ -33,11 +33,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkSelect from '@client/components/ui/select.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkSelect from '@client/components/form/select.vue'; import FormSwitch from '@client/components/form/switch.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import * as os from '@client/os'; import { ColdDeviceStorage } from '@client/store'; import * as symbols from '@client/symbols'; @@ -58,7 +58,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts._plugin.manage, - icon: 'fas fa-plug' + icon: 'fas fa-plug', + bg: 'var(--bg)', }, plugins: ColdDeviceStorage.get('plugins'), } diff --git a/src/client/pages/settings/plugin.vue b/src/client/pages/settings/plugin.vue index 13eaca07fd..23f263bbbd 100644 --- a/src/client/pages/settings/plugin.vue +++ b/src/client/pages/settings/plugin.vue @@ -7,9 +7,9 @@ <script lang="ts"> import { defineComponent } from '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 FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormLink from '@client/components/debobigego/link.vue'; import * as os from '@client/os'; import { ColdDeviceStorage } from '@client/store'; import * as symbols from '@client/symbols'; @@ -26,7 +26,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.plugins, - icon: 'fas fa-plug' + icon: 'fas fa-plug', + bg: 'var(--bg)', }, plugins: ColdDeviceStorage.get('plugins').length, } diff --git a/src/client/pages/settings/privacy.vue b/src/client/pages/settings/privacy.vue index 46d8c17ca2..7756158578 100644 --- a/src/client/pages/settings/privacy.vue +++ b/src/client/pages/settings/privacy.vue @@ -1,43 +1,43 @@ <template> <FormBase> <FormGroup> - <FormSwitch v-model:value="isLocked" @update:value="save()">{{ $ts.makeFollowManuallyApprove }}</FormSwitch> - <FormSwitch v-model:value="autoAcceptFollowed" :disabled="!isLocked" @update:value="save()">{{ $ts.autoAcceptFollowed }}</FormSwitch> + <FormSwitch v-model="isLocked" @update:modelValue="save()">{{ $ts.makeFollowManuallyApprove }}</FormSwitch> + <FormSwitch v-model="autoAcceptFollowed" :disabled="!isLocked" @update:modelValue="save()">{{ $ts.autoAcceptFollowed }}</FormSwitch> <template #caption>{{ $ts.lockedAccountInfo }}</template> </FormGroup> - <FormSwitch v-model:value="hideOnlineStatus" @update:value="save()"> + <FormSwitch v-model="hideOnlineStatus" @update:modelValue="save()"> {{ $ts.hideOnlineStatus }} <template #desc>{{ $ts.hideOnlineStatusDescription }}</template> </FormSwitch> - <FormSwitch v-model:value="noCrawle" @update:value="save()"> + <FormSwitch v-model="noCrawle" @update:modelValue="save()"> {{ $ts.noCrawle }} <template #desc>{{ $ts.noCrawleDescription }}</template> </FormSwitch> - <FormSwitch v-model:value="isExplorable" @update:value="save()"> + <FormSwitch v-model="isExplorable" @update:modelValue="save()"> {{ $ts.makeExplorable }} <template #desc>{{ $ts.makeExplorableDescription }}</template> </FormSwitch> - <FormSwitch v-model:value="rememberNoteVisibility" @update:value="save()">{{ $ts.rememberNoteVisibility }}</FormSwitch> + <FormSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ $ts.rememberNoteVisibility }}</FormSwitch> <FormGroup v-if="!rememberNoteVisibility"> <template #label>{{ $ts.defaultNoteVisibility }}</template> - <FormSelect v-model:value="defaultNoteVisibility"> + <FormSelect v-model="defaultNoteVisibility"> <option value="public">{{ $ts._visibility.public }}</option> <option value="home">{{ $ts._visibility.home }}</option> <option value="followers">{{ $ts._visibility.followers }}</option> <option value="specified">{{ $ts._visibility.specified }}</option> </FormSelect> - <FormSwitch v-model:value="defaultNoteLocalOnly">{{ $ts._visibility.localOnly }}</FormSwitch> + <FormSwitch v-model="defaultNoteLocalOnly">{{ $ts._visibility.localOnly }}</FormSwitch> </FormGroup> - <FormSwitch v-model:value="keepCw" @update:value="save()">{{ $ts.keepCw }}</FormSwitch> + <FormSwitch v-model="keepCw" @update:modelValue="save()">{{ $ts.keepCw }}</FormSwitch> </FormBase> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormSelect from '@client/components/form/select.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormSelect from '@client/components/debobigego/select.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import * as os from '@client/os'; import { defaultStore } from '@client/store'; import * as symbols from '@client/symbols'; @@ -56,7 +56,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.privacy, - icon: 'fas fa-lock-open' + icon: 'fas fa-lock-open', + bg: 'var(--bg)', }, isLocked: false, autoAcceptFollowed: false, diff --git a/src/client/pages/settings/profile.vue b/src/client/pages/settings/profile.vue index de7e86bd12..b993b5fc72 100644 --- a/src/client/pages/settings/profile.vue +++ b/src/client/pages/settings/profile.vue @@ -1,33 +1,33 @@ <template> <FormBase> <FormGroup> - <div class="_formItem _formPanel llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> + <div class="_debobigegoItem _debobigegoPanel llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> <MkAvatar class="avatar" :user="$i"/> </div> <FormButton @click="changeAvatar" primary>{{ $ts._profile.changeAvatar }}</FormButton> <FormButton @click="changeBanner" primary>{{ $ts._profile.changeBanner }}</FormButton> </FormGroup> - <FormInput v-model:value="name" :max="30" manual-save> + <FormInput v-model="name" :max="30" manual-save> <span>{{ $ts._profile.name }}</span> </FormInput> - <FormTextarea v-model:value="description" :max="500" tall manual-save> + <FormTextarea v-model="description" :max="500" tall manual-save> <span>{{ $ts._profile.description }}</span> <template #desc>{{ $ts._profile.youCanIncludeHashtags }}</template> </FormTextarea> - <FormInput v-model:value="location" manual-save> + <FormInput v-model="location" manual-save> <span>{{ $ts.location }}</span> <template #prefix><i class="fas fa-map-marker-alt"></i></template> </FormInput> - <FormInput v-model:value="birthday" type="date" manual-save> + <FormInput v-model="birthday" type="date" manual-save> <span>{{ $ts.birthday }}</span> <template #prefix><i class="fas fa-birthday-cake"></i></template> </FormInput> - <FormSelect v-model:value="lang"> + <FormSelect v-model="lang"> <template #label>{{ $ts.language }}</template> <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option> </FormSelect> @@ -37,23 +37,23 @@ <template #caption>{{ $ts._profile.metadataDescription }}</template> </FormGroup> - <FormSwitch v-model:value="isCat">{{ $ts.flagAsCat }}<template #desc>{{ $ts.flagAsCatDescription }}</template></FormSwitch> + <FormSwitch v-model="isCat">{{ $ts.flagAsCat }}<template #desc>{{ $ts.flagAsCatDescription }}</template></FormSwitch> - <FormSwitch v-model:value="isBot">{{ $ts.flagAsBot }}<template #desc>{{ $ts.flagAsBotDescription }}</template></FormSwitch> + <FormSwitch v-model="isBot">{{ $ts.flagAsBot }}<template #desc>{{ $ts.flagAsBotDescription }}</template></FormSwitch> - <FormSwitch v-model:value="alwaysMarkNsfw">{{ $ts.alwaysMarkSensitive }}</FormSwitch> + <FormSwitch v-model="alwaysMarkNsfw">{{ $ts.alwaysMarkSensitive }}</FormSwitch> </FormBase> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormButton from '@client/components/form/button.vue'; -import FormInput from '@client/components/form/input.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormSelect from '@client/components/form/select.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormSelect from '@client/components/debobigego/select.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import { host, langs } from '@client/config'; import { selectFile } from '@client/scripts/select-file'; import * as os from '@client/os'; @@ -76,7 +76,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.profile, - icon: 'fas fa-user' + icon: 'fas fa-user', + bg: 'var(--bg)', }, host, langs, diff --git a/src/client/pages/settings/reaction.vue b/src/client/pages/settings/reaction.vue index a0024234e4..a5ff46097d 100644 --- a/src/client/pages/settings/reaction.vue +++ b/src/client/pages/settings/reaction.vue @@ -1,8 +1,8 @@ <template> <FormBase> - <div class="_formItem"> - <div class="_formLabel">{{ $ts.reactionSettingDescription }}</div> - <div class="_formPanel"> + <div class="_debobigegoItem"> + <div class="_debobigegoLabel">{{ $ts.reactionSettingDescription }}</div> + <div class="_debobigegoPanel"> <XDraggable class="zoaiodol" v-model="reactions" :item-key="item => item" animation="150" delay="100" delay-on-touch-only="true"> <template #item="{element}"> <button class="_button item" @click="remove(element, $event)"> @@ -14,7 +14,7 @@ </template> </XDraggable> </div> - <div class="_formCaption">{{ $ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ $ts.preview }}</button></div> + <div class="_debobigegoCaption">{{ $ts.reactionSettingDescription2 }} <button class="_textButton" @click="preview">{{ $ts.preview }}</button></div> </div> <FormRadios v-model="reactionPickerWidth"> @@ -37,10 +37,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XDraggable from 'vuedraggable'; -import FormInput from '@client/components/form/input.vue'; -import FormRadios from '@client/components/form/radios.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormInput from '@client/components/debobigego/input.vue'; +import FormRadios from '@client/components/debobigego/radios.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import * as os from '@client/os'; import { defaultStore } from '@client/store'; import * as symbols from '@client/symbols'; @@ -64,7 +64,8 @@ export default defineComponent({ action: { icon: 'fas fa-eye', handler: this.preview - } + }, + bg: 'var(--bg)', }, reactions: JSON.parse(JSON.stringify(this.$store.state.reactions)), } diff --git a/src/client/pages/settings/registry.keys.vue b/src/client/pages/settings/registry.keys.vue index f71589ba4f..d99002e50f 100644 --- a/src/client/pages/settings/registry.keys.vue +++ b/src/client/pages/settings/registry.keys.vue @@ -25,11 +25,11 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import * as JSON5 from 'json5'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -56,7 +56,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.registry, - icon: 'fas fa-cogs' + icon: 'fas fa-cogs', + bg: 'var(--bg)', }, keys: null, } diff --git a/src/client/pages/settings/registry.value.vue b/src/client/pages/settings/registry.value.vue index 48245ae99f..06be5737e9 100644 --- a/src/client/pages/settings/registry.value.vue +++ b/src/client/pages/settings/registry.value.vue @@ -19,7 +19,7 @@ </FormGroup> <FormGroup> - <FormTextarea tall v-model:value="valueForEditor" class="_monospace" style="tab-size: 2;"> + <FormTextarea tall v-model="valueForEditor" class="_monospace" style="tab-size: 2;"> <span>{{ $ts.value }} (JSON)</span> </FormTextarea> <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> @@ -38,14 +38,14 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; import * as JSON5 from 'json5'; -import FormInfo from '@client/components/form/info.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; import FormTextarea from '@client/components/form/textarea.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -76,7 +76,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.registry, - icon: 'fas fa-cogs' + icon: 'fas fa-cogs', + bg: 'var(--bg)', }, value: null, valueForEditor: null, diff --git a/src/client/pages/settings/registry.vue b/src/client/pages/settings/registry.vue index 5ba1bc751b..e4fb230d5c 100644 --- a/src/client/pages/settings/registry.vue +++ b/src/client/pages/settings/registry.vue @@ -13,11 +13,11 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import * as JSON5 from 'json5'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -38,7 +38,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.registry, - icon: 'fas fa-cogs' + icon: 'fas fa-cogs', + bg: 'var(--bg)', }, scopes: null, } diff --git a/src/client/pages/settings/security.vue b/src/client/pages/settings/security.vue index b70fa5a9f3..e051685a82 100644 --- a/src/client/pages/settings/security.vue +++ b/src/client/pages/settings/security.vue @@ -6,7 +6,7 @@ <FormPagination :pagination="pagination"> <template #label>{{ $ts.signinHistory }}</template> <template #default="{items}"> - <div class="_formPanel timnmucd" v-for="item in items" :key="item.id"> + <div class="_debobigegoPanel timnmucd" v-for="item in items" :key="item.id"> <header> <i v-if="item.success" class="fas fa-check icon succ"></i> <i v-else class="fas fa-times-circle icon fail"></i> @@ -25,11 +25,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormBase from '@client/components/form/base.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormPagination from '@client/components/form/pagination.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormPagination from '@client/components/debobigego/pagination.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -48,7 +48,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.security, - icon: 'fas fa-lock' + icon: 'fas fa-lock', + bg: 'var(--bg)', }, pagination: { endpoint: 'i/signin-history', diff --git a/src/client/pages/settings/sounds.vue b/src/client/pages/settings/sounds.vue index 1c51685ce8..07310619c8 100644 --- a/src/client/pages/settings/sounds.vue +++ b/src/client/pages/settings/sounds.vue @@ -1,6 +1,6 @@ <template> <FormBase> - <FormRange v-model:value="masterVolume" :min="0" :max="1" :step="0.05"> + <FormRange v-model="masterVolume" :min="0" :max="1" :step="0.05"> <template #label><i class="fas fa-volume-icon"></i> {{ $ts.masterVolume }}</template> </FormRange> @@ -19,11 +19,11 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import FormRange from '@client/components/form/range.vue'; -import FormSelect from '@client/components/form/select.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormRange from '@client/components/debobigego/range.vue'; +import FormSelect from '@client/components/debobigego/select.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import * as os from '@client/os'; import { ColdDeviceStorage } from '@client/store'; import { playFile } from '@client/scripts/sound'; @@ -71,7 +71,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.sounds, - icon: 'fas fa-music' + icon: 'fas fa-music', + bg: 'var(--bg)', }, sounds: {}, } diff --git a/src/client/pages/settings/theme.install.vue b/src/client/pages/settings/theme.install.vue index d719cc801f..9fbb28929d 100644 --- a/src/client/pages/settings/theme.install.vue +++ b/src/client/pages/settings/theme.install.vue @@ -1,7 +1,7 @@ <template> <FormBase> <FormGroup> - <FormTextarea v-model:value="installThemeCode"> + <FormTextarea v-model="installThemeCode"> <span>{{ $ts._theme.code }}</span> </FormTextarea> <FormButton @click="() => preview(installThemeCode)" :disabled="installThemeCode == null" inline><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton> @@ -17,10 +17,10 @@ import * as JSON5 from 'json5'; 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 FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import { applyTheme, validateTheme } from '@client/scripts/theme'; import * as os from '@client/os'; import { ColdDeviceStorage } from '@client/store'; @@ -44,7 +44,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts._theme.install, - icon: 'fas fa-download' + icon: 'fas fa-download', + bg: 'var(--bg)', }, installThemeCode: null, } diff --git a/src/client/pages/settings/theme.manage.vue b/src/client/pages/settings/theme.manage.vue index 7cc7a0169a..da21a47a50 100644 --- a/src/client/pages/settings/theme.manage.vue +++ b/src/client/pages/settings/theme.manage.vue @@ -1,6 +1,6 @@ <template> <FormBase> - <FormSelect v-model:value="selectedThemeId"> + <FormSelect v-model="selectedThemeId"> <template #label>{{ $ts.theme }}</template> <optgroup :label="$ts._theme.installedThemes"> <option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option> @@ -31,10 +31,10 @@ import * as JSON5 from 'json5'; 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 FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import FormInput from '@client/components/form/input.vue'; -import FormButton from '@client/components/form/button.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import { Theme, builtinThemes } from '@client/scripts/theme'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import * as os from '@client/os'; @@ -59,7 +59,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts._theme.manage, - icon: 'fas fa-folder-open' + icon: 'fas fa-folder-open', + bg: 'var(--bg)', }, installedThemes: getThemes(), builtinThemes, diff --git a/src/client/pages/settings/theme.vue b/src/client/pages/settings/theme.vue index 94eddb1b6f..c6be42251c 100644 --- a/src/client/pages/settings/theme.vue +++ b/src/client/pages/settings/theme.vue @@ -1,7 +1,7 @@ <template> <FormBase> <FormGroup> - <div class="rfqxtzch _formItem _formPanel"> + <div class="rfqxtzch _debobigegoItem _debobigegoPanel"> <div class="darkMode"> <div class="toggleWrapper"> <input type="checkbox" class="dn" id="dn" v-model="darkMode"/> @@ -23,11 +23,11 @@ </div> </div> </div> - <FormSwitch v-model:value="syncDeviceDarkMode">{{ $ts.syncDeviceDarkMode }}</FormSwitch> + <FormSwitch v-model="syncDeviceDarkMode">{{ $ts.syncDeviceDarkMode }}</FormSwitch> </FormGroup> <template v-if="darkMode"> - <FormSelect v-model:value="darkThemeId"> + <FormSelect v-model="darkThemeId"> <template #label>{{ $ts.themeForDarkMode }}</template> <optgroup :label="$ts.darkThemes"> <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option> @@ -36,7 +36,7 @@ <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option> </optgroup> </FormSelect> - <FormSelect v-model:value="lightThemeId"> + <FormSelect v-model="lightThemeId"> <template #label>{{ $ts.themeForLightMode }}</template> <optgroup :label="$ts.lightThemes"> <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option> @@ -47,7 +47,7 @@ </FormSelect> </template> <template v-else> - <FormSelect v-model:value="lightThemeId"> + <FormSelect v-model="lightThemeId"> <template #label>{{ $ts.themeForLightMode }}</template> <optgroup :label="$ts.lightThemes"> <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option> @@ -56,7 +56,7 @@ <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option> </optgroup> </FormSelect> - <FormSelect v-model:value="darkThemeId"> + <FormSelect v-model="darkThemeId"> <template #label>{{ $ts.themeForDarkMode }}</template> <optgroup :label="$ts.darkThemes"> <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option> @@ -86,12 +86,12 @@ <script lang="ts"> import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormSelect from '@client/components/form/select.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 FormSwitch from '@client/components/debobigego/switch.vue'; +import FormSelect from '@client/components/debobigego/select.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormButton from '@client/components/debobigego/button.vue'; import { builtinThemes } from '@client/scripts/theme'; import { selectFile } from '@client/scripts/select-file'; import { isDeviceDarkmode } from '@client/scripts/is-device-darkmode'; @@ -116,7 +116,8 @@ export default defineComponent({ setup(props, { emit }) { const INFO = { title: i18n.locale.theme, - icon: 'fas fa-palette' + icon: 'fas fa-palette', + bg: 'var(--bg)', }; const installedThemes = ref(getThemes()); diff --git a/src/client/pages/settings/update.vue b/src/client/pages/settings/update.vue index 8000327d0c..8bc459e936 100644 --- a/src/client/pages/settings/update.vue +++ b/src/client/pages/settings/update.vue @@ -32,12 +32,12 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import FormSwitch from '@client/components/form/switch.vue'; import FormSelect from '@client/components/form/select.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormInfo from '@client/components/form/info.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; import * as os from '@client/os'; import { version, instanceName } from '@client/config'; import * as symbols from '@client/symbols'; @@ -60,7 +60,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: 'Misskey Update', - icon: 'fas fa-sync-alt' + icon: 'fas fa-sync-alt', + bg: 'var(--bg)', }, version, instanceName, diff --git a/src/client/pages/settings/word-mute.vue b/src/client/pages/settings/word-mute.vue index fe3fece844..53948b1b1e 100644 --- a/src/client/pages/settings/word-mute.vue +++ b/src/client/pages/settings/word-mute.vue @@ -1,21 +1,21 @@ <template> <div> - <MkTab v-model:value="tab"> + <MkTab v-model="tab"> <option value="soft">{{ $ts._wordMute.soft }}</option> <option value="hard">{{ $ts._wordMute.hard }}</option> </MkTab> <FormBase> - <div class="_formItem"> + <div class="_debobigegoItem"> <div v-show="tab === 'soft'"> <FormInfo>{{ $ts._wordMute.softDescription }}</FormInfo> - <FormTextarea v-model:value="softMutedWords"> + <FormTextarea v-model="softMutedWords"> <span>{{ $ts._wordMute.muteWords }}</span> <template #desc>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> </div> <div v-show="tab === 'hard'"> <FormInfo>{{ $ts._wordMute.hardDescription }}</FormInfo> - <FormTextarea v-model:value="hardMutedWords"> + <FormTextarea v-model="hardMutedWords"> <span>{{ $ts._wordMute.muteWords }}</span> <template #desc>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> @@ -33,10 +33,10 @@ <script lang="ts"> import { defineComponent } from 'vue'; import FormTextarea from '@client/components/form/textarea.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormInfo from '@client/components/form/info.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormInfo from '@client/components/debobigego/info.vue'; import MkTab from '@client/components/tab.vue'; import * as os from '@client/os'; import number from '@client/filters/number'; @@ -58,7 +58,8 @@ export default defineComponent({ return { [symbols.PAGE_INFO]: { title: this.$ts.wordMute, - icon: 'fas fa-comment-slash' + icon: 'fas fa-comment-slash', + bg: 'var(--bg)', }, tab: 'soft', softMutedWords: '', diff --git a/src/client/pages/signup-complete.vue b/src/client/pages/signup-complete.vue new file mode 100644 index 0000000000..dada92031a --- /dev/null +++ b/src/client/pages/signup-complete.vue @@ -0,0 +1,50 @@ +<template> +<div> + {{ $ts.processing }} +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import * as os from '@client/os'; +import * as symbols from '@client/symbols'; +import { login } from '@client/account'; + +export default defineComponent({ + components: { + + }, + + props: { + code: { + type: String, + required: true + } + }, + + data() { + return { + [symbols.PAGE_INFO]: { + title: this.$ts.signup, + icon: 'fas fa-user' + }, + } + }, + + mounted() { + os.apiWithDialog('signup-pending', { + code: this.code, + }).then(res => { + login(res.i, '/'); + }); + }, + + methods: { + + } +}); +</script> + +<style lang="scss" scoped> + +</style> diff --git a/src/client/pages/test.vue b/src/client/pages/test.vue index 131571e9dd..fbab0112ed 100644 --- a/src/client/pages/test.vue +++ b/src/client/pages/test.vue @@ -133,10 +133,10 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; -import MkSwitch from '@client/components/ui/switch.vue'; -import MkTextarea from '@client/components/ui/textarea.vue'; -import MkRadio from '@client/components/ui/radio.vue'; +import MkInput from '@client/components/form/input.vue'; +import MkSwitch from '@client/components/form/switch.vue'; +import MkTextarea from '@client/components/form/textarea.vue'; +import MkRadio from '@client/components/form/radio.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; diff --git a/src/client/pages/theme-editor.vue b/src/client/pages/theme-editor.vue index ce8bae4ff5..3b10396ab8 100644 --- a/src/client/pages/theme-editor.vue +++ b/src/client/pages/theme-editor.vue @@ -1,8 +1,8 @@ <template> <FormBase class="cwepdizn"> - <div class="_formItem colorPicker"> - <div class="_formLabel">{{ $ts.backgroundColor }}</div> - <div class="_formPanel colors"> + <div class="_debobigegoItem colorPicker"> + <div class="_debobigegoLabel">{{ $ts.backgroundColor }}</div> + <div class="_debobigegoPanel colors"> <div class="row"> <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" @click="setBgColor(color)" class="color _button" :class="{ active: theme.props.bg === color.color }"> <div class="preview" :style="{ background: color.forPreview }"></div> @@ -15,9 +15,9 @@ </div> </div> </div> - <div class="_formItem colorPicker"> - <div class="_formLabel">{{ $ts.accentColor }}</div> - <div class="_formPanel colors"> + <div class="_debobigegoItem colorPicker"> + <div class="_debobigegoLabel">{{ $ts.accentColor }}</div> + <div class="_debobigegoPanel colors"> <div class="row"> <button v-for="color in accentColors" :key="color" @click="setAccentColor(color)" class="color rounded _button" :class="{ active: theme.props.accent === color }"> <div class="preview" :style="{ background: color }"></div> @@ -25,9 +25,9 @@ </div> </div> </div> - <div class="_formItem colorPicker"> - <div class="_formLabel">{{ $ts.textColor }}</div> - <div class="_formPanel colors"> + <div class="_debobigegoItem colorPicker"> + <div class="_debobigegoLabel">{{ $ts.textColor }}</div> + <div class="_debobigegoPanel colors"> <div class="row"> <button v-for="color in fgColors" :key="color" @click="setFgColor(color)" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }"> <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div> @@ -37,7 +37,7 @@ </div> <FormGroup v-if="codeEnabled"> - <FormTextarea v-model:value="themeCode" tall> + <FormTextarea v-model="themeCode" tall> <span>{{ $ts._theme.code }}</span> </FormTextarea> <FormButton @click="applyThemeCode" primary>{{ $ts.apply }}</FormButton> @@ -45,7 +45,7 @@ <FormButton v-else @click="codeEnabled = true"><i class="fas fa-code"></i> {{ $ts.editCode }}</FormButton> <FormGroup v-if="descriptionEnabled"> - <FormTextarea v-model:value="description"> + <FormTextarea v-model="description"> <span>{{ $ts._theme.description }}</span> </FormTextarea> </FormGroup> @@ -65,10 +65,10 @@ import * as tinycolor from 'tinycolor2'; import { v4 as uuid} from 'uuid'; import * as JSON5 from 'json5'; -import FormBase from '@client/components/form/base.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormGroup from '@client/components/form/group.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; import { Theme, applyTheme, validateTheme, darkTheme, lightTheme } from '@client/scripts/theme'; import { host } from '@client/config'; diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue index 9dda82462d..dfabcbf84b 100644 --- a/src/client/pages/timeline.vue +++ b/src/client/pages/timeline.vue @@ -1,18 +1,21 @@ <template> -<div class="cmuxhskf" v-hotkey.global="keymap" v-size="{ min: [800] }"> - <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/> - <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/> +<div v-hotkey.global="keymap"> + <MkHeader :info="header"/> + <div class="cmuxhskf" v-size="{ min: [800] }"> + <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/> + <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/> - <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> - <div class="tl _block"> - <XTimeline ref="tl" class="tl" - :key="src" - :src="src" - :sound="true" - @before="before()" - @after="after()" - @queue="queueUpdated" - /> + <div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> + <div class="tl _block"> + <XTimeline ref="tl" class="tl" + :key="src" + :src="src" + :sound="true" + @before="before()" + @after="after()" + @queue="queueUpdated" + /> + </div> </div> </div> </template> @@ -43,6 +46,11 @@ export default defineComponent({ title: this.$ts.timeline, icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home', bg: 'var(--bg)', + })), + header: computed(() => ({ + title: this.$ts.timeline, + icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home', + bg: 'var(--bg)', actions: [{ icon: 'fas fa-list-ul', text: this.$ts.lists, @@ -129,7 +137,7 @@ export default defineComponent({ }, top() { - scroll(this.$el, 0); + scroll(this.$el, { top: 0 }); }, async chooseList(ev) { @@ -207,6 +215,10 @@ export default defineComponent({ } } + > .post-form { + border-radius: var(--radius); + } + > .tl { background: var(--bg); border-radius: var(--radius); diff --git a/src/client/pages/user-ap-info.vue b/src/client/pages/user-ap-info.vue index c08a352571..cbdff874ed 100644 --- a/src/client/pages/user-ap-info.vue +++ b/src/client/pages/user-ap-info.vue @@ -58,14 +58,14 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormObjectView from '@client/components/form/object-view.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormObjectView from '@client/components/debobigego/object-view.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import number from '@client/filters/number'; import bytes from '@client/filters/bytes'; diff --git a/src/client/pages/user-info.vue b/src/client/pages/user-info.vue index 503982652b..bf67fc853a 100644 --- a/src/client/pages/user-info.vue +++ b/src/client/pages/user-info.vue @@ -1,7 +1,7 @@ <template> <FormBase> <FormSuspense :p="init"> - <div class="_formItem aeakzknw"> + <div class="_debobigegoItem aeakzknw"> <MkAvatar class="avatar" :user="user" :show-indicator="true"/> </div> @@ -20,9 +20,9 @@ </FormGroup> <FormGroup v-if="iAmModerator"> - <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" @update:value="toggleModerator" v-model:value="moderator">{{ $ts.moderator }}</FormSwitch> - <FormSwitch @update:value="toggleSilence" v-model:value="silenced">{{ $ts.silence }}</FormSwitch> - <FormSwitch @update:value="toggleSuspend" v-model:value="suspended">{{ $ts.suspend }}</FormSwitch> + <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" @update:modelValue="toggleModerator" v-model="moderator">{{ $ts.moderator }}</FormSwitch> + <FormSwitch @update:modelValue="toggleSilence" v-model="silenced">{{ $ts.silence }}</FormSwitch> + <FormSwitch @update:modelValue="toggleSuspend" v-model="suspended">{{ $ts.suspend }}</FormSwitch> </FormGroup> <FormGroup> @@ -56,15 +56,15 @@ <script lang="ts"> import { computed, defineAsyncComponent, defineComponent } from 'vue'; -import FormObjectView from '@client/components/form/object-view.vue'; -import FormTextarea from '@client/components/form/textarea.vue'; -import FormSwitch from '@client/components/form/switch.vue'; -import FormLink from '@client/components/form/link.vue'; -import FormBase from '@client/components/form/base.vue'; -import FormGroup from '@client/components/form/group.vue'; -import FormButton from '@client/components/form/button.vue'; -import FormKeyValueView from '@client/components/form/key-value-view.vue'; -import FormSuspense from '@client/components/form/suspense.vue'; +import FormObjectView from '@client/components/debobigego/object-view.vue'; +import FormTextarea from '@client/components/debobigego/textarea.vue'; +import FormSwitch from '@client/components/debobigego/switch.vue'; +import FormLink from '@client/components/debobigego/link.vue'; +import FormBase from '@client/components/debobigego/base.vue'; +import FormGroup from '@client/components/debobigego/group.vue'; +import FormButton from '@client/components/debobigego/button.vue'; +import FormKeyValueView from '@client/components/debobigego/key-value-view.vue'; +import FormSuspense from '@client/components/debobigego/suspense.vue'; import * as os from '@client/os'; import number from '@client/filters/number'; import bytes from '@client/filters/bytes'; diff --git a/src/client/pages/user-list-timeline.vue b/src/client/pages/user-list-timeline.vue index 491fe948c1..b5e37d4843 100644 --- a/src/client/pages/user-list-timeline.vue +++ b/src/client/pages/user-list-timeline.vue @@ -89,7 +89,7 @@ export default defineComponent({ }, top() { - scroll(this.$el, 0); + scroll(this.$el, { top: 0 }); }, settings() { diff --git a/src/client/pages/user/clips.vue b/src/client/pages/user/clips.vue index fc40d583c6..53ee554383 100644 --- a/src/client/pages/user/clips.vue +++ b/src/client/pages/user/clips.vue @@ -12,7 +12,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkPagination from '@client/components/ui/pagination.vue'; -import { userPage, acct } from '@client/filters/user'; export default defineComponent({ components: { @@ -43,12 +42,6 @@ export default defineComponent({ this.$refs.list.reload(); } }, - - methods: { - userPage, - - acct - } }); </script> diff --git a/src/client/pages/user/follow-list.vue b/src/client/pages/user/follow-list.vue index f6df28309f..1f5ab5993c 100644 --- a/src/client/pages/user/follow-list.vue +++ b/src/client/pages/user/follow-list.vue @@ -12,7 +12,6 @@ import { defineComponent } from 'vue'; import MkUserInfo from '@client/components/user-info.vue'; import MkPagination from '@client/components/ui/pagination.vue'; -import { userPage, acct } from '@client/filters/user'; export default defineComponent({ components: { @@ -51,12 +50,6 @@ export default defineComponent({ user() { this.$refs.list.reload(); } - }, - - methods: { - userPage, - - acct } }); </script> diff --git a/src/client/pages/user/gallery.vue b/src/client/pages/user/gallery.vue index 67a5fac109..c21b3e6428 100644 --- a/src/client/pages/user/gallery.vue +++ b/src/client/pages/user/gallery.vue @@ -12,7 +12,6 @@ import { defineComponent } from 'vue'; import MkGalleryPostPreview from '@client/components/gallery-post-preview.vue'; import MkPagination from '@client/components/ui/pagination.vue'; -import { userPage, acct } from '@client/filters/user'; export default defineComponent({ components: { @@ -43,12 +42,6 @@ export default defineComponent({ user() { this.$refs.list.reload(); } - }, - - methods: { - userPage, - - acct } }); </script> diff --git a/src/client/pages/user/index.timeline.vue b/src/client/pages/user/index.timeline.vue index 287e6c8b22..c3444f26f6 100644 --- a/src/client/pages/user/index.timeline.vue +++ b/src/client/pages/user/index.timeline.vue @@ -1,6 +1,6 @@ <template> <div class="yrzkoczt" v-sticky-container> - <MkTab v-model:value="with_" class="_gap tab"> + <MkTab v-model="with_" class="tab"> <option :value="null">{{ $ts.notes }}</option> <option value="replies">{{ $ts.notesAndReplies }}</option> <option value="files">{{ $ts.withFiles }}</option> @@ -60,6 +60,8 @@ export default defineComponent({ <style lang="scss" scoped> .yrzkoczt { > .tab { + margin: calc(var(--margin) / 2) 0; + padding: calc(var(--margin) / 2) 0; background: var(--bg); } } diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index 86dc7361b5..0ddf73d572 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -1,98 +1,117 @@ <template> -<transition name="fade" mode="out-in"> - <div class="ftskorzw wide" v-if="user && narrow === false"> - <MkRemoteCaution v-if="user.host != null" :href="user.url"/> +<div> + <MkHeader :info="header"/> + <transition name="fade" mode="out-in"> + <div class="ftskorzw wide" v-if="user && narrow === false"> + <MkRemoteCaution v-if="user.host != null" :href="user.url"/> - <div class="banner-container" :style="style"> - <div class="banner" ref="banner" :style="style"></div> - </div> - <div class="contents"> - <div class="side _forceContainerFull_"> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="name"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <MkAcct :user="user" :detail="true" class="acct"/> - </div> - <div class="followed" v-if="$i && $i.id != user.id && user.isFollowed"><span>{{ $ts.followsYou }}</span></div> - <div class="status"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> - </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl class="field" v-if="user.location"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl class="field" v-if="user.birthday"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div class="fields" v-if="user.fields.length > 0"> - <dl class="field" v-for="(field, i) in user.fields" :key="i"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <XActivity :user="user" :key="user.id" class="_gap"/> - <XPhotos :user="user" :key="user.id" class="_gap"/> + <div class="banner-container" :style="style"> + <div class="banner" ref="banner" :style="style"></div> </div> - <div class="main"> - <div class="actions"> - <button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> - </div> - <template v-if="page === 'index'"> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" class="note _gap" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/> + <div class="contents"> + <div class="side _forceContainerFull_"> + <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> + <div class="name"> + <MkUserName :user="user" :nowrap="false" class="name"/> + <MkAcct :user="user" :detail="true" class="acct"/> </div> - <div class="_gap"> - <XUserTimeline :user="user"/> + <div class="followed" v-if="$i && $i.id != user.id && user.isFollowed"><span>{{ $ts.followsYou }}</span></div> + <div class="status"> + <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> + <b>{{ number(user.notesCount) }}</b> + <span>{{ $ts.notes }}</span> + </MkA> + <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> + <b>{{ number(user.followingCount) }}</b> + <span>{{ $ts.following }}</span> + </MkA> + <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> + <b>{{ number(user.followersCount) }}</b> + <span>{{ $ts.followers }}</span> + </MkA> </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> + <div class="description"> + <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> + <p v-else class="empty">{{ $ts.noAccountDescription }}</p> + </div> + <div class="fields system"> + <dl class="field" v-if="user.location"> + <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> + <dd class="value">{{ user.location }}</dd> + </dl> + <dl class="field" v-if="user.birthday"> + <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> + <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> + </dl> + <dl class="field"> + <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> + <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> + </dl> + </div> + <div class="fields" v-if="user.fields.length > 0"> + <dl class="field" v-for="(field, i) in user.fields" :key="i"> + <dt class="name"> + <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> + </dt> + <dd class="value"> + <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> + </dd> + </dl> + </div> + <XActivity :user="user" :key="user.id" class="_gap"/> + <XPhotos :user="user" :key="user.id" class="_gap"/> + </div> + <div class="main"> + <div class="actions"> + <button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button> + <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> + </div> + <template v-if="page === 'index'"> + <div v-if="user.pinnedNotes.length > 0" class="_gap"> + <XNote v-for="note in user.pinnedNotes" class="note _gap" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/> + </div> + <div class="_gap"> + <XUserTimeline :user="user"/> + </div> + </template> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_gap"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_gap"/> + <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> + <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> + </div> </div> </div> - </div> - <div class="ftskorzw narrow _root" v-else-if="user && narrow === true" v-size="{ max: [500] }"> - <!-- TODO --> - <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> - <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> + <div class="ftskorzw narrow _root" v-else-if="user && narrow === true" v-size="{ max: [500] }"> + <!-- TODO --> + <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> + <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> - <div class="profile"> - <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> + <div class="profile"> + <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> - <div class="_block main" :key="user.id"> - <div class="banner-container" :style="style"> - <div class="banner" ref="banner" :style="style"></div> - <div class="fade"></div> + <div class="_block main" :key="user.id"> + <div class="banner-container" :style="style"> + <div class="banner" ref="banner" :style="style"></div> + <div class="fade"></div> + <div class="title"> + <MkUserName class="name" :user="user" :nowrap="true"/> + <div class="bottom"> + <span class="username"><MkAcct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> + <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> + <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + </div> + </div> + <span class="followed" v-if="$i && $i.id != user.id && user.isFollowed">{{ $ts.followsYou }}</span> + <div class="actions" v-if="$i"> + <button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button> + <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> + </div> + </div> + <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> <div class="title"> - <MkUserName class="name" :user="user" :nowrap="true"/> + <MkUserName :user="user" :nowrap="false" class="name"/> <div class="bottom"> <span class="username"><MkAcct :user="user" :detail="true" /></span> <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> @@ -101,92 +120,76 @@ <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> </div> </div> - <span class="followed" v-if="$i && $i.id != user.id && user.isFollowed">{{ $ts.followsYou }}</span> - <div class="actions" v-if="$i"> - <button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> + <div class="description"> + <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> + <p v-else class="empty">{{ $ts.noAccountDescription }}</p> </div> - </div> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="title"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <div class="bottom"> - <span class="username"><MkAcct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> - <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> - <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + <div class="fields system"> + <dl class="field" v-if="user.location"> + <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> + <dd class="value">{{ user.location }}</dd> + </dl> + <dl class="field" v-if="user.birthday"> + <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> + <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> + </dl> + <dl class="field"> + <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> + <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> + </dl> + </div> + <div class="fields" v-if="user.fields.length > 0"> + <dl class="field" v-for="(field, i) in user.fields" :key="i"> + <dt class="name"> + <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> + </dt> + <dd class="value"> + <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> + </dd> + </dl> + </div> + <div class="status"> + <MkA :to="userPage(user)" :class="{ active: page === 'index' }" v-click-anime> + <b>{{ number(user.notesCount) }}</b> + <span>{{ $ts.notes }}</span> + </MkA> + <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }" v-click-anime> + <b>{{ number(user.followingCount) }}</b> + <span>{{ $ts.following }}</span> + </MkA> + <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }" v-click-anime> + <b>{{ number(user.followersCount) }}</b> + <span>{{ $ts.followers }}</span> + </MkA> </div> - </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl class="field" v-if="user.location"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl class="field" v-if="user.birthday"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div class="fields" v-if="user.fields.length > 0"> - <dl class="field" v-for="(field, i) in user.fields" :key="i"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <div class="status"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }" v-click-anime> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }" v-click-anime> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }" v-click-anime> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> </div> </div> - </div> - <div class="contents"> - <template v-if="page === 'index'"> - <div> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" class="note _block" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/> + <div class="contents"> + <template v-if="page === 'index'"> + <div> + <div v-if="user.pinnedNotes.length > 0" class="_gap"> + <XNote v-for="note in user.pinnedNotes" class="note _block" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/> + </div> + <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> + <XPhotos :user="user" :key="user.id"/> + <XActivity :user="user" :key="user.id"/> </div> - <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> - <XPhotos :user="user" :key="user.id"/> - <XActivity :user="user" :key="user.id"/> - </div> - <div> - <XUserTimeline :user="user"/> - </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> - <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> + <div> + <XUserTimeline :user="user"/> + </div> + </template> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> + <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> + <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> + <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> + </div> </div> - </div> - <MkError v-else-if="error" @retry="fetch()"/> - <MkLoading v-else/> -</transition> + <MkError v-else-if="error" @retry="fetch()"/> + <MkLoading v-else/> + </transition> +</div> </template> <script lang="ts"> @@ -242,6 +245,15 @@ export default defineComponent({ data() { return { [symbols.PAGE_INFO]: computed(() => this.user ? { + icon: 'fas fa-user', + title: this.user.name ? `${this.user.name} (@${this.user.username})` : `@${this.user.username}`, + path: `/@${this.user.username}`, + share: { + title: this.user.name, + }, + bg: 'var(--bg)', + } : null), + header: computed(() => this.user ? { title: this.user.name ? `${this.user.name} (@${this.user.username})` : `@${this.user.username}`, subtitle: `@${getAcct(this.user)}`, userName: this.user, @@ -255,21 +267,22 @@ export default defineComponent({ active: this.page === 'index', title: this.$ts.overview, icon: 'fas fa-home', + onClick: () => { this.$router.push('/@' + getAcct(this.user)); }, }, { active: this.page === 'clips', title: this.$ts.clips, icon: 'fas fa-paperclip', - onClick: () => { this.page = 'clips'; }, + onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/clips'); }, }, { active: this.page === 'pages', title: this.$ts.pages, icon: 'fas fa-file-alt', - onClick: () => { this.page = 'pages'; }, + onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/pages'); }, }, { active: this.page === 'gallery', title: this.$ts.gallery, icon: 'fas fa-icons', - onClick: () => { this.page = 'gallery'; }, + onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/gallery'); }, }] } : null), user: null, @@ -814,7 +827,7 @@ export default defineComponent({ } } -._flat_ .ftskorzw.narrow { +._fitSide_ .ftskorzw.narrow { > .profile { > .warn { margin: 0; diff --git a/src/client/pages/user/pages.vue b/src/client/pages/user/pages.vue index 819bd9f2ef..ece418cf62 100644 --- a/src/client/pages/user/pages.vue +++ b/src/client/pages/user/pages.vue @@ -10,7 +10,6 @@ import { defineComponent } from 'vue'; import MkPagePreview from '@client/components/page-preview.vue'; import MkPagination from '@client/components/ui/pagination.vue'; -import { userPage, acct } from '@client/filters/user'; export default defineComponent({ components: { @@ -41,12 +40,6 @@ export default defineComponent({ user() { this.$refs.list.reload(); } - }, - - methods: { - userPage, - - acct } }); </script> diff --git a/src/client/pages/welcome.entrance.a.vue b/src/client/pages/welcome.entrance.a.vue index 82b439ddd3..13f0993793 100644 --- a/src/client/pages/welcome.entrance.a.vue +++ b/src/client/pages/welcome.entrance.a.vue @@ -27,7 +27,7 @@ <div class="desc" v-html="meta.description || $ts.headlineMisskey"></div> </div> <div class="action"> - <MkButton @click="signup()" inline primary data-cy-signup>{{ $ts.signup }}</MkButton> + <MkButton @click="signup()" inline gradate data-cy-signup style="margin-right: 12px;">{{ $ts.signup }}</MkButton> <MkButton @click="signin()" inline data-cy-signin>{{ $ts.login }}</MkButton> </div> <div class="status" v-if="onlineUsersCount && stats"> diff --git a/src/client/pages/welcome.entrance.b.vue b/src/client/pages/welcome.entrance.b.vue index a5c12f09e2..163fc1e35f 100644 --- a/src/client/pages/welcome.entrance.b.vue +++ b/src/client/pages/welcome.entrance.b.vue @@ -12,7 +12,7 @@ <div class="desc" v-html="meta.description || $ts.headlineMisskey"></div> </div> <div class="action"> - <MkButton class="signup" @click="signup()" inline primary>{{ $ts.signup }}</MkButton> + <MkButton class="signup" @click="signup()" inline gradate>{{ $ts.signup }}</MkButton> <MkButton class="signin" @click="signin()" inline>{{ $ts.login }}</MkButton> </div> <div class="status" v-if="onlineUsersCount && stats"> diff --git a/src/client/pages/welcome.entrance.c.vue b/src/client/pages/welcome.entrance.c.vue index 2c8db6e264..bf1c9b1998 100644 --- a/src/client/pages/welcome.entrance.c.vue +++ b/src/client/pages/welcome.entrance.c.vue @@ -24,7 +24,7 @@ <div class="desc" v-html="meta.description || $ts.headlineMisskey"></div> </div> <div class="action"> - <MkButton @click="signup()" inline primary>{{ $ts.signup }}</MkButton> + <MkButton @click="signup()" inline gradate>{{ $ts.signup }}</MkButton> <MkButton @click="signin()" inline>{{ $ts.login }}</MkButton> </div> <div class="status" v-if="onlineUsersCount && stats"> diff --git a/src/client/pages/welcome.setup.vue b/src/client/pages/welcome.setup.vue index d0091bef67..dfefecc8fa 100644 --- a/src/client/pages/welcome.setup.vue +++ b/src/client/pages/welcome.setup.vue @@ -24,7 +24,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@client/components/ui/button.vue'; -import MkInput from '@client/components/ui/input.vue'; +import MkInput from '@client/components/form/input.vue'; import { host } from '@client/config'; import * as os from '@client/os'; import { login } from '@client/account'; diff --git a/src/client/router.ts b/src/client/router.ts index 573f285c79..56dc948669 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -23,6 +23,7 @@ const defaultRoutes = [ { path: '/@:acct/room', props: true, component: page('room/room') }, { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, { path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) }, + { path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) }, { path: '/announcements', component: page('announcements') }, { path: '/about', component: page('about') }, { path: '/about-misskey', component: page('about-misskey') }, diff --git a/src/client/scripts/autocomplete.ts b/src/client/scripts/autocomplete.ts index 924d6a62ee..c0c33b2c7e 100644 --- a/src/client/scripts/autocomplete.ts +++ b/src/client/scripts/autocomplete.ts @@ -7,9 +7,9 @@ export class Autocomplete { private suggestion: { x: Ref<number>; y: Ref<number>; - q: Ref<string>; + q: Ref<string | null>; close: Function; - }; + } | null; private textarea: any; private vm: any; private currentType: string; @@ -70,11 +70,13 @@ export class Autocomplete { const mentionIndex = text.lastIndexOf('@'); const hashtagIndex = text.lastIndexOf('#'); const emojiIndex = text.lastIndexOf(':'); + const mfmTagIndex = text.lastIndexOf('$'); const max = Math.max( mentionIndex, hashtagIndex, - emojiIndex); + emojiIndex, + mfmTagIndex); if (max == -1) { this.close(); @@ -83,6 +85,7 @@ export class Autocomplete { const isMention = mentionIndex != -1; const isHashtag = hashtagIndex != -1; + const isMfmTag = mfmTagIndex != -1; const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); let opened = false; @@ -114,6 +117,14 @@ export class Autocomplete { } } + if (isMfmTag && !opened) { + const mfmTag = text.substr(mfmTagIndex + 1); + if (!mfmTag.includes(' ')) { + this.open('mfmTag', mfmTag.replace('[', '')); + opened = true; + } + } + if (!opened) { this.close(); } @@ -122,7 +133,7 @@ export class Autocomplete { /** * サジェストを提示します。 */ - private async open(type: string, q: string) { + private async open(type: string, q: string | null) { if (type != this.currentType) { this.close(); } @@ -244,6 +255,22 @@ export class Autocomplete { const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); }); + } else if (type == 'mfmTag') { + const source = this.text; + + const before = source.substr(0, caret); + const trimmedBefore = before.substring(0, before.lastIndexOf('$')); + const after = source.substr(caret); + + // 挿入 + this.text = `${trimmedBefore}$[${value} ]${after}`; + + // キャレットを戻す + this.vm.$nextTick(() => { + this.textarea.focus(); + const pos = trimmedBefore.length + (value.length + 3); + this.textarea.setSelectionRange(pos, pos); + }); } } } diff --git a/src/client/scripts/idb-proxy.ts b/src/client/scripts/idb-proxy.ts index 21c4dcff65..5f76ae30bb 100644 --- a/src/client/scripts/idb-proxy.ts +++ b/src/client/scripts/idb-proxy.ts @@ -4,7 +4,6 @@ import { get as iget, set as iset, del as idel, - createStore, } from 'idb-keyval'; const fallbackName = (key: string) => `idbfallback::${key}`; @@ -13,9 +12,9 @@ let idbAvailable = typeof window !== 'undefined' ? !!window.indexedDB : true; if (idbAvailable) { try { - await createStore('keyval-store', 'keyval'); + await iset('idb-test', 'test'); } catch (e) { - console.error('idb open error', e); + console.error('idb error', e); idbAvailable = false; } } diff --git a/src/client/scripts/physics.ts b/src/client/scripts/physics.ts index 8e971d5844..445b6296eb 100644 --- a/src/client/scripts/physics.ts +++ b/src/client/scripts/physics.ts @@ -79,7 +79,7 @@ export function physics(container: HTMLElement) { objEl.offsetWidth, objEl.offsetHeight, { - chamfer: { radius: parseInt(style.borderRadius, 10) }, + chamfer: { radius: parseInt(style.borderRadius || '0', 10) }, restitution: 0.5 } ); diff --git a/src/client/scripts/scroll.ts b/src/client/scripts/scroll.ts index bc6d1530c5..621fe88105 100644 --- a/src/client/scripts/scroll.ts +++ b/src/client/scripts/scroll.ts @@ -1,3 +1,5 @@ +type ScrollBehavior = 'auto' | 'smooth' | 'instant'; + export function getScrollContainer(el: Element | null): Element | null { if (el == null || el.tagName === 'BODY') return null; const overflow = window.getComputedStyle(el).getPropertyValue('overflow'); @@ -45,21 +47,25 @@ export function onScrollBottom(el: Element, cb) { container.addEventListener('scroll', onScroll, { passive: true }); } -export function scroll(el: Element, top: number) { +export function scroll(el: Element, options: { + top?: number; + left?: number; + behavior?: ScrollBehavior; +}) { const container = getScrollContainer(el); if (container == null) { - window.scroll({ top: top, behavior: 'instant' }); + window.scroll(options); } else { - container.scrollTop = top; + container.scroll(options); } } -export function scrollToTop(el: Element) { - scroll(el, 0); +export function scrollToTop(el: Element, options: { behavior?: ScrollBehavior; } = {}) { + scroll(el, { top: 0, ...options }); } -export function scrollToBottom(el: Element) { - scroll(el, 99999); // TODO: ちゃんと計算する +export function scrollToBottom(el: Element, options: { behavior?: ScrollBehavior; } = {}) { + scroll(el, { top: 99999, ...options }); // TODO: ちゃんと計算する } export function isBottom(el: Element, asobi = 0) { diff --git a/src/client/scripts/theme.ts b/src/client/scripts/theme.ts index 3fb5666a72..e79d54fa6d 100644 --- a/src/client/scripts/theme.ts +++ b/src/client/scripts/theme.ts @@ -1,3 +1,4 @@ +import { globalEvents } from '@client/events'; import * as tinycolor from 'tinycolor2'; export type Theme = { @@ -24,6 +25,7 @@ export const builtinThemes = [ require('@client/themes/d-persimmon.json5'), require('@client/themes/d-astro.json5'), require('@client/themes/d-future.json5'), + require('@client/themes/d-botanical.json5'), require('@client/themes/d-black.json5'), ] as Theme[]; @@ -62,6 +64,9 @@ export function applyTheme(theme: Theme, persist = true) { if (persist) { localStorage.setItem('theme', JSON.stringify(props)); } + + // 色計算など再度行えるようにクライアント全体に通知 + globalEvents.emit('themeChanged'); } function compile(theme: Theme): Record<string, string> { @@ -87,6 +92,8 @@ function compile(theme: Theme): Record<string, string> { case 'darken': return color.darken(arg); case 'lighten': return color.lighten(arg); case 'alpha': return color.setAlpha(arg); + case 'hue': return color.spin(arg); + case 'saturate': return color.saturate(arg); } } diff --git a/src/client/style.scss b/src/client/style.scss index 0318013f60..d6bad5a24d 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -178,7 +178,7 @@ hr { pointer-events: none; } - &:focus { + &:focus-visible { outline: none; } @@ -202,6 +202,20 @@ hr { } } +._buttonGradate { + @extend ._buttonPrimary; + color: var(--fgOnAccent); + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + + &:not(:disabled):hover { + background: linear-gradient(90deg, var(--X8), var(--X8)); + } + + &:not(:disabled):active { + background: linear-gradient(90deg, var(--X8), var(--X8)); + } +} + ._help { color: var(--accent); cursor: help @@ -366,7 +380,7 @@ hr { } } -._flat_ { +._fitSide_ { --root-margin: 0px; --baseContentWidth: 100%; --panelBorder: none; @@ -425,12 +439,18 @@ hr { } } -._inputNoTopMargin { - margin-top: 0 !important; +._formBlock { + margin: 20px 0; } -._inputNoBottomMargin { - margin-bottom: 0 !important; +._formRoot { + > ._formBlock:first-child { + margin-top: 0; + } + + > ._formBlock:last-child { + margin-bottom: 0; + } } ._table { @@ -503,7 +523,7 @@ hr { padding: 5px; } -.prism-editor__textarea:focus { +.prism-editor__textarea:focus-visible { outline: none; } diff --git a/src/client/themes/_dark.json5 b/src/client/themes/_dark.json5 index e1d5779a80..d8be16f60a 100644 --- a/src/client/themes/_dark.json5 +++ b/src/client/themes/_dark.json5 @@ -51,11 +51,14 @@ infoFg: '#fff', infoWarnBg: '#42321c', infoWarnFg: '#ffbd3e', + switchBg: 'rgba(255, 255, 255, 0.15)', cwBg: '#687390', cwFg: '#393f4f', cwHoverBg: '#707b97', buttonBg: 'rgba(255, 255, 255, 0.05)', buttonHoverBg: 'rgba(255, 255, 255, 0.1)', + buttonGradateA: '@accent', + buttonGradateB: ':hue<20<@accent', inputBorder: 'rgba(255, 255, 255, 0.1)', inputBorderHover: 'rgba(255, 255, 255, 0.2)', listItemHoverBg: 'rgba(255, 255, 255, 0.03)', diff --git a/src/client/themes/_light.json5 b/src/client/themes/_light.json5 index 87895e6406..251aa36c7a 100644 --- a/src/client/themes/_light.json5 +++ b/src/client/themes/_light.json5 @@ -51,11 +51,14 @@ infoFg: '#72818a', infoWarnBg: '#fff0db', infoWarnFg: '#8f6e31', + switchBg: 'rgba(0, 0, 0, 0.15)', cwBg: '#b1b9c1', cwFg: '#fff', cwHoverBg: '#bbc4ce', buttonBg: 'rgba(0, 0, 0, 0.05)', buttonHoverBg: 'rgba(0, 0, 0, 0.1)', + buttonGradateA: '@accent', + buttonGradateB: ':hue<20<@accent', inputBorder: 'rgba(0, 0, 0, 0.1)', inputBorderHover: 'rgba(0, 0, 0, 0.2)', listItemHoverBg: 'rgba(0, 0, 0, 0.03)', diff --git a/src/client/themes/d-astro.json5 b/src/client/themes/d-astro.json5 index 08846dec20..2350e3d46d 100644 --- a/src/client/themes/d-astro.json5 +++ b/src/client/themes/d-astro.json5 @@ -46,6 +46,8 @@ navIndicator: '@accent', accentLighten: ':lighten<10<@accent', buttonHoverBg: 'rgba(255, 255, 255, 0.1)', + buttonGradateA: '@accent', + buttonGradateB: ':hue<-20<@accent', driveFolderBg: ':alpha<0.3<@accent', fgHighlighted: ':lighten<3<@fg', panelHeaderBg: ':lighten<3<@panel', diff --git a/src/client/themes/d-botanical.json5 b/src/client/themes/d-botanical.json5 new file mode 100644 index 0000000000..c03b95e2d7 --- /dev/null +++ b/src/client/themes/d-botanical.json5 @@ -0,0 +1,26 @@ +{ + id: '504debaf-4912-6a4c-5059-1db08a76b737', + + name: 'Mi Botanical Dark', + author: 'syuilo', + + base: 'dark', + + props: { + accent: 'rgb(148, 179, 0)', + bg: 'rgb(37, 38, 36)', + fg: 'rgb(216, 212, 199)', + fgHighlighted: '#fff', + divider: 'rgba(255, 255, 255, 0.14)', + panel: 'rgb(47, 47, 44)', + panelHeaderBg: '@panel', + panelHeaderDivider: '@divider', + header: ':alpha<0.7<@panel', + navBg: '#363636', + renote: '@accent', + mention: 'rgb(212, 153, 76)', + mentionMe: 'rgb(212, 210, 76)', + hashtag: '#5bcbb0', + link: '@accent', + }, +} diff --git a/src/client/themes/d-future.json5 b/src/client/themes/d-future.json5 index 05ffe87bf0..1882609121 100644 --- a/src/client/themes/d-future.json5 +++ b/src/client/themes/d-future.json5 @@ -21,5 +21,7 @@ mentionMe: '@accent', hashtag: '#70c0e8', link: '#e88080', + buttonGradateA: '@accent', + buttonGradateB: ':saturate<30<:hue<30<@accent', }, } diff --git a/src/client/ui/_common_/header.vue b/src/client/ui/_common_/header.vue deleted file mode 100644 index 1e0db9a3a1..0000000000 --- a/src/client/ui/_common_/header.vue +++ /dev/null @@ -1,302 +0,0 @@ -<template> -<div class="fdidabkb" :class="{ slim: titleOnly || narrow }" :style="`--height:${height};`" :key="key"> - <transition :name="$store.state.animation ? 'header' : ''" mode="out-in" appear> - <div class="buttons left" v-if="backButton"> - <button class="_button button back" @click.stop="$emit('back')" @touchstart="preventDrag" v-tooltip="$ts.goBack"><i class="fas fa-chevron-left"></i></button> - </div> - </transition> - <template v-if="info"> - <div class="titleContainer" @click="showTabsPopup"> - <i v-if="info.icon" class="icon" :class="info.icon"></i> - <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/> - - <div class="title"> - <MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/> - <div v-else-if="info.title" class="title">{{ info.title }}</div> - <div class="subtitle" v-if="!narrow && info.subtitle"> - {{ info.subtitle }} - </div> - <div class="subtitle activeTab" v-if="narrow && hasTabs"> - {{ info.tabs.find(tab => tab.active)?.title }} - <i class="chevron fas fa-chevron-down"></i> - </div> - </div> - </div> - <div class="tabs" v-if="!narrow"> - <button class="tab _button" v-for="tab in info.tabs" :class="{ active: tab.active }" @click="tab.onClick" v-tooltip="tab.title"> - <i v-if="tab.icon" class="icon" :class="tab.icon"></i> - <span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span> - </button> - </div> - </template> - <div class="buttons right"> - <template v-if="info && info.actions && !narrow"> - <button v-for="action in info.actions" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button> - </template> - <button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button> - <button v-if="closeButton" class="_button button" @click.stop="$emit('close')" @touchstart="preventDrag" v-tooltip="$ts.close"><i class="fas fa-times"></i></button> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { popupMenu } from '@client/os'; -import { url } from '@client/config'; - -export default defineComponent({ - props: { - info: { - required: true - }, - menu: { - required: false - }, - backButton: { - type: Boolean, - required: false, - default: false, - }, - closeButton: { - type: Boolean, - required: false, - default: false, - }, - titleOnly: { - type: Boolean, - required: false, - default: false, - }, - }, - - data() { - return { - narrow: false, - height: 0, - key: 0, - }; - }, - - computed: { - hasTabs(): boolean { - return this.info.tabs && this.info.tabs.length > 0; - }, - - shouldShowMenu() { - if (this.info == null) return false; - if (this.info.actions != null && this.narrow) return true; - if (this.info.menu != null) return true; - if (this.info.share != null) return true; - if (this.menu != null) return true; - return false; - } - }, - - watch: { - info() { - this.key++; - }, - }, - - mounted() { - this.height = this.$el.parentElement.offsetHeight + 'px'; - this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500; - new ResizeObserver((entries, observer) => { - this.height = this.$el.parentElement.offsetHeight + 'px'; - this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500; - }).observe(this.$el); - }, - - methods: { - share() { - navigator.share({ - url: url + this.info.path, - ...this.info.share, - }); - }, - - showMenu(ev) { - let menu = this.info.menu ? this.info.menu() : []; - if (this.narrow && this.info.actions) { - menu = [...this.info.actions.map(x => ({ - text: x.text, - icon: x.icon, - action: x.handler - })), menu.length > 0 ? null : undefined, ...menu]; - } - if (this.info.share) { - if (menu.length > 0) menu.push(null); - menu.push({ - text: this.$ts.share, - icon: 'fas fa-share-alt', - action: this.share - }); - } - if (this.menu) { - if (menu.length > 0) menu.push(null); - menu = menu.concat(this.menu); - } - popupMenu(menu, ev.currentTarget || ev.target); - }, - - showTabsPopup(ev) { - if (!this.hasTabs) return; - ev.preventDefault(); - ev.stopPropagation(); - const menu = this.info.tabs.map(tab => ({ - text: tab.title, - icon: tab.icon, - action: tab.onClick, - })); - popupMenu(menu, ev.currentTarget || ev.target); - }, - - preventDrag(ev) { - ev.stopPropagation(); - } - } -}); -</script> - -<style lang="scss" scoped> -.fdidabkb { - display: flex; - - &.slim { - text-align: center; - - > .titleContainer { - margin: 0 auto; - } - - > .buttons { - &.right { - margin-left: 0; - } - } - } - - > .buttons { - --margin: 8px; - display: flex; - align-items: center; - height: var(--height); - margin: 0 var(--margin); - - &.right { - margin-left: auto; - } - - &:empty { - width: var(--height); - } - - > .button { - display: flex; - align-items: center; - justify-content: center; - height: calc(var(--height) - (var(--margin) * 2)); - width: calc(var(--height) - (var(--margin) * 2)); - box-sizing: border-box; - position: relative; - border-radius: 5px; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - &.highlighted { - color: var(--accent); - } - } - } - - > .titleContainer { - display: flex; - align-items: center; - overflow: auto; - white-space: nowrap; - text-align: left; - font-weight: bold; - - > .avatar { - $size: 32px; - display: inline-block; - width: $size; - height: $size; - vertical-align: bottom; - margin: 0 8px; - pointer-events: none; - } - - > .icon { - margin-right: 8px; - } - - > .title { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - line-height: 1.1; - - > .subtitle { - opacity: 0.6; - font-size: 0.8em; - font-weight: normal; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &.activeTab { - text-align: center; - - > .chevron { - display: inline-block; - margin-left: 6px; - } - } - } - } - } - - > .tabs { - margin-left: 16px; - font-size: 0.8em; - - > .tab { - display: inline-block; - position: relative; - padding: 0 10px; - height: 100%; - font-weight: normal; - opacity: 0.7; - - &:hover { - opacity: 1; - } - - &.active { - opacity: 1; - - &:after { - content: ""; - display: block; - position: absolute; - bottom: 0; - left: 0; - right: 0; - margin: 0 auto; - width: 100%; - height: 3px; - background: var(--accent); - } - } - - > .icon + .title { - margin-left: 8px; - } - } - } -} -</style> diff --git a/src/client/ui/_common_/sidebar.vue b/src/client/ui/_common_/sidebar.vue index 9817a46e30..d00327b096 100644 --- a/src/client/ui/_common_/sidebar.vue +++ b/src/client/ui/_common_/sidebar.vue @@ -50,7 +50,7 @@ import { host } from '@client/config'; import { search } from '@client/scripts/search'; import * as os from '@client/os'; import { menuDef } from '@client/menu'; -import { getAccounts, addAccount, login } from '@client/account'; +import { openAccountMenu } from '@client/account'; export default defineComponent({ props: { @@ -134,76 +134,12 @@ export default defineComponent({ search(); }, - async openAccountMenu(ev) { - const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); - const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); - - const accountItemPromises = storedAccounts.map(a => new Promise(res => { - accountsPromise.then(accounts => { - const account = accounts.find(x => x.id === a.id); - if (account == null) return res(null); - res({ - type: 'user', - user: account, - action: () => { this.switchAccount(account); } - }); - }); - })); - - os.popupMenu([...[{ - type: 'link', - text: this.$ts.profile, - to: `/@${ this.$i.username }`, - avatar: this.$i, - }, null, ...accountItemPromises, { - icon: 'fas fa-plus', - text: this.$ts.addAccount, - action: () => { - os.popupMenu([{ - text: this.$ts.existingAccount, - action: () => { this.addAccount(); }, - }, { - text: this.$ts.createAccount, - action: () => { this.createAccount(); }, - }], ev.currentTarget || ev.target); - }, - }]], ev.currentTarget || ev.target, { - align: 'left' - }); - }, - more(ev) { os.popup(import('@client/components/launch-pad.vue'), {}, { }, 'closed'); }, - addAccount() { - os.popup(import('@client/components/signin-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - os.success(); - }, - }, 'closed'); - }, - - createAccount() { - os.popup(import('@client/components/signup-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - this.switchAccountWithToken(res.i); - }, - }, 'closed'); - }, - - async switchAccount(account: any) { - const storedAccounts = await getAccounts(); - const token = storedAccounts.find(x => x.id === account.id).token; - this.switchAccountWithToken(token); - }, - - switchAccountWithToken(token: string) { - login(token); - }, + openAccountMenu, } }); </script> @@ -395,7 +331,7 @@ export default defineComponent({ left: 0; right: 0; bottom: 0; - border-radius: 8px; + border-radius: 999px; background: var(--accentedBg); } } @@ -436,7 +372,7 @@ export default defineComponent({ right: 0; bottom: 0; border-radius: 999px; - background: var(--accent); + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); } &:hover, &.active { diff --git a/src/client/ui/chat/index.vue b/src/client/ui/chat/index.vue index e8275def81..4c068b0d94 100644 --- a/src/client/ui/chat/index.vue +++ b/src/client/ui/chat/index.vue @@ -74,7 +74,7 @@ <main class="main" @contextmenu.stop="onContextmenu"> <header class="header"> - <XHeader class="header" :info="pageInfo" :menu="menu" :center="false" :back-button="true" @back="back()" @click="onHeaderClick"/> + <MkHeader class="header" :info="pageInfo" :menu="menu" :center="false" @click="onHeaderClick"/> </header> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> @@ -101,7 +101,6 @@ import XSidebar from '@client/ui/_common_/sidebar.vue'; import XWidgets from './widgets.vue'; import XCommon from '../_common_/common.vue'; import XSide from './side.vue'; -import XHeader from '../_common_/header.vue'; import XHeaderClock from './header-clock.vue'; import * as os from '@client/os'; import { router } from '@client/router'; @@ -110,6 +109,7 @@ import { search } from '@client/scripts/search'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { store } from './store'; import * as symbols from '@client/symbols'; +import { openAccountMenu } from '@client/account'; export default defineComponent({ components: { @@ -117,7 +117,6 @@ export default defineComponent({ XSidebar, XWidgets, XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる - XHeader, XHeaderClock, }, @@ -255,6 +254,8 @@ export default defineComponent({ } }], e); }, + + openAccountMenu, } }); </script> diff --git a/src/client/ui/chat/note-preview.vue b/src/client/ui/chat/note-preview.vue index 77949e314b..beb38de644 100644 --- a/src/client/ui/chat/note-preview.vue +++ b/src/client/ui/chat/note-preview.vue @@ -6,7 +6,7 @@ <div class="body"> <p v-if="note.cw != null" class="cw"> <span class="text" v-if="note.cw != ''">{{ note.cw }}</span> - <XCwButton v-model:value="showContent" :note="note"/> + <XCwButton v-model="showContent" :note="note"/> </p> <div class="content" v-show="note.cw == null || showContent"> <XSubNote-content class="text" :note="note"/> diff --git a/src/client/ui/chat/note.sub.vue b/src/client/ui/chat/note.sub.vue index bb528dd936..a284ba2bf4 100644 --- a/src/client/ui/chat/note.sub.vue +++ b/src/client/ui/chat/note.sub.vue @@ -7,7 +7,7 @@ <div class="body"> <p v-if="note.cw != null" class="cw"> <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis" /> - <XCwButton v-model:value="showContent" :note="note"/> + <XCwButton v-model="showContent" :note="note"/> </p> <div class="content" v-show="note.cw == null || showContent"> <XSubNote-content class="text" :note="note"/> diff --git a/src/client/ui/chat/note.vue b/src/client/ui/chat/note.vue index 6d2b9bbf54..0a054d1057 100644 --- a/src/client/ui/chat/note.vue +++ b/src/client/ui/chat/note.vue @@ -42,7 +42,7 @@ <div class="body"> <p v-if="appearNote.cw != null" class="cw"> <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <XCwButton v-model:value="showContent" :note="appearNote"/> + <XCwButton v-model="showContent" :note="appearNote"/> </p> <div class="content" :class="{ collapsed }" v-show="appearNote.cw == null || showContent"> <div class="text"> @@ -56,7 +56,7 @@ </div> <XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/> <MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="false" class="url-preview"/> - <div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div> + <div class="renote" v-if="appearNote.renote"><XNoteSimple :note="appearNote.renote"/></div> <button v-if="collapsed" class="fade _button" @click="collapsed = false"> <span>{{ $ts.showMore }}</span> </button> @@ -106,7 +106,7 @@ import * as mfm from 'mfm-js'; import { sum } from '../../../prelude/array'; import XSub from './note.sub.vue'; import XNoteHeader from './note-header.vue'; -import XNotePreview from './note-preview.vue'; +import XNoteSimple from './note-preview.vue'; import XReactionsViewer from '@client/components/reactions-viewer.vue'; import XMediaList from '@client/components/media-list.vue'; import XCwButton from '@client/components/cw-button.vue'; @@ -126,7 +126,7 @@ export default defineComponent({ components: { XSub, XNoteHeader, - XNotePreview, + XNoteSimple, XReactionsViewer, XMediaList, XCwButton, @@ -872,7 +872,7 @@ export default defineComponent({ //content-visibility: auto; //contain-intrinsic-size: 0 128px; - &:focus { + &:focus-visible { outline: none; } diff --git a/src/client/ui/chat/post-form.vue b/src/client/ui/chat/post-form.vue index 0f9a206fab..0cacaf77e7 100644 --- a/src/client/ui/chat/post-form.vue +++ b/src/client/ui/chat/post-form.vue @@ -681,7 +681,7 @@ export default defineComponent({ color: var(--fg); font-family: inherit; - &:focus { + &:focus-visible { outline: none; } diff --git a/src/client/ui/chat/side.vue b/src/client/ui/chat/side.vue index ebf1cf9979..3fd0a0e77b 100644 --- a/src/client/ui/chat/side.vue +++ b/src/client/ui/chat/side.vue @@ -1,15 +1,14 @@ <template> <div class="mrajymqm _narrow_" v-if="component"> <header class="header" @contextmenu.prevent.stop="onContextmenu"> - <XHeader class="title" :info="pageInfo" :center="false" :back-button="history.length > 0" @back="back()" :close-button="true" @close="close()"/> + <MkHeader class="title" :info="pageInfo" :center="false"/> </header> - <component :is="component" v-bind="props" :ref="changePage" class="body _flat_"/> + <component :is="component" v-bind="props" :ref="changePage" class="body _fitSide_"/> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import XHeader from '../_common_/header.vue'; import * as os from '@client/os'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { resolve } from '@client/router'; @@ -18,7 +17,6 @@ import * as symbols from '@client/symbols'; export default defineComponent({ components: { - XHeader }, provide() { diff --git a/src/client/ui/deck/column.vue b/src/client/ui/deck/column.vue index 842a6ff59f..c04297e384 100644 --- a/src/client/ui/deck/column.vue +++ b/src/client/ui/deck/column.vue @@ -37,6 +37,11 @@ import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownCo import { deckStore } from './deck-store'; export default defineComponent({ + provide: { + shouldHeaderThin: true, + shouldOmitHeaderTitle: true, + }, + props: { column: { type: Object, @@ -267,6 +272,7 @@ export default defineComponent({ height: 100%; overflow: hidden; contain: content; + box-shadow: 0 0 8px 0 var(--shadow); &.draghover { box-shadow: 0 0 0 2px var(--focus); @@ -320,15 +326,6 @@ export default defineComponent({ &.paged { background: var(--bg) !important; - - > header { - background: transparent; - box-shadow: none; - - > button { - color: var(--fg); - } - } } > header { @@ -365,7 +362,7 @@ export default defineComponent({ } > .toggleActive, - > .action > *, + > .action > ::v-deep(*), > .menu { z-index: 1; width: var(--deckColumnHeaderHeight); diff --git a/src/client/ui/deck/deck-store.ts b/src/client/ui/deck/deck-store.ts index aa389d7610..6c61bf5539 100644 --- a/src/client/ui/deck/deck-store.ts +++ b/src/client/ui/deck/deck-store.ts @@ -219,10 +219,20 @@ export function stackLeftColumn(id: Column['id']) { export function popRightColumn(id: Column['id']) { let layout = copy(deckStore.state.layout); const i = deckStore.state.layout.findIndex(ids => ids.includes(id)); + const affected = layout[i]; layout = layout.map(ids => ids.filter(_id => _id !== id)); layout.splice(i + 1, 0, [id]); layout = layout.filter(ids => ids.length > 0); deckStore.set('layout', layout); + + const columns = copy(deckStore.state.columns); + for (const column of columns) { + if (affected.includes(column.id)) { + column.active = true; + } + } + deckStore.set('columns', columns); + saveDeck(); } diff --git a/src/client/ui/deck/main-column.vue b/src/client/ui/deck/main-column.vue index 4c591022a5..baf88a9721 100644 --- a/src/client/ui/deck/main-column.vue +++ b/src/client/ui/deck/main-column.vue @@ -1,10 +1,13 @@ <template> <XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked"> <template #header> - <XHeader :info="pageInfo" :back-button="true" @back="back()"/> + <template v-if="pageInfo"> + <i :class="pageInfo.icon"></i> + {{ pageInfo.title }} + </template> </template> - <router-view v-slot="{ Component }" class="_flat_"> + <router-view v-slot="{ Component }" class="_fitSide_"> <transition> <keep-alive :include="['timeline']"> <component :is="Component" :ref="changePage" @contextmenu.stop="onContextmenu"/> @@ -18,7 +21,6 @@ import { defineComponent } from 'vue'; import XColumn from './column.vue'; import XNotes from '@client/components/notes.vue'; -import XHeader from '@client/ui/_common_/header.vue'; import { deckStore } from '@client/ui/deck/deck-store'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; @@ -26,7 +28,6 @@ import * as symbols from '@client/symbols'; export default defineComponent({ components: { XColumn, - XHeader, XNotes }, diff --git a/src/client/ui/default.header.vue b/src/client/ui/default.header.vue index 6fbdd625c7..4f6363e82d 100644 --- a/src/client/ui/default.header.vue +++ b/src/client/ui/default.header.vue @@ -29,7 +29,7 @@ <MkAvatar :user="$i" class="avatar"/><MkAcct class="acct" :user="$i"/> </button> <div class="post" @click="post"> - <MkButton class="button" primary full> + <MkButton class="button" gradate full rounded> <i class="fas fa-pencil-alt fa-fw"></i> </MkButton> </div> @@ -44,7 +44,7 @@ import { host } from '@client/config'; import { search } from '@client/scripts/search'; import * as os from '@client/os'; import { menuDef } from '@client/menu'; -import { getAccounts, addAccount, login } from '@client/account'; +import { openAccountMenu } from '@client/account'; import MkButton from '@client/components/ui/button.vue'; export default defineComponent({ @@ -100,76 +100,12 @@ export default defineComponent({ search(); }, - async openAccountMenu(ev) { - const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); - const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); - - const accountItemPromises = storedAccounts.map(a => new Promise(res => { - accountsPromise.then(accounts => { - const account = accounts.find(x => x.id === a.id); - if (account == null) return res(null); - res({ - type: 'user', - user: account, - action: () => { this.switchAccount(account); } - }); - }); - })); - - os.popupMenu([...[{ - type: 'link', - text: this.$ts.profile, - to: `/@${ this.$i.username }`, - avatar: this.$i, - }, null, ...accountItemPromises, { - icon: 'fas fa-plus', - text: this.$ts.addAccount, - action: () => { - os.popupMenu([{ - text: this.$ts.existingAccount, - action: () => { this.addAccount(); }, - }, { - text: this.$ts.createAccount, - action: () => { this.createAccount(); }, - }], ev.currentTarget || ev.target); - }, - }]], ev.currentTarget || ev.target, { - align: 'left' - }); - }, - more(ev) { os.popup(import('@client/components/launch-pad.vue'), {}, { }, 'closed'); }, - addAccount() { - os.popup(import('@client/components/signin-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - os.success(); - }, - }, 'closed'); - }, - - createAccount() { - os.popup(import('@client/components/signup-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - this.switchAccountWithToken(res.i); - }, - }, 'closed'); - }, - - async switchAccount(account: any) { - const storedAccounts = await getAccounts(); - const token = storedAccounts.find(x => x.id === account.id).token; - this.switchAccountWithToken(token); - }, - - switchAccountWithToken(token: string) { - login(token); - }, + openAccountMenu, } }); </script> diff --git a/src/client/ui/default.side.vue b/src/client/ui/default.side.vue index 4d65779612..c7d2abff26 100644 --- a/src/client/ui/default.side.vue +++ b/src/client/ui/default.side.vue @@ -4,9 +4,10 @@ <header class="header" @contextmenu.prevent.stop="onContextmenu"> <button class="_button" @click="back()" v-if="history.length > 0"><i class="fas fa-chevron-left"></i></button> <button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button> - <XHeader class="title" :info="pageInfo" :back-button="false"/> + <span class="title">{{ pageInfo.title }}</span> <button class="_button" @click="close()"><i class="fas fa-times"></i></button> </header> + <MkHeader class="pageHeader" :info="pageInfo"/> <component :is="component" v-bind="props" :ref="changePage"/> </div> </div> @@ -14,7 +15,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import XHeader from './_common_/header.vue'; import * as os from '@client/os'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { resolve } from '@client/router'; @@ -22,10 +22,6 @@ import { url } from '@client/config'; import * as symbols from '@client/symbols'; export default defineComponent({ - components: { - XHeader - }, - provide() { return { navHook: (path) => { diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue index be907aa2a4..e36febb7fa 100644 --- a/src/client/ui/default.sidebar.vue +++ b/src/client/ui/default.sidebar.vue @@ -4,7 +4,7 @@ <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> </button> <div class="post" @click="post" data-cy-open-post-form> - <MkButton class="button" primary full> + <MkButton class="button" gradate full rounded> <i class="fas fa-pencil-alt fa-fw"></i><span class="text" v-if="!iconOnly">{{ $ts.note }}</span> </MkButton> </div> @@ -46,7 +46,7 @@ import { host } from '@client/config'; import { search } from '@client/scripts/search'; import * as os from '@client/os'; import { menuDef } from '@client/menu'; -import { getAccounts, addAccount, login } from '@client/account'; +import { openAccountMenu } from '@client/account'; import MkButton from '@client/components/ui/button.vue'; import { StickySidebar } from '@client/scripts/sticky-sidebar'; import MisskeyLogo from '@/../assets/client/misskey.svg'; @@ -120,76 +120,12 @@ export default defineComponent({ search(); }, - async openAccountMenu(ev) { - const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); - const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); - - const accountItemPromises = storedAccounts.map(a => new Promise(res => { - accountsPromise.then(accounts => { - const account = accounts.find(x => x.id === a.id); - if (account == null) return res(null); - res({ - type: 'user', - user: account, - action: () => { this.switchAccount(account); } - }); - }); - })); - - os.popupMenu([...[{ - type: 'link', - text: this.$ts.profile, - to: `/@${ this.$i.username }`, - avatar: this.$i, - }, null, ...accountItemPromises, { - icon: 'fas fa-plus', - text: this.$ts.addAccount, - action: () => { - os.popupMenu([{ - text: this.$ts.existingAccount, - action: () => { this.addAccount(); }, - }, { - text: this.$ts.createAccount, - action: () => { this.createAccount(); }, - }], ev.currentTarget || ev.target); - }, - }]], ev.currentTarget || ev.target, { - align: 'left' - }); - }, - more(ev) { os.popup(import('@client/components/launch-pad.vue'), {}, { }, 'closed'); }, - addAccount() { - os.popup(import('@client/components/signin-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - os.success(); - }, - }, 'closed'); - }, - - createAccount() { - os.popup(import('@client/components/signup-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - this.switchAccountWithToken(res.i); - }, - }, 'closed'); - }, - - async switchAccount(account: any) { - const storedAccounts = await getAccounts(); - const token = storedAccounts.find(x => x.id === account.id).token; - this.switchAccountWithToken(token); - }, - - switchAccountWithToken(token: string) { - login(token); - }, + openAccountMenu, } }); </script> diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue index a5ec243e9e..3518b1a91a 100644 --- a/src/client/ui/default.vue +++ b/src/client/ui/default.vue @@ -1,6 +1,6 @@ <template> -<div class="mk-app" :class="{ wallpaper, isMobile }"> - <XHeaderMenu v-if="showMenuOnTop"/> +<div class="mk-app" :class="{ wallpaper, isMobile }" :style="`--globalHeaderHeight:${globalHeaderHeight}px`"> + <XHeaderMenu v-if="showMenuOnTop" v-get-size="(w, h) => globalHeaderHeight = h"/> <div class="columns" :class="{ fullView, withGlobalHeader: showMenuOnTop }"> <template v-if="!isMobile"> @@ -13,10 +13,7 @@ </template> <main class="main" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }"> - <header class="header" @click="onHeaderClick"> - <XHeader :info="pageInfo" :back-button="true" @back="back()"/> - </header> - <div class="content" :class="{ _flat_: !fullView }"> + <div class="content" :class="{ _fitSide_: !fullView }"> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> <keep-alive :include="['timeline']"> @@ -67,7 +64,6 @@ import { StickySidebar } from '@client/scripts/sticky-sidebar'; import XSidebar from './default.sidebar.vue'; import XDrawerSidebar from '@client/ui/_common_/sidebar.vue'; import XCommon from './_common_/common.vue'; -import XHeader from './_common_/header.vue'; import * as os from '@client/os'; import { menuDef } from '@client/menu'; import * as symbols from '@client/symbols'; @@ -80,15 +76,21 @@ export default defineComponent({ XCommon, XSidebar, XDrawerSidebar, - XHeader, XHeaderMenu: defineAsyncComponent(() => import('./default.header.vue')), XWidgets: defineAsyncComponent(() => import('./default.widgets.vue')), }, + provide() { + return { + shouldHeaderThin: this.showMenuOnTop, + }; + }, + data() { return { pageInfo: null, menuDef: menuDef, + globalHeaderHeight: 0, isMobile: window.innerWidth <= MOBILE_THRESHOLD, isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, widgetsShowing: false, @@ -193,10 +195,6 @@ export default defineComponent({ if (window._scroll) window._scroll(); }, - onHeaderClick() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - onContextmenu(e) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; @@ -257,7 +255,6 @@ export default defineComponent({ } .mk-app { - $header-height: 50px; $ui-font-size: 1em; $widgets-hide-threshold: 1200px; $nav-icon-only-width: 78px; // TODO: どこかに集約したい @@ -282,10 +279,6 @@ export default defineComponent({ border: none; width: 100%; border-radius: 0; - - > .header { - width: 100%; - } } } } @@ -325,30 +318,6 @@ export default defineComponent({ border-radius: 0; overflow: clip; --margin: 12px; - - > .header { - position: sticky; - z-index: 1000; - top: var(--globalHeaderHeight, 0px); - height: $header-height; - -webkit-backdrop-filter: var(--blur, blur(32px)); - backdrop-filter: var(--blur, blur(32px)); - background-color: var(--header); - border-bottom: solid 0.5px var(--divider); - } - - > .content { - --stickyTop: calc(var(--globalHeaderHeight, 0px) + #{$header-height}); - } - - @media (max-width: 850px) { - padding-top: $header-height; - - > .header { - position: fixed; - width: calc(100% - #{$nav-icon-only-width}); - } - } } > .widgets { @@ -370,12 +339,11 @@ export default defineComponent({ } &.withGlobalHeader { - --globalHeaderHeight: 60px; // TODO: 60pxと決め打ちしているのを直す - > .main { margin-top: 0; border: solid 1px var(--divider); border-radius: var(--radius); + --stickyTop: var(--globalHeaderHeight); } > .widgets { diff --git a/src/client/ui/universal.vue b/src/client/ui/universal.vue index ec9254b697..7c25d71bb3 100644 --- a/src/client/ui/universal.vue +++ b/src/client/ui/universal.vue @@ -3,9 +3,6 @@ <XSidebar ref="nav" class="sidebar"/> <div class="contents" ref="contents" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }"> - <header class="header" ref="header" @click="onHeaderClick" :style="{ background: pageInfo?.bg }"> - <XHeader :info="pageInfo" :back-button="true" @back="back()"/> - </header> <main ref="main"> <div class="content"> <router-view v-slot="{ Component }"> @@ -58,7 +55,6 @@ import { instanceName } from '@client/config'; import { StickySidebar } from '@client/scripts/sticky-sidebar'; import XSidebar from '@client/ui/_common_/sidebar.vue'; import XCommon from './_common_/common.vue'; -import XHeader from './_common_/header.vue'; import XSide from './default.side.vue'; import * as os from '@client/os'; import { menuDef } from '@client/menu'; @@ -70,7 +66,6 @@ export default defineComponent({ components: { XCommon, XSidebar, - XHeader, XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')), XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる }, @@ -151,9 +146,6 @@ export default defineComponent({ adjustUI() { const navWidth = this.$refs.nav.$el.offsetWidth; this.navHidden = navWidth === 0; - if (this.$refs.contents == null) return; - const width = this.$refs.contents.offsetWidth; - if (this.$refs.header) this.$refs.header.style.width = `${width}px`; }, showNav() { @@ -183,10 +175,6 @@ export default defineComponent({ if (window._scroll) window._scroll(); }, - onHeaderClick() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - onContextmenu(e) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; @@ -243,7 +231,6 @@ export default defineComponent({ } .mk-app { - $header-height: 58px; // TODO: どこかに集約したい $ui-font-size: 1em; // TODO: どこかに集約したい $widgets-hide-threshold: 1090px; @@ -263,37 +250,11 @@ export default defineComponent({ > .contents { width: 100%; min-width: 0; - --stickyTop: #{$header-height}; - padding-top: $header-height; background: var(--panel); - > .header { - position: fixed; - z-index: 1000; - top: 0; - height: $header-height; - width: 100%; - line-height: $header-height; - text-align: center; - font-weight: bold; - //background-color: var(--panel); - -webkit-backdrop-filter: var(--blur, blur(32px)); - backdrop-filter: var(--blur, blur(32px)); - background-color: var(--header); - border-bottom: solid 0.5px var(--divider); - user-select: none; - } - > main { min-width: 0; - > .content { - > * { - // ほんとは単に calc(100vh - #{$header-height}) と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - min-height: calc((var(--vh, 1vh) * 100) - #{$header-height}); - } - } - > .spacer { height: 82px; diff --git a/src/client/ui/zen.vue b/src/client/ui/zen.vue index 3756ddb5c3..98e2b8dac6 100644 --- a/src/client/ui/zen.vue +++ b/src/client/ui/zen.vue @@ -2,7 +2,7 @@ <div class="mk-app"> <div class="contents"> <header class="header"> - <XHeader :info="pageInfo"/> + <MkHeader :info="pageInfo"/> </header> <main ref="main"> <div class="content"> @@ -24,14 +24,12 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; import { host } from '@client/config'; -import XHeader from './_common_/header.vue'; import XCommon from './_common_/common.vue'; import * as symbols from '@client/symbols'; export default defineComponent({ components: { XCommon, - XHeader, }, data() { diff --git a/src/client/widgets/aiscript.vue b/src/client/widgets/aiscript.vue index 2ea6d09ff5..aaf0a0372e 100644 --- a/src/client/widgets/aiscript.vue +++ b/src/client/widgets/aiscript.vue @@ -125,7 +125,7 @@ export default defineComponent({ box-sizing: border-box; font: inherit; - &:focus { + &:focus-visible { outline: none; } } diff --git a/src/client/widgets/memo.vue b/src/client/widgets/memo.vue index 13ab628f24..3f11e6409e 100644 --- a/src/client/widgets/memo.vue +++ b/src/client/widgets/memo.vue @@ -81,7 +81,7 @@ export default defineComponent({ font: inherit; font-size: 0.9em; - &:focus { + &:focus-visible { outline: none; } } diff --git a/src/client/widgets/notifications.vue b/src/client/widgets/notifications.vue index 01c76850d8..b0245eed6a 100644 --- a/src/client/widgets/notifications.vue +++ b/src/client/widgets/notifications.vue @@ -3,7 +3,7 @@ <template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template> <template #func><button @click="configure()" class="_button"><i class="fas fa-cog"></i></button></template> - <div class="_flat_"> + <div class="_fitSide_"> <XNotifications :include-types="props.includingTypes"/> </div> </MkContainer> |