diff --git a/app/appearance/langs/de_DE.json b/app/appearance/langs/de_DE.json index 2d5ad6ae2..bfed92ab8 100644 --- a/app/appearance/langs/de_DE.json +++ b/app/appearance/langs/de_DE.json @@ -232,19 +232,20 @@ "builtIn": "Integriert", "endDate": "Enddatum", "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", - "calcResultAverage": "DURCHSCHNITT", - "calcResultMedian": "MEDIAN", - "calcResultMin": "MIN", - "calcResultMax": "MAX", - "calcResultRange": "BEREICH", + "calcResultAverage": "Durchschnitt", + "calcResultMedian": "Median", + "calcResultMin": "Min", + "calcResultMax": "Max", + "calcResultRange": "Bereich", "calc": "Berechnen", "createWorkspace": "Arbeitsbereich 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", "calcOperatorPercentEmpty": "Prozent leer", "calcOperatorPercentNotEmpty": "Prozent nicht leer", + "calcOperatorPercentUniqueValues": "Prozent einzigartige Werte", "calcOperatorSum": "Summe", "calcOperatorAverage": "Durchschnitt", "calcOperatorMedian": "Median", diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index bc62c35ca..a134824cc 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -232,19 +232,20 @@ "builtIn": "Built-in", "endDate": "End date", "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", "createWorkspace": "Create Workspace", "createWorkspaceTip": "Are you sure to use this path to create a workspace?", @@ -256,6 +257,7 @@ "calcOperatorCountNotEmpty": "Count not empty", "calcOperatorPercentEmpty": "Percent empty", "calcOperatorPercentNotEmpty": "Percent not empty", + "calcOperatorPercentUniqueValues": "Percent unique values", "calcOperatorSum": "Sum", "calcOperatorAverage": "Average", "calcOperatorMedian": "Median", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index e363199a2..c65770957 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -232,19 +232,20 @@ "builtIn": "Incorporado", "endDate": "Fecha de finalización", "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", - "calcResultAverage": "PROMEDIO", - "calcResultMedian": "MEDIANA", - "calcResultMin": "MIN", - "calcResultMax": "MAX", - "calcResultRange": "RANGO", + "calcResultAverage": "Promedio", + "calcResultMedian": "Mediana", + "calcResultMin": "Min", + "calcResultMax": "Máx", + "calcResultRange": "Rango", "calc": "Calcular", "createWorkspace": "Crear 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", "calcOperatorPercentEmpty": "Porcentaje vacío", "calcOperatorPercentNotEmpty": "Porcentaje no vacío", + "calcOperatorPercentUniqueValues": "Porcentaje de valores únicos", "calcOperatorSum": "Suma", "calcOperatorAverage": "Promedio", "calcOperatorMedian": "Mediana", "calcOperatorMin": "Min", - "calcOperatorMax": "Máx.", + "calcOperatorMax": "Máx", "calcOperatorRange": "Rango", "calcOperatorEarliest": "Primero", "calcOperatorLatest": "Último", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index 547f15f3a..79ac2c406 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -232,19 +232,20 @@ "builtIn": "Intégré", "endDate": "Date de fin", "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", - "calcResultAverage": "MOYENNE", - "calcResultMedian": "MÉDIANE", - "calcResultMin": "MIN", - "calcResultMax": "MAX", - "calcResultRange": "PLAGE", + "calcResultAverage": "Moyenne", + "calcResultMedian": "Médiane", + "calcResultMin": "Min", + "calcResultMax": "Max", + "calcResultRange": "Plage", "calc": "Calculer", "createWorkspace": "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", "calcOperatorPercentEmpty": "Pourcentage vide", "calcOperatorPercentNotEmpty": "Pourcentage non vide", + "calcOperatorPercentUniqueValues": "Pourcentage de valeurs uniques", "calcOperatorSum": "Somme", "calcOperatorAverage": "Moyenne", "calcOperatorMedian": "Médiane", diff --git a/app/appearance/langs/he_IL.json b/app/appearance/langs/he_IL.json index 334715c7f..dee1aaef1 100644 --- a/app/appearance/langs/he_IL.json +++ b/app/appearance/langs/he_IL.json @@ -232,13 +232,14 @@ "builtIn": "מותקן", "endDate": "תאריך סיום", "needLogin": "פונקציה זו דורשת כניסה", - "calcResultCountAll": "מנה", - "calcResultCountValues": "ערכים", - "calcResultCountUniqueValues": "ערכים ייחודיים", - "calcResultCountEmpty": "ריק", - "calcResultCountNotEmpty": "לא ריק", - "calcResultPercentEmpty": "ריק", - "calcResultPercentNotEmpty": "לא ריק", + "calcResultCountAll": "ספור הכל", + "calcResultCountValues": "ספור ערכים", + "calcResultCountUniqueValues": "ספור ערכים ייחודיים", + "calcResultCountEmpty": "ספור ריקים", + "calcResultCountNotEmpty": "ספור לא ריקים", + "calcResultPercentEmpty": "אחוז ריקים", + "calcResultPercentNotEmpty": "אחוז לא ריקים", + "calcResultPercentUniqueValues": "אחוז ערכים ייחודיים", "calcResultSum": "סכום", "calcResultAverage": "ממוצע", "calcResultMedian": "חציון", @@ -256,6 +257,7 @@ "calcOperatorCountNotEmpty": "ספור לא ריקים", "calcOperatorPercentEmpty": "אחוז ריקים", "calcOperatorPercentNotEmpty": "אחוז לא ריקים", + "calcOperatorPercentUniqueValues": "אחוז ערכים ייחודיים", "calcOperatorSum": "סכום", "calcOperatorAverage": "ממוצע", "calcOperatorMedian": "חציון", diff --git a/app/appearance/langs/it_IT.json b/app/appearance/langs/it_IT.json index 75d491886..0a043470f 100644 --- a/app/appearance/langs/it_IT.json +++ b/app/appearance/langs/it_IT.json @@ -232,19 +232,20 @@ "builtIn": "Integrato", "endDate": "Data di fine", "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", "createWorkspace": "Crea 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", "calcOperatorPercentEmpty": "Percentuale vuoti", "calcOperatorPercentNotEmpty": "Percentuale non vuoti", + "calcOperatorPercentUniqueValues": "Percentuale di valori unici", "calcOperatorSum": "Somma", "calcOperatorAverage": "Media", "calcOperatorMedian": "Mediana", diff --git a/app/appearance/langs/ja_JP.json b/app/appearance/langs/ja_JP.json index 8b545572b..47800fbd4 100644 --- a/app/appearance/langs/ja_JP.json +++ b/app/appearance/langs/ja_JP.json @@ -239,6 +239,7 @@ "calcResultCountNotEmpty": "空ではない数", "calcResultPercentEmpty": "空のパーセント", "calcResultPercentNotEmpty": "空ではないパーセント", + "calcResultPercentUniqueValues": "ユニーク値の割合", "calcResultSum": "合計値", "calcResultAverage": "平均値", "calcResultMedian": "中央値", @@ -256,6 +257,7 @@ "calcOperatorCountNotEmpty": "空ではない数", "calcOperatorPercentEmpty": "空のパーセント", "calcOperatorPercentNotEmpty": "空ではないパーセント", + "calcOperatorPercentUniqueValues": "ユニーク値の割合", "calcOperatorSum": "合計値", "calcOperatorAverage": "平均値", "calcOperatorMedian": "中央値", diff --git a/app/appearance/langs/pl_PL.json b/app/appearance/langs/pl_PL.json index cfabd3dcd..f43fc9349 100644 --- a/app/appearance/langs/pl_PL.json +++ b/app/appearance/langs/pl_PL.json @@ -232,19 +232,20 @@ "builtIn": "Wbudowane", "endDate": "Data zakończenia", "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", "createWorkspace": "Utwórz obszar roboczy", "createWorkspaceTip": "Czy na pewno chcesz użyć tej ścieżki do utworzenia obszaru roboczego?", @@ -256,6 +257,7 @@ "calcOperatorCountNotEmpty": "Zlicz niepuste", "calcOperatorPercentEmpty": "Procent pustych", "calcOperatorPercentNotEmpty": "Procent niepustych", + "calcOperatorPercentUniqueValues": "Procent unikalnych wartości", "calcOperatorSum": "Suma", "calcOperatorAverage": "Średnia", "calcOperatorMedian": "Mediana", diff --git a/app/appearance/langs/ru_RU.json b/app/appearance/langs/ru_RU.json index 64d2b29f3..a7e954e9d 100644 --- a/app/appearance/langs/ru_RU.json +++ b/app/appearance/langs/ru_RU.json @@ -232,19 +232,20 @@ "builtIn": "Встроенный", "endDate": "Дата окончания", "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": "Вычислить", "createWorkspace": "Создать рабочее пространство", "createWorkspaceTip": "Вы уверены, что хотите использовать этот путь для создания рабочего пространства?", @@ -256,11 +257,12 @@ "calcOperatorCountNotEmpty": "Подсчитать непустые", "calcOperatorPercentEmpty": "Процент пустых", "calcOperatorPercentNotEmpty": "Процент не пустых", + "calcOperatorPercentUniqueValues": "Процент уникальных значений", "calcOperatorSum": "Сумма", "calcOperatorAverage": "Среднее", "calcOperatorMedian": "Медиана", - "calcOperatorMin": "Мин.", - "calcOperatorMax": "Макс.", + "calcOperatorMin": "Мин", + "calcOperatorMax": "Макс", "calcOperatorRange": "Диапазон", "calcOperatorEarliest": "Самый ранний", "calcOperatorLatest": "Самый поздний", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index e39dadf54..4c3910f5d 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -239,6 +239,7 @@ "calcResultCountNotEmpty": "已填寫", "calcResultPercentEmpty": "未填寫佔比", "calcResultPercentNotEmpty": "已填寫佔比", + "calcResultPercentUniqueValues": "唯一值佔比", "calcResultSum": "求和", "calcResultAverage": "平均值", "calcResultMedian": "中位數", @@ -256,6 +257,7 @@ "calcOperatorCountNotEmpty": "已填寫", "calcOperatorPercentEmpty": "未填寫佔比", "calcOperatorPercentNotEmpty": "已填寫佔比", + "calcOperatorPercentUniqueValues": "唯一值佔比", "calcOperatorSum": "求和", "calcOperatorAverage": "平均值", "calcOperatorMedian": "中位數", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 9a060b97f..da133b4dc 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -239,6 +239,7 @@ "calcResultCountNotEmpty": "已填写", "calcResultPercentEmpty": "未填写占比", "calcResultPercentNotEmpty": "已填写占比", + "calcResultPercentUniqueValues": "唯一值占比", "calcResultSum": "求和", "calcResultAverage": "平均值", "calcResultMedian": "中位数", @@ -256,6 +257,7 @@ "calcOperatorCountNotEmpty": "已填写", "calcOperatorPercentEmpty": "未填写占比", "calcOperatorPercentNotEmpty": "已填写占比", + "calcOperatorPercentUniqueValues": "唯一值占比", "calcOperatorSum": "求和", "calcOperatorAverage": "平均值", "calcOperatorMedian": "中位数", diff --git a/app/src/protyle/render/av/calc.ts b/app/src/protyle/render/av/calc.ts index 4d570675a..cdb2b3ef1 100644 --- a/app/src/protyle/render/av/calc.ts +++ b/app/src/protyle/render/av/calc.ts @@ -208,6 +208,17 @@ export const openCalcMenu = async (protyle: IProtyle, calcElement: HTMLElement, blockID, target: calcElement }); + calcItem({ + menu, + protyle, + colId, + avId, + oldOperator, + operator: "Percent unique values", + data: panelData?.data, + blockID, + target: calcElement + }); } else { calcItem({ menu, @@ -427,6 +438,9 @@ export const getCalcValue = (column: IAVColumn) => { case "Percent not empty": value = `${resultCalc.formattedContent}${window.siyuan.languages.calcResultPercentNotEmpty}`; break; + case "Percent unique values": + value = `${resultCalc.formattedContent}${window.siyuan.languages.calcResultPercentUniqueValues}`; + break; case "Sum": value = `${resultCalc.formattedContent}${window.siyuan.languages.calcResultSum}`; break; @@ -486,6 +500,8 @@ export const getNameByOperator = (operator: string, isRollup: boolean) => { return window.siyuan.languages.calcOperatorPercentEmpty; case "Percent not empty": return window.siyuan.languages.calcOperatorPercentNotEmpty; + case "Percent unique values": + return window.siyuan.languages.calcOperatorPercentUniqueValues; case "Checked": return window.siyuan.languages.checked; case "Unchecked": diff --git a/kernel/av/calc.go b/kernel/av/calc.go index 4260fcdda..b312cae11 100644 --- a/kernel/av/calc.go +++ b/kernel/av/calc.go @@ -28,24 +28,25 @@ type ColumnCalc struct { type CalcOperator string 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" ) diff --git a/kernel/av/table_calc.go b/kernel/av/table_calc.go index acd5ec68e..52fb18cc0 100644 --- a/kernel/av/table_calc.go +++ b/kernel/av/table_calc.go @@ -130,6 +130,20 @@ func (table *Table) calcColTemplate(col *TableColumn, colIndex int) { if 0 < len(table.Rows) { 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: sum := 0.0 for _, row := range table.Rows { @@ -276,6 +290,22 @@ func (table *Table) calcColMAsset(col *TableColumn, colIndex int) { if 0 < len(table.Rows) { 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) { 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) { 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) { 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: earliest := int64(0) var isNotTime, hasEndDate bool @@ -581,6 +655,20 @@ func (table *Table) calcColNumber(col *TableColumn, colIndex int) { if 0 < len(table.Rows) { 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: sum := 0.0 for _, row := range table.Rows { @@ -719,6 +807,20 @@ func (table *Table) calcColText(col *TableColumn, colIndex int) { if 0 < len(table.Rows) { 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) { 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) { 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) { 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) { 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) { 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: earliest := int64(0) for _, row := range table.Rows { @@ -1137,6 +1309,20 @@ func (table *Table) calcColUpdated(col *TableColumn, colIndex int) { if 0 < len(table.Rows) { 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: earliest := int64(0) for _, row := range table.Rows { @@ -1285,6 +1471,22 @@ func (table *Table) calcColRelation(col *TableColumn, colIndex int) { if 0 < len(table.Rows) { 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) { 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: sum := 0.0 for _, row := range table.Rows { diff --git a/kernel/av/value.go b/kernel/av/value.go index bc00f0ee8..b50d133ef 100644 --- a/kernel/av/value.go +++ b/kernel/av/value.go @@ -730,6 +730,18 @@ func (r *ValueRollup) RenderContents(calc *RollupCalc, destKey *Key) { if 0 < len(r.Contents) { 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: sum := 0.0 for _, v := range r.Contents { diff --git a/kernel/model/search.go b/kernel/model/search.go index 6a130cc21..b00088f52 100644 --- a/kernel/model/search.go +++ b/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)) } } + + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("a") { if replaceTypes["aText"] { if 0 == method { @@ -636,6 +640,9 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement) } } + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } if replaceTypes["aTitle"] { @@ -661,61 +668,87 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids } } } - } else if n.IsTextMarkType("em") { if !replaceTypes["em"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "em") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("strong") { if !replaceTypes["strong"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "strong") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("kbd") { if !replaceTypes["kbd"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "kbd") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("mark") { if !replaceTypes["mark"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "mark") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("s") { if !replaceTypes["s"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "s") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("sub") { if !replaceTypes["sub"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "sub") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("sup") { if !replaceTypes["sup"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "sup") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("tag") { if !replaceTypes["tag"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "tag") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("u") { if !replaceTypes["u"] { return ast.WalkContinue } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "u") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("inline-math") { if !replaceTypes["inlineMath"] { return ast.WalkContinue @@ -730,6 +763,10 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids n.TextMarkInlineMathContent = r.ReplaceAllString(n.TextMarkInlineMathContent, replacement) } } + + if "" == n.TextMarkInlineMathContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("inline-memo") { if !replaceTypes["inlineMemo"] { return ast.WalkContinue @@ -744,6 +781,10 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids n.TextMarkInlineMemoContent = r.ReplaceAllString(n.TextMarkInlineMemoContent, replacement) } } + + if "" == n.TextMarkInlineMemoContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("text") { // Search and replace fails in some cases https://github.com/siyuan-note/siyuan/issues/10016 if !replaceTypes["text"] { @@ -751,6 +792,9 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids } replaceNodeTextMarkTextContent(n, method, keyword, replacement, r, "text") + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("block-ref") { if !replaceTypes["blockRef"] { return ast.WalkContinue @@ -767,6 +811,10 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids n.TextMarkBlockRefSubtype = "s" } } + + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } else if n.IsTextMarkType("file-annotation-ref") { if !replaceTypes["fileAnnotationRef"] { return ast.WalkContinue @@ -781,6 +829,9 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement) } } + if "" == n.TextMarkTextContent { + unlinks = append(unlinks, n) + } } } return ast.WalkContinue