diff --git a/public/logo/lichess-thick3.svg b/public/logo/lichess-thick3.svg new file mode 100644 index 000000000000..41cf5f4acdfe --- /dev/null +++ b/public/logo/lichess-thick3.svg @@ -0,0 +1,4 @@ + + + diff --git a/ui/msg/css/_convo.scss b/ui/msg/css/_convo.scss index 7edc10adf40e..3b198f00747e 100644 --- a/ui/msg/css/_convo.scss +++ b/ui/msg/css/_convo.scss @@ -5,11 +5,11 @@ @extend %flex-column; flex: 1 1 100%; &__head { - @extend %flex-center; + @extend %flex-between; flex: 0 0 $msg-top-height; background: $c-bg-zebra2; border-bottom: $border; - padding: 0 1vw; + padding: 0 1.5em 0 1vw; .user-link { @extend %flex-center-nowrap; flex: 0 0 auto; @@ -23,6 +23,15 @@ font-size: 1.5em; } } + &__actions { + @extend %flex-center; + } + } + &__action.button { + color: $c-font; + &.bad:hover { + color: $c-bad; + } } &__reply { @extend %flex-center-nowrap; @@ -37,6 +46,11 @@ background: $c-bg-box; resize: none; } + &__block { + flex: 1 1 auto; + text-align: center; + margin: .6em; + } } } } diff --git a/ui/msg/src/ctrl.ts b/ui/msg/src/ctrl.ts index 835ce73d7a15..b70a2519f0fe 100644 --- a/ui/msg/src/ctrl.ts +++ b/ui/msg/src/ctrl.ts @@ -55,4 +55,14 @@ export default class MsgCtrl { this.redraw(); } } + + block = () => { + const userId = this.data.convo?.thread.contact.id; + if (userId) network.block(userId).then(() => this.openConvo(userId)); + } + + unblock = () => { + const userId = this.data.convo?.thread.contact.id; + if (userId) network.unblock(userId).then(() => this.openConvo(userId)); + } } diff --git a/ui/msg/src/network.ts b/ui/msg/src/network.ts index 6d3d508917e9..3515beafa754 100644 --- a/ui/msg/src/network.ts +++ b/ui/msg/src/network.ts @@ -36,6 +36,20 @@ export function search(q: string) { }); } +export function block(u: string) { + return $.ajax({ + url: `/rel/block/${u}`, + method: 'post' + }); +} + +export function unblock(u: string) { + return $.ajax({ + url: `/rel/unblock/${u}`, + method: 'post' + }); +} + export function post(dest: string, text: string) { window.lichess.pubsub.emit('socket.send', 'msgSend', { dest, text }); } diff --git a/ui/msg/src/view/actions.ts b/ui/msg/src/view/actions.ts new file mode 100644 index 000000000000..8ab3900feb2e --- /dev/null +++ b/ui/msg/src/view/actions.ts @@ -0,0 +1,52 @@ +import { h } from 'snabbdom' +import { VNode } from 'snabbdom/vnode' +import { Convo } from '../interfaces' +import { bind } from './util'; +import MsgCtrl from '../ctrl'; + +export default function renderActions(ctrl: MsgCtrl, convo: Convo): VNode[] { + const user = convo.thread.contact, nodes = []; + const cls = 'msg-app__convo__action.button.button-empty'; + nodes.push( + h(`a.${cls}`, { + key: 'play', + attrs: { + 'data-icon': 'U', + href: `/?user=${user.name}#friend`, + title: ctrl.trans.noarg('challengeToPlay') + } + }) + ); + if (convo.relations.out === false) nodes.push( + h(`button.${cls}.unbad.text.hover-text`, { + key: 'unblock', + attrs: { + 'data-icon': 'k', + title: ctrl.trans.noarg('blocked'), + 'data-hover-text': ctrl.trans.noarg('unblock') + }, + hook: bind('click', ctrl.unblock) + }) + ); + else nodes.push( + h(`button.${cls}.bad`, { + key: 'block', + attrs: { + 'data-icon': 'k', + title: ctrl.trans.noarg('block') + }, + hook: bind('click', ctrl.block) + }) + ); + nodes.push( + h(`button.${cls}.bad.confirm`, { + key: 'report', + attrs: { + 'data-icon': '!', + href: '/report/flag', + title: ctrl.trans.noarg('report') + } + }) + ); + return nodes; +} diff --git a/ui/msg/src/view/convo.ts b/ui/msg/src/view/convo.ts index 704c6154cb4c..d513e2ee7c5e 100644 --- a/ui/msg/src/view/convo.ts +++ b/ui/msg/src/view/convo.ts @@ -3,7 +3,8 @@ import { VNode } from 'snabbdom/vnode' import { Convo } from '../interfaces' import { userName } from './util'; import renderMsgs from './msgs'; -import throttle from 'common/throttle'; +import renderActions from './actions'; +import renderTextarea from './textarea'; import MsgCtrl from '../ctrl'; export default function renderConvo(ctrl: MsgCtrl, convo: Convo): VNode { @@ -26,109 +27,11 @@ export default function renderConvo(ctrl: MsgCtrl, convo: Convo): VNode { ]), renderMsgs(ctrl, convo.msgs), h('div.msg-app__convo__reply', [ - h('textarea.msg-app__convo__reply__text', { - attrs: { - rows: 1, - autofocus: 1 - }, - hook: { - insert(vnode) { - setupTextarea(vnode.elm as HTMLTextAreaElement, user.id, ctrl.post); - } - } - }) + convo.relations.out === false || convo.relations.in === false ? + h('div.msg-app__convo__reply__block.text', { + attrs: { 'data-icon': 'k' } + }, 'This conversation is blocked.') : + renderTextarea(ctrl, user) ]) ]); } - -function renderActions(ctrl: MsgCtrl, convo: Convo): VNode[] { - const user = convo.thread.contact, nodes = []; - if (convo.relations.out) nodes.push( - h('button.msg-app__convo__action.text.hover-text', { - attrs: { - 'data-icon': 'h', - href: `/rel/unfollow/${user.id}`, - title: ctrl.trans.noarg('following'), - 'data-hover-text': ctrl.trans.noarg('unfollow') - } - }) - ); - else if (convo.relations.out === false) nodes.push( - h('button.msg-app__convo__action.text.hover-text', { - attrs: { - 'data-icon': 'k', - href: `/rel/unblock/${user.id}`, - title: ctrl.trans.noarg('blocked'), - 'data-hover-text': ctrl.trans.noarg('unblock') - } - }) - ); - else { - nodes.push( - h('a.msg-app__convo__action', { - attrs: { - 'data-icon': 'h', - href: `/rel/follow/${user.id}`, - title: ctrl.trans.noarg('follow') - } - }) - ); - nodes.push( - h('a.msg-app__convo__action', { - attrs: { - 'data-icon': 'k', - href: `/rel/block/${user.id}`, - title: ctrl.trans.noarg('block') - } - }) - ); - } - nodes.push( - h('a.msg-app__convo__action', { - attrs: { - 'data-icon': 'i', - href: '/report/flag', - title: ctrl.trans.noarg('report') - } - }) - ); - return nodes; -} - -function setupTextarea(area: HTMLTextAreaElement, contact: string, post: (text: string) => void) { - - // save the textarea content until sent - const storage = window.lichess.storage.make(`msg:area:${contact}`); - - // hack to automatically resize the textarea based on content - area.value = ''; - let baseScrollHeight = area.scrollHeight; - area.addEventListener('input', throttle(500, () => - setTimeout(() => { - const text = area.value.trim(); - area.rows = 1; - // the resize magic - if (text) area.rows = Math.min(10, 1 + Math.ceil((area.scrollHeight - baseScrollHeight) / 19)); - // and save content - storage.set(text); - }) - )); - - // restore previously saved content - area.value = storage.get() || ''; - if (area.value) area.dispatchEvent(new Event('input')); - - // send the content on { - if ((e.which == 10 || e.which == 13) && !e.shiftKey) { - setTimeout(() => { - const txt = area.value.trim(); - if (txt) post(txt); - area.value = ''; - area.dispatchEvent(new Event('input')); // resize the textarea - storage.remove(); - }); - } - }); - area.focus(); -} diff --git a/ui/msg/src/view/textarea.ts b/ui/msg/src/view/textarea.ts new file mode 100644 index 000000000000..30242385b0c8 --- /dev/null +++ b/ui/msg/src/view/textarea.ts @@ -0,0 +1,57 @@ +import { h } from 'snabbdom' +import { VNode } from 'snabbdom/vnode' +import { User } from '../interfaces' +import MsgCtrl from '../ctrl'; +import throttle from 'common/throttle'; + +export default function renderTextarea(ctrl: MsgCtrl, user: User): VNode { + return h('textarea.msg-app__convo__reply__text', { + attrs: { + rows: 1, + autofocus: 1 + }, + hook: { + insert(vnode) { + setupTextarea(vnode.elm as HTMLTextAreaElement, user.id, ctrl.post); + } + } + }); +} + +function setupTextarea(area: HTMLTextAreaElement, contact: string, post: (text: string) => void) { + + // save the textarea content until sent + const storage = window.lichess.storage.make(`msg:area:${contact}`); + + // hack to automatically resize the textarea based on content + area.value = ''; + let baseScrollHeight = area.scrollHeight; + area.addEventListener('input', throttle(500, () => + setTimeout(() => { + const text = area.value.trim(); + area.rows = 1; + // the resize magic + if (text) area.rows = Math.min(10, 1 + Math.ceil((area.scrollHeight - baseScrollHeight) / 19)); + // and save content + storage.set(text); + }) + )); + + // restore previously saved content + area.value = storage.get() || ''; + if (area.value) area.dispatchEvent(new Event('input')); + + // send the content on { + if ((e.which == 10 || e.which == 13) && !e.shiftKey) { + setTimeout(() => { + const txt = area.value.trim(); + if (txt) post(txt); + area.value = ''; + area.dispatchEvent(new Event('input')); // resize the textarea + storage.remove(); + }); + } + }); + area.focus(); +}