Compare commits

..

1 commit

Author SHA1 Message Date
sevichecc
cd787f3953
Add BlockNote,ref https://github.com/TypeCellOS/BlockNote/pull/130/files 2023-03-23 18:28:36 +08:00
25 changed files with 1718 additions and 1109 deletions

View file

@ -1,3 +0,0 @@
{
"extends": "@antfu"
}

15
.eslintrc.cjs Normal file
View file

@ -0,0 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'@antfu',
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
],
parserOptions: {
ecmaVersion: 'latest',
},
}

View file

@ -6,37 +6,26 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc && vite build", "build": "vue-tsc && vite build",
"preview": "vite preview" "preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"@milkdown/core": "^7.1.0", "@blocknote/core": "^0.4.5",
"@milkdown/ctx": "^7.1.0", "@tiptap/vue-3": "2.0.0-beta.220",
"@milkdown/plugin-block": "^7.1.0",
"@milkdown/plugin-clipboard": "^7.1.0",
"@milkdown/plugin-cursor": "^7.1.0",
"@milkdown/plugin-emoji": "^7.1.0",
"@milkdown/plugin-history": "^7.1.0",
"@milkdown/plugin-indent": "^7.1.0",
"@milkdown/plugin-math": "^7.1.0",
"@milkdown/plugin-slash": "^7.1.0",
"@milkdown/plugin-tooltip": "^7.1.0",
"@milkdown/preset-commonmark": "^7.1.0",
"@milkdown/preset-gfm": "^7.1.0",
"@milkdown/prose": "^7.1.0",
"@milkdown/theme-nord": "^7.1.0",
"@milkdown/transformer": "^7.1.0",
"@milkdown/utils": "^7.1.0",
"@milkdown/vue": "^7.1.0",
"@prosemirror-adapter/vue": "^0.2.2",
"masto": "^5.10.0", "masto": "^5.10.0",
"vue": "^3.2.45" "vue": "^3.2.47"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@types/node": "^18.14.2",
"@vue/eslint-config-typescript": "^11.0.2",
"@vue/tsconfig": "^0.1.3",
"eslint-plugin-vue": "^9.9.0",
"npm-run-all": "^4.1.5",
"@antfu/eslint-config": "^0.35.2", "@antfu/eslint-config": "^0.35.2",
"@tailwindcss/typography": "^0.5.9",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"eslint": "^8.34.0", "eslint": "^8.34.0",
"tailwindcss": "^3.2.7",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^4.1.0", "vite": "^4.1.0",
"vue-tsc": "^1.0.24" "vue-tsc": "^1.0.24"

File diff suppressed because it is too large Load diff

View file

@ -1,6 +0,0 @@
/* Copyright 2021, Milkdown by Mirone. */
module.exports = {
plugins: {
tailwindcss: {},
},
}

View file

@ -1,13 +1,24 @@
<script setup lang="ts"> <script lang="ts" setup>
import { MilkdownProvider } from '@milkdown/vue' import { ref } from 'vue'
import { ProsemirrorAdapterProvider } from '@prosemirror-adapter/vue' import blocks from './blocks.json'
import Milkdown from './components/Milkdown/Editor.vue' import BlockNoteView from './components/BlockNoteView.vue'
// TODO fix any
const content = ref<any[]>(blocks)
// const content: Block[] = ref(blocks) as Block[]
</script> </script>
<template> <template>
<MilkdownProvider> <BlockNoteView v-model="content">
<ProsemirrorAdapterProvider> <template #BlockSideMenu-AddBlock="{ staticParams }">
<Milkdown /> <button @click="staticParams.addBlock()">
</ProsemirrorAdapterProvider> +
</MilkdownProvider> </button>
</template>
<template #SlashMenuItem-Heading="{ menu, selected, onClick }">
<button :style="selected && 'font-weight: bold;'" @click="onClick()">
Slot {{ menu.name }}
</button>
</template>
</BlockNoteView>
</template> </template>

128
src/blocks.json Normal file
View file

@ -0,0 +1,128 @@
[
{
"id": "53fa8b49-af0b-457e-9e1d-dd254215720f",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "Curabitur nisi.",
"styles": {}
}
],
"children": []
},
{
"id": "aedd1f50-d699-4de1-9dd3-7841bc5f1fda",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "Vesatibulum ",
"styles": {}
},
{
"type": "text",
"text": "rutrum",
"styles": {
"bold": true
}
},
{
"type": "text",
"text": ", mi nec ",
"styles": {}
},
{
"type": "text",
"text": "elementum",
"styles": {
"italic": true
}
},
{
"type": "text",
"text": " vehicula, eros quam gravida nisl, id fringilla neque ante vel mi.",
"styles": {}
}
],
"children": []
},
{
"id": "53f09254-e419-44ea-a497-7b1087763a27",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "Vestibulum ullamcorper mauris at ligula.",
"styles": {}
}
],
"children": []
},
{
"id": "34df38b2-9c4b-4a0c-89fa-3bfa694d72ed",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "Maecenas vestibulum mollis diam.",
"styles": {}
}
],
"children": []
},
{
"id": "485903b5-2cd7-4e44-b0ac-978ff2f4f5bd",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "Donec posuere vulputate arcu.",
"styles": {}
}
],
"children": []
},
{
"id": "98a64490-e4d2-4928-9665-1efa7f32b3db",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "auie",
"styles": {}
}
],
"children": []
}
]

View file

@ -0,0 +1,53 @@
<script lang="ts" setup>
import type { ComponentInternalInstance } from 'vue'
import { getCurrentInstance, onMounted, ref } from 'vue'
import { BlockNoteEditor, defaultSlashMenuItems } from '@blocknote/core'
import type { Block, BlockNoteEditorOptions } from '@blocknote/core'
import { EditorContent } from '@tiptap/vue-3'
import { slashMenuFactory } from './SlashMenu/slashMenuFactory'
import { blockSideMenuFactory } from './BlockSideMenu/blockSideMenuFactory'
const props = defineProps<{
modelValue: Block[]
options?: BlockNoteEditorOptions
}>()
const emit = defineEmits<{
(event: 'update:modelValue', payload: Block[]): void
}>()
const component: ComponentInternalInstance = getCurrentInstance()!
const editor = ref()
onMounted(async () => {
// Convert md to html
const Editor = new BlockNoteEditor({})
const content = await Editor.blocksToHTML(props.modelValue)
const editor = new BlockNoteEditor({
parentElement: document.getElementById('app')!,
slashCommands: defaultSlashMenuItems,
uiFactories: {
// Create an example formatting toolbar which just consists of a bold toggle
// formattingToolbarFactory,
// // Create an example menu for hyperlinks
// hyperlinkToolbarFactory,
// Create an example menu for the /-menu
slashMenuFactory: slashMenuFactory(component),
// // Create an example menu for when a block is hovered
blockSideMenuFactory: blockSideMenuFactory(component),
},
onEditorContentChange() {
emit('update:modelValue', editor.topLevelBlocks)
},
editorDOMAttributes: {
class: 'editor',
},
_tiptapOptions: {
content,
},
})
// console.log(editor)
})
</script>
<template>
<div>
<EditorContent :editor="editor?._tiptapEditor" />
</div>
</template>

View file

