Phase 1 MVP - Complete implementation

- Login with JWT and refresh token rotation
- Dashboard with projects cards
- ProjectView with TreeView navigation
- DocumentView with markdown editor and auto-save
- Tag management (create, assign, remove)
- Dark mode CSS variables
- Security fixes applied (logout to backend, createDocument endpoint)
This commit is contained in:
Motoko
2026-03-30 15:17:29 +00:00
commit c9cb07dbfc
1341 changed files with 1084068 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
import type { VirtualCode } from '@volar/language-core';
import type { Sfc, VueLanguagePluginReturn } from '../types';
export declare function computedEmbeddedCodes(plugins: VueLanguagePluginReturn[], fileName: string, sfc: Sfc): () => VirtualCode[];
export declare function resolveCommonLanguageId(lang: string): string;

View File

@@ -0,0 +1,251 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.computedEmbeddedCodes = computedEmbeddedCodes;
exports.resolveCommonLanguageId = resolveCommonLanguageId;
const alien_signals_1 = require("alien-signals");
const muggle_string_1 = require("muggle-string");
const buildMappings_1 = require("../utils/buildMappings");
const embeddedFile_1 = require("./embeddedFile");
function computedEmbeddedCodes(plugins, fileName, sfc) {
const getNameToBlockMap = (0, alien_signals_1.computed)(() => {
const blocks = {};
if (sfc.template) {
blocks[sfc.template.name] = sfc.template;
}
if (sfc.script) {
blocks[sfc.script.name] = sfc.script;
}
if (sfc.scriptSetup) {
blocks[sfc.scriptSetup.name] = sfc.scriptSetup;
}
for (const block of sfc.styles) {
blocks[block.name] = block;
}
for (const block of sfc.customBlocks) {
blocks[block.name] = block;
}
return blocks;
});
const getPluginsResult = plugins.map(plugin => computedPluginEmbeddedCodes(plugins, plugin, fileName, sfc, name => getNameToBlockMap()[name]));
const getFlatResult = (0, alien_signals_1.computed)(() => getPluginsResult.map(r => r()).flat());
const getStructuredResult = (0, alien_signals_1.computed)(() => {
const embeddedCodes = [];
let remain = [...getFlatResult()];
while (remain.length) {
const beforeLength = remain.length;
consumeRemain();
if (beforeLength === remain.length) {
break;
}
}
for (const { code } of remain) {
console.error('Unable to resolve embedded: ' + code.parentCodeId + ' -> ' + code.id);
}
return embeddedCodes;
function consumeRemain() {
for (let i = remain.length - 1; i >= 0; i--) {
const { code, snapshot, mappings } = remain[i];
if (!code.parentCodeId) {
embeddedCodes.push({
id: code.id,
languageId: resolveCommonLanguageId(code.lang),
linkedCodeMappings: code.linkedCodeMappings,
snapshot,
mappings,
embeddedCodes: [],
});
remain.splice(i, 1);
}
else {
const parent = findParentStructure(code.parentCodeId, embeddedCodes);
if (parent) {
parent.embeddedCodes ??= [];
parent.embeddedCodes.push({
id: code.id,
languageId: resolveCommonLanguageId(code.lang),
linkedCodeMappings: code.linkedCodeMappings,
snapshot,
mappings,
embeddedCodes: [],
});
remain.splice(i, 1);
}
}
}
}
function findParentStructure(id, current) {
for (const child of current) {
if (child.id === id) {
return child;
}
let parent = findParentStructure(id, child.embeddedCodes ?? []);
if (parent) {
return parent;
}
}
}
});
return getStructuredResult;
}
function computedPluginEmbeddedCodes(plugins, plugin, fileName, sfc, getBlockByName) {
const computeds = new Map();
const getComputedKey = (code) => code.id + '__' + code.lang;
const getCodes = (0, alien_signals_1.computed)(() => {
try {
if (!plugin.getEmbeddedCodes) {
return [...computeds.values()];
}
const embeddedCodeInfos = plugin.getEmbeddedCodes(fileName, sfc);
for (const oldId of computeds.keys()) {
if (!embeddedCodeInfos.some(code => getComputedKey(code) === oldId)) {
computeds.delete(oldId);
}
}
for (const codeInfo of embeddedCodeInfos) {
if (!computeds.has(getComputedKey(codeInfo))) {
computeds.set(getComputedKey(codeInfo), (0, alien_signals_1.computed)(() => {
const content = [];
const code = new embeddedFile_1.VueEmbeddedCode(codeInfo.id, codeInfo.lang, content);
for (const plugin of plugins) {
if (!plugin.resolveEmbeddedCode) {
continue;
}
try {
plugin.resolveEmbeddedCode(fileName, sfc, code);
}
catch (e) {
console.error(e);
}
}
const newText = (0, muggle_string_1.toString)(code.content);
const changeRanges = new Map();
const snapshot = {
getText: (start, end) => newText.slice(start, end),
getLength: () => newText.length,
getChangeRange(oldSnapshot) {
if (!changeRanges.has(oldSnapshot)) {
changeRanges.set(oldSnapshot, undefined);
const oldText = oldSnapshot.getText(0, oldSnapshot.getLength());
const changeRange = fullDiffTextChangeRange(oldText, newText);
if (changeRange) {
changeRanges.set(oldSnapshot, changeRange);
}
}
return changeRanges.get(oldSnapshot);
},
};
return {
code,
snapshot,
};
}));
}
}
}
catch (e) {
console.error(e);
}
return [...computeds.values()];
});
return (0, alien_signals_1.computed)(() => {
return getCodes().map(_file => {
const { code, snapshot } = _file();
const mappings = (0, buildMappings_1.buildMappings)(code.content.map(segment => {
if (typeof segment === 'string') {
return segment;
}
const source = segment[1];
if (source === undefined) {
return segment;
}
const block = getBlockByName(source);
if (!block) {
// console.warn('Unable to find block: ' + source);
return segment;
}
return [
segment[0],
undefined,
segment[2] + block.startTagEnd,
segment[3],
];
}));
const newMappings = [];
const tokenMappings = new Map();
for (let i = 0; i < mappings.length; i++) {
const mapping = mappings[i];
if (mapping.data.__combineOffset !== undefined) {
const offsetMapping = mappings[i - mapping.data.__combineOffset];
if (typeof offsetMapping === 'string' || !offsetMapping) {
throw new Error('Invalid offset mapping, mappings: ' + mappings.length + ', i: ' + i + ', offset: ' + mapping.data.__combineOffset);
}
offsetMapping.sourceOffsets.push(...mapping.sourceOffsets);
offsetMapping.generatedOffsets.push(...mapping.generatedOffsets);
offsetMapping.lengths.push(...mapping.lengths);
continue;
}
if (mapping.data.__linkedToken !== undefined) {
const token = mapping.data.__linkedToken;
if (tokenMappings.has(token)) {
const prevMapping = tokenMappings.get(token);
code.linkedCodeMappings.push({
sourceOffsets: [prevMapping.generatedOffsets[0]],
generatedOffsets: [mapping.generatedOffsets[0]],
lengths: [Number(token.description)],
data: undefined,
});
}
else {
tokenMappings.set(token, mapping);
}
continue;
}
newMappings.push(mapping);
}
return {
code,
snapshot,
mappings: newMappings,
};
});
});
}
function fullDiffTextChangeRange(oldText, newText) {
for (let start = 0; start < oldText.length && start < newText.length; start++) {
if (oldText[start] !== newText[start]) {
let end = oldText.length;
for (let i = 0; i < oldText.length - start && i < newText.length - start; i++) {
if (oldText[oldText.length - i - 1] !== newText[newText.length - i - 1]) {
break;
}
end--;
}
let length = end - start;
let newLength = length + (newText.length - oldText.length);
if (newLength < 0) {
length -= newLength;
newLength = 0;
}
return {
span: { start, length },
newLength,
};
}
}
}
function resolveCommonLanguageId(lang) {
switch (lang) {
case 'js': return 'javascript';
case 'cjs': return 'javascript';
case 'mjs': return 'javascript';
case 'ts': return 'typescript';
case 'cts': return 'typescript';
case 'mts': return 'typescript';
case 'jsx': return 'javascriptreact';
case 'tsx': return 'typescriptreact';
case 'pug': return 'jade';
case 'md': return 'markdown';
}
return lang;
}
//# sourceMappingURL=computedEmbeddedCodes.js.map

