mirror of
https://codeberg.org/emersion/gamja
synced 2025-03-13 07:48:37 +01:00
Display prefixes in member list
Closes: https://todo.sr.ht/~emersion/gamja/43
This commit is contained in:
parent
e90c07e64e
commit
be1ecf607d
@ -683,6 +683,7 @@ export default class App extends Component {
|
|||||||
if (this.isChannel(target)) {
|
if (this.isChannel(target)) {
|
||||||
this.addMessage(netID, target, msg);
|
this.addMessage(netID, target, msg);
|
||||||
}
|
}
|
||||||
|
this.handleMode(netID, msg);
|
||||||
break;
|
break;
|
||||||
case "NOTICE":
|
case "NOTICE":
|
||||||
case "PRIVMSG":
|
case "PRIVMSG":
|
||||||
@ -1218,6 +1219,89 @@ export default class App extends Component {
|
|||||||
this.setState({ dialog: null, networkDialog: null });
|
this.setState({ dialog: null, networkDialog: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMode(netID, msg) {
|
||||||
|
var client = this.clients.get(netID);
|
||||||
|
var chanmodes = client.isupport.get("CHANMODES") || irc.STD_CHANMODES;
|
||||||
|
var prefix = client.isupport.get("PREFIX") || "";
|
||||||
|
|
||||||
|
var prefixByMode = new Map(irc.parseMemberships(prefix).map((membership) => {
|
||||||
|
return [membership.mode, membership.prefix];
|
||||||
|
}));
|
||||||
|
|
||||||
|
var typeByMode = new Map();
|
||||||
|
var [a, b, c, d] = chanmodes.split(",");
|
||||||
|
Array.from(a).forEach((mode) => typeByMode.set(mode, "A"));
|
||||||
|
Array.from(b).forEach((mode) => typeByMode.set(mode, "B"));
|
||||||
|
Array.from(c).forEach((mode) => typeByMode.set(mode, "C"));
|
||||||
|
Array.from(d).forEach((mode) => typeByMode.set(mode, "D"));
|
||||||
|
prefixByMode.forEach((prefix, mode) => typeByMode.set(mode, "B"));
|
||||||
|
|
||||||
|
var channel = msg.params[0];
|
||||||
|
var change = msg.params[1];
|
||||||
|
var args = msg.params.slice(2);
|
||||||
|
|
||||||
|
var plusMinus = null;
|
||||||
|
var j = 0;
|
||||||
|
for (var i = 0; i < change.length; i++) {
|
||||||
|
if (change[i] === "+" || change[i] === "-") {
|
||||||
|
plusMinus = change[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!plusMinus) {
|
||||||
|
throw new Error("malformed mode string: missing plus/minus");
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode = change[i];
|
||||||
|
var add = plusMinus === "+";
|
||||||
|
|
||||||
|
var modeType = typeByMode.get(mode);
|
||||||
|
if (!modeType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg = null;
|
||||||
|
if (modeType === "A" || modeType === "B" || (modeType === "C" && add)) {
|
||||||
|
arg = args[j];
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefixByMode.has(mode)) {
|
||||||
|
this.handlePrefixChange(netID, channel, arg, prefixByMode.get(mode), add);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: If we eventually want to handle any mode changes with
|
||||||
|
// some special logic, this would be the place to. Not sure
|
||||||
|
// what we'd want to do in that regard, though.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrefixChange(netID, channel, nick, letter, add) {
|
||||||
|
var client = this.clients.get(netID);
|
||||||
|
var prefix = client.isupport.get("PREFIX") || "";
|
||||||
|
|
||||||
|
var prefixPrivs = new Map(irc.parseMemberships(prefix).map((membership, i) => {
|
||||||
|
return [membership.prefix, i];
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.setBufferState({ network: netID, name: channel }, (buf) => {
|
||||||
|
var members = new irc.CaseMapMap(buf.members);
|
||||||
|
var membership = members.get(nick);
|
||||||
|
if (add) {
|
||||||
|
var i = membership.indexOf(letter);
|
||||||
|
if (i < 0) {
|
||||||
|
membership += letter;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
membership = membership.replace(letter, "");
|
||||||
|
}
|
||||||
|
membership = Array.from(membership).sort((a, b) => {
|
||||||
|
return prefixPrivs.get(a) - prefixPrivs.get(b);
|
||||||
|
}).join("");
|
||||||
|
members.set(nick, membership);
|
||||||
|
return { members };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
setupKeybindings(this);
|
setupKeybindings(this);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ class MemberItem extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return this.props.nick !== nextProps.nick;
|
return this.props.nick !== nextProps.nick
|
||||||
|
|| this.props.membership != nextProps.membership;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick(event) {
|
handleClick(event) {
|
||||||
@ -18,14 +19,54 @@ class MemberItem extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
// XXX: If we were feeling creative we could generate unique colors for
|
||||||
|
// each item in ISUPPORT CHANMODES. But I am not feeling creative.
|
||||||
|
const membmap = {
|
||||||
|
"~": "owner",
|
||||||
|
"&": "admin",
|
||||||
|
"@": "op",
|
||||||
|
"%": "halfop",
|
||||||
|
"+": "voice",
|
||||||
|
};
|
||||||
|
const membclass = membmap[this.props.membership[0]] || "";
|
||||||
|
let membership = "";
|
||||||
|
if (this.props.membership) {
|
||||||
|
membership = html`
|
||||||
|
<span class="membership ${membclass}" title=${membclass}>
|
||||||
|
${this.props.membership}
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
};
|
||||||
return html`
|
return html`
|
||||||
<li>
|
<li>
|
||||||
<a href=${getNickURL(this.props.nick)} class="nick" onClick=${this.handleClick}>${this.props.nick}</a>
|
<a
|
||||||
|
href=${getNickURL(this.props.nick)}
|
||||||
|
class="nick"
|
||||||
|
onClick=${this.handleClick}
|
||||||
|
>${membership}${this.props.nick}</a>
|
||||||
</li>
|
</li>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortMembers(a, b) {
|
||||||
|
var [nickA, membA] = a, [nickB, membB] = b;
|
||||||
|
|
||||||
|
const prefixPrivs = ["~", "&", "@", "%", "+"]; // TODO: grab it from ISUPPORT PREFIX
|
||||||
|
var i = prefixPrivs.indexOf(membA[0]), j = prefixPrivs.indexOf(membB[0]);
|
||||||
|
if (i < 0) {
|
||||||
|
i = prefixPrivs.length;
|
||||||
|
}
|
||||||
|
if (j < 0) {
|
||||||
|
j = prefixPrivs.length;
|
||||||
|
}
|
||||||
|
if (i !== j) {
|
||||||
|
return i - j;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nickA < nickB ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
export default class MemberList extends Component {
|
export default class MemberList extends Component {
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return this.props.members !== nextProps.members;
|
return this.props.members !== nextProps.members;
|
||||||
@ -34,8 +75,13 @@ export default class MemberList extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<ul>
|
<ul>
|
||||||
${Array.from(this.props.members).sort().map(([nick, membership]) => html`
|
${Array.from(this.props.members).sort(sortMembers).map(([nick, membership]) => html`
|
||||||
<${MemberItem} key=${nick} nick=${nick} membership=${membership} onClick=${() => this.props.onNickClick(nick)}/>
|
<${MemberItem}
|
||||||
|
key=${nick}
|
||||||
|
nick=${nick}
|
||||||
|
membership=${membership}
|
||||||
|
onClick=${() => this.props.onNickClick(nick)}
|
||||||
|
/>
|
||||||
`)}
|
`)}
|
||||||
</ul>
|
</ul>
|
||||||
`;
|
`;
|
||||||
|
21
lib/irc.js
21
lib/irc.js
@ -41,6 +41,7 @@ export const ERR_SASLABORTED = "906";
|
|||||||
export const ERR_SASLALREADY = "907";
|
export const ERR_SASLALREADY = "907";
|
||||||
|
|
||||||
export const STD_CHANNEL_TYPES = "#&+!";
|
export const STD_CHANNEL_TYPES = "#&+!";
|
||||||
|
export const STD_CHANMODES = "beI,k,l,imnst";
|
||||||
|
|
||||||
const tagEscapeMap = {
|
const tagEscapeMap = {
|
||||||
";": "\\:",
|
";": "\\:",
|
||||||
@ -510,3 +511,23 @@ export class CaseMapMap {
|
|||||||
return this.entries();
|
return this.entries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseMemberships(str) {
|
||||||
|
if (str[0] !== "(") {
|
||||||
|
throw new Error("malformed ISUPPORT PREFIX value: expected opening parenthesis");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sep = str.indexOf(")");
|
||||||
|
if (sep < 0) {
|
||||||
|
throw new Error("malformed ISUPPORT PREFIX value: expected closing parenthesis");
|
||||||
|
}
|
||||||
|
|
||||||
|
var n = str.length - sep - 1;
|
||||||
|
var memberships = [];
|
||||||
|
for (var i = 0; i < n; i++) {
|
||||||
|
var mode = str[i + 1];
|
||||||
|
var prefix = str[sep + i + 1];
|
||||||
|
memberships.push({ mode, prefix });
|
||||||
|
}
|
||||||
|
return memberships;
|
||||||
|
}
|
||||||
|
16
style.css
16
style.css
@ -261,6 +261,22 @@ button.danger:hover {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.membership.owner {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.membership.admin {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
.membership.op {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
.membership.halfop {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
.membership.voice {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
#composer {
|
#composer {
|
||||||
color: var(--main-color);
|
color: var(--main-color);
|
||||||
background: var(--main-background);
|
background: var(--main-background);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user