Compare commits
36 Commits
dev/multip
...
dev/action
Author | SHA1 | Date | |
---|---|---|---|
|
ca75f88cd8 | ||
|
1279e16fad | ||
|
14e56285f5 | ||
11f468a5c1 | |||
d24065471b | |||
|
538ddeeb4f | ||
9a5a082f6d | |||
|
bf9e8c0c69 | ||
|
f35c2e499c | ||
|
769eabd9e4 | ||
|
d21a0e176a | ||
|
f69d19100d | ||
|
dabd5d6a50 | ||
|
1efa4b04d0 | ||
|
255b62a3b3 | ||
e5cb210df2 | |||
aa3accb412 | |||
|
e20e8d907f | ||
|
fe56bd903d | ||
6563838738 | |||
6f5413ce95 | |||
|
9309b7012e | ||
|
c38e4cd8bf | ||
|
b5a4ce68a5 | ||
|
66111b81fe | ||
|
be95f53647 | ||
|
3eaa5c1910 | ||
|
ca8344cbb2 | ||
|
3dbc7050de | ||
c307de995d | |||
|
2acec128e8 | ||
|
ca48d79f55 | ||
3a91b6db3c | |||
38287093b4 | |||
|
2e4903481c | ||
|
70b0d0cba3 |
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"dotnet-ef": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-ef"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -229,6 +229,9 @@ _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/
|
||||||
~$*
|
~$*
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
@@ -8,12 +8,9 @@ namespace BinaryDad.Notes.Controllers
|
|||||||
{
|
{
|
||||||
private readonly INoteService noteService;
|
private readonly INoteService noteService;
|
||||||
|
|
||||||
public ApiController(INoteService noteService)
|
public ApiController(INoteService noteService) => this.noteService = noteService;
|
||||||
{
|
|
||||||
this.noteService = noteService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("note/{noteName}")]
|
[Route("note/{noteName}")]
|
||||||
public string Note(string noteName) => noteService.GetText(noteName);
|
public string Note(string noteName) => noteService.GetNote(noteName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,13 @@ 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()
|
||||||
{
|
{
|
||||||
@@ -21,7 +28,7 @@ namespace BinaryDad.Notes.Controllers
|
|||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
var appPassphrase = Environment.GetEnvironmentVariable("APP_PASSPHRASE");
|
var appPassphrase = configuration["APP_PASSPHRASE"];
|
||||||
|
|
||||||
if (passphrase == appPassphrase)
|
if (passphrase == appPassphrase)
|
||||||
{
|
{
|
||||||
|
@@ -10,10 +10,7 @@ public class NoteController : Controller
|
|||||||
{
|
{
|
||||||
private readonly INoteService noteService;
|
private readonly INoteService noteService;
|
||||||
|
|
||||||
public NoteController(INoteService noteService)
|
public NoteController(INoteService noteService) => this.noteService = noteService;
|
||||||
{
|
|
||||||
this.noteService = noteService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("{noteName=default}")]
|
[Route("{noteName=default}")]
|
||||||
public IActionResult Index(string noteName)
|
public IActionResult Index(string noteName)
|
||||||
@@ -21,14 +18,22 @@ public class NoteController : Controller
|
|||||||
var model = new ContentModel
|
var model = new ContentModel
|
||||||
{
|
{
|
||||||
CurrentNote = noteName,
|
CurrentNote = noteName,
|
||||||
Text = noteService.GetText(noteName),
|
Text = noteService.GetNote(noteName),
|
||||||
NoteNames = noteService.GetNoteNames()
|
NoteNames = noteService.GetNoteNames()
|
||||||
};
|
};
|
||||||
|
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("{noteName}/delete")]
|
[HttpPost, Route("create")]
|
||||||
|
public IActionResult Create(string noteName)
|
||||||
|
{
|
||||||
|
noteService.SaveNote(noteName);
|
||||||
|
|
||||||
|
return Redirect($"/{noteName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("delete/{noteName}")]
|
||||||
public IActionResult Delete(string noteName)
|
public IActionResult Delete(string noteName)
|
||||||
{
|
{
|
||||||
noteService.DeleteNote(noteName);
|
noteService.DeleteNote(noteName);
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:8.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:7.0 AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN mkdir notes
|
||||||
COPY --from=build /app/publish .
|
COPY --from=build /app/publish .
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["dotnet", "BinaryDad.Notes.dll"]
|
ENTRYPOINT ["dotnet", "BinaryDad.Notes.dll"]
|
37
NoteHub.cs
37
NoteHub.cs
@@ -1,15 +1,19 @@
|
|||||||
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)
|
public NoteHub(INoteService noteService, ILogger<NoteHub> logger)
|
||||||
{
|
{
|
||||||
this.noteService = noteService;
|
this.noteService = noteService;
|
||||||
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task OnConnectedAsync()
|
public override Task OnConnectedAsync()
|
||||||
@@ -21,20 +25,29 @@ namespace BinaryDad.Notes
|
|||||||
return base.OnConnectedAsync();
|
return base.OnConnectedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveNote(string content, string? noteName)
|
public async Task SaveNote(string noteName, string content)
|
||||||
{
|
{
|
||||||
noteService.SaveText(content, noteName);
|
try
|
||||||
|
{
|
||||||
|
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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@ 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();
|
||||||
@@ -14,6 +16,7 @@ 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();
|
||||||
|
@@ -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>net7.0</TargetFramework>
|
<TargetFramework>net8.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>
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetText(string noteName)
|
public string GetNote(string noteName)
|
||||||
{
|
{
|
||||||
CheckFile(noteName);
|
CheckFile(noteName);
|
||||||
|
|
||||||
@@ -30,8 +30,13 @@
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveText(string content, string noteName)
|
public void SaveNote(string noteName, string? content = null)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(noteName))
|
||||||
|
{
|
||||||
|
content = "Hi! Feel free to start typing. Everything will be saved soon after you are done typing.";
|
||||||
|
}
|
||||||
|
|
||||||
File.WriteAllText(GetFilePath(noteName), content);
|
File.WriteAllText(GetFilePath(noteName), content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +56,7 @@
|
|||||||
{
|
{
|
||||||
Directory.CreateDirectory(folderPath);
|
Directory.CreateDirectory(folderPath);
|
||||||
|
|
||||||
SaveText("Hi! Feel free to start typing. Everything will be saved soon after you are done typing.", noteName);
|
SaveNote(noteName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,8 +3,8 @@
|
|||||||
public interface INoteService
|
public interface INoteService
|
||||||
{
|
{
|
||||||
ICollection<string> GetNoteNames();
|
ICollection<string> GetNoteNames();
|
||||||
string GetText(string noteName);
|
string GetNote(string noteName);
|
||||||
void SaveText(string content, string noteName);
|
void SaveNote(string noteName, string? content = null);
|
||||||
void DeleteNote(string noteName);
|
void DeleteNote(string noteName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
@Html.ValidationSummary()
|
@Html.ValidationSummary()
|
||||||
|
|
||||||
<form method="post">
|
<form method="post" class="login-form">
|
||||||
<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 {
|
||||||
|
@@ -2,13 +2,20 @@
|
|||||||
|
|
||||||
<textarea id="content" name="content" spellcheck="false">@Model.Text</textarea>
|
<textarea id="content" name="content" spellcheck="false">@Model.Text</textarea>
|
||||||
|
|
||||||
<div class="note-names">
|
<div class="note-actions">
|
||||||
@foreach (var note in Model.NoteNames.Order())
|
<select id="note-dropdown" class="note-dropdown">
|
||||||
{
|
@foreach (var note in Model.NoteNames.Order())
|
||||||
var css = note.Equals(Model.CurrentNote, StringComparison.OrdinalIgnoreCase) ? "current" : null;
|
{
|
||||||
|
var selected = note.Equals(Model.CurrentNote, StringComparison.OrdinalIgnoreCase) ? "selected" : null;
|
||||||
|
<option value="@note" selected="@selected">@note</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
|
||||||
<a href="@note" class="@css">@note</a>
|
<a asp-action="Delete" asp-controller="Note" asp-route-noteName="@Model.CurrentNote" class="btn-symbol btn-delete" title="Delete Note">✕</a>
|
||||||
}
|
<form method="post" class="action-form" asp-action="Create" asp-controller="Note">
|
||||||
|
<input type="text" name="noteName" placeholder="Note name" />
|
||||||
|
<button type="submit" class="btn-symbol btn-create" title="Create Note">💾</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toast" id="saved-indicator">Saved</div>
|
<div class="toast" id="saved-indicator">Saved</div>
|
||||||
@@ -19,4 +26,7 @@
|
|||||||
<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>
|
||||||
}
|
}
|
||||||
|
@@ -8,14 +8,12 @@
|
|||||||
<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>
|
<body class="dark">
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
|
|
||||||
@RenderSection("scripts", false)
|
|
||||||
|
|
||||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
<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>
|
@RenderSection("scripts", false)
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@@ -20,27 +20,126 @@ body {
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.note-names {
|
div.note-actions {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
left: 0;
|
left: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
opacity: 0.5;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.note-names a {
|
select.note-dropdown {
|
||||||
color: #666;
|
padding: 6px 8px;
|
||||||
padding-left: 10px;
|
border-radius: 5px;
|
||||||
text-decoration: none;
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark select.note-dropdown {
|
||||||
|
background-color: #333;
|
||||||
|
color: #ddd;
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.action-form {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.action-form input[type="text"] {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 100px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark form.action-form input[type="text"] {
|
||||||
|
background-color: #333;
|
||||||
|
color: #ddd;
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.action-form input[type="text"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #009E60;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 158, 96, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-symbol {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create {
|
||||||
|
background-color: #009E60;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #009E60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create:hover {
|
||||||
|
background-color: #007a4d;
|
||||||
|
border-color: #006b42;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile responsiveness */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
div.note-actions {
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.note-names a.current {
|
select.note-dropdown {
|
||||||
font-weight: bold;
|
min-width: 100px;
|
||||||
}
|
max-width: 120px;
|
||||||
|
font-size: 12px;
|
||||||
div .note-names a:not(:last-of-type) {
|
padding: 5px 6px;
|
||||||
border-right: 1px solid #666;
|
}
|
||||||
}
|
|
||||||
|
form.action-form input[type="text"] {
|
||||||
|
width: 80px;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 5px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-symbol {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -49,7 +148,7 @@ textarea {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
color: #444;
|
color: #444;
|
||||||
font-size: 12px;
|
font-size: 16px;
|
||||||
font-family: Consolas, 'Courier New', monospace;
|
font-family: Consolas, 'Courier New', monospace;
|
||||||
outline: none;
|
outline: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -57,7 +156,9 @@ textarea {
|
|||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark, body.dark input, body.dark textarea {
|
body.dark,
|
||||||
|
body.dark input,
|
||||||
|
body.dark textarea {
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
@@ -69,29 +170,41 @@ body.dark, body.dark input, body.dark textarea {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
transition: bottom 0.3s;
|
transition: bottom 0.3s, opacity 0.3s;
|
||||||
opacity: 0.8;
|
opacity: 0;
|
||||||
border-radius: 5px 5px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.show {
|
.toast.show {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.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.login-form input[type=password],
|
||||||
|
form.login-form 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 #999;
|
border: 1px solid #333;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login-form input[type=password] {
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button, select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@@ -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());
|
||||||
return alert('Connection error. Reload page.');
|
location.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ function saveContent($textarea) {
|
|||||||
|
|
||||||
var content = $textarea.val();
|
var content = $textarea.val();
|
||||||
|
|
||||||
connection.invoke('SaveNote', content, noteName).then(function () {
|
connection.invoke('SaveNote', noteName, content).then(function () {
|
||||||
showToast('#saved-indicator');
|
showToast('#saved-indicator');
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
console.error(err.toString());
|
console.error(err.toString());
|
||||||
@@ -47,6 +47,14 @@ $(function () {
|
|||||||
// set focus on load
|
// set focus on load
|
||||||
$textarea.focus();
|
$textarea.focus();
|
||||||
|
|
||||||
|
// handle note dropdown change
|
||||||
|
$('#note-dropdown').change(function () {
|
||||||
|
var selectedNote = $(this).val();
|
||||||
|
if (selectedNote && selectedNote !== noteName) {
|
||||||
|
window.location.href = selectedNote;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// update content upon sync save
|
// update content upon sync save
|
||||||
connection.on('updateNote', function (content) {
|
connection.on('updateNote', function (content) {
|
||||||
$textarea.val(content);
|
$textarea.val(content);
|
||||||
@@ -63,11 +71,6 @@ $(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];
|
||||||
|
Reference in New Issue
Block a user