forked from cybrespace/pinafore
		
	
		
			
				
	
	
		
			239 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<div class="lite-compose-box {{overLimit ? 'over-char-limit' : ''}}">
 | 
						|
  <div class="compose-profile-current-user">
 | 
						|
    <Avatar account="{{verifyCredentials}}" className="compose-profile-avatar" size="small"/>
 | 
						|
    <a class="compose-profile-display-name" href="/accounts/{{verifyCredentials.id}}">
 | 
						|
      {{verifyCredentials.display_name || verifyCredentials.acct}}
 | 
						|
    </a>
 | 
						|
    <span class="compose-profile-handle">
 | 
						|
      {{'@' + verifyCredentials.acct}}
 | 
						|
    </span>
 | 
						|
    <textarea
 | 
						|
      class="compose-profile-input"
 | 
						|
      placeholder="What's on your mind?"
 | 
						|
      ref:textarea
 | 
						|
      bind:value=inputText
 | 
						|
    ></textarea>
 | 
						|
    <div class="compose-profile-length-gauge {{shouldAnimate ? 'should-animate' : ''}}"
 | 
						|
         style="transform: scaleX({{inputLengthAsFractionOfMaxRoundedAfterRaf || 0}});"
 | 
						|
         aria-hidden="true"
 | 
						|
    ></div>
 | 
						|
    <div class="compose-profile-toolbar">
 | 
						|
      <IconButton label="Insert emoji" href="#fa-smile" />
 | 
						|
      <IconButton label="Add media" href="#fa-camera" />
 | 
						|
      <IconButton label="Adjust privacy" href="#fa-globe" />
 | 
						|
      <IconButton label="Add content warning" href="#fa-exclamation-triangle" />
 | 
						|
    </div>
 | 
						|
    <span class="compose-profile-length"
 | 
						|
          aria-label="{{inputLengthLabel}}">
 | 
						|
      {{inputLengthToDisplayAfterRaf || '0'}}
 | 
						|
    </span>
 | 
						|
    <button class="primary compose-profile-button">
 | 
						|
      Toot!
 | 
						|
    </button>
 | 
						|
  </div>
 | 
						|
</div>
 | 
						|
