Keep files plugin note-neutral
This commit is contained in:
parent
39d8df339b
commit
fca7716e75
|
|
@ -145,35 +145,11 @@
|
||||||
return dot > 0 ? name.slice(dot + 1).toLowerCase() : '';
|
return dot > 0 ? name.slice(dot + 1).toLowerCase() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeNoteFilename(title) {
|
|
||||||
var value = String(title == null ? '' : title).trim();
|
|
||||||
if (/\.markdown$/i.test(value) && value.length > 9) value = value.slice(0, -9);
|
|
||||||
else if (/\.md$/i.test(value) && value.length > 3) value = value.slice(0, -3);
|
|
||||||
if (!value) throw new Error('note title must not be empty');
|
|
||||||
value = value.replace(/\s+/g, '_');
|
|
||||||
value = value.replace(/[\u2012\u2013\u2014\u2015\u2212]/g, '-');
|
|
||||||
value = value.replace(/[<>:"/\\|?*\x00-\x1f\x7f]/g, '');
|
|
||||||
var out = '';
|
|
||||||
for (var i = 0; i < value.length; i++) {
|
|
||||||
var ch = value.charAt(i);
|
|
||||||
if (/[A-Za-z0-9._-]/.test(ch) || /[\p{L}\p{N}]/u.test(ch)) out += ch;
|
|
||||||
else if (/\S/.test(ch)) out += '_';
|
|
||||||
}
|
|
||||||
out = out.replace(/[_.-]+/g, '_').replace(/^[._\-\s]+|[._\-\s]+$/g, '');
|
|
||||||
if (!out) throw new Error('note title normalizes to an empty filename');
|
|
||||||
return out + '.md';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isConflictError(err) {
|
function isConflictError(err) {
|
||||||
var msg = (err && err.message) ? err.message : String(err || '');
|
var msg = (err && err.message) ? err.message : String(err || '');
|
||||||
return /conflict|already exists|exists/i.test(msg);
|
return /conflict|already exists|exists/i.test(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNotFoundError(err) {
|
|
||||||
var msg = (err && err.message) ? err.message : String(err || '');
|
|
||||||
return /not.?found|does not exist|no such/i.test(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
var FILE_ICONS = {
|
var FILE_ICONS = {
|
||||||
folder: 'M3 5a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v1H3V5Zm0 6h18v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7Z',
|
folder: 'M3 5a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v1H3V5Zm0 6h18v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7Z',
|
||||||
markdown: 'M5 3h10l4 4v14H5V3Zm9 1.5V8h3.5L14 4.5ZM8 11h8v2H8v-2Zm0 4h8v2H8v-2Z',
|
markdown: 'M5 3h10l4 4v14H5V3Zm9 1.5V8h3.5L14 4.5ZM8 11h8v2H8v-2Zm0 4h8v2H8v-2Z',
|
||||||
|
|
@ -314,56 +290,6 @@
|
||||||
return full.indexOf(workspaceRoot + '/') === 0 ? full.slice(workspaceRoot.length + 1) : full;
|
return full.indexOf(workspaceRoot + '/') === 0 ? full.slice(workspaceRoot.length + 1) : full;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureFolder(path) {
|
|
||||||
return api.files.createFolder(path).catch(function (err) {
|
|
||||||
if (!isConflictError(err)) throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNoteInFolder(notesFolderPath, title) {
|
|
||||||
var trimmedTitle = String(title || '').trim();
|
|
||||||
if (!trimmedTitle) return Promise.reject(new Error('note title must not be empty'));
|
|
||||||
var notePath = cleanPath(notesFolderPath) + '/' + normalizeNoteFilename(trimmedTitle);
|
|
||||||
return ensureFolder(notesFolderPath).then(function () {
|
|
||||||
return api.files.metadata(notePath).then(function () {
|
|
||||||
return { path: notePath, conflict: true };
|
|
||||||
}).catch(function (err) {
|
|
||||||
if (!isNotFoundError(err)) throw err;
|
|
||||||
return api.files.writeText(notePath, '# ' + trimmedTitle + '\n', {
|
|
||||||
createIfMissing: true,
|
|
||||||
overwrite: false
|
|
||||||
}).then(function () {
|
|
||||||
return { path: notePath };
|
|
||||||
}).catch(function (writeErr) {
|
|
||||||
if (isConflictError(writeErr)) return { path: notePath, conflict: true };
|
|
||||||
throw writeErr;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureOverviewInFolder(notesFolderPath) {
|
|
||||||
var folderPath = cleanPath(notesFolderPath);
|
|
||||||
var ovPath = folderPath + '/Overview.md';
|
|
||||||
return api.files.metadata(ovPath).then(function () {
|
|
||||||
return { path: ovPath };
|
|
||||||
}).catch(function (err) {
|
|
||||||
if (!isNotFoundError(err)) throw err;
|
|
||||||
return ensureFolder(folderPath).then(function () {
|
|
||||||
var local = localPath(folderPath);
|
|
||||||
var parentName = baseName(parentPath(local)) || baseName(folderPath) || 'Overview';
|
|
||||||
return api.files.writeText(ovPath, '# ' + parentName + '\n', {
|
|
||||||
createIfMissing: true,
|
|
||||||
overwrite: false
|
|
||||||
}).catch(function (writeErr) {
|
|
||||||
if (!isConflictError(writeErr)) throw writeErr;
|
|
||||||
}).then(function () {
|
|
||||||
return { path: ovPath };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var toolbar = el('div', { className: 'files-toolbar' });
|
var toolbar = el('div', { className: 'files-toolbar' });
|
||||||
var breadcrumb = el('div', { className: 'files-breadcrumb' });
|
var breadcrumb = el('div', { className: 'files-breadcrumb' });
|
||||||
var backBtn = iconButton('back', 'Back', 'back', goBack);
|
var backBtn = iconButton('back', 'Back', 'back', goBack);
|
||||||
|
|
@ -842,48 +768,6 @@
|
||||||
}
|
}
|
||||||
var isFolder = entry.type === 'folder';
|
var isFolder = entry.type === 'folder';
|
||||||
ctxMenu.appendChild(ctxItem(isFolder ? 'Open Folder' : 'Open', '', function () { openEntry(entry); }, 'open', 'open'));
|
ctxMenu.appendChild(ctxItem(isFolder ? 'Open Folder' : 'Open', '', function () { openEntry(entry); }, 'open', 'open'));
|
||||||
var entryLocalPath = localPath(entry.relativePath);
|
|
||||||
var isNotes = entryLocalPath === 'Notes' || entryLocalPath.split('/')[0] === 'Notes';
|
|
||||||
if (isNotes) {
|
|
||||||
ctxMenu.appendChild(ctxSep());
|
|
||||||
if (isFolder) {
|
|
||||||
ctxMenu.appendChild(ctxItem('Create Note', '', function () {
|
|
||||||
var title = prompt('Note title:');
|
|
||||||
if (!title) return;
|
|
||||||
createNoteInFolder(entry.relativePath, title).then(function (created) {
|
|
||||||
if (created && created.conflict) {
|
|
||||||
window.alert('A note with this title already exists.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loadEntries();
|
|
||||||
var notePath = (created && created.path) ? created.path : '';
|
|
||||||
if (notePath) {
|
|
||||||
api.workbench.openResource({
|
|
||||||
kind: 'vault-file',
|
|
||||||
path: notePath,
|
|
||||||
mode: 'edit',
|
|
||||||
extension: '.md',
|
|
||||||
context: { notesMode: true, sourcePluginId: 'verstak.files' }
|
|
||||||
}).catch(function () {});
|
|
||||||
}
|
|
||||||
}).catch(function (err) { window.alert('Failed to create note: ' + (err.message || String(err))); });
|
|
||||||
}, 'create-note', 'markdownAdd'));
|
|
||||||
ctxMenu.appendChild(ctxItem('Open Overview', '', function () {
|
|
||||||
ensureOverviewInFolder(entry.relativePath).then(function (result) {
|
|
||||||
var overviewPath = (result && result.path) ? result.path : '';
|
|
||||||
if (overviewPath) {
|
|
||||||
api.workbench.openResource({
|
|
||||||
kind: 'vault-file',
|
|
||||||
path: overviewPath,
|
|
||||||
mode: 'view',
|
|
||||||
extension: '.md',
|
|
||||||
context: { notesMode: true, sourcePluginId: 'verstak.files' }
|
|
||||||
}).catch(function () {});
|
|
||||||
}
|
|
||||||
}).catch(function (err) { window.alert('Failed to open overview: ' + (err.message || String(err))); });
|
|
||||||
}, 'open-overview', 'open'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctxMenu.appendChild(ctxSep());
|
ctxMenu.appendChild(ctxSep());
|
||||||
ctxMenu.appendChild(ctxItem('Rename', '', function () { beginRename(entry); }, 'rename', 'rename'));
|
ctxMenu.appendChild(ctxItem('Rename', '', function () { beginRename(entry); }, 'rename', 'rename'));
|
||||||
if (entry.type !== 'folder') {
|
if (entry.type !== 'folder') {
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,36 @@ else
|
||||||
echo " ⚠️ python3 not available — skipping frontend API boundary"
|
echo " ⚠️ python3 not available — skipping frontend API boundary"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
# Keep Files plugin as a raw file explorer; note workflows belong to Notes.
|
||||||
|
echo "[files plugin note boundaries]"
|
||||||
|
if [ "$HAS_PYTHON" -eq 1 ]; then
|
||||||
|
set +e
|
||||||
|
python3 -c "
|
||||||
|
import os, re, sys
|
||||||
|
|
||||||
|
path = '$ROOT/plugins/files/frontend/src/index.js'
|
||||||
|
forbidden = re.compile(r'createNoteInFolder|ensureOverviewInFolder|Create Note|Open Overview')
|
||||||
|
problems = []
|
||||||
|
|
||||||
|
with open(path, encoding='utf-8') as f:
|
||||||
|
for lineno, line in enumerate(f, 1):
|
||||||
|
if forbidden.search(line):
|
||||||
|
problems.append(f'{os.path.relpath(path, \"$ROOT\")}:{lineno}: {line.strip()}')
|
||||||
|
|
||||||
|
if problems:
|
||||||
|
for p in problems:
|
||||||
|
print(' FAIL ' + p)
|
||||||
|
sys.exit(1)
|
||||||
|
print(' OK files plugin has no note creation or overview actions')
|
||||||
|
"
|
||||||
|
STATUS=$?
|
||||||
|
set -e
|
||||||
|
report "files plugin note boundaries" "$STATUS"
|
||||||
|
else
|
||||||
|
echo " ⚠️ python3 not available — skipping files plugin note boundaries"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
# Ensure source manifests do not require ignored dist files for plain JS plugins.
|
# Ensure source manifests do not require ignored dist files for plain JS plugins.
|
||||||
echo "[frontend entry source contract]"
|
echo "[frontend entry source contract]"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue