(core) updates from grist-core

This commit is contained in:
Paul Fitzpatrick 2023-11-06 08:20:57 -05:00
commit 8053c81d02
16 changed files with 296 additions and 146 deletions

View File

@ -250,6 +250,7 @@ Variable | Purpose
-------- | ------- -------- | -------
ALLOWED_WEBHOOK_DOMAINS | comma-separated list of permitted domains to use in webhooks (e.g. webhook.site,zapier.com). You can set this to `*` to allow all domains, but if doing so, we recommend using a carefully locked-down proxy (see `GRIST_HTTPS_PROXY`) if you do not entirely trust users. Otherwise services on your internal network may become vulnerable to manipulation. ALLOWED_WEBHOOK_DOMAINS | comma-separated list of permitted domains to use in webhooks (e.g. webhook.site,zapier.com). You can set this to `*` to allow all domains, but if doing so, we recommend using a carefully locked-down proxy (see `GRIST_HTTPS_PROXY`) if you do not entirely trust users. Otherwise services on your internal network may become vulnerable to manipulation.
APP_DOC_URL | doc worker url, set when starting an individual doc worker (other servers will find doc worker urls via redis) APP_DOC_URL | doc worker url, set when starting an individual doc worker (other servers will find doc worker urls via redis)
APP_DOC_INTERNAL_URL | like `APP_DOC_URL` but used by the home server to reach the server using an internal domain name resolution (like in a docker environment). Defaults to `APP_DOC_URL`
APP_HOME_URL | url prefix for home api (home and doc servers need this) APP_HOME_URL | url prefix for home api (home and doc servers need this)
APP_STATIC_URL | url prefix for static resources APP_STATIC_URL | url prefix for static resources
APP_STATIC_INCLUDE_CUSTOM_CSS | set to "true" to include custom.css (from APP_STATIC_URL) in static pages APP_STATIC_INCLUDE_CUSTOM_CSS | set to "true" to include custom.css (from APP_STATIC_URL) in static pages

View File

