diff options
| author | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-03-09 22:43:12 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-03-09 22:43:12 +0100 |
| commit | bd8e98d7e24c4dbaee7db6ec7955f7c2f6d396a6 (patch) | |
| tree | e5f0dcbc14d3009c17e5f562404fe19f6aceab73 /src | |
| parent | 862a5f9cdbbda522c608ea63c1e296e81f44de10 (diff) | |
Update to SvelteKit
Diffstat (limited to 'src')
30 files changed, 868 insertions, 0 deletions
diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..26d754f --- /dev/null +++ b/src/app.html @@ -0,0 +1,23 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="description" content="A forum for the year 3000"> + <meta name="robots" content="noindex, nofollow" /> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="theme-color" content="#ffffff"> + + <title>Forum.</title> + + <link rel="manifest" href="/manifest.webmanifest"> + + %svelte.head% + </head> + <body> + <div id="forum">%svelte.body%</div> + <noscript> + <h1>Javascript Required.</h1> + <p>Please enable Javascript to use this forum.</p> + </noscript> + </body> +</html> diff --git a/src/components/author/author.svelte b/src/components/author/author.svelte new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/components/author/author.svelte diff --git a/src/components/error_block/error_block.svelte b/src/components/error_block/error_block.svelte new file mode 100644 index 0000000..e46afa1 --- /dev/null +++ b/src/components/error_block/error_block.svelte @@ -0,0 +1,31 @@ +<script> + import { _ } from 'svelte-i18n'; + export let message; +</script> + +<div> + <h2>{$_('error.generic.title')}</h2> + <p>{message || $_('error.generic.message')}</p> +</div> + +<style> + div { + background: repeating-linear-gradient( -45deg, red, red 5px, black 5px, black 10px ); + border: 5px solid red; + color: yellow; + font-family: 'ヒラギノ角ゴ ProN' , 'Hiragino Kaku Gothic ProN' , '游ゴシック' , '游ゴシック体' , YuGothic , 'Yu Gothic' , 'メイリオ' , Meiryo , 'MS ゴシック' , 'MS Gothic' , HiraKakuProN-W3 , 'TakaoExゴシック' , TakaoExGothic , 'MotoyaLCedar' , 'Droid Sans Japanese' , sans-serif; + margin: 0 10px 0 0; + padding: 100px 5px; + text-align: center; + } + + h2, p { + background-color: black; + font-size: 1em; + } + + h2 { + text-transform: uppercase; + } +</style> + diff --git a/src/components/footer/footer.svelte b/src/components/footer/footer.svelte new file mode 100644 index 0000000..3587aab --- /dev/null +++ b/src/components/footer/footer.svelte @@ -0,0 +1,36 @@ +<script> + import { _ } from 'svelte-i18n'; + + import LanguageSelector from '../language_selector/language_selector.svelte'; + + const licenseUrl = 'https://gitlab.com/rbdr/forum/'; +</script> + +<footer title="{$_('footer.title')}"> + <ul> + <li>{@html $_('footer.license', { values: { licenseUrl } })}</li> + <li>{$_('footer.choose_language')}: <LanguageSelector /></li> + </ul> +</footer> + +<style> + footer { + grid-column: col-start 1 / span 12; + border-top: 1px solid black; + } + + ul { + padding: 0; + } + + li { + display: inline; + margin: 5px; + } + + a { + text-decoration: none; + line-height: 3em; + display: inline-block; + } +</style> diff --git a/src/components/forum_list/forum_list.svelte b/src/components/forum_list/forum_list.svelte new file mode 100644 index 0000000..174ae25 --- /dev/null +++ b/src/components/forum_list/forum_list.svelte @@ -0,0 +1,52 @@ +<script> + import { _ } from 'svelte-i18n'; + import { forums } from '../../stores/forums.js'; + import ErrorBlock from '../error_block/error_block.svelte'; +</script> + +<nav title="{$_('forum_list.title')}"> + {#if !$forums.length} + <ErrorBlock message={$_('forum_list.error.unavailable')} /> + {/if} + <ul> + {#each $forums as forum} + <li> + <a href="/f/{forum.id}"> + <span aria-hidden="true" class="navigation-glyph {forum.glyph}">{forum.glyph}</span> + <span class="navigation-label">{$_(forum.label)}</span> + </a> + </li> + {/each} + </ul> +</nav> + +<style> + nav { + grid-column: col-start 1; + grid-row: 2; + border-right: 1px solid black; + } + + ul { + padding: 0; + } + + li { + display: block; + text-align: left; + margin-bottom: 20px; + } + + .navigation-glyph { + font-size: 1.5rem; + display: block; + } + + .☽ { + font-size: 2rem; + } + + a { + text-decoration: none; + } +</style> diff --git a/src/components/glyph/glyph.svelte b/src/components/glyph/glyph.svelte new file mode 100644 index 0000000..e4b9166 --- /dev/null +++ b/src/components/glyph/glyph.svelte @@ -0,0 +1,39 @@ +<script> + import { _ } from 'svelte-i18n'; + import { getGlyphHash } from '../../utils/glyph_hash'; + + export let uuid; +</script> + +<div class="glyphicon" aria-hidden="true" title="{$_('glyph.title')}"> + {#each getGlyphHash(uuid) as fragment} + <span class="{fragment.glyph}" style="color: {fragment.color} "> + {fragment.glyph} + </span> + {/each} +</div> + +<style> + .glyphicon { + border: 1px solid black; + display: inline-block; + font-size: 1.4rem; + height: 48px; + margin-top: 5px; + width: 48px; + } + + span { + display: block; + float: left; + width: 24px; + height: 24px; + text-align: center; + line-height: 24px; + } + + .☽ { + font-size: 2rem; + line-height: 19px; + } +</style> diff --git a/src/components/header/header.svelte b/src/components/header/header.svelte new file mode 100644 index 0000000..36d4d50 --- /dev/null +++ b/src/components/header/header.svelte @@ -0,0 +1,48 @@ +<script> + import { _ } from 'svelte-i18n'; + + import { version } from '$config/config'; +</script> + +<header title="{$_('header.title')}"> + <ul> + <li><strong><a href="/" aria-label="{$_('header.long_version', { values: { version } })}">{$_('header.short_version', { values: { version } })}</a></strong></li> + <li><a href="/new" aria-label="{$_('header.action.new.title')}">{@html $_('header.action.new.display')}</a></li> + <li><a href="/reply" aria-label="{$_('header.action.reply.title')}">{@html $_('header.action.reply.display')}</a></li> + <li><a href="/search" aria-label="{$_('header.action.search.title')}">{@html $_('header.action.search.display')}</a></li> + <li><a href="/logout" aria-label="{$_('header.action.log_out.title')}">{@html $_('header.action.log_out.display')}</a></li> + </ul> +</header> + +<style> + header { + grid-column: col-start 1 / span 12; + border-bottom: 1px solid black; + } + + ul { + padding: 0; + } + + .action-key { + font-weight: bold; + text-decoration: underline; + } + + li { + display: inline; + margin: 5px; + } + + a { + text-decoration: none; + line-height: 3em; + display: inline-block; + } + + strong a { + color: blue; + text-decoration: underline; + } + +</style> diff --git a/src/components/home/home.svelte b/src/components/home/home.svelte new file mode 100644 index 0000000..7000137 --- /dev/null +++ b/src/components/home/home.svelte @@ -0,0 +1,6 @@ +<script> + import { _ } from 'svelte-i18n'; +</script> + +<h1>{$_('home.title')}</h1> +<p>{$_('home.content')}</p> diff --git a/src/components/invalid_route/invalid_route.svelte b/src/components/invalid_route/invalid_route.svelte new file mode 100644 index 0000000..aaf7927 --- /dev/null +++ b/src/components/invalid_route/invalid_route.svelte @@ -0,0 +1,6 @@ +<script> + import { _ } from 'svelte-i18n'; +</script> + +<h1>{$_('error.invalid_url.title')}</h1> +<p>{$_('error.invalid_url.message')}</p> diff --git a/src/components/language_selector/language_selector.svelte b/src/components/language_selector/language_selector.svelte new file mode 100644 index 0000000..67c0814 --- /dev/null +++ b/src/components/language_selector/language_selector.svelte @@ -0,0 +1,28 @@ +<script> + import { locale, locales } from 'svelte-i18n'; + import { getLangNameFromCode } from 'language-name-map'; + + $: namedLocales = $locales + .map((locale) => ({ + code: locale, + ...getLangNameFromCode(locale) + })) + .sort((a, b) => a.native - b.native); + + let selected = $locale; + + $: { + console.log(`the current locale is ${selected}`); + locale.set(selected); + } +</script> + +<select bind:value={selected}> + {#each namedLocales as namedLocale} + <option value="{ namedLocale.code }">{ namedLocale.native }</option> + {/each} +</select> + +<style> +</style> + diff --git a/src/components/post/post.svelte b/src/components/post/post.svelte new file mode 100644 index 0000000..4720533 --- /dev/null +++ b/src/components/post/post.svelte @@ -0,0 +1,27 @@ +<script> + import { _ } from 'svelte-i18n'; + + import Glyph from '../glyph/glyph.svelte'; +</script> + +<aside class="post-meta" title="{$_('post.metadata_title', { values: { count: 2, total: 2 } })}"> + <Glyph uuid="b33f0339f7d64d1ca27f1c0aefb7d753" /> + <span class="h-card"> + {$_('post.author_credit')} <a href="/a/time4carrots" class="p-nickname u-url">time4carrots</a>. + </span> + <time role="presentation" class="dt-published" datetime="2018-08-15T04:10:00.929Z"> + <a href="/p/da9910f3febde91948000ce1535ea"> + 2018-08-15 04:10:00 + </a> + </time> +</aside> +<article class="e-content" title="{$_('post.title', { values: { count: 1, total: 2, author: 'time4carrots' } })}"> + <p>It's just how it is...</p> +</article> +<hr/> + +<style> + .post-meta * { + vertical-align: top; + } +</style> diff --git a/src/components/topic/topic.svelte b/src/components/topic/topic.svelte new file mode 100644 index 0000000..2aff79a --- /dev/null +++ b/src/components/topic/topic.svelte @@ -0,0 +1,59 @@ +<script> + import { _ } from 'svelte-i18n'; + + import Glyph from '../glyph/glyph.svelte'; +</script> + +<div class="h-entry" title="{$_('topic.title')}"> + <h1 class="p-name">This is a post in the forum.</h1> + <aside class="topic-meta" title="{$_('topic.metadata_title')}"> + <span class="topic-location">{$_('topic.category_location')} <a href="/f/interaction" + class="p-category">{$_('forum.name.interaction')}</a>.</span> + <span class="topic-ttl"><a class="u-url u-uid" title="{$_('topic.permalink_title')}" href="/t/2a3fc567af8c897ca6f55fb5fj">{$_('topic.remaining_time', { values: { remaining: $_('time.days', { values: { count: 3 } }) } })}</a>.</span> + </aside> + <aside class="topic-tags" title="{$_('topic.tags_title')}"> + {$_('topic.tags_location')} + <a href="/g/question" class="p-category">question<span class="tag-weight">(5)</span></a> + <a href="/g/meta" class="p-category">meta<span class="tag-weight">(34)</span></a> + <a href="/g/carrots" class="p-category">carrots<span class="tag-weight">(1)</span></a> + <a href="/g/tpbo" class="p-category">tpbo<span class="tag-weight">(2)</span></a> + </aside> + <aside class="post-meta" title="{$_('post.metadata_title', { values: { count: 1, total: 2 } })}"> + <Glyph uuid="3cd8c84e18144a2da71f6bace9392abc" /> + <span class="h-card"> + {$_('post.author_credit')} <a href="/a/rbdr" class="p-nickname u-url">rbdr</a>. + </span> + <time role="presentation" class="dt-published" datetime="2018-08-14T03:32:10.929Z"> + <a title="{$_('post.permalink_title')}" href="/p/a80c70ea0120387123097ce1907ff"> + 2018-08-14 03:32:10 + </a> + </time> + </aside> + <article class="e-content" title="{$_('post.title', { values: { count: 1, total: 2, author: 'rbdr' } })}"> + <p>This is a main topic in the forum. Does that abstraction still even make sense?</p> + <p>Is this really it??</p> + <p>This might all be fake but at least the links look purple when visited</p> + </article> + <hr/> + <aside class="post-meta" title="{$_('post.metadata_title', { values: { count: 2, total: 2 } })}"> + <Glyph uuid="b33f0339f7d64d1ca27f1c0aefb7d753" /> + <span class="h-card"> + {$_('post.author_credit')} <a href="/a/time4carrots" class="p-nickname u-url">time4carrots</a>. + </span> + <time role="presentation" class="dt-published" datetime="2018-08-15T04:10:00.929Z"> + <a href="/p/da9910f3febde91948000ce1535ea"> + 2018-08-15 04:10:00 + </a> + </time> + </aside> + <article class="e-content" title="{$_('post.title', { values: { count: 1, total: 2, author: 'time4carrots' } })}"> + <p>It's just how it is...</p> + </article> + <hr/> +</div> + +<style> + .post-meta * { + vertical-align: top; + } +</style> diff --git a/src/components/topic_index/topic_index.svelte b/src/components/topic_index/topic_index.svelte new file mode 100644 index 0000000..3de83bb --- /dev/null +++ b/src/components/topic_index/topic_index.svelte @@ -0,0 +1,8 @@ +<script> + export let params; + import ErrorBlock from '../error_block/error_block.svelte'; +</script> + +<ErrorBlock /> +<h1>Topic Index.</h1> +<p>This component lists topics for category or tag with id: {params.id}</p> diff --git a/src/config/config.js b/src/config/config.js new file mode 100644 index 0000000..6d819a3 --- /dev/null +++ b/src/config/config.js @@ -0,0 +1,11 @@ +import { version as packageVersion } from '../../package.json'; + +/* + * The main configuration object for the Forum frontend. These values + * are calculated during compile time and need to be set in a .env + * file, otherwise it won't work. + */ + +export const socketServer = import.meta.env.FORUM_SOCKET_SERVER; + +export const version = packageVersion; diff --git a/src/config/env.dist b/src/config/env.dist new file mode 100644 index 0000000..0bcd782 --- /dev/null +++ b/src/config/env.dist @@ -0,0 +1 @@ +FORUM_SOCKET_SERVER=ws://location_of_socket_server diff --git a/src/config/i18n.js b/src/config/i18n.js new file mode 100644 index 0000000..25ffc45 --- /dev/null +++ b/src/config/i18n.js @@ -0,0 +1,12 @@ +import { addMessages, getLocaleFromNavigator, init } from 'svelte-i18n'; + +import en from './translations/en.json'; +import es from './translations/es.json'; + +addMessages('en', en); +addMessages('es', es); + +init({ + fallbackLocale: 'en', + initialLocale: getLocaleFromNavigator() +}); diff --git a/src/config/translations/en.json b/src/config/translations/en.json new file mode 100644 index 0000000..5b6a4b6 --- /dev/null +++ b/src/config/translations/en.json @@ -0,0 +1,91 @@ +{ + "error": { + "generic": { + "title": "Error!", + "message": "Unknown error has occurred. Panic!" + }, + "invalid_url": { + "title": "This is not right.", + "message": "This URL is not valid. Try another one maybe?" + } + }, + "footer": { + "choose_language": "Choose your language", + "license": "Forum is <a href=\"{licenseUrl}\">open source.</a>", + "title": "Footer" + }, + "forum": { + "name": { + "everything": "Everything", + "us": "Us", + "words": "Words", + "sound": "Sounds", + "structure": "Structure", + "interaction": "Interaction", + "emotion": "Emotion", + "movement": "Movement", + "belief": "Belief", + "experience": "Experience", + "online": "Online", + "the_world": "The World", + "life": "Life" + } + }, + "forum_list": { + "title": "List of forums", + "error": { + "unavailable": "Forum list unavailable." + } + }, + "glyph": { + "title": "User avatar" + }, + "header": { + "action": { + "new": { + "title": "New", + "display": "<u>N</u>ew" + }, + "reply": { + "title": "Reply", + "display": "<u>R</u>eply" + }, + "search": { + "title": "Search", + "display": "<u>S</u>earch" + }, + "log_out": { + "title": "Log Out", + "display": "Log <u>O</u>ut" + } + }, + "long_version": "Forum version {version}", + "title": "Toolbar", + "short_version": "Forum v{version}" + }, + "home": { + "title": "Hello.", + "content": "You are now in a forum. Select a category from the left or input a valid URL." + }, + "post": { + "author_credit": "By:", + "metadata_title": "Post {count} of {total} metadata", + "permalink_title": "Permalink to post", + "title": "Post {count} of {total} by {author}" + }, + "topic": { + "category_location": "Posted on", + "metadata_title": "Topic metadata", + "permalink_title": "Permalink to topic", + "remaining_time": "{remaining} remaining", + "tags_location": "Tags:", + "tags_title": "Topic tags", + "title": "Topic" + }, + "time": { + "days": "{count, plural, =1 {# day} other {# days}}", + "hours": "{count, plural, =1 {# hour} other {# hours}}", + "minutes": "{count, plural, =1 {# minute} other {# minutes}}", + "seconds": "{count, plural, =1 {# second} other {# seconds}}" + } +} diff --git a/src/config/translations/es.json b/src/config/translations/es.json new file mode 100644 index 0000000..be8f96b --- /dev/null +++ b/src/config/translations/es.json @@ -0,0 +1,91 @@ +{ + "error": { + "generic": { + "title": "Error!", + "message": "Hubo un error desconocido. ¡Entra en pánico!" + }, + "invalid_url": { + "title": "Esto no está bien.", + "message": "Este URL no es válido. Intenta otro, ¿Tal vez?" + } + }, + "footer": { + "choose_language": "Escoge un lenguaje", + "license": "Forum es <a href=\"{licenseUrl}\">software libre.</a>", + "title": "Pie de página" + }, + "forum": { + "name": { + "everything": "Todo", + "us": "Nosotros", + "words": "Palabras", + "sound": "Sonidos", + "structure": "Estructura", + "interaction": "Interacción", + "emotion": "Emoción", + "movement": "Movimiento", + "belief": "Creencia", + "experience": "Experiencia", + "online": "En Línea", + "the_world": "El Mundo", + "life": "Vida" + } + }, + "forum_list": { + "title": "Lista de foros", + "error": { + "unavailable": "Lista de foros no disponible." + } + }, + "glyph": { + "title": "Avatar del usuario" + }, + "header": { + "action": { + "new": { + "title": "Nuevo", + "display": "<u>N</u>uevo" + }, + "reply": { + "title": "Responder", + "display": "<u>R</u>esponder" + }, + "search": { + "title": "Buscar", + "display": "Bu<u>s</u>car" + }, + "log_out": { + "title": "Cerrar Sesión", + "display": "Cerrar Sesi<u>ó</u>n" + } + }, + "long_version": "Forum versión {version}", + "title": "Barra de herramientas", + "short_version": "Forum v{version}" + }, + "home": { + "title": "Hola.", + "content": "Ahora estás en un foro. Elige una categoría de la izquierda o escribe un URL válido." + }, + "post": { + "author_credit": "Por:", + "metadata_title": "Metadatos de entrada {count} de {total}", + "permalink_title": "Permalink a entrada", + "title": "Entrada {count} de {total}, por {author}" + }, + "topic": { + "category_location": "Agregado a", + "metadata_title": "Metadatos del tema", + "permalink_title": "Permalink al tema", + "remaining_time": "Quedan {remaining}", + "tags_location": "Etiquetas:", + "tags_title": "Etiquetas del tema", + "title": "Tema" + }, + "time": { + "days": "{count, plural, =1 {# día} other {# días}}", + "hours": "{count, plural, =1 {# hora} other {# horas}}", + "minutes": "{count, plural, =1 {# minuto} other {# minutos}}", + "seconds": "{count, plural, =1 {# segundo} other {# segundos}}" + } +} diff --git a/src/forum.svelte b/src/forum.svelte new file mode 100644 index 0000000..d2a6e1b --- /dev/null +++ b/src/forum.svelte @@ -0,0 +1,63 @@ +<script> + import LightRouter from 'lightrouter'; + + // Initialize localization + + import './config/i18n'; + + // Global components + + import ForumList from './components/forum_list/forum_list.svelte'; + import Header from './components/header/header.svelte'; + import Footer from './components/footer/footer.svelte'; + + // Routed Components + import Author from './components/author/author.svelte'; + import Home from './components/home/home.svelte'; + import InvalidRoute from './components/invalid_route/invalid_route.svelte'; + import Post from './components/post/post.svelte'; + import Topic from './components/topic/topic.svelte'; + import TopicIndex from './components/topic_index/topic_index.svelte'; + + let page; + let params; + + // sets the route params and current page. + + const setRoute = function setRoute(targetPage) { + + return function (routerParams) { + + params = routerParams; + page = targetPage; + }; + }; + + const router = new LightRouter({ + routes: { + '': () => (page = Home) && true, + 'f/{id}': setRoute(TopicIndex), + 'g/{id}': setRoute(TopicIndex), + 'a/{id}': setRoute(Author), + 't/{id}': setRoute(Topic), + 'p/{id}': setRoute(Post), + '.*': setRoute(InvalidRoute) + } + }); + + router.run(); + +</script> + +<Header /> +<main> + <svelte:component this={ page } params={ params } /> +</main> +<ForumList /> +<Footer /> + +<style> + main { + grid-column: col-start 2 / span 11; + } +</style> diff --git a/src/routes/$error.svelte b/src/routes/$error.svelte new file mode 100644 index 0000000..df7da3c --- /dev/null +++ b/src/routes/$error.svelte @@ -0,0 +1,5 @@ +<script> + import InvalidRoute from '$components/invalid_route/invalid_route.svelte'; +</script> + +<InvalidRoute /> diff --git a/src/routes/$layout.svelte b/src/routes/$layout.svelte new file mode 100644 index 0000000..a848a59 --- /dev/null +++ b/src/routes/$layout.svelte @@ -0,0 +1,39 @@ +<script> + import '$config/i18n'; + import { isLoading, waitLocale } from 'svelte-i18n'; + + export const load = function load() { + + return waitLocale; + }; + + import ForumList from '$components/forum_list/forum_list.svelte'; + import Header from '$components/header/header.svelte'; + import Footer from '$components/footer/footer.svelte'; +</script> + +{#if $isLoading} + <h1>Loading.</h1> + <p>Please wait.</p> +{:else} + <Header /> + <main> + <slot></slot> + </main> + <ForumList /> + <Footer /> +{/if} + +<style> + main { + grid-column: col-start 2 / span 11; + } + + :global(#forum) { + display: grid; + font-family : 'ヒラギノ明朝 ProN' , 'Hiragino Mincho ProN' , '游明朝','游明朝体',YuMincho,'Yu Mincho' , 'MS 明朝' , 'MS Mincho' , HiraMinProN-W3 , 'TakaoEx明朝' , TakaoExMincho , 'MotoyaLCedar' , 'Droid Sans Japanese' , serif; + grid-template-columns: repeat(12, [col-start] 1fr); + grid-gap: 20px; + grid-auto-rows: minmax(24px, auto); + } +</style> diff --git a/src/routes/a/[id].svelte b/src/routes/a/[id].svelte new file mode 100644 index 0000000..1b6c705 --- /dev/null +++ b/src/routes/a/[id].svelte @@ -0,0 +1,10 @@ +<script> + export async function load(ctx) { + let id = ctx.page.params.id + return { props: { id }} + } + + import Author from '$components/author/author.svelte'; +</script> + +<Author/> diff --git a/src/routes/f/[id].svelte b/src/routes/f/[id].svelte new file mode 100644 index 0000000..d1fae4c --- /dev/null +++ b/src/routes/f/[id].svelte @@ -0,0 +1,9 @@ +<script> + export async function load(ctx) { + let id = ctx.page.params.id + return { props: { id }} + } +</script> + +<h1>Forum Index.</h1> +<p>This component lists topics for forum with id: {id}</p> diff --git a/src/routes/g/[id].svelte b/src/routes/g/[id].svelte new file mode 100644 index 0000000..d036c36 --- /dev/null +++ b/src/routes/g/[id].svelte @@ -0,0 +1,9 @@ +<script> + export async function load(ctx) { + let id = ctx.page.params.id + return { props: { id }} + } +</script> + +<h1>Tag Index.</h1> +<p>This component lists topics for tag with id: {id}</p> diff --git a/src/routes/index.svelte b/src/routes/index.svelte new file mode 100644 index 0000000..7000137 --- /dev/null +++ b/src/routes/index.svelte @@ -0,0 +1,6 @@ +<script> + import { _ } from 'svelte-i18n'; +</script> + +<h1>{$_('home.title')}</h1> +<p>{$_('home.content')}</p> diff --git a/src/routes/p/[id].svelte b/src/routes/p/[id].svelte new file mode 100644 index 0000000..b8a8165 --- /dev/null +++ b/src/routes/p/[id].svelte @@ -0,0 +1,11 @@ +<script> + export const load = function load(ctx) { + + const id = ctx.page.params.id; + return { props: { id } }; + }; + + import Post from '$components/post/post.svelte'; +</script> + +<Post/> diff --git a/src/routes/t/[id].svelte b/src/routes/t/[id].svelte new file mode 100644 index 0000000..8775cec --- /dev/null +++ b/src/routes/t/[id].svelte @@ -0,0 +1,10 @@ +<script> + export async function load(ctx) { + let id = ctx.page.params.id + return { props: { id }} + } + + import Topic from '$components/topic/topic.svelte'; +</script> + +<Topic/> diff --git a/src/socket_coordinator.js b/src/socket_coordinator.js new file mode 100644 index 0000000..f8fb525 --- /dev/null +++ b/src/socket_coordinator.js @@ -0,0 +1,63 @@ +import EventEmitter from 'eventemitter3'; +import { socketServer } from './config/config'; + +const internals = { + + kReconnectInterval: 3000, // How often we attempt to reconnect + + eventEmitter: new EventEmitter(), // internal event emitter + socket: null, // stores the socket connection + retry: null, // stores the retry operation + + connect() { + + console.debug('Connecting socket.'); + internals.socket = new WebSocket(socketServer); + + internals.socket.addEventListener('message', internals.onMessage); + internals.socket.addEventListener('error', internals.onError); + internals.socket.addEventListener('close', internals.onClose); + }, + + // Handles socket errors. + + onError(event) { + + console.error('Socket error. Closing connection'); + internals.socket.close(); + }, + + // Handles socket errors. + + onClose(event) { + + console.debug(`Connection closed: ${event.reason || 'Unknown reason'}. Retrying in ${internals.kReconnectInterval}ms`); + + internals.retry && clearTimeout(internals.retry); + internals.retry = setTimeout(() => { + + console.debug('Reconnecting socket.'); + internals.retry = null; + internals.connect(); + }, internals.kReconnectInterval); + }, + + // Forwards events from the socket to our internal event emitter. + + onMessage(event) { + + internals.eventEmitter.emit('message', event); + } +}; + +export const onMessage = function (listener) { + + if (!internals.socket) { + internals.connect(); + } + + internals.eventEmitter.on('message', (message) => { + + listener(JSON.parse(message.data)); + }); +}; diff --git a/src/stores/forums.js b/src/stores/forums.js new file mode 100644 index 0000000..e7acb60 --- /dev/null +++ b/src/stores/forums.js @@ -0,0 +1,36 @@ +import { readable } from 'svelte/store'; + +const internals = { + + // Constants + + kChangeFeedEventName: 'changefeed:forums', + + // The exported data structure + + forums: [], + + // Handles messages from the event + + handleChangeFeed(data) { + + // No old value == add + if (!data.old_val) { + return internals.forums.push(data.new_val); + } + + // We have an old value, let's find it. + const index = internals.forums.findIndex((element) => element.id === data.old_val.id); + + if (index > -1) { + if (data.new_val) { + return internals.forums.splice(index, 1, data.new_val || undefined); + } + + return internals.forums.splice(index, 1); + } + } +}; + +export const forums = readable(internals.forums, (set) => { +}); diff --git a/src/utils/glyph_hash.js b/src/utils/glyph_hash.js new file mode 100644 index 0000000..120c02a --- /dev/null +++ b/src/utils/glyph_hash.js @@ -0,0 +1,38 @@ +const internals = { + kSplitterRegex: /.{1,8}/g, + kGlyphs: [ + '☽', + '☆', + '♢', + '♡', + '╱', + '╲', + '╳', + '〰', + '▷', + '⏊', + '〒', + '▢', + '◯', + '⏃', + '⏀', + '⏆' + ] +}; + +// Return a glyph with color based on a 4 byte fragment of a UUIDv4 +const getGlyphHashFragment = function (uuidFragment) { + + const glyphIndex = parseInt(uuidFragment.substring(0,2), 16) % 16; + return { + glyph: internals.kGlyphs[glyphIndex], + color: `#${uuidFragment.substring(2,8)}` + }; +}; + +// Return an array of glyphs based on a UUIDv4 +export const getGlyphHash = function (uuid) { + + const hashFragments = uuid.match(internals.kSplitterRegex); + return hashFragments.map(getGlyphHashFragment); +}; |