Skip to content

Commit

Permalink
init: web frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
abhijit-hota committed May 16, 2022
1 parent 1eabde4 commit 48e0a6e
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 0 deletions.
159 changes: 159 additions & 0 deletions api/views/css/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* Override */
h1,
h2,
h3 {
margin: unset;
}

button {
margin-bottom: unset;
}

.no-style {
all: unset;
}

/** Global **/
body {
display: grid;
grid-template-areas:
"folders nav nav"
"folders bookmarks filters"
;
}

nav {
grid-area: nav;
margin-bottom: 2em;
}

dialog {
margin-top: 100px;
}

.w-full {
width: 100%;
}

#folders {
grid-area: folders;
}

.row {
display: flex;
align-items: center;
}

.col {
display: flex;
flex-direction: column;
}

.m-l-auto {
margin-left: auto;
}

.m-b-1 {
margin-bottom: 1em;
}

.m-b-2 {
margin-bottom: 2em;
}

main>div {
padding-right: 2em;
width: 800px;
}

.red {
color: red;
}

/* Filters */

#filters {
grid-area: filters;
}

#filters input[type="radio"] {
cursor: pointer;
}

#tags {
display: flex;
margin-top: 0.5em;
flex-wrap: wrap;
}

#tags .tag {
cursor: pointer;
opacity: 0.5;
}

#tags .tag:hover {
opacity: 1;
}

/* Bookmark */
.bookmark {
display: flex;
flex-direction: column;
padding: 0.5em;

margin-bottom: 1em;
border-radius: 6px;
}

.bookmark:hover {
background-color: var(--background-alt);
}

.bookmark h3,
.bookmark small {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.favicon {
aspect-ratio: 1/1;
width: 1.17em;
margin-right: 0.5em;
}

.tags {
display: flex;
margin-top: 0.5em;
}

.tag {
background-color: var(--background);
border-radius: 10px;
margin: 4px;
margin-left: 0px;
padding: 4px 8px;
width: max-content;
}

/* Tag input */
#autocomplete {
position: absolute;
}

.input-like {
color: var(--form-text);
background-color: var(--background);
font-family: inherit;
font-size: inherit;
margin-right: 6px;
margin-bottom: 6px;
padding: 10px;
border: none;
border-radius: 6px;
outline: none;

height: 19px;
display: flex;
flex-wrap: wrap;
}
1 change: 1 addition & 0 deletions api/views/css/water.min.css

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions api/views/html/add-bookmark-dialog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{{define "add-bookmark-dialog"}}
<dialog id="addBookmark" style="width: 300px">
<header><h2>Add new bookmark</h2></header>
<form
class="m-l-auto col"
x-data="{ status: '', message: '' }"
@submit.prevent="async () => {
status = 'SUBMITTING'
try {
const newBookmark = await api('/bookmarks', 'POST', { url: $event.target.elements.url.value })
status = 'SUCCESS'
$dispatch('update', { type: 'add', data: newBookmark })
document.getElementById('addBookmark').close()
} catch (error) {
status = 'ERROR'
}
}"
>
<div class="col m-b-1">
<label for="url"><strong>URL</strong><span class="red">*</span></label>
<input type="url" placeholder="Add new URL" name="url" id="url" required />
</div>

<div class="col m-b-1">
<label for="tags"><strong>Tags</strong></label>
<select multiple name="tags" x-data="{ allTags: [] }" x-init="allTags = await api('/tags')">
<template x-for="tag in allTags" :key="tag.id">
<option x-text="tag.name" x-model="tag.id"></option>
</template>
</select>
</div>

