@ariesate/editors
Advanced tools
Comparing version 1.0.0 to 1.0.1
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
6212877
39
2240
5
4
7
3
+ Added@codexteam/shortcuts@^1.1.1
+ Added@codexteam/shortcuts@1.2.0(transitive)