Skip to content
大纲

Npplication Store 实现

商店系统概述

Npplication Store 是一个插件发现和管理系统,允许浏览、安装和更新插件。它具有以下特性:

  • 支持多个商店源
  • 按分类浏览插件 (自动合并相同分类)
  • 插件详情展示 (插件细节页: 包括截图、描述、依赖等)

商店源管理

nitaiPage 支持用户在商店管理页添加多个商店源。默认商店源配置如下:

javascript
// 商店源
const storeSourcesDefault = [
    'https://nfdb.nitai.us.kg/nitaiPage/store'
];

插件数据获取与处理

核心功能:从商店源获取插件数据并合并相同分类。

javascript
// 加载商店数据
async function loadStoreData() {
  // 从所有商店源获取数据
  const storeDataArray = await Promise.all(storeSources.map(source => fetchStoreData(source.url)));
  // 合并所有商店数据
  const mergedData = mergeStoreData(storeDataArray);
  // 渲染商店标签
  renderStoreTabs(mergedData);
  // 渲染插件列表
  renderPlugins(mergedData.all);
}

使用到的关键函数

  • fetchStoreData(url): 从商店源获取商店数据
  • mergeStoreData(dataArray): 合并多个商店源的分类
  • renderStoreTabs(data): 渲染商店分类标签
  • renderPlugins(pluginsArray): 渲染插件列表

插件展示

插件展示包括列表视图和详情视图,支持用户浏览和安装插件。

javascript
// 渲染插件列表
async function renderPlugins(pluginsArray) {
  const contentContainer = document.getElementById('storeContent');
  contentContainer.innerHTML = '';

  // 创建中止控制器,用于取消过时的渲染任务
  const controller = new AbortController();
  window._lastStoreController?.abort();
  window._lastStoreController = controller;

  pluginsArray.forEach(async (plugin) => {
    if (controller.signal.aborted) return;
    try {
      // 提取插件元数据
      const metadata = await extractMetadata(plugin.url);
      const pluginWithMetadata = { ...plugin, ...metadata };

      // 创建插件项 DOM 元素
      const pluginItem = document.createElement('div');
      pluginItem.className = 'plugin-item';
      pluginItem.innerHTML = `
        <img src="default-icon.png" class="plugin-icon">
        <div class="plugin-info">
          <strong translate="none">${pluginWithMetadata.name || '未命名插件'}</strong>
          <div class="plugin-description">${pluginWithMetadata.description || '无描述'}</div>
          <div class="plugin-meta">
            <span class="plugin-version">v${pluginWithMetadata.version || '0.0.1'}</span>
            <span class="plugin-author">${pluginWithMetadata.author || '未知作者'}</span>
          </div>
        </div>
      `;

      // 添加点击事件
      pluginItem.addEventListener('click', () => {
        showPluginDetails(pluginWithMetadata);
      });

      contentContainer.appendChild(pluginItem);
    } catch (error) {
      console.error(`加载插件失败: ${plugin.url}`, error);
    }
  });
}

插件安装

javascript
// 安装 Npplication 插件
async function installNpplication(pluginUrl) {
  try {
    // 1. 检查依赖
    const dependencyCheck = await checkDependencies(pluginUrl);
    if (!dependencyCheck.status) {
      return;
    }

    // 2. 验证是否为 JS 文件
    if (!await verifyJSUrl(pluginUrl)) {
      return;
    }

    // 3. 提取元数据
    const metadata = await extractMetadata(pluginUrl);
    const pluginId = metadata.id || generateId(pluginUrl);

    // 4. 检查现有版本
    const existingPlugin = await getPluginById(pluginId);
    if (existingPlugin) {
      // 比较版本
      const shouldUpdate = compareVersions(metadata.version, existingPlugin.version) > 0;
      if (!shouldUpdate) {
        showUpdateDialog(pluginUrl, existingPlugin, metadata, true);
        return;
      }
    }

    // 5. 保存 JS 文件内容
    const jsContent = await saveJSFile(pluginUrl);

    // 6. 保存元数据
    await savePluginMetadata(pluginId, { ...metadata, url: pluginUrl });

    // 7. 刷新提示
    showRefreshDialog();

  } catch (error) {
    console.error('安装插件失败:', error);
  }
}

插件存储

插件数据储存在 IndexedDB:

javascript
// 初始化数据库
function initializaNppDB() {
  return new Promise((resolve, reject) => {
    // 检查数据库状态
    const request = indexedDB.open(DB_NAME, 1);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;
      db.createObjectStore(NPP_STORE, { keyPath: 'id' });
    };

    request.onsuccess = (event) => {
      const db = event.target.result;
      // 验证数据库状态
      if (!db.objectStoreNames.contains(NPP_STORE)) {
        console.error('缺少必要的对象存储: ' + NPP_STORE);
        reject();
        return;
      }
        db.close();
        resolve();
    };

    request.onerror = (event) => {
      console.error('数据库初始化失败: ' + event.target.error.message);
      reject();
    };
  });
}

Released under the Apache-2.0 License.