Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Entry Tags #202

Open
wants to merge 34 commits into
base: v3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3a2350b
Added baseplate, will require much more changes
selectdev Mar 19, 2022
af5f4c8
Formatted code because ugly
selectdev Mar 19, 2022
298e0dc
Made some mini design changes
selectdev Mar 19, 2022
92a94b7
db search for tags now supported, also in api
stjet Mar 19, 2022
9bf26a6
api accepts query param, added search bar
stjet Mar 20, 2022
6d9a650
Fixed some mini bugs
selectdev Mar 21, 2022
a3efbfc
Added space into CONTRIBUTING.md to trigger Vercel
selectdev Mar 21, 2022
f98fe33
uhh, fixed search and broke database
selectdev Mar 21, 2022
81f9e34
bug fix
stjet Mar 21, 2022
07c595c
another fix?
stjet Mar 21, 2022
e894c4c
bug fix for reals
stjet Mar 21, 2022
081969b
add basic api docs
stjet Mar 22, 2022
62c5017
Made .replit automatically format files before starting
selectdev Mar 23, 2022
39d03f6
you can now add tags to entries
stjet Mar 24, 2022
413c4f9
Made it so the register-commands actually ends rather than stays runn…
selectdev Mar 26, 2022
95e4672
clicking on tags search - does not work completely (onclicks overlap)
stjet Mar 28, 2022
1fe6935
Merge branch 'v3' into add-entry-tags
selectdev Apr 8, 2022
d555303
fixed tag search with extensive help of select, fixed the actual db s…
stjet Jun 18, 2022
242f601
Replaced div with a
selectdev Jun 19, 2022
d9ed894
Update src/routes/edit/[slug].svelte
selectdev Jun 19, 2022
4d6505e
Update src/lib/EntryPreview.svelte
selectdev Jun 19, 2022
12dfe1c
Update src/lib/database/entries.ts
selectdev Jun 19, 2022
e5e63de
Update src/lib/discord/scripts/registerCommands.ts
selectdev Jun 19, 2022
a1dd1b5
Update src/routes/entry/[slug].svelte
selectdev Jun 19, 2022
1759533
Update src/routes/index.svelte
stjet Jun 19, 2022
360e4bb
updates
stjet Jun 19, 2022
dfe3fcf
Merge branch 'v3' into add-entry-tags
stjet Jun 19, 2022
5189b5d
Formatted
selectdev Jun 19, 2022
dcd088f
mat review compliance thing
stjet Jun 19, 2022
d3c90e2
Update src/lib/discord/scripts/registerCommands.ts
selectdev Jul 7, 2022
1ffc71d
Update src/routes/edit/[slug].svelte
selectdev Jul 7, 2022
54e5f5e
Update src/routes/api/entries.json.ts
selectdev Jul 7, 2022
271549f
Formatted
selectdev Jul 7, 2022
558ebed
Merge branch 'v3' into add-entry-tags
stjet Nov 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .replit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
run = "npm run dev:host"
run = "npm run format && npm run dev:host"
onBoot = "npm install"

[packager]
Expand Down
27 changes: 27 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# API Documentation

## GET /api/entries.json

Search for entries

### Params

All parameters should of course be encoded in the request url as URL query string parameters.

Note: If there are no parameters, it will fetch all entries.

- **`tags`**: CSV (comma seperated values) of tags to search for. Eg: "web,programming language,discord user"
- **`query`**: String to search for in article titles and content.
- **`visible`**: Boolean (true/false) of whether to show regular (non unlisted or hidden), 'visible' entries. Defaults to true.
- **`unlisted`**: Boolean (true/false) of whether to show unlisted entries. Some users may not have the proper permissions to view these entries. Defaults to false.
- **`hidden`**: Boolean (true/false) of whether to show hidden entries. Some users may not have the proper permissions to view these entries. Defaults to false.
- **`limit`**: Integer of how many entries it should return. Defaults to all.
- **`skip`**: Integer of how many entries should be skipped (useful for pagination). Defaults to 0 (skip none).

## POST /api/entries.json

Creates entry. Probably should not try and do this programatically.

## GET /api/random.json

Get random entry.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ Repldex is the unofficial community-editable encyclopedia of user created entrie
# Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md)

# API Docs

Looking for the Repldex api documentation? Go to [DOCUMENTATION.md](DOCUMENTATION.md)
63 changes: 58 additions & 5 deletions src/lib/EntryPreview.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,52 @@
<script lang="ts">
import type { Entry } from './database/entries'

import { browser } from '$app/env'
export let entry: Entry

import * as markdown from './markdown'
import { getEntryViewUrl } from './utils'

