~emersion/public-inbox

gamja: Display prefixes (e.g. @, +) in member list v1 APPLIED

Drew DeVault: 1
 Display prefixes (e.g. @, +) in member list

 3 files changed, 140 insertions(+), 4 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/public-inbox/patches/23049/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH gamja] Display prefixes (e.g. @, +) in member list Export this patch

---
 components/app.js         | 85 +++++++++++++++++++++++++++++++++++++++
 components/member-list.js | 43 ++++++++++++++++++--
 style.css                 | 16 ++++++++
 3 files changed, 140 insertions(+), 4 deletions(-)

diff --git a/components/app.js b/components/app.js
index 38d3cb4..35305dd 100644
--- a/components/app.js
+++ b/components/app.js
@@ -204,6 +204,8 @@ export default class App extends Component {
		this.handleAddNetworkClick = this.handleAddNetworkClick.bind(this);
		this.handleNetworkSubmit = this.handleNetworkSubmit.bind(this);
		this.handleNetworkRemove = this.handleNetworkRemove.bind(this);
		this.handleMode = this.handleMode.bind(this);
		this.handlePrefixChange = this.handlePrefixChange.bind(this);
		this.dismissError = this.dismissError.bind(this);

		this.saveReceipts = debounce(this.saveReceipts.bind(this), 500);
@@ -682,6 +684,7 @@ export default class App extends Component {
			if (this.isChannel(target)) {
				this.addMessage(netID, target, msg);
			}
			this.handleMode(netID, msg);
			break;
		case "NOTICE":
		case "PRIVMSG":
@@ -1217,6 +1220,88 @@ export default class App extends Component {
		this.setState({ dialog: null, networkDialog: null });
	}

	handleMode(netID, msg) {
		const network = this.state.networks.get(netID);
		const chanmodes = network.isupport.get("CHANMODES") || "beI,k,l,imnst";
		const prefix = network.isupport.get("PREFIX") || "(ov)@+";
		const classify = (() => {
			let map = {};

			const [a, b, c, d] = chanmodes.split(",");
			Array.from(a).map(m => map[m] = "A");
			Array.from(b).map(m => map[m] = "B");
			Array.from(c).map(m => map[m] = "C");
			Array.from(d).map(m => map[m] = "D");

			const {
				modes, prefixes
			} = prefix.match(/^\((?<modes>[a-zA-Z]+)\)(?<prefixes>.*)$/).groups;
			for (let i = 0; i < modes.length; i++) {
				map[modes[i]] = { prefix: prefixes[i] };
			}

			return map;
		})();

		let client = this.clients.get(netID);
		let channel = msg.params[0];
		let change = msg.params[1];
		let args = msg.params.slice(2);

		let add = null;
		for (let i = 0, j = 0; i < change.length; i++) {
			switch (change[i]) {
			case '+':
				add = true;
				break;
			case '-':
				add = false;
				break;
			default:
				// 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.
				let classification = classify[change[i]];
				switch (classification) {
				case 'A':
				case 'B':
						j++;
						break;
				case 'C':
						if (add) {
							j++;
						}
						break;
				case 'D':
						// Does not accept a parameter
						break;
				default:
						if (typeof classification !== "object") {
							// Not really anything good to do here
							break;
						}
						let nick = args[j++];
						this.handlePrefixChange(netID, channel,
							nick, classification.prefix, add);
						break;
				}
				break;
			}
		}
	}

	handlePrefixChange(netID, channel, nick, prefix, add) {
		this.setBufferState({ network: netID, name: channel }, (buf) => {
			var members = new irc.CaseMapMap(buf.members);
			if (add) {
				members.set(nick, prefix);
			} else {
				members.set(nick, "");
			}
			return { members };
		});
	}

	componentDidMount() {
		setupKeybindings(this);
	}
diff --git a/components/member-list.js b/components/member-list.js
index 59f3efe..7576159 100644
--- a/components/member-list.js
+++ b/components/member-list.js
@@ -9,7 +9,8 @@ class MemberItem extends Component {
	}

	shouldComponentUpdate(nextProps) {
		return this.props.nick !== nextProps.nick;
		return this.props.nick !== nextProps.nick
			|| this.props.membership != nextProps.membership;
	}

	handleClick(event) {
@@ -18,9 +19,27 @@ class MemberItem extends Component {
	}

	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] || "";
		let membership = "";
		if (this.props.membership) {
			membership = html`<span class="membership ${membclass}">${this.props.membership}</span>`;
		};
		return html`
			<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>
		`;
	}
@@ -34,8 +53,24 @@ export default class MemberList extends Component {
	render() {
		return html`
			<ul>
				${Array.from(this.props.members).sort().map(([nick, membership]) => html`
					<${MemberItem} key=${nick} nick=${nick} membership=${membership} onClick=${() => this.props.onNickClick(nick)}/>
				${Array.from(this.props.members).sort((a, b) => {
					let [nick_a, memb_a] = a, [nick_b, memb_b] = b;
					const membs = ["~", "&", "@", "%", "+"];
					let ind_a = membs.indexOf(memb_a),
						ind_b = membs.indexOf(memb_b);
					ind_a = ind_a === -1 ? 999 : ind_a;
					ind_b = ind_b === -1 ? 999 : ind_b;
					if (ind_a !== ind_b) {
						return ind_a - ind_b;
					};
					return nick_a < nick_b ? -1 : 1;
				}).map(([nick, membership]) => html`
					<${MemberItem}
						key=${nick}
						nick=${nick}
						membership=${membership}
						onClick=${() => this.props.onNickClick(nick)}
					/>
				`)}
			</ul>
		`;
diff --git a/style.css b/style.css
index a86ccac..0ae6681 100644
--- a/style.css
+++ b/style.css
@@ -261,6 +261,22 @@ button.danger:hover {
	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 {
	color: var(--main-color);
	background: var(--main-background);
-- 
2.31.1
Merged with some pretty heavy refactoring to make the code more readable and
robust.

Thanks!