more options button to follow/unfollow
This commit is contained in:
		
							parent
							
								
									92176df3ab
								
							
						
					
					
						commit
						c13771c3a0
					
				
					 8 changed files with 164 additions and 75 deletions
				
			
		| 
						 | 
				
			
			@ -3,7 +3,7 @@ import { followAccount, unfollowAccount } from '../_api/follow'
 | 
			
		|||
import { database } from '../_database/database'
 | 
			
		||||
import { toast } from '../_utils/toast'
 | 
			
		||||
 | 
			
		||||
export async function setAccountFollowed (accountId, follow) {
 | 
			
		||||
export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
 | 
			
		||||
  let instanceName = store.get('currentInstance')
 | 
			
		||||
  let accessToken = store.get('accessToken')
 | 
			
		||||
  try {
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +15,9 @@ export async function setAccountFollowed (accountId, follow) {
 | 
			
		|||
    let relationship = await database.getRelationship(instanceName, accountId)
 | 
			
		||||
    relationship.following = follow
 | 
			
		||||
    await database.setRelationship(instanceName, relationship)
 | 
			
		||||
    if (toastOnSuccess) {
 | 
			
		||||
      toast.say(`${follow ? 'Followed' : 'Unfollowed'}`)
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
    toast.say(`Unable to ${follow ? 'follow' : 'unfollow'} account: ` + (e.message || ''))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										57
									
								
								routes/_components/dialog/GenericDialogList.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								routes/_components/dialog/GenericDialogList.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
<ul class="generic-dialog-list">
 | 
			
		||||
  {{#each items as item @key}}
 | 
			
		||||
  <li class="generic-dialog-list-item">
 | 
			
		||||
    <button class="generic-dialog-list-button" on:click="fire('click', item)">
 | 
			
		||||
      <svg>
 | 
			
		||||
        <use xlink:href="{{item.icon}}" />
 | 
			
		||||
      </svg>
 | 
			
		||||
      <span>
 | 
			
		||||
            {{item.label}}
 | 
			
		||||
          </span>
 | 
			
		||||
      <svg class="{{item.selected ? '' : 'hidden'}}" aria-hidden="{{!item.selected}}">
 | 
			
		||||
        <use xlink:href="#fa-check" />
 | 
			
		||||
      </svg>
 | 
			
		||||
    </button>
 | 
			
		||||
  </li>
 | 
			
		||||
  {{/each}}
 | 
			
		||||
</ul>
 | 
			
		||||
<style>
 | 
			
		||||
  .generic-dialog-list {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border: 1px solid var(--settings-list-item-border);
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    min-width: 300px;
 | 
			
		||||
    max-width: calc(100vw - 20px);
 | 
			
		||||
  }
 | 
			
		||||
  .generic-dialog-list-item {
 | 
			
		||||
    border: 1px solid var(--settings-list-item-border);
 | 
			
		||||
    font-size: 1.2em;
 | 
			
		||||
    display: flex;
 | 
			
		||||
  }
 | 
			
		||||
  .generic-dialog-list-item svg {
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    fill: var(--svg-fill);
 | 
			
		||||
  }
 | 
			
		||||
  .generic-dialog-list-button {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    background: var(--settings-list-item-bg);
 | 
			
		||||
    border: none;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
  .generic-dialog-list-button span {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    margin-left: 20px;
 | 
			
		||||
  }
 | 
			
		||||
  .generic-dialog-list-button:hover {
 | 
			
		||||
    background: var(--settings-list-item-bg-hover);
 | 
			
		||||
  }
 | 
			
		||||
  .generic-dialog-list-button:active {
 | 
			
		||||
    background: var(--settings-list-item-bg-active);
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,86 +1,28 @@
 | 
			
		|||
<ModalDialog :label :shown :closed :title background="var(--main-bg)">
 | 
			
		||||
  <ul class="post-privacy">
 | 
			
		||||
    {{#each postPrivacyOptions as option}}
 | 
			
		||||
      <li class="post-privacy-item">
 | 
			
		||||
        <button class="post-privacy-button" on:click="onClick(option)">
 | 
			
		||||
          <svg>
 | 
			
		||||
            <use xlink:href="{{option.icon}}" />
 | 
			
		||||
          </svg>
 | 
			
		||||
          <span>
 | 
			
		||||
            {{option.label}}
 | 
			
		||||
          </span>
 | 
			
		||||
          <svg class="{{isSelected(option, postPrivacy) ? '' : 'hidden'}}"
 | 
			
		||||
               aria-hidden="{{!isSelected(option, postPrivacy)}}">
 | 
			
		||||
            <use xlink:href="#fa-check" />
 | 
			
		||||
          </svg>
 | 
			
		||||
        </button>
 | 
			
		||||
      </li>
 | 
			
		||||
    {{/each}}
 | 
			
		||||
  </ul>
 | 
			
		||||
  <GenericDialogList :items on:click="onClick(event)" />
 | 
			
		||||
</ModalDialog>
 | 
			
		||||
<style>
 | 
			
		||||
  .post-privacy {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border: 1px solid var(--settings-list-item-border);
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    min-width: 300px;
 | 
			
		||||
    max-width: calc(100vw - 20px);
 | 
			
		||||
  }
 | 
			
		||||
  .post-privacy-item {
 | 
			
		||||
    border: 1px solid var(--settings-list-item-border);
 | 
			
		||||
    font-size: 1.2em;
 | 
			
		||||
    display: flex;
 | 
			
		||||
  }
 | 
			
		||||
  .post-privacy-item svg {
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    fill: var(--svg-fill);
 | 
			
		||||
  }
 | 
			
		||||
  .post-privacy-button {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    background: var(--settings-list-item-bg);
 | 
			
		||||
    border: none;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
  .post-privacy-button span {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    margin-left: 20px;
 | 
			
		||||
  }
 | 
			
		||||
  .post-privacy-button:hover {
 | 
			
		||||
    background: var(--settings-list-item-bg-hover);
 | 
			
		||||
  }
 | 
			
		||||
  .post-privacy-button:active {
 | 
			
		||||
    background: var(--settings-list-item-bg-active);
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import ModalDialog from './ModalDialog.html'
 | 
			
		||||
  import { store } from '../../_store/store'
 | 
			
		||||
  import { POST_PRIVACY_OPTIONS } from '../../_static/statuses'
 | 
			
		||||
  import { setPostPrivacy } from '../../_actions/postPrivacy'
 | 
			
		||||
  import GenericDialogList from './GenericDialogList.html'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    components: {
 | 
			
		||||
      ModalDialog
 | 
			
		||||
      ModalDialog,
 | 
			
		||||
      GenericDialogList
 | 
			
		||||
    },
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    data: () => ({
 | 
			
		||||
      postPrivacyOptions: POST_PRIVACY_OPTIONS
 | 
			
		||||
    }),
 | 
			
		||||
    helpers: {
 | 
			
		||||
      isSelected: (option, postPrivacy) => postPrivacy.key === option.key
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      async show() {
 | 
			
		||||
        this.set({shown: true})
 | 
			
		||||
      },
 | 
			
		||||
      onClick(option) {
 | 
			
		||||
        setPostPrivacy(this.get('realm'), option.key)
 | 
			
		||||
      onClick(item) {
 | 
			
		||||
        setPostPrivacy(this.get('realm'), item.key)
 | 
			
		||||
        this.set({closed: true})
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -91,6 +33,14 @@
 | 
			
		|||
      },
 | 
			
		||||
      postPrivacyKey: (composeData, $currentVerifyCredentials) => {
 | 
			
		||||
        return composeData.postPrivacy || $currentVerifyCredentials.source.privacy
 | 
			
		||||
      },
 | 
			
		||||
      items: (postPrivacy, postPrivacyOptions) => {
 | 
			
		||||
        return postPrivacyOptions.map(option => ({
 | 
			
		||||
          key: option.key,
 | 
			
		||||
          label: option.label,
 | 
			
		||||
          icon: option.icon,
 | 
			
		||||
          selected: postPrivacy.key === option.key
 | 
			
		||||
        }))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										52
									
								
								routes/_components/dialog/StatusOptionsDialog.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								routes/_components/dialog/StatusOptionsDialog.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
<ModalDialog :label :shown :closed :title background="var(--main-bg)">
 | 
			
		||||
  <GenericDialogList :items on:click="onClick(event)"/>
 | 
			
		||||
</ModalDialog>
 | 
			
		||||
<script>
 | 
			
		||||
import ModalDialog from './ModalDialog.html'
 | 
			
		||||
import { store } from '../../_store/store'
 | 
			
		||||
import GenericDialogList from './GenericDialogList.html'
 | 
			
		||||
import { setAccountFollowed } from '../../_actions/follow'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  computed: {
 | 
			
		||||
    relationship: ($currentAccountRelationship) => $currentAccountRelationship,
 | 
			
		||||
    account: ($currentAccountProfile) => $currentAccountProfile,
 | 
			
		||||
    following: (relationship) => relationship && !!relationship.following,
 | 
			
		||||
    accountName: (account) => account && (account.display_name || account.acct),
 | 
			
		||||
    accountId: (account) => account && account.id,
 | 
			
		||||
    followLabel: (following, accountName) => {
 | 
			
		||||
      if (typeof following === 'undefined' || !accountName) {
 | 
			
		||||
        return ''
 | 
			
		||||
      }
 | 
			
		||||
      return following ? `Unfollow ${accountName}` : `Follow ${accountName}`
 | 
			
		||||
    },
 | 
			
		||||
    items: (followLabel, following) => {
 | 
			
		||||
      return [
 | 
			
		||||
        {
 | 
			
		||||
          key: 'follow',
 | 
			
		||||
          label: followLabel,
 | 
			
		||||
          icon: following ? '#fa-user-times' : '#fa-user-plus'
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  components: {
 | 
			
		||||
    ModalDialog,
 | 
			
		||||
    GenericDialogList
 | 
			
		||||
  },
 | 
			
		||||
  store: () => store,
 | 
			
		||||
  methods: {
 | 
			
		||||
    async show() {
 | 
			
		||||
      this.set({shown: true})
 | 
			
		||||
    },
 | 
			
		||||
    async onClick(item) {
 | 
			
		||||
      if (item.key === 'follow') {
 | 
			
		||||
        let accountId = this.get('accountId')
 | 
			
		||||
        let following = this.get('following')
 | 
			
		||||
        await setAccountFollowed(accountId, !following, true)
 | 
			
		||||
        this.set({closed: true})
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -3,3 +3,4 @@ export * from './showImageDialog'
 | 
			
		|||
export * from './showVideoDialog'
 | 
			
		||||
export * from './showEmojiDialog'
 | 
			
		||||
export * from './showPostPrivacyDialog'
 | 
			
		||||
export * from './showStatusOptionsDialog'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										13
									
								
								routes/_components/dialog/showStatusOptionsDialog.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								routes/_components/dialog/showStatusOptionsDialog.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
import StatusOptionsDialog from './StatusOptionsDialog.html'
 | 
			
		||||
 | 
			
		||||
export function showStatusOptionsDialog (statusId) {
 | 
			
		||||
  let dialog = new StatusOptionsDialog({
 | 
			
		||||
    target: document.getElementById('modal-dialog'),
 | 
			
		||||
    data: {
 | 
			
		||||
      label: 'Status options dialog',
 | 
			
		||||
      title: 'Status options',
 | 
			
		||||
      statusId: statusId
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  dialog.show()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,6 @@
 | 
			
		|||
    href="#fa-reply"
 | 
			
		||||
    disabled="{{disableReply}}"
 | 
			
		||||
    delegateKey="{{replyKey}}"
 | 
			
		||||
    ref:replyNode
 | 
			
		||||
    />
 | 
			
		||||
  <IconButton
 | 
			
		||||
    label="{{reblogLabel}}"
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +12,6 @@
 | 
			
		|||
    disabled="{{reblogDisabled}}"
 | 
			
		||||
    href="{{reblogIcon}}"
 | 
			
		||||
    delegateKey="{{reblogKey}}"
 | 
			
		||||
    ref:reblogNode
 | 
			
		||||
  />
 | 
			
		||||
  <IconButton
 | 
			
		||||
    label="Favorite"
 | 
			
		||||
| 
						 | 
				
			
			@ -21,11 +19,11 @@
 | 
			
		|||
    pressed="{{favorited}}"
 | 
			
		||||
    href="#fa-star"
 | 
			
		||||
    delegateKey="{{favoriteKey}}"
 | 
			
		||||
    ref:favoriteNode
 | 
			
		||||
    />
 | 
			
		||||
  />
 | 
			
		||||
  <IconButton
 | 
			
		||||
    label="Show more actions"
 | 
			
		||||
    label="Show more options"
 | 
			
		||||
    href="#fa-ellipsis-h"
 | 
			
		||||
    delegateKey="{{optionsKey}}"
 | 
			
		||||
  />
 | 
			
		||||
</div>
 | 
			
		||||
<style>
 | 
			
		||||
| 
						 | 
				
			
			@ -45,17 +43,21 @@
 | 
			
		|||
  import { setFavorited } from '../../_actions/favorite'
 | 
			
		||||
  import { setReblogged } from '../../_actions/reblog'
 | 
			
		||||
  import { goto } from 'sapper/runtime.js'
 | 
			
		||||
  import { importDialogs } from '../../_utils/asyncModules'
 | 
			
		||||
  import { updateProfileAndRelationship } from '../../_actions/accounts'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate() {
 | 
			
		||||
      registerClickDelegate(this.get('favoriteKey'), () => this.onFavoriteClick())
 | 
			
		||||
      registerClickDelegate(this.get('reblogKey'), () => this.onReblogClick())
 | 
			
		||||
      registerClickDelegate(this.get('replyKey'), () => this.onReplyClick())
 | 
			
		||||
      registerClickDelegate(this.get('optionsKey'), () => this.onOptionsClick())
 | 
			
		||||
    },
 | 
			
		||||
    ondestroy() {
 | 
			
		||||
      unregisterClickDelegate(this.get('favoriteKey'))
 | 
			
		||||
      unregisterClickDelegate(this.get('reblogKey'))
 | 
			
		||||
      unregisterClickDelegate(this.get('replyKey'))
 | 
			
		||||
      unregisterClickDelegate(this.get('optionsKey'))
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      IconButton
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +77,14 @@
 | 
			
		|||
      onReplyClick() {
 | 
			
		||||
        let statusId = this.get('statusId')
 | 
			
		||||
        goto(`/statuses/${statusId}/reply`)
 | 
			
		||||
      },
 | 
			
		||||
      async onOptionsClick() {
 | 
			
		||||
        let statusId = this.get('statusId')
 | 
			
		||||
        let accountId = this.get('accountId')
 | 
			
		||||
        let updateRelationshipPromise = updateProfileAndRelationship(accountId)
 | 
			
		||||
        let dialogs = await importDialogs()
 | 
			
		||||
        await updateRelationshipPromise
 | 
			
		||||
        dialogs.showStatusOptionsDialog(statusId)
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
| 
						 | 
				
			
			@ -115,10 +125,13 @@
 | 
			
		|||
        return status.favourited
 | 
			
		||||
      },
 | 
			
		||||
      statusId: (status) => status.id,
 | 
			
		||||
      accountId: (status) => status.account.id,
 | 
			
		||||
      disableReply: (statusId, timelineType, timelineValue) => timelineType === 'reply' && statusId === timelineValue,
 | 
			
		||||
      favoriteKey: (statusId, timelineType, timelineValue) => `fav-${timelineType}-${timelineValue}-${statusId}`,
 | 
			
		||||
      reblogKey: (statusId, timelineType, timelineValue) => `reblog-${timelineType}-${timelineValue}-${statusId}`,
 | 
			
		||||
      replyKey: (statusId, timelineType, timelineValue) => `reply-${timelineType}-${timelineValue}-${statusId}`,
 | 
			
		||||
      keyPrefix: (timelineType, timelineValue, statusId) => `${timelineType}-${timelineValue}-${statusId}`,
 | 
			
		||||
      favoriteKey: (keyPrefix) => `${keyPrefix}-fav`,
 | 
			
		||||
      reblogKey: (keyPrefix) => `${keyPrefix}-reblog`,
 | 
			
		||||
      replyKey: (keyPrefix) => `${keyPrefix}-reply`,
 | 
			
		||||
      optionsKey: (keyPrefix) => `${keyPrefix}-options`,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -8,9 +8,9 @@ test('Changes post privacy', async t => {
 | 
			
		|||
  await t.useRole(foobarRole)
 | 
			
		||||
    .expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)')
 | 
			
		||||
    .click(postPrivacyButton)
 | 
			
		||||
    .click('.post-privacy li:nth-child(2) button')
 | 
			
		||||
    .click('.generic-dialog-list li:nth-child(2) button')
 | 
			
		||||
    .expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Unlisted)')
 | 
			
		||||
    .click(postPrivacyButton)
 | 
			
		||||
    .click('.post-privacy li:nth-child(1) button')
 | 
			
		||||
    .click('.generic-dialog-list li:nth-child(1) button')
 | 
			
		||||
    .expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)')
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue