Skip to content

Commit

Permalink
right click menu for channels and messages
Browse files Browse the repository at this point in the history
  • Loading branch information
slatinsky committed Oct 26, 2022
1 parent ed23223 commit 4e75a4f
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 7 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ View your JSON [DiscordChatExporter](Tyrrrz/DiscordChatExporter) exports as if y
- Browse guild or direct messages
- Discord Markdown rendering support
- Command generator to extend your export with more messages (backup helper)
- Right click message and select "Open in discord" to jump to message in Discord



Expand Down Expand Up @@ -71,6 +72,7 @@ This tool consists of two parts:
## Preprocessor
For development make sure you have nodemon installed globally (used for hot reloading)
```
nvm use 16.16.0
npm install -g nodemon
```

Expand Down
24 changes: 24 additions & 0 deletions src/components/menu/ContextMenu.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script>
import { writable } from 'svelte/store';
import Menu from './Menu.svelte';
export let visible = writable(true);
async function onRightClick(e) {
if ($visible) {
$visible = false;
await new Promise(res => setTimeout(res, 100));
}
$visible = true;
}
function closeMenu() {
$visible = false;
}
</script>

{#if $visible}
<Menu on:click={closeMenu} on:clickoutside={closeMenu} {visible}>
<slot />
</Menu>
{/if}
56 changes: 56 additions & 0 deletions src/components/menu/Menu.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script>
import { onMount, setContext, createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import { position } from './menuStore';
const key = {};
export let visible
$: x = $position.x;
$: y = $position.y;
// whenever x and y is changed, restrict box to be within bounds
$: (() => {
if (!menuEl) return;
const rect = menuEl.getBoundingClientRect();
x = Math.min(window.innerWidth - rect.width, x);
if (y > window.innerHeight - rect.height) y -= rect.height;
})(x, y);
const dispatch = createEventDispatcher();
setContext(key, {
dispatchClick: () => dispatch('click')
});
let menuEl;
function onPageClick(e) {
if (e.target.classList.contains("menu-option")) {
$visible = false;
}
else {
$visible = true;
dispatch('clickoutside');
}
}
// $: console.log("isRightClickMenuVisible", $visible);
</script>

<style>
div {
position: absolute;
display: grid;
border: 1px solid #0003;
box-shadow: 2px 2px 5px 0px #0002;
background: #18191C;
}
</style>

<svelte:body on:click={onPageClick} />

{#if $visible}
<div transition:fade={{ duration: 100 }} bind:this={menuEl} style="top: {y}px; left: {x}px;">
<slot />
</div>
{/if}
48 changes: 48 additions & 0 deletions src/components/menu/MenuOption.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script>
import { onMount, getContext } from 'svelte';
import { createEventDispatcher } from 'svelte';
import { isMenuVisible } from './menuStore';
const key = {};
export let text = '';
export let visible;
const dispatch = createEventDispatcher();
// const { dispatchClick } = getContext(key);
const handleClick = e => {
dispatch('click');
visible.set(false);
}
</script>

<style>
div {
padding: 10px 20px;
cursor: default;
font-size: 14px;
display: flex;
align-items: center;
grid-gap: 5px;
color: #787A7E ;
}
div:hover {
background: #4752C4;
color: white;
}
/* div.disabled {
color: #0006;
}
div.disabled:hover {
background: white;
} */
</style>

<div on:click={handleClick} class="menu-option">
{#if text}
{text}
{:else}
<slot />
{/if}
</div>
9 changes: 9 additions & 0 deletions src/components/menu/menuStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { writable } from "svelte/store";

export const isMenuVisible = writable(false);
export const position = writable({ x: 0, y: 0 });

export const setMenuVisible = (e) => {
position.set({ x: e.clientX, y: e.clientY });
isMenuVisible.set(true);
}
2 changes: 1 addition & 1 deletion src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function copyTextToClipboard(text) {
return;
}
navigator.clipboard.writeText(text).then(function () {
console.log('Async: Copying to clipboard was successful!');
console.log('Async: Copying to clipboard was successful! Copied text: ' + text);
}, function (err) {
console.error('Async: Could not copy text: ', err);
});
Expand Down
16 changes: 15 additions & 1 deletion src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { nameRenderer, timestampFormat } from './settingsStore';
import { nameRenderer, timestampFormat, developerMode } from './settingsStore';
import { timestampRenderers } from './time';
let testDate = '2020-09-16T11:04:47.215+00:00';
Expand Down Expand Up @@ -38,6 +38,20 @@
{/each}
</div>

<p>Show memory usage</p>
<div class="radios">
{#key $nameRenderer}
<label>
<input type="radio" name="developerMode" value={true} bind:group={$developerMode} />
<span>Enabled</span>
</label>
<label>
<input type="radio" name="developerMode" value={false} bind:group={$developerMode} />
<span>Disabled</span>
</label>
{/key}
</div>

<style>
.title {
font-size: 28px;
Expand Down
33 changes: 29 additions & 4 deletions src/routes/channels/[guildId]/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<script>
import { developerMode } from '../../settingsStore';
import ContextMenu from '../../../components/menu/ContextMenu.svelte';
import MenuOption from '../../../components/menu/MenuOption.svelte';
import { isMenuVisible, setMenuVisible } from '../../../components/menu/menuStore';
import { onDestroy, onMount } from 'svelte';
import Header from './Header.svelte';
import SearchResults from './SearchResults.svelte';
import { searched, found_messages, filters } from './searchStores';
import { copyTextToClipboard } from '../../../helpers';
export let data;
let currentGuildId = data.guildId;
Expand Down Expand Up @@ -31,14 +36,27 @@
onDestroy(() => {
clearInterval(memoryInterval);
});
let rightClickId = null;
function onRightClick(e, id) {
$isMenuVisible = false // close previous menu
setTimeout(() => {
rightClickId = id;
setMenuVisible(e)
}, 0);
}
$: if (!$isMenuVisible) {
rightClickId = null
}
</script>
<div id="guild-layout" class={$searched ? 'with-search' : ''}>
<div id="channels">
<div class="guild-name">{data.guilds[data.guildId].name}</div>
{#if "usedJSHeapSize" in memoryUsage}
{#if $developerMode && "usedJSHeapSize" in memoryUsage}
<div>
Memory used:<br>
{Math.round(memoryUsage.usedJSHeapSize / 1024 / 1024)} MB / {Math.round(memoryUsage.jsHeapSizeLimit / 1024 / 1024)} MB ({Math.round(memoryUsage.usedJSHeapSize / memoryUsage.jsHeapSizeLimit * 100)}%)<br>
Expand All @@ -51,7 +69,7 @@
<div class="channel">
<a
href="/channels/{data.guildId}/{channel.id}"
class={data.channelId == channel.id ? 'selected' : ''}># {channel.name}</a
class={data.channelId == channel.id ? 'selected' : ''} on:contextmenu|preventDefault={e=>onRightClick(e, channel.id)}># {channel.name}</a
>
{#if channel.threads}
{#each channel.threads as thread}
Expand All @@ -61,7 +79,7 @@
<a
href="/channels/{data.guildId}/{thread.id}"
class={data.channelId == thread.id ? 'selected' : ''}
>
on:contextmenu|preventDefault={e=>onRightClick(e, thread.id)} >
<!-- svg -->
<svg
class="thread-svg-icon"
Expand Down Expand Up @@ -106,11 +124,18 @@
{#key $filters}
<SearchResults guild={data.guild} />
{/key}
</div>
{/if}
</div>
{#if rightClickId}
<ContextMenu let:visible>
<MenuOption
on:click={() => copyTextToClipboard(BigInt(rightClickId))}
text="Copy channel ID" {visible} />
</ContextMenu>
{/if}
<style>
#guild-layout {
display: grid;
Expand Down
38 changes: 37 additions & 1 deletion src/routes/channels/[guildId]/[channelId]/Message.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import { onMount, onDestroy } from 'svelte';
import { fade } from 'svelte/transition';
import MessageMarkdown from './MessageMarkdown.svelte';
import ContextMenu from '../../../../components/menu/ContextMenu.svelte';
import MenuOption from '../../../../components/menu/MenuOption.svelte';
import { setMenuVisible, isMenuVisible } from '../../../../components/menu/menuStore';
import { copyTextToClipboard } from '../../../../helpers';
import { renderTimestamp } from '../../../time';
export let message;
Expand Down Expand Up @@ -109,6 +113,18 @@
// console.log(message.reference, message.referencedMessage, messages.length, Object.keys(messages)[0]);
}
}
let rightClickMessage = null;
function onRightClick(e, message) {
$isMenuVisible = false // close previous menu
setTimeout(() => {
rightClickMessage = message;
setMenuVisible(e)
}, 0);
}
$: if (!$isMenuVisible) {
rightClickMessage = null
}
</script>
<!-- Rewritten https://github.com/Tyrrrz/DiscordChatExporter/blob/master/DiscordChatExporter.Core/Exporting/Writers/Html/MessageGroupTemplate.cshtml to svelte -->
Expand All @@ -117,7 +133,7 @@
{#if search&& message.searchPrevMessageChannelId && message.searchPrevMessageChannelId !== message.channelId}
<div class="channel-name"><a href="/channels/{guild.id}/{message.channelId}/"># {guild.channels[message.channelId]?.name}</a></div>
{/if}
<div class="chatlog__message-group" transition:fade={{ duration: 125 }}>
<div class="chatlog__message-group" transition:fade={{ duration: 125 }} on:contextmenu|preventDefault={e=>onRightClick(e, message)}>
<!-- <button on:click={()=>copyTextToClipboard(message.id)}>Copy ID</button> -->
<div
id="{search ? 'search-id-' : ''}{message.id}"
Expand Down Expand Up @@ -458,6 +474,26 @@
{/if}
</div>
{#if rightClickMessage}
<ContextMenu let:visible>
<MenuOption
on:click={() => copyTextToClipboard(BigInt(message.author.id))}
text="Copy author ID" {visible} />
<MenuOption
on:click={() => copyTextToClipboard(BigInt(message.id))}
text="Copy message ID" {visible} />
<MenuOption
on:click={() => copyTextToClipboard(`https://discord.com/channels/${BigInt(guild.id)}/${BigInt(message.channelId)}/${BigInt(message.id)}`)}
text="Copy message link" {visible} />
<MenuOption
on:click={() => window.open(`https://discord.com/channels/${BigInt(guild.id)}/${BigInt(message.channelId)}/${BigInt(message.id)}`,'_blank')}
text="Open in discord" {visible} />
<MenuOption
on:click={() => console.log(JSON.stringify(message, null, 2))}
text="Print message object to console" {visible} />
</ContextMenu>
{/if}
<style>
.not-loaded {
height: 75px;
Expand Down
10 changes: 10 additions & 0 deletions src/routes/settingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { writable } from "svelte/store";

export const nameRenderer = writable("nickname");
export const timestampFormat = writable(0);
export const developerMode = writable(false);

// restore from local storage
const storedNameRenderer = localStorage.getItem("nameRenderer");
Expand All @@ -15,6 +16,11 @@ if (storedTimestampFormat) {
timestampFormat.set(parseInt(storedTimestampFormat));
console.log("restored timestampFormat from local storage", storedTimestampFormat);
}
const storedDeveloperMode = localStorage.getItem("developerMode");
if (storedDeveloperMode) {
developerMode.set(storedDeveloperMode === 'true');
console.log("restored developerMode from local storage", storedDeveloperMode);
}

timestampFormat.subscribe((value) => {
// save to local storage
Expand All @@ -24,3 +30,7 @@ nameRenderer.subscribe((value) => {
// save to local storage
localStorage.setItem("nameRenderer", value);
});
developerMode.subscribe((value) => {
// save to local storage
localStorage.setItem("developerMode", value);
});

0 comments on commit 4e75a4f

Please sign in to comment.