Merge remote-tracking branch 'upstream/develop' into shigusegubu

* upstream/develop:
  check for user before checking users props
  Set hide_follows and hide_followers settings when parsing Mastodon format
  all the manual fixes
  fix merge conflict
  fix hot reload always reloading the page
  Use target from the settings
  preserve formatting of content
  add unit tests
  update regex for commit hash
  eslint --fix --ext .js,.vue src
  npm eslint --fix .
  Support compositionupdate event to properly show autocomplete popup for IMEs and android
  place scope selector on the left consistently
  hide text format when only plaintext is available
  update test for clearTimeline action
  make sure that user timelines are empty when opening profile page
  clear userId property of timeline by default in clearTimeline action
  Revoke oAuth token
  Update oc.json
This commit is contained in:
Henry Jameson 2019-07-08 09:06:20 +03:00
commit abf36dc466
152 changed files with 5445 additions and 2648 deletions

View file

@ -21,26 +21,6 @@ module.exports = {
'generator-star-spacing': 0, 'generator-star-spacing': 0,
// allow debugger during development // allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
// Webpack 4 update commit, most of these probably should be fixed and removed in a separate MR 'vue/require-prop-types': 0
// A lot of errors come from .vue files that are now properly linted
'vue/valid-v-if': 1,
'vue/use-v-on-exact': 1,
'vue/no-parsing-error': 1,
'vue/require-v-for-key': 1,
'vue/valid-v-for': 1,
'vue/require-prop-types': 1,
'vue/no-use-v-if-with-v-for': 1,
'indent': 1,
'import/first': 1,
'object-curly-spacing': 1,
'prefer-promise-reject-errors': 1,
'eol-last': 1,
'no-return-await': 1,
'no-multi-spaces': 1,
'no-trailing-spaces': 1,
'no-unused-expressions': 1,
'no-mixed-operators': 1,
'camelcase': 1,
'no-multiple-empty-lines': 1
} }
} }

View file

@ -31,8 +31,13 @@ var hotMiddleware = require('webpack-hot-middleware')(compiler)
// force page reload when html-webpack-plugin template changes // force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) { compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' }) // FIXME: This supposed to reload whole page when index.html is changed,
cb() // however now it reloads entire page on every breath, i suppose the order
// of plugins changed or something. It's a minor thing and douesn't hurt
// disabling it, constant reloads hurt much more
// hotMiddleware.publish({ action: 'reload' })
// cb()
}) })
}) })

View file

@ -48,6 +48,11 @@ module.exports = {
changeOrigin: true, changeOrigin: true,
cookieDomainRewrite: 'localhost', cookieDomainRewrite: 'localhost',
ws: true ws: true
},
'/oauth/revoke': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost'
} }
}, },
// CSS Sourcemaps off by default because relative paths are "buggy" // CSS Sourcemaps off by default because relative paths are "buggy"

View file

@ -1,53 +1,111 @@
<template> <template>
<div id="app" v-bind:style="bgAppStyle"> <div
<div class="app-bg-wrapper" v-bind:style="bgStyle"></div> id="app"
:style="bgAppStyle"
>
<div
class="app-bg-wrapper"
:style="bgStyle"
/>
<MobileNav v-if="isMobileLayout" /> <MobileNav v-if="isMobileLayout" />
<nav v-else class='nav-bar container' @click="scrollToTop()" id="nav"> <nav
<div class='logo' :style='logoBgStyle'> v-else
<div class='mask' :style='logoMaskStyle'></div> id="nav"
<img :src='logo' :style='logoStyle'> class="nav-bar container"
@click="scrollToTop()"
>
<div
class="logo"
:style="logoBgStyle"
>
<div
class="mask"
:style="logoMaskStyle"
/>
<img
:src="logo"
:style="logoStyle"
>
</div> </div>
<div class='inner-nav'> <div class="inner-nav">
<div class='item'> <div class="item">
<router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link> <router-link
class="site-name"
:to="{ name: 'root' }"
active-class="home"
>
{{ sitename }}
</router-link>
</div> </div>
<div class='item right'> <div class="item right">
<user-finder class="button-icon nav-icon mobile-hidden" @toggled="onFinderToggled"></user-finder> <user-finder
<router-link class="mobile-hidden" :to="{ name: 'settings'}"><i class="button-icon icon-cog nav-icon" :title="$t('nav.preferences')"></i></router-link> class="button-icon nav-icon mobile-hidden"
<a href="#" class="mobile-hidden" v-if="currentUser" @click.prevent="logout"><i class="button-icon icon-logout nav-icon" :title="$t('login.logout')"></i></a> @toggled="onFinderToggled"
/>
<router-link
class="mobile-hidden"
:to="{ name: 'settings'}"
>
<i
class="button-icon icon-cog nav-icon"
:title="$t('nav.preferences')"
/>
</router-link>
<a
v-if="currentUser"
href="#"
class="mobile-hidden"
@click.prevent="logout"
><i
class="button-icon icon-logout nav-icon"
:title="$t('login.logout')"
/></a>
</div> </div>
</div> </div>
</nav> </nav>
<div class="container" id="content"> <div
id="content"
class="container"
>
<div class="sidebar-flexer mobile-hidden"> <div class="sidebar-flexer mobile-hidden">
<div class="sidebar-bounds"> <div class="sidebar-bounds">
<div class="sidebar-scroller"> <div class="sidebar-scroller">
<div class="sidebar"> <div class="sidebar">
<user-panel></user-panel> <user-panel />
<div v-if="!isMobileLayout"> <div v-if="!isMobileLayout">
<nav-panel></nav-panel> <nav-panel />
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel> <instance-specific-panel v-if="showInstanceSpecificPanel" />
<features-panel v-if="!currentUser && showFeaturesPanel"></features-panel> <features-panel v-if="!currentUser && showFeaturesPanel" />
<who-to-follow-panel v-if="currentUser && suggestionsEnabled"></who-to-follow-panel> <who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
<notifications v-if="currentUser"></notifications> <notifications v-if="currentUser" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="main"> <div class="main">
<div v-if="!currentUser" class="login-hint panel panel-default"> <div
<router-link :to="{ name: 'login' }" class="panel-body"> v-if="!currentUser"
class="login-hint panel panel-default"
>
<router-link
:to="{ name: 'login' }"
class="panel-body"
>
{{ $t("login.hint") }} {{ $t("login.hint") }}
</router-link> </router-link>
</div> </div>
<transition name="fade"> <transition name="fade">
<router-view></router-view> <router-view />
</transition> </transition>
</div> </div>
<media-modal></media-modal> <media-modal />
</div> </div>
<chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel> <chat-panel
v-if="currentUser && chat"
:floating="true"
class="floating-chat mobile-hidden"
/>
<UserReportingModal /> <UserReportingModal />
<portal-target name="modal" /> <portal-target name="modal" />
</div> </div>

View file

@ -24,8 +24,8 @@ export default (store) => {
path: '/', path: '/',
redirect: _to => { redirect: _to => {
return (store.state.users.currentUser return (store.state.users.currentUser
? store.state.instance.redirectRootLogin ? store.state.instance.redirectRootLogin
: store.state.instance.redirectRootNoLogin) || '/main/all' : store.state.instance.redirectRootNoLogin) || '/main/all'
} }
}, },
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline }, { name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },

View file

@ -1,8 +1,8 @@
<template> <template>
<div class="sidebar"> <div class="sidebar">
<instance-specific-panel></instance-specific-panel> <instance-specific-panel />
<features-panel v-if="showFeaturesPanel"></features-panel> <features-panel v-if="showFeaturesPanel" />
<terms-of-service-panel></terms-of-service-panel> <terms-of-service-panel />
</div> </div>
</template> </template>

View file