@ -0,0 +1,101 @@
<script lang="ts" setup>
const props = defineProps<{
staticParams?: any
}>()
function addBlock() {
props.staticParams.addBlock()
}
function onDragStart(event: DragEvent) {
props.staticParams.blockDragStart(event)
}
function onDragEnd(event: DragEvent) {
props.staticParams.blockDragEnd(event)
}
</script>
<template>
<div>
<slot :static-params="staticParams">
<slot name="BlockSideMenu-AddBlock" :static-params="staticParams">
<button class="block-side-menu__button" @click="addBlock">
<svg
stroke="currentColor" fill="currentColor" stroke-width="0" t="1551322312294" viewBox="0 0 1024 1024"
version="1.1" height="24" width="24" xmlns="http://www.w3.org/2000/svg"
>
<defs />
<path d="M474 152m8 0l60 0q8 0 8 8l0 704q0 8-8 8l-60 0q-8 0-8-8l0-704q0-8 8-8Z" />
<path d="M168 474m8 0l672 0q8 0 8 8l0 60q0 8-8 8l-672 0q-8 0-8-8l0-60q0-8 8-8Z" />
</svg>
</button>
</slot>
<slot name="BlockSideMenu-Drag" :static-params="staticParams">
<button class="block-side-menu__button" draggable="true" @dragstart="onDragStart" @dragend="onDragEnd">
<svg
stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="24" width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path fill="none" d="M0 0h24v24H0V0z" />
<path
d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
/>
</svg>
</button>
</slot>
</slot>
</div>
</template>
<style>
.block-side-menu {
position: absolute;
display: none;
margin-top: -4px;
}
.block-side-menu>div {
display: flex !important;
flex-direction: row;
}
.ProseMirror {
outline: none;
}
</style>
<style lang="postcss">
.block-side-menu__button {
-webkit-tap-highlight-color: transparent;
font-family: Inter;
cursor: pointer;
appearance: none;
font-size: 16px;
text-align: left;
text-decoration: none;
box-sizing: border-box;
border: 1px solid transparent;
background-color: transparent;
color: rgb(194, 199, 208);
position: relative;
height: 24px;
min-height: 24px;
width: 24px;
min-width: 24px;
border-radius: 4px;
padding: 0px;
line-height: 1;
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
}
.block-side-menu__button:hover {
background-color: rgb(246, 246, 248);
}
.block-side-menu__button:focus {
outline-offset: 2px;
outline: rgb(137, 147, 164) solid 2px;
}
button:focus:not(:focus-visible) {
outline: none !important;
}
</style>

View file

@ -0,0 +1,48 @@
import type { BlockSideMenuFactory, BlockSideMenuStaticParams } from '@blocknote/core'
import type { ComponentInternalInstance } from 'vue'
import { mount } from '../../mount'
import BlockSideMenu from './BlockSideMenu.vue'
/**
* This menu is drawn next to a block, when it's hovered over
* It renders a drag handle and + button to create a new block
*/
export function blockSideMenuFactory(component: ComponentInternalInstance) {
const blockSideMenuFactory: BlockSideMenuFactory = (staticParams: BlockSideMenuStaticParams) => {
// Mount component
// https://github.com/pearofducks/mount-vue-component/blob/master/index.js
const { el } = mount(BlockSideMenu, {
app: component.appContext.app,
children: component.slots, // Pass all slots or filter for SideMenu ?
props: {
staticParams,
},
})
el.classList.add('block-side-menu')
document.body.appendChild(el)
// Mount component as a new instance
// const container = document.createElement("div")
// const instance = createApp(BlockSideMenu, {
// staticParams
// }).mount(container)
// document.body.appendChild(el)
return {
element: el,
render: (params, isHidden) => {
if (isHidden)
el.style.display = 'block'
el.style.top = `${params.referenceRect.y}px`
el.style.left = `${params.referenceRect.x - el.offsetWidth}px`
},
hide: () => {
el.style.display = 'none'
},
}
}
return blockSideMenuFactory
}

53
src/components/Editor.vue Normal file
View file

@ -0,0 +1,53 @@
<script lang="ts" setup>
import type { ComponentInternalInstance } from "vue"
import { onMounted, ref, useSlots, getCurrentInstance } from "vue"
import { BlockNoteEditor, defaultSlashMenuItems } from "@blocknote/core"
import type { BlockNoteEditorOptions, Block } from "@blocknote/core"
import { EditorContent } from "@tiptap/vue-3"
import { slashMenuFactory } from "@/SlashMenu/slashMenuFactory"
import { blockSideMenuFactory } from "@/BlockSideMenu/blockSideMenuFactory"
const props = defineProps<{
modelValue: Block[]
options?: BlockNoteEditorOptions
}>()
const emit = defineEmits<{
(event: 'update:modelValue', payload: Block[]): void
}>()
const component: ComponentInternalInstance = getCurrentInstance()!
const editor = ref()
onMounted(async () => {
// Convert md to html
const Editor = new BlockNoteEditor({})
const content = await Editor.blocksToHTML(props.modelValue)
const editor = new BlockNoteEditor({
parentElement: document.getElementById("app")!,
slashCommands: defaultSlashMenuItems,
uiFactories: {
// Create an example formatting toolbar which just consists of a bold toggle
// formattingToolbarFactory,
// // Create an example menu for hyperlinks
// hyperlinkToolbarFactory,
// Create an example menu for the /-menu
slashMenuFactory: slashMenuFactory(component),
// // Create an example menu for when a block is hovered
blockSideMenuFactory: blockSideMenuFactory(component),
},
onEditorContentChange() {
emit('update:modelValue', editor.topLevelBlocks)
},
editorDOMAttributes: {
class: "editor",
},
_tiptapOptions: {
content
}
})
// console.log(editor)
})
</script>
<template>
<div>
<editor-content :editor="editor?._tiptapEditor" />
</div>
</template>

0
src/components/Login.vue Normal file
View file

View file

@ -1,59 +0,0 @@
<script setup lang="ts">
import { BlockProvider } from '@milkdown/plugin-block'
import { useInstance } from '@milkdown/vue'
import { usePluginViewContext } from '@prosemirror-adapter/vue'
import { onUnmounted, ref, watch } from 'vue'
import type { VNodeRef } from 'vue'
const { view } = usePluginViewContext()
const [loading, get] = useInstance()
const divRef = ref<VNodeRef>()
let tooltipProvider: BlockProvider | undefined
watch([loading], () => {
const editor = get()
if (loading.value || !editor || tooltipProvider) return
editor.action((ctx) => {
tooltipProvider = new BlockProvider({
ctx,
content: divRef.value as any,
})
tooltipProvider.update(view.value)
})
})
watch([view], () => {
tooltipProvider?.update(view.value)
})
onUnmounted(() => {
tooltipProvider?.destroy()
tooltipProvider = undefined
})
</script>
<template>
<div
ref="divRef"
className="w-6 bg-slate-200 rounded hover:bg-slate-300 cursor-grab"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="{1.5}"
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z"
/>
</svg>
</div>
</template>

View file

@ -1,67 +0,0 @@
<script setup lang="ts">
import { Editor, defaultValueCtx, rootCtx } from '@milkdown/core'
import { commonmark } from '@milkdown/preset-commonmark'
import { nord } from '@milkdown/theme-nord'
import { history } from '@milkdown/plugin-history'
import { math } from '@milkdown/plugin-math'
import { Milkdown, useEditor } from '@milkdown/vue'
import { tooltipFactory } from '@milkdown/plugin-tooltip'
import { usePluginViewFactory } from '@prosemirror-adapter/vue'
import { gfm } from '@milkdown/preset-gfm'
import { clipboard } from '@milkdown/plugin-clipboard'
import { emoji } from '@milkdown/plugin-emoji'
import { block } from '@milkdown/plugin-block'
import { cursor } from '@milkdown/plugin-cursor'
import Tooltip from './Tooltip.vue'
import Slash from './Slash.vue'
import Block from './Block.vue'
const tooltip = tooltipFactory('Text')
const slash = tooltipFactory('Text')
const markdown = `# Milkdown Vue Commonmark
> You're scared of a world where you're needed.
This is a demo for using Milkdown with **Vue**.`
const pluginViewFactory = usePluginViewFactory()
useEditor((root) => {
return Editor.make()
.config(nord)
.config((ctx) => {
ctx.set(rootCtx, root)
ctx.set(defaultValueCtx, markdown)
ctx.set(tooltip.key, {
view: pluginViewFactory({
component: Tooltip,
}),
})
ctx.set(slash.key, {
view: pluginViewFactory({
component: Slash,
}),
})
ctx.set(block.key, {
view: pluginViewFactory({
component: Block,
}),
})
})
.use(commonmark)
.use(tooltip)
.use(slash)
.use(history)
.use(math)
.use(gfm)
.use(clipboard)
.use(emoji)
.use(block)
.use(cursor)
})
</script>
<template>
<Milkdown />
</template>

View file

