From eb4b723aa7606c8e286340e0bd1746cc580ebce8 Mon Sep 17 00:00:00 2001
From: Helge Jung <hej@c3pb.de>
Date: Sat, 21 Dec 2024 02:48:49 +0100
Subject: [PATCH] (room) links: add 'nav' type and allow internal links, too

Thereby, the handling of all links (assembly, projects, rooms) is now the same like in markdown.
---
 src/core/locale/de/LC_MESSAGES/django.po      |  9 ++----
 src/core/locale/en/LC_MESSAGES/django.po      |  9 ++----
 ...mlink_add_type_nav_remove_internal_link.py | 31 +++++++++++++++++++
 src/core/models/links.py                      | 29 +++++++++++++----
 src/core/models/rooms.py                      | 26 +++++++++++++---
 src/plainui/jinja2.py                         |  1 +
 src/plainui/jinja2/plainui/assembly.html.j2   |  7 ++---
 .../jinja2/plainui/projects/detail.html.j2    |  7 ++---
 src/plainui/jinja2/plainui/room.html.j2       |  7 ++---
 src/plainui/views/rooms.py                    |  2 +-
 10 files changed, 92 insertions(+), 36 deletions(-)
 create mode 100644 src/core/migrations/0167_roomlink_add_type_nav_remove_internal_link.py

diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po
index fc92843e2..f0434a686 100644
--- a/src/core/locale/de/LC_MESSAGES/django.po
+++ b/src/core/locale/de/LC_MESSAGES/django.po
@@ -1224,18 +1224,15 @@ msgstr "Video-Stream"
 msgid "RoomLink__type-audio"
 msgstr "Audio-Stream"
 
+msgid "RoomLink__type-nav"
+msgstr "Navigation"
+
 msgid "RoomLink__type__help"
 msgstr "Was wird hier verlinkt?"
 
 msgid "RoomLink__type"
 msgstr "Typ"
 
-msgid "RoomLink__conference_internal__help"
-msgstr "Es handelt sich um einen Link der keine Vorschaltseite benötigt."
-
-msgid "RoomLink__conference_internal"
-msgstr "interner Link"
-
 msgid "Link__link__must_be_url"
 msgstr "Das Link-Ziel muss eine gültige URL sein."
 
diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po
index dfb63b7f4..2ce7c5b43 100644
--- a/src/core/locale/en/LC_MESSAGES/django.po
+++ b/src/core/locale/en/LC_MESSAGES/django.po
@@ -1224,18 +1224,15 @@ msgstr "video stream"
 msgid "RoomLink__type-audio"
 msgstr "audio stream"
 
+msgid "RoomLink__type-nav"
+msgstr "navigation"
+
 msgid "RoomLink__type__help"
 msgstr "What is being linked here?"
 
 msgid "RoomLink__type"
 msgstr "type"
 
-msgid "RoomLink__conference_internal__help"
-msgstr "This is an internal link which does not require a disclaimer page."
-
-msgid "RoomLink__conference_internal"
-msgstr "internal link"
-
 msgid "Link__link__must_be_url"
 msgstr "The link target must be a URL."
 
