Browse Source

Merge remote-tracking branch 'origin/dev' into dev

Vanessa 7 months ago
parent
commit
028e3118c4

+ 14 - 12
app/appearance/langs/de_DE.json

@@ -232,19 +232,20 @@
   "builtIn": "Integriert",
   "builtIn": "Integriert",
   "endDate": "Enddatum",
   "endDate": "Enddatum",
   "needLogin": "Diese Funktion erfordert ein Anmelden",
   "needLogin": "Diese Funktion erfordert ein Anmelden",
-  "calcResultCountAll": "ANZAHL",
-  "calcResultCountValues": "WERT",
-  "calcResultCountUniqueValues": "EINZIGARTIG",
-  "calcResultCountEmpty": "LEER",
-  "calcResultCountNotEmpty": "NICHT LEER",
-  "calcResultPercentEmpty": "LEER",
-  "calcResultPercentNotEmpty": "NICHT LEER",
+  "calcResultCountAll": "Alle zählen",
+  "calcResultCountValues": "Werte zählen",
+  "calcResultCountUniqueValues": "Eindeutige Werte zählen",
+  "calcResultCountEmpty": "Leer zählen",
+  "calcResultCountNotEmpty": "Nicht leer zählen",
+  "calcResultPercentEmpty": "Prozent leer",
+  "calcResultPercentNotEmpty": "Prozent nicht leer",
+  "calcResultPercentUniqueValues": "Prozent einzigartige Werte",
   "calcResultSum": "SUMME",
   "calcResultSum": "SUMME",
-  "calcResultAverage": "DURCHSCHNITT",
-  "calcResultMedian": "MEDIAN",
-  "calcResultMin": "MIN",
-  "calcResultMax": "MAX",
-  "calcResultRange": "BEREICH",
+  "calcResultAverage": "Durchschnitt",
+  "calcResultMedian": "Median",
+  "calcResultMin": "Min",
+  "calcResultMax": "Max",
+  "calcResultRange": "Bereich",
   "calc": "Berechnen",
   "calc": "Berechnen",
   "createWorkspace": "Arbeitsbereich erstellen",
   "createWorkspace": "Arbeitsbereich erstellen",
   "createWorkspaceTip": "Sind Sie sicher, dass Sie diesen Pfad verwenden möchten, um einen Arbeitsbereich zu erstellen?",
   "createWorkspaceTip": "Sind Sie sicher, dass Sie diesen Pfad verwenden möchten, um einen Arbeitsbereich zu erstellen?",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "Nicht leer zählen",
   "calcOperatorCountNotEmpty": "Nicht leer zählen",
   "calcOperatorPercentEmpty": "Prozent leer",
   "calcOperatorPercentEmpty": "Prozent leer",
   "calcOperatorPercentNotEmpty": "Prozent nicht leer",
   "calcOperatorPercentNotEmpty": "Prozent nicht leer",
+  "calcOperatorPercentUniqueValues": "Prozent einzigartige Werte",
   "calcOperatorSum": "Summe",
   "calcOperatorSum": "Summe",
   "calcOperatorAverage": "Durchschnitt",
   "calcOperatorAverage": "Durchschnitt",
   "calcOperatorMedian": "Median",
   "calcOperatorMedian": "Median",

+ 15 - 13
app/appearance/langs/en_US.json

@@ -232,19 +232,20 @@
   "builtIn": "Built-in",
   "builtIn": "Built-in",
   "endDate": "End date",
   "endDate": "End date",
   "needLogin": "This function needs to be logged in to use",
   "needLogin": "This function needs to be logged in to use",
-  "calcResultCountAll": "COUNT",
-  "calcResultCountValues": "VALUES",
-  "calcResultCountUniqueValues": "UNIQUE",
-  "calcResultCountEmpty": "EMPTY",
-  "calcResultCountNotEmpty": "NOT EMPTY",
-  "calcResultPercentEmpty": "EMPTY",
-  "calcResultPercentNotEmpty": "NOT EMPTY",
-  "calcResultSum": "SUM",
-  "calcResultAverage": "AVERAGE",
-  "calcResultMedian": "MEDIAN",
-  "calcResultMin": "MIN",
-  "calcResultMax": "MAX",
-  "calcResultRange": "RANGE",
+  "calcResultCountAll": "Count all",
+  "calcResultCountValues": "Count Values",
+  "calcResultCountUniqueValues": "Count unique values",
+  "calcResultCountEmpty": "Count empty",
+  "calcResultCountNotEmpty": "Count not empty",
+  "calcResultPercentEmpty": "Percent empty",
+  "calcResultPercentNotEmpty": "Percent not empty",
+  "calcResultPercentUniqueValues": "Percent unique values",
+  "calcResultSum": "Sum",
+  "calcResultAverage": "Average",
+  "calcResultMedian": "Median",
+  "calcResultMin": "Min",
+  "calcResultMax": "Max",
+  "calcResultRange": "Range",
   "calc": "Calculate",
   "calc": "Calculate",
   "createWorkspace": "Create Workspace",
   "createWorkspace": "Create Workspace",
   "createWorkspaceTip": "Are you sure to use this path to create a workspace?",
   "createWorkspaceTip": "Are you sure to use this path to create a workspace?",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "Count not empty",
   "calcOperatorCountNotEmpty": "Count not empty",
   "calcOperatorPercentEmpty": "Percent empty",
   "calcOperatorPercentEmpty": "Percent empty",
   "calcOperatorPercentNotEmpty": "Percent not empty",
   "calcOperatorPercentNotEmpty": "Percent not empty",
+  "calcOperatorPercentUniqueValues": "Percent unique values",
   "calcOperatorSum": "Sum",
   "calcOperatorSum": "Sum",
   "calcOperatorAverage": "Average",
   "calcOperatorAverage": "Average",
   "calcOperatorMedian": "Median",
   "calcOperatorMedian": "Median",