<style>
 | 
						|
  .lite-compose-box {
 | 
						|
    display: grid;
 | 
						|
    flex-direction: row;
 | 
						|
  }
 | 
						|
  .compose-profile-current-user {
 | 
						|
    border-radius: 4px;
 | 
						|
    padding: 20px;
 | 
						|
    display: grid;
 | 
						|
    align-items: flex-start;
 | 
						|
    grid-template-areas:
 | 
						|
      "avatar display-name handle   handle"
 | 
						|
      "avatar input        input    input"
 | 
						|
      "avatar gauge        gauge    gauge"
 | 
						|
      "avatar toolbar      toolbar  length"
 | 
						|
      "avatar button       button   button";
 | 
						|
    grid-template-columns: min-content minmax(0, max-content) 1fr 1fr;
 | 
						|
    border-bottom: 1px solid var(--main-border);
 | 
						|
    width: 560px;
 | 
						|
    max-width: calc(100vw - 40px);
 | 
						|
  }
 | 
						|
  :global(.compose-profile-avatar) {
 | 
						|
    grid-area: avatar;
 | 
						|
    margin-right: 15px;
 | 
						|
  }
 | 
						|
  .compose-profile-display-name {
 | 
						|
    color: var(--deemphasized-text-color);
 | 
						|
    grid-area: display-name;
 | 
						|
    min-width: 0;
 | 
						|
    white-space: nowrap;
 | 
						|
    overflow: hidden;
 | 
						|
    text-overflow: ellipsis;
 | 
						|
    font-size: 1.1em;
 | 
						|
    margin-left: 5px;
 | 
						|
    font-weight: 600;
 | 
						|
  }
 | 
						|
  .compose-profile-display-name,
 | 
						|
  .compose-profile-display-name:hover,
 | 
						|
  .compose-profile-display-name:visited {
 | 
						|
    color: var(--body-text-color);
 | 
						|
  }
 | 
						|
  :global(.compose-profile-handle) {
 | 
						|
    grid-area: handle;
 | 
						|
    color: var(--deemphasized-text-color);
 | 
						|
    min-width: 0;
 | 
						|
    white-space: nowrap;
 | 
						|
    overflow: hidden;
 | 
						|
    text-overflow: ellipsis;
 | 
						|
    font-size: 1.1em;
 | 
						|
    margin-left: 5px;
 | 
						|
  }
 | 
						|
 | 
						|
  :global(.compose-profile-input) {
 | 
						|
    grid-area: input;
 | 
						|
    margin: 10px 0 0 5px;
 | 
						|
    padding: 10px;
 | 
						|
    border: 1px solid var(--input-border);
 | 
						|
    min-height: 75px;
 | 
						|
    resize: none;
 | 
						|
    overflow: hidden;
 | 
						|
    word-wrap: break-word;
 | 
						|
    /* Text must be at least 16px or else iOS Safari zooms in */
 | 
						|
    font-size: 1.2em;
 | 
						|
    /* Hack to make Edge stretch the element all the way to the right.
 | 
						|
     * Also desktop Safari makes the gauge stretch too far to the right without it.
 | 
						|
     */
 | 
						|
    width: calc(100% - 5px);
 | 
						|
  }
 | 
						|
 | 
						|
  .compose-profile-button {
 | 
						|
    grid-area: button;
 | 
						|
    justify-self: right;
 | 
						|
    text-transform: uppercase;
 | 
						|
    margin-top: 10px;
 | 
						|
  }
 | 
						|
 | 
						|
  .compose-profile-length-gauge {
 | 
						|
    grid-area: gauge;
 | 
						|
    margin: 0 0 5px 5px;
 | 
						|
    height: 2px;
 | 
						|
    background: var(--main-theme-color);
 | 
						|
    transform-origin: left;
 | 
						|
  }
 | 
						|
  .compose-profile-length-gauge.should-animate {
 | 
						|
    transition: transform 0.2s linear;
 | 
						|
  }
 | 
						|
 | 
						|
  .compose-profile-length {
 | 
						|
    grid-area: length;
 | 
						|
    justify-self: right;
 | 
						|
    color: var(--main-theme-color);
 | 
						|
    font-size: 1.3em;
 | 
						|
    align-self: center;
 | 
						|
  }
 | 
						|
 | 
						|
  .over-char-limit .compose-profile-length {
 | 
						|
    color: var(--warning-color);
 | 
						|
  }
 | 
						|
 | 
						|
  .over-char-limit .compose-profile-length-gauge {
 | 
						|
    background: var(--warning-color);
 | 
						|
  }
 | 
						|
 | 
						|
  .compose-profile-toolbar {
 | 
						|
    grid-area: toolbar;
 | 
						|
    align-self: center;
 | 
						|
    display: flex;
 | 
						|
    align-items: center;
 | 
						|
  }
 | 
						|
 | 
						|
  @media (max-width: 767px) {
 | 
						|
    .compose-profile-current-user {
 | 
						|
      padding: 10px 10px;
 | 
						|
      max-width: calc(100vw - 20px);
 | 
						|
      width: 580px;
 | 
						|
    }
 | 
						|
    :global(.compose-profile-avatar) {
 | 
						|
      grid-area: avatar;
 | 
						|
      margin-right: 5px;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
</style>
 | 
						|
<script>
 | 
						|
  import Avatar from '../Avatar.html'
 | 
						|
  import { store } from '../../_store/store'
 | 
						|
  import { autosize } from '../../_utils/autosize'
 | 
						|
  import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
 | 
						|
  import debounce from 'lodash/debounce'
 | 
						|
  import { mark, stop } from '../../_utils/marks'
 | 
						|
  import IconButton from '../IconButton.html'
 | 
						|
 | 
						|
  const CHAR_LIMIT = 500
 | 
						|
 | 
						|
  export default {
 | 
						|
    oncreate() {
 | 
						|
      this.set({inputText: store.get('currentInputTextInCompose')})
 | 
						|
 | 
						|
      requestAnimationFrame(() => {
 | 
						|
        mark('autosize()')
 | 
						|
        autosize(this.refs.textarea)
 | 
						|
        stop('autosize()')
 | 
						|
      })
 | 
						|
 | 
						|
      const saveText = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
 | 
						|
      this.observe('inputText', inputText => {
 | 
						|
        let inputTextInCompose = this.store.get('inputTextInCompose')
 | 
						|
        let currentInstance = this.store.get('currentInstance')
 | 
						|
        inputTextInCompose[currentInstance] = inputText || ''
 | 
						|
        this.store.set({inputTextInCompose: inputTextInCompose})
 | 
						|
        saveText()
 | 
						|
      }, {init: false})
 | 
						|
 | 
						|
      // Avoid input delays by updating these values after a rAF
 | 
						|
      this.observe('inputLengthToDisplay', inputLengthToDisplay => {
 | 
						|
        requestAnimationFrame(() => {
 | 
						|
          mark('set inputLengthToDisplayAfterRaf')
 | 
						|
          this.set({inputLengthToDisplayAfterRaf: inputLengthToDisplay})
 | 
						|
          stop('set inputLengthToDisplayAfterRaf')
 | 
						|
        })
 | 
						|
      })
 | 
						|
      this.observe('inputLengthAsFractionOfMaxRounded', inputLengthAsFractionOfMaxRounded => {
 | 
						|
        requestAnimationFrame(() => {
 | 
						|
          mark('set inputLengthAsFractionOfMaxRoundedAfterRaf')
 | 
						|
          this.set({inputLengthAsFractionOfMaxRoundedAfterRaf: inputLengthAsFractionOfMaxRounded})
 | 
						|
          stop('set inputLengthAsFractionOfMaxRoundedAfterRaf')
 | 
						|
          requestAnimationFrame(() => this.set({shouldAnimate: true}))
 | 
						|
        })
 | 
						|
      })
 | 
						|
    },
 | 
						|
    ondestroy() {
 | 
						|
      mark('autosize.destroy()')
 | 
						|
      autosize.destroy(this.refs.textarea)
 | 
						|
      stop('autosize.destroy()')
 | 
						|
    },
 | 
						|
    data: () => ({
 | 
						|
      inputText: ''
 | 
						|
    }),
 | 
						|
    components: {
 | 
						|
      Avatar,
 | 
						|
      IconButton
 | 
						|
    },
 | 
						|
    store: () => store,
 | 
						|
    computed: {
 | 
						|
      currentInputTextInCompose: ($currentInputTextInCompose) => $currentInputTextInCompose,
 | 
						|
      inputLength: (inputText) => inputText ? inputText.length : 0,
 | 
						|
      inputLengthToDisplay: (inputLength) => (inputLength <= CHAR_LIMIT ? inputLength : CHAR_LIMIT - inputLength),
 | 
						|
      inputLengthAsFractionOfMax: (inputLength) => (Math.min(CHAR_LIMIT, inputLength) / CHAR_LIMIT),
 | 
						|
      inputLengthAsFractionOfMaxRounded: (inputLengthAsFractionOfMax) => {
 | 
						|
        // We don't need to update the gauge for every decimal point, so round it to the nearest 0.02
 | 
						|
        let int = Math.round(inputLengthAsFractionOfMax * 100)
 | 
						|
        int -= (int % 2)
 | 
						|
        return int / 100
 | 
						|
      },
 | 
						|
      overLimit: (inputLength) => inputLength > CHAR_LIMIT,
 | 
						|
      inputLengthLabel: (overLimit, inputLengthToDisplay) => {
 | 
						|
        if (overLimit) {
 | 
						|
          return `${inputLengthToDisplay} characters over limit`
 | 
						|
        } else {
 | 
						|
          return `${inputLengthToDisplay} characters`
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
</script>
 |