View File

@@ -0,0 +1,4 @@
import type { SFCParseResult } from '@vue/compiler-sfc';
import type * as ts from 'typescript';
import type { Sfc, VueLanguagePluginReturn } from '../types';
export declare function computedSfc(ts: typeof import('typescript'), plugins: VueLanguagePluginReturn[], fileName: string, getSnapshot: () => ts.IScriptSnapshot, getParseResult: () => SFCParseResult | undefined): Sfc;

View File

@@ -0,0 +1,243 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.computedSfc = computedSfc;
const alien_signals_1 = require("alien-signals");
const parseCssClassNames_1 = require("../utils/parseCssClassNames");
const parseCssVars_1 = require("../utils/parseCssVars");
const signals_1 = require("../utils/signals");
function computedSfc(ts, plugins, fileName, getSnapshot, getParseResult) {
const getUntrackedSnapshot = () => {
(0, alien_signals_1.pauseTracking)();
const res = getSnapshot();
(0, alien_signals_1.resumeTracking)();
return res;
};
const getContent = (0, alien_signals_1.computed)(() => {
return getSnapshot().getText(0, getSnapshot().getLength());
});
const getComments = (0, alien_signals_1.computed)(oldValue => {
const newValue = getParseResult()?.descriptor.comments ?? [];
if (oldValue?.length === newValue.length
&& oldValue?.every((v, i) => v === newValue[i])) {
return oldValue;
}
return newValue;
});
const getTemplate = computedNullableSfcBlock('template', 'html', (0, alien_signals_1.computed)(() => getParseResult()?.descriptor.template ?? undefined), (_block, base) => {
const compiledAst = computedTemplateAst(base);
return mergeObject(base, {
get ast() { return compiledAst()?.ast; },
get errors() { return compiledAst()?.errors; },
get warnings() { return compiledAst()?.warnings; },
});
});
const getScript = computedNullableSfcBlock('script', 'js', (0, alien_signals_1.computed)(() => getParseResult()?.descriptor.script ?? undefined), (block, base) => {
const getSrc = computedAttrValue('__src', base, block);
const getAst = (0, alien_signals_1.computed)(() => {
for (const plugin of plugins) {
const ast = plugin.compileSFCScript?.(base.lang, base.content);
if (ast) {
return ast;
}
}
return ts.createSourceFile(fileName + '.' + base.lang, '', 99);
});
return mergeObject(base, {
get src() { return getSrc(); },
get ast() { return getAst(); },
});
});
const getOriginalScriptSetup = computedNullableSfcBlock('scriptSetup', 'js', (0, alien_signals_1.computed)(() => getParseResult()?.descriptor.scriptSetup ?? undefined), (block, base) => {
const getGeneric = computedAttrValue('__generic', base, block);
const getAst = (0, alien_signals_1.computed)(() => {
for (const plugin of plugins) {
const ast = plugin.compileSFCScript?.(base.lang, base.content);
if (ast) {
return ast;
}
}
return ts.createSourceFile(fileName + '.' + base.lang, '', 99);
});
return mergeObject(base, {
get generic() { return getGeneric(); },
get ast() { return getAst(); },
});
});
const hasScript = (0, alien_signals_1.computed)(() => !!getParseResult()?.descriptor.script);
const hasScriptSetup = (0, alien_signals_1.computed)(() => !!getParseResult()?.descriptor.scriptSetup);
const getScriptSetup = (0, alien_signals_1.computed)(() => {
if (!hasScript() && !hasScriptSetup()) {
//#region monkey fix: https://github.com/vuejs/language-tools/pull/2113
return {
content: '',
lang: 'ts',
name: '',
start: 0,
end: 0,
startTagEnd: 0,
endTagStart: 0,
generic: undefined,
genericOffset: 0,
attrs: {},
ast: ts.createSourceFile('', '', 99, false, ts.ScriptKind.TS),
};
}
return getOriginalScriptSetup();
});
const styles = (0, signals_1.computedArray)((0, alien_signals_1.computed)(() => getParseResult()?.descriptor.styles ?? []), (getBlock, i) => {
const base = computedSfcBlock('style_' + i, 'css', getBlock);
const getModule = computedAttrValue('__module', base, getBlock);
const getScoped = (0, alien_signals_1.computed)(() => !!getBlock().scoped);
const getCssVars = (0, alien_signals_1.computed)(() => [...(0, parseCssVars_1.parseCssVars)(base.content)]);
const getClassNames = (0, alien_signals_1.computed)(() => [...(0, parseCssClassNames_1.parseCssClassNames)(base.content)]);
return () => mergeObject(base, {
get module() { return getModule(); },
get scoped() { return getScoped(); },
get cssVars() { return getCssVars(); },
get classNames() { return getClassNames(); },
});
});
const customBlocks = (0, signals_1.computedArray)((0, alien_signals_1.computed)(() => getParseResult()?.descriptor.customBlocks ?? []), (getBlock, i) => {
const base = computedSfcBlock('custom_block_' + i, 'txt', getBlock);
const getType = (0, alien_signals_1.computed)(() => getBlock().type);
return () => mergeObject(base, {
get type() { return getType(); },
});
});
return {
get content() { return getContent(); },
get comments() { return getComments(); },
get template() { return getTemplate(); },
get script() { return getScript(); },
get scriptSetup() { return getScriptSetup(); },
get styles() { return styles; },
get customBlocks() { return customBlocks; },
};
function computedTemplateAst(base) {
let cache;
return (0, alien_signals_1.computed)(() => {
if (cache?.template === base.content) {
return {
errors: [],
warnings: [],
ast: cache?.result.ast,
};
}
// incremental update
if (cache?.plugin.updateSFCTemplate) {
const change = getUntrackedSnapshot().getChangeRange(cache.snapshot);
if (change) {
(0, alien_signals_1.pauseTracking)();
const templateOffset = base.startTagEnd;
(0, alien_signals_1.resumeTracking)();
const newText = getUntrackedSnapshot().getText(change.span.start, change.span.start + change.newLength);
const newResult = cache.plugin.updateSFCTemplate(cache.result, {
start: change.span.start - templateOffset,
end: change.span.start + change.span.length - templateOffset,
newText,
});
if (newResult) {
cache.template = base.content;
cache.snapshot = getUntrackedSnapshot();
cache.result = newResult;
return {
errors: [],
warnings: [],
ast: newResult.ast,
};
}
}
}
const errors = [];
const warnings = [];
let options = {
onError: (err) => errors.push(err),
onWarn: (err) => warnings.push(err),
expressionPlugins: ['typescript'],
};
for (const plugin of plugins) {
if (plugin.resolveTemplateCompilerOptions) {
options = plugin.resolveTemplateCompilerOptions(options);
}
}
for (const plugin of plugins) {
let result;
try {
result = plugin.compileSFCTemplate?.(base.lang, base.content, options);
}
catch (e) {
const err = e;
errors.push(err);
}
if (result || errors.length) {
if (result && !errors.length && !warnings.length) {
cache = {
template: base.content,
snapshot: getUntrackedSnapshot(),
result: result,
plugin,
};
}
else {
cache = undefined;
}
return {
errors,
warnings,
ast: result?.ast,
};
}
}
return {
errors,
warnings,
ast: undefined,
};
});
}
function computedNullableSfcBlock(name, defaultLang, getBlock, resolve) {
const hasBlock = (0, alien_signals_1.computed)(() => !!getBlock());
return (0, alien_signals_1.computed)(() => {
if (!hasBlock()) {
return;
}
const _block = (0, alien_signals_1.computed)(() => getBlock());
return resolve(_block, computedSfcBlock(name, defaultLang, _block));
});
}
function computedSfcBlock(name, defaultLang, getBlock) {
const getLang = (0, alien_signals_1.computed)(() => getBlock().lang ?? defaultLang);
const getAttrs = (0, alien_signals_1.computed)(() => getBlock().attrs); // TODO: computed it
const getContent = (0, alien_signals_1.computed)(() => getBlock().content);
const getStartTagEnd = (0, alien_signals_1.computed)(() => getBlock().loc.start.offset);
const getEndTagStart = (0, alien_signals_1.computed)(() => getBlock().loc.end.offset);
const getStart = (0, alien_signals_1.computed)(() => getUntrackedSnapshot().getText(0, getStartTagEnd()).lastIndexOf('<' + getBlock().type));
const getEnd = (0, alien_signals_1.computed)(() => getEndTagStart() + getUntrackedSnapshot().getText(getEndTagStart(), getUntrackedSnapshot().getLength()).indexOf('>') + 1);
return {
name,
get lang() { return getLang(); },
get attrs() { return getAttrs(); },
get content() { return getContent(); },
get startTagEnd() { return getStartTagEnd(); },
get endTagStart() { return getEndTagStart(); },
get start() { return getStart(); },
get end() { return getEnd(); },
};
}
function computedAttrValue(key, base, getBlock) {
return (0, alien_signals_1.computed)(() => {
const val = getBlock()[key];
if (typeof val === 'object') {
return {
...val,
offset: base.start + val.offset,
};
}
return val;
});
}
}
function mergeObject(a, b) {
return Object.defineProperties(a, Object.getOwnPropertyDescriptors(b));
}
//# sourceMappingURL=computedSfc.js.map