diff --git a/src/core/migrations/0167_roomlink_add_type_nav_remove_internal_link.py b/src/core/migrations/0167_roomlink_add_type_nav_remove_internal_link.py
new file mode 100644
index 000000000..92d338bd3
--- /dev/null
+++ b/src/core/migrations/0167_roomlink_add_type_nav_remove_internal_link.py
@@ -0,0 +1,31 @@
+# Generated by Django 5.1.3 on 2024-12-21 01:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0166_alter_room_official_room_order'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='link',
+            name='conference_internal',
+        ),
+        migrations.RemoveField(
+            model_name='roomlink',
+            name='conference_internal',
+        ),
+        migrations.AlterField(
+            model_name='link',
+            name='link_type',
+            field=models.CharField(choices=[('website', 'RoomLink__type-website'), ('chat', 'RoomLink__type-chat'), ('bbb', 'RoomLink__type-bbb'), ('jitsi', 'RoomLink__type-jitsi'), ('pad', 'RoomLink__type-pad'), ('media.ccc.de', 'RoomLink__type-media_ccc_de'), ('video', 'RoomLink__type-video'), ('audio', 'RoomLink__type-audio'), ('nav', 'RoomLink__type-nav')], help_text='RoomLink__type__help', max_length=20, verbose_name='RoomLink__type'),
+        ),
+        migrations.AlterField(
+            model_name='roomlink',
+            name='link_type',
+            field=models.CharField(choices=[('website', 'RoomLink__type-website'), ('chat', 'RoomLink__type-chat'), ('bbb', 'RoomLink__type-bbb'), ('jitsi', 'RoomLink__type-jitsi'), ('pad', 'RoomLink__type-pad'), ('media.ccc.de', 'RoomLink__type-media_ccc_de'), ('video', 'RoomLink__type-video'), ('audio', 'RoomLink__type-audio'), ('nav', 'RoomLink__type-nav')], help_text='RoomLink__type__help', max_length=20, verbose_name='RoomLink__type'),
+        ),
+    ]
diff --git a/src/core/models/links.py b/src/core/models/links.py
index 2b7ae3e5f..86764e7e4 100644
--- a/src/core/models/links.py
+++ b/src/core/models/links.py
@@ -5,6 +5,8 @@ from django.core.validators import URLValidator
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 
+from core.utils import resolve_link
+
 
 class Link(models.Model):
     class Meta:
@@ -19,8 +21,9 @@ class Link(models.Model):
         MEDIA_CCC_DE = 'media.ccc.de', _('RoomLink__type-media_ccc_de')
         VIDEO = 'video', _('RoomLink__type-video')
         AUDIO = 'audio', _('RoomLink__type-audio')
+        NAV = 'nav', _('RoomLink__type-nav')
 
-    URL_LINK_TYPES = [LinkType.WEBSITE, LinkType.CHAT, LinkType.BBB, LinkType.JITSI, LinkType.PAD, LinkType.VIDEO, LinkType.AUDIO]
+    URL_LINK_TYPES = [LinkType.WEBSITE, LinkType.CHAT, LinkType.BBB, LinkType.JITSI, LinkType.PAD, LinkType.VIDEO, LinkType.AUDIO, LinkType.NAV]
     """All LinkTypes which require the link to be a valid URL. The notable exception is media_ccc_de where only a global id is required."""
 
     content_types = models.Q(app_label='core', model='project')
@@ -32,9 +35,24 @@ class Link(models.Model):
     link_type = models.CharField(max_length=20, choices=LinkType.choices, help_text=_('RoomLink__type__help'), verbose_name=_('RoomLink__type'))
     link = models.CharField(max_length=255)
 
-    conference_internal = models.BooleanField(
-        default=False, help_text=_('RoomLink__conference_internal__help'), verbose_name=_('RoomLink__conference_internal')
-    )
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._resolved_url = None
+
+    def _cache_resolved_url_if_necessary(self):
+        if self._resolved_url:
+            return
+        self._resolved_url = resolve_link(self.link)
+
+    @property
+    def resolved_url(self) -> str:
+        self._cache_resolved_url_if_necessary()
+        return self._resolved_url[1]
+
+    @property
+    def resolved_url_internal(self) -> bool:
+        self._cache_resolved_url_if_necessary()
+        return self._resolved_url[0] == 'internal'
 
     def clean(self):
         if self.link_type in Link.URL_LINK_TYPES:
@@ -48,7 +66,6 @@ class Link(models.Model):
         return self.name
 
     def __repr__(self) -> str:
-        repr_str = f'<{self.__class__.__name__}: {self.name} ({self.link_type}'
-        repr_str += ':internal)' if self.conference_internal else ')'
+        repr_str = f'<{self.__class__.__name__}: {self.name} ({self.link_type})'
         repr_str += f': {self.link}>' if self.link != self.name else '>'
         return repr_str
diff --git a/src/core/models/rooms.py b/src/core/models/rooms.py
index 9a4c275f2..b54819c25 100644
--- a/src/core/models/rooms.py
+++ b/src/core/models/rooms.py
@@ -20,7 +20,7 @@ from core.models.base_managers import ConferenceManagerMixin
 from core.models.conference import Conference, ConferenceMember
 from core.models.shared import BackendMixin
 from core.models.tags import TagItem
-from core.utils import resolve_internal_url, str2bool
+from core.utils import resolve_internal_url, resolve_link, str2bool
 from core.validators import FileSizeValidator, ImageDimensionValidator
 
 
@@ -464,10 +464,11 @@ class RoomLink(models.Model):
         MEDIA_CCC_DE = 'media.ccc.de', _('RoomLink__type-media_ccc_de')
         VIDEO = 'video', _('RoomLink__type-video')
         AUDIO = 'audio', _('RoomLink__type-audio')
+        NAV = 'nav', _('RoomLink__type-nav')
 
     objects = RoomLinkManager()
 
-    URL_LINKTYPES = [LinkType.WEBSITE, LinkType.CHAT, LinkType.BBB, LinkType.JITSI, LinkType.PAD, LinkType.VIDEO, LinkType.AUDIO]
+    URL_LINKTYPES = [LinkType.WEBSITE, LinkType.CHAT, LinkType.BBB, LinkType.JITSI, LinkType.PAD, LinkType.VIDEO, LinkType.AUDIO, LinkType.NAV]
     """All LinkTypes which require the link to be a valid URL. The notable exception is media_ccc_de where only a global id is required."""
 
     room = models.ForeignKey(Room, related_name='links', on_delete=models.CASCADE)
@@ -476,9 +477,24 @@ class RoomLink(models.Model):
     link_type = models.CharField(max_length=20, choices=LinkType.choices, help_text=_('RoomLink__type__help'), verbose_name=_('RoomLink__type'))
     link = models.CharField(max_length=255)
 
-    conference_internal = models.BooleanField(
-        default=False, help_text=_('RoomLink__conference_internal__help'), verbose_name=_('RoomLink__conference_internal')
-    )
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._resolved_url = None
+
+    def _cache_resolved_url_if_necessary(self):
+        if self._resolved_url:
+            return
+        self._resolved_url = resolve_link(self.link)
+
+    @property
+    def resolved_url(self) -> str:
+        self._cache_resolved_url_if_necessary()
+        return self._resolved_url[1]
+
+    @property
+    def resolved_url_internal(self) -> bool:
+        self._cache_resolved_url_if_necessary()
+        return self._resolved_url[0] == 'internal'
 
     def clean(self):
         if self.link_type in RoomLink.URL_LINKTYPES:
diff --git a/src/plainui/jinja2.py b/src/plainui/jinja2.py
index 7365c3a32..1094fa4df 100644
--- a/src/plainui/jinja2.py
+++ b/src/plainui/jinja2.py
@@ -159,6 +159,7 @@ link_icon_map = {
     'media.ccc.de': 'play-circle-fill',
     'video': 'camera-reels-fill',
     'audio': 'volume-up-fill',
+    'nav': 'map',
 }
 
 
diff --git a/src/plainui/jinja2/plainui/assembly.html.j2 b/src/plainui/jinja2/plainui/assembly.html.j2
index b5472b45e..9fa0bb821 100644
--- a/src/plainui/jinja2/plainui/assembly.html.j2
+++ b/src/plainui/jinja2/plainui/assembly.html.j2
@@ -47,14 +47,13 @@
         {% endif %}
 
         {% if assembly.assembly_link %}
-          {% set assembly_url = '/' + assembly.assembly_link if assembly.assembly_link.conference_internal else url('plainui:dereferrer', assembly.assembly_link) %}
           <div class="hub-tags">
             {{ tagboxMacro.tag(_("Website") ,
             icon="globe",
-            link=assembly_url,
+            link=assembly.assembly_link.resolved_url,
             style="secondary",
-            target=('_self' if assembly.assembly_link.conference_internal else '_blank'),
-            rel=('' if assembly.assembly_link.conference_internal else 'external')
+            target=('_self' if assembly.assembly_link.resolved_url_internal else '_blank'),
+            rel=('' if assembly.assembly_link.resolved_url_internal else 'external')
             ) }}
           </div>
         {% endif %}
diff --git a/src/plainui/jinja2/plainui/projects/detail.html.j2 b/src/plainui/jinja2/plainui/projects/detail.html.j2
index 541805773..4e9b51722 100644
--- a/src/plainui/jinja2/plainui/projects/detail.html.j2
+++ b/src/plainui/jinja2/plainui/projects/detail.html.j2
@@ -68,13 +68,12 @@
           {% if project.links %}
             <div class="hub-tags" role="list">
               {% for link in project.links.all() %}
-                {% set url = '/' + link.link if link.conference_internal else url('plainui:dereferrer', dst=link.link) %}
                 {{ tagMacros.tag(link.name,
-                                link=url,
+                                link=link.resolved_url,
                                 icon=link_icon(link) ,
                 style='secondary',
-                target=('_self' if link.conference_internal else '_blank'),
-                rel=('' if link.conference_internal else 'external, noreferrer')
+                target=('_self' if link.resolved_url_internal else '_blank'),
+                rel=('' if link.resolved_url_internal else 'external, noreferrer')
                 ) }}
               {% endfor %}
             </div>
diff --git a/src/plainui/jinja2/plainui/room.html.j2 b/src/plainui/jinja2/plainui/room.html.j2
index d37cae1bb..de12e4b29 100644
--- a/src/plainui/jinja2/plainui/room.html.j2
+++ b/src/plainui/jinja2/plainui/room.html.j2
@@ -41,13 +41,12 @@
       {% if links %}
         <div class="hub-tags" role="list">
           {% for link in links %}
-            {% set url = '/' + link.link if link.conference_internal else url('plainui:dereferrer', dst=link.link) %}
             {{ tagMacros.tag(link.name,
-                        link=url,
+                        link=link.resolved_url,
                         icon=link_icon(link) ,
             style='secondary',
-            target=('_self' if link.conference_internal else '_blank'),
-            rel=('' if link.conference_internal else 'external, noreferrer')
+            target=('_self' if link.resolved_url_internal else '_blank'),
+            rel=('' if link.resolved_url_internal else 'external, noreferrer')
             ) }}
           {% endfor %}
         </div>
diff --git a/src/plainui/views/rooms.py b/src/plainui/views/rooms.py
index ae1b7ce71..0de04f39d 100644
--- a/src/plainui/views/rooms.py
+++ b/src/plainui/views/rooms.py
@@ -56,7 +56,7 @@ class RoomView(ConferenceRequiredMixin, DetailView):
         for link in self.room.links.all():
             if link.link_type == RoomLink.LinkType.MEDIA_CCC_DE:
                 media_link = link.link
-            elif link.link_type in {RoomLink.LinkType.WEBSITE, RoomLink.LinkType.CHAT, RoomLink.LinkType.JITSI, RoomLink.LinkType.PAD}:
+            elif link.link_type in [RoomLink.LinkType.WEBSITE, RoomLink.LinkType.CHAT, RoomLink.LinkType.JITSI, RoomLink.LinkType.PAD, RoomLink.LinkType.NAV]:
                 linking_links.append(link)
 
         context['voc_stream'] = media_link
-- 
GitLab