+ 15 - 13
app/appearance/langs/es_ES.json

@@ -232,19 +232,20 @@
   "builtIn": "Incorporado",
   "builtIn": "Incorporado",
   "endDate": "Fecha de finalización",
   "endDate": "Fecha de finalización",
   "needLogin": "Esta función requiere iniciar sesión en la cuenta antes de poder usarla",
   "needLogin": "Esta función requiere iniciar sesión en la cuenta antes de poder usarla",
-  "calcResultCountAll": "CONTAR",
-  "calcResultCountValues": "VALORES",
-  "calcResultCountUniqueValues": "ÚNICO",
-  "calcResultCountEmpty": "VACÍO",
-  "calcResultCountNotEmpty": "NO VACÍO",
-  "calcResultPercentEmpty": "VACÍO",
-  "calcResultPercentNotEmpty": "NO VACÍO",
+  "calcResultCountAll": "Contar todo",
+  "calcResultCountValues": "Valores de conteo",
+  "calcResultCountUniqueValues": "Contar valores únicos",
+  "calcResultCountEmpty": "Cuenta vacía",
+  "calcResultCountNotEmpty": "Cuenta no vacía",
+  "calcResultPercentEmpty": "Porcentaje vacío",
+  "calcResultPercentNotEmpty": "Porcentaje no vacío",
+  "calcResultPercentUniqueValues": "Porcentaje de valores únicos",
   "calcResultSum": "SUMA",
   "calcResultSum": "SUMA",
-  "calcResultAverage": "PROMEDIO",
-  "calcResultMedian": "MEDIANA",
-  "calcResultMin": "MIN",
-  "calcResultMax": "MAX",
-  "calcResultRange": "RANGO",
+  "calcResultAverage": "Promedio",
+  "calcResultMedian": "Mediana",
+  "calcResultMin": "Min",
+  "calcResultMax": "Máx",
+  "calcResultRange": "Rango",
   "calc": "Calcular",
   "calc": "Calcular",
   "createWorkspace": "Crear espacio de trabajo",
   "createWorkspace": "Crear espacio de trabajo",
   "createWorkspaceTip": "¿Estás seguro de usar esta ruta para crear un espacio de trabajo?",
   "createWorkspaceTip": "¿Estás seguro de usar esta ruta para crear un espacio de trabajo?",
@@ -256,11 +257,12 @@
   "calcOperatorCountNotEmpty": "Cuenta no vacía",
   "calcOperatorCountNotEmpty": "Cuenta no vacía",
   "calcOperatorPercentEmpty": "Porcentaje vacío",
   "calcOperatorPercentEmpty": "Porcentaje vacío",
   "calcOperatorPercentNotEmpty": "Porcentaje no vacío",
   "calcOperatorPercentNotEmpty": "Porcentaje no vacío",
+  "calcOperatorPercentUniqueValues": "Porcentaje de valores únicos",
   "calcOperatorSum": "Suma",
   "calcOperatorSum": "Suma",
   "calcOperatorAverage": "Promedio",
   "calcOperatorAverage": "Promedio",
   "calcOperatorMedian": "Mediana",
   "calcOperatorMedian": "Mediana",
   "calcOperatorMin": "Min",
   "calcOperatorMin": "Min",
-  "calcOperatorMax": "Máx.",
+  "calcOperatorMax": "Máx",
   "calcOperatorRange": "Rango",
   "calcOperatorRange": "Rango",
   "calcOperatorEarliest": "Primero",
   "calcOperatorEarliest": "Primero",
   "calcOperatorLatest": "Último",
   "calcOperatorLatest": "Último",

+ 14 - 12
app/appearance/langs/fr_FR.json

@@ -232,19 +232,20 @@
   "builtIn": "Intégré",
   "builtIn": "Intégré",
   "endDate": "Date de fin",
   "endDate": "Date de fin",
   "needLogin": "La fonctionnalité nécessite un numéro de compte de connexion avant de pouvoir être utilisée",
   "needLogin": "La fonctionnalité nécessite un numéro de compte de connexion avant de pouvoir être utilisée",
-  "calcResultCountAll": "COUNT",
-  "calcResultCountValues": "VALEURS",
-  "calcResultCountUniqueValues": "UNIQUE",
-  "calcResultCountVide": "VIDE",
-  "calcResultCountNotEmpty": "NON VIDE",
-  "calcResultPercentEmpty": "VIDE",
-  "calcResultPercentNotEmpty": "NON VIDE",
+  "calcResultCountAll": "Compter tout",
+  "calcResultCountValues": "Compter les valeurs",
+  "calcResultCountUniqueValues": "Compter les valeurs uniques",
+  "calcResultCountVide": "Compter vide",
+  "calcResultCountNotEmpty": "Compter non vide",
+  "calcResultPercentEmpty": "Pourcentage vide",
+  "calcResultPercentNotEmpty": "Pourcentage non vide",
+  "calcResultPercentUniqueValues": "Pourcentage de valeurs uniques",
   "calcResultSum": "SOMME",
   "calcResultSum": "SOMME",
-  "calcResultAverage": "MOYENNE",
-  "calcResultMedian": "MÉDIANE",
-  "calcResultMin": "MIN",
-  "calcResultMax": "MAX",
-  "calcResultRange": "PLAGE",
+  "calcResultAverage": "Moyenne",
+  "calcResultMedian": "Médiane",
+  "calcResultMin": "Min",
+  "calcResultMax": "Max",
+  "calcResultRange": "Plage",
   "calc": "Calculer",
   "calc": "Calculer",
   "createWorkspace": "Créer un espace de travail",
   "createWorkspace": "Créer un espace de travail",
   "createWorkspaceTip": "Êtes-vous sûr d'utiliser ce chemin pour créer un espace de travail ?",
   "createWorkspaceTip": "Êtes-vous sûr d'utiliser ce chemin pour créer un espace de travail ?",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "Compter non vide",
   "calcOperatorCountNotEmpty": "Compter non vide",
   "calcOperatorPercentEmpty": "Pourcentage vide",
   "calcOperatorPercentEmpty": "Pourcentage vide",
   "calcOperatorPercentNotEmpty": "Pourcentage non vide",
   "calcOperatorPercentNotEmpty": "Pourcentage non vide",