@ -551,10 +551,12 @@ export class CustomSectionConfig extends Disposable {
// Options for the select-box (all widgets definitions and Custom URL) // Options for the select-box (all widgets definitions and Custom URL)
const options = Computed.create(holder, use => [ const options = Computed.create(holder, use => [
{label: 'Custom URL', value: 'custom'}, {label: 'Custom URL', value: 'custom'},
...(use(this._widgets) || []).map(w => ({ ...(use(this._widgets) || [])
label: w.source?.name ? `${w.name} (${w.source.name})` : w.name, .filter(w => w?.published !== false)
value: (w.source?.pluginId || '') + ':' + w.widgetId, .map(w => ({
})), label: w.source?.name ? `${w.name} (${w.source.name})` : w.name,
value: (w.source?.pluginId || '') + ':' + w.widgetId,
})),
]); ]);
function buildPrompt(level: AccessLevel|null) { function buildPrompt(level: AccessLevel|null) {
if (!level) { if (!level) {

View File

@ -31,6 +31,11 @@ export interface ICustomWidget {
*/ */
renderAfterReady?: boolean; renderAfterReady?: boolean;
/**
* If set to false, do not offer to user in UI.
*/
published?: boolean;
/** /**
* If the widget came from a plugin, we track that here. * If the widget came from a plugin, we track that here.
*/ */

View File

@ -167,6 +167,12 @@ export interface OrgUrlInfo {
orgInPath?: string; // If /o/{orgInPath} should be used to access the requested org. orgInPath?: string; // If /o/{orgInPath} should be used to access the requested org.
} }
function isDocInternalUrl(host: string) {
if (!process.env.APP_DOC_INTERNAL_URL) { return false; }
const internalUrl = new URL('/', process.env.APP_DOC_INTERNAL_URL);
return internalUrl.host === host;
}
/** /**
* Given host (optionally with port), baseDomain, and pluginUrl, determine whether to interpret host * Given host (optionally with port), baseDomain, and pluginUrl, determine whether to interpret host
* as a custom domain, a native domain, or a plugin domain. * as a custom domain, a native domain, or a plugin domain.
@ -184,8 +190,10 @@ export function getHostType(host: string, options: {
const hostname = host.split(":")[0]; const hostname = host.split(":")[0];
if (!options.baseDomain) { return 'native'; } if (!options.baseDomain) { return 'native'; }
if (hostname !== 'localhost' && !hostname.endsWith(options.baseDomain)) { return 'custom'; } if (hostname === 'localhost' || isDocInternalUrl(host) || hostname.endsWith(options.baseDomain)) {
return 'native'; return 'native';
}
return 'custom';
} }
export function getOrgUrlInfo(newOrg: string, currentHost: string, options: OrgUrlOptions): OrgUrlInfo { export function getOrgUrlInfo(newOrg: string, currentHost: string, options: OrgUrlOptions): OrgUrlInfo {

View File

@ -209,8 +209,8 @@ class CachedWidgetRepository extends WidgetRepositoryImpl {
return list; return list;
} }
public testOverrideUrl(url: string) { public testOverrideUrl(overrideUrl: string) {
super.testOverrideUrl(url); super.testOverrideUrl(overrideUrl);
this._cache.reset(); this._cache.reset();
} }
} }
@ -267,7 +267,7 @@ export function getWidgetsInPlugins(gristServer: GristServer,
gristServer.getTag() + '/widgets/' + plugin.id + '/'; gristServer.getTag() + '/widgets/' + plugin.id + '/';
places.push({ places.push({
urlBase, urlBase,
dir: plugin.path, dir: path.resolve(plugin.path, path.dirname(components.widgets)),
file: path.join(plugin.path, components.widgets), file: path.join(plugin.path, components.widgets),
name: plugin.manifest.name || plugin.id, name: plugin.manifest.name || plugin.id,
pluginId: plugin.id, pluginId: plugin.id,

View File

@ -944,7 +944,8 @@
"Mixed types": "Mixed types", "Mixed types": "Mixed types",
"Revert field settings for {{colId}} to common": "Revert field settings for {{colId}} to common", "Revert field settings for {{colId}} to common": "Revert field settings for {{colId}} to common",
"Save field settings for {{colId}} as common": "Save field settings for {{colId}} as common", "Save field settings for {{colId}} as common": "Save field settings for {{colId}} as common",
"Use separate field settings for {{colId}}": "Use separate field settings for {{colId}}" "Use separate field settings for {{colId}}": "Use separate field settings for {{colId}}",
"Changing column type": "Changing column type"
}, },
"FieldEditor": { "FieldEditor": {
"It should be impossible to save a plain data value into a formula column": "It should be impossible to save a plain data value into a formula column", "It should be impossible to save a plain data value into a formula column": "It should be impossible to save a plain data value into a formula column",
@ -1051,7 +1052,10 @@
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Can't find the right columns? Click 'Change Widget' to select the table with events data.", "Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Can't find the right columns? Click 'Change Widget' to select the table with events data.",
"To configure your calendar, select columns for start": { "To configure your calendar, select columns for start": {
"end dates and event titles. Note each column's type.": "To configure your calendar, select columns for start/end dates and event titles. Note each column's type." "end dates and event titles. Note each column's type.": "To configure your calendar, select columns for start/end dates and event titles. Note each column's type."
} },
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "A UUID is a randomly-generated string that is useful for unique identifiers and link keys.",
"Lookups return data from related tables.": "Lookups return data from related tables.",
"Use reference columns to relate data in different tables.": "Use reference columns to relate data in different tables."
}, },
"DescriptionConfig": { "DescriptionConfig": {
"DESCRIPTION": "DESCRIPTION" "DESCRIPTION": "DESCRIPTION"

View File

@ -960,7 +960,8 @@
"Apply Formula to Data": "Aplicar Fórmula a Datos", "Apply Formula to Data": "Aplicar Fórmula a Datos",
"CELL FORMAT": "FORMATO DE CELDA", "CELL FORMAT": "FORMATO DE CELDA",
"Changing multiple column types": "Cambiar varios tipos de columna", "Changing multiple column types": "Cambiar varios tipos de columna",
"Mixed format": "Formato mixto" "Mixed format": "Formato mixto",
"Changing column type": "Cambiar el tipo de columna"
}, },
"CurrencyPicker": { "CurrencyPicker": {
"Invalid currency": "Moneda inválida" "Invalid currency": "Moneda inválida"
@ -1105,7 +1106,10 @@
"end dates and event titles. Note each column's type.": "Para configurar tu calendario, selecciona las columnas para las fechas de inicio y fin y los títulos de los eventos. Ten en cuenta el tipo de cada columna." "end dates and event titles. Note each column's type.": "Para configurar tu calendario, selecciona las columnas para las fechas de inicio y fin y los títulos de los eventos. Ten en cuenta el tipo de cada columna."
}, },
"Calendar": "Calendario", "Calendar": "Calendario",
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "¿No encuentras las columnas adecuadas? Haz clic en \"Cambiar widget\" para seleccionar la tabla con los datos de los eventos." "Can't find the right columns? Click 'Change Widget' to select the table with events data.": "¿No encuentras las columnas adecuadas? Haz clic en \"Cambiar widget\" para seleccionar la tabla con los datos de los eventos.",
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "Un UUID es una cadena generada aleatoriamente que resulta útil para identificadores únicos y claves de los enlaces.",
"Lookups return data from related tables.": "Las búsquedas devuelven datos de tablas relacionadas.",
"Use reference columns to relate data in different tables.": "Utilizar las columnas de referencia para relacionar los datos de distintas tablas."
}, },
"DescriptionConfig": { "DescriptionConfig": {
"DESCRIPTION": "DESCRIPCIÓN" "DESCRIPTION": "DESCRIPCIÓN"

View File

@ -239,7 +239,10 @@
"There was an error: {{message}}": "Si è verificato un errore: {{message}}", "There was an error: {{message}}": "Si è verificato un errore: {{message}}",
"You are now signed out.": "Adesso sei scollegato.", "You are now signed out.": "Adesso sei scollegato.",
"You are signed in as {{email}}. You can sign in with a different account, or ask an administrator for access.": "Sei collegato come {{email}}. Puoi accedere con un account diverso, o chiedere l'accesso a un amministratore.", "You are signed in as {{email}}. You can sign in with a different account, or ask an administrator for access.": "Sei collegato come {{email}}. Puoi accedere con un account diverso, o chiedere l'accesso a un amministratore.",
"You do not have access to this organization's documents.": "Non hai accesso ai documenti di questa organizzazione." "You do not have access to this organization's documents.": "Non hai accesso ai documenti di questa organizzazione.",
"Account deleted{{suffix}}": "Account eliminato {{suffix}}",
"Your account has been deleted.": "Il tuo account è stato cancellato.",
"Sign up": "Iscriviti"
}, },
"duplicatePage": { "duplicatePage": {
"Note that this does not copy data, but creates another view of the same data.": "Notare che questo non copia i dati ma crea un'altra vista dagli stessi dati.", "Note that this does not copy data, but creates another view of the same data.": "Notare che questo non copia i dati ma crea un'altra vista dagli stessi dati.",
@ -306,7 +309,8 @@
"DATA FROM TABLE": "DATI DALLA TABELLA", "DATA FROM TABLE": "DATI DALLA TABELLA",
"Revert field settings for {{colId}} to common": "Ripristina le impostazioni dei campi per {{colId}} a quelli comuni", "Revert field settings for {{colId}} to common": "Ripristina le impostazioni dei campi per {{colId}} a quelli comuni",
"Use separate field settings for {{colId}}": "Usa impostazioni dei campi separate per {{colId}}", "Use separate field settings for {{colId}}": "Usa impostazioni dei campi separate per {{colId}}",
"Save field settings for {{colId}} as common": "Salva le impostazioni dei campi per {{colId}} come quelli comuni" "Save field settings for {{colId}} as common": "Salva le impostazioni dei campi per {{colId}} come quelli comuni",
"Changing column type": "Cambio tipo colonna"
}, },
"FormulaEditor": { "FormulaEditor": {
"Error in the cell": "Errore nella cella", "Error in the cell": "Errore nella cella",
@ -387,7 +391,8 @@
"Column {{colId}} was subsequently removed in action #{{action.actionNum}}": "La colonna {{colId}} è stata successivamente rimossa nell'azione #{{action.actionNum}}", "Column {{colId}} was subsequently removed in action #{{action.actionNum}}": "La colonna {{colId}} è stata successivamente rimossa nell'azione #{{action.actionNum}}",
"Table {{tableId}} was subsequently removed in action #{{actionNum}}": "La tabella {{tableId}} è stata successivamente rimossa nell'azione #{{actionNum}}", "Table {{tableId}} was subsequently removed in action #{{actionNum}}": "La tabella {{tableId}} è stata successivamente rimossa nell'azione #{{actionNum}}",
"Action Log failed to load": "Impossibile caricare il log delle azioni", "Action Log failed to load": "Impossibile caricare il log delle azioni",
"This row was subsequently removed in action {{action.actionNum}}": "Questa riga è stata successivamente rimossa nell'azione {{action.actionNum}}" "This row was subsequently removed in action {{action.actionNum}}": "Questa riga è stata successivamente rimossa nell'azione {{action.actionNum}}",
"All tables": "Tutte le tabelle"
}, },
"App": { "App": {
"Memory Error": "Errore di memoria", "Memory Error": "Errore di memoria",
@ -558,7 +563,9 @@
"Add": "Aggiungi", "Add": "Aggiungi",
"Enter Custom URL": "Inserisci URL personalizzata", "Enter Custom URL": "Inserisci URL personalizzata",
"Learn more about custom widgets": "Scopri di più sui widget personalizzati", "Learn more about custom widgets": "Scopri di più sui widget personalizzati",
"Select Custom Widget": "Seleziona un Widget personalizzato" "Select Custom Widget": "Seleziona un Widget personalizzato",
"No {{columnType}} columns in table.": "Nessuna colonna {{columnType}} nella tabella.",
"Clear selection": "Deseleziona tutto"
}, },
"DataTables": { "DataTables": {
"Click to copy": "Clicca per copiare", "Click to copy": "Clicca per copiare",
@ -756,7 +763,27 @@
"Sorted (#{{count}})_other": "Ordinati (#{{count}})", "Sorted (#{{count}})_other": "Ordinati (#{{count}})",
"Unfreeze {{count}} columns_other": "Sblocca {{count}} colonne", "Unfreeze {{count}} columns_other": "Sblocca {{count}} colonne",
"Insert column to the left": "Inserisci colonna a sinistra", "Insert column to the left": "Inserisci colonna a sinistra",
"Insert column to the right": "Inserisci colonna a destra" "Insert column to the right": "Inserisci colonna a destra",
"Detect Duplicates in...": "Rilevati duplicati in...",
"UUID": "UUID",
"Shortcuts": "Scorciatoie",
"Show hidden columns": "Mostra colonne nascoste",
"Created At": "Creato il",
"Authorship": "Autore",
"Last Updated By": "Ultimo aggiornamento da",
"Hidden Columns": "Colonne nascoste",
"Lookups": "Campi relativi",
"No reference columns.": "Non ci sono colonne referenziate.",
"Apply on record changes": "Applica quando il record è modificato",
"Duplicate in {{- label}}": "Duplicato in {{- label}}",
"Created By": "Creato da",
"Last Updated At": "Ultimo aggiornamento il",
"Apply to new records": "Applica ai nuovi record",
"Search columns": "Cerca colonne",
"Timestamp": "Data e ora",
"no reference column": "nessuna colonna referenziata",
"Adding UUID column": "Aggiungere colonna UUID",
"Adding duplicates column": "Aggiungere colonna duplicati"
}, },
"GristDoc": { "GristDoc": {
"Import from file": "Importa da file", "Import from file": "Importa da file",
@ -928,7 +955,8 @@
"Choice List": "Scelta da lista", "Choice List": "Scelta da lista",
"Attachment": "Allegato", "Attachment": "Allegato",
"Numeric": "Numerico", "Numeric": "Numerico",
"Choice": "Scelta" "Choice": "Scelta",
"Search columns": "Cerca colonne"
}, },
"modals": { "modals": {
"Cancel": "Annulla", "Cancel": "Annulla",
@ -1019,7 +1047,15 @@
"Anchor Links": "Link interno", "Anchor Links": "Link interno",
"Custom Widgets": "Widget personalizzati", "Custom Widgets": "Widget personalizzati",
"You can choose one of our pre-made widgets or embed your own by providing its full URL.": "Puoi scegliere uno dei nostri widget pronti all'uso, o inserirne uno fatto da te, immettendo la sua URL completa.", "You can choose one of our pre-made widgets or embed your own by providing its full URL.": "Puoi scegliere uno dei nostri widget pronti all'uso, o inserirne uno fatto da te, immettendo la sua URL completa.",
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Per creare un link che porta l'utente a una cella specifica, fai clic su una riga e premi {{shortcut}}." "To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Per creare un link che porta l'utente a una cella specifica, fai clic su una riga e premi {{shortcut}}.",
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "Un UUID è una stringa generata automaticamente, uitle come identificatore univoco e chiave per i link.",
"To configure your calendar, select columns for start": {
"end dates and event titles. Note each column's type.": "Per configurare il calendario, seleziona le colonne per le date di inizio/fine, e i titoli degli eventi. Nota il tipo di ciascuna colonna."
},
"Calendar": "Calendario",
"Lookups return data from related tables.": "Un lookup restituisce dati dalle tabelle collegate.",
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Non trovi la colonna giusta? Fai clic su \"Cambia widget\" per selezionare la tabella con i dati degli eventi.",
"Use reference columns to relate data in different tables.": "Usa colonne di riferimenti per collegare dati da altre tabelle."
}, },
"DescriptionConfig": { "DescriptionConfig": {
"DESCRIPTION": "DESCRIZIONE" "DESCRIPTION": "DESCRIZIONE"

View File

@ -0,0 +1,7 @@
{
"ACUserManager": {
"Invite new member": "Nieuw lid uitnodigen",
"We'll email an invite to {{email}}": "We sturen een uitnodiging naar {{email}}",
"Enter email address": "E-mailadres ingeven"
}
}

View File

@ -216,7 +216,8 @@
"Mixed types": "Смешанные типы", "Mixed types": "Смешанные типы",
"Revert field settings for {{colId}} to common": "Вернуть настройки полей для {{colId}} к общим", "Revert field settings for {{colId}} to common": "Вернуть настройки полей для {{colId}} к общим",
"Save field settings for {{colId}} as common": "Сохранить настройки полей для {{colId}} как общие", "Save field settings for {{colId}} as common": "Сохранить настройки полей для {{colId}} как общие",
"Use separate field settings for {{colId}}": "Использовать отдельные настройки полей для {{colId}}" "Use separate field settings for {{colId}}": "Использовать отдельные настройки полей для {{colId}}",
"Changing column type": "Изменение типа столбца"
}, },
"FieldConfig": { "FieldConfig": {
"TRIGGER FORMULA": "ТРИГГЕРНАЯ ФОРМУЛА", "TRIGGER FORMULA": "ТРИГГЕРНАЯ ФОРМУЛА",
@ -1050,7 +1051,8 @@
"end dates and event titles. Note each column's type.": "Чтобы настроить календарь, выберите столбцы для дат начала/окончания и названий событий. Обратите внимание на тип каждого столбца." "end dates and event titles. Note each column's type.": "Чтобы настроить календарь, выберите столбцы для дат начала/окончания и названий событий. Обратите внимание на тип каждого столбца."
}, },
"Calendar": "Календарь", "Calendar": "Календарь",
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Не можете найти нужные столбцы? Нажмите «Изменить виджет», чтобы выбрать таблицу с данными о событиях." "Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Не можете найти нужные столбцы? Нажмите «Изменить виджет», чтобы выбрать таблицу с данными о событиях.",
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID - это случайно сгенерированная строка, которая полезна для уникальных идентификаторов и ключевых ссылок."
}, },
"DescriptionConfig": { "DescriptionConfig": {
"DESCRIPTION": "ОПИСАНИЕ" "DESCRIPTION": "ОПИСАНИЕ"

View File

@ -674,7 +674,10 @@
"Try out changes in a copy, then decide whether to replace the original with your edits.": "Preizkusite spremembe v kopiji, nato pa se odločite, ali boste izvirnik zamenjali s svojimi popravki.", "Try out changes in a copy, then decide whether to replace the original with your edits.": "Preizkusite spremembe v kopiji, nato pa se odločite, ali boste izvirnik zamenjali s svojimi popravki.",
"The Raw Data page lists all data tables in your document, including summary tables and tables not included in page layouts.": "Na strani z neobdelanimi podatki so navedene vse podatkovne tabele v vašem dokumentu, vključno s tabelami s povzetki in tabelami, ki niso vključene v postavitve strani.", "The Raw Data page lists all data tables in your document, including summary tables and tables not included in page layouts.": "Na strani z neobdelanimi podatki so navedene vse podatkovne tabele v vašem dokumentu, vključno s tabelami s povzetki in tabelami, ki niso vključene v postavitve strani.",
"Reference columns are the key to {{relational}} data in Grist.": "Referenčni stolpci so ključ do {{relational}} podatkov v Gristu.", "Reference columns are the key to {{relational}} data in Grist.": "Referenčni stolpci so ključ do {{relational}} podatkov v Gristu.",
"Cells in a reference column always identify an {{entire}} record in that table, but you may select which column from that record to show.": "Celice v referenčnem stolpcu vedno identificirajo {{entire}} zapis v tej tabeli, vendar lahko izberete, kateri stolpec iz tega zapisa želite prikazati." "Cells in a reference column always identify an {{entire}} record in that table, but you may select which column from that record to show.": "Celice v referenčnem stolpcu vedno identificirajo {{entire}} zapis v tej tabeli, vendar lahko izberete, kateri stolpec iz tega zapisa želite prikazati.",
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID je naključno ustvarjen niz, ki je uporaben za edinstvene identifikatorje in ključe povezav.",
"Lookups return data from related tables.": "Iskanje vrne podatke iz povezanih tabel.",
"Use reference columns to relate data in different tables.": "Uporabite referenčne stolpce za povezavo podatkov v različnih tabelah."
}, },
"UserManager": { "UserManager": {
"Anyone with link ": "Vsakdo s povezavo ", "Anyone with link ": "Vsakdo s povezavo ",
@ -1136,7 +1139,8 @@
"Save field settings for {{colId}} as common": "Shrani nastavitve polja za {{colId}} kot običajne", "Save field settings for {{colId}} as common": "Shrani nastavitve polja za {{colId}} kot običajne",
"Changing multiple column types": "Spreminjanje več tipov stolpca", "Changing multiple column types": "Spreminjanje več tipov stolpca",
"Use separate field settings for {{colId}}": "Uporabite ločene nastavitve polja za {{colId}}", "Use separate field settings for {{colId}}": "Uporabite ločene nastavitve polja za {{colId}}",
"Apply Formula to Data": "Izvedi formulo nad podatki" "Apply Formula to Data": "Izvedi formulo nad podatki",
"Changing column type": "Spreminjanje vrste stolpca"
}, },
"FormulaEditor": { "FormulaEditor": {
"Enter formula or {{button}}.": "Vnesite formulo ali {{button}}.", "Enter formula or {{button}}.": "Vnesite formulo ali {{button}}.",

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

View File

@ -1,5 +1,6 @@
import {decodeUrl, IGristUrlState, parseFirstUrlPart} from 'app/common/gristUrls'; import {decodeUrl, getHostType, IGristUrlState, parseFirstUrlPart} from 'app/common/gristUrls';
import {assert} from 'chai'; import {assert} from 'chai';
import * as testUtils from 'test/server/testUtils';
describe('gristUrls', function() { describe('gristUrls', function() {
@ -76,4 +77,56 @@ describe('gristUrls', function() {
assert.deepEqual(parseFirstUrlPart('o', ''), {path: ''}); assert.deepEqual(parseFirstUrlPart('o', ''), {path: ''});
}); });
}); });
describe('getHostType', function() {
const defaultOptions = {
baseDomain: 'getgrist.com',
pluginUrl: 'https://plugin.getgrist.com',
};
let oldEnv: testUtils.EnvironmentSnapshot;
beforeEach(function () {
oldEnv = new testUtils.EnvironmentSnapshot();
});
afterEach(function () {
oldEnv.restore();
});
it('should interpret localhost as "native"', function() {
assert.equal(getHostType('localhost', defaultOptions), 'native');
assert.equal(getHostType('localhost:8080', defaultOptions), 'native');
});
it('should interpret base domain as "native"', function() {
assert.equal(getHostType('getgrist.com', defaultOptions), 'native');
assert.equal(getHostType('www.getgrist.com', defaultOptions), 'native');
assert.equal(getHostType('foo.getgrist.com', defaultOptions), 'native');
assert.equal(getHostType('foo.getgrist.com:8080', defaultOptions), 'native');
});
it('should interpret plugin domain as "plugin"', function() {
assert.equal(getHostType('plugin.getgrist.com', defaultOptions), 'plugin');
assert.equal(getHostType('PLUGIN.getgrist.com', { pluginUrl: 'https://pLuGin.getgrist.com' }), 'plugin');
});
it('should interpret other domains as "custom"', function() {
assert.equal(getHostType('foo.com', defaultOptions), 'custom');
assert.equal(getHostType('foo.bar.com', defaultOptions), 'custom');
});
it('should interpret doc internal url as "native"', function() {
process.env.APP_DOC_INTERNAL_URL = 'https://doc-worker-123.internal/path';
assert.equal(getHostType('doc-worker-123.internal', defaultOptions), 'native');
assert.equal(getHostType('doc-worker-123.internal:8080', defaultOptions), 'custom');
assert.equal(getHostType('doc-worker-124.internal', defaultOptions), 'custom');
process.env.APP_DOC_INTERNAL_URL = 'https://doc-worker-123.internal:8080/path';
assert.equal(getHostType('doc-worker-123.internal:8080', defaultOptions), 'native');
assert.equal(getHostType('doc-worker-123.internal', defaultOptions), 'custom');
assert.equal(getHostType('doc-worker-124.internal:8080', defaultOptions), 'custom');
assert.equal(getHostType('doc-worker-123.internal:8079', defaultOptions), 'custom');
});
});
}); });

View File

@ -734,146 +734,167 @@ describe('CustomWidgets', function () {
oldEnv = new EnvironmentSnapshot(); oldEnv = new EnvironmentSnapshot();
}); });
after(async function() { afterEach(async function() {
oldEnv.restore(); oldEnv.restore();
await server.restart(); await server.restart();
await gu.reloadDoc();
}); });
it('can add widgets via plugins', async function () { for (const variant of ['flat', 'nested'] as const) {
// Double-check that using one external widget, we see it(`can add widgets via plugins (${variant} layout)`, async function () {
// just that widget listed. // Double-check that using one external widget, we see
widgets = [widget1]; // just that widget listed.
await useManifest(manifestEndpoint); widgets = [widget1];
await enableWidgetsAndShowPanel(); await useManifest(manifestEndpoint);
await toggle(); await enableWidgetsAndShowPanel();
assert.deepEqual(await options(), [ await toggle();
CUSTOM_URL, widget1.name, assert.deepEqual(await options(), [
]); CUSTOM_URL, widget1.name,
]);
// Get a temporary directory that will be cleaned up, // Get a temporary directory that will be cleaned up,
// and populated it as follows: // and populated it as follows ('flat' variant)
// plugins/ // plugins/
// my-widgets/ // my-widgets/
// manifest.yml # a plugin manifest, listing widgets.json // manifest.yml # a plugin manifest, listing widgets.json
// widgets.json # a widget set manifest, grist-widget style // widgets.json # a widget set manifest, grist-widget style
// p1.html # one of the widgets in widgets.json // p1.html # one of the widgets in widgets.json
// p2.html # another of the widgets in widgets.json // p2.html # another of the widgets in widgets.json
// grist-plugin-api.js # a dummy api file, to check it is overridden // grist-plugin-api.js # a dummy api file, to check it is overridden
const dir = await createTmpDir(); // In 'nested' variant, widgets.json and the files it refers to are in
const pluginDir = path.join(dir, 'plugins', 'my-widgets'); // a subdirectory.
await fse.mkdirp(pluginDir); const dir = await createTmpDir();
const pluginDir = path.join(dir, 'plugins', 'my-widgets');
const widgetDir = variant === 'nested' ? path.join(pluginDir, 'nested') : pluginDir;
await fse.mkdirp(pluginDir);
await fse.mkdirp(widgetDir);
// A plugin, with some widgets in it. // A plugin, with some widgets in it.
await fse.writeFile(path.join(pluginDir, 'manifest.yml'), ` await fse.writeFile(
name: My Widgets path.join(pluginDir, 'manifest.yml'),
components: `name: My Widgets\n` +
widgets: widgets.json `components:\n` +
`); ` widgets: ${variant === 'nested' ? 'nested/' : ''}widgets.json\n`
);
// A list of a pair of custom widgets, with the widget // A list of a pair of custom widgets, with the widget
// source in the same directory. // source in the same directory.
await fse.writeFile( await fse.writeFile(
path.join(pluginDir, 'widgets.json'), path.join(widgetDir, 'widgets.json'),
JSON.stringify([ JSON.stringify([
{ {
widgetId: 'p1', widgetId: 'p1',
name: 'P1', name: 'P1',
url: './p1.html', url: './p1.html',
}, },
{ {
widgetId: 'p2', widgetId: 'p2',
name: 'P2', name: 'P2',
url: './p2.html', url: './p2.html',
}, },
]), {
); widgetId: 'p3',
name: 'P3',
url: './p3.html',
published: false,
},
]),
);
// The first widget - just contains the text P1. // The first widget - just contains the text P1.
await fse.writeFile( await fse.writeFile(
path.join(pluginDir, 'p1.html'), path.join(widgetDir, 'p1.html'),
'<html><body>P1</body></html>', '<html><body>P1</body></html>',
); );
// The second widget. This contains the text P2 // The second widget. This contains the text P2
// if grist is defined after loading grist-plugin-api.js // if grist is defined after loading grist-plugin-api.js
// (but the js bundled with the widget just throws an // (but the js bundled with the widget just throws an
// alert). // alert).
await fse.writeFile( await fse.writeFile(
path.join(pluginDir, 'p2.html'), path.join(widgetDir, 'p2.html'),
` `
<html> <html>
<script src="./grist-plugin-api.js"></script> <head><script src="./grist-plugin-api.js"></script></head>
<body> <body>
<div id="readout"></div> <div id="readout"></div>
<script> <script>
if (typeof grist !== 'undefined') { if (typeof grist !== 'undefined') {
document.getElementById('readout').innerText = 'P2'; document.getElementById('readout').innerText = 'P2';
} }
</script> </script>
</body> </body>
</html> </html>
` `
); );
// A dummy grist-plugin-api.js - hopefully the actual
// js for the current version of Grist will be served in
// its place.
await fse.writeFile(
path.join(pluginDir, 'grist-plugin-api.js'),
'alert("Error: built in api version used");',
);
// Restart server and reload doc now plugins are in place. // The third widget - just contains the text P3.
process.env.GRIST_USER_ROOT = dir; await fse.writeFile(
await server.restart(); path.join(widgetDir, 'p3.html'),
await gu.reloadDoc(); '<html><body>P3</body></html>',
);
// Continue using one external widget. // A dummy grist-plugin-api.js - hopefully the actual
await useManifest(manifestEndpoint); // js for the current version of Grist will be served in
await enableWidgetsAndShowPanel(); // its place.
await fse.writeFile(
path.join(widgetDir, 'grist-plugin-api.js'),
'alert("Error: built in api version used");',
);
// Check we see one external widget and two bundled ones. // Restart server and reload doc now plugins are in place.
await toggle(); process.env.GRIST_USER_ROOT = dir;
assert.deepEqual(await options(), [ await server.restart();
CUSTOM_URL, widget1.name, 'P1 (My Widgets)', 'P2 (My Widgets)', await gu.reloadDoc();
]);
// Prepare to check content of widgets. // Continue using one external widget.
async function getWidgetText(): Promise<string> { await useManifest(manifestEndpoint);
return gu.doInIframe(await getCustomWidgetFrame(), () => { await enableWidgetsAndShowPanel();
return driver.executeScript(
() => document.body.innerText // Check we see one external widget and two bundled ones.
); await toggle();
assert.deepEqual(await options(), [
CUSTOM_URL, widget1.name, 'P1 (My Widgets)', 'P2 (My Widgets)',
]);
// Prepare to check content of widgets.
async function getWidgetText(): Promise<string> {
return gu.doInIframe(await getCustomWidgetFrame(), () => {
return driver.executeScript(
() => document.body.innerText
);
});
}
// Check built-in P1 works as expected.
await select(/P1/);
assert.equal(await current(), 'P1 (My Widgets)');
await gu.waitToPass(async () => {
assert.equal(await getWidgetText(), 'P1');
}); });
}
// Check built-in P1 works as expected. // Check external W1 works as expected.
await select(/P1/); await toggle();
assert.equal(await current(), 'P1 (My Widgets)'); await select(/W1/);
await gu.waitToPass(async () => { assert.equal(await current(), 'W1');
assert.equal(await getWidgetText(), 'P1'); await gu.waitToPass(async () => {
}); assert.equal(await getWidgetText(), 'W1');
});
// Check external W1 works as expected. // Check build-in P2 works as expected.
await toggle(); await toggle();
await select(/W1/); await select(/P2/);
assert.equal(await current(), 'W1'); assert.equal(await current(), 'P2 (My Widgets)');
await gu.waitToPass(async () => { await gu.waitToPass(async () => {
assert.equal(await getWidgetText(), 'W1'); assert.equal(await getWidgetText(), 'P2');
}); });
// Check build-in P2 works as expected. // Make sure widget setting is sticky.
await toggle(); await gu.reloadDoc();
await select(/P2/); await gu.waitToPass(async () => {
assert.equal(await current(), 'P2 (My Widgets)'); assert.equal(await getWidgetText(), 'P2');
await gu.waitToPass(async () => { });
assert.equal(await getWidgetText(), 'P2');
}); });
}
// Make sure widget setting is sticky.
await gu.reloadDoc();
await gu.waitToPass(async () => {
assert.equal(await getWidgetText(), 'P2');
});
});
}); });
}); });

View File

@ -17,9 +17,10 @@ describe('DocApi2', function() {
let owner: UserAPI; let owner: UserAPI;
let wsId: number; let wsId: number;
testUtils.setTmpLogLevel('error'); testUtils.setTmpLogLevel('error');
const oldEnv = new testUtils.EnvironmentSnapshot(); let oldEnv: testUtils.EnvironmentSnapshot;
before(async function() { before(async function() {
oldEnv = new testUtils.EnvironmentSnapshot();
const tmpDir = await createTmpDir(); const tmpDir = await createTmpDir();
process.env.GRIST_DATA_DIR = tmpDir; process.env.GRIST_DATA_DIR = tmpDir;
process.env.STRIPE_ENDPOINT_SECRET = 'TEST_WITHOUT_ENDPOINT_SECRET'; process.env.STRIPE_ENDPOINT_SECRET = 'TEST_WITHOUT_ENDPOINT_SECRET';