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='/'>
|
<a href='/'>
|
||||||
<img src='/icon_sanifae.svg' alt='Sanifae Logo'>
|
<img src='/icon_sanifae.svg' alt='Sanifae Logo'>
|
||||||
</a>
|
</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'}
|
{#if data.username && data.username != 'false'}
|
||||||
<a href='/users/{data.username}'>
|
<a href='/users/{data.username}'>
|
||||||
{data.username}
|
{data.username}
|
||||||
|
|
|
@ -11,15 +11,24 @@
|
||||||
p {
|
p {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bold, .bolditalic {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.italic, .bolditalic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {formatPost} from '$lib/util.js';
|
import {formatPost} from '$lib/util.js';
|
||||||
|
|
||||||
export let content = '';
|
export let content = '';
|
||||||
|
export let excludeImg = false;
|
||||||
|
|
||||||
let contentSplit;
|
let contentSplit;
|
||||||
$: contentSplit = formatPost(content || '');
|
$: contentSplit = formatPost(content || '', excludeImg);
|
||||||
|
|
||||||
console.log(contentSplit);
|
console.log(contentSplit);
|
||||||
</script>
|
</script>
|
||||||
|
@ -28,27 +37,29 @@
|
||||||
{#each contentSplit as line}
|
{#each contentSplit as line}
|
||||||
<p>
|
<p>
|
||||||
{#each line as elem}
|
{#each line as elem}
|
||||||
{#if elem && elem.type == 'img'}
|
<span class={elem.format}>
|
||||||
{#if line.filter(x => x.type == 'img').length < 2}
|
{#if elem && elem.type == 'img'}
|
||||||
<img src={elem.url} class='only-img' alt='Image preview'>
|
{#if line.filter(x => x.type == 'img').length < 2}
|
||||||
{:else}
|
<img src={elem.url} class='only-img' alt='Image preview'>
|
||||||
<img src={elem.url} alt='Image preview'>
|
{:else}
|
||||||
|
<img src={elem.url} alt='Image preview'>
|
||||||
|
{/if}
|
||||||
|
{:else if elem.type == 'video'}
|
||||||
|
{#if line.filter(x => x.type == 'video').length < 2}
|
||||||
|
<video class='only-img' alt='Video preview' controls>
|
||||||
|
<source src={elem.url}>
|
||||||
|
</video>
|
||||||
|
{:else}
|
||||||
|
<video alt='Video preview' controls>
|
||||||
|
<source src={elem.url}>
|
||||||
|
</video>
|
||||||
|
{/if}
|
||||||
|
{:else if elem.type == 'link'}
|
||||||
|
<a href={elem.url}>{elem.display}</a>
|
||||||
|
{:else if elem.type == 'text'}
|
||||||
|
{elem.content}
|
||||||
{/if}
|
{/if}
|
||||||
{:else if elem.type == 'video'}
|
</span>
|
||||||
{#if line.filter(x => x.type == 'video').length < 2}
|
|
||||||
<video class='only-img' alt='Video preview' controls>
|
|
||||||
<source src={elem.url}>
|
|
||||||
</video>
|
|
||||||
{:else}
|
|
||||||
<video alt='Video preview' controls>
|
|
||||||
<source src={elem.url}>
|
|
||||||
</video>
|
|
||||||
{/if}
|
|
||||||
{:else if elem.type == 'link'}
|
|
||||||
<a href={elem.url}>{elem.display + ' '}</a>
|
|
||||||
{:else if !elem.type}
|
|
||||||
{elem + ' '}
|
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
</p>
|
</p>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import {setLocation} from '$lib/util.js';
|
import {setLocation} from '$lib/util.js';
|
||||||
|
|
||||||
export let data;
|
export let data, noRatings;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<Button clickFunc={() => { window.location.search = setLocation(window.location,'sort','hot')}}>Hot</Button>
|
{#if !noRatings}
|
||||||
<Button clickFunc={() => { window.location.search = setLocation(window.location,'sort','rating')}}>Top</Button>
|
<Button clickFunc={() => { window.location.search = setLocation(window.location,'sort','hot')}}>Hot</Button>
|
||||||
<Button clickFunc={() => { window.location.search = setLocation(window.location,'sort','time')}}>Recent</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>
|
</p>
|
||||||
|
|
||||||
{#if data && data.postJson && data.postJson.data}
|
{#if data && data.postJson && data.postJson.data}
|
||||||
|
|
|
@ -83,6 +83,13 @@ async function initDb() {
|
||||||
username CHAR(64), \
|
username CHAR(64), \
|
||||||
following 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}) => {
|
let backendProxy = async ({route, backendParams}) => {
|
||||||
|
@ -99,7 +106,7 @@ let backendProxy = async ({route, backendParams}) => {
|
||||||
console.log(user);
|
console.log(user);
|
||||||
|
|
||||||
if ((!user || user == '') && AUTH_ACTIONS.indexOf(route) != -1) return {'success': 'Not authorized.' };
|
if ((!user || user == '') && AUTH_ACTIONS.indexOf(route) != -1) return {'success': 'Not authorized.' };
|
||||||
3
|
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
if (user && user != '') isAdmin = ((await backend.userRoles({user}, {db})) || []).indexOf('Admin') != -1;
|
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}) => {
|
backend.postDelete = async ({id}, {user, admin, db}) => {
|
||||||
if (admin) {
|
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 = ?', [
|
await db.run('DELETE FROM post WHERE id = ?', [
|
||||||
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 {
|
} else {
|
||||||
await db.run('DELETE FROM post WHERE username = ? AND id = ?', [
|
await db.run('DELETE FROM post WHERE username = ? AND id = ?', [
|
||||||
user,
|
user,
|
||||||
|
@ -248,7 +262,7 @@ backend.postBulk = async ({page, id, user, cookies, sort, type}, {admin, db}) =>
|
||||||
|
|
||||||
sort = (LEGAL_SORTS[sort]) || 'rating';
|
sort = (LEGAL_SORTS[sort]) || 'rating';
|
||||||
|
|
||||||
if (sort + '' != sort) sort = 'rating';
|
if (sort + '' !== sort) sort = 'rating';
|
||||||
|
|
||||||
sort = sort.replaceAll('%d',Math.floor(new Date() * 1000));
|
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 = ?', [
|
var isCreator = (await db.all('SELECT * from post WHERE id = ?', [
|
||||||
id
|
id
|
||||||
]))[0].username == user;
|
]))[0].username;
|
||||||
|
|
||||||
if (isCreator)
|
if (isCreator == user)
|
||||||
return {success: 'fail' };
|
return {success: 'fail' };
|
||||||
|
|
||||||
await db.run('DELETE FROM vote WHERE username = ? AND id = ?', [
|
await db.run('DELETE FROM vote WHERE username = ? AND id = ?', [
|
||||||
|
@ -327,6 +341,13 @@ backend.vote = async ({id, vote}, {user, db}) => {
|
||||||
id
|
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 = ?', [
|
var user = await db.all('SELECT * from post WHERE id = ?', [
|
||||||
id
|
id
|
||||||
]) || [];
|
]) || [];
|
||||||
|
@ -353,12 +374,20 @@ backend.token = async ({cookies}, {db}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
backend.follow = async ({target}, {user, 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 = ?',[
|
var isFollowing = await db.all('SELECT * FROM follow WHERE username = ? AND following = ?',[
|
||||||
user,
|
user,
|
||||||
target
|
target
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isFollowing && isFollowing.length > 0) {
|
let unfollowed = (isFollowing && isFollowing.length > 0);
|
||||||
|
|
||||||
|
if (unfollowed) {
|
||||||
await db.run('DELETE FROM follow WHERE username = ? AND following = ?',[
|
await db.run('DELETE FROM follow WHERE username = ? AND following = ?',[
|
||||||
user,
|
user,
|
||||||
target
|
target
|
||||||
|
@ -378,9 +407,31 @@ backend.follow = async ({target}, {user, db}) => {
|
||||||
target
|
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}};
|
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 {
|
export {
|
||||||
|
|
|
@ -7,6 +7,12 @@ const EXTENSION_MAP = {
|
||||||
'mp4': 'video'
|
'mp4': 'video'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formats = [
|
||||||
|
'italic',
|
||||||
|
'bold',
|
||||||
|
'bolditalic'
|
||||||
|
];
|
||||||
|
|
||||||
let checkLength = function(string, field, lowerBound, upperBound) {
|
let checkLength = function(string, field, lowerBound, upperBound) {
|
||||||
if (string.length < lowerBound) {
|
if (string.length < lowerBound) {
|
||||||
if (string.length == 0) {
|
if (string.length == 0) {
|
||||||
|
@ -66,11 +72,49 @@ let safeName = function (text) {
|
||||||
return text.replaceAll(/[^A-Za-z0-9\-\_]/g, '');
|
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.split('\n');
|
||||||
|
|
||||||
post = post.map(subPost => {
|
post = post.map(subPost => {
|
||||||
return subPost.split(' ');
|
return subPost.split(/( )/);
|
||||||
});
|
});
|
||||||
|
|
||||||
post = post.map(line => {
|
post = post.map(line => {
|
||||||
|
@ -78,7 +122,7 @@ let formatPost = function(post) {
|
||||||
var splitPost = subPost.split('||');
|
var splitPost = subPost.split('||');
|
||||||
|
|
||||||
|
|
||||||
if (splitPost.length > 1) {
|
if (splitPost.length > 1 && !ignoreImg) {
|
||||||
var cap1 = splitPost[0];
|
var cap1 = splitPost[0];
|
||||||
|
|
||||||
if (cap1 == 'img') {
|
if (cap1 == 'img') {
|
||||||
|
@ -94,7 +138,7 @@ let formatPost = function(post) {
|
||||||
} else if (subPost[0] == '@' || subPost[0] == '#') {
|
} else if (subPost[0] == '@' || subPost[0] == '#') {
|
||||||
var subPostIn = safeName(subPost.substring(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}`};
|
splitPost = {'type': 'link', 'display': subPost, 'subtype': type, 'url': `/${type}/${subPostIn}`};
|
||||||
|
|
||||||
|
@ -103,7 +147,7 @@ let formatPost = function(post) {
|
||||||
var url;
|
var url;
|
||||||
var extension = subPost.split('.').pop().toLowerCase();
|
var extension = subPost.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
if (EXTENSION_MAP[extension]) {
|
if (EXTENSION_MAP[extension] && !ignoreImg) {
|
||||||
url = `/embed?url=${encodeURIComponent(subPost)}`;
|
url = `/embed?url=${encodeURIComponent(subPost)}`;
|
||||||
splitPost = [
|
splitPost = [
|
||||||
{'type': 'link', 'display': subPost, 'url': url},
|
{'type': 'link', 'display': subPost, 'url': url},
|
||||||
|
@ -119,7 +163,7 @@ let formatPost = function(post) {
|
||||||
|
|
||||||
return subPost;
|
return subPost;
|
||||||
})
|
})
|
||||||
return line.flat();
|
return formatPostText(line.flat());
|
||||||
});
|
});
|
||||||
|
|
||||||
return post;
|
return post;
|
||||||
|
|
|
@ -4,5 +4,8 @@ export async function load({ fetch }) {
|
||||||
|
|
||||||
const username = await res.json();
|
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>
|
<p>Create a post for the world to see.</p>
|
||||||
<h2>Post syntax</h2>
|
<h2>Post syntax</h2>
|
||||||
<p>
|
<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>
|
||||||
<p>
|
<p>
|
||||||
<b>@user</b> mentions a user
|
<b>@user</b> mentions a user
|
||||||
|
@ -90,5 +90,11 @@
|
||||||
<p>
|
<p>
|
||||||
<b>#post</b> replies to a post by ID
|
<b>#post</b> replies to a post by ID
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>*x*</b> produces italic text
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>**x**</b> produces bolded text
|
||||||
|
</p>
|
||||||
</span>
|
</span>
|
||||||
</Area>
|
</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