messages + post formatting
This commit is contained in:
parent
4c75756abc
commit
34d7d516b5
12 changed files with 359 additions and 38 deletions
|
@ -40,6 +40,15 @@
|
|||
<a href='/'>
|
||||
<img src='/icon_sanifae.svg' alt='Sanifae Logo'>
|
||||
</a>
|
||||
{#if data.read > 0}
|
||||
<a href='/messages'>
|
||||
<img src='/unread.svg' alt='Messages'>
|
||||
</a>
|
||||
{:else}
|
||||
<a href='/messages'>
|
||||
<img src='/read.svg' alt='Messages'>
|
||||
</a>
|
||||
{/if}
|
||||
{#if data.username && data.username != 'false'}
|
||||
<a href='/users/{data.username}'>
|
||||
{data.username}
|
||||
|
|
|
@ -11,15 +11,24 @@
|
|||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.bold, .bolditalic {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.italic, .bolditalic {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {formatPost} from '$lib/util.js';
|
||||
|
||||
export let content = '';
|
||||
export let excludeImg = false;
|
||||
|
||||
let contentSplit;
|
||||
$: contentSplit = formatPost(content || '');
|
||||
$: contentSplit = formatPost(content || '', excludeImg);
|
||||
|
||||
console.log(contentSplit);
|
||||
</script>
|
||||
|
@ -28,6 +37,7 @@
|
|||
{#each contentSplit as line}
|
||||
<p>
|
||||
{#each line as elem}
|
||||
<span class={elem.format}>
|
||||
{#if elem && elem.type == 'img'}
|
||||
{#if line.filter(x => x.type == 'img').length < 2}
|
||||
<img src={elem.url} class='only-img' alt='Image preview'>
|
||||
|
@ -45,10 +55,11 @@
|
|||
</video>
|
||||
{/if}
|
||||
{:else if elem.type == 'link'}
|
||||
<a href={elem.url}>{elem.display + ' '}</a>
|
||||
{:else if !elem.type}
|
||||
{elem + ' '}
|
||||
<a href={elem.url}>{elem.display}</a>
|
||||
{:else if elem.type == 'text'}
|
||||
{elem.content}
|
||||
{/if}
|
||||
</span>
|
||||
{/each}
|
||||
</p>
|
||||
{/each}
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
import Button from '$lib/components/Button.svelte';
|
||||
import {setLocation} from '$lib/util.js';
|
||||
|
||||
export let data;
|
||||
export let data, noRatings;
|
||||
</script>
|
||||
|
||||
<p>
|
||||
{#if !noRatings}
|
||||
<Button clickFunc={() => { window.location.search = setLocation(window.location,'sort','hot')}}>Hot</Button>
|
||||
<Button clickFunc={() => { window.location.search = setLocation(window.location,'sort','rating')}}>Top</Button>
|
||||
<Button clickFunc={() => { window.location.search = setLocation(window.location,'sort','time')}}>Recent</Button>
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
{#if data && data.postJson && data.postJson.data}
|
||||
|
|
|
@ -83,6 +83,13 @@ async function initDb() {
|
|||
username CHAR(64), \
|
||||
following CHAR(64) \
|
||||
)');
|
||||
|
||||
await db.run('CREATE TABLE IF NOT EXISTS messages (\
|
||||
username CHAR(64), \
|
||||
content CHAR(10240), \
|
||||
time INTEGER, \
|
||||
read INTEGER \
|
||||
)');
|
||||
}
|
||||
|
||||
let backendProxy = async ({route, backendParams}) => {
|
||||
|
@ -99,7 +106,7 @@ let backendProxy = async ({route, backendParams}) => {
|
|||
console.log(user);
|
||||
|
||||
if ((!user || user == '') && AUTH_ACTIONS.indexOf(route) != -1) return {'success': 'Not authorized.' };
|
||||
3
|
||||
|
||||
let isAdmin = false;
|
||||
if (user && user != '') isAdmin = ((await backend.userRoles({user}, {db})) || []).indexOf('Admin') != -1;
|
||||
|
||||
|
|
|
@ -202,9 +202,23 @@ backend.postCreate = async ({content}, {user,db}) => {
|
|||
|
||||
backend.postDelete = async ({id}, {user, admin, db}) => {
|
||||
if (admin) {
|
||||
let postUser = await db.all('SELECT * from post where id = ?',[
|
||||
id
|
||||
]) || {};
|
||||
|
||||
postUser = postUser[0].username;
|
||||
|
||||
await db.run('DELETE FROM post WHERE id = ?', [
|
||||
id
|
||||
])
|
||||
|
||||
await db.run('INSERT INTO messages (username, content, time,read) VALUES (?, ?, ?, ?)', [
|
||||
postUser,
|
||||
`#${id} was deleted by an admin for rule violations.`,
|
||||
Math.floor(new Date() * 1000),
|
||||
0
|
||||
]);
|
||||
|
||||
} else {
|
||||
await db.run('DELETE FROM post WHERE username = ? AND id = ?', [
|
||||
user,
|
||||
|
@ -248,7 +262,7 @@ backend.postBulk = async ({page, id, user, cookies, sort, type}, {admin, db}) =>
|
|||
|
||||
sort = (LEGAL_SORTS[sort]) || 'rating';
|
||||
|
||||
if (sort + '' != sort) sort = 'rating';
|
||||
if (sort + '' !== sort) sort = 'rating';
|
||||
|
||||
sort = sort.replaceAll('%d',Math.floor(new Date() * 1000));
|
||||
|
||||
|
@ -297,9 +311,9 @@ backend.vote = async ({id, vote}, {user, db}) => {
|
|||
|
||||
var isCreator = (await db.all('SELECT * from post WHERE id = ?', [
|
||||
id
|
||||
]))[0].username == user;
|
||||
]))[0].username;
|
||||
|
||||
if (isCreator)
|
||||
if (isCreator == user)
|
||||
return {success: 'fail' };
|
||||
|
||||
await db.run('DELETE FROM vote WHERE username = ? AND id = ?', [
|
||||
|
@ -327,6 +341,13 @@ backend.vote = async ({id, vote}, {user, db}) => {
|
|||
id
|
||||
]);
|
||||
|
||||
await db.run('INSERT INTO messages (username, content, time,read) VALUES (?, ?, ?, ?)', [
|
||||
isCreator,
|
||||
`@${user} ${vote == 'up' ? 'upvoted' : 'downvoted'} #${id}`,
|
||||
Math.floor(new Date() * 1000),
|
||||
0
|
||||
]);
|
||||
|
||||
var user = await db.all('SELECT * from post WHERE id = ?', [
|
||||
id
|
||||
]) || [];
|
||||
|
@ -353,12 +374,20 @@ backend.token = async ({cookies}, {db}) => {
|
|||
}
|
||||
|
||||
backend.follow = async ({target}, {user, db}) => {
|
||||
var userExists = ((await db.all('SELECT * FROM user WHERE username = ?',[
|
||||
target
|
||||
])) || []).length;
|
||||
|
||||
if (userExists < 1) return;
|
||||
|
||||
var isFollowing = await db.all('SELECT * FROM follow WHERE username = ? AND following = ?',[
|
||||
user,
|
||||
target
|
||||
]);
|
||||
|
||||
if (isFollowing && isFollowing.length > 0) {
|
||||
let unfollowed = (isFollowing && isFollowing.length > 0);
|
||||
|
||||
if (unfollowed) {
|
||||
await db.run('DELETE FROM follow WHERE username = ? AND following = ?',[
|
||||
user,
|
||||
target
|
||||
|
@ -378,9 +407,31 @@ backend.follow = async ({target}, {user, db}) => {
|
|||
target
|
||||
]);
|
||||
|
||||
await db.run('INSERT INTO messages (username, content, time,read) VALUES (?, ?, ?, ?)', [
|
||||
target,
|
||||
`@${user} ${unfollowed ? 'is now following' : 'unfollowed'} you`,
|
||||
Math.floor(new Date() * 1000),
|
||||
0
|
||||
]);
|
||||
|
||||
return {'success': 'User followed/unfollowed.', 'data': {following, followers}};
|
||||
};
|
||||
|
||||
backend.messages = async ({isRead}, {user, db}) => {
|
||||
var msg = await db.all('SELECT * FROM messages WHERE username = ? ORDER BY time DESC', [
|
||||
user
|
||||
]) || [];
|
||||
|
||||
if (isRead) {
|
||||
await db.run('UPDATE messages SET read = 1 WHERE username = ?', [
|
||||
user
|
||||
]);
|
||||
}
|
||||
|
||||
let read = msg.filter(x => !x.read).length;
|
||||
|
||||
return {'data': {msg, read}};
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
|
|
|
@ -7,6 +7,12 @@ const EXTENSION_MAP = {
|
|||
'mp4': 'video'
|
||||
}
|
||||
|
||||
const formats = [
|
||||
'italic',
|
||||
'bold',
|
||||
'bolditalic'
|
||||
];
|
||||
|
||||
let checkLength = function(string, field, lowerBound, upperBound) {
|
||||
if (string.length < lowerBound) {
|
||||
if (string.length == 0) {
|
||||
|
@ -66,11 +72,49 @@ let safeName = function (text) {
|
|||
return text.replaceAll(/[^A-Za-z0-9\-\_]/g, '');
|
||||
}
|
||||
|
||||
let formatPost = function(post) {
|
||||
let formatPostText = function(post) {
|
||||
post = post.map(x => (x === x + '') ? x.split(/(\*)/).filter(x => x != '') : x).flat();
|
||||
|
||||
let inc = false;
|
||||
let formatType = -1;
|
||||
let wasInc = false;
|
||||
let dir = 1;
|
||||
|
||||
let newArr = [];
|
||||
|
||||
for (var i = 0; i < post.length; i++) {
|
||||
inc = (post[i] == '*');
|
||||
|
||||
if (inc) {
|
||||
formatType += dir;
|
||||
} else {
|
||||
if (wasInc) dir = !dir + 0;
|
||||
if (dir) formatType = -1;
|
||||
|
||||
let formatTypeStr = formats[formatType] || 'default';
|
||||
|
||||
if (post[i] === post[i] + '') {
|
||||
newArr.push({'type': 'text', 'format': formatTypeStr, 'content': post[i]})
|
||||
} else {
|
||||
let content = post[i];
|
||||
content.format = formatTypeStr;
|
||||
newArr.push(content);
|
||||
}
|
||||
}
|
||||
|
||||
wasInc = inc;
|
||||
}
|
||||
|
||||
console.log(newArr);
|
||||
|
||||
return newArr;
|
||||
}
|
||||
|
||||
let formatPost = function(post, ignoreImg) {
|
||||
post = post.split('\n');
|
||||
|
||||
post = post.map(subPost => {
|
||||
return subPost.split(' ');
|
||||
return subPost.split(/( )/);
|
||||
});
|
||||
|
||||
post = post.map(line => {
|
||||
|
@ -78,7 +122,7 @@ let formatPost = function(post) {
|
|||
var splitPost = subPost.split('||');
|
||||
|
||||
|
||||
if (splitPost.length > 1) {
|
||||
if (splitPost.length > 1 && !ignoreImg) {
|
||||
var cap1 = splitPost[0];
|
||||
|
||||
if (cap1 == 'img') {
|
||||
|
@ -94,7 +138,7 @@ let formatPost = function(post) {
|
|||
} else if (subPost[0] == '@' || subPost[0] == '#') {
|
||||
var subPostIn = safeName(subPost.substring(0));
|
||||
|
||||
var type = (subPost[0] == '@') ? 'user' : 'post';
|
||||
var type = (subPost[0] == '@') ? 'users' : 'post';
|
||||
|
||||
splitPost = {'type': 'link', 'display': subPost, 'subtype': type, 'url': `/${type}/${subPostIn}`};
|
||||
|
||||
|
@ -103,7 +147,7 @@ let formatPost = function(post) {
|
|||
var url;
|
||||
var extension = subPost.split('.').pop().toLowerCase();
|
||||
|
||||
if (EXTENSION_MAP[extension]) {
|
||||
if (EXTENSION_MAP[extension] && !ignoreImg) {
|
||||
url = `/embed?url=${encodeURIComponent(subPost)}`;
|
||||
splitPost = [
|
||||
{'type': 'link', 'display': subPost, 'url': url},
|
||||
|
@ -119,7 +163,7 @@ let formatPost = function(post) {
|
|||
|
||||
return subPost;
|
||||
})
|
||||
return line.flat();
|
||||
return formatPostText(line.flat());
|
||||
});
|
||||
|
||||
return post;
|
||||
|
|
|
@ -4,5 +4,8 @@ export async function load({ fetch }) {
|
|||
|
||||
const username = await res.json();
|
||||
|
||||
return { username: username.data };
|
||||
const res2 = await fetch(`/api/messages`);
|
||||
const read = (await res2.json()).data.read;
|
||||
|
||||
return { username: username.data, read };
|
||||
}
|
12
src/routes/messages/+page.js
Normal file
12
src/routes/messages/+page.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export async function load({ fetch, url }) {
|
||||
var page = url.searchParams.get('page') * 1;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const res = await fetch(`/api/messages?isRead=1`);
|
||||
const postJson = (await res.json()).data.msg;
|
||||
return {postJson, id: page};
|
||||
}
|
44
src/routes/messages/+page.svelte
Normal file
44
src/routes/messages/+page.svelte
Normal file
|
@ -0,0 +1,44 @@
|
|||
<script>
|
||||
import { setLocation} from '$lib/util.js';
|
||||
import PostBody from '$lib/components/PostBody.svelte';
|
||||
import Area from '$lib/components/Area.svelte';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.date {
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Area handleSubmit=''>
|
||||
<span slot="header">
|
||||
Messages
|
||||
</span>
|
||||
|
||||
<span slot='main'>
|
||||
{#if data && data.postJson}
|
||||
{#each data.postJson as post}
|
||||
<PostBody content={post.content} excludeImg={true} />
|
||||
<div class='date'>
|
||||
{new Date(post.time / 1000)}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</span>
|
||||
<p slot="footer">
|
||||
Any messages and notifications sent to your profile.
|
||||
</p>
|
||||
</Area>
|
||||
|
||||
<p>
|
||||
{#if data.id > 0}
|
||||
<a on:click={() => { window.location.search = setLocation(window.location,'page',((data.id)-1)) }} href='#'>← Page {(data.id)-1}</a>
|
||||
{/if}
|
||||
<b>Page {(data.id)}</b>
|
||||
<a on:click={() => { window.location.search = setLocation(window.location,'page',((data.id)+1)) }} href='#'>Page {(data.id)+1} →</a>
|
||||
</p>
|
|
@ -82,7 +82,7 @@
|
|||
<p>Create a post for the world to see.</p>
|
||||
<h2>Post syntax</h2>
|
||||
<p>
|
||||
<b>img||filename.blah</b> embeds a user-uploaded file in this site
|
||||
<b>img||file.name</b> embeds a user-uploaded file in this site
|
||||
</p>
|
||||
<p>
|
||||
<b>@user</b> mentions a user
|
||||
|
@ -90,5 +90,11 @@
|
|||
<p>
|
||||
<b>#post</b> replies to a post by ID
|
||||
</p>
|
||||
<p>
|
||||
<b>*x*</b> produces italic text
|
||||
</p>
|
||||
<p>
|
||||
<b>**x**</b> produces bolded text
|
||||
</p>
|
||||
</span>
|
||||
</Area>
|
66
static/read.svg
Normal file
66
static/read.svg
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="100mm"
|
||||
height="100mm"
|
||||
viewBox="0 0 100 100"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="read.svg"
|
||||
inkscape:export-filename="pfp.png"
|
||||
inkscape:export-xdpi="960.936"
|
||||
inkscape:export-ydpi="960.936"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.72693604"
|
||||
inkscape:cx="167.13988"
|
||||
inkscape:cy="241.42427"
|
||||
inkscape:window-width="1868"
|
||||
inkscape:window-height="1006"
|
||||
inkscape:window-x="24"
|
||||
inkscape:window-y="46"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showguides="true" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-26.001732,-18.540503)">
|
||||
<circle
|
||||
style="fill:#6da4ff;fill-rule:evenodd;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers;fill-opacity:1"
|
||||
id="path449"
|
||||
cx="76.001732"
|
||||
cy="68.540504"
|
||||
r="50" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
|
||||
id="rect1586"
|
||||
width="64.08213"
|
||||
height="39.604939"
|
||||
x="43.960667"
|
||||
y="48.738033"
|
||||
ry="9.901206" />
|
||||
<path
|
||||
style="fill:none;stroke:#6da4ff;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="M 43.960666,48.738033 76.001732,68.540503 108.0428,48.738033"
|
||||
id="path1752" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
66
static/unread.svg
Normal file
66
static/unread.svg
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="100mm"
|
||||
height="100mm"
|
||||
viewBox="0 0 100 100"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="unread.svg"
|
||||
inkscape:export-filename="pfp.png"
|
||||
inkscape:export-xdpi="960.936"
|
||||
inkscape:export-ydpi="960.936"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4538721"
|
||||
inkscape:cx="50.210744"
|
||||
inkscape:cy="240.39254"
|
||||
inkscape:window-width="1868"
|
||||
inkscape:window-height="1006"
|
||||
inkscape:window-x="24"
|
||||
inkscape:window-y="46"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showguides="true" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-26.001732,-18.540503)">
|
||||
<circle
|
||||
style="fill:#ff6d6d;fill-rule:evenodd;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers;fill-opacity:1"
|
||||
id="path449"
|
||||
cx="76.001732"
|
||||
cy="68.540504"
|
||||
r="50" />
|
||||
<path
|
||||
id="rect1059"
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
|
||||
d="M 56.89182,25.389756 H 95.111644 L 81.748186,84.382122 H 70.255277 Z"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-width:2.64584;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke fill markers"
|
||||
id="path1418"
|
||||
cx="76.001732"
|
||||
cy="99.319008"
|
||||
rx="11.677505"
|
||||
ry="11.018041" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
Loading…
Reference in a new issue