2018-04-11 07:08:14 +02:00
|
|
|
{{#if realm === 'home'}}
|
|
|
|
<h1 class="sr-only">Compose toot</h1>
|
|
|
|
{{/if}}
|
2018-03-30 10:06:17 +02:00
|
|
|
<div class="{{computedClassName}} {{hideAndFadeIn}}">
|
2018-03-03 19:11:32 +01:00
|
|
|
<ComposeAuthor />
|
2018-03-04 00:44:43 +01:00
|
|
|
{{#if contentWarningShown}}
|
2018-03-04 01:25:22 +01:00
|
|
|
<div class="compose-content-warning-wrapper"
|
|
|
|
transition:slide="{duration: 333}">
|
|
|
|
<ComposeContentWarning :realm :contentWarning />
|
|
|
|
</div>
|
2018-03-04 00:44:43 +01:00
|
|
|
{{/if}}
|
2018-04-12 05:00:43 +02:00
|
|
|
<ComposeInput :realm :text :autoFocus on:postAction="doPostStatus()" />
|
2018-03-04 01:12:48 +01:00
|
|
|
<ComposeLengthGauge :length :overLimit />
|
2018-03-25 03:04:54 +02:00
|
|
|
<ComposeToolbar :realm :postPrivacy :media :contentWarningShown :text />
|
2018-03-04 01:12:48 +01:00
|
|
|
<ComposeLengthIndicator :length :overLimit />
|
2018-04-10 03:30:15 +02:00
|
|
|
<ComposeMedia :realm :media :mediaDescriptions />
|
2018-02-26 01:26:43 +01:00
|
|
|
</div>
|
2018-03-30 08:16:53 +02:00
|
|
|
<div class="compose-box-button-sentinel {{hideAndFadeIn}}" ref:sentinel></div>
|
2018-03-30 10:06:17 +02:00
|
|
|
<div class="compose-box-button-wrapper {{realm === 'home' ? 'compose-button-sticky' : ''}} {{hideAndFadeIn}}" >
|
2018-03-27 09:02:55 +02:00
|
|
|
<ComposeButton :length :overLimit :sticky on:click="onClickPostButton()" />
|
|
|
|
</div>
|
2018-03-30 10:06:17 +02:00
|
|
|
{{#if !hideBottomBorder}}
|
|
|
|
<div class="compose-box-border-bottom {{hideAndFadeIn}}"></div>
|
|
|
|
{{/if}}
|
2018-02-26 01:26:43 +01:00
|
|
|
<style>
|
2018-02-27 06:54:21 +01:00
|
|
|
.compose-box {
|
2018-02-26 01:26:43 +01:00
|
|
|
border-radius: 4px;
|
2018-03-27 09:02:55 +02:00
|
|
|
padding: 20px 20px 0 20px;
|
2018-02-26 01:26:43 +01:00
|
|
|
display: grid;
|
|
|
|
align-items: flex-start;
|
|
|
|
grid-template-areas:
|
2018-03-04 00:44:43 +01:00
|
|
|
"avatar name handle handle"
|
|
|
|
"avatar cw cw cw"
|
|
|
|
"avatar input input input"
|
|
|
|
"avatar gauge gauge gauge"
|
|
|
|
"avatar toolbar toolbar length"
|
2018-03-27 09:02:55 +02:00
|
|
|
"avatar media media media";
|
2018-02-27 05:55:49 +01:00
|
|
|
grid-template-columns: min-content minmax(0, max-content) 1fr 1fr;
|
2018-02-26 01:26:43 +01:00
|
|
|
width: 560px;
|
|
|
|
max-width: calc(100vw - 40px);
|
|
|
|
}
|
|
|
|
|
2018-04-05 08:03:26 +02:00
|
|
|
.compose-box.direct-reply {
|
|
|
|
background-color: var(--status-direct-background);
|
|
|
|
}
|
|
|
|
|
2018-03-30 10:06:17 +02:00
|
|
|
.compose-box.slim-size {
|
2018-03-27 09:02:55 +02:00
|
|
|
width: 540px;
|
2018-03-28 18:05:22 +02:00
|
|
|
max-width: calc(100vw - 60px);
|
2018-03-27 09:02:55 +02:00
|
|
|
}
|
|
|
|
|
2018-03-30 08:16:53 +02:00
|
|
|
.compose-box-fade-in {
|
|
|
|
transition: opacity 0.2s linear; /* main page reveal */
|
|
|
|
}
|
|
|
|
|
2018-03-27 09:02:55 +02:00
|
|
|
.compose-box-border-bottom {
|
|
|
|
height: 1px;
|
|
|
|
background: var(--main-border);
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.compose-box-button-wrapper {
|
2018-04-13 05:23:38 +02:00
|
|
|
/*
|
|
|
|
* We want pointer-events only for the sticky button, so use fit-content so that
|
|
|
|
* the element doesn't take up the full width, and then set its left margin to
|
|
|
|
* auto so that it sticks to the right. fit-content doesn't work in Edge, but
|
|
|
|
* that just means that content that is level with the button is not clickable.
|
|
|
|
*/
|
|
|
|
width: -moz-fit-content;
|
|
|
|
width: fit-content;
|
|
|
|
margin-left: auto;
|
2018-03-27 09:02:55 +02:00
|
|
|
display: flex;
|
|
|
|
justify-content: flex-end;
|
2018-03-30 10:06:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
.compose-box-button-wrapper.compose-button-sticky {
|
2018-03-27 09:02:55 +02:00
|
|
|
position: -webkit-sticky;
|
|
|
|
position: sticky;
|
|
|
|
top: 10px;
|
2018-04-05 05:32:54 +02:00
|
|
|
z-index: 5000;
|
2018-03-27 09:02:55 +02:00
|
|
|
}
|
|
|
|
|
2018-03-04 01:25:22 +01:00
|
|
|
.compose-content-warning-wrapper {
|
|
|
|
grid-area: cw;
|
|
|
|
}
|
|
|
|
|
2018-02-26 01:26:43 +01:00
|
|
|
@media (max-width: 767px) {
|
2018-02-27 06:54:21 +01:00
|
|
|
.compose-box {
|
2018-03-27 09:02:55 +02:00
|
|
|
padding: 10px 10px 0 10px;
|
2018-02-26 01:26:43 +01:00
|
|
|
max-width: calc(100vw - 20px);
|
|
|
|
width: 580px;
|
|
|
|
}
|
2018-03-30 10:06:17 +02:00
|
|
|
.compose-box.slim-size {
|
2018-03-27 09:02:55 +02:00
|
|
|
width: 560px;
|
|
|
|
max-width: calc(100vw - 40px);
|
|
|
|
}
|
2018-04-13 04:37:37 +02:00
|
|
|
.compose-box-button-wrapper.compose-button-sticky {
|
2018-03-27 09:02:55 +02:00
|
|
|
top: 5px;
|
|
|
|
}
|
2018-02-26 01:26:43 +01:00
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<script>
|
2018-02-27 06:50:03 +01:00
|
|
|
import ComposeToolbar from './ComposeToolbar.html'
|
|
|
|
import ComposeLengthGauge from './ComposeLengthGauge.html'
|
|
|
|
import ComposeLengthIndicator from './ComposeLengthIndicator.html'
|
2018-02-27 06:54:21 +01:00
|
|
|
import ComposeAuthor from './ComposeAuthor.html'
|
2018-02-27 07:22:56 +01:00
|
|
|
import ComposeInput from './ComposeInput.html'
|
|
|
|
import ComposeButton from './ComposeButton.html'
|
2018-03-02 06:21:49 +01:00
|
|
|
import ComposeMedia from './ComposeMedia.html'
|
2018-03-04 00:44:43 +01:00
|
|
|
import ComposeContentWarning from './ComposeContentWarning.html'
|
2018-03-03 23:51:48 +01:00
|
|
|
import { measureText } from '../../_utils/measureText'
|
|
|
|
import { CHAR_LIMIT, POST_PRIVACY_OPTIONS } from '../../_static/statuses'
|
|
|
|
import { store } from '../../_store/store'
|
2018-03-04 01:25:22 +01:00
|
|
|
import { slide } from 'svelte-transitions'
|
2018-04-04 02:50:48 +02:00
|
|
|
import { postStatus, insertHandleForReply, setReplySpoiler, setReplyVisibility } from '../../_actions/compose'
|
2018-03-27 09:02:55 +02:00
|
|
|
import { importDialogs } from '../../_utils/asyncModules'
|
2018-03-30 08:16:53 +02:00
|
|
|
import { classname } from '../../_utils/classname'
|
2018-02-26 02:21:17 +01:00
|
|
|
|
2018-02-26 01:26:43 +01:00
|
|
|
export default {
|
2018-04-20 06:38:01 +02:00
|
|
|
oncreate () {
|
2018-04-19 18:37:05 +02:00
|
|
|
let { realm } = this.get()
|
2018-03-27 09:02:55 +02:00
|
|
|
if (realm === 'home') {
|
2018-03-31 18:36:29 +02:00
|
|
|
this.setupStickyObserver()
|
2018-03-27 09:02:55 +02:00
|
|
|
} else if (realm !== 'dialog') {
|
2018-03-09 17:45:12 +01:00
|
|
|
// if this is a reply, populate the handle immediately
|
|
|
|
insertHandleForReply(realm)
|
2018-03-27 09:02:55 +02:00
|
|
|
}
|
2018-03-09 17:45:12 +01:00
|
|
|
|
2018-04-19 18:37:05 +02:00
|
|
|
let { replySpoiler } = this.get()
|
2018-04-03 18:45:17 +02:00
|
|
|
if (replySpoiler) {
|
|
|
|
// default spoiler is same as the replied-to status
|
|
|
|
setReplySpoiler(realm, replySpoiler)
|
|
|
|
}
|
|
|
|
|
2018-04-19 18:37:05 +02:00
|
|
|
let { replyVisibility } = this.get()
|
2018-04-04 02:50:48 +02:00
|
|
|
if (replyVisibility) {
|
|
|
|
// make sure the visibility is consistent with the replied-to status
|
|
|
|
setReplyVisibility(realm, replyVisibility)
|
|
|
|
}
|
2018-03-27 09:02:55 +02:00
|
|
|
},
|
2018-04-20 06:38:01 +02:00
|
|
|
ondestroy () {
|
2018-03-31 18:36:29 +02:00
|
|
|
this.teardownStickyObserver()
|
2018-03-08 03:04:20 +01:00
|
|
|
},
|
2018-02-26 01:26:43 +01:00
|
|
|
components: {
|
2018-02-27 06:54:21 +01:00
|
|
|
ComposeAuthor,
|
2018-02-27 06:50:03 +01:00
|
|
|
ComposeToolbar,
|
|
|
|
ComposeLengthGauge,
|
2018-02-27 07:22:56 +01:00
|
|
|
ComposeLengthIndicator,
|
|
|
|
ComposeInput,
|
2018-03-02 06:21:49 +01:00
|
|
|
ComposeButton,
|
2018-03-04 00:44:43 +01:00
|
|
|
ComposeMedia,
|
|
|
|
ComposeContentWarning
|
2018-03-03 23:51:48 +01:00
|
|
|
},
|
|
|
|
store: () => store,
|
|
|
|
computed: {
|
2018-04-20 19:36:05 +02:00
|
|
|
computedClassName: (overLimit, realm, size, postPrivacyKey, isReply) => (classname(
|
|
|
|
'compose-box',
|
|
|
|
overLimit && 'over-char-limit',
|
|
|
|
size === 'slim' && 'slim-size',
|
|
|
|
isReply && postPrivacyKey === 'direct' && 'direct-reply'
|
|
|
|
)),
|
|
|
|
hideAndFadeIn: (hidden) => (classname(
|
|
|
|
'compose-box-fade-in',
|
|
|
|
hidden && 'hidden'
|
|
|
|
)),
|
2018-03-03 23:51:48 +01:00
|
|
|
composeData: ($currentComposeData, realm) => $currentComposeData[realm] || {},
|
|
|
|
text: (composeData) => composeData.text || '',
|
|
|
|
media: (composeData) => composeData.media || [],
|
|
|
|
postPrivacy: (postPrivacyKey) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey),
|
2018-04-04 02:50:48 +02:00
|
|
|
defaultPostPrivacyKey: ($currentVerifyCredentials) => $currentVerifyCredentials.source.privacy,
|
2018-03-03 23:51:48 +01:00
|
|
|
postPrivacyKey: (composeData, defaultPostPrivacyKey) => composeData.postPrivacy || defaultPostPrivacyKey,
|
|
|
|
textLength: (text) => measureText(text),
|
2018-03-04 01:12:48 +01:00
|
|
|
contentWarningLength: (contentWarning) => measureText(contentWarning),
|
2018-04-20 19:36:05 +02:00
|
|
|
length: (textLength, contentWarningLength, contentWarningShown) => (
|
|
|
|
textLength + (contentWarningShown ? contentWarningLength : 0)
|
|
|
|
),
|
2018-03-04 01:12:48 +01:00
|
|
|
overLimit: (length) => length > CHAR_LIMIT,
|
2018-03-04 00:44:43 +01:00
|
|
|
contentWarningShown: (composeData) => composeData.contentWarningShown,
|
2018-03-08 03:04:20 +01:00
|
|
|
contentWarning: (composeData) => composeData.contentWarning || '',
|
2018-04-10 03:30:15 +02:00
|
|
|
timelineInitialized: ($timelineInitialized) => $timelineInitialized,
|
|
|
|
mediaDescriptions: (composeData) => composeData.mediaDescriptions || []
|
2018-03-04 01:25:22 +01:00
|
|
|
},
|
|
|
|
transitions: {
|
|
|
|
slide
|
2018-03-05 01:27:15 +01:00
|
|
|
},
|
|
|
|
methods: {
|
2018-04-20 06:38:01 +02:00
|
|
|
async onClickPostButton () {
|
2018-04-19 18:37:05 +02:00
|
|
|
let { sticky } = this.get()
|
|
|
|
if (sticky) {
|
2018-03-27 09:02:55 +02:00
|
|
|
// when the button is sticky, we're scrolled down the home timeline,
|
|
|
|
// so we should launch a new compose dialog
|
|
|
|
let dialogs = await importDialogs()
|
|
|
|
dialogs.showComposeDialog()
|
|
|
|
} else {
|
|
|
|
// else we're actually posting a new toot
|
2018-04-20 06:38:01 +02:00
|
|
|
this.doPostStatus()
|
2018-04-12 05:01:17 +02:00
|
|
|
}
|
|
|
|
},
|
2018-04-20 06:38:01 +02:00
|
|
|
doPostStatus () {
|
2018-04-19 18:37:05 +02:00
|
|
|
let {
|
|
|
|
text,
|
|
|
|
media,
|
|
|
|
postPrivacyKey,
|
|
|
|
contentWarning,
|
|
|
|
realm,
|
|
|
|
overLimit,
|
|
|
|
mediaDescriptions,
|
|
|
|
inReplyToUuid
|
|
|
|
} = this.get()
|
2018-04-12 05:01:17 +02:00
|
|
|
let sensitive = media.length && !!contentWarning
|
|
|
|
let mediaIds = media.map(_ => _.data.id)
|
|
|
|
let inReplyTo = (realm === 'home' || realm === 'dialog') ? null : realm
|
2018-03-08 03:04:20 +01:00
|
|
|
|
2018-04-12 05:01:17 +02:00
|
|
|
if (!text || overLimit) {
|
|
|
|
return // do nothing if invalid
|
2018-03-27 09:02:55 +02:00
|
|
|
}
|
2018-04-12 05:01:17 +02:00
|
|
|
|
|
|
|
/* no await */
|
|
|
|
postStatus(realm, text, inReplyTo, mediaIds,
|
2018-04-13 06:18:14 +02:00
|
|
|
sensitive, contentWarning, postPrivacyKey,
|
|
|
|
mediaDescriptions, inReplyToUuid)
|
2018-03-31 18:36:29 +02:00
|
|
|
},
|
2018-04-20 06:38:01 +02:00
|
|
|
setupStickyObserver () {
|
2018-03-31 18:36:29 +02:00
|
|
|
this.__stickyObserver = new IntersectionObserver(entries => {
|
|
|
|
this.set({sticky: !entries[0].isIntersecting})
|
|
|
|
})
|
|
|
|
this.__stickyObserver.observe(this.refs.sentinel)
|
|
|
|
|
|
|
|
// also create a one-shot observer for the $timelineInitialized event,
|
|
|
|
// due to a bug in Firefox where when the scrollTop is set
|
|
|
|
// manually, the other observer doesn't necessarily fire
|
|
|
|
this.observe('timelineInitialized', timelineInitialized => {
|
|
|
|
if (timelineInitialized) {
|
|
|
|
let observer = new IntersectionObserver(entries => {
|
|
|
|
this.set({sticky: !entries[0].isIntersecting})
|
|
|
|
observer.disconnect()
|
|
|
|
})
|
|
|
|
observer.observe(this.refs.sentinel)
|
|
|
|
}
|
|
|
|
}, {init: false})
|
|
|
|
},
|
2018-04-20 06:38:01 +02:00
|
|
|
teardownStickyObserver () {
|
2018-03-31 18:36:29 +02:00
|
|
|
if (this.__stickyObserver) {
|
|
|
|
this.__stickyObserver.disconnect()
|
|
|
|
}
|
2018-03-05 01:27:15 +01:00
|
|
|
}
|
2018-02-26 01:26:43 +01:00
|
|
|
}
|
|
|
|
}
|
2018-02-26 09:24:28 +01:00
|
|
|
</script>
|