@ -51,7 +51,7 @@ const Attachment = {
} }
}, },
methods: { methods: {
linkClicked ({target}) { linkClicked ({ target }) {
if (target.tagName === 'A') { if (target.tagName === 'A') {
window.open(target.href, '_blank') window.open(target.href, '_blank')
} }

View file

@ -1,54 +1,106 @@
<template> <template>
<div v-if="usePlaceHolder" @click="openModal"> <div
<a class="placeholder" v-if="usePlaceHolder"
@click="openModal"
>
<a
v-if="type !== 'html'" v-if="type !== 'html'"
target="_blank" :href="attachment.url" class="placeholder"
target="_blank"
:href="attachment.url"
> >
[{{nsfw ? "NSFW/" : ""}}{{type.toUpperCase()}}] [{{ nsfw ? "NSFW/" : "" }}{{ type.toUpperCase() }}]
</a> </a>
</div> </div>
<div <div
v-else class="attachment" v-else
:class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
v-show="!isEmpty" v-show="!isEmpty"
class="attachment"
:class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
> >
<a class="image-attachment" v-if="hidden" :href="attachment.url" @click.prevent="toggleHidden"> <a
<img class="nsfw" :key="nsfwImage" :src="nsfwImage" :class="{'small': isSmall}"/> v-if="hidden"
<i v-if="type === 'video'" class="play-icon icon-play-circled"></i> class="image-attachment"
:href="attachment.url"
@click.prevent="toggleHidden"
>
<img
:key="nsfwImage"
class="nsfw"
:src="nsfwImage"
:class="{'small': isSmall}"
>
<i
v-if="type === 'video'"
class="play-icon icon-play-circled"
/>
</a> </a>
<div class="hider" v-if="nsfw && hideNsfwLocal && !hidden"> <div
<a href="#" @click.prevent="toggleHidden">Hide</a> v-if="nsfw && hideNsfwLocal && !hidden"
class="hider"
>
<a
href="#"
@click.prevent="toggleHidden"
>Hide</a>
</div> </div>
<a v-if="type === 'image' && (!hidden || preloadImage)" <a
@click="openModal" v-if="type === 'image' && (!hidden || preloadImage)"
class="image-attachment" class="image-attachment"
:class="{'hidden': hidden && preloadImage }" :class="{'hidden': hidden && preloadImage }"
:href="attachment.url" target="_blank" :href="attachment.url"
target="_blank"
:title="attachment.description" :title="attachment.description"
@click="openModal"
> >
<StillImage :referrerpolicy="referrerpolicy" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/> <StillImage
:referrerpolicy="referrerpolicy"
:mimetype="attachment.mimetype"
:src="attachment.large_thumb_url || attachment.url"
/>
</a> </a>
<a class="video-container" <a
@click="openModal"
v-if="type === 'video' && !hidden" v-if="type === 'video' && !hidden"
class="video-container"
:class="{'small': isSmall}" :class="{'small': isSmall}"
:href="allowPlay ? undefined : attachment.url" :href="allowPlay ? undefined : attachment.url"
@click="openModal"
> >
<VideoAttachment class="video" :attachment="attachment" :controls="allowPlay" /> <VideoAttachment
<i v-if="!allowPlay" class="play-icon icon-play-circled"></i> class="video"
:attachment="attachment"
:controls="allowPlay"
/>
<i
v-if="!allowPlay"
class="play-icon icon-play-circled"
/>
</a> </a>
<audio v-if="type === 'audio'" :src="attachment.url" controls></audio> <audio
v-if="type === 'audio'"
:src="attachment.url"
controls
/>
<div @click.prevent="linkClicked" v-if="type === 'html' && attachment.oembed" class="oembed"> <div
<div v-if="attachment.thumb_url" class="image"> v-if="type === 'html' && attachment.oembed"
<img :src="attachment.thumb_url"/> class="oembed"
@click.prevent="linkClicked"
>
<div
v-if="attachment.thumb_url"
class="image"
>
<img :src="attachment.thumb_url">
</div> </div>
<div class="text"> <div class="text">
<h1><a :href="attachment.url">{{attachment.oembed.title}}</a></h1> <!-- eslint-disable vue/no-v-html -->
<div v-html="attachment.oembed.oembedHTML"></div> <h1><a :href="attachment.url">{{ attachment.oembed.title }}</a></h1>
<div v-html="attachment.oembed.oembedHTML" />
<!-- eslint-enabled vue/no-v-html -->
</div> </div>
</div> </div>
</div> </div>

View file

@ -2,11 +2,11 @@ const debounceMilliseconds = 500
export default { export default {
props: { props: {
query: { // function to query results and return a promise query: { // function to query results and return a promise
type: Function, type: Function,
required: true required: true
}, },
filter: { // function to filter results in real time filter: { // function to filter results in real time
type: Function type: Function
}, },
placeholder: { placeholder: {

View file

@ -1,8 +1,22 @@
<template> <template>
<div class="autosuggest" v-click-outside="onClickOutside"> <div
<input v-model="term" :placeholder="placeholder" @click="onInputClick" class="autosuggest-input" /> v-click-outside="onClickOutside"
<div class="autosuggest-results" v-if="resultsVisible && filtered.length > 0"> class="autosuggest"
<slot v-for="item in filtered" :item="item" /> >
<input
v-model="term"
:placeholder="placeholder"
class="autosuggest-input"
@click="onInputClick"
>
<div
v-if="resultsVisible && filtered.length > 0"
class="autosuggest-results"
>
<slot
v-for="item in filtered"
:item="item"
/>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,12 +1,15 @@
<template> <template>
<div class="avatars"> <div class="avatars">
<router-link <router-link
:to="userProfileLink(user)"
class="avatars-item"
v-for="user in slicedUsers" v-for="user in slicedUsers"
:key="user.id" :key="user.id"
:to="userProfileLink(user)"
class="avatars-item"
> >
<UserAvatar :user="user" class="avatar-small" /> <UserAvatar
:user="user"
class="avatar-small"
/>
</router-link> </router-link>
</div> </div>
</template> </template>

View file

@ -7,20 +7,45 @@
@click.prevent.native="toggleUserExpanded" @click.prevent.native="toggleUserExpanded"
/> />
</router-link> </router-link>
<div class="basic-user-card-expanded-content" v-if="userExpanded"> <div
<UserCard :user="user" :rounded="true" :bordered="true"/> v-if="userExpanded"
class="basic-user-card-expanded-content"
>
<UserCard
:user="user"
:rounded="true"
:bordered="true"
/>
</div> </div>
<div class="basic-user-card-collapsed-content" v-else> <div
<div :title="user.name" class="basic-user-card-user-name"> v-else
<span v-if="user.name_html" class="basic-user-card-user-name-value" v-html="user.name_html"></span> class="basic-user-card-collapsed-content"
<span v-else class="basic-user-card-user-name-value">{{ user.name }}</span> >
<div
:title="user.name"
class="basic-user-card-user-name"
>
<!-- eslint-disable vue/no-v-html -->
<span
v-if="user.name_html"
class="basic-user-card-user-name-value"
v-html="user.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<span
v-else
class="basic-user-card-user-name-value"
>{{ user.name }}</span>
</div> </div>
<div> <div>
<router-link class="basic-user-card-screen-name" :to="userProfileLink(user)"> <router-link
@{{user.screen_name}} class="basic-user-card-screen-name"
:to="userProfileLink(user)"
>
@{{ user.screen_name }}
</router-link> </router-link>
</div> </div>
<slot></slot> <slot />
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,7 +1,12 @@
<template> <template>
<basic-user-card :user="user"> <basic-user-card :user="user">
<div class="block-card-content-container"> <div class="block-card-content-container">
<button class="btn btn-default" @click="unblockUser" :disabled="progress" v-if="blocked"> <button
v-if="blocked"
class="btn btn-default"
:disabled="progress"
@click="unblockUser"
>
<template v-if="progress"> <template v-if="progress">
{{ $t('user_card.unblock_progress') }} {{ $t('user_card.unblock_progress') }}
</template> </template>
@ -9,7 +14,12 @@
{{ $t('user_card.unblock') }} {{ $t('user_card.unblock') }}
</template> </template>
</button> </button>
<button class="btn btn-default" @click="blockUser" :disabled="progress" v-else> <button
v-else
class="btn btn-default"
:disabled="progress"
@click="blockUser"
>
<template v-if="progress"> <template v-if="progress">
{{ $t('user_card.block_progress') }} {{ $t('user_card.block_progress') }}
</template> </template>

View file

@ -16,7 +16,7 @@ const chatPanel = {
}, },
methods: { methods: {
submit (message) { submit (message) {
this.$store.state.chat.channel.push('new_msg', {text: message}, 10000) this.$store.state.chat.channel.push('new_msg', { text: message }, 10000)
this.currentMessage = '' this.currentMessage = ''
}, },
togglePanel () { togglePanel () {

View file

@ -1,41 +1,70 @@
<template> <template>
<div class="chat-panel" v-if="!this.collapsed || !this.floating"> <div
v-if="!collapsed || !floating"
class="chat-panel"
>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading timeline-heading" :class="{ 'chat-heading': floating }" @click.stop.prevent="togglePanel"> <div
class="panel-heading timeline-heading"
:class="{ 'chat-heading': floating }"
@click.stop.prevent="togglePanel"
>
<div class="title"> <div class="title">
<span>{{$t('chat.title')}}</span> <span>{{ $t('chat.title') }}</span>
<i class="icon-cancel" v-if="floating"></i> <i
v-if="floating"
class="icon-cancel"
/>
</div> </div>
</div> </div>
<div class="chat-window" v-chat-scroll> <div
<div class="chat-message" v-for="message in messages" :key="message.id"> v-chat-scroll
class="chat-window"
>
<div
v-for="message in messages"
:key="message.id"
class="chat-message"
>
<span class="chat-avatar"> <span class="chat-avatar">
<img :src="message.author.avatar" /> <img :src="message.author.avatar">
</span> </span>
<div class="chat-content"> <div class="chat-content">
<router-link <router-link
class="chat-name" class="chat-name"
:to="userProfileLink(message.author)"> :to="userProfileLink(message.author)"
{{message.author.username}} >
{{ message.author.username }}
</router-link> </router-link>
<br> <br>
<span class="chat-text"> <span class="chat-text">
{{message.text}} {{ message.text }}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div class="chat-input"> <div class="chat-input">
<textarea @keyup.enter="submit(currentMessage)" v-model="currentMessage" class="chat-input-textarea" rows="1"></textarea> <textarea
v-model="currentMessage"
class="chat-input-textarea"
rows="1"
@keyup.enter="submit(currentMessage)"
/>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="chat-panel"> <div
v-else
class="chat-panel"
>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading stub timeline-heading chat-heading" @click.stop.prevent="togglePanel"> <div
class="panel-heading stub timeline-heading chat-heading"
@click.stop.prevent="togglePanel"
>
<div class="title"> <div class="title">
<i class="icon-comment-empty"></i> <i class="icon-comment-empty" />
{{$t('chat.title')}} {{ $t('chat.title') }}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,8 +1,13 @@
<template> <template>
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" :indeterminate.prop="indeterminate"> <input
type="checkbox"
:checked="checked"
:indeterminate.prop="indeterminate"
@change="$emit('change', $event.target.checked)"
>
<i class="checkbox-indicator" /> <i class="checkbox-indicator" />
<span v-if="!!$slots.default"><slot></slot></span> <span v-if="!!$slots.default"><slot /></span>
</label> </label>
</template> </template>

View file

@ -1,33 +1,44 @@
<template> <template>
<div class="color-control style-control" :class="{ disabled: !present || disabled }"> <div
<label :for="name" class="label"> class="color-control style-control"
{{label}} :class="{ disabled: !present || disabled }"
</label> >
<input <label
v-if="typeof fallback !== 'undefined'" :for="name"
class="opt exlcude-disabled" class="label"
:id="name + '-o'"
type="checkbox"
:checked="present"
@input="$emit('input', typeof value === 'undefined' ? fallback : undefined)">
<label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label>
<input
:id="name"
class="color-input"
type="color"
:value="value || fallback"
:disabled="!present || disabled"
@input="$emit('input', $event.target.value)"
> >
<input {{ label }}
:id="name + '-t'" </label>
class="text-input" <input
type="text" v-if="typeof fallback !== 'undefined'"
:value="value || fallback" :id="name + '-o'"
:disabled="!present || disabled" class="opt exlcude-disabled"
@input="$emit('input', $event.target.value)" type="checkbox"
:checked="present"
@input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
> >
</div> <label
v-if="typeof fallback !== 'undefined'"
class="opt-l"
:for="name + '-o'"
/>
<input
:id="name"
class="color-input"
type="color"
:value="value || fallback"
:disabled="!present || disabled"
@input="$emit('input', $event.target.value)"
>
<input
:id="name + '-t'"
class="text-input"
type="text"
:value="value || fallback"
:disabled="!present || disabled"
@input="$emit('input', $event.target.value)"
>
</div>
</template> </template>
<script> <script>

View file

@ -1,28 +1,38 @@
<template> <template>
<span v-if="contrast" class="contrast-ratio"> <span
<span :title="hint" class="rating"> v-if="contrast"
<span v-if="contrast.aaa"> class="contrast-ratio"
<i class="icon-thumbs-up-alt"/> >
<span
:title="hint"
class="rating"
>
<span v-if="contrast.aaa">
<i class="icon-thumbs-up-alt" />
</span>
<span v-if="!contrast.aaa && contrast.aa">
<i class="icon-adjust" />
</span>
<span v-if="!contrast.aaa && !contrast.aa">
<i class="icon-attention" />
</span>
</span> </span>
<span v-if="!contrast.aaa && contrast.aa"> <span
<i class="icon-adjust"/> v-if="contrast && large"
</span> class="rating"
<span v-if="!contrast.aaa && !contrast.aa"> :title="hint_18pt"
<i class="icon-attention"/> >
<span v-if="contrast.laaa">
<i class="icon-thumbs-up-alt" />
</span>
<span v-if="!contrast.laaa && contrast.laa">
<i class="icon-adjust" />
</span>
<span v-if="!contrast.laaa && !contrast.laa">
<i class="icon-attention" />
</span>
</span> </span>
</span> </span>
<span class="rating" v-if="contrast && large" :title="hint_18pt">
<span v-if="contrast.laaa">
<i class="icon-thumbs-up-alt"/>
</span>
<span v-if="!contrast.laaa && contrast.laa">
<i class="icon-adjust"/>
</span>
<span v-if="!contrast.laaa && !contrast.laa">
<i class="icon-attention"/>
</span>
</span>
</span>
</template> </template>
<script> <script>

View file

@ -1,9 +1,9 @@
<template> <template>
<conversation <conversation
:collapsable="false" :collapsable="false"
isPage="true" is-page="true"
:statusoid="statusoid" :statusoid="statusoid"
></conversation> />
</template> </template>
<script src="./conversation-page.js"></script> <script src="./conversation-page.js"></script>

View file

@ -86,7 +86,8 @@ const conversation = {
}, },
replies () { replies () {
let i = 1 let i = 1
return reduce(this.conversation, (result, {id, in_reply_to_status_id}) => { // eslint-disable-next-line camelcase
return reduce(this.conversation, (result, { id, in_reply_to_status_id }) => {
/* eslint-disable camelcase */ /* eslint-disable camelcase */
const irid = in_reply_to_status_id const irid = in_reply_to_status_id
/* eslint-enable camelcase */ /* eslint-enable camelcase */
@ -119,15 +120,15 @@ const conversation = {
methods: { methods: {
fetchConversation () { fetchConversation () {
if (this.status) { if (this.status) {
this.$store.state.api.backendInteractor.fetchConversation({id: this.status.id}) this.$store.state.api.backendInteractor.fetchConversation({ id: this.status.id })
.then(({ancestors, descendants}) => { .then(({ ancestors, descendants }) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors }) this.$store.dispatch('addNewStatuses', { statuses: ancestors })
this.$store.dispatch('addNewStatuses', { statuses: descendants }) this.$store.dispatch('addNewStatuses', { statuses: descendants })
}) })
.then(() => this.setHighlight(this.statusId)) .then(() => this.setHighlight(this.statusId))
} else { } else {
const id = this.$route.params.id const id = this.$route.params.id
this.$store.state.api.backendInteractor.fetchStatus({id}) this.$store.state.api.backendInteractor.fetchStatus({ id })
.then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] })) .then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] }))
.then(() => this.fetchConversation()) .then(() => this.fetchConversation())
} }

View file

@ -1,25 +1,34 @@
<template> <template>
<div class="timeline panel-default" :class="[isExpanded ? 'panel' : 'panel-disabled']"> <div
<div v-if="isExpanded" class="panel-heading conversation-heading"> class="timeline panel-default"
:class="[isExpanded ? 'panel' : 'panel-disabled']"
>
<div
v-if="isExpanded"
class="panel-heading conversation-heading"
>
<span class="title"> {{ $t('timeline.conversation') }} </span> <span class="title"> {{ $t('timeline.conversation') }} </span>
<span v-if="collapsable"> <span v-if="collapsable">
<a href="#" @click.prevent="toggleExpanded">{{ $t('timeline.collapse') }}</a> <a
href="#"
@click.prevent="toggleExpanded"
>{{ $t('timeline.collapse') }}</a>
</span> </span>
</div> </div>
<status <status
v-for="status in conversation" v-for="status in conversation"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
:key="status.id" :key="status.id"
:inlineExpanded="collapsable && isExpanded" :inline-expanded="collapsable && isExpanded"
:statusoid="status" :statusoid="status"
:expandable='!isExpanded' :expandable="!isExpanded"
:showPinned="showPinned" :show-pinned="showPinned"
:focused="focused(status.id)" :focused="focused(status.id)"
:inConversation="isExpanded" :in-conversation="isExpanded"
:highlight="getHighlight()" :highlight="getHighlight()"
:replies="getReplies(status.id)" :replies="getReplies(status.id)"
class="status-fadein panel-body" class="status-fadein panel-body"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
/> />
</div> </div>
</template> </template>

View file

@ -1,16 +1,22 @@
<template> <template>
<span v-bind:class="{ 'dark-overlay': darkOverlay }" @click.self.stop='onCancel()'> <span
<div class="dialog-modal panel panel-default" @click.stop=''> :class="{ 'dark-overlay': darkOverlay }"
@click.self.stop="onCancel()"
>
<div
class="dialog-modal panel panel-default"
@click.stop=""
>
<div class="panel-heading dialog-modal-heading"> <div class="panel-heading dialog-modal-heading">
<div class="title"> <div class="title">
<slot name="header"></slot> <slot name="header" />
</div> </div>
</div> </div>
<div class="dialog-modal-content"> <div class="dialog-modal-content">
<slot name="default"></slot> <slot name="default" />
</div> </div>
<div class="dialog-modal-footer user-interactions panel-footer"> <div class="dialog-modal-footer user-interactions panel-footer">
<slot name="footer"></slot> <slot name="footer" />
</div> </div>
</div> </div>
</span> </span>

View file

@ -1,5 +1,9 @@
<template> <template>
<Timeline :title="$t('nav.dms')" v-bind:timeline="timeline" v-bind:timeline-name="'dms'"/> <Timeline
:title="$t('nav.dms')"
:timeline="timeline"
:timeline-name="'dms'"
/>
</template> </template>
<script src="./dm_timeline.js"></script> <script src="./dm_timeline.js"></script>

View file

@ -53,7 +53,7 @@ const EmojiInput = {
required: true, required: true,
type: String type: String
} }
}, },
data () { data () {
return { return {
input: undefined, input: undefined,
@ -105,6 +105,7 @@ const EmojiInput = {
input.elm.addEventListener('keyup', this.onKeyUp) input.elm.addEventListener('keyup', this.onKeyUp)
input.elm.addEventListener('keydown', this.onKeyDown) input.elm.addEventListener('keydown', this.onKeyDown)
input.elm.addEventListener('transitionend', this.onTransition) input.elm.addEventListener('transitionend', this.onTransition)
input.elm.addEventListener('compositionupdate', this.onCompositionUpdate)
}, },
unmounted () { unmounted () {
const { input } = this const { input } = this
@ -115,6 +116,7 @@ const EmojiInput = {
input.elm.removeEventListener('keyup', this.onKeyUp) input.elm.removeEventListener('keyup', this.onKeyUp)
input.elm.removeEventListener('keydown', this.onKeyDown) input.elm.removeEventListener('keydown', this.onKeyDown)
input.elm.removeEventListener('transitionend', this.onTransition) input.elm.removeEventListener('transitionend', this.onTransition)
input.elm.removeEventListener('compositionupdate', this.onCompositionUpdate)
} }
}, },
methods: { methods: {
@ -225,6 +227,12 @@ const EmojiInput = {
} }
}, },
onInput (e) { onInput (e) {
this.setCaret(e)
this.$emit('input', e.target.value)
},
onCompositionUpdate (e) {
this.setCaret(e)
this.resize()
this.$emit('input', e.target.value) this.$emit('input', e.target.value)
}, },
setCaret ({ target: { selectionStart } }) { setCaret ({ target: { selectionStart } }) {

View file

@ -1,27 +1,34 @@
<template> <template>
<div class="emoji-input"> <div class="emoji-input">
<slot></slot> <slot />
<div ref="panel" class="autocomplete-panel" :class="{ hide: !showPopup }"> <div
<div class="autocomplete-panel-body"> ref="panel"
<div class="autocomplete-panel"
v-for="(suggestion, index) in suggestions" :class="{ hide: !showPopup }"
:key="index" >
@click.stop.prevent="onClick($event, suggestion)" <div class="autocomplete-panel-body">
class="autocomplete-item" <div
:class="{ highlighted: suggestion.highlighted }" v-for="(suggestion, index) in suggestions"
:key="index"
class="autocomplete-item"
:class="{ highlighted: suggestion.highlighted }"
@click.stop.prevent="onClick($event, suggestion)"
> >
<span class="image"> <span class="image">
<img v-if="suggestion.img":src="suggestion.img" /> <img
<span v-else>{{suggestion.replacement}}</span> v-if="suggestion.img"
</span> :src="suggestion.img"
<div class="label"> >
<span class="displayText">{{suggestion.displayText}}</span> <span v-else>{{ suggestion.replacement }}</span>
<span class="detailText">{{suggestion.detailText}}</span> </span>
<div class="label">
<span class="displayText">{{ suggestion.displayText }}</span>
<span class="detailText">{{ suggestion.detailText }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script src="./emoji-input.js"></script> <script src="./emoji-input.js"></script>
@ -103,7 +110,6 @@
} }
} }
input, textarea { input, textarea {
flex: 1 0 auto; flex: 1 0 auto;
} }

View file

@ -1,12 +1,27 @@
<template> <template>
<div class="import-export-container"> <div class="import-export-container">
<slot name="before"/> <slot name="before" />
<button class="btn" @click="exportData">{{ exportLabel }}</button> <button
<button class="btn" @click="importData">{{ importLabel }}</button> class="btn"
<slot name="afterButtons"/> @click="exportData"
<p v-if="importFailed" class="alert error">{{ importFailedText }}</p> >
<slot name="afterError"/> {{ exportLabel }}
</div> </button>
<button
class="btn"
@click="importData"
>
{{ importLabel }}
</button>
<slot name="afterButtons" />
<p
v-if="importFailed"
class="alert error"
>
{{ importFailedText }}
</p>
<slot name="afterError" />
</div>
</template> </template>
<script> <script>
@ -49,7 +64,7 @@ export default {
if (event.target.files[0]) { if (event.target.files[0]) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const reader = new FileReader() const reader = new FileReader()
reader.onload = ({target}) => { reader.onload = ({ target }) => {
try { try {
const parsed = JSON.parse(target.result) const parsed = JSON.parse(target.result)
const valid = this.validator(parsed) const valid = this.validator(parsed)

View file

@ -1,10 +1,16 @@
<template> <template>
<div class="exporter"> <div class="exporter">
<div v-if="processing"> <div v-if="processing">
<i class="icon-spin4 animate-spin exporter-processing"></i> <i class="icon-spin4 animate-spin exporter-processing" />
<span>{{processingMessage}}</span> <span>{{ processingMessage }}</span>
</div> </div>
<button class="btn btn-default" @click="process" v-else>{{exportButtonLabel}}</button> <button
v-else
class="btn btn-default"
@click="process"
>
{{ exportButtonLabel }}
</button>
</div> </div>
</template> </template>

View file

@ -1,9 +1,8 @@
<template> <template>
<Popper <Popper
trigger="click"
@hide='showDropDown = false'
append-to-body
v-if="enabled && showPopper" v-if="enabled && showPopper"
trigger="click"
append-to-body
:options="{ :options="{
placement: 'top', placement: 'top',
modifiers: { modifiers: {
@ -11,22 +10,42 @@
offset: { offset: '0, 5px' }, offset: { offset: '0, 5px' },
} }
}" }"
@hide="showDropDown = false"
> >
<div class="popper-wrapper"> <div class="popper-wrapper">
<div class="dropdown-menu"> <div class="dropdown-menu">
<button class="dropdown-item dropdown-item-icon" @click.prevent="pinStatus" v-if="!status.pinned && canPin"> <button
<i class="icon-pin"></i><span>{{$t("status.pin")}}</span> v-if="!status.pinned && canPin"
class="dropdown-item dropdown-item-icon"
@click.prevent="pinStatus"
>
<i class="icon-pin" /><span>{{ $t("status.pin") }}</span>
</button> </button>
<button class="dropdown-item dropdown-item-icon" @click.prevent="unpinStatus" v-if="status.pinned && canPin"> <button
<i class="icon-pin"></i><span>{{$t("status.unpin")}}</span> v-if="status.pinned && canPin"
class="dropdown-item dropdown-item-icon"
@click.prevent="unpinStatus"
>
<i class="icon-pin" /><span>{{ $t("status.unpin") }}</span>
</button> </button>
<button class="dropdown-item dropdown-item-icon" @click.prevent="deleteStatus" v-if="canDelete"> <button
<i class="icon-cancel"></i><span>{{$t("status.delete")}}</span> v-if="canDelete"
class="dropdown-item dropdown-item-icon"
@click.prevent="deleteStatus"
>
<i class="icon-cancel" /><span>{{ $t("status.delete") }}</span>
</button> </button>
</div> </div>
</div> </div>
<div class="button-icon" slot="reference" @click="toggleMenu"> <div
<i class='icon-ellipsis' :class="{'icon-clicked': showDropDown}"></i> slot="reference"
class="button-icon"
@click="toggleMenu"
>
<i
class="icon-ellipsis"
:class="{'icon-clicked': showDropDown}"
/>
</div> </div>
</Popper> </Popper>
</template> </template>

View file

@ -11,9 +11,9 @@ const FavoriteButton = {
methods: { methods: {
favorite () { favorite () {
if (!this.status.favorited) { if (!this.status.favorited) {
this.$store.dispatch('favorite', {id: this.status.id}) this.$store.dispatch('favorite', { id: this.status.id })
} else { } else {
this.$store.dispatch('unfavorite', {id: this.status.id}) this.$store.dispatch('unfavorite', { id: this.status.id })
} }
this.animated = true this.animated = true
setTimeout(() => { setTimeout(() => {

View file

@ -1,11 +1,20 @@
<template> <template>
<div v-if="loggedIn"> <div v-if="loggedIn">
<i :class='classes' class='button-icon favorite-button fav-active' @click.prevent='favorite()' :title="$t('tool_tip.favorite')"/> <i
<span v-if='!hidePostStatsLocal && status.fave_num > 0'>{{status.fave_num}}</span> :class="classes"
class="button-icon favorite-button fav-active"
:title="$t('tool_tip.favorite')"
@click.prevent="favorite()"
/>
<span v-if="!hidePostStatsLocal && status.fave_num > 0">{{ status.fave_num }}</span>
</div> </div>
<div v-else> <div v-else>
<i :class='classes' class='button-icon favorite-button' :title="$t('tool_tip.favorite')"/> <i
<span v-if='!hidePostStatsLocal && status.fave_num > 0'>{{status.fave_num}}</span> :class="classes"
class="button-icon favorite-button"
:title="$t('tool_tip.favorite')"
/>
<span v-if="!hidePostStatsLocal && status.fave_num > 0">{{ status.fave_num }}</span>
</div> </div>
</template> </template>

View file

@ -3,17 +3,25 @@
<div class="panel panel-default base01-background"> <div class="panel panel-default base01-background">
<div class="panel-heading timeline-heading base02-background base04"> <div class="panel-heading timeline-heading base02-background base04">
<div class="title"> <div class="title">
{{$t('features_panel.title')}} {{ $t('features_panel.title') }}
</div> </div>
</div> </div>
<div class="panel-body features-panel"> <div class="panel-body features-panel">
<ul> <ul>
<li v-if="chat">{{$t('features_panel.chat')}}</li> <li v-if="chat">
<li v-if="gopher">{{$t('features_panel.gopher')}}</li> {{ $t('features_panel.chat') }}
<li v-if="whoToFollow">{{$t('features_panel.who_to_follow')}}</li> </li>
<li v-if="mediaProxy">{{$t('features_panel.media_proxy')}}</li> <li v-if="gopher">
<li>{{$t('features_panel.scope_options')}}</li> {{ $t('features_panel.gopher') }}
<li>{{$t('features_panel.text_limit')}} = {{textlimit}}</li> </li>
<li v-if="whoToFollow">
{{ $t('features_panel.who_to_follow') }}
</li>
<li v-if="mediaProxy">
{{ $t('features_panel.media_proxy') }}
</li>
<li>{{ $t('features_panel.scope_options') }}</li>
<li>{{ $t('features_panel.text_limit') }} = {{ textlimit }}</li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -1,11 +1,17 @@
<template> <template>
<basic-user-card :user="user"> <basic-user-card :user="user">
<div class="follow-card-content-container"> <div class="follow-card-content-container">
<span class="faint" v-if="!noFollowsYou && user.follows_you"> <span
v-if="!noFollowsYou && user.follows_you"
class="faint"
>
{{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }} {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span> </span>
<template v-if="!loggedIn"> <template v-if="!loggedIn">
<div class="follow-card-follow-button" v-if="!user.following"> <div
v-if="!user.following"
class="follow-card-follow-button"
>
<RemoteFollow :user="user" /> <RemoteFollow :user="user" />
</div> </div>
</template> </template>
@ -13,9 +19,9 @@
<button <button
v-if="!user.following" v-if="!user.following"
class="btn btn-default follow-card-follow-button" class="btn btn-default follow-card-follow-button"
@click="followUser"
:disabled="inProgress" :disabled="inProgress"
:title="requestSent ? $t('user_card.follow_again') : ''" :title="requestSent ? $t('user_card.follow_again') : ''"
@click="followUser"
> >
<template v-if="inProgress"> <template v-if="inProgress">
{{ $t('user_card.follow_progress') }} {{ $t('user_card.follow_progress') }}
@ -27,7 +33,12 @@
{{ $t('user_card.follow') }} {{ $t('user_card.follow') }}
</template> </template>
</button> </button>
<button v-else class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress"> <button
v-else
class="btn btn-default follow-card-follow-button pressed"
:disabled="inProgress"
@click="unfollowUser"
>
<template v-if="inProgress"> <template v-if="inProgress">
{{ $t('user_card.follow_progress') }} {{ $t('user_card.follow_progress') }}
</template> </template>

View file

@ -1,8 +1,18 @@
<template> <template>
<basic-user-card :user="user"> <basic-user-card :user="user">
<div class="follow-request-card-content-container"> <div class="follow-request-card-content-container">
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button> <button
<button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button> class="btn btn-default"
@click="approveUser"
>
{{ $t('user_card.approve') }}
</button>
<button
class="btn btn-default"
@click="denyUser"
>
{{ $t('user_card.deny') }}
</button>
</div> </div>
</basic-user-card> </basic-user-card>
</template> </template>

View file

@ -1,10 +1,15 @@
<template> <template>
<div class="settings panel panel-default"> <div class="settings panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{{$t('nav.friend_requests')}} {{ $t('nav.friend_requests') }}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<FollowRequestCard v-for="request in requests" :key="request.id" :user="request" class="list-item"/> <FollowRequestCard
v-for="request in requests"
:key="request.id"
:user="request"
class="list-item"
/>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,35 +1,56 @@
<template> <template>
<div class="font-control style-control" :class="{ custom: isCustom }"> <div
<label :for="preset === 'custom' ? name : name + '-font-switcher'" class="label"> class="font-control style-control"
{{label}} :class="{ custom: isCustom }"
</label> >
<input <label
v-if="typeof fallback !== 'undefined'" :for="preset === 'custom' ? name : name + '-font-switcher'"
class="opt exlcude-disabled" class="label"
type="checkbox" >
:id="name + '-o'" {{ label }}
:checked="present" </label>
@input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"> <input
<label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label> v-if="typeof fallback !== 'undefined'"
<label :for="name + '-font-switcher'" class="select" :disabled="!present"> :id="name + '-o'"
<select class="opt exlcude-disabled"
type="checkbox"
:checked="present"
@input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
>
<label
v-if="typeof fallback !== 'undefined'"
class="opt-l"
:for="name + '-o'"
/>
<label
:for="name + '-font-switcher'"
class="select"
:disabled="!present" :disabled="!present"
v-model="preset" >
class="font-switcher" <select
:id="name + '-font-switcher'"> :id="name + '-font-switcher'"
<option v-for="option in availableOptions" :value="option"> v-model="preset"
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }} :disabled="!present"
</option> class="font-switcher"
</select> >
<i class="icon-down-open"/> <option
</label> v-for="option in availableOptions"
<input :key="option"
v-if="isCustom" :value="option"
class="custom-font" >
type="text" {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
:id="name" </option>
v-model="family"> </select>
</div> <i class="icon-down-open" />
</label>
<input
v-if="isCustom"
:id="name"
v-model="family"
class="custom-font"
type="text"
>
</div>
</template> </template>
<script src="./font_control.js" ></script> <script src="./font_control.js" ></script>

View file

@ -1,5 +1,9 @@
<template> <template>
<Timeline :title="$t('nav.timeline')" v-bind:timeline="timeline" v-bind:timeline-name="'friends'"/> <Timeline
:title="$t('nav.timeline')"
:timeline="timeline"
:timeline-name="'friends'"
/>
</template> </template>
<script src="./friends_timeline.js"></script> <script src="./friends_timeline.js"></script>

View file

@ -1,13 +1,22 @@
<template> <template>
<div ref="galleryContainer" style="width: 100%;"> <div
<div class="gallery-row" v-for="row in rows" :style="rowHeight(row.length)" :class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }"> ref="galleryContainer"
style="width: 100%;"
>
<div
v-for="(row, index) in rows"
:key="index"
class="gallery-row"
:style="rowHeight(row.length)"
:class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }"
>
<attachment <attachment
v-for="attachment in row" v-for="attachment in row"
:setMedia="setMedia" :key="attachment.id"
:set-media="setMedia"
:nsfw="nsfw" :nsfw="nsfw"
:attachment="attachment" :attachment="attachment"
:allowPlay="false" :allow-play="false"
:key="attachment.id"
/> />
</div> </div>
</div> </div>

View file

@ -2,20 +2,57 @@
<div class="image-cropper"> <div class="image-cropper">
<div v-if="dataUrl"> <div v-if="dataUrl">
<div class="image-cropper-image-container"> <div class="image-cropper-image-container">
<img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" /> <img
ref="img"
:src="dataUrl"
alt=""
@load.stop="createCropper"
>
</div> </div>
<div class="image-cropper-buttons-wrapper"> <div class="image-cropper-buttons-wrapper">
<button class="btn" type="button" :disabled="submitting" @click="submit()" v-text="saveText"></button> <button
<button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button> class="btn"
<button class="btn" type="button" :disabled="submitting" @click="submit(false)" v-text="saveWithoutCroppingText"></button> type="button"
<i class="icon-spin4 animate-spin" v-if="submitting"></i> :disabled="submitting"
@click="submit()"
v-text="saveText"
/>
<button
class="btn"
type="button"
:disabled="submitting"
@click="destroy"
v-text="cancelText"
/>
<button
class="btn"
type="button"
:disabled="submitting"
@click="submit(false)"
v-text="saveWithoutCroppingText"
/>
<i
v-if="submitting"
class="icon-spin4 animate-spin"
/>
</div> </div>
<div class="alert error" v-if="submitError"> <div
{{submitErrorMsg}} v-if="submitError"
<i class="button-icon icon-cancel" @click="clearError"></i> class="alert error"
>
{{ submitErrorMsg }}
<i
class="button-icon icon-cancel"
@click="clearError"
/>
</div> </div>
</div> </div>
<input ref="input" type="file" class="image-cropper-img-input" :accept="mimes"> <input
ref="input"
type="file"
class="image-cropper-img-input"
:accept="mimes"
>
</div> </div>
</template> </template>

View file

@ -1,17 +1,36 @@
<template> <template>
<div class="importer"> <div class="importer">
<form> <form>
<input type="file" ref="input" v-on:change="change" /> <input
ref="input"
type="file"
@change="change"
>
</form> </form>
<i class="icon-spin4 animate-spin importer-uploading" v-if="submitting"></i> <i
<button class="btn btn-default" v-else @click="submit">{{submitButtonLabel}}</button> v-if="submitting"
class="icon-spin4 animate-spin importer-uploading"
/>
<button
v-else
class="btn btn-default"
@click="submit"
>
{{ submitButtonLabel }}
</button>
<div v-if="success"> <div v-if="success">
<i class="icon-cross" @click="dismiss"></i> <i
<p>{{successMessage}}</p> class="icon-cross"
@click="dismiss"
/>
<p>{{ successMessage }}</p>
</div> </div>
<div v-else-if="error"> <div v-else-if="error">
<i class="icon-cross" @click="dismiss"></i> <i
<p>{{errorMessage}}</p> class="icon-cross"
@click="dismiss"
/>
<p>{{ errorMessage }}</p>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,9 +1,13 @@
<template> <template>
<div v-if="show" class="instance-specific-panel"> <div
v-if="show"
class="instance-specific-panel"
>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-body"> <div class="panel-body">
<div v-html="instanceSpecificPanelContent"> <!-- eslint-disable vue/no-v-html -->
</div> <div v-html="instanceSpecificPanelContent" />
<!-- eslint-enable vue/no-v-html -->
</div> </div>
</div> </div>
</div> </div>

View file

@ -7,18 +7,30 @@
</div> </div>
<tab-switcher <tab-switcher
ref="tabSwitcher" ref="tabSwitcher"
:onSwitch="onModeSwitch" :on-switch="onModeSwitch"
> >
<span data-tab-dummy data-filter="mentions" :label="$t('nav.mentions')"/> <span
<span data-tab-dummy data-filter="likes+repeats" :label="$t('interactions.favs_repeats')"/> data-tab-dummy
<span data-tab-dummy data-filter="follows" :label="$t('interactions.follows')"/> data-filter="mentions"
:label="$t('nav.mentions')"
/>
<span
data-tab-dummy
data-filter="likes+repeats"
:label="$t('interactions.favs_repeats')"
/>
<span
data-tab-dummy
data-filter="follows"
:label="$t('interactions.follows')"
/>
</tab-switcher> </tab-switcher>
<Notifications <Notifications
ref="notifications" ref="notifications"
:noHeading="true" :no-heading="true"
:minimalMode="true" :minimal-mode="true"
:filterMode="filterMode" :filter-mode="filterMode"
/> />
</div> </div>
</template> </template>

View file

@ -3,50 +3,60 @@
<label for="interface-language-switcher"> <label for="interface-language-switcher">
{{ $t('settings.interfaceLanguage') }} {{ $t('settings.interfaceLanguage') }}
</label> </label>
<label for="interface-language-switcher" class='select'> <label
<select id="interface-language-switcher" v-model="language"> for="interface-language-switcher"
<option v-for="(langCode, i) in languageCodes" :value="langCode"> class="select"
>
<select
id="interface-language-switcher"
v-model="language"
>
<option
v-for="(langCode, i) in languageCodes"
:key="langCode"
:value="langCode"
>
{{ languageNames[i] }} {{ languageNames[i] }}
</option> </option>
</select> </select>
<i class="icon-down-open"/> <i class="icon-down-open" />
</label> </label>
</div> </div>
</template> </template>
<script> <script>
import languagesObject from '../../i18n/messages' import languagesObject from '../../i18n/messages'
import ISO6391 from 'iso-639-1' import ISO6391 from 'iso-639-1'
import _ from 'lodash' import _ from 'lodash'
export default { export default {
computed: { computed: {
languageCodes () { languageCodes () {
return Object.keys(languagesObject) return Object.keys(languagesObject)
},
languageNames () {
return _.map(this.languageCodes, this.getLanguageName)
},
language: {
get: function () { return this.$store.state.config.interfaceLanguage },
set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
this.$i18n.locale = val
}
}
}, },
methods: { languageNames () {
getLanguageName (code) { return _.map(this.languageCodes, this.getLanguageName)
const specialLanguageNames = { },
'ja': 'Japanese (やさしいにほんご)',
'ja_pedantic': 'Japanese (日本語)', language: {
'zh': 'Chinese (简体中文)' get: function () { return this.$store.state.config.interfaceLanguage },
} set: function (val) {
return specialLanguageNames[code] || ISO6391.getName(code) this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
this.$i18n.locale = val
} }
} }
},
methods: {
getLanguageName (code) {
const specialLanguageNames = {
'ja': 'Japanese (やさしいにほんご)',
'ja_pedantic': 'Japanese (日本語)',
'zh': 'Chinese (简体中文)'
}
return specialLanguageNames[code] || ISO6391.getName(code)
}
} }
}
</script> </script>

View file

@ -1,13 +1,25 @@
<template> <template>
<div> <div>
<a class="link-preview-card" :href="card.url" target="_blank" rel="noopener"> <a
<div class="card-image" :class="{ 'small-image': size === 'small' }" v-if="useImage"> class="link-preview-card"
<img :src="card.image"></img> :href="card.url"
target="_blank"
rel="noopener"
>
<div
v-if="useImage"
class="card-image"
:class="{ 'small-image': size === 'small' }"
>
<img :src="card.image">
</div> </div>
<div class="card-content"> <div class="card-content">
<span class="card-host faint">{{ card.provider_name }}</span> <span class="card-host faint">{{ card.provider_name }}</span>
<h4 class="card-title">{{ card.title }}</h4> <h4 class="card-title">{{ card.title }}</h4>
<p class="card-description" v-if="useDescription">{{ card.description }}</p> <p
v-if="useDescription"
class="card-description"
>{{ card.description }}</p>
</div> </div>
</a> </a>
</div> </div>

View file

@ -1,9 +1,19 @@
<template> <template>
<div class="list"> <div class="list">
<div v-for="item in items" class="list-item" :key="getKey(item)"> <div
<slot name="item" :item="item" /> v-for="item in items"
:key="getKey(item)"
class="list-item"
>
<slot
name="item"
:item="item"
/>
</div> </div>
<div class="list-empty-content faint" v-if="items.length === 0 && !!$slots.empty"> <div
v-if="items.length === 0 && !!$slots.empty"
class="list-empty-content faint"
>
<slot name="empty" /> <slot name="empty" />
</div> </div>
</div> </div>

View file

@ -58,7 +58,7 @@ const LoginForm = {
).then((result) => { ).then((result) => {
if (result.error) { if (result.error) {
if (result.error === 'mfa_required') { if (result.error === 'mfa_required') {
this.requireMFA({app: app, settings: result}) this.requireMFA({ app: app, settings: result })
} else { } else {
this.error = result.error this.error = result.error
this.focusOnPasswordInput() this.focusOnPasswordInput()
@ -66,7 +66,7 @@ const LoginForm = {
return return
} }
this.login(result).then(() => { this.login(result).then(() => {
this.$router.push({name: 'friends'}) this.$router.push({ name: 'friends' })
}) })
}) })
}) })

View file

@ -1,53 +1,83 @@
<template> <template>
<div class="login panel panel-default"> <div class="login panel panel-default">
<!-- Default panel contents --> <!-- Default panel contents -->
<div class="panel-heading">{{$t('login.login')}}</div> <div class="panel-heading">
{{ $t('login.login') }}
</div>
<div class="panel-body"> <div class="panel-body">
<form class='login-form' @submit.prevent='submit'> <form
<template v-if="isPasswordAuth"> class="login-form"
<div class='form-group'> @submit.prevent="submit"
<label for='username'>{{$t('login.username')}}</label> >
<input :disabled="loggingIn" v-model='user.username' <template v-if="isPasswordAuth">
class='form-control' id='username' <div class="form-group">
:placeholder="$t('login.placeholder')"> <label for="username">{{ $t('login.username') }}</label>
</div> <input
<div class='form-group'> id="username"
<label for='password'>{{$t('login.password')}}</label> v-model="user.username"
<input :disabled="loggingIn" v-model='user.password' :disabled="loggingIn"
ref='passwordInput' class='form-control' id='password' type='password'> class="form-control"
</div> :placeholder="$t('login.placeholder')"
</template> >
<div class="form-group" v-if="isTokenAuth">
<p>{{$t('login.description')}}</p>
</div>
<div class='form-group'>
<div class='login-bottom'>
<div>
<router-link :to="{name: 'registration'}"
v-if='registrationOpen'
class='register'>
{{$t('login.register')}}
</router-link>
</div> </div>
<button :disabled="loggingIn" type='submit' class='btn btn-default'> <div class="form-group">
{{$t('login.login')}} <label for="password">{{ $t('login.password') }}</label>
</button> <input
</div> id="password"
</div> ref="passwordInput"
</form> v-model="user.password"
</div> :disabled="loggingIn"
class="form-control"
type="password"
>
</div>
</template>
<div v-if="error" class='form-group'> <div
<div class='alert error'> v-if="isTokenAuth"
{{error}} class="form-group"
<i class="button-icon icon-cancel" @click="clearError"></i> >
<p>{{ $t('login.description') }}</p>
</div>
<div class="form-group">
<div class="login-bottom">
<div>
<router-link
v-if="registrationOpen"
:to="{name: 'registration'}"
class="register"
>
{{ $t('login.register') }}
</router-link>
</div>
<button
:disabled="loggingIn"
type="submit"
class="btn btn-default"
>
{{ $t('login.login') }}
</button>
</div>
</div>
</form>
</div>
<div
v-if="error"
class="form-group"
>
<div class="alert error">
{{ error }}
<i
class="button-icon icon-cancel"
@click="clearError"
/>
</div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script src="./login_form.js" ></script> <script src="./login_form.js" ></script>

View file

@ -1,25 +1,33 @@
<template> <template>
<div class="modal-view media-modal-view" v-if="showing" @click.prevent="hide"> <div
<img class="modal-image" v-if="type === 'image'" :src="currentMedia.url"></img> v-if="showing"
<VideoAttachment class="modal-view media-modal-view"
@click.prevent="hide"
>
<img
v-if="type === 'image'"
class="modal-image" class="modal-image"
:src="currentMedia.url"
>
<VideoAttachment
v-if="type === 'video'" v-if="type === 'video'"
class="modal-image"
:attachment="currentMedia" :attachment="currentMedia"
:controls="true" :controls="true"
@click.stop.native=""> @click.stop.native=""
</VideoAttachment> />
<button <button
v-if="canNavigate"
:title="$t('media_modal.previous')" :title="$t('media_modal.previous')"
class="modal-view-button-arrow modal-view-button-arrow--prev" class="modal-view-button-arrow modal-view-button-arrow--prev"
v-if="canNavigate"
@click.stop.prevent="goPrev" @click.stop.prevent="goPrev"
> >
<i class="icon-left-open arrow-icon" /> <i class="icon-left-open arrow-icon" />
</button> </button>
<button <button
v-if="canNavigate"
:title="$t('media_modal.next')" :title="$t('media_modal.next')"
class="modal-view-button-arrow modal-view-button-arrow--next" class="modal-view-button-arrow modal-view-button-arrow--next"
v-if="canNavigate"
@click.stop.prevent="goNext" @click.stop.prevent="goNext"
> >
<i class="icon-right-open arrow-icon" /> <i class="icon-right-open arrow-icon" />

View file

@ -16,7 +16,7 @@ const mediaUpload = {
if (file.size > store.state.instance.uploadlimit) { if (file.size > store.state.instance.uploadlimit) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size) const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(store.state.instance.uploadlimit) const allowedsize = fileSizeFormatService.fileSizeFormat(store.state.instance.uploadlimit)
self.$emit('upload-failed', 'file_too_big', {filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit}) self.$emit('upload-failed', 'file_too_big', { filesize: filesize.num, filesizeunit: filesize.unit, allowedsize: allowedsize.num, allowedsizeunit: allowedsize.unit })
return return
} }
const formData = new FormData() const formData = new FormData()
@ -36,7 +36,7 @@ const mediaUpload = {
}, },
fileDrop (e) { fileDrop (e) {
if (e.dataTransfer.files.length > 0) { if (e.dataTransfer.files.length > 0) {
e.preventDefault() // allow dropping text like before e.preventDefault() // allow dropping text like before
this.uploadFile(e.dataTransfer.files[0]) this.uploadFile(e.dataTransfer.files[0])
} }
}, },
@ -54,7 +54,7 @@ const mediaUpload = {
this.uploadReady = true this.uploadReady = true
}) })
}, },
change ({target}) { change ({ target }) {
for (var i = 0; i < target.files.length; i++) { for (var i = 0; i < target.files.length; i++) {
let file = target.files[i] let file = target.files[i]
this.uploadFile(file) this.uploadFile(file)

View file

@ -1,9 +1,29 @@
<template> <template>
<div class="media-upload" @drop.prevent @dragover.prevent="fileDrag" @drop="fileDrop"> <div
<label class="btn btn-default" :title="$t('tool_tip.media_upload')"> class="media-upload"
<i class="icon-spin4 animate-spin" v-if="uploading"></i> @drop.prevent
<i class="icon-upload" v-if="!uploading"></i> @dragover.prevent="fileDrag"
<input type="file" v-if="uploadReady" @change="change" style="position: fixed; top: -100em" multiple="true"></input> @drop="fileDrop"
>
<label
class="btn btn-default"
:title="$t('tool_tip.media_upload')"
>
<i
v-if="uploading"
class="icon-spin4 animate-spin"
/>
<i
v-if="!uploading"
class="icon-upload"
/>
<input
v-if="uploadReady"
type="file"
style="position: fixed; top: -100em"
multiple="true"
@change="change"
>
</label> </label>
</div> </div>
</template> </template>

View file

@ -1,5 +1,9 @@
<template> <template>
<Timeline :title="$t('nav.interactions')" v-bind:timeline="timeline" v-bind:timeline-name="'mentions'"/> <Timeline
:title="$t('nav.interactions')"
:timeline="timeline"
:timeline-name="'mentions'"
/>
</template> </template>
<script src="./mentions.js"></script> <script src="./mentions.js"></script>

View file

@ -33,7 +33,7 @@ export default {
} }
this.login(result).then(() => { this.login(result).then(() => {
this.$router.push({name: 'friends'}) this.$router.push({ name: 'friends' })
}) })
}) })
} }

View file

@ -1,42 +1,65 @@
<template> <template>
<div class="login panel panel-default"> <div class="login panel panel-default">
<!-- Default panel contents --> <!-- Default panel contents -->
<div class="panel-heading">{{$t('login.heading.recovery')}}</div> <div class="panel-heading">
{{ $t('login.heading.recovery') }}
</div>
<div class="panel-body"> <div class="panel-body">
<form class='login-form' @submit.prevent='submit'> <form
<div class='form-group'> class="login-form"
<label for='code'>{{$t('login.recovery_code')}}</label> @submit.prevent="submit"
<input v-model='code' class='form-control' id='code'> >
</div> <div class="form-group">
<label for="code">{{ $t('login.recovery_code') }}</label>
<div class='form-group'> <input
<div class='login-bottom'> id="code"
<div> v-model="code"
<a href="#" @click.prevent="requireTOTP"> class="form-control"
{{$t('login.enter_two_factor_code')}} >
</a>
<br />
<a href="#" @click.prevent="abortMFA">
{{$t('general.cancel')}}
</a>
</div>
<button type='submit' class='btn btn-default'>
{{$t('general.verify')}}
</button>
</div> </div>
<div class="form-group">
<div class="login-bottom">
<div>
<a
href="#"
@click.prevent="requireTOTP"
>
{{ $t('login.enter_two_factor_code') }}
</a>
<br>
<a
href="#"
@click.prevent="abortMFA"
>
{{ $t('general.cancel') }}
</a>
</div>
<button
type="submit"
class="btn btn-default"
>
{{ $t('general.verify') }}
</button>
</div>
</div>
</form>
</div>
<div
v-if="error"
class="form-group"
>
<div class="alert error">
{{ error }}
<i
class="button-icon icon-cancel"
@click="clearError"
/>
</div> </div>
</form>
</div>
<div v-if="error" class='form-group'>
<div class='alert error'>
{{error}}
<i class="button-icon icon-cancel" @click="clearError"></i>
</div> </div>
</div> </div>
</div>
</template> </template>
<script src="./recovery_form.js" ></script> <script src="./recovery_form.js" ></script>

View file

@ -32,7 +32,7 @@ export default {
} }
this.login(result).then(() => { this.login(result).then(() => {
this.$router.push({name: 'friends'}) this.$router.push({ name: 'friends' })
}) })
}) })
} }

