logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git

post_status_form.vue (16057B)


  1. <template>
  2. <div
  3. ref="form"
  4. class="post-status-form"
  5. >
  6. <form
  7. autocomplete="off"
  8. @submit.prevent
  9. @dragover.prevent="fileDrag"
  10. >
  11. <div class="form-group">
  12. <i18n-t
  13. v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
  14. keypath="post_status.account_not_locked_warning"
  15. tag="p"
  16. class="visibility-notice"
  17. scope="global"
  18. >
  19. <button
  20. class="button-unstyled -link"
  21. @click="openProfileTab"
  22. >
  23. {{ $t('post_status.account_not_locked_warning_link') }}
  24. </button>
  25. </i18n-t>
  26. <p
  27. v-if="!hideScopeNotice && newStatus.visibility === 'public'"
  28. class="visibility-notice notice-dismissible"
  29. >
  30. <span>{{ $t('post_status.scope_notice.public') }}</span>
  31. <a
  32. class="fa-scale-110 fa-old-padding dismiss"
  33. :title="$t('post_status.scope_notice_dismiss')"
  34. role="button"
  35. tabindex="0"
  36. @click.prevent="dismissScopeNotice()"
  37. >
  38. <FAIcon icon="times" />
  39. </a>
  40. </p>
  41. <p
  42. v-else-if="!hideScopeNotice && newStatus.visibility === 'unlisted'"
  43. class="visibility-notice notice-dismissible"
  44. >
  45. <span>{{ $t('post_status.scope_notice.unlisted') }}</span>
  46. <a
  47. class="fa-scale-110 fa-old-padding dismiss"
  48. :title="$t('post_status.scope_notice_dismiss')"
  49. role="button"
  50. tabindex="0"
  51. @click.prevent="dismissScopeNotice()"
  52. >
  53. <FAIcon icon="times" />
  54. </a>
  55. </p>
  56. <p
  57. v-else-if="!hideScopeNotice && newStatus.visibility === 'private' && $store.state.users.currentUser.locked"
  58. class="visibility-notice notice-dismissible"
  59. >
  60. <span>{{ $t('post_status.scope_notice.private') }}</span>
  61. <a
  62. class="fa-scale-110 fa-old-padding dismiss"
  63. :title="$t('post_status.scope_notice_dismiss')"
  64. role="button"
  65. tabindex="0"
  66. @click.prevent="dismissScopeNotice()"
  67. >
  68. <FAIcon icon="times" />
  69. </a>
  70. </p>
  71. <p
  72. v-else-if="newStatus.visibility === 'direct'"
  73. class="visibility-notice"
  74. >
  75. <span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
  76. <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
  77. </p>
  78. <div
  79. v-if="isEdit"
  80. class="visibility-notice edit-warning"
  81. >
  82. <p>{{ $t('post_status.edit_remote_warning') }}</p>
  83. <p>{{ $t('post_status.edit_unsupported_warning') }}</p>
  84. </div>
  85. <div
  86. v-if="!disablePreview"
  87. class="preview-heading faint"
  88. >
  89. <a
  90. class="preview-toggle faint"
  91. @click.stop.prevent="togglePreview"
  92. >
  93. {{ $t('post_status.preview') }}
  94. <FAIcon :icon="showPreview ? 'chevron-left' : 'chevron-right'" />
  95. </a>
  96. <div
  97. v-show="previewLoading"
  98. class="preview-spinner"
  99. >
  100. <FAIcon
  101. class="fa-old-padding"
  102. spin
  103. icon="circle-notch"
  104. />
  105. </div>
  106. </div>
  107. <div
  108. v-if="showPreview"
  109. class="preview-container"
  110. >
  111. <div
  112. v-if="!preview"
  113. class="preview-status"
  114. >
  115. {{ $t('general.loading') }}
  116. </div>
  117. <div
  118. v-else-if="preview.error"
  119. class="preview-status preview-error"
  120. >
  121. {{ preview.error }}
  122. </div>
  123. <StatusContent
  124. v-else
  125. :status="preview"
  126. class="preview-status"
  127. />
  128. </div>
  129. <div
  130. v-if="quotable"
  131. role="radiogroup"
  132. class="btn-group reply-or-quote-selector"
  133. >
  134. <button
  135. :id="`reply-or-quote-option-${randomSeed}-reply`"
  136. class="btn button-default reply-or-quote-option"
  137. :class="{ toggled: !newStatus.quoting }"
  138. tabindex="0"
  139. role="radio"
  140. :aria-labelledby="`reply-or-quote-option-${randomSeed}-reply`"
  141. :aria-checked="!newStatus.quoting"
  142. @click="newStatus.quoting = false"
  143. >
  144. {{ $t('post_status.reply_option') }}
  145. </button>
  146. <button
  147. :id="`reply-or-quote-option-${randomSeed}-quote`"
  148. class="btn button-default reply-or-quote-option"
  149. :class="{ toggled: newStatus.quoting }"
  150. tabindex="0"
  151. role="radio"
  152. :aria-labelledby="`reply-or-quote-option-${randomSeed}-quote`"
  153. :aria-checked="newStatus.quoting"
  154. @click="newStatus.quoting = true"
  155. >
  156. {{ $t('post_status.quote_option') }}
  157. </button>
  158. </div>
  159. <EmojiInput
  160. v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
  161. v-model="newStatus.spoilerText"
  162. enable-emoji-picker
  163. :suggest="emojiSuggestor"
  164. class="input form-control"
  165. >
  166. <template #default="inputProps">
  167. <input
  168. v-model="newStatus.spoilerText"
  169. type="text"
  170. :placeholder="$t('post_status.content_warning')"
  171. :disabled="posting && !optimisticPosting"
  172. v-bind="propsToNative(inputProps)"
  173. size="1"
  174. class="input form-post-subject"
  175. >
  176. </template>
  177. </EmojiInput>
  178. <EmojiInput
  179. ref="emoji-input"
  180. v-model="newStatus.status"
  181. :suggest="emojiUserSuggestor"
  182. :placement="emojiPickerPlacement"
  183. class="input form-control main-input"
  184. enable-emoji-picker
  185. hide-emoji-button
  186. :newline-on-ctrl-enter="submitOnEnter"
  187. enable-sticker-picker
  188. @input="onEmojiInputInput"
  189. @sticker-uploaded="addMediaFile"
  190. @sticker-upload-failed="uploadFailed"
  191. @shown="handleEmojiInputShow"
  192. >
  193. <template #default="inputProps">
  194. <textarea
  195. ref="textarea"
  196. v-model="newStatus.status"
  197. :placeholder="placeholder || $t('post_status.default')"
  198. rows="1"
  199. cols="1"
  200. :disabled="posting && !optimisticPosting"
  201. class="input form-post-body"
  202. :class="{ 'scrollable-form': !!maxHeight }"
  203. v-bind="propsToNative(inputProps)"
  204. @keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
  205. @keydown.meta.enter="postStatus($event, newStatus)"
  206. @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
  207. @input="resize"
  208. @compositionupdate="resize"
  209. @paste="paste"
  210. />
  211. <p
  212. v-if="hasStatusLengthLimit"
  213. class="character-counter faint"
  214. :class="{ error: isOverLengthLimit }"
  215. >
  216. {{ charactersLeft }}
  217. </p>
  218. </template>
  219. </EmojiInput>
  220. <div
  221. v-if="!disableScopeSelector"
  222. class="visibility-tray"
  223. >
  224. <scope-selector
  225. v-if="!disableVisibilitySelector"
  226. :show-all="showAllScopes"
  227. :user-default="userDefaultScope"
  228. :original-scope="copyMessageScope"
  229. :initial-scope="newStatus.visibility"
  230. :on-scope-change="changeVis"
  231. />
  232. <div
  233. v-if="postFormats.length > 1"
  234. class="text-format"
  235. >
  236. <Select
  237. id="post-content-type"
  238. v-model="newStatus.contentType"
  239. class="input form-control"
  240. :attrs="{ 'aria-label': $t('post_status.content_type_selection') }"
  241. >
  242. <option
  243. v-for="postFormat in postFormats"
  244. :key="postFormat"
  245. :value="postFormat"
  246. >
  247. {{ $t(`post_status.content_type["${postFormat}"]`) }}
  248. </option>
  249. </Select>
  250. </div>
  251. <div
  252. v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
  253. class="text-format"
  254. >
  255. <span class="only-format">
  256. {{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
  257. </span>
  258. </div>
  259. </div>
  260. </div>
  261. <poll-form
  262. v-if="pollsAvailable"
  263. ref="pollForm"
  264. :visible="pollFormVisible"
  265. @update-poll="setPoll"
  266. />
  267. <div
  268. ref="bottom"
  269. class="form-bottom"
  270. >
  271. <div class="form-bottom-left">
  272. <media-upload
  273. ref="mediaUpload"
  274. class="media-upload-icon"
  275. :drop-files="dropFiles"
  276. :disabled="uploadFileLimitReached"
  277. @uploading="startedUploadingFiles"
  278. @uploaded="addMediaFile"
  279. @upload-failed="uploadFailed"
  280. @all-uploaded="finishedUploadingFiles"
  281. />
  282. <button
  283. class="emoji-icon button-unstyled"
  284. :title="$t('emoji.add_emoji')"
  285. @click="showEmojiPicker"
  286. >
  287. <FAIcon icon="smile-beam" />
  288. </button>
  289. <button
  290. v-if="pollsAvailable"
  291. class="poll-icon button-unstyled"
  292. :class="{ selected: pollFormVisible }"
  293. :title="$t('polls.add_poll')"
  294. @click="togglePollForm"
  295. >
  296. <FAIcon icon="poll-h" />
  297. </button>
  298. </div>
  299. <button
  300. v-if="posting"
  301. disabled
  302. class="btn button-default"
  303. >
  304. {{ $t('post_status.posting') }}
  305. </button>
  306. <button
  307. v-else-if="isOverLengthLimit"
  308. disabled
  309. class="btn button-default"
  310. >
  311. {{ $t('post_status.post') }}
  312. </button>
  313. <button
  314. v-else
  315. :disabled="uploadingFiles || disableSubmit"
  316. class="btn button-default"
  317. @click.stop.prevent="postStatus($event, newStatus)"
  318. >
  319. {{ $t('post_status.post') }}
  320. </button>
  321. </div>
  322. <div
  323. v-show="showDropIcon !== 'hide'"
  324. :style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
  325. class="drop-indicator"
  326. @dragleave="fileDragStop"
  327. @drop.stop="fileDrop"
  328. >
  329. <FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
  330. </div>
  331. <div
  332. v-if="error"
  333. class="alert error"
  334. >
  335. Error: {{ error }}
  336. <button
  337. class="button-unstyled"
  338. @click="clearError"
  339. >
  340. <FAIcon
  341. class="fa-scale-110 fa-old-padding"
  342. icon="times"
  343. />
  344. </button>
  345. </div>
  346. <gallery
  347. v-if="newStatus.files && newStatus.files.length > 0"
  348. class="attachments"
  349. :grid="true"
  350. :nsfw="false"
  351. :attachments="newStatus.files"
  352. :descriptions="newStatus.mediaDescriptions"
  353. :set-media="() => $store.dispatch('setMedia', newStatus.files)"
  354. :editable="true"
  355. :edit-attachment="editAttachment"
  356. :remove-attachment="removeMediaFile"
  357. :shift-up-attachment="newStatus.files.length > 1 && shiftUpMediaFile"
  358. :shift-dn-attachment="newStatus.files.length > 1 && shiftDnMediaFile"
  359. @play="$emit('mediaplay', attachment.id)"
  360. @pause="$emit('mediapause', attachment.id)"
  361. />
  362. <div
  363. v-if="newStatus.files.length > 0 && !disableSensitivityCheckbox"
  364. class="upload_settings"
  365. >
  366. <Checkbox v-model="newStatus.nsfw">
  367. {{ $t('post_status.attachments_sensitive') }}
  368. </Checkbox>
  369. </div>
  370. </form>
  371. </div>
  372. </template>
  373. <script src="./post_status_form.js"></script>
  374. <style lang="scss">
  375. .post-status-form {
  376. position: relative;
  377. .attachments {
  378. margin-bottom: 0.5em;
  379. }
  380. .form-bottom {
  381. display: flex;
  382. justify-content: space-between;
  383. padding: 0.5em;
  384. height: 2.5em;
  385. button {
  386. width: 10em;
  387. }
  388. p {
  389. margin: 0.35em;
  390. padding: 0.35em;
  391. display: flex;
  392. }
  393. }
  394. .form-bottom-left {
  395. display: flex;
  396. flex: 1;
  397. padding-right: 7px;
  398. margin-right: 7px;
  399. max-width: 10em;
  400. }
  401. .preview-heading {
  402. display: flex;
  403. padding-left: 0.5em;
  404. }
  405. .preview-toggle {
  406. flex: 1;
  407. cursor: pointer;
  408. user-select: none;
  409. &:hover {
  410. text-decoration: underline;
  411. }
  412. svg,
  413. i {
  414. margin-left: 0.2em;
  415. font-size: 0.8em;
  416. transform: rotate(90deg);
  417. }
  418. }
  419. .preview-container {
  420. margin-bottom: 1em;
  421. }
  422. .preview-error {
  423. font-style: italic;
  424. color: var(--textFaint);
  425. }
  426. .preview-status {
  427. border: 1px solid var(--border);
  428. border-radius: var(--roundness);
  429. padding: 0.5em;
  430. margin: 0;
  431. }
  432. .reply-or-quote-selector {
  433. margin-bottom: 0.5em;
  434. }
  435. .text-format {
  436. .only-format {
  437. color: var(--textFaint);
  438. }
  439. }
  440. .visibility-tray {
  441. display: flex;
  442. justify-content: space-between;
  443. padding-top: 5px;
  444. align-items: baseline;
  445. }
  446. .visibility-notice.edit-warning {
  447. > :first-child {
  448. margin-top: 0;
  449. }
  450. > :last-child {
  451. margin-bottom: 0;
  452. }
  453. }
  454. // Order is not necessary but a good indicator
  455. .media-upload-icon {
  456. order: 1;
  457. justify-content: left;
  458. }
  459. .emoji-icon {
  460. order: 2;
  461. justify-content: center;
  462. }
  463. .poll-icon {
  464. order: 3;
  465. justify-content: right;
  466. }
  467. .media-upload-icon,
  468. .poll-icon,
  469. .emoji-icon {
  470. font-size: 1.85em;
  471. line-height: 1.1;
  472. flex: 1;
  473. padding: 0 0.1em;
  474. display: flex;
  475. align-items: center;
  476. }
  477. .error {
  478. text-align: center;
  479. }
  480. .media-upload-wrapper {
  481. margin-right: 0.2em;
  482. margin-bottom: 0.5em;
  483. width: 18em;
  484. img,
  485. video {
  486. object-fit: contain;
  487. max-height: 10em;
  488. }
  489. .video {
  490. max-height: 10em;
  491. }
  492. input {
  493. flex: 1;
  494. width: 100%;
  495. }
  496. }
  497. .status-input-wrapper {
  498. display: flex;
  499. position: relative;
  500. width: 100%;
  501. flex-direction: column;
  502. }
  503. .btn[disabled] {
  504. cursor: not-allowed;
  505. }
  506. form {
  507. display: flex;
  508. flex-direction: column;
  509. margin: 0.6em;
  510. position: relative;
  511. }
  512. .form-group {
  513. display: flex;
  514. flex-direction: column;
  515. padding: 0.25em 0.5em 0.5em;
  516. line-height: 1.85;
  517. }
  518. .input.form-post-body {
  519. // TODO: make a resizable textarea component?
  520. box-sizing: content-box; // needed for easier computation of dynamic size
  521. overflow: hidden;
  522. transition: min-height 200ms 100ms;
  523. // stock padding + 1 line of text (for counter)
  524. padding-bottom: calc(var(--_padding) + var(--post-line-height) * 1em);
  525. // two lines of text
  526. height: calc(var(--post-line-height) * 1em);
  527. min-height: calc(var(--post-line-height) * 1em);
  528. resize: none;
  529. background: transparent;
  530. &.scrollable-form {
  531. overflow-y: auto;
  532. }
  533. }
  534. .main-input {
  535. position: relative;
  536. }
  537. .character-counter {
  538. position: absolute;
  539. bottom: 0;
  540. right: 0;
  541. padding: 0;
  542. margin: 0 0.5em;
  543. &.error {
  544. color: var(--cRed);
  545. }
  546. }
  547. @keyframes fade-in {
  548. from { opacity: 0; }
  549. to { opacity: 0.6; }
  550. }
  551. @keyframes fade-out {
  552. from { opacity: 0.6; }
  553. to { opacity: 0; }
  554. }
  555. .drop-indicator {
  556. position: absolute;
  557. width: 100%;
  558. height: 100%;
  559. font-size: 5em;
  560. display: flex;
  561. align-items: center;
  562. justify-content: center;
  563. opacity: 0.6;
  564. color: var(--text);
  565. background-color: var(--bg);
  566. border-radius: var(--roundness);
  567. border: 2px dashed var(--text);
  568. }
  569. }
  570. </style>