fix: use modal forms for notes
This commit is contained in:
parent
99172944a1
commit
c83c15bced
|
|
@ -43,14 +43,16 @@
|
||||||
'.notes-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--vt-color-text-muted,#7f8aa3);font-size:.85rem;padding:2rem;gap:.5rem;text-align:center}',
|
'.notes-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--vt-color-text-muted,#7f8aa3);font-size:.85rem;padding:2rem;gap:.5rem;text-align:center}',
|
||||||
'.notes-empty-hint{font-size:.75rem;color:var(--vt-color-text-muted,#7f8aa3)}',
|
'.notes-empty-hint{font-size:.75rem;color:var(--vt-color-text-muted,#7f8aa3)}',
|
||||||
'.notes-error{flex:1;display:flex;align-items:center;justify-content:center;color:var(--vt-color-danger,#e94560);padding:1rem;font-size:.85rem}',
|
'.notes-error{flex:1;display:flex;align-items:center;justify-content:center;color:var(--vt-color-danger,#e94560);padding:1rem;font-size:.85rem}',
|
||||||
'.notes-panel{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border-top:1px solid var(--vt-color-border,#202b46);flex-shrink:0;background:var(--vt-color-surface-muted,#111629)}',
|
|
||||||
'.notes-input{flex:1;font-size:.78rem;padding:.32rem .5rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-sm,4px);background:#0f1424;color:var(--vt-color-text-primary,#f4f7fb);outline:none;min-width:120px}',
|
|
||||||
'.notes-input:focus{border-color:var(--vt-color-accent,#4ecca3);box-shadow:var(--vt-focus-ring,0 0 0 2px rgba(78,204,163,.34))}',
|
|
||||||
'.notes-title-bar{padding:.4rem .75rem;font-size:.72rem;color:var(--vt-color-text-muted,#7f8aa3);background:var(--vt-color-surface-muted,#111629);border-bottom:1px solid var(--vt-color-border,#202b46);text-transform:uppercase;letter-spacing:.04em}',
|
'.notes-title-bar{padding:.4rem .75rem;font-size:.72rem;color:var(--vt-color-text-muted,#7f8aa3);background:var(--vt-color-surface-muted,#111629);border-bottom:1px solid var(--vt-color-border,#202b46);text-transform:uppercase;letter-spacing:.04em}',
|
||||||
'.notes-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:10000;display:flex;align-items:center;justify-content:center}',
|
'.notes-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:10000;display:flex;align-items:center;justify-content:center}',
|
||||||
'.notes-modal{width:380px;max-width:90vw;padding:20px;background:#1a1a2e;border:1px solid #333;border-radius:10px;color:#e0e0e0;font-family:inherit;box-shadow:0 12px 40px rgba(0,0,0,.5)}',
|
'.notes-modal{width:380px;max-width:90vw;padding:20px;background:#1a1a2e;border:1px solid #333;border-radius:10px;color:#e0e0e0;font-family:inherit;box-shadow:0 12px 40px rgba(0,0,0,.5)}',
|
||||||
'.notes-modal-title{font-size:.9rem;font-weight:600;margin-bottom:12px}',
|
'.notes-modal-title{font-size:.9rem;font-weight:600;margin-bottom:12px}',
|
||||||
'.notes-modal-msg{font-size:.82rem;color:#aaa;margin-bottom:16px;word-wrap:break-word}',
|
'.notes-modal-msg{font-size:.82rem;color:#aaa;margin-bottom:16px;word-wrap:break-word}',
|
||||||
|
'.notes-modal-form{display:grid;gap:.55rem}',
|
||||||
|
'.notes-modal-input{width:100%;font:inherit;font-size:.84rem;padding:.5rem .6rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-sm,4px);background:#0f1424;color:var(--vt-color-text-primary,#f4f7fb);outline:none}',
|
||||||
|
'.notes-modal-input:focus{border-color:var(--vt-color-accent,#4ecca3);box-shadow:var(--vt-focus-ring,0 0 0 2px rgba(78,204,163,.34))}',
|
||||||
|
'.notes-modal-input[aria-invalid="true"]{border-color:var(--vt-color-danger,#e94560)}',
|
||||||
|
'.notes-modal-error{display:none;font-size:.78rem;line-height:1.35;color:#ffc6ce}',
|
||||||
'.notes-modal-actions{display:flex;justify-content:flex-end;gap:8px}',
|
'.notes-modal-actions{display:flex;justify-content:flex-end;gap:8px}',
|
||||||
'.notes-modal-btn{font-size:.8rem;padding:.35rem .9rem;border:1px solid #333;border-radius:5px;cursor:pointer;font-family:inherit}',
|
'.notes-modal-btn{font-size:.8rem;padding:.35rem .9rem;border:1px solid #333;border-radius:5px;cursor:pointer;font-family:inherit}',
|
||||||
'.notes-modal-btn.cancel{background:#2a2a4e;color:#ccc}',
|
'.notes-modal-btn.cancel{background:#2a2a4e;color:#ccc}',
|
||||||
|
|
@ -171,7 +173,7 @@
|
||||||
|
|
||||||
var workspaceNode = props && props.workspaceNode;
|
var workspaceNode = props && props.workspaceNode;
|
||||||
var workspaceRoot = (workspaceNode && (workspaceNode.rootPath || workspaceNode.name || workspaceNode.id)) || '';
|
var workspaceRoot = (workspaceNode && (workspaceNode.rootPath || workspaceNode.name || workspaceNode.id)) || '';
|
||||||
var workspaceName = workspaceRoot || (workspaceNode && (workspaceNode.name || workspaceNode.title)) || 'Workspace';
|
var workspaceName = workspaceRoot || (workspaceNode && (workspaceNode.name || workspaceNode.title)) || '';
|
||||||
|
|
||||||
var notes = [];
|
var notes = [];
|
||||||
var selectedPath = '';
|
var selectedPath = '';
|
||||||
|
|
@ -287,23 +289,12 @@
|
||||||
var listContainer = el('div', { className: 'notes-list', 'data-notes-list': '' });
|
var listContainer = el('div', { className: 'notes-list', 'data-notes-list': '' });
|
||||||
containerEl.appendChild(listContainer);
|
containerEl.appendChild(listContainer);
|
||||||
|
|
||||||
var createPanel = el('div', { className: 'notes-panel', style: { display: 'none' } });
|
var createModal = null;
|
||||||
var createInput = el('input', { className: 'notes-input', 'data-notes-create-input': '', placeholder: tr('ui.noteTitle', null, 'Note title') });
|
var createInput = null;
|
||||||
var createConfirm = el('button', { className: 'notes-btn', textContent: tr('ui.create', null, 'Create') });
|
var createError = null;
|
||||||
var createCancel = el('button', { className: 'notes-btn', textContent: tr('ui.cancel', null, 'Cancel') });
|
var renameModal = null;
|
||||||
createPanel.appendChild(createInput);
|
var renameInput = null;
|
||||||
createPanel.appendChild(createConfirm);
|
var renameError = null;
|
||||||
createPanel.appendChild(createCancel);
|
|
||||||
containerEl.appendChild(createPanel);
|
|
||||||
|
|
||||||
var renamePanel = el('div', { className: 'notes-panel', style: { display: 'none' } });
|
|
||||||
var renameInput = el('input', { className: 'notes-input', 'data-notes-rename-input': '', placeholder: tr('ui.newTitle', null, 'New title') });
|
|
||||||
var renameConfirm = el('button', { className: 'notes-btn', textContent: tr('ui.rename', null, 'Rename') });
|
|
||||||
var renameCancel = el('button', { className: 'notes-btn', textContent: tr('ui.cancel', null, 'Cancel') });
|
|
||||||
renamePanel.appendChild(renameInput);
|
|
||||||
renamePanel.appendChild(renameConfirm);
|
|
||||||
renamePanel.appendChild(renameCancel);
|
|
||||||
containerEl.appendChild(renamePanel);
|
|
||||||
|
|
||||||
// ─── Core Functions ─────────────────────────────────────
|
// ─── Core Functions ─────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -495,28 +486,134 @@
|
||||||
}).catch(function (err) { console.error('[notes] openResource:', err); });
|
}).catch(function (err) { console.error('[notes] openResource:', err); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function conflictMessage(title, existingPath) {
|
||||||
|
return [
|
||||||
|
tr('ui.conflictMessage', { title: title }, 'A note with the title "' + title + '" already exists.'),
|
||||||
|
existingPath ? tr('ui.existingFile', { path: existingPath }, ' Existing file: ' + existingPath + '.') : '',
|
||||||
|
tr('ui.chooseDifferent', null, ' Please choose a different title.')
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNoteFormModal(mode, note) {
|
||||||
|
var isRename = mode === 'rename';
|
||||||
|
var modalTitle = isRename
|
||||||
|
? tr('ui.renameTitle', null, 'Rename note')
|
||||||
|
: tr('ui.createTitle', null, 'Create note');
|
||||||
|
var overlay = el('div', {
|
||||||
|
className: 'notes-modal-overlay',
|
||||||
|
role: 'presentation'
|
||||||
|
});
|
||||||
|
overlay.setAttribute('data-notes-' + mode + '-modal', '');
|
||||||
|
var input = el('input', {
|
||||||
|
className: 'notes-modal-input',
|
||||||
|
type: 'text',
|
||||||
|
value: isRename ? (note.title || fileName(note.path)) : '',
|
||||||
|
placeholder: isRename
|
||||||
|
? tr('ui.newTitle', null, 'New title')
|
||||||
|
: tr('ui.noteTitle', null, 'Note title'),
|
||||||
|
autocomplete: 'off',
|
||||||
|
'aria-invalid': 'false'
|
||||||
|
});
|
||||||
|
input.setAttribute('data-notes-' + mode + '-input', '');
|
||||||
|
input.value = isRename ? (note.title || fileName(note.path)) : '';
|
||||||
|
var error = el('div', {
|
||||||
|
className: 'notes-modal-error',
|
||||||
|
role: 'alert'
|
||||||
|
});
|
||||||
|
error.setAttribute('data-notes-' + mode + '-error', '');
|
||||||
|
var confirm = el('button', {
|
||||||
|
className: 'notes-modal-btn confirm',
|
||||||
|
type: 'button',
|
||||||
|
textContent: isRename ? tr('ui.rename', null, 'Rename') : tr('ui.create', null, 'Create'),
|
||||||
|
onClick: function () {
|
||||||
|
if (isRename) confirmRename();
|
||||||
|
else confirmCreate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var cancel = el('button', {
|
||||||
|
className: 'notes-modal-btn cancel',
|
||||||
|
type: 'button',
|
||||||
|
textContent: tr('ui.cancel', null, 'Cancel'),
|
||||||
|
onClick: function () {
|
||||||
|
if (isRename) hideRename();
|
||||||
|
else hideCreate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function handleFormKeydown(event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
if (event.preventDefault) event.preventDefault();
|
||||||
|
if (isRename) confirmRename();
|
||||||
|
else confirmCreate();
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
if (event.preventDefault) event.preventDefault();
|
||||||
|
if (isRename) hideRename();
|
||||||
|
else hideCreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.addEventListener('keydown', handleFormKeydown);
|
||||||
|
var modal = el('div', {
|
||||||
|
className: 'notes-modal notes-modal-form',
|
||||||
|
role: 'dialog',
|
||||||
|
'aria-modal': 'true',
|
||||||
|
'aria-label': modalTitle
|
||||||
|
}, [
|
||||||
|
el('div', { className: 'notes-modal-title', textContent: modalTitle }),
|
||||||
|
input,
|
||||||
|
error,
|
||||||
|
el('div', { className: 'notes-modal-actions' }, [cancel, confirm])
|
||||||
|
]);
|
||||||
|
overlay.appendChild(modal);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
if (isRename) {
|
||||||
|
renameInput = input;
|
||||||
|
renameError = error;
|
||||||
|
} else {
|
||||||
|
createInput = input;
|
||||||
|
createError = error;
|
||||||
|
}
|
||||||
|
input.focus();
|
||||||
|
if (isRename && input.select) input.select();
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Create ────────────────────────────────────────────
|
// ─── Create ────────────────────────────────────────────
|
||||||
|
|
||||||
function showCreate() {
|
function showCreate() {
|
||||||
createInput.value = '';
|
hideCreate();
|
||||||
createPanel.style.display = 'flex';
|
createModal = showNoteFormModal('create');
|
||||||
createInput.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideCreate() {
|
function hideCreate() {
|
||||||
createPanel.style.display = 'none';
|
if (createModal) createModal.remove();
|
||||||
|
createModal = null;
|
||||||
|
createInput = null;
|
||||||
|
createError = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCreateError(message) {
|
||||||
|
if (!createError || !createInput) return;
|
||||||
|
createError.textContent = message || '';
|
||||||
|
createError.style.display = message ? 'block' : 'none';
|
||||||
|
createInput.setAttribute('aria-invalid', message ? 'true' : 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmCreate() {
|
function confirmCreate() {
|
||||||
|
if (!createInput) return;
|
||||||
var title = createInput.value.trim();
|
var title = createInput.value.trim();
|
||||||
if (!title) return;
|
if (!title) {
|
||||||
|
setCreateError(tr('ui.titleRequired', null, 'Enter a note title.'));
|
||||||
|
createInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setStatus(tr('ui.creating', null, 'Creating note...'), 'loading');
|
setStatus(tr('ui.creating', null, 'Creating note...'), 'loading');
|
||||||
var parent = notesParent();
|
var parent = notesParent();
|
||||||
createNote(parent, title).then(function (data) {
|
createNote(parent, title).then(function (data) {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
data = data || {};
|
data = data || {};
|
||||||
if (data.conflict) {
|
if (data.conflict) {
|
||||||
showConflictModal(title, data.path, createInput);
|
setCreateError(conflictMessage(title, data.path));
|
||||||
|
createInput.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideCreate();
|
hideCreate();
|
||||||
|
|
@ -539,42 +636,55 @@
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus(userFacingError('ui.createError', 'Could not create the note. Please try again.', err), 'error');
|
setCreateError(userFacingError('ui.createError', 'Could not create the note. Please try again.', err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Rename ─────────────────────────────────────────────
|
// ─── Rename ─────────────────────────────────────────────
|
||||||
|
|
||||||
function beginRename(note) {
|
function beginRename(note) {
|
||||||
|
hideRename();
|
||||||
renameTarget = note;
|
renameTarget = note;
|
||||||
renameInput.value = note.title || fileName(note.path);
|
renameModal = showNoteFormModal('rename', note);
|
||||||
renamePanel.style.display = 'flex';
|
|
||||||
renameInput.focus();
|
|
||||||
renameInput.select();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideRename() {
|
function hideRename() {
|
||||||
renameTarget = null;
|
renameTarget = null;
|
||||||
renamePanel.style.display = 'none';
|
if (renameModal) renameModal.remove();
|
||||||
|
renameModal = null;
|
||||||
|
renameInput = null;
|
||||||
|
renameError = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRenameError(message) {
|
||||||
|
if (!renameError || !renameInput) return;
|
||||||
|
renameError.textContent = message || '';
|
||||||
|
renameError.style.display = message ? 'block' : 'none';
|
||||||
|
renameInput.setAttribute('aria-invalid', message ? 'true' : 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmRename() {
|
function confirmRename() {
|
||||||
if (!renameTarget) return;
|
if (!renameTarget || !renameInput) return;
|
||||||
var newTitle = renameInput.value.trim();
|
var newTitle = renameInput.value.trim();
|
||||||
if (!newTitle) return;
|
if (!newTitle) {
|
||||||
|
setRenameError(tr('ui.titleRequired', null, 'Enter a note title.'));
|
||||||
|
renameInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setStatus(tr('ui.renaming', null, 'Renaming...'), 'loading');
|
setStatus(tr('ui.renaming', null, 'Renaming...'), 'loading');
|
||||||
renameNote(renameTarget.path, newTitle).then(function (data) {
|
renameNote(renameTarget.path, newTitle).then(function (data) {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
data = data || {};
|
data = data || {};
|
||||||
if (data.conflict) {
|
if (data.conflict) {
|
||||||
showConflictModal(newTitle, data.path, renameInput);
|
setRenameError(conflictMessage(newTitle, data.path));
|
||||||
|
renameInput.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideRename();
|
hideRename();
|
||||||
setStatus(tr('ui.renamed', null, 'Note renamed'), 'success');
|
setStatus(tr('ui.renamed', null, 'Note renamed'), 'success');
|
||||||
loadNotes();
|
loadNotes();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus(userFacingError('ui.renameError', 'Could not rename the note. Please try again.', err), 'error');
|
setRenameError(userFacingError('ui.renameError', 'Could not rename the note. Please try again.', err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,25 +706,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Conflict Modal ─────────────────────────────────────
|
|
||||||
|
|
||||||
function showConflictModal(title, existingPath, focusTarget) {
|
|
||||||
var overlay = el('div', { className: 'notes-modal-overlay' });
|
|
||||||
var modal = el('div', { className: 'notes-modal' }, [
|
|
||||||
el('div', { className: 'notes-modal-title' }, [tr('ui.conflictTitle', null, 'Name Conflict')]),
|
|
||||||
el('div', { className: 'notes-modal-msg' }, [
|
|
||||||
tr('ui.conflictMessage', { title: title }, 'A note with the title "' + title + '" already exists.'),
|
|
||||||
existingPath ? tr('ui.existingFile', { path: existingPath }, ' Existing file: ' + existingPath + '.') : '',
|
|
||||||
tr('ui.chooseDifferent', null, ' Please choose a different title.')
|
|
||||||
].join('')),
|
|
||||||
el('div', { className: 'notes-modal-actions' }, [
|
|
||||||
el('button', { className: 'notes-modal-btn confirm', textContent: 'OK', onClick: function () { overlay.remove(); (focusTarget || createInput).focus(); } })
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
overlay.appendChild(modal);
|
|
||||||
document.body.appendChild(overlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTrashModal(note) {
|
function showTrashModal(note) {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
var overlay = el('div', { className: 'notes-modal-overlay' });
|
var overlay = el('div', { className: 'notes-modal-overlay' });
|
||||||
|
|
@ -648,18 +739,6 @@
|
||||||
sortMode = sortSelect.value || 'title-asc';
|
sortMode = sortSelect.value || 'title-asc';
|
||||||
renderList();
|
renderList();
|
||||||
});
|
});
|
||||||
createConfirm.addEventListener('click', confirmCreate);
|
|
||||||
createCancel.addEventListener('click', hideCreate);
|
|
||||||
renameConfirm.addEventListener('click', confirmRename);
|
|
||||||
renameCancel.addEventListener('click', hideRename);
|
|
||||||
createInput.addEventListener('keydown', function (e) {
|
|
||||||
if (e.key === 'Enter') confirmCreate();
|
|
||||||
if (e.key === 'Escape') hideCreate();
|
|
||||||
});
|
|
||||||
renameInput.addEventListener('keydown', function (e) {
|
|
||||||
if (e.key === 'Enter') confirmRename();
|
|
||||||
if (e.key === 'Escape') hideRename();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ─── Init ───────────────────────────────────────────────
|
// ─── Init ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -672,12 +751,6 @@
|
||||||
createBtn.innerHTML = iconSvg('add') + ' ' + tr('ui.newNote', null, 'New Note');
|
createBtn.innerHTML = iconSvg('add') + ' ' + tr('ui.newNote', null, 'New Note');
|
||||||
filterInput.setAttribute('placeholder', tr('ui.filter', null, 'Filter notes'));
|
filterInput.setAttribute('placeholder', tr('ui.filter', null, 'Filter notes'));
|
||||||
titleBar.textContent = tr('ui.title', { workspace: workspaceName }, 'Notes in ' + workspaceName);
|
titleBar.textContent = tr('ui.title', { workspace: workspaceName }, 'Notes in ' + workspaceName);
|
||||||
createInput.setAttribute('placeholder', tr('ui.noteTitle', null, 'Note title'));
|
|
||||||
renameInput.setAttribute('placeholder', tr('ui.newTitle', null, 'New title'));
|
|
||||||
createConfirm.textContent = tr('ui.create', null, 'Create');
|
|
||||||
createCancel.textContent = tr('ui.cancel', null, 'Cancel');
|
|
||||||
renameConfirm.textContent = tr('ui.rename', null, 'Rename');
|
|
||||||
renameCancel.textContent = tr('ui.cancel', null, 'Cancel');
|
|
||||||
renderList();
|
renderList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -696,6 +769,8 @@
|
||||||
|
|
||||||
containerEl.__notesCleanup = function () {
|
containerEl.__notesCleanup = function () {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
hideCreate();
|
||||||
|
hideRename();
|
||||||
if (typeof localeUnsubscribe === 'function') localeUnsubscribe();
|
if (typeof localeUnsubscribe === 'function') localeUnsubscribe();
|
||||||
if (typeof fileChangedUnsubscribe === 'function') fileChangedUnsubscribe();
|
if (typeof fileChangedUnsubscribe === 'function') fileChangedUnsubscribe();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
{
|
{
|
||||||
"manifest.name": "Notes",
|
"manifest.name": "Notes",
|
||||||
"manifest.description": "Workspace-scoped notes manager for Markdown files in canonical Notes folders with create, rename, trash, and Workbench integration.",
|
"manifest.description": "Deal-scoped notes manager for Markdown files in canonical Notes folders with create, rename, trash, and Workbench integration.",
|
||||||
"contributions.workspaceItems.verstak.notes.workspace.title": "Notes",
|
"contributions.workspaceItems.verstak.notes.workspace.title": "Notes",
|
||||||
"ui.newNote": "New Note",
|
"ui.newNote": "New Note",
|
||||||
"ui.filter": "Filter notes",
|
"ui.filter": "Filter notes",
|
||||||
"ui.title": "Notes in {workspace}",
|
"ui.title": "Notes in {workspace}",
|
||||||
"ui.noteTitle": "Note title",
|
"ui.noteTitle": "Note title",
|
||||||
|
"ui.createTitle": "Create note",
|
||||||
|
"ui.renameTitle": "Rename note",
|
||||||
|
"ui.titleRequired": "Enter a note title.",
|
||||||
"ui.create": "Create",
|
"ui.create": "Create",
|
||||||
"ui.cancel": "Cancel",
|
"ui.cancel": "Cancel",
|
||||||
"ui.newTitle": "New title",
|
"ui.newTitle": "New title",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
{
|
{
|
||||||
"manifest.name": "Заметки",
|
"manifest.name": "Заметки",
|
||||||
"manifest.description": "Менеджер Markdown-заметок рабочего пространства с созданием, переименованием и корзиной.",
|
"manifest.description": "Менеджер Markdown-заметок Дела с созданием, переименованием и корзиной.",
|
||||||
"contributions.workspaceItems.verstak.notes.workspace.title": "Заметки",
|
"contributions.workspaceItems.verstak.notes.workspace.title": "Заметки",
|
||||||
"ui.newNote": "Новая заметка",
|
"ui.newNote": "Новая заметка",
|
||||||
"ui.filter": "Фильтр заметок",
|
"ui.filter": "Фильтр заметок",
|
||||||
"ui.title": "Заметки · {workspace}",
|
"ui.title": "Заметки · {workspace}",
|
||||||
"ui.noteTitle": "Название заметки",
|
"ui.noteTitle": "Название заметки",
|
||||||
|
"ui.createTitle": "Создать заметку",
|
||||||
|
"ui.renameTitle": "Переименовать заметку",
|
||||||
|
"ui.titleRequired": "Введите название заметки.",
|
||||||
"ui.create": "Создать",
|
"ui.create": "Создать",
|
||||||
"ui.cancel": "Отмена",
|
"ui.cancel": "Отмена",
|
||||||
"ui.newTitle": "Новое название",
|
"ui.newTitle": "Новое название",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"name": "Notes",
|
"name": "Notes",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"apiVersion": "0.1.0",
|
"apiVersion": "0.1.0",
|
||||||
"description": "Workspace-scoped notes manager for Markdown files in canonical Notes folders with create, rename, trash, and Workbench integration.",
|
"description": "Deal-scoped notes manager for Markdown files in canonical Notes folders with create, rename, trash, and Workbench integration.",
|
||||||
"localization": { "defaultLocale": "en", "locales": { "en": "locales/en.json", "ru": "locales/ru.json" } },
|
"localization": { "defaultLocale": "en", "locales": { "en": "locales/en.json", "ru": "locales/ru.json" } },
|
||||||
"source": "official",
|
"source": "official",
|
||||||
"icon": "edit",
|
"icon": "edit",
|
||||||
|
|
|
||||||
|
|
@ -264,12 +264,19 @@ async function mountNotes(api) {
|
||||||
const createButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-action') === 'create');
|
const createButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-action') === 'create');
|
||||||
if (!createButton) throw new Error('create button not found');
|
if (!createButton) throw new Error('create button not found');
|
||||||
createButton.click();
|
createButton.click();
|
||||||
const input = walk(container, (node) => node.getAttribute && node.getAttribute('data-notes-create-input') !== undefined);
|
const createModal = walk(document.body, (node) => node.getAttribute && node.getAttribute('data-notes-create-modal') !== undefined);
|
||||||
|
if (!createModal) throw new Error('create modal not found');
|
||||||
|
const input = walk(createModal, (node) => node.getAttribute && node.getAttribute('data-notes-create-input') !== undefined);
|
||||||
if (!input) throw new Error('create input not found');
|
if (!input) throw new Error('create input not found');
|
||||||
input.value = 'First Note';
|
const confirm = walk(createModal, (node) => node.tagName === 'BUTTON' && node.textContent === 'Create');
|
||||||
const confirm = walk(container, (node) => node.tagName === 'BUTTON' && node.textContent === 'Create');
|
|
||||||
if (!confirm) throw new Error('create confirm button not found');
|
if (!confirm) throw new Error('create confirm button not found');
|
||||||
confirm.click();
|
confirm.click();
|
||||||
|
const createError = walk(createModal, (node) => node.getAttribute && node.getAttribute('data-notes-create-error') !== undefined);
|
||||||
|
if (!createError || !createError.textContent.includes('Enter a note title') || input.getAttribute('aria-invalid') !== 'true') {
|
||||||
|
throw new Error('create modal does not show a validation error for an empty title');
|
||||||
|
}
|
||||||
|
input.value = 'First Note';
|
||||||
|
input.dispatchEvent('keydown', { key: 'Enter' });
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const created = createApi.entries.get('Project/Notes/First_Note.md');
|
const created = createApi.entries.get('Project/Notes/First_Note.md');
|
||||||
|
|
@ -319,21 +326,25 @@ async function mountNotes(api) {
|
||||||
const renameButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-note-action') === 'rename');
|
const renameButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-note-action') === 'rename');
|
||||||
if (!renameButton) throw new Error('rename note button not found');
|
if (!renameButton) throw new Error('rename note button not found');
|
||||||
renameButton.click();
|
renameButton.click();
|
||||||
const renameInput = walk(container, (node) => node.getAttribute && node.getAttribute('data-notes-rename-input') !== undefined);
|
const renameModal = walk(document.body, (node) => node.getAttribute && node.getAttribute('data-notes-rename-modal') !== undefined);
|
||||||
|
if (!renameModal) throw new Error('rename modal not found');
|
||||||
|
const renameInput = walk(renameModal, (node) => node.getAttribute && node.getAttribute('data-notes-rename-input') !== undefined);
|
||||||
if (!renameInput) throw new Error('rename input not found');
|
if (!renameInput) throw new Error('rename input not found');
|
||||||
renameInput.value = 'Second Note';
|
renameInput.value = 'Second Note';
|
||||||
const renameConfirm = walk(container, (node) => node.tagName === 'BUTTON' && node.textContent === 'Rename');
|
renameInput.dispatchEvent('keydown', { key: 'Enter' });
|
||||||
if (!renameConfirm) throw new Error('rename confirm button not found');
|
|
||||||
renameConfirm.click();
|
|
||||||
await flush();
|
await flush();
|
||||||
const conflictModal = walk(document.body, (node) => node.className === 'notes-modal-msg');
|
const renameError = walk(renameModal, (node) => node.getAttribute && node.getAttribute('data-notes-rename-error') !== undefined);
|
||||||
if (!conflictModal || !conflictModal.textContent.includes('Project/Notes/Second_Note.md')) {
|
if (!renameError || !renameError.textContent.includes('Project/Notes/Second_Note.md')) {
|
||||||
throw new Error(`rename conflict modal should include existing path, got ${conflictModal && conflictModal.textContent}`);
|
throw new Error(`rename conflict should stay in the form modal, got ${renameError && renameError.textContent}`);
|
||||||
}
|
}
|
||||||
const conflictOk = walk(document.body, (node) => node.tagName === 'BUTTON' && node.textContent === 'OK');
|
if (walk(document.body, (node) => node.className === 'notes-modal-msg')) {
|
||||||
if (!conflictOk) throw new Error('rename conflict OK button not found');
|
throw new Error('rename conflict opened a second modal instead of reporting in the form');
|
||||||
conflictOk.click();
|
}
|
||||||
|
renameInput.dispatchEvent('keydown', { key: 'Escape' });
|
||||||
await flush();
|
await flush();
|
||||||
|
if (walk(document.body, (node) => node.getAttribute && node.getAttribute('data-notes-rename-modal') !== undefined)) {
|
||||||
|
throw new Error('Escape did not close the rename modal');
|
||||||
|
}
|
||||||
|
|
||||||
const providerAction = walk(container, (node) => node.getAttribute && node.getAttribute('data-note-contribution-action') === 'provider.note.action');
|
const providerAction = walk(container, (node) => node.getAttribute && node.getAttribute('data-note-contribution-action') === 'provider.note.action');
|
||||||
if (!providerAction) throw new Error('provider note action button not found');
|
if (!providerAction) throw new Error('provider note action button not found');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue