diff --git a/AGENTS.md b/AGENTS.md
index d560a40..c5219ae 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,7 +14,6 @@ verstak-official-plugins/
files/
notes/
markdown-editor/
- markdown-preview/
file-preview/
activity/
journal/
@@ -45,7 +44,7 @@ verstak-official-plugins/
- `official.files` — файловый менеджер
- `official.notes` — заметки
- `official.markdown-editor` — редактор
-- `official.markdown-preview` — предпросмотр
+- `official.file-preview` — предпросмотр изображений/metadata
- `official.activity` — активность
- `official.browser-inbox` — браузерный inbox
diff --git a/plugins/file-preview/frontend/src/index.js b/plugins/file-preview/frontend/src/index.js
index 35933a9..23817e5 100644
--- a/plugins/file-preview/frontend/src/index.js
+++ b/plugins/file-preview/frontend/src/index.js
@@ -25,7 +25,6 @@
'.fp-btn{font-size:.75rem;padding:.28rem .58rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ccc;cursor:pointer}',
'.fp-btn:hover{background:#2a2a4e;border-color:#4ecca3;color:#4ecca3}',
'.fp-body{flex:1;min-height:0;overflow:auto;padding:1rem 1.2rem}',
- '.fp-pre{margin:0;white-space:pre-wrap;font:13px/1.6 "SF Mono","Fira Code","Cascadia Code",Consolas,monospace;color:#d8d8e8}',
'.fp-meta{display:grid;grid-template-columns:max-content 1fr;gap:.45rem .8rem;max-width:760px;font-size:.86rem}',
'.fp-meta dt{color:#8b8ba8}.fp-meta dd{margin:0;color:#e0e0e0;word-break:break-word}',
'.fp-kind{margin:0 0 1rem;color:#f0f0ff;font-size:1.1rem}',
@@ -111,15 +110,7 @@
containerEl.appendChild(body);
api.files.metadata(path).then(function (meta) {
- if (IMAGE_EXTS.indexOf(ext) !== -1 || meta.isText === false) {
- renderMeta(body, path, meta || {}, ext);
- return;
- }
- return api.files.readText(path).then(function (text) {
- body.className = 'fp-body';
- body.innerHTML = '';
- body.appendChild(el('pre', { className: 'fp-pre' }, [text]));
- });
+ renderMeta(body, path, meta || {}, ext);
}).catch(function (err) {
body.className = 'fp-error';
body.textContent = 'Preview error: ' + (err && err.message ? err.message : String(err));
diff --git a/plugins/file-preview/plugin.json b/plugins/file-preview/plugin.json
index 55297c8..61e8cc7 100644
--- a/plugins/file-preview/plugin.json
+++ b/plugins/file-preview/plugin.json
@@ -4,7 +4,7 @@
"name": "File Preview",
"version": "0.1.0",
"apiVersion": "0.1.0",
- "description": "Read-only preview provider for text-like and image vault files.",
+ "description": "Read-only image metadata preview provider for vault files.",
"source": "official",
"icon": "eye",
"provides": [
@@ -23,23 +23,9 @@
},
"contributes": {
"openProviders": [
- {
- "id": "verstak.file-preview.text",
- "title": "File Preview",
- "priority": 80,
- "component": "FilePreview",
- "supports": [
- {
- "kind": "vault-file",
- "extensions": [".txt", ".log", ".conf", ".ini", ".toml", ".yaml", ".yml", ".json", ".csv", ".xml", ".html", ".css", ".js", ".ts"],
- "contexts": ["generic-text"],
- "modes": ["view"]
- }
- ]
- },
{
"id": "verstak.file-preview.image",
- "title": "Image File Preview",
+ "title": "Image Preview",
"priority": 80,
"component": "FilePreview",
"supports": [
diff --git a/plugins/markdown-preview/frontend/src/index.js b/plugins/markdown-preview/frontend/src/index.js
deleted file mode 100644
index ddb350b..0000000
--- a/plugins/markdown-preview/frontend/src/index.js
+++ /dev/null
@@ -1,173 +0,0 @@
-/* ===========================================================
- Markdown Preview Plugin — Verstak v2 Frontend Bundle
- Contract: window.VerstakPluginRegister(id, { components })
- =========================================================== */
-
-(function () {
- 'use strict';
-
- function injectStyles() {
- if (document.getElementById('md-preview-style-injected')) return;
- var style = document.createElement('style');
- style.id = 'md-preview-style-injected';
- style.textContent = STYLES;
- document.head.appendChild(style);
- }
-
- var STYLES = [
- '.mp-root{height:100%;min-height:0;display:flex;flex-direction:column;background:#0d0d1a;color:#d8d8e8;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif}',
- '.mp-toolbar{display:flex;align-items:center;gap:.5rem;padding:.45rem .75rem;border-bottom:1px solid #16213e;background:#12122a;flex-shrink:0}',
- '.mp-mode{font-size:.72rem;color:#4ecca3;background:#1a2a3a;border-radius:3px;padding:.14rem .45rem}',
- '.mp-path{font-size:.75rem;color:#a0a0bb;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
- '.mp-body{flex:1;min-height:0;overflow:auto;padding:1rem 1.2rem;line-height:1.7;font-size:.93rem}',
- '.mp-body h1,.mp-body h2,.mp-body h3,.mp-body h4{color:#f0f0ff;margin:1rem 0 .5rem}',
- '.mp-body h1{font-size:1.55rem;border-bottom:1px solid #16213e;padding-bottom:.35rem}.mp-body h2{font-size:1.3rem;border-bottom:1px solid #16213e;padding-bottom:.25rem}.mp-body h3{font-size:1.12rem}',
- '.mp-body p{margin:.55rem 0}.mp-body code{background:#1a1a2e;padding:.15rem .35rem;border-radius:3px;font-size:.87em;color:#4ecca3}',
- '.mp-body pre{background:#1a1a2e;padding:.85rem;border-radius:4px;overflow:auto;margin:.8rem 0}.mp-body pre code{background:none;padding:0;color:#d8d8e8}',
- '.mp-body ul,.mp-body ol{padding-left:1.5rem;margin:.55rem 0}.mp-body li{margin:.25rem 0}',
- '.mp-body blockquote{border-left:3px solid #4ecca3;margin:.6rem 0;padding:.25rem .85rem;color:#aaa;background:#101028}',
- '.mp-body a{color:#4ecca3;text-decoration:none}.mp-body a:hover{text-decoration:underline}',
- '.mp-body table{border-collapse:collapse;margin:.8rem 0;max-width:100%;display:block;overflow:auto}.mp-body th,.mp-body td{border:1px solid #333;padding:.35rem .6rem;text-align:left}.mp-body th{background:#1a1a2e}',
- '.mp-loading,.mp-error{flex:1;display:flex;align-items:center;justify-content:center;color:#777;padding:2rem}.mp-error{color:#e74c3c;flex-direction:column;gap:.5rem}'
- ].join('\n');
-
- function el(tag, attrs, children) {
- var elem = document.createElement(tag);
- if (attrs) {
- Object.keys(attrs).forEach(function (k) {
- if (k === 'className') elem.className = attrs[k];
- else if (k === 'style' && typeof attrs[k] === 'object') Object.assign(elem.style, attrs[k]);
- else if (k.slice(0, 2) === 'on') elem.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
- else if (k === 'innerHTML') elem.innerHTML = attrs[k];
- else if (k === 'textContent') elem.textContent = attrs[k];
- else elem.setAttribute(k, attrs[k]);
- });
- }
- if (children) {
- (Array.isArray(children) ? children : [children]).forEach(function (c) {
- if (c == null) return;
- elem.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
- });
- }
- return elem;
- }
-
- function escapeHtml(value) {
- return String(value == null ? '' : value)
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
- }
-
- function renderInline(text) {
- var html = escapeHtml(text);
- html = html.replace(/`([^`\n]+)`/g, '$1');
- html = html.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+|mailto:[^)]+)\)/g, '$1');
- html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1');
- html = html.replace(/\*\*(.+?)\*\*/g, '$1');
- html = html.replace(/\*(.+?)\*/g, '$1');
- return html;
- }
-
- function renderMarkdown(markdown) {
- var lines = String(markdown || '').replace(/\r\n/g, '\n').split('\n');
- var html = [];
- var inCode = false;
- var code = [];
- var inList = false;
-
- function closeList() {
- if (inList) {
- html.push('');
- inList = false;
- }
- }
- function closeCode() {
- if (inCode) {
- html.push('
' + escapeHtml(code.join('\n')) + '');
- code = [];
- inCode = false;
- }
- }
-
- lines.forEach(function (line) {
- if (/^```/.test(line)) {
- if (inCode) closeCode();
- else {
- closeList();
- inCode = true;
- code = [];
- }
- return;
- }
- if (inCode) {
- code.push(line);
- return;
- }
- if (!line.trim()) {
- closeList();
- return;
- }
- var heading = /^(#{1,4})\s+(.+)$/.exec(line);
- if (heading) {
- closeList();
- html.push('' + renderInline(quote[1]) + ''); - return; - } - var item = /^\s*[-*]\s+(.+)$/.exec(line); - if (item) { - if (!inList) { - html.push('
' + renderInline(line) + '
'); - }); - closeCode(); - closeList(); - return html.join('\n'); - } - - var MarkdownPreview = { - mount: function (containerEl, props, api) { - injectStyles(); - var request = props && props.request || {}; - var path = request.path || ''; - containerEl.innerHTML = ''; - containerEl.className = 'mp-root'; - containerEl.setAttribute('data-plugin-id', 'verstak.markdown-preview'); - containerEl.setAttribute('data-preview-path', path); - containerEl.appendChild(el('div', { className: 'mp-toolbar' }, [ - el('span', { className: 'mp-mode' }, ['Preview']), - el('span', { className: 'mp-path' }, [path]) - ])); - var body = el('div', { className: 'mp-loading' }, ['Loading...']); - containerEl.appendChild(body); - api.files.readText(path).then(function (text) { - body.className = 'mp-body'; - body.innerHTML = renderMarkdown(text); - }).catch(function (err) { - body.className = 'mp-error'; - body.textContent = 'Preview error: ' + (err && err.message ? err.message : String(err)); - }); - }, - unmount: function (containerEl) { - containerEl.innerHTML = ''; - } - }; - - window.VerstakPluginRegister('verstak.markdown-preview', { - components: { MarkdownPreview: MarkdownPreview } - }); -})(); diff --git a/plugins/markdown-preview/plugin.json b/plugins/markdown-preview/plugin.json deleted file mode 100644 index 57c89a1..0000000 --- a/plugins/markdown-preview/plugin.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "schemaVersion": 1, - "id": "verstak.markdown-preview", - "name": "Markdown Preview", - "version": "0.1.0", - "apiVersion": "0.1.0", - "description": "Read-only Markdown preview provider for vault Markdown files.", - "source": "official", - "icon": "eye", - "provides": [ - "verstak/markdown-preview/v1" - ], - "requires": [ - "verstak/core/files/v1", - "verstak/core/workbench/v1" - ], - "permissions": [ - "files.read" - ], - "frontend": { - "entry": "frontend/src/index.js" - }, - "contributes": { - "openProviders": [ - { - "id": "verstak.markdown-preview.generic", - "title": "Markdown Preview", - "priority": 80, - "component": "MarkdownPreview", - "supports": [ - { - "kind": "vault-file", - "extensions": [".md", ".markdown"], - "contexts": ["generic-markdown"], - "modes": ["view"] - } - ] - }, - { - "id": "verstak.markdown-preview.notes", - "title": "Notes Markdown Preview", - "priority": 80, - "component": "MarkdownPreview", - "supports": [ - { - "kind": "vault-file", - "extensions": [".md", ".markdown"], - "contexts": ["notes-markdown"], - "modes": ["view"] - } - ] - } - ] - } -} diff --git a/scripts/check.sh b/scripts/check.sh index fc735bd..5177561 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -213,8 +213,6 @@ if command -v node &>/dev/null; then report "platform-test frontend components mount" $? node "$ROOT/scripts/smoke-notes-plugin.js" report "notes frontend behavior" $? - node "$ROOT/scripts/smoke-markdown-preview-plugin.js" - report "markdown-preview frontend behavior" $? node "$ROOT/scripts/smoke-file-preview-plugin.js" report "file-preview frontend behavior" $? node "$ROOT/scripts/smoke-files-plugin.js" diff --git a/scripts/smoke-file-preview-plugin.js b/scripts/smoke-file-preview-plugin.js index bdf6616..7802b9b 100755 --- a/scripts/smoke-file-preview-plugin.js +++ b/scripts/smoke-file-preview-plugin.js @@ -121,24 +121,6 @@ async function flush() { const document = makeDocument(); const component = loadComponent(document); - const textContainer = new FakeNode('div'); - const readPaths = []; - component.mount(textContainer, { - request: { path: 'Docs/app.log', extension: '.log', mode: 'view' }, - }, { - files: { - metadata: async () => ({ type: 'file', size: 11, isText: true, modifiedAt: '2026-06-27T00:00:00Z' }), - readText: async (relativePath) => { - readPaths.push(relativePath); - return 'hello log'; - }, - openExternal: async () => undefined, - }, - }); - await flush(); - if (readPaths[0] !== 'Docs/app.log') throw new Error('text preview did not read text file'); - if (!textContainer.textContent.includes('hello log')) throw new Error('text preview did not render file text'); - const imageContainer = new FakeNode('div'); const opened = []; component.mount(imageContainer, { @@ -146,7 +128,7 @@ async function flush() { }, { files: { metadata: async () => ({ type: 'file', size: 2048, isText: false, modifiedAt: '2026-06-27T00:00:00Z' }), - readText: async () => { throw new Error('image preview should not read bytes through readText'); }, + readText: async () => { throw new Error('file preview should not read text content'); }, openExternal: async (relativePath) => { opened.push(relativePath); }, }, }); diff --git a/scripts/smoke-markdown-preview-plugin.js b/scripts/smoke-markdown-preview-plugin.js deleted file mode 100755 index a66537e..0000000 --- a/scripts/smoke-markdown-preview-plugin.js +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); -const vm = require('vm'); - -const root = path.resolve(__dirname, '..'); -const sourcePath = path.join(root, 'plugins', 'markdown-preview', 'frontend', 'src', 'index.js'); -const source = fs.readFileSync(sourcePath, 'utf8'); - -class FakeNode { - constructor(tagName) { - this.tagName = String(tagName || '').toUpperCase(); - this.children = []; - this.attributes = {}; - this.listeners = {}; - this.style = {}; - this.className = ''; - this.id = ''; - this.parentNode = null; - this._innerHTML = ''; - this._textContent = ''; - } - - appendChild(node) { - if (!(node instanceof FakeNode)) throw new TypeError('appendChild expects FakeNode'); - this.children.push(node); - node.parentNode = this; - return node; - } - - setAttribute(name, value) { - this.attributes[name] = String(value); - if (name === 'id') this.id = String(value); - } - - getAttribute(name) { - return this.attributes[name]; - } - - addEventListener(type, handler) { - this.listeners[type] = this.listeners[type] || []; - this.listeners[type].push(handler); - } - - set innerHTML(value) { - this._innerHTML = String(value || ''); - this.children = []; - } - - get innerHTML() { - return this._innerHTML + this.children.map((child) => child.innerHTML).join(''); - } - - set textContent(value) { - this._textContent = String(value || ''); - this.children = []; - } - - get textContent() { - if (this.tagName === '#TEXT') return this._textContent; - return this._textContent + this.children.map((child) => child.textContent).join(''); - } -} - -function makeDocument() { - return { - body: new FakeNode('body'), - head: new FakeNode('head'), - createElement(tagName) { - return new FakeNode(tagName); - }, - createTextNode(text) { - const node = new FakeNode('#text'); - node.textContent = text; - return node; - }, - getElementById() { - return null; - }, - }; -} - -function loadComponent(document) { - const registry = {}; - const sandbox = { - console, - document, - window: { - VerstakPluginRegister(pluginId, bundle) { - registry[pluginId] = bundle.components || {}; - }, - }, - }; - sandbox.window.window = sandbox.window; - sandbox.window.document = document; - vm.runInNewContext(source, sandbox, { filename: sourcePath }); - const component = registry['verstak.markdown-preview'] && registry['verstak.markdown-preview'].MarkdownPreview; - if (!component) throw new Error('MarkdownPreview was not registered'); - return component; -} - -async function flush() { - for (let i = 0; i < 8; i++) await Promise.resolve(); -} - -(async () => { - const document = makeDocument(); - const component = loadComponent(document); - const container = new FakeNode('div'); - const readPaths = []; - component.mount(container, { - request: { path: 'Docs/readme.md', mode: 'view' }, - }, { - files: { - readText: async (relativePath) => { - readPaths.push(relativePath); - return '# Title\n\nParagraph with `code`.\n\n- one\n- two\n'; - }, - }, - }); - await flush(); - - if (container.getAttribute('data-plugin-id') !== 'verstak.markdown-preview') { - throw new Error('plugin id marker missing'); - } - if (readPaths[0] !== 'Docs/readme.md') { - throw new Error(`expected readText Docs/readme.md, got ${readPaths[0] || 'code')) throw new Error('inline code was not rendered');
- if (!html.includes('