View file

@ -1,45 +1,67 @@
<template> <template>
<div class="login panel panel-default"> <div class="login panel panel-default">
<!-- Default panel contents --> <!-- Default panel contents -->
<div class="panel-heading"> <div class="panel-heading">
{{$t('login.heading.totp')}} {{ $t('login.heading.totp') }}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form class='login-form' @submit.prevent='submit'> <form
<div class='form-group'> class="login-form"
<label for='code'> @submit.prevent="submit"
{{$t('login.authentication_code')}} >
</label> <div class="form-group">
<input v-model='code' class='form-control' id='code'> <label for="code">
</div> {{ $t('login.authentication_code') }}
</label>
<div class='form-group'> <input
<div class='login-bottom'> id="code"
<div> v-model="code"
<a href="#" @click.prevent="requireRecovery"> class="form-control"
{{$t('login.enter_recovery_code')}} >
</a>
<br />
<a href="#" @click.prevent="abortMFA">
{{$t('general.cancel')}}
</a>
</div>
<button type='submit' class='btn btn-default'>
{{$t('general.verify')}}
</button>
</div> </div>
</div>
</form>
</div>
<div v-if="error" class='form-group'> <div class="form-group">
<div class='alert error'> <div class="login-bottom">
{{error}} <div>
<i class="button-icon icon-cancel" @click="clearError"></i> <a
href="#"
@click.prevent="requireRecovery"
>
{{ $t('login.enter_recovery_code') }}
</a>
<br>
<a
href="#"
@click.prevent="abortMFA"
>
{{ $t('general.cancel') }}
</a>
</div>
<button
type="submit"
class="btn btn-default"
>
{{ $t('general.verify') }}
</button>
</div>
</div>
</form>
</div>
<div
v-if="error"
class="form-group"
>
<div class="alert error">
{{ error }}
<i
class="button-icon icon-cancel"
@click="clearError"
/>
</div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script src="./totp_form.js"></script> <script src="./totp_form.js"></script>

View file

@ -1,38 +1,75 @@
<template> <template>
<div> <div>
<nav class='nav-bar container' id="nav"> <nav
<div class='mobile-inner-nav' @click="scrollToTop()"> id="nav"
<div class='item'> class="nav-bar container"
<a href="#" class="mobile-nav-button" @click.stop.prevent="toggleMobileSidebar()"> >
<i class="button-icon icon-menu"></i> <div
class="mobile-inner-nav"
@click="scrollToTop()"
>
<div class="item">
<a
href="#"
class="mobile-nav-button"
@click.stop.prevent="toggleMobileSidebar()"
>
<i class="button-icon icon-menu" />
</a> </a>
<router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link> <router-link
class="site-name"
:to="{ name: 'root' }"
active-class="home"
>
{{ sitename }}
</router-link>
</div> </div>
<div class='item right'> <div class="item right">
<a class="mobile-nav-button" v-if="currentUser" href="#" @click.stop.prevent="openMobileNotifications()"> <a
<i class="button-icon icon-bell-alt"></i> v-if="currentUser"
<div class="alert-dot" v-if="unseenNotificationsCount"></div> class="mobile-nav-button"
href="#"
@click.stop.prevent="openMobileNotifications()"
>
<i class="button-icon icon-bell-alt" />
<div
v-if="unseenNotificationsCount"
class="alert-dot"
/>
</a> </a>
</div> </div>
</div> </div>
</nav> </nav>
<div v-if="currentUser" <div
v-if="currentUser"
class="mobile-notifications-drawer" class="mobile-notifications-drawer"
:class="{ 'closed': !notificationsOpen }" :class="{ 'closed': !notificationsOpen }"
@touchstart.stop="notificationsTouchStart" @touchstart.stop="notificationsTouchStart"
@touchmove.stop="notificationsTouchMove" @touchmove.stop="notificationsTouchMove"
> >
<div class="mobile-notifications-header"> <div class="mobile-notifications-header">
<span class="title">{{$t('notifications.notifications')}}</span> <span class="title">{{ $t('notifications.notifications') }}</span>
<a class="mobile-nav-button" @click.stop.prevent="closeMobileNotifications()"> <a
<i class="button-icon icon-cancel"/> class="mobile-nav-button"
@click.stop.prevent="closeMobileNotifications()"
>
<i class="button-icon icon-cancel" />
</a> </a>
</div> </div>
<div class="mobile-notifications" @scroll="onScroll"> <div
<Notifications ref="notifications" :noHeading="true"/> class="mobile-notifications"
@scroll="onScroll"
>
<Notifications
ref="notifications"
:no-heading="true"
/>
</div> </div>
</div> </div>
<SideDrawer ref="sideDrawer" :logout="logout"/> <SideDrawer
ref="sideDrawer"
:logout="logout"
/>
<MobilePostStatusModal /> <MobilePostStatusModal />
</div> </div>
</template> </template>

View file

@ -96,12 +96,12 @@ const MobilePostStatusModal = {
this.hidden = false this.hidden = false
} }
this.oldScrollPos = window.scrollY this.oldScrollPos = window.scrollY
}, 100, {leading: true, trailing: false}), }, 100, { leading: true, trailing: false }),
handleScrollEnd: debounce(function () { handleScrollEnd: debounce(function () {
this.hidden = false this.hidden = false
this.oldScrollPos = window.scrollY this.oldScrollPos = window.scrollY
}, 100, {leading: false, trailing: true}) }, 100, { leading: false, trailing: true })
} }
} }

View file

@ -1,23 +1,31 @@
<template> <template>
<div v-if="currentUser"> <div v-if="currentUser">
<div <div
class="post-form-modal-view modal-view" v-show="postFormOpen"
v-show="postFormOpen" class="post-form-modal-view modal-view"
@click="closePostForm" @click="closePostForm"
> >
<div class="post-form-modal-panel panel" @click.stop=""> <div
<div class="panel-heading">{{$t('post_status.new_status')}}</div> class="post-form-modal-panel panel"
<PostStatusForm class="panel-body" @posted="closePostForm" /> @click.stop=""
>
<div class="panel-heading">
{{ $t('post_status.new_status') }}
</div>
<PostStatusForm
class="panel-body"
@posted="closePostForm"
/>
</div>
</div> </div>
<button
class="new-status-button"
:class="{ 'hidden': isHidden }"
@click="openPostForm"
>
<i class="icon-edit" />
</button>
</div> </div>
<button
class="new-status-button"
:class="{ 'hidden': isHidden }"
@click="openPostForm"
>
<i class="icon-edit" />
</button>
</div>
</template> </template>
<script src="./mobile_post_status_modal.js"></script> <script src="./mobile_post_status_modal.js"></script>

View file

@ -52,12 +52,12 @@ const ModerationTools = {
if (this.tagsSet.has(tag)) { if (this.tagsSet.has(tag)) {
store.state.api.backendInteractor.untagUser(this.user, tag).then(response => { store.state.api.backendInteractor.untagUser(this.user, tag).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('untagUser', {user: this.user, tag}) store.commit('untagUser', { user: this.user, tag })
}) })
} else { } else {
store.state.api.backendInteractor.tagUser(this.user, tag).then(response => { store.state.api.backendInteractor.tagUser(this.user, tag).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('tagUser', {user: this.user, tag}) store.commit('tagUser', { user: this.user, tag })
}) })
} }
}, },
@ -66,12 +66,12 @@ const ModerationTools = {
if (this.user.rights[right]) { if (this.user.rights[right]) {
store.state.api.backendInteractor.deleteRight(this.user, right).then(response => { store.state.api.backendInteractor.deleteRight(this.user, right).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('updateRight', {user: this.user, right: right, value: false}) store.commit('updateRight', { user: this.user, right: right, value: false })
}) })
} else { } else {
store.state.api.backendInteractor.addRight(this.user, right).then(response => { store.state.api.backendInteractor.addRight(this.user, right).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('updateRight', {user: this.user, right: right, value: true}) store.commit('updateRight', { user: this.user, right: right, value: true })
}) })
} }
}, },
@ -80,7 +80,7 @@ const ModerationTools = {
const status = !!this.user.deactivated const status = !!this.user.deactivated
store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => { store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => {
if (!response.ok) { return } if (!response.ok) { return }
store.commit('updateActivationStatus', {user: this.user, status: status}) store.commit('updateActivationStatus', { user: this.user, status: status })
}) })
}, },
deleteUserDialog (show) { deleteUserDialog (show) {
@ -89,7 +89,7 @@ const ModerationTools = {
deleteUser () { deleteUser () {
const store = this.$store const store = this.$store
const user = this.user const user = this.user
const {id, name} = user const { id, name } = user
store.state.api.backendInteractor.deleteUser(user) store.state.api.backendInteractor.deleteUser(user)
.then(e => { .then(e => {
this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id) this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)

View file

@ -1,85 +1,168 @@
<template> <template>
<div class='block' style='position: relative'> <div
<Popper class="block"
trigger="click" style="position: relative"
@hide='showDropDown = false' >
append-to-body <Popper
:options="{ trigger="click"
placement: 'bottom-end', append-to-body
modifiers: { :options="{
arrow: { enabled: true }, placement: 'bottom-end',
offset: { offset: '0, 5px' }, modifiers: {
} arrow: { enabled: true },
}"> offset: { offset: '0, 5px' },
<div class="popper-wrapper"> }
<div class="dropdown-menu"> }"
<span v-if='user.is_local'> @hide="showDropDown = false"
<button class="dropdown-item" @click='toggleRight("admin")'> >
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }} <div class="popper-wrapper">
<div class="dropdown-menu">
<span v-if="user.is_local">
<button
class="dropdown-item"
@click="toggleRight(&quot;admin&quot;)"
>
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
</button>
<button
class="dropdown-item"
@click="toggleRight(&quot;moderator&quot;)"
>
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
</button>
<div
role="separator"
class="dropdown-divider"
/>
</span>
<button
class="dropdown-item"
@click="toggleActivationStatus()"
>
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
</button> </button>
<button class="dropdown-item" @click='toggleRight("moderator")'> <button
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }} class="dropdown-item"
@click="deleteUserDialog(true)"
>
{{ $t('user_card.admin_menu.delete_account') }}
</button> </button>
<div role="separator" class="dropdown-divider"></div> <div
</span> v-if="hasTagPolicy"
<button class="dropdown-item" @click='toggleActivationStatus()'> role="separator"
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }} class="dropdown-divider"
</button> />
<button class="dropdown-item" @click='deleteUserDialog(true)'> <span v-if="hasTagPolicy">
{{ $t('user_card.admin_menu.delete_account') }} <button
</button> class="dropdown-item"
<div role="separator" class="dropdown-divider" v-if='hasTagPolicy'></div> @click="toggleTag(tags.FORCE_NSFW)"
<span v-if='hasTagPolicy'> >
<button class="dropdown-item" @click='toggleTag(tags.FORCE_NSFW)'> {{ $t('user_card.admin_menu.force_nsfw') }}
{{ $t('user_card.admin_menu.force_nsfw') }} <span
<span class="menu-checkbox" v-bind:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"></span> class="menu-checkbox"
</button> :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
<button class="dropdown-item" @click='toggleTag(tags.STRIP_MEDIA)'> />
{{ $t('user_card.admin_menu.strip_media') }} </button>
<span class="menu-checkbox" v-bind:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"></span> <button
</button> class="dropdown-item"
<button class="dropdown-item" @click='toggleTag(tags.FORCE_UNLISTED)'> @click="toggleTag(tags.STRIP_MEDIA)"
{{ $t('user_card.admin_menu.force_unlisted') }} >
<span class="menu-checkbox" v-bind:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"></span> {{ $t('user_card.admin_menu.strip_media') }}
</button> <span
<button class="dropdown-item" @click='toggleTag(tags.SANDBOX)'> class="menu-checkbox"
{{ $t('user_card.admin_menu.sandbox') }} :class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
<span class="menu-checkbox" v-bind:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"></span> />
</button> </button>
<button class="dropdown-item" v-if='user.is_local' @click='toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)'> <button
{{ $t('user_card.admin_menu.disable_remote_subscription') }} class="dropdown-item"
<span class="menu-checkbox" v-bind:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"></span> @click="toggleTag(tags.FORCE_UNLISTED)"
</button> >
<button class="dropdown-item" v-if='user.is_local' @click='toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)'> {{ $t('user_card.admin_menu.force_unlisted') }}
{{ $t('user_card.admin_menu.disable_any_subscription') }} <span
<span class="menu-checkbox" v-bind:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"></span> class="menu-checkbox"
</button> :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
<button class="dropdown-item" v-if='user.is_local' @click='toggleTag(tags.QUARANTINE)'> />
{{ $t('user_card.admin_menu.quarantine') }} </button>
<span class="menu-checkbox" v-bind:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"></span> <button
</button> class="dropdown-item"
</span> @click="toggleTag(tags.SANDBOX)"
>
{{ $t('user_card.admin_menu.sandbox') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/>
</button>
<button
v-if="user.is_local"
class="dropdown-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
>
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
/>
</button>
<button
v-if="user.is_local"
class="dropdown-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
>
{{ $t('user_card.admin_menu.disable_any_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
/>
</button>
<button
v-if="user.is_local"
class="dropdown-item"
@click="toggleTag(tags.QUARANTINE)"
>
{{ $t('user_card.admin_menu.quarantine') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
/>
</button>
</span>
</div>
</div> </div>
</div> <button
<button slot="reference" v-bind:class="{ pressed: showDropDown }" @click='toggleMenu'> slot="reference"
{{ $t('user_card.admin_menu.moderation') }} :class="{ pressed: showDropDown }"
</button> @click="toggleMenu"
</Popper> >
<portal to="modal"> {{ $t('user_card.admin_menu.moderation') }}
<DialogModal v-if="showDeleteUserDialog" :onCancel='deleteUserDialog.bind(this, false)'> </button>
<template slot="header">{{ $t('user_card.admin_menu.delete_user') }}</template> </Popper>
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p> <portal to="modal">
<template slot="footer"> <DialogModal
<button class="btn btn-default" @click='deleteUserDialog(false)'> v-if="showDeleteUserDialog"
{{ $t('general.cancel') }} :on-cancel="deleteUserDialog.bind(this, false)"
</button> >
<button class="btn btn-default danger" @click='deleteUser()'> <template slot="header">
{{ $t('user_card.admin_menu.delete_user') }} {{ $t('user_card.admin_menu.delete_user') }}
</button> </template>
</template> <p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
</DialogModal> <template slot="footer">
</portal> <button
</div> class="btn btn-default"
@click="deleteUserDialog(false)"
>
{{ $t('general.cancel') }}
</button>
<button
class="btn btn-default danger"
@click="deleteUser()"
>
{{ $t('user_card.admin_menu.delete_user') }}
</button>
</template>
</DialogModal>
</portal>
</div>
</template> </template>
<script src="./moderation_tools.js"></script> <script src="./moderation_tools.js"></script>

View file

@ -1,7 +1,12 @@
<template> <template>
<basic-user-card :user="user"> <basic-user-card :user="user">
<div class="mute-card-content-container"> <div class="mute-card-content-container">
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted"> <button
v-if="muted"
class="btn btn-default"
:disabled="progress"
@click="unmuteUser"
>
<template v-if="progress"> <template v-if="progress">
{{ $t('user_card.unmute_progress') }} {{ $t('user_card.unmute_progress') }}
</template> </template>
@ -9,7 +14,12 @@
{{ $t('user_card.unmute') }} {{ $t('user_card.unmute') }}
</template> </template>
</button> </button>
<button class="btn btn-default" @click="muteUser" :disabled="progress" v-else> <button
v-else
class="btn btn-default"
:disabled="progress"
@click="muteUser"
>
<template v-if="progress"> <template v-if="progress">
{{ $t('user_card.mute_progress') }} {{ $t('user_card.mute_progress') }}
</template> </template>

View file

@ -2,26 +2,29 @@
<div class="nav-panel"> <div class="nav-panel">
<div class="panel panel-default"> <div class="panel panel-default">
<ul> <ul>
<li v-if='currentUser'> <li v-if="currentUser">
<router-link :to="{ name: 'friends' }"> <router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }} {{ $t("nav.timeline") }}
</router-link> </router-link>
</li> </li>
<li v-if='currentUser'> <li v-if="currentUser">
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
{{ $t("nav.interactions") }} {{ $t("nav.interactions") }}
</router-link> </router-link>
</li> </li>
<li v-if='currentUser'> <li v-if="currentUser">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }} {{ $t("nav.dms") }}
</router-link> </router-link>
</li> </li>
<li v-if='currentUser && currentUser.locked'> <li v-if="currentUser && currentUser.locked">
<router-link :to="{ name: 'friend-requests' }"> <router-link :to="{ name: 'friend-requests' }">
{{ $t("nav.friend_requests")}} {{ $t("nav.friend_requests") }}
<span v-if='followRequestCount > 0' class="badge follow-request-count"> <span
{{followRequestCount}} v-if="followRequestCount > 0"
class="badge follow-request-count"
>
{{ followRequestCount }}
</span> </span>
</router-link> </router-link>
</li> </li>

View file

@ -3,49 +3,104 @@
v-if="notification.type === 'mention'" v-if="notification.type === 'mention'"
:compact="true" :compact="true"
:statusoid="notification.status" :statusoid="notification.status"
/>
<div
v-else
class="non-mention"
:class="[userClass, { highlighted: userStyle }]"
:style="[ userStyle ]"
> >
</status> <a
<div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]" v-else> class="avatar-container"
<a class='avatar-container' :href="notification.from_profile.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded"> :href="notification.from_profile.statusnet_profile_url"
<UserAvatar :compact="true" :betterShadow="betterShadow" :user="notification.from_profile"/> @click.stop.prevent.capture="toggleUserExpanded"
>
<UserAvatar
:compact="true"
:better-shadow="betterShadow"
:user="notification.from_profile"
/>
</a> </a>
<div class='notification-right'> <div class="notification-right">
<UserCard :user="getUser(notification)" :rounded="true" :bordered="true" v-if="userExpanded" /> <UserCard
v-if="userExpanded"
:user="getUser(notification)"
:rounded="true"
:bordered="true"
/>
<span class="notification-details"> <span class="notification-details">
<div class="name-and-action"> <div class="name-and-action">
<span class="username" v-if="!!notification.from_profile.name_html" :title="'@'+notification.from_profile.screen_name" v-html="notification.from_profile.name_html"></span> <!-- eslint-disable vue/no-v-html -->
<span class="username" v-else :title="'@'+notification.from_profile.screen_name">{{ notification.from_profile.name }}</span> <span
v-if="!!notification.from_profile.name_html"
class="username"
:title="'@'+notification.from_profile.screen_name"
v-html="notification.from_profile.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<span
v-else
class="username"
:title="'@'+notification.from_profile.screen_name"
>{{ notification.from_profile.name }}</span>
<span v-if="notification.type === 'like'"> <span v-if="notification.type === 'like'">
<i class="fa icon-star lit"></i> <i class="fa icon-star lit" />
<small>{{$t('notifications.favorited_you')}}</small> <small>{{ $t('notifications.favorited_you') }}</small>
</span> </span>
<span v-if="notification.type === 'repeat'"> <span v-if="notification.type === 'repeat'">
<i class="fa icon-retweet lit" :title="$t('tool_tip.repeat')"></i> <i
<small>{{$t('notifications.repeated_you')}}</small> class="fa icon-retweet lit"
:title="$t('tool_tip.repeat')"
/>
<small>{{ $t('notifications.repeated_you') }}</small>
</span> </span>
<span v-if="notification.type === 'follow'"> <span v-if="notification.type === 'follow'">
<i class="fa icon-user-plus lit"></i> <i class="fa icon-user-plus lit" />
<small>{{$t('notifications.followed_you')}}</small> <small>{{ $t('notifications.followed_you') }}</small>
</span> </span>
</div> </div>
<div class="timeago" v-if="notification.type === 'follow'"> <div
v-if="notification.type === 'follow'"
class="timeago"
>
<span class="faint"> <span class="faint">
<Timeago :time="notification.created_at" :auto-update="240"></Timeago> <Timeago
:time="notification.created_at"
:auto-update="240"
/>
</span> </span>
</div> </div>
<div class="timeago" v-else> <div
<router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }" class="faint-link"> v-else
<Timeago :time="notification.created_at" :auto-update="240"></Timeago> class="timeago"
>
<router-link
v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }"
class="faint-link"
>
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</router-link> </router-link>
</div> </div>
</span> </span>
<div class="follow-text" v-if="notification.type === 'follow'"> <div
v-if="notification.type === 'follow'"
class="follow-text"
>
<router-link :to="userProfileLink(notification.from_profile)"> <router-link :to="userProfileLink(notification.from_profile)">
@{{notification.from_profile.screen_name}} @{{ notification.from_profile.screen_name }}
</router-link> </router-link>
</div> </div>
<template v-else> <template v-else>
<status class="faint" :compact="true" :statusoid="notification.action" :noHeading="true"></status> <status
class="faint"
:compact="true"
:statusoid="notification.action"
:no-heading="true"
/>
</template> </template>
</div> </div>
</div> </div>

View file

@ -1,33 +1,67 @@
<template> <template>
<div :class="{ minimal: minimalMode }" class="notifications"> <div
:class="{ minimal: minimalMode }"
class="notifications"
>
<div :class="mainClass"> <div :class="mainClass">
<div v-if="!noHeading" class="panel-heading"> <div
v-if="!noHeading"
class="panel-heading"
>
<div class="title"> <div class="title">
{{$t('notifications.notifications')}} {{ $t('notifications.notifications') }}
<span class="badge badge-notification unseen-count" v-if="unseenCount">{{unseenCount}}</span> <span
v-if="unseenCount"
class="badge badge-notification unseen-count"
>{{ unseenCount }}</span>
</div> </div>
<div @click.prevent class="loadmore-error alert error" v-if="error"> <div
{{$t('timeline.error_fetching')}} v-if="error"
class="loadmore-error alert error"
@click.prevent
>
{{ $t('timeline.error_fetching') }}
</div> </div>
<button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button> <button
v-if="unseenCount"
class="read-button"
@click.prevent="markAsSeen"
>
{{ $t('notifications.read') }}
</button>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div v-for="notification in visibleNotifications" :key="notification.id" class="notification" :class='{"unseen": !minimalMode && !notification.seen}'> <div
<div class="notification-overlay"></div> v-for="notification in visibleNotifications"
<notification :notification="notification"></notification> :key="notification.id"
class="notification"
:class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
>
<div class="notification-overlay" />
<notification :notification="notification" />
</div> </div>
</div> </div>
<div class="panel-footer"> <div class="panel-footer">
<div v-if="bottomedOut" class="new-status-notification text-center panel-footer faint"> <div
{{$t('notifications.no_more_notifications')}} v-if="bottomedOut"
class="new-status-notification text-center panel-footer faint"
>
{{ $t('notifications.no_more_notifications') }}
</div> </div>
<a v-else-if="!loading" href="#" v-on:click.prevent="fetchOlderNotifications()"> <a
v-else-if="!loading"
href="#"
@click.prevent="fetchOlderNotifications()"
>
<div class="new-status-notification text-center panel-footer"> <div class="new-status-notification text-center panel-footer">
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older')}} {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
</div> </div>
</a> </a>
<div v-else class="new-status-notification text-center panel-footer"> <div
<i class="icon-spin3 animate-spin"/> v-else
class="new-status-notification text-center panel-footer"
>
<i class="icon-spin3 animate-spin" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,27 +1,39 @@
<template> <template>
<div class="opacity-control style-control" :class="{ disabled: !present || disabled }"> <div
<label :for="name" class="label"> class="opacity-control style-control"
{{$t('settings.style.common.opacity')}} :class="{ disabled: !present || disabled }"
</label> >
<input <label
v-if="typeof fallback !== 'undefined'" :for="name"
class="opt exclude-disabled" class="label"
:id="name + '-o'" >
type="checkbox" {{ $t('settings.style.common.opacity') }}
:checked="present" </label>
@input="$emit('input', !present ? fallback : undefined)"> <input
<label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label> v-if="typeof fallback !== 'undefined'"
<input :id="name + '-o'"
:id="name" class="opt exclude-disabled"
class="input-number" type="checkbox"
type="number" :checked="present"
:value="value || fallback" @input="$emit('input', !present ? fallback : undefined)"
:disabled="!present || disabled" >
@input="$emit('input', $event.target.value)" <label
max="1" v-if="typeof fallback !== 'undefined'"
min="0" class="opt-l"
step=".05"> :for="name + '-o'"
</div> />
<input
:id="name"
class="input-number"
type="number"
:value="value || fallback"
:disabled="!present || disabled"
max="1"
min="0"
step=".05"
@input="$emit('input', $event.target.value)"
>
</div>
</template> </template>
<script> <script>

