aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-04-09 17:35:13 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-04-09 17:35:13 -0400
commit56e0a38b2ca246e8beeaef3c6c4b9c0ce7d0f09b (patch)
tree5ba2556e42510cbbdf9c287f67041c1b1eb46206
parent0945210c0492dd8a8de99ccd8e5e66cf05e3a1c1 (diff)
chore(app): Update deps, login spinner, curl msg, view prep
-rw-r--r--back-end/Taskfile.yaml4
-rw-r--r--back-end/src/Cache/UserSettings.cs35
-rw-r--r--back-end/src/Cache/UserSettingsStore.cs48
-rw-r--r--back-end/src/Endpoints/BookmarkEndpoint.cs26
-rw-r--r--back-end/src/Model/BookmarkStore.cs38
-rw-r--r--back-end/src/Model/SimpleBookmarkContext.cs54
-rw-r--r--back-end/src/Model/UserSettingsDbStore.cs75
-rw-r--r--back-end/src/Model/UserSettingsEntry.cs39
-rw-r--r--back-end/src/SimpleBookmark.csproj8
-rw-r--r--ci/plugins.taskfile.yaml6
-rw-r--r--front-end/package-lock.json248
-rw-r--r--front-end/package.json4
-rw-r--r--front-end/src/App.vue4
-rw-r--r--front-end/src/components/Bookmarks.vue77
-rw-r--r--front-end/src/components/Boomarks/AddOrUpdateForm.vue53
-rw-r--r--front-end/src/components/Boomarks/BookmarkList.vue157
-rw-r--r--front-end/src/components/Login/AdminReg.vue16
-rw-r--r--front-end/src/components/Login/PkiLogin.vue39
-rw-r--r--front-end/src/components/Login/UserPass.vue13
-rw-r--r--front-end/src/components/global/Dialog.vue2
-rw-r--r--front-end/src/index.scss2
-rw-r--r--front-end/src/main.ts2
-rw-r--r--front-end/src/store/socialMfaPlugin.ts1
23 files changed, 458 insertions, 493 deletions
diff --git a/back-end/Taskfile.yaml b/back-end/Taskfile.yaml
index 92448fe..a3791d3 100644
--- a/back-end/Taskfile.yaml
+++ b/back-end/Taskfile.yaml
@@ -29,14 +29,14 @@ tasks:
- powershell -Command "cp '{{.MODULE_DIR}}/LICENSE' -Destination '{{.OUT_DIR}}/LICENSE'"
#tar the plugin output and put it in the bin dir
- - cd {{.OUT_DIR}} && tar -czvf '{{.USER_WORKING_DIR}}/bin/release.tgz' .
+ - cd {{.OUT_DIR}} && tar -czf '{{.USER_WORKING_DIR}}/bin/release.tgz' .
packsource:
dir: '{{.USER_WORKING_DIR}}'
internal: true
cmds:
#copy source code to target
- - powershell -Command "Get-ChildItem -Include *.cs,*.csproj -Recurse | Where { \$_.FullName -notlike '*\obj\*' -and \$_.FullName -notlike '*\bin\*' } | Resolve-Path -Relative | tar --files-from - -cvzf 'bin/src.tgz'"
+ - powershell -Command "Get-ChildItem -Include *.cs,*.csproj -Recurse | Where { \$_.FullName -notlike '*\obj\*' -and \$_.FullName -notlike '*\bin\*' } | Resolve-Path -Relative | tar --files-from - -czf 'bin/src.tgz'"
#clean hook
clean:
diff --git a/back-end/src/Cache/UserSettings.cs b/back-end/src/Cache/UserSettings.cs
deleted file mode 100644
index b656f83..0000000
--- a/back-end/src/Cache/UserSettings.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2024 Vaughn Nugent
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-
-using System.Text.Json.Serialization;
-
-using MemoryPack;
-
-namespace SimpleBookmark.Cache
-{
- [MemoryPackable]
- internal sealed partial class UserSettings
- {
- [JsonPropertyName("limit")]
- public uint PreferredLimit { get; set; } = 10;
-
- [JsonPropertyName("new_tab")]
- public bool OpenInNewTab { get; set; } = true;
-
- [JsonPropertyName("dark_mode")]
- public bool DarkMode { get; set; } = false;
- }
-}
diff --git a/back-end/src/Cache/UserSettingsStore.cs b/back-end/src/Cache/UserSettingsStore.cs
deleted file mode 100644
index 8887973..0000000
--- a/back-end/src/Cache/UserSettingsStore.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2024 Vaughn Nugent
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-
-using VNLib.Plugins;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Data.Caching;
-using VNLib.Plugins.Extensions.VNCache;
-using VNLib.Plugins.Extensions.VNCache.DataModel;
-
-namespace SimpleBookmark.Cache
-{
-
- [ConfigurationName("settings")]
- internal sealed class UserSettingsStore
- {
- private readonly IEntityCache<UserSettings>? Cache;
-
- public UserSettingsStore(PluginBase plugin, IConfigScope config)
- {
- //try to get the global cache provider
- IGlobalCacheProvider? cache = plugin.GetDefaultGlobalCache();
- if (cache != null)
- {
- MemPackCacheSerializer serializer = new(null);
-
- //Recover the cache prefix
- string prefix = config.GetRequiredProperty("cache_prefix", p => p.GetString()!);
-
- //Create a prefixed cache, then create an entity cache for the user settings
- Cache = cache.GetPrefixedCache(prefix)
- .CreateEntityCache<UserSettings>(serializer, serializer);
- }
- }
- }
-}
diff --git a/back-end/src/Endpoints/BookmarkEndpoint.cs b/back-end/src/Endpoints/BookmarkEndpoint.cs
index 001a41b..19f5118 100644
--- a/back-end/src/Endpoints/BookmarkEndpoint.cs
+++ b/back-end/src/Endpoints/BookmarkEndpoint.cs
@@ -243,20 +243,26 @@ namespace SimpleBookmark.Endpoints
return VirtualClose(entity, webm, HttpStatusCode.UnprocessableEntity);
}
- //See if the uses has reached their quota
- long count = await Bookmarks.GetUserRecordCountAsync(entity.Session.UserID, entity.EventCancellation);
+ /*
+ * Add the new entry to the database if the user is below their quota
+ * and the entry does not already exist by the desired url.
+ */
+ int result = await Bookmarks.AddSingleIfNotExists(
+ entity.Session.UserID,
+ newBookmark,
+ entity.RequestedTimeUtc.DateTime,
+ BmConfig.PerPersonQuota,
+ entity.EventCancellation
+ );
- if(webm.Assert(count <= BmConfig.PerPersonQuota, "You have reached your bookmark quota"))
+ if (webm.Assert(result > -1, "You have reached your bookmark quota"))
{
- return VirtualClose(entity, webm, HttpStatusCode.OK);
- }
-
- //Try to create the record
- ERRNO result = await Bookmarks.CreateUserRecordAsync(newBookmark, entity.Session.UserID, entity.EventCancellation);
+ return VirtualOk(entity, webm);
+ }
- if (webm.Assert(result > 0, "Failed to create new bookmark"))
+ if (webm.Assert(result > 0, "Bookmark with the same url alreay exists"))
{
- return VirtualClose(entity, webm, HttpStatusCode.OK);
+ return VirtualOk(entity, webm);
}
webm.Result = "Successfully created bookmark";
diff --git a/back-end/src/Model/BookmarkStore.cs b/back-end/src/Model/BookmarkStore.cs
index ec020e8..d53ab01 100644
--- a/back-end/src/Model/BookmarkStore.cs
+++ b/back-end/src/Model/BookmarkStore.cs
@@ -52,6 +52,44 @@ namespace SimpleBookmark.Model
existing.Description = newRecord.Description;
existing.JsonTags = newRecord.JsonTags;
}
+
+ public async Task<int> AddSingleIfNotExists(string userId, BookmarkEntry entry, DateTime now, uint maxRecords, CancellationToken cancellation)
+ {
+ ArgumentNullException.ThrowIfNull(userId);
+ ArgumentNullException.ThrowIfNull(entry);
+
+ //Init new db connection
+ await using SimpleBookmarkContext context = new(dbOptions.Value);
+
+ //Check if any bookmarks exist for the user with a given url
+ bool exists = await context.Bookmarks.AnyAsync(b => b.UserId == userId && b.Url == entry.Url, cancellation);
+
+ //If no bookmarks exist, add a new one
+ if (!exists)
+ {
+ //Check if the user has reached the maximum number of bookmarks
+ if (await context.Bookmarks.CountAsync(b => b.UserId == userId, cancellation) >= maxRecords)
+ {
+ await context.SaveAndCloseAsync(true, cancellation);
+ return -1;
+ }
+
+ context.Bookmarks.Add(new ()
+ {
+ Id = GetNewRecordId(), //Overwrite with new record id
+ UserId = userId, //Enforce user id
+ Created = now,
+ LastModified = now,
+ Name = entry.Name, //Copy over the entry data
+ Url = entry.Url,
+ Description = entry.Description,
+ Tags = entry.Tags
+ });
+ }
+
+ await context.SaveAndCloseAsync(true, cancellation);
+ return exists ? 0 : 1; //1 if added, 0 if already exists
+ }
public async Task<BookmarkEntry[]> SearchBookmarksAsync(string userId, string? query, string[] tags, int limit, int page, CancellationToken cancellation)
{
diff --git a/back-end/src/Model/SimpleBookmarkContext.cs b/back-end/src/Model/SimpleBookmarkContext.cs
index 25343d9..f0e53b1 100644
--- a/back-end/src/Model/SimpleBookmarkContext.cs
+++ b/back-end/src/Model/SimpleBookmarkContext.cs
@@ -27,8 +27,6 @@ namespace SimpleBookmark.Model
public DbSet<BookmarkEntry> Bookmarks { get; set; }
- public DbSet<UserSettingsEntry> SbSettings { get; set; }
-
public SimpleBookmarkContext(DbContextOptions options) : base(options)
{ }
@@ -37,43 +35,21 @@ namespace SimpleBookmark.Model
public void OnDatabaseCreating(IDbContextBuilder builder, object? userState)
{
- builder.DefineTable<BookmarkEntry>(nameof(Bookmarks))
- .WithColumn(p => p.Id)
- .SetIsKey()
- .Next()
-
- .WithColumn(p => p.Created)
- .AllowNull(false)
- .Next()
-
- .WithColumn(p => p.LastModified)
- .AllowNull(false)
- .Next()
-
- .WithColumn(p => p.UserId)
- .AllowNull(false)
- .Next()
-
- .WithColumn(p => p.Name)
- .AllowNull(true)
- .Next()
-
- .WithColumn(p => p.Version)
- .TimeStamp()
- .AllowNull(true)
- .Next()
-
- .WithColumn(p => p.Url)
- .AllowNull(true)
- .Next()
-
- .WithColumn(p => p.Description)
- .AllowNull(true)
- .Next()
-
- .WithColumn(p => p.Tags)
- .AllowNull(true)
- .Next();
+ /*
+ * Define the coloumn mappings for the BookmarkEntry table
+ */
+ builder.DefineTable<BookmarkEntry>(nameof(Bookmarks), table =>
+ {
+ table.WithColumn(p => p.Id).AllowNull(false);
+ table.WithColumn(p => p.Created);
+ table.WithColumn(p => p.LastModified);
+ table.WithColumn(p => p.UserId).AllowNull(false);
+ table.WithColumn(p => p.Name);
+ table.WithColumn(p => p.Version);
+ table.WithColumn(p => p.Url);
+ table.WithColumn(p => p.Description);
+ table.WithColumn(p => p.Tags);
+ });
}
}
diff --git a/back-end/src/Model/UserSettingsDbStore.cs b/back-end/src/Model/UserSettingsDbStore.cs
deleted file mode 100644
index d392262..0000000
--- a/back-end/src/Model/UserSettingsDbStore.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (C) 2024 Vaughn Nugent
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Microsoft.EntityFrameworkCore;
-
-using VNLib.Utils;
-using VNLib.Plugins.Extensions.Loading;
-
-namespace SimpleBookmark.Model
-{
- internal sealed class UserSettingsDbStore(IAsyncLazy<DbContextOptions> dbOptions)
- {
-
- public async Task<UserSettingsEntry?> GetSettingsForUserAsync(string userId, CancellationToken cancellation)
- {
- ArgumentNullException.ThrowIfNull(userId);
-
- //Init new db connection
- await using SimpleBookmarkContext context = new(dbOptions.Value);
-
- UserSettingsEntry? settings = await context.SbSettings.FirstOrDefaultAsync(p => p.UserId == userId, cancellation);
-
- //Close db and commit transaction
- await context.SaveAndCloseAsync(true, cancellation);
-
- return settings;
- }
-
- public async Task<ERRNO> SetSettingsForUser(string userId, UserSettingsEntry settings, CancellationToken cancellation)
- {
- ArgumentNullException.ThrowIfNull(userId);
- ArgumentNullException.ThrowIfNull(settings);
-
- //Init new db connection
- await using SimpleBookmarkContext context = new(dbOptions.Value);
-
- //Search for existing settings entry
- UserSettingsEntry? existing = await context.SbSettings.FirstOrDefaultAsync(p => p.UserId == userId, cancellation);
-
- if (existing is null)
- {
- //Add a new entry
- settings.UserId = userId;
- settings.LastModified = DateTime.UtcNow;
- context.Add(settings);
- }
- else
- {
- //Update existing entry
- existing.SettingsData = settings.SettingsData;
- existing.LastModified = DateTime.UtcNow;
- context.Update(existing);
- }
-
- //Close db and commit transaction
- return await context.SaveAndCloseAsync(true, cancellation);
- }
- }
-}
diff --git a/back-end/src/Model/UserSettingsEntry.cs b/back-end/src/Model/UserSettingsEntry.cs
deleted file mode 100644
index c27af8a..0000000
--- a/back-end/src/Model/UserSettingsEntry.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2024 Vaughn Nugent
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-using System;
-using System.Text.Json.Serialization;
-using System.ComponentModel.DataAnnotations;
-
-using VNLib.Plugins.Extensions.Data.Abstractions;
-
-namespace SimpleBookmark.Model
-{
- internal sealed class UserSettingsEntry : IUserEntity
- {
- public DateTime LastModified { get; set; }
-
- [Timestamp]
- [JsonIgnore]
- public byte[]? Version { get; set; }
-
- [Key]
- [JsonIgnore]
- public string? UserId { get; set; }
-
- [MaxLength(5000)]
- public byte[]? SettingsData { get; set; }
- }
-}
diff --git a/back-end/src/SimpleBookmark.csproj b/back-end/src/SimpleBookmark.csproj
index 190dc44..d0235a2 100644
--- a/back-end/src/SimpleBookmark.csproj
+++ b/back-end/src/SimpleBookmark.csproj
@@ -34,10 +34,10 @@
<ItemGroup>
<PackageReference Include="MemoryPack" Version="1.21.0" />
- <PackageReference Include="VNLib.Plugins.Extensions.Data" Version="0.1.0-ci0053" />
- <PackageReference Include="VNLib.Plugins.Extensions.Loading" Version="0.1.0-ci0053" />
- <PackageReference Include="VNLib.Plugins.Extensions.Loading.Sql" Version="0.1.0-ci0053" />
- <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0053" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Data" Version="0.1.0-ci0061" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Loading" Version="0.1.0-ci0061" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Loading.Sql" Version="0.1.0-ci0061" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0061" />
<PackageReference Include="VNLib.Plugins.Extensions.VNCache" Version="0.1.0-ci0054" />
</ItemGroup>
diff --git a/ci/plugins.taskfile.yaml b/ci/plugins.taskfile.yaml
index 385890a..dfc22b2 100644
--- a/ci/plugins.taskfile.yaml
+++ b/ci/plugins.taskfile.yaml
@@ -11,11 +11,11 @@ includes:
vars:
CORE_VERSION: 'e07537a3dde8e16100ef1bcc2a54f9ade8ae856f'
- ESSENTIALS_VERSION: '27b487b6d0befdb2197a58ceadb1f1ac2b337786'
+ ESSENTIALS_VERSION: 'a7cf7c8987b8847984629293d8eb27908f3de3dd'
CACHE_VERSION: '49c3641def5ae1b7557ed61ed7bb28bbf425ccc9'
- USERS_VERSION: '884ed18f900b59be30a6e51c2ec7b714ac860bfd'
+ USERS_VERSION: 'cbe49da211039957b431b307a0cb2c3a20bd2c10'
SESSION_VERSION: '9c8da6ea8fabe1d752bb28fd5eaeeb0b1d06d94d'
- EXTENSIONS_VERSION: '6da9d3b34fb0dd61cf8a81290e573e54851fcd07'
+ EXTENSIONS_VERSION: '40c634b0f37ce9922dbc32c86e26d5a771daeca3'
tasks:
diff --git a/front-end/package-lock.json b/front-end/package-lock.json
index ebc2f59..64ffa7b 100644
--- a/front-end/package-lock.json
+++ b/front-end/package-lock.json
@@ -10,7 +10,7 @@
"license": "agpl3",
"dependencies": {
"@headlessui/vue": "^1.7.17",
- "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/27b487b6d0befdb2197a58ceadb1f1ac2b337786/@vnuge-vnlib.browser/release.tgz",
+ "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/a7cf7c8987b8847984629293d8eb27908f3de3dd/@vnuge-vnlib.browser/release.tgz",
"@vuelidate/core": "^2.0.2",
"@vuelidate/validators": "^2.0.2",
"@vueuse/core": "^10.3.x",
@@ -37,7 +37,7 @@
"typescript": "^5.0.2",
"vite": "^5.0.x",
"vue-eslint-parser": "^9.3.0",
- "vue-tsc": "^1.4.2"
+ "vue-tsc": "^2.0.x"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -62,9 +62,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
- "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==",
+ "version": "7.24.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
+ "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -538,9 +538,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
- "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"peer": true
},
"node_modules/@isaacs/cliui": {
@@ -686,9 +686,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz",
- "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz",
+ "integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==",
"cpu": [
"arm"
],
@@ -699,9 +699,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz",
- "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz",
+ "integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==",
"cpu": [
"arm64"
],
@@ -712,9 +712,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz",
- "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz",
+ "integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==",
"cpu": [
"arm64"
],
@@ -725,9 +725,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz",
- "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz",
+ "integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==",
"cpu": [
"x64"
],
@@ -738,9 +738,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz",
- "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz",
+ "integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==",
"cpu": [
"arm"
],
@@ -751,9 +751,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz",
- "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz",
+ "integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==",
"cpu": [
"arm64"
],
@@ -764,9 +764,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz",
- "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz",
+ "integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==",
"cpu": [
"arm64"
],
@@ -777,9 +777,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz",
- "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz",
+ "integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==",
"cpu": [
"ppc64le"
],
@@ -790,9 +790,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz",
- "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz",
+ "integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==",
"cpu": [
"riscv64"
],
@@ -803,9 +803,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz",
- "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz",
+ "integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==",
"cpu": [
"s390x"
],
@@ -816,9 +816,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz",
- "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz",
+ "integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==",
"cpu": [
"x64"
],
@@ -829,9 +829,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz",
- "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz",
+ "integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==",
"cpu": [
"x64"
],
@@ -842,9 +842,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz",
- "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz",
+ "integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==",
"cpu": [
"arm64"
],
@@ -855,9 +855,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz",
- "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz",
+ "integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==",
"cpu": [
"ia32"
],
@@ -868,9 +868,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz",
- "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz",
+ "integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==",
"cpu": [
"x64"
],
@@ -956,8 +956,8 @@
},
"node_modules/@vnuge/vnlib.browser": {
"version": "0.1.13",
- "resolved": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/27b487b6d0befdb2197a58ceadb1f1ac2b337786/@vnuge-vnlib.browser/release.tgz",
- "integrity": "sha512-4vhBVC9vro4wwtnsNCCno0LLY5ipS2JLe4em2Ucn+BzSCzxTb75JoTWzeMLjWHxb5io6fq9LV676WU6t5nnF0Q==",
+ "resolved": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/a7cf7c8987b8847984629293d8eb27908f3de3dd/@vnuge-vnlib.browser/release.tgz",
+ "integrity": "sha512-o8jj4LlMUFc9Z082+7toRjFZB9HhGTuRlzvzSZfutAtmuWbljjEiGdl53s5MUVzshwEFjo+Kj+ehHJnPK7niIw==",
"license": "MIT",
"peerDependencies": {
"@vueuse/core": "^10.x",
@@ -970,30 +970,30 @@
}
},
"node_modules/@volar/language-core": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz",
- "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==",
+ "version": "2.2.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.0-alpha.6.tgz",
+ "integrity": "sha512-GmT28LX2w4x82uuQqNN/P94VOCsZRHBbGcGe+5bFtA2hbIbH6f8tFdMfgXFtyhbft/pj6f3xl37xe+t+nomLIA==",
"dev": true,
"dependencies": {
- "@volar/source-map": "1.11.1"
+ "@volar/source-map": "2.2.0-alpha.6"
}
},
"node_modules/@volar/source-map": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz",
- "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==",
+ "version": "2.2.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.0-alpha.6.tgz",
+ "integrity": "sha512-EztD2zoUopETY+ZCUZAGUHKgj4gOkY/2WnaOS+RSTc56xm85miSA4qOBS8Lt1Ruu5vV52WIZKHW/R9PbjkZWFA==",
"dev": true,
"dependencies": {
- "muggle-string": "^0.3.1"
+ "muggle-string": "^0.4.0"
}
},
"node_modules/@volar/typescript": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz",
- "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==",
+ "version": "2.2.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.0-alpha.6.tgz",
+ "integrity": "sha512-wTr0jO3wVXQ9FjBbWE2iX8GgDoiHp1Nttsb+tKk5IeUUb6f1uOjyeIXuS4KfeMBpCufthRO2st2O2uatAs/UXQ==",
"dev": true,
"dependencies": {
- "@volar/language-core": "1.11.1",
+ "@volar/language-core": "2.2.0-alpha.6",
"path-browserify": "^1.0.1"
}
},
@@ -1049,18 +1049,16 @@
"integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
},
"node_modules/@vue/language-core": {
- "version": "1.8.27",
- "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz",
- "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==",
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.11.tgz",
+ "integrity": "sha512-5ivg8Vem/yckzXI3L3n0mdKBPRcHSlsGt6/dpbEx42PcH3MIHAjSAJBYvENXeWJxv2ClQc8BS2mH1Ho2U7jZig==",
"dev": true,
"dependencies": {
- "@volar/language-core": "~1.11.1",
- "@volar/source-map": "~1.11.1",
- "@vue/compiler-dom": "^3.3.0",
- "@vue/shared": "^3.3.0",
+ "@volar/language-core": "~2.2.0-alpha.6",
+ "@vue/compiler-dom": "^3.4.0",
+ "@vue/shared": "^3.4.0",
"computeds": "^0.0.1",
"minimatch": "^9.0.3",
- "muggle-string": "^0.3.1",
"path-browserify": "^1.0.1",
"vue-template-compiler": "^2.7.14"
},
@@ -1549,9 +1547,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001603",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001603.tgz",
- "integrity": "sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q==",
+ "version": "1.0.30001607",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz",
+ "integrity": "sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w==",
"dev": true,
"funding": [
{
@@ -1785,9 +1783,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
- "version": "1.4.722",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.722.tgz",
- "integrity": "sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ==",
+ "version": "1.4.730",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.730.tgz",
+ "integrity": "sha512-oJRPo82XEqtQAobHpJIR3zW5YO3sSRRkPz2an4yxi1UvqhsGm54vR/wzTFV74a3soDOJ8CKW7ajOOX5ESzddwg==",
"dev": true
},
"node_modules/emoji-regex": {
@@ -2461,9 +2459,9 @@
}
},
"node_modules/jose": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz",
- "integrity": "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==",
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.4.tgz",
+ "integrity": "sha512-6ScbIk2WWCeXkmzF6bRPmEuaqy1m8SbsRFMa/FLrSCkGIhj8OLVG/IH+XHVmNMx/KUo8cVWEE6oKR4dJ+S0Rkg==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@@ -2588,9 +2586,9 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.8",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
- "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
+ "version": "0.30.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz",
+ "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@@ -2674,9 +2672,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/muggle-string": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz",
- "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
"dev": true
},
"node_modules/mz": {
@@ -3252,9 +3250,9 @@
}
},
"node_modules/rollup": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz",
- "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==",
+ "version": "4.14.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz",
+ "integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -3267,21 +3265,21 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.13.2",
- "@rollup/rollup-android-arm64": "4.13.2",
- "@rollup/rollup-darwin-arm64": "4.13.2",
- "@rollup/rollup-darwin-x64": "4.13.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.13.2",
- "@rollup/rollup-linux-arm64-gnu": "4.13.2",
- "@rollup/rollup-linux-arm64-musl": "4.13.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.13.2",
- "@rollup/rollup-linux-s390x-gnu": "4.13.2",
- "@rollup/rollup-linux-x64-gnu": "4.13.2",
- "@rollup/rollup-linux-x64-musl": "4.13.2",
- "@rollup/rollup-win32-arm64-msvc": "4.13.2",
- "@rollup/rollup-win32-ia32-msvc": "4.13.2",
- "@rollup/rollup-win32-x64-msvc": "4.13.2",
+ "@rollup/rollup-android-arm-eabi": "4.14.1",
+ "@rollup/rollup-android-arm64": "4.14.1",
+ "@rollup/rollup-darwin-arm64": "4.14.1",
+ "@rollup/rollup-darwin-x64": "4.14.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.14.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.14.1",
+ "@rollup/rollup-linux-arm64-musl": "4.14.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.14.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.14.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.14.1",
+ "@rollup/rollup-linux-x64-gnu": "4.14.1",
+ "@rollup/rollup-linux-x64-musl": "4.14.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.14.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.14.1",
+ "@rollup/rollup-win32-x64-msvc": "4.14.1",
"fsevents": "~2.3.2"
}
},
@@ -3308,9 +3306,9 @@
}
},
"node_modules/sass": {
- "version": "1.72.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz",
- "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==",
+ "version": "1.74.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz",
+ "integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
@@ -3686,9 +3684,9 @@
}
},
"node_modules/typescript": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
- "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz",
+ "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==",
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
@@ -3699,9 +3697,9 @@
}
},
"node_modules/universal-cookie": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.2.tgz",
- "integrity": "sha512-GK9ygNUNk+u1umTtFoWItePuIGYy0TEu2w084mfjBpIIg9pikcN18EM6IMt+9VJCyR3uftu3yF2fFUMbwH1Kdw==",
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.4.tgz",
+ "integrity": "sha512-Q+DVJsdykStWRMtXr2Pdj3EF98qZHUH/fXv/gwFz/unyToy1Ek1w5GsWt53Pf38tT8Gbcy5QNsj61Xe9TggP4g==",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0"
@@ -3753,9 +3751,9 @@
"dev": true
},
"node_modules/vite": {
- "version": "5.2.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz",
- "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==",
+ "version": "5.2.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
+ "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
"dev": true,
"dependencies": {
"esbuild": "^0.20.1",
@@ -3862,13 +3860,13 @@
}
},
"node_modules/vue-tsc": {
- "version": "1.8.27",
- "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz",
- "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==",
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.11.tgz",
+ "integrity": "sha512-dl5MEU4VGZdQFGBnKfPpAfV3SQmBDWs9o4YhUPvDmwk+zmb/RprzFJK2sagR6EWazogZhXENvykd3wBXWS9kng==",
"dev": true,
"dependencies": {
- "@volar/typescript": "~1.11.1",
- "@vue/language-core": "1.8.27",
+ "@volar/typescript": "~2.2.0-alpha.6",
+ "@vue/language-core": "2.0.11",
"semver": "^7.5.4"
},
"bin": {
diff --git a/front-end/package.json b/front-end/package.json
index 12b2c89..2141b26 100644
--- a/front-end/package.json
+++ b/front-end/package.json
@@ -20,7 +20,7 @@
},
"dependencies": {
"@headlessui/vue": "^1.7.17",
- "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/27b487b6d0befdb2197a58ceadb1f1ac2b337786/@vnuge-vnlib.browser/release.tgz",
+ "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/builds/Plugins.Essentials/a7cf7c8987b8847984629293d8eb27908f3de3dd/@vnuge-vnlib.browser/release.tgz",
"@vuelidate/core": "^2.0.2",
"@vuelidate/validators": "^2.0.2",
"@vueuse/core": "^10.3.x",
@@ -47,6 +47,6 @@
"typescript": "^5.0.2",
"vite": "^5.0.x",
"vue-eslint-parser": "^9.3.0",
- "vue-tsc": "^1.4.2"
+ "vue-tsc": "^2.0.x"
}
}
diff --git a/front-end/src/App.vue b/front-end/src/App.vue
index 4bd94c8..d1e9f50 100644
--- a/front-end/src/App.vue
+++ b/front-end/src/App.vue
@@ -73,7 +73,7 @@ const showIf = (tabId: TabId, active: TabId) => isEqual(tabId, active)
</template>
</SideMenuItem>
- <SideMenuItem :tab="TabId.Login" :name="loggedIn ? 'Logout' : 'Login'">
+ <SideMenuItem :tab="TabId.Login" :name="loggedIn ? 'Sign Out' : 'Sign In'">
<template #icon>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 7.5h11m0 0L8 3.786M12 7.5l-4 3.714M12 1h3c.53 0 1.04.196 1.414.544.375.348.586.82.586 1.313v9.286c0 .492-.21.965-.586 1.313A2.081 2.081 0 0 1 15 14h-3"/>
</template>
@@ -147,7 +147,7 @@ const showIf = (tabId: TabId, active: TabId) => isEqual(tabId, active)
</template>
</BottomMenuItem>
- <BottomMenuItem :tab="TabId.Login" :name="loggedIn ? 'Logout' : 'Login'">
+ <BottomMenuItem :tab="TabId.Login" :name="loggedIn ? 'Sign Out' : 'Sign In'">
<template #icon>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 7.5h11m0 0L8 3.786M12 7.5l-4 3.714M12 1h3c.53 0 1.04.196 1.414.544.375.348.586.82.586 1.313v9.286c0 .492-.21.965-.586 1.313A2.081 2.081 0 0 1 15 14h-3"/>
</template>
diff --git a/front-end/src/components/Bookmarks.vue b/front-end/src/components/Bookmarks.vue
index 274b0b4..b83743c 100644
--- a/front-end/src/components/Bookmarks.vue
+++ b/front-end/src/components/Bookmarks.vue
@@ -1,31 +1,28 @@
<script setup lang="ts">
import { MaybeRef, Ref, computed, defineAsyncComponent, ref, shallowRef, watch } from 'vue';
import { useQuery, useStore } from '../store';
-import { get, set, formatTimeAgo, useToggle, useTimestamp, useFileDialog, asyncComputed, toReactive, useClipboard } from '@vueuse/core';
+import { get, set, useToggle, useFileDialog, asyncComputed, toReactive } from '@vueuse/core';
import { useVuelidate } from '@vuelidate/core';
import { required, maxLength, minLength, helpers } from '@vuelidate/validators';
-import { apiCall, useConfirm, useGeneralToaster, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser';
-import { clone, cloneDeep, join, defaultTo, every, filter, includes, isEmpty, isEqual, first, isString, chunk, map, forEach, isNil } from 'lodash-es';
+import { apiCall, useGeneralToaster, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser';
+import { clone, cloneDeep, defaultTo, every, filter, includes, isEmpty, isEqual, first, isString, chunk, map, forEach, isNil } from 'lodash-es';
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import { parseNetscapeBookmarkString } from './Boomarks/util.ts';
import type { BatchUploadResult, Bookmark, BookmarkError } from '../store/bookmarks';
import AddOrUpdateForm from './Boomarks/AddOrUpdateForm.vue';
+import BookmarkList from './Boomarks/BookmarkList.vue';
const Dialog = defineAsyncComponent(() => import('./global/Dialog.vue'));
const store = useStore();
const { waiting } = useWait();
-const { reveal } = useConfirm();
const toaster = useGeneralToaster();
const bookmarks = computed(() => store.bookmarks.list);
const tags = computed(() => store.bookmarks.allTags);
-const now = useTimestamp({interval: 1000});
+
const selectedTags = computed(() => store.bookmarks.tags);
const localSearch = shallowRef<string>(store.bookmarks.query);
const nextPageAvailable = computed(() => isEqual(bookmarks.value?.length, get(store.bookmarks.pages.currentPageSize)));
-const { copy } = useClipboard()
-//Refresh on page load
-store.bookmarks.refresh();
const safeNameRegex = /^[a-zA-Z0-9_\-\|\., ]*$/;
const safeUrlRegex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/;
@@ -76,27 +73,6 @@ const clear = () => {
set(localSearch, '');
}
-const bmDelete = async (bookmark: Bookmark) => {
- const { isCanceled } = await reveal({
- title: 'Delete bookmark',
- text: `Are you sure you want to delete ${bookmark.Name} ?`,
- })
-
- if(isCanceled) return;
-
- apiCall(async ({ toaster }) => {
-
- await store.bookmarks.api.delete(bookmark);
-
- toaster.general.success({
- title: 'Bookmark deleted',
- text: 'Bookmark has been deleted successfully'
- });
-
- store.bookmarks.refresh();
- })
-}
-
const isTagSelected = (tag: string, currentTags: MaybeRef<string[]>) => includes(get(currentTags), tag);
const execSearch = () => store.bookmarks.query = get(localSearch);
const clearTags = () => store.bookmarks.tags = [];
@@ -441,43 +417,9 @@ const upload = (() => {
</div>
<div class="mx-auto sm:mt-2">
- <div class="grid h-full grid-cols-1 gap-1 leading-tight md:leading-normal">
-
- <div v-for="bm in bookmarks" :key="bm.Id" :id="join(['bm', bm.Id], '-')" class="w-full p-1">
- <div class="">
- <a class="bl-link" :href="bm.Url" target="_blank">
- {{ bm.Name }}
- </a>
- </div>
- <div class="flex flex-row items-center">
- <span v-for="tag in bm.Tags">
- <span class="mr-1 text-sm text-teal-500 cursor-pointer dark:text-teal-300" @click="toggleTag(tag)">
- #{{ tag }}
- </span>
- </span>
- <p class="ml-2 text-sm text-gray-500 truncate dark:text-gray-400 text-ellipsis">
- {{ bm.Description }}
- </p>
- </div>
- <div class="">
- <span class="text-xs text-gray-500 dark:text-gray-400">
- {{ formatTimeAgo(new Date(bm.Created), {}, now) }}
- </span>
- |
- <span class="inline-flex gap-1.5">
- <button class="text-xs text-gray-700 dark:text-gray-400" @click="copy(bm.Url)">
- Copy
- </button>
- <button class="text-xs text-gray-700 dark:text-gray-400" @click="edit.editBookmark(bm)">
- Edit
- </button>
- <button class="text-xs text-gray-700 dark:text-gray-400" @click="bmDelete(bm)">
- Delete
- </button>
- </span>
- </div>
- </div>
- </div>
+
+ <BookmarkList @toggle-tag="toggleTag" @edit="edit.editBookmark" />
+
<div class="pr-4 mt-5 mb-10 ml-auto w-fit">
<div class="flex flex-col items-center">
<div class="text-sm">
@@ -600,9 +542,6 @@ const upload = (() => {
Upload
</button>
</div>
- <div class="">
-
- </div>
</form>
</div>
</div>
diff --git a/front-end/src/components/Boomarks/AddOrUpdateForm.vue b/front-end/src/components/Boomarks/AddOrUpdateForm.vue
index 0370e0c..d6ea4bc 100644
--- a/front-end/src/components/Boomarks/AddOrUpdateForm.vue
+++ b/front-end/src/components/Boomarks/AddOrUpdateForm.vue
@@ -1,8 +1,10 @@
<script setup lang="ts">
-import { computed, toRefs } from 'vue';
-import { isEmpty, join, split } from 'lodash-es';
+import { computed, shallowRef, toRefs } from 'vue';
+import { set, watchDebounced } from '@vueuse/core'
+import { isEmpty, join, noop, split } from 'lodash-es';
import { useStore } from '../../store';
-import { useWait } from '@vnuge/vnlib.browser';
+import { WebMessage, useWait } from '@vnuge/vnlib.browser';
+import { AxiosError } from 'axios';
const emit = defineEmits(['submit'])
const props = defineProps<{
@@ -20,6 +22,8 @@ const tags = computed({
const { websiteLookup:lookup } = useStore()
const { setWaiting, waiting } = useWait()
+const errMessage = shallowRef();
+
const execLookup = async () => {
//url must be valid before searching
if(v$.value.Url.$invalid) return
@@ -45,9 +49,10 @@ const execLookup = async () => {
v$.value.Tags.$dirty = true;
}
}
- catch(e){
- //Mostly ignore errors
+ catch(e){
console.error(e)
+ const res = (e as AxiosError).response?.data;
+ set(errMessage, (res as WebMessage)?.result);
}
finally{
setWaiting(false)
@@ -56,6 +61,9 @@ const execLookup = async () => {
const showSearchButton = computed(() => lookup.isSupported && !isEmpty(v$.value.Url.$model))
+//Clear error message after 5 seconds
+watchDebounced(errMessage, v => v ? setTimeout(() => set(errMessage, ''), 5000) : noop())
+
</script>
<template>
<form id="bm-add-or-update-form" class="grid grid-cols-1 gap-4 p-4" @submit.prevent="emit('submit')">
@@ -67,22 +75,31 @@ const showSearchButton = computed(() => lookup.isSupported && !isEmpty(v$.value.
<input type="text" id="url" class="input" placeholder="https://www.example.com" v-model="v$.Url.$model"
:class="{'dirty': v$.Url.$dirty, 'error': v$.Url.$invalid}" required>
- <div class="">
- <button
- type="button"
- :disabled="!showSearchButton || waiting"
- @click.self.prevent="execLookup"
- id="search-btn"
- class="btn blue search-btn"
- >
- <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
- viewBox="0 0 20 20">
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
- d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
- </svg>
+ <div class="my-auto">
+ <button type="button" :disabled="!showSearchButton || waiting" @click.prevent="execLookup"
+ id="search-btn" class="btn blue search-btn">
+ <span v-if="waiting" class="mx-auto">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 animate-spin" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
+ viewBox="0 0 20 20">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
+ stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
+ </svg>
+ </span>
</button>
</div>
</div>
+ <div v-if="errMessage" class="pl-2">
+ <p class="text-xs italic text-red-800 dark:text-red-500">
+ {{ errMessage }}
+ </p>
+ </div>
</fieldset>
<fieldset>
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Title</label>
diff --git a/front-end/src/components/Boomarks/BookmarkList.vue b/front-end/src/components/Boomarks/BookmarkList.vue
new file mode 100644
index 0000000..ac0d49b
--- /dev/null
+++ b/front-end/src/components/Boomarks/BookmarkList.vue
@@ -0,0 +1,157 @@
+<script setup lang="ts">
+import { type Bookmark } from '../../store/bookmarks';
+
+import { computed, ref } from 'vue';
+import { useStore } from '../../store';
+import { formatTimeAgo, useTimestamp, useClipboard } from '@vueuse/core';
+import { apiCall, useConfirm } from '@vnuge/vnlib.browser';
+import { join, truncate } from 'lodash-es';
+
+const emit = defineEmits(['toggleTag', 'edit']);
+
+const store = useStore();
+const bookmarks = computed(() => store.bookmarks.list);
+const readable = ref(true); //Future allow users to switch between clean and readable layout
+
+//Refresh on page load
+store.bookmarks.refresh();
+
+const { copy } = useClipboard()
+const { reveal } = useConfirm();
+const now = useTimestamp({ interval: 1000 });
+
+const bmDelete = async (bookmark: Bookmark) => {
+ const { isCanceled } = await reveal({
+ title: 'Delete bookmark',
+ text: `Are you sure you want to delete ${bookmark.Name} ?`,
+ })
+
+ if (isCanceled) return;
+
+ apiCall(async ({ toaster }) => {
+
+ await store.bookmarks.api.delete(bookmark);
+
+ toaster.general.success({
+ title: 'Bookmark deleted',
+ text: 'Bookmark has been deleted successfully'
+ });
+
+ store.bookmarks.refresh();
+ })
+}
+
+const truncatText = (desc: string) => truncate(desc, { length: 100 });
+
+</script>
+
+<template>
+ <div class="grid h-full grid-cols-1 gap-0">
+ <div v-for="bm in bookmarks" :key="bm.Id" :id="join(['bm', bm.Id], '-')" class="w-full p-1">
+ <div v-if="readable" class="leading-tight md:leading-normal">
+ <div class="">
+ <a class="bl-link" :href="bm.Url" target="_blank">
+ {{ bm.Name }}
+ </a>
+ </div>
+ <div class="flex flex-row items-center">
+ <span v-for="tag in bm.Tags">
+ <span class="mr-1 text-sm text-teal-500 cursor-pointer dark:text-teal-300"
+ @click="emit('toggleTag', tag)">
+ #{{ tag }}
+ </span>
+ </span>
+ <p class="ml-2 text-sm text-gray-500 truncate dark:text-gray-400 text-ellipsis">
+ {{ bm.Description }}
+ </p>
+ </div>
+ <div class="flex items-center gap-1.5">
+ <span class="text-xs text-gray-500 dark:text-gray-400">
+ {{ formatTimeAgo(new Date(bm.Created), {}, now) }}
+ </span>
+ |
+ <span class="flex flex-row gap-1.5">
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="copy(bm.Url)">
+ Copy
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="emit('edit', bm)">
+ Edit
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="bmDelete(bm)">
+ Delete
+ </button>
+ </span>
+ </div>
+ </div>
+ <div v-else class="leading-tight clean-layout">
+ <div class="flex flex-row">
+ <div class="flex-1">
+ <a class="text-sm font-bold bl-link" :href="bm.Url" target="_blank">
+ {{ bm.Name }}
+ </a>
+ </div>
+ <div class="">
+ <span class="inline-flex gap-1">
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="copy(bm.Url)">
+ <svg class="w-5 h-5 text-gray-800 dark:text-white" aria-hidden="true"
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
+ viewBox="0 0 24 24">
+ <path stroke="currentColor" stroke-linejoin="round" stroke-width="2"
+ d="M9 8v3a1 1 0 0 1-1 1H5m11 4h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-7a1 1 0 0 0-1 1v1m4 3v10a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-7.13a1 1 0 0 1 .24-.65L7.7 8.35A1 1 0 0 1 8.46 8H13a1 1 0 0 1 1 1Z" />
+ </svg>
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400" @click="emit('edit', bm)">
+ <svg class="w-5 h-5 text-gray-800 dark:text-white" aria-hidden="true"
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
+ viewBox="0 0 24 24">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
+ stroke-width="2"
+ d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z" />
+ </svg>
+ </button>
+ <button class="text-xs text-gray-700 dark:text-gray-400 " @click="bmDelete(bm)">
+ <svg class="w-5 h-5 text-gray-800 duration-100 ease-in dark:text-white trash"
+ aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
+ fill="none" viewBox="0 0 24 24">
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
+ stroke-width="2"
+ d="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z" />
+ </svg>
+ </button>
+ </span>
+ </div>
+ </div>
+ <div class="flex flex-row items-start">
+ <p class="text-sm text-gray-500 dark:text-gray-300 max-w-[26rem] flex-auto">
+ {{ truncatText(bm.Description) }}
+ </p>
+ <div class="flex flex-col flex-wrap items-end ml-5 class gap-x-2 max-h-16">
+ <span v-for="tag in bm.Tags">
+ <span class="mr-1 text-xs text-gray-500 duration-75 ease-linear cursor-pointer dark:text-gray-500 hover:text-teal-500 hover:dark:text-teal-400"
+ @click="emit('toggleTag', tag)">
+ {{ tag }}
+ </span>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+ .clean-layout {
+ @apply shadow-sm md:px-6 p-3 border rounded-md h-[7rem];
+ @apply bg-white dark:bg-gray-800 dark:border-gray-700 max-w-[40rem];
+
+ button svg{
+ &:hover{
+ @apply text-gray-500 dark:text-gray-300 ease-linear duration-75;
+
+ &.trash{
+ @apply hover:text-red-500;
+ }
+ }
+ }
+ }
+</style> \ No newline at end of file
diff --git a/front-end/src/components/Login/AdminReg.vue b/front-end/src/components/Login/AdminReg.vue
index 8546512..f9bbbb8 100644
--- a/front-end/src/components/Login/AdminReg.vue
+++ b/front-end/src/components/Login/AdminReg.vue
@@ -74,10 +74,22 @@ const onSubmit = async () => {
<input type="password" name="repeat-password" id="repeat-password" class="input" placeholder="••••••••" required
v-model="v$.repeat.$model">
</fieldset>
- <button form="admin-registation" type="submit" class="btn">Register</button>
+ <button type="submit" for="admin-registation" class="flex justify-center btn">
+ <span v-if="waiting" class="mx-auto animate-spin">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ Register
+ </span>
+ </button>
</form>
<p class="py-4 text-sm text-red-500">
- This tab is only visible when the server is in setup mode. You can create as many admin accounts as you like now.
+ This tab is only visible when the server is in setup mode. You can create as many admin accounts as you like
+ now.
</p>
</template>
diff --git a/front-end/src/components/Login/PkiLogin.vue b/front-end/src/components/Login/PkiLogin.vue
index 4515062..d3a635e 100644
--- a/front-end/src/components/Login/PkiLogin.vue
+++ b/front-end/src/components/Login/PkiLogin.vue
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { isEmpty } from 'lodash-es';
-import { apiCall, debugLog, useMessage } from '@vnuge/vnlib.browser';
+import { apiCall, debugLog, useMessage, useWait } from '@vnuge/vnlib.browser';
import { ref } from 'vue'
import { decodeJwt } from 'jose'
import { useStore } from '../../store';
const { setMessage } = useMessage()
const { pkiAuth } = useStore()
+const { waiting } = useWait()
const otp = ref('')
@@ -32,19 +33,27 @@ const onSubmit = () => {
</script>
<template>
- <form id="pki-login-form" class="max-w-sm mx-auto" @submit.prevent="onSubmit">
- <label for="message" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
- Paste your one time password (OTP)
- </label>
- <textarea
- id="message" rows="5"
- v-model="otp"
- class="block p-2.5 w-full text-sm text-gray-900 bg-transparent rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
- placeholder="Enter your OTP"
- >
+ <form id="pki-login-form" class="max-w-sm mx-auto" @submit.prevent="onSubmit">
+ <label for="message" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
+ Paste your one time password (OTP)
+ </label>
+ <textarea id="message" rows="5" v-model="otp"
+ class="block p-2.5 w-full text-sm text-gray-900 bg-transparent rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
+ placeholder="Enter your OTP">
</textarea>
-
- <button type="submit" for="pki-login-form" class="mt-4 btn">Submit</button>
-
- </form>
+
+ <button type="submit" for="pki-login-form" class="flex justify-center mt-4 btn">
+ <span v-if="waiting" class="mx-auto animate-spin">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ Submit
+ </span>
+ </button>
+
+ </form>
</template> \ No newline at end of file
diff --git a/front-end/src/components/Login/UserPass.vue b/front-end/src/components/Login/UserPass.vue
index c47e594..9fd64f4 100644
--- a/front-end/src/components/Login/UserPass.vue
+++ b/front-end/src/components/Login/UserPass.vue
@@ -110,7 +110,18 @@ const onSubmit = async () => {
v-model="v$.password.$model"
>
</fieldset>
- <button type="submit" class="btn">Sign in</button>
+ <button type="submit" class="flex justify-center btn">
+ <span v-if="waiting" class="mx-auto animate-spin">
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 15 15">
+ <path fill="currentColor" fill-rule="evenodd"
+ d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
+ clip-rule="evenodd" />
+ </svg>
+ </span>
+ <span v-else>
+ Sign in
+ </span>
+ </button>
</form>
</template>
diff --git a/front-end/src/components/global/Dialog.vue b/front-end/src/components/global/Dialog.vue
index 65d9165..18ad0fe 100644
--- a/front-end/src/components/global/Dialog.vue
+++ b/front-end/src/components/global/Dialog.vue
@@ -22,7 +22,7 @@ onClickOutside(dialog, () => get(open) ? cancel() : noop())
class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-20 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="fixed inset-0 bg-black/30" aria-hidden="true" />
- <div class="relative w-full max-w-xl max-h-full p-4 mx-auto mt-[8rem] md:mt-32">
+ <div class="relative w-full max-w-xl max-h-full p-4 mx-auto mt-16 md:mt-32">
<!-- Modal content -->
<div class="relative bg-white rounded shadow dark:bg-gray-700" ref="dialog">
<!-- Modal header -->
diff --git a/front-end/src/index.scss b/front-end/src/index.scss
index 5575eb3..0b86ea1 100644
--- a/front-end/src/index.scss
+++ b/front-end/src/index.scss
@@ -18,7 +18,7 @@
.input {
@apply bg-transparent border border-gray-300 text-gray-900 text-sm rounded focus:ring-blue-500 focus:border-blue-500 block w-full p-2 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500;
- &.dirty{
+ &.dirty:focus{
@apply bg-green-50 border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 focus:ring-green-500 focus:border-green-500 dark:bg-gray-700 dark:border-green-500;
}
diff --git a/front-end/src/main.ts b/front-end/src/main.ts
index 2f2ca8e..eeac96d 100644
--- a/front-end/src/main.ts
+++ b/front-end/src/main.ts
@@ -68,7 +68,7 @@ store.use(profilePlugin('/account/profile'))
//Enable mfa with totp settings plugin (optional pki config)
.use(mfaSettingsPlugin('/account/mfa', '/account/pki'))
//Setup social mfa plugin
- .use(socialMfaPlugin("/account/social/portals"))
+ .use(socialMfaPlugin("/login/social/portals"))
//Add the oauth2 apps plugin
.use(bookmarkPlugin('/bookmarks'))
.use(siteLookupPlugin('/lookup', 2000))
diff --git a/front-end/src/store/socialMfaPlugin.ts b/front-end/src/store/socialMfaPlugin.ts
index e0ec972..79cb088 100644
--- a/front-end/src/store/socialMfaPlugin.ts
+++ b/front-end/src/store/socialMfaPlugin.ts
@@ -1,4 +1,3 @@
-
import 'pinia'
import { MaybeRef } from 'vue';
import {