Compare commits

..

1 Commits

Author SHA1 Message Date
Simon Ser
488e11eddc eslint: add extra rules 2024-11-16 12:59:42 +01:00
16 changed files with 1344 additions and 1083 deletions

View File

@ -1,4 +1,5 @@
image: alpine/latest # TODO switch back to alpine/latest once the "npm install" deadlock is fixed
image: alpine/edge
packages: packages:
- npm - npm
- rsync - rsync
@ -6,8 +7,6 @@ sources:
- https://codeberg.org/emersion/gamja.git - https://codeberg.org/emersion/gamja.git
secrets: secrets:
- 7a146c8e-aeb4-46e7-99bf-05af7486bbe9 # deploy SSH key - 7a146c8e-aeb4-46e7-99bf-05af7486bbe9 # deploy SSH key
artifacts:
- gamja/gamja.tar.gz
tasks: tasks:
- setup: | - setup: |
cd gamja cd gamja
@ -15,7 +14,6 @@ tasks:
- build: | - build: |
cd gamja cd gamja
npm run build npm run build
tar -czf gamja.tar.gz -C dist .
- lint: | - lint: |
cd gamja cd gamja
npm run -- lint --max-warnings 0 npm run -- lint --max-warnings 0

View File

@ -2,7 +2,7 @@
A simple IRC web client. A simple IRC web client.
<img src="https://fs.emersion.fr/protected/img/gamja/main.png" alt="Screenshot" width="800"> ![screenshot](https://l.sr.ht/7Npm.png)
## Usage ## Usage

View File

@ -124,7 +124,7 @@ const commands = [
if (args.length) { if (args.length) {
params.push(args.join(" ")); params.push(args.join(" "));
} }
getActiveClient(app).send({ command: "AWAY", params }); getActiveClient(app).send({command: "AWAY", params});
}, },
}, },
ban, ban,
@ -190,10 +190,9 @@ const commands = [
throw new Error("Missing nick"); throw new Error("Missing nick");
} }
let activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
getActiveClient(app).send({ getActiveClient(app).send({ command: "INVITE", params: [
command: "INVITE", nick, activeChannel,
params: [nick, activeChannel], ]});
});
}, },
}, },
{ ...join, name: "j" }, { ...join, name: "j" },

View File