+  "calcOperatorPercentUniqueValues": "Pourcentage de valeurs uniques",
   "calcOperatorSum": "Somme",
   "calcOperatorSum": "Somme",
   "calcOperatorAverage": "Moyenne",
   "calcOperatorAverage": "Moyenne",
   "calcOperatorMedian": "Médiane",
   "calcOperatorMedian": "Médiane",

+ 9 - 7
app/appearance/langs/he_IL.json

@@ -232,13 +232,14 @@
   "builtIn": "מותקן",
   "builtIn": "מותקן",
   "endDate": "תאריך סיום",
   "endDate": "תאריך סיום",
   "needLogin": "פונקציה זו דורשת כניסה",
   "needLogin": "פונקציה זו דורשת כניסה",
-  "calcResultCountAll": "מנה",
-  "calcResultCountValues": "ערכים",
-  "calcResultCountUniqueValues": "ערכים ייחודיים",
-  "calcResultCountEmpty": "ריק",
-  "calcResultCountNotEmpty": "לא ריק",
-  "calcResultPercentEmpty": "ריק",
-  "calcResultPercentNotEmpty": "לא ריק",
+  "calcResultCountAll": "ספור הכל",
+  "calcResultCountValues": "ספור ערכים",
+  "calcResultCountUniqueValues": "ספור ערכים ייחודיים",
+  "calcResultCountEmpty": "ספור ריקים",
+  "calcResultCountNotEmpty": "ספור לא ריקים",
+  "calcResultPercentEmpty": "אחוז ריקים",
+  "calcResultPercentNotEmpty": "אחוז לא ריקים",
+  "calcResultPercentUniqueValues": "אחוז ערכים ייחודיים",
   "calcResultSum": "סכום",
   "calcResultSum": "סכום",
   "calcResultAverage": "ממוצע",
   "calcResultAverage": "ממוצע",
   "calcResultMedian": "חציון",
   "calcResultMedian": "חציון",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "ספור לא ריקים",
   "calcOperatorCountNotEmpty": "ספור לא ריקים",
   "calcOperatorPercentEmpty": "אחוז ריקים",
   "calcOperatorPercentEmpty": "אחוז ריקים",
   "calcOperatorPercentNotEmpty": "אחוז לא ריקים",
   "calcOperatorPercentNotEmpty": "אחוז לא ריקים",
+  "calcOperatorPercentUniqueValues": "אחוז ערכים ייחודיים",
   "calcOperatorSum": "סכום",
   "calcOperatorSum": "סכום",
   "calcOperatorAverage": "ממוצע",
   "calcOperatorAverage": "ממוצע",
   "calcOperatorMedian": "חציון",
   "calcOperatorMedian": "חציון",

+ 15 - 13
app/appearance/langs/it_IT.json

@@ -232,19 +232,20 @@
   "builtIn": "Integrato",
   "builtIn": "Integrato",
   "endDate": "Data di fine",
   "endDate": "Data di fine",
   "needLogin": "Questa funzione richiede il login per essere utilizzata",
   "needLogin": "Questa funzione richiede il login per essere utilizzata",
-  "calcResultCountAll": "CONTA",
-  "calcResultCountValues": "VALORI",
-  "calcResultCountUniqueValues": "UNICI",
-  "calcResultCountEmpty": "VUOTO",
-  "calcResultCountNotEmpty": "NON VUOTO",
-  "calcResultPercentEmpty": "VUOTO",
-  "calcResultPercentNotEmpty": "NON VUOTO",
-  "calcResultSum": "SOMMA",
-  "calcResultAverage": "MEDIA",
-  "calcResultMedian": "MEDIANA",
-  "calcResultMin": "MIN",
-  "calcResultMax": "MAX",
-  "calcResultRange": "INTERVALLO",
+  "calcResultCountAll": "Conta tutto",
+  "calcResultCountValues": "Conta valori",
+  "calcResultCountUniqueValues": "Conta valori unici",
+  "calcResultCountEmpty": "Conta vuoti",
+  "calcResultCountNotEmpty": "Conta non vuoti",
+  "calcResultPercentEmpty": "Percentuale vuoti",
+  "calcResultPercentNotEmpty": "Percentuale non vuoti",
+  "calcResultPercentUniqueValues": "Percentuale di valori unici",
+  "calcResultSum": "Somma",
+  "calcResultAverage": "Media",
+  "calcResultMedian": "Mediana",
+  "calcResultMin": "Min",
+  "calcResultMax": "Max",
+  "calcResultRange": "Intervallo",
   "calc": "Calcola",
   "calc": "Calcola",
   "createWorkspace": "Crea area di lavoro",
   "createWorkspace": "Crea area di lavoro",
   "createWorkspaceTip": "Sei sicuro di voler utilizzare questo percorso per creare un'area di lavoro?",
   "createWorkspaceTip": "Sei sicuro di voler utilizzare questo percorso per creare un'area di lavoro?",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "Conta non vuoti",
   "calcOperatorCountNotEmpty": "Conta non vuoti",
   "calcOperatorPercentEmpty": "Percentuale vuoti",
   "calcOperatorPercentEmpty": "Percentuale vuoti",
   "calcOperatorPercentNotEmpty": "Percentuale non vuoti",
   "calcOperatorPercentNotEmpty": "Percentuale non vuoti",
