组装个支持记笔记的CodePen

简介: 前言emmm。。。,有好长一段时间没码文了(近几个月实在是太忙了),这个玩具刚好是这两周抽空拼的拿出来和大家分享一下朋友最近刚学前端,经常问一些问题,通过聊天软件发代码和贴图实在是不太方便,就给它推荐了CodePen

前言

emmm。。。,有好长一段时间没码文了(近几个月实在是太忙了),这个玩具刚好是这两周抽空的拿出来和大家分享一下

朋友最近刚学前端,经常问一些问题,通过聊天软件发代码和贴图实在是不太方便,就给它推荐了

前者国内访问实在不稳定,后者emm有个内容审核,导致内容实时性较差。

就搜了搜GitHub上有不有类似的项目,搜了一圈还真有不少,这里贴两个感觉不错的

朋友使用后反馈,问了问有不有啥支持直接在旁边 写笔记贴图片(这个),方便做记录

emm...,检索了一圈记忆中除了 VsCode 好像还真没有这种东西(可能是我孤陋寡闻了,读者有推荐的可以评论区补充)

那就给他造个吧,练练手,有段时间没写自己的代码了

等不及了?屏幕截图 2023-12-13 170630.png

技术选型

行动肯定是要站在巨人的"键盘上"(手滑打错了,那就这样吧),先看看有哪些可用的 "零件"

内容编辑器

先是写笔记部分,挑了几个库玩了一下

  • 开源 Web 富文本编辑器,开箱即用,配置简单
  • 支持部分MarkDown语法,可拓展定制的富文本编辑器
  • 基于Vue3开发的块编辑器
  • 官方:"Next generation block styled editor.",下一代块编辑器

因为屏幕中需要展示 笔记/代码/预览 3个部分,直接使用markdown语法会有个切换的动作(这里就不考虑markdown格式做编辑了)

上述的几个库分大体上分为两类富文本编辑器块编辑器

屏幕截图 2023-12-13 170740.png

前者是比较传统的编辑器,后者从社区反应来看像是下一代趋势,但国内好像还没看到使用此方案的成熟产品(可能是我孤陋寡闻了,读者有推荐的可以评论区补充)

最后本着技术尝鲜(喜欢折腾)的精神选了,官方提供了一系列的基础插件,提供了简单的API支持自定义的插件(后面单独开文章介绍)

代码编辑器

这个就  没得跑了

不过在使用之前先看了一下最近阿里开源的 ,看之前以为是个可直接用的NPM包,文档翻了半天,只给了个demo,emm 拉下来,果然如官方预料 卡在了 yarn install,感觉有一定上手成本,先不看了

后端部分

思考了一下都是简单的CRUD场景,存储和鉴权MongoDBRedis感觉就够了(也没有配置成本,安装即用)

服务端框架部分就直接拿自己的之前写的玩具开整

接下来就是组装了

实现

项目搭建

粗糙的原型图如下

屏幕截图 2023-12-13 170845.png

布局也比较简单

<header>工具条...</header>
<!-- 主体内容 --> 
<main>
    <Note />
    <Code />
    <Render />
</main>

直接拿之前整的模板进行创建 出前端工程

文本编辑部分

示例代码在线演示:

code.sugarat.top/share/630b8…

直接 ,CV起来就能运行,这里仅仅贴几个关键部分(避免代码占用太大篇幅,降低文章可读性)

 const editor = new EditorJS({
    holder: 'note-editor',
    placeholder: '在这里开始记录你的笔记',
    onReady: () => {
      console.log('Editor.js is ready to work!')
      // 内容初始化
      // 下方拿到的outputData直接塞进来就行
      // 可以在页面加载的时候从数据库取历史数据进行展示
      editor.render(xxx)
    },
    onChange: (api, e) => {
      editor
        .save()
        .then((outputData) => {
            // 取得编写的内容
            // 可以将这个内存存起来
            // 在合适的时机调接口存入数据库即可
        })
        .catch((error) => {
          console.log('Saving failed: ', error)
        })
    },
    tools: {
      // 图片处理
      image: {
        class: Image,
        config: {
          uploader: {
            uploadByFile(file: File) {
              // 需要自己处理图片上传逻辑 
              // 按结构进行返回即可
              return {
                success: 1,
                file: {
                  url: 'https://img.cdn.sugarat.top/online-editor/6302403434e52962875fbf3e/1661169105550/pupza3m486'
                }
              }
            }
          }
        }
      },
    // 一系列官方插件
      header: Header,
      list: List,
    //   ...
    },
    i18n: {
      // 国际化相关配置
    }
  })

代码编辑

参照官方文档,几步就起飞,直接调用 monaco-editor 进行初始化,以HTML编辑器为例

import * as monaco from 'monaco-editor'
const htmlEditor = monaco.editor.create(
  document.getElementById('html-editor'),
  {
    // 初始化展示的内容
    value: '<h1>hello world</h1>',
    language: 'html',
    theme: 'vs-dark',
    fontSize: 18,
    automaticLayout: true
  }
)
// 如果数据是异步从接口拿,那么可以调用setValue方法,设置内容
setTimeout(() => {
  htmlEditor.setValue('<h2>hello world</h2>')
}, 2000);
// 在这个方法监听编辑器的内容变动
htmlEditor.onDidChangeModelContent(() => {
  const newValue = htmlEditor.getValue()
})

通过上述3个简单的方法即可实现1个简单编辑器的内容读写

当然笔者这里用的Vite + Vue3,这里还有几个坑

导入worker,参考

// main.ts
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import JSONWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import CSSWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import HTMLWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import TSWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
self.MonacoEnvironment = {
  getWorker(_: any, label: string) {
    if (label === 'json') {
      return new JSONWorker()
    }
    if (label === 'css' || label === 'scss' || label === 'less') {
      return new CSSWorker()
    }
    if (label === 'html' || label === 'handlebars' || label === 'razor') {
      return new HTMLWorker()
    }
    if (label === 'typescript' || label === 'javascript') {
      return new TSWorker()
    }
    return new EditorWorker()
  }
}

editor使用ref绑定调用的时候需要toRaw,不然应用就会卡死

const htmlEditor = ref<monaco.editor.IStandaloneCodeEditor>(null as any)
// 根据props更新
watchEffect(() => {
  if (htmlEditor.value && props.html) {
    toRaw(htmlEditor.value).setValue(props.html)
  }
})
onMounted(()=>{
  // 初始化实例
  htmlEditor.value = monaco.editor.create({...ops})
    toRaw(htmlEditor.value).onDidChangeModelContent(() => {
    console.log(toRaw(htmlEditor.value).getValue())
  })
})

代码渲染

这里使用iframe承载内容,期望iframe里页面最终结构如下

<head>
  <style>
    /* 内置一些样式重载 */
  </style>
  <style>
    /* 用户书写样式 */
  </style>
</head>
<body>
  <!--console信息面板 -->
  <script src="//cdn.jsdelivr.net/npm/eruda"></script>
  <script>
    if(window.eruda){
      window.eruda.init({
        defaults: {
          displaySize: 25,
          transparency: 0.9,
        }
      })
      window.eruda.show()
    }
  </script>
  <div>
    <!-- ...用户html代码 -->
  </div>
  <script>
    // 用户js代码
  </script>
</body>

其中主要用于展示 iframe 页面的 log 日志信息、 Dom 结构等,避免打开Chrome DevTools

所有dom的创建均通过 Dom API 完成

在线预览示例代码效果:

code.sugarat.top/share/6312f…

下面简单过一下实现的代码

先给iframe搞个样式,避免看不到

<style>
iframe {
  width: 100%;
  height: 500px;
}
</style>

然后来3个dom承载我们的三剑客代码

// 一系列用户编写的代码
const cssCode = `h1{
    color:red;
}`;
const htmlCode = `<h1>hello world</h1>`;
const jsCode = `console.log("hello world")`;
// 3个dom
const $style = document.createElement("style");
$style.innerHTML = cssCode;
const $html = document.createElement("div");
$html.innerHTML = htmlCode;
const $userScript = document.createElement("script");
$userScript.textContent = jsCode;

紧接着创建iframe将其装进去 就okk了

const $iframe = document.createElement("iframe");
$iframe.addEventListener("load", () => {
  $iframe.contentDocument?.head.append($style);
  $iframe.contentDocument.body.append($html, $userScript);
});
document.body.append($iframe);

如果要引入eruda,咱们需要先等eruda加载完再插入咱们得script,不然捕获不到代码console

详细如下

$iframe.addEventListener("load", () => {
  $iframe.contentDocument.head.append($style);
  // eruda cdn资源
  const $eruda = document.createElement("script");
  $eruda.src = "//cdn.jsdelivr.net/npm/eruda";
  // 打开面板的代码
  const debugExec = document.createElement("script");
  debugExec.textContent = `window.eruda.init({
      defaults: {
        displaySize: 25,
        transparency: 0.9,
      }
    })
    eruda.show()
    `;
  $iframe.contentDocument?.body.append($eruda);
  // eruda 加载完再加载HTML与用户脚本
  $eruda.onload = function () {
    $iframe.contentDocument?.body.append(debugExec, $html, $userScript);
  };
});

代码格式化

写完代码后,格式化是很有必要的,避免乱糟糟

笔者这里直接采用prettier,啥都提供好了,CV一下就能用

出参入参都比较简单

import prettier from 'prettier/standalone'
import htmlPlugin from 'prettier/parser-html'
import cssPlugin from 'prettier/parser-postcss'
import jsPlugin from 'prettier/parser-babel'
export function formatHTML(html: string) {
  return prettier.format(html, {
    parser: 'html',
    plugins: [htmlPlugin]
  })
}
export function formatJS(tsCode: string) {
  return prettier.format(tsCode, {
    parser: 'babel',
    plugins: [jsPlugin]
  })
}
export function formatCSS(css: string) {
  return prettier.format(css, {
    parser: 'css',
    plugins: [cssPlugin]
  })
}

到这里构成应用的几个主要开源库的使用介绍差不多完了

前人栽树后人乘凉,对UI/UE要求不高的时候,拼凑出一个新的应用花费时间不高

当有一丝想法的时候,得及时抓住。

最后

EditorJS还是有很大的可玩性,后面有机会围绕输出一些插件实践,试着整个编辑器替代日常的Markdown

这个在线的Code也会在后续中会把一些想法迭代进去,目前看主要是代码编辑部分不太友好(代码提示这一块),访问了其它的在线编码的,基本都有这个问题(无论怎么搞都达不到本地VS Code的地步)

后面准备另辟蹊径搞一搞,先把Web版的交互优化一下下

相关文章
|
5天前
|
算法 搜索推荐 C++
统一结果封装
统一结果封装
17 0
|
8月前
快速生成通用接口业务配置
快速生成通用接口业务配置
|
11月前
|
传感器
(4)(4.2.3) NAVIO2的组装和布线快速入门
(4)(4.2.3) NAVIO2的组装和布线快速入门
129 0
|
SQL Java 数据库连接
数据层设计与开发(数据层标准实现类) | 学习笔记
简介:快速学习数据层设计与开发(数据层标准实现类)
127 0
数据层设计与开发(数据层标准实现类) | 学习笔记
|
数据库 开发者
数据层设计与开发(定义数据层标准) | 学习笔记
简介:快速学习数据层设计与开发(定义数据层标准)
155 0
数据层设计与开发(定义数据层标准) | 学习笔记
|
架构师 测试技术 微服务
组装式开发
组装式开发组装式开发
|
存储 数据可视化
浅谈组装式的应用
浅谈组装式的应用。
|
开发框架 运维 安全
浅谈组装式应用
在数字化转型的浪潮中,企业数字化转型在实施过程中所面临的问题和挑战非常的明显,包括 - 交付成本高、质量低、客户满意度低 - 代码难以复用 、无法形成有效沉淀 - 无法形成行业竞争力 、不可持续等等 在这种情况下,如何降低交付成本,提升交付效率,提高客户满意度,并且实现可持续的能力沉淀,成为数字化转型实施者的当务之急。
6584 14
浅谈组装式应用
|
人工智能 数据可视化 安全
组装式应用
组装式应用
763 0
|
ARouter Android开发
浅谈组装式应用--Android组件化开发
当我们做项目的时候,大部分功能都是重复的,尤其一些定制化saas的APP,提供基础版本后,进行定制修改,但是可能有6,7成的功能是重复的,这样子就造成大量的浪费,如果我们像一个组装积木一样,对公共模块以及定制模块进行组装,这样子来提高人效,于是引入了组件化开发。
浅谈组装式应用--Android组件化开发