@ -1,59 +0,0 @@
<script setup lang="ts">
import { editorViewCtx } from '@milkdown/core'
import { SlashProvider } from '@milkdown/plugin-slash'
import { createCodeBlockCommand } from '@milkdown/preset-commonmark'
import { callCommand } from '@milkdown/utils'
import { useInstance } from '@milkdown/vue'
import { usePluginViewContext } from '@prosemirror-adapter/vue'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import type { VNodeRef } from 'vue'
const { view, prevState } = usePluginViewContext()
const [loading, get] = useInstance()
const divRef = ref<VNodeRef>()
let tooltipProvider: SlashProvider
onMounted(() => {
tooltipProvider = new SlashProvider({
content: divRef.value as any,
})
tooltipProvider.update(view.value, prevState.value)
})
watch([view, prevState], () => {
tooltipProvider?.update(view.value, prevState.value)
})
onUnmounted(() => {
tooltipProvider.destroy()
})
const addCodeBlock = (e: Event) => {
if (loading.value) return
e.preventDefault()
get()!.action((ctx) => {
const view = ctx.get(editorViewCtx)
const { dispatch, state } = view
const { tr, selection } = state
const { from } = selection
dispatch(tr.deleteRange(from - 1, from))
return callCommand(createCodeBlockCommand.key)(ctx)
})
}
</script>
<template>
<div ref="divRef">
<button
className="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
@mousedown="addCodeBlock"
>
Code Block
</button>
</div>
</template>

View file

@ -1,50 +0,0 @@
<script setup lang="ts">
import { TooltipProvider } from '@milkdown/plugin-tooltip'
import { toggleStrongCommand } from '@milkdown/preset-commonmark'
import { callCommand } from '@milkdown/utils'
import { useInstance } from '@milkdown/vue'
import { usePluginViewContext } from '@prosemirror-adapter/vue'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import type { VNodeRef } from 'vue'
const { view, prevState } = usePluginViewContext()
const [loading, get] = useInstance()
const divRef = ref<VNodeRef>()
let tooltipProvider: TooltipProvider
onMounted(() => {
tooltipProvider = new TooltipProvider({
content: divRef.value as any,
})
tooltipProvider.update(view.value, prevState.value)
})
watch([view, prevState], () => {
tooltipProvider?.update(view.value, prevState.value)
})
onUnmounted(() => {
tooltipProvider.destroy()
})
const toggleBold = (e: Event) => {
if (loading.value) return
e.preventDefault()
get()!.action(callCommand(toggleStrongCommand.key))
}
</script>
<template>
<div ref="divRef">
<button
className="text-gray-600 bg-slate-200 px-2 py-1 rounded-lg hover:bg-slate-300 border hover:text-gray-900"
@mousedown="toggleBold"
>
Bold
</button>
</div>
</template>

View file

@ -0,0 +1,35 @@
<script lang="ts" setup>
import type { BaseSlashMenuItem } from '@blocknote/core'
import type { SlashMenuProps } from './slashMenuFactory'
import SlashMenuItem from './SlashMenuItem.vue'
const props = defineProps<{
reactiveParams: SlashMenuProps
}>()
function onClick(menu: BaseSlashMenuItem) {
props.reactiveParams.itemCallback(menu)
}
function isSelected(i: number) {
return props.reactiveParams.keyboardHoveredItemIndex === i
}
</script>
<template>
<slot name="SlashMenu">
<template v-for="(menu, i) in reactiveParams.items" :key="menu.name">
<slot :name="`SlashMenuItem-${menu.name}`" :menu="menu" :selected="isSelected(i)" :on-click="() => onClick(menu)">
<SlashMenuItem :menu="menu" :selected="isSelected(i)" :on-click="() => onClick(menu)" />
</slot>
</template>
</slot>
</template>
<style style="postcss">
.slash-menu {
position: absolute;
background: white;
box-shadow: rgb(223, 225, 230) 0px 4px 8px, rgb(223, 225, 230) 0px 0px 1px;
border: 1px solid rgb(236, 237, 240);
border-radius: 6px;
padding: 4px;
}
</style>

View file

@ -0,0 +1,22 @@
<script lang="ts" setup>
import type { BaseSlashMenuItem } from '@blocknote/core'
const props = defineProps<{
menu: BaseSlashMenuItem
selected: boolean
onClick: Function
}>()
</script>
<template>
<div>
<button :class="selected && 'selected'" @click="onClick()">
{{ menu.name }}
</button>
</div>
</template>
<style style="postcss" scoped>
.selected {
font-weight: bold;
}
</style>

View file