View file

@ -1,24 +1,33 @@
<template> <template>
<div class="poll" v-bind:class="containerClass"> <div
class="poll"
:class="containerClass"
>
<div <div
class="poll-option"
v-for="(option, index) in options" v-for="(option, index) in options"
:key="index" :key="index"
class="poll-option"
> >
<div v-if="showResults" :title="resultTitle(option)" class="option-result"> <div
v-if="showResults"
:title="resultTitle(option)"
class="option-result"
>
<div class="option-result-label"> <div class="option-result-label">
<span class="result-percentage"> <span class="result-percentage">
{{percentageForOption(option.votes_count)}}% {{ percentageForOption(option.votes_count) }}%
</span> </span>
<span>{{option.title}}</span> <span>{{ option.title }}</span>
</div> </div>
<div <div
class="result-fill" class="result-fill"
:style="{ 'width': `${percentageForOption(option.votes_count)}%` }" :style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
> />
</div>
</div> </div>
<div v-else @click="activateOption(index)"> <div
v-else
@click="activateOption(index)"
>
<input <input
v-if="poll.multiple" v-if="poll.multiple"
type="checkbox" type="checkbox"
@ -32,7 +41,7 @@
:value="index" :value="index"
> >
<label class="option-vote"> <label class="option-vote">
<div>{{option.title}}</div> <div>{{ option.title }}</div>
</label> </label>
</div> </div>
</div> </div>
@ -41,16 +50,20 @@
v-if="!showResults" v-if="!showResults"
class="btn btn-default poll-vote-button" class="btn btn-default poll-vote-button"
type="button" type="button"
@click="vote"
:disabled="isDisabled" :disabled="isDisabled"
@click="vote"
> >
{{$t('polls.vote')}} {{ $t('polls.vote') }}
</button> </button>
<div class="total"> <div class="total">
{{totalVotesCount}} {{ $t("polls.votes") }}&nbsp;·&nbsp; {{ totalVotesCount }} {{ $t("polls.votes") }}&nbsp;·&nbsp;
</div> </div>
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'"> <i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
<Timeago :time="this.expiresAt" :auto-update="60" :now-threshold="0" /> <Timeago
:time="expiresAt"
:auto-update="60"
:now-threshold="0"
/>
</i18n> </i18n>
</div> </div>
</div> </div>

View file

@ -1,20 +1,33 @@
<template> <template>
<div class="poll-form" v-if="visible"> <div
<div class="poll-option" v-for="(option, index) in options" :key="index"> v-if="visible"
class="poll-form"
>
<div
v-for="(option, index) in options"
:key="index"
class="poll-option"
>
<div class="input-container"> <div class="input-container">
<input <input
:id="`poll-${index}`"
v-model="options[index]"
class="poll-option-input" class="poll-option-input"
type="text" type="text"
:placeholder="$t('polls.option')" :placeholder="$t('polls.option')"
:maxlength="maxLength" :maxlength="maxLength"
:id="`poll-${index}`"
v-model="options[index]"
@change="updatePollToParent" @change="updatePollToParent"
@keydown.enter.stop.prevent="nextOption(index)" @keydown.enter.stop.prevent="nextOption(index)"
> >
</div> </div>
<div class="icon-container" v-if="options.length > 2"> <div
<i class="icon-cancel" @click="deleteOption(index)"></i> v-if="options.length > 2"
class="icon-container"
>
<i
class="icon-cancel"
@click="deleteOption(index)"
/>
</div> </div>
</div> </div>
<a <a
@ -26,34 +39,51 @@
{{ $t("polls.add_option") }} {{ $t("polls.add_option") }}
</a> </a>
<div class="poll-type-expiry"> <div class="poll-type-expiry">
<div class="poll-type" :title="$t('polls.type')"> <div
<label for="poll-type-selector" class="select"> class="poll-type"
<select class="select" v-model="pollType" @change="updatePollToParent"> :title="$t('polls.type')"
<option value="single">{{$t('polls.single_choice')}}</option> >
<option value="multiple">{{$t('polls.multiple_choices')}}</option> <label
for="poll-type-selector"
class="select"
>
<select
v-model="pollType"
class="select"
@change="updatePollToParent"
>
<option value="single">{{ $t('polls.single_choice') }}</option>
<option value="multiple">{{ $t('polls.multiple_choices') }}</option>
</select> </select>
<i class="icon-down-open"/> <i class="icon-down-open" />
</label> </label>
</div> </div>
<div class="poll-expiry" :title="$t('polls.expiry')"> <div
<input class="poll-expiry"
:title="$t('polls.expiry')"
>
<input
v-model="expiryAmount"
type="number" type="number"
class="expiry-amount hide-number-spinner" class="expiry-amount hide-number-spinner"
:min="minExpirationInCurrentUnit" :min="minExpirationInCurrentUnit"
:max="maxExpirationInCurrentUnit" :max="maxExpirationInCurrentUnit"
v-model="expiryAmount"
@change="expiryAmountChange" @change="expiryAmountChange"
> >
<label class="expiry-unit select"> <label class="expiry-unit select">
<select <select
v-model="expiryUnit" v-model="expiryUnit"
@change="expiryAmountChange" @change="expiryAmountChange"
> >
<option v-for="unit in expiryUnits" :value="unit"> <option
v-for="unit in expiryUnits"
:key="unit"
:value="unit"
>
{{ $t(`time.${unit}_short`, ['']) }} {{ $t(`time.${unit}_short`, ['']) }}
</option> </option>
</select> </select>
<i class="icon-down-open"/> <i class="icon-down-open" />
</label> </label>
</div> </div>
</div> </div>

View file

