some more stuff

This commit is contained in:
tezlm 2022-07-20 17:01:22 -07:00 committed by tezlm
parent 0000050037
commit 000005102e
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
33 changed files with 363 additions and 60 deletions

View file

@ -17,6 +17,8 @@
"vite": "^2.9.14"
},
"dependencies": {
"emojibase": "^6.1.0",
"emojibase-data": "^7.0.1",
"idb": "^7.0.2",
"linkifyjs": "^3.0.5",
"marked": "^4.0.17",

View file

@ -3,6 +3,8 @@ lockfileVersion: 5.4
specifiers:
'@sveltejs/vite-plugin-svelte': next
'@types/marked': ^4.0.3
emojibase: ^6.1.0
emojibase-data: ^7.0.1
idb: ^7.0.2
linkifyjs: ^3.0.5
marked: ^4.0.17
@ -11,6 +13,8 @@ specifiers:
vite: ^2.9.14
dependencies:
emojibase: 6.1.0
emojibase-data: 7.0.1_emojibase@6.1.0
idb: 7.0.2
linkifyjs: 3.0.5
marked: 4.0.17
@ -103,6 +107,18 @@ packages:
domhandler: 4.3.1
dev: false
/emojibase-data/7.0.1_emojibase@6.1.0:
resolution: {integrity: sha512-BLZpOdwyFpZ7lzBWyDtnxmKVm/SJMYgAfp1if3o6n1TVUMSXAf0nikONXl90LZuJ/m3XWPBkkubgCet2BsCGGQ==}
peerDependencies:
emojibase: '*'
dependencies:
emojibase: 6.1.0
dev: false
/emojibase/6.1.0:
resolution: {integrity: sha512-1GkKJPXP6tVkYJHOBSJHoGOr/6uaDxZ9xJ6H7m6PfdGXTmQgbALHLWaVRY4Gi/qf5x/gT/NUXLPuSHYLqtLtrQ==}
dev: false
/entities/2.2.0:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
dev: false

19
src/.pnpm-debug.log Normal file
View file

@ -0,0 +1,19 @@
{
"0 debug pnpm:scope": {
"selected": 1
},
"1 error pnpm": {
"errno": 1,
"code": "ELIFECYCLE",
"pkgid": "discard@0.1.0",
"stage": "build",
"script": "vite build",
"pkgname": "discard",
"err": {
"name": "pnpm",
"message": "discard@0.1.0 build: `vite build`\nExit status 1",
"code": "ELIFECYCLE",
"stack": "pnpm: discard@0.1.0 build: `vite build`\nExit status 1\n at EventEmitter.<anonymous> (/usr/lib/node_modules/pnpm/dist/pnpm.cjs:108597:20)\n at EventEmitter.emit (node:events:537:28)\n at ChildProcess.<anonymous> (/usr/lib/node_modules/pnpm/dist/pnpm.cjs:95163:18)\n at ChildProcess.emit (node:events:537:28)\n at maybeClose (node:internal/child_process:1091:16)\n at ChildProcess._handle.onexit (node:internal/child_process:302:5)"
}
}
}

View file

@ -23,7 +23,7 @@ function queueRelation(id, event) {
relations.get(id).push(event);
}
export function getTimeline(roomId) {
function getTimeline(roomId) {
return state.roomTimelines.get(roomId).live;
}
@ -68,7 +68,6 @@ export function send(roomId, type, content) {
}
export function handle(roomId, event, toStart = false) {
console.log(roomId, event)
if (event.type === "m.room.redaction" && !toStart) return redact(roomId, event);
if (!supportedEvents.includes(event.type)) return;
if (event.unsigned?.redacted_because) return;
@ -110,17 +109,23 @@ export function handle(roomId, event, toStart = false) {
eventId: original.eventId, // this is why the todo exists
original,
});
console.log("handle edit")
} else if (relation.rel_type === "m.annotation") {
const key = relation.key;
const key = relation.key;
if (!original.reactions) original.reactions = new Map();
if (!original.reactions.has(key)) original.reactions.set(key, [0, null]);
if (event.sender === state.userId) original.reactions.get(relation.key)[1] = id;
original.reactions.get(relation.key)[0]++;
}
state.events.set(id, format(roomId, event));
const slice = actions.slice.get(roomId);
slice.reslice();
if (roomId === state.focusedRoomId) state.slice.set(slice);
} else if(event.state_key) {
actions.rooms.update();
actions.spaces.update();
if (roomId === state.focusedRoomId) state.slice.set(actions.slice.get(roomId));
} else {
// TODO: update on state events
state.events.set(id, format(roomId, event));
addToTimeline(roomId, id, toStart);
if (relations.has(id)) {

View file

@ -138,7 +138,7 @@ export default class Api {
...(type && {
creation_content: { type },
}),
});
});
}
joinRoom() { throw "TODO" }