@ -0,0 +1,51 @@
import type { BaseSlashMenuItem, SuggestionsMenuFactory } from '@blocknote/core'
import type { ComponentInternalInstance } from 'vue'
import { reactive } from 'vue'
import { mount } from '../../mount'
import SlashMenu from './SlashMenu.vue'
export interface SlashMenuProps {
items: BaseSlashMenuItem[]
keyboardHoveredItemIndex: number
itemCallback: (item: BaseSlashMenuItem) => void
}
/**
* This menu is drawn next to a block, when it's hovered over
* It renders a drag handle and + button to create a new block
*/
export function slashMenuFactory(component: ComponentInternalInstance) {
const slashMenuFactory: SuggestionsMenuFactory<BaseSlashMenuItem> = (staticParams: SlashMenuProps) => {
// Mount component
const reactiveParams = reactive<SlashMenuProps>(staticParams)
const { el } = mount(SlashMenu, {
app: component.appContext.app,
children: component.slots, // Pass all slots or filter for slashMenu ?
props: { reactiveParams },
})
el.classList.add('slash-menu')
document.body.appendChild(el)
return {
element: el,
render: (params, isActive) => {
Object.assign(reactiveParams, params)
if (isActive)
el.style.display = 'block'
el.style.top = `${params.referenceRect.y}px`
el.style.left = `${params.referenceRect.x - el.offsetWidth}px`
},
hide: () => {
el.style.display = 'none'
},
}
}
return slashMenuFactory
}

52
src/mount.ts Normal file
View file

@ -0,0 +1,52 @@
import type { App, Component, VNode } from 'vue'
import { cloneVNode, createVNode, render } from 'vue'
/**
* Inspiration from https://github.com/pearofducks/mount-vue-component/blob/master/index.js
* And official `mount` Vue3 https://github.com/vuejs/core/blob/650f5c26f464505d9e865bdb0eafb24350859528/packages/runtime-core/src/apiCreateApp.ts#L294
*/
interface Mount {
props: any
children?: unknown
element?: HTMLElement
app: App
}
const __DEV__ = process.env.NODE_ENV === 'development'
export function mount(component: Component, { props, children, element, app }: Mount): {
vnode: VNode, destroy: () => void, el: HTMLElement
} {
const el = element || document.createElement('div')
let vnode: VNode | null = createVNode(component, props, children)
if (app && app._context)
vnode.appContext = app._context
// HMR root reload
if (__DEV__) {
app._context.reload = () => {
render(cloneVNode(vnode!), el) // , isSVG)
}
}
if (el)
render(vnode, el)
else if (typeof document !== 'undefined')
render(vnode, el)
// if (isHydrate && hydrate) {
// hydrate(vnode as VNode<Node, Element>, el as any)
// } else {
// render(vnode, el, isSVG)
// }
const destroy = () => {
if (el)
render(null, el)
vnode = null
}
return { vnode, destroy, el: el as HTMLElement }
}

View file

@ -1,11 +1,91 @@
@tailwind base; :root {
@tailwind components; font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
@tailwind utilities; line-height: 1.5;
font-weight: 400;
.milkdown { color-scheme: light dark;
@apply bg-slate-50 px-2 py-4 m-5 border rounded; color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
html,
body,
#root {
height: 100%;
} }
.editor { .editor {
@apply mx-auto; padding: 0 calc((100% - 731px) / 2);
height: 100%;
} }

4
src/vite-env.d.ts vendored
View file

@ -1,5 +1 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.vue' {
export = any;
}

View file

@ -1,9 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['**/*.tsx', '**/*.ts', '**/*.vue', '**/*.html'],
darkMode: 'class',
theme: {
extend: {},
},
plugins: [require('@tailwindcss/typography')],
}

View file

@ -11,9 +11,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["ESNext", "DOM"], "lib": ["ESNext", "DOM"],
"skipLibCheck": true, "skipLibCheck": true,
"noEmit": true, "noEmit": true
"noPropertyAccessFromIndexSignature": false,
"allowJs": true
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]

View file

@ -1,7 +1,27 @@
import { URL, fileURLToPath } from 'node:url'
import * as path from 'path'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig((conf) => {
plugins: [vue()], const config = {
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
}
// Comment out the lines below to load a built version of blocknote
// or, keep as is to load live from sources with live reload working
if (conf.command === 'build') {
Object.assign(config.resolve.alias, {
'@blocknote/core': path.resolve(__dirname, '../../packages/core/src/'),
})
}
return config
}) })