diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58f53a4b77..4e3955cbac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,8 +23,8 @@ jobs: with: java-version: "adopt@1.11" - - name: Build the docker-compose stack - run: docker-compose up -d + - name: Build the docker compose stack + run: docker compose up -d - name: Cache node modules uses: actions/cache@v2 diff --git a/Gruntfile.js b/Gruntfile.js index 7a678f4784..87c89e0e52 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -201,11 +201,19 @@ module.exports = function (grunt) { expand: true, cwd: 'node_modules/vue/dist', src: [ - 'vue.global.js', 'vue.runtime.global.js' ], dest: paths.adminJsLib }, + { + expand: true, + cwd: 'node_modules/vue-i18n/dist', + src: [ + // not using runtime 'cos we need the messages compiler + 'vue-i18n.global.js' + ], + dest: paths.adminJsLib + }, ], }, }, diff --git a/build.sbt b/build.sbt index d547e0c801..a8af504f29 100644 --- a/build.sbt +++ b/build.sbt @@ -57,8 +57,8 @@ val coreDependencies = backendDependencies ++ Seq( "com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion, // Anorm DB lib - "org.playframework.anorm" %% "anorm" % "2.6.10", - "org.playframework.anorm" %% "anorm-postgres" % "2.6.10", + "org.playframework.anorm" %% "anorm" % "2.7.0", + "org.playframework.anorm" %% "anorm-postgres" % "2.7.0", // Commons IO "commons-io" % "commons-io" % "2.5", diff --git a/conf/evolutions/default/1.sql b/conf/evolutions/default/1.sql index f9e4d76ba6..337c3327bf 100644 --- a/conf/evolutions/default/1.sql +++ b/conf/evolutions/default/1.sql @@ -358,9 +358,42 @@ CREATE INDEX coreference_value_text ON coreference_value(text); CREATE INDEX coreference_value_target_id ON coreference_value(target_id); CREATE INDEX coreference_value_set_id ON coreference_value(set_id); +CREATE TABLE entity_type_meta( + entity_type VARCHAR(50) NOT NULL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description TEXT, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP +); + +CREATE INDEX entity_type_meta_entity_type ON entity_type_meta(entity_type); + +CREATE TABLE field_meta( + entity_type VARCHAR(50) NOT NULL, + id VARCHAR(50) NOT NULL, + name VARCHAR(100) NOT NULL, + description TEXT, + usage VARCHAR(50), + category VARCHAR(50), + default_val TEXT, + see_also TEXT[], + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP, + PRIMARY KEY (entity_type, id), + CONSTRAINT field_meta_entity_type + FOREIGN KEY (entity_type) + REFERENCES entity_type_meta (entity_type) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX field_meta_entity_type ON field_meta(entity_type); +CREATE INDEX field_meta_id ON field_meta(id); # --- !Downs +DROP TABLE IF EXISTS field_meta CASCADE; +DROP TABLE IF EXISTS entity_type_meta CASCADE; DROP TABLE IF EXISTS coreference CASCADE; DROP TABLE IF EXISTS coreference_value CASCADE; DROP TABLE IF EXISTS cleanup_action_redirect CASCADE; diff --git a/conf/schema/pg.sql b/conf/schema/pg.sql deleted file mode 100644 index 59eca9824c..0000000000 --- a/conf/schema/pg.sql +++ /dev/null @@ -1,104 +0,0 @@ -CREATE TABLE users ( - id VARCHAR(50) NOT NULL PRIMARY KEY, - email VARCHAR(255) NOT NULL, - verified BOOLEAN NOT NULL DEFAULT FALSE, - staff BOOLEAN NOT NULL DEFAULT FALSE, - active BOOLEAN NOT NULL DEFAULT TRUE, - allow_messaging BOOLEAN NOT NULL DEFAULT TRUE, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - last_login TIMESTAMP NULL, - password VARCHAR(255) NULL, - is_legacy BOOLEAN NOT NULL DEFAULT FALSE -); - -CREATE UNIQUE INDEX users_email ON users (email); - -CREATE TABLE openid_association ( - id VARCHAR(50) NOT NULL, - openid_url VARCHAR(255) NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY(id, openid_url) -); - -ALTER TABLE openid_association ADD CONSTRAINT openid_association_id FOREIGN KEY (id) REFERENCES users (id) ON DELETE CASCADE; - -CREATE TABLE oauth2_association ( - id VARCHAR(50) NOT NULL, - provider_id VARCHAR(255) NOT NULL, - provider VARCHAR(255) NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY(id, provider_id, provider) -); - -ALTER TABLE oauth2_association ADD CONSTRAINT oauth2_association_id FOREIGN KEY (id) REFERENCES users (id) ON DELETE CASCADE; - -CREATE TABLE token ( - id VARCHAR(50) NOT NULL, - token VARCHAR(255) NOT NULL PRIMARY KEY, - expires TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - is_sign_up BOOLEAN NOT NULL DEFAULT FALSE -); - -ALTER TABLE token ADD CONSTRAINT token_profile_id FOREIGN KEY (id) REFERENCES users (id) ON DELETE CASCADE; - -CREATE TABLE moved_pages( - original_path_sha1 CHAR(40) PRIMARY KEY, - original_path TEXT NOT NULL, - new_path TEXT NOT NULL, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE research_guide ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - path VARCHAR(255) DEFAULT NULL, - picture VARCHAR(255) DEFAULT NULL, - virtual_unit VARCHAR(255) DEFAULT NULL, - description TEXT, - css TEXT, - active BOOLEAN NOT NULL DEFAULT TRUE, - default_page INTEGER NULL -); - -ALTER TABLE research_guide ADD CONSTRAINT research_guide_path_unique UNIQUE(path); - -CREATE TABLE research_guide_page ( - id SERIAL PRIMARY KEY, - research_guide_id INTEGER NOT NULL, - name TEXT NOT NULL, - layout VARCHAR(45) DEFAULT NULL, - content TEXT, - path VARCHAR(45) DEFAULT NULL, - position VARCHAR(45) DEFAULT NULL, - description TEXT, - params VARCHAR(255) DEFAULT NULL -); - -ALTER TABLE research_guide_page ADD CONSTRAINT research_guide_page_id FOREIGN KEY (research_guide_id) REFERENCES research_guide (id) ON DELETE CASCADE; -ALTER TABLE research_guide_page ADD CONSTRAINT research_guide_path_guide_id UNIQUE (research_guide_id, path); - -CREATE TABLE feedback ( - id CHAR(10) NOT NULL PRIMARY KEY, - user_id VARCHAR(50) DEFAULT NULL, - name VARCHAR (255) DEFAULT NULL, - email VARCHAR (255) DEFAULT NULL, - text TEXT, - type VARCHAR (10) DEFAULT NULL, - copy BOOLEAN DEFAULT FALSE, - context TEXT, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP NULL, - mode VARCHAR(10) NOT NULL -); - -CREATE TABLE cypher_queries ( - id CHAR(10) NOT NULL PRIMARY KEY , - user_id VARCHAR(50) DEFAULT NULL, - name VARCHAR(255) NOT NULL, - query TEXT NOT NULL, - description TEXT, - public BOOLEAN DEFAULT FALSE, - created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP NULL -); - diff --git a/etc/db_migrations/20240807_add_entity_meta_tables.sql b/etc/db_migrations/20240807_add_entity_meta_tables.sql new file mode 100644 index 0000000000..a16f249480 --- /dev/null +++ b/etc/db_migrations/20240807_add_entity_meta_tables.sql @@ -0,0 +1,35 @@ +BEGIN TRANSACTION; + +CREATE TABLE entity_type_meta( + entity_type VARCHAR(50) NOT NULL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description TEXT, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP +); + +CREATE INDEX entity_type_meta_entity_type ON entity_type_meta(entity_type); + +CREATE TABLE field_meta( + entity_type VARCHAR(50) NOT NULL, + id VARCHAR(50) NOT NULL, + name VARCHAR(100) NOT NULL, + description TEXT, + usage VARCHAR(50), + category VARCHAR(50), + default_val TEXT, + see_also TEXT[], + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP, + PRIMARY KEY (entity_type, id), + CONSTRAINT field_meta_entity_type + FOREIGN KEY (entity_type) + REFERENCES entity_type_meta (entity_type) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE INDEX field_meta_entity_type ON field_meta(entity_type); +CREATE INDEX field_meta_id ON field_meta(id); + +COMMIT; diff --git a/modules/admin/app/assets/css/admin/_admin.scss b/modules/admin/app/assets/css/admin/_admin.scss index c4f6d2b470..05f1991331 100644 --- a/modules/admin/app/assets/css/admin/_admin.scss +++ b/modules/admin/app/assets/css/admin/_admin.scss @@ -80,11 +80,11 @@ body > .admin-content > .flash { background-color: $gray-100; } } +} - .markdown-helper { - font-style: italic; - cursor: help; - } +.markdown-helper { + font-style: italic; + cursor: pointer; } .inline-remove > a:hover { @@ -92,6 +92,24 @@ body > .admin-content > .flash { cursor: pointer; } +.form-legend { + font-size: $font-size-base; + + dl { + display: flex; + flex-wrap: wrap; + + dt { + @include make-col(3); + font-weight: bold; + } + + dd { + @include make-col(9); + } + } +} + .address-form, .concept-description-form { background-color: $gray-100; @@ -127,8 +145,11 @@ body > .admin-content > .flash { padding-top: $margin-md; } -.add-address { +.add-address, +.add-concept +{ float: right; + margin-bottom: $margin-md; } .admin-help-notice { @@ -315,12 +336,18 @@ pre.code-format { margin-bottom: $margin-md; } -.add-description { - margin-bottom: $margin-md; - @include clearfix(); - a { - float: right; +.control-elements { + .add-inline-element:not(.btn) { + display: block; + background-color: $gray-100; + border-bottom: 1px solid darken($gray-100, 5%); + padding: 0 $margin-xs; + color: $gray-700; + } + .add-inline-element:hover:not(.btn) { + background-color: darken($gray-100, 5%); } + } .inline-element-block-controls { @@ -436,6 +463,20 @@ pre.code-format { } } +// Metadata validation +.metadata-validation { + summary { + font-size: $font-size-lg; + @extend .alert, .alert-warning; + } + .validation-errors { + @extend .alert, .alert-danger; + } + .validation-warnings { + @extend .alert, .alert-warning; + } +} + // Vue Single-page applications %expanding-row { display: flex; @@ -470,11 +511,13 @@ body.spa-app { } .app-content { + @extend %expanding-column; +} + +.app-content-inner { @include make-container(); @include make-container-max-widths(); - flex: 1; - display: flex; - flex-direction: column; + @extend %expanding-column; margin-top: $margin-md; } diff --git a/modules/admin/app/assets/css/apps.scss b/modules/admin/app/assets/css/apps.scss new file mode 100644 index 0000000000..0fc97e8a7b --- /dev/null +++ b/modules/admin/app/assets/css/apps.scss @@ -0,0 +1,85 @@ +@import "lib/docview-portal/css/portal.scss"; + +//Admin +@import "./admin/admin"; +@import "./admin/forms"; + +// Styling common to docview VueJS applications + +#app-error-notice { + position: absolute; + bottom: $margin-sm; + right: $margin-sm; + padding: $margin-xs $margin-sm; + z-index: 100; + + .close { + margin-left: $margin-xs; + margin-top: -3px; + cursor: pointer; + } +} + +.modal { + background-color: rgba(0, 0, 0, 0.1); +} + +.modal-alert { + z-index: 1000; + display: flex !important; + + .modal-content { + box-shadow: $box-shadow-lg; + } +} + +.modal-content.resizable { + min-height: 30rem; +} + +.modal-content.resizable > .modal-body { + display: flex; + flex-direction: column; + flex: 1; +} + +.modal-resize-handle { + position: absolute; + right: 0; + bottom: 0; + display: inline; + width: 1rem; + height: 1rem; + cursor: nwse-resize; +} + +.modal-resize-handle:after { + content:''; + display:block; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid $ehri-border-gray; + width: 2px; + height: 5px; + position: absolute; + pointer-events: none; + right: -1px; + bottom: 1px; + transform: rotate(135deg); +} + +.options-form { + padding: $margin-md; + input:invalid { + background-color: lighten($danger, 40%); + } +} + +.resizable .options-form { + flex: 1; +} + +.options-form .small.form-text { + color: $text-muted; +} + diff --git a/modules/admin/app/assets/css/datasets.scss b/modules/admin/app/assets/css/datasets.scss index 8f2cb30039..66546dac4d 100644 --- a/modules/admin/app/assets/css/datasets.scss +++ b/modules/admin/app/assets/css/datasets.scss @@ -1,8 +1,4 @@ -@import "lib/docview-portal/css/portal.scss"; - -//Admin -@import "./admin/admin"; -@import "./admin/forms"; +@import "apps"; $active-table-row: #e7f1ff; @@ -165,60 +161,6 @@ $active-table-row: #e7f1ff; flex: 1; justify-content: flex-end; } - -#app-error-notice { - position: absolute; - bottom: $margin-sm; - right: $margin-sm; - padding: $margin-xs $margin-sm; - z-index: 100; - - .close { - margin-left: $margin-xs; - margin-top: -3px; - cursor: pointer; - } -} - -.modal { - background-color: rgba(0, 0, 0, 0.1); -} - -.modal-content.resizable { - min-height: 30rem; -} - -.modal-content.resizable > .modal-body { - display: flex; - flex-direction: column; - flex: 1; -} - -.modal-resize-handle { - position: absolute; - right: 0; - bottom: 0; - display: inline; - width: 1rem; - height: 1rem; - cursor: nwse-resize; -} - -.modal-resize-handle:after { - content:''; - display:block; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-bottom: 5px solid $ehri-border-gray; - width: 2px; - height: 5px; - position: absolute; - pointer-events: none; - right: -1px; - bottom: 1px; - transform: rotate(135deg); -} - #stage-tabs, #dataset-manager-tabs { @@ -236,15 +178,6 @@ $active-table-row: #e7f1ff; margin-bottom: $margin-sm; } -.modal-alert { - z-index: 1000; - display: flex !important; - - .modal-content { - box-shadow: $box-shadow-lg; - } -} - .file-picker { display: flex; align-items: center; @@ -676,6 +609,7 @@ $active-table-row: #e7f1ff; // strange gaps. border-collapse: separate; border-spacing: 0; + border: none; // table doesn't need a double border, however cells do have one } // Sticky headers. Note: applying `position: sticky` to `thead` @@ -985,21 +919,6 @@ $active-table-row: #e7f1ff; background-color: $gray-100; } -.options-form { - padding: $margin-md; - input:invalid { - background-color: lighten($danger, 40%); - } -} - -.resizable .options-form { - flex: 1; -} - -.options-form .small.form-text { - color: $text-muted; -} - .data-manager-form-item { margin-bottom: $margin-xs; @include make-row(); diff --git a/modules/admin/app/assets/css/dmeditor.scss b/modules/admin/app/assets/css/dmeditor.scss new file mode 100644 index 0000000000..5614af5b12 --- /dev/null +++ b/modules/admin/app/assets/css/dmeditor.scss @@ -0,0 +1,48 @@ +@import "apps"; + +$active-table-row: #e7f1ff; + +.app-wrapper { + background-color: $white; +} + +// Make content area scrollable, this prevents +// weird behaviour with modal popups +.app-content { + overflow: auto; + flex-basis: 0; +} + +.section { + background-color: $gray-100; + font-weight: bold; +} + +.fm-list td.fm-actions { + width: 5rem; +} + +.fm-see-also strong { + margin-right: $margin-xs; +} + +.fm-see-also a + a { + display: inline-block; +} + +.fm-see-also a + a:before { + content: ", "; +} + +.fm-usage { + white-space: nowrap; +} + +.fm-list td:last-child a { + margin-left: $margin-xs; +} + +#delete-metadata { + margin-right: auto; +} + diff --git a/modules/admin/app/assets/css/vocabeditor.scss b/modules/admin/app/assets/css/vocabeditor.scss index 09301ed996..0c92eb8d4f 100644 --- a/modules/admin/app/assets/css/vocabeditor.scss +++ b/modules/admin/app/assets/css/vocabeditor.scss @@ -3,6 +3,7 @@ //Admin @import "./admin/admin"; @import "./admin/forms"; +//@import "apps"; // import this after compat check .footer-buttons { margin-top: $margin-sm; diff --git a/modules/admin/app/assets/js/admin.js b/modules/admin/app/assets/js/admin.js index 350c6c4d38..c8cb3b104f 100644 --- a/modules/admin/app/assets/js/admin.js +++ b/modules/admin/app/assets/js/admin.js @@ -188,7 +188,7 @@ jQuery(function ($) { event.preventDefault(); var container = $(event.target).closest(".inline-formset"); - var set = container.children(".inline-element-list"); + var set = container.children(".control-elements").children(".inline-element-list").first(); var prefix = container.data("prefix"); if (!prefix) { throw "No prefix found for formset"; @@ -212,6 +212,10 @@ jQuery(function ($) { // And a help popover addPopover($elem.find("textarea,input")); + + // Focus the first input + $elem.find("input,textarea").first().focus(); + }).on("click", ".remove-inline-element", function (event) { event.preventDefault(); $(this) diff --git a/modules/admin/app/assets/js/datasets/app.vue b/modules/admin/app/assets/js/datasets/app.vue index 5f73a1d23f..2cc4bd6175 100644 --- a/modules/admin/app/assets/js/datasets/app.vue +++ b/modules/admin/app/assets/js/datasets/app.vue @@ -45,7 +45,7 @@ export default {