View File

@@ -0,0 +1,4 @@
import type { SFCParseResult } from '@vue/compiler-sfc';
import type * as ts from 'typescript';
import type { VueLanguagePluginReturn } from '../types';
export declare function computedVueSfc(plugins: VueLanguagePluginReturn[], fileName: string, languageId: string, getSnapshot: () => ts.IScriptSnapshot): () => SFCParseResult | undefined;

View File

@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.computedVueSfc = computedVueSfc;
const alien_signals_1 = require("alien-signals");
function computedVueSfc(plugins, fileName, languageId, getSnapshot) {
let cache;
return (0, alien_signals_1.computed)(() => {
// incremental update
if (cache?.plugin.updateSFC) {
const change = getSnapshot().getChangeRange(cache.snapshot);
if (change) {
const newSfc = cache.plugin.updateSFC(cache.sfc, {
start: change.span.start,
end: change.span.start + change.span.length,
newText: getSnapshot().getText(change.span.start, change.span.start + change.newLength),
});
if (newSfc) {
cache.snapshot = getSnapshot();
// force dirty
cache.sfc = JSON.parse(JSON.stringify(newSfc));
return cache.sfc;
}
}
}
for (const plugin of plugins) {
const sfc = plugin.parseSFC?.(fileName, getSnapshot().getText(0, getSnapshot().getLength()))
?? plugin.parseSFC2?.(fileName, languageId, getSnapshot().getText(0, getSnapshot().getLength()));
if (sfc) {
if (!sfc.errors.length) {
cache = {
snapshot: getSnapshot(),
sfc,
plugin,
};
}
return sfc;
}
}
});
}
//# sourceMappingURL=computedVueSfc.js.map

