summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore460
-rw-r--r--IOL.BookmarkThing.sln37
-rw-r--r--LICENSE24
-rw-r--r--README.md33
-rw-r--r--src/server/Api/Internal/Account/CreateSessionRequest.cs8
-rw-r--r--src/server/Api/Internal/Account/CreateSessionRoute.cs41
-rw-r--r--src/server/Api/Internal/Account/CreateTokenRequest.cs6
-rw-r--r--src/server/Api/Internal/Account/CreateTokenRoute.cs34
-rw-r--r--src/server/Api/Internal/Account/DeleteTokenRoute.cs24
-rw-r--r--src/server/Api/Internal/Account/EndSessionRoute.cs13
-rw-r--r--src/server/Api/Internal/Account/GetArchiveRoute.cs36
-rw-r--r--src/server/Api/Internal/Account/GetProfileDataRoute.cs11
-rw-r--r--src/server/Api/Internal/Account/GetTokensRoute.cs17
-rw-r--r--src/server/Api/Internal/Account/UpdatePasswordRequest.cs6
-rw-r--r--src/server/Api/Internal/Account/UpdatePasswordRoute.cs35
-rw-r--r--src/server/Api/Internal/BaseInternalRoute.cs15
-rw-r--r--src/server/Api/Internal/Dtos/SiteReportDto.cs8
-rw-r--r--src/server/Api/Internal/Dtos/UserArchiveDto.cs32
-rw-r--r--src/server/Api/Internal/GetSiteReportRequest.cs6
-rw-r--r--src/server/Api/Internal/GetSiteReportRoute.cs54
-rw-r--r--src/server/Api/Internal/LoggedInInternalUser.cs7
-rw-r--r--src/server/Api/Internal/RouteBaseAsync.cs73
-rw-r--r--src/server/Api/Internal/RouteBaseSync.cs53
-rw-r--r--src/server/Api/V1/ApiSpecV1.cs18
-rw-r--r--src/server/Api/V1/BaseV1Route.cs17
-rw-r--r--src/server/Api/V1/Entries/CreateEntryRequest.cs27
-rw-r--r--src/server/Api/V1/Entries/CreateEntryRoute.cs34
-rw-r--r--src/server/Api/V1/Entries/DeleteEntryRoute.cs30
-rw-r--r--src/server/Api/V1/Entries/Dtos/EntryDto.cs16
-rw-r--r--src/server/Api/V1/Entries/GetEntriesRoute.cs21
-rw-r--r--src/server/Api/V1/Entries/UpdateEntryRequest.cs18
-rw-r--r--src/server/Api/V1/Entries/UpdateEntryRoute.cs45
-rw-r--r--src/server/Api/V1/LoggedInV1User.cs7
-rw-r--r--src/server/Api/V1/RouteBaseV1Async.cs73
-rw-r--r--src/server/Api/V1/RouteBaseV1Sync.cs53
-rw-r--r--src/server/GlobalUsings.cs23
-rw-r--r--src/server/IOL.BookmarkThing.Server.csproj38
-rw-r--r--src/server/Migrations/20210627082118_Initial_Migration.Designer.cs51
-rw-r--r--src/server/Migrations/20210627082118_Initial_Migration.cs31
-rw-r--r--src/server/Migrations/20211217163105_Entries.Designer.cs84
-rw-r--r--src/server/Migrations/20211217163105_Entries.cs33
-rw-r--r--src/server/Migrations/AppDbContextModelSnapshot.cs85
-rw-r--r--src/server/Models/Database/AccessToken.cs11
-rw-r--r--src/server/Models/Database/AppDbContext.cs25
-rw-r--r--src/server/Models/Database/Base.cs11
-rw-r--r--src/server/Models/Database/Entry.cs9
-rw-r--r--src/server/Models/Database/User.cs24
-rw-r--r--src/server/Models/General/ApiSpecDocument.cs9
-rw-r--r--src/server/Models/General/AppPath.cs23
-rw-r--r--src/server/Models/Result/ErrorResult.cs12
-rw-r--r--src/server/Program.cs31
-rw-r--r--src/server/Properties/launchSettings.json14
-rw-r--r--src/server/Startup.cs131
-rw-r--r--src/server/StartupTasks.cs30
-rw-r--r--src/server/StaticData/AppJsonSettings.cs11
-rw-r--r--src/server/StaticData/AppPaths.cs12
-rw-r--r--src/server/StaticData/Constants.cs7
-rw-r--r--src/server/Utilities/BasicAuthenticationHandler.cs49
-rw-r--r--src/server/Utilities/ConfigurationExtensions.cs19
-rw-r--r--src/server/Utilities/SwaggerDefaultValues.cs55
-rw-r--r--src/server/Utilities/SwaggerGenOptionsExtensions.cs37
-rw-r--r--src/webapp/index.html26
-rw-r--r--src/webapp/package-lock.json3632
-rw-r--r--src/webapp/package.json29
-rw-r--r--src/webapp/pnpm-lock.yaml1693
-rw-r--r--src/webapp/public/favicon.icobin0 -> 1150 bytes
-rw-r--r--src/webapp/src/app.svelte36
-rw-r--r--src/webapp/src/components/carbon-extras/HeaderPanelToggle.svelte50
-rw-r--r--src/webapp/src/components/entry-card.svelte19
-rw-r--r--src/webapp/src/components/entry-list.svelte29
-rw-r--r--src/webapp/src/components/forms/entry-form.svelte200
-rw-r--r--src/webapp/src/components/forms/login-form.svelte118
-rw-r--r--src/webapp/src/global.d.ts11
-rw-r--r--src/webapp/src/lib/api/account.ts27
-rw-r--r--src/webapp/src/lib/api/entries.ts73
-rw-r--r--src/webapp/src/lib/api/root.ts13
-rw-r--r--src/webapp/src/lib/configuration.ts18
-rw-r--r--src/webapp/src/lib/enums/ApplicationTheme.ts4
-rw-r--r--src/webapp/src/lib/enums/ImportServiceEnum.ts4
-rw-r--r--src/webapp/src/lib/helpers.ts32
-rw-r--r--src/webapp/src/lib/models/ICreateEntryRequest.ts5
-rw-r--r--src/webapp/src/lib/models/ICreateEntryResponse.ts7
-rw-r--r--src/webapp/src/lib/models/ICreateSessionRequest.ts19
-rw-r--r--src/webapp/src/lib/models/IEntryDto.ts6
-rw-r--r--src/webapp/src/lib/models/IErrorResult.ts4
-rw-r--r--src/webapp/src/lib/models/IGetSiteReportRequest.ts3
-rw-r--r--src/webapp/src/lib/models/ILarderBookmark.ts17
-rw-r--r--src/webapp/src/lib/models/IPinboardPin.ts11
-rw-r--r--src/webapp/src/lib/models/IPreferences.ts6
-rw-r--r--src/webapp/src/lib/models/IProfileData.ts4
-rw-r--r--src/webapp/src/lib/models/ISiteReportDto.ts5
-rw-r--r--src/webapp/src/lib/models/IUpdateEntryRequest.ts6
-rw-r--r--src/webapp/src/lib/models/PickFileOptions.ts3
-rw-r--r--src/webapp/src/lib/stores/entries.ts58
-rw-r--r--src/webapp/src/lib/stores/persistent-store.ts108
-rw-r--r--src/webapp/src/lib/stores/preferences.ts34
-rw-r--r--src/webapp/src/lib/stores/session.ts89
-rw-r--r--src/webapp/src/main.ts9
-rw-r--r--src/webapp/src/routes.ts10
-rw-r--r--src/webapp/src/routes/app/_header.svelte113
-rw-r--r--src/webapp/src/routes/app/home.svelte26
-rw-r--r--src/webapp/src/routes/app/modals/access-tokens-modal.svelte0
-rw-r--r--src/webapp/src/routes/app/modals/file-import-modal.svelte0
-rw-r--r--src/webapp/src/routes/app/modals/github-modal.svelte0
-rw-r--r--src/webapp/src/routes/app/modals/profile-modal.svelte0
-rw-r--r--src/webapp/src/routes/public/404.svelte5
-rw-r--r--src/webapp/src/routes/public/login.svelte21
-rw-r--r--src/webapp/src/styles/carbon.scss49
-rw-r--r--src/webapp/src/styles/global.scss5
-rw-r--r--src/webapp/src/vite-env.d.ts3
-rw-r--r--src/webapp/svelte.config.js6
-rw-r--r--src/webapp/tsconfig.json23
-rw-r--r--src/webapp/vite.config.js14
113 files changed, 8940 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea3868d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,460 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+**/build
+**/.svelte-kit
+**/package
+**/server-secrets.json
+**/AppData
diff --git a/IOL.BookmarkThing.sln b/IOL.BookmarkThing.sln
new file mode 100644
index 0000000..3e680db
--- /dev/null
+++ b/IOL.BookmarkThing.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOL.BookmarkThing.Server", "src\server\IOL.BookmarkThing.Server.csproj", "{E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Debug|x64.Build.0 = Debug|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Debug|x86.Build.0 = Debug|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Release|x64.ActiveCfg = Release|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Release|x64.Build.0 = Release|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Release|x86.ActiveCfg = Release|Any CPU
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {E3DEA595-4AD5-4C14-B8C9-506CBCF0D98B} = {91903BD5-A9D4-45D3-B9C4-B5C637E5E976}
+ EndGlobalSection
+EndGlobal
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fdddb29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..64c54df
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+# IOL.BookmarkThing
+
+IOL.BookmarkThing is a bookmark indexing service, it provides a web-app and web-extension for adding websites and notes into a index which aims to provide a context-relevant search engine of your own.
+A personally curated google if you want
+
+# Feature list
+ - Bookmarks
+ - A bookmark has the following schema:
+ - id guid
+ - name string
+ - url string
+ - description string
+ - tags string
+ - Folders
+ - Organise bookmarks with folders
+ - Color-based
+ - Tags
+ - Organise bookmarks with tags
+ - Color-based
+ - Import
+ - Connect to github in order to import stars into the the index
+ -
+ - Account
+ - Login with github
+ - Login with email
+ - Login/Logout
+ - Forgot password mechanism
+ - Export all data
+ - Import bookmarks
+ - Import from file (support larder.io, pinboard, custom)
+ - Delete all data
+ - Search
+ - A full text search for bookmarks
diff --git a/src/server/Api/Internal/Account/CreateSessionRequest.cs b/src/server/Api/Internal/Account/CreateSessionRequest.cs
new file mode 100644
index 0000000..24c28b4
--- /dev/null
+++ b/src/server/Api/Internal/Account/CreateSessionRequest.cs
@@ -0,0 +1,8 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class CreateSessionRequest
+{
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public bool Persist { get; set; }
+}
diff --git a/src/server/Api/Internal/Account/CreateSessionRoute.cs b/src/server/Api/Internal/Account/CreateSessionRoute.cs
new file mode 100644
index 0000000..09e05b6
--- /dev/null
+++ b/src/server/Api/Internal/Account/CreateSessionRoute.cs
@@ -0,0 +1,41 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class CreateSessionRoute : RouteBaseInternalAsync.WithRequest<CreateSessionRequest>.WithActionResult
+{
+ private readonly AppDbContext _context;
+
+ public CreateSessionRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ [AllowAnonymous]
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpPost("~/v{version:apiVersion}/account/create-session")]
+ public override async Task<ActionResult> HandleAsync(CreateSessionRequest payload, CancellationToken cancellationToken = default) {
+ var user = _context.Users.SingleOrDefault(u => u.Username == payload.Username);
+ if (user == default || !user.VerifyPassword(payload.Password)) {
+ return BadRequest(new ErrorResult("Invalid username or password"));
+ }
+
+ var claims = user.DefaultClaims();
+ var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
+ var principal = new ClaimsPrincipal(identity);
+ var authenticationProperties = new AuthenticationProperties {
+ AllowRefresh = true,
+ IssuedUtc = DateTimeOffset.UtcNow,
+ };
+
+ if (payload.Persist) {
+ authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddYears(100);
+ authenticationProperties.IsPersistent = true;
+ }
+
+ await HttpContext.SignInAsync(principal, authenticationProperties);
+ // Return new LoggedInInternalUser here, because it is not materialised in AppControllerBase yet.
+ return Ok(new LoggedInInternalUser {
+ Id = user.Id,
+ Username = user.Username
+ });
+ }
+}
diff --git a/src/server/Api/Internal/Account/CreateTokenRequest.cs b/src/server/Api/Internal/Account/CreateTokenRequest.cs
new file mode 100644
index 0000000..399bdfc
--- /dev/null
+++ b/src/server/Api/Internal/Account/CreateTokenRequest.cs
@@ -0,0 +1,6 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class CreateTokenRequest
+{
+ public string Name { get; set; }
+}
diff --git a/src/server/Api/Internal/Account/CreateTokenRoute.cs b/src/server/Api/Internal/Account/CreateTokenRoute.cs
new file mode 100644
index 0000000..ea0e01f
--- /dev/null
+++ b/src/server/Api/Internal/Account/CreateTokenRoute.cs
@@ -0,0 +1,34 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class CreateTokenRoute : RouteBaseInternalSync.WithRequest<CreateTokenRequest>.WithActionResult
+{
+ private readonly AppDbContext _context;
+
+ public CreateTokenRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpPost("~/v{version:apiVersion}/account/create-token")]
+ public override ActionResult Handle(CreateTokenRequest request) {
+ var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id);
+ if (user == default) {
+ return NotFound(new ErrorResult("User does not exist"));
+ }
+
+ if (request.Name.IsNullOrWhiteSpace()) {
+ return BadRequest(new ErrorResult("Token name is required"));
+ }
+
+ var token = new AccessToken {
+ Id = Guid.NewGuid(),
+ Name = request.Name,
+ User = user
+ };
+
+ _context.AccessTokens.Add(token);
+ _context.SaveChanges();
+ return Ok(token);
+ }
+}
diff --git a/src/server/Api/Internal/Account/DeleteTokenRoute.cs b/src/server/Api/Internal/Account/DeleteTokenRoute.cs
new file mode 100644
index 0000000..f423b6f
--- /dev/null
+++ b/src/server/Api/Internal/Account/DeleteTokenRoute.cs
@@ -0,0 +1,24 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class DeleteTokenRoute : RouteBaseInternalSync.WithRequest<Guid>.WithActionResult
+{
+ private readonly AppDbContext _context;
+
+ public DeleteTokenRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpDelete("~/v{version:apiVersion}/account/delete-token")]
+ public override ActionResult Handle(Guid id) {
+ var token = _context.AccessTokens.SingleOrDefault(c => c.Id == id);
+ if (token == default) {
+ return NotFound();
+ }
+
+ _context.AccessTokens.Remove(token);
+ _context.SaveChanges();
+ return Ok();
+ }
+}
diff --git a/src/server/Api/Internal/Account/EndSessionRoute.cs b/src/server/Api/Internal/Account/EndSessionRoute.cs
new file mode 100644
index 0000000..4f32168
--- /dev/null
+++ b/src/server/Api/Internal/Account/EndSessionRoute.cs
@@ -0,0 +1,13 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class EndSessionRoute : RouteBaseInternalAsync.WithoutRequest.WithActionResult
+{
+ [AllowAnonymous]
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpGet("~/v{version:apiVersion}/account/end-session")]
+ public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) {
+ await HttpContext.SignOutAsync();
+ return Ok();
+ }
+}
diff --git a/src/server/Api/Internal/Account/GetArchiveRoute.cs b/src/server/Api/Internal/Account/GetArchiveRoute.cs
new file mode 100644
index 0000000..5dc006e
--- /dev/null
+++ b/src/server/Api/Internal/Account/GetArchiveRoute.cs
@@ -0,0 +1,36 @@
+using IOL.BookmarkThing.Server.Api.Internal.Dtos;
+
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class GetArchiveRoute : RouteBaseInternalSync.WithoutRequest.WithActionResult
+{
+ private readonly AppDbContext _context;
+
+ public GetArchiveRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpGet("~/v{version:apiVersion}/account/archive")]
+ public override ActionResult Handle() {
+ var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id);
+ if (user == default) {
+ return NotFound();
+ }
+
+ var entries = _context.Entries.Where(c => c.UserId == user.Id);
+ var archive = new UserArchiveDto() {
+ Created = DateTime.UtcNow,
+ User = new UserArchiveDto.UserArchiveUser(user),
+ Entries = entries.Select(c => new UserArchiveDto.UserArchiveEntry(c)).ToList()
+ };
+ var jsonOptions = new JsonSerializerOptions {
+ WriteIndented = true
+ };
+ var archiveBytes = JsonSerializer.SerializeToUtf8Bytes(archive, jsonOptions);
+ return File(archiveBytes,
+ "application/json",
+ "bookmark-thing-archive-" + user.Username + "-" + DateTime.UtcNow.ToString("yyyyMMddTHHmmss") + ".json");
+ }
+}
diff --git a/src/server/Api/Internal/Account/GetProfileDataRoute.cs b/src/server/Api/Internal/Account/GetProfileDataRoute.cs
new file mode 100644
index 0000000..adf1cba
--- /dev/null
+++ b/src/server/Api/Internal/Account/GetProfileDataRoute.cs
@@ -0,0 +1,11 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class GetProfileDataRoute : RouteBaseInternalSync.WithoutRequest.WithActionResult<LoggedInInternalUser>
+{
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpGet("~/v{version:apiVersion}/account/profile-data")]
+ public override ActionResult<LoggedInInternalUser> Handle() {
+ return Ok(LoggedInUser);
+ }
+}
diff --git a/src/server/Api/Internal/Account/GetTokensRoute.cs b/src/server/Api/Internal/Account/GetTokensRoute.cs
new file mode 100644
index 0000000..7e87bc7
--- /dev/null
+++ b/src/server/Api/Internal/Account/GetTokensRoute.cs
@@ -0,0 +1,17 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class GetTokensRoute : RouteBaseInternalSync.WithoutRequest.WithResult<ActionResult<IList<AccessToken>>>
+{
+ private readonly AppDbContext _context;
+
+ public GetTokensRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpGet("~/v{version:apiVersion}/account/tokens")]
+ public override ActionResult<IList<AccessToken>> Handle() {
+ return Ok(_context.AccessTokens.Where(c => c.User.Id == LoggedInUser.Id));
+ }
+}
diff --git a/src/server/Api/Internal/Account/UpdatePasswordRequest.cs b/src/server/Api/Internal/Account/UpdatePasswordRequest.cs
new file mode 100644
index 0000000..1773bb0
--- /dev/null
+++ b/src/server/Api/Internal/Account/UpdatePasswordRequest.cs
@@ -0,0 +1,6 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class UpdatePasswordRequest
+{
+ public string NewPassword { get; set; }
+}
diff --git a/src/server/Api/Internal/Account/UpdatePasswordRoute.cs b/src/server/Api/Internal/Account/UpdatePasswordRoute.cs
new file mode 100644
index 0000000..d06e850
--- /dev/null
+++ b/src/server/Api/Internal/Account/UpdatePasswordRoute.cs
@@ -0,0 +1,35 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Account;
+
+public class UpdatePasswordRoute : RouteBaseInternalSync.WithRequest<UpdatePasswordRequest>.WithActionResult
+{
+ private readonly AppDbContext _context;
+
+ public UpdatePasswordRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpPost("~/v{version:apiVersion}/account/update-password")]
+ public override ActionResult Handle(UpdatePasswordRequest payload) {
+ if (payload.NewPassword.IsNullOrWhiteSpace()) {
+ return BadRequest(new ErrorResult("Invalid request",
+ "The new password field is required"));
+ }
+
+ if (payload.NewPassword.Length < 6) {
+ return BadRequest(new ErrorResult("Invalid request",
+ "The new password must contain atleast 6 characters"));
+ }
+
+ var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id);
+ if (user == default) {
+ HttpContext.SignOutAsync();
+ return StatusCode(403);
+ }
+
+ user.HashAndSetPassword(payload.NewPassword);
+ _context.SaveChanges();
+ return Ok();
+ }
+}
diff --git a/src/server/Api/Internal/BaseInternalRoute.cs b/src/server/Api/Internal/BaseInternalRoute.cs
new file mode 100644
index 0000000..2f92c8e
--- /dev/null
+++ b/src/server/Api/Internal/BaseInternalRoute.cs
@@ -0,0 +1,15 @@
+namespace IOL.BookmarkThing.Server.Api.Internal;
+
+/// <inheritdoc />
+[Authorize]
+[ApiController]
+public class BaseInternalRoute : ControllerBase
+{
+ /// <summary>
+ /// User data for the currently logged on user.
+ /// </summary>
+ protected LoggedInInternalUser LoggedInUser => new() {
+ Username = User.Identity?.Name,
+ Id = User.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value.ToGuid() ?? default
+ };
+}
diff --git a/src/server/Api/Internal/Dtos/SiteReportDto.cs b/src/server/Api/Internal/Dtos/SiteReportDto.cs
new file mode 100644
index 0000000..9c4aba8
--- /dev/null
+++ b/src/server/Api/Internal/Dtos/SiteReportDto.cs
@@ -0,0 +1,8 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Dtos;
+
+public class SiteReportDto
+{
+ public string Description { get; set; }
+ public bool Duplicate { get; set; }
+ public bool Unreachable { get; set; }
+}
diff --git a/src/server/Api/Internal/Dtos/UserArchiveDto.cs b/src/server/Api/Internal/Dtos/UserArchiveDto.cs
new file mode 100644
index 0000000..ec691b3
--- /dev/null
+++ b/src/server/Api/Internal/Dtos/UserArchiveDto.cs
@@ -0,0 +1,32 @@
+namespace IOL.BookmarkThing.Server.Api.Internal.Dtos;
+
+public class UserArchiveDto
+{
+ public DateTime Created { get; set; }
+ public UserArchiveUser User { get; set; }
+ public List<UserArchiveEntry> Entries { get; set; }
+
+ public class UserArchiveUser
+ {
+ public UserArchiveUser(User user) {
+ Created = user.Created;
+ Username = user.Username;
+ }
+
+ public DateTime Created { get; set; }
+ public string Username { get; set; }
+ }
+
+ public class UserArchiveEntry
+ {
+ public UserArchiveEntry(Entry entry) {
+ Url = entry.Url;
+ Description = entry.Description;
+ Tags = entry.Tags;
+ }
+
+ public Uri Url { get; set; }
+ public string Description { get; set; }
+ public string Tags { get; set; }
+ }
+}
diff --git a/src/server/Api/Internal/GetSiteReportRequest.cs b/src/server/Api/Internal/GetSiteReportRequest.cs
new file mode 100644
index 0000000..52fbfe8
--- /dev/null
+++ b/src/server/Api/Internal/GetSiteReportRequest.cs
@@ -0,0 +1,6 @@
+namespace IOL.BookmarkThing.Server.Api.Internal;
+
+public class GetSiteReportRequest
+{
+ public Uri Url { get; set; }
+}
diff --git a/src/server/Api/Internal/GetSiteReportRoute.cs b/src/server/Api/Internal/GetSiteReportRoute.cs
new file mode 100644
index 0000000..58d6637
--- /dev/null
+++ b/src/server/Api/Internal/GetSiteReportRoute.cs
@@ -0,0 +1,54 @@
+using HtmlAgilityPack;
+using IOL.BookmarkThing.Server.Api.Internal.Dtos;
+
+namespace IOL.BookmarkThing.Server.Api.Internal;
+
+public class GetSiteReportRoute : RouteBaseInternalAsync.WithRequest<GetSiteReportRequest>.WithActionResult
+{
+ private readonly HttpClient _client;
+ private readonly ILogger<GetSiteReportRequest> _logger;
+ private readonly AppDbContext _context;
+
+ public GetSiteReportRoute(HttpClient client, ILogger<GetSiteReportRequest> logger, AppDbContext context) {
+ _client = client;
+ _logger = logger;
+ _context = context;
+ }
+
+ [ApiVersionNeutral]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [HttpPost("~/v{version:apiVersion}/site-report")]
+ public override async Task<ActionResult> HandleAsync(GetSiteReportRequest request, CancellationToken cancellationToken = default) {
+ var report = new SiteReportDto();
+ var exists = _context.Entries.Any(c => c.Url == request.Url && c.UserId == LoggedInUser.Id);
+ if (exists) {
+ report.Duplicate = true;
+ return Ok(report);
+ }
+
+ try {
+ var http_request = await _client.GetAsync(request.Url, cancellationToken);
+ if (http_request.IsSuccessStatusCode) {
+ try {
+ var document = new HtmlDocument();
+ document.Load(await http_request.Content.ReadAsStreamAsync(cancellationToken));
+ var htmlNodes = document.DocumentNode.Descendants("meta")
+ .Where(p => p.GetAttributeValue("name", string.Empty).Equals("description", StringComparison.InvariantCultureIgnoreCase));
+ report.Description = htmlNodes.FirstOrDefault()?.GetAttributeValue("content", string.Empty);
+ } catch (Exception e) {
+ _logger.LogWarning(e, "An error occured when parsing site for site report");
+ }
+ } else {
+ report.Unreachable = true;
+ }
+ } catch (Exception e) {
+ if (e is HttpRequestException) {
+ report.Unreachable = true;
+ }
+
+ _logger.LogError(e, "An error occured when getting external site for site report");
+ }
+
+ return Ok(report);
+ }
+}
diff --git a/src/server/Api/Internal/LoggedInInternalUser.cs b/src/server/Api/Internal/LoggedInInternalUser.cs
new file mode 100644
index 0000000..e08dd51
--- /dev/null
+++ b/src/server/Api/Internal/LoggedInInternalUser.cs
@@ -0,0 +1,7 @@
+namespace IOL.BookmarkThing.Server.Api.Internal;
+
+public class LoggedInInternalUser
+{
+ public Guid Id { get; set; }
+ public string Username { get; set; }
+}
diff --git a/src/server/Api/Internal/RouteBaseAsync.cs b/src/server/Api/Internal/RouteBaseAsync.cs
new file mode 100644
index 0000000..b5ad709
--- /dev/null
+++ b/src/server/Api/Internal/RouteBaseAsync.cs
@@ -0,0 +1,73 @@
+namespace IOL.BookmarkThing.Server.Api.Internal;
+
+/// <summary>
+/// A base class for an endpoint that accepts parameters.
+/// </summary>
+public static class RouteBaseInternalAsync
+{
+ public static class WithRequest<TRequest>
+ {
+ public abstract class WithResult<TResponse> : BaseInternalRoute
+ {
+ public abstract Task<TResponse> HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithoutResult : BaseInternalRoute
+ {
+ public abstract Task HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseInternalRoute
+ {
+ public abstract Task<ActionResult<TResponse>> HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult : BaseInternalRoute
+ {
+ public abstract Task<ActionResult> HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+ }
+
+ public static class WithoutRequest
+ {
+ public abstract class WithResult<TResponse> : BaseInternalRoute
+ {
+ public abstract Task<TResponse> HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithoutResult : BaseInternalRoute
+ {
+ public abstract Task HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseInternalRoute
+ {
+ public abstract Task<ActionResult<TResponse>> HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult : BaseInternalRoute
+ {
+ public abstract Task<ActionResult> HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+ }
+}
diff --git a/src/server/Api/Internal/RouteBaseSync.cs b/src/server/Api/Internal/RouteBaseSync.cs
new file mode 100644
index 0000000..128a3a9
--- /dev/null
+++ b/src/server/Api/Internal/RouteBaseSync.cs
@@ -0,0 +1,53 @@
+namespace IOL.BookmarkThing.Server.Api.Internal;
+
+/// <summary>
+/// A base class for an endpoint that accepts parameters.
+/// </summary>
+public static class RouteBaseInternalSync
+{
+ public static class WithRequest<TRequest>
+ {
+ public abstract class WithResult<TResponse> : BaseInternalRoute
+ {
+ public abstract TResponse Handle(TRequest request);
+ }
+
+ public abstract class WithoutResult : BaseInternalRoute
+ {
+ public abstract void Handle(TRequest request);
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseInternalRoute
+ {
+ public abstract ActionResult<TResponse> Handle(TRequest request);
+ }
+
+ public abstract class WithActionResult : BaseInternalRoute
+ {
+ public abstract ActionResult Handle(TRequest entry);
+ }
+ }
+
+ public static class WithoutRequest
+ {
+ public abstract class WithResult<TResponse> : BaseInternalRoute
+ {
+ public abstract TResponse Handle();
+ }
+
+ public abstract class WithoutResult : BaseInternalRoute
+ {
+ public abstract void Handle();
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseInternalRoute
+ {
+ public abstract ActionResult<TResponse> Handle();
+ }
+
+ public abstract class WithActionResult : BaseInternalRoute
+ {
+ public abstract ActionResult Handle();
+ }
+ }
+}
diff --git a/src/server/Api/V1/ApiSpecV1.cs b/src/server/Api/V1/ApiSpecV1.cs
new file mode 100644
index 0000000..c1d1cbf
--- /dev/null
+++ b/src/server/Api/V1/ApiSpecV1.cs
@@ -0,0 +1,18 @@
+namespace IOL.BookmarkThing.Server.Api.V1;
+
+public static class ApiSpecV1
+{
+ private const int MAJOR = 1;
+ private const int MINOR = 0;
+ public const string VERSION_STRING = "1.0";
+
+ public static ApiSpecDocument Document => new() {
+ Version = new ApiVersion(MAJOR, MINOR),
+ VersionName = VERSION_STRING,
+ SwaggerPath = $"/swagger/{VERSION_STRING}/swagger.json",
+ OpenApiInfo = new OpenApiInfo {
+ Title = Constants.API_NAME,
+ Version = VERSION_STRING
+ }
+ };
+}
diff --git a/src/server/Api/V1/BaseV1Route.cs b/src/server/Api/V1/BaseV1Route.cs
new file mode 100644
index 0000000..ba7d978
--- /dev/null
+++ b/src/server/Api/V1/BaseV1Route.cs
@@ -0,0 +1,17 @@
+namespace IOL.BookmarkThing.Server.Api.V1;
+
+/// <inheritdoc />
+[Authorize(AuthenticationSchemes = AuthSchemes)]
+[ApiController]
+public class BaseV1Route : ControllerBase
+{
+ private const string AuthSchemes = CookieAuthenticationDefaults.AuthenticationScheme + "," + Constants.BASIC_AUTH_SCHEME;
+
+ /// <summary>
+ /// User data for the currently logged on user.
+ /// </summary>
+ protected LoggedInV1User LoggedInUser => new() {
+ Username = User.Identity?.Name,
+ Id = User.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value.ToGuid() ?? default
+ };
+}
diff --git a/src/server/Api/V1/Entries/CreateEntryRequest.cs b/src/server/Api/V1/Entries/CreateEntryRequest.cs
new file mode 100644
index 0000000..a9c5070
--- /dev/null
+++ b/src/server/Api/V1/Entries/CreateEntryRequest.cs
@@ -0,0 +1,27 @@
+namespace IOL.BookmarkThing.Server.Api.V1.Entries;
+
+public class CreateEntryRequest
+{
+ public Uri Url { get; set; }
+ public string Description { get; set; }
+ public string Tags { get; set; }
+
+ public List<ErrorResult> GetErrors() {
+ var errors = new List<ErrorResult>();
+ if (Url == default) {
+ errors.Add(new ErrorResult("Url is a required field"));
+ }
+
+ return errors;
+ }
+
+ public Entry AsDbEntity(Guid userId) {
+ return new Entry {
+ Id = Guid.NewGuid(),
+ UserId = userId,
+ Url = Url,
+ Description = Description,
+ Tags = Tags
+ };
+ }
+}
diff --git a/src/server/Api/V1/Entries/CreateEntryRoute.cs b/src/server/Api/V1/Entries/CreateEntryRoute.cs
new file mode 100644
index 0000000..ebe49fc
--- /dev/null
+++ b/src/server/Api/V1/Entries/CreateEntryRoute.cs
@@ -0,0 +1,34 @@
+using IOL.BookmarkThing.Server.Api.V1.Entries.Dtos;
+
+namespace IOL.BookmarkThing.Server.Api.V1.Entries;
+
+public class CreateEntryRoute : RouteBaseV1Sync.WithRequest<CreateEntryRequest>.WithActionResult<EntryDto>
+{
+ private readonly AppDbContext _context;
+
+ public CreateEntryRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ /// <summary>
+ /// Create a new entry
+ /// </summary>
+ /// <param name="entry">The entry to create</param>
+ /// <response code="200">Entry created successfully</response>
+ /// <response code="400">Invalid entry</response>
+ [ProducesResponseType(typeof(EntryDto), 200)]
+ [ProducesResponseType(typeof(List<ErrorResult>), 400)]
+ [ApiVersion(ApiSpecV1.VERSION_STRING)]
+ [HttpPost("~/v{version:apiVersion}/entries/create")]
+ public override ActionResult<EntryDto> Handle(CreateEntryRequest entry) {
+ var errors = entry.GetErrors();
+ if (errors.Count != 0) {
+ return BadRequest(errors);
+ }
+
+ var dbEntry = entry.AsDbEntity(LoggedInUser.Id);
+ _context.Entries.Add(dbEntry);
+ _context.SaveChanges();
+ return Ok(new EntryDto(dbEntry));
+ }
+}
diff --git a/src/server/Api/V1/Entries/DeleteEntryRoute.cs b/src/server/Api/V1/Entries/DeleteEntryRoute.cs
new file mode 100644
index 0000000..fc79049
--- /dev/null
+++ b/src/server/Api/V1/Entries/DeleteEntryRoute.cs
@@ -0,0 +1,30 @@
+namespace IOL.BookmarkThing.Server.Api.V1.Entries;
+
+public class DeleteEntryRoute : RouteBaseV1Sync.WithRequest<Guid>.WithActionResult
+{
+ private readonly AppDbContext _context;
+
+ public DeleteEntryRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ /// <summary>
+ /// Delete a entry
+ /// </summary>
+ /// <param name="entryId">The guid id of the entry to delete</param>
+ /// <response code="200">Entry deleted successfully</response>
+ /// <response code="404">Entry not found</response>
+ [ProducesResponseType(typeof(ErrorResult), 404)]
+ [ApiVersion(ApiSpecV1.VERSION_STRING)]
+ [HttpDelete("~/v{version:apiVersion}/entries/{entryId:guid}")]
+ public override ActionResult Handle(Guid entryId) {
+ var entry = _context.Entries.SingleOrDefault(c => c.Id == entryId && c.UserId == LoggedInUser.Id);
+ if (entry == default) {
+ return NotFound(new ErrorResult("Entry does not exist"));
+ }
+
+ _context.Remove(entry);
+ _context.SaveChanges();
+ return Ok();
+ }
+}
diff --git a/src/server/Api/V1/Entries/Dtos/EntryDto.cs b/src/server/Api/V1/Entries/Dtos/EntryDto.cs
new file mode 100644
index 0000000..5161373
--- /dev/null
+++ b/src/server/Api/V1/Entries/Dtos/EntryDto.cs
@@ -0,0 +1,16 @@
+namespace IOL.BookmarkThing.Server.Api.V1.Entries.Dtos;
+
+public class EntryDto
+{
+ public EntryDto(Entry dbEntry) {
+ Id = dbEntry.Id;
+ Url = dbEntry.Url;
+ Description = dbEntry.Description;
+ Tags = dbEntry.Tags;
+ }
+
+ public Guid Id { get; set; }
+ public Uri Url { get; set; }
+ public string Description { get; set; }
+ public string Tags { get; set; }
+}
diff --git a/src/server/Api/V1/Entries/GetEntriesRoute.cs b/src/server/Api/V1/Entries/GetEntriesRoute.cs
new file mode 100644
index 0000000..adadf01
--- /dev/null
+++ b/src/server/Api/V1/Entries/GetEntriesRoute.cs
@@ -0,0 +1,21 @@
+using IOL.BookmarkThing.Server.Api.V1.Entries.Dtos;
+
+namespace IOL.BookmarkThing.Server.Api.V1.Entries;
+
+public class GetEntriesRoute : RouteBaseV1Sync.WithoutRequest.WithActionResult<List<EntryDto>>
+{
+ private readonly AppDbContext _context;
+
+ public GetEntriesRoute(AppDbContext context) {
+ _context = context;
+ }
+
+ /// <summary>
+ /// Get all entries
+ /// </summary>
+ [ApiVersion(ApiSpecV1.VERSION_STRING)]
+ [HttpGet("~/v{version:apiVersion}/entries")]
+ public override ActionResult<List<EntryDto>> Handle() {
+ return Ok(_context.Entries.Where(c => c.UserId == LoggedInUser.Id).Select(c => new EntryDto(c)));
+ }
+}
diff --git a/src/server/Api/V1/Entries/UpdateEntryRequest.cs b/src/server/Api/V1/Entries/UpdateEntryRequest.cs
new file mode 100644
index 0000000..819e2d2
--- /dev/null
+++ b/src/server/Api/V1/Entries/UpdateEntryRequest.cs
@@ -0,0 +1,18 @@
+namespace IOL.BookmarkThing.Server.Api.V1.Entries;
+
+public class UpdateEntryRequest
+{
+ public Guid Id { get; set; }
+ public Uri Url { get; set; }
+ public string Description { get; set; }
+ public string Tags { get; set; }
+
+ public List<ErrorResult> GetErrors() {
+ var errors = new List<ErrorResult>();
+ if (Url == default) {
+ errors.Add(new ErrorResult("Url is a required field"));
+ }
+
+ return errors;
+ }
+}
diff --git a/src/server/Api/V1/Entries/UpdateEntryRoute.cs b/src/server/Api/V1/Entries/UpdateEntryRoute.cs
new file mode 100644
index 0000000..96c60fe
--- /dev/null
+++ b/src/server/Api/V1/Entries/UpdateEntryRoute.cs
@@ -0,0 +1,45 @@
+using System.Security.Cryptography;
+using IOL.BookmarkThing.Server.Api.V1.Entries.Dtos;
+
+namespace IOL.BookmarkThing.Server.Api.V1.Entries;
+
+public class UpdateEntryRoute : RouteBaseV1Sync.WithRequest<UpdateEntryRequest>.WithActionResult<EntryDto>
+{
+ private readonly AppDbContext _context;
+
+ public UpdateEntryRoute(AppDbContext context) {
+ _context = context;
+ }
+
+
+ /// <summary>
+ /// Update an entry
+ /// </summary>
+ /// <param name="entryToUpdate">The entry to update</param>
+ /// <response code="200">Entry updated successfully</response>
+ /// <response code="400">Invalid entry</response>
+ [ProducesResponseType(typeof(EntryDto), 200)]
+ [ProducesResponseType(typeof(List<ErrorResult>), 400)]
+ [ProducesResponseType(typeof(ErrorResult), 404)]
+ [ApiVersion(ApiSpecV1.VERSION_STRING)]
+ [HttpPost("~/v{version:apiVersion}/entries/update")]
+ public override ActionResult<EntryDto> Handle(UpdateEntryRequest entryToUpdate) {
+ var entry = _context.Entries.SingleOrDefault(c => c.Id == entryToUpdate.Id && c.UserId == LoggedInUser.Id);
+ if (entry == default) {
+ return NotFound(new ErrorResult("Entry does not exist"));
+ }
+
+ var errors = entryToUpdate.GetErrors();
+ if (errors.Count != 0) {
+ return BadRequest(errors);
+ }
+
+ entry.Description = entry.Description;
+ entry.Tags = entry.Tags;
+ entry.Url = entry.Url;
+
+ _context.Update(entry);
+ _context.SaveChanges();
+ return Ok(new EntryDto(entry));
+ }
+}
diff --git a/src/server/Api/V1/LoggedInV1User.cs b/src/server/Api/V1/LoggedInV1User.cs
new file mode 100644
index 0000000..8c9f67a
--- /dev/null
+++ b/src/server/Api/V1/LoggedInV1User.cs
@@ -0,0 +1,7 @@
+namespace IOL.BookmarkThing.Server.Api.V1;
+
+public class LoggedInV1User
+{
+ public Guid Id { get; set; }
+ public string Username { get; set; }
+}
diff --git a/src/server/Api/V1/RouteBaseV1Async.cs b/src/server/Api/V1/RouteBaseV1Async.cs
new file mode 100644
index 0000000..d86bc7c
--- /dev/null
+++ b/src/server/Api/V1/RouteBaseV1Async.cs
@@ -0,0 +1,73 @@
+namespace IOL.BookmarkThing.Server.Api.V1;
+
+/// <summary>
+/// A base class for an endpoint that accepts parameters.
+/// </summary>
+public static class RouteBaseV1Async
+{
+ public static class WithRequest<TRequest>
+ {
+ public abstract class WithResult<TResponse> : BaseV1Route
+ {
+ public abstract Task<TResponse> HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithoutResult : BaseV1Route
+ {
+ public abstract Task HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseV1Route
+ {
+ public abstract Task<ActionResult<TResponse>> HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult : BaseV1Route
+ {
+ public abstract Task<ActionResult> HandleAsync(
+ TRequest request,
+ CancellationToken cancellationToken = default
+ );
+ }
+ }
+
+ public static class WithoutRequest
+ {
+ public abstract class WithResult<TResponse> : BaseV1Route
+ {
+ public abstract Task<TResponse> HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithoutResult : BaseV1Route
+ {
+ public abstract Task HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseV1Route
+ {
+ public abstract Task<ActionResult<TResponse>> HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+
+ public abstract class WithActionResult : BaseV1Route
+ {
+ public abstract Task<ActionResult> HandleAsync(
+ CancellationToken cancellationToken = default
+ );
+ }
+ }
+}
diff --git a/src/server/Api/V1/RouteBaseV1Sync.cs b/src/server/Api/V1/RouteBaseV1Sync.cs
new file mode 100644
index 0000000..0bcc0ed
--- /dev/null
+++ b/src/server/Api/V1/RouteBaseV1Sync.cs
@@ -0,0 +1,53 @@
+namespace IOL.BookmarkThing.Server.Api.V1;
+
+/// <summary>
+/// A base class for an endpoint that accepts parameters.
+/// </summary>
+public static class RouteBaseV1Sync
+{
+ public static class WithRequest<TRequest>
+ {
+ public abstract class WithResult<TResponse> : BaseV1Route
+ {
+ public abstract TResponse Handle(TRequest request);
+ }
+
+ public abstract class WithoutResult : BaseV1Route
+ {
+ public abstract void Handle(TRequest request);
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseV1Route
+ {
+ public abstract ActionResult<TResponse> Handle(TRequest request);
+ }
+
+ public abstract class WithActionResult : BaseV1Route
+ {
+ public abstract ActionResult Handle(TRequest entry);
+ }
+ }
+
+ public static class WithoutRequest
+ {
+ public abstract class WithResult<TResponse> : BaseV1Route
+ {
+ public abstract TResponse Handle();
+ }
+
+ public abstract class WithoutResult : BaseV1Route
+ {
+ public abstract void Handle();
+ }
+
+ public abstract class WithActionResult<TResponse> : BaseV1Route
+ {
+ public abstract ActionResult<TResponse> Handle();
+ }
+
+ public abstract class WithActionResult : BaseV1Route
+ {
+ public abstract ActionResult Handle();
+ }
+ }
+}
diff --git a/src/server/GlobalUsings.cs b/src/server/GlobalUsings.cs
new file mode 100644
index 0000000..882de1c
--- /dev/null
+++ b/src/server/GlobalUsings.cs
@@ -0,0 +1,23 @@
+global using System.Security.Claims;
+global using System.Text.Json;
+global using System.Text.Json.Serialization;
+global using Swashbuckle.AspNetCore.SwaggerGen;
+global using Serilog;
+global using Microsoft.AspNetCore.Mvc.Versioning;
+global using IOL.Helpers;
+global using IOL.BookmarkThing.Server.Models.General;
+global using IOL.BookmarkThing.Server.Models.Result;
+global using IOL.BookmarkThing.Server.Models.Database;
+global using IOL.BookmarkThing.Server.Api;
+global using IOL.BookmarkThing.Server.StaticData;
+global using IOL.BookmarkThing.Server.Utilities;
+global using IOL.BookmarkThing.Server.Api.V1;
+global using Microsoft.OpenApi.Models;
+global using Microsoft.AspNetCore.Authentication;
+global using Microsoft.AspNetCore.Authorization;
+global using Microsoft.AspNetCore.Mvc;
+global using Microsoft.AspNetCore.Authentication.Cookies;
+global using Microsoft.AspNetCore.DataProtection;
+global using Microsoft.EntityFrameworkCore;
+global using Microsoft.AspNetCore.Mvc.ApiExplorer;
+global using Microsoft.AspNetCore.Mvc.Controllers;
diff --git a/src/server/IOL.BookmarkThing.Server.csproj b/src/server/IOL.BookmarkThing.Server.csproj
new file mode 100644
index 0000000..e68b37e
--- /dev/null
+++ b/src/server/IOL.BookmarkThing.Server.csproj
@@ -0,0 +1,38 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <UserSecretsId>29e502ec-7a83-4028-bc69-3cfcdb827037</UserSecretsId>
+ <ImplicitUsings>true</ImplicitUsings>
+ <Nullable>disable</Nullable>
+ <NoWarn>CS1591</NoWarn>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <DocumentationFile>bin\Debug\net6.0\IOL.BookmarkThing.Server.xml</DocumentationFile>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <DocumentationFile>bin\Release\net6.0\IOL.BookmarkThing.Server.xml</DocumentationFile>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
+ <PackageReference Include="HtmlAgilityPack" Version="1.11.40" />
+ <PackageReference Include="IOL.Helpers" Version="1.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.2" />
+ <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+ <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="AppData\data-protection-keys" />
+ </ItemGroup>
+</Project>
diff --git a/src/server/Migrations/20210627082118_Initial_Migration.Designer.cs b/src/server/Migrations/20210627082118_Initial_Migration.Designer.cs
new file mode 100644
index 0000000..b6002fd
--- /dev/null
+++ b/src/server/Migrations/20210627082118_Initial_Migration.Designer.cs
@@ -0,0 +1,51 @@
+// <auto-generated />
+using System;
+using IOL.BookmarkThing.Server.Models.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+namespace IOL.BookmarkThing.Server.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20210627082118_Initial_Migration")]
+ partial class Initial_Migration
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Relational:MaxIdentifierLength", 63)
+ .HasAnnotation("ProductVersion", "5.0.7")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ modelBuilder.Entity("IOL.BookmarkThing.Server.Data.Database.User", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("Created")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("created");
+
+ b.Property<string>("Password")
+ .HasColumnType("text")
+ .HasColumnName("password");
+
+ b.Property<string>("Username")
+ .HasColumnType("text")
+ .HasColumnName("username");
+
+ b.HasKey("Id")
+ .HasName("pk_users");
+
+ b.ToTable("users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/server/Migrations/20210627082118_Initial_Migration.cs b/src/server/Migrations/20210627082118_Initial_Migration.cs
new file mode 100644
index 0000000..20f5c4a
--- /dev/null
+++ b/src/server/Migrations/20210627082118_Initial_Migration.cs
@@ -0,0 +1,31 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace IOL.BookmarkThing.Server.Migrations
+{
+ public partial class Initial_Migration : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "users",
+ columns: table => new
+ {
+ id = table.Column<Guid>(type: "uuid", nullable: false),
+ username = table.Column<string>(type: "text", nullable: true),
+ password = table.Column<string>(type: "text", nullable: true),
+ created = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_users", x => x.id);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "users");
+ }
+ }
+}
diff --git a/src/server/Migrations/20211217163105_Entries.Designer.cs b/src/server/Migrations/20211217163105_Entries.Designer.cs
new file mode 100644
index 0000000..952c9c0
--- /dev/null
+++ b/src/server/Migrations/20211217163105_Entries.Designer.cs
@@ -0,0 +1,84 @@
+// <auto-generated />
+using System;
+using IOL.BookmarkThing.Server.Models.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+namespace IOL.BookmarkThing.Server.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20211217163105_Entries")]
+ partial class Entries
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Relational:MaxIdentifierLength", 63)
+ .HasAnnotation("ProductVersion", "5.0.7")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ modelBuilder.Entity("IOL.BookmarkThing.Server.Data.Database.Entry", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("Created")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("created");
+
+ b.Property<string>("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property<string>("Tags")
+ .HasColumnType("text")
+ .HasColumnName("tags");
+
+ b.Property<string>("Url")
+ .HasColumnType("text")
+ .HasColumnName("url");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("pk_entries");
+
+ b.ToTable("entries");
+ });
+
+ modelBuilder.Entity("IOL.BookmarkThing.Server.Data.Database.User", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("Created")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("created");
+
+ b.Property<string>("Password")
+ .HasColumnType("text")
+ .HasColumnName("password");
+
+ b.Property<string>("Username")
+ .HasColumnType("text")
+ .HasColumnName("username");
+
+ b.HasKey("Id")
+ .HasName("pk_users");
+
+ b.ToTable("users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/server/Migrations/20211217163105_Entries.cs b/src/server/Migrations/20211217163105_Entries.cs
new file mode 100644
index 0000000..e8acecf
--- /dev/null
+++ b/src/server/Migrations/20211217163105_Entries.cs
@@ -0,0 +1,33 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace IOL.BookmarkThing.Server.Migrations
+{
+ public partial class Entries : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "entries",
+ columns: table => new
+ {
+ id = table.Column<Guid>(type: "uuid", nullable: false),
+ user_id = table.Column<Guid>(type: "uuid", nullable: false),
+ url = table.Column<string>(type: "text", nullable: true),
+ description = table.Column<string>(type: "text", nullable: true),
+ tags = table.Column<string>(type: "text", nullable: true),
+ created = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_entries", x => x.id);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "entries");
+ }
+ }
+}
diff --git a/src/server/Migrations/AppDbContextModelSnapshot.cs b/src/server/Migrations/AppDbContextModelSnapshot.cs
new file mode 100644
index 0000000..5476b90
--- /dev/null
+++ b/src/server/Migrations/AppDbContextModelSnapshot.cs
@@ -0,0 +1,85 @@
+// <auto-generated />
+using System;
+using IOL.BookmarkThing.Server.Models.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace IOL.BookmarkThing.Server.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ partial class AppDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("IOL.BookmarkThing.Server.Data.Database.Entry", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("Created")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("created");
+
+ b.Property<string>("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property<string>("Tags")
+ .HasColumnType("text")
+ .HasColumnName("tags");
+
+ b.Property<string>("Url")
+ .HasColumnType("text")
+ .HasColumnName("url");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("pk_entries");
+
+ b.ToTable("entries", (string)null);
+ });
+
+ modelBuilder.Entity("IOL.BookmarkThing.Server.Data.Database.User", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("Created")
+ .HasColumnType("timestamp without time zone")
+ .HasColumnName("created");
+
+ b.Property<string>("Password")
+ .HasColumnType("text")
+ .HasColumnName("password");
+
+ b.Property<string>("Username")
+ .HasColumnType("text")
+ .HasColumnName("username");
+
+ b.HasKey("Id")
+ .HasName("pk_users");
+
+ b.ToTable("users", (string)null);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/server/Models/Database/AccessToken.cs b/src/server/Models/Database/AccessToken.cs
new file mode 100644
index 0000000..51ada27
--- /dev/null
+++ b/src/server/Models/Database/AccessToken.cs
@@ -0,0 +1,11 @@
+namespace IOL.BookmarkThing.Server.Models.Database;
+
+public class AccessToken : Base
+{
+ public User User { get; set; }
+ public string Name { get; set; }
+
+ public string PublicId() {
+ return Convert.ToBase64String(Id.ToByteArray());
+ }
+}
diff --git a/src/server/Models/Database/AppDbContext.cs b/src/server/Models/Database/AppDbContext.cs
new file mode 100644
index 0000000..ae1f45b
--- /dev/null
+++ b/src/server/Models/Database/AppDbContext.cs
@@ -0,0 +1,25 @@
+namespace IOL.BookmarkThing.Server.Models.Database;
+
+public class AppDbContext : DbContext
+{
+ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
+ public DbSet<User> Users { get; set; }
+ public DbSet<Entry> Entries { get; set; }
+ public DbSet<AccessToken> AccessTokens { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder) {
+ modelBuilder.Entity<User>(e => {
+ e.ToTable("users");
+ });
+
+ modelBuilder.Entity<Entry>(e => {
+ e.ToTable("entries");
+ });
+
+ modelBuilder.Entity<AccessToken>(e => {
+ e.ToTable("access_tokens");
+ });
+
+ base.OnModelCreating(modelBuilder);
+ }
+}
diff --git a/src/server/Models/Database/Base.cs b/src/server/Models/Database/Base.cs
new file mode 100644
index 0000000..e6d6646
--- /dev/null
+++ b/src/server/Models/Database/Base.cs
@@ -0,0 +1,11 @@
+namespace IOL.BookmarkThing.Server.Models.Database;
+
+public class Base
+{
+ public Base() {
+ Created = DateTime.UtcNow;
+ }
+
+ public Guid Id { get; set; }
+ public DateTime Created { get; set; }
+}
diff --git a/src/server/Models/Database/Entry.cs b/src/server/Models/Database/Entry.cs
new file mode 100644
index 0000000..a992a87
--- /dev/null
+++ b/src/server/Models/Database/Entry.cs
@@ -0,0 +1,9 @@
+namespace IOL.BookmarkThing.Server.Models.Database;
+
+public class Entry : Base
+{
+ public Guid UserId { get; set; }
+ public Uri Url { get; set; }
+ public string Description { get; set; }
+ public string Tags { get; set; }
+}
diff --git a/src/server/Models/Database/User.cs b/src/server/Models/Database/User.cs
new file mode 100644
index 0000000..1215f4e
--- /dev/null
+++ b/src/server/Models/Database/User.cs
@@ -0,0 +1,24 @@
+namespace IOL.BookmarkThing.Server.Models.Database;
+
+public class User : Base
+{
+ public User(string username) => Username = username;
+
+ public string Username { get; set; }
+ public string Password { get; set; }
+
+ public void HashAndSetPassword(string password) {
+ Password = PasswordHelper.HashPassword(password);
+ }
+
+ public bool VerifyPassword(string password) {
+ return PasswordHelper.Verify(password, Password);
+ }
+
+ public IEnumerable<Claim> DefaultClaims() {
+ return new Claim[] {
+ new(ClaimTypes.NameIdentifier, Id.ToString()),
+ new(ClaimTypes.Name, Username),
+ };
+ }
+}
diff --git a/src/server/Models/General/ApiSpecDocument.cs b/src/server/Models/General/ApiSpecDocument.cs
new file mode 100644
index 0000000..5f5c9df
--- /dev/null
+++ b/src/server/Models/General/ApiSpecDocument.cs
@@ -0,0 +1,9 @@
+namespace IOL.BookmarkThing.Server.Models.General;
+
+public class ApiSpecDocument
+{
+ public string VersionName { get; set; }
+ public string SwaggerPath { get; set; }
+ public ApiVersion Version { get; set; }
+ public OpenApiInfo OpenApiInfo { get; set; }
+}
diff --git a/src/server/Models/General/AppPath.cs b/src/server/Models/General/AppPath.cs
new file mode 100644
index 0000000..063fafe
--- /dev/null
+++ b/src/server/Models/General/AppPath.cs
@@ -0,0 +1,23 @@
+namespace IOL.BookmarkThing.Server.Models.General;
+
+public sealed record AppPath
+{
+ public string HostPath { get; init; }
+ public string WebPath { get; init; }
+
+ public string GetHostPathForFilename(string filename, string fallback = "") {
+ if (filename.IsNullOrWhiteSpace()) {
+ return fallback;
+ }
+
+ return Path.Combine(HostPath, filename);
+ }
+
+ public string GetWebPathForFilename(string filename, string fallback = "") {
+ if (filename.IsNullOrWhiteSpace()) {
+ return fallback;
+ }
+
+ return Path.Combine(WebPath, filename);
+ }
+}
diff --git a/src/server/Models/Result/ErrorResult.cs b/src/server/Models/Result/ErrorResult.cs
new file mode 100644
index 0000000..64c5a28
--- /dev/null
+++ b/src/server/Models/Result/ErrorResult.cs
@@ -0,0 +1,12 @@
+namespace IOL.BookmarkThing.Server.Models.Result;
+
+public class ErrorResult
+{
+ public ErrorResult(string title = default, string text = default) {
+ Title = title;
+ Text = text;
+ }
+
+ public string Title { get; set; }
+ public string Text { get; set; }
+}
diff --git a/src/server/Program.cs b/src/server/Program.cs
new file mode 100644
index 0000000..206d22d
--- /dev/null
+++ b/src/server/Program.cs
@@ -0,0 +1,31 @@
+namespace IOL.BookmarkThing.Server;
+
+public class Program
+{
+ public static int Main(string[] args) {
+ Log.Logger = new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .WriteTo.Console()
+ .CreateLogger();
+
+ try {
+ Log.Information("Starting web host");
+ CreateHostBuilder(args).Build().Run();
+ return 0;
+ } catch (Exception ex) {
+ Log.Fatal(ex, "Host terminated unexpectedly");
+ return 1;
+ } finally {
+ Log.CloseAndFlush();
+ }
+ }
+
+ private static IHostBuilder CreateHostBuilder(string[] args) {
+ return Host.CreateDefaultBuilder(args)
+ .UseSerilog()
+ .ConfigureWebHostDefaults(webBuilder => {
+ webBuilder.UseKestrel(o => o.AddServerHeader = false);
+ webBuilder.UseStartup<Startup>();
+ });
+ }
+}
diff --git a/src/server/Properties/launchSettings.json b/src/server/Properties/launchSettings.json
new file mode 100644
index 0000000..e494e11
--- /dev/null
+++ b/src/server/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "IOL.BookmarkThing.Server": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5003",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/src/server/Startup.cs b/src/server/Startup.cs
new file mode 100644
index 0000000..8fd6955
--- /dev/null
+++ b/src/server/Startup.cs
@@ -0,0 +1,131 @@
+using System.Reflection;
+
+namespace IOL.BookmarkThing.Server;
+
+public class Startup
+{
+ public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) {
+ Configuration = configuration;
+ WebHostEnvironment = webHostEnvironment;
+ }
+
+ private IWebHostEnvironment WebHostEnvironment { get; }
+ private IConfiguration Configuration { get; }
+
+ private string AppDatabaseConnectionString() {
+ var host = Configuration.GetValue<string>("DB_HOST");
+ var port = Configuration.GetValue<string>("DB_PORT");
+ var database = Configuration.GetValue<string>("DB_NAME");
+ var user = Configuration.GetValue<string>("DB_USER");
+ var password = Configuration.GetValue<string>("DB_PASSWORD");
+ return $"Server={host};Port={port};Database={database};User Id={user};Password={password}";
+ }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services) {
+ services.AddDataProtection()
+ .PersistKeysToFileSystem(new(AppPaths.DataProtectionKeys.HostPath));
+
+ if (WebHostEnvironment.IsDevelopment()) {
+ services.AddCors();
+ }
+
+ services.Configure(AppJsonSettings.Default);
+
+ services.AddDbContext<AppDbContext>(options => {
+ options.UseNpgsql(AppDatabaseConnectionString(),
+ builder => {
+ builder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
+ builder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), default);
+ })
+ .UseSnakeCaseNamingConvention();
+ if (WebHostEnvironment.IsDevelopment()) {
+ options.EnableSensitiveDataLogging();
+ }
+ });
+
+ services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
+ .AddCookie(options => {
+ options.Cookie.Name = "bookmarkthing_session";
+ options.Cookie.SameSite = SameSiteMode.Strict;
+ options.Cookie.HttpOnly = true;
+ options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
+ options.Cookie.IsEssential = true;
+ options.SlidingExpiration = true;
+ options.Events.OnRedirectToAccessDenied =
+ options.Events.OnRedirectToLogin = c => {
+ c.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ return Task.FromResult<object>(null);
+ };
+ })
+ .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(Constants.BASIC_AUTH_SCHEME, default);
+
+ services.AddLogging();
+ services.AddHttpClient();
+ services.AddControllers()
+ .AddJsonOptions(AppJsonSettings.Default);
+
+ services.AddApiVersioning(options => {
+ options.ApiVersionReader = new UrlSegmentApiVersionReader();
+ options.ReportApiVersions = true;
+ options.AssumeDefaultVersionWhenUnspecified = false;
+ });
+ services.AddVersionedApiExplorer(options => {
+ options.SubstituteApiVersionInUrl = true;
+ });
+ services.AddSwaggerGen(options => {
+ options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, Assembly.GetExecutingAssembly().GetName().Name + ".xml"));
+ options.UseApiEndpoints();
+ options.OperationFilter<SwaggerDefaultValues>();
+ options.SwaggerDoc(ApiSpecV1.Document.VersionName, ApiSpecV1.Document.OpenApiInfo);
+ options.AddSecurityDefinition("Basic",
+ new OpenApiSecurityScheme {
+ Name = "Authorization",
+ Type = SecuritySchemeType.ApiKey,
+ Scheme = "Basic",
+ BearerFormat = "Custom",
+ In = ParameterLocation.Header,
+ Description =
+ "Enter your token in the text input below.\r\n\r\nExample: \"12345abcdef\"",
+ });
+
+ options.AddSecurityRequirement(new OpenApiSecurityRequirement {
+ {
+ new OpenApiSecurityScheme {
+ Reference = new OpenApiReference {
+ Type = ReferenceType.SecurityScheme,
+ Id = "Basic"
+ }
+ },
+ Array.Empty<string>()
+ }
+ });
+ });
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app) {
+ if (WebHostEnvironment.IsDevelopment()) {
+ app.UseDeveloperExceptionPage();
+ app.UseCors(x => x
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .SetIsOriginAllowed(_ => true) // allow any origin
+ .AllowCredentials()); // allow credentials
+ }
+
+ app.UseRouting();
+ app.UseSerilogRequestLogging();
+ app.UseStatusCodePages();
+ app.UseAuthentication();
+ app.UseAuthorization();
+ app.UseEndpoints(endpoints => {
+ endpoints.MapControllers();
+ });
+ app.UseSwagger();
+ app.UseSwaggerUI(options => {
+ options.SwaggerEndpoint(ApiSpecV1.Document.SwaggerPath, ApiSpecV1.Document.VersionName);
+ options.DocumentTitle = Constants.API_NAME;
+ });
+ }
+}
diff --git a/src/server/StartupTasks.cs b/src/server/StartupTasks.cs
new file mode 100644
index 0000000..0284f34
--- /dev/null
+++ b/src/server/StartupTasks.cs
@@ -0,0 +1,30 @@
+namespace IOL.BookmarkThing.Server;
+
+public static class StartupTasks
+{
+ private static IEnumerable<string> PathsToEnsureCreated => new List<string> {
+ AppPaths.DataProtectionKeys.HostPath,
+ AppPaths.AppData.HostPath,
+ };
+
+ /// <summary>
+ /// Execute startup tasks.
+ /// </summary>
+ /// <param name="stoppingToken"></param>
+ /// <returns></returns>
+ public static Task ExecuteAsync() {
+ EnsureCreated();
+ return Task.CompletedTask;
+ }
+
+ private static void EnsureCreated() {
+ foreach (var path in PathsToEnsureCreated) {
+ if (path.IsNullOrWhiteSpace() || Directory.Exists(path)) {
+ continue;
+ }
+
+ Directory.CreateDirectory(path!);
+ Console.WriteLine("EnsuredCreated: " + path);
+ }
+ }
+}
diff --git a/src/server/StaticData/AppJsonSettings.cs b/src/server/StaticData/AppJsonSettings.cs
new file mode 100644
index 0000000..47c67a7
--- /dev/null
+++ b/src/server/StaticData/AppJsonSettings.cs
@@ -0,0 +1,11 @@
+namespace IOL.BookmarkThing.Server.StaticData;
+
+public static class AppJsonSettings
+{
+ public static Action<JsonOptions> Default { get; } = options => {
+ options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
+ options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
+ options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+ };
+}
diff --git a/src/server/StaticData/AppPaths.cs b/src/server/StaticData/AppPaths.cs
new file mode 100644
index 0000000..f7b5adc
--- /dev/null
+++ b/src/server/StaticData/AppPaths.cs
@@ -0,0 +1,12 @@
+namespace IOL.BookmarkThing.Server.StaticData;
+
+public static class AppPaths
+{
+ public static AppPath AppData => new() {
+ HostPath = Path.Combine(Directory.GetCurrentDirectory(), "AppData")
+ };
+
+ public static AppPath DataProtectionKeys => new() {
+ HostPath = Path.Combine(Directory.GetCurrentDirectory(), "AppData", "data-protection-keys")
+ };
+}
diff --git a/src/server/StaticData/Constants.cs b/src/server/StaticData/Constants.cs
new file mode 100644
index 0000000..818004f
--- /dev/null
+++ b/src/server/StaticData/Constants.cs
@@ -0,0 +1,7 @@
+namespace IOL.BookmarkThing.Server.StaticData;
+
+public static class Constants
+{
+ public const string API_NAME = "Bookmark API";
+ public const string BASIC_AUTH_SCHEME = "BasicAuthenticationScheme";
+}
diff --git a/src/server/Utilities/BasicAuthenticationHandler.cs b/src/server/Utilities/BasicAuthenticationHandler.cs
new file mode 100644
index 0000000..7961b82
--- /dev/null
+++ b/src/server/Utilities/BasicAuthenticationHandler.cs
@@ -0,0 +1,49 @@
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Encodings.Web;
+using Microsoft.Extensions.Options;
+
+namespace IOL.BookmarkThing.Server.Utilities;
+
+public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
+{
+ private readonly AppDbContext _context;
+
+ public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, AppDbContext context) :
+ base(options, logger, encoder, clock) {
+ _context = context;
+ }
+
+ protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
+ var endpoint = Context.GetEndpoint();
+ if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
+ return Task.FromResult(AuthenticateResult.NoResult());
+
+ if (!Request.Headers.ContainsKey("Authorization"))
+ return Task.FromResult(AuthenticateResult.Fail("Missing Authorization Header"));
+
+ try {
+ var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
+ if (authHeader.Parameter == null) return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
+ var token_is_guid = Guid.TryParse(Encoding.UTF8.GetString(credentialBytes), out var token_id);
+ if (token_is_guid) {
+ var token = _context.AccessTokens.Include(c => c.User).SingleOrDefault(c => c.Id == token_id);
+ if (token == default) {
+ return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ }
+
+ var claims = token.User.DefaultClaims();
+ var identity = new ClaimsIdentity(claims, Scheme.Name);
+ var principal = new ClaimsPrincipal(identity);
+ var ticket = new AuthenticationTicket(principal, Scheme.Name);
+
+ return Task.FromResult(AuthenticateResult.Success(ticket));
+ }
+
+ return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ } catch {
+ return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ }
+ }
+}
diff --git a/src/server/Utilities/ConfigurationExtensions.cs b/src/server/Utilities/ConfigurationExtensions.cs
new file mode 100644
index 0000000..d42b117
--- /dev/null
+++ b/src/server/Utilities/ConfigurationExtensions.cs
@@ -0,0 +1,19 @@
+namespace IOL.BookmarkThing.Server.Utilities;
+
+public static class ConfigurationExtensions
+{
+ /// <summary>
+ /// Get the contents of AppData/version.txt.
+ /// </summary>
+ /// <param name="configuration"></param>
+ /// <returns></returns>
+ public static string GetVersion(this IConfiguration configuration) {
+ var versionFilePath = Path.Combine(AppPaths.AppData.HostPath, "version.txt");
+ if (File.Exists(versionFilePath)) {
+ var versionText = File.ReadAllText(versionFilePath);
+ return versionText + "-" + configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT");
+ }
+
+ return "unknown-" + configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT");
+ }
+}
diff --git a/src/server/Utilities/SwaggerDefaultValues.cs b/src/server/Utilities/SwaggerDefaultValues.cs
new file mode 100644
index 0000000..24855ae
--- /dev/null
+++ b/src/server/Utilities/SwaggerDefaultValues.cs
@@ -0,0 +1,55 @@
+namespace IOL.BookmarkThing.Server.Utilities;
+
+/// <summary>
+/// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
+/// </summary>
+/// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>.
+/// Once they are fixed and published, this class can be removed.</remarks>
+public class SwaggerDefaultValues : IOperationFilter
+{
+ /// <summary>
+ /// Applies the filter to the specified operation using the given context.
+ /// </summary>
+ /// <param name="operation">The operation to apply the filter to.</param>
+ /// <param name="context">The current operation filter context.</param>
+ public void Apply(OpenApiOperation operation, OperationFilterContext context) {
+ var apiDescription = context.ApiDescription;
+
+ operation.Deprecated |= apiDescription.IsDeprecated();
+
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077
+ foreach (var responseType in context.ApiDescription.SupportedResponseTypes) {
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387
+ var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
+ var response = operation.Responses[responseKey];
+
+ foreach (var contentType in response.Content.Keys) {
+ if (!responseType.ApiResponseFormats.Any(x => x.MediaType == contentType)) {
+ response.Content.Remove(contentType);
+ }
+ }
+ }
+
+ if (operation.Parameters == null) {
+ return;
+ }
+
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413
+ foreach (var parameter in operation.Parameters) {
+ var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
+
+ if (parameter.Description == null) {
+ parameter.Description = description.ModelMetadata?.Description;
+ }
+
+ if (parameter.Schema.Default == null && description.DefaultValue != null) {
+ // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330
+ var json = JsonSerializer.Serialize(description.DefaultValue, description.ModelMetadata.ModelType);
+ parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
+ }
+
+ parameter.Required |= description.IsRequired;
+ }
+ }
+}
diff --git a/src/server/Utilities/SwaggerGenOptionsExtensions.cs b/src/server/Utilities/SwaggerGenOptionsExtensions.cs
new file mode 100644
index 0000000..0203f77
--- /dev/null
+++ b/src/server/Utilities/SwaggerGenOptionsExtensions.cs
@@ -0,0 +1,37 @@
+namespace IOL.BookmarkThing.Server.Utilities;
+
+public static class SwaggerGenOptionsExtensions
+{
+ /// <summary>
+ /// Updates Swagger document to support ApiEndpoints.<br/><br/>
+ /// For controllers inherited from <see cref="BaseRoute"/>:<br/>
+ /// - Replaces action Tag with <c>[namespace]</c><br/>
+ /// </summary>
+ public static void UseApiEndpoints(this SwaggerGenOptions options) {
+ options.TagActionsBy(EndpointNamespaceOrDefault);
+ }
+
+ private static IEnumerable<Type> GetBaseTypesAndThis(this Type type) {
+ var current = type;
+ while (current != null) {
+ yield return current;
+ current = current.BaseType;
+ }
+ }
+
+ private static IList<string> EndpointNamespaceOrDefault(ApiDescription api) {
+ if (api.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) {
+ throw new InvalidOperationException($"Unable to determine tag for endpoint: {api.ActionDescriptor.DisplayName}");
+ }
+
+ if (actionDescriptor.ControllerTypeInfo.GetBaseTypesAndThis().Any(t => t == typeof(BaseV1Route))) {
+ return new[] {
+ actionDescriptor.ControllerTypeInfo.Namespace?.Split('.').Last(),
+ };
+ }
+
+ return new[] {
+ actionDescriptor.ControllerName,
+ };
+ }
+}
diff --git a/src/webapp/index.html b/src/webapp/index.html
new file mode 100644
index 0000000..490ab50
--- /dev/null
+++ b/src/webapp/index.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en"
+ theme="white">
+<head>
+ <meta charset="UTF-8"/>
+ <link rel="icon"
+ href="/favicon.ico"/>
+ <meta name="viewport"
+ content="width=device-width, initial-scale=1.0"/>
+ <script>
+ const prefs = sessionStorage.getItem("preferences");
+ const prefsObj = JSON.parse(prefs ?? "{}");
+ if (prefsObj?.theme === 1 || window.matchMedia("(prefers-color-scheme: dark)").matches) {
+ document.documentElement.setAttribute("theme", "g100");
+ } else {
+ document.documentElement.setAttribute("theme", "white");
+ }
+ </script>
+ <title>Bookmark thing</title>
+</head>
+<body>
+<div id="app"></div>
+<script type="module"
+ src="/src/main.ts"></script>
+</body>
+</html>
diff --git a/src/webapp/package-lock.json b/src/webapp/package-lock.json
new file mode 100644
index 0000000..2ccdaf8
--- /dev/null
+++ b/src/webapp/package-lock.json
@@ -0,0 +1,3632 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "version": "0.0.0",
+ "dependencies": {
+ "@types/validator": "^13.7.1"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^1.0.0-next.33",
+ "@tailwindcss/forms": "^0.4.0",
+ "@tailwindcss/typography": "^0.5.0",
+ "@tsconfig/svelte": "^3.0.0",
+ "autoprefixer": "^10.4.1",
+ "lz-string": "^1.4.4",
+ "postcss": "^8.4.5",
+ "svelte": "^3.44.3",
+ "svelte-check": "^2.2.11",
+ "svelte-fsm": "^1.1.2",
+ "svelte-preprocess": "^4.10.1",
+ "svelte-spa-router": "^3.2.0",
+ "svelte-tiny-virtual-list": "^1.1.7",
+ "tailwindcss": "^3.0.8",
+ "tslib": "^2.3.1",
+ "typescript": "^4.5.4",
+ "validator": "^13.7.0",
+ "vest": "^4.0.1",
+ "vite": "^2.7.10"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
+ "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.16.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.15.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
+ "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.15.7",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz",
+ "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==",
+ "dev": true,
+ "dependencies": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "1.0.0-next.33",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.33.tgz",
+ "integrity": "sha512-aj0h2+ZixgT+yoJFIs8dRRw/Cj9tgNu3+hY4CJikpa04mfhR61wXqJFfi2ZEFMUvFda5nCxKYIChFkc6wq5fJA==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^4.1.2",
+ "debug": "^4.3.3",
+ "kleur": "^4.1.4",
+ "magic-string": "^0.25.7",
+ "require-relative": "^0.8.7",
+ "svelte-hmr": "^0.14.9"
+ },
+ "engines": {
+ "node": "^14.13.1 || >= 16"
+ },
+ "peerDependencies": {
+ "diff-match-patch": "^1.0.5",
+ "svelte": "^3.44.0",
+ "vite": "^2.7.0"
+ },
+ "peerDependenciesMeta": {
+ "diff-match-patch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tailwindcss/forms": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz",
+ "integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==",
+ "dev": true,
+ "dependencies": {
+ "mini-svg-data-uri": "^1.2.3"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
+ }
+ },
+ "node_modules/@tailwindcss/typography": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.0.tgz",
+ "integrity": "sha512-1p/3C6C+JJziS/ghtG8ACYalbA2SyLJY27Pm33cVTlAoY6VQ7zfm2H64cPxUMBkVIlWXTtWHhZcZJPobMRmQAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash.castarray": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "lodash.uniq": "^4.5.0"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || insiders"
+ }
+ },
+ "node_modules/@tsconfig/svelte": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-3.0.0.tgz",
+ "integrity": "sha512-pYrtLtOwku/7r1i9AMONsJMVYAtk3hzOfiGNekhtq5tYBGA7unMve8RvUclKLMT3PrihvJqUmzsRGh0RP84hKg==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "17.0.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz",
+ "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==",
+ "dev": true
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
+ "dev": true
+ },
+ "node_modules/@types/pug": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
+ "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
+ "dev": true
+ },
+ "node_modules/@types/sass": {
+ "version": "1.43.1",
+ "resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.43.1.tgz",
+ "integrity": "sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/validator": {
+ "version": "13.7.1",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.1.tgz",
+ "integrity": "sha512-I6OUIZ5cYRk5lp14xSOAiXjWrfVoMZVjDuevBYgQDYzZIjsf2CAISpEcXOkFAtpAHbmWIDLcZObejqny/9xq5Q=="
+ },
+ "node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz",
+ "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==",
+ "dev": true
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.1.tgz",
+ "integrity": "sha512-B3ZEG7wtzXDRCEFsan7HmR2AeNsxdJB0+sEC0Hc5/c2NbhJqPwuZm+tn233GBVw82L+6CtD6IPSfVruwKjfV3A==",
+ "dev": true,
+ "dependencies": {
+ "browserslist": "^4.19.1",
+ "caniuse-lite": "^1.0.30001294",
+ "fraction.js": "^4.1.2",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.19.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
+ "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
+ "dev": true,
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001286",
+ "electron-to-chromium": "^1.4.17",
+ "escalade": "^3.1.1",
+ "node-releases": "^2.0.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001294",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001294.tgz",
+ "integrity": "sha512-LiMlrs1nSKZ8qkNhpUf5KD0Al1KCBE3zaT7OLOwEkagXMEDij98SiOovn9wxVGQpklk9vVC/pUSqgYmkmKOS8g==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
+ "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/context": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/context/-/context-2.0.1.tgz",
+ "integrity": "sha512-8QV3EXa7WP9l8aL8a3BMM792vvuPbIyp6ktGHEX6V4XiGNaiqxCGMRGzEavE3cPnHWqI9fXTujPEfBPt0iRhQw==",
+ "dev": true
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
+ "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/defined": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
+ "dev": true
+ },
+ "node_modules/detect-indent": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
+ "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detective": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
+ "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
+ "dev": true,
+ "dependencies": {
+ "acorn-node": "^1.6.1",
+ "defined": "^1.0.0",
+ "minimist": "^1.1.1"
+ },
+ "bin": {
+ "detective": "bin/detective.js"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.30",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.30.tgz",
+ "integrity": "sha512-609z9sIMxDHg+TcR/VB3MXwH+uwtrYyeAwWc/orhnr90ixs6WVGSrt85CDLGUdNnLqCA7liv426V20EecjvflQ==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es6-promise": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+ "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
+ "integrity": "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "optionalDependencies": {
+ "esbuild-android-arm64": "0.13.15",
+ "esbuild-darwin-64": "0.13.15",
+ "esbuild-darwin-arm64": "0.13.15",
+ "esbuild-freebsd-64": "0.13.15",
+ "esbuild-freebsd-arm64": "0.13.15",
+ "esbuild-linux-32": "0.13.15",
+ "esbuild-linux-64": "0.13.15",
+ "esbuild-linux-arm": "0.13.15",
+ "esbuild-linux-arm64": "0.13.15",
+ "esbuild-linux-mips64le": "0.13.15",
+ "esbuild-linux-ppc64le": "0.13.15",
+ "esbuild-netbsd-64": "0.13.15",
+ "esbuild-openbsd-64": "0.13.15",
+ "esbuild-sunos-64": "0.13.15",
+ "esbuild-windows-32": "0.13.15",
+ "esbuild-windows-64": "0.13.15",
+ "esbuild-windows-arm64": "0.13.15"
+ }
+ },
+ "node_modules/esbuild-android-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz",
+ "integrity": "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/esbuild-darwin-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz",
+ "integrity": "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/esbuild-darwin-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz",
+ "integrity": "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/esbuild-freebsd-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz",
+ "integrity": "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/esbuild-freebsd-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz",
+ "integrity": "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/esbuild-linux-32": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz",
+ "integrity": "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz",
+ "integrity": "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/esbuild-linux-arm": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz",
+ "integrity": "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/esbuild-linux-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz",
+ "integrity": "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/esbuild-linux-mips64le": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz",
+ "integrity": "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/esbuild-linux-ppc64le": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz",
+ "integrity": "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/esbuild-netbsd-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz",
+ "integrity": "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ]
+ },
+ "node_modules/esbuild-openbsd-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz",
+ "integrity": "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/esbuild-sunos-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz",
+ "integrity": "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ]
+ },
+ "node_modules/esbuild-windows-32": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz",
+ "integrity": "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/esbuild-windows-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz",
+ "integrity": "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/esbuild-windows-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz",
+ "integrity": "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
+ "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+ "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz",
+ "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://www.patreon.com/infusion"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
+ "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
+ "dev": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
+ "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
+ "dev": true,
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/import-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
+ "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==",
+ "dev": true,
+ "dependencies": {
+ "import-from": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/import-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
+ "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==",
+ "dev": true,
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/import-from/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
+ "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/kleur": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
+ "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
+ "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/lodash.castarray": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+ "integrity": "sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=",
+ "dev": true
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
+ "dev": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+ "dev": true
+ },
+ "node_modules/lz-string": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
+ "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
+ "dev": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+ "dev": true,
+ "dependencies": {
+ "sourcemap-codec": "^1.4.4"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mini-svg-data-uri": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz",
+ "integrity": "sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA==",
+ "dev": true,
+ "bin": {
+ "mini-svg-data-uri": "cli.js"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/n4s": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/n4s/-/n4s-4.0.1.tgz",
+ "integrity": "sha512-S3/8mLYORi1SjgUflavLmZg/d3r0K7t+G9LsYdE3zPIILKX2A8nXpdUTauOBk6ayUUhJD0zQEtfJY+qNsYysBw==",
+ "dev": true,
+ "dependencies": {
+ "context": "^2.0.1"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.1.30",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
+ "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
+ "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
+ "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.5",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
+ "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
+ "dev": true,
+ "dependencies": {
+ "nanoid": "^3.1.30",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz",
+ "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==",
+ "dev": true,
+ "dependencies": {
+ "camelcase-css": "^2.0.1",
+ "postcss": "^8.1.6"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz",
+ "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==",
+ "dev": true,
+ "dependencies": {
+ "import-cwd": "^3.0.0",
+ "lilconfig": "^2.0.3",
+ "yaml": "^1.10.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
+ "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
+ "dev": true,
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.6"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.8",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz",
+ "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/regexparam": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.0.tgz",
+ "integrity": "sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/require-relative": {
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
+ "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
+ "dev": true
+ },
+ "node_modules/resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.62.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.62.0.tgz",
+ "integrity": "sha512-cJEQq2gwB0GWMD3rYImefQTSjrPYaC6s4J9pYqnstVLJ1CHa/aZNVkD4Epuvg4iLeMA4KRiq7UM7awKK6j7jcw==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz",
+ "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==",
+ "dev": true,
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/sander": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
+ "integrity": "sha1-dB4kXiMfB8r7b98PEzrfohalAq0=",
+ "dev": true,
+ "dependencies": {
+ "es6-promise": "^3.1.2",
+ "graceful-fs": "^4.1.3",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.2"
+ }
+ },
+ "node_modules/sass": {
+ "version": "1.45.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.45.1.tgz",
+ "integrity": "sha512-pwPRiq29UR0o4X3fiQyCtrESldXvUQAAE0QmcJTpsI4kuHHcLzZ54M1oNBVIXybQv8QF2zfkpFcTxp8ta97dUA==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/sorcery": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz",
+ "integrity": "sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=",
+ "dev": true,
+ "dependencies": {
+ "buffer-crc32": "^0.2.5",
+ "minimist": "^1.2.0",
+ "sander": "^0.5.0",
+ "sourcemap-codec": "^1.3.0"
+ },
+ "bin": {
+ "sorcery": "bin/index.js"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
+ "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/svelte": {
+ "version": "3.44.3",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.44.3.tgz",
+ "integrity": "sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/svelte-check": {
+ "version": "2.2.11",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.2.11.tgz",
+ "integrity": "sha512-clotPGGZPj3LuS9qP1lk+Wwnsj+js42ehCPmHk+qtyaQh/dU95e0qkpPmtmOMYHN6My5Y75XqeN1QNLj5V5gwA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "chokidar": "^3.4.1",
+ "fast-glob": "^3.2.7",
+ "import-fresh": "^3.2.1",
+ "minimist": "^1.2.5",
+ "sade": "^1.7.4",
+ "source-map": "^0.7.3",
+ "svelte-preprocess": "^4.0.0",
+ "typescript": "*"
+ },
+ "bin": {
+ "svelte-check": "bin/svelte-check"
+ },
+ "peerDependencies": {
+ "svelte": "^3.24.0"
+ }
+ },
+ "node_modules/svelte-fsm": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/svelte-fsm/-/svelte-fsm-1.1.2.tgz",
+ "integrity": "sha512-lGeuhELDkQC5WK4+MHJtdigXCqwsYMR1euyRTacDCwtbORzEy5ovivTY3Mnd5bM7H+huO3nKjNjdPvDLcLn3pQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/svelte-hmr": {
+ "version": "0.14.9",
+ "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.9.tgz",
+ "integrity": "sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg==",
+ "dev": true,
+ "peerDependencies": {
+ "svelte": ">=3.19.0"
+ }
+ },
+ "node_modules/svelte-preprocess": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.1.tgz",
+ "integrity": "sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@types/pug": "^2.0.4",
+ "@types/sass": "^1.16.0",
+ "detect-indent": "^6.0.0",
+ "magic-string": "^0.25.7",
+ "sorcery": "^0.10.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 9.11.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.10.2",
+ "coffeescript": "^2.5.1",
+ "less": "^3.11.3",
+ "postcss": "^7 || ^8",
+ "postcss-load-config": "^2.1.0 || ^3.0.0",
+ "pug": "^3.0.0",
+ "sass": "^1.26.8",
+ "stylus": "^0.54.7",
+ "sugarss": "^2.0.0",
+ "svelte": "^3.23.0",
+ "typescript": "^4.5.2"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "coffeescript": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "node-sass": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "postcss-load-config": {
+ "optional": true
+ },
+ "pug": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/svelte-spa-router": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.2.0.tgz",
+ "integrity": "sha512-igemo5Vs82TGBBw+DjWt6qKameXYzNs6aDXcTxou5XbEvOjiRcAM6MLkdVRCatn6u8r42dE99bt/br7T4qe/AQ==",
+ "dev": true,
+ "dependencies": {
+ "regexparam": "2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ItalyPaleAle"
+ }
+ },
+ "node_modules/svelte-tiny-virtual-list": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/svelte-tiny-virtual-list/-/svelte-tiny-virtual-list-1.1.7.tgz",
+ "integrity": "sha512-8314cmLXOVqIQwSc3NFu8yGU7BdHEJeixrwFirmnMoWl6YNcCq5jVjCKjE3tDNJfUenz1LN5M5YshPt6GcPuww==",
+ "dev": true
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.8.tgz",
+ "integrity": "sha512-Yww1eRYO1AxITJmW/KduZPxNvYdHuedeKwPju9Oakp7MdiixRi5xkpLhirsc81QCxHL0eoce6qKmxXwYGt4Cjw==",
+ "dev": true,
+ "dependencies": {
+ "arg": "^5.0.1",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.2",
+ "color-name": "^1.1.4",
+ "cosmiconfig": "^7.0.1",
+ "detective": "^5.2.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.2.7",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^2.2.0",
+ "postcss-js": "^3.0.3",
+ "postcss-load-config": "^3.1.0",
+ "postcss-nested": "5.0.6",
+ "postcss-selector-parser": "^6.0.7",
+ "postcss-value-parser": "^4.2.0",
+ "quick-lru": "^5.1.1",
+ "resolve": "^1.20.0",
+ "tmp": "^0.2.1"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "autoprefixer": "^10.0.2",
+ "postcss": "^8.0.9"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "dependencies": {
+ "rimraf": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8.17.0"
+ }
+ },
+ "node_modules/tmp/node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+ "dev": true
+ },
+ "node_modules/typescript": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
+ "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "node_modules/validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/vest": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/vest/-/vest-4.0.1.tgz",
+ "integrity": "sha512-J1WJeMVyyjzkQ4uiLxDoIBiiW44FxAl/Z2znNxTQlwPQe7Ys/VDjbEJV1GnErB9oCr0O1tUc5JbwXh8/eQlfzA==",
+ "dev": true,
+ "dependencies": {
+ "context": "^2.0.1",
+ "n4s": "^4.0.1"
+ }
+ },
+ "node_modules/vite": {
+ "version": "2.7.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-2.7.10.tgz",
+ "integrity": "sha512-KEY96ntXUid1/xJihJbgmLZx7QSC2D4Tui0FdS0Old5OokYzFclcofhtxtjDdGOk/fFpPbHv9yw88+rB93Tb8w==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.13.12",
+ "postcss": "^8.4.5",
+ "resolve": "^1.20.0",
+ "rollup": "^2.59.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": ">=12.2.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "less": "*",
+ "sass": "*",
+ "stylus": "*"
+ },
+ "peerDependenciesMeta": {
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ }
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
+ "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.16.0"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.15.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
+ "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.15.7",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz",
+ "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==",
+ "dev": true,
+ "requires": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ }
+ },
+ "@sveltejs/vite-plugin-svelte": {
+ "version": "1.0.0-next.33",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.33.tgz",
+ "integrity": "sha512-aj0h2+ZixgT+yoJFIs8dRRw/Cj9tgNu3+hY4CJikpa04mfhR61wXqJFfi2ZEFMUvFda5nCxKYIChFkc6wq5fJA==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^4.1.2",
+ "debug": "^4.3.3",
+ "kleur": "^4.1.4",
+ "magic-string": "^0.25.7",
+ "require-relative": "^0.8.7",
+ "svelte-hmr": "^0.14.9"
+ }
+ },
+ "@tailwindcss/forms": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz",
+ "integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==",
+ "dev": true,
+ "requires": {
+ "mini-svg-data-uri": "^1.2.3"
+ }
+ },
+ "@tailwindcss/typography": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.0.tgz",
+ "integrity": "sha512-1p/3C6C+JJziS/ghtG8ACYalbA2SyLJY27Pm33cVTlAoY6VQ7zfm2H64cPxUMBkVIlWXTtWHhZcZJPobMRmQAA==",
+ "dev": true,
+ "requires": {
+ "lodash.castarray": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "lodash.uniq": "^4.5.0"
+ }
+ },
+ "@tsconfig/svelte": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-3.0.0.tgz",
+ "integrity": "sha512-pYrtLtOwku/7r1i9AMONsJMVYAtk3hzOfiGNekhtq5tYBGA7unMve8RvUclKLMT3PrihvJqUmzsRGh0RP84hKg==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "17.0.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz",
+ "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==",
+ "dev": true
+ },
+ "@types/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
+ "dev": true
+ },
+ "@types/pug": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
+ "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
+ "dev": true
+ },
+ "@types/sass": {
+ "version": "1.43.1",
+ "resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.43.1.tgz",
+ "integrity": "sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "@types/validator": {
+ "version": "13.7.1",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.1.tgz",
+ "integrity": "sha512-I6OUIZ5cYRk5lp14xSOAiXjWrfVoMZVjDuevBYgQDYzZIjsf2CAISpEcXOkFAtpAHbmWIDLcZObejqny/9xq5Q=="
+ },
+ "acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "dev": true
+ },
+ "acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "acorn-walk": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "arg": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz",
+ "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==",
+ "dev": true
+ },
+ "autoprefixer": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.1.tgz",
+ "integrity": "sha512-B3ZEG7wtzXDRCEFsan7HmR2AeNsxdJB0+sEC0Hc5/c2NbhJqPwuZm+tn233GBVw82L+6CtD6IPSfVruwKjfV3A==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.19.1",
+ "caniuse-lite": "^1.0.30001294",
+ "fraction.js": "^4.1.2",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "browserslist": {
+ "version": "4.19.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
+ "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001286",
+ "electron-to-chromium": "^1.4.17",
+ "escalade": "^3.1.1",
+ "node-releases": "^2.0.1",
+ "picocolors": "^1.0.0"
+ }
+ },
+ "buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
+ "dev": true
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001294",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001294.tgz",
+ "integrity": "sha512-LiMlrs1nSKZ8qkNhpUf5KD0Al1KCBE3zaT7OLOwEkagXMEDij98SiOovn9wxVGQpklk9vVC/pUSqgYmkmKOS8g==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "chokidar": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
+ "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "context": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/context/-/context-2.0.1.tgz",
+ "integrity": "sha512-8QV3EXa7WP9l8aL8a3BMM792vvuPbIyp6ktGHEX6V4XiGNaiqxCGMRGzEavE3cPnHWqI9fXTujPEfBPt0iRhQw==",
+ "dev": true
+ },
+ "cosmiconfig": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
+ "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
+ "dev": true,
+ "requires": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ }
+ },
+ "cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "defined": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
+ "dev": true
+ },
+ "detect-indent": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
+ "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
+ "dev": true
+ },
+ "detective": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
+ "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
+ "dev": true,
+ "requires": {
+ "acorn-node": "^1.6.1",
+ "defined": "^1.0.0",
+ "minimist": "^1.1.1"
+ }
+ },
+ "didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
+ "dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
+ },
+ "electron-to-chromium": {
+ "version": "1.4.30",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.30.tgz",
+ "integrity": "sha512-609z9sIMxDHg+TcR/VB3MXwH+uwtrYyeAwWc/orhnr90ixs6WVGSrt85CDLGUdNnLqCA7liv426V20EecjvflQ==",
+ "dev": true
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es6-promise": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+ "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
+ "dev": true
+ },
+ "esbuild": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
+ "integrity": "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==",
+ "dev": true,
+ "requires": {
+ "esbuild-android-arm64": "0.13.15",
+ "esbuild-darwin-64": "0.13.15",
+ "esbuild-darwin-arm64": "0.13.15",
+ "esbuild-freebsd-64": "0.13.15",
+ "esbuild-freebsd-arm64": "0.13.15",
+ "esbuild-linux-32": "0.13.15",
+ "esbuild-linux-64": "0.13.15",
+ "esbuild-linux-arm": "0.13.15",
+ "esbuild-linux-arm64": "0.13.15",
+ "esbuild-linux-mips64le": "0.13.15",
+ "esbuild-linux-ppc64le": "0.13.15",
+ "esbuild-netbsd-64": "0.13.15",
+ "esbuild-openbsd-64": "0.13.15",
+ "esbuild-sunos-64": "0.13.15",
+ "esbuild-windows-32": "0.13.15",
+ "esbuild-windows-64": "0.13.15",
+ "esbuild-windows-arm64": "0.13.15"
+ }
+ },
+ "esbuild-android-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz",
+ "integrity": "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz",
+ "integrity": "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz",
+ "integrity": "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz",
+ "integrity": "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz",
+ "integrity": "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-32": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz",
+ "integrity": "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz",
+ "integrity": "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz",
+ "integrity": "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz",
+ "integrity": "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-mips64le": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz",
+ "integrity": "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-ppc64le": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz",
+ "integrity": "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-netbsd-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz",
+ "integrity": "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-openbsd-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz",
+ "integrity": "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-sunos-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz",
+ "integrity": "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-32": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz",
+ "integrity": "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz",
+ "integrity": "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-arm64": {
+ "version": "0.13.15",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz",
+ "integrity": "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==",
+ "dev": true,
+ "optional": true
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "fast-glob": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
+ "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ }
+ },
+ "fastq": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+ "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+ "dev": true,
+ "requires": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "fraction.js": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz",
+ "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
+ "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "immutable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
+ "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
+ "dev": true,
+ "optional": true,
+ "peer": true
+ },
+ "import-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
+ "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==",
+ "dev": true,
+ "requires": {
+ "import-from": "^3.0.0"
+ }
+ },
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "import-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
+ "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^5.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true
+ }
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-core-module": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
+ "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "kleur": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
+ "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==",
+ "dev": true
+ },
+ "lilconfig": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
+ "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==",
+ "dev": true
+ },
+ "lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "lodash.castarray": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+ "integrity": "sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=",
+ "dev": true
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
+ "dev": true
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+ "dev": true
+ },
+ "lz-string": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
+ "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
+ "dev": true
+ },
+ "magic-string": {
+ "version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+ "dev": true,
+ "requires": {
+ "sourcemap-codec": "^1.4.4"
+ }
+ },
+ "merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
+ "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.2.3"
+ }
+ },
+ "min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true
+ },
+ "mini-svg-data-uri": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz",
+ "integrity": "sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "n4s": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/n4s/-/n4s-4.0.1.tgz",
+ "integrity": "sha512-S3/8mLYORi1SjgUflavLmZg/d3r0K7t+G9LsYdE3zPIILKX2A8nXpdUTauOBk6ayUUhJD0zQEtfJY+qNsYysBw==",
+ "dev": true,
+ "requires": {
+ "context": "^2.0.1"
+ }
+ },
+ "nanoid": {
+ "version": "3.1.30",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
+ "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
+ "dev": true
+ },
+ "node-releases": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
+ "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+ "dev": true
+ },
+ "object-hash": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
+ "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true
+ },
+ "postcss": {
+ "version": "8.4.5",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
+ "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
+ "dev": true,
+ "requires": {
+ "nanoid": "^3.1.30",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.1"
+ }
+ },
+ "postcss-js": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz",
+ "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==",
+ "dev": true,
+ "requires": {
+ "camelcase-css": "^2.0.1",
+ "postcss": "^8.1.6"
+ }
+ },
+ "postcss-load-config": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz",
+ "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==",
+ "dev": true,
+ "requires": {
+ "import-cwd": "^3.0.0",
+ "lilconfig": "^2.0.3",
+ "yaml": "^1.10.2"
+ }
+ },
+ "postcss-nested": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
+ "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
+ "dev": true,
+ "requires": {
+ "postcss-selector-parser": "^6.0.6"
+ }
+ },
+ "postcss-selector-parser": {
+ "version": "6.0.8",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz",
+ "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true
+ },
+ "quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true
+ },
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "regexparam": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.0.tgz",
+ "integrity": "sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==",
+ "dev": true
+ },
+ "require-relative": {
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
+ "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "rollup": {
+ "version": "2.62.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.62.0.tgz",
+ "integrity": "sha512-cJEQq2gwB0GWMD3rYImefQTSjrPYaC6s4J9pYqnstVLJ1CHa/aZNVkD4Epuvg4iLeMA4KRiq7UM7awKK6j7jcw==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "sade": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz",
+ "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==",
+ "dev": true,
+ "requires": {
+ "mri": "^1.1.0"
+ }
+ },
+ "sander": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
+ "integrity": "sha1-dB4kXiMfB8r7b98PEzrfohalAq0=",
+ "dev": true,
+ "requires": {
+ "es6-promise": "^3.1.2",
+ "graceful-fs": "^4.1.3",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.2"
+ }
+ },
+ "sass": {
+ "version": "1.45.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.45.1.tgz",
+ "integrity": "sha512-pwPRiq29UR0o4X3fiQyCtrESldXvUQAAE0QmcJTpsI4kuHHcLzZ54M1oNBVIXybQv8QF2zfkpFcTxp8ta97dUA==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "requires": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ }
+ },
+ "sorcery": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz",
+ "integrity": "sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=",
+ "dev": true,
+ "requires": {
+ "buffer-crc32": "^0.2.5",
+ "minimist": "^1.2.0",
+ "sander": "^0.5.0",
+ "sourcemap-codec": "^1.3.0"
+ }
+ },
+ "source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "dev": true
+ },
+ "source-map-js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
+ "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
+ "dev": true
+ },
+ "sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "requires": {
+ "min-indent": "^1.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "svelte": {
+ "version": "3.44.3",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.44.3.tgz",
+ "integrity": "sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg==",
+ "dev": true
+ },
+ "svelte-check": {
+ "version": "2.2.11",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.2.11.tgz",
+ "integrity": "sha512-clotPGGZPj3LuS9qP1lk+Wwnsj+js42ehCPmHk+qtyaQh/dU95e0qkpPmtmOMYHN6My5Y75XqeN1QNLj5V5gwA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.0.0",
+ "chokidar": "^3.4.1",
+ "fast-glob": "^3.2.7",
+ "import-fresh": "^3.2.1",
+ "minimist": "^1.2.5",
+ "sade": "^1.7.4",
+ "source-map": "^0.7.3",
+ "svelte-preprocess": "^4.0.0",
+ "typescript": "*"
+ }
+ },
+ "svelte-fsm": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/svelte-fsm/-/svelte-fsm-1.1.2.tgz",
+ "integrity": "sha512-lGeuhELDkQC5WK4+MHJtdigXCqwsYMR1euyRTacDCwtbORzEy5ovivTY3Mnd5bM7H+huO3nKjNjdPvDLcLn3pQ==",
+ "dev": true
+ },
+ "svelte-hmr": {
+ "version": "0.14.9",
+ "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.9.tgz",
+ "integrity": "sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg==",
+ "dev": true,
+ "requires": {}
+ },
+ "svelte-preprocess": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.1.tgz",
+ "integrity": "sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==",
+ "dev": true,
+ "requires": {
+ "@types/pug": "^2.0.4",
+ "@types/sass": "^1.16.0",
+ "detect-indent": "^6.0.0",
+ "magic-string": "^0.25.7",
+ "sorcery": "^0.10.0",
+ "strip-indent": "^3.0.0"
+ }
+ },
+ "svelte-spa-router": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.2.0.tgz",
+ "integrity": "sha512-igemo5Vs82TGBBw+DjWt6qKameXYzNs6aDXcTxou5XbEvOjiRcAM6MLkdVRCatn6u8r42dE99bt/br7T4qe/AQ==",
+ "dev": true,
+ "requires": {
+ "regexparam": "2.0.0"
+ }
+ },
+ "svelte-tiny-virtual-list": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/svelte-tiny-virtual-list/-/svelte-tiny-virtual-list-1.1.7.tgz",
+ "integrity": "sha512-8314cmLXOVqIQwSc3NFu8yGU7BdHEJeixrwFirmnMoWl6YNcCq5jVjCKjE3tDNJfUenz1LN5M5YshPt6GcPuww==",
+ "dev": true
+ },
+ "tailwindcss": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.8.tgz",
+ "integrity": "sha512-Yww1eRYO1AxITJmW/KduZPxNvYdHuedeKwPju9Oakp7MdiixRi5xkpLhirsc81QCxHL0eoce6qKmxXwYGt4Cjw==",
+ "dev": true,
+ "requires": {
+ "arg": "^5.0.1",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.2",
+ "color-name": "^1.1.4",
+ "cosmiconfig": "^7.0.1",
+ "detective": "^5.2.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.2.7",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^2.2.0",
+ "postcss-js": "^3.0.3",
+ "postcss-load-config": "^3.1.0",
+ "postcss-nested": "5.0.6",
+ "postcss-selector-parser": "^6.0.7",
+ "postcss-value-parser": "^4.2.0",
+ "quick-lru": "^5.1.1",
+ "resolve": "^1.20.0",
+ "tmp": "^0.2.1"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ }
+ }
+ },
+ "tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dev": true,
+ "requires": {
+ "rimraf": "^3.0.0"
+ },
+ "dependencies": {
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ }
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+ "dev": true
+ },
+ "typescript": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
+ "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
+ "dev": true
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
+ "dev": true
+ },
+ "vest": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/vest/-/vest-4.0.1.tgz",
+ "integrity": "sha512-J1WJeMVyyjzkQ4uiLxDoIBiiW44FxAl/Z2znNxTQlwPQe7Ys/VDjbEJV1GnErB9oCr0O1tUc5JbwXh8/eQlfzA==",
+ "dev": true,
+ "requires": {
+ "context": "^2.0.1",
+ "n4s": "^4.0.1"
+ }
+ },
+ "vite": {
+ "version": "2.7.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-2.7.10.tgz",
+ "integrity": "sha512-KEY96ntXUid1/xJihJbgmLZx7QSC2D4Tui0FdS0Old5OokYzFclcofhtxtjDdGOk/fFpPbHv9yw88+rB93Tb8w==",
+ "dev": true,
+ "requires": {
+ "esbuild": "^0.13.12",
+ "fsevents": "~2.3.2",
+ "postcss": "^8.4.5",
+ "resolve": "^1.20.0",
+ "rollup": "^2.59.0"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ },
+ "yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true
+ }
+ }
+}
diff --git a/src/webapp/package.json b/src/webapp/package.json
new file mode 100644
index 0000000..5fc86d8
--- /dev/null
+++ b/src/webapp/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-check --tsconfig ./tsconfig.json"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^1.0.0-next.33",
+ "@tsconfig/svelte": "^3.0.0",
+ "carbon-components": "^10.51.0",
+ "carbon-components-svelte": "^0.51.3",
+ "carbon-icons-svelte": "^10.44.3",
+ "carbon-preprocess-svelte": "^0.6.0",
+ "lz-string": "^1.4.4",
+ "svelte": "^3.44.3",
+ "svelte-check": "^2.2.11",
+ "svelte-fsm": "^1.1.2",
+ "svelte-preprocess": "^4.10.1",
+ "svelte-spa-router": "^3.2.0",
+ "tslib": "^2.3.1",
+ "typescript": "^4.5.4",
+ "validator": "^13.7.0",
+ "vite": "^2.7.10"
+ }
+}
diff --git a/src/webapp/pnpm-lock.yaml b/src/webapp/pnpm-lock.yaml
new file mode 100644
index 0000000..72c54e8
--- /dev/null
+++ b/src/webapp/pnpm-lock.yaml
@@ -0,0 +1,1693 @@
+lockfileVersion: 5.3
+
+specifiers:
+ '@sveltejs/vite-plugin-svelte': ^1.0.0-next.33
+ '@tsconfig/svelte': ^3.0.0
+ carbon-components: ^10.51.0
+ carbon-components-svelte: ^0.51.3
+ carbon-icons-svelte: ^10.44.3
+ carbon-preprocess-svelte: ^0.6.0
+ lz-string: ^1.4.4
+ svelte: ^3.44.3
+ svelte-check: ^2.2.11
+ svelte-fsm: ^1.1.2
+ svelte-preprocess: ^4.10.1
+ svelte-spa-router: ^3.2.0
+ tslib: ^2.3.1
+ typescript: ^4.5.4
+ validator: ^13.7.0
+ vite: ^2.7.10
+
+devDependencies:
+ '@sveltejs/vite-plugin-svelte': 1.0.0-next.34_svelte@3.46.1+vite@2.7.12
+ '@tsconfig/svelte': 3.0.0
+ carbon-components: 10.51.0
+ carbon-components-svelte: 0.51.3
+ carbon-icons-svelte: 10.44.3
+ carbon-preprocess-svelte: 0.6.0_svelte@3.46.1
+ lz-string: 1.4.4
+ svelte: 3.46.1
+ svelte-check: 2.3.0_svelte@3.46.1
+ svelte-fsm: 1.1.2
+ svelte-preprocess: 4.10.1_svelte@3.46.1+typescript@4.5.4
+ svelte-spa-router: 3.2.0
+ tslib: 2.3.1
+ typescript: 4.5.4
+ validator: 13.7.0
+ vite: 2.7.12
+
+packages:
+
+ /@babel/code-frame/7.16.7:
+ resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/highlight': 7.16.10
+ dev: true
+
+ /@babel/generator/7.16.8:
+ resolution: {integrity: sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.16.8
+ jsesc: 2.5.2
+ source-map: 0.5.7
+ dev: true
+
+ /@babel/helper-environment-visitor/7.16.7:
+ resolution: {integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.16.8
+ dev: true
+
+ /@babel/helper-function-name/7.16.7:
+ resolution: {integrity: sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-get-function-arity': 7.16.7
+ '@babel/template': 7.16.7
+ '@babel/types': 7.16.8
+ dev: true
+
+ /@babel/helper-get-function-arity/7.16.7:
+ resolution: {integrity: sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.16.8
+ dev: true
+
+ /@babel/helper-hoist-variables/7.16.7:
+ resolution: {integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.16.8
+ dev: true
+
+ /@babel/helper-split-export-declaration/7.16.7:
+ resolution: {integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.16.8
+ dev: true
+
+ /@babel/helper-validator-identifier/7.16.7:
+ resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/highlight/7.16.10:
+ resolution: {integrity: sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.16.7
+ chalk: 2.4.2
+ js-tokens: 4.0.0
+ dev: true
+
+ /@babel/parser/7.16.12:
+ resolution: {integrity: sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dev: true
+
+ /@babel/template/7.16.7:
+ resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.16.7
+ '@babel/parser': 7.16.12
+ '@babel/types': 7.16.8
+ dev: true
+
+ /@babel/traverse/7.16.10:
+ resolution: {integrity: sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.16.7
+ '@babel/generator': 7.16.8
+ '@babel/helper-environment-visitor': 7.16.7
+ '@babel/helper-function-name': 7.16.7
+ '@babel/helper-hoist-variables': 7.16.7
+ '@babel/helper-split-export-declaration': 7.16.7
+ '@babel/parser': 7.16.12
+ '@babel/types': 7.16.8
+ debug: 4.3.3
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/types/7.16.8:
+ resolution: {integrity: sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.16.7
+ to-fast-properties: 2.0.0
+ dev: true
+
+ /@carbon/telemetry/0.0.0-alpha.6:
+ resolution: {integrity: sha512-DCE8ui/tFi+qvCH+mewbUbWzsiq5Ko3HU1lgrVbpjWv1LfswLKFmMg4Os+PmX6edYoBj39qVChJPeaN/UyfJDw==}
+ hasBin: true
+ dependencies:
+ '@babel/parser': 7.16.12
+ '@babel/traverse': 7.16.10
+ ci-info: 2.0.0
+ configstore: 5.0.1
+ fast-glob: 3.2.11
+ fs-extra: 9.1.0
+ got: 11.8.3
+ semver: 7.3.5
+ winston: 3.4.0
+ yargs: 16.2.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@dabh/diagnostics/2.0.2:
+ resolution: {integrity: sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==}
+ dependencies:
+ colorspace: 1.1.4
+ enabled: 2.0.0
+ kuler: 2.0.0
+ dev: true
+
+ /@nodelib/fs.scandir/2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+ dev: true
+
+ /@nodelib/fs.stat/2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /@nodelib/fs.walk/1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.13.0
+ dev: true
+
+ /@rollup/pluginutils/4.1.2:
+ resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==}
+ engines: {node: '>= 8.0.0'}
+ dependencies:
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /@sindresorhus/is/4.3.0:
+ resolution: {integrity: sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /@sveltejs/vite-plugin-svelte/1.0.0-next.34_svelte@3.46.1+vite@2.7.12:
+ resolution: {integrity: sha512-qZH2jndijrdkvevgbO7OH3iQsviM5Kz7h5APiNP4yEMZTrwq9bifzYvco6BprwtPvLb5wYlRVFZUOdusY6AovQ==}
+ engines: {node: ^14.13.1 || >= 16}
+ peerDependencies:
+ diff-match-patch: ^1.0.5
+ svelte: ^3.44.0
+ vite: ^2.7.0
+ peerDependenciesMeta:
+ diff-match-patch:
+ optional: true
+ dependencies:
+ '@rollup/pluginutils': 4.1.2
+ debug: 4.3.3
+ kleur: 4.1.4
+ magic-string: 0.25.7
+ require-relative: 0.8.7
+ svelte: 3.46.1
+ svelte-hmr: 0.14.9_svelte@3.46.1
+ vite: 2.7.12
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@szmarczak/http-timer/4.0.6:
+ resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
+ engines: {node: '>=10'}
+ dependencies:
+ defer-to-connect: 2.0.1
+ dev: true
+
+ /@tsconfig/svelte/3.0.0:
+ resolution: {integrity: sha512-pYrtLtOwku/7r1i9AMONsJMVYAtk3hzOfiGNekhtq5tYBGA7unMve8RvUclKLMT3PrihvJqUmzsRGh0RP84hKg==}
+ dev: true
+
+ /@types/cacheable-request/6.0.2:
+ resolution: {integrity: sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==}
+ dependencies:
+ '@types/http-cache-semantics': 4.0.1
+ '@types/keyv': 3.1.3
+ '@types/node': 17.0.8
+ '@types/responselike': 1.0.0
+ dev: true
+
+ /@types/http-cache-semantics/4.0.1:
+ resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
+ dev: true
+
+ /@types/keyv/3.1.3:
+ resolution: {integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==}
+ dependencies:
+ '@types/node': 17.0.8
+ dev: true
+
+ /@types/node/17.0.8:
+ resolution: {integrity: sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==}
+ dev: true
+
+ /@types/pug/2.0.6:
+ resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
+ dev: true
+
+ /@types/responselike/1.0.0:
+ resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
+ dependencies:
+ '@types/node': 17.0.8
+ dev: true
+
+ /@types/sass/1.43.1:
+ resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
+ dependencies:
+ '@types/node': 17.0.8
+ dev: true
+
+ /ansi-regex/5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /ansi-styles/3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: true
+
+ /ansi-styles/4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+ dependencies:
+ color-convert: 2.0.1
+ dev: true
+
+ /anymatch/3.1.2:
+ resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+ dev: true
+
+ /async/3.2.3:
+ resolution: {integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==}
+ dev: true
+
+ /at-least-node/1.0.0:
+ resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
+ engines: {node: '>= 4.0.0'}
+ dev: true
+
+ /balanced-match/1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ dev: true
+
+ /binary-extensions/2.2.0:
+ resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /brace-expansion/1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+ dev: true
+
+ /braces/3.0.2:
+ resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ engines: {node: '>=8'}
+ dependencies:
+ fill-range: 7.0.1
+ dev: true
+
+ /buffer-crc32/0.2.13:
+ resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
+ dev: true
+
+ /cacheable-lookup/5.0.4:
+ resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
+ engines: {node: '>=10.6.0'}
+ dev: true
+
+ /cacheable-request/7.0.2:
+ resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==}
+ engines: {node: '>=8'}
+ dependencies:
+ clone-response: 1.0.2
+ get-stream: 5.2.0
+ http-cache-semantics: 4.1.0
+ keyv: 4.0.5
+ lowercase-keys: 2.0.0
+ normalize-url: 6.1.0
+ responselike: 2.0.0
+ dev: true
+
+ /callsites/3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /carbon-components-svelte/0.51.3:
+ resolution: {integrity: sha512-d2CmC0abuZCU+jBEgwgoOiq/R3AqZftUyBiishujLV71W/2/C7SQzIIXJPY7kCVl1bydvGikrFPPgkUb70jL+w==}
+ dependencies:
+ flatpickr: 4.6.9
+ dev: true
+
+ /carbon-components/10.51.0:
+ resolution: {integrity: sha512-TkcfsYdFioZBx5vasC6bMOxI4XqJPt6EM7AqWAjwOeS7HByfBREYALHbZ3pBslb9yauoSbxQFKO2JlK5l+Baag==}
+ requiresBuild: true
+ dependencies:
+ '@carbon/telemetry': 0.0.0-alpha.6
+ flatpickr: 4.6.1
+ lodash.debounce: 4.0.8
+ warning: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /carbon-icons-svelte/10.44.3:
+ resolution: {integrity: sha512-vLBy3T5Dnv68vjTJEtwL7DKHX4VqXHeJ6DwPtiO9zTwoMOU3eLx/exzR6KTISuUMqfEPmrOy4dOwZc5sPWuxLA==}
+ dev: true
+
+ /carbon-preprocess-svelte/0.6.0_svelte@3.46.1:
+ resolution: {integrity: sha512-z/SOcB1jqrodshM87YPWdTcLdQzCKKLiWueM4RPfTIz9EIo7DgJSn0gbnVtEOk8Vy/xOZ0EnnAu5fY8ualzngQ==}
+ dependencies:
+ purgecss: 4.1.3
+ svelte-preprocess: 4.10.1_svelte@3.46.1+typescript@4.5.4
+ typescript: 4.5.4
+ transitivePeerDependencies:
+ - '@babel/core'
+ - coffeescript
+ - less
+ - node-sass
+ - postcss
+ - postcss-load-config
+ - pug
+ - sass
+ - stylus
+ - sugarss
+ - svelte
+ dev: true
+
+ /chalk/2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: true
+
+ /chokidar/3.5.2:
+ resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==}
+ engines: {node: '>= 8.10.0'}
+ dependencies:
+ anymatch: 3.1.2
+ braces: 3.0.2
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /ci-info/2.0.0:
+ resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
+ dev: true
+
+ /cliui/7.0.4:
+ resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+ dev: true
+
+ /clone-response/1.0.2:
+ resolution: {integrity: sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=}
+ dependencies:
+ mimic-response: 1.0.1
+ dev: true
+
+ /color-convert/1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: true
+
+ /color-convert/2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+ dependencies:
+ color-name: 1.1.4
+ dev: true
+
+ /color-name/1.1.3:
+ resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}
+ dev: true
+
+ /color-name/1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ dev: true
+
+ /color-string/1.9.0:
+ resolution: {integrity: sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==}
+ dependencies:
+ color-name: 1.1.4
+ simple-swizzle: 0.2.2
+ dev: true
+
+ /color/3.2.1:
+ resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
+ dependencies:
+ color-convert: 1.9.3
+ color-string: 1.9.0
+ dev: true
+
+ /colors/1.4.0:
+ resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
+ engines: {node: '>=0.1.90'}
+ dev: true
+
+ /colorspace/1.1.4:
+ resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
+ dependencies:
+ color: 3.2.1
+ text-hex: 1.0.0
+ dev: true
+
+ /commander/8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+ engines: {node: '>= 12'}
+ dev: true
+
+ /concat-map/0.0.1:
+ resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
+ dev: true
+
+ /configstore/5.0.1:
+ resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==}
+ engines: {node: '>=8'}
+ dependencies:
+ dot-prop: 5.3.0
+ graceful-fs: 4.2.9
+ make-dir: 3.1.0
+ unique-string: 2.0.0
+ write-file-atomic: 3.0.3
+ xdg-basedir: 4.0.0
+ dev: true
+
+ /crypto-random-string/2.0.0:
+ resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /cssesc/3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /debug/4.3.3:
+ resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+ dev: true
+
+ /decompress-response/6.0.0:
+ resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ mimic-response: 3.1.0
+ dev: true
+
+ /defer-to-connect/2.0.1:
+ resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /detect-indent/6.1.0:
+ resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /dot-prop/5.3.0:
+ resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
+ engines: {node: '>=8'}
+ dependencies:
+ is-obj: 2.0.0
+ dev: true
+
+ /emoji-regex/8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+ dev: true
+
+ /enabled/2.0.0:
+ resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
+ dev: true
+
+ /end-of-stream/1.4.4:
+ resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+ dependencies:
+ once: 1.4.0
+ dev: true
+
+ /es6-promise/3.3.1:
+ resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
+ dev: true
+
+ /esbuild-android-arm64/0.13.15:
+ resolution: {integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-darwin-64/0.13.15:
+ resolution: {integrity: sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-darwin-arm64/0.13.15:
+ resolution: {integrity: sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-freebsd-64/0.13.15:
+ resolution: {integrity: sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-freebsd-arm64/0.13.15:
+ resolution: {integrity: sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-32/0.13.15:
+ resolution: {integrity: sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-64/0.13.15:
+ resolution: {integrity: sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-arm/0.13.15:
+ resolution: {integrity: sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-arm64/0.13.15:
+ resolution: {integrity: sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-mips64le/0.13.15:
+ resolution: {integrity: sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-linux-ppc64le/0.13.15:
+ resolution: {integrity: sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-netbsd-64/0.13.15:
+ resolution: {integrity: sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-openbsd-64/0.13.15:
+ resolution: {integrity: sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-sunos-64/0.13.15:
+ resolution: {integrity: sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-windows-32/0.13.15:
+ resolution: {integrity: sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-windows-64/0.13.15:
+ resolution: {integrity: sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild-windows-arm64/0.13.15:
+ resolution: {integrity: sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /esbuild/0.13.15:
+ resolution: {integrity: sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ esbuild-android-arm64: 0.13.15
+ esbuild-darwin-64: 0.13.15
+ esbuild-darwin-arm64: 0.13.15
+ esbuild-freebsd-64: 0.13.15
+ esbuild-freebsd-arm64: 0.13.15
+ esbuild-linux-32: 0.13.15
+ esbuild-linux-64: 0.13.15
+ esbuild-linux-arm: 0.13.15
+ esbuild-linux-arm64: 0.13.15
+ esbuild-linux-mips64le: 0.13.15
+ esbuild-linux-ppc64le: 0.13.15
+ esbuild-netbsd-64: 0.13.15
+ esbuild-openbsd-64: 0.13.15
+ esbuild-sunos-64: 0.13.15
+ esbuild-windows-32: 0.13.15
+ esbuild-windows-64: 0.13.15
+ esbuild-windows-arm64: 0.13.15
+ dev: true
+
+ /escalade/3.1.1:
+ resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /escape-string-regexp/1.0.5:
+ resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
+ engines: {node: '>=0.8.0'}
+ dev: true
+
+ /estree-walker/2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+ dev: true
+
+ /fast-glob/3.2.11:
+ resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.4
+ dev: true
+
+ /fastq/1.13.0:
+ resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
+ dependencies:
+ reusify: 1.0.4
+ dev: true
+
+ /fecha/4.2.1:
+ resolution: {integrity: sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==}
+ dev: true
+
+ /fill-range/7.0.1:
+ resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ to-regex-range: 5.0.1
+ dev: true
+
+ /flatpickr/4.6.1:
+ resolution: {integrity: sha512-3ULSxbXmcMIRzer/2jLNweoqHpwDvsjEawO2FUd9UFR8uPwLM+LruZcPDpuZStcEgbQKhuFOfXo4nYdGladSNw==}
+ dev: true
+
+ /flatpickr/4.6.9:
+ resolution: {integrity: sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==}
+ dev: true
+
+ /fn.name/1.1.0:
+ resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
+ dev: true
+
+ /fs-extra/9.1.0:
+ resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ at-least-node: 1.0.0
+ graceful-fs: 4.2.9
+ jsonfile: 6.1.0
+ universalify: 2.0.0
+ dev: true
+
+ /fs.realpath/1.0.0:
+ resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
+ dev: true
+
+ /fsevents/2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /function-bind/1.1.1:
+ resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+ dev: true
+
+ /get-caller-file/2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+ dev: true
+
+ /get-stream/5.2.0:
+ resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
+ engines: {node: '>=8'}
+ dependencies:
+ pump: 3.0.0
+ dev: true
+
+ /glob-parent/5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob/7.2.0:
+ resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.0.4
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
+ /globals/11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /got/11.8.3:
+ resolution: {integrity: sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==}
+ engines: {node: '>=10.19.0'}
+ dependencies:
+ '@sindresorhus/is': 4.3.0
+ '@szmarczak/http-timer': 4.0.6
+ '@types/cacheable-request': 6.0.2
+ '@types/responselike': 1.0.0
+ cacheable-lookup: 5.0.4
+ cacheable-request: 7.0.2
+ decompress-response: 6.0.0
+ http2-wrapper: 1.0.3
+ lowercase-keys: 2.0.0
+ p-cancelable: 2.1.1
+ responselike: 2.0.0
+ dev: true
+
+ /graceful-fs/4.2.9:
+ resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==}
+ dev: true
+
+ /has-flag/3.0.0:
+ resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=}
+ engines: {node: '>=4'}
+ dev: true
+
+ /has/1.0.3:
+ resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
+ engines: {node: '>= 0.4.0'}
+ dependencies:
+ function-bind: 1.1.1
+ dev: true
+
+ /http-cache-semantics/4.1.0:
+ resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==}
+ dev: true
+
+ /http2-wrapper/1.0.3:
+ resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
+ engines: {node: '>=10.19.0'}
+ dependencies:
+ quick-lru: 5.1.1
+ resolve-alpn: 1.2.1
+ dev: true
+
+ /import-fresh/3.3.0:
+ resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+ engines: {node: '>=6'}
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+ dev: true
+
+ /imurmurhash/0.1.4:
+ resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=}
+ engines: {node: '>=0.8.19'}
+ dev: true
+
+ /inflight/1.0.6:
+ resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+ dev: true
+
+ /inherits/2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ dev: true
+
+ /is-arrayish/0.3.2:
+ resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
+ dev: true
+
+ /is-binary-path/2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+ dependencies:
+ binary-extensions: 2.2.0
+ dev: true
+
+ /is-core-module/2.8.1:
+ resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==}
+ dependencies:
+ has: 1.0.3
+ dev: true
+
+ /is-extglob/2.1.1:
+ resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /is-fullwidth-code-point/3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-glob/4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+ dev: true
+
+ /is-number/7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+ dev: true
+
+ /is-obj/2.0.0:
+ resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-stream/2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-typedarray/1.0.0:
+ resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=}
+ dev: true
+
+ /js-tokens/4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+ dev: true
+
+ /jsesc/2.5.2:
+ resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /json-buffer/3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+ dev: true
+
+ /jsonfile/6.1.0:
+ resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+ dependencies:
+ universalify: 2.0.0
+ optionalDependencies:
+ graceful-fs: 4.2.9
+ dev: true
+
+ /keyv/4.0.5:
+ resolution: {integrity: sha512-531pkGLqV3BMg0eDqqJFI0R1mkK1Nm5xIP2mM6keP5P8WfFtCkg2IOwplTUmlGoTgIg9yQYZ/kdihhz89XH3vA==}
+ dependencies:
+ json-buffer: 3.0.1
+ dev: true
+
+ /kleur/4.1.4:
+ resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /kuler/2.0.0:
+ resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
+ dev: true
+
+ /lodash.debounce/4.0.8:
+ resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=}
+ dev: true
+
+ /logform/2.3.2:
+ resolution: {integrity: sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA==}
+ dependencies:
+ colors: 1.4.0
+ fecha: 4.2.1
+ ms: 2.1.2
+ safe-stable-stringify: 1.1.1
+ triple-beam: 1.3.0
+ dev: true
+
+ /loose-envify/1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+ dependencies:
+ js-tokens: 4.0.0
+ dev: true
+
+ /lowercase-keys/2.0.0:
+ resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /lru-cache/6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+ dependencies:
+ yallist: 4.0.0
+ dev: true
+
+ /lz-string/1.4.4:
+ resolution: {integrity: sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=}
+ hasBin: true
+ dev: true
+
+ /magic-string/0.25.7:
+ resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
+ dependencies:
+ sourcemap-codec: 1.4.8
+ dev: true
+
+ /make-dir/3.1.0:
+ resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
+ engines: {node: '>=8'}
+ dependencies:
+ semver: 6.3.0
+ dev: true
+
+ /merge2/1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /micromatch/4.0.4:
+ resolution: {integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /mimic-response/1.0.1:
+ resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /mimic-response/3.1.0:
+ resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /min-indent/1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /minimatch/3.0.4:
+ resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
+ dependencies:
+ brace-expansion: 1.1.11
+ dev: true
+
+ /minimist/1.2.5:
+ resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}
+ dev: true
+
+ /mkdirp/0.5.5:
+ resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.5
+ dev: true
+
+ /mri/1.2.0:
+ resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /ms/2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+ dev: true
+
+ /nanoid/3.1.32:
+ resolution: {integrity: sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+ dev: true
+
+ /normalize-path/3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /normalize-url/6.1.0:
+ resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /once/1.4.0:
+ resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
+ dependencies:
+ wrappy: 1.0.2
+ dev: true
+
+ /one-time/1.0.0:
+ resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
+ dependencies:
+ fn.name: 1.1.0
+ dev: true
+
+ /p-cancelable/2.1.1:
+ resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /parent-module/1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+ dependencies:
+ callsites: 3.1.0
+ dev: true
+
+ /path-is-absolute/1.0.1:
+ resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /path-parse/1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+ dev: true
+
+ /picocolors/1.0.0:
+ resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+ dev: true
+
+ /picomatch/2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+ dev: true
+
+ /postcss-selector-parser/6.0.9:
+ resolution: {integrity: sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+ dev: true
+
+ /postcss/8.4.5:
+ resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.1.32
+ picocolors: 1.0.0
+ source-map-js: 1.0.1
+ dev: true
+
+ /pump/3.0.0:
+ resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
+ dependencies:
+ end-of-stream: 1.4.4
+ once: 1.4.0
+ dev: true
+
+ /purgecss/4.1.3:
+ resolution: {integrity: sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==}
+ hasBin: true
+ dependencies:
+ commander: 8.3.0
+ glob: 7.2.0
+ postcss: 8.4.5
+ postcss-selector-parser: 6.0.9
+ dev: true
+
+ /queue-microtask/1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ dev: true
+
+ /quick-lru/5.1.1:
+ resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /readable-stream/3.6.0:
+ resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
+ engines: {node: '>= 6'}
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+ dev: true
+
+ /readdirp/3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ dependencies:
+ picomatch: 2.3.1
+ dev: true
+
+ /regexparam/2.0.0:
+ resolution: {integrity: sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /require-directory/2.1.1:
+ resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /require-relative/0.8.7:
+ resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}
+ dev: true
+
+ /resolve-alpn/1.2.1:
+ resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
+ dev: true
+
+ /resolve-from/4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /resolve/1.21.0:
+ resolution: {integrity: sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.8.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+ dev: true
+
+ /responselike/2.0.0:
+ resolution: {integrity: sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==}
+ dependencies:
+ lowercase-keys: 2.0.0
+ dev: true
+
+ /reusify/1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ dev: true
+
+ /rimraf/2.7.1:
+ resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+ hasBin: true
+ dependencies:
+ glob: 7.2.0
+ dev: true
+
+ /rollup/2.64.0:
+ resolution: {integrity: sha512-+c+lbw1lexBKSMb1yxGDVfJ+vchJH3qLbmavR+awDinTDA2C5Ug9u7lkOzj62SCu0PKUExsW36tpgW7Fmpn3yQ==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /run-parallel/1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+ dev: true
+
+ /sade/1.8.1:
+ resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+ engines: {node: '>=6'}
+ dependencies:
+ mri: 1.2.0
+ dev: true
+
+ /safe-buffer/5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ dev: true
+
+ /safe-stable-stringify/1.1.1:
+ resolution: {integrity: sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==}
+ dev: true
+
+ /sander/0.5.1:
+ resolution: {integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0=}
+ dependencies:
+ es6-promise: 3.3.1
+ graceful-fs: 4.2.9
+ mkdirp: 0.5.5
+ rimraf: 2.7.1
+ dev: true
+
+ /semver/6.3.0:
+ resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
+ hasBin: true
+ dev: true
+
+ /semver/7.3.5:
+ resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ lru-cache: 6.0.0
+ dev: true
+
+ /signal-exit/3.0.6:
+ resolution: {integrity: sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==}
+ dev: true
+
+ /simple-swizzle/0.2.2:
+ resolution: {integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=}
+ dependencies:
+ is-arrayish: 0.3.2
+ dev: true
+
+ /sorcery/0.10.0:
+ resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
+ hasBin: true
+ dependencies:
+ buffer-crc32: 0.2.13
+ minimist: 1.2.5
+ sander: 0.5.1
+ sourcemap-codec: 1.4.8
+ dev: true
+
+ /source-map-js/1.0.1:
+ resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /source-map/0.5.7:
+ resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /source-map/0.7.3:
+ resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /sourcemap-codec/1.4.8:
+ resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
+ dev: true
+
+ /stack-trace/0.0.10:
+ resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=}
+ dev: true
+
+ /string-width/4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+ dev: true
+
+ /string_decoder/1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: true
+
+ /strip-ansi/6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+ dependencies:
+ ansi-regex: 5.0.1
+ dev: true
+
+ /strip-indent/3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ min-indent: 1.0.1
+ dev: true
+
+ /supports-color/5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+ dependencies:
+ has-flag: 3.0.0
+ dev: true
+
+ /supports-preserve-symlinks-flag/1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /svelte-check/2.3.0_svelte@3.46.1:
+ resolution: {integrity: sha512-SBKdJyUmxzPmJf/ZPqDSQOoa9JzOcgEpV7u3UaYcgVn7fA0veZ3FA5JgLU8KYtf84Gp6guBVcrC7XKLjJa5SXQ==}
+ hasBin: true
+ peerDependencies:
+ svelte: ^3.24.0
+ dependencies:
+ chokidar: 3.5.2
+ fast-glob: 3.2.11
+ import-fresh: 3.3.0
+ minimist: 1.2.5
+ picocolors: 1.0.0
+ sade: 1.8.1
+ source-map: 0.7.3
+ svelte: 3.46.1
+ svelte-preprocess: 4.10.1_svelte@3.46.1+typescript@4.5.4
+ typescript: 4.5.4
+ transitivePeerDependencies:
+ - '@babel/core'
+ - coffeescript
+ - less
+ - node-sass
+ - postcss
+ - postcss-load-config
+ - pug
+ - sass
+ - stylus
+ - sugarss
+ dev: true
+
+ /svelte-fsm/1.1.2:
+ resolution: {integrity: sha512-lGeuhELDkQC5WK4+MHJtdigXCqwsYMR1euyRTacDCwtbORzEy5ovivTY3Mnd5bM7H+huO3nKjNjdPvDLcLn3pQ==}
+ engines: {node: '>=14.0.0', npm: '>=7.0.0'}
+ dev: true
+
+ /svelte-hmr/0.14.9_svelte@3.46.1:
+ resolution: {integrity: sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg==}
+ peerDependencies:
+ svelte: '>=3.19.0'
+ dependencies:
+ svelte: 3.46.1
+ dev: true
+
+ /svelte-preprocess/4.10.1_svelte@3.46.1+typescript@4.5.4:
+ resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==}
+ engines: {node: '>= 9.11.2'}
+ requiresBuild: true
+ peerDependencies:
+ '@babel/core': ^7.10.2
+ coffeescript: ^2.5.1
+ less: ^3.11.3
+ node-sass: '*'
+ postcss: ^7 || ^8
+ postcss-load-config: ^2.1.0 || ^3.0.0
+ pug: ^3.0.0
+ sass: ^1.26.8
+ stylus: ^0.54.7
+ sugarss: ^2.0.0
+ svelte: ^3.23.0
+ typescript: ^4.5.2
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ coffeescript:
+ optional: true
+ less:
+ optional: true
+ node-sass:
+ optional: true
+ postcss:
+ optional: true
+ postcss-load-config:
+ optional: true
+ pug:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ '@types/pug': 2.0.6
+ '@types/sass': 1.43.1
+ detect-indent: 6.1.0
+ magic-string: 0.25.7
+ sorcery: 0.10.0
+ strip-indent: 3.0.0
+ svelte: 3.46.1
+ typescript: 4.5.4
+ dev: true
+
+ /svelte-spa-router/3.2.0:
+ resolution: {integrity: sha512-igemo5Vs82TGBBw+DjWt6qKameXYzNs6aDXcTxou5XbEvOjiRcAM6MLkdVRCatn6u8r42dE99bt/br7T4qe/AQ==}
+ dependencies:
+ regexparam: 2.0.0
+ dev: true
+
+ /svelte/3.46.1:
+ resolution: {integrity: sha512-Ue8ivq+G45AfZZL4Z93xNFiC352wPkyGiY9QSuWjxXh6jiaZMrpthinjc1rz0OSTceuST7Pxr1HDBj2KioliZg==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /text-hex/1.0.0:
+ resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
+ dev: true
+
+ /to-fast-properties/2.0.0:
+ resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=}
+ engines: {node: '>=4'}
+ dev: true
+
+ /to-regex-range/5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ is-number: 7.0.0
+ dev: true
+
+ /triple-beam/1.3.0:
+ resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
+ dev: true
+
+ /tslib/2.3.1:
+ resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
+ dev: true
+
+ /typedarray-to-buffer/3.1.5:
+ resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
+ dependencies:
+ is-typedarray: 1.0.0
+ dev: true
+
+ /typescript/4.5.4:
+ resolution: {integrity: sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+ dev: true
+
+ /unique-string/2.0.0:
+ resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
+ engines: {node: '>=8'}
+ dependencies:
+ crypto-random-string: 2.0.0
+ dev: true
+
+ /universalify/2.0.0:
+ resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
+ engines: {node: '>= 10.0.0'}
+ dev: true
+
+ /util-deprecate/1.0.2:
+ resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
+ dev: true
+
+ /validator/13.7.0:
+ resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==}
+ engines: {node: '>= 0.10'}
+ dev: true
+
+ /vite/2.7.12:
+ resolution: {integrity: sha512-KvPYToRQWhRfBeVkyhkZ5hASuHQkqZUUdUcE3xyYtq5oYEPIJ0h9LWiWTO6v990glmSac2cEPeYeXzpX5Z6qKQ==}
+ engines: {node: '>=12.2.0'}
+ hasBin: true
+ peerDependencies:
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ peerDependenciesMeta:
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ dependencies:
+ esbuild: 0.13.15
+ postcss: 8.4.5
+ resolve: 1.21.0
+ rollup: 2.64.0
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /warning/3.0.0:
+ resolution: {integrity: sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: true
+
+ /winston-transport/4.4.2:
+ resolution: {integrity: sha512-9jmhltAr5ygt5usgUTQbEiw/7RYXpyUbEAFRCSicIacpUzPkrnQsQZSPGEI12aLK9Jth4zNcYJx3Cvznwrl8pw==}
+ engines: {node: '>= 6.4.0'}
+ dependencies:
+ logform: 2.3.2
+ readable-stream: 3.6.0
+ triple-beam: 1.3.0
+ dev: true
+
+ /winston/3.4.0:
+ resolution: {integrity: sha512-FqilVj+5HKwCfIHQzMxrrd5tBIH10JTS3koFGbLVWBODjiIYq7zir08rFyBT4rrTYG/eaTqDcfSIbcjSM78YSw==}
+ engines: {node: '>= 6.4.0'}
+ dependencies:
+ '@dabh/diagnostics': 2.0.2
+ async: 3.2.3
+ is-stream: 2.0.1
+ logform: 2.3.2
+ one-time: 1.0.0
+ readable-stream: 3.6.0
+ stack-trace: 0.0.10
+ triple-beam: 1.3.0
+ winston-transport: 4.4.2
+ dev: true
+
+ /wrap-ansi/7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ dev: true
+
+ /wrappy/1.0.2:
+ resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
+ dev: true
+
+ /write-file-atomic/3.0.3:
+ resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
+ dependencies:
+ imurmurhash: 0.1.4
+ is-typedarray: 1.0.0
+ signal-exit: 3.0.6
+ typedarray-to-buffer: 3.1.5
+ dev: true
+
+ /xdg-basedir/4.0.0:
+ resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /y18n/5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /yallist/4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ dev: true
+
+ /yargs-parser/20.2.9:
+ resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /yargs/16.2.0:
+ resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+ engines: {node: '>=10'}
+ dependencies:
+ cliui: 7.0.4
+ escalade: 3.1.1
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 20.2.9
+ dev: true
diff --git a/src/webapp/public/favicon.ico b/src/webapp/public/favicon.ico
new file mode 100644
index 0000000..d75d248
--- /dev/null
+++ b/src/webapp/public/favicon.ico
Binary files differ
diff --git a/src/webapp/src/app.svelte b/src/webapp/src/app.svelte
new file mode 100644
index 0000000..92c9824
--- /dev/null
+++ b/src/webapp/src/app.svelte
@@ -0,0 +1,36 @@
+<script lang="ts">
+ import Router, {replace, location} from "svelte-spa-router";
+ import routes from "./routes";
+ import {onMount} from "svelte";
+ import {session_is_valid_async} from "@/lib/stores/session";
+ import preferences from "@/lib/stores/preferences";
+ import {ApplicationTheme} from "@/lib/enums/ApplicationTheme";
+
+ preferences.subscribe(e => {
+ switch (e.theme) {
+ case ApplicationTheme.DARK:
+ document.documentElement.setAttribute("theme", "g100");
+ break;
+ case ApplicationTheme.LIGHT:
+ document.documentElement.setAttribute("theme", "white");
+ break;
+ }
+ });
+
+ onMount(async () => {
+ const session_is_valid = await session_is_valid_async();
+ if (session_is_valid) {
+ console.log("Valid session, navigating to /app...");
+ if ($location !== "/app") {
+ await replace("/app");
+ }
+ } else {
+ if ($location !== "/login") {
+ console.log("Invalid session, navigating to /login...");
+ await replace("/login");
+ }
+ }
+ });
+</script>
+
+<Router {routes}/>
diff --git a/src/webapp/src/components/carbon-extras/HeaderPanelToggle.svelte b/src/webapp/src/components/carbon-extras/HeaderPanelToggle.svelte
new file mode 100644
index 0000000..f7303ac
--- /dev/null
+++ b/src/webapp/src/components/carbon-extras/HeaderPanelToggle.svelte
@@ -0,0 +1,50 @@
+<script lang="ts">
+ import {ToggleSkeleton} from "carbon-components-svelte";
+ import {createEventDispatcher, onMount} from "svelte";
+
+ const dispatch = createEventDispatcher();
+ export let toggled = false;
+ export let size = "sm";
+
+ let toggle_id;
+
+ onMount(() => {
+ //@ts-ignore
+ document.getElementById(toggle_id).checked = toggled;
+ });
+
+ function handle_click(e) {
+ if (e.target.classList.contains("bx--toggle")) {
+ //@ts-ignore
+ document.getElementById(toggle_id).checked = !toggled;
+ dispatch("toggle");
+ }
+ }
+</script>
+<style lang="scss">
+ .bx--switcher__item-toggle {
+ font-size: 0.875rem;
+ font-weight: 600;
+ line-height: 1.28572;
+ letter-spacing: 0.16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 2rem;
+ padding: 0.375rem 1rem;
+ color: #c6c6c6;
+ text-decoration: none;
+ }
+</style>
+<li class="bx--switcher__item">
+ <div class="bx--switcher__item-toggle">
+ <div style="display: flex;">
+ <slot/>
+ </div>
+ <ToggleSkeleton size="{size}"
+ on:click={handle_click}
+ bind:id={toggle_id}
+ style="flex: initial !important;"
+ {toggled}/>
+ </div>
+</li>
diff --git a/src/webapp/src/components/entry-card.svelte b/src/webapp/src/components/entry-card.svelte
new file mode 100644
index 0000000..30408fb
--- /dev/null
+++ b/src/webapp/src/components/entry-card.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+ import type {IEntryDto} from "@/lib/models/IEntryDto";
+ import {Tag, Tile} from "carbon-components-svelte";
+
+ export let entry = {} as IEntryDto;
+ export let editing = false;
+</script>
+
+<Tile>
+ <p>{entry.url}</p>
+ {#if entry.tags.length > 0}
+ {#each entry.tags.split(" ") as tag}
+ <Tag size="sm">{tag}</Tag>
+ {/each}
+ {/if}
+ {#if entry.description}
+ <p>{@html entry.description.replace("\n", "<br>")}</p>
+ {/if}
+</Tile>
diff --git a/src/webapp/src/components/entry-list.svelte b/src/webapp/src/components/entry-list.svelte
new file mode 100644
index 0000000..a546de5
--- /dev/null
+++ b/src/webapp/src/components/entry-list.svelte
@@ -0,0 +1,29 @@
+<script lang="ts">
+ import EntryCard from "@/components/entry-card.svelte";
+ import entries, {hydrate_entry_cache_async} from "@/lib/stores/entries";
+ import {Grid, InlineLoading, InlineNotification, Search, Tile} from "carbon-components-svelte";
+ import {onMount} from "svelte";
+
+ onMount(async () => {
+ await hydrate_entry_cache_async();
+ });
+</script>
+
+<Tile>
+ <Grid>
+ <Search placeholder="Search for entries..."
+ size="sm"/>
+ <!--TODO: Show loader when something is changing in the store-->
+ {#if false}
+ <InlineLoading description="Loading entries..."/>
+ {:else if $entries.length > 0}
+ {#each $entries as entry}
+ <EntryCard {entry}/>
+ {/each}
+ {:else}
+ <InlineNotification title="No entries..."
+ kind="info"
+ hideCloseButton/>
+ {/if}
+ </Grid>
+</Tile>
diff --git a/src/webapp/src/components/forms/entry-form.svelte b/src/webapp/src/components/forms/entry-form.svelte
new file mode 100644
index 0000000..973065b
--- /dev/null
+++ b/src/webapp/src/components/forms/entry-form.svelte
@@ -0,0 +1,200 @@
+<script lang="ts">
+ import {Button, Column, Form, Grid, InlineNotification, Row, TextArea, TextInput, Tile} from "carbon-components-svelte";
+ import type {IErrorResult} from "@/lib/models/IErrorResult";
+ import type {IEntryDto} from "@/lib/models/IEntryDto";
+ import isFQDN from "validator/lib/isURL";
+ import {create_entry_async} from "@/lib/stores/entries";
+ import {createEventDispatcher} from "svelte";
+ import {get_site_report} from "@/lib/api/root";
+ import type {ISiteReportDto} from "@/lib/models/ISiteReportDto";
+ import preferences from "@/lib/stores/preferences";
+ import {get} from "svelte/store";
+
+ const dispatch = createEventDispatcher();
+ let url_change_temp = "";
+ let description_is_autopopulated = false;
+ let enable_site_report = get(preferences).enable_site_report;
+
+ const form = {
+ loading: false,
+ error: {} as IErrorResult,
+ url: {
+ value: "",
+ error: "",
+ warning: "",
+ async change(event) {
+ if (!enable_site_report) return;
+ let latest_value = event.target.value;
+ if (!latest_value.startsWith("http")) latest_value = "http://" + latest_value;
+ if (latest_value === url_change_temp) return;
+ url_change_temp = latest_value;
+ form.url.warning = "";
+ if (!isFQDN(latest_value)) {
+ return;
+ }
+ try {
+ const http_request = await get_site_report({
+ url: latest_value
+ });
+ if (http_request.ok) {
+ form.url.warning = "";
+ const report = await http_request.json() as ISiteReportDto;
+ if (report.duplicate) {
+ form.url.warning = "This url is already in you database";
+ } else if (report.unreachable) {
+ if (description_is_autopopulated) form.description.value = "";
+ form.url.warning = "Unreachable url";
+ } else if (report.description && !form.description.value || description_is_autopopulated) {
+ form.description.value = report.description;
+ description_is_autopopulated = true;
+ }
+ } else if (http_request.status === 404) {
+ if (description_is_autopopulated) form.description.value = "";
+ form.url.warning = "";
+ }
+ } catch {
+ form.url.warning = "";
+ }
+ },
+ validate() {
+ form.url.error = "";
+ let is_valid = true;
+ if (!isFQDN(form.url.value)) {
+ is_valid = false;
+ form.url.error = "Invalid url";
+ }
+ return is_valid;
+ },
+ reset() {
+ form.url.value = "";
+ form.url.warning = "";
+ form.url.error = "";
+ }
+ },
+ tags: {
+ value: "",
+ error: "",
+ warning: "",
+ validate() {
+ form.tags.error = "";
+ return true;
+ },
+ reset() {
+ form.tags.value = "";
+ form.tags.warning = "";
+ form.tags.error = "";
+ }
+ },
+ description: {
+ value: "",
+ error: "",
+ warning: "",
+ validate() {
+ form.description.error = "";
+ return true;
+ },
+ reset() {
+ form.description.value = "";
+ form.description.warning = "";
+ form.description.error = "";
+ }
+ },
+ is_valid() {
+ const url_is_valid = form.url.validate();
+ const tags_is_valid = form.tags.validate();
+ const description_is_valid = form.description.validate();
+ return (url_is_valid && tags_is_valid && description_is_valid);
+ },
+ payload(): IEntryDto {
+ return {
+ url: this.url.value,
+ tags: this.tags.value,
+ description: this.description.value
+ } as IEntryDto;
+ },
+ reset() {
+ form.url.reset();
+ form.description.reset();
+ form.tags.reset();
+ },
+ async submit() {
+ if (!form.is_valid()) return;
+ if (!form.url.value.startsWith("http")) {
+ form.url.value = "http://" + form.url.value;
+ }
+ form.error = {} as IErrorResult;
+ form.loading = true;
+ const request = await create_entry_async(form.payload());
+ form.loading = false;
+ if (request.ok) {
+ form.reset();
+ dispatch("created");
+ } else if (request?.error) {
+ form.error = request.error;
+ }
+ }
+ };
+
+ preferences.subscribe(val => {
+ enable_site_report = val.enable_site_report;
+ if (!enable_site_report) form.url.warning = "";
+ });
+</script>
+
+<Tile>
+ <Form on:submit={form.submit}
+ disabled={form.loading}>
+ <Grid condensed>
+ <Row style="padding-bottom: 12px">
+ <Column>
+ <h2>New entry</h2>
+ </Column>
+ </Row>
+ {#if form.error.title || form.error.text}
+ <Row>
+ <Column>
+ <InlineNotification
+ hideCloseButton
+ title={form.error.title ?? ""}
+ subtitle={form.error.text ?? ""}/>
+ </Column>
+ </Row>
+ {/if}
+ <Row>
+ <Column>
+ <TextInput placeholder="url"
+ inputmode="url"
+ size="sm"
+ warn={form.url.warning !== ""}
+ warnText={form.url.warning}
+ invalid={form.url.error !== ""}
+ invalidMessage={form.url.error}
+ on:keyup={form.url.change}
+ on:focusout={form.url.validate}
+ on:change={form.url.validate}
+ bind:value={form.url.value}/>
+ </Column>
+ </Row>
+ <Row>
+ <Column>
+ <TextInput placeholder="tags"
+ size="sm"
+ bind:value={form.tags.value}/>
+ </Column>
+ </Row>
+ <Row style="padding-bottom: 5px">
+ <Column>
+ <TextArea placeholder="description"
+ size="sm"
+ bind:value={form.description.value}/>
+ </Column>
+ </Row>
+ <Row style="justify-content: end">
+ <Button size="sm"
+ type="submit">
+ Submit
+ </Button>
+ </Row>
+ </Grid>
+ </Form>
+</Tile>
diff --git a/src/webapp/src/components/forms/login-form.svelte b/src/webapp/src/components/forms/login-form.svelte
new file mode 100644
index 0000000..aa90917
--- /dev/null
+++ b/src/webapp/src/components/forms/login-form.svelte
@@ -0,0 +1,118 @@
+<script lang="ts">
+ import {start_session_async} from "@/lib/stores/session";
+ import type {ICreateSessionRequest} from "@/lib/models/ICreateSessionRequest";
+ import {createEventDispatcher} from "svelte";
+ import type {IErrorResult} from "@/lib/models/IErrorResult";
+ import {Button, Checkbox, Column, Form, Grid, InlineNotification, PasswordInput, Row, TextInput, Tile} from "carbon-components-svelte";
+
+ const dispatch = createEventDispatcher();
+
+ const form = {
+ loading: false,
+ error: {} as IErrorResult,
+ username: {
+ value: "",
+ error: "",
+ validate() {
+ form.username.error = "";
+ let is_valid = true;
+ if (!form.username.value) {
+ form.username.error = "Username is required";
+ is_valid = false;
+ }
+ return is_valid;
+ }
+ },
+ password: {
+ value: "",
+ error: "",
+ validate() {
+ form.password.error = "";
+ let is_valid = true;
+ if (!form.password.value) {
+ form.password.error = "Password is required";
+ is_valid = false;
+ }
+ return is_valid;
+ }
+ },
+ persist: {
+ value: false,
+ error: "",
+ validate() {
+ return true;
+ }
+ },
+ is_valid() {
+ const username_is_valid = form.username.validate();
+ const password_is_valid = form.password.validate();
+ const persist_is_valid = form.persist.validate();
+ return (username_is_valid && password_is_valid && persist_is_valid);
+ },
+ payload(): ICreateSessionRequest {
+ return {
+ username: this.username.value,
+ password: this.password.value,
+ persist: this.persist.value
+ } as ICreateSessionRequest;
+ },
+ async submit() {
+ if (!form.is_valid()) return;
+ form.error = {} as IErrorResult;
+ form.loading = true;
+ const login_request = await start_session_async(form.payload());
+ form.loading = false;
+ if (login_request.ok) {
+ dispatch("authenticated");
+ } else if (login_request.error) {
+ form.error = login_request.error;
+ }
+ }
+ };
+</script>
+
+<Tile>
+ <Form on:submit={form.submit}>
+ <Grid condensed>
+ <Row style="padding-bottom: 12px">
+ <Column>
+ <h1>Login</h1>
+ </Column>
+ </Row>
+ {#if form.error.title || form.error.text}
+ <Row>
+ <Column>
+ <InlineNotification
+ hideCloseButton
+ title={form.error.title ?? ""}
+ subtitle={form.error.text ?? ""}/>
+ </Column>
+ </Row>
+ {/if}
+ <Row>
+ <Column>
+ <TextInput placeholder="Username"
+ size="sm"
+ invalid={form.username.error !== ""}
+ invalidText={form.username.error}
+ bind:value={form.username.value}/>
+ <PasswordInput placeholder="Password"
+ size="sm"
+ invalid={form.password.error !== ""}
+ invalidText={form.password.error}
+ bind:value={form.password.value}/>
+ </Column>
+ </Row>
+ <Row>
+ <Checkbox name="persist"
+ labelText="Persist session"
+ bind:checked={form.persist.value}/>
+ <Button type="submit"
+ disabled={form.loading}
+ size="sm">
+ Submit
+ </Button>
+ </Row>
+ </Grid>
+ </Form>
+</Tile>
diff --git a/src/webapp/src/global.d.ts b/src/webapp/src/global.d.ts
new file mode 100644
index 0000000..90ee84c
--- /dev/null
+++ b/src/webapp/src/global.d.ts
@@ -0,0 +1,11 @@
+import type {IsURLOptions} from "validator/lib/isURL";
+
+declare global {
+ namespace n4s {
+ interface EnforceCustomMatchers<R> {
+ isURL(str: string, options?: IsURLOptions): R;
+ }
+ }
+}
+
+export {};
diff --git a/src/webapp/src/lib/api/account.ts b/src/webapp/src/lib/api/account.ts
new file mode 100644
index 0000000..f1c1708
--- /dev/null
+++ b/src/webapp/src/lib/api/account.ts
@@ -0,0 +1,27 @@
+import {api_base} from "@/lib/configuration";
+import type {ICreateSessionRequest} from "@/lib/models/ICreateSessionRequest";
+
+export async function create_session_async(request: ICreateSessionRequest): Promise<Response> {
+ return fetch(api_base("account/create-session"), {
+ method: "post",
+ credentials: "include",
+ body: JSON.stringify(request),
+ headers: {
+ "Content-Type": "application/json;charset=UTF-8"
+ }
+ });
+}
+
+export async function get_profile_async(): Promise<Response> {
+ return fetch(api_base("account/profile-data"), {
+ method: "get",
+ credentials: "include"
+ });
+}
+
+export async function end_session_async(): Promise<Response> {
+ return fetch(api_base("account/end-session"), {
+ method: "get",
+ credentials: "include"
+ });
+}
diff --git a/src/webapp/src/lib/api/entries.ts b/src/webapp/src/lib/api/entries.ts
new file mode 100644
index 0000000..b727b61
--- /dev/null
+++ b/src/webapp/src/lib/api/entries.ts
@@ -0,0 +1,73 @@
+import {api_base} from "@/lib/configuration";
+import {replace} from "svelte-spa-router";
+import {clear_session_async} from "@/lib/stores/session";
+import type {ICreateEntryRequest} from "@/lib/models/ICreateEntryRequest";
+import type {IUpdateEntryRequest} from "@/lib/models/IUpdateEntryRequest";
+
+export async function get_all_entries_async(): Promise<Response> {
+ const http_request = await fetch(api_base("entries"), {
+ method: "get",
+ credentials: "include"
+ });
+
+ if (http_request.status === 401) {
+ await clear_session_async();
+ await replace("/login");
+ return {ok: false} as Response;
+ }
+
+ return http_request;
+}
+
+export async function create_new_entry_async(request: ICreateEntryRequest): Promise<Response> {
+ const http_request = await fetch(api_base("entries/create"), {
+ method: "post",
+ credentials: "include",
+ body: JSON.stringify(request),
+ headers: {
+ "Content-Type": "application/json;charset=utf-8"
+ }
+ });
+
+ if (http_request.status === 401) {
+ await clear_session_async();
+ await replace("/login");
+ return {ok: false} as Response;
+ }
+
+ return http_request;
+}
+
+export async function delete_entry_async(entry_id: string): Promise<Response> {
+ const http_request = await fetch(api_base("entries/" + entry_id), {
+ method: "delete",
+ credentials: "include"
+ });
+
+ if (http_request.status === 401) {
+ await clear_session_async();
+ await replace("/login");
+ return {ok: false} as Response;
+ }
+
+ return http_request;
+}
+
+export async function update_entry_async(request: IUpdateEntryRequest): Promise<Response> {
+ const http_request = await fetch(api_base("entries/update"), {
+ method: "post",
+ credentials: "include",
+ body: JSON.stringify(request),
+ headers: {
+ "Content-Type": "application/json;charset=utf-8"
+ }
+ });
+
+ if (http_request.status === 401) {
+ await clear_session_async();
+ await replace("/login");
+ return {ok: false} as Response;
+ }
+
+ return http_request;
+}
diff --git a/src/webapp/src/lib/api/root.ts b/src/webapp/src/lib/api/root.ts
new file mode 100644
index 0000000..220834c
--- /dev/null
+++ b/src/webapp/src/lib/api/root.ts
@@ -0,0 +1,13 @@
+import {api_base} from "@/lib/configuration";
+import type {IGetSiteReportRequest} from "@/lib/models/IGetSiteReportRequest";
+
+export async function get_site_report(request: IGetSiteReportRequest): Promise<Response> {
+ return fetch(api_base("site-report"), {
+ method: "post",
+ credentials: "include",
+ body: JSON.stringify(request),
+ headers: {
+ "Content-Type": "application/json;charset=UTF-8"
+ }
+ });
+}
diff --git a/src/webapp/src/lib/configuration.ts b/src/webapp/src/lib/configuration.ts
new file mode 100644
index 0000000..e7dbcfe
--- /dev/null
+++ b/src/webapp/src/lib/configuration.ts
@@ -0,0 +1,18 @@
+const api_version = "v1/";
+
+export function api_base(path) {
+ return is_development() ? "http://localhost:5003/" + api_version + path : "/" + api_version + path;
+}
+
+export function is_development() {
+ return document.location.hostname === "localhost";
+}
+
+export const storage_keys = {
+ profile_data: "profile_data",
+ entries: "entries",
+ last_active_check: "last_active_check",
+ preferences: "preferences"
+};
+
+export const SECONDS_BETWEEN_SESSION_CHECK = 600;
diff --git a/src/webapp/src/lib/enums/ApplicationTheme.ts b/src/webapp/src/lib/enums/ApplicationTheme.ts
new file mode 100644
index 0000000..e4f5c72
--- /dev/null
+++ b/src/webapp/src/lib/enums/ApplicationTheme.ts
@@ -0,0 +1,4 @@
+export enum ApplicationTheme {
+ LIGHT,
+ DARK,
+}
diff --git a/src/webapp/src/lib/enums/ImportServiceEnum.ts b/src/webapp/src/lib/enums/ImportServiceEnum.ts
new file mode 100644
index 0000000..1de544d
--- /dev/null
+++ b/src/webapp/src/lib/enums/ImportServiceEnum.ts
@@ -0,0 +1,4 @@
+export enum ImportServiceEnum {
+ LARDER = "LARDER",
+ PINBOARD = "PINBOARD"
+}
diff --git a/src/webapp/src/lib/helpers.ts b/src/webapp/src/lib/helpers.ts
new file mode 100644
index 0000000..c8f8e62
--- /dev/null
+++ b/src/webapp/src/lib/helpers.ts
@@ -0,0 +1,32 @@
+import type {PickFileOptions} from "@/lib/models/PickFileOptions";
+
+export function merge_obj_arr<T>(a, b, prop: string): Array<T> {
+ const reduced = a.filter(aitem => !b.find(bitem => aitem[prop] === bitem[prop]));
+ return reduced.concat(b);
+}
+
+export function generate_unsafe_id(length: number): string {
+ if (!length) {
+ throw new Error("length is undefined");
+ }
+ let result = "";
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ const charactersLength = characters.length;
+ for (let i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}
+
+export function pick_file(cb: Function, options: PickFileOptions = {} as PickFileOptions) {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.multiple = options?.multiple;
+ input.style.display = "none";
+ document.body.appendChild(input);
+ input.click();
+ input.onchange = e => {
+ cb(input.files);
+ input.remove();
+ };
+}
diff --git a/src/webapp/src/lib/models/ICreateEntryRequest.ts b/src/webapp/src/lib/models/ICreateEntryRequest.ts
new file mode 100644
index 0000000..542ac62
--- /dev/null
+++ b/src/webapp/src/lib/models/ICreateEntryRequest.ts
@@ -0,0 +1,5 @@
+export interface ICreateEntryRequest {
+ url: string;
+ tags: string;
+ description: string;
+}
diff --git a/src/webapp/src/lib/models/ICreateEntryResponse.ts b/src/webapp/src/lib/models/ICreateEntryResponse.ts
new file mode 100644
index 0000000..d8c63dc
--- /dev/null
+++ b/src/webapp/src/lib/models/ICreateEntryResponse.ts
@@ -0,0 +1,7 @@
+import type {IErrorResult} from "@/lib/models/IErrorResult";
+
+export interface ICreateEntryResponse {
+ ok: boolean,
+ response?: object,
+ error?: IErrorResult
+}
diff --git a/src/webapp/src/lib/models/ICreateSessionRequest.ts b/src/webapp/src/lib/models/ICreateSessionRequest.ts
new file mode 100644
index 0000000..33d5e07
--- /dev/null
+++ b/src/webapp/src/lib/models/ICreateSessionRequest.ts
@@ -0,0 +1,19 @@
+export interface ICreateSessionRequest {
+ password: string;
+ username: string;
+ persist: boolean;
+}
+
+export function is_valid_create_session_payload(payload: ICreateSessionRequest): boolean | string[] {
+ let is_valid = true;
+ let errors = [] as Array<string>;
+ if (!payload.username) {
+ is_valid = false;
+ errors.push("Username is required");
+ }
+ if (!payload.password) {
+ is_valid = false;
+ errors.push("Password is required");
+ }
+ return is_valid;
+}
diff --git a/src/webapp/src/lib/models/IEntryDto.ts b/src/webapp/src/lib/models/IEntryDto.ts
new file mode 100644
index 0000000..3113adb
--- /dev/null
+++ b/src/webapp/src/lib/models/IEntryDto.ts
@@ -0,0 +1,6 @@
+export interface IEntryDto {
+ id: string;
+ url: string;
+ tags: string;
+ description: string;
+}
diff --git a/src/webapp/src/lib/models/IErrorResult.ts b/src/webapp/src/lib/models/IErrorResult.ts
new file mode 100644
index 0000000..a0ac4f6
--- /dev/null
+++ b/src/webapp/src/lib/models/IErrorResult.ts
@@ -0,0 +1,4 @@
+export interface IErrorResult {
+ title: string;
+ text?: string;
+}
diff --git a/src/webapp/src/lib/models/IGetSiteReportRequest.ts b/src/webapp/src/lib/models/IGetSiteReportRequest.ts
new file mode 100644
index 0000000..b0d4366
--- /dev/null
+++ b/src/webapp/src/lib/models/IGetSiteReportRequest.ts
@@ -0,0 +1,3 @@
+export interface IGetSiteReportRequest {
+ url: string;
+}
diff --git a/src/webapp/src/lib/models/ILarderBookmark.ts b/src/webapp/src/lib/models/ILarderBookmark.ts
new file mode 100644
index 0000000..54c1b24
--- /dev/null
+++ b/src/webapp/src/lib/models/ILarderBookmark.ts
@@ -0,0 +1,17 @@
+export interface ILarderBookmark {
+ id: string;
+ tags: Array<ILarderTag>;
+ title: string;
+ description: string;
+ url: string;
+ created: string;
+ modified: string;
+}
+
+export interface ILarderTag {
+ id: string;
+ name: string;
+ color: string;
+ created: string;
+ modified: string;
+}
diff --git a/src/webapp/src/lib/models/IPinboardPin.ts b/src/webapp/src/lib/models/IPinboardPin.ts
new file mode 100644
index 0000000..dde84ac
--- /dev/null
+++ b/src/webapp/src/lib/models/IPinboardPin.ts
@@ -0,0 +1,11 @@
+export interface IPinboardPin {
+ href: string;
+ description: string;
+ extended: string;
+ meta: string;
+ hash: string;
+ time: Date;
+ shared: string;
+ toread: string;
+ tags: string;
+}
diff --git a/src/webapp/src/lib/models/IPreferences.ts b/src/webapp/src/lib/models/IPreferences.ts
new file mode 100644
index 0000000..48ca8ae
--- /dev/null
+++ b/src/webapp/src/lib/models/IPreferences.ts
@@ -0,0 +1,6 @@
+import type {ApplicationTheme} from "@/lib/enums/ApplicationTheme";
+
+export interface IPreferences {
+ theme: ApplicationTheme,
+ enable_site_report: boolean
+}
diff --git a/src/webapp/src/lib/models/IProfileData.ts b/src/webapp/src/lib/models/IProfileData.ts
new file mode 100644
index 0000000..ca4c50f
--- /dev/null
+++ b/src/webapp/src/lib/models/IProfileData.ts
@@ -0,0 +1,4 @@
+export interface IProfileData {
+ username: string;
+ id: string;
+}
diff --git a/src/webapp/src/lib/models/ISiteReportDto.ts b/src/webapp/src/lib/models/ISiteReportDto.ts
new file mode 100644
index 0000000..954286f
--- /dev/null
+++ b/src/webapp/src/lib/models/ISiteReportDto.ts
@@ -0,0 +1,5 @@
+export interface ISiteReportDto {
+ description?: string,
+ duplicate: boolean,
+ unreachable: boolean
+}
diff --git a/src/webapp/src/lib/models/IUpdateEntryRequest.ts b/src/webapp/src/lib/models/IUpdateEntryRequest.ts
new file mode 100644
index 0000000..57d575a
--- /dev/null
+++ b/src/webapp/src/lib/models/IUpdateEntryRequest.ts
@@ -0,0 +1,6 @@
+export interface IUpdateEntryRequest {
+ id: string;
+ url: string;
+ tags: string;
+ description: string;
+}
diff --git a/src/webapp/src/lib/models/PickFileOptions.ts b/src/webapp/src/lib/models/PickFileOptions.ts
new file mode 100644
index 0000000..4f30888
--- /dev/null
+++ b/src/webapp/src/lib/models/PickFileOptions.ts
@@ -0,0 +1,3 @@
+export interface PickFileOptions {
+ multiple: boolean;
+}
diff --git a/src/webapp/src/lib/stores/entries.ts b/src/webapp/src/lib/stores/entries.ts
new file mode 100644
index 0000000..66be0cf
--- /dev/null
+++ b/src/webapp/src/lib/stores/entries.ts
@@ -0,0 +1,58 @@
+import {StoreType, writable_persistent} from "@/lib/stores/persistent-store";
+import {storage_keys} from "@/lib/configuration";
+import {create_new_entry_async, get_all_entries_async} from "@/lib/api/entries";
+import {get} from "svelte/store";
+import type {IEntryDto} from "@/lib/models/IEntryDto";
+import {merge_obj_arr} from "@/lib/helpers";
+import type {ICreateEntryRequest} from "@/lib/models/ICreateEntryRequest";
+import type {ICreateEntryResponse} from "@/lib/models/ICreateEntryResponse";
+
+const entries = writable_persistent<Array<IEntryDto>>({
+ name: storage_keys.entries,
+ initialState: [] as Array<IEntryDto>,
+ options: {
+ store: StoreType.SESSION,
+ compress: false,
+ }
+});
+
+export function clear_entries() {
+ entries.set([] as Array<IEntryDto>);
+}
+
+export async function hydrate_entry_cache_async() {
+ const get_entries_request = await get_all_entries_async();
+ if (get_entries_request.ok) {
+ const all_entries = await get_entries_request.json();
+ const stored_entries = get(entries);
+ entries.set(merge_obj_arr<IEntryDto>(all_entries, stored_entries, "id"));
+ }
+}
+
+export async function create_entry_async(request: ICreateEntryRequest): Promise<ICreateEntryResponse> {
+ const create_entry_request = await create_new_entry_async(request);
+ if (create_entry_request.ok) {
+ const new_entry = await create_entry_request.json();
+ const stored_entries = get(entries);
+ entries.set(merge_obj_arr<IEntryDto>([new_entry], stored_entries, "id"));
+ return {
+ ok: true,
+ };
+ } else {
+ const error = await create_entry_request.json();
+ return {
+ ok: false,
+ error: error
+ };
+ }
+}
+
+export async function edit_entry_async(entry: IEntryDto) {
+ if (!entry.id) return;
+}
+
+export async function delete_entry_async(entry: IEntryDto) {
+ if (!entry.id) return;
+}
+
+export default entries;
diff --git a/src/webapp/src/lib/stores/persistent-store.ts b/src/webapp/src/lib/stores/persistent-store.ts
new file mode 100644
index 0000000..6720bdc
--- /dev/null
+++ b/src/webapp/src/lib/stores/persistent-store.ts
@@ -0,0 +1,108 @@
+import {writable as _writable, readable as _readable,} from "svelte/store";
+import type {Writable, Readable, StartStopNotifier} from "svelte/store";
+import lzString from "lz-string";
+
+enum StoreType {
+ SESSION = 0,
+ LOCAL = 1
+}
+
+interface StoreOptions {
+ compress?: boolean,
+ store?: StoreType
+}
+
+const default_store_options = {
+ compress: false,
+ store: StoreType.SESSION
+} as StoreOptions;
+
+interface WritableStore<T> {
+ name: string,
+ initialState: T,
+ options?: StoreOptions
+}
+
+interface ReadableStore<T> {
+ name: string,
+ initialState: T,
+ callback: StartStopNotifier<any>,
+ options?: StoreOptions
+}
+
+function get_store(type: StoreType): Storage {
+ switch (type) {
+ case StoreType.SESSION:
+ return window.sessionStorage;
+ case StoreType.LOCAL:
+ return window.localStorage;
+ }
+}
+
+function prepared_store_value(value, compress): string {
+ try {
+ let returnValue = JSON.stringify(value);
+ if (compress) returnValue = lzString.compressToUTF16(returnValue);
+ return returnValue;
+ } catch (e) {
+ console.error(e);
+ return "__INVALID__";
+ }
+}
+
+function get_store_value<T>(options: WritableStore<T> | ReadableStore<T>): object | boolean {
+ try {
+ const storage = get_store(options.options.store);
+ let value = storage.getItem(options.name);
+ if (!value) return false;
+ if (options.options.compress) value = lzString.decompressFromUTF16(value);
+ return JSON.parse(value);
+ } catch (e) {
+ console.error(e);
+ return {__INVALID__: true};
+ }
+}
+
+function hydrate<T>(store, options: WritableStore<T> | ReadableStore<T>): void {
+ const value = get_store_value(options);
+ if (value && store.set) store.set(value);
+}
+
+function subscribe<T>(store, options: WritableStore<T> | ReadableStore<T>): void {
+ const storage = get_store(options.options.store);
+ if (!store.subscribe) return;
+ store.subscribe((state) => {
+ storage.setItem(options.name, prepared_store_value(state, options.options.compress));
+ });
+}
+
+function writable_persistent<T>(options: WritableStore<T>): Writable<T> {
+ if (options.options === undefined) options.options = default_store_options;
+ console.log("Creating writable store with options: ", options);
+ const store = _writable<T>(options.initialState);
+ hydrate(store, options);
+ subscribe(store, options);
+ return store;
+}
+
+function readable_persistent<T>(options: ReadableStore<T>): Readable<T> {
+ if (options.options === undefined) options.options = default_store_options;
+ console.log("Creating readable store with options: ", options);
+ const store = _readable<T>(options.initialState, options.callback);
+ hydrate(store, options);
+ subscribe(store, options);
+ return store;
+}
+
+export {
+ writable_persistent,
+ readable_persistent,
+ StoreType
+};
+
+export type {
+ WritableStore,
+ ReadableStore,
+ StoreOptions
+};
+
diff --git a/src/webapp/src/lib/stores/preferences.ts b/src/webapp/src/lib/stores/preferences.ts
new file mode 100644
index 0000000..e99f90d
--- /dev/null
+++ b/src/webapp/src/lib/stores/preferences.ts
@@ -0,0 +1,34 @@
+import {StoreType, writable_persistent} from "@/lib/stores/persistent-store";
+import {storage_keys} from "@/lib/configuration";
+import type {IPreferences} from "@/lib/models/IPreferences";
+import {ApplicationTheme} from "@/lib/enums/ApplicationTheme";
+import {get} from "svelte/store";
+
+const default_preferences = {
+ // remember to change default in index.html
+ theme: ApplicationTheme.LIGHT,
+ enable_site_report: true,
+} as IPreferences;
+
+const preferences = writable_persistent({
+ name: storage_keys.preferences,
+ options: {
+ store: StoreType.LOCAL,
+ compress: false
+ },
+ initialState: default_preferences
+});
+
+export function set_site_report_state(enabled: boolean) {
+ const prefs = get(preferences);
+ prefs.enable_site_report = enabled;
+ preferences.set(prefs);
+}
+
+export function set_theme(theme: ApplicationTheme) {
+ const prefs = get(preferences);
+ prefs.theme = theme;
+ preferences.set(prefs);
+}
+
+export default preferences;
diff --git a/src/webapp/src/lib/stores/session.ts b/src/webapp/src/lib/stores/session.ts
new file mode 100644
index 0000000..63441df
--- /dev/null
+++ b/src/webapp/src/lib/stores/session.ts
@@ -0,0 +1,89 @@
+import {get} from "svelte/store";
+import {SECONDS_BETWEEN_SESSION_CHECK, storage_keys} from "@/lib/configuration";
+import {get_profile_async, create_session_async, end_session_async} from "@/lib/api/account";
+import {writable_persistent, StoreType} from "@/lib/stores/persistent-store";
+import type {IProfileData} from "@/lib/models/IProfileData";
+import type {IErrorResult} from "@/lib/models/IErrorResult";
+import type {ICreateSessionRequest} from "@/lib/models/ICreateSessionRequest";
+
+const session = writable_persistent<IProfileData>({
+ name: storage_keys.profile_data,
+ initialState: {} as IProfileData,
+ options: {
+ store: StoreType.SESSION,
+ compress: false,
+ }
+});
+
+export async function start_session_async(payload: ICreateSessionRequest): Promise<StartSessionResponse> {
+ const login_request = await create_session_async(payload);
+ if (login_request.ok) {
+ const profile_data = await login_request.json();
+ session.set(profile_data);
+ return {
+ ok: true
+ };
+ } else {
+ const error = await login_request.json();
+ if (error) {
+ return {
+ ok: false,
+ error,
+ };
+ } else {
+ return {
+ ok: false,
+ error: {
+ title: "An unknown error occured, please try again later."
+ },
+ };
+ }
+ }
+}
+
+export interface StartSessionResponse {
+ ok: boolean,
+ error?: IErrorResult
+}
+
+export async function clear_session_async() {
+ session.set({} as IProfileData);
+}
+
+export async function session_is_valid_async(refresh = false) {
+ const hasId = get(session).id !== "";
+ const storage_value = localStorage.getItem(storage_keys.last_active_check);
+ if (!storage_value) {
+ localStorage.setItem(storage_keys.last_active_check, String(0));
+ }
+ const last_active_check = parseInt(storage_value);
+ const next_active_check = last_active_check + SECONDS_BETWEEN_SESSION_CHECK;
+ const epoch_seconds_now = Math.floor(Date.now() / 1000);
+ const force_server_check = next_active_check < epoch_seconds_now;
+ console.log("Checking session validity...");
+ if (!hasId || refresh || force_server_check) {
+ const profile_data_request = await get_profile_async();
+ localStorage.setItem(storage_keys.last_active_check, String(epoch_seconds_now));
+ console.log("Session is valid, because (!hasId || refresh || force_server_check)", {
+ last_active_check,
+ next_active_check,
+ epoch_seconds_now,
+ force_server_check,
+ refresh
+ });
+ if (profile_data_request.ok) {
+ const profile_data = await profile_data_request.json();
+ session.set(profile_data);
+ }
+ return profile_data_request.ok;
+ }
+
+ if (hasId) {
+ console.log("Session is valid, because (hasId)", {last_active_check, next_active_check, epoch_seconds_now, force_server_check, refresh, hasId});
+ return true;
+ }
+ console.log("Session is invalid, because of unknown reasons", {last_active_check, next_active_check, epoch_seconds_now, force_server_check, refresh, hasId});
+ return false;
+}
+
+export default session;
diff --git a/src/webapp/src/main.ts b/src/webapp/src/main.ts
new file mode 100644
index 0000000..f70b640
--- /dev/null
+++ b/src/webapp/src/main.ts
@@ -0,0 +1,9 @@
+import App from "./app.svelte";
+import "./styles/carbon.scss";
+import "./styles/global.scss";
+
+const app = new App({
+ target: document.getElementById("app")
+});
+
+export default app;
diff --git a/src/webapp/src/routes.ts b/src/webapp/src/routes.ts
new file mode 100644
index 0000000..0768469
--- /dev/null
+++ b/src/webapp/src/routes.ts
@@ -0,0 +1,10 @@
+import Login from "./routes/public/login.svelte";
+import Home from "./routes/app/home.svelte";
+import NotFound from "./routes/public/404.svelte";
+
+export default {
+ "/login": Login,
+ "/": Login,
+ "/app": Home,
+ "*": NotFound
+};
diff --git a/src/webapp/src/routes/app/_header.svelte b/src/webapp/src/routes/app/_header.svelte
new file mode 100644
index 0000000..4b2ddb7
--- /dev/null
+++ b/src/webapp/src/routes/app/_header.svelte
@@ -0,0 +1,113 @@
+<script lang="ts">
+ import session, {clear_session_async} from "@/lib/stores/session";
+ import preferences, {set_site_report_state, set_theme} from "@/lib/stores/preferences";
+ import {replace} from "svelte-spa-router";
+ import HeaderPanelToggle from "@/components/carbon-extras/HeaderPanelToggle.svelte";
+ import SettingsAdjust20 from "carbon-icons-svelte/lib/SettingsAdjust20";
+ import UserAvatarFilledAlt20 from "carbon-icons-svelte/lib/UserAvatarFilledAlt20";
+ import {ApplicationTheme} from "@/lib/enums/ApplicationTheme";
+ import {get} from "svelte/store";
+ import {Header, HeaderAction, HeaderPanelDivider, HeaderPanelLink, HeaderPanelLinks, HeaderUtilities, Tooltip,} from "carbon-components-svelte";
+ import {api_base} from "@/lib/configuration";
+ import Help16 from "carbon-icons-svelte/lib/Help16";
+ import {end_session_async} from "@/lib/api/account";
+ import {clear_entries} from "@/lib/stores/entries";
+
+ let profile_dropdown_is_open = false;
+ let options_dropdown_is_open = false;
+ let enable_dark_theme = get(preferences).theme === ApplicationTheme.DARK;
+ let enable_site_report = get(preferences).enable_site_report;
+
+ preferences.subscribe(e => {
+ enable_site_report = e.enable_site_report;
+ enable_dark_theme = e.theme === ApplicationTheme.DARK;
+ });
+
+ function start_file_import() {
+ alert("Not implemented");
+ }
+
+ function download_data() {
+ const anchor = document.createElement("a");
+ anchor.href = api_base("account/archive");
+ anchor.click();
+ anchor.remove();
+ }
+
+ function manage_profile() {
+ alert("Not implemented");
+ }
+
+ function connect_github() {
+ alert("Not implemented");
+ }
+
+ function manage_access_tokens() {
+ alert("Not implemented");
+ }
+
+ function toggle_site_report() {
+ set_site_report_state(!enable_site_report);
+ }
+
+ function toggle_dark_theme() {
+ set_theme(enable_dark_theme ? ApplicationTheme.LIGHT : ApplicationTheme.DARK);
+ }
+
+ async function handle_logout() {
+ await clear_session_async();
+ await end_session_async();
+ clear_entries();
+ await replace("/login");
+ }
+</script>
+
+<Header company="IOL"
+ platformName="Bookmark Thing">
+ <HeaderUtilities>
+ <HeaderAction aria-label="Settings"
+ bind:isOpen={options_dropdown_is_open}
+ icon={SettingsAdjust20}
+ closeIcon={SettingsAdjust20}
+ transition={false}>
+ <HeaderPanelLinks>
+ <HeaderPanelDivider>Actions</HeaderPanelDivider>
+ <HeaderPanelLink on:click={start_file_import}>Import data from file</HeaderPanelLink>
+ <HeaderPanelLink on:click={connect_github}>Connect github</HeaderPanelLink>
+ <HeaderPanelDivider>Options</HeaderPanelDivider>
+ <HeaderPanelToggle size="sm"
+ on:toggle={toggle_site_report}
+ toggled="{enable_site_report}">
+ <span>Enable site report</span>
+ <Tooltip tooltipBodyId="tooltip-body"
+ icon={Help16}>
+ <div id="tooltip-body">
+ A service that runs and provides insights when you write a FQDN inside of the entry form.
+ 1.Checks if the url is alive
+ 2.Checks if the url is already in your database
+ 3.Autopopulates the entry description based on the sites own description
+ </div>
+ </Tooltip>
+ </HeaderPanelToggle>
+ <HeaderPanelToggle size="sm"
+ on:toggle={toggle_dark_theme}
+ toggled="{enable_dark_theme}">
+ Use dark theme
+ </HeaderPanelToggle>
+ </HeaderPanelLinks>
+ </HeaderAction>
+ <HeaderAction aria-label="Profile"
+ bind:isOpen={profile_dropdown_is_open}
+ icon={UserAvatarFilledAlt20}
+ closeIcon={UserAvatarFilledAlt20}
+ transition={false}>
+ <HeaderPanelLinks>
+ <HeaderPanelDivider>Logged in as {$session?.username}</HeaderPanelDivider>
+ <HeaderPanelLink on:click={manage_profile}>Manage profile</HeaderPanelLink>
+ <HeaderPanelLink on:click={manage_access_tokens}>Manage access tokens</HeaderPanelLink>
+ <HeaderPanelLink on:click={download_data}>Download data</HeaderPanelLink>
+ <HeaderPanelLink on:click={handle_logout}>Log out</HeaderPanelLink>
+ </HeaderPanelLinks>
+ </HeaderAction>
+ </HeaderUtilities>
+</Header>
diff --git a/src/webapp/src/routes/app/home.svelte b/src/webapp/src/routes/app/home.svelte
new file mode 100644
index 0000000..5b0b141
--- /dev/null
+++ b/src/webapp/src/routes/app/home.svelte
@@ -0,0 +1,26 @@
+<script lang="ts">
+ import EntryForm from "@/components/forms/entry-form.svelte";
+ import EntryList from "@/components/entry-list.svelte";
+ import Header from "./_header.svelte";
+ import {
+ Column,
+ Content,
+ Grid,
+ Row
+ } from "carbon-components-svelte";
+</script>
+
+<Header/>
+
+<Content>
+ <Grid>
+ <Row>
+ <Column>
+ <EntryForm/>
+ </Column>
+ <Column>
+ <EntryList/>
+ </Column>
+ </Row>
+ </Grid>
+</Content>
diff --git a/src/webapp/src/routes/app/modals/access-tokens-modal.svelte b/src/webapp/src/routes/app/modals/access-tokens-modal.svelte
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/webapp/src/routes/app/modals/access-tokens-modal.svelte
diff --git a/src/webapp/src/routes/app/modals/file-import-modal.svelte b/src/webapp/src/routes/app/modals/file-import-modal.svelte
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/webapp/src/routes/app/modals/file-import-modal.svelte
diff --git a/src/webapp/src/routes/app/modals/github-modal.svelte b/src/webapp/src/routes/app/modals/github-modal.svelte
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/webapp/src/routes/app/modals/github-modal.svelte
diff --git a/src/webapp/src/routes/app/modals/profile-modal.svelte b/src/webapp/src/routes/app/modals/profile-modal.svelte
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/webapp/src/routes/app/modals/profile-modal.svelte
diff --git a/src/webapp/src/routes/public/404.svelte b/src/webapp/src/routes/public/404.svelte
new file mode 100644
index 0000000..1445688
--- /dev/null
+++ b/src/webapp/src/routes/public/404.svelte
@@ -0,0 +1,5 @@
+<script lang="ts">
+ import {pop} from "svelte-spa-router";
+</script>
+<h1>404</h1>
+<button on:click={() => pop()}>Go back</button>
diff --git a/src/webapp/src/routes/public/login.svelte b/src/webapp/src/routes/public/login.svelte
new file mode 100644
index 0000000..c0d65a6
--- /dev/null
+++ b/src/webapp/src/routes/public/login.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+ import {replace} from "svelte-spa-router";
+ import LoginForm from "@/components/forms/login-form.svelte";
+ import {Column, Content, Grid, Row} from "carbon-components-svelte";
+
+ async function handle_authenticated() {
+ await replace("/app");
+ }
+</script>
+
+<Content>
+ <Grid noGutter={true}>
+ <Row>
+ <Column sm={12}
+ md={6}
+ lg={6}>
+ <LoginForm on:authenticated={handle_authenticated}/>
+ </Column>
+ </Row>
+ </Grid>
+</Content>
diff --git a/src/webapp/src/styles/carbon.scss b/src/webapp/src/styles/carbon.scss
new file mode 100644
index 0000000..201c0e8
--- /dev/null
+++ b/src/webapp/src/styles/carbon.scss
@@ -0,0 +1,49 @@
+// This is a recipe for dynamic, client-side theming
+
+$feature-flags: (
+ // Custom CSS properties must be enabled to dynamically switch themes
+ enable-css-custom-properties: true,
+ ui-shell: true,
+ grid-columns-16: true
+);
+
+$css--font-face: false;
+$css--helpers: true;
+$css--body: true;
+$css--use-layer: true;
+$css--reset: true;
+$css--default-type: true;
+$css--plex: true;
+
+// Use all Carbon themes
+@import "../node_modules/carbon-components/scss/globals/scss/vendor/@carbon/themes/scss";
+@import "../node_modules/carbon-components/scss/globals/scss/component-tokens";
+@import "../node_modules/carbon-components/src/components/tag/tag";
+@import "../node_modules/carbon-components/src/components/notification/inline-notification";
+@import "../node_modules/carbon-components/src/components/notification/toast-notification";
+
+// The default theme is "white" (White)
+:root {
+ @include carbon--theme($carbon--theme--white, true) {
+ @include emit-component-tokens($tag-colors);
+ @include emit-component-tokens($notification-colors);
+ }
+}
+
+// Set the <html> theme attribute to "g100" to use the Gray 100 theme
+// <html theme="g100">
+:root[theme="g100"] {
+ @include carbon--theme($carbon--theme--g100, true) {
+ @include emit-component-tokens($tag-colors);
+ @include emit-component-tokens($notification-colors);
+ }
+}
+
+@import "../node_modules/carbon-components/scss/globals/scss/_css--reset";
+@import "../node_modules/carbon-components/scss/globals/scss/_css--font-face";
+@import "../node_modules/carbon-components/scss/globals/scss/_css--helpers";
+@import "../node_modules/carbon-components/scss/globals/scss/_css--body";
+@import "../node_modules/carbon-components/scss/globals/grid/grid";
+
+// Import all component styles
+@import "../node_modules/carbon-components/scss/globals/scss/styles";
diff --git a/src/webapp/src/styles/global.scss b/src/webapp/src/styles/global.scss
new file mode 100644
index 0000000..49d45a7
--- /dev/null
+++ b/src/webapp/src/styles/global.scss
@@ -0,0 +1,5 @@
+textarea {
+ min-height: 39px !important;
+ resize: vertical;
+}
+
diff --git a/src/webapp/src/vite-env.d.ts b/src/webapp/src/vite-env.d.ts
new file mode 100644
index 0000000..33a0646
--- /dev/null
+++ b/src/webapp/src/vite-env.d.ts
@@ -0,0 +1,3 @@
+/// <reference types="svelte" />
+/// <reference types="vite/client" />
+/// <reference types="validator" />
diff --git a/src/webapp/svelte.config.js b/src/webapp/svelte.config.js
new file mode 100644
index 0000000..5680f19
--- /dev/null
+++ b/src/webapp/svelte.config.js
@@ -0,0 +1,6 @@
+import sveltePreprocess from "svelte-preprocess";
+import {optimizeImports} from "carbon-preprocess-svelte";
+
+export default {
+ preprocess: [sveltePreprocess(), optimizeImports],
+};
diff --git a/src/webapp/tsconfig.json b/src/webapp/tsconfig.json
new file mode 100644
index 0000000..c143677
--- /dev/null
+++ b/src/webapp/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "extends": "@tsconfig/svelte/tsconfig.json",
+ "compilerOptions": {
+ "target": "esnext",
+ "useDefineForClassFields": true,
+ "module": "esnext",
+ "resolveJsonModule": true,
+ "baseUrl": ".",
+ "allowJs": true,
+ "checkJs": true,
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ }
+ },
+ "include": [
+ "src/**/*.d.ts",
+ "src/**/*.ts",
+ "src/**/*.js",
+ "src/**/*.svelte"
+ ]
+}
diff --git a/src/webapp/vite.config.js b/src/webapp/vite.config.js
new file mode 100644
index 0000000..8cbaae5
--- /dev/null
+++ b/src/webapp/vite.config.js
@@ -0,0 +1,14 @@
+import {defineConfig} from "vite";
+import {svelte} from "@sveltejs/vite-plugin-svelte";
+import path from "path";
+import {optimizeCss} from "carbon-preprocess-svelte";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [svelte(), process.env.NODE_ENV === "production" && optimizeCss()],
+ resolve: {
+ alias: {
+ "@": path.resolve("/src"),
+ },
+ },
+});