diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f9e1a11fb2..99a022451d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,14 +41,15 @@ jobs: - run: npm test - run: reaction test - - save_cache: name: Saving Meteor dev_bundle to cache key: dev_bundle paths: - /home/reaction/.meteor/local - - run: .circleci/build.sh + - run: + command: .circleci/build.sh + no_output_timeout: 30m # deploy the build (if on a deployment branch) - deploy: diff --git a/.meteor/packages b/.meteor/packages index 213943a56bc..22c3926c661 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -11,13 +11,13 @@ meteor-base@1.1.0 # Packages every Meteor app needs to have mobile-experience@1.0.4 # Packages for a great mobile UX blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers. -ecmascript@0.8.0 # Enable ECMAScript2015+ syntax in app code +ecmascript@0.8.1 # Enable ECMAScript2015+ syntax in app code audit-argument-checks@1.0.7 # ensure meteor method argument validation browser-policy@1.1.0 # security-related policies enforced by newer browsers juliancwirko:postcss # CSS post-processing plugin (replaces standard-minifier-css) session@1.1.7 # ReactiveDict whose contents are preserved across Hot Code Push tracker@1.1.3 # Meteor transparent reactive programming library -mongo@1.1.18 +mongo@1.1.19 random@1.0.10 reactive-var@1.0.11 reactive-dict@1.1.9 @@ -31,13 +31,13 @@ ejson@1.0.13 less@2.7.9 service-configuration@1.0.11 mdg:validated-method -shell-server@0.2.3 -dynamic-import +shell-server@0.2.4 +dynamic-import@0.1.1 # Meteor Auth Packages -accounts-base@1.3.0 -accounts-password@1.3.6 -accounts-facebook@1.2.0 +accounts-base@1.3.1 +accounts-password@1.4.0 +accounts-facebook@1.2.1 accounts-google@1.2.0 accounts-twitter@1.3.0 oauth-encryption@1.2.1 @@ -93,3 +93,4 @@ johanbrook:publication-collector # Custom Packages abernix:standard-minifier-js@2.1.0-beta.0 +bozhao:accounts-instagram diff --git a/.meteor/release b/.meteor/release index 025f64e7073..1e7fc5b564c 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.5 +METEOR@1.5.1 diff --git a/.meteor/versions b/.meteor/versions index 671a440f2dd..4fa112a0a02 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,10 +1,10 @@ -abernix:minifier-js@2.1.0-beta.0 -abernix:standard-minifier-js@2.1.0-beta.0 -accounts-base@1.3.0 -accounts-facebook@1.2.0 +abernix:minifier-js@2.1.0 +abernix:standard-minifier-js@2.1.0 +accounts-base@1.3.1 +accounts-facebook@1.2.1 accounts-google@1.2.0 accounts-oauth@1.1.15 -accounts-password@1.3.7 +accounts-password@1.4.0 accounts-twitter@1.3.0 alanning:roles@1.2.16 aldeed:autoform@5.8.1 @@ -15,17 +15,18 @@ aldeed:schema-deny@1.1.0 aldeed:schema-index@1.1.1 aldeed:simple-schema@1.5.3 aldeed:template-extension@4.1.0 -allow-deny@1.0.5 +allow-deny@1.0.6 audit-argument-checks@1.0.7 autoupdate@1.3.12 -babel-compiler@6.19.3 +babel-compiler@6.19.4 babel-runtime@1.0.1 base64@1.0.10 binary-heap@1.0.10 blaze@2.3.2 blaze-html-templates@1.1.2 blaze-tools@1.0.10 -boilerplate-generator@1.1.0 +boilerplate-generator@1.1.1 +bozhao:accounts-instagram@0.2.2 browser-policy@1.1.0 browser-policy-common@1.0.11 browser-policy-content@1.1.0 @@ -56,25 +57,25 @@ cfs:worker@0.1.4 check@1.2.5 coffeescript@1.12.6_1 dburles:factory@1.1.0 -ddp@1.2.5 -ddp-client@1.3.4 -ddp-common@1.2.8 +ddp@1.3.0 +ddp-client@2.0.0 +ddp-common@1.2.9 ddp-rate-limiter@1.0.7 -ddp-server@1.3.14 +ddp-server@2.0.0 deps@1.0.12 diff-sequence@1.0.7 dispatch:mocha@0.4.1 dispatch:run-as-user@1.1.1 dynamic-import@0.1.1 -ecmascript@0.8.1 +ecmascript@0.8.2 ecmascript-runtime@0.4.1 -ecmascript-runtime-client@0.4.2 +ecmascript-runtime-client@0.4.3 ecmascript-runtime-server@0.4.1 ejson@1.0.13 email@1.2.3 es5-shim@4.6.15 facebook-config-ui@1.0.0 -facebook-oauth@1.3.1 +facebook-oauth@1.3.2 fastclick@1.0.13 gadicc:blaze-react-component@1.4.0 geojson-utils@1.0.10 @@ -85,7 +86,7 @@ html-tools@1.0.11 htmljs@1.0.11 http@1.2.12 id-map@1.0.9 -johanbrook:publication-collector@1.0.8 +johanbrook:publication-collector@1.0.9 jparker:crypto-core@0.1.0 jparker:crypto-md5@0.1.1 jparker:gravatar@0.5.1 @@ -97,12 +98,12 @@ kadira:dochead@1.5.0 launch-screen@1.1.1 less@2.7.9 livedata@1.0.18 -localstorage@1.1.0 +localstorage@1.1.1 logging@1.1.17 matb33:collection-hooks@0.8.4 mdg:validated-method@1.1.0 mdg:validation-error@0.5.1 -meteor@1.6.1 +meteor@1.7.0 meteor-base@1.1.0 meteorhacks:ssr@2.2.0 meteorhacks:subs-manager@1.6.4 @@ -113,7 +114,7 @@ mobile-status-bar@1.0.14 modules@0.9.2 modules-runtime@0.8.0 momentjs:moment@2.18.1 -mongo@1.1.18 +mongo@1.1.19 mongo-id@1.0.6 mongo-livedata@1.0.12 mrt:later@1.6.1 @@ -145,7 +146,7 @@ routepolicy@1.0.12 service-configuration@1.0.11 session@1.1.7 sha@1.0.9 -shell-server@0.2.3 +shell-server@0.2.4 spacebars@1.0.15 spacebars-compiler@1.1.2 srp@1.0.10 @@ -162,5 +163,5 @@ ui@1.0.13 underscore@1.0.10 url@1.1.0 vsivsi:job-collection@1.4.0 -webapp@1.3.16 +webapp@1.3.17 webapp-hashing@1.0.9 diff --git a/client/modules/accounts/components/auth/loginButtons.js b/client/modules/accounts/components/auth/loginButtons.js index a349cb76b67..f7800833f7d 100644 --- a/client/modules/accounts/components/auth/loginButtons.js +++ b/client/modules/accounts/components/auth/loginButtons.js @@ -33,6 +33,7 @@ class LoginButtons extends Component { {this.props.currentView === "loginFormSignInView" && +   } diff --git a/client/modules/accounts/containers/dropdown/mainDropdownContainer.js b/client/modules/accounts/containers/dropdown/mainDropdownContainer.js index 258f0e79837..2865225a81c 100644 --- a/client/modules/accounts/containers/dropdown/mainDropdownContainer.js +++ b/client/modules/accounts/containers/dropdown/mainDropdownContainer.js @@ -70,18 +70,21 @@ class MainDropdownContainer extends Component { function getCurrentUser() { const shopId = Reaction.getShopId(); - const user = Accounts.user(); + const user = Accounts.user() || {}; if (!shopId || typeof user !== "object") { return null; } + // shoppers should always be guests const isGuest = Roles.userIsInRole(user, "guest", shopId); // but if a user has never logged in then they are anonymous const isAnonymous = Roles.userIsInRole(user, "anonymous", shopId); - return isGuest && !isAnonymous ? user : null; + const account = Collections.Accounts.findOne(user._id); + + return isGuest && !isAnonymous ? account : null; } function getUserGravatar(currentUser, size) { diff --git a/client/modules/accounts/helpers/util.js b/client/modules/accounts/helpers/util.js index fb47aab4fa5..3e2bcc7cffc 100644 --- a/client/modules/accounts/helpers/util.js +++ b/client/modules/accounts/helpers/util.js @@ -10,7 +10,8 @@ function capitalize(str) { const providers = { Facebook: {}, Google: {}, - Twitter: {} + Twitter: {}, + Instagram: {} }; providers.Facebook.fields = function () { @@ -34,6 +35,13 @@ providers.Twitter.fields = function () { ]; }; +providers.Instagram.fields = function () { + return [ + { property: "clientId", label: "Client ID" }, + { property: "secret", label: "Client secret" } + ]; +}; + export class ServiceConfigHelper { availableServices() { const services = Package["accounts-oauth"] ? Accounts.oauth.serviceNames() : []; diff --git a/client/modules/accounts/templates/profile/profile.js b/client/modules/accounts/templates/profile/profile.js index 952ab68b4a1..ea34baec851 100644 --- a/client/modules/accounts/templates/profile/profile.js +++ b/client/modules/accounts/templates/profile/profile.js @@ -1,7 +1,9 @@ import { Meteor } from "meteor/meteor"; import { Template } from "meteor/templating"; +import { Roles } from "meteor/alanning:roles"; import { ReactiveVar } from "meteor/reactive-var"; import { Reaction } from "/client/api"; +import { i18next } from "/client/api"; import * as Collections from "/lib/collections"; /** @@ -58,6 +60,30 @@ Template.accountProfile.helpers({ return Collections.Accounts.findOne(); }, + /** + * User's display name + * @return {String} display name + */ + displayName() { + const userId = Meteor.userId() || {}; + const user = Collections.Accounts.findOne(userId); + + if (user) { + if (user.name) { + return user.name; + } else if (user.username) { + return user.username; + } else if (user.profile && user.profile.name) { + return user.profile.name; + } + } + + if (Roles.userIsInRole(user._id || user.userId, "account/profile", + Reaction.getShopId())) { + return i18next.t("accountsUI.guest", { defaultValue: "Guest" }); + } + }, + /** * Returns the address book default view * @return {String} "addressBookGrid" || "addressBookAdd" diff --git a/client/modules/core/helpers/utils.js b/client/modules/core/helpers/utils.js index 447f54258e6..aa1d49d24b0 100644 --- a/client/modules/core/helpers/utils.js +++ b/client/modules/core/helpers/utils.js @@ -1,7 +1,4 @@ -/* global slugify */ -// client slugify only works when import minified version. -import "transliteration/lib/browser/transliteration.js"; - +import { slugify } from "transliteration"; /** * getSlug - return a client slugified string using the "slugify" * global from the transliteration package diff --git a/client/modules/i18n/startup.js b/client/modules/i18n/startup.js index 16a8409878e..7fbe8d5f0c3 100644 --- a/client/modules/i18n/startup.js +++ b/client/modules/i18n/startup.js @@ -51,11 +51,7 @@ Meteor.startup(() => { // return Meteor.subscribe("Translations", language, () => { // fetch reaction translations - const translations = Translations.find({}, { - fields: { - _id: 0 - } - }).fetch(); + const translations = Translations.find({}).fetch(); // // reduce and merge translations diff --git a/imports/plugins/core/collections/index.js b/imports/plugins/core/collections/index.js new file mode 100644 index 00000000000..5bc57d78a9e --- /dev/null +++ b/imports/plugins/core/collections/index.js @@ -0,0 +1 @@ +export { default as Validation } from "./lib/validation"; diff --git a/imports/plugins/core/collections/lib/validation.js b/imports/plugins/core/collections/lib/validation.js new file mode 100644 index 00000000000..1011a3770bd --- /dev/null +++ b/imports/plugins/core/collections/lib/validation.js @@ -0,0 +1,93 @@ +/** + * Validation class + * @summary Helper to streamline getting simple-schema validation in react components + */ +class Validation { + /** + * Instantiate with a schema to validate against + * @param {SimpleSchema} schema aldeed:simpleschema class + * @param {Object} options extra options such as { pick: ["fieldName"] } + */ + constructor(schema, options) { + if (options && options.pick) { + this.validationContext = schema.pick(options.pick).newContext(); + } else { + this.validationContext = schema.namedContext(); + } + + this.schema = schema; + this.options = options; + this.validationStatus = { + isValid: undefined, + fields: {}, + messages: {}, + isFieldValid: this.isFieldValid + }; + } + + get cleanOptions() { + return this.options && this.options.cleanOptions || { getAutoValues: false }; + } + + /** + * validate + * @param {Object} objectToValidate Object to validate against schema + * @return {Object} object containting {isValid: true|false, validationMessages: undefined|object} + */ + validate(objectToValidate) { + const messages = {}; + const fields = {}; + + // clean object, removing fields that aren't in the schema, and convert types + // based on schema + const cleanedObject = this.schema.clean(objectToValidate, this.cleanOptions); + + // Validate the cleaned object + const isValid = this.validationContext.validate(cleanedObject); + + // Avoiding the reactive-stuff built into simple-schema, grab invalid + // keys from the private var _invalidKeys, and create a new object with + // the validation error and message. + this.validationContext._invalidKeys + .forEach((validationError) => { + messages[validationError.name] = { + ...validationError, + isValid: false, + message: this.validationContext.keyErrorMessage(validationError.name) + }; + }); + + for (const fieldName of Object.keys(cleanedObject)) { + const hasMessage = messages[fieldName]; + + fields[fieldName] = { + isValid: hasMessage ? false : true, + value: cleanedObject[fieldName] + }; + } + + + // Set the current validation status of the validated object on class instance + this.validationStatus = { + isValid, + fields, + messages, + isFieldValid: this.isFieldValid + }; + + // Return object validation status, fields, and helpers + return this.validationStatus; + } + + /** + * isFieldValid - get status of a field after running `validate` + * @param {String} fieldName Name of field to check status + * @return {Boolean} `true` if valid / `false` if not valid / `undefined` if unknown or not yet tested + */ + isFieldValid = (fieldName) => { + const field = this.validationStatus.fields[fieldName]; + return field && field.isValid; + } +} + +export default Validation; diff --git a/imports/plugins/core/ui/client/components/table/sortableTable.js b/imports/plugins/core/ui/client/components/table/sortableTable.js index d0a90e855a0..50d798bc8e8 100644 --- a/imports/plugins/core/ui/client/components/table/sortableTable.js +++ b/imports/plugins/core/ui/client/components/table/sortableTable.js @@ -27,7 +27,7 @@ class SortableTable extends Component { * @prop {String} matchingResultsCount - Send to Counts collection to get results count of sub * @prop {String} publication - publication to subscribe to * @prop {Object} collection - collection to get data from - * Use props to get collection, EmailTableColumn + * Use props to get collection * Use that info to call meteor and get subscription * Output data for table * @returns {Object} loading status (bool), results (object), and matchingResults (number) diff --git a/imports/plugins/core/ui/client/components/textfield/textfield.js b/imports/plugins/core/ui/client/components/textfield/textfield.js index 23af808a0bb..76aa6b6cdb8 100644 --- a/imports/plugins/core/ui/client/components/textfield/textfield.js +++ b/imports/plugins/core/ui/client/components/textfield/textfield.js @@ -14,6 +14,32 @@ class TextField extends Component { return this.props.value || ""; } + /** + * Getter: isValid + * @return {Boolean} true/false if field is valid from props.isValid or props.valitation[this.props.name].isValid + */ + get isValid() { + const { isValid } = this.props; + + if (typeof isValid === "boolean") { + return isValid; + } else if (this.validationMessage) { + return false; + } + + return undefined; + } + + get validationMessage() { + const { name, validation } = this.props; + + if (typeof validation === "object" && validation.messages && validation.messages[name]) { + return validation.messages[name]; + } + + return undefined; + } + /** * onValueChange * @summary set the state when the value of the input is changed @@ -121,6 +147,10 @@ class TextField extends Component { return this.renderSingleLineInput(); } + /** + * Render the label for the text field if one is provided in props + * @return {ReactNode|null} react node or null + */ renderLabel() { if (this.props.label) { return ( @@ -133,11 +163,24 @@ class TextField extends Component { return null; } + /** + * Render help text or validation message + * @return {ReactNode|null} react node or null + */ renderHelpText() { - if (this.props.helpText) { + const message = this.validationMessage; + let helpText = this.props.helpText; + let i18nKey = this.props.i18nKeyHelpText; + + if (this.isValid === false && message) { + helpText = message.message; + i18nKey = message.i18nKeyMessage; + } + + if (helpText) { return ( - + ); } @@ -155,6 +198,8 @@ class TextField extends Component { "rui": true, "textfield": true, "form-group": true, + "has-error": this.isValid === false, + "has-success": this.isValid === true, // Alignment "center": this.props.align === "center", @@ -186,6 +231,7 @@ TextField.propTypes = { i18nKeyLabel: PropTypes.string, i18nKeyPlaceholder: PropTypes.string, id: PropTypes.string, + isValid: PropTypes.bool, label: PropTypes.string, multiline: PropTypes.bool, name: PropTypes.string, @@ -197,6 +243,7 @@ TextField.propTypes = { style: PropTypes.object, textFieldStyle: PropTypes.object, type: PropTypes.string, + validation: PropTypes.object, value: PropTypes.any }; diff --git a/imports/plugins/core/ui/server/i18n/ar.json b/imports/plugins/core/ui/server/i18n/ar.json index 65b9404c5a1..6002b8d569e 100644 --- a/imports/plugins/core/ui/server/i18n/ar.json +++ b/imports/plugins/core/ui/server/i18n/ar.json @@ -17,7 +17,20 @@ "publishThemeError": "`لا يمكن نشر موضوع {{themeName}}", "components": { "navbar": "شريط التنقل", - "button": "زر" + "button": "زر", + "sortableTable": { + "filterPlaceholder": "تصفية البيانات", + "tableText": { + "noDataMessage": "لا توجد نتائج", + "previousText": "سابق", + "nextText": "التالى", + "loadingText": "تحميل ...", + "noDataText": "لا توجد نتائج", + "pageText": "صفحة", + "ofText": "من", + "rowsText": "الصفوف" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/bg.json b/imports/plugins/core/ui/server/i18n/bg.json index 4f6fda0ac9b..c179b74fb5c 100644 --- a/imports/plugins/core/ui/server/i18n/bg.json +++ b/imports/plugins/core/ui/server/i18n/bg.json @@ -17,7 +17,20 @@ "publishThemeError": "`Не може да публикувате тема {{themeName}}", "components": { "navbar": "Navigation Bar", - "button": "бутон" + "button": "бутон", + "sortableTable": { + "filterPlaceholder": "Филтриране на данни", + "tableText": { + "noDataMessage": "Няма намерени резултати", + "previousText": "Предишен", + "nextText": "Следващия", + "loadingText": "Зарежда се ...", + "noDataText": "Няма намерени резултати", + "pageText": "страница", + "ofText": "на", + "rowsText": "редове" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/cs.json b/imports/plugins/core/ui/server/i18n/cs.json index 03c2609c7d9..2ce5f5b796b 100644 --- a/imports/plugins/core/ui/server/i18n/cs.json +++ b/imports/plugins/core/ui/server/i18n/cs.json @@ -17,7 +17,20 @@ "publishThemeError": "`Nelze zveřejnit téma {{themeName}}", "components": { "navbar": "navigační lišta", - "button": "Tlačítko" + "button": "Tlačítko", + "sortableTable": { + "filterPlaceholder": "Filtrování dat", + "tableText": { + "noDataMessage": "Nebyly nalezeny žádné výsledky", + "previousText": "Předchozí", + "nextText": "Další", + "loadingText": "Loading ...", + "noDataText": "Nebyly nalezeny žádné výsledky", + "pageText": "Strana", + "ofText": "z", + "rowsText": "Řádky" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/de.json b/imports/plugins/core/ui/server/i18n/de.json index e51d8bcfc22..268e61ef159 100644 --- a/imports/plugins/core/ui/server/i18n/de.json +++ b/imports/plugins/core/ui/server/i18n/de.json @@ -17,7 +17,20 @@ "publishThemeError": "`Konnte nicht Thema {{themeName}} veröffentlichen", "components": { "navbar": "Navigationsleiste", - "button": "Taste" + "button": "Taste", + "sortableTable": { + "filterPlaceholder": "Filterdaten", + "tableText": { + "noDataMessage": "Keine Ergebnisse gefunden", + "previousText": "Früher", + "nextText": "Nächster", + "loadingText": "Wird geladen ...", + "noDataText": "Keine Ergebnisse gefunden", + "pageText": "Page", + "ofText": "von", + "rowsText": "Reihen" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/el.json b/imports/plugins/core/ui/server/i18n/el.json index b90c2b996c3..e09da1b7d90 100644 --- a/imports/plugins/core/ui/server/i18n/el.json +++ b/imports/plugins/core/ui/server/i18n/el.json @@ -17,7 +17,20 @@ "publishThemeError": "`Δεν ήταν δυνατή η δημοσίευση θέμα {{themeName}}", "components": { "navbar": "Μπάρα πλοήγησης", - "button": "Κουμπί" + "button": "Κουμπί", + "sortableTable": { + "filterPlaceholder": "Φιλτράρετε δεδομένα", + "tableText": { + "noDataMessage": "Δεν βρέθηκαν αποτελέσματα", + "previousText": "Προηγούμενος", + "nextText": "Επόμενος", + "loadingText": "Φόρτωση ...", + "noDataText": "Δεν βρέθηκαν αποτελέσματα", + "pageText": "Σελίδα", + "ofText": "του", + "rowsText": "Σειρές" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/es.json b/imports/plugins/core/ui/server/i18n/es.json index 27df297c454..ea87192f86b 100644 --- a/imports/plugins/core/ui/server/i18n/es.json +++ b/imports/plugins/core/ui/server/i18n/es.json @@ -17,7 +17,20 @@ "publishThemeError": "`No se pudo publicar {{themeName}} tema", "components": { "navbar": "Barra de navegación", - "button": "Botón" + "button": "Botón", + "sortableTable": { + "filterPlaceholder": "Filtrar datos", + "tableText": { + "noDataMessage": "Sin resultados", + "previousText": "Anterior", + "nextText": "Siguiente", + "loadingText": "Cargando ...", + "noDataText": "Sin resultados", + "pageText": "Página", + "ofText": "de", + "rowsText": "Filas" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/fr.json b/imports/plugins/core/ui/server/i18n/fr.json index 015ae7e4a93..3a21188fc56 100644 --- a/imports/plugins/core/ui/server/i18n/fr.json +++ b/imports/plugins/core/ui/server/i18n/fr.json @@ -17,7 +17,20 @@ "publishThemeError": "`Impossible de publier le thème {{themeName}}", "components": { "navbar": "Barre de navigation", - "button": "Bouton" + "button": "Bouton", + "sortableTable": { + "filterPlaceholder": "Données de filtrage", + "tableText": { + "noDataMessage": "Aucun résultat trouvé", + "previousText": "Précédent", + "nextText": "Suivant", + "loadingText": "Chargement ...", + "noDataText": "Aucun résultat trouvé", + "pageText": "Page", + "ofText": "de", + "rowsText": "Lignes" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/hr.json b/imports/plugins/core/ui/server/i18n/hr.json index 810a3f264d0..1c05cdc9f64 100644 --- a/imports/plugins/core/ui/server/i18n/hr.json +++ b/imports/plugins/core/ui/server/i18n/hr.json @@ -17,7 +17,20 @@ "publishThemeError": "'Ne može se objaviti temu {{themeName}}", "components": { "navbar": "Nalazite se ovdje", - "button": "Dugme" + "button": "Dugme", + "sortableTable": { + "filterPlaceholder": "Filtriranje podataka", + "tableText": { + "noDataMessage": "nisu pronađeni rezultati", + "previousText": "Prethodna", + "nextText": "Sljedeće", + "loadingText": "Učitavanje ...", + "noDataText": "nisu pronađeni rezultati", + "pageText": "Stranica", + "ofText": "od", + "rowsText": "redovi" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/hu.json b/imports/plugins/core/ui/server/i18n/hu.json index e18d0d0f1be..db3eef746f7 100644 --- a/imports/plugins/core/ui/server/i18n/hu.json +++ b/imports/plugins/core/ui/server/i18n/hu.json @@ -17,7 +17,20 @@ "publishThemeError": "`Nem sikerült a téma {{themeName}}", "components": { "navbar": "Navigációs sáv", - "button": "Gomb" + "button": "Gomb", + "sortableTable": { + "filterPlaceholder": "adatok szűrése", + "tableText": { + "noDataMessage": "Nincs találat", + "previousText": "Előző", + "nextText": "Következő", + "loadingText": "Terhelés...", + "noDataText": "Nincs találat", + "pageText": "oldal", + "ofText": "nak,-nek", + "rowsText": "sorok" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/it.json b/imports/plugins/core/ui/server/i18n/it.json index d2b7615c72e..0694d40f933 100644 --- a/imports/plugins/core/ui/server/i18n/it.json +++ b/imports/plugins/core/ui/server/i18n/it.json @@ -17,7 +17,20 @@ "publishThemeError": "`Impossibile pubblicare tema {{themeName}}", "components": { "navbar": "Barra di navigazione", - "button": "Pulsante" + "button": "Pulsante", + "sortableTable": { + "filterPlaceholder": "Filtro Dati", + "tableText": { + "noDataMessage": "nessun risultato trovato", + "previousText": "Precedente", + "nextText": "Il Prossimo", + "loadingText": "Caricamento in corso ...", + "noDataText": "nessun risultato trovato", + "pageText": "Pagina", + "ofText": "di", + "rowsText": "righe" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/my.json b/imports/plugins/core/ui/server/i18n/my.json index 975d8abecab..3347f19259c 100644 --- a/imports/plugins/core/ui/server/i18n/my.json +++ b/imports/plugins/core/ui/server/i18n/my.json @@ -17,7 +17,20 @@ "publishThemeError": "`ဆောင်ပုဒ်ထုတ်ဝေရန်လို့မရပါ {{themeName}}", "components": { "navbar": "navigation ဘား", - "button": "ကြယ်သီး" + "button": "ကြယ်သီး", + "sortableTable": { + "filterPlaceholder": "filter မှာ Data", + "tableText": { + "noDataMessage": "ရလဒ်များမတွေ့", + "previousText": "လွန်ခဲ့သော", + "nextText": "နောက်တစ်ခု", + "loadingText": "Loading ...", + "noDataText": "ရလဒ်များမတွေ့", + "pageText": "စာမျက်နှာ", + "ofText": "၏", + "rowsText": "အတန်း" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/nl.json b/imports/plugins/core/ui/server/i18n/nl.json index 53c494afac2..cb96c21a3de 100644 --- a/imports/plugins/core/ui/server/i18n/nl.json +++ b/imports/plugins/core/ui/server/i18n/nl.json @@ -17,7 +17,20 @@ "publishThemeError": "`Kon thema {{themeName}} niet publiceren", "components": { "navbar": "Navigatiebalk", - "button": "Knop" + "button": "Knop", + "sortableTable": { + "filterPlaceholder": "Filter data", + "tableText": { + "noDataMessage": "geen resultaten gevonden", + "previousText": "Vorig", + "nextText": "Volgende", + "loadingText": "Laden ...", + "noDataText": "geen resultaten gevonden", + "pageText": "Pagina", + "ofText": "van", + "rowsText": "rijen" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/pl.json b/imports/plugins/core/ui/server/i18n/pl.json index 7a5b3dee7be..5f135205ae8 100644 --- a/imports/plugins/core/ui/server/i18n/pl.json +++ b/imports/plugins/core/ui/server/i18n/pl.json @@ -17,7 +17,20 @@ "publishThemeError": "`Nie można opublikować tematu {{themeName}}", "components": { "navbar": "Pasek nawigacyjny", - "button": "Przycisk" + "button": "Przycisk", + "sortableTable": { + "filterPlaceholder": "Filtruj dane", + "tableText": { + "noDataMessage": "Nie znaleziono wyników", + "previousText": "Poprzedni", + "nextText": "Następny", + "loadingText": "Ładowanie ...", + "noDataText": "Nie znaleziono wyników", + "pageText": "Strona", + "ofText": "z", + "rowsText": "wydziwianie" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/pt.json b/imports/plugins/core/ui/server/i18n/pt.json index 0fa8d872cfc..676673b966f 100644 --- a/imports/plugins/core/ui/server/i18n/pt.json +++ b/imports/plugins/core/ui/server/i18n/pt.json @@ -17,7 +17,20 @@ "publishThemeError": "`Não foi possível publicar {{themeName}} tema", "components": { "navbar": "Barra de navegação", - "button": "Botão" + "button": "Botão", + "sortableTable": { + "filterPlaceholder": "Dados do filtro", + "tableText": { + "noDataMessage": "Nenhum resultado encontrado", + "previousText": "Anterior", + "nextText": "Next", + "loadingText": "Carregando...", + "noDataText": "Nenhum resultado encontrado", + "pageText": "Página", + "ofText": "de", + "rowsText": "Linhas" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/ro.json b/imports/plugins/core/ui/server/i18n/ro.json index 42e822c220b..3a0f04814cd 100644 --- a/imports/plugins/core/ui/server/i18n/ro.json +++ b/imports/plugins/core/ui/server/i18n/ro.json @@ -17,7 +17,20 @@ "publishThemeError": "`Nu am putut publica tema {{themeName}}", "components": { "navbar": "Bară de navigare", - "button": "Buton" + "button": "Buton", + "sortableTable": { + "filterPlaceholder": "Filtrați datele", + "tableText": { + "noDataMessage": "Nici un rezultat gasit", + "previousText": "Anterior", + "nextText": "Următorul", + "loadingText": "Loading ...", + "noDataText": "Nici un rezultat gasit", + "pageText": "Pagină", + "ofText": "de", + "rowsText": "rânduri" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/ru.json b/imports/plugins/core/ui/server/i18n/ru.json index c182c9d8fdd..1c8dbb3cd4a 100644 --- a/imports/plugins/core/ui/server/i18n/ru.json +++ b/imports/plugins/core/ui/server/i18n/ru.json @@ -17,7 +17,20 @@ "publishThemeError": "`Не удалось опубликовать тему {{themeName}}", "components": { "navbar": "Панель навигации", - "button": "кнопка" + "button": "кнопка", + "sortableTable": { + "filterPlaceholder": "Данные фильтра", + "tableText": { + "noDataMessage": "результатов не найдено", + "previousText": "Предыдущий", + "nextText": "Следующий", + "loadingText": "Загрузка ...", + "noDataText": "результатов не найдено", + "pageText": "страница", + "ofText": "из", + "rowsText": "строки" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/sl.json b/imports/plugins/core/ui/server/i18n/sl.json index 029f447f8dc..34fc3d21ef5 100644 --- a/imports/plugins/core/ui/server/i18n/sl.json +++ b/imports/plugins/core/ui/server/i18n/sl.json @@ -17,7 +17,20 @@ "publishThemeError": "'Ni bilo mogoče objaviti temo {{themeName}}", "components": { "navbar": "Navigation Bar", - "button": "Button" + "button": "Button", + "sortableTable": { + "filterPlaceholder": "filter podatkov", + "tableText": { + "noDataMessage": "Ni zadetkov", + "previousText": "Prejšnja", + "nextText": "Naslednja", + "loadingText": "Nalaganje ...", + "noDataText": "Ni zadetkov", + "pageText": "stran", + "ofText": "za", + "rowsText": "vrstice" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/sv.json b/imports/plugins/core/ui/server/i18n/sv.json index 8ed22864cea..3f6b167d4e4 100644 --- a/imports/plugins/core/ui/server/i18n/sv.json +++ b/imports/plugins/core/ui/server/i18n/sv.json @@ -17,7 +17,20 @@ "publishThemeError": "`Det gick inte att publicera tema {{themeName}}", "components": { "navbar": "Navigeringsfält", - "button": "Knapp" + "button": "Knapp", + "sortableTable": { + "filterPlaceholder": "Filtrera data", + "tableText": { + "noDataMessage": "Inga resultat funna", + "previousText": "Tidigare", + "nextText": "Nästa", + "loadingText": "Hämtar ...", + "noDataText": "Inga resultat funna", + "pageText": "Sida", + "ofText": "av", + "rowsText": "rader" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/tr.json b/imports/plugins/core/ui/server/i18n/tr.json index e86e0a5e6f2..a1665fc5d45 100644 --- a/imports/plugins/core/ui/server/i18n/tr.json +++ b/imports/plugins/core/ui/server/i18n/tr.json @@ -17,7 +17,20 @@ "publishThemeError": "`Tema {{themeName}} yayınlamak Could not", "components": { "navbar": "Gezinti çubuğu", - "button": "düğme" + "button": "düğme", + "sortableTable": { + "filterPlaceholder": "Verileri Filtreleme", + "tableText": { + "noDataMessage": "Sonuç bulunamadı", + "previousText": "Önceki", + "nextText": "Sonraki", + "loadingText": "Yükleniyor ...", + "noDataText": "Sonuç bulunamadı", + "pageText": "Sayfa", + "ofText": "arasında", + "rowsText": "satırlar" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/vi.json b/imports/plugins/core/ui/server/i18n/vi.json index 338d6a29d1a..3e568967b8b 100644 --- a/imports/plugins/core/ui/server/i18n/vi.json +++ b/imports/plugins/core/ui/server/i18n/vi.json @@ -17,7 +17,20 @@ "publishThemeError": "`Không thể xuất bản chủ đề {{themeName}}", "components": { "navbar": "Thanh điều hướng", - "button": "nút" + "button": "nút", + "sortableTable": { + "filterPlaceholder": "Lọc dữ liệu", + "tableText": { + "noDataMessage": "không có kết quả nào được tìm thấy", + "previousText": "Trước", + "nextText": "Kế tiếp", + "loadingText": "Tải...", + "noDataText": "không có kết quả nào được tìm thấy", + "pageText": "Trang", + "ofText": "Của", + "rowsText": "Hàng" + } + } } } } diff --git a/imports/plugins/core/ui/server/i18n/zh.json b/imports/plugins/core/ui/server/i18n/zh.json index 96818298180..c9988584b83 100644 --- a/imports/plugins/core/ui/server/i18n/zh.json +++ b/imports/plugins/core/ui/server/i18n/zh.json @@ -17,7 +17,20 @@ "publishThemeError": "`无法发布主题{{themeName}}", "components": { "navbar": "导航栏", - "button": "按键" + "button": "按键", + "sortableTable": { + "filterPlaceholder": "过滤数据", + "tableText": { + "noDataMessage": "未找到结果", + "previousText": "上一页", + "nextText": "下一个", + "loadingText": "加载中...", + "noDataText": "未找到结果", + "pageText": "页", + "ofText": "的", + "rowsText": "行" + } + } } } } diff --git a/imports/plugins/included/default-theme/client/styles/tagNav.less b/imports/plugins/included/default-theme/client/styles/tagNav.less index 1297aa589bc..404a6bd8522 100644 --- a/imports/plugins/included/default-theme/client/styles/tagNav.less +++ b/imports/plugins/included/default-theme/client/styles/tagNav.less @@ -294,7 +294,7 @@ } .rui.tagnav .navbar-item { - height: 100%; + height: @navbar-height; } .rui.tagnav.vertical .navbar-item { diff --git a/imports/plugins/included/default-theme/client/styles/textfield.less b/imports/plugins/included/default-theme/client/styles/textfield.less index 772efe661ef..54b7c8cbd56 100644 --- a/imports/plugins/included/default-theme/client/styles/textfield.less +++ b/imports/plugins/included/default-theme/client/styles/textfield.less @@ -40,3 +40,11 @@ border: 1px solid @border-color; border-radius: @input-border-radius; } + +.rui.textfield.has-error input { + border-color: @rui-danger; +} + +.rui.textfield.help-text input { + border-color: @state-danger-border +} diff --git a/imports/plugins/included/product-detail-simple/server/i18n/ar.json b/imports/plugins/included/product-detail-simple/server/i18n/ar.json index 1970eff6745..ea18cf1a71d 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/ar.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/ar.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "تم تعديل كمية المنتج الخاص بك إلى الكمية القصوى في الأوراق المالية" + }, + "mediaGallery": { + "deleteImage": "انقر لإزالة الصورة", + "addedImage": "هذه صورة جديدة. نشر لحفظ التغييرات.", + "removedImage": "تم حذف الصورة. نشر لحفظ التغييرات." } }, "availableOptions": "خيارات متاحة" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/bg.json b/imports/plugins/included/product-detail-simple/server/i18n/bg.json index ed7e7d7504e..9fee484275f 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/bg.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/bg.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Вашият продукт количество бе коригиран до макс количество на склад" + }, + "mediaGallery": { + "deleteImage": "Кликнете, за да премахнете изображението", + "addedImage": "Това е ново изображение. Публикуване, за да запазите промените.", + "removedImage": "Изображението бе изтрито. Публикуване, за да запазите промените." } }, "availableOptions": "Налични варианти" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/cs.json b/imports/plugins/included/product-detail-simple/server/i18n/cs.json index 011a5bec26e..1f1389648c9 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/cs.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/cs.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Váš množství produktu byla upravena tak, aby max množství na skladě" + }, + "mediaGallery": { + "deleteImage": "Klepnutím odeberete obrázek", + "addedImage": "Toto je nový obrázek. Publikujte, chcete-li uložit změny.", + "removedImage": "Obrázek byl smazán. Publikujte, chcete-li uložit změny." } }, "availableOptions": "dostupné možnosti" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/de.json b/imports/plugins/included/product-detail-simple/server/i18n/de.json index b4e1332c113..8b2737e5f56 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/de.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/de.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Ihre Produktmenge wurde die Höchstmenge eingestellt auf Lager" + }, + "mediaGallery": { + "deleteImage": "Klicken Sie, um das Bild zu entfernen", + "addedImage": "Dies ist ein neues Bild. Veröffentlichen, um Änderungen zu speichern.", + "removedImage": "Bild wurde gelöscht. Veröffentlichen, um Änderungen zu speichern." } }, "availableOptions": "Verfügbare Optionen" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/el.json b/imports/plugins/included/product-detail-simple/server/i18n/el.json index bee39e538ed..7f6b0f10466 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/el.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/el.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "ποσότητα του προϊόντος σας έχει ρυθμιστεί στο μέγιστο ποσότητα στο απόθεμα" + }, + "mediaGallery": { + "deleteImage": "Κάντε κλικ για να αφαιρέσετε την εικόνα", + "addedImage": "Αυτή είναι μια νέα εικόνα. Δημοσιεύστε για να αποθηκεύσετε τις αλλαγές.", + "removedImage": "Η εικόνα έχει διαγραφεί. Δημοσιεύστε για να αποθηκεύσετε τις αλλαγές." } }, "availableOptions": "διαθέσιμες Επιλογές" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/es.json b/imports/plugins/included/product-detail-simple/server/i18n/es.json index d1ea84fd01b..b3a7234454c 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/es.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/es.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Su cantidad de producto se ha ajustado a la cantidad máxima en stock" + }, + "mediaGallery": { + "deleteImage": "Haga clic para eliminar la imagen", + "addedImage": "Esta es una nueva imagen. Publicar para guardar los cambios.", + "removedImage": "Se ha eliminado la imagen. Publicar para guardar los cambios." } }, "availableOptions": "Opciones disponibles" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/fr.json b/imports/plugins/included/product-detail-simple/server/i18n/fr.json index b97cf697df3..8c7562054c0 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/fr.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/fr.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Votre quantité de produit a été ajustée à la quantité maximale en stock" + }, + "mediaGallery": { + "deleteImage": "Cliquez pour supprimer l'image", + "addedImage": "C'est une nouvelle image. Publiez pour enregistrer les modifications.", + "removedImage": "L'image a été supprimée. Publiez pour enregistrer les modifications." } }, "availableOptions": "Options disponibles" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/hr.json b/imports/plugins/included/product-detail-simple/server/i18n/hr.json index 0a6bb2b5ccf..b88edbc0905 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/hr.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/hr.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Vaš proizvod količina je prilagođena max količini na lageru" + }, + "mediaGallery": { + "deleteImage": "Kliknite da biste uklonili sliku", + "addedImage": "Ovo je nova slika. Objavi za spremanje promjena.", + "removedImage": "Slika je izbrisana. Objavi za spremanje promjena." } }, "availableOptions": "Dostupne opcije" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/hu.json b/imports/plugins/included/product-detail-simple/server/i18n/hu.json index 5138b65f40e..8a1cdda3184 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/hu.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/hu.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "A termék mennyisége állították be a max mennyiség raktáron" + }, + "mediaGallery": { + "deleteImage": "Kattintson a kép eltávolítása", + "addedImage": "Ez egy új képet. Adja változások mentéséhez.", + "removedImage": "Képet törölték. Adja változások mentéséhez." } }, "availableOptions": "elérhető opciók" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/it.json b/imports/plugins/included/product-detail-simple/server/i18n/it.json index ab5e2bc5b4d..eaab691bad3 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/it.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/it.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "La vostra quantità di prodotto è stato adattato per la quantità massima in magazzino" + }, + "mediaGallery": { + "deleteImage": "Fare clic per rimuovere l'immagine", + "addedImage": "Questa è una nuova immagine. Pubblica per salvare le modifiche.", + "removedImage": "L'immagine è stata cancellata. Pubblica per salvare le modifiche." } }, "availableOptions": "Opzioni disponibili" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/my.json b/imports/plugins/included/product-detail-simple/server/i18n/my.json index fc0be4a08c8..64250adda5d 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/my.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/my.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "သင့်ရဲ့ကုန်ပစ္စည်းအရေအတွက်စတော့ရှယ်ယာအတွက် max ကိုအရေအတွက်မှချိန်ညှိထားပြီး" + }, + "mediaGallery": { + "deleteImage": "image ကိုဖယ်ရှားပစ်ရန်ကလစ်နှိပ်ပါ", + "addedImage": "ဒါကအသစ်တခုပုံရိပ်ဖြစ်ပါတယ်။ အပြောင်းအလဲများကိုကယ်ဖို့ Publish ။", + "removedImage": "Image ကိုဖျက်ထားသည်။ အပြောင်းအလဲများကိုကယ်ဖို့ Publish ။" } }, "availableOptions": "ရရှိနိုင် Options ကို" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/nl.json b/imports/plugins/included/product-detail-simple/server/i18n/nl.json index 6cd0fae4367..f5268831a2f 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/nl.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/nl.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Uw product hoeveelheid is aangepast aan de maximale hoeveelheid in voorraad" + }, + "mediaGallery": { + "deleteImage": "Klik om de afbeelding te verwijderen", + "addedImage": "Dit is een nieuwe afbeelding. Publiceer om wijzigingen op te slaan.", + "removedImage": "Afbeelding is verwijderd. Publiceer om wijzigingen op te slaan." } }, "availableOptions": "Beschikbare opties" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/pl.json b/imports/plugins/included/product-detail-simple/server/i18n/pl.json index 41f28553985..2be203cfd23 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/pl.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/pl.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Twoja ilość produktów została dostosowana do maksymalnej ilości w magazynie" + }, + "mediaGallery": { + "deleteImage": "Kliknij, aby usunąć obraz", + "addedImage": "To jest nowy wizerunek. Opublikuj, aby zapisać zmiany.", + "removedImage": "Obraz został usunięty. Opublikuj, aby zapisać zmiany." } }, "availableOptions": "Dostępne opcje" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/pt.json b/imports/plugins/included/product-detail-simple/server/i18n/pt.json index 40b5e6d8416..6c986074210 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/pt.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/pt.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Sua quantidade de produto foi ajustado para a quantidade máxima em estoque" + }, + "mediaGallery": { + "deleteImage": "Clique para remover a imagem", + "addedImage": "Esta é uma nova imagem. Publique para salvar as alterações.", + "removedImage": "A imagem foi excluída. Publique para salvar as alterações." } }, "availableOptions": "Opções disponíveis" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/ro.json b/imports/plugins/included/product-detail-simple/server/i18n/ro.json index ec1b6df3ad7..be649a3dd99 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/ro.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/ro.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Cantitatea dvs. de produs a fost ajustat la cantitatea maximă în stoc" + }, + "mediaGallery": { + "deleteImage": "Faceți clic pentru a elimina imaginea", + "addedImage": "Aceasta este o imagine nouă. Publicați pentru a salva modificările.", + "removedImage": "Imaginea a fost ștearsă. Publicați pentru a salva modificările." } }, "availableOptions": "Opțiuni disponibile" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/ru.json b/imports/plugins/included/product-detail-simple/server/i18n/ru.json index d3b9cd6ab35..40fa6ae4842 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/ru.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/ru.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Ваше количество продукта было скорректировано до максимального количества в наличии" + }, + "mediaGallery": { + "deleteImage": "Нажмите, чтобы удалить изображение", + "addedImage": "Это новый образ. Публикация для сохранения изменений.", + "removedImage": "Изображение удалено. Публикация для сохранения изменений." } }, "availableOptions": "Доступные опции" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/sl.json b/imports/plugins/included/product-detail-simple/server/i18n/sl.json index dfd476ef71f..8ce59648e04 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/sl.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/sl.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Vaš količina izdelka je bil prilagojen za maksimalno količino na zalogi" + }, + "mediaGallery": { + "deleteImage": "Kliknite, da odstranite sliko", + "addedImage": "To je nova podoba. Objavi, da shranite spremembe.", + "removedImage": "Slika je bila izbrisana. Objavi, da shranite spremembe." } }, "availableOptions": "Opcije na voljo" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/sv.json b/imports/plugins/included/product-detail-simple/server/i18n/sv.json index adc3b6b56a7..19014835935 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/sv.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/sv.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Din produkt mängd har justerats till max antal i lager" + }, + "mediaGallery": { + "deleteImage": "Klicka för att ta bort bilden", + "addedImage": "Det här är en ny bild. Publicera för att spara ändringar.", + "removedImage": "Bilden har raderats. Publicera för att spara ändringar." } }, "availableOptions": "tillgängliga alternativ" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/tr.json b/imports/plugins/included/product-detail-simple/server/i18n/tr.json index dc0994980de..4bae0ccd041 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/tr.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/tr.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "Ürününüz miktar stok maksimum miktara ayarlandı" + }, + "mediaGallery": { + "deleteImage": "Resmi kaldırmak için tıklayın", + "addedImage": "Bu yeni bir görüntü. Değişiklikleri kaydetmek için yayınla.", + "removedImage": "Resim silindi. Değişiklikleri kaydetmek için yayınla." } }, "availableOptions": "mevcut seçenekler" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/vi.json b/imports/plugins/included/product-detail-simple/server/i18n/vi.json index 58f24a293e2..ad240de63ee 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/vi.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/vi.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "số lượng sản phẩm của bạn đã được điều chỉnh theo số lượng tối đa trong kho" + }, + "mediaGallery": { + "deleteImage": "Nhấp để xóa hình ảnh", + "addedImage": "Đây là hình ảnh mới. Xuất bản để lưu thay đổi.", + "removedImage": "Hình ảnh đã bị xóa. Xuất bản để lưu thay đổi." } }, "availableOptions": "Tùy chọn có sẵn" diff --git a/imports/plugins/included/product-detail-simple/server/i18n/zh.json b/imports/plugins/included/product-detail-simple/server/i18n/zh.json index 93074855a36..ea3475f5130 100644 --- a/imports/plugins/included/product-detail-simple/server/i18n/zh.json +++ b/imports/plugins/included/product-detail-simple/server/i18n/zh.json @@ -9,6 +9,11 @@ }, "inventoryAlerts": { "adjustedQuantity": "您的产品数量进行了调整,在股票的最大数量" + }, + "mediaGallery": { + "deleteImage": "点击删除图片", + "addedImage": "这是一个新的形象。发布以保存更改。", + "removedImage": "图片已被删除。发布以保存更改。" } }, "availableOptions": "可用选项" diff --git a/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.html b/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.html index 2b500ec10ac..f9a8ab9c1e1 100644 --- a/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.html +++ b/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.html @@ -73,7 +73,7 @@
-
+
+ {{#with hasValidationMessage 'optionTitle'}} + {{message}} + {{/with}}
-
+
+ {{#with hasValidationMessage 'title'}} + {{message}} + {{/with}}
-
+
+ {{#with hasValidationMessage 'inventoryQuantity'}} + {{message}} + {{/with}}
-
+
+ {{#with hasValidationMessage 'price'}} + {{message}} + {{/with}}
diff --git a/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.js b/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.js index 3fbfcb2973b..2ff2057a36c 100644 --- a/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.js +++ b/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/childVariant.js @@ -1,19 +1,37 @@ import { $ } from "meteor/jquery"; import { Template } from "meteor/templating"; import { Meteor } from "meteor/meteor"; +import { ReactiveDict } from "meteor/reactive-dict"; import { Reaction, i18next } from "/client/api"; import { ReactionProduct } from "/lib/api"; import { Media } from "/lib/collections"; import { Icon } from "/imports/plugins/core/ui/client/components"; +import { Validation } from "@reactioncommerce/reaction-collections"; +import { ProductVariant } from "/lib/collections/schemas/products"; function productHandle() { const selectedProduct = ReactionProduct.selectedProduct(); return selectedProduct.__published && selectedProduct.__published.handle || selectedProduct.handle; } + +Template.childVariantForm.onCreated(function () { + this.validation = new Validation(ProductVariant); + this.state = new ReactiveDict(); + this.state.setDefault({ + validationStatus: {} + }); +}); + + /** * childVariantForm onRendered */ -Template.onRendered(function () { +Template.childVariantForm.onRendered(function () { + const validationStatus = this.validation.validate(this.data); + + this.state.set("validationStatus", validationStatus); + this.state.set("variant", this.data); + this.autorun(() => { const selectedVariantId = Reaction.Router.getParam("variantId"); @@ -88,6 +106,26 @@ Template.childVariantForm.helpers({ } return "panel-default"; + }, + hasValidationMessage(fieldName) { + const instance = Template.instance(); + const validationStatus = instance.state.get("validationStatus"); + + if (validationStatus && validationStatus.messages && validationStatus.messages[fieldName]) { + return validationStatus.messages[fieldName]; + } + + return false; + }, + hasErrorClassName(fieldName) { + const instance = Template.instance(); + const validationStatus = instance.state.get("validationStatus"); + + if (validationStatus && validationStatus.messages && validationStatus.messages[fieldName]) { + return "has-error"; + } + + return false; } }); @@ -111,12 +149,25 @@ Template.childVariantForm.events({ const value = Template.instance().$(event.currentTarget).val(); const field = Template.instance().$(event.currentTarget).attr("name"); - Meteor.call("products/updateProductField", variant._id, field, value, - error => { - if (error) { - Alerts.toast(error.message, "error"); - } - }); + const variantToValidate = template.state.get("variant") || variant; + const updated = { + ...variantToValidate, + [field]: value + }; + const validationStatus = template.validation.validate(updated); + + template.state.set("validationStatus", validationStatus); + template.state.set("variant", updated); + + if (validationStatus.isValid === true) { + Meteor.call("products/updateProductField", variant._id, field, value, + error => { + if (error) { + Alerts.toast(error.message, "error"); + } + }); + } + return ReactionProduct.setCurrentVariant(variant._id); }, "click .js-child-variant-heading": function (event, instance) { diff --git a/imports/plugins/included/product-variant/client/templates/products/productGrid/controls.html b/imports/plugins/included/product-variant/client/templates/products/productGrid/controls.html deleted file mode 100644 index 5b8635fa673..00000000000 --- a/imports/plugins/included/product-variant/client/templates/products/productGrid/controls.html +++ /dev/null @@ -1,25 +0,0 @@ - diff --git a/imports/plugins/included/product-variant/client/templates/products/productGrid/controls.js b/imports/plugins/included/product-variant/client/templates/products/productGrid/controls.js deleted file mode 100644 index 5fa3f16ce46..00000000000 --- a/imports/plugins/included/product-variant/client/templates/products/productGrid/controls.js +++ /dev/null @@ -1,70 +0,0 @@ -import _ from "lodash"; -import { Session } from "meteor/session"; -import { Template } from "meteor/templating"; -import { ReactiveDict } from "meteor/reactive-dict"; -import { Reaction } from "/lib/api"; -import { IconButton } from "/imports/plugins/core/ui/client/components"; - -Template.gridControls.onCreated(function () { - this.state = new ReactiveDict(); - - this.autorun(() => { - if (this.data.product) { - const selectedProducts = Session.get("productGrid/selectedProducts"); - const isSelected = _.isArray(selectedProducts) ? selectedProducts.indexOf(this.data.product._id) >= 0 : false; - - this.state.set("isSelected", isSelected); - } - }); -}); - -Template.gridControls.onRendered(function () { - return this.$("[data-toggle='tooltip']").tooltip({ - position: "top" - }); -}); - - -Template.gridControls.helpers({ - checked: function () { - return Template.instance().state.equals("isSelected", true); - }, - - isVisible() { - const currentData = Template.currentData(); - return currentData && currentData.product && currentData.product.isVisible; - }, - - hasControl() { - if (Reaction.hasOwnerAccess()) { - return true; - } - - const instance = Template.instance(); - const shopId = Reaction.getShopId(); - - return ( - Reaction.hasPermission("createProduct") && - // does product belong to this shop seller - shopId === instance.data.product.shopId - ); - }, - - hasChanges() { - const { product } = Template.currentData(); - if (product.__draft) { - return true; - } - - return false; - }, - - VisibilityButton() { - return { - component: IconButton, - icon: "", - onIcon: "", - status: "info" - }; - } -}); diff --git a/imports/plugins/included/product-variant/components/products.js b/imports/plugins/included/product-variant/components/products.js index 8ebfdbe9dca..dc12d8b722d 100644 --- a/imports/plugins/included/product-variant/components/products.js +++ b/imports/plugins/included/product-variant/components/products.js @@ -69,7 +69,7 @@ class ProductsComponent extends Component { render() { if (this.props.ready()) { return ( -
+
{this.renderProductGrid()} {this.renderLoadMoreProductsButton()} {this.renderSpinner()} diff --git a/imports/plugins/included/product-variant/components/variantForm.js b/imports/plugins/included/product-variant/components/variantForm.js index 3bff0a07544..09d01c36baf 100644 --- a/imports/plugins/included/product-variant/components/variantForm.js +++ b/imports/plugins/included/product-variant/components/variantForm.js @@ -3,7 +3,6 @@ import PropTypes from "prop-types"; import { isEqual } from "lodash"; import Velocity from "velocity-animate"; import "velocity-animate/velocity.ui"; -import update from "react/lib/update"; import { formatPriceString } from "/client/api"; import { Button, @@ -55,7 +54,7 @@ class VariantForm extends Component { super(props); this.state = { - expandedCard: this.fieldGroupForFieldName(props.editFocus), + expandedCard: "variantDetails", variant: props.variant, inventoryPolicy: props.variant.inventoryPolicy, taxable: props.variant.taxable, @@ -74,10 +73,11 @@ class VariantForm extends Component { } } } - const cardGroupName = this.fieldGroupForFieldName(nextProps.editFocus); this.setState({ - expandedCard: cardGroupName, + inventoryManagement: nextProps.variant.inventoryManagement, + inventoryPolicy: nextProps.variant.inventoryPolicy, + taxable: nextProps.variant.taxable, variant: nextProps.variant }); } @@ -110,9 +110,11 @@ class VariantForm extends Component { if (fieldRef) { const input = fieldRef.refs.input; + const isFieldValid = this.props.validation.isFieldValid(fieldName); + const flashColor = isFieldValid ? "#f0fff4" : "#ffeeef"; Velocity.RunSequence([ - { e: input, p: { backgroundColor: "#e2f2e2" }, o: { duration: 200 } }, + { e: input, p: { backgroundColor: flashColor }, o: { duration: 200 } }, { e: input, p: { backgroundColor: "#fff" }, o: { duration: 100 } } ]); } @@ -123,51 +125,52 @@ class VariantForm extends Component { } handleFieldChange = (event, value, field) => { - const newState = update(this.state, { + this.setState(({ variant }) => ({ variant: { - $merge: { - [field]: value - } + ...variant, + [field]: value } - }); - - this.setState(newState); + })); } handleFieldBlur = (event, value, field) => { if (this.props.onVariantFieldSave) { - this.props.onVariantFieldSave(this.variant._id, field, value); + this.props.onVariantFieldSave(this.variant._id, field, value, this.state.variant); } } handleSelectChange = (value, field) => { - this.handleFieldChange(event, value, field); - - if (this.props.onVariantFieldSave) { - this.props.onVariantFieldSave(this.variant._id, field, value); - } + this.setState(({ variant }) => ({ + variant: { + ...variant, + [field]: value + } + }), () => { + if (this.props.onVariantFieldSave) { + this.props.onVariantFieldSave(this.variant._id, field, value, this.state.variant); + } + }); } handleCheckboxChange = (event, value, field) => { - this.setState({ - [field]: value - }); + this.setState(({ variant }) => ({ + variant: { + ...variant, + [field]: value + } + })); this.handleFieldBlur(event, value, field); } - handleCardExpand(cardName) { - if (this.props.onCardExpand) { - this.props.onCardExpand(cardName); + handleCardExpand = (event, card, cardName, isExpanded) => { + if (typeof this.props.onCardExpand === "function") { + this.props.onCardExpand(isExpanded ? cardName : undefined); } } - isExpanded(groupName) { - if (this.state.expandedCard && this.state.expandedCard === groupName) { - return true; - } - - return false; + isExpanded = (groupName) => { + return this.state.expandedCard === groupName; } renderTaxCodeField() { @@ -198,6 +201,7 @@ class VariantForm extends Component { onBlur={this.handleFieldBlur} onChange={this.handleFieldChange} onReturnKeyDown={this.handleFieldBlur} + validation={this.props.validation} /> ); } @@ -253,6 +257,7 @@ class VariantForm extends Component {
); } + return (
); @@ -275,8 +281,10 @@ class VariantForm extends Component { return (