From dca99e959e43e9234878bd771f37d5af18c77526 Mon Sep 17 00:00:00 2001 From: "Yang.Y" Date: Fri, 17 Jun 2016 11:19:47 +0800 Subject: [PATCH 01/18] Bump version 1.2 --- CHANGELOG.rst | 10 +++++++++- notifications/__init__.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 77c8fd0..a464143 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,15 @@ Changelog ========= +1.2 +----- + +- Django 1.9 template tag compatibility: due to ``register.simple_tag`` automatically espacing ``unsafe_html`` in Django 1.9, it is now recommended to use format_html (@ikkebr) +- Fixed parameter name error in README.rst: there is no to_fetch parameter, the correct name is fetch (@ikkebr) +- Add missing migration (@marcgibbons) +- Minor documentation correction (@tkwon, @zhang-z) +- Return updated count in QuerySet (@zhang-z) + 1.1 ----- @@ -91,4 +100,3 @@ Fix package descriptions and doc links. --- First version based on `django-activity-stream `_ v0.4.3 - diff --git a/notifications/__init__.py b/notifications/__init__.py index c4949bf..3fa1e19 100644 --- a/notifications/__init__.py +++ b/notifications/__init__.py @@ -8,6 +8,6 @@ """ # PEP 386-compliant version number: N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN] -__version__ = '1.1' +__version__ = '1.2' default_app_config = 'notifications.apps.Config' From c7fd00a0f394ee08ee857f0f5dc48ac1eb5f2aaf Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sat, 13 Aug 2016 23:46:04 +0300 Subject: [PATCH 02/18] Redirect to unread view after mark as read --- notifications/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notifications/views.py b/notifications/views.py index c99edf8..a7dcef9 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -63,7 +63,7 @@ def mark_all_as_read(request): if _next: return redirect(_next) - return redirect('notifications:all') + return redirect('notifications:unread') @login_required @@ -79,7 +79,7 @@ def mark_as_read(request, slug=None): if _next: return redirect(_next) - return redirect('notifications:all') + return redirect('notifications:unread') @login_required @@ -95,7 +95,7 @@ def mark_as_unread(request, slug=None): if _next: return redirect(_next) - return redirect('notifications:all') + return redirect('notifications:unread') @login_required From feeec2c28bd49ad7645637d87d983280fb281af4 Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sun, 14 Aug 2016 10:53:12 +0300 Subject: [PATCH 03/18] Fix test_unread_messages_pages test --- notifications/tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notifications/tests/tests.py b/notifications/tests/tests.py index 99ccf9a..336bc5a 100644 --- a/notifications/tests/tests.py +++ b/notifications/tests/tests.py @@ -177,7 +177,7 @@ class NotificationTestPages(TestCase): self.assertTrue(len(response.context['notifications']) < self.message_count) response = self.client.get(reverse('notifications:mark_all_as_read')) - self.assertRedirects(response, reverse('notifications:all')) + self.assertRedirects(response, reverse('notifications:unread')) response = self.client.get(reverse('notifications:unread')) self.assertEqual(len(response.context['notifications']), len(self.to_user.notifications.unread())) self.assertEqual(len(response.context['notifications']), 0) From 1fe41a1eafebc8c5a98a11b8082540864395ee74 Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sat, 3 Sep 2016 22:07:20 +0300 Subject: [PATCH 04/18] New Django classifiers added in setup.py --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index bd81cc7..2677d9e 100755 --- a/setup.py +++ b/setup.py @@ -48,6 +48,11 @@ setup( 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', + 'Framework :: Django', + 'Framework :: Django :: 1.10', + 'Framework :: Django :: 1.9', + 'Framework :: Django :: 1.8', + 'Framework :: Django :: 1.7', # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. 'Programming Language :: Python', From cfe907d79346d519cfe3efbe5c4a0890c540d511 Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sat, 3 Sep 2016 22:12:04 +0300 Subject: [PATCH 05/18] Django 1.10 support (latest LTS release) --- .travis.yml | 1 + README.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b8bcff3..734f5b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ env: - DJANGO=1.7 - DJANGO=1.8 - DJANGO=1.9 + - DJANGO=1.10 install: # command to install dependencies - "pip install coveralls" diff --git a/README.rst b/README.rst index 20cc675..7847f3f 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ Requirements ============ - Python 2.7, 3.3, 3.4, 3.5 -- Django 1.7, 1.8, 1.9 +- Django 1.7, 1.8, 1.9, 1.10 Installation ============ From 36eeabe9c2e20ec978bc95a7a6dfd1267d4c37f4 Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sun, 4 Sep 2016 12:13:35 +0300 Subject: [PATCH 06/18] Ignore checks Django 1.10 on Python 3.3 (not supported) --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 734f5b2..6517450 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,5 +24,7 @@ matrix: env: DJANGO=1.7 - python: "3.3" env: DJANGO=1.9 + - python: "3.3" + env: DJANGO=1.10 after_success: - coveralls From 4a2b3d691c4cc40127dc597b90b4e1ae46cf71ab Mon Sep 17 00:00:00 2001 From: Roman Vasilyev Date: Mon, 12 Sep 2016 12:29:48 -0700 Subject: [PATCH 07/18] don't request full user list, it takes time --- notifications/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/notifications/admin.py b/notifications/admin.py index cefc9f2..da6f26d 100644 --- a/notifications/admin.py +++ b/notifications/admin.py @@ -5,6 +5,7 @@ from .models import Notification class NotificationAdmin(admin.ModelAdmin): + raw_id_fields = ('recipient', ) list_display = ('recipient', 'actor', 'level', 'target', 'unread', 'public') list_filter = ('level', 'unread', 'public', 'timestamp', ) From 99dfa0a81873e9916e4e95c0d04d7a5376f8104b Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sun, 25 Sep 2016 00:22:33 +0300 Subject: [PATCH 08/18] Add mark_as_read param for API --- README.rst | 21 ++++++++++++++------- notifications/views.py | 2 ++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 20cc675..7d66c7d 100644 --- a/README.rst +++ b/README.rst @@ -239,18 +239,25 @@ for updating specific fields within a django template. There are two possible API calls that can be made: - 1. ``api/unread_count/`` that returns a javascript object with 1 key: ``unread_count`` eg:: +1. ``api/unread_count/`` that returns a javascript object with 1 key: ``unread_count`` eg:: {"unread_count":1} - #. ``api/unread_list/`` that returns a javascript object with 2 keys: `unread_count` and `unread_list` eg:: +#. ``api/unread_list/`` that returns a javascript object with 2 keys: `unread_count` and `unread_list` eg:: - { - "unread_count":1, - "unread_list":[--list of json representations of notifications--] - } + { + "unread_count":1, + "unread_list":[--list of json representations of notifications--] + } - Representations of notifications are based on the django method: ``model_to_dict`` + Representations of notifications are based on the django method: ``model_to_dict`` + + Query string arguments: + + - **max** + - **mark_as_read** + + For example, get ``api/unread_list/?max=3&mark_as_read=true`` returns 3 notifications and mark them read (remove from list on next time). How to use: diff --git a/notifications/views.py b/notifications/views.py index a7dcef9..bbc545a 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -156,6 +156,8 @@ def live_unread_notification_list(request): if n.action_object: struct['action_object'] = str(n.action_object) unread_list.append(struct) + if request.GET.get('mark_as_read'): + n.mark_as_read() data = { 'unread_count': request.user.notifications.unread().count(), 'unread_list': unread_list From f2175fe77eab209d82bd6ccf912b611b3b195e41 Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sun, 25 Sep 2016 15:06:45 +0300 Subject: [PATCH 09/18] Testing mark_as_read query param --- notifications/tests/tests.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/notifications/tests/tests.py b/notifications/tests/tests.py index 336bc5a..32f4b98 100644 --- a/notifications/tests/tests.py +++ b/notifications/tests/tests.py @@ -295,6 +295,24 @@ class NotificationTestPages(TestCase): self.assertEqual(len(data['unread_list']), 1) self.assertEqual(data['unread_list'][0]['verb'], 'commented') + def test_unread_list_api_mark_as_read(self): + self.login('to', 'pwd') + num_requested = 3 + response = self.client.get(reverse('notifications:live_unread_notification_list'), data={ + "max": num_requested, + "mark_as_read": 1, + }) + data = json.loads(response.content.decode('utf-8')) + self.assertEqual(data['unread_count'], self.message_count - num_requested) + self.assertEqual(len(data['unread_list']), num_requested) + response = self.client.get(reverse('notifications:live_unread_notification_list'), data={ + "max": num_requested, + "mark_as_read": 1, + }) + data = json.loads(response.content.decode('utf-8')) + self.assertEqual(data['unread_count'], 4) + self.assertEqual(len(data['unread_list']), num_requested) + def test_live_update_tags(self): from django.shortcuts import render From 1ab6fe2a0464073ae2cbd8f60121bd6eee29703c Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sun, 25 Sep 2016 15:11:08 +0300 Subject: [PATCH 10/18] Minor fixes to documentation --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 7d66c7d..05425d1 100644 --- a/README.rst +++ b/README.rst @@ -254,10 +254,10 @@ There are two possible API calls that can be made: Query string arguments: - - **max** - - **mark_as_read** + - **max** - maximum length of unread list. + - **mark_as_read** - mark notification in list as read. - For example, get ``api/unread_list/?max=3&mark_as_read=true`` returns 3 notifications and mark them read (remove from list on next time). + For example, get ``api/unread_list/?max=3&mark_as_read=true`` returns 3 notifications and mark them read (remove from list on next request). How to use: From 3b61a71a712a4b077c3f083aed747a8e2e910f51 Mon Sep 17 00:00:00 2001 From: Vladimir Osintsev Date: Sun, 25 Sep 2016 15:43:56 +0300 Subject: [PATCH 11/18] Minor fixes --- notifications/tests/tests.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/notifications/tests/tests.py b/notifications/tests/tests.py index 32f4b98..6b57db6 100644 --- a/notifications/tests/tests.py +++ b/notifications/tests/tests.py @@ -298,19 +298,21 @@ class NotificationTestPages(TestCase): def test_unread_list_api_mark_as_read(self): self.login('to', 'pwd') num_requested = 3 - response = self.client.get(reverse('notifications:live_unread_notification_list'), data={ - "max": num_requested, - "mark_as_read": 1, - }) + response = self.client.get( + reverse('notifications:live_unread_notification_list'), + data={"max": num_requested, "mark_as_read": 1} + ) data = json.loads(response.content.decode('utf-8')) - self.assertEqual(data['unread_count'], self.message_count - num_requested) + self.assertEqual(data['unread_count'], + self.message_count - num_requested) self.assertEqual(len(data['unread_list']), num_requested) - response = self.client.get(reverse('notifications:live_unread_notification_list'), data={ - "max": num_requested, - "mark_as_read": 1, - }) + response = self.client.get( + reverse('notifications:live_unread_notification_list'), + data={"max": num_requested, "mark_as_read": 1} + ) data = json.loads(response.content.decode('utf-8')) - self.assertEqual(data['unread_count'], 4) + self.assertEqual(data['unread_count'], + self.message_count - 2*num_requested) self.assertEqual(len(data['unread_list']), num_requested) def test_live_update_tags(self): From f68f9e8b71cb72594ae124d470bc90988cda6e0f Mon Sep 17 00:00:00 2001 From: Tazavoo Date: Mon, 26 Sep 2016 15:43:44 +0300 Subject: [PATCH 12/18] Corrected 'api_url_name' to 'api_name' in README.rst Corrected argument name 'api_url_name' to 'api_name' in the README.rst for function register_notify_callbacks in templatetags/notificationtags.py. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fd09cc7..a979336 100644 --- a/README.rst +++ b/README.rst @@ -276,7 +276,7 @@ How to use: #. ``refresh_period`` (default ``15``) - How often to fetch unread items from the server (integer in seconds). #. ``fetch`` (default ``5``) - How many notifications to fetch each time. #. ``callbacks`` (default ````) - A comma-separated list of javascript functions to call each period. - #. ``api_url_name`` (default ``list``) - The name of the API to call (this can be either ``list`` or ``count``). + #. ``api_name`` (default ``list``) - The name of the API to call (this can be either ``list`` or ``count``). 3. To insert a live-updating unread count, use the following template:: From 59fe781d82fd1aa47fdef946987c581c3b42a63a Mon Sep 17 00:00:00 2001 From: Nick Hargreaves Date: Thu, 16 Feb 2017 20:46:39 +0300 Subject: [PATCH 13/18] Ref to badge count instead of list --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a979336..103752b 100644 --- a/README.rst +++ b/README.rst @@ -287,7 +287,7 @@ How to use: 1. ``badge_id`` (default ``live_notify_badge``) - The ``id`` attribute for the ```` element that will be created to show the unread count. #. ``classes`` (default ````) - A string used to populate the ``class`` attribute of the above element. - 4. To insert a live-updating unread count, use the following template:: + 4. To insert a live-updating unread list, use the following template:: {% live_notify_list %} From f7f90b83c8b72d76e0726779b6f4573dc4e8b4de Mon Sep 17 00:00:00 2001 From: Roman Vasilyev Date: Thu, 23 Feb 2017 09:27:00 -0800 Subject: [PATCH 14/18] sent/unsent convenience functions --- notifications/models.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/notifications/models.py b/notifications/models.py index 0376b8d..a564f73 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -40,6 +40,12 @@ def assert_soft_delete(): class NotificationQuerySet(models.query.QuerySet): + def unsent(self): + return self.filter(emailed=False) + + def sent(self): + return self.filter(emailed=True) + def unread(self, include_deleted=False): """Return only unread items in the current queryset""" if is_soft_delete() and not include_deleted: @@ -117,6 +123,12 @@ class NotificationQuerySet(models.query.QuerySet): return qs.update(deleted=False) + def mark_as_unsent(self): + return self.update(emailed=False) + + def mark_as_sent(self): + return self.update(emailed=True) + class Notification(models.Model): """ From d07c7a7a5cf614ef366e8340dab5612fc76a34f7 Mon Sep 17 00:00:00 2001 From: Zhongyuan Date: Fri, 3 Mar 2017 11:11:59 +0800 Subject: [PATCH 15/18] Update README --- README.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.rst b/README.rst index 103752b..7380b82 100644 --- a/README.rst +++ b/README.rst @@ -343,6 +343,14 @@ Testing the live-updater 4. Browse to `yourserverip/test/` 5. Click 'Make a notification' and a new notification should appear in the list in 5-10 seconds. +Notes +===== + +Email Notification +------------------ + +Sending email to users has not been integrated into this library. So for now you need to implement it if needed. There is a reserved field `Notification.emailed` to make it easier. + ``django-notifications`` Team ============================== From 09d01810fa09c38440190448d8d4d1b8cbf71410 Mon Sep 17 00:00:00 2001 From: Satyajeet Kanetkar Date: Tue, 18 Apr 2017 11:32:38 +0530 Subject: [PATCH 16/18] return saved Notification instances from notify_handler, add tests --- notifications/models.py | 5 +++++ notifications/tests/tests.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/notifications/models.py b/notifications/models.py index a564f73..780958f 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -266,6 +266,8 @@ def notify_handler(verb, **kwargs): else: recipients = [recipient] + new_notifications = [] + for recipient in recipients: newnotify = Notification( recipient=recipient, @@ -289,6 +291,9 @@ def notify_handler(verb, **kwargs): newnotify.data = kwargs newnotify.save() + new_notifications.append(newnotify) + + return new_notifications # connect the signal diff --git a/notifications/tests/tests.py b/notifications/tests/tests.py index 6b57db6..cfc6bef 100644 --- a/notifications/tests/tests.py +++ b/notifications/tests/tests.py @@ -21,7 +21,7 @@ import pytz import json from notifications import notify -from notifications.models import Notification +from notifications.models import Notification, notify_handler from notifications.utils import id2slug @@ -55,15 +55,37 @@ class NotificationManagersTest(TestCase): def setUp(self): self.message_count = 10 + self.other_user = User.objects.create(username="other1", password="pwd", email="example@example.com") + self.from_user = User.objects.create(username="from2", password="pwd", email="example@example.com") self.to_user = User.objects.create(username="to2", password="pwd", email="example@example.com") self.to_group = Group.objects.create(name="to2_g") + self.to_group.user_set.add(self.to_user) + self.to_group.user_set.add(self.other_user) + for i in range(self.message_count): notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user) # Send notification to group notify.send(self.from_user, recipient=self.to_group, verb='commented', action_object=self.from_user) - self.message_count += 1 + self.message_count += self.to_group.user_set.count() + + def test_notify_send_return_val(self): + results = notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user) + for r in results: + if r[0] is notify_handler: + self.assertEqual(len(r[1]), 1) + # only check types for now + self.assertEqual(type(r[1][0]), Notification) + + def test_notify_send_return_val_group(self): + results = notify.send(self.from_user, recipient=self.to_group, verb='commented', action_object=self.from_user) + for r in results: + if r[0] is notify_handler: + self.assertEqual(len(r[1]), self.to_group.user_set.count()) + for n in r[1]: + # only check types for now + self.assertEqual(type(n), Notification) def test_unread_manager(self): self.assertEqual(Notification.objects.unread().count(), self.message_count) @@ -84,7 +106,7 @@ class NotificationManagersTest(TestCase): def test_mark_all_as_read_manager(self): self.assertEqual(Notification.objects.unread().count(), self.message_count) Notification.objects.filter(recipient=self.to_user).mark_all_as_read() - self.assertEqual(Notification.objects.unread().count(), 0) + self.assertEqual(self.to_user.notifications.unread().count(), 0) @override_settings(NOTIFICATIONS_SOFT_DELETE=True) def test_mark_all_as_read_manager_with_soft_delete(self): @@ -100,7 +122,7 @@ class NotificationManagersTest(TestCase): def test_mark_all_as_unread_manager(self): self.assertEqual(Notification.objects.unread().count(), self.message_count) Notification.objects.filter(recipient=self.to_user).mark_all_as_read() - self.assertEqual(Notification.objects.unread().count(), 0) + self.assertEqual(self.to_user.notifications.unread().count(), 0) Notification.objects.filter(recipient=self.to_user).mark_all_as_unread() self.assertEqual(Notification.objects.unread().count(), self.message_count) From 498a471c342c9ec07a56deac405022b0875a78cf Mon Sep 17 00:00:00 2001 From: Curtis Maloney Date: Wed, 24 May 2017 21:57:43 +1000 Subject: [PATCH 17/18] Make JS better performant --- notifications/static/notifications/notify.js | 32 ++++++++------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/notifications/static/notifications/notify.js b/notifications/static/notifications/notify.js index 7e464a5..6b786f9 100644 --- a/notifications/static/notifications/notify.js +++ b/notifications/static/notifications/notify.js @@ -18,11 +18,9 @@ function fill_notification_badge(data) { function fill_notification_list(data) { var menu = document.getElementById(notify_menu_id); if (menu) { - menu.innerHTML = ""; - for (var i=0; i < data.unread_list.length; i++) { - var item = data.unread_list[i]; - console.log(item) - var message = "" + var content = []; + while(var item = data.unread_list.shift()) { + var message = ""; if(typeof item.actor !== 'undefined'){ message = item.actor; } @@ -35,9 +33,9 @@ function fill_notification_list(data) { if(typeof item.timestamp !== 'undefined'){ message = message + " " + item.timestamp; } - - menu.innerHTML = menu.innerHTML + "
  • "+ message + "
  • "; + content.append('
  • ' + message + '
  • '); } + menu.innerHTML = content.join(''); } } @@ -50,17 +48,13 @@ function fetch_api_data() { //only fetch data if a function is setup var r = new XMLHttpRequest(); r.open("GET", notify_api_url+'?max='+notify_fetch_count, true); - r.onreadystatechange = function () { - if (r.readyState != 4 || r.status != 200) { - consecutive_misfires++; - } - else { - consecutive_misfires = 0; - for (var i=0; i < registered_functions.length; i++) { - var func = registered_functions[i]; - func(JSON.parse(r.responseText)); - } - } + r.onerror = function () { + consecutive_misfires++; + } + r.onready = function () { + consecutive_misfires = 0; + var data = JSON.parse(r.responseText); + registered_functions.forEach(function (func) { func(data); }); } r.send(); } @@ -75,4 +69,4 @@ function fetch_api_data() { } } -setTimeout(fetch_api_data,1000); \ No newline at end of file +setTimeout(fetch_api_data, 1000); From ef12471f653912f9365c7ea1c02bf5c0cc569baf Mon Sep 17 00:00:00 2001 From: Curtis Maloney Date: Wed, 24 May 2017 22:57:46 +1000 Subject: [PATCH 18/18] Use map for building menu list --- notifications/static/notifications/notify.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/notifications/static/notifications/notify.js b/notifications/static/notifications/notify.js index 6b786f9..df21a25 100644 --- a/notifications/static/notifications/notify.js +++ b/notifications/static/notifications/notify.js @@ -19,7 +19,7 @@ function fill_notification_list(data) { var menu = document.getElementById(notify_menu_id); if (menu) { var content = []; - while(var item = data.unread_list.shift()) { + menu.innerHTML = data.unread_list.map(function (item) { var message = ""; if(typeof item.actor !== 'undefined'){ message = item.actor; @@ -33,9 +33,8 @@ function fill_notification_list(data) { if(typeof item.timestamp !== 'undefined'){ message = message + " " + item.timestamp; } - content.append('
  • ' + message + '
  • '); - } - menu.innerHTML = content.join(''); + return '
  • ' + message + '
  • '; + }).join('') } }