@ariesate/editors
Advanced tools
Comparing version
88
api.js
import axios from 'axios' | ||
const instance = axios.create({ | ||
baseURL : __API_SERVER__, // 在 server.js 中定义的 | ||
}) | ||
export function createAPI(apiServer) { | ||
const instance = axios.create({ | ||
baseURL : apiServer, // 在 server.js 中定义的 | ||
}) | ||
export const api = new Proxy({}, { | ||
get(target, method) { | ||
return (...argv) => { | ||
const hasFile = argv.some(a => a instanceof File) | ||
if (!hasFile) { | ||
return instance.post('/', { | ||
method, | ||
argv, | ||
return new Proxy({}, { | ||
get(target, method) { | ||
return (...argv) => { | ||
const hasFile = argv.some(a => a instanceof File) | ||
if (!hasFile) { | ||
return instance.post('/', { | ||
method, | ||
argv, | ||
}).then(({ data }) => { | ||
// TODO 处理可能出现的问题 | ||
return data.result | ||
}) | ||
} | ||
// 有 file 的情况 | ||
const files = [] | ||
const transformedArgv = [] | ||
argv.forEach((a, i) => { | ||
if( a instanceof File){ | ||
files.push({index:i, file:a}) | ||
transformedArgv.push(`__file.${i}`) | ||
}else { | ||
transformedArgv.push(a) | ||
} | ||
}) | ||
const formData = new FormData(); | ||
formData.append('method', method) | ||
formData.append('argv', JSON.stringify(transformedArgv)) | ||
files.forEach(({ index, file}) => { | ||
formData.append(index, file) | ||
}) | ||
return instance.post('/', formData, { | ||
headers: { | ||
'Content-Type': 'multipart/form-data' | ||
} | ||
}).then(({ data }) => { | ||
@@ -19,38 +50,9 @@ // TODO 处理可能出现的问题 | ||
}) | ||
} | ||
// 有 file 的情况 | ||
const files = [] | ||
const transformedArgv = [] | ||
argv.forEach((a, i) => { | ||
if( a instanceof File){ | ||
files.push({index:i, file:a}) | ||
transformedArgv.push(`__file.${i}`) | ||
}else { | ||
transformedArgv.push(a) | ||
} | ||
}) | ||
const formData = new FormData(); | ||
formData.append('method', method) | ||
formData.append('argv', JSON.stringify(transformedArgv)) | ||
files.forEach(({ index, file}) => { | ||
formData.append(index, file) | ||
}) | ||
return instance.post('/', formData, { | ||
headers: { | ||
'Content-Type': 'multipart/form-data' | ||
} | ||
}).then(({ data }) => { | ||
// TODO 处理可能出现的问题 | ||
return data.result | ||
}) | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
#!/usr/bin/env node | ||
import open from 'open' | ||
import { setup } from '../server.js' | ||
import {readdir, readFile, writeFile} from "fs/promises"; | ||
import {readdir, readFile, writeFile, mkdir, rmdir} from "fs/promises"; | ||
const { viewPort } = await setup({ | ||
readdir, | ||
mkdir, | ||
rmdir, | ||
readFile, | ||
writeFile | ||
writeFile, | ||
}) | ||
@@ -11,0 +13,0 @@ |
/** @jsx createElement */ | ||
import { createElement, render, reactive, ref, refComputed, useViewEffect, watchReactive } from 'axii' | ||
import {Split, useLocation, IconPark} from 'axii-components' | ||
import message from 'axii-components/message/message.jsx' | ||
import path from 'path-browserify' | ||
import Split from 'axii-components/split/Split.jsx' | ||
import message from 'axii-components/message/message.jsx' | ||
import useLocation from 'axii-components/hooks/useLocation.js' | ||
import defaultPredefinedEditors from '../predefinedEditors.json' | ||
import { createAPI } from '../api' | ||
@@ -11,6 +12,2 @@ import FileTree from "./FileTree"; | ||
import { editors } from './editors.jsx' | ||
import { api } from '../api' | ||
const location = useLocation() | ||
@@ -30,13 +27,31 @@ | ||
function getContainer(root, path) { | ||
let base = { children: root } | ||
path.forEach(p => { | ||
base = base.children.find(b => b.key === p) | ||
}) | ||
return base.children | ||
} | ||
const loadData = async (pathToRead, dirContainer) => { | ||
const loadData = async ({api, dirs}, pathToRead, dirContainer) => { | ||
const files = await api.readdir(pathToRead) | ||
if(!dirContainer.splice) debugger | ||
console.log(dirContainer) | ||
dirContainer.splice(0, dirContainer.length) | ||
files.forEach(file => { | ||
dirContainer.push({ | ||
title: file, | ||
// CAUTION 支持伪造的文件系统 | ||
dirContainer.push((typeof file === 'object') ? { | ||
// 自己提供 name,key, | ||
...file, | ||
title: file.name, | ||
path: path.join(pathToRead, file.key), | ||
relativePath: path.join(pathToRead, file.key).replace(dirs.cwd, ''), | ||
children: isDir(file.name) ? undefined : [] | ||
} :{ | ||
name: file, | ||
fold: true, | ||
title: file, // 这是 menu 的数据格式要的,为了方便先卸载这里 | ||
key: file, | ||
path: path.join(pathToRead, file), | ||
relativePath: path.join(pathToRead, file).replace(__CWD_DIR__, ''), | ||
relativePath: path.join(pathToRead, file).replace(dirs.cwd, ''), | ||
children: isDir(file) ? undefined : [] | ||
@@ -47,11 +62,24 @@ }) | ||
const findEditor = ({dirs, predefinedEditors}, filePath) => { | ||
const editor = predefinedEditors.find(({ extension: inputExtension }) => { | ||
const extensionsRegExp = (Array.isArray(inputExtension) ? inputExtension : [inputExtension]).map(extension => { | ||
return new RegExp(extension.replace('.', '\.') + '$') | ||
}) | ||
return extensionsRegExp.some(exp => exp.test(filePath)) | ||
}) | ||
return editor ? { | ||
//之后再改 | ||
path: `${dirs.viewServer}${editor.path}` | ||
} : null | ||
} | ||
export default function EditorContainer() { | ||
export default function EditorContainer({ dirs, api = createAPI(dirs.apiServer), getEditorFrameURL, predefinedEditors=defaultPredefinedEditors}) { | ||
const fileTreeData = reactive([]) | ||
const openedFiles = reactive([]) | ||
const activeKey = ref() | ||
const activeFilePath = ref([]) | ||
const onOpenFile = async (filePath) => { | ||
const fileTitle = filePath.split('/').reverse()[0] | ||
const editor = editors.find(({ test }) => test.test(fileTitle)) | ||
const editor = findEditor({dirs, predefinedEditors}, fileTitle) | ||
if (!editor) { | ||
@@ -63,16 +91,3 @@ message.error('没有合适的 editor') | ||
if (!openedFiles.includes(filePath)) { | ||
openedFiles.push({ path: filePath, title: fileTitle }) | ||
// 异步加载内容 | ||
const stringContent = await api.readFile(path.join(__CWD_DIR__, filePath), readAsText(filePath) ? {encoding: 'utf-8'} : null) | ||
const file = openedFiles.find(({ path: p }) => p === filePath ) | ||
if (editor.setup) { | ||
file.data = await editor.setup(stringContent) | ||
} else { | ||
file.data = { | ||
content: stringContent | ||
} | ||
} | ||
file.ready = true | ||
openedFiles.push({ path: filePath, title: fileTitle, editor }) | ||
} | ||
@@ -85,20 +100,8 @@ | ||
if (dir.children.length === 0 ) { | ||
loadData(path.join(__CWD_DIR__, dirPathToOpen), dir.children) | ||
loadData({api, dirs} ,path.join(dirs.cwd, dirPathToOpen), dir.children) | ||
} | ||
} | ||
const onSave = async (data) => { | ||
const file = openedFiles.find(f => f.path === activeKey.value) | ||
console.log(file.path, data) | ||
if (isJSONFile(file.path)){ | ||
await api.writeFile(path.join(__CWD_DIR__, file.path), JSON.stringify(data, null, 4)) | ||
message.success('写入成功') | ||
} else { | ||
await api.writeFile(path.join(__CWD_DIR__, file.path), data) | ||
message.success('写入成功') | ||
} | ||
} | ||
useViewEffect(async () => { | ||
await loadData(__CWD_DIR__, fileTreeData) | ||
await loadData({dirs, api}, dirs.cwd, fileTreeData) | ||
// 自动打开 | ||
@@ -114,6 +117,53 @@ if (location.query.path) await onOpenFile(location.query.path) | ||
const onAddDir = async () => { | ||
console.log(activeFilePath) | ||
await api.mkdir(path.join(dirs.cwd, activeFilePath.value.concat(await prompt('folder name:')).join('/'))) | ||
await loadData( | ||
{dirs, api}, | ||
path.join(dirs.cwd, activeFilePath.value.join('/')), | ||
getContainer(fileTreeData, activeFilePath.value) | ||
) | ||
} | ||
const onDeleteDir = async () => { | ||
// TODO 要 confirm | ||
// alert(`删除 ${path.join(dirs.cwd, activeFilePath.value.join('/'))}`) | ||
await api.rmdir(path.join(dirs.cwd, activeFilePath.value.join('/'))) | ||
activeFilePath.value = activeFilePath.value.slice(0, activeFilePath.value.length -1) | ||
await loadData( | ||
{dirs, api}, | ||
path.join(dirs.cwd, activeFilePath.value.join('/')), | ||
getContainer(fileTreeData, activeFilePath.value.slice(0, activeFilePath.value.length - 1)) | ||
) | ||
} | ||
const createFile = async (editor) => { | ||
console.log(editor) | ||
const fileName = `${await prompt('file name:')}${Array.isArray( editor.extension) ? editor.extension[0] : editor.extension}` | ||
const fullFilePath = path.join(dirs.cwd, activeFilePath.value.concat(fileName).join('/')) | ||
await api.writeFile(fullFilePath, editor.emptyContent) | ||
await loadData( | ||
{dirs, api}, | ||
path.join(dirs.cwd, activeFilePath.value.join('/')), | ||
getContainer(fileTreeData, activeFilePath.value) | ||
) | ||
// 自动代开 | ||
return onOpenFile(fullFilePath) | ||
} | ||
return ( | ||
<shadowContainer block block-height="100%"> | ||
<toolbar block> | ||
<IconPark type='FolderPlus' onClick={onAddDir} size={1.4}/> | ||
<IconPark type='FolderMinus' onClick={onDeleteDir} size={1.4}/> | ||
{predefinedEditors.map(editor => { | ||
return <addFile onClick={() => createFile(editor)}> | ||
<IconPark type='FileAddition'/> | ||
<fileTypeName>{editor.name}</fileTypeName> | ||
</addFile> | ||
})} | ||
</toolbar> | ||
<Split layout:block layout:block-height="100%" asideLeft> | ||
<FileTree data={fileTreeData} onOpenFile={onOpenFile} onOpenDir={openDir}/> | ||
<FileTree data={fileTreeData} onOpenFile={onOpenFile} onOpenDir={openDir} activeFilePath={activeFilePath}/> | ||
<Workspace | ||
@@ -127,4 +177,4 @@ layout:block | ||
activeKey={activeKey} | ||
editors={editors} | ||
onSave={onSave} | ||
dirs={dirs} | ||
getEditorFrameURL={getEditorFrameURL} | ||
/> | ||
@@ -131,0 +181,0 @@ <fileTree>editor</fileTree> |
@@ -20,3 +20,3 @@ /** @jsx createElement */ | ||
export default function FileTree({ onOpenFile, onOpenDir, data }) { | ||
export default function FileTree({ onOpenFile, onOpenDir, data, activeFilePath }) { | ||
const openDir = disableDraft((item, parents) => { | ||
@@ -27,9 +27,8 @@ onOpenDir(path.join(...parents.map(p => p.title), item.title), item) | ||
const openFile = disableDraft((item, parents) => { | ||
console.log(parents) | ||
onOpenFile(path.join(...parents.map(p => p.title), item.title), item) | ||
}) | ||
return <MenuWithDBClick data={data} onOpen={openDir} onItemDBClick={openFile}/> | ||
return <MenuWithDBClick data={data} onOpen={openDir} activeItemKeyPath={activeFilePath} onItemDBClick={openFile} /> | ||
} | ||
/** @jsx createElement */ | ||
import { createElement, render, reactive, ref, computed, useViewEffect, disableDraft } from 'axii' | ||
import Tab from 'axii-components/tabs/Tabs.jsx' | ||
import editors from '../predefinedEditors.json' | ||
import path from 'path-browserify' | ||
const TabFullHeight = Tab.extend(function FullHeight(fragments) { | ||
@@ -25,6 +27,9 @@ fragments.root.elements.container.style({ | ||
}) | ||
}) | ||
export default function Workspace({ openedFiles, activeKey, editors, onSave }) { | ||
// TODO 中间的内容改成 Iframe | ||
export default function Workspace({ openedFiles, activeKey, getEditorFrameURL }) { | ||
// CAUTION 这里是一个典型数据变化作为信号传递的场景? | ||
@@ -38,5 +43,3 @@ return ( | ||
{() => { | ||
const Editor = editors.find(({ test }) => test.test(file.title)).editor | ||
console.log(file) | ||
return file.ready ? <Editor {...file.data} onSave={onSave} title={file.title} layout:block layout:block-height="100%" /> : <loading>加载中</loading> | ||
return <iframe src={getEditorFrameURL(file)} height="100%" width="100%"/> | ||
}} | ||
@@ -43,0 +46,0 @@ </Tab.TabPane> |
@@ -1,5 +0,49 @@ | ||
/** @jsx createElement */ | ||
import { createElement, render, reactive, ref, refComputed } from 'axii' | ||
import EditorContainer from './components/EditorContainer' | ||
/** | ||
* TODO | ||
* 1. 读取 url 上的 filePath 和 editor,动态加载 editor. | ||
* 2. editor 应该自己启动 | ||
* 通过 window 上注入的 api 去读文件 | ||
*/ | ||
render(<EditorContainer />, document.getElementById('root')) | ||
// TODO api 也是根据情况来决定到底是用 本地 还是 github 之类的 | ||
import { createAPI } from './api' | ||
const dirs = { | ||
cwd: __CWD_DIR__, | ||
viewServer: __VIEW_SERVER__, | ||
apiServer: __API_SERVER__, | ||
} | ||
const api = createAPI(dirs.apiServer) | ||
function parseSearch(search) { | ||
return search ? search.slice(1).split('&').reduce((last, currentPair) => { | ||
const current = currentPair.split('=') | ||
return { | ||
...last, | ||
[current[0]]: current[1] || true | ||
} | ||
}, {}) : {} | ||
} | ||
async function setup() { | ||
const params = parseSearch(location.search) | ||
/* @vite-ignore */ | ||
const { setup, render, readAsText } = await import(params.editor) | ||
// 先去官方地址找 注册了的 editor 地址?再去 unpkg 上找? | ||
const content = await api.readFile(params.path, readAsText ? { encoding: 'utf-8' } : undefined) | ||
const props = setup ? | ||
await setup(content, params.path, {dirs, api}) : | ||
{ content } | ||
const onSave = async (content) => { | ||
// TODO 图片怎么处理 | ||
await api.writeFile(params.path, content) | ||
} | ||
// TODO 编辑器外面iframe传过来的 save 信号怎么处理。 | ||
const instance = render({ ...props, title: params.title, onSave }, document.getElementById('root')) | ||
} | ||
setup() |
{ | ||
"name": "@ariesate/editors", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "", | ||
@@ -9,2 +9,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"@codexteam/shortcuts": "^1.1.1", | ||
"axios": "^0.21.1", | ||
@@ -16,2 +17,4 @@ "lodash-es": "^4.17.20", | ||
"devDependencies": { | ||
"@babel/generator": "^7.13.9", | ||
"@babel/parser": "^7.13.12", | ||
"less": "^3.12.2", | ||
@@ -21,3 +24,4 @@ "vite": "^2.0.0" | ||
"scripts": { | ||
"start": "node server.js" | ||
"start": "node ./bin/index.js", | ||
"build": "vite build" | ||
}, | ||
@@ -24,0 +28,0 @@ "bin": { |
@@ -10,4 +10,11 @@ import Koa from 'koa' | ||
import {readdir, readFile, writeFile} from "fs/promises"; | ||
import path from "path"; | ||
import {generatePredefinedEditorsMap} from "./scripts/generatePredefinedEditorsMap.js"; | ||
function makePath(relativePath) { | ||
return path.join(path.dirname(import.meta.url.replace('file:', '')), relativePath) | ||
} | ||
export function useAPI(serviceAPIs) { | ||
@@ -76,2 +83,3 @@ return async (ctx, next) => { | ||
const viteServer = await createServer({ | ||
root: fsPath.dirname(import.meta.url.replace('file:', '')), | ||
server: { | ||
@@ -83,3 +91,4 @@ port: availableViewPort, | ||
__CWD_DIR__ : JSON.stringify(fsPath.join(process.cwd(), './')), | ||
__API_SERVER__: JSON.stringify(`http://localhost:${apiPort}${path}`) | ||
__VIEW_SERVER__: JSON.stringify(`http://localhost:${availableViewPort}`), | ||
__API_SERVER__: JSON.stringify(`http://localhost:${availableAPIPort}${path}`) | ||
} | ||
@@ -95,6 +104,9 @@ }) | ||
await setup({ | ||
readdir, | ||
readFile, | ||
writeFile | ||
}) | ||
// TODO 没法读 jsx,先手写把 | ||
await generatePredefinedEditorsMap(makePath('./components/editors'), makePath('')) | ||
// await setup({ | ||
// readdir, | ||
// readFile, | ||
// writeFile | ||
// }) |
@@ -1,47 +0,1 @@ | ||
[ | ||
{ | ||
"name": "sheet1", | ||
"freeze": "A1", | ||
"styles": [], | ||
"merges": [], | ||
"rows": { | ||
"2": { | ||
"cells": { | ||
"0": { | ||
"text": "asdf" | ||
}, | ||
"1": { | ||
"text": "asdf" | ||
} | ||
} | ||
}, | ||
"6": { | ||
"cells": { | ||
"0": { | ||
"text": "asdf" | ||
} | ||
} | ||
}, | ||
"8": { | ||
"cells": { | ||
"1": { | ||
"text": "asdf" | ||
} | ||
} | ||
}, | ||
"9": { | ||
"cells": { | ||
"1": { | ||
"text": "asdf" | ||
} | ||
} | ||
}, | ||
"len": 100 | ||
}, | ||
"cols": { | ||
"len": 26 | ||
}, | ||
"validations": [], | ||
"autofilter": {} | ||
} | ||
] | ||
[] |
import path from 'path' | ||
const PACKAGE_ROOT_PATH = path.join(path.dirname(import.meta.url.replace('file:', '')), '../../engine/packages') | ||
const ASTIDE_PACKAGE_ROOT_PATH = path.join(path.dirname(import.meta.url.replace('file:', '')), '../../astide/packages') | ||
function makePath(relativePath) { | ||
return path.join(path.dirname(import.meta.url.replace('file:', '')), relativePath) | ||
} | ||
const PACKAGE_ROOT_PATH = makePath('../../engine/packages') | ||
const ASTIDE_PACKAGE_ROOT_PATH = makePath('../../astide/packages') | ||
export default { | ||
@@ -29,3 +34,14 @@ esbuild: { | ||
__DEV__: true | ||
}, | ||
build: { | ||
rollupOptions: { | ||
input: { | ||
DocEditor: makePath('./components/editors/DocEditor.jsx'), | ||
EREditor: makePath('./components/editors/EREditor.jsx'), | ||
SheetEditor: makePath('./components/editors/SheetEditor.jsx'), | ||
TableEditor: makePath('./components/editors/TableEditor.jsx'), | ||
ImageEditor: makePath('./components/editors/ImageEditor.jsx'), | ||
}, | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
6212877
3068.38%39
178.57%2240
156.29%5
25%4
100%4
300%8
100%3
Infinity%+ Added
+ Added