npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mqn00/file-manager-plugin-file-viewer

v0.1.0

Published

File Manager 插件核心:劫持文件列表单击打开文件,维护查看器注册表并分发到子查看插件,提供公共文件 I/O 后端(read/write/bytes/write-range/token/stream)

Readme

@mqn00/file-manager-plugin-file-viewer

File Manager 文件查看核心插件:劫持文件列表单击打开文件,维护查看器注册表并把渲染分发到各子查看插件(code / music / video / office / hex),并提供查看器映射配置主页(扩展名 → 查看器)。

本插件不内置任何查看模块,也不再提供文件 I/O 后端——通用文件读写能力已上收主项目平台(/api/files/* + ctx.api.fileIO / ctx.services.fileIO),子插件直接消费平台 API,与 file-viewer 无耦合。首次安装时请同时安装至少一个子插件,否则查看页会提示"未安装任何查看插件"。

功能

  • 主页(配置页)/plugin/file-viewer:按查看器分组编辑各自打开的后缀列表,保存为 config.yml 完整映射表(plugins.file-viewer.extensionMappings),立即生效并全局持久化
  • 查看页 /plugin/file-viewer/view:单击文件列表中文件名打开(文件夹双击导航不受影响),按扩展名解析默认查看模块,页面内可切换"打开方式"并记住选择(localStorage)
  • 注册表分发:子插件在前端 install() 时注册自己的默认后缀,核心负责按扩展名解析默认模块
  • 依赖平台 I/O:文本读写、字节分页、短期流令牌 + Range 流式输出由主项目 /api/files/* 提供

前置要求

  • File Manager >= 3.0.0-beta7(ctx.router.addRouterequiresAuth 路由拦截、ctx.config.get/updatePluginctx.api.fileIOctx.services.fileIO
  • Node.js >= 22

安装

npm install @mqn00/file-manager-plugin-file-viewer
# 同时安装子插件(按需):
npm install @mqn00/file-manager-plugin-file-code-viewer @mqn00/file-manager-plugin-file-music-viewer \
  @mqn00/file-manager-plugin-file-video-viewer @mqn00/file-manager-plugin-file-image-viewer \
  @mqn00/file-manager-plugin-file-office-viewer @mqn00/file-manager-plugin-file-binary-viewer \
  @mqn00/file-manager-plugin-file-markdown-viewer

config.yml 中启用(子插件通过 fileManagerPlugin.dependsOn: ["file-viewer"] 保证在核心之后加载,无需手动排序):

plugins:
  file-viewer:
    enabled: true
    source: local   # 本地开发目录;npm 安装后改为 npm
  file-code-viewer:
    enabled: true
    source: local
  # ... 其余子插件同理

extensionMappings 首次保存前可省略(缺省时使用各查看器注册的默认后缀)。

使用

  1. 配置主页:插件管理页「打开页面」→ /plugin/file-viewer,或任意查看页头部「查看器设置」按钮。每个查看器一行:编辑后缀列表、还原默认 恢复该查看器注册的后缀;保存后写入 config.yml 并立即生效
  2. 进入文件列表,单击文件名(非文件夹)→ 自动打开 /plugin/file-viewer/view?path=...&mode=... 查看页
  3. 查看页头部有"打开方式"下拉:可切换到该文件可用的其他查看模块(如代码 → 十六进制),选择会被记住(localStorage),下次单击同名后缀文件时优先使用
  4. 未登录访问查看页/配置页会被重定向到登录页(路由 requiresAuth

打开方式优先级

URL 指定 mode > 页面内选择(localStorage) > config.yml 映射(extensionMappings) > 注册表默认

默认打开方式映射表(注册表默认,可在配置主页覆盖)

| 扩展名 | 默认模块 | 页面内可切换 | |---|---|---| | 代码/文本后缀(ts/js/vue/json/txt/py 等) | 代码编辑器(file-code-viewer) | 十六进制 | | mp3/wav/flac/ogg/m4a/aac/opus | 音乐播放器(file-music-viewer) | 十六进制 | | mp4/webm/mkv/avi/mov/flv/m4v/wmv | 视频播放器(file-video-viewer) | 十六进制 | | png/jpg/jpeg/gif/webp/svg/bmp/ico/avif/tiff/heic 等 | 图片查看器(file-image-viewer) | 十六进制 | | pdf/docx/doc/xlsx/xls/pptx/ppt | 办公文档查看器(file-office-viewer) | 十六进制 | | 其他全部(未知扩展名) | 十六进制查看器(file-binary-viewer,兜底) | — |

注:.md 同时被 file-code-viewerfile-markdown-viewer 注册,默认打开方式取决于二者前端加载顺序;若需固定,可在配置主页(扩展名→查看器映射)显式指定,或在查看页「打开方式」中手动切换并记住选择。

查看器注册表契约(第三方接入点)

核心在 globalThis.__fm_file_viewer_registry__ 上维护注册表。子插件在前端 install() 时调用:

const registry = getRegistry() // globalThis.__fm_file_viewer_registry__
registry.register({
  id: 'code',             // 唯一标识,同时作为 URL query 的 mode 值
  label: '代码编辑器',      // 下拉/配置页显示名
  extensions: ['ts', 'js'], // 默认支持后缀(小写、不含点);空数组 = 兜底模块
  editable: true,          // 是否可编辑保存(页面头部按此显示保存按钮)
  component: defineComponent({ /* props: { file: FileItem } */ }),
})
  • 模块组件 props 仅一个 { file: FileItem };文件内容/保存/令牌由组件内部调用平台 I/O 完成(见下)
  • getApplicable(ext) 返回扩展名命中的模块 + 兜底模块;getDefault(ext) 按「config 映射 > 注册表命中 > 兜底」解析
  • 配置页保存时调用 registry.setConfigMappings(map) 同步全局解析;查看页在打开时读取注册表,与插件加载顺序解耦