@ -1,147 +1,254 @@
<template> <template>
<div class="post-status-form"> <div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)" autocomplete="off"> <form
<div class="form-group" > autocomplete="off"
<i18n @submit.prevent="postStatus(newStatus)"
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'" >
path="post_status.account_not_locked_warning" <div class="form-group">
tag="p" <i18n
class="visibility-notice"> v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
<router-link :to="{ name: 'user-settings' }">{{ $t('post_status.account_not_locked_warning_link') }}</router-link> path="post_status.account_not_locked_warning"
</i18n> tag="p"
<p v-if="!hideScopeNotice && newStatus.visibility === 'public'" class="visibility-notice notice-dismissible"> class="visibility-notice"
<span>{{ $t('post_status.scope_notice.public') }}</span>
<a v-on:click.prevent="dismissScopeNotice()" class="button-icon dismiss">
<i class='icon-cancel'></i>
</a>
</p>
<p v-else-if="!hideScopeNotice && newStatus.visibility === 'unlisted'" class="visibility-notice notice-dismissible">
<span>{{ $t('post_status.scope_notice.unlisted') }}</span>
<a v-on:click.prevent="dismissScopeNotice()" class="button-icon dismiss">
<i class='icon-cancel'></i>
</a>
</p>
<p v-else-if="!hideScopeNotice && newStatus.visibility === 'private' && $store.state.users.currentUser.locked" class="visibility-notice notice-dismissible">
<span>{{ $t('post_status.scope_notice.private') }}</span>
<a v-on:click.prevent="dismissScopeNotice()" class="button-icon dismiss">
<i class='icon-cancel'></i>
</a>
</p>
<p v-else-if="newStatus.visibility === 'direct'" class="visibility-notice">
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p>
<EmojiInput
v-if="newStatus.spoilerText || alwaysShowSubject"
:suggest="emojiSuggestor"
v-model="newStatus.spoilerText"
class="form-control"
> >
<input <router-link :to="{ name: 'user-settings' }">
{{ $t('post_status.account_not_locked_warning_link') }}
type="text" </router-link>
:placeholder="$t('post_status.content_warning')" </i18n>
v-model="newStatus.spoilerText"
class="form-post-subject"
/>
</EmojiInput>
<EmojiInput
:suggest="emojiUserSuggestor"
v-model="newStatus.status"
class="form-control main-input"
>
<textarea
ref="textarea"
v-model="newStatus.status"
:placeholder="$t('post_status.default')"
rows="1"
@keydown.meta.enter="postStatus(newStatus)"
@keyup.ctrl.enter="postStatus(newStatus)"
@drop="fileDrop"
@dragover.prevent="fileDrag"
@input="resize"
@paste="paste"
:disabled="posting"
class="form-post-body"
>
</textarea>
<p <p
v-if="hasStatusLengthLimit" v-if="!hideScopeNotice && newStatus.visibility === 'public'"
class="character-counter faint" class="visibility-notice notice-dismissible"
:class="{ error: isOverLengthLimit }"
> >
{{ charactersLeft }} <span>{{ $t('post_status.scope_notice.public') }}</span>
<a
class="button-icon dismiss"
@click.prevent="dismissScopeNotice()"
>
<i class="icon-cancel" />
</a>
</p> </p>
</EmojiInput> <p
<div class="visibility-tray"> v-else-if="!hideScopeNotice && newStatus.visibility === 'unlisted'"
<div class="text-format" v-if="postFormats.length > 1"> class="visibility-notice notice-dismissible"
<label for="post-content-type" class="select"> >
<select id="post-content-type" v-model="newStatus.contentType" class="form-control"> <span>{{ $t('post_status.scope_notice.unlisted') }}</span>
<option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat"> <a
{{$t(`post_status.content_type["${postFormat}"]`)}} class="button-icon dismiss"
</option> @click.prevent="dismissScopeNotice()"
</select> >
<i class="icon-down-open"></i> <i class="icon-cancel" />
</label> </a>
</div> </p>
<div class="text-format" v-if="postFormats.length === 1"> <p
<span class="only-format"> v-else-if="!hideScopeNotice && newStatus.visibility === 'private' && $store.state.users.currentUser.locked"
{{$t(`post_status.content_type["${postFormats[0]}"]`)}} class="visibility-notice notice-dismissible"
</span> >
</div> <span>{{ $t('post_status.scope_notice.private') }}</span>
<a
class="button-icon dismiss"
@click.prevent="dismissScopeNotice()"
>
<i class="icon-cancel" />
</a>
</p>
<p
v-else-if="newStatus.visibility === 'direct'"
class="visibility-notice"
>
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p>
<EmojiInput
v-if="newStatus.spoilerText || alwaysShowSubject"
v-model="newStatus.spoilerText"
:suggest="emojiSuggestor"
class="form-control"
>
<input
<scope-selector v-model="newStatus.spoilerText"
:showAll="showAllScopes" type="text"
:userDefault="userDefaultScope" :placeholder="$t('post_status.content_warning')"
:originalScope="copyMessageScope" class="form-post-subject"
:initialScope="newStatus.visibility" >
:onScopeChange="changeVis"/> </EmojiInput>
</div> <EmojiInput
</div> v-model="newStatus.status"
<poll-form :suggest="emojiUserSuggestor"
ref="pollForm" class="form-control main-input"
v-if="pollsAvailable" >
:visible="pollFormVisible" <textarea
@update-poll="setPoll" ref="textarea"
/> v-model="newStatus.status"
<div class='form-bottom'> :placeholder="$t('post_status.default')"
<div class='form-bottom-left'> rows="1"
<media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload> :disabled="posting"
<div v-if="pollsAvailable" class="poll-icon"> class="form-post-body"
<i @keydown.meta.enter="postStatus(newStatus)"
:title="$t('polls.add_poll')" @keyup.ctrl.enter="postStatus(newStatus)"
@click="togglePollForm" @drop="fileDrop"
class="icon-chart-bar btn btn-default" @dragover.prevent="fileDrag"
:class="pollFormVisible && 'selected'" @input="resize"
@paste="paste"
/>
<p
v-if="hasStatusLengthLimit"
class="character-counter faint"
:class="{ error: isOverLengthLimit }"
>
{{ charactersLeft }}
</p>
</EmojiInput>
<div class="visibility-tray">
<scope-selector
:show-all="showAllScopes"
:user-default="userDefaultScope"
:original-scope="copyMessageScope"
:initial-scope="newStatus.visibility"
:on-scope-change="changeVis"
/> />
</div>
</div>
<button v-if="posting" disabled class="btn btn-default">{{$t('post_status.posting')}}</button> <div
<button v-else-if="isOverLengthLimit" disabled class="btn btn-default">{{$t('general.submit')}}</button> v-if="postFormats.length > 1"
<button v-else :disabled="submitDisabled" type="submit" class="btn btn-default">{{$t('general.submit')}}</button> class="text-format"
</div> >
<div class='alert error' v-if="error"> <label
Error: {{ error }} for="post-content-type"
<i class="button-icon icon-cancel" @click="clearError"></i> class="select"
</div> >
<div class="attachments"> <select
<div class="media-upload-wrapper" v-for="file in newStatus.files"> id="post-content-type"
<i class="fa button-icon icon-cancel" @click="removeMediaFile(file)"></i> v-model="newStatus.contentType"
<div class="media-upload-container attachment"> class="form-control"
<img class="thumbnail media-upload" :src="file.url" v-if="type(file) === 'image'"></img> >
<video v-if="type(file) === 'video'" :src="file.url" controls></video> <option
<audio v-if="type(file) === 'audio'" :src="file.url" controls></audio> v-for="postFormat in postFormats"
<a v-if="type(file) === 'unknown'" :href="file.url">{{file.url}}</a> :key="postFormat"
:value="postFormat"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
<div
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
class="text-format"
>
<span class="only-format">
{{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
</span>
</div>
</div> </div>
</div> </div>
</div> <poll-form
<div class="upload_settings" v-if="newStatus.files.length > 0"> v-if="pollsAvailable"
<input type="checkbox" id="filesSensitive" v-model="newStatus.nsfw"> ref="pollForm"
<label for="filesSensitive">{{$t('post_status.attachments_sensitive')}}</label> :visible="pollFormVisible"
</div> @update-poll="setPoll"
</form> />
</div> <div class="form-bottom">
<div class="form-bottom-left">
<media-upload
ref="mediaUpload"
:drop-files="dropFiles"
@uploading="disableSubmit"
@uploaded="addMediaFile"
@upload-failed="uploadFailed"
/>
<div
v-if="pollsAvailable"
class="poll-icon"
>
<i
:title="$t('polls.add_poll')"
class="icon-chart-bar btn btn-default"
:class="pollFormVisible && 'selected'"
@click="togglePollForm"
/>
</div>
</div>
<button
v-if="posting"
disabled
class="btn btn-default"
>
{{ $t('post_status.posting') }}
</button>
<button
v-else-if="isOverLengthLimit"
disabled
class="btn btn-default"
>
{{ $t('general.submit') }}
</button>
<button
v-else
:disabled="submitDisabled"
type="submit"
class="btn btn-default"
>
{{ $t('general.submit') }}
</button>
</div>
<div
v-if="error"
class="alert error"
>
Error: {{ error }}
<i
class="button-icon icon-cancel"
@click="clearError"
/>
</div>
<div class="attachments">
<div
v-for="file in newStatus.files"
:key="file.url"
class="media-upload-wrapper"
>
<i
class="fa button-icon icon-cancel"
@click="removeMediaFile(file)"
/>
<div class="media-upload-container attachment">
<img
v-if="type(file) === 'image'"
class="thumbnail media-upload"
:src="file.url"
>
<video
v-if="type(file) === 'video'"
:src="file.url"
controls
/>
<audio
v-if="type(file) === 'audio'"
:src="file.url"
controls
/>
<a
v-if="type(file) === 'unknown'"
:href="file.url"
>{{ file.url }}</a>
</div>
</div>
</div>
<div
v-if="newStatus.files.length > 0"
class="upload_settings"
>
<input
id="filesSensitive"
v-model="newStatus.nsfw"
type="checkbox"
>
<label for="filesSensitive">{{ $t('post_status.attachments_sensitive') }}</label>
</div>
</form>
</div>
</template> </template>
<script src="./post_status_form.js"></script> <script src="./post_status_form.js"></script>
@ -170,7 +277,6 @@
.visibility-tray { .visibility-tray {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-direction: row-reverse;
padding-top: 5px; padding-top: 5px;
} }
} }
@ -217,7 +323,6 @@
.icon-chart-bar { .icon-chart-bar {
cursor: pointer; cursor: pointer;
} }
.error { .error {
text-align: center; text-align: center;

View file

@ -1,5 +1,8 @@
<template> <template>
<button :disabled="progress || disabled" @click="onClick"> <button
:disabled="progress || disabled"
@click="onClick"
>
<template v-if="progress"> <template v-if="progress">
<slot name="progress" /> <slot name="progress" />
</template> </template>

View file

@ -1,5 +1,9 @@
<template> <template>
<Timeline :title="$t('nav.twkn')" v-bind:timeline="timeline" v-bind:timeline-name="'publicAndExternal'"/> <Timeline
:title="$t('nav.twkn')"
:timeline="timeline"
:timeline-name="'publicAndExternal'"
/>
</template> </template>
<script src="./public_and_external_timeline.js"></script> <script src="./public_and_external_timeline.js"></script>

View file

@ -1,5 +1,9 @@
<template> <template>
<Timeline :title="$t('nav.public_tl')" v-bind:timeline="timeline" v-bind:timeline-name="'public'"/> <Timeline
:title="$t('nav.public_tl')"
:timeline="timeline"
:timeline-name="'public'"
/>
</template> </template>
<script src="./public_timeline.js"></script> <script src="./public_timeline.js"></script>

View file

@ -1,37 +1,50 @@
<template> <template>
<div class="range-control style-control" :class="{ disabled: !present || disabled }"> <div
<label :for="name" class="label"> class="range-control style-control"
{{label}} :class="{ disabled: !present || disabled }"
</label> >
<input <label
v-if="typeof fallback !== 'undefined'" :for="name"
class="opt exclude-disabled" class="label"
:id="name + '-o'" >
type="checkbox" {{ label }}
:checked="present" </label>
@input="$emit('input', !present ? fallback : undefined)"> <input
<label v-if="typeof fallback !== 'undefined'" class="opt-l" :for="name + '-o'"></label> v-if="typeof fallback !== 'undefined'"
<input :id="name + '-o'"
:id="name" class="opt exclude-disabled"
class="input-number" type="checkbox"
type="range" :checked="present"
:value="value || fallback" @input="$emit('input', !present ? fallback : undefined)"
:disabled="!present || disabled" >
@input="$emit('input', $event.target.value)" <label
:max="max || hardMax || 100" v-if="typeof fallback !== 'undefined'"
:min="min || hardMin || 0" class="opt-l"
:step="step || 1"> :for="name + '-o'"
<input />
:id="name" <input
class="input-number" :id="name"
type="number" class="input-number"
:value="value || fallback" type="range"
:disabled="!present || disabled" :value="value || fallback"
@input="$emit('input', $event.target.value)" :disabled="!present || disabled"
:max="hardMax" :max="max || hardMax || 100"
:min="hardMin" :min="min || hardMin || 0"
:step="step || 1"> :step="step || 1"
</div> @input="$emit('input', $event.target.value)"
>
<input
:id="name"
class="input-number"
type="number"
:value="value || fallback"
:disabled="!present || disabled"
:max="hardMax"
:min="hardMin"
:step="step || 1"
@input="$emit('input', $event.target.value)"
>
</div>
</template> </template>
<script> <script>

View file

@ -28,7 +28,7 @@ const registration = {
}, },
created () { created () {
if ((!this.registrationOpen && !this.token) || this.signedIn) { if ((!this.registrationOpen && !this.token) || this.signedIn) {
this.$router.push({name: 'root'}) this.$router.push({ name: 'root' })
} }
this.setCaptcha() this.setCaptcha()
@ -61,7 +61,7 @@ const registration = {
if (!this.$v.$invalid) { if (!this.$v.$invalid) {
try { try {
await this.signUp(this.user) await this.signUp(this.user)
this.$router.push({name: 'friends'}) this.$router.push({ name: 'friends' })
} catch (error) { } catch (error) {
console.warn('Registration failed: ' + error) console.warn('Registration failed: ' + error)
} }

View file

@ -1,109 +1,236 @@
<template> <template>
<div class="settings panel panel-default"> <div class="settings panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{{$t('registration.registration')}} {{ $t('registration.registration') }}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form v-on:submit.prevent='submit(user)' class='registration-form'> <form
<div class='container'> class="registration-form"
<div class='text-fields'> @submit.prevent="submit(user)"
<div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }"> >
<label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label> <div class="container">
<input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' :placeholder="$t('registration.username_placeholder')"> <div class="text-fields">
<div
class="form-group"
:class="{ 'form-group--error': $v.user.username.$error }"
>
<label
class="form--label"
for="sign-up-username"
>{{ $t('login.username') }}</label>
<input
id="sign-up-username"
v-model.trim="$v.user.username.$model"
:disabled="isPending"
class="form-control"
:placeholder="$t('registration.username_placeholder')"
>
</div> </div>
<div class="form-error" v-if="$v.user.username.$dirty"> <div
v-if="$v.user.username.$dirty"
class="form-error"
>
<ul> <ul>
<li v-if="!$v.user.username.required"> <li v-if="!$v.user.username.required">
<span>{{$t('registration.validations.username_required')}}</span> <span>{{ $t('registration.validations.username_required') }}</span>
</li> </li>
</ul> </ul>
</div> </div>
<div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }"> <div
<label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label> class="form-group"
<input :disabled="isPending" v-model.trim='$v.user.fullname.$model' class='form-control' id='sign-up-fullname' :placeholder="$t('registration.fullname_placeholder')"> :class="{ 'form-group--error': $v.user.fullname.$error }"
>
<label
class="form--label"
for="sign-up-fullname"
>{{ $t('registration.fullname') }}</label>
<input
id="sign-up-fullname"
v-model.trim="$v.user.fullname.$model"
:disabled="isPending"
class="form-control"
:placeholder="$t('registration.fullname_placeholder')"
>
</div> </div>
<div class="form-error" v-if="$v.user.fullname.$dirty"> <div
v-if="$v.user.fullname.$dirty"
class="form-error"
>
<ul> <ul>
<li v-if="!$v.user.fullname.required"> <li v-if="!$v.user.fullname.required">
<span>{{$t('registration.validations.fullname_required')}}</span> <span>{{ $t('registration.validations.fullname_required') }}</span>
</li> </li>
</ul> </ul>
</div> </div>
<div class='form-group' :class="{ 'form-group--error': $v.user.email.$error }"> <div
<label class='form--label' for='email'>{{$t('registration.email')}}</label> class="form-group"
<input :disabled="isPending" v-model='$v.user.email.$model' class='form-control' id='email' type="email"> :class="{ 'form-group--error': $v.user.email.$error }"
>
<label
class="form--label"
for="email"
>{{ $t('registration.email') }}</label>
<input
id="email"
v-model="$v.user.email.$model"
:disabled="isPending"
class="form-control"
type="email"
>
</div> </div>
<div class="form-error" v-if="$v.user.email.$dirty"> <div
v-if="$v.user.email.$dirty"
class="form-error"
>
<ul> <ul>
<li v-if="!$v.user.email.required"> <li v-if="!$v.user.email.required">
<span>{{$t('registration.validations.email_required')}}</span> <span>{{ $t('registration.validations.email_required') }}</span>
</li> </li>
</ul> </ul>
</div> </div>
<div class='form-group'> <div class="form-group">
<label class='form--label' for='bio'>{{$t('registration.bio')}} ({{$t('general.optional')}})</label> <label
<textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="bioPlaceholder"></textarea> class="form--label"
for="bio"
>{{ $t('registration.bio') }} ({{ $t('general.optional') }})</label>
<textarea
id="bio"
v-model="user.bio"
:disabled="isPending"
class="form-control"
:placeholder="bioPlaceholder"
/>
</div> </div>
<div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }"> <div
<label class='form--label' for='sign-up-password'>{{$t('login.password')}}</label> class="form-group"
<input :disabled="isPending" v-model='user.password' class='form-control' id='sign-up-password' type='password'> :class="{ 'form-group--error': $v.user.password.$error }"
>
<label
class="form--label"
for="sign-up-password"
>{{ $t('login.password') }}</label>
<input
id="sign-up-password"
v-model="user.password"
:disabled="isPending"
class="form-control"
type="password"
>
</div> </div>
<div class="form-error" v-if="$v.user.password.$dirty"> <div
v-if="$v.user.password.$dirty"
class="form-error"
>
<ul> <ul>
<li v-if="!$v.user.password.required"> <li v-if="!$v.user.password.required">
<span>{{$t('registration.validations.password_required')}}</span> <span>{{ $t('registration.validations.password_required') }}</span>
</li> </li>
</ul> </ul>
</div> </div>
<div class='form-group' :class="{ 'form-group--error': $v.user.confirm.$error }"> <div
<label class='form--label' for='sign-up-password-confirmation'>{{$t('registration.password_confirm')}}</label> class="form-group"
<input :disabled="isPending" v-model='user.confirm' class='form-control' id='sign-up-password-confirmation' type='password'> :class="{ 'form-group--error': $v.user.confirm.$error }"
>
<label
class="form--label"
for="sign-up-password-confirmation"
>{{ $t('registration.password_confirm') }}</label>
<input
id="sign-up-password-confirmation"
v-model="user.confirm"
:disabled="isPending"
class="form-control"
type="password"
>
</div> </div>
<div class="form-error" v-if="$v.user.confirm.$dirty"> <div
v-if="$v.user.confirm.$dirty"
class="form-error"
>
<ul> <ul>
<li v-if="!$v.user.confirm.required"> <li v-if="!$v.user.confirm.required">
<span>{{$t('registration.validations.password_confirmation_required')}}</span> <span>{{ $t('registration.validations.password_confirmation_required') }}</span>
</li> </li>
<li v-if="!$v.user.confirm.sameAsPassword"> <li v-if="!$v.user.confirm.sameAsPassword">
<span>{{$t('registration.validations.password_confirmation_match')}}</span> <span>{{ $t('registration.validations.password_confirmation_match') }}</span>
</li> </li>
</ul> </ul>
</div> </div>
<div class="form-group" id="captcha-group" v-if="captcha.type != 'none'"> <div
<label class='form--label' for='captcha-label'>{{$t('captcha')}}</label> v-if="captcha.type != 'none'"
id="captcha-group"
class="form-group"
>
<label
class="form--label"
for="captcha-label"
>{{ $t('captcha') }}</label>
<template v-if="captcha.type == 'kocaptcha'"> <template v-if="captcha.type == 'kocaptcha'">
<img v-bind:src="captcha.url" v-on:click="setCaptcha"> <img
:src="captcha.url"
@click="setCaptcha"
>
<sub>{{$t('registration.new_captcha')}}</sub> <sub>{{ $t('registration.new_captcha') }}</sub>
<input :disabled="isPending" <input
v-model='captcha.solution' id="captcha-answer"
class='form-control' id='captcha-answer' type='text' autocomplete="off"> v-model="captcha.solution"
:disabled="isPending"
class="form-control"
type="text"
autocomplete="off"
>
</template> </template>
</div> </div>
<div class='form-group' v-if='token' > <div
<label for='token'>{{$t('registration.token')}}</label> v-if="token"
<input disabled='true' v-model='token' class='form-control' id='token' type='text'> class="form-group"
>
<label for="token">{{ $t('registration.token') }}</label>
<input
id="token"
v-model="token"
disabled="true"
class="form-control"
type="text"
>
</div> </div>
<div class='form-group'> <div class="form-group">
<button :disabled="isPending" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button> <button
:disabled="isPending"
type="submit"
class="btn btn-default"
>
{{ $t('general.submit') }}
</button>
</div> </div>
</div> </div>
<div class='terms-of-service' v-html="termsOfService"> <!-- eslint-disable vue/no-v-html -->
</div> <div
class="terms-of-service"
v-html="termsOfService"
/>
<!-- eslint-enable vue/no-v-html -->
</div> </div>
<div v-if="serverValidationErrors.length" class='form-group'> <div
<div class='alert error'> v-if="serverValidationErrors.length"
<span v-for="error in serverValidationErrors">{{error}}</span> class="form-group"
>
<div class="alert error">
<span
v-for="error in serverValidationErrors"
:key="error"
>{{ error }}</span>
</div> </div>
</div> </div>
</form> </form>

View file

@ -1,9 +1,23 @@
<template> <template>
<div class="remote-follow"> <div class="remote-follow">
<form method="POST" :action='subscribeUrl'> <form
<input type="hidden" name="nickname" :value="user.screen_name"> method="POST"
<input type="hidden" name="profile" value=""> :action="subscribeUrl"
<button click="submit" class="remote-button"> >
<input
type="hidden"
name="nickname"
:value="user.screen_name"
>
<input
type="hidden"
name="profile"
value=""
>
<button
click="submit"
class="remote-button"
>
{{ $t('user_card.remote_follow') }} {{ $t('user_card.remote_follow') }}
</button> </button>
</form> </form>

View file

@ -11,9 +11,9 @@ const RetweetButton = {
methods: { methods: {
retweet () { retweet () {
if (!this.status.repeated) { if (!this.status.repeated) {
this.$store.dispatch('retweet', {id: this.status.id}) this.$store.dispatch('retweet', { id: this.status.id })
} else { } else {
this.$store.dispatch('unretweet', {id: this.status.id}) this.$store.dispatch('unretweet', { id: this.status.id })
} }
this.animated = true this.animated = true
setTimeout(() => { setTimeout(() => {

View file

@ -1,16 +1,29 @@
<template> <template>
<div v-if="loggedIn"> <div v-if="loggedIn">
<template v-if="visibility !== 'private' && visibility !== 'direct'"> <template v-if="visibility !== 'private' && visibility !== 'direct'">
<i :class='classes' class='button-icon retweet-button icon-retweet rt-active' v-on:click.prevent='retweet()' :title="$t('tool_tip.repeat')"></i> <i
<span v-if='!hidePostStatsLocal && status.repeat_num > 0'>{{status.repeat_num}}</span> :class="classes"
class="button-icon retweet-button icon-retweet rt-active"
:title="$t('tool_tip.repeat')"
@click.prevent="retweet()"
/>
<span v-if="!hidePostStatsLocal && status.repeat_num > 0">{{ status.repeat_num }}</span>
</template> </template>
<template v-else> <template v-else>
<i :class='classes' class='button-icon icon-lock' :title="$t('timeline.no_retweet_hint')"></i> <i
:class="classes"
class="button-icon icon-lock"
:title="$t('timeline.no_retweet_hint')"
/>
</template> </template>
</div> </div>
<div v-else-if="!loggedIn"> <div v-else-if="!loggedIn">
<i :class='classes' class='button-icon icon-retweet' :title="$t('tool_tip.repeat')"></i> <i
<span v-if='!hidePostStatsLocal && status.repeat_num > 0'>{{status.repeat_num}}</span> :class="classes"
class="button-icon icon-retweet"
:title="$t('tool_tip.repeat')"
/>
<span v-if="!hidePostStatsLocal && status.repeat_num > 0">{{ status.repeat_num }}</span>
</div> </div>
</template> </template>

View file

@ -29,10 +29,10 @@ const ScopeSelector = {
}, },
css () { css () {
return { return {
public: {selected: this.currentScope === 'public'}, public: { selected: this.currentScope === 'public' },
unlisted: {selected: this.currentScope === 'unlisted'}, unlisted: { selected: this.currentScope === 'unlisted' },
private: {selected: this.currentScope === 'private'}, private: { selected: this.currentScope === 'private' },
direct: {selected: this.currentScope === 'direct'} direct: { selected: this.currentScope === 'direct' }
} }
} }
}, },

View file

@ -1,30 +1,37 @@
<template> <template>
<div v-if="!showNothing" class="scope-selector"> <div
<i class="icon-mail-alt" v-if="!showNothing"
:class="css.direct" class="scope-selector"
:title="$t('post_status.scope.direct')" >
v-if="showDirect" <i
@click="changeVis('direct')"> v-if="showDirect"
</i> class="icon-mail-alt"
<i class="icon-lock" :class="css.direct"
:class="css.private" :title="$t('post_status.scope.direct')"
:title="$t('post_status.scope.private')" @click="changeVis('direct')"
v-if="showPrivate" />
v-on:click="changeVis('private')"> <i
</i> v-if="showPrivate"
<i class="icon-lock-open-alt" class="icon-lock"
:class="css.unlisted" :class="css.private"
:title="$t('post_status.scope.unlisted')" :title="$t('post_status.scope.private')"
v-if="showUnlisted" @click="changeVis('private')"
@click="changeVis('unlisted')"> />
</i> <i
<i class="icon-globe" v-if="showUnlisted"
:class="css.public" class="icon-lock-open-alt"
:title="$t('post_status.scope.public')" :class="css.unlisted"
v-if="showPublic" :title="$t('post_status.scope.unlisted')"
@click="changeVis('public')"> @click="changeVis('unlisted')"
</i> />
</div> <i
v-if="showPublic"
class="icon-globe"
:class="css.public"
:title="$t('post_status.scope.public')"
@click="changeVis('public')"
/>
</div>
</template> </template>
<script src="./scope_selector.js"></script> <script src="./scope_selector.js"></script>

View file

@ -1,23 +1,52 @@
<template> <template>
<div class="selectable-list"> <div class="selectable-list">
<div class="selectable-list-header" v-if="items.length > 0"> <div
v-if="items.length > 0"
class="selectable-list-header"
>
<div class="selectable-list-checkbox-wrapper"> <div class="selectable-list-checkbox-wrapper">
<Checkbox :checked="allSelected" @change="toggleAll" :indeterminate="someSelected">{{ $t('selectable_list.select_all') }}</Checkbox> <Checkbox
:checked="allSelected"
:indeterminate="someSelected"
@change="toggleAll"
>
{{ $t('selectable_list.select_all') }}
</Checkbox>
</div> </div>
<div class="selectable-list-header-actions"> <div class="selectable-list-header-actions">
<slot name="header" :selected="filteredSelected" /> <slot
name="header"
:selected="filteredSelected"
/>
</div> </div>
</div> </div>
<List :items="items" :getKey="getKey"> <List
<template slot="item" slot-scope="{item}"> :items="items"
<div class="selectable-list-item-inner" :class="{ 'selectable-list-item-selected-inner': isSelected(item) }"> :get-key="getKey"
>
<template
slot="item"
slot-scope="{item}"
>
<div
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
>
<div class="selectable-list-checkbox-wrapper"> <div class="selectable-list-checkbox-wrapper">
<Checkbox :checked="isSelected(item)" @change="checked => toggle(checked, item)" /> <Checkbox
:checked="isSelected(item)"
@change="checked => toggle(checked, item)"
/>
</div> </div>
<slot name="item" :item="item" /> <slot
name="item"
:item="item"
/>
</div> </div>
</template> </template>
<template slot="empty"><slot name="empty" /></template> <template slot="empty">
<slot name="empty" />
</template>
</List> </List>
</div> </div>
</template> </template>

View file

@ -1,305 +1,483 @@
<template> <template>
<div class="settings panel panel-default"> <div class="settings panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="title"> <div class="title">
{{$t('settings.settings')}} {{ $t('settings.settings') }}
</div>
<transition name="fade">
<template v-if="currentSaveStateNotice">
<div
v-if="currentSaveStateNotice.error"
class="alert error"
@click.prevent
>
{{ $t('settings.saving_err') }}
</div>
<div
v-if="!currentSaveStateNotice.error"
class="alert transparent"
@click.prevent
>
{{ $t('settings.saving_ok') }}
</div>
</template>
</transition>
</div> </div>
<div class="panel-body">
<transition name="fade"> <keep-alive>
<template v-if="currentSaveStateNotice"> <tab-switcher>
<div @click.prevent class="alert error" v-if="currentSaveStateNotice.error"> <div :label="$t('settings.general')">
{{ $t('settings.saving_err') }} <div class="setting-item">
</div> <h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list">
<div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error">
{{ $t('settings.saving_ok') }}
</div>
</template>
</transition>
</div>
<div class="panel-body">
<keep-alive>
<tab-switcher>
<div :label="$t('settings.general')" >
<div class="setting-item">
<h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list">
<li>
<interface-language-switcher />
</li>
<li v-if="instanceSpecificPanelPresent">
<input type="checkbox" id="hideISP" v-model="hideISPLocal">
<label for="hideISP">{{$t('settings.hide_isp')}}</label>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{$t('nav.timeline')}}</h2>
<ul class="setting-list">
<li>
<input type="checkbox" id="hideMutedPosts" v-model="hideMutedPostsLocal">
<label for="hideMutedPosts">{{$t('settings.hide_muted_posts')}} {{$t('settings.instance_default', { value: hideMutedPostsDefault })}}</label>
</li>
<li>
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
<label for="collapseMessageWithSubject">{{$t('settings.collapse_subject')}} {{$t('settings.instance_default', { value: collapseMessageWithSubjectDefault })}}</label>
</li>
<li>
<input type="checkbox" id="streaming" v-model="streamingLocal">
<label for="streaming">{{$t('settings.streaming')}}</label>
<ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
<li> <li>
<input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal"> <interface-language-switcher />
<label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label> </li>
<li v-if="instanceSpecificPanelPresent">
<input
id="hideISP"
v-model="hideISPLocal"
type="checkbox"
>
<label for="hideISP">{{ $t('settings.hide_isp') }}</label>
</li> </li>
</ul> </ul>
</li> </div>
<li> <div class="setting-item">
<input type="checkbox" id="autoload" v-model="autoLoadLocal"> <h2>{{ $t('nav.timeline') }}</h2>
<label for="autoload">{{$t('settings.autoload')}}</label> <ul class="setting-list">
</li>
<li>
<input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
<label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{$t('settings.composing')}}</h2>
<ul class="setting-list">
<li>
<input type="checkbox" id="scopeCopy" v-model="scopeCopyLocal">
<label for="scopeCopy">
{{$t('settings.scope_copy')}} {{$t('settings.instance_default', { value: scopeCopyDefault })}}
</label>
</li>
<li>
<input type="checkbox" id="subjectHide" v-model="alwaysShowSubjectInputLocal">
<label for="subjectHide">
{{$t('settings.subject_input_always_show')}} {{$t('settings.instance_default', { value: alwaysShowSubjectInputDefault })}}
</label>
</li>
<li>
<div>
{{$t('settings.subject_line_behavior')}}
<label for="subjectLineBehavior" class="select">
<select id="subjectLineBehavior" v-model="subjectLineBehaviorLocal">
<option value="email">
{{$t('settings.subject_line_email')}}
{{subjectLineBehaviorDefault == 'email' ? $t('settings.instance_default_simple') : ''}}
</option>
<option value="masto">
{{$t('settings.subject_line_mastodon')}}
{{subjectLineBehaviorDefault == 'mastodon' ? $t('settings.instance_default_simple') : ''}}
</option>
<option value="noop">
{{$t('settings.subject_line_noop')}}
{{subjectLineBehaviorDefault == 'noop' ? $t('settings.instance_default_simple') : ''}}
</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
</li>
<li v-if="postFormats.length > 0">
<div>
{{$t('settings.post_status_content_type')}}
<label for="postContentType" class="select">
<select id="postContentType" v-model="postContentTypeLocal">
<option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat">
{{$t(`post_status.content_type["${postFormat}"]`)}}
{{postContentTypeDefault === postFormat ? $t('settings.instance_default_simple') : ''}}
</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
</li>
<li>
<input type="checkbox" id="minimalScopesMode" v-model="minimalScopesModeLocal">
<label for="minimalScopesMode">
{{$t('settings.minimal_scopes_mode')}} {{$t('settings.instance_default', { value: minimalScopesModeDefault })}}
</label>
</li>
<li>
<input type="checkbox" id="autohideFloatingPostButton" v-model="autohideFloatingPostButtonLocal">
<label for="autohideFloatingPostButton">{{$t('settings.autohide_floating_post_button')}}</label>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{$t('settings.attachments')}}</h2>
<ul class="setting-list">
<li>
<input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal">
<label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label>
</li>
<li>
<input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
<label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
</li>
<li>
<label for="maxThumbnails">{{$t('settings.max_thumbnails')}}</label>
<input class="number-input" type="number" id="maxThumbnails" v-model.number="maxThumbnails" min="0" step="1">
</li>
<li>
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
<label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
</li>
<ul class="setting-list suboptions" >
<li>
<input :disabled="!hideNsfwLocal" type="checkbox" id="preloadImage" v-model="preloadImage">
<label for="preloadImage">{{$t('settings.preload_images')}}</label>
</li>
<li>
<input :disabled="!hideNsfwLocal" type="checkbox" id="useOneClickNsfw" v-model="useOneClickNsfw">
<label for="useOneClickNsfw">{{$t('settings.use_one_click_nsfw')}}</label>
</li>
</ul>
<li>
<input type="checkbox" id="stopGifs" v-model="stopGifs">
<label for="stopGifs">{{$t('settings.stop_gifs')}}</label>
</li>
<li>
<input type="checkbox" id="loopVideo" v-model="loopVideoLocal">
<label for="loopVideo">{{$t('settings.loop_video')}}</label>
<ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
<li> <li>
<input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal"> <input
<label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label> id="hideMutedPosts"
<div v-if="!loopSilentAvailable" class="unavailable"> v-model="hideMutedPostsLocal"
<i class="icon-globe"/>! {{$t('settings.limited_availability')}} type="checkbox"
>
<label for="hideMutedPosts">{{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsDefault }) }}</label>
</li>
<li>
<input
id="collapseMessageWithSubject"
v-model="collapseMessageWithSubjectLocal"
type="checkbox"
>
<label for="collapseMessageWithSubject">{{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectDefault }) }}</label>
</li>
<li>
<input
id="streaming"
v-model="streamingLocal"
type="checkbox"
>
<label for="streaming">{{ $t('settings.streaming') }}</label>
<ul
class="setting-list suboptions"
:class="[{disabled: !streamingLocal}]"
>
<li>
<input
id="pauseOnUnfocused"
v-model="pauseOnUnfocusedLocal"
:disabled="!streamingLocal"
type="checkbox"
>
<label for="pauseOnUnfocused">{{ $t('settings.pause_on_unfocused') }}</label>
</li>
</ul>
</li>
<li>
<input
id="autoload"
v-model="autoLoadLocal"
type="checkbox"
>
<label for="autoload">{{ $t('settings.autoload') }}</label>
</li>
<li>
<input
id="hoverPreview"
v-model="hoverPreviewLocal"
type="checkbox"
>
<label for="hoverPreview">{{ $t('settings.reply_link_preview') }}</label>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.composing') }}</h2>
<ul class="setting-list">
<li>
<input
id="scopeCopy"
v-model="scopeCopyLocal"
type="checkbox"
>
<label for="scopeCopy">
{{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyDefault }) }}
</label>
</li>
<li>
<input
id="subjectHide"
v-model="alwaysShowSubjectInputLocal"
type="checkbox"
>
<label for="subjectHide">
{{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputDefault }) }}
</label>
</li>
<li>
<div>
{{ $t('settings.subject_line_behavior') }}
<label
for="subjectLineBehavior"
class="select"
>
<select
id="subjectLineBehavior"
v-model="subjectLineBehaviorLocal"
>
<option value="email">
{{ $t('settings.subject_line_email') }}
{{ subjectLineBehaviorDefault == 'email' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="masto">
{{ $t('settings.subject_line_mastodon') }}
{{ subjectLineBehaviorDefault == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="noop">
{{ $t('settings.subject_line_noop') }}
{{ subjectLineBehaviorDefault == 'noop' ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</li>
<li v-if="postFormats.length > 0">
<div>
{{ $t('settings.post_status_content_type') }}
<label
for="postContentType"
class="select"
>
<select
id="postContentType"
v-model="postContentTypeLocal"
>
<option
v-for="postFormat in postFormats"
:key="postFormat"
:value="postFormat"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
{{ postContentTypeDefault === postFormat ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div> </div>
</li> </li>
</ul>
</li>
<li>
<input type="checkbox" id="playVideosInModal" v-model="playVideosInModal">
<label for="playVideosInModal">{{$t('settings.play_videos_in_modal')}}</label>
</li>
<li>
<input type="checkbox" id="useContainFit" v-model="useContainFit">
<label for="useContainFit">{{$t('settings.use_contain_fit')}}</label>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{$t('settings.notifications')}}</h2>
<ul class="setting-list">
<li>
<input type="checkbox" id="webPushNotifications" v-model="webPushNotificationsLocal">
<label for="webPushNotifications">
{{$t('settings.enable_web_push_notifications')}}
</label>
</li>
</ul>
</div>
</div>
<div :label="$t('settings.theme')" >
<div class="setting-item">
<style-switcher></style-switcher>
</div>
</div>
<div :label="$t('settings.filtering')" >
<div class="setting-item">
<div class="select-multiple">
<span class="label">{{$t('settings.notification_visibility')}}</span>
<ul class="option-list">
<li>
<input type="checkbox" id="notification-visibility-likes" v-model="notificationVisibilityLocal.likes">
<label for="notification-visibility-likes">
{{$t('settings.notification_visibility_likes')}}
</label>
</li>
<li>
<input type="checkbox" id="notification-visibility-repeats" v-model="notificationVisibilityLocal.repeats">
<label for="notification-visibility-repeats">
{{$t('settings.notification_visibility_repeats')}}
</label>
</li>
<li>
<input type="checkbox" id="notification-visibility-follows" v-model="notificationVisibilityLocal.follows">
<label for="notification-visibility-follows">
{{$t('settings.notification_visibility_follows')}}
</label>
</li>
<li>
<input type="checkbox" id="notification-visibility-mentions" v-model="notificationVisibilityLocal.mentions">
<label for="notification-visibility-mentions">
{{$t('settings.notification_visibility_mentions')}}
</label>
</li>
</ul>
</div>
<div>
{{$t('settings.replies_in_timeline')}}
<label for="replyVisibility" class="select">
<select id="replyVisibility" v-model="replyVisibilityLocal">
<option value="all" selected>{{$t('settings.reply_visibility_all')}}</option>
<option value="following">{{$t('settings.reply_visibility_following')}}</option>
<option value="self">{{$t('settings.reply_visibility_self')}}</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
<div>
<input type="checkbox" id="hidePostStats" v-model="hidePostStatsLocal">
<label for="hidePostStats">
{{$t('settings.hide_post_stats')}} {{$t('settings.instance_default', { value: hidePostStatsDefault })}}
</label>
</div>
<div>
<input type="checkbox" id="hideUserStats" v-model="hideUserStatsLocal">
<label for="hideUserStats">
{{$t('settings.hide_user_stats')}} {{$t('settings.instance_default', { value: hideUserStatsDefault })}}
</label>
</div>
</div>
<div class="setting-item">
<div>
<p>{{$t('settings.filtering_explanation')}}</p>
<textarea id="muteWords" v-model="muteWordsString"></textarea>
</div>
<div>
<input type="checkbox" id="hideFilteredStatuses" v-model="hideFilteredStatusesLocal">
<label for="hideFilteredStatuses">
{{$t('settings.hide_filtered_statuses')}} {{$t('settings.instance_default', { value: hideFilteredStatusesDefault })}}
</label>
</div>
</div>
</div>
<div :label="$t('settings.version.title')" >
<div class="setting-item">
<ul class="setting-list">
<li>
<p>{{$t('settings.version.backend_version')}}</p>
<ul class="option-list">
<li> <li>
<a :href="backendVersionLink" target="_blank">{{backendVersion}}</a> <input
id="minimalScopesMode"
v-model="minimalScopesModeLocal"
type="checkbox"
>
<label for="minimalScopesMode">
{{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeDefault }) }}
</label>
</li>
<li>
<input
id="autohideFloatingPostButton"
v-model="autohideFloatingPostButtonLocal"
type="checkbox"
>
<label for="autohideFloatingPostButton">{{ $t('settings.autohide_floating_post_button') }}</label>
</li> </li>
</ul> </ul>
</li> </div>
<li>
<p>{{$t('settings.version.frontend_version')}}</p> <div class="setting-item">
<ul class="option-list"> <h2>{{ $t('settings.attachments') }}</h2>
<ul class="setting-list">
<li> <li>
<a :href="frontendVersionLink" target="_blank">{{frontendVersion}}</a> <input
id="hideAttachments"
v-model="hideAttachmentsLocal"
type="checkbox"
>
<label for="hideAttachments">{{ $t('settings.hide_attachments_in_tl') }}</label>
</li>
<li>
<input
id="hideAttachmentsInConv"
v-model="hideAttachmentsInConvLocal"
type="checkbox"
>
<label for="hideAttachmentsInConv">{{ $t('settings.hide_attachments_in_convo') }}</label>
</li>
<li>
<label for="maxThumbnails">{{ $t('settings.max_thumbnails') }}</label>
<input
id="maxThumbnails"
v-model.number="maxThumbnails"
class="number-input"
type="number"
min="0"
step="1"
>
</li>
<li>
<input
id="hideNsfw"
v-model="hideNsfwLocal"
type="checkbox"
>
<label for="hideNsfw">{{ $t('settings.nsfw_clickthrough') }}</label>
</li>
<ul class="setting-list suboptions">
<li>
<input
id="preloadImage"
v-model="preloadImage"
:disabled="!hideNsfwLocal"
type="checkbox"
>
<label for="preloadImage">{{ $t('settings.preload_images') }}</label>
</li>
<li>
<input
id="useOneClickNsfw"
v-model="useOneClickNsfw"
:disabled="!hideNsfwLocal"
type="checkbox"
>
<label for="useOneClickNsfw">{{ $t('settings.use_one_click_nsfw') }}</label>
</li>
</ul>
<li>
<input
id="stopGifs"
v-model="stopGifs"
type="checkbox"
>
<label for="stopGifs">{{ $t('settings.stop_gifs') }}</label>
</li>
<li>
<input
id="loopVideo"
v-model="loopVideoLocal"
type="checkbox"
>
<label for="loopVideo">{{ $t('settings.loop_video') }}</label>
<ul
class="setting-list suboptions"
:class="[{disabled: !streamingLocal}]"
>
<li>
<input
id="loopVideoSilentOnly"
v-model="loopVideoSilentOnlyLocal"
:disabled="!loopVideoLocal || !loopSilentAvailable"
type="checkbox"
>
<label for="loopVideoSilentOnly">{{ $t('settings.loop_video_silent_only') }}</label>
<div
v-if="!loopSilentAvailable"
class="unavailable"
>
<i class="icon-globe" />! {{ $t('settings.limited_availability') }}
</div>
</li>
</ul>
</li>
<li>
<input
id="playVideosInModal"
v-model="playVideosInModal"
type="checkbox"
>
<label for="playVideosInModal">{{ $t('settings.play_videos_in_modal') }}</label>
</li>
<li>
<input
id="useContainFit"
v-model="useContainFit"
type="checkbox"
>
<label for="useContainFit">{{ $t('settings.use_contain_fit') }}</label>
</li> </li>
</ul> </ul>
</li> </div>
</ul>
</div> <div class="setting-item">
</div> <h2>{{ $t('settings.notifications') }}</h2>
</tab-switcher> <ul class="setting-list">
</keep-alive> <li>
<input
id="webPushNotifications"
v-model="webPushNotificationsLocal"
type="checkbox"
>
<label for="webPushNotifications">
{{ $t('settings.enable_web_push_notifications') }}
</label>
</li>
</ul>
</div>
</div>
<div :label="$t('settings.theme')">
<div class="setting-item">
<style-switcher />
</div>
</div>
<div :label="$t('settings.filtering')">
<div class="setting-item">
<div class="select-multiple">
<span class="label">{{ $t('settings.notification_visibility') }}</span>
<ul class="option-list">
<li>
<input
id="notification-visibility-likes"
v-model="notificationVisibilityLocal.likes"
type="checkbox"
>
<label for="notification-visibility-likes">
{{ $t('settings.notification_visibility_likes') }}
</label>
</li>
<li>
<input
id="notification-visibility-repeats"
v-model="notificationVisibilityLocal.repeats"
type="checkbox"
>
<label for="notification-visibility-repeats">
{{ $t('settings.notification_visibility_repeats') }}
</label>
</li>
<li>
<input
id="notification-visibility-follows"
v-model="notificationVisibilityLocal.follows"
type="checkbox"
>
<label for="notification-visibility-follows">
{{ $t('settings.notification_visibility_follows') }}
</label>
</li>
<li>
<input
id="notification-visibility-mentions"
v-model="notificationVisibilityLocal.mentions"
type="checkbox"
>
<label for="notification-visibility-mentions">
{{ $t('settings.notification_visibility_mentions') }}
</label>
</li>
</ul>
</div>
<div>
{{ $t('settings.replies_in_timeline') }}
<label
for="replyVisibility"
class="select"
>
<select
id="replyVisibility"
v-model="replyVisibilityLocal"
>
<option
value="all"
selected
>{{ $t('settings.reply_visibility_all') }}</option>
<option value="following">{{ $t('settings.reply_visibility_following') }}</option>
<option value="self">{{ $t('settings.reply_visibility_self') }}</option>
</select>
<i class="icon-down-open" />
</label>
</div>
<div>
<input
id="hidePostStats"
v-model="hidePostStatsLocal"
type="checkbox"
>
<label for="hidePostStats">
{{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsDefault }) }}
</label>
</div>
<div>
<input
id="hideUserStats"
v-model="hideUserStatsLocal"
type="checkbox"
>
<label for="hideUserStats">
{{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsDefault }) }}
</label>
</div>
</div>
<div class="setting-item">
<div>
<p>{{ $t('settings.filtering_explanation') }}</p>
<textarea
id="muteWords"
v-model="muteWordsString"
/>
</div>
<div>
<input
id="hideFilteredStatuses"
v-model="hideFilteredStatusesLocal"
type="checkbox"
>
<label for="hideFilteredStatuses">
{{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesDefault }) }}
</label>
</div>
</div>
</div>
<div :label="$t('settings.version.title')">
<div class="setting-item">
<ul class="setting-list">
<li>
<p>{{ $t('settings.version.backend_version') }}</p>
<ul class="option-list">
<li>
<a
:href="backendVersionLink"
target="_blank"
>{{ backendVersion }}</a>
</li>
</ul>
</li>
<li>
<p>{{ $t('settings.version.frontend_version') }}</p>
<ul class="option-list">
<li>
<a
:href="frontendVersionLink"
target="_blank"
>{{ frontendVersion }}</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</tab-switcher>
</keep-alive>
</div>
</div> </div>
</div>
</template> </template>
<script src="./settings.js"> <script src="./settings.js">
</script> </script>

View file

