This commit is contained in:
Aravinth Manivannan 2023-09-19 19:58:46 +05:30
commit d68b103f36
Signed by: realaravinth
GPG key ID: F8F50389936984FF
39 changed files with 1913 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

2
.npmrc Normal file
View file

@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
18

22
.pnpm-debug.log Normal file
View file

@ -0,0 +1,22 @@
{
"0 debug pnpm:scope": {
"selected": 1
},
"1 error pnpm": {
"code": "ELIFECYCLE",
"errno": "ENOENT",
"syscall": "spawn",
"file": "sh",
"pkgid": "forgejo-notifications@0.0.1",
"stage": "dev",
"script": "vite dev",
"pkgname": "forgejo-notifications",
"err": {
"name": "pnpm",
"message": "forgejo-notifications@0.0.1 dev: `vite dev`\nspawn ENOENT",
"code": "ELIFECYCLE",
"stack": "pnpm: forgejo-notifications@0.0.1 dev: `vite dev`\nspawn ENOENT\n at ChildProcess.<anonymous> (/usr/lib/node_modules/pnpm/dist/pnpm.cjs:93465:22)\n at ChildProcess.emit (node:events:527:28)\n at maybeClose (node:internal/child_process:1090:16)\n at ChildProcess._handle.onexit (node:internal/child_process:302:5)"
}
},
"2 warn pnpm:global": " Local package.json exists, but node_modules missing, did you mean to install?"
}

3
.prettierrc Normal file
View file

@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-svelte"]
}

38
README.md Normal file
View file

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

Binary file not shown.

17
jsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

27
package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "forgejo-notifications",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"lint": " prettier src/**/** --write"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"prettier": "3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"forgejo-notifications-core": "file:../forgejo-notifications-core/forgejo-notifications-core-0.1.0-alpha-1.tgz"
}
}

1126
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

12
src/app.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

12
src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

1
src/lib/index.js Normal file
View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

