Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support multi level grouping(Upper limit up to 4th level) #149

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 120 additions & 29 deletions app/scripts/content/channel-grouper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ import {
ChannelItemContext,
ChannelItemContextGroupType,
ChannelManipulator,
ConnectedGroupedChannelItemContext,
ConnectionType,
GroupedChannelItemContext,
hasDownConnection,
hasLeftConnection,
hasUpConnection,
removeDownConnection,
removeRightConnection,
} from './channel-manipulators/channel-manipulator';

// constants
const GROUPING_IDLE_CALLBACK_TIMEOUT = 3 * 1000;
const UPDATE_CHANNEL_LIST_MIN_INTERVAL = 200;
const REGEX_CHANNEL_MATCH = /(^.+?)[-_].*/;
const REGEX_CHANNEL_MATCH = /(^.+?)[-_]/;
const REGEX_CHANNEL_MATCH_FOR_SPLIT = /[-_]/;
const CHANNEL_NAME_ROOT = '-/';

/**
Expand Down Expand Up @@ -67,7 +75,7 @@ export default class ChannelGrouper {
return groupedChannelItemContext;
}

private applyGroupingToContexts(channelItemContexts: ChannelItemContext[]): GroupedChannelItemContext[] {
private applyGroupingToContexts(channelItemContexts: ChannelItemContext[]): ConnectedGroupedChannelItemContext[] {
let prefixeMap = new Map<number, string | null>();

// Process for root
Expand All @@ -86,48 +94,131 @@ export default class ChannelGrouper {
}
});

// Get prefix map
for (const context of channelItemContexts) {
const prefix = context.name.match(REGEX_CHANNEL_MATCH)?.[1] ?? null;
prefixeMap.set(context.index, prefix);
}

// Grouping
return channelItemContexts.map((context, index): GroupedChannelItemContext => {
const currentPrefix = prefixeMap.get(index) ?? null;
const prevPrefix = prefixeMap.get(index - 1) ?? null;
const nextPrefix = prefixeMap.get(index + 1) ?? null;

let groupType: ChannelItemContextGroupType | null = null;

const items = channelItemContexts.map((context, index): GroupedChannelItemContext => {
// If channelItemType is 'im' or 'mpim', skip grouping
if (context.channelItemType === 'im' || context.channelItemType === 'mpim') {
return {
...context,
prefix: null,
groupType: ChannelItemContextGroupType.Alone,
prefix2: null,
prefix3: null,
};
}
const split = context.name.split(REGEX_CHANNEL_MATCH_FOR_SPLIT);
return {
...context,
prefix: split[0] || null,
prefix2: split[1] || null,
prefix3: split[2] || null,
};
});
let connectedItems = items.map((context, index): ConnectedGroupedChannelItemContext => {
// If channelItemType is 'im' or 'mpim', skip grouping
if (context.channelItemType === 'im' || context.channelItemType === 'mpim') {
return {
...context,
connection1: null,
connection2: null,
connection3: null,
};
}
const prev = items[index - 1];
const next = items[index + 1];

let connection1: ConnectionType | null = null;
if (context.prefix != prev?.prefix && context.prefix != next?.prefix) {
connection1 = null;
} else if (context.prefix != prev?.prefix && context.prefix == next?.prefix) {
connection1 = '┬';
} else if (context.prefix == prev?.prefix && context.prefix == next?.prefix) {
connection1 = '├';
} else if (context.prefix == prev?.prefix && context.prefix != next?.prefix) {
connection1 = '└';
}

if (currentPrefix === null || (currentPrefix !== prevPrefix && currentPrefix !== nextPrefix)) {
groupType = ChannelItemContextGroupType.Alone;
} else {
if (prevPrefix !== currentPrefix && nextPrefix === currentPrefix) {
groupType = ChannelItemContextGroupType.Parent;
} else {
if (nextPrefix !== currentPrefix) {
groupType = ChannelItemContextGroupType.LastChild;
} else {
groupType = ChannelItemContextGroupType.Child;
}
let connection2: ConnectionType | null = null;
if (connection1 == null) {
connection2 = null;
} else if (context.prefix2 == null || context.prefix2 != prev?.prefix2 && context.prefix2 != next?.prefix2) {
connection2 = null;
} else if (context.prefix2 != prev?.prefix2 && context.prefix2 == next?.prefix2) {
connection2 = '┬';
} else if (context.prefix == prev?.prefix && context.prefix2 == prev?.prefix2) {
if (context.prefix2 == next?.prefix2) {
connection2 = '├';
} else if (context.prefix2 != next?.prefix2) {
connection2 = '└';
}
}

let connection3: ConnectionType | null = null;
if (connection1 == null || connection2 == null) {
connection2 = null;
} else if (context.prefix3 == null || context.prefix3 != prev?.prefix3 && context.prefix3 != next?.prefix3) {
connection3 = null;
} else if (context.prefix3 != prev?.prefix3 && context.prefix3 == next?.prefix3) {
connection3 = '┬';
} else if (context.prefix == prev?.prefix && context.prefix2 == prev?.prefix2 && context.prefix3 == prev?.prefix3) {
if (context.prefix3 == next?.prefix3) {
connection3 = '├';
} else if (context.prefix3 != next?.prefix3) {
connection3 = '└';
}
}

return {
...context,
prefix: currentPrefix,
groupType,
connection1,
connection2,
connection3,
yamadashy marked this conversation as resolved.
Show resolved Hide resolved
};
});

// change connections
connectedItems = connectedItems.reverse().map((context, index): ConnectedGroupedChannelItemContext => {
// If channelItemType is 'im' or 'mpim', skip grouping
if (context.channelItemType === 'im' || context.channelItemType === 'mpim') {
return {
...context,
connection1: null,
connection2: null,
connection3: null,
};
}
const prev = connectedItems[index + 1]; // reversed array
const next = connectedItems[index - 1];

if (context.connection3 !== null && !hasLeftConnection(context.connection3)) {
context.connection2 = removeRightConnection(context.connection2);
context.connection1 = removeRightConnection(context.connection1);
}
if (context.connection2 !== null && !hasLeftConnection(context.connection2)) {
context.connection1 = removeRightConnection(context.connection1);
}

if (hasDownConnection(context.connection1!) && !hasUpConnection(next?.connection1!)) {
context.connection1 = removeDownConnection(context.connection1)
}
if (hasDownConnection(context.connection2!) && !hasUpConnection(next?.connection2!)) {
context.connection2 = removeDownConnection(context.connection2)
}
if (hasDownConnection(context.connection3!) && !hasUpConnection(next?.connection3!)) {
context.connection3 = removeDownConnection(context.connection3)
}

if (context.connection1 === '┬' && context.prefix2 == null) {
context.connection1 = '┐';
}
if (context.connection2 === '┬' && context.prefix3 == null) {
context.connection2 = '┐';
}
if (context.connection3 === '┬' && context.name.substring(context.prefix!.length + 1 + context.prefix2!.length + 1 + context.prefix3!.length).length === 0) {
context.connection3 = '┐';
}

return context;
}).reverse();
yamadashy marked this conversation as resolved.
Show resolved Hide resolved
return connectedItems;
}
}
47 changes: 46 additions & 1 deletion app/scripts/content/channel-manipulators/channel-manipulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,52 @@ export interface ChannelItemContext {

export interface GroupedChannelItemContext extends ChannelItemContext {
prefix: string | null;
groupType: ChannelItemContextGroupType;
prefix2: string | null;
prefix3: string | null;
}

export type ConnectionType = '┬' | '└' | '├' | '│' | "─" | '┐' | " ";
export const hasUpConnection = (conn: ConnectionType | null): boolean => {
return conn == '└' || conn == '├' || conn == '│';
}
export const hasDownConnection = (conn: ConnectionType | null): boolean => {
return conn == '┬' || conn == '├' || conn == '│' || conn == '┐';
}
export const hasLeftConnection = (conn: ConnectionType | null): boolean => {
return conn == '┬' || conn == '─' || conn == '┐';
}
export const hasRightConnection = (conn: ConnectionType | null): boolean => {
return conn == '┬' || conn == '└' || conn == '├' || conn == '─';
}
export const removeRightConnection = (conn: ConnectionType | null): ConnectionType | null => {
if (conn === '┬') {
return '┐';
// } else if (conn === '└' || conn === "─") {
// return " ";
} else if (conn === '├') {
return '│';
} else if (conn === '│') {
return '│';
} else if (conn === '┐') {
return '┐';
}
return " ";
}

export const removeDownConnection = (conn: ConnectionType | null): ConnectionType | null => {
// '┬' | '└' | '├' | '│' | "─" | '┐'
if (conn === '┬') {
return '─';
} else if (conn === '├') {
return '└';
}
return " ";
yamadashy marked this conversation as resolved.
Show resolved Hide resolved
}

export interface ConnectedGroupedChannelItemContext extends GroupedChannelItemContext {
connection1: ConnectionType | null;
connection2: ConnectionType | null;
connection3: ConnectionType | null;
}

export interface ChannelManipulator {
Expand Down
112 changes: 82 additions & 30 deletions app/scripts/content/channel-manipulators/dom-channel-manipulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import {
ChannelItemContext,
ChannelItemContextGroupType,
ChannelItemType,
ChannelManipulator,
GroupedChannelItemContext,
ChannelManipulator, ConnectedGroupedChannelItemContext, ConnectionType,
GroupedChannelItemContext, hasLeftConnection, hasRightConnection,
} from './channel-manipulator';
import { logger } from '../logger';

export class DomChannelManipulator implements ChannelManipulator {
public getChannelItemContexts(): ChannelItemContext[] {
Expand Down Expand Up @@ -55,16 +56,14 @@ export class DomChannelManipulator implements ChannelManipulator {
});
}

public updateChannelItems(channelItemContexts: GroupedChannelItemContext[]): void {
public updateChannelItems(channelItemContexts: ConnectedGroupedChannelItemContext[]): void {
const $channelItems = $(SELECTOR_CHANNEL_LIST_ITEMS);

$channelItems.each((index: number, channelItem: HTMLElement) => {
const context = channelItemContexts[index];
const $channelName = $(channelItem).find(SELECTOR_CHANNEL_ITEM_NAME_SELECTOR);
const channelItemType = context.channelItemType;
const prefix: string | null = context.prefix;
const isParent = context.groupType === ChannelItemContextGroupType.Parent;
const isLastChild = context.groupType === ChannelItemContextGroupType.LastChild;
let separator = '';

// Skip direct message
Expand All @@ -82,43 +81,96 @@ export class DomChannelManipulator implements ChannelManipulator {
return;
}

if (context.groupType === ChannelItemContextGroupType.Alone) {
if (context.connection1 === null) {
$channelName.removeClass('scg-ch-parent scg-ch-child').text($channelName.data(DATA_KEY_RAW_CHANNEL_NAME));
} else {
let separatorPseudoClass: string;

if (isParent) {
separator = '┬';
separatorPseudoClass = 'scg-ch-separator-pseudo-bottom';
} else if (isLastChild) {
separator = '└';
separatorPseudoClass = 'scg-ch-separator-pseudo-top';
} else {
separator = '├';
separatorPseudoClass = 'scg-ch-separator-pseudo-both';
}

// Skip no changed
if (separator === $channelName.find('.scg-ch-separator').text()) {
if (context.connection1 === $channelName.find('.scg-ch-separator').text()) {
return;
}

$channelName
.removeClass('scg scg-ch-parent scg-ch-child')
.addClass(isParent ? 'scg scg-ch-parent' : 'scg scg-ch-child')
.addClass(hasLeftConnection(context.connection1!) ? 'scg scg-ch-parent' : 'scg scg-ch-child')
.empty()
.append([
$('<span>')
.addClass('scg scg-ch-prefix')
.text(prefix ?? ''),
$('<span>')
.addClass('scg scg-ch-separator ' + separatorPseudoClass)
.text(separator),
$('<span>')
.addClass('scg scg-ch-name')
.text(context.name.replace(/(^.+?)[-_](.*)/, '$2')),
]);
.append(this.getSpans(context));
}
});
}

getSpans(item: ConnectedGroupedChannelItemContext) {

let regex = new RegExp(`${item.prefix}[-_]?`, "g")
yamadashy marked this conversation as resolved.
Show resolved Hide resolved
if (item.connection2 == null && item.connection3 == null) {
return [
$('<span>')
.addClass('scg scg-ch-prefix')
.text(item.prefix ?? ''),
$('<span>')
.addClass(this.getPseudoClass(item.connection1!))
.text(item.connection1!),
$('<span>')
.addClass('scg scg-ch-name')
.text(item.name.replace(regex, "")),
];
}
if (item.connection3 == null) {
regex = new RegExp(`${item.prefix}[-_]${item.prefix2}[-_]?`, "g")
yamadashy marked this conversation as resolved.
Show resolved Hide resolved
logger.labeledLog(item.name, item.connection1, hasLeftConnection(item.connection1!))
return [
$('<span>')
.addClass('scg ' + (hasLeftConnection(item.connection1!) ? 'scg-ch-name' : 'scg-ch-prefix'))
.text(item.prefix ?? ''),
$('<span>')
.addClass(this.getPseudoClass(item.connection1!))
.text(item.connection1!),
$('<span>')
.addClass('scg ' + (hasRightConnection(item.connection1!) ? 'scg-ch-name' : 'scg-ch-prefix'))
.text(item.prefix2 ?? ''),
$('<span>')
.addClass(this.getPseudoClass(item.connection2!))
.text(item.connection2!),
$('<span>')
.addClass('scg scg-ch-name')
.text(item.name.replace(regex, "")),
];
}
regex = new RegExp(`${item.prefix}[-_]${item.prefix2}[-_]${item.prefix3}[-_]?`, "g")
yamadashy marked this conversation as resolved.
Show resolved Hide resolved
return [
$('<span>')
.addClass('scg ' + (hasLeftConnection(item.connection1!) ? 'scg-ch-name' : 'scg-ch-prefix'))
.text(item.prefix ?? ''),
$('<span>')
.addClass(this.getPseudoClass(item.connection1!))
.text(item.connection1!),
$('<span>')
.addClass('scg ' + (hasRightConnection(item.connection1!) ? 'scg-ch-name' : 'scg-ch-prefix'))
.text(item.prefix2 ?? ''),
$('<span>')
.addClass(this.getPseudoClass(item.connection2!))
.text(item.connection2!),
$('<span>')
.addClass('scg ' + (hasRightConnection(item.connection2!) ? 'scg-ch-name' : 'scg-ch-prefix'))
.text(item.prefix3 ?? ''),
$('<span>')
.addClass(this.getPseudoClass(item.connection3!))
.text(item.connection3!),
$('<span>')
.addClass('scg scg-ch-name')
.text(item.name.replace(regex, "")),
];
}

getPseudoClass(conn: ConnectionType) {
if (conn === '┬' || conn === '┐') {
return 'scg scg-ch-separator scg-ch-separator-pseudo-bottom';
} else if (conn === '└') {
return 'scg scg-ch-separator scg-ch-separator-pseudo-top';
} else if (conn === ' ' || conn === '─') {
return 'scg scg-ch-separator-no-vertical-line scg-ch-separator-pseudo-both';
}
return 'scg scg-ch-separator scg-ch-separator-pseudo-both';
}
}
Loading
Loading