@ -1,134 +1,207 @@
<template> <template>
<div class="shadow-control" :class="{ disabled: !present }"> <div
<div class="shadow-preview-container"> class="shadow-control"
<div :disabled="!present" class="y-shift-control"> :class="{ disabled: !present }"
<input >
v-model="selected.y" <div class="shadow-preview-container">
<div
:disabled="!present" :disabled="!present"
class="input-number" class="y-shift-control"
type="number"> >
<div class="wrap">
<input <input
v-model="selected.y" v-model="selected.y"
:disabled="!present" :disabled="!present"
class="input-range" class="input-number"
type="range" type="number"
max="20" >
min="-20"> <div class="wrap">
<input
v-model="selected.y"
:disabled="!present"
class="input-range"
type="range"
max="20"
min="-20"
>
</div>
</div> </div>
</div> <div class="preview-window">
<div class="preview-window"> <div
<div class="preview-block" :style="style"></div> class="preview-block"
</div> :style="style"
<div :disabled="!present" class="x-shift-control"> />
<input </div>
v-model="selected.x" <div
:disabled="!present" :disabled="!present"
class="input-number" class="x-shift-control"
type="number"> >
<div class="wrap">
<input <input
v-model="selected.x" v-model="selected.x"
:disabled="!present" :disabled="!present"
class="input-number"
type="number"
>
<div class="wrap">
<input
v-model="selected.x"
:disabled="!present"
class="input-range"
type="range"
max="20"
min="-20"
>
</div>
</div>
</div>
<div class="shadow-tweak">
<div
:disabled="usingFallback"
class="id-control style-control"
>
<label
for="shadow-switcher"
class="select"
:disabled="!ready || usingFallback"
>
<select
id="shadow-switcher"
v-model="selectedId"
class="shadow-switcher"
:disabled="!ready || usingFallback"
>
<option
v-for="(shadow, index) in cValue"
:key="index"
:value="index"
>
{{ $t('settings.style.shadows.shadow_id', { value: index }) }}
</option>
</select>
<i class="icon-down-open" />
</label>
<button
class="btn btn-default"
:disabled="!ready || !present"
@click="del"
>
<i class="icon-cancel" />
</button>
<button
class="btn btn-default"
:disabled="!moveUpValid"
@click="moveUp"
>
<i class="icon-up-open" />
</button>
<button
class="btn btn-default"
:disabled="!moveDnValid"
@click="moveDn"
>
<i class="icon-down-open" />
</button>
<button
class="btn btn-default"
:disabled="usingFallback"
@click="add"
>
<i class="icon-plus" />
</button>
</div>
<div
:disabled="!present"
class="inset-control style-control"
>
<label
for="inset"
class="label"
>
{{ $t('settings.style.shadows.inset') }}
</label>
<input
id="inset"
v-model="selected.inset"
:disabled="!present"
name="inset"
class="input-inset"
type="checkbox"
>
<label
class="checkbox-label"
for="inset"
/>
</div>
<div
:disabled="!present"
class="blur-control style-control"
>
<label
for="spread"
class="label"
>
{{ $t('settings.style.shadows.blur') }}
</label>
<input
id="blur"
v-model="selected.blur"
:disabled="!present"
name="blur"
class="input-range" class="input-range"
type="range" type="range"
max="20" max="20"
min="-20"> min="0"
>
<input
v-model="selected.blur"
:disabled="!present"
class="input-number"
type="number"
min="0"
>
</div> </div>
<div
:disabled="!present"
class="spread-control style-control"
>
<label
for="spread"
class="label"
>
{{ $t('settings.style.shadows.spread') }}
</label>
<input
id="spread"
v-model="selected.spread"
:disabled="!present"
name="spread"
class="input-range"
type="range"
max="20"
min="-20"
>
<input
v-model="selected.spread"
:disabled="!present"
class="input-number"
type="number"
>
</div>
<ColorInput
v-model="selected.color"
:disabled="!present"
:label="$t('settings.style.common.color')"
name="shadow"
/>
<OpacityInput
v-model="selected.alpha"
:disabled="!present"
/>
<p>
{{ $t('settings.style.shadows.hint') }}
</p>
</div> </div>
</div> </div>
<div class="shadow-tweak">
<div :disabled="usingFallback" class="id-control style-control">
<label for="shadow-switcher" class="select" :disabled="!ready || usingFallback">
<select
v-model="selectedId" class="shadow-switcher"
:disabled="!ready || usingFallback"
id="shadow-switcher">
<option v-for="(shadow, index) in cValue" :value="index">
{{$t('settings.style.shadows.shadow_id', { value: index })}}
</option>
</select>
<i class="icon-down-open"/>
</label>
<button class="btn btn-default" :disabled="!ready || !present" @click="del">
<i class="icon-cancel"/>
</button>
<button class="btn btn-default" :disabled="!moveUpValid" @click="moveUp">
<i class="icon-up-open"/>
</button>
<button class="btn btn-default" :disabled="!moveDnValid" @click="moveDn">
<i class="icon-down-open"/>
</button>
<button class="btn btn-default" :disabled="usingFallback" @click="add">
<i class="icon-plus"/>
</button>
</div>
<div :disabled="!present" class="inset-control style-control">
<label for="inset" class="label">
{{$t('settings.style.shadows.inset')}}
</label>
<input
v-model="selected.inset"
:disabled="!present"
name="inset"
id="inset"
class="input-inset"
type="checkbox">
<label class="checkbox-label" for="inset"></label>
</div>
<div :disabled="!present" class="blur-control style-control">
<label for="spread" class="label">
{{$t('settings.style.shadows.blur')}}
</label>
<input
v-model="selected.blur"
:disabled="!present"
name="blur"
id="blur"
class="input-range"
type="range"
max="20"
min="0">
<input
v-model="selected.blur"
:disabled="!present"
class="input-number"
type="number"
min="0">
</div>
<div :disabled="!present" class="spread-control style-control">
<label for="spread" class="label">
{{$t('settings.style.shadows.spread')}}
</label>
<input
v-model="selected.spread"
:disabled="!present"
name="spread"
id="spread"
class="input-range"
type="range"
max="20"
min="-20">
<input
v-model="selected.spread"
:disabled="!present"
class="input-number"
type="number">
</div>
<ColorInput
v-model="selected.color"
:disabled="!present"
:label="$t('settings.style.common.color')"
name="shadow"/>
<OpacityInput
v-model="selected.alpha"
:disabled="!present"/>
<p>
{{$t('settings.style.shadows.hint')}}
</p>
</div>
</div>
</template> </template>
<script src="./shadow_control.js" ></script> <script src="./shadow_control.js" ></script>

View file