@ -323,8 +323,6 @@ export default class App extends Component {
} }
if (queryParams.debug === "1") { if (queryParams.debug === "1") {
this.debug = true; this.debug = true;
} else if (queryParams.debug === "0") {
this.debug = false;
} }
if (window.location.hash) { if (window.location.hash) {
@ -762,7 +760,7 @@ export default class App extends Component {
// Open a new buffer if the message doesn't come from me or is a // Open a new buffer if the message doesn't come from me or is a
// self-message // self-message
if ((!client.isMyNick(msg.prefix.name) || client.isMyNick(bufName)) && (msg.command !== "PART" && msg.command !== "QUIT" && msg.command !== irc.RPL_MONONLINE && msg.command !== irc.RPL_MONOFFLINE)) { if ((!client.isMyNick(msg.prefix.name) || client.isMyNick(bufName)) && (msg.command !== "PART" && msg.comand !== "QUIT" && msg.command !== irc.RPL_MONONLINE && msg.command !== irc.RPL_MONOFFLINE)) {
this.createBuffer(serverID, bufName); this.createBuffer(serverID, bufName);
} }
@ -1075,7 +1073,6 @@ export default class App extends Component {
case "ACK": case "ACK":
case "BOUNCER": case "BOUNCER":
case "MARKREAD": case "MARKREAD":
case "REDACT":
// Ignore these // Ignore these
return []; return [];
default: default:
@ -2006,9 +2003,7 @@ export default class App extends Component {
this.lastFocusPingDate = now; this.lastFocusPingDate = now;
for (let client of this.clients.values()) { for (let client of this.clients.values()) {
if (client.status === Client.Status.REGISTERED) { client.send({ command: "PING", params: ["gamja"] });
client.send({ command: "PING", params: ["gamja"] });
}
} }
} }

View File

@ -43,7 +43,7 @@ function _Timestamp({ date, url, showSeconds }) {
if (showSeconds) { if (showSeconds) {
timestamp += ":--"; timestamp += ":--";
} }
return html`<span class="timestamp">${timestamp}</span>`; return html`<spam class="timestamp">${timestamp}</span>`;
} }
let hh = date.getHours().toString().padStart(2, "0"); let hh = date.getHours().toString().padStart(2, "0");
@ -94,7 +94,7 @@ function canFoldMessage(msg) {
class LogLine extends Component { class LogLine extends Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
return this.props.message !== nextProps.message || this.props.redacted !== nextProps.redacted; return this.props.message !== nextProps.message;
} }
render() { render() {
@ -143,18 +143,12 @@ class LogLine extends Component {
`; `;
} }
} else { } else {
lineClass = "talk";
let prefix = "<", suffix = ">"; let prefix = "<", suffix = ">";
if (msg.command === "NOTICE") { if (msg.command === "NOTICE") {
lineClass += " notice";
prefix = suffix = "-"; prefix = suffix = "-";
} }
if (this.props.redacted) { content = html`${prefix}${createNick(msg.prefix.name)}${suffix} ${linkify(stripANSI(text), onChannelClick)}`;
content = html`<i>This message has been deleted.</i>`;
} else {
content = html`${linkify(stripANSI(text), onChannelClick)}`;
lineClass += " talk";
}
content = html`<span class="nick-caret">${prefix}</span>${createNick(msg.prefix.name)}<span class="nick-caret">${suffix}</span> ${content}`;
} }
let allowedPrefixes = server.statusMsg; let allowedPrefixes = server.statusMsg;
@ -280,15 +274,9 @@ class LogLine extends Component {
break; break;
case "TOPIC": case "TOPIC":
let topic = msg.params[1]; let topic = msg.params[1];
if (topic) { content = html`
content = html` ${createNick(msg.prefix.name)} changed the topic to: ${linkify(stripANSI(topic), onChannelClick)}
${createNick(msg.prefix.name)} changed the topic to: ${linkify(stripANSI(topic), onChannelClick)} `;
`;
} else {
content = html`
${createNick(msg.prefix.name)} cleared the topic
`;
}
break; break;
case "INVITE": case "INVITE":
invitee = msg.params[0]; invitee = msg.params[0];
@ -715,7 +703,6 @@ export default class Buffer extends Component {
message=${msg} message=${msg}
buffer=${buf} buffer=${buf}
server=${server} server=${server}
redacted=${buf.redacted.has(msg.tags.msgid)}
onChannelClick=${onChannelClick} onChannelClick=${onChannelClick}
onNickClick=${onNickClick} onNickClick=${onNickClick}
onVerifyClick=${onVerifyClick} onVerifyClick=${onVerifyClick}
@ -821,7 +808,7 @@ export default class Buffer extends Component {
if (sep.length > 0) { if (sep.length > 0) {
children.push(createFoldGroup(foldMessages)); children.push(createFoldGroup(foldMessages));
children.push(...sep); children.push(sep);
foldMessages = []; foldMessages = [];
} }

View File

@ -42,8 +42,8 @@ function KeyBindingsHelp() {
} }
function CommandsHelp() { function CommandsHelp() {
let l = [...commands.keys()].map((name) => { let l = Object.keys(commands).map((name) => {
let cmd = commands.get(name); let cmd = commands[name];
let usage = [html`<strong>/${name}</strong>`]; let usage = [html`<strong>/${name}</strong>`];
if (cmd.usage) { if (cmd.usage) {

View File

@ -63,8 +63,7 @@ if (remoteHost) {
ws.close(); ws.close();
}); });
client.on("error", (err) => { client.on("error", () => {
console.log(err);
ws.close(WS_BAD_GATEWAY); ws.close(WS_BAD_GATEWAY);
}); });
}); });

View File

@ -7,7 +7,7 @@ gamja settings can be overridden using URL query parameters:
replaced with a randomly generated value) replaced with a randomly generated value)
- `channels`: comma-separated list of channels to join (`#` needs to be escaped) - `channels`: comma-separated list of channels to join (`#` needs to be escaped)
- `open`: [IRC URL] to open - `open`: [IRC URL] to open
- `debug`: enable debug logs if set to `1`, disable debug logs if set to `0` - `debug`: if set to 1, debug mode is enabled
Alternatively, the channels can be set with the URL fragment (ie, by just Alternatively, the channels can be set with the URL fragment (ie, by just
appending the channel name to the gamja URL). appending the channel name to the gamja URL).

View File

@ -23,34 +23,19 @@ export default [
destructuredArrayIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_",
}], }],
"no-var": "error", "no-var": "error",
"no-eval": "error",
"no-implied-eval": "error",
"eqeqeq": "error", "eqeqeq": "error",
"no-invalid-this": "error", "no-invalid-this": "error",
"no-extend-native": "error",
"prefer-arrow-callback": "error", "prefer-arrow-callback": "error",
"no-implicit-globals": "error", "require-atomic-updates": "error",
"no-throw-literal": "error",
"no-implicit-coercion": "warn", "no-implicit-coercion": "warn",
"object-shorthand": "warn", "object-shorthand": "warn",
"curly": "warn", //"sort-imports": ["warn", { ignoreMemberSort: true, allowSeparatedGroups: true }],
"camelcase": "warn", //"func-style": ["warn", "declaration"],
"@stylistic/js/indent": ["warn", "tab"], "@stylistic/js/indent": ["warn", "tab"],
"@stylistic/js/quotes": ["warn", "double"], "@stylistic/js/quotes": ["warn", "double"],
"@stylistic/js/semi": "warn", "@stylistic/js/semi": "warn",
"@stylistic/js/brace-style": ["warn", "1tbs"],
"@stylistic/js/comma-dangle": ["warn", "always-multiline"], "@stylistic/js/comma-dangle": ["warn", "always-multiline"],
"@stylistic/js/comma-spacing": "warn",
"@stylistic/js/arrow-parens": "warn", "@stylistic/js/arrow-parens": "warn",
"@stylistic/js/arrow-spacing": "warn",
"@stylistic/js/block-spacing": "warn",
"@stylistic/js/object-curly-spacing": ["warn", "always"],
"@stylistic/js/object-curly-newline": ["warn", {
multiline: true,
consistent: true,
}],
"@stylistic/js/array-bracket-spacing": ["warn", "never"],
"@stylistic/js/array-bracket-newline": ["warn", "consistent"],
}, },
}, },
]; ];

