skyra-project / discord-components

Discord Webcomponents for real looking messages on the web
https://discord-components.js.org
MIT License
260 stars 41 forks source link

request: Non image Attachments #258

Closed itohatweb closed 5 months ago

itohatweb commented 2 years ago

Describe the solution you'd like A way to add non image attachments, aka. classic text files for example: image

Describe alternatives you've considered None

diamkil commented 1 year ago

I have this ejs code from an old project I used with a web component, would love to see this integrated here as I much prefer using React to build my UIs and I hope it can help others :)

EJS:

    <% const textRegex = /\.(?:txt|md|log|c\+\+|cpp|cc|c|h|hpp|mm|m|json|js|jsx|rb|rake|py|asm|fs|cgi|bat|rss|java|graphml|idb|lua|o|gml|prl|sls|conf|cmake|make|sln|vbe|cxx|wbf|vbs|r|wml|php|bash|applescript|fcgi|yaml|ex|exs|sh|ml|actionscript|html|xhtml|htm|xml|xls|xsd|css|styl|scss|go)$/i%>
    <% const isAudio = /\.(mp3|ogg|wav|flac)$/i.test(attachment.name) && attachment.url %>
    <message-attachment class='<%= isAudio ? ' audio' : '' %>'>
        <div class='data'>
            <img src='https://discord.com/assets/<%= attachment.iconHash %>.svg' alt='' class='icon'/>
            <div class='details'>
                <a href='<%= attachment.url %>' target='_blank'><%= attachment.name %></a>
                <span><%= attachment.formattedBytes %></span>
            </div>
            <% if (textRegex.test(attachment.name)) { %>
                <div class='preview'>
                    <svg width='20' height='20' viewBox='0 0 18 20'>
                        <path fill='currentColor'
                              d='M15 15H3V13H15Zm0-4H3V9H15Zm0-4H3V5H15ZM0 20l1.5-1.5L3 20l1.5-1.5L6 20l1.5-1.5L9 20l1.5-1.5L12 20l1.5-1.5L15 20l1.5-1.5L18 20V0L16.5 1.5 15 0 13.5 1.5 12 0 10.5 1.5 9 0 7.5 1.5 6 0 4.5 1.5 3 0 1.5 1.5 0 0Z'/>
                    </svg>
                </div>
            <% } %>
            <a href='<%= attachment.url %>' target='_blank' class='download'>
                <svg width='24' height='24' viewBox='0 0 24 24'>
                    <path fill='currentColor' d='M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z'/>
                </svg>
            </a>
        </div>
        <% if (isAudio) { %>
            <audio src='<%= attachment.url %>' controls controlsList='nodownload'></audio>
        <% } %>
    </message-attachment>

Web Component JS:

import e from '../utils/createElement'

class MessageAttachment extends HTMLElement {
  constructor () {
    super()
    this.onClick = this.onClick.bind(this)
  }

  connectedCallback () {
    const el = this.querySelector('.preview')
    if (el) el.addEventListener('click', this.onClick)
  }

  async onClick () {
    this.renderModal('LOADING')
    const file = this.querySelector('a').href
    const res = await fetch(file) //.replace('https://cdn.discordapp.com', window.GLOBAL_ENV.HOSTNAME)
    if (!res.ok) {
      return this.renderModal('ERRORED')
    }
    this.renderModal('FETCHED', await res.text())
  }

