feat: add closing and saving button to modal. Closes #15 (#16)
Frontend CD / build (push) Successful in 5m7s
Frontend CD / build (push) Successful in 5m7s
# What - Closes #15 # Why - Make users life easier.Reviewed-on: #16
This commit was merged in pull request #16.
This commit is contained in:
@@ -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
|
||||
|
||||
Generated
-18
@@ -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": [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user