Skip to content

Commit

Permalink
Add admin web frontend for managing and curating dictionary data.
Browse files Browse the repository at this point in the history
The dashboard is built with static HTML pages served by Go with
AlpineJS lib for interacting with the DOM. There is no build system
after this fiasco:
https://nadh.in/blog/javascript-ecosystem-software-development-are-a-hot-mess/

The dashboard allows to:
- Search entries.
- Add / edit / delete entries.
- Add / edit / delete definitions (relations) under entries.
- Re-order (up/down) relationships under entries.
  • Loading branch information
knadh committed Nov 28, 2021
1 parent 29da6c9 commit 01423a7
Show file tree
Hide file tree
Showing 32 changed files with 1,372 additions and 10,259 deletions.
17 changes: 0 additions & 17 deletions admin/.eslintrc.js

This file was deleted.

23 changes: 0 additions & 23 deletions admin/.gitignore

This file was deleted.

24 changes: 0 additions & 24 deletions admin/README.md

This file was deleted.

5 changes: 0 additions & 5 deletions admin/babel.config.js

This file was deleted.

58 changes: 58 additions & 0 deletions admin/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{{ define "header" }}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ .Title }} {{ if .Title }}|{{ end}} dictmaker</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="/admin/static/grid.css">
<link rel="stylesheet" type="text/css" href="/admin/static/style.css">

<script defer src="/admin/static/main.js"></script>
<script defer src="/admin/static/alpine.js"></script>
</head>
<body x-init="onLoad()" x-data="globalComponent()">
<template x-if="ready">
<div class="container wrap">
<header class="header">
<div class="row">
<div class="three columns logo">
<a href="/admin"><img src="/admin/static/logo.svg" alt="logo" /></a>
<template x-if="Object.keys(loading).length > 0"><span class="loading"></span></template>
</div>
<nav class="eight columns">
<a href="" @click.prevent="onNewEntry">Add new</a>
</nav>
</div>
</header>

<form class="search" action="/admin/search" x-data="searchFormComponent()" @submit="onSearch">
<fieldset class="row">
<div class="column four">
<select name="from_lang" x-model="fromLang">
<option value="*guid">*GUID</option>
<template x-for="[id, l] in Object.entries(config.languages)" :key="id">
<option :value="id" x-text="l.name" x-bind:selected="id === fromLang"></option>
</template>
</select>
</div>
<div class="column six">
<input type="text" name="query" x-model="query" placeholder="Search" required />
</div>
<div class="column two">
<button class="button" type="submit" x-bind:disabled="loading['entries.search'] === true">Search</button>
</div>
</fieldset>
</form>

{{ end }}

{{ define "footer" }}
{{ template "entry" . }}
</div>
</template>
<footer class="footer">
<a href="https://dict.press">DictPress <span x-text="config.version"></span></a>
</footer>
</body>
</html>
{{ end }}
59 changes: 59 additions & 0 deletions admin/definition.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{{ define "definition" }}
<section x-data="definitionComponent()"
@open-definition-form.window="onOpen"
@keyup.escape.window="onClose"
@close-definition-form.window="onClose">
<template x-if="isVisible">
<div class="panel relation-form">
<h3>Add definition</h3>
<form @submit.prevent="onSave">
<div x-text="parent.content"></div>
<div>&darr;</div>
<div>
<label>Definition</label>
<textarea autofocus name="content" x-model="def.content"></textarea>
</div>
<br />
<template x-if="Object.keys(config.languages[parent.lang].types).length > 0">
<fieldset class="row">
<div class="column four">
<label>Language</label>
<select name="lang" x-model="def.lang">
<template x-for="[id, l] in Object.entries(config.languages)" :key="id">
<option :value="id" x-text="l.name"></option>
</template>
</select>
</div>

<div class="column four">
<label>Relation types</label>
<select name="lang" x-model="def.types" multiple>
<template x-for="[id, typ] in Object.entries(config.languages[def.lang].types)" :key="id">
<option :value="id" x-text="typ"></option>
</template>
</select>
<span class="help">Ctrl+click to select multiple values</span>
</div>

<div class="column four">
<label>Relation tags</label>
<textarea name="tags" x-model="def.tags"></textarea>
<span class="help">One per line</span>
</div>
</fieldset>
</template>

<fieldset>
<label>Notes</label>
<textarea name="notes" x-model="def.notes"></textarea>
</fieldset>