+  "calcOperatorPercentUniqueValues": "Percentuale di valori unici",
   "calcOperatorSum": "Somma",
   "calcOperatorSum": "Somma",
   "calcOperatorAverage": "Media",
   "calcOperatorAverage": "Media",
   "calcOperatorMedian": "Mediana",
   "calcOperatorMedian": "Mediana",

+ 2 - 0
app/appearance/langs/ja_JP.json

@@ -239,6 +239,7 @@
   "calcResultCountNotEmpty": "空ではない数",
   "calcResultCountNotEmpty": "空ではない数",
   "calcResultPercentEmpty": "空のパーセント",
   "calcResultPercentEmpty": "空のパーセント",
   "calcResultPercentNotEmpty": "空ではないパーセント",
   "calcResultPercentNotEmpty": "空ではないパーセント",
+  "calcResultPercentUniqueValues": "ユニーク値の割合",
   "calcResultSum": "合計値",
   "calcResultSum": "合計値",
   "calcResultAverage": "平均値",
   "calcResultAverage": "平均値",
   "calcResultMedian": "中央値",
   "calcResultMedian": "中央値",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "空ではない数",
   "calcOperatorCountNotEmpty": "空ではない数",
   "calcOperatorPercentEmpty": "空のパーセント",
   "calcOperatorPercentEmpty": "空のパーセント",
   "calcOperatorPercentNotEmpty": "空ではないパーセント",
   "calcOperatorPercentNotEmpty": "空ではないパーセント",
+  "calcOperatorPercentUniqueValues": "ユニーク値の割合",
   "calcOperatorSum": "合計値",
   "calcOperatorSum": "合計値",
   "calcOperatorAverage": "平均値",
   "calcOperatorAverage": "平均値",
   "calcOperatorMedian": "中央値",
   "calcOperatorMedian": "中央値",

+ 15 - 13
app/appearance/langs/pl_PL.json

@@ -232,19 +232,20 @@
   "builtIn": "Wbudowane",
   "builtIn": "Wbudowane",
   "endDate": "Data zakończenia",
   "endDate": "Data zakończenia",
   "needLogin": "Ta funkcja wymaga zalogowania się",
   "needLogin": "Ta funkcja wymaga zalogowania się",
-  "calcResultCountAll": "LICZBA",
-  "calcResultCountValues": "WARTOŚCI",
-  "calcResultCountUniqueValues": "UNIKALNE",
-  "calcResultCountEmpty": "PUSTY",
-  "calcResultCountNotEmpty": "NIE PUSTY",
-  "calcResultPercentEmpty": "PUSTE",
-  "calcResultPercentNotEmpty": "NIE PUSTE",
-  "calcResultSum": "SUMA",
-  "calcResultAverage": "ŚREDNIA",
-  "calcResultMedian": "MEDIANA",
-  "calcResultMin": "MIN",
-  "calcResultMax": "MAX",
-  "calcResultRange": "ZAKRES",
+  "calcResultCountAll": "Zlicz wszystko",
+  "calcResultCountValues": "Zlicz wartości",
+  "calcResultCountUniqueValues": "Zlicz unikalne wartości",
+  "calcResultCountEmpty": "Zlicz puste",
+  "calcResultCountNotEmpty": "Zlicz niepuste",
+  "calcResultPercentEmpty": "Procent pustych",
+  "calcResultPercentNotEmpty": "Procent niepustych",
+  "calcResultPercentUniqueValues": "Procent unikalnych wartości",
+  "calcResultSum": "Suma",
+  "calcResultAverage": "Średnia",
+  "calcResultMedian": "Mediana",
+  "calcResultMin": "Min",
+  "calcResultMax": "Max",
+  "calcResultRange": "Zakres",
   "calc": "Oblicz",
   "calc": "Oblicz",
   "createWorkspace": "Utwórz obszar roboczy",
   "createWorkspace": "Utwórz obszar roboczy",
   "createWorkspaceTip": "Czy na pewno chcesz użyć tej ścieżki do utworzenia obszaru roboczego?",
   "createWorkspaceTip": "Czy na pewno chcesz użyć tej ścieżki do utworzenia obszaru roboczego?",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "Zlicz niepuste",
   "calcOperatorCountNotEmpty": "Zlicz niepuste",
   "calcOperatorPercentEmpty": "Procent pustych",
   "calcOperatorPercentEmpty": "Procent pustych",
   "calcOperatorPercentNotEmpty": "Procent niepustych",
   "calcOperatorPercentNotEmpty": "Procent niepustych",
+  "calcOperatorPercentUniqueValues": "Procent unikalnych wartości",
   "calcOperatorSum": "Suma",
   "calcOperatorSum": "Suma",
   "calcOperatorAverage": "Średnia",
   "calcOperatorAverage": "Średnia",
   "calcOperatorMedian": "Mediana",
   "calcOperatorMedian": "Mediana",

+ 17 - 15
app/appearance/langs/ru_RU.json

@@ -232,19 +232,20 @@
   "builtIn": "Встроенный",
   "builtIn": "Встроенный",
   "endDate": "Дата окончания",
   "endDate": "Дата окончания",
   "needLogin": "Эта функция требует входа в систему для использования",
   "needLogin": "Эта функция требует входа в систему для использования",
