feat: add closing and saving button to modal. Closes #15
CI / Build Backend (pull_request) Successful in 58s
CI / Build Frontend (pull_request) Successful in 7m11s

This commit is contained in:
2026-07-22 11:44:06 -03:00
parent a79e79c04f
commit 16621cd73b
5 changed files with 85 additions and 67 deletions
+10
View File
@@ -7,12 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## 2026-07-22
### Added
- Button to save notes from the preview modal. Closes [#15](https://lightroasted.vps-kinghost.net/rmcampos/tasknote/issues/15)
### Changed
- Removed deployments to staging in PR pipelines. PR only runs CI now. Closes [#14](https://lightroasted.vps-kinghost.net/rmcampos/tasknote/issues/14)
### Removed
- Old files from project and moved scripts to `tools` folder.
```bash
# Docker images
docker pull rmcampos/tasknote-app:app-v2026.07.22.?
```
## 2026-07-20
### Changed
-18
View File
@@ -1404,9 +1404,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1423,9 +1420,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1442,9 +1436,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1461,9 +1452,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1480,9 +1468,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1499,9 +1484,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
+22 -1
View File
@@ -10,6 +10,8 @@ type Props = {
title: string;
markdownText: string;
onHide: () => void;
onSave?: () => Promise<boolean>;
saveButtonLabel?: string;
};
/**
@@ -73,13 +75,18 @@ const ModalMarkdown: React.FC<Props> = (props: Props): React.ReactNode => {
)}
</Modal.Body>
<Modal.Footer className="d-flex flex-wrap gap-2 justify-content-end">
<Button variant="outline-secondary" onClick={handleHide}>
<Button
variant="outline-secondary"
onClick={handleHide}
className="modal-action-btn"
>
Close
</Button>
<Button
variant={showSource ? 'info' : 'outline-info'}
onClick={handleToggleSource}
data-testid="modal-source-button"
className="modal-action-btn"
>
Source
</Button>
@@ -87,9 +94,23 @@ const ModalMarkdown: React.FC<Props> = (props: Props): React.ReactNode => {
variant="outline-primary"
onClick={handleCopy}
data-testid="modal-copy-button"
className="modal-action-btn"
>
{copied ? 'Copied!' : 'Copy'}
</Button>
{props.onSave && (
<Button
variant="primary"
onClick={async () => {
handleHide();
await props.onSave!();
}}
data-testid="modal-save-button"
className="home-new-item modal-action-btn"
>
{props.saveButtonLabel ?? 'Save note'}
</Button>
)}
</Modal.Footer>
</Modal>
)
@@ -168,6 +168,11 @@
max-height: 60vh;
}
.modal-action-btn {
border-radius: 3px;
height: 48px;
}
@media (max-width: 576px) {
.markdown-source {
font-size: 12px;
+48 -48
View File
@@ -201,6 +201,51 @@ function NoteAdd(): React.ReactNode {
saveDraft(noteTitle, noteContent, noteUrl, newTags);
};
/**
* Saves the note, either adding a new one or editing an existing one.
*
* @returns {Promise<boolean>} True if the note was saved successfully, false otherwise.
*/
const saveNote = async (): Promise<boolean> => {
setValidated(true);
if (!noteTitle.trim() || !noteContent.trim()) {
setErrorMessage(translateServerResponse('Please fill in all the fields', i18n.language));
return false;
}
const finalTags = [...selectedTags];
if (currentTag.trim()) {
const normalized = currentTag.trim().toLowerCase();
if (!finalTags.includes(normalized)) {
finalTags.push(normalized);
}
}
const payload: NoteResponse = {
id: action === 'edit' ? noteId : 0,
title: noteTitle,
description: noteContent,
url: noteUrl,
tags: finalTags,
lastUpdate: '',
shared: false,
shareToken: null
};
const saved = action === 'add'
? await addNote(payload)
: await submitEditNote(payload);
if (saved) {
clearDraft();
resetInputs();
navigate('/home');
}
return saved;
};
/**
* Handles the form submission.
*
@@ -217,54 +262,7 @@ function NoteAdd(): React.ReactNode {
return;
}
const finalTags = [...selectedTags];
if (currentTag.trim()) {
const normalized = currentTag.trim().toLowerCase();
if (!finalTags.includes(normalized)) {
finalTags.push(normalized);
}
}
if (action === 'add') {
const payload: NoteResponse = {
id: 0,
title: noteTitle,
description: noteContent,
url: noteUrl,
tags: finalTags,
lastUpdate: '',
shared: false,
shareToken: null
};
const added: boolean = await addNote(payload);
if (added) {
clearDraft();
form.reset();
resetInputs();
navigate('/home');
}
}
else if (action === 'edit') {
const payload: NoteResponse = {
id: noteId,
title: noteTitle,
description: noteContent,
url: noteUrl,
tags: finalTags,
lastUpdate: '',
shared: false,
shareToken: null
};
const edited: boolean = await submitEditNote(payload);
if (edited) {
clearDraft();
form.reset();
resetInputs();
navigate('/home');
}
}
await saveNote();
};
/**
@@ -569,6 +567,8 @@ function NoteAdd(): React.ReactNode {
onHide={handleCloseModal}
title={noteTitle}
markdownText={noteContent}
onSave={saveNote}
saveButtonLabel={t('note_form_submit')}
/>
</Container>
);