<button
type="submit"
:disabled="status === 'SUBMITTING'"
x-text="status === 'SUBMITTING' ? 'Loading' : 'Add'"
></button>
</form>
</dialog>
{{end}}
35 changes: 35 additions & 0 deletions api/views/html/bookmark.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{{define "bookmark"}}
<template x-for="bookmark in bookmarks" :key="bookmark.id">
<div class="bookmark" x-data="{ hovered: false }" @mouseover="hovered = true" @mouseout="hovered = false">
<div class="row">
<img class="favicon" :src="bookmark.meta.favicon" alt="Favicon" />
<h3 x-text="bookmark.meta.title"></h3>
</div>
<small><a :href="bookmark.url" x-text="bookmark.url"></a></small>
<p x-text="bookmark.meta.description"></p>
<div class="footer row">
<div class="tags">
<template x-for="tag in bookmark.tags" :key="tag.id">
<div class="tag" x-text="tag.name"></div>
</template>
</div>
<div class="m-l-auto">
<button
x-show="hovered"
style="background-color: var(--text-muted); color: var(--background)"
@click="async () => {
try {
const res = await api('/bookmarks/' + bookmark.id, 'DELETE')
$dispatch('update', { type: 'delete', data: bookmark.id })
} catch (error) {
console.debug(error);
}
}"
>
Delete
</button>
</div>
</div>
</div>
</template>
{{end}}
49 changes: 49 additions & 0 deletions api/views/html/filter-sidebar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{{define "filter-sidebar"}}
<input type="text" id="search" name="search" placeholder="Search" class="m-b-2" x-model="$store.queryParams.search" />
<div class="row m-b-2">
<div class="col">
<strong>SORT BY</strong>
<div>
<input type="radio" name="sortBy" id="title" value="title" x-model="$store.queryParams.sortBy" />
<label for="title">Title</label>
</div>
<div>
<input type="radio" name="sortBy" id="date" value="date" x-model="$store.queryParams.sortBy" />
<label for="date">Date</label>
</div>
</div>
<div class="col m-l-auto">
<strong>ORDER</strong>
<div>
<input type="radio" name="order" id="asc" value="asc" x-model="$store.queryParams.order" />
<label for="asc">Ascending</label>
</div>
<div>
<input type="radio" name="order" id="desc" value="desc" x-model="$store.queryParams.order" />
<label for="desc">Descending</label>
</div>
</div>
</div>

<div class="row">
<strong>FILTER BY TAGS</strong>
<button class="m-l-auto" :disabled="$store.queryParams.tags.length === 0" @click="$store.queryParams.tags = []">
Clear
</button>
</div>
<div id="tags" class="m-b-2" x-data="{tags:[]}" x-init="$nextTick(async () => { tags = await api('/tags') })">
<template x-for="tag in tags" :key="tag.id">
<div
class="tag"
role="checkbox"
:style="{opacity: $store.queryParams.tags.includes(tag.id) ? 1 : 0.5}"
x-text="tag.name"
@click="$store.queryParams.toggleTag(tag.id)"
></div>
</template>
</div>

<div x-data="{ open: false }">
<button class="w-full" @click="document.getElementById('settings').showModal()">Open Settings</button>
</div>
{{end}}
51 changes: 51 additions & 0 deletions api/views/html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>gnapsach</title>

<link rel="stylesheet" href="css/water.min.css" />
<link rel="stylesheet" href="css/styles.css" />

<script src="js/script.js"></script>
<script src="js/alpine.min.js"></script>
</head>

<body>
<nav>
<div class="row">
<h1>Mango</h1>
<button class="m-l-auto" @click="document.getElementById('addBookmark').showModal()">
+ Add Bookmark
</button>
</div>
<hr />
</nav>
<aside id="folders"></aside>
<main>
<div
class="bookmark-list"
x-data="{ bookmarks: [] }"
x-effect="async () => { bookmarks = await api('/bookmarks' + $store.queryParams.get()) }"
@update.window="() => {
switch ($event.detail.type) {
case 'add':
bookmarks.push($event.detail.data)
break;
case 'delete':
const i = bookmarks.findIndex((bm) => bm.id === $event.detail.data)
bookmarks.splice(i, 1)
break;
}
}"
>
{{block "bookmark" .}} {{end}}
</div>
</main>
<aside id="filters">{{block "filter-sidebar" .}} {{end}}</aside>
{{block "settings-dialog" .}} {{end}} {{block "add-bookmark-dialog" .}} {{end}}
</body>
</html>
35 changes: 35 additions & 0 deletions api/views/html/settings-dialog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{{define "settings-dialog"}}
<dialog
id="settings"
x-data="{
config: {
shouldSaveOffline: null,
urlActions: []
}
}"
x-init="config = await api('/config')"
>
<header><h2>Settings</h2></header>
<div class="col" style="width: 300px">
<div class="row m-b-2">
<strong>Save pages offline</strong>
<input
class="m-l-auto"
type="checkbox"
name="saveOffline"
id="saveOffline"
:value="config.shouldSaveOffline"
/>
</div>

<strong>URL Actions</strong>
<template x-for="urlAction in config.urlActions">
<div class="url-action">
<input type="text" x-text="urlAction.pattern" />
<select name="matchDetection" x-text="urlAction.matchDetection"></select>
<input type="checkbox" name="saveOffline" id="saveOffline" :value="urlAction.shouldSaveOffline" />
</div>
</template>
</div>
</dialog>
{{end}}
5 changes: 5 additions & 0 deletions api/views/js/alpine.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit 48e0a6e

Please sign in to comment.