-  "calcResultCountAll": "СЧЕТ",
-  "calcResultCountValues": "ЗНАЧЕНИЯ",
-  "calcResultCountUniqueValues": "УНИКАЛЬНЫЕ",
-  "calcResultCountEmpty": "ПУСТО",
-  "calcResultCountNotEmpty": "НЕ ПУСТО",
-  "calcResultPercentEmpty": "ПУСТО",
-  "calcResultPercentNotEmpty": "НЕ ПУСТО",
-  "calcResultSum": "СУММА",
-  "calcResultAverage": "СРЕДНЕЕ",
-  "calcResultMedian": "МЕДИАНА",
-  "calcResultMin": "МИН.",
-  "calcResultMax": "МАКС.",
-  "calcResultRange": "ДИАПАЗОН",
+  "calcResultCountAll": "Подсчитать все",
+  "calcResultCountValues": "Подсчитать значения",
+  "calcResultCountUniqueValues": "Подсчитать уникальные значения",
+  "calcResultCountEmpty": "Подсчитать пустые",
+  "calcResultCountNotEmpty": "Подсчитать непустые",
+  "calcResultPercentEmpty": "Процент пустых",
+  "calcResultPercentNotEmpty": "Процент не пустых",
+  "calcResultPercentUniqueValues": "Процент уникальных значений",
+  "calcResultSum": "Сумма",
+  "calcResultAverage": "Среднее",
+  "calcResultMedian": "Медиана",
+  "calcResultMin": "Мин",
+  "calcResultMax": "Макс",
+  "calcResultRange": "Диапазон",
   "calc": "Вычислить",
   "calc": "Вычислить",
   "createWorkspace": "Создать рабочее пространство",
   "createWorkspace": "Создать рабочее пространство",
   "createWorkspaceTip": "Вы уверены, что хотите использовать этот путь для создания рабочего пространства?",
   "createWorkspaceTip": "Вы уверены, что хотите использовать этот путь для создания рабочего пространства?",
@@ -256,11 +257,12 @@
   "calcOperatorCountNotEmpty": "Подсчитать непустые",
   "calcOperatorCountNotEmpty": "Подсчитать непустые",
   "calcOperatorPercentEmpty": "Процент пустых",
   "calcOperatorPercentEmpty": "Процент пустых",
   "calcOperatorPercentNotEmpty": "Процент не пустых",
   "calcOperatorPercentNotEmpty": "Процент не пустых",
+  "calcOperatorPercentUniqueValues": "Процент уникальных значений",
   "calcOperatorSum": "Сумма",
   "calcOperatorSum": "Сумма",
   "calcOperatorAverage": "Среднее",
   "calcOperatorAverage": "Среднее",
   "calcOperatorMedian": "Медиана",
   "calcOperatorMedian": "Медиана",
-  "calcOperatorMin": "Мин.",
-  "calcOperatorMax": "Макс.",
+  "calcOperatorMin": "Мин",
+  "calcOperatorMax": "Макс",
   "calcOperatorRange": "Диапазон",
   "calcOperatorRange": "Диапазон",
   "calcOperatorEarliest": "Самый ранний",
   "calcOperatorEarliest": "Самый ранний",
   "calcOperatorLatest": "Самый поздний",
   "calcOperatorLatest": "Самый поздний",

+ 2 - 0
app/appearance/langs/zh_CHT.json

@@ -239,6 +239,7 @@
   "calcResultCountNotEmpty": "已填寫",
   "calcResultCountNotEmpty": "已填寫",
   "calcResultPercentEmpty": "未填寫佔比",
   "calcResultPercentEmpty": "未填寫佔比",
   "calcResultPercentNotEmpty": "已填寫佔比",
   "calcResultPercentNotEmpty": "已填寫佔比",
+  "calcResultPercentUniqueValues": "唯一值佔比",
   "calcResultSum": "求和",
   "calcResultSum": "求和",
   "calcResultAverage": "平均值",
   "calcResultAverage": "平均值",
   "calcResultMedian": "中位數",
   "calcResultMedian": "中位數",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "已填寫",
   "calcOperatorCountNotEmpty": "已填寫",
   "calcOperatorPercentEmpty": "未填寫佔比",
   "calcOperatorPercentEmpty": "未填寫佔比",
   "calcOperatorPercentNotEmpty": "已填寫佔比",
   "calcOperatorPercentNotEmpty": "已填寫佔比",
+  "calcOperatorPercentUniqueValues": "唯一值佔比",
   "calcOperatorSum": "求和",
   "calcOperatorSum": "求和",
   "calcOperatorAverage": "平均值",
   "calcOperatorAverage": "平均值",
   "calcOperatorMedian": "中位數",
   "calcOperatorMedian": "中位數",

+ 2 - 0
app/appearance/langs/zh_CN.json

@@ -239,6 +239,7 @@
   "calcResultCountNotEmpty": "已填写",
   "calcResultCountNotEmpty": "已填写",
   "calcResultPercentEmpty": "未填写占比",
   "calcResultPercentEmpty": "未填写占比",
   "calcResultPercentNotEmpty": "已填写占比",
   "calcResultPercentNotEmpty": "已填写占比",
+  "calcResultPercentUniqueValues": "唯一值占比",
   "calcResultSum": "求和",
   "calcResultSum": "求和",
   "calcResultAverage": "平均值",
   "calcResultAverage": "平均值",
   "calcResultMedian": "中位数",
   "calcResultMedian": "中位数",
@@ -256,6 +257,7 @@
   "calcOperatorCountNotEmpty": "已填写",
   "calcOperatorCountNotEmpty": "已填写",
   "calcOperatorPercentEmpty": "未填写占比",
   "calcOperatorPercentEmpty": "未填写占比",
   "calcOperatorPercentNotEmpty": "已填写占比",
   "calcOperatorPercentNotEmpty": "已填写占比",
+  "calcOperatorPercentUniqueValues": "唯一值占比",
   "calcOperatorSum": "求和",
   "calcOperatorSum": "求和",
   "calcOperatorAverage": "平均值",
   "calcOperatorAverage": "平均值",
   "calcOperatorMedian": "中位数",
   "calcOperatorMedian": "中位数",

+ 16 - 0
app/src/protyle/render/av/calc.ts

@@ -208,6 +208,17 @@ export const openCalcMenu = async (protyle: IProtyle, calcElement: HTMLElement,
             blockID,
             blockID,
             target: calcElement
             target: calcElement
         });
         });