平台文件 I/O(主项目提供,子插件直接使用)

file-viewer 后端只提供配置 API(/api/file-viewer/config),不提供任何文件读写。文件读写是主项目平台能力:

| 路由 | 鉴权 | 说明 | |---|---|---| | GET /api/files/read?path=&offset=&length= | Bearer | 二进制透传读取,返回 {offset,length,size,data:base64};省略 offset/length = 整文件读取(截断到 8MB,size 返回真实大小);传 offset/length = 分页读取。平台不判文本/二进制 | | POST /api/files/write | Bearer | {path, data(base64), offset?} 二进制写回;省略 offset = 整文件覆盖(允许空内容清空文件),传 offset = 定位写入 | | POST /api/files/token | Bearer | {path} → 30 分钟流令牌(绑定安全路径校验后的绝对路径) | | GET /api/files/stream?token= | 公开(令牌) | Range 流式输出(206/200、Accept-Ranges、按扩展名 mime),供 <audio>/<video>/<iframe> 使用 |

子插件前端在 install() 中直接用 ctx.api.fileIOread / write / createToken / streamUrl / base64ToBytes / bytesToBase64),子插件后端可用 ctx.services.fileIO

const api = ctx.api.fileIO
// 文本应用(如代码编辑器):平台只透传二进制,判断与解码由应用完成
const r = await api.read(props.file.path)            // { offset, length, size, data: base64 }
if (r.size > MY_LIMIT) { /* 应用自行判断"太大" */ }
const bytes = api.base64ToBytes(r.data)
if (bytes.subarray(0, 8192).includes(0)) { /* 应用自行判断二进制 */ }
const text = new TextDecoder('utf-8').decode(bytes)
// 保存:应用自行编码为字节后写回
await api.write(props.file.path, new TextEncoder().encode(text))
// 二进制应用(如 hex 编辑器):分页读取 + 定位写入
await api.read(props.file.path, offset, 256 * 1024)
await api.write(props.file.path, nextBytes, pageOffset)
// 媒体/PDF:流式输出(无法携带 Bearer header)
const token = await api.createToken(props.file.path)
const src = api.streamUrl(token)            // <audio src>

主应用鉴权为 Bearer header,<video>/<audio>/<iframe> 无法携带 header,因此媒体/PDF 流统一走 /token + /stream。路径参数一律经主项目 safePath 校验,.. 穿越会被拒绝。

⚠️ 迁移提示:旧版 file-viewer 曾在插件内提供 /api/file-viewer/read|write|bytes|write-range|token|stream。这些路由已上收主项目并移除,未迁移的旧版子插件(image/video/music/office/binary)调用会 404,需升级到使用 ctx.api.fileIO 的新版本。

配置数据(config.yml)

plugins:
  file-viewer:
    enabled: true
    extensionMappings:      # 可选;保存配置页时写入,缺省使用注册表默认
      ts: code
      md: code
      mp3: music
  • 后缀键一律小写、不含点;值为查看器模块 id
  • 未列出的后缀在打开文件时匹配首个声明它的查看器,否则回退兜底(hex)
  • 映射指向未安装的查看器时自动回退注册表默认,配置页会提示并允许清除

与主应用的耦合说明(重要)

  • 单击劫持通过捕获阶段 document 级 click 监听实现,匹配 .file-name-text 类名元素且不含 .is-folder(broken 符号链接跳过)
  • 主应用重构文件列表 DOM 时需保留 file-name-text / is-folder 类名,否则本插件单击打开失效(文件列表类名位于主应用 frontend/src/views/FileList.vue
  • SPA 导航不刷新页面:history.pushState + 合成 PopStateEvent,vue-router 守卫照常生效(未登录会拦截)

构建与发布

npm install          # 安装依赖(国内镜像:--registry=https://registry.npmmirror.com)
npm run build        # tsc 编译后端 + esbuild 打包前端 → dist/
npm test             # vitest 单测(注册表解析 + 配置 API)
node scripts/publish.mjs   # 构建并发布到 npm(自动 patch 递增版本号)

目录结构

src/
├── frontend.ts      # 单击劫持 + 注册表初始化 + 双路由注册(配置主页 / 查看页)
├── backend.ts       # 配置 API(GET/PUT /api/file-viewer/config)
├── registry.ts      # 注册表 + 默认解析(含 config 映射优先级,纯逻辑,可单测)
├── config-page.ts   # 配置主页(按查看器分组编辑后缀列表)
├── config-api.ts    # 配置读写客户端
├── overrides.ts     # 打开方式用户覆盖(localStorage)
├── page.ts          # 查看页外壳(返回/文件名/打开方式下拉/模块渲染区)
├── backend.test.ts  # 配置 API 路由单测
└── registry.test.ts # 注册表单测(含 config 映射优先级)