Compare commits

..

No commits in common. "master" and "dev/multiple-notes" have entirely different histories.

17 changed files with 69 additions and 114 deletions

View File

@ -1,12 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.0",
"commands": [
"dotnet-ef"
]
}
}
}

3
.gitignore vendored
View File

@ -229,9 +229,6 @@ _pkginfo.txt
# but keep track of directories ending in .cache # but keep track of directories ending in .cache
!?*.[Cc]ache/ !?*.[Cc]ache/
# exclude notes
notes/*
# Others # Others
ClientBin/ ClientBin/
~$* ~$*

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>

View File

@ -8,9 +8,12 @@ namespace BinaryDad.Notes.Controllers
{ {
private readonly INoteService noteService; private readonly INoteService noteService;
public ApiController(INoteService noteService) => this.noteService = noteService; public ApiController(INoteService noteService)
{
this.noteService = noteService;
}
[Route("note/{noteName}")] [Route("note/{noteName}")]
public string Note(string noteName) => noteService.GetNote(noteName); public string Note(string noteName) => noteService.GetText(noteName);
} }
} }

View File

@ -8,13 +8,6 @@ namespace BinaryDad.Notes.Controllers
{ {
public class LoginController : Controller public class LoginController : Controller
{ {
private readonly IConfiguration configuration;
public LoginController(IConfiguration configuration)
{
this.configuration = configuration;
}
[Route("login")] [Route("login")]
public IActionResult Login() public IActionResult Login()
{ {
@ -28,7 +21,7 @@ namespace BinaryDad.Notes.Controllers
{ {
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
var appPassphrase = configuration["APP_PASSPHRASE"]; var appPassphrase = Environment.GetEnvironmentVariable("APP_PASSPHRASE");
if (passphrase == appPassphrase) if (passphrase == appPassphrase)
{ {

View File

@ -10,7 +10,10 @@ public class NoteController : Controller
{ {
private readonly INoteService noteService; private readonly INoteService noteService;
public NoteController(INoteService noteService) => this.noteService = noteService; public NoteController(INoteService noteService)
{
this.noteService = noteService;
}
[Route("{noteName=default}")] [Route("{noteName=default}")]
public IActionResult Index(string noteName) public IActionResult Index(string noteName)
@ -18,7 +21,7 @@ public class NoteController : Controller
var model = new ContentModel var model = new ContentModel
{ {
CurrentNote = noteName, CurrentNote = noteName,
Text = noteService.GetNote(noteName), Text = noteService.GetText(noteName),
NoteNames = noteService.GetNoteNames() NoteNames = noteService.GetNoteNames()
}; };

View File

@ -1,14 +1,13 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet publish "BinaryDad.Notes.csproj" -c Release -o /app/publish /p:UseAppHost=false RUN dotnet publish "BinaryDad.Notes.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app WORKDIR /app
RUN mkdir notes
COPY --from=build /app/publish . COPY --from=build /app/publish .
EXPOSE 8080 EXPOSE 80
ENTRYPOINT ["dotnet", "BinaryDad.Notes.dll"] ENTRYPOINT ["dotnet", "BinaryDad.Notes.dll"]

View File

@ -1,19 +1,15 @@
using BinaryDad.Notes.Services; using BinaryDad.Notes.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
namespace BinaryDad.Notes namespace BinaryDad.Notes
{ {
[Authorize]
public class NoteHub : Hub public class NoteHub : Hub
{ {
private readonly INoteService noteService; private readonly INoteService noteService;
private readonly ILogger<NoteHub> logger;
public NoteHub(INoteService noteService, ILogger<NoteHub> logger) public NoteHub(INoteService noteService)
{ {
this.noteService = noteService; this.noteService = noteService;
this.logger = logger;
} }
public override Task OnConnectedAsync() public override Task OnConnectedAsync()
@ -25,29 +21,20 @@ namespace BinaryDad.Notes
return base.OnConnectedAsync(); return base.OnConnectedAsync();
} }
public async Task SaveNote(string noteName, string content) public async Task SaveNote(string content, string? noteName)
{ {
try noteService.SaveText(content, noteName);
{
noteService.SaveNote(noteName, content);
// find all other connections except for the current one // find all other connections except for the current one
var clientConnections = NoteContext.ClientNotes var clientConnections = NoteContext.ClientNotes
.Where(c => c.Value == noteName && c.Key != Context.ConnectionId) .Where(c => c.Value == noteName && c.Key != Context.ConnectionId)
.Select(c => c.Key) .Select(c => c.Key)
.ToList(); .ToList();
// update note for all other clients // update note for all other clients
await Clients await Clients
.Clients(clientConnections) .Clients(clientConnections)
.SendAsync("updateNote", content); .SendAsync("updateNote", content);
logger.LogInformation($"Note \"{noteName}\" saved! Updated {clientConnections.Count} other client(s).");
}
catch (Exception ex)
{
logger.LogError($"Unable to save note \"{noteName}\" => {ex}");
}
} }
} }
} }

View File

@ -4,8 +4,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddEnvironmentVariables();
// Add services to the container. // Add services to the container.
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews();
builder.Services.AddSignalR(); builder.Services.AddSignalR();
@ -16,7 +14,6 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
o.LoginPath = "/login"; o.LoginPath = "/login";
o.Cookie.Name = "NotesUser"; o.Cookie.Name = "NotesUser";
o.Cookie.MaxAge = TimeSpan.FromDays(3); o.Cookie.MaxAge = TimeSpan.FromDays(3);
o.SlidingExpiration = true;
}); });
var app = builder.Build(); var app = builder.Build();

View File

@ -14,7 +14,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<WebPublishMethod>FileSystem</WebPublishMethod> <WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId> <_TargetId>Folder</_TargetId>
<SiteUrlToLaunchAfterPublish /> <SiteUrlToLaunchAfterPublish />
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ProjectGuid>bf137709-fcd2-4bb0-ade0-8fc71a244485</ProjectGuid> <ProjectGuid>bf137709-fcd2-4bb0-ade0-8fc71a244485</ProjectGuid>
<SelfContained>false</SelfContained> <SelfContained>false</SelfContained>

View File

@ -16,7 +16,7 @@
} }
} }
public string GetNote(string noteName) public string GetText(string noteName)
{ {
CheckFile(noteName); CheckFile(noteName);
@ -30,7 +30,7 @@
.ToList(); .ToList();
} }
public void SaveNote(string noteName, string content) public void SaveText(string content, string noteName)
{ {
File.WriteAllText(GetFilePath(noteName), content); File.WriteAllText(GetFilePath(noteName), content);
} }
@ -51,7 +51,7 @@
{ {
Directory.CreateDirectory(folderPath); Directory.CreateDirectory(folderPath);
SaveNote(noteName, "Hi! Feel free to start typing. Everything will be saved soon after you are done typing."); SaveText("Hi! Feel free to start typing. Everything will be saved soon after you are done typing.", noteName);
} }
} }

View File

@ -3,8 +3,8 @@
public interface INoteService public interface INoteService
{ {
ICollection<string> GetNoteNames(); ICollection<string> GetNoteNames();
string GetNote(string noteName); string GetText(string noteName);
void SaveNote(string noteName, string content); void SaveText(string content, string noteName);
void DeleteNote(string noteName); void DeleteNote(string noteName);
} }
} }

View File

@ -2,7 +2,6 @@
<form method="post"> <form method="post">
<input type="password" name="passphrase" placeholder="Passphrase" /> <input type="password" name="passphrase" placeholder="Passphrase" />
<button type="submit">Login</button>
</form> </form>
@section scripts { @section scripts {

View File

@ -19,7 +19,4 @@
<script> <script>
var noteName = '@Model.CurrentNote'; var noteName = '@Model.CurrentNote';
</script> </script>
<script src="~/lib/signalr/dist/browser/signalr.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
} }

View File

@ -8,13 +8,15 @@
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
</head> </head>
<body class="dark"> <body>
@RenderBody() @RenderBody()
<script src="~/lib/jquery/dist/jquery.min.js"></script>
@RenderSection("scripts", false) @RenderSection("scripts", false)
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/signalr/dist/browser/signalr.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</body> </body>
</html> </html>

View File

@ -28,19 +28,19 @@ div.note-names {
opacity: 0.5; opacity: 0.5;
} }
div.note-names a { div.note-names a {
color: #aaa; color: #666;
padding-left: 10px; padding-left: 10px;
text-decoration: none; text-decoration: none;
} }
div.note-names a.current { div.note-names a.current {
font-weight: bold; font-weight: bold;
} }
div .note-names a:not(:last-of-type) { div .note-names a:not(:last-of-type) {
border-right: 1px solid #666; border-right: 1px solid #666;
} }
textarea { textarea {
width: 100%; width: 100%;
@ -57,9 +57,7 @@ textarea {
border-width: 0; border-width: 0;
} }
body.dark, body.dark, body.dark input, body.dark textarea {
body.dark input,
body.dark textarea {
background-color: #222; background-color: #222;
color: #ddd; color: #ddd;
} }
@ -76,37 +74,24 @@ body.dark textarea {
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
} }
.toast.show { .toast.show {
bottom: 0; bottom: 0;
} }
.toast#saved-indicator { .toast#saved-indicator {
background-color: green; background-color: green;
} }
.toast#update-indicator { .toast#update-indicator {
background-color: orangered; background-color: orangered;
} }
form input[type=password], form input[type=password] {
button {
display: block; display: block;
width: 100%;
max-width: 300px;
margin: 20px auto; margin: 20px auto;
font-size: 20px; font-size: 20px;
padding: 8px; padding: 8px;
border: 1px solid #333; border: 1px solid #999;
border-radius: 8px; border-radius: 4px;
box-sizing: border-box;
}
form input[type=password] {
color: #999; color: #999;
} }
form button {
cursor: pointer;
color: #fff;
background-color: #009E60;
}

View File

@ -9,7 +9,7 @@ function start() {
console.log('Started websocket listener'); console.log('Started websocket listener');
}).catch(function (err) { }).catch(function (err) {
console.error(err.toString()); console.error(err.toString());
location.reload(); return alert('Connection error. Reload page.');
}); });
} }
@ -29,7 +29,7 @@ function saveContent($textarea) {
var content = $textarea.val(); var content = $textarea.val();
connection.invoke('SaveNote', noteName, content).then(function () { connection.invoke('SaveNote', content, noteName).then(function () {
showToast('#saved-indicator'); showToast('#saved-indicator');
}).catch(function (err) { }).catch(function (err) {
console.error(err.toString()); console.error(err.toString());
@ -63,6 +63,11 @@ $(function () {
}); });
}); });
// set dark mode
if (window.location.hash == '#dark') {
$('body').addClass('dark');
}
let timer = null; let timer = null;
const ignoredKeyCodes = [17, 18, 20, 27, 37, 38, 39, 40, 91]; const ignoredKeyCodes = [17, 18, 20, 27, 37, 38, 39, 40, 91];