diff --git a/app/resources/locales/en_US/error.po b/app/resources/locales/en_US/error.po index ac016e359..5ce7898dd 100644 --- a/app/resources/locales/en_US/error.po +++ b/app/resources/locales/en_US/error.po @@ -54,9 +54,6 @@ msgstr "Username \"{0}\" not found in api_users table" msgid "auto.viewvar.type.unknown" msgstr "Unknown Auto View Var Type {0}" -msgid "copy.error" -msgstr "Could not copy." - msgid "coid" msgstr "CO ID not found" @@ -199,6 +196,12 @@ msgstr "{0} must be provided" msgid "invalid" msgstr "Invalid value \"{0}\"" +msgid "javascript.copy" +msgstr "Could not copy." + +msgid "javascript.requires.https" +msgstr "This feature requires HTTPS." + msgid "Jobs.failed.abnormal" msgstr "The Job terminated unexpectedly" diff --git a/app/resources/locales/en_US/information.po b/app/resources/locales/en_US/information.po index 3d35cff34..b9547d811 100644 --- a/app/resources/locales/en_US/information.po +++ b/app/resources/locales/en_US/information.po @@ -96,9 +96,6 @@ msgstr "No title" msgid "global.value.none" msgstr "No value" -msgid "global.visit.link" -msgstr "Visit link" - msgid "pagination.format" msgstr "Page {{page}} of {{pages}}, Viewing {{start}}-{{end}} of {{count}}" diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index 3828656ba..5be0470ee 100644 --- a/app/resources/locales/en_US/operation.po +++ b/app/resources/locales/en_US/operation.po @@ -252,3 +252,6 @@ msgstr "View" msgid "view.a" msgstr "View {0}" +msgid "visit.link" +msgstr "Visit link" + diff --git a/app/src/Controller/NamesController.php b/app/src/Controller/NamesController.php index 36084b4e7..d4dc3b604 100644 --- a/app/src/Controller/NamesController.php +++ b/app/src/Controller/NamesController.php @@ -92,7 +92,8 @@ public function primary(string $id) { catch(\Exception $e) { $this->Flash->error($e->getMessage()); } - + + $this->Names->setRedirectGoal('primaryLink'); return $this->generateRedirect($obj ?? null); } } \ No newline at end of file diff --git a/app/src/View/Helper/VueHelper.php b/app/src/View/Helper/VueHelper.php index 552218571..2d3afa9e5 100644 --- a/app/src/View/Helper/VueHelper.php +++ b/app/src/View/Helper/VueHelper.php @@ -47,19 +47,21 @@ class VueHelper extends Helper { 'SuspendableStatusEnum.S' ], 'error' => [ - 'copy.error' + 'javascript.copy', + 'javascript.requires.https' ], 'field' => [ 'email', 'login', 'primary', + 'datepicker.chooseTime', 'datepicker.hour', + 'datepicker.minute', 'status', 'unverified' ], 'information' => [ 'global.value.none', - 'datepicker.hour', 'record', 'report.for', 'value.copied' @@ -73,7 +75,9 @@ class VueHelper extends Helper { 'autocomplete.people.placeholder', 'close', 'copy', - 'copy.value' + 'copy.value', + 'primary', + 'visit.link' ], 'result' => [ 'failed', @@ -101,9 +105,9 @@ public function locales(string $lang = 'en_US'): array { if(getType($key) == 'array') { // for getting plural or singular instances of a language string; // XXX we should do better than this so we can use both. - $locales[$key['0']] = __d($domain, $key[0], $key[1]); + $locales[$domain . '.' . $key['0']] = __d($domain, $key[0], $key[1]); } else { - $locales[$key] = __d($domain, $key); + $locales[$domain . '.' . $key] = __d($domain, $key); } } } diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css index 18ebcd491..fc00bf520 100644 --- a/app/webroot/css/co-base.css +++ b/app/webroot/css/co-base.css @@ -1315,6 +1315,74 @@ h2.card-title a { color: var(--cmg-color-txt-soft); font-size: 0.8em; } +.field-data.with-actions { + display: flex; + justify-content: space-between; +} +/* JavaScript Component Action Dropdowns (e.g. on MVEAs) */ +.cm-component-actions ul.dropdown-menu { + margin: -0.5em 0 !important; + padding: 0; + font-size: 0.9em; + min-width: unset; +} +.cm-component-actions li.action-list-item { + margin: 0; + padding: 0; +} +.cm-component-actions .action-menu-toggle { + padding: 0.5em; +} +.cm-component-actions a.dropdown-item { + display: flex; + gap: 0.25em; + padding: 0.25rem 0.5rem; +} +#main .cm-component-actions a.dropdown-item:hover { + color: var(--cmg-color-btn-bg-002); + text-decoration: none; +} +.with-hover-button { + display: flex; + justify-content: space-between; + gap: 0.5em; + align-items: center; +} +.with-row-actions { + display: flex; + justify-content: space-between; + gap: 0.5em; + align-items: end; +} +.field-data.with-row-actions { + padding-right: 0.25em; +} +.cm-copy-value-button .material-icons-outlined { + font-size: 1em !important; +} +button.cm-row-button, +a.cm-row-button { + display: flex; + align-items: center; + gap: 0.1em; + padding: 0 0.5em 0.2em; + text-transform: unset; + box-shadow: none; + background-color: var(--cmg-color-body-bg); + border: 1px solid var(--cmg-color-bg-006); + color: var(--cmg-color-link); + white-space: nowrap; +} +button.cm-row-button:hover, +button.cm-row-button:active, +button.cm-row-button:focus, +a.cm-row-button:hover, +a.cm-row-button:active, +a.cm-row-button:focus { + background-color: var(--cmg-color-btn-bg-001); + border: 1px solid var(--cmg-color-btn-bg-001); + color: var(--cmg-color-txt-inverse) !important; +} /* DATA LISTS */ ul.data-list { padding-left: 0; @@ -1340,6 +1408,10 @@ label { ul.fields li { overflow-x: auto; /* required for data tables on mobile */ } +.card ul.fields li { + overflow: unset; /* but don't overflow in a card row */ + list-style: none; +} /*** ul.form-list is the main structure for representing forms as well as data where field name is in the left column and field data is in the right column; see also ul.data-list @@ -1917,34 +1989,6 @@ code, code.source-record { overflow-wrap: anywhere; } -.with-copy-icon { - display: flex; - justify-content: space-between; - gap: 0.5em; - align-items: center; -} -button.cm-copy-value-button { - display: flex; - align-items: center; - gap: 0.1em; - padding: 0 0.5em 0.2em; - text-transform: unset; - box-shadow: none; - background-color: var(--cmg-color-body-bg); - border: 1px solid var(--cmg-color-bg-006); - color: var(--cmg-color-link); - white-space: nowrap; -} -button.cm-copy-value-button:hover, -button.cm-copy-value-button:active, -button.cm-copy-value-button:focus { - background-color: var(--cmg-color-btn-bg-001); - border: 1px solid var(--cmg-color-btn-bg-001); - color: var(--cmg-color-txt-inverse) !important; -} -.cm-copy-value-button .material-icons-outlined { - font-size: 1em !important; -} /* INDEX VIEWS and TABLES */ table { width: 100%; diff --git a/app/webroot/css/co-responsive.css b/app/webroot/css/co-responsive.css index cb6607a04..f77964f68 100644 --- a/app/webroot/css/co-responsive.css +++ b/app/webroot/css/co-responsive.css @@ -204,11 +204,11 @@ padding: 0 0.75em; border-right: 1px dashed var(--cmg-color-bg-006); } - .with-copy-icon { + .with-hover-button { justify-content: flex-start; position: relative; } - .with-copy-icon .cm-copy-value-button { + .with-hover-button .cm-hover-button { display: none; /* The following two rules are a trade-off; remove them to make the copy button appear just to the right of the value being copied; the trade-off is that long lines will wrap when the button appears causing the row to @@ -216,10 +216,10 @@ position: absolute; right: 1em; } - .field-data-container:hover .with-copy-icon .cm-copy-value-button, - .field-data-container:active .with-copy-icon .cm-copy-value-button, - .field-data-container:focus .with-copy-icon .cm-copy-value-button, - .field-data-container:focus-within .with-copy-icon .cm-copy-value-button { + .field-data-container:hover .with-hover-button .cm-hover-button, + .field-data-container:active .with-hover-button .cm-hover-button, + .field-data-container:focus .with-hover-button .cm-hover-button, + .field-data-container:focus-within .with-hover-button .cm-hover-button { display: flex; } /* SEARCH RESULTS */ diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index 990378c2e..0bc1305a5 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -181,10 +181,10 @@ export default { "itemId": `${item?.id}`, "email": this.filterByEmailAddressType(item?.email_addresses), "emailPretty": this.shortenString(this.constructEmailCsv(this.filterByEmailAddressType(item?.email_addresses))), - "emailLabel": this.txt['email'] + ": ", + "emailLabel": this.txt['field.email'] + ": ", "identifier": this.filterByIdentifierType(item?.identifiers), "identifierPretty": this.shortenString(this.constructIdentifierCsv(this.filterByIdentifierType(item?.identifiers))), - "identifierLabel": this.txt['Identifiers'] + ": ", + "identifierLabel": this.txt['controller.Identifiers'] + ": ", "isMember": !!item?._matchingData?.GroupMembers?.id } }) @@ -199,9 +199,9 @@ export default { } else { // The picker is stand-alone, and should render the configured page in a modal on @item-select const urlForModal = this.options.actionUrl + '&person_id=' + this.person.value; - let titleForModal = this.txt['registry.meta.registry']; + let titleForModal = this.txt['default.registry.meta.registry']; if(this.api.viewConfigParameters.groupId != undefined) { - titleForModal = this.txt['GroupMembers']; + titleForModal = this.txt['controller.GroupMembers']; } launchCmModal(urlForModal, titleForModal); } @@ -271,7 +271,7 @@ export default { return this.options.label; } // Otherwise return the default - return this.txt['autocomplete.people.label']; + return this.txt['operation.autocomplete.people.label']; } }, template: ` @@ -282,7 +282,7 @@ export default { inputClass="cm-autocomplete" :inputId="this.options.htmlId" :inputProps="this.options.inputProps" - :placeholder="this.txt['autocomplete.people.placeholder']" + :placeholder="this.txt['operation.autocomplete.people.placeholder']" panelClass="cm-autocomplete-panel" optionLabel="label" optionDisabled="isMember" @@ -302,7 +302,7 @@ export default {
- {{ this.txt['GroupMembers'] }} + {{ this.txt['controller.GroupMembers'] }}
ID: {{ slotProps.option.itemId }} @@ -340,7 +340,7 @@ export default { diff --git a/app/webroot/js/comanage/components/bulk/bulk-actions.js b/app/webroot/js/comanage/components/bulk/bulk-actions.js index 950d9b191..3360b1f2b 100644 --- a/app/webroot/js/comanage/components/bulk/bulk-actions.js +++ b/app/webroot/js/comanage/components/bulk/bulk-actions.js @@ -169,14 +169,14 @@ export default { return this.ids.length == 0 || this.selected == undefined || this.selected == '' }, failedMessage() { - return `${this.failed} ${this.txt['failed']}` + return `${this.failed} ${this.txt['result.failed']}` }, succeededMessage() { if(this.selected == 'delete') { console.log(JSON.parse(JSON.stringify(this.txt))); - return `${this.succeeded} ${this.txt['removed']}` + return `${this.succeeded} ${this.txt['result.removed']}` } - return `${this.succeeded} ${this.txt['updated']}` + return `${this.succeeded} ${this.txt['result.updated']}` } }, template: ` diff --git a/app/webroot/js/comanage/components/bulk/datatable.js b/app/webroot/js/comanage/components/bulk/datatable.js index b15065032..dc2c26da1 100644 --- a/app/webroot/js/comanage/components/bulk/datatable.js +++ b/app/webroot/js/comanage/components/bulk/datatable.js @@ -84,12 +84,12 @@ export default { DataTable }, template: ` -

{{ this.txt['report.for'] }} {{ action }}

+

{{ this.txt['information.report.for'] }} {{ action }}

- - + +
{{ this.txt['record'] }}{{ this.txt['status'] }}{{ this.txt['information.record'] }}{{ this.txt['field.status'] }}
diff --git a/app/webroot/js/comanage/components/common/actions.js b/app/webroot/js/comanage/components/common/actions.js new file mode 100644 index 000000000..b027168b6 --- /dev/null +++ b/app/webroot/js/comanage/components/common/actions.js @@ -0,0 +1,60 @@ +/** + * COmanage Registry Actions Drop-Down Component JavaScript + * + * Portions licensed to the University Corporation for Advanced Internet + * Development, Inc. ("UCAID") under one or more contributor license agreements. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * UCAID licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @link https://www.internet2.edu/comanage COmanage Project + * @package registry + * @since COmanage Registry v5.0.0 + * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +import { + constructLanguageString +} from '../utils/helpers.js'; + +export default { + props: { + actions: Object + }, + template: ` + + ` +} diff --git a/app/webroot/js/comanage/components/common/copy-value-button.js b/app/webroot/js/comanage/components/common/copy-value-button.js index 3ffb360ee..a0be72407 100644 --- a/app/webroot/js/comanage/components/common/copy-value-button.js +++ b/app/webroot/js/comanage/components/common/copy-value-button.js @@ -36,7 +36,7 @@ export default { data() { return { copyIcon: "content_copy", - ariaLabel: this.txt['copy.value'] + ariaLabel: this.txt['operation.copy.value'] } }, methods: { @@ -48,24 +48,28 @@ export default { await navigator.clipboard.writeText(valWithNormalizedSpaces); // provide feedback this.copyIcon = 'thumb_up'; - this.ariaLabel = this.txt['value.copied']; + this.ariaLabel = this.txt['information.value.copied']; // reset feedback setTimeout(() => this.copyIcon = 'content_copy', 800); - setTimeout(() => this.ariaLabel = this.txt['copy.value'], 2200); + setTimeout(() => this.ariaLabel = this.txt['operation.copy.value'], 2200); } catch($e) { // this will be rendered if browser is not on HTTPS - alert(this.txt["copy.error"] + "\n" + $e); + let msg = this.txt['error.javascript.copy']; + if(window.location.protocol != 'https:') { + msg += " " + this.txt['error.javascript.requires.https']; + } + alert(msg + "\n\n" + $e); } } }, template: ` ` } diff --git a/app/webroot/js/comanage/components/mvea/mvea-item.js b/app/webroot/js/comanage/components/mvea/mvea-item.js index ad6fba924..a3e1d1a99 100644 --- a/app/webroot/js/comanage/components/mvea/mvea-item.js +++ b/app/webroot/js/comanage/components/mvea/mvea-item.js @@ -25,6 +25,7 @@ */ import CopyValueButton from '../common/copy-value-button.js'; +import Actions from '../common/actions.js'; import { constructLanguageString } from '../utils/helpers.js'; @@ -36,6 +37,7 @@ export default { txt: Object }, components: { + Actions, CopyValueButton }, computed: { @@ -64,7 +66,7 @@ export default { template: `
  • -
  • -
    +
    - {{ this.txt.unverified }} + {{ this.txt['field.unverified'] }} {{ this.mvea.type.display_name }}
  • -
    +
    - {{ this.txt["SuspendableStatusEnum.S"] }} - + {{ this.txt['enumeration.SuspendableStatusEnum.S'] }} + {{ this.mvea.type.display_name }}
  • -
  • -
  • -
  • -
  • -