mensa 0.6.10 → 0.6.11
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.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer-lock.json +34 -0
- data/.devcontainer/devcontainer.json +16 -19
- data/.devcontainer/docker-compose.yml +35 -0
- data/.devcontainer/postStart.sh +9 -5
- data/.prettierrc.json +3 -0
- data/.vscode/settings.json +8 -0
- data/.vscode/tasks.json +47 -0
- data/Gemfile.lock +1 -1
- data/app/components/mensa/header/component.html.erb +1 -1
- data/app/components/mensa/view/paging.css +40 -40
- data/app/tables/mensa/base.rb +16 -0
- data/app/tables/mensa/column.rb +22 -0
- data/app/tables/mensa/filter.rb +37 -2
- data/app/tables/mensa/row.rb +18 -1
- data/app/tables/mensa/scope.rb +37 -1
- data/config/locales/en.yml +1 -0
- data/config/locales/nl.yml +1 -0
- data/lib/generators/mensa/templates/config/initializers/mensa.rb +6 -0
- data/lib/mensa/configuration.rb +6 -0
- data/lib/mensa/version.rb +1 -1
- metadata +7 -4
- data/.devcontainer/compose.yaml +0 -49
- data/.devcontainer/postCreate.sh +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 595e1c13d2966b2cf3991e4f145bf408655431ca50a388d47c5fc8532d534635
|
|
4
|
+
data.tar.gz: 06a44600cbf0f6cdf14f0cdc8dfd3e4cb31ca0749b0cfbbb9017295bcea6ec9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7873d0830a89a852aae065431b35312ab71d00284330bf6b20a7aacf64250f3480c28478955a86b6c5e90b2882be7d1de3d3df1ab3e0a8ee16e04834ddb9f258
|
|
7
|
+
data.tar.gz: f50ca9748a2fe082d257d79339f2408bd36dfca6bbd4dda32b184fefc5beee6570280eb1f289028d4c36ed7586a5cd6f56d80b87476992800621a68cdaa67adf
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"features": {
|
|
3
|
+
"ghcr.io/devcontainers/features/common-utils:2": {
|
|
4
|
+
"version": "2.5.9",
|
|
5
|
+
"resolved": "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a",
|
|
6
|
+
"integrity": "sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a"
|
|
7
|
+
},
|
|
8
|
+
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
|
9
|
+
"version": "2.17.0",
|
|
10
|
+
"resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c",
|
|
11
|
+
"integrity": "sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c"
|
|
12
|
+
},
|
|
13
|
+
"ghcr.io/devcontainers/features/github-cli:1": {
|
|
14
|
+
"version": "1.1.0",
|
|
15
|
+
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
|
|
16
|
+
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
|
|
17
|
+
},
|
|
18
|
+
"ghcr.io/devcontainers/features/node:2": {
|
|
19
|
+
"version": "2.1.0",
|
|
20
|
+
"resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857",
|
|
21
|
+
"integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857"
|
|
22
|
+
},
|
|
23
|
+
"ghcr.io/rails/devcontainer/features/activestorage": {
|
|
24
|
+
"version": "1.1.1",
|
|
25
|
+
"resolved": "ghcr.io/rails/devcontainer/features/activestorage@sha256:7fa8fff898ac33076ebf65631d3c6c902dc9bad87de3e5dfa645f3e2d7a35c07",
|
|
26
|
+
"integrity": "sha256:7fa8fff898ac33076ebf65631d3c6c902dc9bad87de3e5dfa645f3e2d7a35c07"
|
|
27
|
+
},
|
|
28
|
+
"ghcr.io/rails/devcontainer/features/postgres-client": {
|
|
29
|
+
"version": "1.2.0",
|
|
30
|
+
"resolved": "ghcr.io/rails/devcontainer/features/postgres-client@sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050",
|
|
31
|
+
"integrity": "sha256:7e6b118646d6e0d82a9ec06e81ad1b5406f7042f0279d35fff3a7a612be7a050"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,37 +1,34 @@
|
|
|
1
1
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
|
2
|
-
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
|
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby-rails-postgres
|
|
3
3
|
{
|
|
4
4
|
"name": "mensa",
|
|
5
|
-
"dockerComposeFile": "compose.
|
|
5
|
+
"dockerComposeFile": "docker-compose.yml",
|
|
6
6
|
"service": "app",
|
|
7
|
-
"workspaceFolder": "/workspaces/mensa",
|
|
8
|
-
// Features to add to the dev container. More info: https://containers.dev/features.
|
|
9
7
|
"features": {
|
|
10
|
-
"ghcr.io/devcontainers/features/
|
|
8
|
+
"ghcr.io/devcontainers/features/node:2": {},
|
|
9
|
+
"ghcr.io/devcontainers/features/common-utils:2": {
|
|
10
|
+
"configureZshAsDefaultShell": true,
|
|
11
|
+
"installOhMyZsh": false,
|
|
12
|
+
"installOhMyZshConfig": false
|
|
13
|
+
},
|
|
11
14
|
"ghcr.io/rails/devcontainer/features/activestorage": {},
|
|
12
15
|
"ghcr.io/rails/devcontainer/features/postgres-client": {
|
|
13
|
-
"version": "
|
|
16
|
+
"version": "18"
|
|
14
17
|
},
|
|
15
|
-
"ghcr.io/devcontainers/features/
|
|
16
|
-
"ghcr.io/
|
|
18
|
+
"ghcr.io/devcontainers/features/github-cli:1": {},
|
|
19
|
+
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
|
20
|
+
"moby": false
|
|
21
|
+
}
|
|
17
22
|
},
|
|
18
23
|
"mounts": [
|
|
19
|
-
"source=/run/host-services/ssh-auth.sock,target=/ssh-agent,type=bind",
|
|
24
|
+
"source=/run/host-services/ssh-auth.sock,target=/ssh-agent,type=bind,consistency=cached",
|
|
20
25
|
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached"
|
|
21
26
|
],
|
|
22
|
-
"postCreateCommand": ".devcontainer/postCreate.sh",
|
|
23
27
|
"postStartCommand": ".devcontainer/postStart.sh",
|
|
24
28
|
"containerEnv": {
|
|
25
|
-
"CAPYBARA_SERVER_PORT": "45678",
|
|
26
|
-
"SELENIUM_URL": "http://host.docker.internal:4444/",
|
|
27
29
|
"VISUAL": "vi",
|
|
28
|
-
"DB_HOST": "postgres",
|
|
29
30
|
"SSH_AUTH_SOCK": "/ssh-agent"
|
|
30
31
|
},
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
"RUBY_VERSION": "3.4.7"
|
|
34
|
-
},
|
|
35
|
-
"dockerfile": "./devcontainer/Dockerfile"
|
|
36
|
-
}
|
|
32
|
+
"customizations": {},
|
|
33
|
+
"workspaceFolder": "/workspaces/mensa"
|
|
37
34
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: mensa
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
app:
|
|
5
|
+
image: ghcr.io/rails/devcontainer/images/ruby:4.0.5
|
|
6
|
+
|
|
7
|
+
volumes:
|
|
8
|
+
- ../..:/workspaces
|
|
9
|
+
|
|
10
|
+
# Overrides default command so things don't shut down after the process ends.
|
|
11
|
+
command: sleep infinity
|
|
12
|
+
|
|
13
|
+
depends_on:
|
|
14
|
+
- postgres
|
|
15
|
+
- redis
|
|
16
|
+
|
|
17
|
+
postgres:
|
|
18
|
+
image: postgres:18
|
|
19
|
+
restart: unless-stopped
|
|
20
|
+
volumes:
|
|
21
|
+
- postgres-data:/var/lib/postgresql/18/docker
|
|
22
|
+
environment:
|
|
23
|
+
POSTGRES_USER: postgres
|
|
24
|
+
POSTGRES_PASSWORD: postgres
|
|
25
|
+
|
|
26
|
+
redis:
|
|
27
|
+
image: valkey/valkey:8
|
|
28
|
+
restart: unless-stopped
|
|
29
|
+
|
|
30
|
+
selenium:
|
|
31
|
+
image: selenium/standalone-firefox
|
|
32
|
+
restart: unless-stopped
|
|
33
|
+
|
|
34
|
+
volumes:
|
|
35
|
+
postgres-data:
|
data/.devcontainer/postStart.sh
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
|
-
MISE
|
|
5
|
-
eval "$($MISE activate bash)"
|
|
4
|
+
MISE="$HOME/.local/bin/mise"
|
|
5
|
+
eval "$("$MISE" activate bash)"
|
|
6
|
+
"$MISE" trust
|
|
7
|
+
"$MISE" install
|
|
8
|
+
sudo chmod 666 /ssh-agent
|
|
6
9
|
|
|
7
|
-
npm install
|
|
8
|
-
bundle install
|
|
9
|
-
rake db:prepare
|
|
10
|
+
"$MISE" exec -- npm install
|
|
11
|
+
"$MISE" exec -- bundle install
|
|
12
|
+
"$MISE" exec --cd test/dummy -- rake db:prepare
|
|
13
|
+
"$MISE" exec --cd test/dummy -- rake tailwindcss:config
|
data/.prettierrc.json
ADDED
data/.vscode/tasks.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
3
|
+
// for the documentation about the tasks.json format
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"tasks": [
|
|
6
|
+
{
|
|
7
|
+
"label": "server",
|
|
8
|
+
"type": "shell",
|
|
9
|
+
"command": "if [ -f tmp/pids/server.pid ]; then pid=$(cat tmp/pids/server.pid); kill \"$pid\" 2>/dev/null || true; rm -f tmp/pids/server.pid; fi; mise exec -- bin/rails server -p 3000 -b 0.0.0.0",
|
|
10
|
+
"presentation": {
|
|
11
|
+
"reveal": "always",
|
|
12
|
+
"panel": "new"
|
|
13
|
+
},
|
|
14
|
+
"isBackground": true,
|
|
15
|
+
"problemMatcher": [],
|
|
16
|
+
"icon": {
|
|
17
|
+
"id": "server"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"label": "css",
|
|
22
|
+
"type": "shell",
|
|
23
|
+
"command": "cd test/dummy && mise exec -- bin/rails tailwindcss:config && mise exec -- bin/rails tailwindcss:watch",
|
|
24
|
+
"presentation": {
|
|
25
|
+
"reveal": "always",
|
|
26
|
+
"panel": "new"
|
|
27
|
+
},
|
|
28
|
+
"isBackground": true,
|
|
29
|
+
"problemMatcher": [],
|
|
30
|
+
"icon": {
|
|
31
|
+
"id": "json"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"label": "start all",
|
|
36
|
+
"dependsOn": [
|
|
37
|
+
"server",
|
|
38
|
+
"css"
|
|
39
|
+
],
|
|
40
|
+
"problemMatcher": [],
|
|
41
|
+
"presentation": {
|
|
42
|
+
"reveal": "always",
|
|
43
|
+
"panel": "new"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
data/Gemfile.lock
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<% if column.sortable? %>
|
|
3
3
|
<%= link_to table.path(order: {column.name => column.next_sort_direction}, turbo_frame_id: table.table_id), "data-turbo-frame": "_self", class: "order cursor-pointer" do %>
|
|
4
4
|
<span><%= column.human_name %></span>
|
|
5
|
-
<i class="<%= Mensa.config.icons[
|
|
5
|
+
<i class="<%= Mensa.config.icons[column.sort_indicator_icon_key.to_sym] %>"></i>
|
|
6
6
|
<% end %>
|
|
7
7
|
<% else %>
|
|
8
8
|
<div class="container">
|
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
.mensa-table {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
2
|
+
.paging {
|
|
3
|
+
@apply flex items-center justify-between border-t border-gray-100 dark:border-gray-700 bg-white dark:bg-gray-800 px-4 py-3 sm:px-6 text-sm;
|
|
4
|
+
|
|
5
|
+
.pagy {
|
|
6
|
+
@apply isolate inline-flex -space-x-px text-gray-500 dark:text-gray-400;
|
|
7
|
+
|
|
8
|
+
a:not(.gap) {
|
|
9
|
+
@apply relative inline-flex items-center px-4 py-1.5 text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:z-20 no-underline;
|
|
10
|
+
|
|
11
|
+
&:first-child {
|
|
12
|
+
border-top-left-radius: 4px;
|
|
13
|
+
border-bottom-left-radius: 4px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&:last-child {
|
|
17
|
+
border-top-right-radius: 4px;
|
|
18
|
+
border-bottom-right-radius: 4px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&:hover {
|
|
22
|
+
@apply bg-gray-300 dark:bg-gray-600;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&:not([href]) {
|
|
26
|
+
/* disabled links */
|
|
27
|
+
@apply bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 hover:dark:bg-gray-800 text-gray-700 dark:text-gray-100 text-opacity-25 dark:text-opacity-50 cursor-default;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&[aria-current="page"] {
|
|
31
|
+
@apply z-10 bg-primary-200 dark:bg-primary-800 hover:bg-primary-200 hover:dark:bg-primary-800 text-gray-900 dark:text-white text-opacity-100 dark:text-opacity-100 border-none font-semibold;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
label {
|
|
36
|
+
@apply inline-block whitespace-nowrap bg-gray-200 dark:bg-gray-600 rounded-lg px-4 py-1.5;
|
|
37
|
+
|
|
38
|
+
input {
|
|
39
|
+
@apply bg-gray-100 dark:bg-gray-700 border-none rounded-md;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
40
42
|
}
|
|
41
|
-
}
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
44
|
}
|
data/app/tables/mensa/base.rb
CHANGED
|
@@ -200,6 +200,22 @@ module Mensa
|
|
|
200
200
|
config[:turbo_frame_id]
|
|
201
201
|
end
|
|
202
202
|
|
|
203
|
+
# Minimal state needed to reconstruct this table on another page (for
|
|
204
|
+
# next_record/previous_record navigation).
|
|
205
|
+
def navigation_context
|
|
206
|
+
context = {}
|
|
207
|
+
|
|
208
|
+
context[:params] = params if params.present?
|
|
209
|
+
context[:query] = current_query if config.key?(:query) && !current_query.nil?
|
|
210
|
+
context[:order] = current_order if current_order.present?
|
|
211
|
+
context[:filters] = config[:filters] if config[:filters].present?
|
|
212
|
+
context[:table_view_id] = current_table_view_id if config.key?(:table_view_id) && !current_table_view_id.nil?
|
|
213
|
+
context[:column_order] = current_column_order if config.key?(:column_order) && !current_column_order.nil?
|
|
214
|
+
context[:hidden_columns] = current_hidden_columns if config.key?(:hidden_columns) && !current_hidden_columns.nil?
|
|
215
|
+
|
|
216
|
+
context
|
|
217
|
+
end
|
|
218
|
+
|
|
203
219
|
private
|
|
204
220
|
|
|
205
221
|
def ensure_internal_columns_for_joined_associations
|
data/app/tables/mensa/column.rb
CHANGED
|
@@ -100,5 +100,27 @@ module Mensa
|
|
|
100
100
|
name.to_s.humanize
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
|
+
|
|
104
|
+
# Determines the sort indicator icon key based on column type
|
|
105
|
+
def sort_indicator_icon_key
|
|
106
|
+
numeric_types = [:integer, :decimal, :float, :bigint]
|
|
107
|
+
date_types = [:date, :datetime, :time, :timestamp]
|
|
108
|
+
text_types = [:string, :text]
|
|
109
|
+
|
|
110
|
+
column_type = type
|
|
111
|
+
direction = sort_direction.to_s.presence || ""
|
|
112
|
+
direction_suffix = direction.present? ? "_#{direction}" : ""
|
|
113
|
+
|
|
114
|
+
case column_type
|
|
115
|
+
when *numeric_types
|
|
116
|
+
"header_order_indicator_numeric#{direction_suffix}"
|
|
117
|
+
when *date_types
|
|
118
|
+
"header_order_indicator_date#{direction_suffix}"
|
|
119
|
+
when *text_types
|
|
120
|
+
"header_order_indicator_text#{direction_suffix}"
|
|
121
|
+
else
|
|
122
|
+
"header_order_indicator#{direction_suffix}"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
103
125
|
end
|
|
104
126
|
end
|
data/app/tables/mensa/filter.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Mensa
|
|
|
25
25
|
[:gteq, I18n.t("mensa.operators.gteq"), true],
|
|
26
26
|
[:lt, I18n.t("mensa.operators.lt"), true],
|
|
27
27
|
[:lteq, I18n.t("mensa.operators.lteq"), true],
|
|
28
|
+
[:is_duplicate, I18n.t("mensa.operators.is_duplicate"), false],
|
|
28
29
|
[:is_current, I18n.t("mensa.operators.is_current"), false],
|
|
29
30
|
[:is_empty, I18n.t("mensa.operators.is_empty"), false],
|
|
30
31
|
[:isnt_empty, I18n.t("mensa.operators.isnt_empty"), false]
|
|
@@ -36,6 +37,7 @@ module Mensa
|
|
|
36
37
|
@column = column
|
|
37
38
|
@config = self.class.definition.merge(config || {})
|
|
38
39
|
@table = table
|
|
40
|
+
validate_operators!
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def multiple?
|
|
@@ -80,6 +82,8 @@ module Mensa
|
|
|
80
82
|
if scope
|
|
81
83
|
record_scope.instance_exec(normalize(value), &scope)
|
|
82
84
|
else
|
|
85
|
+
return apply_duplicate_filter(record_scope) if operator == :is_duplicate
|
|
86
|
+
|
|
83
87
|
query, hash = query_and_hash_for_operator
|
|
84
88
|
record_scope = (column.filter.having? ? record_scope.having(query, hash) : record_scope.where(query, hash)) if query.present?
|
|
85
89
|
record_scope
|
|
@@ -130,8 +134,7 @@ module Mensa
|
|
|
130
134
|
when :lteq
|
|
131
135
|
":column <= :value"
|
|
132
136
|
else
|
|
133
|
-
#
|
|
134
|
-
nil
|
|
137
|
+
raise ArgumentError, "Unknown filter operator #{operator.inspect} for column :#{column.name}"
|
|
135
138
|
end
|
|
136
139
|
[query, hash]
|
|
137
140
|
end
|
|
@@ -159,6 +162,23 @@ module Mensa
|
|
|
159
162
|
|
|
160
163
|
private
|
|
161
164
|
|
|
165
|
+
def validate_operators!
|
|
166
|
+
valid_keys = Mensa::Filter.OPERATORS.map(&:first)
|
|
167
|
+
|
|
168
|
+
op = @config[:operator]
|
|
169
|
+
if op.present? && valid_keys.exclude?(op.to_sym)
|
|
170
|
+
raise ArgumentError, "Unknown filter operator #{op.inspect} configured for column :#{column.name}. " \
|
|
171
|
+
"Valid operators are: #{valid_keys.join(", ")}"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
allowed_ops = Array(@config[:operators])
|
|
175
|
+
invalid = allowed_ops.map(&:to_sym) - valid_keys
|
|
176
|
+
if invalid.any?
|
|
177
|
+
raise ArgumentError, "Unknown filter operator(s) #{invalid.map(&:inspect).join(", ")} configured for column :#{column.name}. " \
|
|
178
|
+
"Valid operators are: #{valid_keys.join(", ")}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
162
182
|
def label_for_value(selected_value, options)
|
|
163
183
|
option = options.find { |_label, value| value.to_s == selected_value.to_s }
|
|
164
184
|
option ? option.first : selected_value
|
|
@@ -173,6 +193,21 @@ module Mensa
|
|
|
173
193
|
end
|
|
174
194
|
end
|
|
175
195
|
|
|
196
|
+
def apply_duplicate_filter(record_scope)
|
|
197
|
+
column_sql = column.attribute_for_condition.to_s
|
|
198
|
+
base_scope = record_scope.except(:select, :order, :limit, :offset)
|
|
199
|
+
|
|
200
|
+
duplicate_values_scope = base_scope
|
|
201
|
+
.where("#{column_sql} IS NOT NULL")
|
|
202
|
+
.group(column_sql)
|
|
203
|
+
.having("COUNT(*) > 1")
|
|
204
|
+
.select(Arel.sql(column_sql))
|
|
205
|
+
|
|
206
|
+
record_scope
|
|
207
|
+
.where("#{column_sql} IS NOT NULL")
|
|
208
|
+
.where("#{column_sql} IN (#{duplicate_values_scope.to_sql})")
|
|
209
|
+
end
|
|
210
|
+
|
|
176
211
|
# Unused at the moment
|
|
177
212
|
def normalize(query)
|
|
178
213
|
query
|
data/app/tables/mensa/row.rb
CHANGED
|
@@ -30,7 +30,24 @@ module Mensa
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def link
|
|
33
|
-
|
|
33
|
+
return unless table.link
|
|
34
|
+
|
|
35
|
+
append_navigation_context(table.original_view_context.instance_exec(record, &table.link))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def append_navigation_context(url)
|
|
41
|
+
return url if url.blank?
|
|
42
|
+
|
|
43
|
+
uri = URI.parse(url)
|
|
44
|
+
existing_query = Rack::Utils.parse_nested_query(uri.query)
|
|
45
|
+
merged_query = table.navigation_context.deep_stringify_keys.deep_merge(existing_query)
|
|
46
|
+
query_string = merged_query.to_query
|
|
47
|
+
uri.query = query_string.presence
|
|
48
|
+
uri.to_s
|
|
49
|
+
rescue URI::InvalidURIError
|
|
50
|
+
url
|
|
34
51
|
end
|
|
35
52
|
end
|
|
36
53
|
end
|
data/app/tables/mensa/scope.rb
CHANGED
|
@@ -62,8 +62,41 @@ module Mensa
|
|
|
62
62
|
@pagy_details
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# Returns the next record in the fully filtered/ordered result set.
|
|
66
|
+
# Pagination is intentionally ignored.
|
|
67
|
+
def next_record(record)
|
|
68
|
+
adjacent_record(record, step: 1)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns the previous record in the fully filtered/ordered result set.
|
|
72
|
+
# Pagination is intentionally ignored.
|
|
73
|
+
def previous_record(record)
|
|
74
|
+
adjacent_record(record, step: -1)
|
|
75
|
+
end
|
|
76
|
+
|
|
65
77
|
private
|
|
66
78
|
|
|
79
|
+
def adjacent_record(record, step:)
|
|
80
|
+
records = ordered_scope.to_a
|
|
81
|
+
index = records.find_index { |candidate| same_record?(candidate, record) }
|
|
82
|
+
return if index.nil?
|
|
83
|
+
|
|
84
|
+
adjacent_index = index + step
|
|
85
|
+
return if adjacent_index.negative? || adjacent_index >= records.length
|
|
86
|
+
|
|
87
|
+
records[adjacent_index]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def same_record?(left, right)
|
|
91
|
+
return false if left.nil? || right.nil?
|
|
92
|
+
|
|
93
|
+
if left.respond_to?(:id) && right.respond_to?(:id)
|
|
94
|
+
left.id == right.id
|
|
95
|
+
else
|
|
96
|
+
left == right
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
67
100
|
def pagy_object
|
|
68
101
|
return if @pagy_details && @records
|
|
69
102
|
|
|
@@ -76,7 +109,10 @@ module Mensa
|
|
|
76
109
|
def effective_order
|
|
77
110
|
result = current_order_provided? ? (current_order || {}) : (config[:order] || {})
|
|
78
111
|
result = result.symbolize_keys.compact_blank.transform_values(&:to_sym)
|
|
79
|
-
result.
|
|
112
|
+
result.filter_map { |k, v|
|
|
113
|
+
attribute = column(k)&.attribute_for_condition || k
|
|
114
|
+
"#{attribute} #{v} NULLS LAST"
|
|
115
|
+
}.join(", ")
|
|
80
116
|
end
|
|
81
117
|
|
|
82
118
|
# Builds an order hash for URL generation. Merges current order with overrides;
|
data/config/locales/en.yml
CHANGED
data/config/locales/nl.yml
CHANGED
|
@@ -26,6 +26,12 @@ Mensa.setup do |config|
|
|
|
26
26
|
filter_pill_list_clear: "fa-solid fa-xmark",
|
|
27
27
|
header_order_indicator_asc: "fa-solid fa-arrow-up",
|
|
28
28
|
header_order_indicator_desc: "fa-solid fa-arrow-down",
|
|
29
|
+
header_order_indicator_numeric_asc: "fa-solid fa-arrow-down-1-9",
|
|
30
|
+
header_order_indicator_numeric_desc: "fa-solid fa-arrow-down-9-1",
|
|
31
|
+
header_order_indicator_date_asc: "fa-solid fa-arrow-down-1-9",
|
|
32
|
+
header_order_indicator_date_desc: "fa-solid fa-arrow-down-9-1",
|
|
33
|
+
header_order_indicator_text_asc: "fa-solid fa-arrow-down-a-z",
|
|
34
|
+
header_order_indicator_text_desc: "fa-solid fa-arrow-down-z-a",
|
|
29
35
|
action_column_header: "fa-solid fa-ellipsis",
|
|
30
36
|
views_trigger: "fa-solid fa-sort",
|
|
31
37
|
views_option_selected: "fa-solid fa-check",
|
data/lib/mensa/configuration.rb
CHANGED
|
@@ -59,6 +59,12 @@ module Mensa
|
|
|
59
59
|
filter_pill_list_clear: "fa-solid fa-xmark",
|
|
60
60
|
header_order_indicator_asc: "fa-solid fa-sm fa-arrow-up",
|
|
61
61
|
header_order_indicator_desc: "fa-solid fa-sm fa-arrow-down",
|
|
62
|
+
header_order_indicator_numeric_asc: "fa-solid fa-sm fa-arrow-down-1-9",
|
|
63
|
+
header_order_indicator_numeric_desc: "fa-solid fa-sm fa-arrow-down-9-1",
|
|
64
|
+
header_order_indicator_date_asc: "fa-solid fa-sm fa-arrow-down-1-9",
|
|
65
|
+
header_order_indicator_date_desc: "fa-solid fa-sm fa-arrow-down-9-1",
|
|
66
|
+
header_order_indicator_text_asc: "fa-solid fa-sm fa-arrow-down-a-z",
|
|
67
|
+
header_order_indicator_text_desc: "fa-solid fa-sm fa-arrow-down-z-a",
|
|
62
68
|
action_column_header: "fa-solid fa-ellipsis",
|
|
63
69
|
views_trigger: "fa-solid fa-sort",
|
|
64
70
|
views_option_selected: "fa-solid fa-check",
|
data/lib/mensa/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mensa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom de Grunt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -159,14 +159,17 @@ extensions: []
|
|
|
159
159
|
extra_rdoc_files: []
|
|
160
160
|
files:
|
|
161
161
|
- ".devcontainer/Dockerfile"
|
|
162
|
-
- ".devcontainer/
|
|
162
|
+
- ".devcontainer/devcontainer-lock.json"
|
|
163
163
|
- ".devcontainer/devcontainer.json"
|
|
164
|
-
- ".devcontainer/
|
|
164
|
+
- ".devcontainer/docker-compose.yml"
|
|
165
165
|
- ".devcontainer/postStart.sh"
|
|
166
166
|
- ".gem_release.yml"
|
|
167
167
|
- ".github/workflows/gem-push.yml"
|
|
168
168
|
- ".gitignore"
|
|
169
|
+
- ".prettierrc.json"
|
|
169
170
|
- ".rubocop.yml"
|
|
171
|
+
- ".vscode/settings.json"
|
|
172
|
+
- ".vscode/tasks.json"
|
|
170
173
|
- ".zed/tasks.json"
|
|
171
174
|
- CHANGELOG.md
|
|
172
175
|
- Gemfile
|
data/.devcontainer/compose.yaml
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
name: "mensa"
|
|
2
|
-
|
|
3
|
-
services:
|
|
4
|
-
app:
|
|
5
|
-
cpu_count: 2
|
|
6
|
-
build:
|
|
7
|
-
context: ..
|
|
8
|
-
dockerfile: .devcontainer/Dockerfile
|
|
9
|
-
|
|
10
|
-
volumes:
|
|
11
|
-
- ../..:/workspaces:cached
|
|
12
|
-
# Overrides default command so things don't shut down after the process ends.
|
|
13
|
-
command: sleep infinity
|
|
14
|
-
|
|
15
|
-
# Uncomment the next line to use a non-root user for all processes.
|
|
16
|
-
# user: vscode
|
|
17
|
-
|
|
18
|
-
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
|
|
19
|
-
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
|
20
|
-
depends_on:
|
|
21
|
-
- selenium
|
|
22
|
-
- postgres
|
|
23
|
-
- redis
|
|
24
|
-
|
|
25
|
-
environment:
|
|
26
|
-
- DATABASE_URL=postgres://postgres:postgres@postgres:5432
|
|
27
|
-
- RUBY_YJIT_ENABLE=1
|
|
28
|
-
|
|
29
|
-
selenium:
|
|
30
|
-
image: selenium/standalone-firefox
|
|
31
|
-
restart: unless-stopped
|
|
32
|
-
|
|
33
|
-
redis:
|
|
34
|
-
image: redis:6.2
|
|
35
|
-
restart: unless-stopped
|
|
36
|
-
|
|
37
|
-
postgres:
|
|
38
|
-
image: postgres:17.0
|
|
39
|
-
restart: unless-stopped
|
|
40
|
-
networks:
|
|
41
|
-
- default
|
|
42
|
-
volumes:
|
|
43
|
-
- postgres-data:/var/lib/postgresql/data
|
|
44
|
-
environment:
|
|
45
|
-
POSTGRES_USER: postgres
|
|
46
|
-
POSTGRES_PASSWORD: postgres
|
|
47
|
-
|
|
48
|
-
volumes:
|
|
49
|
-
postgres-data:
|