View File

@ -3,19 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; frame-src 'none'; object-src 'none'; connect-src *;"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; frame-src 'none'; object-src 'none'; connect-src *;">
<title>Cringe Studios Internet IRC Relay Chat Client</title> <title>gamja IRC client</title>
<meta name="description" content="Cringe Studios Internet IRC Relay Chat Client">
<meta property="og:image" content="https://nsfw.cringe-studios.com/Wikimedia_Community_Logo-IRC.png">
<meta property="og:type" content="website">
<meta property="og:url" content="https://irc.cringe-studios.com/">
<meta property="og:title" content="Cringe Studios Internet IRC Relay Chat Client">
<meta property="og:description" content="Cringe Studios Internet IRC Relay Chat Client">
<meta name="theme-color" content="#8000f0" data-react-helmet="true">
<link rel="icon" href="https://nsfw.cringe-studios.com/Wikimedia_Community_Logo-IRC.png">
<link rel="stylesheet" href="./style.css"> <link rel="stylesheet" href="./style.css">
<script type="module" src="./main.js"></script> <script type="module" src="./main.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -21,7 +21,6 @@ const permanentCaps = [
"draft/account-registration", "draft/account-registration",
"draft/chathistory", "draft/chathistory",
"draft/extended-monitor", "draft/extended-monitor",
"draft/message-redaction",
"draft/read-marker", "draft/read-marker",
"soju.im/bouncer-networks", "soju.im/bouncer-networks",

View File