+        calcItem({
+            menu,
+            protyle,
+            colId,
+            avId,
+            oldOperator,
+            operator: "Percent unique values",
+            data: panelData?.data,
+            blockID,
+            target: calcElement
+        });
     } else {
     } else {
         calcItem({
         calcItem({
             menu,
             menu,
@@ -427,6 +438,9 @@ export const getCalcValue = (column: IAVColumn) => {
         case "Percent not empty":
         case "Percent not empty":
             value = `<span>${resultCalc.formattedContent}</span>${window.siyuan.languages.calcResultPercentNotEmpty}`;
             value = `<span>${resultCalc.formattedContent}</span>${window.siyuan.languages.calcResultPercentNotEmpty}`;
             break;
             break;
+        case "Percent unique values":
+            value = `<span>${resultCalc.formattedContent}</span>${window.siyuan.languages.calcResultPercentUniqueValues}`;
+            break;
         case "Sum":
         case "Sum":
             value = `<span>${resultCalc.formattedContent}</span>${window.siyuan.languages.calcResultSum}`;
             value = `<span>${resultCalc.formattedContent}</span>${window.siyuan.languages.calcResultSum}`;
             break;
             break;
@@ -486,6 +500,8 @@ export const getNameByOperator = (operator: string, isRollup: boolean) => {
             return window.siyuan.languages.calcOperatorPercentEmpty;
             return window.siyuan.languages.calcOperatorPercentEmpty;
         case "Percent not empty":
         case "Percent not empty":
             return window.siyuan.languages.calcOperatorPercentNotEmpty;
             return window.siyuan.languages.calcOperatorPercentNotEmpty;
+        case "Percent unique values":
+            return window.siyuan.languages.calcOperatorPercentUniqueValues;
         case "Checked":
         case "Checked":
             return window.siyuan.languages.checked;
             return window.siyuan.languages.checked;
         case "Unchecked":
         case "Unchecked":

+ 21 - 20
kernel/av/calc.go

@@ -28,24 +28,25 @@ type ColumnCalc struct {
 type CalcOperator string
 type CalcOperator string
 
 
 const (
 const (
-	CalcOperatorNone              CalcOperator = ""
-	CalcOperatorCountAll          CalcOperator = "Count all"
-	CalcOperatorCountValues       CalcOperator = "Count values"
-	CalcOperatorCountUniqueValues CalcOperator = "Count unique values"
-	CalcOperatorCountEmpty        CalcOperator = "Count empty"
-	CalcOperatorCountNotEmpty     CalcOperator = "Count not empty"
-	CalcOperatorPercentEmpty      CalcOperator = "Percent empty"
-	CalcOperatorPercentNotEmpty   CalcOperator = "Percent not empty"
-	CalcOperatorSum               CalcOperator = "Sum"
-	CalcOperatorAverage           CalcOperator = "Average"
-	CalcOperatorMedian            CalcOperator = "Median"
-	CalcOperatorMin               CalcOperator = "Min"
-	CalcOperatorMax               CalcOperator = "Max"
-	CalcOperatorRange             CalcOperator = "Range"
-	CalcOperatorEarliest          CalcOperator = "Earliest"
-	CalcOperatorLatest            CalcOperator = "Latest"
-	CalcOperatorChecked           CalcOperator = "Checked"
-	CalcOperatorUnchecked         CalcOperator = "Unchecked"
-	CalcOperatorPercentChecked    CalcOperator = "Percent checked"
-	CalcOperatorPercentUnchecked  CalcOperator = "Percent unchecked"
+	CalcOperatorNone                CalcOperator = ""
+	CalcOperatorCountAll            CalcOperator = "Count all"
+	CalcOperatorCountValues         CalcOperator = "Count values"
+	CalcOperatorCountUniqueValues   CalcOperator = "Count unique values"
+	CalcOperatorCountEmpty          CalcOperator = "Count empty"
+	CalcOperatorCountNotEmpty       CalcOperator = "Count not empty"
+	CalcOperatorPercentEmpty        CalcOperator = "Percent empty"
+	CalcOperatorPercentNotEmpty     CalcOperator = "Percent not empty"
+	CalcOperatorPercentUniqueValues CalcOperator = "Percent unique values"
+	CalcOperatorSum                 CalcOperator = "Sum"
+	CalcOperatorAverage             CalcOperator = "Average"
+	CalcOperatorMedian              CalcOperator = "Median"
+	CalcOperatorMin                 CalcOperator = "Min"
+	CalcOperatorMax                 CalcOperator = "Max"
+	CalcOperatorRange               CalcOperator = "Range"
+	CalcOperatorEarliest            CalcOperator = "Earliest"
+	CalcOperatorLatest              CalcOperator = "Latest"
+	CalcOperatorChecked             CalcOperator = "Checked"
+	CalcOperatorUnchecked           CalcOperator = "Unchecked"
+	CalcOperatorPercentChecked      CalcOperator = "Percent checked"
+	CalcOperatorPercentUnchecked    CalcOperator = "Percent unchecked"
 )
 )

+ 218 - 0
kernel/av/table_calc.go

@@ -130,6 +130,20 @@ func (table *Table) calcColTemplate(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Template && "" != row.Cells[colIndex].Value.Template.Content {
+				if !uniqueValues[row.Cells[colIndex].Value.Template.Content] {
+					uniqueValues[row.Cells[colIndex].Value.Template.Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	case CalcOperatorSum:
 	case CalcOperatorSum:
 		sum := 0.0
 		sum := 0.0
 		for _, row := range table.Rows {
 		for _, row := range table.Rows {
@@ -276,6 +290,22 @@ func (table *Table) calcColMAsset(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MAsset && 0 < len(row.Cells[colIndex].Value.MAsset) {
+				for _, sel := range row.Cells[colIndex].Value.MAsset {
+					if _, ok := uniqueValues[sel.Content]; !ok {
+						uniqueValues[sel.Content] = true
+						countUniqueValues++
+					}
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -341,6 +371,22 @@ func (table *Table) calcColMSelect(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
+				for _, sel := range row.Cells[colIndex].Value.MSelect {
+					if _, ok := uniqueValues[sel.Content]; !ok {
+						uniqueValues[sel.Content] = true
+						countUniqueValues++
+					}
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -404,6 +450,20 @@ func (table *Table) calcColSelect(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
+				if _, ok := uniqueValues[row.Cells[colIndex].Value.MSelect[0].Content]; !ok {
+					uniqueValues[row.Cells[colIndex].Value.MSelect[0].Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -467,6 +527,20 @@ func (table *Table) calcColDate(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[int64]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && row.Cells[colIndex].Value.Date.IsNotEmpty {
+				if _, ok := uniqueValues[row.Cells[colIndex].Value.Date.Content]; !ok {
+					countUniqueValues++
+					uniqueValues[row.Cells[colIndex].Value.Date.Content] = true
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	case CalcOperatorEarliest:
 	case CalcOperatorEarliest:
 		earliest := int64(0)
 		earliest := int64(0)
 		var isNotTime, hasEndDate bool
 		var isNotTime, hasEndDate bool
@@ -581,6 +655,20 @@ func (table *Table) calcColNumber(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[float64]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+				if !uniqueValues[row.Cells[colIndex].Value.Number.Content] {
+					uniqueValues[row.Cells[colIndex].Value.Number.Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	case CalcOperatorSum:
 	case CalcOperatorSum:
 		sum := 0.0
 		sum := 0.0
 		for _, row := range table.Rows {
 		for _, row := range table.Rows {
@@ -719,6 +807,20 @@ func (table *Table) calcColText(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
+				if !uniqueValues[row.Cells[colIndex].Value.Text.Content] {
+					uniqueValues[row.Cells[colIndex].Value.Text.Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -782,6 +884,20 @@ func (table *Table) calcColURL(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.URL && "" != row.Cells[colIndex].Value.URL.Content {
+				if !uniqueValues[row.Cells[colIndex].Value.URL.Content] {
+					uniqueValues[row.Cells[colIndex].Value.URL.Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -845,6 +961,20 @@ func (table *Table) calcColEmail(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Email && "" != row.Cells[colIndex].Value.Email.Content {
+				if !uniqueValues[row.Cells[colIndex].Value.Email.Content] {
+					uniqueValues[row.Cells[colIndex].Value.Email.Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -908,6 +1038,20 @@ func (table *Table) calcColPhone(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Phone && "" != row.Cells[colIndex].Value.Phone.Content {
+				if !uniqueValues[row.Cells[colIndex].Value.Phone.Content] {
+					uniqueValues[row.Cells[colIndex].Value.Phone.Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -971,6 +1115,20 @@ func (table *Table) calcColBlock(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Block && "" != row.Cells[colIndex].Value.Block.Content {
+				if !uniqueValues[row.Cells[colIndex].Value.Block.Content] {
+					uniqueValues[row.Cells[colIndex].Value.Block.Content] = true
+					countUniqueValues++
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -1034,6 +1192,20 @@ func (table *Table) calcColCreated(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[int64]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Created {
+				if _, ok := uniqueValues[row.Cells[colIndex].Value.Created.Content]; !ok {
+					countUniqueValues++
+					uniqueValues[row.Cells[colIndex].Value.Created.Content] = true
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	case CalcOperatorEarliest:
 	case CalcOperatorEarliest:
 		earliest := int64(0)
 		earliest := int64(0)
 		for _, row := range table.Rows {
 		for _, row := range table.Rows {
@@ -1137,6 +1309,20 @@ func (table *Table) calcColUpdated(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[int64]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty {
+				if _, ok := uniqueValues[row.Cells[colIndex].Value.Updated.Content]; !ok {
+					countUniqueValues++
+					uniqueValues[row.Cells[colIndex].Value.Updated.Content] = true
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	case CalcOperatorEarliest:
 	case CalcOperatorEarliest:
 		earliest := int64(0)
 		earliest := int64(0)
 		for _, row := range table.Rows {
 		for _, row := range table.Rows {
@@ -1285,6 +1471,22 @@ func (table *Table) calcColRelation(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Relation {
+				for _, id := range row.Cells[colIndex].Value.Relation.BlockIDs {
+					if !uniqueValues[id] {
+						uniqueValues[id] = true
+						countUniqueValues++
+					}
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	}
 	}
 }
 }
 
 
@@ -1350,6 +1552,22 @@ func (table *Table) calcColRollup(col *TableColumn, colIndex int) {
 		if 0 < len(table.Rows) {
 		if 0 < len(table.Rows) {
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, row := range table.Rows {
+			if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup {
+				for _, content := range row.Cells[colIndex].Value.Rollup.Contents {
+					if !uniqueValues[content.String(true)] {
+						uniqueValues[content.String(true)] = true
+						countUniqueValues++
+					}
+				}
+			}
+		}
+		if 0 < len(table.Rows) {
+			col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(table.Rows)), NumberFormatPercent)}
+		}
 	case CalcOperatorSum:
 	case CalcOperatorSum:
 		sum := 0.0
 		sum := 0.0
 		for _, row := range table.Rows {
 		for _, row := range table.Rows {

+ 12 - 0
kernel/av/value.go

@@ -730,6 +730,18 @@ func (r *ValueRollup) RenderContents(calc *RollupCalc, destKey *Key) {
 		if 0 < len(r.Contents) {
 		if 0 < len(r.Contents) {
 			r.Contents = []*Value{{Type: KeyTypeNumber, Number: NewFormattedValueNumber(float64(countNonEmpty)/float64(len(r.Contents)), NumberFormatPercent)}}
 			r.Contents = []*Value{{Type: KeyTypeNumber, Number: NewFormattedValueNumber(float64(countNonEmpty)/float64(len(r.Contents)), NumberFormatPercent)}}
 		}
 		}
+	case CalcOperatorPercentUniqueValues:
+		countUniqueValues := 0
+		uniqueValues := map[string]bool{}
+		for _, v := range r.Contents {
+			if _, ok := uniqueValues[v.String(true)]; !ok {
+				uniqueValues[v.String(true)] = true
+				countUniqueValues++
+			}
+		}
+		if 0 < len(r.Contents) {
+			r.Contents = []*Value{{Type: KeyTypeNumber, Number: NewFormattedValueNumber(float64(countUniqueValues)/float64(len(r.Contents)), NumberFormatPercent)}}
+		}
 	case CalcOperatorSum:
 	case CalcOperatorSum:
 		sum := 0.0
 		sum := 0.0
 		for _, v := range r.Contents {
 		for _, v := range r.Contents {

+ 52 - 1
kernel/model/search.go

@@ -625,6 +625,10 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 								n.TextMarkTextContent = escapedR.ReplaceAllString(n.TextMarkTextContent, util.EscapeHTML(replacement))
 								n.TextMarkTextContent = escapedR.ReplaceAllString(n.TextMarkTextContent, util.EscapeHTML(replacement))
 							}
 							}
 						}
 						}
+
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("a") {
 					} else if n.IsTextMarkType("a") {
 						if replaceTypes["aText"] {
 						if replaceTypes["aText"] {
 							if 0 == method {
 							if 0 == method {
@@ -636,6 +640,9 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 									n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement)
 									n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement)
 								}
 								}
 							}
 							}
+							if "" == n.TextMarkTextContent {
+								unlinks = append(unlinks, n)
+							}
 						}
 						}
 
 
 						if replaceTypes["aTitle"] {
 						if replaceTypes["aTitle"] {
@@ -661,61 +668,87 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 								}
 								}
 							}
 							}
 						}
 						}
-
 					} else if n.IsTextMarkType("em") {
 					} else if n.IsTextMarkType("em") {
 						if !replaceTypes["em"] {
 						if !replaceTypes["em"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "em")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "em")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("strong") {
 					} else if n.IsTextMarkType("strong") {
 						if !replaceTypes["strong"] {
 						if !replaceTypes["strong"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "strong")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "strong")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("kbd") {
 					} else if n.IsTextMarkType("kbd") {
 						if !replaceTypes["kbd"] {
 						if !replaceTypes["kbd"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "kbd")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "kbd")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("mark") {
 					} else if n.IsTextMarkType("mark") {
 						if !replaceTypes["mark"] {
 						if !replaceTypes["mark"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "mark")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "mark")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("s") {
 					} else if n.IsTextMarkType("s") {
 						if !replaceTypes["s"] {
 						if !replaceTypes["s"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "s")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "s")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("sub") {
 					} else if n.IsTextMarkType("sub") {
 						if !replaceTypes["sub"] {
 						if !replaceTypes["sub"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "sub")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "sub")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("sup") {
 					} else if n.IsTextMarkType("sup") {
 						if !replaceTypes["sup"] {
 						if !replaceTypes["sup"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "sup")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "sup")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("tag") {
 					} else if n.IsTextMarkType("tag") {
 						if !replaceTypes["tag"] {
 						if !replaceTypes["tag"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "tag")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "tag")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("u") {
 					} else if n.IsTextMarkType("u") {
 						if !replaceTypes["u"] {
 						if !replaceTypes["u"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "u")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "u")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("inline-math") {
 					} else if n.IsTextMarkType("inline-math") {
 						if !replaceTypes["inlineMath"] {
 						if !replaceTypes["inlineMath"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
@@ -730,6 +763,10 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 								n.TextMarkInlineMathContent = r.ReplaceAllString(n.TextMarkInlineMathContent, replacement)
 								n.TextMarkInlineMathContent = r.ReplaceAllString(n.TextMarkInlineMathContent, replacement)
 							}
 							}
 						}
 						}
+
+						if "" == n.TextMarkInlineMathContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("inline-memo") {
 					} else if n.IsTextMarkType("inline-memo") {
 						if !replaceTypes["inlineMemo"] {
 						if !replaceTypes["inlineMemo"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
@@ -744,6 +781,10 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 								n.TextMarkInlineMemoContent = r.ReplaceAllString(n.TextMarkInlineMemoContent, replacement)
 								n.TextMarkInlineMemoContent = r.ReplaceAllString(n.TextMarkInlineMemoContent, replacement)
 							}
 							}
 						}
 						}
+
+						if "" == n.TextMarkInlineMemoContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("text") {
 					} else if n.IsTextMarkType("text") {
 						// Search and replace fails in some cases https://github.com/siyuan-note/siyuan/issues/10016
 						// Search and replace fails in some cases https://github.com/siyuan-note/siyuan/issues/10016
 						if !replaceTypes["text"] {
 						if !replaceTypes["text"] {
@@ -751,6 +792,9 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 						}
 						}
 
 
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "text")
 						replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "text")
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("block-ref") {
 					} else if n.IsTextMarkType("block-ref") {
 						if !replaceTypes["blockRef"] {
 						if !replaceTypes["blockRef"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
@@ -767,6 +811,10 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 								n.TextMarkBlockRefSubtype = "s"
 								n.TextMarkBlockRefSubtype = "s"
 							}
 							}
 						}
 						}
+
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					} else if n.IsTextMarkType("file-annotation-ref") {
 					} else if n.IsTextMarkType("file-annotation-ref") {
 						if !replaceTypes["fileAnnotationRef"] {
 						if !replaceTypes["fileAnnotationRef"] {
 							return ast.WalkContinue
 							return ast.WalkContinue
@@ -781,6 +829,9 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
 								n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement)
 								n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement)
 							}
 							}
 						}
 						}
+						if "" == n.TextMarkTextContent {
+							unlinks = append(unlinks, n)
+						}
 					}
 					}
 				}
 				}
 				return ast.WalkContinue
 				return ast.WalkContinue