mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) updates from grist-core
This commit is contained in:
commit
8053c81d02
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
7
static/locales/nl.client.json
Normal file
7
static/locales/nl.client.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -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": "ОПИСАНИЕ"
|
||||||
|
@ -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}}.",
|
||||||
|
1
static/locales/th.client.json
Normal file
1
static/locales/th.client.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
1
static/locales/zh-Hant.client.json
Normal file
1
static/locales/zh-Hant.client.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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';
|
||||||
|
Loading…
Reference in New Issue
Block a user