@rebasepro/app 0.14.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/LoginView/LoginView.d.ts +12 -5
- package/dist/hooks/data/collectionQuery.d.ts +6 -6
- package/dist/hooks/useTranslation.d.ts +1 -1
- package/dist/index.es.js +139 -31
- package/dist/index.es.js.map +1 -1
- package/package.json +8 -8
- package/src/components/Debug/UIReferenceView.tsx +8 -1
- package/src/components/Debug/UIStyleGuide.tsx +4 -1
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +5 -1
- package/src/components/LoginView/LoginView.tsx +111 -28
- package/src/components/common/useDataTableController.tsx +40 -18
- package/src/hooks/data/collectionQuery.ts +6 -6
- package/src/hooks/useTranslation.ts +6 -1
- package/src/locales/de.ts +10 -0
- package/src/locales/en.ts +10 -0
- package/src/locales/es.ts +10 -0
- package/src/locales/fr.ts +10 -0
- package/src/locales/hi.ts +10 -0
- package/src/locales/it.ts +10 -0
- package/src/locales/pt.ts +10 -0
|
@@ -105,11 +105,18 @@ export interface LoginViewProps {
|
|
|
105
105
|
*/
|
|
106
106
|
defaultPassword?: string;
|
|
107
107
|
/**
|
|
108
|
-
* When set,
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
108
|
+
* When set, a "Join the newsletter" opt-in checkbox is offered. Called with
|
|
109
|
+
* the address that just authenticated — by any method — if the box was
|
|
110
|
+
* ticked. Wire it to whatever stores the subscription; failures are the
|
|
111
|
+
* callback's problem, the login flow never waits on it.
|
|
112
|
+
*
|
|
113
|
+
* The checkbox sits on the **provider screen**, beside the buttons that
|
|
114
|
+
* choose how to sign in, rather than on the credentials form. It is a
|
|
115
|
+
* decision about the visitor, not about the password: asking on the
|
|
116
|
+
* credentials form put it in front of only the people who got that far by
|
|
117
|
+
* typing an address, so anyone signing in with Google was never offered it
|
|
118
|
+
* at all. Bootstrap mode is the exception — there is no provider screen
|
|
119
|
+
* there, so the form keeps it.
|
|
113
120
|
*/
|
|
114
121
|
onNewsletterOptIn?: (email: string) => void;
|
|
115
122
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FindParams, FilterValues, LogicalCondition } from "@rebasepro/types";
|
|
1
|
+
import type { FindParams, FilterValues, LogicalCondition, OrderBySpec } from "@rebasepro/types";
|
|
2
2
|
/**
|
|
3
3
|
* The one place a collection read's parameters are assembled.
|
|
4
4
|
*
|
|
@@ -23,12 +23,12 @@ export interface CollectionQueryInput<M extends Record<string, unknown>> {
|
|
|
23
23
|
logical?: LogicalCondition;
|
|
24
24
|
/**
|
|
25
25
|
* Loosened from `FindParams["orderBy"]` on purpose: callers hold the sort
|
|
26
|
-
* as
|
|
27
|
-
*
|
|
28
|
-
* every call site, which is the kind of friction that sends people back
|
|
29
|
-
* hand-building the object this exists to replace.
|
|
26
|
+
* as plain `[string, direction]` pairs read off user config or a URL, where
|
|
27
|
+
* the columns are not statically known. Narrowing it here would push a cast
|
|
28
|
+
* onto every call site, which is the kind of friction that sends people back
|
|
29
|
+
* to hand-building the object this exists to replace.
|
|
30
30
|
*/
|
|
31
|
-
orderBy?:
|
|
31
|
+
orderBy?: OrderBySpec;
|
|
32
32
|
limit?: number;
|
|
33
33
|
offset?: number;
|
|
34
34
|
page?: number;
|
package/dist/index.es.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ALL_WHERE_FILTER_OPS, DEFAULT_DATA_SOURCE_KEY, DEFAULT_FILTERABLE_RELAT
|
|
|
4
4
|
import { UNRENDERED_SLOTS, resolveAdminCollection } from "@rebasepro/admin-types";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
|
|
7
|
-
import { buildRebaseData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, evaluateCondition, getChildViewDeclaringProperties, getChildViewRelationPropertyKeys, getEntityChildViews, getLabelOrConfigFrom, getPrimaryKeys, getSubcollections, isPropertyBuilder, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, stripCollectionPath, wrapAsEntityData, wrapAsSdkData } from "@rebasepro/common";
|
|
7
|
+
import { buildRebaseData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, evaluateCondition, getChildViewDeclaringProperties, getChildViewRelationPropertyKeys, getEntityChildViews, getLabelOrConfigFrom, getPrimaryKeys, getSubcollections, isPropertyBuilder, normalizeOrderBy, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, serializeOrderBy, stripCollectionPath, wrapAsEntityData, wrapAsSdkData } from "@rebasepro/common";
|
|
8
8
|
import { getTitlePropertyKey as getTitlePropertyKey$1, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1 } from "@rebasepro/app";
|
|
9
9
|
import { Link, Routes, createBrowserRouter, useBlocker, useLocation } from "react-router";
|
|
10
10
|
import { generateForeignKeyName, hashString, isArrayValue, isObject, isPlainObject, isRecordValue, mergeDeep, randomString, readStoredJson, slugify, writeStoredJson } from "@rebasepro/utils";
|
|
@@ -2227,6 +2227,11 @@ function useTranslation() {
|
|
|
2227
2227
|
* Also supports i18next interpolation variables, e.g.
|
|
2228
2228
|
* t("add_to_field", { fieldName: "Tags" })
|
|
2229
2229
|
* t("error_deleting", { message: err.message })
|
|
2230
|
+
* t("sort_key_position", { position: 2 })
|
|
2231
|
+
*
|
|
2232
|
+
* Numbers are accepted as well as strings: i18next interpolates them
|
|
2233
|
+
* either way, and a signature that took only strings pushed a `String()`
|
|
2234
|
+
* onto every call site with a count in it — `{{count}}`, `{{position}}`.
|
|
2230
2235
|
*/
|
|
2231
2236
|
const typedT = useCallback((key, vars) => t(key, vars), [t]);
|
|
2232
2237
|
return useMemo(() => ({
|
|
@@ -2859,11 +2864,12 @@ function useDataTableController({ path, collection, scrollRestoration, entitiesD
|
|
|
2859
2864
|
return true;
|
|
2860
2865
|
}, []);
|
|
2861
2866
|
const sortInternal = useMemo(() => {
|
|
2862
|
-
|
|
2867
|
+
const keys = normalizeOrderBy(sort);
|
|
2868
|
+
if (keys && fixedFilter && !checkFilterCombination(fixedFilter, keys)) {
|
|
2863
2869
|
console.warn("Initial sort is not compatible with the force filter. Ignoring initial sort");
|
|
2864
2870
|
return;
|
|
2865
2871
|
}
|
|
2866
|
-
return
|
|
2872
|
+
return keys;
|
|
2867
2873
|
}, [sort, fixedFilter]);
|
|
2868
2874
|
const { filterValues: filterUrl, sortBy: sortUrl } = parseFilterAndSort(location.search);
|
|
2869
2875
|
const [filterValues, setFilterValues] = React.useState(fixedFilter ?? (updateUrl ? filterUrl : void 0) ?? defaultFilter ?? void 0);
|
|
@@ -2900,8 +2906,7 @@ function useDataTableController({ path, collection, scrollRestoration, entitiesD
|
|
|
2900
2906
|
});
|
|
2901
2907
|
}, []);
|
|
2902
2908
|
const [itemCount, setItemCount] = React.useState(paginationEnabled ? initialItemCount : void 0);
|
|
2903
|
-
const
|
|
2904
|
-
const currentSort = sortBy ? sortBy[1] : void 0;
|
|
2909
|
+
const sortKey = sortBy ? serializeOrderBy(sortBy) : void 0;
|
|
2905
2910
|
const context = useRebaseContext();
|
|
2906
2911
|
const [rawData, setRawData] = useState(collectionScroll?.data ?? []);
|
|
2907
2912
|
const onScroll = useCallback(({ scrollOffset }) => {
|
|
@@ -2965,7 +2970,7 @@ function useDataTableController({ path, collection, scrollRestoration, entitiesD
|
|
|
2965
2970
|
};
|
|
2966
2971
|
const accessor = dataClient.collection(path);
|
|
2967
2972
|
const whereParams = filterValues && Object.keys(filterValues).length > 0 ? filterValues : void 0;
|
|
2968
|
-
const orderByParams = sortBy ?
|
|
2973
|
+
const orderByParams = sortBy && sortBy.length > 0 ? sortBy.map(([field, direction]) => [String(field), direction]) : void 0;
|
|
2969
2974
|
let unsubscribe;
|
|
2970
2975
|
const includeParams = getRelationIncludeParams(collection);
|
|
2971
2976
|
if (accessor.listen) unsubscribe = accessor.listen(toFindParams({
|
|
@@ -2990,8 +2995,7 @@ function useDataTableController({ path, collection, scrollRestoration, entitiesD
|
|
|
2990
2995
|
dataClient,
|
|
2991
2996
|
path,
|
|
2992
2997
|
itemCount,
|
|
2993
|
-
|
|
2994
|
-
sortByProperty,
|
|
2998
|
+
sortKey,
|
|
2995
2999
|
filterValues,
|
|
2996
3000
|
searchString
|
|
2997
3001
|
]);
|
|
@@ -3067,9 +3071,9 @@ function useUpdateUrl(filterValues, sortBy, searchString, updateUrl) {
|
|
|
3067
3071
|
}
|
|
3068
3072
|
function encodeFilterAndSort(filterValues, sortBy) {
|
|
3069
3073
|
const entries = {};
|
|
3070
|
-
if (sortBy) {
|
|
3071
|
-
entries["__sort"] =
|
|
3072
|
-
entries["__sort_order"] =
|
|
3074
|
+
if (sortBy && sortBy.length > 0) {
|
|
3075
|
+
entries["__sort"] = sortBy.map(([field]) => encodeURIComponent(field)).join(",");
|
|
3076
|
+
entries["__sort_order"] = sortBy.map(([, direction]) => direction).join(",");
|
|
3073
3077
|
}
|
|
3074
3078
|
if (filterValues) Object.entries(filterValues).forEach(([key, value]) => {
|
|
3075
3079
|
if (value) {
|
|
@@ -3105,8 +3109,11 @@ function parseFilterAndSort(search) {
|
|
|
3105
3109
|
const filterValues = {};
|
|
3106
3110
|
let sortBy = void 0;
|
|
3107
3111
|
entries.forEach((value, key) => {
|
|
3108
|
-
if (key === "__sort")
|
|
3109
|
-
|
|
3112
|
+
if (key === "__sort") {
|
|
3113
|
+
const directions = (entries.get("__sort_order") ?? "").split(",");
|
|
3114
|
+
const keys = value.split(",").map((field) => decodeURIComponent(field).trim()).filter(Boolean).map((field, index) => [field, directions[index] === "desc" ? "desc" : "asc"]);
|
|
3115
|
+
sortBy = keys.length > 0 ? keys : void 0;
|
|
3116
|
+
} else if (key.endsWith("_op")) {
|
|
3110
3117
|
const field = key.replace("_op", "");
|
|
3111
3118
|
const filterOp = decodeURIComponent(value);
|
|
3112
3119
|
const filterValStr = entries.get(`${field}_value`);
|
|
@@ -3408,6 +3415,7 @@ var UIStyleGuide = () => {
|
|
|
3408
3415
|
children: variant
|
|
3409
3416
|
}), /* @__PURE__ */ jsxs(Typography, {
|
|
3410
3417
|
variant,
|
|
3418
|
+
component: "p",
|
|
3411
3419
|
children: [
|
|
3412
3420
|
"The quick brown fox jumps over the lazy dog (",
|
|
3413
3421
|
variant,
|
|
@@ -5899,10 +5907,10 @@ function CrmDashboardDemo() {
|
|
|
5899
5907
|
children: [
|
|
5900
5908
|
/* @__PURE__ */ jsxs("div", {
|
|
5901
5909
|
className: "flex flex-col sm:flex-row sm:items-end justify-between mb-6 gap-4",
|
|
5902
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */
|
|
5910
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
5903
5911
|
variant: "h4",
|
|
5904
5912
|
className: "tracking-tight",
|
|
5905
|
-
children:
|
|
5913
|
+
children: getGreeting()
|
|
5906
5914
|
}), /* @__PURE__ */ jsxs(Typography, {
|
|
5907
5915
|
variant: "body2",
|
|
5908
5916
|
color: "secondary",
|
|
@@ -7800,6 +7808,7 @@ function UIReferenceView() {
|
|
|
7800
7808
|
children: v
|
|
7801
7809
|
}), /* @__PURE__ */ jsx(Typography, {
|
|
7802
7810
|
variant: v,
|
|
7811
|
+
component: "p",
|
|
7803
7812
|
children: "The quick brown fox jumps over the lazy dog"
|
|
7804
7813
|
})]
|
|
7805
7814
|
}, v))
|
|
@@ -9529,6 +9538,21 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
|
|
|
9529
9538
|
const [fadeIn, setFadeIn] = useState(false);
|
|
9530
9539
|
const [viewVisible, setViewVisible] = useState(true);
|
|
9531
9540
|
const [newsletterOptIn, setNewsletterOptIn] = useState(false);
|
|
9541
|
+
const authControllerRef = useRef(authController);
|
|
9542
|
+
authControllerRef.current = authController;
|
|
9543
|
+
/**
|
|
9544
|
+
* Subscribe the address that just authenticated, if the visitor asked for
|
|
9545
|
+
* it. Every sign-in path calls this, which is the point: the checkbox sits
|
|
9546
|
+
* beside the provider buttons, so it has to mean the same thing whichever
|
|
9547
|
+
* one is pressed.
|
|
9548
|
+
*
|
|
9549
|
+
* Only ever from a resolution path — a ticked box on a *failed* attempt
|
|
9550
|
+
* must not subscribe an address nobody proved they control.
|
|
9551
|
+
*/
|
|
9552
|
+
const subscribeIfOptedIn = useCallback((email) => {
|
|
9553
|
+
if (!newsletterOptIn || !onNewsletterOptIn || !email) return;
|
|
9554
|
+
onNewsletterOptIn(email);
|
|
9555
|
+
}, [newsletterOptIn, onNewsletterOptIn]);
|
|
9532
9556
|
const switchMode = (newMode) => {
|
|
9533
9557
|
setViewVisible(false);
|
|
9534
9558
|
setTimeout(() => {
|
|
@@ -9657,9 +9681,10 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
|
|
|
9657
9681
|
bootstrapMode: true,
|
|
9658
9682
|
defaultEmail,
|
|
9659
9683
|
defaultPassword,
|
|
9660
|
-
|
|
9684
|
+
onSignedIn: subscribeIfOptedIn,
|
|
9661
9685
|
newsletterOptIn,
|
|
9662
|
-
setNewsletterOptIn
|
|
9686
|
+
setNewsletterOptIn,
|
|
9687
|
+
showNewsletterOptIn: Boolean(onNewsletterOptIn)
|
|
9663
9688
|
}), !isBootstrapMode && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9664
9689
|
mode === "buttons" && /* @__PURE__ */ jsxs("div", {
|
|
9665
9690
|
className: "w-full flex flex-col gap-3 mt-2",
|
|
@@ -9681,6 +9706,19 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
|
|
|
9681
9706
|
className: "w-full",
|
|
9682
9707
|
children: topComponent
|
|
9683
9708
|
}),
|
|
9709
|
+
onNewsletterOptIn && /* @__PURE__ */ jsxs("label", {
|
|
9710
|
+
className: cls("flex items-center gap-2 cursor-pointer", (topComponent || title || subtitle) && "-mt-3"),
|
|
9711
|
+
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
9712
|
+
checked: newsletterOptIn,
|
|
9713
|
+
onCheckedChange: (checked) => setNewsletterOptIn(checked === true),
|
|
9714
|
+
size: "small"
|
|
9715
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
9716
|
+
variant: "caption",
|
|
9717
|
+
color: "secondary",
|
|
9718
|
+
className: "select-none",
|
|
9719
|
+
children: t("join_newsletter")
|
|
9720
|
+
})]
|
|
9721
|
+
}),
|
|
9684
9722
|
/* @__PURE__ */ jsx(LoginButton, {
|
|
9685
9723
|
disabled,
|
|
9686
9724
|
text: "Sign in with email",
|
|
@@ -9690,7 +9728,8 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
|
|
|
9690
9728
|
hasGoogleLogin && googleClientId && /* @__PURE__ */ jsx(GoogleLoginButton, {
|
|
9691
9729
|
disabled,
|
|
9692
9730
|
googleClientId,
|
|
9693
|
-
authController
|
|
9731
|
+
authController,
|
|
9732
|
+
onSignedIn: () => subscribeIfOptedIn(authControllerRef.current.user?.email)
|
|
9694
9733
|
}),
|
|
9695
9734
|
hasGitHubLogin && githubClientId && /* @__PURE__ */ jsx(GitHubLoginButton, {
|
|
9696
9735
|
disabled,
|
|
@@ -9729,9 +9768,7 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
|
|
|
9729
9768
|
switchToRegister: showRegistration ? () => switchMode("register") : void 0,
|
|
9730
9769
|
defaultEmail,
|
|
9731
9770
|
defaultPassword,
|
|
9732
|
-
|
|
9733
|
-
newsletterOptIn,
|
|
9734
|
-
setNewsletterOptIn
|
|
9771
|
+
onSignedIn: subscribeIfOptedIn
|
|
9735
9772
|
}),
|
|
9736
9773
|
mode === "register" && /* @__PURE__ */ jsx(LoginForm, {
|
|
9737
9774
|
authController,
|
|
@@ -9743,9 +9780,7 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
|
|
|
9743
9780
|
switchToLogin: () => switchMode("login"),
|
|
9744
9781
|
defaultEmail,
|
|
9745
9782
|
defaultPassword,
|
|
9746
|
-
|
|
9747
|
-
newsletterOptIn,
|
|
9748
|
-
setNewsletterOptIn
|
|
9783
|
+
onSignedIn: subscribeIfOptedIn
|
|
9749
9784
|
}),
|
|
9750
9785
|
mode === "forgot" && authController.forgotPassword && /* @__PURE__ */ jsx(ForgotPasswordForm, {
|
|
9751
9786
|
authController,
|
|
@@ -9803,8 +9838,10 @@ var GoogleIcon = () => /* @__PURE__ */ jsxs("svg", {
|
|
|
9803
9838
|
})
|
|
9804
9839
|
]
|
|
9805
9840
|
});
|
|
9806
|
-
function GoogleLoginButton({ disabled, googleClientId, authController }) {
|
|
9841
|
+
function GoogleLoginButton({ disabled, googleClientId, authController, onSignedIn }) {
|
|
9807
9842
|
const codeClientRef = useRef(null);
|
|
9843
|
+
const onSignedInRef = useRef(onSignedIn);
|
|
9844
|
+
onSignedInRef.current = onSignedIn;
|
|
9808
9845
|
useEffect(() => {
|
|
9809
9846
|
if (!authController.googleLogin) return;
|
|
9810
9847
|
const google = window.google;
|
|
@@ -9823,6 +9860,7 @@ function GoogleLoginButton({ disabled, googleClientId, authController }) {
|
|
|
9823
9860
|
code: response.code,
|
|
9824
9861
|
redirectUri: "postmessage"
|
|
9825
9862
|
});
|
|
9863
|
+
onSignedInRef.current?.();
|
|
9826
9864
|
} catch (err) {
|
|
9827
9865
|
console.error("Google login error:", err);
|
|
9828
9866
|
}
|
|
@@ -9891,7 +9929,7 @@ function LinkedInLoginButton({ disabled, linkedinClientId }) {
|
|
|
9891
9929
|
onClick: handleClick
|
|
9892
9930
|
});
|
|
9893
9931
|
}
|
|
9894
|
-
function LoginForm({ onClose, onForgotPassword, authController, registrationMode, noUserComponent, disableSignupScreen, bootstrapMode = false, switchToRegister, switchToLogin, defaultEmail, defaultPassword,
|
|
9932
|
+
function LoginForm({ onClose, onForgotPassword, authController, registrationMode, noUserComponent, disableSignupScreen, bootstrapMode = false, switchToRegister, switchToLogin, defaultEmail, defaultPassword, onSignedIn, newsletterOptIn = false, setNewsletterOptIn, showNewsletterOptIn = false }) {
|
|
9895
9933
|
const passwordRef = useRef(null);
|
|
9896
9934
|
const { t } = useTranslation();
|
|
9897
9935
|
const emailId = useId();
|
|
@@ -9910,14 +9948,14 @@ function LoginForm({ onClose, onForgotPassword, authController, registrationMode
|
|
|
9910
9948
|
document.removeEventListener("keydown", escFunction, false);
|
|
9911
9949
|
};
|
|
9912
9950
|
}, [onClose]);
|
|
9913
|
-
function
|
|
9914
|
-
|
|
9951
|
+
function reportSignedIn(email) {
|
|
9952
|
+
onSignedIn?.(email);
|
|
9915
9953
|
}
|
|
9916
9954
|
function handleEnterPassword() {
|
|
9917
|
-
if (email && password && authController.emailPasswordLogin) Promise.resolve(authController.emailPasswordLogin(email, password)).then(() =>
|
|
9955
|
+
if (email && password && authController.emailPasswordLogin) Promise.resolve(authController.emailPasswordLogin(email, password)).then(() => reportSignedIn(email)).catch(() => void 0);
|
|
9918
9956
|
}
|
|
9919
9957
|
function handleRegistration() {
|
|
9920
|
-
if (email && password && authController.register) Promise.resolve(authController.register(email, password, displayName)).then(() =>
|
|
9958
|
+
if (email && password && authController.register) Promise.resolve(authController.register(email, password, displayName)).then(() => reportSignedIn(email)).catch(() => void 0);
|
|
9921
9959
|
}
|
|
9922
9960
|
const handleSubmit = (event) => {
|
|
9923
9961
|
event.preventDefault();
|
|
@@ -10038,7 +10076,7 @@ function LoginForm({ onClose, onForgotPassword, authController, registrationMode
|
|
|
10038
10076
|
children: "Forgot password?"
|
|
10039
10077
|
})
|
|
10040
10078
|
}),
|
|
10041
|
-
|
|
10079
|
+
showNewsletterOptIn && /* @__PURE__ */ jsxs("label", {
|
|
10042
10080
|
className: "flex items-center gap-2 cursor-pointer mt-1 mb-1",
|
|
10043
10081
|
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
10044
10082
|
checked: newsletterOptIn,
|
|
@@ -10300,6 +10338,16 @@ var en = {
|
|
|
10300
10338
|
clear_sort: "Clear sort",
|
|
10301
10339
|
sort: "Sort",
|
|
10302
10340
|
sort_by: "Sort by",
|
|
10341
|
+
sort_then_by: "Then by",
|
|
10342
|
+
sort_ascending: "Sort ascending",
|
|
10343
|
+
sort_descending: "Sort descending",
|
|
10344
|
+
sort_remove: "Remove sort",
|
|
10345
|
+
sort_move_up: "Make this key more important",
|
|
10346
|
+
sort_move_down: "Make this key less important",
|
|
10347
|
+
sort_remove_key: "Remove this sort key",
|
|
10348
|
+
sort_key_position: "Sort key {{position}}",
|
|
10349
|
+
sort_shift_click_hint: "Shift-click to add a column under the current sort",
|
|
10350
|
+
save_entity_before_subcollections: "You need to save your entity before adding additional collections",
|
|
10303
10351
|
clear_all: "Clear all",
|
|
10304
10352
|
no_items: "No items",
|
|
10305
10353
|
no_entries_found: "No entries found",
|
|
@@ -11202,6 +11250,16 @@ var es = {
|
|
|
11202
11250
|
clear_sort: "Borrar orden",
|
|
11203
11251
|
sort: "Ordenar",
|
|
11204
11252
|
sort_by: "Ordenar por",
|
|
11253
|
+
sort_then_by: "Luego por",
|
|
11254
|
+
sort_ascending: "Ordenar ascendente",
|
|
11255
|
+
sort_descending: "Ordenar descendente",
|
|
11256
|
+
sort_remove: "Quitar orden",
|
|
11257
|
+
sort_move_up: "Dar más importancia a esta clave",
|
|
11258
|
+
sort_move_down: "Dar menos importancia a esta clave",
|
|
11259
|
+
sort_remove_key: "Quitar esta clave de orden",
|
|
11260
|
+
sort_key_position: "Clave de orden {{position}}",
|
|
11261
|
+
sort_shift_click_hint: "Mayús+clic para añadir una columna bajo el orden actual",
|
|
11262
|
+
save_entity_before_subcollections: "Debes guardar la entidad antes de añadir colecciones adicionales",
|
|
11205
11263
|
clear_all: "Borrar todo",
|
|
11206
11264
|
no_items: "Sin elementos",
|
|
11207
11265
|
no_entries_found: "No se encontraron entradas",
|
|
@@ -12060,6 +12118,16 @@ var de = {
|
|
|
12060
12118
|
clear_sort: "Sortierung löschen",
|
|
12061
12119
|
sort: "Sortieren",
|
|
12062
12120
|
sort_by: "Sortieren nach",
|
|
12121
|
+
sort_then_by: "Dann nach",
|
|
12122
|
+
sort_ascending: "Aufsteigend sortieren",
|
|
12123
|
+
sort_descending: "Absteigend sortieren",
|
|
12124
|
+
sort_remove: "Sortierung entfernen",
|
|
12125
|
+
sort_move_up: "Diesen Schlüssel wichtiger machen",
|
|
12126
|
+
sort_move_down: "Diesen Schlüssel weniger wichtig machen",
|
|
12127
|
+
sort_remove_key: "Diesen Sortierschlüssel entfernen",
|
|
12128
|
+
sort_key_position: "Sortierschlüssel {{position}}",
|
|
12129
|
+
sort_shift_click_hint: "Umschalt+Klick, um eine Spalte unter der aktuellen Sortierung hinzuzufügen",
|
|
12130
|
+
save_entity_before_subcollections: "Du musst den Eintrag speichern, bevor du weitere Sammlungen hinzufügst",
|
|
12063
12131
|
clear_all: "Alle löschen",
|
|
12064
12132
|
no_items: "Keine Elemente",
|
|
12065
12133
|
no_entries_found: "Keine Einträge gefunden",
|
|
@@ -12908,6 +12976,16 @@ var fr = {
|
|
|
12908
12976
|
clear_sort: "Effacer le tri",
|
|
12909
12977
|
sort: "Trier",
|
|
12910
12978
|
sort_by: "Trier par",
|
|
12979
|
+
sort_then_by: "Puis par",
|
|
12980
|
+
sort_ascending: "Trier par ordre croissant",
|
|
12981
|
+
sort_descending: "Trier par ordre décroissant",
|
|
12982
|
+
sort_remove: "Supprimer le tri",
|
|
12983
|
+
sort_move_up: "Rendre cette clé plus importante",
|
|
12984
|
+
sort_move_down: "Rendre cette clé moins importante",
|
|
12985
|
+
sort_remove_key: "Supprimer cette clé de tri",
|
|
12986
|
+
sort_key_position: "Clé de tri {{position}}",
|
|
12987
|
+
sort_shift_click_hint: "Maj+clic pour ajouter une colonne sous le tri actuel",
|
|
12988
|
+
save_entity_before_subcollections: "Vous devez enregistrer l'entité avant d'ajouter des collections supplémentaires",
|
|
12911
12989
|
clear_all: "Tout effacer",
|
|
12912
12990
|
no_items: "Aucun élément",
|
|
12913
12991
|
no_entries_found: "Aucune entrée trouvée",
|
|
@@ -13756,6 +13834,16 @@ var it = {
|
|
|
13756
13834
|
clear_sort: "Cancella ordinamento",
|
|
13757
13835
|
sort: "Ordina",
|
|
13758
13836
|
sort_by: "Ordina per",
|
|
13837
|
+
sort_then_by: "Poi per",
|
|
13838
|
+
sort_ascending: "Ordina in modo crescente",
|
|
13839
|
+
sort_descending: "Ordina in modo decrescente",
|
|
13840
|
+
sort_remove: "Rimuovi ordinamento",
|
|
13841
|
+
sort_move_up: "Rendi questa chiave più importante",
|
|
13842
|
+
sort_move_down: "Rendi questa chiave meno importante",
|
|
13843
|
+
sort_remove_key: "Rimuovi questa chiave di ordinamento",
|
|
13844
|
+
sort_key_position: "Chiave di ordinamento {{position}}",
|
|
13845
|
+
sort_shift_click_hint: "Maiusc+clic per aggiungere una colonna sotto l'ordinamento attuale",
|
|
13846
|
+
save_entity_before_subcollections: "Devi salvare l'entità prima di aggiungere altre raccolte",
|
|
13759
13847
|
clear_all: "Cancella tutto",
|
|
13760
13848
|
no_items: "Nessun elemento",
|
|
13761
13849
|
no_entries_found: "Nessuna voce trovata",
|
|
@@ -14604,6 +14692,16 @@ var hi = {
|
|
|
14604
14692
|
clear_sort: "सॉर्ट साफ़ करें",
|
|
14605
14693
|
sort: "क्रमबद्ध करें",
|
|
14606
14694
|
sort_by: "इसके अनुसार क्रमबद्ध करें",
|
|
14695
|
+
sort_then_by: "फिर इसके अनुसार",
|
|
14696
|
+
sort_ascending: "आरोही क्रम में लगाएँ",
|
|
14697
|
+
sort_descending: "अवरोही क्रम में लगाएँ",
|
|
14698
|
+
sort_remove: "क्रम हटाएँ",
|
|
14699
|
+
sort_move_up: "इस कुंजी को अधिक महत्व दें",
|
|
14700
|
+
sort_move_down: "इस कुंजी को कम महत्व दें",
|
|
14701
|
+
sort_remove_key: "यह क्रम कुंजी हटाएँ",
|
|
14702
|
+
sort_key_position: "क्रम कुंजी {{position}}",
|
|
14703
|
+
sort_shift_click_hint: "वर्तमान क्रम के नीचे कॉलम जोड़ने के लिए Shift+क्लिक करें",
|
|
14704
|
+
save_entity_before_subcollections: "अतिरिक्त संग्रह जोड़ने से पहले आपको एंटिटी सहेजनी होगी",
|
|
14607
14705
|
clear_all: "सभी साफ़ करें",
|
|
14608
14706
|
no_items: "कोई आइटम नहीं",
|
|
14609
14707
|
no_entries_found: "कोई प्रविष्टि नहीं मिली",
|
|
@@ -15457,6 +15555,16 @@ var pt = {
|
|
|
15457
15555
|
clear_sort: "Limpar ordenação",
|
|
15458
15556
|
sort: "Ordenar",
|
|
15459
15557
|
sort_by: "Ordenar por",
|
|
15558
|
+
sort_then_by: "Depois por",
|
|
15559
|
+
sort_ascending: "Ordenar ascendente",
|
|
15560
|
+
sort_descending: "Ordenar descendente",
|
|
15561
|
+
sort_remove: "Remover ordenação",
|
|
15562
|
+
sort_move_up: "Tornar esta chave mais importante",
|
|
15563
|
+
sort_move_down: "Tornar esta chave menos importante",
|
|
15564
|
+
sort_remove_key: "Remover esta chave de ordenação",
|
|
15565
|
+
sort_key_position: "Chave de ordenação {{position}}",
|
|
15566
|
+
sort_shift_click_hint: "Shift+clique para adicionar uma coluna sob a ordenação atual",
|
|
15567
|
+
save_entity_before_subcollections: "Precisas de guardar a entidade antes de adicionar coleções adicionais",
|
|
15460
15568
|
clear_all: "Limpar tudo",
|
|
15461
15569
|
no_items: "Sem itens",
|
|
15462
15570
|
no_entries_found: "Nenhum registo encontrado",
|