26
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,26 @@
<script>
import Forgejo from "../../../forgejo-notifications-core/dist/web.js";
import Login from "./Login.svelte";
import Notifications from "./Notifications.svelte";
import Search from "./Search.svelte";
import { user } from "./store.js";
let user_value;
user.subscribe((value) => {
user_value = value;
});
</script>
<main>
<Search />
{#if user_value}
<p>Hello {user_value.username}</p>
{/if}
<Login />
<Notifications />
</main>

View file

@ -0,0 +1,8 @@
<script>
export let count = 0;
export let img;
</script>
{#if count > 0 && img}
<img src={img} /> {count}
{/if}

View file

@ -0,0 +1,33 @@
<script>
import { user } from "./store.js";
import IssueAttr from "./IssueAttr.svelte";
let user_val = [];
user.subscribe((value) => {
user_val = value;
});
export let mentions = 0;
export let assignments = 0;
export let issues = 0;
let mentions_icon = "/at-sign.svg";
let issues_icon = "/bell.svg";
</script>
<span>
{#if assignments > 0}
{#if user_val}
<IssueAttr count={user.avatar_url} img={mentions_icon} />
{/if}
{/if}
{#if mentions > 0}
<IssueAttr count={mentions} img={mentions_icon} />
{/if}
{#if issues > 0}
<IssueAttr count={issues} img={issues_icon} />
{/if}
</span>

222
src/routes/Login.svelte Normal file
View file

@ -0,0 +1,222 @@
<script>
import Forgejo from "../../../forgejo-notifications-core/dist/web.js";
import { user, api, notification, organisations, index } from "./store.js";
let instanceUrl = "";
let accessToken = "";
let user_val;
user.subscribe((value) => {
user_val = value;
});
let notification_val;
notification.subscribe((value) => {
notification_val = value;
});
let organisations_val;
organisations.subscribe((value) => {
organisations_val = value;
});
let index_val;
index.subscribe((value) => {
index_val = value;
});
const updateOrg = (n, orgs) => {
let repo = n.subject["local_issue"].repository.full_name;
let org = n.subject["local_issue"].repository.owner;
let stored_org = orgs.find((o) => o.name == org);
if (stored_org) {
let stored_repo = stored_org.repo.find((r) => r.name == repo);
if (stored_repo) {
let mentions = 0;
let issues = 1;
let assignments = 0;
if (n.subject.local_mention) {
mentions += 1;
}
if (n.subject.local_assigned) {
assignments += 1;
}
stored_org.mentions += mentions;
stored_org.assignments += assignments;
stored_org.issues += issues;
stored_repo.mentions += mentions;
stored_repo.assignments += assignments;
stored_repo.issues += issues;
} else {
let mentions = 0;
let issues = 1;
let assignments = 0;
if (n.subject.local_mention) {
mentions += 1;
}
if (n.subject.local_assigned) {
assignments += 1;
}
stored_org.repo.push({
name: repo,
mentions: mentions,
issues: issues,
assignments: assignments,
});
stored_org.mentions += mentions;
stored_org.assignments += assignments;
stored_org.issues += issues;
}
} else {
let mentions = 0;
let issues = 1;
let assignments = 0;
if (n.subject.local_mention) {
mentions += 1;
}
if (n.subject.local_assigned) {
assignments += 1;
}
orgs.push({
name: org,
mentions: mentions,
issues: issues,
assignments: assignments,
repo: [
{
name: repo,
mentions: mentions,
issues: issues,
assignments: assignments,
},
],
});
}
return orgs;
};
const indexNotification = (n, i) => {
index.update(() => {
index_val.index(n.subject.html_url, i.body, [n.subject.type]);
return index_val;
});
index.update(() => {
index_val.index(n.subject.html_url, i.title, [n.subject.type]);
return index_val;
});
i.lazy_comments_data.forEach((c) => {
index.update(() => {
index_val.index(c.html_url, c.body, [n.subject.type, "comment"]);
return index_val;
});
});
};
const login = async (e) => {
e.preventDefault();
let api_val = new Forgejo(instanceUrl);
await api_val.setTokenAuth(accessToken);
let tmpUser = await api_val.getUser();
let local_notifications = await api_val.getNotifications();
local_notifications.forEach(async (n) => {
if (n.subject.type == "Issue") {
let splits = n.subject.html_url.split("/");
let owner = splits[3];
let repo = splits[4];
let id = splits[6];
let i = await api_val.getIssue(owner, repo, id);
i = await api_val.getCommentsForIssue(i);
indexNotification(n, i);
let local_assigned = false;
if (i.assignee) {
if (i.assignee.username == user_val.username) {
local_assigned = true;
}
}
if (i.assignees) {
i.assignees.forEach((a) => {
if (a.username == user_val.username) {
local_assigned = true;
}
});
}
n.subject["local_issue"] = i;
n.subject["local_mention"] = await api_val.findMentionsInIssue(i);
}
organisations.update(() => updateOrg(n, organisations_val));
});
user.update(() => tmpUser);
api.update(() => api_val);
notification.update(() => {
return local_notifications;
});
};
</script>
<form on:submit|preventDefault={login}>
Login
<label for="instance_url"
>Instance URL
<input
required
bind:value={instanceUrl}
type="url"
name="instance_url"
id="instance_url"
/>
</label>
<label for="access_token"
>Access Token
<input
required
bind:value={accessToken}
type="password"
name="access_token"
id="access_token"
/>
</label>
<button type="submit">Login</button>
</form>
<style>
form {
width: 80%;
display: flex;
flex-direction: column;
}
input,
label,
button {
display: block;
margin: auto;
}
label,
button {
width: 60%;
}
input {
width: 100%;
}
</style>

42
src/routes/Notifications Normal file
View file

@ -0,0 +1,42 @@
<script>
import { organisations, user } from "./store.js";
import IssueAttrGrp from "./IssueAttrGrp.svelte";
let organisations_val = [];
organisations.subscribe((value) => {
organisations_val = value;
});
let user_val = [];
user.subscribe((value) => {
user_val = value;
});
</script>
<section>
<h2>Repositories</h2>
<ol>
{#each organisations_val as org}
<h3>
{org.name}
<IssueAttrGrp
mentions={org.mentions}
assignments={org.assignments}
issues={org.issues}
/>
</h3>
<ol>
{#each org.repo as repo}
<li>
{repo.name}
<IssueAttrGrp
mentions={repo.mentions}
assignments={repo.assignments}
issues={repo.issues}
/>
</li>
{/each}
</ol>
{/each}
</ol>
</section>

View file

@ -0,0 +1,46 @@
<script>
import { user, api, notification } from "./store.js";
import Repository from "./Repository.svelte";
let notification_val;
let notification_val_local;
notification.subscribe((value) => {
notification_val = value;
});
</script>
<section>
<h2>Notifications</h2>
<ol>
{#each notification_val as n}
<li>
<a href={n.subject.html_url}>
{#if n.subject.type == "Issue"}
{#if n.subject.state == "open"}
<img src="/issue-open.svg" />
{:else}
<img src="/issue-close.svg" />
{/if}
{:else if n.subject.type == "Pull"}
{#if n.subject.state == "open"}
<img src="/pr-open.svg" />
{:else if n.subject.state == "closed"}
<img src="/pr-close.svg" />
{:else if n.subject.state == "merged"}
<img src="/pr-merged.svg" />
{:else if n.subject.state == "draft"}
<img src="/pr-draft.svg" />
{/if}
{/if}
{n.subject.title}
{#if n.subject.html_url.local_mention}
<img src="/at-sign.svg" />
{/if}
</a>
</li>
{/each}
</ol>
</section>
<Repository />

View file

@ -0,0 +1,42 @@
<script>
import { organisations, user } from "./store.js";
import IssueAttrGrp from "./IssueAttrGrp.svelte";
let organisations_val = [];
organisations.subscribe((value) => {
organisations_val = value;
});
let user_val = [];
user.subscribe((value) => {
user_val = value;
});
</script>
<section>
<h2>Repositories</h2>
<ol>
{#each organisations_val as org}
<h3>
{org.name}
<IssueAttrGrp
mentions={org.mentions}
assignments={org.assignments}
issues={org.issues}
/>
</h3>
<ol>
{#each org.repo as repo}
<li>
{repo.name}
<IssueAttrGrp
mentions={repo.mentions}
assignments={repo.assignments}
issues={repo.issues}
/>
</li>
{/each}
</ol>
{/each}
</ol>
</section>

74
src/routes/Search.svelte Normal file
View file

@ -0,0 +1,74 @@
<script>
import { user, api, notification, organisations, index } from "./store.js";
let index_val;
index.subscribe((value) => {
index_val = value;
});
let search = "";
let results = [];
const findDocuments = () => {
let trimmed = search.trim();
results = index_val.search(trimmed);
if (results.length > 0) {
enableClearResults = true;
}
};
let enableClearResults = false;
const clearResults = () => {
$: results = [];
console.log("cleared");
enableClearResults = false;
};
</script>
<input
type="search"
id="search"
placeholder="Search"
bind:value={search}
on:keydown={findDocuments}
/>
{#if enableClearResults}
<button on:click={clearResults}>Clear</button>
{/if}
<ol>
<h2>Search Results</h2>
{#each results as r}
<a href={r.id}>
<li>
<p>{r.content}</p>
{r.id}
<div>
{#each r.itemType as i}
<span class="type">{i}</span>
{/each}
</div>
</li></a
>
{/each}
</ol>
<style>
.type {
background-color: grey;
padding: 2px;
margin: 1px;
}
a {
display: block;
text-decoration: none !important;
color: black !important;
border-bottom: 1px solid grey;
}
a:hover {
background-color: lightgrey;
}
</style>

View file

@ -0,0 +1,26 @@
<script>
import Forgejo from "../../../forgejo-notifications-core/dist/web.js";
import Login from "./Login.svelte";
import Notifications from "./Notifications.svelte";
import Search from "./Search.svelte";
import { user } from "./store.js";
let user_value;
user.subscribe((value) => {
user_value = value;
});
</script>
<main>
<Search />
{#if user_value}
<p>Hello {user_value.username}</p>
{/if}
<Login />
<Notifications />
</main>

View file

@ -0,0 +1,8 @@
<script>
export let count = 0;
export let img;
</script>
{#if count > 0 && img}
<img src={img} /> {count}
{/if}

View file

@ -0,0 +1,33 @@
<script>
import { user } from "./store.js";
import IssueAttr from "./IssueAttr.svelte";
let user_val = [];
user.subscribe((value) => {
user_val = value;
});
export let mentions = 0;
export let assignments = 0;
export let issues = 0;
let mentions_icon = "/at-sign.svg";
let issues_icon = "/bell.svg";
</script>
<span>
{#if assignments > 0}
{#if user_val}
<IssueAttr count={user.avatar_url} img={mentions_icon} />
{/if}
{/if}
{#if mentions > 0}
<IssueAttr count={mentions} img={mentions_icon} />
{/if}
{#if issues > 0}
<IssueAttr count={issues} img={issues_icon} />
{/if}
</span>

45
src/routes/search.js Normal file
View file

@ -0,0 +1,45 @@
export class Item {
constructor(id, content, itemType) {
this.id = id;
this.content = content;
this.itemType = itemType;
}
isType(itemType) {
if (this.itemType.find((i) => i == itemType)) {
return true;
} else {
return false;
}
}
match(query) {
return this.content.includes(query);
}
}
export class Index {
constructor() {
this.items = [];
}
index(id, content, itemType) {
this.items.push(new Item(id, content, itemType));
}
typedSearch(itemType, query) {
let filtered = this.items.filter((indexItem) =>
itemType.find((it) => indexItem.isType(it))
);
return this.innerSearch(filtered, query);
}
search(query) {
return this.innerSearch(this.items, query);
}
innerSearch(items, query) {
let matches = items.filter((i) => i.match(query));
return matches;
}
}

8
src/routes/store.js Normal file
View file

@ -0,0 +1,8 @@
import { writable } from "svelte/store";
import { Index } from "./search";
export const api = writable(null);
export const user = writable(null);
export const notification = writable([]);
export const organisations = writable([]);
export const index = writable(new Index());

1
static/at-sign.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-at-sign"><circle cx="12" cy="12" r="4"></circle><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"></path></svg>

After

Width:  |  Height:  |  Size: 322 B

1
static/bell.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>

After

Width:  |  Height:  |  Size: 321 B

1
static/chevron-down.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 269 B

1
static/chevron-up.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-up"><polyline points="18 15 12 9 6 15"></polyline></svg>

After

Width:  |  Height:  |  Size: 268 B

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

1
static/issue-close.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-issue-closed" width="16" height="16" aria-hidden="true"><path d="M11.28 6.78a.75.75 0 0 0-1.06-1.06L7.25 8.69 5.78 7.22a.75.75 0 0 0-1.06 1.06l2 2a.75.75 0 0 0 1.06 0l3.5-3.5Z"/><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0Zm-1.5 0a6.5 6.5 0 1 0-13 0 6.5 6.5 0 0 0 13 0Z"/></svg>

After

Width:  |  Height:  |  Size: 356 B

1
static/issue-open.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-issue-opened" width="16" height="16" aria-hidden="true"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"/></svg>

After

Width:  |  Height:  |  Size: 287 B

1
static/pr-close.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-git-pull-request-closed" width="16" height="16" aria-hidden="true"><path d="M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251 0 0 1 3.25 1Zm9.5 5.5a.75.75 0 0 1 .75.75v3.378a2.251 2.251 0 1 1-1.5 0V7.25a.75.75 0 0 1 .75-.75Zm-2.03-5.273a.75.75 0 0 1 1.06 0l.97.97.97-.97a.748.748 0 0 1 1.265.332.75.75 0 0 1-.205.729l-.97.97.97.97a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-.97-.97-.97.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l.97-.97-.97-.97a.75.75 0 0 1 0-1.06ZM2.5 3.25a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"/></svg>

After

Width:  |  Height:  |  Size: 739 B

1
static/pr-draft.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-git-pull-request-draft" width="16" height="16" aria-hidden="true"><path d="M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251 0 0 1 3.25 1Zm9.5 14a2.25 2.25 0 1 1 0-4.5 2.25 2.25 0 0 1 0 4.5ZM2.5 3.25a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM14 7.5a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm0-4.25a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Z"/></svg>

After

Width:  |  Height:  |  Size: 550 B

1
static/pr-merged.svg Normal file
View file

@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="text purple svg octicon-git-merge" width="16" height="16" aria-hidden="true"><path d="M5.45 5.154A4.25 4.25 0 0 0 9.25 7.5h1.378a2.251 2.251 0 1 1 0 1.5H9.25A5.734 5.734 0 0 1 5 7.123v3.505a2.25 2.25 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.95-.218ZM4.25 13.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm8.5-4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM5 3.25a.75.75 0 1 0 0 .005V3.25Z"></path></svg>

After

Width:  |  Height:  |  Size: 419 B

1
static/pr-open.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-git-pull-request" width="16" height="16" aria-hidden="true"><path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"/></svg>

After

Width:  |  Height:  |  Size: 569 B

13
svelte.config.js Normal file
View file

@ -0,0 +1,13 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

6
vite.config.js Normal file
View file

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});