Keep files plugin note-neutral

This commit is contained in:
mirivlad 2026-06-27 12:39:51 +08:00
parent 39d8df339b
commit fca7716e75
2 changed files with 30 additions and 116 deletions

View File

@ -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') {

View File

@ -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]"