Apps Script用Sheet生成動態網頁(23): 移植HTML連結列表

 在上一篇中我們移植了HTML檔案上傳的部份,但是只能在上傳後點擊連結來查看,所以這一篇就是來移植上傳HTML的連結列表到React上。(之前是在第2篇中實作)

這裡因為需要可以接受URL直接連結到網頁,所以需要增加一個處理網頁doGet的處理者。不然網頁連結不管怎連入,都只會給我們React網頁本身,而不是上傳的HTML。

之前這個部份是寫在 src/server/handlers.js 中,doGet就只回覆index.html,所以這裡就會新增處理者來處理一般的網頁連結,這裡先定義的網頁連結的格式是

{APP_SCRIPT_URL}?show=html&name={HTML網頁名稱}

const Handlers = {
default: {
func: () => HtmlService.createTemplateFromFile('index.html').evaluate(),
},
};

1. Apps Script的部份(後端/伺服端)

這裡新增一個name2link()函數,統一呼叫這個函數產生html的連結。類似setContentToSheet的角色,getContentByName日後也有機會用到,也export成共用函數。

src/server/content.js

import { SERVER_URL } from './settings';
...
const name2link = name => ({
name,
url: `${SERVER_URL}?show=html&name=${name}`,
});
...
throw new Error(`檔案類型錯誤:${file.type}`);
setContentToSheet(name, file.contents);
return name2link(name);
}

export function getLinkList() {
const validator = row => row && row[0] != null && row[0].length > 0;
try {
const sheet = getContentSheet();
return sheet
.getRange(1, 1 + COLUMN_IDX_OF_NAME, sheet.getLastRow(), 1)
.getValues()
.filter(validator)
.map(e => name2link(e[0]));
} catch (error) {
Logger.log(`getLinkList error:${error.stack || error}`);
}
return [[]];
}

export function getContentByName(name) {
Logger.log(`getContentByName ${name}`);
const DEFAULT_RESPONSE = `找不到${name}內容`;
try {
const sheet = getContentSheet();
const rowIdx = findIndexInColumn(name, COLUMN_IDX_OF_NAME, sheet);
if (rowIdx < 0) return DEFAULT_RESPONSE;
const content = sheet
.getRange(1 + rowIdx, 1 + COLUMN_IDX_OF_CONTENT)
.getValue(); // 取一個儲存格內容
return content == null ? DEFAULT_RESPONSE : content;
} catch (error) {
Logger.log(`getContentByName error:${error.stack || error}`);
}
return DEFAULT_RESPONSE;
}

新增Web的doGet處理者

為了處理web連結,這裡新增html的handler。

src/server/handlers.js

import { getContentByName } from './content';
...
const Handlers = {
...
html: {
func: name => HtmlService.createHtmlOutput(getContentByName(name)),
},
};
...

更動getServerUrl API

因為編譯React時回報遞迴引用了getServerUrl,所以改寫一下把它獨立出來。所以getServerUrl()先從src/server/web.js 裡頭移除。再到index.js直接實作,套用settings裡頭的變數。順便暴露一下API給前端getLinkList()。

src/server/index.js

import { SERVER_URL } from './settings';
...
global.getServerUrl = () => SERVER_URL;
...
global.getLinkList = contentFunctions.getLinkList;

src/server/settings.js

export const SERVER_URL = ScriptApp.getService().getUrl();

2. React的部份(前端/使用者端)

新增Route和選單按鈕

src/client/demo-bootstrap/App.jsx

import LinkLister from './pages/LinkLister';
...

<Route path="/link-lister">
<LinkLister />
</Route>

src/client/demo-bootstrap/components/MyNav.jsx

import {
...
BsListUl,
...
} from 'react-icons/bs';
...
<IconNavLink linkTo="/link-lister" iconComp={<BsListUl />}>
連結列表
</IconNavLink>
...

新增頁面元件

顯示HTML連結列表的元件

src/client/demo-bootstrap/pages/LinkLister.jsx

import React, { useEffect, useState } from 'react';
import { Container } from 'react-bootstrap';
import Server from '../../utils/server';

const { serverFunctions } = Server;

const LinkLister = () => {
const [linkList, setlinkList] = useState(null);

useEffect(() => {
serverFunctions
.getLinkList()
.then(setlinkList)
.catch(alert);
}, []);

return (
<Container>
<h1>Sheet儲存的網頁</h1>
{linkList && (
<ul>
{linkList.map((link, idx) => (
<li key={idx}>
<a href={link.url}>{link.name}</a>
</li>
))}
</ul>
)}
</Container>
);
};

export default LinkLister;

這裡可以看到跟前一篇上傳HTML檔案時一樣,使用<a href={網址}>來處理server給的URL。

3. 成果展示





留言