Implemented custom user links
This commit is contained in:
parent
ea738ebb04
commit
78f1d291f3
|
@ -25,6 +25,11 @@
|
||||||
<ProfileFormPartial User="@User" />
|
<ProfileFormPartial User="@User" />
|
||||||
}
|
}
|
||||||
</BoardCardComponent>
|
</BoardCardComponent>
|
||||||
|
<BoardCardComponent Heading="@Localizer["Links"]">
|
||||||
|
@if (User is not null) {
|
||||||
|
<LinksPartial User="@User" />
|
||||||
|
}
|
||||||
|
</BoardCardComponent>
|
||||||
<BoardCardComponent Heading="@Localizer["AboutMe"]">
|
<BoardCardComponent Heading="@Localizer["AboutMe"]">
|
||||||
@if (User is not null) {
|
@if (User is not null) {
|
||||||
<AboutMeFormPartial User="@User" />
|
<AboutMeFormPartial User="@User" />
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
@using Wave.Data
|
||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
@using Microsoft.AspNetCore.Identity
|
||||||
|
|
||||||
|
@inject UserManager<ApplicationUser> UserManager
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject IStringLocalizer<Wave.Components.Account.Pages.Manage.Index> Localizer
|
||||||
|
|
||||||
|
<ul class="flex flex-col gap-2 my-3">
|
||||||
|
@foreach (var link in User.Links) {
|
||||||
|
<li class="flex justify-between items-center">
|
||||||
|
<UserLinkComponent Link="link" class="link flex gap-2" />
|
||||||
|
<form action="/api/user/link/@link.Id" method="post">
|
||||||
|
<AntiforgeryToken />
|
||||||
|
<input type="hidden" name="linkId" value="@link.Id" />
|
||||||
|
<input type="hidden" name="ReturnUrl" value="~/@Navigation.ToBaseRelativePath(Navigation.Uri)" />
|
||||||
|
<input type="hidden" name="_method" value="delete" />
|
||||||
|
<button type="submit" class="btn btn-square btn-error btn-sm" title="@Localizer["Links_Delete_Label"]">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
|
||||||
|
<path fill-rule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<EditForm FormName="user-links" EditContext="@Context" OnValidSubmit="OnValidSubmit" method="post" class="w-full">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
<InputLabelComponent LabelText="@Localizer["Links_Url_Label"]" For="() => Model.Url">
|
||||||
|
<div class="join join-vertical md:join-horizontal">
|
||||||
|
<InputText class="input input-bordered md:flex-1 join-item" maxlength="64" autocomplete="off" type="url"
|
||||||
|
@bind-Value="@Model.Url" placeholder="@Localizer["Links_Url_Placeholder"]" />
|
||||||
|
<button type="submit" class="btn btn-primary join-item">@Localizer["Links_Submit"]</button>
|
||||||
|
</div>
|
||||||
|
</InputLabelComponent>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public required ApplicationUser User { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "user-links")]
|
||||||
|
private InputModel Model { get; set; } = new();
|
||||||
|
private EditContext Context { get; set; } = null!;
|
||||||
|
private ValidationMessageStore Validation { get; set; } = null!;
|
||||||
|
[SupplyParameterFromForm(Name = "LinkId")]
|
||||||
|
private int? LinkId { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized() {
|
||||||
|
Context = new EditContext(Model);
|
||||||
|
Validation = new ValidationMessageStore(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnValidSubmit() {
|
||||||
|
var link = new UserLink { UrlString = Model.Url };
|
||||||
|
|
||||||
|
if (!link.Validate()) {
|
||||||
|
Validation.Add(() => Model.Url, "Url is invalid.");
|
||||||
|
Context.NotifyValidationStateChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.UrlString = link.Url.AbsoluteUri;
|
||||||
|
User.Links.Add(link);
|
||||||
|
await UserManager.UpdateAsync(User);
|
||||||
|
Model = new InputModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class InputModel {
|
||||||
|
[Url, Required(AllowEmptyStrings = false), MaxLength(1024)]
|
||||||
|
public string Url { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -55,6 +55,11 @@
|
||||||
<p>
|
<p>
|
||||||
@Article.Author.AboutTheAuthor
|
@Article.Author.AboutTheAuthor
|
||||||
</p>
|
</p>
|
||||||
|
<div class="card-actions flex gap-2 flex-wrap">
|
||||||
|
@foreach (var link in Article.Author.Links) {
|
||||||
|
<UserLinkComponent Link="link" class="badge hover:badge-outline flex gap-2 p-4" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
<section class="w-80 max-w-xs">
|
<section class="w-80 max-w-full md:max-w-xs">
|
||||||
@if (!string.IsNullOrWhiteSpace(Heading)) {
|
@if (!string.IsNullOrWhiteSpace(Heading)) {
|
||||||
<h2 class="text-2xl lg:hyphens-auto mb-3" lang="@CultureInfo.CurrentCulture">@Heading</h2>
|
<h2 class="text-2xl lg:hyphens-auto mb-3" lang="@CultureInfo.CurrentCulture">@Heading</h2>
|
||||||
}
|
}
|
||||||
|
|
13
Wave/Components/UserLinkComponent.razor
Normal file
13
Wave/Components/UserLinkComponent.razor
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@using Wave.Data
|
||||||
|
|
||||||
|
<a href="@Link.UrlString" rel="me" target="_blank" @attributes="AdditionalAttributes">
|
||||||
|
<img src="@("https://" + Link.Url.Host + "/favicon.png")" alt="" width="16" height="16" data-errors="0" />
|
||||||
|
@(Link.Url.Host.LastIndexOf('.') > -1 ? Link.Url.Host[..Link.Url.Host.LastIndexOf('.')] : Link.Url.Host)
|
||||||
|
</a>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public required UserLink Link { get; set; }
|
||||||
|
[Parameter(CaptureUnmatchedValues = true)]
|
||||||
|
public IDictionary<string, object>? AdditionalAttributes { get; set; }
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Wave.Data;
|
using Wave.Data;
|
||||||
using Wave.Services;
|
using Wave.Services;
|
||||||
|
@ -6,13 +8,13 @@
|
||||||
namespace Wave.Controllers;
|
namespace Wave.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/api/user")]
|
[Microsoft.AspNetCore.Mvc.Route("/api/[controller]")]
|
||||||
public class UserController(ImageService imageService, IDbContextFactory<ApplicationDbContext> contextFactory) : ControllerBase {
|
public class UserController(ImageService imageService, IDbContextFactory<ApplicationDbContext> contextFactory) : ControllerBase {
|
||||||
private ImageService ImageService { get; } = imageService;
|
private ImageService ImageService { get; } = imageService;
|
||||||
private IDbContextFactory<ApplicationDbContext> ContextFactory { get; } = contextFactory;
|
private IDbContextFactory<ApplicationDbContext> ContextFactory { get; } = contextFactory;
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("pfp/{userId}")]
|
[Microsoft.AspNetCore.Mvc.Route("pfp/{userId}")]
|
||||||
public async Task<IActionResult> Get(string userId) {
|
public async Task<IActionResult> Get(string userId) {
|
||||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||||
var user = await context.Users.Include(u => u.ProfilePicture).FirstOrDefaultAsync(u => u.Id == userId);
|
var user = await context.Users.Include(u => u.ProfilePicture).FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
|
@ -26,4 +28,23 @@ public class UserController(ImageService imageService, IDbContextFactory<Applica
|
||||||
|
|
||||||
return File(System.IO.File.OpenRead(path), ImageService.ImageMimeType);
|
return File(System.IO.File.OpenRead(path), ImageService.ImageMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("link/{linkId:int}")]
|
||||||
|
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
|
||||||
|
public async Task<IActionResult> DeleteUserLink(int linkId, [FromServices] UserManager<ApplicationUser> userManager) {
|
||||||
|
if (!string.Equals(Request.Form["_method"], "delete", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
string returnUrl = Request.Form["ReturnUrl"].FirstOrDefault() ?? string.Empty;
|
||||||
|
|
||||||
|
var user = await userManager.GetUserAsync(User);
|
||||||
|
if (user is null) return Unauthorized();
|
||||||
|
|
||||||
|
var link = user.Links.FirstOrDefault(l => l.Id == linkId);
|
||||||
|
if (link is null) return NotFound();
|
||||||
|
|
||||||
|
user.Links.Remove(link);
|
||||||
|
await userManager.UpdateAsync(user);
|
||||||
|
return LocalRedirect(string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -22,8 +22,10 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
||||||
|
|
||||||
user.HasOne(u => u.ProfilePicture).WithOne().HasForeignKey(typeof(ProfilePicture))
|
user.HasOne(u => u.ProfilePicture).WithOne().HasForeignKey(typeof(ProfilePicture))
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
user.HasMany(u => u.Links).WithOne().OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
user.Navigation(u => u.ProfilePicture).IsRequired(false);
|
user.Navigation(u => u.ProfilePicture).IsRequired(false);
|
||||||
|
user.Navigation(u => u.Links).AutoInclude();
|
||||||
});
|
});
|
||||||
builder.Entity<ProfilePicture>(pfp => {
|
builder.Entity<ProfilePicture>(pfp => {
|
||||||
pfp.HasKey(p => p.Id);
|
pfp.HasKey(p => p.Id);
|
||||||
|
@ -36,7 +38,7 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
||||||
article.Property(a => a.Title)
|
article.Property(a => a.Title)
|
||||||
.IsRequired().HasMaxLength(256);
|
.IsRequired().HasMaxLength(256);
|
||||||
|
|
||||||
article.HasOne(a => a.Author).WithMany()
|
article.HasOne(a => a.Author).WithMany(a => a.Articles)
|
||||||
.IsRequired().OnDelete(DeleteBehavior.Cascade);
|
.IsRequired().OnDelete(DeleteBehavior.Cascade);
|
||||||
article.HasOne(a => a.Reviewer).WithMany()
|
article.HasOne(a => a.Reviewer).WithMany()
|
||||||
.IsRequired(false).OnDelete(DeleteBehavior.SetNull);
|
.IsRequired(false).OnDelete(DeleteBehavior.SetNull);
|
||||||
|
@ -57,7 +59,8 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
||||||
builder.Entity<Category>(category => {
|
builder.Entity<Category>(category => {
|
||||||
category.HasKey(c => c.Id);
|
category.HasKey(c => c.Id);
|
||||||
category.Property(c => c.Name).IsRequired().HasMaxLength(128);
|
category.Property(c => c.Name).IsRequired().HasMaxLength(128);
|
||||||
category.Property(c => c.Color).IsRequired().HasDefaultValue(CategoryColors.Default);
|
category.Property(c => c.Color).IsRequired().HasSentinel(CategoryColors.Default)
|
||||||
|
.HasDefaultValue(CategoryColors.Default);
|
||||||
|
|
||||||
category.HasMany<Article>().WithMany(a => a.Categories)
|
category.HasMany<Article>().WithMany(a => a.Categories)
|
||||||
.UsingEntity<ArticleCategory>(
|
.UsingEntity<ArticleCategory>(
|
||||||
|
|
|
@ -16,4 +16,7 @@ public class ApplicationUser : IdentityUser {
|
||||||
public string BiographyHtml { get; set; } = string.Empty;
|
public string BiographyHtml { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string Name => FullName ?? UserName ?? "Anon";
|
public string Name => FullName ?? UserName ?? "Anon";
|
||||||
|
|
||||||
|
public IList<Article> Articles { get; set; } = [];
|
||||||
|
public IList<UserLink> Links { get; set; } = [];
|
||||||
}
|
}
|
|
@ -7,6 +7,9 @@
|
||||||
namespace Wave.Data.Migrations.postgres;
|
namespace Wave.Data.Migrations.postgres;
|
||||||
|
|
||||||
// Add-Migration InitialCreate -OutputDir Data/Migrations/postgres -Project Wave -StartupProject Wave -Context ApplicationDbContext -Args "ConnectionStrings:DefaultConnection=Host=localhost;Port=5432;AllowAnonymousConnections=true"
|
// Add-Migration InitialCreate -OutputDir Data/Migrations/postgres -Project Wave -StartupProject Wave -Context ApplicationDbContext -Args "ConnectionStrings:DefaultConnection=Host=localhost;Port=5432;AllowAnonymousConnections=true"
|
||||||
|
|
||||||
|
// Remove-Migration -force -Project Wave -StartupProject Wave -Context ApplicationDbContext -Args "ConnectionStrings:DefaultConnection=Host=localhost;Port=5432;AllowAnonymousConnections=true"
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class InitialCreate : Migration {
|
public partial class InitialCreate : Migration {
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
500
Wave/Data/Migrations/postgres/20240202223047_UserLinks.Designer.cs
generated
Normal file
500
Wave/Data/Migrations/postgres/20240202223047_UserLinks.Designer.cs
generated
Normal file
|
@ -0,0 +1,500 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using Wave.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Wave.Data.Migrations.postgres
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20240202223047_UserLinks")]
|
||||||
|
partial class UserLinks
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.0")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.ApplicationUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("AboutTheAuthor")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Biography")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)");
|
||||||
|
|
||||||
|
b.Property<string>("BiographyHtml")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("FullName")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.Article", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("AuthorId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Body")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("BodyHtml")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreationDate")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("LastModified")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("PublishDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("ReviewerId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("ReviewerId");
|
||||||
|
|
||||||
|
b.ToTable("Articles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.ArticleCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<Guid>("ArticleId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("CategoryId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ArticleId");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("ArticleCategories", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("Color")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(25);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.ProfilePicture", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ApplicationUserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("ImageId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ApplicationUserId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ProfilePictures", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.UserLink", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ApplicationUserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UrlString")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ApplicationUserId");
|
||||||
|
|
||||||
|
b.ToTable("UserLink");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.Article", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", "Author")
|
||||||
|
.WithMany("Articles")
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", "Reviewer")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ReviewerId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Reviewer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.ArticleCategory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.Article", "Article")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ArticleId")
|
||||||
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Wave.Data.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Article");
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.ProfilePicture", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", null)
|
||||||
|
.WithOne("ProfilePicture")
|
||||||
|
.HasForeignKey("Wave.Data.ProfilePicture", "ApplicationUserId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.UserLink", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", null)
|
||||||
|
.WithMany("Links")
|
||||||
|
.HasForeignKey("ApplicationUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.ApplicationUser", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Articles");
|
||||||
|
|
||||||
|
b.Navigation("Links");
|
||||||
|
|
||||||
|
b.Navigation("ProfilePicture");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
Wave/Data/Migrations/postgres/20240202223047_UserLinks.cs
Normal file
42
Wave/Data/Migrations/postgres/20240202223047_UserLinks.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Wave.Data.Migrations.postgres;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class UserLinks : Migration {
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) {
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "UserLink",
|
||||||
|
columns: table => new {
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy",
|
||||||
|
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
UrlString = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
ApplicationUserId = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table => {
|
||||||
|
table.PrimaryKey("PK_UserLink", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_UserLink_AspNetUsers_ApplicationUserId",
|
||||||
|
column: x => x.ApplicationUserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_UserLink_ApplicationUserId",
|
||||||
|
table: "UserLink",
|
||||||
|
column: "ApplicationUserId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) {
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "UserLink");
|
||||||
|
}
|
||||||
|
}
|
|
@ -356,6 +356,29 @@ protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
b.ToTable("ProfilePictures", (string)null);
|
b.ToTable("ProfilePictures", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.UserLink", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ApplicationUserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UrlString")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ApplicationUserId");
|
||||||
|
|
||||||
|
b.ToTable("UserLink");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
@ -410,7 +433,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
modelBuilder.Entity("Wave.Data.Article", b =>
|
modelBuilder.Entity("Wave.Data.Article", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Wave.Data.ApplicationUser", "Author")
|
b.HasOne("Wave.Data.ApplicationUser", "Author")
|
||||||
.WithMany()
|
.WithMany("Articles")
|
||||||
.HasForeignKey("AuthorId")
|
.HasForeignKey("AuthorId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
@ -452,8 +475,20 @@ protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Wave.Data.UserLink", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Wave.Data.ApplicationUser", null)
|
||||||
|
.WithMany("Links")
|
||||||
|
.HasForeignKey("ApplicationUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Wave.Data.ApplicationUser", b =>
|
modelBuilder.Entity("Wave.Data.ApplicationUser", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Articles");
|
||||||
|
|
||||||
|
b.Navigation("Links");
|
||||||
|
|
||||||
b.Navigation("ProfilePicture");
|
b.Navigation("ProfilePicture");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
|
|
21
Wave/Data/UserLink.cs
Normal file
21
Wave/Data/UserLink.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Wave.Data;
|
||||||
|
|
||||||
|
public class UserLink {
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(1024)]
|
||||||
|
public string UrlString { get; set; } = string.Empty;
|
||||||
|
public Uri Url => new(UrlString, UriKind.Absolute);
|
||||||
|
|
||||||
|
public bool Validate() {
|
||||||
|
try {
|
||||||
|
_ = Url;
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -150,4 +150,19 @@ Ich koche seit dem jungen Alter von 7...</value>
|
||||||
<data name="Submit" xml:space="preserve">
|
<data name="Submit" xml:space="preserve">
|
||||||
<value>Speichern</value>
|
<value>Speichern</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Links_Delete_Label" xml:space="preserve">
|
||||||
|
<value>Link löschen</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links" xml:space="preserve">
|
||||||
|
<value>Benutzerdefinierte Links</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links_Url_Label" xml:space="preserve">
|
||||||
|
<value>Neuer Link</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links_Url_Placeholder" xml:space="preserve">
|
||||||
|
<value>https://example.de/user/anna-muster</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links_Submit" xml:space="preserve">
|
||||||
|
<value>Hinzufügen</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -150,4 +150,19 @@ I started cooking at the young age of 7...</value>
|
||||||
<data name="Submit" xml:space="preserve">
|
<data name="Submit" xml:space="preserve">
|
||||||
<value>Save</value>
|
<value>Save</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Links" xml:space="preserve">
|
||||||
|
<value>Custom Links</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links_Delete_Label" xml:space="preserve">
|
||||||
|
<value>Delete link</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links_Url_Label" xml:space="preserve">
|
||||||
|
<value>New Link</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links_Url_Placeholder" xml:space="preserve">
|
||||||
|
<value>https://example.com/user/jane-smith</value>
|
||||||
|
</data>
|
||||||
|
<data name="Links_Submit" xml:space="preserve">
|
||||||
|
<value>Add</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
2
Wave/wwwroot/css/main.min.css
vendored
2
Wave/wwwroot/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue