thema chooser
authorRadek Czajka <rczajka@rczajka.pl>
Fri, 28 Jul 2023 14:12:33 +0000 (16:12 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Fri, 28 Jul 2023 14:12:33 +0000 (16:12 +0200)
src/catalogue/urls.py
src/catalogue/views.py
src/redakcja/settings/__init__.py
src/redakcja/static/css/dialogs.css [deleted file]
src/redakcja/static/css/dialogs.scss [new file with mode: 0644]
src/redakcja/static/js/wiki/view_properties.js
src/wiki/templates/wiki/document_details.html
src/wiki/templates/wiki/meta_chooser.html [new file with mode: 0644]
src/wlxml/views.py

index d512eea..e0b1a50 100644 (file)
@@ -23,6 +23,9 @@ urlpatterns = [
 
     path('terms/editor/', views.EditorTerms.as_view()),
 
+    path('chooser/thema/', views.ThemaChooser.as_view()),
+    path('chooser/thema-main/', views.MainThemaChooser.as_view()),
+
     path('wikidata/<slug:model>/<qid>', views.WikidataView.as_view()),
 
     path('publish/author/<int:pk>/', views.publish_author, name='catalogue_publish_author'),
index 44ad305..697a5ac 100644 (file)
@@ -92,7 +92,7 @@ class BookAPIView(RetrieveAPIView):
                 'original_year',
                 'pd_year',
             ]
-    
+
 
 class TermSearchFilter(SearchFilter):
     search_param = 'term'
@@ -135,7 +135,7 @@ class EditorTerms(Terms):
 
         def get_label(self, obj):
             return f'{obj.last_name}, {obj.first_name}'
-    
+
 class BookTitleTerms(Terms):
     queryset = models.Book.objects.all()
     search_fields = ['title', 'slug']
@@ -146,7 +146,7 @@ class BookTitleTerms(Terms):
 class WLURITerms(Terms):
     queryset = models.Book.objects.all()
     search_fields = ['title', 'slug']
-    
+
     class serializer_class(serializers.Serializer):
         label = serializers.CharField(source='wluri')
 
@@ -163,6 +163,78 @@ class MainThemaTerms(ThemaTerms):
     queryset = models.Thema.objects.filter(usable=True, hidden=False, usable_as_main=True)
 
 
+
+class Chooser(APIView):
+    def get(self, request):
+        return Response([{
+            'value': 'x',
+            'name': 'name',
+            'description': 'desc',
+            'sub': [
+                {
+                    'value': 'y',
+                    'name': 'name y',
+                    'description': 'desc y',
+                }
+            ]
+        }])
+
+
+class ThemaChooser(Chooser):
+    queryset = models.Thema.objects.filter(usable=True, hidden=False)
+
+    def get(self, request):
+        tree = {}
+
+        def getitem(code):
+            if len(code) == 1:
+                parent = tree
+            else:
+                parent = getitem(code[:-1]).setdefault('sub', {})
+            return parent.setdefault(code, {})
+
+        def getmissing(t):
+            for k, v in t.items():
+                if 'name' not in v:
+                    yield k
+                if 'sub' in v:
+                    for c in getmissing(v['sub']):
+                        yield c
+
+        def populate(thema):
+            item = getitem(thema.code)
+            item['usable'] = thema.usable
+            item['hidden'] = thema.hidden
+            item['name'] = thema.name
+            item['description'] = thema.description
+
+        def order(tree):
+            res = []
+            for k, v in tree.items():
+                v.update(value=k)
+                if 'sub' in v:
+                    v['sub'] = order(v['sub'])
+                res.append(v)
+            while len(res) == 1 and 'name' not in res[0] and 'sub' in res[0]:
+                res = res[0]['sub']
+            return res
+
+        for thema in self.queryset.all():
+            populate(thema)
+
+        missing = list(getmissing(tree))
+        for thema in models.Thema.objects.filter(code__in=missing):
+            populate(thema)
+
+        tree = order(tree)
+
+        return Response(tree)
+
+
+class MainThemaChooser(ThemaChooser):
+    queryset = models.Thema.objects.filter(usable=True, hidden=False, usable_as_main=True)[:1000]
+
+
 class WikidataView(APIView):
     permission_classes = [IsAdminUser]
 
@@ -218,7 +290,7 @@ class WikidataView(APIView):
                 else:
                     d[fieldname] = localize_input(d[fieldname])
         return Response(d)
-    
+
     def get(self, request, model, qid):
         return self.get_object(model, qid, save=False)
 
index ce3289b..61a80c8 100644 (file)
@@ -153,7 +153,7 @@ PIPELINE = {
                 'css/history.css',
                 'css/summary.css',
                 'css/imgareaselect-default.css',
-                'css/dialogs.css',
+                'css/dialogs.scss',
 
                 'wiki/scss/splitter.scss',
                 'wiki/scss/visual.scss'
diff --git a/src/redakcja/static/css/dialogs.css b/src/redakcja/static/css/dialogs.css
deleted file mode 100644 (file)
index 1032b13..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-.dialog {
-       display: none;
-       padding: 5px;
-       text-align: left;
-       cursor: auto;
-       background-color: #E1E1E1; /* #e0ffb8; */
-}
-
-.dialog .help_text {
-       font-size: 11px;
-       color: #2e3536;
-}
-
-.dialog .action_area {
-       padding: 1em 0.5em 0.5em;
-       border-top: 1px solid black;
-       margin-top: 0.5em;
-}
-
-.dialog p {
-       margin: 0.5em;
-}
-
-*[data-ui-error-for] {
-       color: red;
-       font-weight: bold;
-}
-
-#save_dialog textarea, #revert_dialog textarea {
-       width: 90%;
-       margin: 0.2em 4%;
-}
\ No newline at end of file
diff --git a/src/redakcja/static/css/dialogs.scss b/src/redakcja/static/css/dialogs.scss
new file mode 100644 (file)
index 0000000..d19d874
--- /dev/null
@@ -0,0 +1,49 @@
+.dialog {
+       display: none;
+       padding: 5px;
+       text-align: left;
+       cursor: auto;
+       background-color: #E1E1E1; /* #e0ffb8; */
+}
+
+.dialog .help_text {
+       font-size: 11px;
+       color: #2e3536;
+}
+
+.dialog .action_area {
+       padding: 1em 0.5em 0.5em;
+       border-top: 1px solid black;
+       margin-top: 0.5em;
+}
+
+.dialog p {
+       margin: 0.5em;
+}
+
+*[data-ui-error-for] {
+       color: red;
+       font-weight: bold;
+}
+
+#save_dialog textarea, #revert_dialog textarea {
+       width: 90%;
+       margin: 0.2em 4%;
+}
+
+
+
+#meta-chooser {
+    .form-check {
+        margin: 1em;
+    }
+    .meta-chooser_toggle {
+        position: absolute;
+        top: 1.5em;
+        left: 0;
+        cursor: pointer;
+    }
+    .name {
+        font-weight: bold;
+    }
+}
index 9fa1c9d..56bd84d 100644 (file)
                     $('#media-chooser').modal('hide');
                 });
 
+            /* Meta chooser */
+            $('#meta-chooser').on('show.bs.modal', function (event) {
+                let input = $("input", $(event.relatedTarget).closest('.input-group'));
+                let $fg = $(event.relatedTarget).closest('.form-group');
+                let field = $fg.data('field');
+                let modal = $(this);
+                modal.data('target-input', input);
+                let body = modal.find('.modal-body');
+                body.html('');
+
+                let add_options = function(cnt, options, value) {
+                    $.each(options, (i, item) => {
+                        let elem = $('<div class="form-check"><label class="form-check-label"><input class="form-check-input" type="radio" name="metachoose"><div class="value"></div><div class="name"></div><div class="description"></div></label></div>');
+                        if (!item.usable) {
+                            $('input', elem).remove();
+                        }
+                        if (item.hidden) {
+                            $('input', elem).prop('disabled', 'disabled');
+                        }
+                        $('input', elem).val(item.value);
+                        $('input', elem).val(item.value);
+                        $('.value', elem).text(item.value);
+                        $('.name', elem).append(item.name);
+                        $('.description', elem).append(item.description);
+                        let valueMatch = value && value.startsWith(item.value);
+                        if (valueMatch) {
+                            $('label', elem).addClass('text-primary')
+                            if (value == item.value) {
+                                $('input', elem).prop('checked', true);
+                            }
+                        }
+                        if (item.sub) {
+                            let subT = $('<div class="meta-chooser_toggle">+</div>');
+                            let sub = $('<div>');
+                            elem.append(subT);
+                            elem.append(sub);
+                            subT.on('click', () => {
+                                sub.toggle()
+                            });
+                            add_options(sub, item.sub, valueMatch ? value : null);
+                        }
+                        elem.appendTo(cnt);
+                    });
+                };
+
+                $.ajax({
+                    url: field.value_type.chooser.source,
+                    success: function(data) {
+                        add_options(body, data, input.val());
+                    }
+                });
+            })
+            $('#meta-chooser .ctrl-ok').on('click', function (event) {
+                $('#meta-chooser').data('target-input').val(
+                    $('#meta-chooser :checked').val()
+                ).trigger('change');
+                $('#meta-chooser').modal('hide');
+            });
+
                 self.$pane.on('click', '.current-convert', function() {
                     self.convert($(this).attr('data-to'));
                 });
 
             let ap = $("<div class='input-group-append'>");
             ap.appendTo(ig);
+
+            if (field.value_type.chooser) {
+                ap.append($("<button type='button' class='btn btn-outline-secondary' data-toggle='modal' data-target='#meta-chooser'>…</button>"));
+            }
             $("<button class='meta-delete btn btn-outline-secondary'>x</button>").appendTo(ap);
 
             // lang
index 214940e..7236ee2 100644 (file)
@@ -60,6 +60,7 @@
   {% include "wiki/revert_dialog.html" %}
   {% include "wiki/media_dialog.html" %}
   {% include "wiki/gallery_dialog.html" %}
+  {% include "wiki/meta_chooser.html" %}
   {% if can_pubmark %}
     {% include "wiki/pubmark_dialog.html" %}
   {% endif %}
diff --git a/src/wiki/templates/wiki/meta_chooser.html b/src/wiki/templates/wiki/meta_chooser.html
new file mode 100644 (file)
index 0000000..b7b42a0
--- /dev/null
@@ -0,0 +1,13 @@
+{% load i18n %}
+<div id="meta-chooser" class="modal">
+  <div class="modal-dialog modal-lg">
+    <div " class="modal-content">
+      <div class="modal-body">
+      </div>
+      <div class="modal-footer">
+        <button type="button" class="ctrl-ok btn btn-primary">{% translate "Choose" %}</button>
+        <button type="button" class="btn btn-primary" data-dismiss="modal">{% translate "Cancel" %}</button>
+      </div>
+    </div>
+  </div>
+</div>
index 3784b77..64a49dc 100644 (file)
@@ -58,12 +58,18 @@ VALUE_TYPES = {
     ThemaCategory: {
         'autocomplete': {
             'source': '/catalogue/terms/thema/',
-        }
+        },
+        'chooser': {
+            'source': '/catalogue/chooser/thema/',
+        },
     },
     MainThemaCategory: {
         'autocomplete': {
             'source': '/catalogue/terms/thema-main/',
-        }
+        },
+        'chooser': {
+            'source': '/catalogue/chooser/thema-main/',
+        },
     },
     Epoch: {
         'autocomplete': {