const setTagSearch = (tag: string) => {
if (browser) {
let search_bar = document.getElementById('search') as HTMLInputElement
search_bar.value = 'tags:' + tag.tag.replaceAll(' ', '_')
//search_bar.oninput()
search_bar.dispatchEvent(new InputEvent('input'))
selectdev marked this conversation as resolved.
Show resolved Hide resolved
}
}
</script>

<a class="entry-preview-container" href={getEntryViewUrl(entry)}>
selectdev marked this conversation as resolved.
Show resolved Hide resolved
<div
class="entry-preview-container"
on:click={() => {
window.location.href = getEntryViewUrl(entry)
}}
>
{#if entry.visibility === 'unlisted'}
<p class="visibility-warning">Unlisted</p>
{:else if entry.visibility === 'hidden'}
<p class="visibility-warning">Hidden</p>
{/if}
<h2 class="entry-preview-title">{entry.title}</h2>

<a href={getEntryViewUrl(entry)} class="entry-preview-title-link"
><h2 class="entry-preview-title">{entry.title}</h2></a
>
<p class="entry-preview-content">{@html markdown.render(entry.content)}</p>
</a>

{#if entry.tags}
{#each entry.tags as tag}
<span
class="tag"
on:click={event => {
setTagSearch({ tag })
event.cancelBubble = true
}}
>
{tag}
</span>
{/each}
{/if}
</div>

<style>
.entry-preview-container {
Expand All @@ -25,7 +56,9 @@
display: block;
color: inherit;
text-decoration: none;
cursor: pointer;
}

.entry-preview-content {
display: -webkit-box;
-webkit-box-orient: vertical;
Expand All @@ -34,9 +67,14 @@
margin: 0;
}

.entry-preview-title-link {
text-decoration: inherit;
color: inherit;
}

.entry-preview-title {
margin-top: 0;
margin-bottom: 0.25em;
margin-bottom: 0.15em;
}

.visibility-warning {
Expand All @@ -48,6 +86,21 @@
position: relative;
top: -0.5em;
right: -0.5em;
margin-left: 5px;
box-shadow: 0 0 0.5em #0004;
letter-spacing: 0.05ch;
}

.tag {
float: right;
border: 2px solid var(--alternate-background-color);
padding: 0.2rem;
border-radius: 0.2rem;
margin: 0;
position: relative;
bottom: -0.5em;
margin-left: 5px;
right: -0.5em;
box-shadow: 0 0 0.5em #0004;
letter-spacing: 0.05ch;
}
Expand Down
9 changes: 9 additions & 0 deletions src/lib/database/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Entry {
visibility: Visibility
createdAt: Date
editedAt: Date
tags: string[]
}

let triedCreatingSearchIndex = false
Expand All @@ -34,6 +35,7 @@ interface FilterEntriesOptions {
interface FetchEntriesOptions extends FilterEntriesOptions {
limit: number
skip: number
tags?: string[]
}

/**
Expand All @@ -44,6 +46,7 @@ export async function fetchEntries(options: FetchEntriesOptions): Promise<Entry[
const searchQuery = options.query
const skip = options.skip
const limit = options.limit
const tags = options.tags

const searchFilter: Filter<ReplaceIdWithUuid<Entry>> = {
visibility: {
Expand All @@ -54,7 +57,13 @@ export async function fetchEntries(options: FetchEntriesOptions): Promise<Entry[
].filter(Boolean) as Visibility[],
},
}

if (searchQuery) searchFilter.$text = { $search: searchQuery }
if (tags) {
searchFilter.tags = {
$all: tags,
}
}

const foundEntries = await collection
.find(searchFilter)
Expand Down
11 changes: 4 additions & 7 deletions src/lib/discord/scripts/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,19 @@ import fetch from 'node-fetch'
async function registerCommands() {
const bulkUpdate: RESTPutAPIApplicationCommandsJSONBody = commands.map(c => c.json)

console.log(JSON.stringify(bulkUpdate, null, 2))

const json = await fetch(GLOBAL_COMMAND_API_URL, {
await fetch(GLOBAL_COMMAND_API_URL, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bot ${process.env.DISCORD_TOKEN}`,
},
body: JSON.stringify(bulkUpdate),
}).then(res => res.json())
})

console.log(JSON.stringify(json, null, 2))
console.log('Registered commands :)')
console.log(
'Please note that you only have to run this whenever the syntax of a command is changed, i.e. not on every code change.'
'Registered commands, Please note that you only have to run this whenever the syntax of a command is changed, i.e. not on every code change. :)'
)
process.exit(0)
}

registerCommands()
10 changes: 10 additions & 0 deletions src/routes/api/entries.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export const get: RequestHandler = async req => {
const showVisible = req.url.searchParams.get('visible') !== 'false'
const showUnlisted = req.url.searchParams.get('unlisted') === 'true'
const showHidden = req.url.searchParams.get('hidden') === 'true'
const tags = req.url.searchParams
.get('tags')
?.split(',')
?.map(tag => tag.trim())
?.filter(tag => tag.length > 0)
const query = req.url.searchParams.get('query')

const user = req.locals.user ? await fetchUser({ id: req.locals.user.id }) : null

Expand All @@ -29,6 +35,8 @@ export const get: RequestHandler = async req => {
visible: showVisible,
unlisted: showUnlisted,
hidden: showHidden,
tags: tags,
query: query ? query : undefined,
})

return {
Expand Down Expand Up @@ -66,6 +74,7 @@ export const post: RequestHandler = async req => {
// the typings for req.body are wrong, so we have to do `as any`
const entryContent = body.content
const entryTitle = body.title
const tags = body.tags

const slug = createSlug(entryTitle)

Expand Down Expand Up @@ -105,6 +114,7 @@ export const post: RequestHandler = async req => {
title: entryTitle,
slug,
visibility,
tags,
})

if (entry) {
Expand Down
2 changes: 2 additions & 0 deletions src/routes/api/entry/[slug].json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const put: RequestHandler = async req => {
const title = (body.title as string) ?? null
const entryId = (body.id as string) ?? null
const visibility = (body.visibility as Visibility) ?? null
const tags = (body.tags as string[]) ?? null

const basicUser = req.locals.user

Expand Down Expand Up @@ -111,6 +112,7 @@ export const put: RequestHandler = async req => {
slug,
title,
visibility,
tags,
})
await createHistoryItem({
entryId: createUuid(entry.id),
Expand Down
18 changes: 17 additions & 1 deletion src/routes/edit/[slug].svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

let entryTitle: string = entry.title
let entryContent: string = entry.content
let entryTags: string = (entry.tags ?? []).join(',')
let visibility: Visibility = entry.visibility

// automatically update the page title
Expand All @@ -46,7 +47,10 @@
async function submitEntry() {
// make a put request to /api/entry/<id>.json
// if successful, redirect to /entry/<slug>

entryTags = entryTags
.split(',')
.map(tag => tag.trim())
.filter(tag => tag.length > 0)
const response: Entry | { error: string } = await fetch(`/api/entry/${entry.id}.json`, {
method: 'PUT',
headers: {
Expand All @@ -56,6 +60,7 @@
id: entry.id,
title: entryTitle,
content: entryContent,
tags: entryTags,
visibility: visibility,
}),
}).then(response => response.json())
Expand Down Expand Up @@ -97,6 +102,12 @@
<MarkdownEditor bind:value={entryContent} />
</Labelled>

<div class="tags">
<Labelled text="Tags (seperated with commas)">
<TextInput bind:value={entryTags} />
</Labelled>
</div>

<button on:click={submitEntry}>Save</button>
</div>
</div>
Expand All @@ -112,6 +123,11 @@
margin-bottom: 1rem;
}

.tags {
margin-top: 1rem;
margin-bottom: 1rem;
}

/* vertically align */
#editor-container {
display: grid;
Expand Down
21 changes: 19 additions & 2 deletions src/routes/edit/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,19 @@

let entryTitle = ''
let entryContent = ''
let entryTags = ''

// automatically update the page title
let pageTitle = 'Create entry'
$: {
pageTitle = entryTitle.length ? `New entry "${entryTitle}"` : 'New entry'
}

async function submitEntry() {
const submitEntry = async () => {
// make a post/put request to /api/entries.json
// if successful, redirect to /entry/<slug>
entryTags = entryTags.split(',')
if (entryTags.length == 1 && entryTags[0] == '') entryTags = []

const response: Entry | { error: string } = await fetch('/api/entries.json', {
method: 'POST',
Expand All @@ -53,6 +56,7 @@
body: JSON.stringify({
title: entryTitle,
content: entryContent,
tags: entryTags,
visibility,
}),
}).then(response => response.json())
Expand Down Expand Up @@ -89,11 +93,18 @@
<TextInput bind:value={entryTitle} />
</Labelled>
</div>

<Labelled text="Content">
<MarkdownEditor bind:value={entryContent} />
</Labelled>

<button on:click={submitEntry}>Submit</button>
<div class="tags">
<Labelled text="Tags (seperated with commas)">
<TextInput bind:value={entryTags} />
</Labelled>
</div>

<button on:click={submitEntry}>Save</button>
</div>
</div>

Expand All @@ -108,11 +119,17 @@
margin-bottom: 1rem;
}

.tags {
margin-top: 1rem;
margin-bottom: 1rem;
}

/* vertically align */
#editor-container {
display: grid;
align-items: center;
height: 100vh;
margin-top: 1.5rem;
}

button {
Expand Down
Loading