@ -1,63 +1,98 @@
<template> <template>
<div class="side-drawer-container" <div
class="side-drawer-container"
:class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }" :class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }"
> >
<div class="side-drawer-darken" :class="{ 'side-drawer-darken-closed': closed}" /> <div
<div class="side-drawer" class="side-drawer-darken"
:class="{ 'side-drawer-darken-closed': closed}"
/>
<div
class="side-drawer"
:class="{'side-drawer-closed': closed}" :class="{'side-drawer-closed': closed}"
@touchstart="touchStart" @touchstart="touchStart"
@touchmove="touchMove" @touchmove="touchMove"
> >
<div class="side-drawer-heading" @click="toggleDrawer"> <div
<UserCard :user="currentUser" :hideBio="true" v-if="currentUser"/> class="side-drawer-heading"
<div class="side-drawer-logo-wrapper" v-else> @click="toggleDrawer"
<img :src="logo"/> >
<span>{{sitename}}</span> <UserCard
v-if="currentUser"
:user="currentUser"
:hide-bio="true"
/>
<div
v-else
class="side-drawer-logo-wrapper"
>
<img :src="logo">
<span>{{ sitename }}</span>
</div> </div>
</div> </div>
<ul> <ul>
<li v-if="!currentUser" @click="toggleDrawer"> <li
v-if="!currentUser"
@click="toggleDrawer"
>
<router-link :to="{ name: 'login' }"> <router-link :to="{ name: 'login' }">
{{ $t("login.login") }} {{ $t("login.login") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser" @click="toggleDrawer"> <li
v-if="currentUser"
@click="toggleDrawer"
>
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }} {{ $t("nav.dms") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser" @click="toggleDrawer"> <li
v-if="currentUser"
@click="toggleDrawer"
>
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
{{ $t("nav.interactions") }} {{ $t("nav.interactions") }}
</router-link> </router-link>
</li> </li>
</ul> </ul>
<ul> <ul>
<li v-if="currentUser" @click="toggleDrawer"> <li
v-if="currentUser"
@click="toggleDrawer"
>
<router-link :to="{ name: 'friends' }"> <router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }} {{ $t("nav.timeline") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser && currentUser.locked" @click="toggleDrawer"> <li
<router-link to='/friend-requests'> v-if="currentUser && currentUser.locked"
@click="toggleDrawer"
>
<router-link to="/friend-requests">
{{ $t("nav.friend_requests") }} {{ $t("nav.friend_requests") }}
<span v-if='followRequestCount > 0' class="badge follow-request-count"> <span
{{followRequestCount}} v-if="followRequestCount > 0"
class="badge follow-request-count"
>
{{ followRequestCount }}
</span> </span>
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link to='/main/public'> <router-link to="/main/public">
{{ $t("nav.public_tl") }} {{ $t("nav.public_tl") }}
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link to='/main/all'> <router-link to="/main/all">
{{ $t("nav.twkn") }} {{ $t("nav.twkn") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser && chat" @click="toggleDrawer"> <li
v-if="currentUser && chat"
@click="toggleDrawer"
>
<router-link :to="{ name: 'chat' }"> <router-link :to="{ name: 'chat' }">
{{ $t("nav.chat") }} {{ $t("nav.chat") }}
</router-link> </router-link>
@ -69,7 +104,10 @@
{{ $t("nav.user_search") }} {{ $t("nav.user_search") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser && suggestionsEnabled" @click="toggleDrawer"> <li
v-if="currentUser && suggestionsEnabled"
@click="toggleDrawer"
>
<router-link :to="{ name: 'who-to-follow' }"> <router-link :to="{ name: 'who-to-follow' }">
{{ $t("nav.who_to_follow") }} {{ $t("nav.who_to_follow") }}
</router-link> </router-link>
@ -84,17 +122,24 @@
{{ $t("nav.about") }} {{ $t("nav.about") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser" @click="toggleDrawer"> <li
<a @click="doLogout" href="#"> v-if="currentUser"
@click="toggleDrawer"
>
<a
href="#"
@click="doLogout"
>
{{ $t("login.logout") }} {{ $t("login.logout") }}
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="side-drawer-click-outside" <div
@click.stop.prevent="toggleDrawer" class="side-drawer-click-outside"
:class="{'side-drawer-click-outside-closed': closed}" :class="{'side-drawer-click-outside-closed': closed}"
></div> @click.stop.prevent="toggleDrawer"
/>
</div> </div>
</template> </template>

View file

@ -173,12 +173,13 @@ const Status = {
if (this.status.type === 'retweet') { if (this.status.type === 'retweet') {
return false return false
} }
var checkFollowing = this.$store.state.config.replyVisibility === 'following' const checkFollowing = this.$store.state.config.replyVisibility === 'following'
for (var i = 0; i < this.status.attentions.length; ++i) { for (var i = 0; i < this.status.attentions.length; ++i) {
if (this.status.user.id === this.status.attentions[i].id) { if (this.status.user.id === this.status.attentions[i].id) {
continue continue
} }
if (checkFollowing && this.$store.getters.findUser(this.status.attentions[i].id).following) { const taggedUser = this.$store.getters.findUser(this.status.attentions[i].id)
if (checkFollowing && taggedUser && taggedUser.following) {
return false return false
} }
if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) { if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
@ -221,7 +222,7 @@ const Status = {
? this.$store.state.instance.subjectLineBehavior ? this.$store.state.instance.subjectLineBehavior
: this.$store.state.config.subjectLineBehavior : this.$store.state.config.subjectLineBehavior
const startsWithRe = decodedSummary.match(/^re[: ]/i) const startsWithRe = decodedSummary.match(/^re[: ]/i)
if (behavior !== 'noop' && startsWithRe || behavior === 'masto') { if ((behavior !== 'noop' && startsWithRe) || behavior === 'masto') {
return decodedSummary return decodedSummary
} else if (behavior === 'email') { } else if (behavior === 'email') {
return 're: '.concat(decodedSummary) return 're: '.concat(decodedSummary)

View file

@ -1,190 +1,431 @@
<template> <template>
<div class="status-el" v-if="!hideStatus" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]"> <!-- eslint-disable vue/no-v-html -->
<div v-if="error" class="alert error"> <div
{{error}} v-if="!hideStatus"
<i class="button-icon icon-cancel" @click="clearError"></i> class="status-el"
:class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]"
>
<div
v-if="error"
class="alert error"
>
{{ error }}
<i
class="button-icon icon-cancel"
@click="clearError"
/>
</div> </div>
<template v-if="muted && !isPreview"> <template v-if="muted && !isPreview">
<div class="media status container muted"> <div class="media status container muted">
<small> <small>
<router-link :to="userProfileLink"> <router-link :to="userProfileLink">
{{status.user.screen_name}} {{ status.user.screen_name }}
</router-link> </router-link>
</small> </small>
<small class="muteWords">{{muteWordHits.join(', ')}}</small> <small class="muteWords">{{ muteWordHits.join(', ') }}</small>
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="button-icon icon-eye-off"></i></a> <a
href="#"
class="unmute"
@click.prevent="toggleMute"
><i class="button-icon icon-eye-off" /></a>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div v-if="showPinned && statusoid.pinned" class="status-pin"> <div
<i class="fa icon-pin faint"></i> v-if="showPinned && statusoid.pinned"
<span class="faint">{{$t('status.pinned')}}</span> class="status-pin"
>
<i class="fa icon-pin faint" />
<span class="faint">{{ $t('status.pinned') }}</span>
</div> </div>
<div v-if="retweet && !noHeading && !inConversation" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info"> <div
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :user="statusoid.user"/> v-if="retweet && !noHeading && !inConversation"
:class="[repeaterClass, { highlighted: repeaterStyle }]"
:style="[repeaterStyle]"
class="media container retweet-info"
>
<UserAvatar
v-if="retweet"
class="media-left"
:better-shadow="betterShadow"
:user="statusoid.user"
/>
<div class="media-body faint"> <div class="media-body faint">
<span class="user-name"> <span class="user-name">
<router-link v-if="retweeterHtml" :to="retweeterProfileLink" v-html="retweeterHtml"/> <router-link
<router-link v-else :to="retweeterProfileLink">{{retweeter}}</router-link> v-if="retweeterHtml"
:to="retweeterProfileLink"
v-html="retweeterHtml"
/>
<router-link
v-else
:to="retweeterProfileLink"
>{{ retweeter }}</router-link>
</span> </span>
<i class='fa icon-retweet retweeted' :title="$t('tool_tip.repeat')"></i> <i
{{$t('timeline.repeated')}} class="fa icon-retweet retweeted"
:title="$t('tool_tip.repeat')"
/>
{{ $t('timeline.repeated') }}
</div> </div>
</div> </div>
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status" :data-tags="tags"> <div
<div v-if="!noHeading" class="media-left"> :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]"
<router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded"> :style="[ userStyle ]"
<UserAvatar :compact="compact" :betterShadow="betterShadow" :user="status.user"/> class="media status"
:data-tags="tags"
>
<div
v-if="!noHeading"
class="media-left"
>
<router-link
:to="userProfileLink"
@click.stop.prevent.capture.native="toggleUserExpanded"
>
<UserAvatar
:compact="compact"
:better-shadow="betterShadow"
:user="status.user"
/>
</router-link> </router-link>
</div> </div>
<div class="status-body"> <div class="status-body">
<UserCard :user="status.user" :rounded="true" :bordered="true" class="status-usercard" v-if="userExpanded"/> <UserCard
<div v-if="!noHeading" class="media-heading"> v-if="userExpanded"
:user="status.user"
:rounded="true"
:bordered="true"
class="status-usercard"
/>
<div
v-if="!noHeading"
class="media-heading"
>
<div class="heading-name-row"> <div class="heading-name-row">
<div class="name-and-account-name"> <div class="name-and-account-name">
<h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4> <h4
<h4 class="user-name" v-else>{{status.user.name}}</h4> v-if="status.user.name_html"
<router-link class="account-name" :to="userProfileLink"> class="user-name"
{{status.user.screen_name}} v-html="status.user.name_html"
/>
<h4
v-else
class="user-name"
>
{{ status.user.name }}
</h4>
<router-link
class="account-name"
:to="userProfileLink"
>
{{ status.user.screen_name }}
</router-link> </router-link>
</div> </div>
<span class="heading-right"> <span class="heading-right">
<router-link class="timeago faint-link" :to="{ name: 'conversation', params: { id: status.id } }"> <router-link
<Timeago :time="status.created_at" :auto-update="60"></Timeago> class="timeago faint-link"
:to="{ name: 'conversation', params: { id: status.id } }"
>
<Timeago
:time="status.created_at"
:auto-update="60"
/>
</router-link> </router-link>
<div class="button-icon visibility-icon" v-if="status.visibility"> <div
<i :class="visibilityIcon(status.visibility)" :title="status.visibility | capitalize"></i> v-if="status.visibility"
class="button-icon visibility-icon"
>
<i
:class="visibilityIcon(status.visibility)"
:title="status.visibility | capitalize"
/>
</div> </div>
<a :href="status.external_url" target="_blank" v-if="!status.is_local && !isPreview" class="source_url" title="Source"> <a
<i class="button-icon icon-link-ext-alt"></i> v-if="!status.is_local && !isPreview"
:href="status.external_url"
target="_blank"
class="source_url"
title="Source"
>
<i class="button-icon icon-link-ext-alt" />
</a> </a>
<template v-if="expandable && !isPreview"> <template v-if="expandable && !isPreview">
<a href="#" @click.prevent="toggleExpanded" title="Expand"> <a
<i class="button-icon icon-plus-squared"></i> href="#"
title="Expand"
@click.prevent="toggleExpanded"
>
<i class="button-icon icon-plus-squared" />
</a> </a>
</template> </template>
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="button-icon icon-eye-off"></i></a> <a
v-if="unmuted"
href="#"
@click.prevent="toggleMute"
><i class="button-icon icon-eye-off" /></a>
</span> </span>
</div> </div>
<div class="heading-reply-row"> <div class="heading-reply-row">
<div v-if="isReply" class="reply-to-and-accountname"> <div
<a class="reply-to" v-if="isReply"
href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)" class="reply-to-and-accountname"
>
<a
class="reply-to"
href="#"
:aria-label="$t('tool_tip.reply')" :aria-label="$t('tool_tip.reply')"
@click.prevent="gotoOriginal(status.in_reply_to_status_id)"
@mouseenter.prevent.stop="replyEnter(status.in_reply_to_status_id, $event)" @mouseenter.prevent.stop="replyEnter(status.in_reply_to_status_id, $event)"
@mouseleave.prevent.stop="replyLeave()" @mouseleave.prevent.stop="replyLeave()"
> >
<i class="button-icon icon-reply" v-if="!isPreview"></i> <i
<span class="faint-link reply-to-text">{{$t('status.reply_to')}}</span> v-if="!isPreview"
class="button-icon icon-reply"
/>
<span class="faint-link reply-to-text">{{ $t('status.reply_to') }}</span>
</a> </a>
<router-link :to="replyProfileLink"> <router-link :to="replyProfileLink">
{{replyToName}} {{ replyToName }}
</router-link> </router-link>
<span class="faint replies-separator" v-if="replies && replies.length"> <span
v-if="replies && replies.length"
class="faint replies-separator"
>
- -
</span> </span>
</div> </div>
<div class="replies" v-if="inConversation && !isPreview"> <div
<span class="faint" v-if="replies && replies.length">{{$t('status.replies_list')}}</span> v-if="inConversation && !isPreview"
<span class="reply-link faint" v-if="replies" v-for="reply in replies"> class="replies"
<a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}}</a> >
</span> <span
v-if="replies && replies.length"
class="faint"
>{{ $t('status.replies_list') }}</span>
<template v-if="replies">
<span
v-for="reply in replies"
:key="reply.id"
class="reply-link faint"
>
<a
href="#"
@click.prevent="gotoOriginal(reply.id)"
@mouseenter="replyEnter(reply.id, $event)"
@mouseout="replyLeave()"
>{{ reply.name }}</a>
</span>
</template>
</div> </div>
</div> </div>
</div> </div>
<div v-if="showPreview" class="status-preview-container"> <div
<status class="status-preview" v-if="showPreview"
class="status-preview-container"
>
<status
v-if="preview" v-if="preview"
:isPreview="true" class="status-preview"
:is-preview="true"
:statusoid="preview" :statusoid="preview"
:compact="true" :compact="true"
/> />
<div v-else class="status-preview status-preview-loading"> <div
<i class="icon-spin4 animate-spin"></i> v-else
class="status-preview status-preview-loading"
>
<i class="icon-spin4 animate-spin" />
</div> </div>
</div> </div>
<div class="status-content-wrapper" :class="{ 'tall-status': !showingLongSubject }" v-if="longSubject"> <div
<a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="!showingLongSubject" href="#" @click.prevent="showingLongSubject=true">{{$t("general.show_more")}}</a> v-if="longSubject"
<div @click.prevent="linkClicked" class="status-content media-body" v-html="contentHtml"></div> class="status-content-wrapper"
<a v-if="showingLongSubject" href="#" class="status-unhider" @click.prevent="showingLongSubject=false">{{$t("general.show_less")}}</a> :class="{ 'tall-status': !showingLongSubject }"
>
<a
v-if="!showingLongSubject"
class="tall-status-hider"
:class="{ 'tall-status-hider_focused': isFocused }"
href="#"
@click.prevent="showingLongSubject=true"
>{{ $t("general.show_more") }}</a>
<div
class="status-content media-body"
@click.prevent="linkClicked"
v-html="contentHtml"
/>
<a
v-if="showingLongSubject"
href="#"
class="status-unhider"
@click.prevent="showingLongSubject=false"
>{{ $t("general.show_less") }}</a>
</div> </div>
<div :class="{'tall-status': hideTallStatus}" class="status-content-wrapper" v-else> <div
<a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="hideTallStatus" href="#" @click.prevent="toggleShowMore">{{$t("general.show_more")}}</a> v-else
<div @click.prevent="linkClicked" class="status-content media-body" v-html="contentHtml" v-if="!hideSubjectStatus"></div> :class="{'tall-status': hideTallStatus}"
<div @click.prevent="linkClicked" class="status-content media-body" v-html="status.summary_html" v-else></div> class="status-content-wrapper"
<a v-if="hideSubjectStatus" href="#" class="cw-status-hider" @click.prevent="toggleShowMore">{{$t("general.show_more")}}</a> >
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">{{$t("general.show_less")}}</a> <a
v-if="hideTallStatus"
class="tall-status-hider"
:class="{ 'tall-status-hider_focused': isFocused }"
href="#"
@click.prevent="toggleShowMore"
>{{ $t("general.show_more") }}</a>
<div
v-if="!hideSubjectStatus"
class="status-content media-body"
@click.prevent="linkClicked"
v-html="contentHtml"
/>
<div
v-else
class="status-content media-body"
@click.prevent="linkClicked"
v-html="status.summary_html"
/>
<a
v-if="hideSubjectStatus"
href="#"
class="cw-status-hider"
@click.prevent="toggleShowMore"
>{{ $t("general.show_more") }}</a>
<a
v-if="showingMore"
href="#"
class="status-unhider"
@click.prevent="toggleShowMore"
>{{ $t("general.show_less") }}</a>
</div> </div>
<div v-if="status.poll && status.poll.options"> <div v-if="status.poll && status.poll.options">
<poll :base-poll="status.poll" /> <poll :base-poll="status.poll" />
</div> </div>
<div v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)" class="attachments media-body"> <div
v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)"
class="attachments media-body"
>
<attachment <attachment
class="non-gallery"
v-for="attachment in nonGalleryAttachments" v-for="attachment in nonGalleryAttachments"
:key="attachment.id"
class="non-gallery"
:size="attachmentSize" :size="attachmentSize"
:nsfw="nsfwClickthrough" :nsfw="nsfwClickthrough"
:attachment="attachment" :attachment="attachment"
:allowPlay="true" :allow-play="true"
:setMedia="setMedia()" :set-media="setMedia()"
:key="attachment.id"
/> />
<gallery <gallery
v-if="galleryAttachments.length > 0" v-if="galleryAttachments.length > 0"
:nsfw="nsfwClickthrough" :nsfw="nsfwClickthrough"
:attachments="galleryAttachments" :attachments="galleryAttachments"
:setMedia="setMedia()" :set-media="setMedia()"
/> />
</div> </div>
<div v-if="status.card && !hideSubjectStatus && !noHeading" class="link-preview media-body"> <div
<link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" /> v-if="status.card && !hideSubjectStatus && !noHeading"
class="link-preview media-body"
>
<link-preview
:card="status.card"
:size="attachmentSize"
:nsfw="nsfwClickthrough"
/>
</div> </div>
<transition name="fade"> <transition name="fade">
<div class="favs-repeated-users" v-if="isFocused && combinedFavsAndRepeatsUsers.length > 0"> <div
v-if="isFocused && combinedFavsAndRepeatsUsers.length > 0"
class="favs-repeated-users"
>
<div class="stats"> <div class="stats">
<div class="stat-count" v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0"> <div
v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0"
class="stat-count"
>
<a class="stat-title">{{ $t('status.repeats') }}</a> <a class="stat-title">{{ $t('status.repeats') }}</a>
<div class="stat-number">{{ statusFromGlobalRepository.rebloggedBy.length }}</div> <div class="stat-number">
{{ statusFromGlobalRepository.rebloggedBy.length }}
</div>
</div> </div>
<div class="stat-count" v-if="statusFromGlobalRepository.favoritedBy && statusFromGlobalRepository.favoritedBy.length > 0"> <div
v-if="statusFromGlobalRepository.favoritedBy && statusFromGlobalRepository.favoritedBy.length > 0"
class="stat-count"
>
<a class="stat-title">{{ $t('status.favorites') }}</a> <a class="stat-title">{{ $t('status.favorites') }}</a>
<div class="stat-number">{{ statusFromGlobalRepository.favoritedBy.length }}</div> <div class="stat-number">
{{ statusFromGlobalRepository.favoritedBy.length }}
</div>
</div> </div>
<div class="avatar-row"> <div class="avatar-row">
<AvatarList :users="combinedFavsAndRepeatsUsers"></AvatarList> <AvatarList :users="combinedFavsAndRepeatsUsers" />
</div> </div>
</div> </div>
</div> </div>
</transition> </transition>
<div v-if="!noHeading && !isPreview" class='status-actions media-body'> <div
v-if="!noHeading && !isPreview"
class="status-actions media-body"
>
<div> <div>
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'button-icon-active': replying}" v-if="loggedIn"/> <i
<i class="button-icon button-icon-disabled icon-reply" :title="$t('tool_tip.reply')" v-else /> v-if="loggedIn"
<span v-if="status.replies_count > 0">{{status.replies_count}}</span> class="button-icon icon-reply"
:title="$t('tool_tip.reply')"
:class="{'button-icon-active': replying}"
@click.prevent="toggleReplying"
/>
<i
v-else
class="button-icon button-icon-disabled icon-reply"
:title="$t('tool_tip.reply')"
/>
<span v-if="status.replies_count > 0">{{ status.replies_count }}</span>
</div> </div>
<retweet-button :visibility='status.visibility' :loggedIn='loggedIn' :status='status'></retweet-button> <retweet-button
<favorite-button :loggedIn='loggedIn' :status='status'></favorite-button> :visibility="status.visibility"
<extra-buttons :status="status" @onError="showError" @onSuccess="clearError"></extra-buttons> :logged-in="loggedIn"
:status="status"
/>
<favorite-button
:logged-in="loggedIn"
:status="status"
/>
<extra-buttons
:status="status"
@onError="showError"
@onSuccess="clearError"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="container" v-if="replying"> <div
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" :copy-message-scope="status.visibility" :subject="replySubject" v-on:posted="toggleReplying"/> v-if="replying"
class="container"
>
<post-status-form
class="reply-body"
:reply-to="status.id"
:attentions="status.attentions"
:replied-user="status.user"
:copy-message-scope="status.visibility"
:subject="replySubject"
@posted="toggleReplying"
/>
</div> </div>
</template> </template>
</div> </div>
<!-- eslint-enable vue/no-v-html -->
</template> </template>
<script src="./status.js" ></script> <script src="./status.js" ></script>
@ -453,6 +694,7 @@ $status-margin: 0.75em;
.status-content { .status-content {
font-family: var(--postFont, sans-serif); font-family: var(--postFont, sans-serif);
line-height: 1.4em; line-height: 1.4em;
white-space: pre-wrap;
img, video { img, video {
max-width: 100%; max-width: 100%;

View file

@ -1,7 +1,19 @@
<template> <template>
<div class='still-image' :class='{ animated: animated }' > <div
<canvas ref="canvas" v-if="animated"></canvas> class="still-image"
<img ref="src" :src="src" :referrerpolicy="referrerpolicy" v-on:load="onLoad" @error="onError"/> :class="{ animated: animated }"
>
<canvas
v-if="animated"
ref="canvas"
/>
<img
ref="src"
:src="src"
:referrerpolicy="referrerpolicy"
@load="onLoad"
@error="onError"
>
</div> </div>
</template> </template>

View file

@ -1,78 +1,101 @@
<template> <template>
<div class="panel dummy"> <div class="panel dummy">
<div class="panel-heading"> <div class="panel-heading">
<div class="title"> <div class="title">
{{$t('settings.style.preview.header')}} {{ $t('settings.style.preview.header') }}
<span class="badge badge-notification"> <span class="badge badge-notification">
99 99
</span>
</div>
<span class="faint">
{{ $t('settings.style.preview.header_faint') }}
</span> </span>
</div> <span class="alert error">
<span class="faint"> {{ $t('settings.style.preview.error') }}
{{$t('settings.style.preview.header_faint')}}
</span>
<span class="alert error">
{{$t('settings.style.preview.error')}}
</span>
<button class="btn">
{{$t('settings.style.preview.button')}}
</button>
</div>
<div class="panel-body theme-preview-content">
<div class="post">
<div class="avatar">
( ͡° ͜ʖ ͡°)
</div>
<div class="content">
<h4>
{{$t('settings.style.preview.content')}}
</h4>
<i18n path="settings.style.preview.text">
<code style="font-family: var(--postCodeFont)">
{{$t('settings.style.preview.mono')}}
</code>
<a style="color: var(--link)">
{{$t('settings.style.preview.link')}}
</a>
</i18n>
<div class="icons">
<i style="color: var(--cBlue)" class="button-icon icon-reply"/>
<i style="color: var(--cGreen)" class="button-icon icon-retweet"/>
<i style="color: var(--cOrange)" class="button-icon icon-star"/>
<i style="color: var(--cRed)" class="button-icon icon-cancel"/>
</div>
</div>
</div>
<div class="after-post">
<div class="avatar-alt">
:^)
</div>
<div class="content">
<i18n path="settings.style.preview.fine_print" tag="span" class="faint">
<a style="color: var(--faintLink)">
{{$t('settings.style.preview.faint_link')}}
</a>
</i18n>
</div>
</div>
<div class="separator"></div>
<span class="alert error">
{{$t('settings.style.preview.error')}}
</span>
<input :value="$t('settings.style.preview.input')" type="text">
<div class="actions">
<span class="checkbox">
<input checked="very yes" type="checkbox" id="preview_checkbox">
<label for="preview_checkbox">{{$t('settings.style.preview.checkbox')}}</label>
</span> </span>
<button class="btn"> <button class="btn">
{{$t('settings.style.preview.button')}} {{ $t('settings.style.preview.button') }}
</button> </button>
</div> </div>
<div class="panel-body theme-preview-content">
<div class="post">
<div class="avatar">
( ͡° ͜ʖ ͡°)
</div>
<div class="content">
<h4>
{{ $t('settings.style.preview.content') }}
</h4>
<i18n path="settings.style.preview.text">
<code style="font-family: var(--postCodeFont)">
{{ $t('settings.style.preview.mono') }}
</code>
<a style="color: var(--link)">
{{ $t('settings.style.preview.link') }}
</a>
</i18n>
<div class="icons">
<i
style="color: var(--cBlue)"
class="button-icon icon-reply"
/>
<i
style="color: var(--cGreen)"
class="button-icon icon-retweet"
/>
<i
style="color: var(--cOrange)"
class="button-icon icon-star"
/>
<i
style="color: var(--cRed)"
class="button-icon icon-cancel"
/>
</div>
</div>
</div>
<div class="after-post">
<div class="avatar-alt">
:^)
</div>
<div class="content">
<i18n
path="settings.style.preview.fine_print"
tag="span"
class="faint"
>
<a style="color: var(--faintLink)">
{{ $t('settings.style.preview.faint_link') }}
</a>
</i18n>
</div>
</div>
<div class="separator" />
<span class="alert error">
{{ $t('settings.style.preview.error') }}
</span>
<input
:value="$t('settings.style.preview.input')"
type="text"
>
<div class="actions">
<span class="checkbox">
<input
id="preview_checkbox"
checked="very yes"
type="checkbox"
>
<label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
</span>
<button class="btn">
{{ $t('settings.style.preview.button') }}
</button>
</div>
</div>
</div> </div>
</div>
</template> </template>

View file

@ -1,274 +1,593 @@
<template> <template>
<div class="style-switcher"> <div class="style-switcher">
<div class="presets-container"> <div class="presets-container">
<div class="save-load"> <div class="save-load">
<export-import <export-import
:exportObject='exportedTheme' :export-object="exportedTheme"
:exportLabel='$t("settings.export_theme")' :export-label="$t(&quot;settings.export_theme&quot;)"
:importLabel='$t("settings.import_theme")' :import-label="$t(&quot;settings.import_theme&quot;)"
:importFailedText='$t("settings.invalid_theme_imported")' :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
:onImport='onImport' :on-import="onImport"
:validator='importValidator'> :validator="importValidator"
<template slot="before"> >
<div class="presets"> <template slot="before">
{{$t('settings.presets')}} <div class="presets">
<label for="preset-switcher" class='select'> {{ $t('settings.presets') }}
<select id="preset-switcher" v-model="selected" class="preset-switcher"> <label
<option v-for="style in availableStyles" for="preset-switcher"
:value="style" class="select"
:style="{ >
backgroundColor: style[1] || style.theme.colors.bg, <select
color: style[3] || style.theme.colors.text id="preset-switcher"
}"> v-model="selected"
{{style[0] || style.name}} class="preset-switcher"
</option> >
</select> <option
<i class="icon-down-open"/> v-for="style in availableStyles"
</label> :key="style.name"
</div> :value="style"
</template> :style="{
</export-import> backgroundColor: style[1] || style.theme.colors.bg,
color: style[3] || style.theme.colors.text
}"
>
{{ style[0] || style.name }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</template>
</export-import>
</div>
<div class="save-load-options">
<span class="keep-option">
<input
id="keep-color"
v-model="keepColor"
type="checkbox"
>
<label for="keep-color">{{ $t('settings.style.switcher.keep_color') }}</label>
</span>
<span class="keep-option">
<input
id="keep-shadows"
v-model="keepShadows"
type="checkbox"
>
<label for="keep-shadows">{{ $t('settings.style.switcher.keep_shadows') }}</label>
</span>
<span class="keep-option">
<input
id="keep-opacity"
v-model="keepOpacity"
type="checkbox"
>
<label for="keep-opacity">{{ $t('settings.style.switcher.keep_opacity') }}</label>
</span>
<span class="keep-option">
<input
id="keep-roundness"
v-model="keepRoundness"
type="checkbox"
>
<label for="keep-roundness">{{ $t('settings.style.switcher.keep_roundness') }}</label>
</span>
<span class="keep-option">
<input
id="keep-fonts"
v-model="keepFonts"
type="checkbox"
>
<label for="keep-fonts">{{ $t('settings.style.switcher.keep_fonts') }}</label>
</span>
<p>{{ $t('settings.style.switcher.save_load_hint') }}</p>
</div>
</div> </div>
<div class="save-load-options">
<span class="keep-option"> <div class="preview-container">
<input <preview :style="previewRules" />
id="keep-color" </div>
type="checkbox"
v-model="keepColor"> <keep-alive>
<label for="keep-color">{{$t('settings.style.switcher.keep_color')}}</label> <tab-switcher key="style-tweak">
</span> <div
<span class="keep-option"> :label="$t('settings.style.common_colors._tab_label')"
<input class="color-container"
id="keep-shadows" >
type="checkbox" <div class="tab-header">
v-model="keepShadows"> <p>{{ $t('settings.theme_help') }}</p>
<label for="keep-shadows">{{$t('settings.style.switcher.keep_shadows')}}</label> <button
</span> class="btn"
<span class="keep-option"> @click="clearOpacity"
<input >
id="keep-opacity" {{ $t('settings.style.switcher.clear_opacity') }}
type="checkbox" </button>
v-model="keepOpacity"> <button
<label for="keep-opacity">{{$t('settings.style.switcher.keep_opacity')}}</label> class="btn"
</span> @click="clearV1"
<span class="keep-option"> >
<input {{ $t('settings.style.switcher.clear_all') }}
id="keep-roundness" </button>
type="checkbox" </div>
v-model="keepRoundness"> <p>{{ $t('settings.theme_help_v2_1') }}</p>
<label for="keep-roundness">{{$t('settings.style.switcher.keep_roundness')}}</label> <h4>{{ $t('settings.style.common_colors.main') }}</h4>
</span> <div class="color-item">
<span class="keep-option"> <ColorInput
<input v-model="bgColorLocal"
id="keep-fonts" name="bgColor"
type="checkbox" :label="$t('settings.background')"
v-model="keepFonts"> />
<label for="keep-fonts">{{$t('settings.style.switcher.keep_fonts')}}</label> <OpacityInput
</span> v-model="bgOpacityLocal"
<p>{{$t('settings.style.switcher.save_load_hint')}}</p> name="bgOpacity"
:fallback="previewTheme.opacity.bg || 1"
/>
<ColorInput
v-model="textColorLocal"
name="textColor"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.bgText" />
<ColorInput
v-model="linkColorLocal"
name="linkColor"
:label="$t('settings.links')"
/>
<ContrastRatio :contrast="previewContrast.bgLink" />
</div>
<div class="color-item">
<ColorInput
v-model="fgColorLocal"
name="fgColor"
:label="$t('settings.foreground')"
/>
<ColorInput
v-model="fgTextColorLocal"
name="fgTextColor"
:label="$t('settings.text')"
:fallback="previewTheme.colors.fgText"
/>
<ColorInput
v-model="fgLinkColorLocal"
name="fgLinkColor"
:label="$t('settings.links')"
:fallback="previewTheme.colors.fgLink"
/>
<p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
</div>
<h4>{{ $t('settings.style.common_colors.rgbo') }}</h4>
<div class="color-item">
<ColorInput
v-model="cRedColorLocal"
name="cRedColor"
:label="$t('settings.cRed')"
/>
<ContrastRatio :contrast="previewContrast.bgRed" />
<ColorInput
v-model="cBlueColorLocal"
name="cBlueColor"
:label="$t('settings.cBlue')"
/>
<ContrastRatio :contrast="previewContrast.bgBlue" />
</div>
<div class="color-item">
<ColorInput
v-model="cGreenColorLocal"
name="cGreenColor"
:label="$t('settings.cGreen')"
/>
<ContrastRatio :contrast="previewContrast.bgGreen" />
<ColorInput
v-model="cOrangeColorLocal"
name="cOrangeColor"
:label="$t('settings.cOrange')"
/>
<ContrastRatio :contrast="previewContrast.bgOrange" />
</div>
<p>{{ $t('settings.theme_help_v2_2') }}</p>
</div>
<div
:label="$t('settings.style.advanced_colors._tab_label')"
class="color-container"
>
<div class="tab-header">
<p>{{ $t('settings.theme_help') }}</p>
<button
class="btn"
@click="clearOpacity"
>
{{ $t('settings.style.switcher.clear_opacity') }}
</button>
<button
class="btn"
@click="clearV1"
>
{{ $t('settings.style.switcher.clear_all') }}
</button>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
<ColorInput
v-model="alertErrorColorLocal"
name="alertError"
:label="$t('settings.style.advanced_colors.alert_error')"
:fallback="previewTheme.colors.alertError"
/>
<ContrastRatio :contrast="previewContrast.alertError" />
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
<ColorInput
v-model="badgeNotificationColorLocal"
name="badgeNotification"
:label="$t('settings.style.advanced_colors.badge_notification')"
:fallback="previewTheme.colors.badgeNotification"
/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
<ColorInput
v-model="panelColorLocal"
name="panelColor"
:fallback="fgColorLocal"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="panelOpacityLocal"
name="panelOpacity"
:fallback="previewTheme.opacity.panel || 1"
/>
<ColorInput
v-model="panelTextColorLocal"
name="panelTextColor"
:fallback="previewTheme.colors.panelText"
:label="$t('settings.text')"
/>
<ContrastRatio
:contrast="previewContrast.panelText"
large="1"
/>
<ColorInput
v-model="panelLinkColorLocal"
name="panelLinkColor"
:fallback="previewTheme.colors.panelLink"
:label="$t('settings.links')"
/>
<ContrastRatio
:contrast="previewContrast.panelLink"
large="1"
/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.top_bar') }}</h4>
<ColorInput
v-model="topBarColorLocal"
name="topBarColor"
:fallback="fgColorLocal"
:label="$t('settings.background')"
/>
<ColorInput
v-model="topBarTextColorLocal"
name="topBarTextColor"
:fallback="previewTheme.colors.topBarText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.topBarText" />
<ColorInput
v-model="topBarLinkColorLocal"
name="topBarLinkColor"
:fallback="previewTheme.colors.topBarLink"
:label="$t('settings.links')"
/>
<ContrastRatio :contrast="previewContrast.topBarLink" />
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.inputs') }}</h4>
<ColorInput
v-model="inputColorLocal"
name="inputColor"
:fallback="fgColorLocal"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="inputOpacityLocal"
name="inputOpacity"
:fallback="previewTheme.opacity.input || 1"
/>
<ColorInput
v-model="inputTextColorLocal"
name="inputTextColor"
:fallback="previewTheme.colors.inputText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.inputText" />
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.buttons') }}</h4>
<ColorInput
v-model="btnColorLocal"
name="btnColor"
:fallback="fgColorLocal"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="btnOpacityLocal"
name="btnOpacity"
:fallback="previewTheme.opacity.btn || 1"
/>
<ColorInput
v-model="btnTextColorLocal"
name="btnTextColor"
:fallback="previewTheme.colors.btnText"
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.btnText" />
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
<ColorInput
v-model="borderColorLocal"
name="borderColor"
:fallback="previewTheme.colors.border"
:label="$t('settings.style.common.color')"
/>
<OpacityInput
v-model="borderOpacityLocal"
name="borderOpacity"
:fallback="previewTheme.opacity.border || 1"
/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.faint_text') }}</h4>
<ColorInput
v-model="faintColorLocal"
name="faintColor"
:fallback="previewTheme.colors.faint || 1"
:label="$t('settings.text')"
/>
<ColorInput
v-model="faintLinkColorLocal"
name="faintLinkColor"
:fallback="previewTheme.colors.faintLink"
:label="$t('settings.links')"
/>
<ColorInput
v-model="panelFaintColorLocal"
name="panelFaintColor"
:fallback="previewTheme.colors.panelFaint"
:label="$t('settings.style.advanced_colors.panel_header')"
/>
<OpacityInput
v-model="faintOpacityLocal"
name="faintOpacity"
:fallback="previewTheme.opacity.faint || 0.5"
/>
</div>
</div>
<div
:label="$t('settings.style.radii._tab_label')"
class="radius-container"
>
<div class="tab-header">
<p>{{ $t('settings.radii_help') }}</p>
<button
class="btn"
@click="clearRoundness"
>
{{ $t('settings.style.switcher.clear_all') }}
</button>
</div>
<RangeInput
v-model="btnRadiusLocal"
name="btnRadius"
:label="$t('settings.btnRadius')"
:fallback="previewTheme.radii.btn"
max="16"
hard-min="0"
/>
<RangeInput
v-model="inputRadiusLocal"
name="inputRadius"
:label="$t('settings.inputRadius')"
:fallback="previewTheme.radii.input"
max="9"
hard-min="0"
/>
<RangeInput
v-model="checkboxRadiusLocal"
name="checkboxRadius"
:label="$t('settings.checkboxRadius')"
:fallback="previewTheme.radii.checkbox"
max="16"
hard-min="0"
/>
<RangeInput
v-model="panelRadiusLocal"
name="panelRadius"
:label="$t('settings.panelRadius')"
:fallback="previewTheme.radii.panel"
max="50"
hard-min="0"
/>
<RangeInput
v-model="avatarRadiusLocal"
name="avatarRadius"
:label="$t('settings.avatarRadius')"
:fallback="previewTheme.radii.avatar"
max="28"
hard-min="0"
/>
<RangeInput
v-model="avatarAltRadiusLocal"
name="avatarAltRadius"
:label="$t('settings.avatarAltRadius')"
:fallback="previewTheme.radii.avatarAlt"
max="28"
hard-min="0"
/>
<RangeInput
v-model="attachmentRadiusLocal"
name="attachmentRadius"
:label="$t('settings.attachmentRadius')"
:fallback="previewTheme.radii.attachment"
max="50"
hard-min="0"
/>
<RangeInput
v-model="tooltipRadiusLocal"
name="tooltipRadius"
:label="$t('settings.tooltipRadius')"
:fallback="previewTheme.radii.tooltip"
max="50"
hard-min="0"
/>
</div>
<div
:label="$t('settings.style.shadows._tab_label')"
class="shadow-container"
>
<div class="tab-header shadow-selector">
<div class="select-container">
{{ $t('settings.style.shadows.component') }}
<label
for="shadow-switcher"
class="select"
>
<select
id="shadow-switcher"
v-model="shadowSelected"
class="shadow-switcher"
>
<option
v-for="shadow in shadowsAvailable"
:key="shadow"
:value="shadow"
>
{{ $t('settings.style.shadows.components.' + shadow) }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
<div class="override">
<label
for="override"
class="label"
>
{{ $t('settings.style.shadows.override') }}
</label>
<input
id="override"
v-model="currentShadowOverriden"
name="override"
class="input-override"
type="checkbox"
>
<label
class="checkbox-label"
for="override"
/>
</div>
<button
class="btn"
@click="clearShadows"
>
{{ $t('settings.style.switcher.clear_all') }}
</button>
</div>
<shadow-control
v-model="currentShadow"
:ready="!!currentShadowFallback"
:fallback="currentShadowFallback"
/>
<div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
<i18n
path="settings.style.shadows.filter_hint.always_drop_shadow"
tag="p"
>
<code>filter: drop-shadow()</code>
</i18n>
<p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
<i18n
path="settings.style.shadows.filter_hint.drop_shadow_syntax"
tag="p"
>
<code>drop-shadow</code>
<code>spread-radius</code>
<code>inset</code>
</i18n>
<i18n
path="settings.style.shadows.filter_hint.inset_classic"
tag="p"
>
<code>box-shadow</code>
</i18n>
<p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
</div>
</div>
<div
:label="$t('settings.style.fonts._tab_label')"
class="fonts-container"
>
<div class="tab-header">
<p>{{ $t('settings.style.fonts.help') }}</p>
<button
class="btn"
@click="clearFonts"
>
{{ $t('settings.style.switcher.clear_all') }}
</button>
</div>
<FontControl
v-model="fontsLocal.interface"
name="ui"
:label="$t('settings.style.fonts.components.interface')"
:fallback="previewTheme.fonts.interface"
no-inherit="1"
/>
<FontControl
v-model="fontsLocal.input"
name="input"
:label="$t('settings.style.fonts.components.input')"
:fallback="previewTheme.fonts.input"
/>
<FontControl
v-model="fontsLocal.post"
name="post"
:label="$t('settings.style.fonts.components.post')"
:fallback="previewTheme.fonts.post"
/>
<FontControl
v-model="fontsLocal.postCode"
name="postCode"
:label="$t('settings.style.fonts.components.postCode')"
:fallback="previewTheme.fonts.postCode"
/>
</div>
</tab-switcher>
</keep-alive>
<div class="apply-container">
<button
class="btn submit"
:disabled="!themeValid"
@click="setCustomTheme"
>
{{ $t('general.apply') }}
</button>
<button
class="btn"
@click="clearAll"
>
{{ $t('settings.style.switcher.reset') }}
</button>
</div> </div>
</div> </div>
<div class="preview-container">
<preview :style="previewRules"/>
</div>
<keep-alive>
<tab-switcher key="style-tweak">
<div :label="$t('settings.style.common_colors._tab_label')" class="color-container">
<div class="tab-header">
<p>{{$t('settings.theme_help')}}</p>
<button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
<button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<p>{{$t('settings.theme_help_v2_1')}}</p>
<h4>{{ $t('settings.style.common_colors.main') }}</h4>
<div class="color-item">
<ColorInput name="bgColor" v-model="bgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="bgOpacity" v-model="bgOpacityLocal" :fallback="previewTheme.opacity.bg || 1"/>
<ColorInput name="textColor" v-model="textColorLocal" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.bgText"/>
<ColorInput name="linkColor" v-model="linkColorLocal" :label="$t('settings.links')"/>
<ContrastRatio :contrast="previewContrast.bgLink"/>
</div>
<div class="color-item">
<ColorInput name="fgColor" v-model="fgColorLocal" :label="$t('settings.foreground')"/>
<ColorInput name="fgTextColor" v-model="fgTextColorLocal" :label="$t('settings.text')" :fallback="previewTheme.colors.fgText"/>
<ColorInput name="fgLinkColor" v-model="fgLinkColorLocal" :label="$t('settings.links')" :fallback="previewTheme.colors.fgLink"/>
<p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
</div>
<h4>{{ $t('settings.style.common_colors.rgbo') }}</h4>
<div class="color-item">
<ColorInput name="cRedColor" v-model="cRedColorLocal" :label="$t('settings.cRed')"/>
<ContrastRatio :contrast="previewContrast.bgRed"/>
<ColorInput name="cBlueColor" v-model="cBlueColorLocal" :label="$t('settings.cBlue')"/>
<ContrastRatio :contrast="previewContrast.bgBlue"/>
</div>
<div class="color-item">
<ColorInput name="cGreenColor" v-model="cGreenColorLocal" :label="$t('settings.cGreen')"/>
<ContrastRatio :contrast="previewContrast.bgGreen"/>
<ColorInput name="cOrangeColor" v-model="cOrangeColorLocal" :label="$t('settings.cOrange')"/>
<ContrastRatio :contrast="previewContrast.bgOrange"/>
</div>
<p>{{$t('settings.theme_help_v2_2')}}</p>
</div>
<div :label="$t('settings.style.advanced_colors._tab_label')" class="color-container">
<div class="tab-header">
<p>{{$t('settings.theme_help')}}</p>
<button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
<button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
<ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.style.advanced_colors.alert_error')" :fallback="previewTheme.colors.alertError"/>
<ContrastRatio :contrast="previewContrast.alertError"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
<ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.style.advanced_colors.badge_notification')" :fallback="previewTheme.colors.badgeNotification"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
<ColorInput name="panelColor" v-model="panelColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="panelOpacity" v-model="panelOpacityLocal" :fallback="previewTheme.opacity.panel || 1"/>
<ColorInput name="panelTextColor" v-model="panelTextColorLocal" :fallback="previewTheme.colors.panelText" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.panelText" large="1"/>
<ColorInput name="panelLinkColor" v-model="panelLinkColorLocal" :fallback="previewTheme.colors.panelLink" :label="$t('settings.links')"/>
<ContrastRatio :contrast="previewContrast.panelLink" large="1"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.top_bar') }}</h4>
<ColorInput name="topBarColor" v-model="topBarColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<ColorInput name="topBarTextColor" v-model="topBarTextColorLocal" :fallback="previewTheme.colors.topBarText" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.topBarText"/>
<ColorInput name="topBarLinkColor" v-model="topBarLinkColorLocal" :fallback="previewTheme.colors.topBarLink" :label="$t('settings.links')"/>
<ContrastRatio :contrast="previewContrast.topBarLink"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.inputs') }}</h4>
<ColorInput name="inputColor" v-model="inputColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="inputOpacity" v-model="inputOpacityLocal" :fallback="previewTheme.opacity.input || 1"/>
<ColorInput name="inputTextColor" v-model="inputTextColorLocal" :fallback="previewTheme.colors.inputText" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.inputText"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.buttons') }}</h4>
<ColorInput name="btnColor" v-model="btnColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
<OpacityInput name="btnOpacity" v-model="btnOpacityLocal" :fallback="previewTheme.opacity.btn || 1"/>
<ColorInput name="btnTextColor" v-model="btnTextColorLocal" :fallback="previewTheme.colors.btnText" :label="$t('settings.text')"/>
<ContrastRatio :contrast="previewContrast.btnText"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
<ColorInput name="borderColor" v-model="borderColorLocal" :fallback="previewTheme.colors.border" :label="$t('settings.style.common.color')"/>
<OpacityInput name="borderOpacity" v-model="borderOpacityLocal" :fallback="previewTheme.opacity.border || 1"/>
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.faint_text') }}</h4>
<ColorInput name="faintColor" v-model="faintColorLocal" :fallback="previewTheme.colors.faint || 1" :label="$t('settings.text')"/>
<ColorInput name="faintLinkColor" v-model="faintLinkColorLocal" :fallback="previewTheme.colors.faintLink" :label="$t('settings.links')"/>
<ColorInput name="panelFaintColor" v-model="panelFaintColorLocal" :fallback="previewTheme.colors.panelFaint" :label="$t('settings.style.advanced_colors.panel_header')"/>
<OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
</div>
</div>
<div :label="$t('settings.style.radii._tab_label')" class="radius-container">
<div class="tab-header">
<p>{{$t('settings.radii_help')}}</p>
<button class="btn" @click="clearRoundness">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<RangeInput name="btnRadius" :label="$t('settings.btnRadius')" v-model="btnRadiusLocal" :fallback="previewTheme.radii.btn" max="16" hardMin="0"/>
<RangeInput name="inputRadius" :label="$t('settings.inputRadius')" v-model="inputRadiusLocal" :fallback="previewTheme.radii.input" max="9" hardMin="0"/>
<RangeInput name="checkboxRadius" :label="$t('settings.checkboxRadius')" v-model="checkboxRadiusLocal" :fallback="previewTheme.radii.checkbox" max="16" hardMin="0"/>
<RangeInput name="panelRadius" :label="$t('settings.panelRadius')" v-model="panelRadiusLocal" :fallback="previewTheme.radii.panel" max="50" hardMin="0"/>
<RangeInput name="avatarRadius" :label="$t('settings.avatarRadius')" v-model="avatarRadiusLocal" :fallback="previewTheme.radii.avatar" max="28" hardMin="0"/>
<RangeInput name="avatarAltRadius" :label="$t('settings.avatarAltRadius')" v-model="avatarAltRadiusLocal" :fallback="previewTheme.radii.avatarAlt" max="28" hardMin="0"/>
<RangeInput name="attachmentRadius" :label="$t('settings.attachmentRadius')" v-model="attachmentRadiusLocal" :fallback="previewTheme.radii.attachment" max="50" hardMin="0"/>
<RangeInput name="tooltipRadius" :label="$t('settings.tooltipRadius')" v-model="tooltipRadiusLocal" :fallback="previewTheme.radii.tooltip" max="50" hardMin="0"/>
</div>
<div :label="$t('settings.style.shadows._tab_label')" class="shadow-container">
<div class="tab-header shadow-selector">
<div class="select-container">
{{$t('settings.style.shadows.component')}}
<label for="shadow-switcher" class="select">
<select id="shadow-switcher" v-model="shadowSelected" class="shadow-switcher">
<option v-for="shadow in shadowsAvailable"
:value="shadow">
{{$t('settings.style.shadows.components.' + shadow)}}
</option>
</select>
<i class="icon-down-open"/>
</label>
</div>
<div class="override">
<label for="override" class="label">
{{$t('settings.style.shadows.override')}}
</label>
<input
v-model="currentShadowOverriden"
name="override"
id="override"
class="input-override"
type="checkbox">
<label class="checkbox-label" for="override"></label>
</div>
<button class="btn" @click="clearShadows">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<shadow-control :ready="!!currentShadowFallback" :fallback="currentShadowFallback" v-model="currentShadow"/>
<div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
<i18n path="settings.style.shadows.filter_hint.always_drop_shadow" tag="p">
<code>filter: drop-shadow()</code>
</i18n>
<p>{{$t('settings.style.shadows.filter_hint.avatar_inset')}}</p>
<i18n path="settings.style.shadows.filter_hint.drop_shadow_syntax" tag="p">
<code>drop-shadow</code>
<code>spread-radius</code>
<code>inset</code>
</i18n>
<i18n path="settings.style.shadows.filter_hint.inset_classic" tag="p">
<code>box-shadow</code>
</i18n>
<p>{{$t('settings.style.shadows.filter_hint.spread_zero')}}</p>
</div>
</div>
<div :label="$t('settings.style.fonts._tab_label')" class="fonts-container">
<div class="tab-header">
<p>{{$t('settings.style.fonts.help')}}</p>
<button class="btn" @click="clearFonts">{{$t('settings.style.switcher.clear_all')}}</button>
</div>
<FontControl
name="ui"
v-model="fontsLocal.interface"
:label="$t('settings.style.fonts.components.interface')"
:fallback="previewTheme.fonts.interface"
no-inherit="1"/>
<FontControl
name="input"
v-model="fontsLocal.input"
:label="$t('settings.style.fonts.components.input')"
:fallback="previewTheme.fonts.input"/>
<FontControl
name="post"
v-model="fontsLocal.post"
:label="$t('settings.style.fonts.components.post')"
:fallback="previewTheme.fonts.post"/>
<FontControl
name="postCode"
v-model="fontsLocal.postCode"
:label="$t('settings.style.fonts.components.postCode')"
:fallback="previewTheme.fonts.postCode"/>
</div>
</tab-switcher>
</keep-alive>
<div class="apply-container">
<button class="btn submit" :disabled="!themeValid" @click="setCustomTheme">{{$t('general.apply')}}</button>
<button class="btn" @click="clearAll">{{$t('settings.style.switcher.reset')}}</button>
</div>
</div>
</template> </template>
<script src="./style_switcher.js"></script> <script src="./style_switcher.js"></script>

View file

@ -10,6 +10,12 @@ export default Vue.component('tab-switcher', {
active: this.$slots.default.findIndex(_ => _.tag) active: this.$slots.default.findIndex(_ => _.tag)
} }
}, },
beforeUpdate () {
const currentSlot = this.$slots.default[this.active]
if (!currentSlot.tag) {
this.active = this.$slots.default.findIndex(_ => _.tag)
}
},
methods: { methods: {
activateTab (index, dataset) { activateTab (index, dataset) {
return () => { return () => {
@ -20,34 +26,28 @@ export default Vue.component('tab-switcher', {
} }
} }
}, },
beforeUpdate () {
const currentSlot = this.$slots.default[this.active]
if (!currentSlot.tag) {
this.active = this.$slots.default.findIndex(_ => _.tag)
}
},
render (h) { render (h) {
const tabs = this.$slots.default const tabs = this.$slots.default
.map((slot, index) => { .map((slot, index) => {
if (!slot.tag) return if (!slot.tag) return
const classesTab = ['tab'] const classesTab = ['tab']
const classesWrapper = ['tab-wrapper'] const classesWrapper = ['tab-wrapper']
if (index === this.active) { if (index === this.active) {
classesTab.push('active') classesTab.push('active')
classesWrapper.push('active') classesWrapper.push('active')
} }
return ( return (
<div class={ classesWrapper.join(' ')}> <div class={ classesWrapper.join(' ')}>
<button <button
disabled={slot.data.attrs.disabled} disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)} onClick={this.activateTab(index)}
class={classesTab.join(' ')}> class={classesTab.join(' ')}>
{slot.data.attrs.label}</button> {slot.data.attrs.label}</button>
</div> </div>
) )
}) })
const contents = this.$slots.default.map((slot, index) => { const contents = this.$slots.default.map((slot, index) => {
if (!slot.tag) return if (!slot.tag) return

View file

@ -1,5 +1,10 @@
<template> <template>
<Timeline :title="tag" :timeline="timeline" :timeline-name="'tag'" :tag="tag" /> <Timeline
:title="tag"
:timeline="timeline"
:timeline-name="'tag'"
:tag="tag"
/>
</template> </template>
<script src='./tag_timeline.js'></script> <script src='./tag_timeline.js'></script>

View file

@ -2,8 +2,12 @@
<div> <div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-body"> <div class="panel-body">
<div v-html="content" class="tos-content"> <!-- eslint-disable vue/no-v-html -->
</div> <div
class="tos-content"
v-html="content"
/>
<!-- eslint-enable vue/no-v-html -->
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,8 @@
<template> <template>
<time :datetime="time" :title="localeDateString"> <time
:datetime="time"
:title="localeDateString"
>
{{ $t(relativeTime.key, [relativeTime.num]) }} {{ $t(relativeTime.key, [relativeTime.num]) }}
</time> </time>
</template> </template>
@ -16,12 +19,6 @@ export default {
interval: null interval: null
} }
}, },
created () {
this.refreshRelativeTimeObject()
},
destroyed () {
clearTimeout(this.interval)
},
computed: { computed: {
localeDateString () { localeDateString () {
return typeof this.time === 'string' return typeof this.time === 'string'
@ -29,6 +26,12 @@ export default {
: this.time.toLocaleString() : this.time.toLocaleString()
} }
}, },
created () {
this.refreshRelativeTimeObject()
},
destroyed () {
clearTimeout(this.interval)
},
methods: { methods: {
refreshRelativeTimeObject () { refreshRelativeTimeObject () {
const nowThreshold = typeof this.nowThreshold === 'number' ? this.nowThreshold : 1 const nowThreshold = typeof this.nowThreshold === 'number' ? this.nowThreshold : 1
@ -45,4 +48,4 @@ export default {
} }
} }
} }
</script> </script>

View file

@ -86,7 +86,7 @@ const Timeline = {
if (this.newStatusCount === 0) return if (this.newStatusCount === 0) return
if (this.timeline.flushMarker !== 0) { if (this.timeline.flushMarker !== 0) {
this.$store.commit('clearTimeline', { timeline: this.timelineName }) this.$store.commit('clearTimeline', { timeline: this.timelineName, excludeUserId: true })
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 }) this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
this.fetchOlderStatuses() this.fetchOlderStatuses()
} else { } else {
@ -139,7 +139,7 @@ const Timeline = {
if (top < 15 && if (top < 15 &&
!this.paused && !this.paused &&
!(this.unfocused && this.$store.state.config.pauseOnUnfocused) !(this.unfocused && this.$store.state.config.pauseOnUnfocused)
) { ) {
this.showNewStatuses() this.showNewStatuses()
} else { } else {
this.paused = true this.paused = true

View file

@ -2,41 +2,66 @@
<div :class="classes.root"> <div :class="classes.root">
<div :class="classes.header"> <div :class="classes.header">
<div class="title"> <div class="title">
{{title}} {{ title }}
</div> </div>
<div @click.prevent class="loadmore-error alert error" v-if="timelineError"> <div
{{$t('timeline.error_fetching')}} v-if="timelineError"
class="loadmore-error alert error"
@click.prevent
>
{{ $t('timeline.error_fetching') }}
</div> </div>
<button @click.prevent="showNewStatuses" class="loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError"> <button
{{$t('timeline.show_new')}}{{newStatusCountStr}} v-if="timeline.newStatusCount > 0 && !timelineError"
class="loadmore-button"
@click.prevent="showNewStatuses"
>
{{ $t('timeline.show_new') }}{{ newStatusCountStr }}
</button> </button>
<div @click.prevent class="loadmore-text faint" v-if="!timeline.newStatusCount > 0 && !timelineError"> <div
{{$t('timeline.up_to_date')}} v-if="!timeline.newStatusCount > 0 && !timelineError"
class="loadmore-text faint"
@click.prevent
>
{{ $t('timeline.up_to_date') }}
</div> </div>
</div> </div>
<div :class="classes.body"> <div :class="classes.body">
<div class="timeline"> <div class="timeline">
<conversation <conversation
v-for="status in timeline.visibleStatuses" v-for="status in timeline.visibleStatuses"
class="status-fadein"
:key="status.id" :key="status.id"
class="status-fadein"
:statusoid="status" :statusoid="status"
:collapsable="true" :collapsable="true"
/> />
</div> </div>
</div> </div>
<div :class="classes.footer"> <div :class="classes.footer">
<div v-if="count===0" class="new-status-notification text-center panel-footer faint"> <div
{{$t('timeline.no_statuses')}} v-if="count===0"
class="new-status-notification text-center panel-footer faint"
>
{{ $t('timeline.no_statuses') }}
</div> </div>
<div v-else-if="bottomedOut" class="new-status-notification text-center panel-footer faint"> <div
{{$t('timeline.no_more_statuses')}} v-else-if="bottomedOut"
class="new-status-notification text-center panel-footer faint"
>
{{ $t('timeline.no_more_statuses') }}
</div> </div>
<a v-else-if="!timeline.loading" href="#" v-on:click.prevent='fetchOlderStatuses()'> <a
<div class="new-status-notification text-center panel-footer">{{$t('timeline.load_older')}}</div> v-else-if="!timeline.loading"
href="#"
@click.prevent="fetchOlderStatuses()"
>
<div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
</a> </a>
<div v-else class="new-status-notification text-center panel-footer"> <div
<i class="icon-spin3 animate-spin"/> v-else
class="new-status-notification text-center panel-footer"
>
<i class="icon-spin3 animate-spin" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,7 +5,7 @@
:title="user.screen_name" :title="user.screen_name"
:src="user.profile_image_url_original" :src="user.profile_image_url_original"
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }" :class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
:imageLoadError="imageLoadError" :image-load-error="imageLoadError"
/> />
</template> </template>

View file

@ -23,15 +23,15 @@ export default {
computed: { computed: {
classes () { classes () {
return [{ return [{
'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius 'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
'user-card-rounded': this.rounded === true, // set border-radius for all sides 'user-card-rounded': this.rounded === true, // set border-radius for all sides
'user-card-bordered': this.bordered === true // set border for all sides 'user-card-bordered': this.bordered === true // set border for all sides
}] }]
}, },
style () { style () {
const color = this.$store.state.config.customTheme.colors const color = this.$store.state.config.customTheme.colors
? this.$store.state.config.customTheme.colors.bg // v2 ? this.$store.state.config.customTheme.colors.bg // v2
: this.$store.state.config.colors.bg // v1 : this.$store.state.config.colors.bg // v1
if (color) { if (color) {
const rgb = (typeof color === 'string') ? hex2rgb(color) : color const rgb = (typeof color === 'string') ? hex2rgb(color) : color
@ -73,12 +73,12 @@ export default {
userHighlightType: { userHighlightType: {
get () { get () {
const data = this.$store.state.config.highlight[this.user.screen_name] const data = this.$store.state.config.highlight[this.user.screen_name]
return data && data.type || 'disabled' return (data && data.type) || 'disabled'
}, },
set (type) { set (type) {
const data = this.$store.state.config.highlight[this.user.screen_name] const data = this.$store.state.config.highlight[this.user.screen_name]
if (type !== 'disabled') { if (type !== 'disabled') {
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: data && data.color || '#FFFFFF', type }) this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: (data && data.color) || '#FFFFFF', type })
} else { } else {
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: undefined }) this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: undefined })
} }
@ -110,7 +110,7 @@ export default {
followUser () { followUser () {
const store = this.$store const store = this.$store
this.followRequestInProgress = true this.followRequestInProgress = true
requestFollow(this.user, store).then(({sent}) => { requestFollow(this.user, store).then(({ sent }) => {
this.followRequestInProgress = false this.followRequestInProgress = false
this.followRequestSent = sent this.followRequestSent = sent
}) })
@ -141,7 +141,7 @@ export default {
store.commit('setProfileView', { v }) store.commit('setProfileView', { v })
} }
}, },
linkClicked ({target}) { linkClicked ({ target }) {
if (target.tagName === 'SPAN') { if (target.tagName === 'SPAN') {
target = target.parentNode target = target.parentNode
} }

View file

@ -1,134 +1,260 @@
<template> <template>
<div class="user-card" :class="classes" :style="style"> <div
<div class="panel-heading"> class="user-card"
<div class='user-info'> :class="classes"
<div class='container'> :style="style"
<router-link :to="userProfileLink(user)"> >
<UserAvatar :betterShadow="betterShadow" :user="user"/> <div class="panel-heading">
</router-link> <div class="user-info">
<div class="user-summary"> <div class="container">
<div class="top-line"> <router-link :to="userProfileLink(user)">
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div> <UserAvatar
<div :title="user.name" class='user-name' v-else>{{user.name}}</div> :better-shadow="betterShadow"
<router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser"> :user="user"
<i class="button-icon icon-wrench usersettings" :title="$t('tool_tip.user_settings')"></i> />
</router-link> </router-link>
<a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local"> <div class="user-summary">
<i class="icon-link-ext usersettings"></i> <div class="top-line">
</a> <!-- eslint-disable vue/no-v-html -->
</div> <div
v-if="user.name_html"
:title="user.name"
class="user-name"
v-html="user.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<div
v-else
:title="user.name"
class="user-name"
>
{{ user.name }}
</div>
<router-link
v-if="!isOtherUser"
:to="{ name: 'user-settings' }"
>
<i
class="button-icon icon-wrench usersettings"
:title="$t('tool_tip.user_settings')"
/>
</router-link>
<a
v-if="isOtherUser && !user.is_local"
:href="user.statusnet_profile_url"
target="_blank"
>
<i class="icon-link-ext usersettings" />
</a>
</div>
<div class="bottom-line"> <div class="bottom-line">
<router-link class="user-screen-name" :to="userProfileLink(user)">@{{user.screen_name}}</router-link> <router-link
<span class="alert staff" v-if="!hideBio && !!visibleRole">{{visibleRole}}</span> class="user-screen-name"
<span v-if="user.locked"><i class="icon icon-lock"></i></span> :to="userProfileLink(user)"
<span v-if="!hideUserStatsLocal && !hideBio" class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span> >
@{{ user.screen_name }}
</router-link>
<span
v-if="!hideBio && !!visibleRole"
class="alert staff"
>{{ visibleRole }}</span>
<span v-if="user.locked"><i class="icon icon-lock" /></span>
<span
v-if="!hideUserStatsLocal && !hideBio"
class="dailyAvg"
>{{ dailyAvg }} {{ $t('user_card.per_day') }}</span>
</div>
</div> </div>
</div> </div>
</div> <div class="user-meta">
<div class="user-meta"> <div
<div v-if="user.follows_you && loggedIn && isOtherUser" class="following"> v-if="user.follows_you && loggedIn && isOtherUser"
{{ $t('user_card.follows_you') }} class="following"
>
{{ $t('user_card.follows_you') }}
</div>
<div
v-if="isOtherUser && (loggedIn || !switcher)"
class="highlighter"
>
<!-- id's need to be unique, otherwise vue confuses which user-card checkbox belongs to -->
<input
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColorTx'+user.id"
v-model="userHighlightColor"
class="userHighlightText"
type="text"
>
<input
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColor'+user.id"
v-model="userHighlightColor"
class="userHighlightCl"
type="color"
>
<label
for="style-switcher"
class="userHighlightSel select"
>
<select
:id="'userHighlightSel'+user.id"
v-model="userHighlightType"
class="userHighlightSel"
>
<option value="disabled">No highlight</option>
<option value="solid">Solid bg</option>
<option value="striped">Striped bg</option>
<option value="side">Side stripe</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</div> </div>
<div class="highlighter" v-if="isOtherUser && (loggedIn || !switcher)"> <div
<!-- id's need to be unique, otherwise vue confuses which user-card checkbox belongs to --> v-if="isOtherUser"
<input class="userHighlightText" type="text" :id="'userHighlightColorTx'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/> class="user-interactions"
<input class="userHighlightCl" type="color" :id="'userHighlightColor'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/> >
<label for="style-switcher" class='userHighlightSel select'> <div
<select class="userHighlightSel" :id="'userHighlightSel'+user.id" v-model="userHighlightType"> v-if="loggedIn"
<option value="disabled">No highlight</option> class="follow"
<option value="solid">Solid bg</option> >
<option value="striped">Striped bg</option> <span v-if="user.following">
<option value="side">Side stripe</option> <!--Following them!-->
</select> <button
<i class="icon-down-open"/> class="pressed"
</label> :disabled="followRequestInProgress"
:title="$t('user_card.follow_unfollow')"
@click="unfollowUser"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else>
{{ $t('user_card.following') }}
</template>
</button>
</span>
<span v-if="!user.following">
<button
:disabled="followRequestInProgress"
:title="followRequestSent ? $t('user_card.follow_again') : ''"
@click="followUser"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="followRequestSent">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
</span>
</div>
<div
v-if="isOtherUser && loggedIn"
class="mute"
>
<span v-if="user.muted">
<button
class="pressed"
@click="unmuteUser"
>
{{ $t('user_card.muted') }}
</button>
</span>
<span v-if="!user.muted">
<button @click="muteUser">
{{ $t('user_card.mute') }}
</button>
</span>
</div>
<div v-if="!loggedIn && user.is_local">
<RemoteFollow :user="user" />
</div>
<div
v-if="isOtherUser && loggedIn"
class="block"
>
<span v-if="user.statusnet_blocking">
<button
class="pressed"
@click="unblockUser"
>
{{ $t('user_card.blocked') }}
</button>
</span>
<span v-if="!user.statusnet_blocking">
<button @click="blockUser">
{{ $t('user_card.block') }}
</button>
</span>
</div>
<div
v-if="isOtherUser && loggedIn"
class="block"
>
<span>
<button @click="reportUser">
{{ $t('user_card.report') }}
</button>
</span>
</div>
<ModerationTools
v-if="loggedIn.role === &quot;admin&quot;"
:user="user"
/>
</div> </div>
</div> </div>
<div v-if="isOtherUser" class="user-interactions">
<div class="follow" v-if="loggedIn">
<span v-if="user.following">
<!--Following them!-->
<button @click="unfollowUser" class="pressed" :disabled="followRequestInProgress" :title="$t('user_card.follow_unfollow')">
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else>
{{ $t('user_card.following') }}
</template>
</button>
</span>
<span v-if="!user.following">
<button @click="followUser" :disabled="followRequestInProgress" :title="followRequestSent ? $t('user_card.follow_again') : ''">
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="followRequestSent">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
</span>
</div>
<div class='mute' v-if='isOtherUser && loggedIn'>
<span v-if='user.muted'>
<button @click="unmuteUser" class="pressed">
{{ $t('user_card.muted') }}
</button>
</span>
<span v-if='!user.muted'>
<button @click="muteUser">
{{ $t('user_card.mute') }}
</button>
</span>
</div>
<div v-if='!loggedIn && user.is_local'>
<RemoteFollow :user="user" />
</div>
<div class='block' v-if='isOtherUser && loggedIn'>
<span v-if='user.statusnet_blocking'>
<button @click="unblockUser" class="pressed">
{{ $t('user_card.blocked') }}
</button>
</span>
<span v-if='!user.statusnet_blocking'>
<button @click="blockUser">
{{ $t('user_card.block') }}
</button>
</span>
</div>
<div class='block' v-if='isOtherUser && loggedIn'>
<span>
<button @click="reportUser">
{{ $t('user_card.report') }}
</button>
</span>
</div>
<ModerationTools :user='user' v-if='loggedIn.role === "admin"'/>
</div>
</div> </div>
</div> <div
<div class="panel-body" v-if="!hideBio"> v-if="!hideBio"
<div v-if="!hideUserStatsLocal && switcher" class="user-counts"> class="panel-body"
<div class="user-count" v-on:click.prevent="setProfileView('statuses')"> >
<h5>{{ $t('user_card.statuses') }}</h5> <div
<span>{{user.statuses_count}} <br></span> v-if="!hideUserStatsLocal && switcher"
</div> class="user-counts"
<div class="user-count" v-on:click.prevent="setProfileView('friends')"> >
<h5>{{ $t('user_card.followees') }}</h5> <div
<span>{{user.friends_count}}</span> class="user-count"
</div> @click.prevent="setProfileView('statuses')"
<div class="user-count" v-on:click.prevent="setProfileView('followers')"> >
<h5>{{ $t('user_card.followers') }}</h5> <h5>{{ $t('user_card.statuses') }}</h5>
<span>{{user.followers_count}}</span> <span>{{ user.statuses_count }} <br></span>
</div>
<div
class="user-count"
@click.prevent="setProfileView('friends')"
>
<h5>{{ $t('user_card.followees') }}</h5>
<span>{{ user.friends_count }}</span>
</div>
<div
class="user-count"
@click.prevent="setProfileView('followers')"
>
<h5>{{ $t('user_card.followers') }}</h5>
<span>{{ user.followers_count }}</span>
</div>
</div> </div>
<!-- eslint-disable vue/no-v-html -->
<p
v-if="!hideBio && user.description_html"
class="user-card-bio"
@click.prevent="linkClicked"
v-html="user.description_html"
/>
<!-- eslint-enable vue/no-v-html -->
<p
v-else-if="!hideBio"
class="user-card-bio"
>
{{ user.description }}
</p>
</div> </div>
<p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="user-card-bio" v-html="user.description_html"></p>
<p v-else-if="!hideBio" class="user-card-bio">{{ user.description }}</p>
</div> </div>
</div>
</template> </template>
<script src="./user_card.js"></script> <script src="./user_card.js"></script>

View file

@ -1,14 +1,38 @@
<template> <template>
<div> <div>
<div class="user-finder-container"> <div class="user-finder-container">
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" /> <i
<a href="#" v-if="hidden" :title="$t('finder.find_user')"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a> v-if="loading"
class="icon-spin4 user-finder-icon animate-spin-slow"
/>
<a
v-if="hidden"
href="#"
:title="$t('finder.find_user')"
><i
class="icon-user-plus user-finder-icon"
@click.prevent.stop="toggleHidden"
/></a>
<template v-else> <template v-else>
<input class="user-finder-input" ref="userSearchInput" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/> <input
<button class="btn search-button" @click="findUser(username)"> id="user-finder-input"
<i class="icon-search"/> ref="userSearchInput"
v-model="username"
class="user-finder-input"
:placeholder="$t('finder.find_user')"
type="text"
@keyup.enter="findUser(username)"
>
<button
class="btn search-button"
@click="findUser(username)"
>
<i class="icon-search" />
</button> </button>
<i class="button-icon icon-cancel user-finder-icon" @click.prevent.stop="toggleHidden"/> <i
class="button-icon icon-cancel user-finder-icon"
@click.prevent.stop="toggleHidden"
/>
</template> </template>
</div> </div>
</div> </div>
@ -25,7 +49,6 @@
align-items: baseline; align-items: baseline;
vertical-align: baseline; vertical-align: baseline;
.user-finder-input, .user-finder-input,
.search-button { .search-button {
height: 29px; height: 29px;

View file

@ -1,13 +1,23 @@
<template> <template>
<div class="user-panel"> <div class="user-panel">
<div
<div v-if="signedIn" key="user-panel" class="panel panel-default signed-in"> v-if="signedIn"
<UserCard :user="user" :hideBio="true" rounded="top"/> key="user-panel"
class="panel panel-default signed-in"
>
<UserCard
:user="user"
:hide-bio="true"
rounded="top"
/>
<div class="panel-footer"> <div class="panel-footer">
<post-status-form v-if='user'></post-status-form> <post-status-form v-if="user" />
</div> </div>
</div> </div>
<auth-form v-else key="user-panel"/> <auth-form
v-else
key="user-panel"
/>
</div> </div>
</template> </template>

View file

@ -31,6 +31,8 @@ const UserProfile = {
} }
}, },
created () { created () {
// Make sure that timelines used in this page are empty
this.cleanUp()
const routeParams = this.$route.params const routeParams = this.$route.params
this.load(routeParams.name || routeParams.id) this.load(routeParams.name || routeParams.id)
}, },

View file

@ -1,75 +1,110 @@
<template> <template>
<div> <div>
<div v-if="user" class="user-profile panel panel-default"> <div
<UserCard :user="user" :switcher="true" :selected="timeline.viewing" rounded="top"/> v-if="user"
<tab-switcher :renderOnlyFocused="true" ref="tabSwitcher"> class="user-profile panel panel-default"
<div :label="$t('user_card.statuses')"> >
<div class="timeline"> <UserCard
<template v-for="statusId in user.pinnedStatuseIds"> :user="user"
<Conversation :switcher="true"
v-if="timeline.statusesObject[statusId]" :selected="timeline.viewing"
class="status-fadein" rounded="top"
:key="statusId" />
:statusoid="timeline.statusesObject[statusId]" <tab-switcher
:collapsable="true" ref="tabSwitcher"
:showPinned="true" :render-only-focused="true"
/> >
</template> <div :label="$t('user_card.statuses')">
<div class="timeline">
<template v-for="statusId in user.pinnedStatuseIds">
<Conversation
v-if="timeline.statusesObject[statusId]"
:key="statusId"
class="status-fadein"
:statusoid="timeline.statusesObject[statusId]"
:collapsable="true"
:show-pinned="true"
/>
</template>
</div>
<Timeline
:count="user.statuses_count"
:embedded="true"
:title="$t('user_profile.timeline_title')"
:timeline="timeline"
:timeline-name="'user'"
:user-id="userId"
/>
</div>
<div
v-if="followsTabVisible"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
<FriendList :user-id="userId">
<template
slot="item"
slot-scope="{item}"
>
<FollowCard :user="item" />
</template>
</FriendList>
</div>
<div
v-if="followersTabVisible"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
<FollowerList :user-id="userId">
<template
slot="item"
slot-scope="{item}"
>
<FollowCard
:user="item"
:no-follows-you="isUs"
/>
</template>
</FollowerList>
</div> </div>
<Timeline <Timeline
:count="user.statuses_count" :label="$t('user_card.media')"
:disabled="!media.visibleStatuses.length"
:embedded="true" :embedded="true"
:title="$t('user_profile.timeline_title')" :title="$t('user_card.media')"
:timeline="timeline" timeline-name="media"
:timeline-name="'user'" :timeline="media"
:user-id="userId" :user-id="userId"
/> />
<Timeline
v-if="isUs"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
:embedded="true"
:title="$t('user_card.favorites')"
timeline-name="favorites"
:timeline="favorites"
/>
</tab-switcher>
</div>
<div
v-else
class="panel user-profile-placeholder"
>
<div class="panel-heading">
<div class="title">
{{ $t('settings.profile_tab') }}
</div>
</div> </div>
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count"> <div class="panel-body">
<FriendList :userId="userId"> <span v-if="error">{{ error }}</span>
<template slot="item" slot-scope="{item}"> <i
<FollowCard :user="item" /> v-else
</template> class="icon-spin3 animate-spin"
</FriendList> />
</div>
<div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
<FollowerList :userId="userId">
<template slot="item" slot-scope="{item}">
<FollowCard :user="item" :noFollowsYou="isUs" />
</template>
</FollowerList>
</div>
<Timeline
:label="$t('user_card.media')"
:disabled="!media.visibleStatuses.length"
:embedded="true" :title="$t('user_card.media')"
timeline-name="media"
:timeline="media"
:user-id="userId"
/>
<Timeline
v-if="isUs"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
:embedded="true"
:title="$t('user_card.favorites')"
timeline-name="favorites"
:timeline="favorites"
/>
</tab-switcher>
</div>
<div v-else class="panel user-profile-placeholder">
<div class="panel-heading">
<div class="title">
{{ $t('settings.profile_tab') }}
</div> </div>
</div> </div>
<div class="panel-body">
<span v-if="error">{{ error }}</span>
<i class="icon-spin3 animate-spin" v-else></i>
</div>
</div> </div>
</div>
</template> </template>
<script src="./user_profile.js"></script> <script src="./user_profile.js"></script>

Some files were not shown because too many files have changed in this diff Show more