mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-03-28 17:42:48 +01:00
handle edge case when primary channel has a name (#421)
This commit is contained in:
@@ -72,17 +72,26 @@ test('buildChatTabModel returns sorted nodes and channel buckets', () => {
|
||||
['recent-node', 'iso-node', 'encrypted']
|
||||
);
|
||||
|
||||
assert.equal(model.channels.length, 2);
|
||||
const [channel0, channel1] = model.channels;
|
||||
assert.equal(channel0.index, 0);
|
||||
assert.equal(channel0.label, 'MediumFast');
|
||||
assert.equal(channel0.entries.length, 2);
|
||||
assert.deepEqual(channel0.entries.map(entry => entry.message.id), ['no-index', 'recent-default']);
|
||||
assert.equal(model.channels.length, 3);
|
||||
const [fallbackChannel, namedPrimaryChannel, secondaryChannel] = model.channels;
|
||||
|
||||
assert.equal(channel1.index, 1);
|
||||
assert.equal(channel1.label, 'BerlinMesh');
|
||||
assert.equal(channel1.entries.length, 2);
|
||||
assert.deepEqual(channel1.entries.map(entry => entry.message.id), ['iso-ts', 'recent-alt']);
|
||||
assert.equal(fallbackChannel.index, 0);
|
||||
assert.equal(fallbackChannel.label, 'Fallback');
|
||||
assert.equal(fallbackChannel.id, 'channel-0-fallback');
|
||||
assert.equal(fallbackChannel.entries.length, 1);
|
||||
assert.deepEqual(fallbackChannel.entries.map(entry => entry.message.id), ['no-index']);
|
||||
|
||||
assert.equal(namedPrimaryChannel.index, 0);
|
||||
assert.equal(namedPrimaryChannel.label, 'MediumFast');
|
||||
assert.equal(namedPrimaryChannel.id, 'channel-0-mediumfast');
|
||||
assert.equal(namedPrimaryChannel.entries.length, 1);
|
||||
assert.deepEqual(namedPrimaryChannel.entries.map(entry => entry.message.id), ['recent-default']);
|
||||
|
||||
assert.equal(secondaryChannel.index, 1);
|
||||
assert.equal(secondaryChannel.label, 'BerlinMesh');
|
||||
assert.equal(secondaryChannel.id, 'channel-1');
|
||||
assert.equal(secondaryChannel.entries.length, 2);
|
||||
assert.deepEqual(secondaryChannel.entries.map(entry => entry.message.id), ['iso-ts', 'recent-alt']);
|
||||
});
|
||||
|
||||
test('buildChatTabModel always includes channel zero bucket', () => {
|
||||
|
||||
@@ -58,7 +58,7 @@ export const CHAT_LOG_ENTRY_TYPES = Object.freeze({
|
||||
* }} params Aggregation inputs.
|
||||
* @returns {{
|
||||
* logEntries: Array<{ ts: number, type: string, nodeId?: string, nodeNum?: number }>,
|
||||
* channels: Array<{ index: number, label: string, entries: Array<{ ts: number, message: Object }> }>
|
||||
* channels: Array<{ id: string, index: number, label: string, entries: Array<{ ts: number, message: Object }> }>
|
||||
* }} Sorted tab model data.
|
||||
*/
|
||||
export function buildChatTabModel({
|
||||
@@ -138,23 +138,24 @@ export function buildChatTabModel({
|
||||
if (channelIndex != null && channelIndex > maxChannelIndex) {
|
||||
continue;
|
||||
}
|
||||
const safeIndex = channelIndex != null && channelIndex >= 0 ? channelIndex : 0;
|
||||
const bucketKey = safeIndex;
|
||||
let bucket = channelBuckets.get(bucketKey);
|
||||
if (!bucket) {
|
||||
bucket = {
|
||||
index: safeIndex,
|
||||
label: String(safeIndex),
|
||||
entries: [],
|
||||
hasExplicitName: false
|
||||
};
|
||||
channelBuckets.set(bucketKey, bucket);
|
||||
}
|
||||
|
||||
const channelName = normaliseChannelName(
|
||||
message.channel_name ?? message.channelName ?? message.channel_display ?? message.channelDisplay
|
||||
);
|
||||
if (channelName && !bucket.hasExplicitName) {
|
||||
const safeIndex = channelIndex != null && channelIndex >= 0 ? channelIndex : 0;
|
||||
const bucketKey = buildChannelBucketKey(safeIndex, channelName);
|
||||
let bucket = channelBuckets.get(bucketKey);
|
||||
if (!bucket) {
|
||||
bucket = {
|
||||
key: bucketKey,
|
||||
id: buildChannelTabId(bucketKey),
|
||||
index: safeIndex,
|
||||
label: channelName || String(safeIndex),
|
||||
entries: [],
|
||||
hasExplicitName: Boolean(channelName),
|
||||
isPrimaryFallback: bucketKey === '0'
|
||||
};
|
||||
channelBuckets.set(bucketKey, bucket);
|
||||
} else if (channelName && !bucket.hasExplicitName) {
|
||||
bucket.label = channelName;
|
||||
bucket.hasExplicitName = true;
|
||||
}
|
||||
@@ -164,16 +165,32 @@ export function buildChatTabModel({
|
||||
|
||||
logEntries.sort((a, b) => a.ts - b.ts);
|
||||
|
||||
if (!channelBuckets.has(0)) {
|
||||
channelBuckets.set(0, {
|
||||
let hasPrimaryBucket = false;
|
||||
for (const bucket of channelBuckets.values()) {
|
||||
if (bucket.index === 0) {
|
||||
hasPrimaryBucket = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasPrimaryBucket) {
|
||||
const bucketKey = '0';
|
||||
channelBuckets.set(bucketKey, {
|
||||
key: bucketKey,
|
||||
id: buildChannelTabId(bucketKey),
|
||||
index: 0,
|
||||
label: '0',
|
||||
entries: [],
|
||||
hasExplicitName: false
|
||||
hasExplicitName: false,
|
||||
isPrimaryFallback: true
|
||||
});
|
||||
}
|
||||
|
||||
const channels = Array.from(channelBuckets.values()).sort((a, b) => a.index - b.index);
|
||||
const channels = Array.from(channelBuckets.values()).sort((a, b) => {
|
||||
if (a.index !== b.index) {
|
||||
return a.index - b.index;
|
||||
}
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
for (const channel of channels) {
|
||||
channel.entries.sort((a, b) => a.ts - b.ts);
|
||||
}
|
||||
@@ -290,6 +307,52 @@ export function normaliseChannelName(value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildChannelBucketKey(index, channelName) {
|
||||
const safeIndex = Number.isFinite(index) ? Math.max(0, Math.trunc(index)) : 0;
|
||||
if (safeIndex === 0 && channelName) {
|
||||
return `0::${channelName.toLowerCase()}`;
|
||||
}
|
||||
return String(safeIndex);
|
||||
}
|
||||
|
||||
function buildChannelTabId(bucketKey) {
|
||||
if (bucketKey === '0') {
|
||||
return 'channel-0';
|
||||
}
|
||||
const slug = slugify(bucketKey);
|
||||
if (slug) {
|
||||
if (slug !== '0') {
|
||||
return `channel-${slug}`;
|
||||
}
|
||||
return `channel-${slug}-${hashChannelKey(bucketKey)}`;
|
||||
}
|
||||
return `channel-${hashChannelKey(bucketKey)}`;
|
||||
}
|
||||
|
||||
function slugify(value) {
|
||||
if (value == null) return '';
|
||||
return String(value)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
function hashChannelKey(value) {
|
||||
const input = String(value ?? '');
|
||||
if (!input) {
|
||||
return '0';
|
||||
}
|
||||
let hash = 0;
|
||||
for (let i = 0; i < input.length; i += 1) {
|
||||
hash = (hash * 31 + input.charCodeAt(i)) | 0;
|
||||
}
|
||||
if (hash < 0) {
|
||||
hash = (hash * -1) >>> 0;
|
||||
}
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
export const __test__ = {
|
||||
resolveTimestampSeconds,
|
||||
normaliseChannelIndex,
|
||||
|
||||
@@ -2743,13 +2743,15 @@ let messagesById = new Map();
|
||||
});
|
||||
|
||||
const channelTabs = filteredChannels.map(channel => ({
|
||||
id: `channel-${channel.index}`,
|
||||
id: channel.id || `channel-${channel.index}`,
|
||||
label: channel.label,
|
||||
content: buildChatFragment({
|
||||
entries: channel.entries.map(e => ({ ts: e.ts, item: e.message })),
|
||||
renderEntry: entry => createMessageChatEntry(entry.item),
|
||||
emptyLabel: 'No messages on this channel.'
|
||||
})
|
||||
}),
|
||||
index: channel.index,
|
||||
isPrimaryFallback: Boolean(channel.isPrimaryFallback)
|
||||
}));
|
||||
|
||||
const tabs = [
|
||||
@@ -2758,7 +2760,11 @@ let messagesById = new Map();
|
||||
];
|
||||
|
||||
const previousActive = chatEl.dataset?.activeTab || null;
|
||||
const defaultActive = channelTabs.find(tab => tab.id === 'channel-0')?.id || channelTabs[0]?.id || 'log';
|
||||
const defaultActive =
|
||||
channelTabs.find(tab => tab.isPrimaryFallback)?.id ||
|
||||
channelTabs.find(tab => tab.index === 0)?.id ||
|
||||
channelTabs[0]?.id ||
|
||||
'log';
|
||||
renderChatTabs({
|
||||
document,
|
||||
container: chatEl,
|
||||
|
||||
Reference in New Issue
Block a user