GROWI CSV/XLSXプレビュープラグインの紹介

[オープンソースのWikiであるGROWI](https://growi.org/ja/)にはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。

前回紹介したCSVプレビュープラグインを、XLSXファイルにも対応させました。今回はその使い方と、コードを紹介します。

![FireShot Capture 066 - プラグイン開発 - GROWI - localhost.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/05e7cd74-ba1f-6862-7ffd-6f6c3f9558f0.jpeg)


## プラグインを追加する

利用する際には、GROWIの管理画面の `プラグイン` にて追加してください。URLは `https://github.com/goofmint/growi-plugin-csv-preview` です。

![Admin](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f5b3e2d3-ccf3-d536-d59e-7301ea1b2f7e.jpeg)

## 使い方

利用する際には、編集画面でCSVファイル、またはXLSXファイルをアップロードします。そうすると、右側のプレビューにてテーブル表示されます。

![FireShot Capture 066 - プラグイン開発 - GROWI - localhost.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/3783e3f3-8012-00d4-dcb5-edfc00561bf9.jpeg)

これは編集中のプレビュー、ページ表示時の両方に対応しています。なお、ヘッダー行をスティッキーするようにしましたが、プレビュー時には高さの計算がずれるので注意してください。

![FireShot Capture 064 - プラグイン開発 - GROWI - localhost.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/3b3a323a-700b-3bd1-d82d-3b8cfebbf6f4.jpeg)


## コードについて

今回は添付ファイルを利用しているので、プラグインコンポーネントで `attachment` の処理を上書きしています。 `CSVPreview` がCSVプレビュー用のコンポーネントです。

```tsx
const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }

  const { optionsGenerators } = growiFacade.markdownRenderer;

  // For page view
  optionsGenerators.customGenerateViewOptions = (...args) => {
    const options = optionsGenerators.generateViewOptions(...args);
    const { attachment } = options.components;
    options.components.attachment = CSVPreview(attachment); // Wrap the default component
    return options;
  };

  // For preview
  optionsGenerators.customGeneratePreviewOptions = (...args) => {
    const preview = optionsGenerators.generatePreviewOptions(...args);
    const { attachment } = preview.components;
    preview.components.attachment = CSVPreview(attachment); // Wrap the default component
    return preview;
  };
};
```

そして、添付ファイル名が `.csv` で終わっていれば、プレビュー表示処理に入ります。

```tsx
export const CSVPreview = (Tag: React.FunctionComponent<any>): React.FunctionComponent<any> => {
  return ({ children, ...props }) => {
    const { attachmentName, url }: {attachmentName: string, url: string} = props;
    if (!attachmentName.endsWith('.csv') && !attachmentName.endsWith('.xlsx')) {
			// 拡張子が.csv、または.xlsxでない場合は、通常の処理を行う
      return (<Tag {...props}>
        {children}
      </Tag>);
    }
    try {
      return (<>
				{/* プレビュー表示用の処理 */}
      </>
      );
    }
    catch (err) {
      // console.error(err);
    }
    // Return the original component if an error occurs
    return (
      <Tag {...props}>{children}</Tag>
    );
  };
};
```

### プレビュー表示について

プレビュー表示はファイルを読み込む（非同期処理）なので、 `react-async` を使います。拡張子ごとに関数を分けています。

```tsx
return (<>
	<strong>Preview: {attachmentName}</strong>
	<Async promiseFn={loadFile} filePath={url}>
		{({ data, error, isPending }) => {
			if (isPending) return 'Loading...';
			if (error) return `Something went wrong: ${error.message}`;
			if (data) {
				return (<>
					{TableView(data)}
					<Tag {...props}>
						{children}
					</Tag>
				</>
				);
			}
			return null;
		}}
	</Async>
</>
);
```

`loadFile` は以下のようになります。ファイルの拡張子部分によって処理を分けています。

```tsx
const loadFile = ({ filePath }: any, { signal }: any) => {
  if (filePath.endsWith('.csv')) {
    return loadCSV(filePath, { signal });
  }
  return loadXLSX(filePath, { signal });
};
```

`loadCSV` は以下のようになります。ファイルパスを受け取り、それをテキストとして読み込みます。また、 [vanillaes/csv](https://github.com/vanillaes/csv) を使ってCSVをパースします。

```tsx
const loadCSV = async({ filePath }: any, { signal }: any) => {
  const res = await fetch(filePath, { signal });
  const text = await res.text();
  return parse(text);
};
```

`loadXLSX` は以下のようになります。ファイルパスを受け取り、それをバイナリとして読み込みます。XLSXファイルのパースは [xlsx](https://www.npmjs.com/package/xlsx) を使っています。

```tsx
const loadXLSX = async(filePath: string, { signal }: any) => {
  const file = await (await fetch(filePath, { signal })).arrayBuffer();
  const workbook = XLSX.read(file);
  const worksheet = workbook.Sheets[workbook.SheetNames[0]];
  const text = XLSX.utils.sheet_to_csv(worksheet, { forceQuotes: true });
  const res = parse(text);
  return res;
};
```

そして、受け取った配列をテーブル表示します。ヘッダーを固定した際に背景の情報が見えてしまわないように、背景色を取得しています。

```tsx
const bgColor = typeof document !== 'undefined'
	? window.getComputedStyle(document.querySelector('body')!, null).getPropertyValue('background-color')
	: 'rgb(255, 255, 255)';
return (
	<table className='table sticky-header'>
		<thead>
			{data[0].map((row: string) => (
				<th
					style={{ backgroundColor: bgColor }}
				>{row}</th>
			))}
		</thead>
		<tbody>
			{data.slice(1).map((row: string[]) => (
				<tr>
					{row.map(str => (
						<td>{str}</td>
					))}
				</tr>
			))}
		</tbody>
	</table>
);
```

## 注意点

### 表示されるXLSXファイルについて

表示されるのは1つ目のシートになります。シート切り替えには対応していません。

### プレビュー表示させたくない場合

プレビュー表示させたくない場合は、ファイル名を `.csv` や `.xlsx` で終わらない形（末尾にスペースを入れるなど）に変更してください。

## まとめ

GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。

[OSS開発wikiツールのGROWI | 快適な情報共有を、全ての人へ](https://growi.org/ja/)