diff --git a/plugins/plugin-md-power/src/client/components/Replit.vue b/plugins/plugin-md-power/src/client/components/Replit.vue
new file mode 100644
index 00000000..aff6c02e
--- /dev/null
+++ b/plugins/plugin-md-power/src/client/components/Replit.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
diff --git a/plugins/plugin-md-power/src/node/features/replit.ts b/plugins/plugin-md-power/src/node/features/replit.ts
new file mode 100644
index 00000000..7e49ceae
--- /dev/null
+++ b/plugins/plugin-md-power/src/node/features/replit.ts
@@ -0,0 +1,93 @@
+/**
+ * @[replit](user/repl-name)
+ * @[replit](user/repl-name#filepath)
+ * @[replit title="" height="400px" width="100%" theme="dark"](user/repl-name)
+ */
+import type { PluginWithOptions } from 'markdown-it'
+import type { RuleBlock } from 'markdown-it/lib/parser_block.js'
+import { resolveAttrs } from '../utils/resolveAttrs.js'
+import { parseRect } from '../utils/parseRect.js'
+import type { ReplitTokenMeta } from '../../shared/replit.js'
+
+// @[replit]()
+const MIN_LENGTH = 11
+
+// char codes of `@[replit`
+const START_CODES = [64, 91, 114, 101, 112, 108, 105, 116]
+
+// regexp to match the import syntax
+const SYNTAX_RE = /^@\[replit(?:\s+([^]*?))?\]\(([^)]*?)\)/
+
+function createReplitRuleBlock(): RuleBlock {
+ return (state, startLine, endLine, silent) => {
+ const pos = state.bMarks[startLine] + state.tShift[startLine]
+ const max = state.eMarks[startLine]
+
+ // return false if the length is shorter than min length
+ if (pos + MIN_LENGTH > max)
+ return false
+
+ // check if it's matched the start
+ for (let i = 0; i < START_CODES.length; i += 1) {
+ if (state.src.charCodeAt(pos + i) !== START_CODES[i])
+ return false
+ }
+
+ // check if it's matched the syntax
+ const match = state.src.slice(pos, max).match(SYNTAX_RE)
+ if (!match)
+ return false
+
+ // return true as we have matched the syntax
+ if (silent)
+ return true
+
+ const [, info = '', source] = match
+
+ const { attrs } = resolveAttrs(info)
+
+ const meta: ReplitTokenMeta = {
+ width: attrs.width ? parseRect(attrs.width) : '100%',
+ height: attrs.height ? parseRect(attrs.height) : '450px',
+ source: source.startsWith('@') ? source : `@${source}`,
+ title: attrs.title,
+ theme: attrs.theme || '',
+ }
+
+ const token = state.push('replit', '', 0)
+
+ token.meta = meta
+ token.map = [startLine, startLine + 1]
+ token.info = info
+
+ state.line = startLine + 1
+
+ return true
+ }
+}
+
+function resolveReplit(meta: ReplitTokenMeta): string {
+ const { title, height, width, source, theme } = meta
+
+ return ``
+}
+
+export const replitPlugin: PluginWithOptions = (md) => {
+ md.block.ruler.before(
+ 'import_code',
+ 'replit',
+ createReplitRuleBlock(),
+ {
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
+ },
+ )
+
+ md.renderer.rules.replit = (tokens, index) => {
+ const token = tokens[index]
+
+ const content = resolveReplit(token.meta)
+ token.content = content
+
+ return content
+ }
+}
diff --git a/plugins/plugin-md-power/src/shared/replit.ts b/plugins/plugin-md-power/src/shared/replit.ts
new file mode 100644
index 00000000..7f8414a5
--- /dev/null
+++ b/plugins/plugin-md-power/src/shared/replit.ts
@@ -0,0 +1,7 @@
+import type { SizeOptions } from './size'
+
+export interface ReplitTokenMeta extends SizeOptions {
+ title?: string
+ source?: string
+ theme?: string
+}