@ -43,9 +43,9 @@ export function redirectAuthorize({ serverMetadata, clientId, redirectUri, scope
// TODO: use the state param to prevent cross-site request // TODO: use the state param to prevent cross-site request
// forgery // forgery
let params = { let params = {
"response_type": "code", response_type: "code",
"client_id": clientId, client_id: clientId,
"redirect_uri": redirectUri, redirect_uri: redirectUri,
}; };
if (scope) { if (scope) {
params.scope = scope; params.scope = scope;
@ -66,12 +66,12 @@ function buildPostHeaders(clientId, clientSecret) {
export async function exchangeCode({ serverMetadata, redirectUri, code, clientId, clientSecret }) { export async function exchangeCode({ serverMetadata, redirectUri, code, clientId, clientSecret }) {
let data = { let data = {
"grant_type": "authorization_code", grant_type: "authorization_code",
code, code,
"redirect_uri": redirectUri, redirect_uri: redirectUri,
}; };
if (!clientSecret) { if (!clientSecret) {
data["client_id"] = clientId; data.client_id = clientId;
} }
let resp = await fetch(serverMetadata.token_endpoint, { let resp = await fetch(serverMetadata.token_endpoint, {

View File

@ -1,6 +1,6 @@
{ {
"name": "Cringe Studios Internet IRC Relay Chat Client", "name": "gamja IRC client",
"short_name": "IRC", "short_name": "gamja",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"scope": "." "scope": "."

2278
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,13 +4,13 @@
"dependencies": { "dependencies": {
"htm": "^3.0.4", "htm": "^3.0.4",
"linkifyjs": "^4.1.3", "linkifyjs": "^4.1.3",
"preact": "^10.17.1" "preact": "10.17.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.11.1", "@eslint/js": "^9.11.1",
"@parcel/packager-raw-url": "^2.0.0", "@parcel/packager-raw-url": "^2.0.0",
"@parcel/transformer-webmanifest": "^2.0.0", "@parcel/transformer-webmanifest": "^2.0.0",
"@stylistic/eslint-plugin-js": "^3.1.0", "@stylistic/eslint-plugin-js": "^2.8.0",
"eslint": "^9.11.1", "eslint": "^9.11.1",
"globals": "^15.9.0", "globals": "^15.9.0",
"node-static": "^0.7.11", "node-static": "^0.7.11",

View File

@ -153,24 +153,10 @@ function trimStartCharacter(s, c) {
return s.substring(i); return s.substring(i);
} }
function getBouncerNetworkNameFromBuffer(state, buffer) {
let server = state.servers.get(buffer.server);
let network = state.bouncerNetworks.get(server.bouncerNetID);
if (!network) {
return null;
}
return getServerName(server, network);
}
/* Returns 1 if a should appear after b, -1 if a should appear before b, or /* Returns 1 if a should appear after b, -1 if a should appear before b, or
* 0 otherwise. */ * 0 otherwise. */
function compareBuffers(state, a, b) { function compareBuffers(a, b) {
if (a.server !== b.server) { if (a.server !== b.server) {
let aServerName = getBouncerNetworkNameFromBuffer(state, a);
let bServerName = getBouncerNetworkNameFromBuffer(state, b);
if (aServerName && bServerName && aServerName !== bServerName) {
return aServerName.localeCompare(bServerName);
}
return a.server > b.server ? 1 : -1; return a.server > b.server ? 1 : -1;
} }
if (isServerBuffer(a) !== isServerBuffer(b)) { if (isServerBuffer(a) !== isServerBuffer(b)) {
@ -231,7 +217,7 @@ function insertMessage(list, msg) {
} }
console.assert(insertBefore >= 0, ""); console.assert(insertBefore >= 0, "");
list = [...list]; list = [ ...list ];
list.splice(insertBefore, 0, msg); list.splice(insertBefore, 0, msg);
return list; return list;
} }
@ -375,11 +361,10 @@ export const State = {
hasInitialWho: false, // if channel hasInitialWho: false, // if channel
members: new irc.CaseMapMap(null, client.cm), // if channel members: new irc.CaseMapMap(null, client.cm), // if channel
messages: [], messages: [],
redacted: new Set(),
unread: Unread.NONE, unread: Unread.NONE,
prevReadReceipt: null, prevReadReceipt: null,
}); });
bufferList = bufferList.sort((a, b) => compareBuffers(state, a, b)); bufferList = bufferList.sort(compareBuffers);
let buffers = new Map(bufferList.map((buf) => [buf.id, buf])); let buffers = new Map(bufferList.map((buf) => [buf.id, buf]));
return [id, { buffers }]; return [id, { buffers }];
}, },
@ -680,14 +665,6 @@ export const State = {
return { members }; return { members };
}); });
case "REDACT":
target = msg.params[0];
if (client.isMyNick(target)) {
target = msg.prefix.name;
}
return updateBuffer(target, (buf) => {
return { redacted: new Set(buf.redacted).add(msg.params[1]) };
});
case irc.RPL_MONONLINE: case irc.RPL_MONONLINE:
case irc.RPL_MONOFFLINE: case irc.RPL_MONOFFLINE:
targets = msg.params[1].split(","); targets = msg.params[1].split(",");