View file

@ -1,5 +1,10 @@
const defaultSettings = new Map();
defaultSettings.set("namecolors", "always");
defaultSettings.set("showjoinleave", true);
defaultSettings.set("showmembership", true);
defaultSettings.set("shownickavatar", true);
defaultSettings.set("shownametopic", true);
defaultSettings.set("autojoin", true);
export default class Settings extends Map {
constructor(data) {

View file

@ -4,6 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<base target="_blank" />
<title>discard</title>
<link href="/style.css" rel="stylesheet"/>
<link href="/fonts.css" rel="stylesheet"/>
<link href="/elements.css" rel="stylesheet"/>

View file

@ -2,6 +2,8 @@
import UserId from "./atoms/UserId.svelte";
import Button from "./atoms/Button.svelte";
import Input from "./atoms/Input.svelte";
import Textarea from "./atoms/Textarea.svelte";
import Dropdown from "./atoms/Dropdown.svelte";
import Popup from "./atoms/Popup.svelte";
import Logout from "./popups/Logout.svelte";
import Leave from "./popups/Leave.svelte";
@ -92,7 +94,7 @@ current.subscribe(reset);
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
margin-bottom: 16px;
margin: 8px 0;
}
pre {
@ -150,13 +152,13 @@ pre {
<div slot="content" style:margin-top="-8px">
{#if ["invite", "ban"].includes($current.membership)}
<div style:color="var(--fg-muted)">
{#if $current.membership === "ban"}
{#if $current.member.power < 0}{rnd(["violently", "thoroughly", "briskly"])} {/if}
{$current.powerLevel > 0 ? "fired" : rnd(["banned", "yeeted", "purged", "banished", "eliminated", "neutralized", "exiled", "expelled", "discontinued", "abolished", "discharged", "vanquished", "ejected"])}
{:else}
invited
{/if}
{formatDate($current.member.date, true)}
{#if $current.membership === "ban"}
{#if $current.member.power < 0}{rnd(["violently", "thoroughly", "briskly"])} {/if}
{$current.powerLevel > 0 ? "fired" : rnd(["banned", "yeeted", "purged", "banished", "eliminated", "neutralized", "exiled", "expelled", "discontinued", "abolished", "discharged", "vanquished", "ejected"])}
{:else}
invited
{/if}
{formatDate($current.member.date, true)}
</div>
<hr />
<div>
@ -177,7 +179,7 @@ pre {
{#if $current.membership === "ban"}
<Button type="danger" label="Unban" clicked={() => state.popup.set({ id: "todo" })} />
{:else}
<Button type="danger" label="Ban" clicked={() => state.popup.set({ id: "todo" })} />
<Button type="danger" label="Ban" clicked={() => state.popup.set({ id: "ban", member: $current.member, room: $current.room })} />
{/if}
{/if}
</div>
@ -235,5 +237,23 @@ pre {
<h2 slot="header">todo <div class="close" on:click={closePopup}>&#xd7;</div></h2>
<p slot="content">nice, you found a todo popup</p>
</Popup>
{:else if $current.id === "ban"}
<Popup>
<h2 slot="header">Ban {$current.member.name}?</h2>
<div slot="content">
<div class="title">Ban scope</div>
<Dropdown options={[
["Only this room", "room"],
["This space", "space"],
// ["This server", "server"],
]} />
<div class="title">Reason for ban</div>
<Textarea />
</div>
<div slot="footer">
<Button type="link" label="Nevermind!" clicked={closePopup} />
<Button type="danger" label="Do it!" clicked={() => state.popup.set({ id: "todo" })} />
</div>
</Popup>
{/if}
<svelte:window on:keydown={handleKeyDown} />

View file

@ -19,6 +19,10 @@ button {
transition: all 170ms;
}
:not(.link) {
min-width: 5rem;
}
.standard::after {
content: "";
position: absolute;

View file

@ -0,0 +1,46 @@
<script>
export let options;
export let selected = options[0];
export let changed = () => {};
let show = false;
function select(option) {
selected = option;
changed(option);
}
</script>
<style>
.option {
display: flex;
align-items: center;
margin: 8px 0;
padding: 10px;
background: var(--bg-rooms-members);
border-radius: 3px;
cursor: pointer;
}
.option:hover {
background: rgba(4,4,5,0.07);
}
.option.selected {
background: #40444b;
}
</style>
<div class="container">
<div class="dropdown" on:click={() => show = true} on:blur={() => show = false}>
{options.find(i => i[1] === selected)?.[0]}
</div>
{#if show}
<div class="options">
{#each options as option}
<div class="option" class:selected={selected === option[1]} on:click={() => select(option[1])}>
<div class="label">
{option[0]}
</div>
</div>
{/each}
</div>
{/if}
</div>

View file

@ -2,6 +2,7 @@
// should only be used in src/Popups.svelte
import { quadOut } from 'svelte/easing';
export let raw = false;
let current = state.popup;
function card() {
return {
@ -73,7 +74,7 @@ function opacity() {
padding: 16px;
}
</style>
<div class="background" on:click={() => state.popup.set({})} transition:opacity>
<div class="background" on:click={() => state.popup.set({ ...$current, id: null })} transition:opacity>
<div class="card" on:click={e => e.stopPropagation()} class:raw transition:card>
{#if raw}
<slot name="content"></slot>

View file

@ -1,6 +1,7 @@
<script>
export let fetchBackwards = async () => {};
export let fetchForwards = async () => {};
export let getDefault = () => {};
export let scrollTop, scrollMax;
export let items, itemKey;
export let margin = 400;
@ -12,8 +13,7 @@ let atItemsTop, atItemsBottom;
let paginating = false;
export function reset() {
atItemsTop = direction === "up";
atItemsBottom = direction === "down";
[atItemsTop, atItemsBottom] = getDefault();
if (direction === "down") {
queueMicrotask(() => scrollEl && (scrollEl.scrollTop = scrollEl.scrollHeight));
}

View file

@ -18,7 +18,7 @@ function isRead(room) {
if (index === -1) return null;
for (let i = index; i >= 0; i--) {
const event = state.events.get(timeline[i]);
if (event.type === "m.room.message" && event.special !== "redacted") return event.eventId;
if (["m.room.message", "m.room.create"].includes(event.type) && event.special !== "redacted") return event.eventId;
}
return null;
}
@ -145,9 +145,11 @@ function isRead(room) {
<div class="name">{room.name.toLowerCase().replace(/ /g, "-")}</div>
<div class="spacer"></div>
{#if room.pings}<div class="pings">{room.pings}</div>{/if}
{#if room.power.me >= room.power.getBase("invite") || room.joinRule === "public"}
<div class="settings" on:click={(e) => { e.stopImmediatePropagation(); state.popup.set({ id: "invite", type: "room", room }) }}>
<Tooltip tip="Send Invite">&#129730;</Tooltip>
</div>
{/if}
<div class="settings" on:click={(e) => { e.stopImmediatePropagation(); state.selectedRoom.set(room); state.scene.set("room-settings") }}>
<Tooltip tip="Edit Room">🔧</Tooltip>
</div>

View file

@ -21,7 +21,7 @@ function isRead(room) {
if (index === -1) return null;
for (let i = index; i >= 0; i--) {
const event = state.events.get(timeline[i]);
if (event.type === "m.room.message" && event.special !== "redacted") return event.eventId;
if (["m.room.message", "m.room.create"].includes(event.type) && event.special !== "redacted") return event.eventId;
}
return null;
}

View file

@ -15,10 +15,14 @@ async function create() {
const { room_id } = await state.api.createRoom({
name,
creator: state.userId,
...($current.type === "space" && { type: "m.space" }),
});
state.focusedRoomId = room_id;
state.focusedRoom.set(state.rooms.get(room_id));
state.popup.set({ type: $current.type });
const interval = setInterval(() => {
if (!state.rooms.has(room_id)) return;
actions._rooms.focus(state.rooms.get(room_id));
state.popup.set({ type: $current.type });
clearInterval(interval);
}, 10);
}
</script>
<style>
@ -35,13 +39,13 @@ async function create() {
}
</style>
<Popup>
<h3 slot="header">Create {capitalize($current.type)} <div class="close" on:click={() => state.popup.set({ type: $current.type })}>&#xd7;</div></h3>
<h3 slot="header">Create {capitalize($current.type)} <div class="close" on:click={() => state.popup.set({ id: null, ...$current })}>&#xd7;</div></h3>
<div slot="content" style="display: flex; flex-direction: column">
<span class="title">{capitalize($current.type)} Name</span>
<Input placeholder="awesome-{$current.type}" bind:value={name} submitted={create} autofocus />
</div>
<div slot="footer">
<Button type="link" label="Cancel" clicked={() => state.popup.set({ type: $current.type })} />
<Button type="link" label="Cancel" clicked={() => state.popup.set({ id: null, ...$current })} />
<Button type="primary" disabled={!name.length} label="Create {capitalize($current.type)}" clicked={create} />
</div>
</Popup>

View file

@ -23,11 +23,11 @@ function leave() {
{#if $current.room.joinRule !== "public"}
You won't be able to rejoin this {$current.type} unless you're re-invited.
{:else}
This {$current.type} is public and can be rejoined at any time. (Unless it gets made private!)
This {$current.type} is public and can be rejoined at any time. (Unless it's made private!)
{/if}
</p>
<div slot="footer">
<Button type="link" label="Cancel" clicked={() => state.popup.set({ room: $current.room })} />
<Button type="link" label="Cancel" clicked={() => state.popup.set({ ...$current, id: null })} />
<Button type="danger" label="Leave {capitalize($current.type)}" clicked={leave} />
</div>
</Popup>

View file

@ -31,22 +31,20 @@ function dividerProps(prev, ev) {
};
}
function atBottom() {
return $slice.atEnd();
}
async function fetchBackwards() {
const success = await actions.slice.backwards();
return [!success || $slice.events[0]?.type === "m.room.create", atBottom()];
return [!success || $slice.events[0]?.type === "m.room.create", $slice.atEnd()];
}
async function fetchForwards() {
const success = await actions.slice.forwards();
return [!success || $slice.events[0]?.type === "m.room.create", atBottom()];
return [!success || $slice.events[0]?.type === "m.room.create", $slice.atEnd()];
}
function refocus() {
if (scrollTo && scrollTop === scrollMax) queueMicrotask(() => scrollTo(-1));
if (scrollTo && scrollTop === scrollMax) {
queueMicrotask(() => scrollTo(-1));
}
}
let resizeTimeout;
@ -80,6 +78,7 @@ function checkShift(e) {
onDestroy(state.focusedRoom.subscribe(() => reset && reset()));
onDestroy(slice.subscribe(refocus));
onDestroy(upload.subscribe(refocus));
onDestroy(reply.subscribe(refocus));
onDestroy(edit.subscribe(refocus));
onDestroy(focused.subscribe(() => {
@ -91,6 +90,13 @@ onDestroy(focused.subscribe(() => {
setTimeout(() => id === $focused && focused.set(null), 2000);
}
}));
onDestroy(edit.subscribe(() => {
if (!$edit) return;
const element = document.querySelector(`[data-event-id="${$edit}"]`);
if (element) {
setTimeout(() => element.scrollIntoView({ behavior: "smooth", block: "center" }));
}
}));
</script>
<style>
.content {
@ -174,6 +180,7 @@ onDestroy(focused.subscribe(() => {
let:index={index}
{fetchBackwards}
{fetchForwards}
getDefault={() => [$slice.events[0]?.type === "m.room.create", $slice.atEnd()]}
>
<div slot="top" style="margin-top: auto"></div>
<div slot="placeholder-start" class="tall" style="align-items: end"><Placeholder /></div>

View file

@ -137,6 +137,7 @@ async function sendMessage(content, roomId = $room.roomId) {
onDestroy(state.focusedRoom.subscribe(() => queueMicrotask(() => textarea?.focus())));
onDestroy(reply.subscribe(() => queueMicrotask(() => textarea?.focus())));
onDestroy(edit.subscribe(() => queueMicrotask(() => $edit || textarea?.focus())));
</script>
<style>
.container {

View file

@ -104,7 +104,6 @@ function handleClick(e) {
.author {
font-weight: 500;
cursor: pointer;
margin-right: 0.25rem;
display: inline-block;
height: 22px;
}
@ -113,6 +112,19 @@ function handleClick(e) {
text-decoration: underline;
}
.badge {
display: inline-block;
padding: 0 4px;
color: var(--fg-notice);
background: var(--color-accent);
font-family: var(--font-display);
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
border-radius: 3px;
}
.side {
position: absolute;
display: block;
@ -169,6 +181,10 @@ time {
{#if header}
<div class="top">
<span class="author" style:color={getColor(sender)}>{sender.name || event.sender}</span>
{#if event.content.msgtype === "m.notice"}
<div class="badge">bot</div>
{/if}
<span style="margin-right: 0.25rem;"></span>
<time datetime={event.date.toISOString()} style="display: inline">{formatDate(event.date)}</time>
</div>
{/if}

View file

@ -97,8 +97,7 @@ img {
}
.text {
display: inline-block;
width: 100%;
display: inline;
word-break: break-word;
}
@ -150,17 +149,17 @@ img {
<div class="text" class:emote={type === "m.emote"}>
{#if type === "m.emote"}*{/if}
{@html parseHtml(content.formatted_body.trim()).trim()}
{#if edited}
<span class="edited">(edited)</span>
{/if}
</div>
{#if edited}
<span class="edited">(edited)</span>
{/if}
{:else if content.body}
<div class="text" class:emote={type === "m.emote"}>
{#if type === "m.emote"}*{/if}
{@html parseHtml(content.body.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;"), { linkify: true })}
{#if edited}
<span class="edited">(edited)</span>
{/if}
</div>
{#if edited}
<span class="edited">(edited)</span>
{/if}
{/if}
</div>

View file

@ -1,6 +1,5 @@
<script>
import { marked } from "marked";
import Button from "../../atoms/Button.svelte";
export let event;
let textarea;
@ -24,6 +23,7 @@ async function handleKeyDown(e) {
} else if (e.key === "Escape") {
$edit = null;
} if (e.key === "ArrowUp") {
if (input !== event.content.body) return;
if (textarea.selectionStart !== 0) return;
if (textarea.selectionEnd !== 0) return;
for (let i = $slice.events.findIndex(i => i.eventId === $edit) - 1; i >= 0; i--) {
@ -34,6 +34,7 @@ async function handleKeyDown(e) {
}
}
} if (e.key === "ArrowDown") {
if (input !== event.content.body) return;
if (textarea.selectionStart !== input.length) return;
if (textarea.selectionEnd !== input.length) return;
for (let i = $slice.events.findIndex(i => i.eventId === $edit) + 1; i < $slice.events.length; i++) {
@ -43,6 +44,7 @@ async function handleKeyDown(e) {
return;
}
}
$edit = null;
}
}
@ -51,6 +53,9 @@ function handleInput() {
}
function save() {
if (!input.trim()) {
// TODO: delete message
}
const newEvent = {
"m.relates_to": {
rel_type: "m.replace",

View file

@ -8,12 +8,14 @@ import Nitor from '../settings/user/Nitor.svelte';
import Appearance from '../settings/user/Appearance.svelte';
// Text and Images
import RoomsSpaces from '../settings/user/RoomsSpaces.svelte';
import Notifications from '../settings/user/Notifications.svelte';
import Keybinds from '../settings/user/Keybinds.svelte';
// Language
import Changelog from '../settings/user/Changelog.svelte';
import Help from '../settings/user/Help.svelte';
import Version from '../settings/user/Version.svelte';
import Testbed from '../settings/user/Testbed.svelte'; // temporary
const views = [
@ -23,6 +25,7 @@ const views = [
{ view: Nitor, name: "Nitor", color: "#ff73fa", raw: true },
null,
{ view: Appearance, name: "Appearance" },
{ view: RoomsSpaces, name: "Rooms and Spaces" },
{ view: null, name: "Text and Images" },
{ view: Notifications, name: "Notifications" },
{ view: Keybinds, name: "Keybinds" },
@ -31,6 +34,7 @@ const views = [
{ view: Testbed, name: "Testbed" },
{ view: Changelog, name: "Changelog" },
{ view: Help, name: "Help" },
{ view: Version, name: "Credits" },
null,
{ name: "Log Out", color: "var(--color-red)", clicked: () => state.popup.set({ id: "logout", confirm: actions.client.logout }) },
];

View file

@ -24,6 +24,7 @@ function showPopup(member) {
id: "member",
membership,
member,
room: $room,
canBan: power.me > power.getUser(member.userId) && power.me >= power.getBase("ban"),
canKick: power.me > power.getUser(member.userId) && power.me >= power.getBase("kick"),
canInvite: power.me > power.getUser(member.userId) && power.me >= power.getBase("invite"),

View file

@ -1 +1,16 @@
<script>
export let room;
const children = $room.state
.filter(i => i.type === "m.space.child")
.filter(i => Object.keys(i.content).length)
</script>
<p>todo: list rooms, create rooms, remove rooms</p>
<br />
<table>
<tr><th>id</th><th>suggested</th></tr>
{#each children as child}
<tr><td>{child.state_key}</td><td>{child.content.suggested}</td></tr>
{:else}
<tr><i>no rooms</i></tr>
{/each}
</table>

View file

@ -1,6 +1,8 @@
<script>
import Radio from "../../atoms/Radio.svelte";
import Toggle from "../../atoms/Toggle.svelte";
// TODO: live update settings
const opts = state.settings;
</script>
<style>
.title {
@ -11,6 +13,12 @@ import Radio from "../../atoms/Radio.svelte";
text-transform: uppercase;
margin-bottom: 8px;
}
.option {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
</style>
<div class="preview">
<!-- TODO: preview message -->
@ -18,8 +26,25 @@ import Radio from "../../atoms/Radio.svelte";
<div class="title">User colors</div>
<Radio
options={[["Always", "always"], ["If they have a power level", "power"], ["Never", "never"]]}
selected={state.settings.get("namecolors")}
changed={(op) => state.settings.put("namecolors", op)}
selected={opts.get("namecolors")}
changed={(op) => opts.put("namecolors", op)}
/>
<br>
<p>todo, low priority: show/hide join, leave, invite, nick/avatar change, room name/topic change</p>
<br />
<div class="title">Room timeline</div>
<div class="option">
<b>Show join and leaves</b>
<Toggle checked={opts.get("showjoinleave")} toggled={(val) => opts.put("showjoinleave", val)}/>
</div>
<div class="option">
<b>Show invites, kicks, bans, and unbans</b>
<Toggle checked={opts.get("showmembership")} toggled={(val) => opts.put("showmembership", val)}/>
</div>
<div class="option">
<b>Show name and avatar changes</b>
<Toggle checked={opts.get("shownickavatar")} toggled={(val) => opts.put("shownickavatar", val)}/>
</div>
<div class="option">
<b>Show room name and topic changes</b>
<Toggle checked={opts.get("shownametopic")} toggled={(val) => opts.put("shownametopic", val)}/>
</div>
<br />

View file

@ -4,4 +4,4 @@ h2 {
font-weight: 500;
}
</style>
<p>you're using a pre-alpha build! nothing will be here until the first version is released</p>
<p>you're using a pre-alpha {"bui"+"ld"}! nothing will be here until the first version is released</p>

View file

@ -0,0 +1,18 @@
<script>
import Toggle from "../../atoms/Toggle.svelte";
// TODO: live update settings
</script>
<style>
.setting {
display: flex;
justify-content: space-between;
}
.setting > div {
font-weight: 500;
}
</style>
<div class="setting">
<div>Automatically join suggested rooms</div>
<Toggle checked={state.settings.get("autojoin")} toggled={(val) => state.settings.put("autojoin", val)} />
</div>

View file

@ -1,8 +1,28 @@
<script>
import Toggle from "../../atoms/Toggle.svelte";
import emojis from "emojibase-data/en/compact.json";
import Input from "../../atoms/Input.svelte";
const groups = [[], [], null, [], [], [], [], [], [], []];
let selected;
console.log(state.settings);
for (let emoji of emojis) {
if (emoji.group === 2) continue;
groups[emoji.group ?? 8].push(emoji);
}
function getGroupName(id) {
switch(id) {
case 0: return "Smilies";
case 1: return "People";
case 2: return "???";
case 3: return "Animals & nature";
case 4: return "Food & Drink";
case 5: return "Travel & Places";
case 6: return "Activities";
case 7: return "Objects";
case 8: return "Symbols";
case 9: return "Flags";
}
}
</script>
<style>
.selector {
@ -18,12 +38,27 @@ console.log(state.settings);
}
.emojis {
display: grid;
grid-template-columns: repeat(auto-fill, 40px);
background: var(--bg-rooms-members);
overflow-y: scroll;
}
.group {
display: grid;
grid-template-columns: repeat(auto-fill, 40px);
}
.emojis .label {
position: sticky;
top: 0;
padding: 8px;
background: var(--bg-rooms-members);
font-family: var(--font-display);
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
}
.emoji {
height: 40px;
width: 40px;
@ -39,6 +74,12 @@ console.log(state.settings);
background: var(--color-gray-light);
}
.preview {
display: flex;
background: var(--bg-misc);
align-items: center;
}
.header {
grid-column: 1/3;
background: var(--bg-rooms-members);
@ -95,18 +136,29 @@ console.log(state.settings);
</style>
<p>testbed for random stuff</p>
<br>
<Toggle />
<div class="selector">
<div class="header">
<Input small placeholder="search do it you wont" />
</div>
<div style="grid-row: 2/4; background: var(--bg-spaces)"></div>
<div class="emojis">
{#each new Array(100) as _, i}
<div class="emoji">{String.fromCodePoint(i + 127744)}</div>
{#each groups as emojis, i}
{#if emojis}
<div class="label">{getGroupName(i)}</div>
<div class="group">
{#each emojis as emoji}
<div class="emoji" on:mouseover={() => selected = emoji}>{emoji.unicode}</div>
{/each}
</div>
{/if}
{/each}
</div>
<div style="background: var(--bg-misc)"></div>
<div class="preview">
{#if selected}
<div class="preview">{selected?.unicode}</div>
<b>{selected.label}</b>
{/if}
</div>
</div>
<br>
<div class="popout">

View file

@ -0,0 +1,20 @@
<script>
// TODO: this looks horrible, add styling and stuff later
</script>
{build.package.name} version {build.package.version}, commit <a href="https://git.celery.eu.org/tezlm/discard/commit/{build.commit}">{build.commit}</a>
<br>
<br>
made with the following dependencies:
<ul>
{#each Object.entries(build.package.dependencies) as [pkg, ver]}
<li><a href="https://npmjs.com/package/{pkg}">{pkg}</a> version {ver.replace(/^\^/, "")}</li>
{/each}
</ul>
<br>
along with the following, for development:
<ul>
{#each Object.entries(build.package.devDependencies) as [pkg, ver]}
<li><a href="https://npmjs.com/package/{pkg}">{pkg}</a> version {ver.replace(/^\^/, "")}</li>
{/each}
</ul>

View file

@ -84,14 +84,18 @@ function showPopup(id, opts) {
<span>{$focusedSpace ? $focusedSpace?.name ?? "unknown" : "Home"}</span>
{#if $focusedSpace && showMenu}
<div class="menu" transition:zoomIn>
{#if $focusedSpace.power.me >= $focusedSpace.power.getBase("invite") || $focusedSpace.joinRule === "public"}
<div class="item" on:click={() => showPopup("invite")}><span class="color-accent">Invite People</span></div>
<div class="spacer"></div>
{/if}
<div class="item" on:click={() => { state.selectedRoom.set($focusedSpace); state.scene.set("space-settings") }}>Space Settings</div>
<div class="spacer"></div>
{#if $focusedSpace.power.me >= $focusedSpace.power.getState("m.space.child")}
<div class="item" on:click={() => showPopup("create", { type: "room", confirm: todo })}>Create Room</div>
<div class="item" on:click={() => showPopup("create", { confirm: todo })}>Create Subspace</div>
<div class="item" on:click={todo}>Add Existing Room</div>
<div class="spacer"></div>
{/if}
<div class="item" on:click={() => showPopup("leave", { confirm: () => queueMicrotask(todo) })}><span class="color-red">Leave Space</span></div>
</div>
{/if}

View file

@ -4,7 +4,7 @@ import sanitizeHtml from "sanitize-html";
import { parseMxc } from "./content.js";
const permittedHtmlTagsInline = [
"font", "del", "p", "a", "sup", "sub", "b", "i",
"font", "del", /*"p", */ "a", "sup", "sub", "b", "i",
"u", "strong", "em", "strike", "code", "br", "span",
];
@ -88,6 +88,7 @@ const sanitizeOpts = {
span: transformFontSpanTags,
a: transformATag,
img: transformImgTag,
p: "span",
},
nonTextTags: ["style", "script", "textarea", "option", "noscript", "mx-reply"],
nestingLimit: 100,
@ -100,6 +101,7 @@ const sanitizeOptsInline = {
font: transformFontSpanTags,
span: transformFontSpanTags,
a: transformATag,
p: "span",
},
}

View file

@ -12,7 +12,7 @@ export function formatRoom(roomId, stateEvents, accountData) {
}
function getPower() {
const power = getState("m.room.power_levels") ?? { state_default: 0 };
const power = getState("m.room.power_levels") ?? {};
power.me = power.users?.[state.userId] ?? power.users_default ?? 0;
return {
...power,

View file

@ -1,5 +1,7 @@
import { defineConfig } from "vite"
import { svelte } from "@sveltejs/vite-plugin-svelte"
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { execSync } from "child_process";
import pkgjson from "./package.json" assert { type: "json" };
// https://vitejs.dev/config/
export default defineConfig({
@ -10,4 +12,11 @@ export default defineConfig({
outDir: "../dist",
emptyOutDir: true,
},
define: {
build: {
package: pkgjson,
time: new Date(),
commit: execSync("git log -n 1 --oneline HEAD").toString().match(/[a-z0-9]+/)[0],
}
},
});