View File

@@ -0,0 +1,11 @@
import type { Mapping } from '@volar/language-core';
import type { Code } from '../types';
export declare class VueEmbeddedCode {
id: string;
lang: string;
content: Code[];
parentCodeId?: string;
linkedCodeMappings: Mapping[];
embeddedCodes: VueEmbeddedCode[];
constructor(id: string, lang: string, content: Code[]);
}

View File

@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VueEmbeddedCode = void 0;
class VueEmbeddedCode {
constructor(id, lang, content) {
this.id = id;
this.lang = lang;
this.content = content;
this.linkedCodeMappings = [];
this.embeddedCodes = [];
}
}
exports.VueEmbeddedCode = VueEmbeddedCode;
//# sourceMappingURL=embeddedFile.js.map

View File

@@ -0,0 +1,29 @@
import type { VirtualCode } from '@volar/language-core';
import type * as ts from 'typescript';
import type { VueCompilerOptions, VueLanguagePluginReturn } from '../types';
export declare class VueVirtualCode implements VirtualCode {
fileName: string;
languageId: string;
initSnapshot: ts.IScriptSnapshot;
vueCompilerOptions: VueCompilerOptions;
plugins: VueLanguagePluginReturn[];
ts: typeof import('typescript');
id: string;
private _snapshot;
private _vueSfc;
private _sfc;
private _embeddedCodes;
private _mappings;
get snapshot(): ts.IScriptSnapshot;
get vueSfc(): import("@vue/compiler-sfc").SFCParseResult | undefined;
get sfc(): import("../types").Sfc;
get embeddedCodes(): VirtualCode[];
get mappings(): {
sourceOffsets: number[];
generatedOffsets: number[];
lengths: number[];
data: import("@volar/language-core").CodeInformation;
}[];
constructor(fileName: string, languageId: string, initSnapshot: ts.IScriptSnapshot, vueCompilerOptions: VueCompilerOptions, plugins: VueLanguagePluginReturn[], ts: typeof import('typescript'));
update(newSnapshot: ts.IScriptSnapshot): void;
}

