擴充
擴充為 Methodox Office 新增命令、工作表函式和指令碼庫,面向所有安裝它的人,而不是僅針對某一個文件。擴充使用與文件自身的指令碼相同的語言編寫,即 Lua、C# 或 Divooka 圖,並遵循相同的規則執行。
擴充可以新增什麼
| 內容 | 出現位置 |
|---|---|
| 選單命令 | 在擴充所針對的應用程式(Impulse、Formula、Expo 和 Nu)中,作為 Tools ▸ Extensions 的條目出現,每個命令都在視窗的文件上執行,並作為 Edit ▸ Undo 中的一個步驟 |
| 工作表函式 | 在每個 Formula 活頁簿中以 =PREFIX.NAME(...) 形式使用,列在 Insert Function 中;字首屬於擴充,例如 TOOLS. 或 FX.,並且永遠不會替換活頁簿中已有的名稱 |
| 指令碼庫 | 可以在任何指令碼中按名稱載入:Lua 中為 require("rates"),C# 中為 #load "rates" |
擴充能做的就只有這些。它不能處理文件事件,不能新增自己的選單、工具列按鈕或按鍵,也不能改變開啟、儲存或重新計算的行為,因此在有人執行擴充所新增的內容之前,安裝擴充絕不會改變文件的行為。語言包將來會成為第四種擴充。
擴充的程式碼可以做什麼
與文件自身的程式碼可以做的完全相同(參見程式設計模型):命令在一個事務中編輯其執行所在的文件,失敗時事務會回滾;函式根據其實參進行計算,不改變任何內容,並受到與文件的 LUA. 函式相同的時間限制;庫在載入它的指令碼內部執行。與文件一樣,由版本決定圖可以使用什麼。
擴充只能通過宿主存取網路(Lua 中為 host.getJson(...),C# 中為 Host.GetJson(...)),並且只能存取其清單中列出的網站。你在安裝之前就能看到這些網站,安裝即授予存取權限,並且權限授予的是該版本的確切包:程式碼不同的更新是另一個包。擴充無法獲得檔案存取權限和完全信任。
安裝和啟用
在 Start Centre 的 Extensions 頁面上:使用 Install from File…,或將 .dvb 拖放到 Start Centre 上,或從目錄安裝:官方目錄 Methodox Office Extensions 對所有人列出,Add Catalogue… 可新增其他目錄。每個擴充的卡片上,為其適用的每個應用程式各有一個核取方塊;取消勾選即可在該應用程式中禁用它。Uninstall 會將其刪除。
擴充只為你自己安裝,位於 %AppData%\DivookaOffice\extensions,因此不需要管理員權限,並且一個人的擴充不會影響另一個人。
簽名的包
包可以由其發行者簽名,無論包來自何處(目錄、檔案或拖放),Methodox Office 都會在安裝任何內容之前檢查簽名。
- Verified(已驗證):包使用 Methodox Office 信任的發行者金鑰簽名,並且簽名中註明的是該發行者。發行者會在擴充卡片上以及安裝前的詢問中帶勾號顯示。
- Not verified(未驗證):包未簽名,或者使用 Methodox Office 不認識的金鑰簽名。顯示的發行者只是包對自身的宣告;請閱讀擴充的用途及其可能存取的內容,並且只有在信任其來源時才安裝。
- Refused(已拒絕):包已簽名,但其檔案與簽名不再匹配,或者簽名中註明的發行者與其金鑰的發行者不同。這說明包在簽名後被更改過,因此不會安裝。
Methodox Office 信任的金鑰內建於程式中。每個發行者自行生成並保管自己的私鑰,只把公鑰部分提供給 Methodox;Methodox 自己的私鑰由 Methodox 保管,從不放在套件或其程式碼倉庫中。目前列表中只有 Methodox 的金鑰,因此 Methodox 簽名的包會顯示為已驗證,而來自任何其他發行者的包即使已簽名也會顯示為未驗證;隨 Extensions 資料夾附帶的官方擴充之所以受信任,是因為它們隨 Office 一起提供。如果某個發行者的金鑰後來從列表中移除,其包會重新顯示為未驗證。目錄的索引還包含每個包的 SHA-256,下載的檔案如果不匹配,會被刪除而不是安裝。
Office Tools
Office Tools 是隨 Methodox Office 提供的官方擴充。每個人第一次啟動某個應用程式時,它會被安裝並啟用;它可以被禁用,但不能解除安裝。
| 位置 | 作用 | |
|---|---|---|
| Summary Sheet | Formula ▸ Tools ▸ Extensions | 按第一列對第一個表格(或 A1 處的資料塊)進行分組,將數值列彙總到 Summary 工作表上,並在每列下方放置一個即時的 SUM |
| Format All Tables | Impulse ▸ Tools ▸ Extensions | 為每個表格設定細灰色邊框、帶網底的粗體標題行,並在每頁重複標題行 |
TOOLS.SLUG、TOOLS.WORDCOUNT、TOOLS.INITIALS、TOOLS.SPELLNUMBER |
Formula 儲存格 | =TOOLS.SPELLNUMBER(1234.5) 的結果為 "One thousand two hundred thirty-four and 50/100" |
tools |
Lua 和 C# 指令碼 | local tools = require("tools");在 C# 中先 #load "tools",然後使用 Tools.Slug(...) |
通過命令列
mos 無需 Start Centre 即可管理擴充,還可以構建擴充:
| 命令 | 作用 |
|---|---|
mos extension pack <folder> [-o <file.dvb>] |
檢查清單以及其中列出的每個指令碼是否存在,然後將擴充的資料夾打包為包;列印包的 SHA-256 |
mos extension index <folder> [--title <name>] |
為包含包的資料夾寫入 index.json,使該資料夾成為目錄 |
mos extension install <file.dvb> |
為你安裝一個包 |
mos extension install <catalogue> <id> |
從目錄地址下載並安裝擴充 |
mos extension list |
列出已安裝的擴充及其啟用位置 |
mos extension enable <id> [<app>...]、disable |
在指定的應用程式中(或在其適用的所有應用程式中)啟用或禁用擴充 |
mos extension uninstall <id> |
刪除你安裝的擴充 |
包
擴充是一個 Divooka 應用包,即 .dvb 檔案:一個 zip 檔案,根目錄下有 divooka.json,指令碼位於 OfficeExtensions/ 下,按慣例每個應用程式一個資料夾,庫則放在 Shared/ 中。這與 Divooka 用於其自身庫的包格式相同。
ExchangeRates/
divooka.json
README.md
OfficeExtensions/
Formula/InsertRates.lua a command
Formula/functions.lua the FX. functions
Impulse/RatesTable.csx a command, in C#
Shared/rates.lua the "rates" library
divooka.json 宣告所有內容;它沒有列出的指令碼永遠不會執行。
{
"format": "divooka-app-bundle/1",
"kind": "library",
"title": "Exchange Rates",
"package": {
"name": "exchange-rates",
"version": "1.0.0",
"description": "Today's exchange rates in Formula and Impulse: a Rates sheet, a rates table, and functions that format an amount in a currency.",
"authors": [ "Example Publisher" ],
"license": "MIT",
"readme": "README.md",
"requires": { "methodox-office": ">=0.4.0" }
},
"office": {
"id": "example/exchange-rates",
"publisher": "Example Publisher",
"apps": [ "Formula", "Impulse" ],
"commands": [
{ "id": "insert-rates", "title": "Insert Exchange Rates", "app": "Formula", "script": "OfficeExtensions/Formula/InsertRates.lua", "description": "Writes today's rates against the base currency onto a Rates sheet." },
{ "id": "rates-table", "title": "Insert Rates Table", "app": "Impulse", "script": "OfficeExtensions/Impulse/RatesTable.csx", "description": "Adds a table of today's rates for the major currencies at the end of the document." }
],
"functions": [ { "prefix": "FX", "script": "OfficeExtensions/Formula/functions.lua" } ],
"libraries": [ { "name": "rates", "script": "OfficeExtensions/Shared/rates.lua" } ],
"capabilities": [ "network.get https://api.frankfurter.dev" ]
}
}
| 欄位 | 含義 |
|---|---|
kind |
對於擴充,始終為 library |
package.name、package.version |
包的名稱及其版本,格式為 major.minor.patch |
package.requires |
methodox-office 及其可執行的 Methodox Office 版本,例如 >=0.4.0;在安裝和載入時檢查 |
office.id |
owner/name,擴充的唯一標識 |
office.publisher |
製作者,顯示在其卡片上 |
office.apps |
擴充適用的應用程式:Impulse、Formula、Expo、Nu |
office.commands[] |
每個命令的 id、其在選單上的 title、命令所在 Tools ▸ Extensions 所屬的 app、其 script,以及作為工具提示顯示的 description |
office.functions[] |
一個 prefix 和一個函式指令碼,其中名稱為大寫的函式會成為 PREFIX.NAME |
office.libraries[] |
庫的 name 及其 script;同一個名稱可以同時有 Lua 指令碼和 C# 指令碼 |
office.capabilities[] |
其程式碼可以從中取得資料的網站,每項格式為 network.get https://<origin> |
函式指令碼的寫法與活頁簿自身的工作表函式相同:每個名稱為大寫的頂層函式都是一個函式,其上方的 --- 註解是它的說明。
構建擴充:Exchange Rates 示例
Exchange Rates 使用了所有擴充點:Formula 中的一個 Lua 命令、Impulse 中的一個 C# 命令、兩個函式和一個庫,以及一個網站。先按上面的結構建立資料夾和清單,然後編寫四個指令碼。
庫 OfficeExtensions/Shared/rates.lua 通過宿主取得資料,因此只能存取清單中列出的網站:
local rates = {}
local symbols = { USD = "$", EUR = "€", GBP = "£", JPY = "¥", CNY = "¥", KRW = "₩", INR = "₹", CAD = "C$", AUD = "A$", CHF = "CHF " }
--- Today's rates against a base currency: { date = "2026-10-30", base = "USD", rates = { EUR = 0.89, ... } }.
function rates.latest(base)
base = (base or "USD"):upper()
return host.getJson(host.url("https://api.frankfurter.dev/v1/latest?base={0}", base))
end
--- The currency's symbol, or its code and a space when it has none here.
function rates.symbol(code)
code = tostring(code or ""):upper()
return symbols[code] or (code .. " ")
end
--- An amount written in a currency, rounded to its usual places: 1234.5 in EUR is "€1,234.50".
function rates.format(amount, code)
local places = (code == "JPY" or code == "KRW") and 0 or 2
local text = string.format("%." .. places .. "f", math.abs(amount))
local whole, fraction = text:match("^(%d+)(%.?%d*)$")
whole = whole:reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "")
return (amount < 0 and "-" or "") .. rates.symbol(code) .. whole .. fraction
end
return rates
函式指令碼 OfficeExtensions/Formula/functions.lua 中的函式會成為 =FX.FORMAT(1234.5, "EUR") 和 =FX.SYMBOL("GBP")。它們只格式化工作表提供的資料,不取得任何內容:
local rates = require("rates")
--- An amount written in a currency: =FX.FORMAT(1234.5, "EUR") is "€1,234.50".
---@param amount number
---@param code string
function FORMAT(amount, code)
return rates.format(amount, code:upper())
end
--- A currency's symbol: =FX.SYMBOL("GBP") is "£".
---@param code string
function SYMBOL(code)
return (rates.symbol(code):gsub(" $", ""))
end
Formula 命令 OfficeExtensions/Formula/InsertRates.lua 將今日匯率寫入 Rates 工作表;如果文件有 Base 參數,則以該參數為基準貨幣:
local rates = require("rates")
-- Reading a parameter the document does not have is an error, so it is read under pcall.
local has, base = pcall(function() return param.Base end)
if not has or base == nil or base == "" then base = "USD" end
local today = rates.latest(base)
local codes = {}
for code in pairs(today.rates) do codes[#codes + 1] = code end
table.sort(codes)
local rows = {}
for _, code in ipairs(codes) do rows[#rows + 1] = { code, today.rates[code] } end
local sheet = doc.sheets:find("Rates")
if sheet then sheet.usedRange:clear() else sheet = doc.sheets:add("Rates") end
local written = sheet:range("A1"):write(rows, { header = { "Currency", "Per " .. today.base } })
written:resize(1, 2).format.bold = true
sheet:cells(2, 2):resize(#rows, 1).format.numberFormat = "0.0000"
sheet:cells(#rows + 3, 1).value = "Rates of " .. today.date .. " from frankfurter.dev"
print(string.format("Wrote %d rates against %s for %s.", #rows, today.base, today.date))
Impulse 命令 OfficeExtensions/Impulse/RatesTable.csx 用 C# 編寫,其中 Doc 是 Impulse 文件,Host 負責取得資料:
string[] wanted = { "EUR", "GBP", "JPY", "CNY", "CAD", "AUD" };
JsonNode today = Host.GetJson("https://api.frankfurter.dev/v1/latest?base=USD")!;
object?[,] cells = new object?[wanted.Length + 1, 2];
cells[0, 0] = "Currency";
cells[0, 1] = "Per US dollar";
for (int i = 0; i < wanted.Length; i++)
{
cells[i + 1, 0] = wanted[i];
cells[i + 1, 1] = today["rates"]![wanted[i]]!.GetValue<double>().ToString("0.0000", System.Globalization.CultureInfo.InvariantCulture);
}
Doc.Paragraphs.Add("Exchange rates on " + (string?)today["date"]);
var table = Doc.AddTable(wanted.Length + 1, 2, cells);
table.SetBorders();
table.BoldRow(1);
Print("Added rates for " + wanted.Length + " currencies.");
然後打包、安裝並試用:
mos extension pack ExchangeRates -o exchange-rates-1.0.0.dvb
mos extension install exchange-rates-1.0.0.dvb
mos extension list
將相同的檔案打包兩次,會寫出完全相同的位元組。開啟一個活頁簿並輸入 =FX.FORMAT(1234.5, "EUR"),或者選擇 Tools ▸ Extensions ▸ Insert Exchange Rates。在編寫擴充程式碼時,可以先在 Scripts 窗格中編寫指令碼,等它實現了你想要的功能後再打包。
為擴充簽名
office-extension-signer 是 Parcel NExT 程式碼倉庫中的一個工具(Parcel.NExT/Utilities/OfficeExtensionSigner),它可以生成發行者的金鑰對、為包簽名,並以與 Office 完全相同的方式檢查包:
office-extension-signer keygen --publisher "Example Publisher" --out <folder outside any repository>
office-extension-signer sign exchange-rates-1.0.0.dvb --key <private.pem> --publisher "Example Publisher"
office-extension-signer verify exchange-rates-1.0.0.dvb
keygen 會寫出一個私鑰檔案和一個公鑰檔案。私鑰只能由你自己保管:任何擁有私鑰的人都可以以你的名義簽名,因此切勿提交它、將其放入構建資料夾或傳送給他人。發行者名稱就是 Office 顯示為已驗證的名稱,因此請完全按照希望顯示的樣子填寫。要讓 Office 將你的包顯示為已驗證,公鑰必須在 Office 信任的金鑰之中,這些金鑰內建於每個發行版本中。
簽名是包內的 divooka.signature.json,覆蓋發行者名稱、金鑰以及包中的所有其他檔案。因此簽名隨包一起傳遞,將相同的檔案重新壓縮會保留簽名,而更改、新增、刪除或重新命名任何檔案都會使簽名失效。請在寫入目錄索引之前簽名,因為簽名會新增一個檔案並改變包的 SHA-256。對於已驗證的包,verify 的退出程式碼為 0;對於未簽名或由 Office 不信任的金鑰簽名的包,退出程式碼為 3;對於簽名不匹配的包,退出程式碼為 4。
通過目錄提供擴充
目錄是一個包含若干包的資料夾,旁邊有一個 index.json,可以由任何 Web 伺服器或共享提供;無需執行任何服務。官方目錄就是這樣一個目錄,位於 https://office.methodox.io/extensions/;Start Centre 會將其列在首位,直到你移除它,再次新增其地址即可恢復。要提供你自己的目錄,請將包放入一個資料夾,並在旁邊寫入索引:
mos extension index <folder> --title "My extensions"
通過任何 Web 伺服器提供該資料夾或將其共享,然後在 Start Centre 中使用 Add Catalogue… 新增其地址;在命令提示符下執行 mos extension install <address> example/exchange-rates 即可從中安裝。
尚未支援
語言包、Math 中的擴充命令、為套件中附帶的官方擴充簽名、取得資料的函式(取得資料的函式會返回 #N/A),以及具有檔案存取權限或完全信任的擴充。