  renderModal (state, contents) {
    const name = this.querySelector('a').textContent
    let container = document.querySelector('.modal-container')
    if (!container) {
      const close = () => {
        const el = document.querySelector('.modal-container')
        el.classList.add('leaving')
        setTimeout(() => el.remove(), 150)
      }
      container = e('div', {
        class: 'modal-container entering',
        bindEvents: { click: close }
      }, e('div', {
        class: 'modal-inner',
        bindEvents: { click: e => e.stopPropagation() }
      }, e('div', { class: 'modal' }, [
        e('div', { class: 'modal-header' }, 'Attachment'),
        e('div', { class: 'modal-body' }, [
          e('div', null, [ e('b', null, 'name:'), ` ${name}` ]),
          e('div', null, [ e('b', null, 'File size:'), ` ${this.querySelector('span').textContent}` ]),
          e('div', { class: 'attachment-details' })
        ]),
        e('div', { class: 'modal-footer' }, [
          e('button', { bindEvents: { click: close } }, 'Got it'),
          e('a', {
            href: this.querySelector('a').href,
            target: '_blank'
          }, 'Download')
        ])
      ])))
      document.body.appendChild(container)
    }

    let el = null
    switch (state) {
      case 'LOADING':
        el = e('div', { class: 'spinner' })
        break
      case 'FETCHED':
        el = e('div', { class: 'contents' }, [
          e('div', { class: 'lang' }, name.split('.').pop()),
          e('div', { class: 'shitcode' }, [
            e('div', { class: 'lines' }),
            e('code', null, contents)
          ]),
          e('div', { class: 'copy' }, 'Copy')
        ])
        break
      case 'ERRORED':
        el = e('div', { class: 'error' }, 'Failed to load attachment contents.')
        break
    }
    const inner = container.querySelector('.attachment-details')
    inner.innerHTML = ''
    inner.appendChild(el)
  }
}

customElements.define('message-attachment', MessageAttachment)

SCSS:

message-attachment {
  display: block;
  border: 1px solid;
  max-width: 520px;
  width: 100%;
  padding: 10px;
  border-radius: 3px;
  border-color: rgba(47, 49, 54, .6);
  background-color: rgba(47, 49, 54, .3);

  &.audio {
    max-width: 400px;
  }

  .data {
    display: flex;
    align-items: center;

    .icon {
      width: 30px;
      height: 40px;
      margin-right: 8px;
      margin-top: 0;
      flex-shrink: 0;
    }

    .details {
      flex: 1;
      display: flex;
      flex-direction: column;

      a {
        opacity: .85;
        text-overflow: ellipsis;
        overflow: hidden;
        line-height: 16px;
      }

      span {
        font-weight: 300;
        color: #72767d;
        margin-right: 8px;
        line-height: 16px;
        font-size: 12px;
      }
    }

    .preview, .download {
      display: flex;
      align-items: center;
      justify-content: center;
      color: #4f545c;
      width: 24px;
      height: 24px;
      cursor: pointer;
    }
  }

  audio {
    width: 100%;
    margin-top: 10px;
  }
}

audio {
  height: 32px;
  width: 380px;
  border-radius: 3px;

  // noinspection CssInvalidPseudoSelector
  &::-webkit-media-controls-enclosure {
    border-radius: 3px;
  }
}
diamkil commented 1 year ago

I also have it for videos.

EJS:

        <div class='decorated-video'
             style='width: <%= attachment.displayMaxWidth %>; height: <%= attachment.displayMaxHeight %>'>
            <div class='metadata'>
                <div class='details'>
                    <span class='name'><%= attachment.name %></span>
                    <span class='size'><%= attachment.formattedBytes %></span>
                </div>
                <a href='<%= attachment.url %>' target='_blank' class='download'>
                    <svg width='24' height='24' viewBox='0 0 24 24'>
                        <path fill='currentColor' d='M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z'/>
                    </svg>
                </a>
            </div>
            <video src='<%= attachment.url %>' poster='<%= attachment.proxyURL %>?format=jpeg' controls
                   controlsList='nodownload' disablePictureInPicture></video>
        </div>

SCSS:

.decorated-video {
  width: 380px;
  max-height: 540px;
  position: relative;
  overflow: hidden;

  .metadata {
    z-index: 666;
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    display: flex;
    background-image: linear-gradient(0deg, transparent, rgba(0, 0, 0, .9));
    padding: 12px;
    transform: translateY(-100%);
    transition: transform .15s;
    color: #fff;

    .details {
      flex: 1;
      display: flex;
      flex-direction: column;

      .name, .size {
        font-weight: 500;
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
      }

      .name {
        font-size: 16px;
        line-height: 20px;
      }

      .size {
        font-size: 12px;
        line-height: 16px;
        opacity: .7;
      }
    }

    .download {
      opacity: .6;
      color: #fff;
    }
  }

  video {
    width: 100%;
  }

  &:hover .metadata {
    transform: translateY(0);
  }
}
favna commented 5 months ago

Added in v4.0.0-alpha.20

Use the discord-file-attachment component