View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VueVirtualCode = void 0;
const alien_signals_1 = require("alien-signals");
const plugins_1 = require("../plugins");
const computedEmbeddedCodes_1 = require("./computedEmbeddedCodes");
const computedSfc_1 = require("./computedSfc");
const computedVueSfc_1 = require("./computedVueSfc");
class VueVirtualCode {
// others
get snapshot() {
return this._snapshot();
}
get vueSfc() {
return this._vueSfc();
}
get sfc() {
return this._sfc;
}
get embeddedCodes() {
return this._embeddedCodes();
}
get mappings() {
return this._mappings();
}
constructor(fileName, languageId, initSnapshot, vueCompilerOptions, plugins, ts) {
this.fileName = fileName;
this.languageId = languageId;
this.initSnapshot = initSnapshot;
this.vueCompilerOptions = vueCompilerOptions;
this.plugins = plugins;
this.ts = ts;
// sources
this.id = 'main';
this._snapshot = (0, alien_signals_1.signal)(undefined);
// computeds
this._vueSfc = (0, computedVueSfc_1.computedVueSfc)(this.plugins, this.fileName, this.languageId, this._snapshot);
this._sfc = (0, computedSfc_1.computedSfc)(this.ts, this.plugins, this.fileName, this._snapshot, this._vueSfc);
this._embeddedCodes = (0, computedEmbeddedCodes_1.computedEmbeddedCodes)(this.plugins, this.fileName, this._sfc);
this._mappings = (0, alien_signals_1.computed)(() => {
const snapshot = this._snapshot();
return [{
sourceOffsets: [0],
generatedOffsets: [0],
lengths: [snapshot.getLength()],
data: plugins_1.allCodeFeatures,
}];
});
this._snapshot(initSnapshot);
}
update(newSnapshot) {
this._snapshot(newSnapshot);
}
}
exports.VueVirtualCode = VueVirtualCode;
//# sourceMappingURL=vueFile.js.map