<fieldset class="buttons">
<button type="submit" class="button">Save</button>
<button type="button" class="button button-outline" @click.prevent="onClose">Close</button>
</fieldset>
</form>
</div>
</template>
</section>
{{ end}}
121 changes: 121 additions & 0 deletions admin/entry.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{{ define "entry" }}
<section x-data="entryComponent()"
@open-entry-form.window="onOpen"
@keyup.escape.window="onClose"
@close-entry-form.window="onClose">
<template x-if="isVisible">
<div class="panel entry-form">
<h3 x-text="isNew ? 'New entry' : 'Edit entry'"></h3>
<form @submit.prevent="onSave">
<fieldset class="row box">
<div class="column nine">
<label>Content</label>
<textarea required autofocus name="content" x-ref="content" x-model="entry.content"></textarea>
</div>
<div class="column three">
<label>Initial</label>
<input required type="text" name="initial" x-model="entry.initial" @focus="onFocusInitial" />
<span class="help">First letter</span>
</div>
</fieldset>
<template x-if="isNew">
<fieldset>
<label>GUID</label>
<input type="text" name="guid" x-model="entry.guid" />
<span class="help">Leave empty to auto-generate.</span>
</fieldset>
</template>
<p>
<a href="" @click.prevent="onToggleOptions">
<span x-text="!isFormOpen ? 'More options +' : 'Hide options -'"></span>
</a>
</p>
<template x-if="isFormOpen">
<div class="box">
<fieldset class="row">
<div class="column six">
<label>Language</label>
<select name="lang" x-model="entry.lang" required>
<template x-for="[id, l] in Object.entries(config.languages)" :key="id">
<option :value="id" x-text="l.name" x-bind:selected="id === entry.lang"></option>
</template>
</select>
</div>
<div class="column three">
<label>Status</label>
<select name="status" x-model="entry.status" required>
<option value="enabled">Enabled</option>
<option value="disabled">Disabled</option>
</select>
</div>
<div class="column three">
<label>Weight</label>
<input type="number" step="0.01" x-model="entry.weight" />
<span class="help">Sort order. Leave as 0 to automatically set.</span>
</div>
</fieldset>

<fieldset class="row">
<div class="column four">
<label>Phonetic notations</label>
<textarea name="phones" x-model="entry.phones" cols="10"></textarea>
<span class="help">One per line</span>
</div>
<div class="column four">
<label>Tags</label>
<textarea name="tags" x-model="entry.tags"></textarea>
<span class="help">One per line</span>
</div>
<div class="column four">
<label>Search tokens</label>
<textarea name="tokens" x-model="entry.tokens"></textarea>
<span class="help">TSVECTOR tokens. One per line. Leave empty to auto-generate.</span>
</div>
</fieldset>

<fieldset>
<label>Notes</label>
<textarea name="notes" x-model="entry.notes"></textarea>
</fieldset>
</div>
</template>
<br />
<fieldset class="buttons">
<button class="button button-outline float-right" @click.prevent="onDeleteEntry"
x-bind:disabled="loading['entries.delete'] === true">Delete</button>
<button type="submit" class="button"
x-bind:disabled="loading['entries.update'] === true || loading['entries.create'] === true">Save</button>
<button class="button button-outline" @click.prevent="onClose">Close</button>
</fieldset>
</form>

<template x-if="!isNew">
<ul class="no meta">
<li><label>Created</label> <span x-text="entry.created_at.slice(0, 16).replace('T', ' ')"></span></li>
<li><label>Updated</label> <span x-text="entry.updated_at.slice(0, 16).replace('T', ' ')"></span></li>
<li><label>GUID</label> <span x-text="entry.guid"></span></li>
<li><label>ID</label> <span x-text="entry.id"></span></li>
</ul>
</template>

<template x-if="!isNew && parentEntries.length > 0">
<div class="parents">
<h3>Parent entries (<span x-text="parentEntries.length"></span>)</h3>
<ol class="relations">
<template x-for="r in parentEntries" :key="r.id">
<li class="rel">
<p>
<a x-bind:href="makeURL({'guid': r.guid})" x-text="r.content" class="content"></a>
<template x-if="r.phones.length > 0">
<span class="phones" x-text="r.phones.join(', ')"></span>
</template>
</p>
</li>
</template>
</ol>
</div>
</template>
</div>
</template>
</section>
{{ end}}
31 changes: 31 additions & 0 deletions admin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{{ define "index" }}
{{ template "header" . }}

<section class="home" x-data="homeComponent()" x-init="onLoad">
<template x-if="stats">
<div class="box stats row">
<div class="column">
<h2 x-text="formatNumber(stats.entries)"></h2>
<p>Entries</p>
</div>
<div class="column">
<h2 x-text="formatNumber(stats.relations)"></h2>
<p>Relations</p>
</div>
<div class="column">
<h2>Languages</h2>
<ul class="no">
<template x-for="[l, num] in Object.entries(stats.languages)" :key="l">
<li>
<label class="label" x-text="config.languages[l].name"></label>
<span x-text="formatNumber(num)"></span>
</li>
</template>
</ul>
</div>
</div>
</template>
</section>

{{ template "footer" . }}
{{ end}}
33 changes: 0 additions & 33 deletions admin/package.json

This file was deleted.

Binary file removed admin/public/favicon.ico
Binary file not shown.
17 changes: 0 additions & 17 deletions admin/public/index.html

This file was deleted.

0 comments on commit 01423a7

Please sign in to comment.