From 8d266fda4dca59ac106f097d8f4a15fdd1c9ec16 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 8 Apr 2022 15:21:06 -0700 Subject: [PATCH 001/426] Removes unused `related_book` field on notification model --- .../0149_remove_notification_related_book.py | 17 +++++++++++++++++ bookwyrm/models/notification.py | 2 -- bookwyrm/views/notifications.py | 1 - 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/migrations/0149_remove_notification_related_book.py diff --git a/bookwyrm/migrations/0149_remove_notification_related_book.py b/bookwyrm/migrations/0149_remove_notification_related_book.py new file mode 100644 index 000000000..c976af6c9 --- /dev/null +++ b/bookwyrm/migrations/0149_remove_notification_related_book.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.12 on 2022-04-08 22:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0148_alter_user_preferred_language"), + ] + + operations = [ + migrations.RemoveField( + model_name="notification", + name="related_book", + ), + ] diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 417bf7591..28c5b803e 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -15,7 +15,6 @@ class Notification(BookWyrmModel): """you've been tagged, liked, followed, etc""" user = models.ForeignKey("User", on_delete=models.CASCADE) - related_book = models.ForeignKey("Edition", on_delete=models.CASCADE, null=True) related_user = models.ForeignKey( "User", on_delete=models.CASCADE, null=True, related_name="related_user" ) @@ -38,7 +37,6 @@ class Notification(BookWyrmModel): # there's probably a better way to do this if self.__class__.objects.filter( user=self.user, - related_book=self.related_book, related_user=self.related_user, related_group=self.related_group, related_status=self.related_status, diff --git a/bookwyrm/views/notifications.py b/bookwyrm/views/notifications.py index 0a7a62002..e081b07c3 100644 --- a/bookwyrm/views/notifications.py +++ b/bookwyrm/views/notifications.py @@ -22,7 +22,6 @@ class Notifications(View): "related_import", "related_report", "related_user", - "related_book", "related_list_item", "related_list_item__book", ) From a2a04da49356165e53cbcd557ad2b9ad2a1e75e1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 9 Apr 2022 09:41:10 -0700 Subject: [PATCH 002/426] Adds many to many related items to notifications --- .../migrations/0150_auto_20220408_2236.py | 128 ++++++++++++++++++ bookwyrm/models/notification.py | 41 ++---- 2 files changed, 142 insertions(+), 27 deletions(-) create mode 100644 bookwyrm/migrations/0150_auto_20220408_2236.py diff --git a/bookwyrm/migrations/0150_auto_20220408_2236.py b/bookwyrm/migrations/0150_auto_20220408_2236.py new file mode 100644 index 000000000..bf1c30487 --- /dev/null +++ b/bookwyrm/migrations/0150_auto_20220408_2236.py @@ -0,0 +1,128 @@ +# Generated by Django 3.2.12 on 2022-04-08 22:36 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0149_remove_notification_related_book"), + ] + + operations = [ + migrations.AddField( + model_name="notification", + name="related_groups", + field=models.ManyToManyField( + related_name="notifications", to="bookwyrm.Group" + ), + ), + migrations.AddField( + model_name="notification", + name="related_list_items", + field=models.ManyToManyField( + related_name="notifications", to="bookwyrm.ListItem" + ), + ), + migrations.AddField( + model_name="notification", + name="related_reports", + field=models.ManyToManyField(to="bookwyrm.Report"), + ), + migrations.AddField( + model_name="notification", + name="related_statuses", + field=models.ManyToManyField( + related_name="notifications", to="bookwyrm.Status" + ), + ), + migrations.AddField( + model_name="notification", + name="related_users", + field=models.ManyToManyField( + related_name="notifications", to=settings.AUTH_USER_MODEL + ), + ), + migrations.AlterField( + model_name="notification", + name="related_group", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications_temp", + to="bookwyrm.group", + ), + ), + migrations.AlterField( + model_name="notification", + name="related_list_item", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications_tmp", + to="bookwyrm.listitem", + ), + ), + migrations.AlterField( + model_name="notification", + name="related_report", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications_tmp", + to="bookwyrm.report", + ), + ), + migrations.RunSQL( + sql=""" + INSERT INTO bookwyrm_notification_related_statuses (notification_id, status_id) + SELECT id, related_status_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_status_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_users (notification_id, user_id) + SELECT id, related_user_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_user_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_groups (notification_id, group_id) + SELECT id, related_group_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_group_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_list_items (notification_id, listitem_id) + SELECT id, related_list_item_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_list_item_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_reports (notification_id, report_id) + SELECT id, related_report_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_report_id IS NOT NULL; + + """, + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RemoveField( + model_name="notification", + name="related_group", + ), + migrations.RemoveField( + model_name="notification", + name="related_list_item", + ), + migrations.RemoveField( + model_name="notification", + name="related_report", + ), + migrations.RemoveField( + model_name="notification", + name="related_status", + ), + migrations.RemoveField( + model_name="notification", + name="related_user", + ), + ] diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 28c5b803e..7b8059e0f 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -15,38 +15,25 @@ class Notification(BookWyrmModel): """you've been tagged, liked, followed, etc""" user = models.ForeignKey("User", on_delete=models.CASCADE) - related_user = models.ForeignKey( - "User", on_delete=models.CASCADE, null=True, related_name="related_user" - ) - related_group = models.ForeignKey( - "Group", on_delete=models.CASCADE, null=True, related_name="notifications" - ) - related_status = models.ForeignKey("Status", on_delete=models.CASCADE, null=True) - related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True) - related_list_item = models.ForeignKey( - "ListItem", on_delete=models.CASCADE, null=True - ) - related_report = models.ForeignKey("Report", on_delete=models.CASCADE, null=True) read = models.BooleanField(default=False) notification_type = models.CharField( max_length=255, choices=NotificationType.choices ) - def save(self, *args, **kwargs): - """save, but don't make dupes""" - # there's probably a better way to do this - if self.__class__.objects.filter( - user=self.user, - related_user=self.related_user, - related_group=self.related_group, - related_status=self.related_status, - related_import=self.related_import, - related_list_item=self.related_list_item, - related_report=self.related_report, - notification_type=self.notification_type, - ).exists(): - return - super().save(*args, **kwargs) + related_users = models.ManyToManyField( + "User", symmetrical=False, related_name="notifications" + ) + related_groups = models.ManyToManyField( + "Group", symmetrical=False, related_name="notifications" + ) + related_statuses = models.ManyToManyField( + "Status", symmetrical=False, related_name="notifications" + ) + related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True) + related_list_items = models.ManyToManyField( + "ListItem", symmetrical=False, related_name="notifications" + ) + related_reports = models.ManyToManyField("Report", symmetrical=False) class Meta: """checks if notifcation is in enum list for valid types""" From 7ae0db7f4ae3edffdd5d54e25d3fb5fe9339e590 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Thu, 5 May 2022 13:29:07 -0700 Subject: [PATCH 003/426] Ignore VariableDoesNotExist errors in debug logging They're so noisy as to make debug logging useless otherwise --- bookwyrm/settings.py | 4 ++++ bookwyrm/utils/log.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 bookwyrm/utils/log.py diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index e16c576e1..236413fe8 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -147,6 +147,9 @@ LOGGING = { "require_debug_true": { "()": "django.utils.log.RequireDebugTrue", }, + "ignore_missing_variable": { + "()": "bookwyrm.utils.log.IgnoreVariableDoesNotExist", + }, }, "handlers": { # Overrides the default handler to make it log to console @@ -154,6 +157,7 @@ LOGGING = { # console if DEBUG=False) "console": { "level": LOG_LEVEL, + "filters": ["ignore_missing_variable"], "class": "logging.StreamHandler", }, # This is copied as-is from the default logger, and is diff --git a/bookwyrm/utils/log.py b/bookwyrm/utils/log.py new file mode 100644 index 000000000..8ad86895c --- /dev/null +++ b/bookwyrm/utils/log.py @@ -0,0 +1,12 @@ +import logging + + +class IgnoreVariableDoesNotExist(logging.Filter): + def filter(self, record): + if(record.exc_info): + (errType, errValue, _) = record.exc_info + while errValue: + if type(errValue).__name__ == 'VariableDoesNotExist': + return False + errValue = errValue.__context__ + return True From 7014786fe03a73c7c8fe525aaad19384aa05c159 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Sun, 5 Jun 2022 13:41:00 -0700 Subject: [PATCH 004/426] Run formatters --- bookwyrm/utils/log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/utils/log.py b/bookwyrm/utils/log.py index 8ad86895c..4ea24d81d 100644 --- a/bookwyrm/utils/log.py +++ b/bookwyrm/utils/log.py @@ -3,10 +3,10 @@ import logging class IgnoreVariableDoesNotExist(logging.Filter): def filter(self, record): - if(record.exc_info): + if record.exc_info: (errType, errValue, _) = record.exc_info while errValue: - if type(errValue).__name__ == 'VariableDoesNotExist': + if type(errValue).__name__ == "VariableDoesNotExist": return False errValue = errValue.__context__ return True From b6cd64f82a7b292ceb9341b92d85d5305b41d721 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sat, 11 Jun 2022 14:19:03 +1000 Subject: [PATCH 005/426] add Shepherd version 10.0.0 --- bookwyrm/static/js/vendor/shepherd.min.js | 120 ++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 bookwyrm/static/js/vendor/shepherd.min.js diff --git a/bookwyrm/static/js/vendor/shepherd.min.js b/bookwyrm/static/js/vendor/shepherd.min.js new file mode 100644 index 000000000..eea679596 --- /dev/null +++ b/bookwyrm/static/js/vendor/shepherd.min.js @@ -0,0 +1,120 @@ +/*! shepherd.js 10.0.0 */ + +'use strict';(function(O,pa){"object"===typeof exports&&"undefined"!==typeof module?module.exports=pa():"function"===typeof define&&define.amd?define(pa):(O="undefined"!==typeof globalThis?globalThis:O||self,O.Shepherd=pa())})(this,function(){function O(a,b){return!1!==b.clone&&b.isMergeableObject(a)?ea(Array.isArray(a)?[]:{},a,b):a}function pa(a,b,c){return a.concat(b).map(function(d){return O(d,c)})}function Cb(a){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(a).filter(function(b){return a.propertyIsEnumerable(b)}): +[]}function Sa(a){return Object.keys(a).concat(Cb(a))}function Ta(a,b){try{return b in a}catch(c){return!1}}function Db(a,b,c){var d={};c.isMergeableObject(a)&&Sa(a).forEach(function(e){d[e]=O(a[e],c)});Sa(b).forEach(function(e){if(!Ta(a,e)||Object.hasOwnProperty.call(a,e)&&Object.propertyIsEnumerable.call(a,e))if(Ta(a,e)&&c.isMergeableObject(b[e])){if(c.customMerge){var f=c.customMerge(e);f="function"===typeof f?f:ea}else f=ea;d[e]=f(a[e],b[e],c)}else d[e]=O(b[e],c)});return d}function ea(a,b,c){c= +c||{};c.arrayMerge=c.arrayMerge||pa;c.isMergeableObject=c.isMergeableObject||Eb;c.cloneUnlessOtherwiseSpecified=O;var d=Array.isArray(b),e=Array.isArray(a);return d!==e?O(b,c):d?c.arrayMerge(a,b,c):Db(a,b,c)}function Z(a){return"function"===typeof a}function qa(a){return"string"===typeof a}function Ua(a){let b=Object.getOwnPropertyNames(a.constructor.prototype);for(let c=0;c +{if(b.isOpen()){let d=b.el&&c.currentTarget===b.el;(void 0!==a&&c.currentTarget.matches(a)||d)&&b.tour.next()}}}function Gb(a){let {event:b,selector:c}=a.options.advanceOn||{};if(b){let d=Fb(c,a),e;try{e=document.querySelector(c)}catch(f){}if(void 0===c||e)e?(e.addEventListener(b,d),a.on("destroy",()=>e.removeEventListener(b,d))):(document.body.addEventListener(b,d,!0),a.on("destroy",()=>document.body.removeEventListener(b,d,!0)));else return console.error(`No element was found for the selector supplied to advanceOn: ${c}`)}else return console.error("advanceOn was defined, but no event name was passed.")} +function M(a){return a?(a.nodeName||"").toLowerCase():null}function K(a){return null==a?window:"[object Window]"!==a.toString()?(a=a.ownerDocument)?a.defaultView||window:window:a}function fa(a){var b=K(a).Element;return a instanceof b||a instanceof Element}function F(a){var b=K(a).HTMLElement;return a instanceof b||a instanceof HTMLElement}function Ea(a){if("undefined"===typeof ShadowRoot)return!1;var b=K(a).ShadowRoot;return a instanceof b||a instanceof ShadowRoot}function N(a){return a.split("-")[0]} +function ha(a,b){void 0===b&&(b=!1);var c=a.getBoundingClientRect(),d=1,e=1;F(a)&&b&&(b=a.offsetHeight,a=a.offsetWidth,0=Math.abs(b.width-c)&&(c=b.width);1>=Math.abs(b.height-d)&&(d=b.height);return{x:a.offsetLeft,y:a.offsetTop,width:c,height:d}}function Va(a,b){var c= +b.getRootNode&&b.getRootNode();if(a.contains(b))return!0;if(c&&Ea(c)){do{if(b&&a.isSameNode(b))return!0;b=b.parentNode||b.host}while(b)}return!1}function P(a){return K(a).getComputedStyle(a)}function U(a){return((fa(a)?a.ownerDocument:a.document)||window.document).documentElement}function wa(a){return"html"===M(a)?a:a.assignedSlot||a.parentNode||(Ea(a)?a.host:null)||U(a)}function Wa(a){return F(a)&&"fixed"!==P(a).position?a.offsetParent:null}function ra(a){for(var b=K(a),c=Wa(a);c&&0<=["table","td", +"th"].indexOf(M(c))&&"static"===P(c).position;)c=Wa(c);if(c&&("html"===M(c)||"body"===M(c)&&"static"===P(c).position))return b;if(!c)a:{c=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1===navigator.userAgent.indexOf("Trident")||!F(a)||"fixed"!==P(a).position)for(a=wa(a),Ea(a)&&(a=a.host);F(a)&&0>["html","body"].indexOf(M(a));){var d=P(a);if("none"!==d.transform||"none"!==d.perspective||"paint"===d.contain||-1!==["transform","perspective"].indexOf(d.willChange)||c&&"filter"===d.willChange|| +c&&d.filter&&"none"!==d.filter){c=a;break a}else a=a.parentNode}c=null}return c||b}function Ga(a){return 0<=["top","bottom"].indexOf(a)?"x":"y"}function Xa(a){return Object.assign({},{top:0,right:0,bottom:0,left:0},a)}function Ya(a,b){return b.reduce(function(c,d){c[d]=a;return c},{})}function ja(a){return a.split("-")[1]}function Za(a){var b,c=a.popper,d=a.popperRect,e=a.placement,f=a.variation,g=a.offsets,l=a.position,m=a.gpuAcceleration,k=a.adaptive,p=a.roundOffsets,q=a.isFixed;a=g.x;a=void 0=== +a?0:a;var n=g.y,r=void 0===n?0:n;n="function"===typeof p?p({x:a,y:r}):{x:a,y:r};a=n.x;r=n.y;n=g.hasOwnProperty("x");g=g.hasOwnProperty("y");var x="left",h="top",t=window;if(k){var v=ra(c),A="clientHeight",u="clientWidth";v===K(c)&&(v=U(c),"static"!==P(v).position&&"absolute"===l&&(A="scrollHeight",u="scrollWidth"));if("top"===e||("left"===e||"right"===e)&&"end"===f)h="bottom",r-=(q&&v===t&&t.visualViewport?t.visualViewport.height:v[A])-d.height,r*=m?1:-1;if("left"===e||("top"===e||"bottom"===e)&& +"end"===f)x="right",a-=(q&&v===t&&t.visualViewport?t.visualViewport.width:v[u])-d.width,a*=m?1:-1}c=Object.assign({position:l},k&&Hb);!0===p?(p=r,d=window.devicePixelRatio||1,a={x:ia(a*d)/d||0,y:ia(p*d)/d||0}):a={x:a,y:r};p=a;a=p.x;r=p.y;if(m){var w;return Object.assign({},c,(w={},w[h]=g?"0":"",w[x]=n?"0":"",w.transform=1>=(t.devicePixelRatio||1)?"translate("+a+"px, "+r+"px)":"translate3d("+a+"px, "+r+"px, 0)",w))}return Object.assign({},c,(b={},b[h]=g?r+"px":"",b[x]=n?a+"px":"",b.transform="",b))} +function xa(a){return a.replace(/left|right|bottom|top/g,function(b){return Ib[b]})}function $a(a){return a.replace(/start|end/g,function(b){return Jb[b]})}function Ha(a){a=K(a);return{scrollLeft:a.pageXOffset,scrollTop:a.pageYOffset}}function Ia(a){return ha(U(a)).left+Ha(a).scrollLeft}function Ja(a){a=P(a);return/auto|scroll|overlay|hidden/.test(a.overflow+a.overflowY+a.overflowX)}function ab(a){return 0<=["html","body","#document"].indexOf(M(a))?a.ownerDocument.body:F(a)&&Ja(a)?a:ab(wa(a))}function sa(a, +b){var c;void 0===b&&(b=[]);var d=ab(a);a=d===(null==(c=a.ownerDocument)?void 0:c.body);c=K(d);d=a?[c].concat(c.visualViewport||[],Ja(d)?d:[]):d;b=b.concat(d);return a?b:b.concat(sa(wa(d)))}function Ka(a){return Object.assign({},a,{left:a.x,top:a.y,right:a.x+a.width,bottom:a.y+a.height})}function bb(a,b){if("viewport"===b){b=K(a);var c=U(a);b=b.visualViewport;var d=c.clientWidth;c=c.clientHeight;var e=0,f=0;b&&(d=b.width,c=b.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(e=b.offsetLeft, +f=b.offsetTop));a={width:d,height:c,x:e+Ia(a),y:f};a=Ka(a)}else fa(b)?(a=ha(b),a.top+=b.clientTop,a.left+=b.clientLeft,a.bottom=a.top+b.clientHeight,a.right=a.left+b.clientWidth,a.width=b.clientWidth,a.height=b.clientHeight,a.x=a.left,a.y=a.top):(f=U(a),a=U(f),d=Ha(f),b=null==(c=f.ownerDocument)?void 0:c.body,c=L(a.scrollWidth,a.clientWidth,b?b.scrollWidth:0,b?b.clientWidth:0),e=L(a.scrollHeight,a.clientHeight,b?b.scrollHeight:0,b?b.clientHeight:0),f=-d.scrollLeft+Ia(f),d=-d.scrollTop,"rtl"===P(b|| +a).direction&&(f+=L(a.clientWidth,b?b.clientWidth:0)-c),a=Ka({width:c,height:e,x:f,y:d}));return a}function Kb(a){var b=sa(wa(a)),c=0<=["absolute","fixed"].indexOf(P(a).position)&&F(a)?ra(a):a;return fa(c)?b.filter(function(d){return fa(d)&&Va(d,c)&&"body"!==M(d)}):[]}function Lb(a,b,c){b="clippingParents"===b?Kb(a):[].concat(b);c=[].concat(b,[c]);c=c.reduce(function(d,e){e=bb(a,e);d.top=L(e.top,d.top);d.right=V(e.right,d.right);d.bottom=V(e.bottom,d.bottom);d.left=L(e.left,d.left);return d},bb(a, +c[0]));c.width=c.right-c.left;c.height=c.bottom-c.top;c.x=c.left;c.y=c.top;return c}function cb(a){var b=a.reference,c=a.element,d=(a=a.placement)?N(a):null;a=a?ja(a):null;var e=b.x+b.width/2-c.width/2,f=b.y+b.height/2-c.height/2;switch(d){case "top":e={x:e,y:b.y-c.height};break;case "bottom":e={x:e,y:b.y+b.height};break;case "right":e={x:b.x+b.width,y:f};break;case "left":e={x:b.x-c.width,y:f};break;default:e={x:b.x,y:b.y}}d=d?Ga(d):null;if(null!=d)switch(f="y"===d?"height":"width",a){case "start":e[d]-= +b[f]/2-c[f]/2;break;case "end":e[d]+=b[f]/2-c[f]/2}return e}function ta(a,b){void 0===b&&(b={});var c=b;b=c.placement;b=void 0===b?a.placement:b;var d=c.boundary,e=void 0===d?"clippingParents":d;d=c.rootBoundary;var f=void 0===d?"viewport":d;d=c.elementContext;d=void 0===d?"popper":d;var g=c.altBoundary,l=void 0===g?!1:g;c=c.padding;c=void 0===c?0:c;c=Xa("number"!==typeof c?c:Ya(c,ua));g=a.rects.popper;l=a.elements[l?"popper"===d?"reference":"popper":d];e=Lb(fa(l)?l:l.contextElement||U(a.elements.popper), +e,f);f=ha(a.elements.reference);l=cb({reference:f,element:g,strategy:"absolute",placement:b});g=Ka(Object.assign({},g,l));f="popper"===d?g:f;var m={top:e.top-f.top+c.top,bottom:f.bottom-e.bottom+c.bottom,left:e.left-f.left+c.left,right:f.right-e.right+c.right};a=a.modifiersData.offset;if("popper"===d&&a){var k=a[b];Object.keys(m).forEach(function(p){var q=0<=["right","bottom"].indexOf(p)?1:-1,n=0<=["top","bottom"].indexOf(p)?"y":"x";m[p]+=k[n]*q})}return m}function Mb(a,b){void 0===b&&(b={});var c= +b.boundary,d=b.rootBoundary,e=b.padding,f=b.flipVariations,g=b.allowedAutoPlacements,l=void 0===g?db:g,m=ja(b.placement);b=m?f?eb:eb.filter(function(p){return ja(p)===m}):ua;f=b.filter(function(p){return 0<=l.indexOf(p)});0===f.length&&(f=b);var k=f.reduce(function(p,q){p[q]=ta(a,{placement:q,boundary:c,rootBoundary:d,padding:e})[N(q)];return p},{});return Object.keys(k).sort(function(p,q){return k[p]-k[q]})}function Nb(a){if("auto"===N(a))return[];var b=xa(a);return[$a(a),b,$a(b)]}function fb(a, +b,c){void 0===c&&(c={x:0,y:0});return{top:a.top-b.height-c.y,right:a.right-b.width+c.x,bottom:a.bottom-b.height+c.y,left:a.left-b.width-c.x}}function gb(a){return["top","right","bottom","left"].some(function(b){return 0<=a[b]})}function Ob(a,b,c){void 0===c&&(c=!1);var d=F(b),e;if(e=F(b)){var f=b.getBoundingClientRect();e=ia(f.width)/b.offsetWidth||1;f=ia(f.height)/b.offsetHeight||1;e=1!==e||1!==f}f=e;e=U(b);a=ha(a,f);f={scrollLeft:0,scrollTop:0};var g={x:0,y:0};if(d||!d&&!c){if("body"!==M(b)||Ja(e))f= +b!==K(b)&&F(b)?{scrollLeft:b.scrollLeft,scrollTop:b.scrollTop}:Ha(b);F(b)?(g=ha(b,!0),g.x+=b.clientLeft,g.y+=b.clientTop):e&&(g.x=Ia(e))}return{x:a.left+f.scrollLeft-g.x,y:a.top+f.scrollTop-g.y,width:a.width,height:a.height}}function Pb(a){function b(f){d.add(f.name);[].concat(f.requires||[],f.requiresIfExists||[]).forEach(function(g){d.has(g)||(g=c.get(g))&&b(g)});e.push(f)}var c=new Map,d=new Set,e=[];a.forEach(function(f){c.set(f.name,f)});a.forEach(function(f){d.has(f.name)||b(f)});return e}function Qb(a){var b= +Pb(a);return Rb.reduce(function(c,d){return c.concat(b.filter(function(e){return e.phase===d}))},[])}function Sb(a){var b;return function(){b||(b=new Promise(function(c){Promise.resolve().then(function(){b=void 0;c(a())})}));return b}}function Tb(a){var b=a.reduce(function(c,d){var e=c[d.name];c[d.name]=e?Object.assign({},e,d,{options:Object.assign({},e.options,d.options),data:Object.assign({},e.data,d.data)}):d;return c},{});return Object.keys(b).map(function(c){return b[c]})}function hb(){for(var a= +arguments.length,b=Array(a),c=0;c{if("popper"===c){var d=b.attributes[c]|| +{},e=b.elements[c];Object.assign(e.style,{position:"fixed",left:"50%",top:"50%",transform:"translate(-50%, -50%)"});Object.keys(d).forEach(f=>{let g=d[f];!1===g?e.removeAttribute(f):e.setAttribute(f,!0===g?"":g)})}})}},{name:"computeStyles",options:{adaptive:!1}}]}function Vb(a){let b=Ub(),c={placement:"top",strategy:"fixed",modifiers:[{name:"focusAfterRender",enabled:!0,phase:"afterWrite",fn(){setTimeout(()=>{a.el&&a.el.focus()},300)}}]};return c=La({},c,{modifiers:Array.from(new Set([...c.modifiers, +...b]))})}function ib(a){return qa(a)&&""!==a?"-"!==a.charAt(a.length-1)?`${a}-`:a:""}function Ma(){let a=Date.now();return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,b=>{let c=(a+16*Math.random())%16|0;a=Math.floor(a/16);return("x"==b?c:c&3|8).toString(16)})}function Wb(a,b){let c={modifiers:[{name:"preventOverflow",options:{altAxis:!0,tether:!1}},{name:"focusAfterRender",enabled:!0,phase:"afterWrite",fn(){setTimeout(()=>{b.el&&b.el.focus()},300)}}],strategy:"absolute"};void 0!==a&&null!== +a&&a.element&&a.on?c.placement=a.on:c=Vb(b);(a=b.tour&&b.tour.options&&b.tour.options.defaultStepOptions)&&(c=jb(a,c));return c=jb(b.options,c)}function jb(a,b){if(a.popperOptions){let c=Object.assign({},b,a.popperOptions);if(a.popperOptions.modifiers&&0e.name);b=b.modifiers.filter(e=>!d.includes(e.name));c.modifiers=Array.from(new Set([...b,...a.popperOptions.modifiers]))}return c}return b}function G(){}function Xb(a,b){for(let c in b)a[c]= +b[c];return a}function ka(a){return a()}function kb(a){return"function"===typeof a}function Q(a,b){return a!=a?b==b:a!==b||a&&"object"===typeof a||"function"===typeof a}function H(a){a.parentNode.removeChild(a)}function lb(a){return document.createElementNS("http://www.w3.org/2000/svg",a)}function ya(a,b,c,d){a.addEventListener(b,c,d);return()=>a.removeEventListener(b,c,d)}function B(a,b,c){null==c?a.removeAttribute(b):a.getAttribute(b)!==c&&a.setAttribute(b,c)}function mb(a,b){let c=Object.getOwnPropertyDescriptors(a.__proto__); +for(let d in b)null==b[d]?a.removeAttribute(d):"style"===d?a.style.cssText=b[d]:"__value"===d?a.value=a[d]=b[d]:c[d]&&c[d].set?a[d]=b[d]:B(a,d,b[d])}function la(a,b,c){a.classList[c?"add":"remove"](b)}function za(){if(!R)throw Error("Function called outside component initialization");return R}function Na(a){Aa.push(a)}function nb(){let a=R;do{for(;Ba{Ca.delete(a);d&&(c&&a.d(1),d())}),a.o(b))}function da(a){a&&a.c()}function W(a,b,c,d){let {fragment:e, +on_mount:f,on_destroy:g,after_update:l}=a.$$;e&&e.m(b,c);d||Na(()=>{let m=f.map(ka).filter(kb);g?g.push(...m):m.forEach(ka);a.$$.on_mount=[]});l.forEach(Na)}function X(a,b){a=a.$$;null!==a.fragment&&(a.on_destroy.forEach(ka),a.fragment&&a.fragment.d(b),a.on_destroy=a.fragment=null,a.ctx=[])}function S(a,b,c,d,e,f,g,l){void 0===l&&(l=[-1]);let m=R;R=a;let k=a.$$={fragment:null,ctx:null,props:f,update:G,not_equal:e,bound:Object.create(null),on_mount:[],on_destroy:[],on_disconnect:[],before_update:[], +after_update:[],context:new Map(b.context||(m?m.$$.context:[])),callbacks:Object.create(null),dirty:l,skip_bound:!1,root:b.target||m.$$.root};g&&g(k.root);let p=!1;k.ctx=c?c(a,b.props||{},function(q,n){let r=(2>=arguments.length?0:arguments.length-2)?2>=arguments.length?void 0:arguments[2]:n;if(k.ctx&&e(k.ctx[q],k.ctx[q]=r)){if(!k.skip_bound&&k.bound[q])k.bound[q](r);p&&(-1===a.$$.dirty[0]&&(va.push(a),Pa||(Pa=!0,Yb.then(nb)),a.$$.dirty.fill(0)),a.$$.dirty[q/31|0]|=1<{"config"in n&&c(6,e=n.config);"step"in n&&c(7,f= +n.step)};a.$$.update=()=>{a.$$.dirty&192&&(c(0,g=e.action?e.action.bind(f.tour):null),c(1,l=e.classes),c(2,m=e.disabled?d(e.disabled):!1),c(3,k=e.label?d(e.label):null),c(4,p=e.secondary),c(5,q=e.text?d(e.text):null))};return[g,l,m,k,p,q,e,f]}function pb(a,b,c){a=a.slice();a[2]=b[c];return a}function qb(a){let b,c,d=a[1],e=[];for(let g=0;gC(e[g],1,1,()=>{e[g]=null});return{c(){for(let g=0;g{d=null}),ca())},i(e){c||(z(d), +c=!0)},o(e){C(d);c=!1},d(e){e&&H(b);d&&d.d()}}}function cc(a,b,c){let d,{step:e}=b;a.$$set=f=>{"step"in f&&c(0,e=f.step)};a.$$.update=()=>{a.$$.dirty&1&&c(1,d=e.options.buttons)};return[e,d]}function dc(a){let b,c,d,e,f;return{c(){b=document.createElement("button");c=document.createElement("span");c.textContent="\u00d7";B(c,"aria-hidden","true");B(b,"aria-label",d=a[0].label?a[0].label:"Close Tour");B(b,"class","shepherd-cancel-icon");B(b,"type","button")},m(g,l){g.insertBefore(b,l||null);b.appendChild(c); +e||(f=ya(b,"click",a[1]),e=!0)},p(g,l){[l]=l;l&1&&d!==(d=g[0].label?g[0].label:"Close Tour")&&B(b,"aria-label",d)},i:G,o:G,d(g){g&&H(b);e=!1;f()}}}function ec(a,b,c){let {cancelIcon:d,step:e}=b;a.$$set=f=>{"cancelIcon"in f&&c(0,d=f.cancelIcon);"step"in f&&c(2,e=f.step)};return[d,f=>{f.preventDefault();e.cancel()},e]}function fc(a){let b;return{c(){b=document.createElement("h3");B(b,"id",a[1]);B(b,"class","shepherd-title")},m(c,d){c.insertBefore(b,d||null);a[3](b)},p(c,d){[d]=d;d&2&&B(b,"id",c[1])}, +i:G,o:G,d(c){c&&H(b);a[3](null)}}}function gc(a,b,c){let {labelId:d,element:e,title:f}=b;za().$$.after_update.push(()=>{Z(f)&&c(2,f=f());c(0,e.innerHTML=f,e)});a.$$set=g=>{"labelId"in g&&c(1,d=g.labelId);"element"in g&&c(0,e=g.element);"title"in g&&c(2,f=g.title)};return[e,d,f,function(g){ma[g?"unshift":"push"](()=>{e=g;c(0,e)})}]}function sb(a){let b,c;b=new hc({props:{labelId:a[0],title:a[2]}});return{c(){da(b.$$.fragment)},m(d,e){W(b,d,e);c=!0},p(d,e){let f={};e&1&&(f.labelId=d[0]);e&4&&(f.title= +d[2]);b.$set(f)},i(d){c||(z(b.$$.fragment,d),c=!0)},o(d){C(b.$$.fragment,d);c=!1},d(d){X(b,d)}}}function tb(a){let b,c;b=new ic({props:{cancelIcon:a[3],step:a[1]}});return{c(){da(b.$$.fragment)},m(d,e){W(b,d,e);c=!0},p(d,e){let f={};e&8&&(f.cancelIcon=d[3]);e&2&&(f.step=d[1]);b.$set(f)},i(d){c||(z(b.$$.fragment,d),c=!0)},o(d){C(b.$$.fragment,d);c=!1},d(d){X(b,d)}}}function jc(a){let b,c,d,e=a[2]&&sb(a),f=a[3]&&a[3].enabled&&tb(a);return{c(){b=document.createElement("header");e&&e.c();c=document.createTextNode(" "); +f&&f.c();B(b,"class","shepherd-header")},m(g,l){g.insertBefore(b,l||null);e&&e.m(b,null);b.appendChild(c);f&&f.m(b,null);d=!0},p(g,l){[l]=l;g[2]?e?(e.p(g,l),l&4&&z(e,1)):(e=sb(g),e.c(),z(e,1),e.m(b,c)):e&&(aa(),C(e,1,1,()=>{e=null}),ca());g[3]&&g[3].enabled?f?(f.p(g,l),l&8&&z(f,1)):(f=tb(g),f.c(),z(f,1),f.m(b,null)):f&&(aa(),C(f,1,1,()=>{f=null}),ca())},i(g){d||(z(e),z(f),d=!0)},o(g){C(e);C(f);d=!1},d(g){g&&H(b);e&&e.d();f&&f.d()}}}function kc(a,b,c){let {labelId:d,step:e}=b,f,g;a.$$set=l=>{"labelId"in +l&&c(0,d=l.labelId);"step"in l&&c(1,e=l.step)};a.$$.update=()=>{a.$$.dirty&2&&(c(2,f=e.options.title),c(3,g=e.options.cancelIcon))};return[d,e,f,g]}function lc(a){let b;return{c(){b=document.createElement("div");B(b,"class","shepherd-text");B(b,"id",a[1])},m(c,d){c.insertBefore(b,d||null);a[3](b)},p(c,d){[d]=d;d&2&&B(b,"id",c[1])},i:G,o:G,d(c){c&&H(b);a[3](null)}}}function mc(a,b,c){let {descriptionId:d,element:e,step:f}=b;za().$$.after_update.push(()=>{let {text:g}=f.options;Z(g)&&(g=g.call(f)); +g instanceof HTMLElement?e.appendChild(g):c(0,e.innerHTML=g,e)});a.$$set=g=>{"descriptionId"in g&&c(1,d=g.descriptionId);"element"in g&&c(0,e=g.element);"step"in g&&c(2,f=g.step)};return[e,d,f,function(g){ma[g?"unshift":"push"](()=>{e=g;c(0,e)})}]}function ub(a){let b,c;b=new nc({props:{labelId:a[1],step:a[2]}});return{c(){da(b.$$.fragment)},m(d,e){W(b,d,e);c=!0},p(d,e){let f={};e&2&&(f.labelId=d[1]);e&4&&(f.step=d[2]);b.$set(f)},i(d){c||(z(b.$$.fragment,d),c=!0)},o(d){C(b.$$.fragment,d);c=!1},d(d){X(b, +d)}}}function vb(a){let b,c;b=new oc({props:{descriptionId:a[0],step:a[2]}});return{c(){da(b.$$.fragment)},m(d,e){W(b,d,e);c=!0},p(d,e){let f={};e&1&&(f.descriptionId=d[0]);e&4&&(f.step=d[2]);b.$set(f)},i(d){c||(z(b.$$.fragment,d),c=!0)},o(d){C(b.$$.fragment,d);c=!1},d(d){X(b,d)}}}function wb(a){let b,c;b=new pc({props:{step:a[2]}});return{c(){da(b.$$.fragment)},m(d,e){W(b,d,e);c=!0},p(d,e){let f={};e&4&&(f.step=d[2]);b.$set(f)},i(d){c||(z(b.$$.fragment,d),c=!0)},o(d){C(b.$$.fragment,d);c=!1},d(d){X(b, +d)}}}function qc(a){let b,c=void 0!==a[2].options.title||a[2].options.cancelIcon&&a[2].options.cancelIcon.enabled,d,e=void 0!==a[2].options.text,f,g=Array.isArray(a[2].options.buttons)&&a[2].options.buttons.length,l,m=c&&ub(a),k=e&&vb(a),p=g&&wb(a);return{c(){b=document.createElement("div");m&&m.c();d=document.createTextNode(" ");k&&k.c();f=document.createTextNode(" ");p&&p.c();B(b,"class","shepherd-content")},m(q,n){q.insertBefore(b,n||null);m&&m.m(b,null);b.appendChild(d);k&&k.m(b,null);b.appendChild(f); +p&&p.m(b,null);l=!0},p(q,n){[n]=n;n&4&&(c=void 0!==q[2].options.title||q[2].options.cancelIcon&&q[2].options.cancelIcon.enabled);c?m?(m.p(q,n),n&4&&z(m,1)):(m=ub(q),m.c(),z(m,1),m.m(b,d)):m&&(aa(),C(m,1,1,()=>{m=null}),ca());n&4&&(e=void 0!==q[2].options.text);e?k?(k.p(q,n),n&4&&z(k,1)):(k=vb(q),k.c(),z(k,1),k.m(b,f)):k&&(aa(),C(k,1,1,()=>{k=null}),ca());n&4&&(g=Array.isArray(q[2].options.buttons)&&q[2].options.buttons.length);g?p?(p.p(q,n),n&4&&z(p,1)):(p=wb(q),p.c(),z(p,1),p.m(b,null)):p&&(aa(), +C(p,1,1,()=>{p=null}),ca())},i(q){l||(z(m),z(k),z(p),l=!0)},o(q){C(m);C(k);C(p);l=!1},d(q){q&&H(b);m&&m.d();k&&k.d();p&&p.d()}}}function rc(a,b,c){let {descriptionId:d,labelId:e,step:f}=b;a.$$set=g=>{"descriptionId"in g&&c(0,d=g.descriptionId);"labelId"in g&&c(1,e=g.labelId);"step"in g&&c(2,f=g.step)};return[d,e,f]}function xb(a){let b;return{c(){b=document.createElement("div");B(b,"class","shepherd-arrow");B(b,"data-popper-arrow","")},m(c,d){c.insertBefore(b,d||null)},d(c){c&&H(b)}}}function sc(a){let b, +c,d,e,f,g,l,m,k=a[4].options.arrow&&a[4].options.attachTo&&a[4].options.attachTo.element&&a[4].options.attachTo.on&&xb();d=new tc({props:{descriptionId:a[2],labelId:a[3],step:a[4]}});let p=[{"aria-describedby":e=void 0!==a[4].options.text?a[2]:null},{"aria-labelledby":f=a[4].options.title?a[3]:null},a[1],{role:"dialog"},{tabindex:"0"}],q={};for(let n=0;n!!b.length)}function uc(a,b,c){let {classPrefix:d,element:e,descriptionId:f,firstFocusableElement:g,focusableElements:l,labelId:m,lastFocusableElement:k,step:p,dataStepId:q}=b,n,r,x;za().$$.on_mount.push(()=>{c(1,q={[`data-${d}shepherd-step-id`]:p.id});c(9,l=e.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]')); +c(8,g=l[0]);c(10,k=l[l.length-1])});za().$$.after_update.push(()=>{if(x!==p.options.classes){var h=x;qa(h)&&(h=yb(h),h.length&&e.classList.remove(...h));h=x=p.options.classes;qa(h)&&(h=yb(h),h.length&&e.classList.add(...h))}});a.$$set=h=>{"classPrefix"in h&&c(11,d=h.classPrefix);"element"in h&&c(0,e=h.element);"descriptionId"in h&&c(2,f=h.descriptionId);"firstFocusableElement"in h&&c(8,g=h.firstFocusableElement);"focusableElements"in h&&c(9,l=h.focusableElements);"labelId"in h&&c(3,m=h.labelId);"lastFocusableElement"in +h&&c(10,k=h.lastFocusableElement);"step"in h&&c(4,p=h.step);"dataStepId"in h&&c(1,q=h.dataStepId)};a.$$.update=()=>{a.$$.dirty&16&&(c(5,n=p.options&&p.options.cancelIcon&&p.options.cancelIcon.enabled),c(6,r=p.options&&p.options.title))};return[e,q,f,m,p,n,r,h=>{const {tour:t}=p;switch(h.keyCode){case 9:if(0===l.length){h.preventDefault();break}if(h.shiftKey){if(document.activeElement===g||document.activeElement.classList.contains("shepherd-element"))h.preventDefault(),k.focus()}else document.activeElement=== +k&&(h.preventDefault(),g.focus());break;case 27:t.options.exitOnEsc&&p.cancel();break;case 37:t.options.keyboardNavigation&&t.back();break;case 39:t.options.keyboardNavigation&&t.next()}},g,l,k,d,()=>e,function(h){ma[h?"unshift":"push"](()=>{e=h;c(0,e)})}]}function vc(a){a&&({steps:a}=a,a.forEach(b=>{b.options&&!1===b.options.canClickTarget&&b.options.attachTo&&b.target instanceof HTMLElement&&b.target.classList.remove("shepherd-target-click-disabled")}))}function wc(a){let b,c,d,e,f;return{c(){b= +lb("svg");c=lb("path");B(c,"d",a[2]);B(b,"class",d=`${a[1]?"shepherd-modal-is-visible":""} shepherd-modal-overlay-container`)},m(g,l){g.insertBefore(b,l||null);b.appendChild(c);a[11](b);e||(f=ya(b,"touchmove",a[3]),e=!0)},p(g,l){[l]=l;l&4&&B(c,"d",g[2]);l&2&&d!==(d=`${g[1]?"shepherd-modal-is-visible":""} shepherd-modal-overlay-container`)&&B(b,"class",d)},i:G,o:G,d(g){g&&H(b);a[11](null);e=!1;f()}}}function zb(a){if(!a)return null;let b=a instanceof HTMLElement&&window.getComputedStyle(a).overflowY; +return"hidden"!==b&&"visible"!==b&&a.scrollHeight>=a.clientHeight?a:zb(a.parentElement)}function xc(a,b,c){function d(){c(4,p={width:0,height:0,x:0,y:0,r:0})}function e(){c(1,q=!1);l()}function f(h,t,v,A){void 0===h&&(h=0);void 0===t&&(t=0);if(A){var u=A.getBoundingClientRect();let y=u.y||u.top;u=u.bottom||y+u.height;if(v){var w=v.getBoundingClientRect();v=w.y||w.top;w=w.bottom||v+w.height;y=Math.max(y,v);u=Math.min(u,w)}let {y:Y,height:E}={y,height:Math.max(u-y,0)},{x:I,width:D,left:na}=A.getBoundingClientRect(); +c(4,p={width:D+2*h,height:E+2*h,x:(I||na)-h,y:Y-h,r:t})}else d()}function g(){c(1,q=!0)}function l(){n&&(cancelAnimationFrame(n),n=void 0);window.removeEventListener("touchmove",x,{passive:!1})}function m(h){let {modalOverlayOpeningPadding:t,modalOverlayOpeningRadius:v}=h.options,A=zb(h.target),u=()=>{n=void 0;f(t,v,A,h.target);n=requestAnimationFrame(u)};u();window.addEventListener("touchmove",x,{passive:!1})}let {element:k,openingProperties:p}=b;Ma();let q=!1,n=void 0,r;d();let x=h=>{h.preventDefault()}; +a.$$set=h=>{"element"in h&&c(0,k=h.element);"openingProperties"in h&&c(4,p=h.openingProperties)};a.$$.update=()=>{if(a.$$.dirty&16){let {width:h,height:t,x:v=0,y:A=0,r:u=0}=p,{innerWidth:w,innerHeight:y}=window;c(2,r=`M${w},${y}\ +H0\ +V0\ +H${w}\ +V${y}\ +Z\ +M${v+u},${A}\ +a${u},${u},0,0,0-${u},${u}\ +V${t+A-u}\ +a${u},${u},0,0,0,${u},${u}\ +H${h+v-u}\ +a${u},${u},0,0,0,${u}-${u}\ +V${A+u}\ +a${u},${u},0,0,0-${u}-${u}\ +Z`)}};return[k,q,r,h=>{h.stopPropagation()},p,()=>k,d,e,f,function(h){l();h.tour.options.useModalOverlay?(m(h),g()):e()},g,function(h){ma[h?"unshift":"push"](()=>{k=h;c(0,k)})}]}var Eb=function(a){var b;if(b=!!a&&"object"===typeof a)b=Object.prototype.toString.call(a),b=!("[object RegExp]"===b||"[object Date]"===b||a.$$typeof===yc);return b},yc="function"===typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;ea.all=function(a,b){if(!Array.isArray(a))throw Error("first argument should be an array"); +return a.reduce(function(c,d){return ea(c,d,b)},{})};var zc=ea;class Qa{on(a,b,c,d){void 0===d&&(d=!1);void 0===this.bindings&&(this.bindings={});void 0===this.bindings[a]&&(this.bindings[a]=[]);this.bindings[a].push({handler:b,ctx:c,once:d});return this}once(a,b,c){return this.on(a,b,c,!0)}off(a,b){if(void 0===this.bindings||void 0===this.bindings[a])return this;void 0===b?delete this.bindings[a]:this.bindings[a].forEach((c,d)=>{c.handler===b&&this.bindings[a].splice(d,1)});return this}trigger(a){for(var b= +arguments.length,c=Array(1{let {ctx:g,handler:l,once:m}=e;l.apply(g||this,c);m&&this.bindings[a].splice(f,1)});return this}}var ua=["top","bottom","right","left"],eb=ua.reduce(function(a,b){return a.concat([b+"-start",b+"-end"])},[]),db=[].concat(ua,["auto"]).reduce(function(a,b){return a.concat([b,b+"-start",b+"-end"])},[]),Rb="beforeRead read afterRead beforeMain main afterMain beforeWrite write afterWrite".split(" "), +L=Math.max,V=Math.min,ia=Math.round,Hb={top:"auto",right:"auto",bottom:"auto",left:"auto"},Da={passive:!0},Ib={left:"right",right:"left",bottom:"top",top:"bottom"},Jb={start:"end",end:"start"},Ab={placement:"bottom",modifiers:[],strategy:"absolute"},Ac=function(a){void 0===a&&(a={});var b=a.defaultModifiers,c=void 0===b?[]:b;a=a.defaultOptions;var d=void 0===a?Ab:a;return function(e,f,g){function l(){k.orderedModifiers.forEach(function(r){var x=r.name,h=r.options;h=void 0===h?{}:h;r=r.effect;"function"=== +typeof r&&(x=r({state:k,name:x,instance:n,options:h}),p.push(x||function(){}))})}function m(){p.forEach(function(r){return r()});p=[]}void 0===g&&(g=d);var k={placement:"bottom",orderedModifiers:[],options:Object.assign({},Ab,d),modifiersData:{},elements:{reference:e,popper:f},attributes:{},styles:{}},p=[],q=!1,n={state:k,setOptions:function(r){r="function"===typeof r?r(k.options):r;m();k.options=Object.assign({},d,k.options,r);k.scrollParents={reference:fa(e)?sa(e):e.contextElement?sa(e.contextElement): +[],popper:sa(f)};r=Qb(Tb([].concat(c,k.options.modifiers)));k.orderedModifiers=r.filter(function(x){return x.enabled});l();return n.update()},forceUpdate:function(){if(!q){var r=k.elements,x=r.reference;r=r.popper;if(hb(x,r))for(k.rects={reference:Ob(x,ra(r),"fixed"===k.options.strategy),popper:Fa(r)},k.reset=!1,k.placement=k.options.placement,k.orderedModifiers.forEach(function(v){return k.modifiersData[v.name]=Object.assign({},v.data)}),x=0;xf[y]&&(u=xa(u));y=xa(u);w=[];d&&w.push(0>=Y[A]);e&&w.push(0>=Y[u],0>=Y[y]);if(w.every(function(E){return E})){h=v;p=!1;break}x.set(v,w)}if(p)for(d=function(E){var I=r.find(function(D){if(D=x.get(D))return D.slice(0, +E).every(function(na){return na})});if(I)return h=I,"break"},e=q?3:1;0r?r:J):J=L(g?J:v,V(f,g?r:m));d[c]=J;l[c]=J-f}b.modifiersData[a]=l}},requiresIfExists:["offset"]},{name:"arrow",enabled:!0,phase:"main", +fn:function(a){var b,c=a.state,d=a.name,e=a.options,f=c.elements.arrow,g=c.modifiersData.popperOffsets,l=N(c.placement);a=Ga(l);l=0<=["left","right"].indexOf(l)?"height":"width";if(f&&g){e=e.padding;e="function"===typeof e?e(Object.assign({},c.rects,{placement:c.placement})):e;e=Xa("number"!==typeof e?e:Ya(e,ua));var m=Fa(f),k="y"===a?"top":"left",p="y"===a?"bottom":"right",q=c.rects.reference[l]+c.rects.reference[a]-g[a]-c.rects.popper[l];g=g[a]-c.rects.reference[a];f=(f=ra(f))?"y"===a?f.clientHeight|| +0:f.clientWidth||0:0;g=f/2-m[l]/2+(q/2-g/2);l=L(e[k],V(g,f-m[l]-e[p]));c.modifiersData[d]=(b={},b[a]=l,b.centerOffset=l-g,b)}},effect:function(a){var b=a.state;a=a.options.element;a=void 0===a?"[data-popper-arrow]":a;if(null!=a){if("string"===typeof a&&(a=b.elements.popper.querySelector(a),!a))return;Va(b.elements.popper,a)&&(b.elements.arrow=a)}},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},{name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(a){var b= +a.state;a=a.name;var c=b.rects.reference,d=b.rects.popper,e=b.modifiersData.preventOverflow,f=ta(b,{elementContext:"reference"}),g=ta(b,{altBoundary:!0});c=fb(f,c);d=fb(g,d,e);e=gb(c);g=gb(d);b.modifiersData[a]={referenceClippingOffsets:c,popperEscapeOffsets:d,isReferenceHidden:e,hasPopperEscaped:g};b.attributes.popper=Object.assign({},b.attributes.popper,{"data-popper-reference-hidden":e,"data-popper-escaped":g})}}]});let R,va=[],ma=[],Aa=[],ob=[],Yb=Promise.resolve(),Pa=!1,Oa=new Set,Ba=0,Ca=new Set, +ba;class T{$destroy(){X(this,1);this.$destroy=G}$on(a,b){let c=this.$$.callbacks[a]||(this.$$.callbacks[a]=[]);c.push(b);return()=>{let d=c.indexOf(b);-1!==d&&c.splice(d,1)}}$set(a){this.$$set&&0!==Object.keys(a).length&&(this.$$.skip_bound=!0,this.$$set(a),this.$$.skip_bound=!1)}}class ac extends T{constructor(a){super();S(this,a,$b,Zb,Q,{config:6,step:7})}}class pc extends T{constructor(a){super();S(this,a,cc,bc,Q,{step:0})}}class ic extends T{constructor(a){super();S(this,a,ec,dc,Q,{cancelIcon:0, +step:2})}}class hc extends T{constructor(a){super();S(this,a,gc,fc,Q,{labelId:1,element:0,title:2})}}class nc extends T{constructor(a){super();S(this,a,kc,jc,Q,{labelId:0,step:1})}}class oc extends T{constructor(a){super();S(this,a,mc,lc,Q,{descriptionId:1,element:0,step:2})}}class tc extends T{constructor(a){super();S(this,a,rc,qc,Q,{descriptionId:0,labelId:1,step:2})}}class Bc extends T{constructor(a){super();S(this,a,uc,sc,Q,{classPrefix:11,element:0,descriptionId:2,firstFocusableElement:8,focusableElements:9, +labelId:3,lastFocusableElement:10,step:4,dataStepId:1,getElement:12})}get getElement(){return this.$$.ctx[12]}}var Bb=function(a,b){return b={exports:{}},a(b,b.exports),b.exports}(function(a,b){(function(){a.exports={polyfill:function(){function c(h,t){this.scrollLeft=h;this.scrollTop=t}function d(h){if(null===h||"object"!==typeof h||void 0===h.behavior||"auto"===h.behavior||"instant"===h.behavior)return!0;if("object"===typeof h&&"smooth"===h.behavior)return!1;throw new TypeError("behavior member of ScrollOptions "+ +h.behavior+" is not a valid value for enumeration ScrollBehavior.");}function e(h,t){if("Y"===t)return h.clientHeight+xthis._show())}this._show()}updateStepOptions(a){Object.assign(this.options,a);this.shepherdElementComponent&&this.shepherdElementComponent.$set({step:this})}getElement(){return this.el}getTarget(){return this.target}_createTooltipContent(){this.shepherdElementComponent=new Bc({target:this.tour.options.stepsContainer|| +document.body,props:{classPrefix:this.classPrefix,descriptionId:`${this.id}-description`,labelId:`${this.id}-label`,step:this,styles:this.styles}});return this.shepherdElementComponent.getElement()}_scrollTo(a){let {element:b}=this._getResolvedAttachToOptions();Z(this.options.scrollToHandler)?this.options.scrollToHandler(b):b instanceof Element&&"function"===typeof b.scrollIntoView&&b.scrollIntoView(a)}_getClassOptions(a){var b=this.tour&&this.tour.options&&this.tour.options.defaultStepOptions;b= +b&&b.classes?b.classes:"";a=[...(a.classes?a.classes:"").split(" "),...b.split(" ")];a=new Set(a);return Array.from(a).join(" ").trim()}_setOptions(a){void 0===a&&(a={});let b=this.tour&&this.tour.options&&this.tour.options.defaultStepOptions;b=zc({},b||{});this.options=Object.assign({arrow:!0},b,a);let {when:c}=this.options;this.options.classes=this._getClassOptions(a);this.destroy();this.id=this.options.id||`step-${Ma()}`;c&&Object.keys(c).forEach(d=>{this.on(d,c[d],this)})}_setupElements(){void 0!== +this.el&&this.destroy();this.el=this._createTooltipContent();this.options.advanceOn&&Gb(this);this.tooltip&&this.tooltip.destroy();let a=this._getResolvedAttachToOptions(),b=a.element,c=Wb(a,this);void 0!==a&&null!==a&&a.element&&a.on||(b=document.body,this.shepherdElementComponent.getElement().classList.add("shepherd-centered"));this.tooltip=Ac(b,this.el,c);this.target=a.element}_show(){this.trigger("before-show");this._resolveAttachToOptions();this._setupElements();this.tour.modal||this.tour._setupModal(); +this.tour.modal.setupForStep(this);this._styleTargetElementForStep(this);this.el.hidden=!1;this.options.scrollTo&&setTimeout(()=>{this._scrollTo(this.options.scrollTo)});this.el.hidden=!1;let a=this.shepherdElementComponent.getElement(),b=this.target||document.body;b.classList.add(`${this.classPrefix}shepherd-enabled`);b.classList.add(`${this.classPrefix}shepherd-target`);a.classList.add("shepherd-enabled");this.trigger("show")}_styleTargetElementForStep(a){let b=a.target;b&&(a.options.highlightClass&& +b.classList.add(a.options.highlightClass),b.classList.remove("shepherd-target-click-disabled"),!1===a.options.canClickTarget&&b.classList.add("shepherd-target-click-disabled"))}_updateStepTargetOnHide(){let a=this.target||document.body;this.options.highlightClass&&a.classList.remove(this.options.highlightClass);a.classList.remove("shepherd-target-click-disabled",`${this.classPrefix}shepherd-enabled`,`${this.classPrefix}shepherd-target`)}}class Cc extends T{constructor(a){super();S(this,a,xc,wc,Q, +{element:0,openingProperties:4,getElement:5,closeModalOpening:6,hide:7,positionModal:8,setupForStep:9,show:10})}get getElement(){return this.$$.ctx[5]}get closeModalOpening(){return this.$$.ctx[6]}get hide(){return this.$$.ctx[7]}get positionModal(){return this.$$.ctx[8]}get setupForStep(){return this.$$.ctx[9]}get show(){return this.$$.ctx[10]}}let oa=new Qa;class Dc extends Qa{constructor(a){void 0===a&&(a={});super(a);Ua(this);this.options=Object.assign({},{exitOnEsc:!0,keyboardNavigation:!0}, +a);this.classPrefix=ib(this.options.classPrefix);this.steps=[];this.addSteps(this.options.steps);"active cancel complete inactive show start".split(" ").map(b=>{(c=>{this.on(c,d=>{d=d||{};d.tour=this;oa.trigger(c,d)})})(b)});this._setTourID();return this}addStep(a,b){a instanceof Ra?a.tour=this:a=new Ra(this,a);void 0!==b?this.steps.splice(b,0,a):this.steps.push(a);return a}addSteps(a){Array.isArray(a)&&a.forEach(b=>{this.addStep(b)});return this}back(){let a=this.steps.indexOf(this.currentStep); +this.show(a-1,!1)}cancel(){this.options.confirmCancel?window.confirm(this.options.confirmCancelMessage||"Are you sure you want to stop the tour?")&&this._done("cancel"):this._done("cancel")}complete(){this._done("complete")}getById(a){return this.steps.find(b=>b.id===a)}getCurrentStep(){return this.currentStep}hide(){let a=this.getCurrentStep();if(a)return a.hide()}isActive(){return oa.activeTour===this}next(){let a=this.steps.indexOf(this.currentStep);a===this.steps.length-1?this.complete():this.show(a+ +1,!0)}removeStep(a){let b=this.getCurrentStep();this.steps.some((c,d)=>{if(c.id===a)return c.isOpen()&&c.hide(),c.destroy(),this.steps.splice(d,1),!0});b&&b.id===a&&(this.currentStep=void 0,this.steps.length?this.show(0):this.cancel())}show(a,b){void 0===a&&(a=0);void 0===b&&(b=!0);if(a=qa(a)?this.getById(a):this.steps[a])this._updateStateBeforeShow(),Z(a.options.showOn)&&!a.options.showOn()?this._skipStep(a,b):(this.trigger("show",{step:a,previous:this.currentStep}),this.currentStep=a,a.show())}start(){this.trigger("start"); +this.focusedElBeforeOpen=document.activeElement;this.currentStep=null;this._setupModal();this._setupActiveTour();this.next()}_done(a){let b=this.steps.indexOf(this.currentStep);Array.isArray(this.steps)&&this.steps.forEach(c=>c.destroy());vc(this);this.trigger(a,{index:b});oa.activeTour=null;this.trigger("inactive",{tour:this});this.modal&&this.modal.hide();"cancel"!==a&&"complete"!==a||!this.modal||(a=document.querySelector(".shepherd-modal-overlay-container"))&&a.remove();this.focusedElBeforeOpen instanceof +HTMLElement&&this.focusedElBeforeOpen.focus()}_setupActiveTour(){this.trigger("active",{tour:this});oa.activeTour=this}_setupModal(){this.modal=new Cc({target:this.options.modalContainer||document.body,props:{classPrefix:this.classPrefix,styles:this.styles}})}_skipStep(a,b){a=this.steps.indexOf(a);a===this.steps.length-1?this.complete():this.show(b?a+1:a-1,b)}_updateStateBeforeShow(){this.currentStep&&this.currentStep.hide();this.isActive()||this._setupActiveTour()}_setTourID(){this.id=`${this.options.tourName|| +"tour"}--${Ma()}`}}Object.assign(oa,{Tour:Dc,Step:Ra});return oa}) +//# sourceMappingURL=shepherd.min.js.map From cfa659748ce21bb5ee233465b0aabba4dc11bf30 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 12 Jun 2022 13:42:06 +1000 Subject: [PATCH 006/426] add shepherd styles for guided tour Adds a sass file based on the v10.0.0 Shepherd CSS. Original Shepherd styles are kept where appropriate, otherwise this is intended to inherit whatever styles are being used through the Bulma and Bookwyrm SASS, so that it uses appropriate colours in both light and dark modes. --- bookwyrm/static/css/themes/bookwyrm-dark.scss | 1 + .../static/css/themes/bookwyrm-light.scss | 1 + bookwyrm/static/css/vendor/shepherd.scss | 40 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 bookwyrm/static/css/vendor/shepherd.scss diff --git a/bookwyrm/static/css/themes/bookwyrm-dark.scss b/bookwyrm/static/css/themes/bookwyrm-dark.scss index 88ee865bb..a2eb94efb 100644 --- a/bookwyrm/static/css/themes/bookwyrm-dark.scss +++ b/bookwyrm/static/css/themes/bookwyrm-dark.scss @@ -94,3 +94,4 @@ $family-secondary: $family-sans-serif; @import "../bookwyrm.scss"; @import "../vendor/icons.css"; +@import "../vendor/shepherd.scss"; diff --git a/bookwyrm/static/css/themes/bookwyrm-light.scss b/bookwyrm/static/css/themes/bookwyrm-light.scss index 75f05164b..efb13c23e 100644 --- a/bookwyrm/static/css/themes/bookwyrm-light.scss +++ b/bookwyrm/static/css/themes/bookwyrm-light.scss @@ -67,3 +67,4 @@ $family-secondary: $family-sans-serif; @import "../bookwyrm.scss"; @import "../vendor/icons.css"; +@import "../vendor/shepherd.scss"; diff --git a/bookwyrm/static/css/vendor/shepherd.scss b/bookwyrm/static/css/vendor/shepherd.scss new file mode 100644 index 000000000..727ef55db --- /dev/null +++ b/bookwyrm/static/css/vendor/shepherd.scss @@ -0,0 +1,40 @@ +/* + Shepherd styles for guided tour. + Based on Shepherd v 10.0.0 styles. +*/ + +@import 'bulma/bulma.sass'; + +.shepherd-button { + @extend .button.mr-2; +} + +.shepherd-button.shepherd-button-secondary { + @extend .button.is-light; +} + +.shepherd-footer { + @extend .message-body; + @extend .is-primary.is-light; + border-color: $primary-light; +} + +.shepherd-cancel-icon{background:transparent;border:none;color:hsla(0,0%,50%,.75);cursor:pointer;font-size:2em;font-weight:400;margin:0;padding:0;transition:color .5s ease}.shepherd-cancel-icon:hover{color:rgba(0,0,0,.75)}.shepherd-has-title .shepherd-content .shepherd-cancel-icon{color:hsla(0,0%,50%,.75)}.shepherd-has-title .shepherd-content .shepherd-cancel-icon:hover{color:rgba(0,0,0,.75)} + +.shepherd-header { + @extend .message-header; + @extend .is-primary; +} + +.shepherd-text { + @extend .message-body; + @extend .is-primary.is-light; +} + +.shepherd-content { + @extend .message; +} + +.shepherd-element{background:$primary-light;border-radius:5px;box-shadow:0 1px 4px rgba(0,0,0,.2);max-width:400px;opacity:0;outline:none;transition:opacity .3s,visibility .3s;visibility:hidden;width:100%;z-index:9999}.shepherd-enabled.shepherd-element{opacity:1;visibility:visible}.shepherd-element[data-popper-reference-hidden]:not(.shepherd-centered){opacity:0;pointer-events:none;visibility:hidden}.shepherd-element,.shepherd-element *,.shepherd-element :after,.shepherd-element :before{box-sizing:border-box}.shepherd-arrow,.shepherd-arrow:before{height:16px;position:absolute;width:16px;z-index:-1}.shepherd-arrow:before{background:$primary-light;box-shadow:0 1px 4px rgba(0,0,0,.2);content:"";transform:rotate(45deg)}.shepherd-element[data-popper-placement^=top]>.shepherd-arrow{bottom:-8px}.shepherd-element[data-popper-placement^=bottom]>.shepherd-arrow{top:-8px}.shepherd-element[data-popper-placement^=left]>.shepherd-arrow{right:-8px}.shepherd-element[data-popper-placement^=right]>.shepherd-arrow{left:-8px}.shepherd-element.shepherd-centered>.shepherd-arrow{opacity:0}.shepherd-element.shepherd-has-title[data-popper-placement^=bottom]>.shepherd-arrow:before{background-color:$primary-light}.shepherd-target-click-disabled.shepherd-enabled.shepherd-target,.shepherd-target-click-disabled.shepherd-enabled.shepherd-target *{pointer-events:none} + +.shepherd-modal-overlay-container{height:0;left:0;opacity:0;overflow:hidden;pointer-events:none;position:fixed;top:0;transition:all .3s ease-out,height 0ms .3s,opacity .3s 0ms;width:100vw;z-index:9997}.shepherd-modal-overlay-container.shepherd-modal-is-visible{height:100vh;opacity:.5;transform:translateZ(0);transition:all .3s ease-out,height 0s 0s,opacity .3s 0s}.shepherd-modal-overlay-container.shepherd-modal-is-visible path{pointer-events:all} From f81095cb649295b3f27f595b9bc030ca0c0ed953 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 12 Jun 2022 19:40:36 +1000 Subject: [PATCH 007/426] give suggested books block an id --- bookwyrm/templates/feed/suggested_books.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/feed/suggested_books.html b/bookwyrm/templates/feed/suggested_books.html index 12e478201..d94435800 100644 --- a/bookwyrm/templates/feed/suggested_books.html +++ b/bookwyrm/templates/feed/suggested_books.html @@ -2,7 +2,7 @@ {% load feed_page_tags %} {% suggested_books as suggested_books %} -
+

{% trans "Your Books" %}

{% if not suggested_books %} From 806e2778dfe828e27f2d2ce6460434b1bf4d35da Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 12 Jun 2022 19:41:50 +1000 Subject: [PATCH 008/426] add help button if page has tour - include logic in main layout to add button if there is a page tour available - add button for main user feed page --- bookwyrm/templates/layout.html | 6 ++++++ bookwyrm/views/feed.py | 1 + 2 files changed, 7 insertions(+) diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 6b9e4daa1..426d5cd0b 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -85,6 +85,10 @@ {% trans "Discover" %} + {% if has_tour %} + + {% endif %} {% endif %} @@ -219,6 +223,8 @@ + + {% block scripts %}{% endblock %} diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 2814e9399..eb4678f75 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -65,6 +65,7 @@ class Feed(View): "filters_applied": filters_applied, "path": f"/{tab['key']}", "annual_summary_year": get_annual_summary_year(), + "has_tour": True, }, } return TemplateResponse(request, "feed/feed.html", data) From c614aeb28e9db845fa6e856c75fdcd4b40743e95 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 12 Jun 2022 19:43:21 +1000 Subject: [PATCH 009/426] add shepherd tours This file creates and triggers tours using shepherd. Initially this is a tour on the home feed page, triggered by clicking on the help button in the top nav. --- bookwyrm/static/js/tour.js | 105 +++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 bookwyrm/static/js/tour.js diff --git a/bookwyrm/static/js/tour.js b/bookwyrm/static/js/tour.js new file mode 100644 index 000000000..b8cecfae5 --- /dev/null +++ b/bookwyrm/static/js/tour.js @@ -0,0 +1,105 @@ +const homeTour = new Shepherd.Tour({ + exitOnEsc: true, +}); + +homeTour.addSteps([ + { + text: "Search for books, users, or lists using this search box.", + title: "Search box", + attachTo: { + element: "#search_input", + on: "bottom", + }, + buttons: [ + { + action() { + return this.cancel(); + }, + secondary: true, + text: "Cancel", + classes: "is-danger", + }, + { + action() { + return this.next(); + }, + text: "Next", + }, + ], + }, + { + text: "The latest books to be added to your reading shelves will be shown here.", + title: "Your Books", + attachTo: { + element: "#suggested_books_block", + on: "right", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "Back", + }, + { + action() { + return this.next(); + }, + text: "Next", + }, + ], + }, + { + text: "The bell will light up when you have a new notification. Click on it to find out what exciting thing has happened!", + title: "Notifications", + attachTo: { + element: '[href="/notifications"]', + on: "left-end", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "Back", + }, + { + action() { + return this.next(); + }, + text: "Next", + }, + ], + }, + { + text: "Your profile, books, direct messages, and settings can be accessed by clicking on your name here.

Try selecting Profile from the drop down menu to continue the tour.", + title: "Profile and settings menu", + attachTo: { + element: "#navbar-dropdown", + on: "left-end", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "Back", + }, + { + action() { + return this.next(); + }, + text: "Ok", + }, + ], + } +]); + +function startTour(tourName) { + if (tourName === 'home') { + homeTour.start() + } +} From e768cf49a3e5d5ba5e422dc670499264114bc7c7 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 12 Jun 2022 20:11:21 +1000 Subject: [PATCH 010/426] add barcode reader to home feed tour --- bookwyrm/static/js/tour.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/bookwyrm/static/js/tour.js b/bookwyrm/static/js/tour.js index b8cecfae5..93587614c 100644 --- a/bookwyrm/static/js/tour.js +++ b/bookwyrm/static/js/tour.js @@ -27,6 +27,29 @@ homeTour.addSteps([ }, ], }, + { + text: "Search book records by scanning an ISBN barcode using your camera.", + title: "Barcode reader", + attachTo: { + element: ".icon-barcode", + on: "bottom", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "Back", + }, + { + action() { + return this.next(); + }, + text: "Next", + }, + ], + }, { text: "The latest books to be added to your reading shelves will be shown here.", title: "Your Books", @@ -98,6 +121,18 @@ homeTour.addSteps([ } ]); +// TODO: User Profile +// TODO: Groups + // TODO: creating groups and adding users + // TODO: visibility +// TODO: Lists + // TODO: creating lists and adding books + // TODO: visibility - followers-only +// TODO: Books + // TODO: reading status shelves + // TODO: creating a shelf + // TODO: importing + function startTour(tourName) { if (tourName === 'home') { homeTour.start() From 2b431986d6ec90502887f1d708656bd14828c559 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 12 Jun 2022 20:39:37 +1000 Subject: [PATCH 011/426] help tour button styling updates --- bookwyrm/templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 426d5cd0b..1c7177210 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -86,7 +86,7 @@ {% trans "Discover" %} {% if has_tour %} - {% endif %} {% endif %} From 80c71928c3588459e95023d8f225df4b0c4296dc Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 13:07:48 +1000 Subject: [PATCH 012/426] add show_guided_tour value to User This boolean value indicates whether the user wishes to be show the guided tour. It defaults to True but will be able to be easily set to False. --- .../migrations/0151_user_show_guided_tour.py | 19 +++++++++++++++++++ bookwyrm/models/user.py | 1 + bookwyrm/views/__init__.py | 2 +- bookwyrm/views/user.py | 7 +++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/migrations/0151_user_show_guided_tour.py diff --git a/bookwyrm/migrations/0151_user_show_guided_tour.py b/bookwyrm/migrations/0151_user_show_guided_tour.py new file mode 100644 index 000000000..c5b50b185 --- /dev/null +++ b/bookwyrm/migrations/0151_user_show_guided_tour.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.13 on 2022-06-13 01:59 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0150_readthrough_stopped_date'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='show_guided_tour', + field=bookwyrm.models.fields.BooleanField(default=True), + ), + ] diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index dce74022c..5fe49f118 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -143,6 +143,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): show_goal = models.BooleanField(default=True) show_suggested_users = models.BooleanField(default=True) discoverable = fields.BooleanField(default=False) + show_guided_tour = fields.BooleanField(default=True) # feed options feed_status_types = ArrayField( diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 2d085b02d..bc2d748df 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -127,7 +127,7 @@ from .setup import InstanceConfig, CreateAdmin from .status import CreateStatus, EditStatus, DeleteStatus, update_progress from .status import edit_readthrough from .updates import get_notification_count, get_unread_status_string -from .user import User, Followers, Following, hide_suggestions, user_redirect +from .user import User, Followers, Following, hide_suggestions, user_redirect, toggle_guided_tour from .wellknown import * from .annual_summary import ( AnnualSummary, diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index e00aaa8e4..502f9b56f 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -165,3 +165,10 @@ def hide_suggestions(request): def user_redirect(request, username): """redirect to a user's feed""" return redirect("user-feed", username=username) + +@login_required +def toggle_guided_tour(request): + """most people don't want a tour every time they load a page""" + request.user.show_guided_tour = request.GET.get("tour") + request.user.save(broadcast=False, update_fields=["show_guided_tour"]) + return redirect("/") From 6b7caa9c718fc2901d738ddd4046e653fc865b18 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 13:10:31 +1000 Subject: [PATCH 013/426] url for setting show_guided_tour Uses a URL param to indicate whether the value should be set to True or False. Redirects to home page. --- bookwyrm/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 5abacae14..e5b04d111 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -650,4 +650,5 @@ urlpatterns = [ re_path( r"^summary_revoke_key/?$", views.summary_revoke_key, name="summary-revoke-key" ), + path("guided-tour/", views.toggle_guided_tour), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From 07225c6ddc6400009bb32b0eb98276c3dcf708cd Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 13:14:31 +1000 Subject: [PATCH 014/426] add guided tour link --- bookwyrm/templates/layout.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 1c7177210..40a414d4b 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -85,10 +85,6 @@ {% trans "Discover" %} - {% if has_tour %} - - {% endif %} {% endif %} @@ -193,6 +189,11 @@

{% trans "Documentation" %}

+ {% if request.user.is_authenticated %} +

+ {% trans "Guided Tour" %} +

+ {% endif %}
{% if site.support_link %} @@ -224,7 +225,6 @@ - {% block scripts %}{% endblock %} From 8cadb3dc3b3450ecc6d1bc3aea61b67bbdad6005 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 15:22:23 +1000 Subject: [PATCH 015/426] fix guided tour url Use a url fragment () instead of a classic url param (/?tour=True) --- bookwyrm/urls.py | 2 +- bookwyrm/views/user.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index e5b04d111..9287c3705 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -650,5 +650,5 @@ urlpatterns = [ re_path( r"^summary_revoke_key/?$", views.summary_revoke_key, name="summary-revoke-key" ), - path("guided-tour/", views.toggle_guided_tour), + path("guided-tour/", views.toggle_guided_tour), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index 502f9b56f..124a2e96c 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -167,8 +167,9 @@ def user_redirect(request, username): return redirect("user-feed", username=username) @login_required -def toggle_guided_tour(request): +def toggle_guided_tour(request, tour): """most people don't want a tour every time they load a page""" - request.user.show_guided_tour = request.GET.get("tour") + + request.user.show_guided_tour = tour request.user.save(broadcast=False, update_fields=["show_guided_tour"]) return redirect("/") From 6ffb0863d165fdc86c8f600020820685e30ab028 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 15:27:05 +1000 Subject: [PATCH 016/426] update tour link in main template --- bookwyrm/templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 40a414d4b..0155c6650 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -191,7 +191,7 @@

{% if request.user.is_authenticated %}

- {% trans "Guided Tour" %} + {% trans "Guided Tour" %}

{% endif %}
From 5f0e14934f25e79e038b828311f1cb4e6d579d33 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 15:29:20 +1000 Subject: [PATCH 017/426] add guided tour to main feed page This uses an embedded script tag so that we can use django templates for logic - most importantly, we need to be able to use translations within the tour text. --- bookwyrm/templates/feed/layout.html | 6 + bookwyrm/templates/guided_tour/home.html | 192 +++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 bookwyrm/templates/guided_tour/home.html diff --git a/bookwyrm/templates/feed/layout.html b/bookwyrm/templates/feed/layout.html index 5083c0ab8..aed7a7ee4 100644 --- a/bookwyrm/templates/feed/layout.html +++ b/bookwyrm/templates/feed/layout.html @@ -32,4 +32,10 @@ {% block scripts %} + + +{% if user.show_guided_tour %} + {% include 'guided_tour/home.html' %} +{% endif %} + {% endblock %} diff --git a/bookwyrm/templates/guided_tour/home.html b/bookwyrm/templates/guided_tour/home.html new file mode 100644 index 000000000..ad1aba63e --- /dev/null +++ b/bookwyrm/templates/guided_tour/home.html @@ -0,0 +1,192 @@ +{% load i18n %} +{% csrf_token %} + + From 2030dc834fce40231fe5f309887c1e482d02346f Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 16:48:49 +1000 Subject: [PATCH 018/426] clean up tour files --- bookwyrm/static/js/tour.js | 140 ---------------------------- bookwyrm/templates/feed/layout.html | 1 - 2 files changed, 141 deletions(-) delete mode 100644 bookwyrm/static/js/tour.js diff --git a/bookwyrm/static/js/tour.js b/bookwyrm/static/js/tour.js deleted file mode 100644 index 93587614c..000000000 --- a/bookwyrm/static/js/tour.js +++ /dev/null @@ -1,140 +0,0 @@ -const homeTour = new Shepherd.Tour({ - exitOnEsc: true, -}); - -homeTour.addSteps([ - { - text: "Search for books, users, or lists using this search box.", - title: "Search box", - attachTo: { - element: "#search_input", - on: "bottom", - }, - buttons: [ - { - action() { - return this.cancel(); - }, - secondary: true, - text: "Cancel", - classes: "is-danger", - }, - { - action() { - return this.next(); - }, - text: "Next", - }, - ], - }, - { - text: "Search book records by scanning an ISBN barcode using your camera.", - title: "Barcode reader", - attachTo: { - element: ".icon-barcode", - on: "bottom", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "Back", - }, - { - action() { - return this.next(); - }, - text: "Next", - }, - ], - }, - { - text: "The latest books to be added to your reading shelves will be shown here.", - title: "Your Books", - attachTo: { - element: "#suggested_books_block", - on: "right", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "Back", - }, - { - action() { - return this.next(); - }, - text: "Next", - }, - ], - }, - { - text: "The bell will light up when you have a new notification. Click on it to find out what exciting thing has happened!", - title: "Notifications", - attachTo: { - element: '[href="/notifications"]', - on: "left-end", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "Back", - }, - { - action() { - return this.next(); - }, - text: "Next", - }, - ], - }, - { - text: "Your profile, books, direct messages, and settings can be accessed by clicking on your name here.

Try selecting Profile from the drop down menu to continue the tour.", - title: "Profile and settings menu", - attachTo: { - element: "#navbar-dropdown", - on: "left-end", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "Back", - }, - { - action() { - return this.next(); - }, - text: "Ok", - }, - ], - } -]); - -// TODO: User Profile -// TODO: Groups - // TODO: creating groups and adding users - // TODO: visibility -// TODO: Lists - // TODO: creating lists and adding books - // TODO: visibility - followers-only -// TODO: Books - // TODO: reading status shelves - // TODO: creating a shelf - // TODO: importing - -function startTour(tourName) { - if (tourName === 'home') { - homeTour.start() - } -} diff --git a/bookwyrm/templates/feed/layout.html b/bookwyrm/templates/feed/layout.html index aed7a7ee4..23b467156 100644 --- a/bookwyrm/templates/feed/layout.html +++ b/bookwyrm/templates/feed/layout.html @@ -33,7 +33,6 @@ {% block scripts %} - {% if user.show_guided_tour %} {% include 'guided_tour/home.html' %} {% endif %} From 83e7302bc14b439bc587705ce591eb875432d13f Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 16:56:07 +1000 Subject: [PATCH 019/426] update home feed guided tour --- bookwyrm/templates/guided_tour/home.html | 31 ++++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/bookwyrm/templates/guided_tour/home.html b/bookwyrm/templates/guided_tour/home.html index ad1aba63e..0ae15a56c 100644 --- a/bookwyrm/templates/guided_tour/home.html +++ b/bookwyrm/templates/guided_tour/home.html @@ -7,12 +7,10 @@ fetch('guided-tour/False', { headers: { 'X-CSRFToken': csrftoken, - 'Content-Type': 'application/json' }, method: 'POST', redirect: 'follow', mode: 'same-origin', - body: 'tour=False' }) .then( resp => {console.log(resp.statusText) }) } @@ -30,6 +28,7 @@ buttons: [ { action() { + disableTour(); return this.next(); }, secondary: true, @@ -55,8 +54,7 @@ buttons: [ { action() { - this.complete() - return disableTour(); + return this.complete() }, text: "{% trans 'Ok' %}", classes: "is-success", @@ -118,7 +116,30 @@ ], }, { - text: "{% trans 'The latest books to be added to your reading shelves will be shown here.' %}", + text: "{% trans 'Use these links to discover the latest news from your feed, lists of books by topic, and the latest happenings on this Bookwyrm server!' %}", + title: "{% trans 'Navigation Bar' %}", + attachTo: { + element: ".navbar-start", + on: "bottom", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + return this.next(); + }, + text: "{% trans 'Next' %}", + }, + ], + }, + { + text: "{% trans 'Books on your reading status shelves will be shown here.' %}", title: "{% trans 'Your Books' %}", attachTo: { element: "#suggested_books_block", From aebeac9112074219bc8d2f64a6d039b3e71afa3e Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 17:26:53 +1000 Subject: [PATCH 020/426] add guided tour to user profile - adds ids to relevant elements to enable tour - adds guided tour using Shepherd --- .../templates/guided_tour/user_profile.html | 136 ++++++++++++++++++ bookwyrm/templates/user/layout.html | 14 +- 2 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 bookwyrm/templates/guided_tour/user_profile.html diff --git a/bookwyrm/templates/guided_tour/user_profile.html b/bookwyrm/templates/guided_tour/user_profile.html new file mode 100644 index 000000000..3e2bc3f2f --- /dev/null +++ b/bookwyrm/templates/guided_tour/user_profile.html @@ -0,0 +1,136 @@ +{% load i18n %} + + diff --git a/bookwyrm/templates/user/layout.html b/bookwyrm/templates/user/layout.html index 3dbfc9dcd..c4a71735e 100755 --- a/bookwyrm/templates/user/layout.html +++ b/bookwyrm/templates/user/layout.html @@ -69,25 +69,25 @@ {% if is_self or user.goal.exists %} {% now 'Y' as year %} {% url 'user-goal' user|username year as url %} - + {% trans "Reading Goal" %} {% endif %} {% if is_self or user|has_groups %} {% url 'user-groups' user|username as url %} - + {% trans "Groups" %} {% endif %} {% if is_self or user.list_set.exists %} {% url 'user-lists' user|username as url %} - + {% trans "Lists" %} {% endif %} {% if user.shelf_set.exists %} {% url 'user-shelves' user|username as url %} - + {% trans "Books" %} {% endif %} @@ -99,3 +99,9 @@ {% block panel %}{% endblock %} {% endblock %} + +{% block scripts %} +{% if user.show_guided_tour %} + {% include 'guided_tour/user_profile.html' %} +{% endif %} +{% endblock %} From fbf7f376644397e44202dbb02be017ae63f44731 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 19:36:14 +1000 Subject: [PATCH 021/426] user profile tour only on activity tab --- bookwyrm/templates/user/layout.html | 6 ------ bookwyrm/templates/user/user.html | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bookwyrm/templates/user/layout.html b/bookwyrm/templates/user/layout.html index c4a71735e..29668b1f9 100755 --- a/bookwyrm/templates/user/layout.html +++ b/bookwyrm/templates/user/layout.html @@ -99,9 +99,3 @@ {% block panel %}{% endblock %} {% endblock %} - -{% block scripts %} -{% if user.show_guided_tour %} - {% include 'guided_tour/user_profile.html' %} -{% endif %} -{% endblock %} diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index af85159fc..45c08909d 100755 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -86,3 +86,9 @@ {% endblock %} + +{% block scripts %} +{% if user.show_guided_tour %} + {% include 'guided_tour/user_profile.html' %} +{% endif %} +{% endblock %} From 7fbc9914de12c7b8dbf236c3844594397f2c8109 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Mon, 13 Jun 2022 20:25:42 +1000 Subject: [PATCH 022/426] change cancel buttons for guided tour The first pop up in the guided tour on each page should provide a button to switch off the guided tour altogether, not simply cancel the current iteration. If we don't do this, then the only way to turn off the guided tour is to go right to the end, which could be really irritating, especially for people who star the tour and then start exploring on their own. --- bookwyrm/templates/guided_tour/home.html | 10 +--------- .../templates/guided_tour/user_profile.html | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/bookwyrm/templates/guided_tour/home.html b/bookwyrm/templates/guided_tour/home.html index 0ae15a56c..f0aef4667 100644 --- a/bookwyrm/templates/guided_tour/home.html +++ b/bookwyrm/templates/guided_tour/home.html @@ -4,7 +4,7 @@ diff --git a/bookwyrm/templates/snippets/privacy_select_no_followers.html b/bookwyrm/templates/snippets/privacy_select_no_followers.html index 2c601e7ff..41f7ff41a 100644 --- a/bookwyrm/templates/snippets/privacy_select_no_followers.html +++ b/bookwyrm/templates/snippets/privacy_select_no_followers.html @@ -1,6 +1,6 @@ {% load i18n %} {% load utilities %} -
+
{% firstof privacy_uuid 0|uuid as uuid %} {% if not no_label %} diff --git a/bookwyrm/templates/user/groups.html b/bookwyrm/templates/user/groups.html index 6f3619fd3..05481699f 100644 --- a/bookwyrm/templates/user/groups.html +++ b/bookwyrm/templates/user/groups.html @@ -13,7 +13,7 @@
{% if is_self %} -
+
{% trans "Create group" as button_text %} {% include 'snippets/toggle/open_button.html' with controls_text="create_group" icon_with_text="plus" text=button_text %}
@@ -35,3 +35,9 @@ {% include 'snippets/pagination.html' with page=user.memberships path=path %}
{% endblock %} + +{% block scripts %} +{% if user.show_guided_tour %} + {% include 'guided_tour/user_groups.html' %} +{% endif %} +{% endblock %} From d36dd9ce965e9114d3dd2612d1a3463c34c33578 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sat, 18 Jun 2022 10:48:14 +1000 Subject: [PATCH 024/426] guided tour for user groups Includes adding creating a new group. --- bookwyrm/templates/groups/group.html | 8 +- bookwyrm/templates/groups/members.html | 2 +- bookwyrm/templates/guided_tour/group.html | 131 ++++++++++++++++++++++ 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 bookwyrm/templates/guided_tour/group.html diff --git a/bookwyrm/templates/groups/group.html b/bookwyrm/templates/groups/group.html index 5f5b58601..2e2f12437 100644 --- a/bookwyrm/templates/groups/group.html +++ b/bookwyrm/templates/groups/group.html @@ -22,7 +22,7 @@

{% if request.user.is_authenticated and group|is_member:request.user %} -
+
{% trans "Create List" as button_text %} {% include 'snippets/toggle/open_button.html' with controls_text="create_list" icon_with_text="plus" text=button_text focus="create_list_header" %}
@@ -80,3 +80,9 @@
{% endblock %} + +{% block scripts %} +{% if user.show_guided_tour %} + {% include 'guided_tour/group.html' %} +{% endif %} +{% endblock %} diff --git a/bookwyrm/templates/groups/members.html b/bookwyrm/templates/groups/members.html index e9c9047c9..2fb48b25c 100644 --- a/bookwyrm/templates/groups/members.html +++ b/bookwyrm/templates/groups/members.html @@ -10,7 +10,7 @@
-
+
@@ -405,4 +405,7 @@ {% block scripts %} +{% if user.show_guided_tour %} + {% include 'guided_tour/book.html' %} +{% endif %} {% endblock %} diff --git a/bookwyrm/templates/guided_tour/book.html b/bookwyrm/templates/guided_tour/book.html new file mode 100644 index 000000000..33001b256 --- /dev/null +++ b/bookwyrm/templates/guided_tour/book.html @@ -0,0 +1,149 @@ +{% load i18n %} + + From d1079a1f7df18b6ca59c8cf4e148abc9e638d4f6 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sat, 2 Jul 2022 18:44:18 +1000 Subject: [PATCH 032/426] remove console log from guided_tour.js --- bookwyrm/static/js/guided_tour.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bookwyrm/static/js/guided_tour.js b/bookwyrm/static/js/guided_tour.js index 7c9d9a7f1..89785df17 100644 --- a/bookwyrm/static/js/guided_tour.js +++ b/bookwyrm/static/js/guided_tour.js @@ -5,7 +5,6 @@ */ function disableGuidedTour(csrf_token) { - console.log(csrf_token); fetch('/guided-tour/False', { headers: { 'X-CSRFToken': csrf_token, From 827a63b4eb57d3b4ebde95bceef0c5024eacaadb Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sat, 2 Jul 2022 18:46:16 +1000 Subject: [PATCH 033/426] add shelves to guided tour --- .../templates/guided_tour/user_books.html | 90 +++++++++++-------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/bookwyrm/templates/guided_tour/user_books.html b/bookwyrm/templates/guided_tour/user_books.html index 071203ecd..9d288b67c 100644 --- a/bookwyrm/templates/guided_tour/user_books.html +++ b/bookwyrm/templates/guided_tour/user_books.html @@ -7,8 +7,8 @@ tour.addSteps([ { - text: "{% trans 'ffff' %}", - title: "{% trans 'bbbb' %}", + text: "{% trans 'This is the page where your books are listed, organised into shelves.' %}", + title: "{% trans 'Your books' %}", buttons: [ { action() { @@ -28,30 +28,13 @@ ], }, { - text: "{% trans 'These are your book shelves.' %}", - title: "{% trans 'Shelves' %}", - attachTo: { - element: "#user-shelves", - on: "top", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "{% trans 'Back' %}", - }, - { - action() { - return this.next(); - }, - text: "{% trans 'Next' %}", - }, - ], - }, - { - text: "{% trans 'The BLAH are default shelves that are used to indicate the reading status of books. A book can only be assigned to one of these shelves at any given moment e.g. a book is not allowed to be on both the Currently Reading and the To Read shelves. Usually Bookwyrm will automatically allocate a book to one of these shelves when you change the reading status of the book.' %}", + text: "{% trans 'To Read' %}, \ + {% trans 'Currently Reading' %}, \ + {% trans 'Read' %},\ + {% trans ' and ' %}\ + {% trans 'Stopped Reading' %} \ + {% trans 'are default shelves that are used to indicate the reading status of books. A book can only be assigned to one of these shelves at any given moment e.g. a book is not allowed to be on both the Currently Reading and the To Read shelves. Usually Bookwyrm will automatically allocate a book to one of these shelves when you change the reading status of the book.' %}\ + ", title: "{% trans 'Creating a group' %}", attachTo: { element: "#user-shelves", @@ -74,15 +57,10 @@ ], }, { - text: "{% trans 'Groups can be ' %}\ - {% trans 'Public' %},\ - {% trans 'Unlisted' %},\ - {% trans 'or ' %}\ - {% trans 'Private' %}

\ - {% trans 'Anyone can see and join a public group. Unlisted groups are currently exactly the same as public groups. Private groups can only be seen by members of the group.' %}", - title: "{% trans 'Group visibility' %}", + text: "{% trans 'You can create additional custom shelves to organise your books. A book on a custom shelf can be on any number of other shelves simultaneously, including one of the default reading status shelves' %}", + title: "{% trans 'Adding custom shelves.' %}", attachTo: { - element: "#privacy_select_button", + element: "[data-controls='create_shelf_form']", on: "left", }, buttons: [ @@ -102,17 +80,53 @@ ], }, { - text: "{% trans 'Once you\'re happy with how your group is set up, click the \'Save\' button.' %}", - title: "{% trans 'Save your group' %}", + text: "{% trans 'If you have an export file from another service like Goodreads or LibraryThing, you can import your books and shelves here.' %}", + title: "{% trans 'Import from another service' %}", + attachTo: { + element: "[href='/import']", + on: "left", + }, buttons: [ { action() { - return this.complete(); + return this.back(); }, - text: "{% trans 'Ok' %}", + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + return this.next(); + }, + text: "{% trans 'Next' %}", }, ], }, + { + text: "{% trans 'Now that we have explored your shelves, let\'s take a look at a related concept: book lists!' %}", + title: "{% trans 'Lists' %}", + attachTo: { + element: "[href='/list']", + on: "bottom", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + this.complete(); + return window.location.href = '/list' + }, + text: "{% trans 'I\'m ready!' %}", + classes: "is-success", + }, + ] + } ]) tour.start() From 06b4a55979879dc05eedf1ec5e9e0151c644292c Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 3 Jul 2022 09:10:43 +1000 Subject: [PATCH 034/426] add lists to guided tour Takes user through the main /list page, as well as the options for creating a list. --- bookwyrm/templates/guided_tour/lists.html | 179 ++++++++++++++++++++++ bookwyrm/templates/lists/form.html | 6 +- bookwyrm/templates/lists/lists.html | 8 +- 3 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 bookwyrm/templates/guided_tour/lists.html diff --git a/bookwyrm/templates/guided_tour/lists.html b/bookwyrm/templates/guided_tour/lists.html new file mode 100644 index 000000000..e3f1bf4d8 --- /dev/null +++ b/bookwyrm/templates/guided_tour/lists.html @@ -0,0 +1,179 @@ +{% load i18n %} + + diff --git a/bookwyrm/templates/lists/form.html b/bookwyrm/templates/lists/form.html index 3558d8cc0..d5b76f15e 100644 --- a/bookwyrm/templates/lists/form.html +++ b/bookwyrm/templates/lists/form.html @@ -16,7 +16,7 @@
- {% trans "List curation:" %} + {% trans "List curation:" %} {% endif %}
-
+
{% include 'snippets/privacy_select.html' with current=list.privacy %}
diff --git a/bookwyrm/templates/lists/lists.html b/bookwyrm/templates/lists/lists.html index 49091bcf0..a9f061fb2 100644 --- a/bookwyrm/templates/lists/lists.html +++ b/bookwyrm/templates/lists/lists.html @@ -16,7 +16,7 @@
{% if request.user.is_authenticated %} -
+
{% trans "Create List" as button_text %} {% include 'snippets/toggle/open_button.html' with controls_text="create_list" icon_with_text="plus" text=button_text focus="create_list_header" %}
@@ -54,3 +54,9 @@ {% endif %} {% endblock %} + +{% block scripts %} +{% if user.show_guided_tour %} + {% include 'guided_tour/lists.html' %} +{% endif %} +{% endblock %} From a8940b8e12118f6f60b1ac1c6cb373bf98faa241 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 3 Jul 2022 14:48:23 +1000 Subject: [PATCH 035/426] Fix order of tour The tour now shows users how to add a book first, then bookshelves, lists, and finally groups. --- bookwyrm/templates/guided_tour/book.html | 64 +++++------ bookwyrm/templates/guided_tour/group.html | 20 ++-- bookwyrm/templates/guided_tour/home.html | 4 +- bookwyrm/templates/guided_tour/lists.html | 49 +++++++-- bookwyrm/templates/guided_tour/search.html | 22 +++- .../templates/guided_tour/user_books.html | 18 ++- .../templates/guided_tour/user_groups.html | 4 +- .../templates/guided_tour/user_profile.html | 104 +++++++++--------- 8 files changed, 180 insertions(+), 105 deletions(-) diff --git a/bookwyrm/templates/guided_tour/book.html b/bookwyrm/templates/guided_tour/book.html index 33001b256..b90a50ef6 100644 --- a/bookwyrm/templates/guided_tour/book.html +++ b/bookwyrm/templates/guided_tour/book.html @@ -7,7 +7,7 @@ tour.addSteps([ { - text: "{% trans 'This is home page of a book. Let\s see what you can do while you are here!' %}", + text: "{% trans 'This is home page of a book. Let\s see what you can do while you\'re here!' %}", title: "{% trans 'Book page' %}", buttons: [ { @@ -50,6 +50,37 @@ }, ], }, + { + text: "{% trans 'You can also manually add reading dates here. Unlike changing the reading status using the previous method, adding read-through dates manually will not automatically add them to your ' %}\ + \ + {% trans 'Read' %}\ + \ + {% trans ' or '%}\ + \ + {% trans 'Reading' %}\ + \ + {% trans ' shelves. Got a favourite you re-read every year? We\'ve got you covered - you can add multiple read dates for the same book 😀' %}", + title: "{% trans 'Add readthrough dates' %}", + attachTo: { + element: "button[data-modal-open='add-readthrough']", + on: "top", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + return this.next(); + }, + text: "{% trans 'Next' %}", + }, + ], + }, { text: "{% trans 'There can be multiple editions of a book, in various formats or languages. You may wish to view a different edition (for example if you are setting a reading status). ' %}", title: "{% trans 'Other editions' %}", @@ -96,37 +127,6 @@ }, ], }, - { - text: "{% trans 'If you have already read this book you can add your reading dates here. Note that adding read-through dates manually will not automatically add them to your ' %}\ - \ - {% trans 'Read' %}\ - \ - {% trans ' or '%}\ - \ - {% trans 'Reading' %}\ - \ - {% trans ' shelves.'%}", - title: "{% trans 'Add readthrough dates' %}", - attachTo: { - element: "button[data-modal-open='add-readthrough']", - on: "top", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "{% trans 'Back' %}", - }, - { - action() { - return this.next(); - }, - text: "{% trans 'Next' %}", - }, - ], - }, { text: "{% trans 'Let\'s continue the tour by selecting \'Your Books\' from the drop down menu.' %}", title: "{% trans 'Next' %}", diff --git a/bookwyrm/templates/guided_tour/group.html b/bookwyrm/templates/guided_tour/group.html index 784c26a75..3c32c14b8 100644 --- a/bookwyrm/templates/guided_tour/group.html +++ b/bookwyrm/templates/guided_tour/group.html @@ -7,7 +7,7 @@ tour.addSteps([ { - text: "{% trans 'This is the home page of your new group! This is where you can add and remove users, create user-curated lists, and edit the group details.' %}", + text: "{% trans 'Welcome to the page for your group! This is where you can add and remove users, create user-curated lists, and edit the group details.' %}", title: "{% trans 'Your group' %}", buttons: [ { @@ -74,9 +74,7 @@ ], }, { - text: "{% trans 'Groups can have group-curated lists. We haven\'t discussed lists yet, so now let\s fix that.' %}\ -

\ - {% trans 'Click on the button to create a list.' %}", + text: "{% trans 'We saw on the Lists page how you can create a group-curated list. You can also create a list here on the group\'s homepage. Any member of the group can create a list curated by group members.' %}", title: "{% trans 'Group lists' %}", attachTo: { element: "#create_group_list_button", @@ -99,14 +97,22 @@ ], }, { - text: "{% trans 'todo' %}", - title: "{% trans 'TODO' %}", + text: "{% trans 'Thanks for taking the tour. There\'s lots more to explore, but now you are familiar with the basics needed to enjoy Bookwyrm. Happy reading!' %}", + title: "{% trans 'You\'re ready!' %}", buttons: [ { action() { + return this.back(); + }, + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + disableGuidedTour(csrf_token); return this.complete(); }, - text: "{% trans 'Ok' %}", + text: "{% trans 'Finish tour' %}", }, ], }, diff --git a/bookwyrm/templates/guided_tour/home.html b/bookwyrm/templates/guided_tour/home.html index 724b4740a..6ea7bed2a 100644 --- a/bookwyrm/templates/guided_tour/home.html +++ b/bookwyrm/templates/guided_tour/home.html @@ -163,7 +163,9 @@ homeTour.addSteps([ ], }, { - text: "{% trans 'Your profile, books, direct messages, and settings can be accessed by clicking on your name here.

Try selecting Profile from the drop down menu to continue the tour.' %}", + text: "{% trans 'Your profile, books, direct messages, and settings can be accessed by clicking on your name here.' %}\ +

\ + {% trans 'Try selecting Profile from the drop down menu to continue the tour.' %}", title: "{% trans 'Profile and settings menu' %}", attachTo: { element: "#navbar-dropdown", diff --git a/bookwyrm/templates/guided_tour/lists.html b/bookwyrm/templates/guided_tour/lists.html index e3f1bf4d8..4c78830f8 100644 --- a/bookwyrm/templates/guided_tour/lists.html +++ b/bookwyrm/templates/guided_tour/lists.html @@ -1,6 +1,9 @@ {% load i18n %} diff --git a/bookwyrm/templates/guided_tour/search.html b/bookwyrm/templates/guided_tour/search.html index e92bd6606..a2622b1db 100644 --- a/bookwyrm/templates/guided_tour/search.html +++ b/bookwyrm/templates/guided_tour/search.html @@ -123,7 +123,7 @@ ], }, { - text: "{% trans 'If you still cannot find your book, you can add a record manually.' %}", + text: "{% trans 'If you still can\'t find your book, you can add a record manually.' %}", title: "{% trans 'Add a record manally' %}", attachTo: { element: "#manually-add-book", @@ -147,5 +147,25 @@ }]) } + tour.addStep({ + text: "{% trans 'Import, manually add, or view an existing book to continue the tour.' %}", + title: "{% trans 'Continue the tour' %}", + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + return this.next(); + }, + text: "{% trans 'Ok' %}", + }, + ], + }) + tour.start() diff --git a/bookwyrm/templates/guided_tour/user_books.html b/bookwyrm/templates/guided_tour/user_books.html index 9d288b67c..f2b1daf88 100644 --- a/bookwyrm/templates/guided_tour/user_books.html +++ b/bookwyrm/templates/guided_tour/user_books.html @@ -33,7 +33,11 @@ {% trans 'Read' %},\ {% trans ' and ' %}\ {% trans 'Stopped Reading' %} \ - {% trans 'are default shelves that are used to indicate the reading status of books. A book can only be assigned to one of these shelves at any given moment e.g. a book is not allowed to be on both the Currently Reading and the To Read shelves. Usually Bookwyrm will automatically allocate a book to one of these shelves when you change the reading status of the book.' %}\ + {% trans 'are default shelves used to indicate the current reading status of books. A book can only be assigned to one of these shelves at any given moment e.g. a book is not allowed to be on both the ' %}\ + {% trans 'Currently Reading' %}, \ + {% trans ' and the ' %}\ + {% trans 'To Read' %}, \ + {% trans ' shelves. When you change the reading status of a book it will automatically be moved to the matching shelf.' %}\ ", title: "{% trans 'Creating a group' %}", attachTo: { @@ -103,11 +107,15 @@ ], }, { - text: "{% trans 'Now that we have explored your shelves, let\'s take a look at a related concept: book lists!' %}", + text: "{% trans 'Now that we\'ve explored book shelves, let\'s take a look at a related concept: book lists! Click on the ' %}\ + \ + {% trans 'Lists' %}\ + \ + {% trans ' link here to continue the tour.' %}", title: "{% trans 'Lists' %}", attachTo: { element: "[href='/list']", - on: "bottom", + on: "right", }, buttons: [ { @@ -120,10 +128,8 @@ { action() { this.complete(); - return window.location.href = '/list' }, - text: "{% trans 'I\'m ready!' %}", - classes: "is-success", + text: "{% trans 'Ok' %}" }, ] } diff --git a/bookwyrm/templates/guided_tour/user_groups.html b/bookwyrm/templates/guided_tour/user_groups.html index 603bc53b6..fb3b0d8fa 100644 --- a/bookwyrm/templates/guided_tour/user_groups.html +++ b/bookwyrm/templates/guided_tour/user_groups.html @@ -106,7 +106,9 @@ ], }, { - text: "{% trans 'Once you\'re happy with how your group is set up, click the \'Save\' button.' %}", + text: "{% trans 'Once you\'re happy with how your group is set up, click the ' %}\ + {% trans 'Save' %}\ + {% trans ' button to continue the tour.' %}", title: "{% trans 'Save your group' %}", buttons: [ { diff --git a/bookwyrm/templates/guided_tour/user_profile.html b/bookwyrm/templates/guided_tour/user_profile.html index 3003c349b..c755b3435 100644 --- a/bookwyrm/templates/guided_tour/user_profile.html +++ b/bookwyrm/templates/guided_tour/user_profile.html @@ -7,7 +7,7 @@ const tour = new Shepherd.Tour({ tour.addSteps([ { - text: "{% trans 'This is your user profile. All your latest activities will be listed here, as well as links to your reading goal, groups, lists, and shelves. Other Bookwyrm users can see this page too - though exactly what they can see depends on your settings.' %}", + text: "{% trans 'This is your user profile. All your latest activities will be listed here, as well as links to your reading goal, groups, lists, and shelves. Other Bookwyrm users can see parts of this page too - what they can see depends on your privacy settings.' %}", title: "{% trans 'User Profile' %}", buttons: [ { @@ -28,7 +28,7 @@ tour.addSteps([ ], }, { - text: "{% trans 'This tab shows everything you have read towards your annual reading goal, or allows you to set one. You do not have to set a reading goal if that\'s not your thing!' %}", + text: "{% trans 'This tab shows everything you have read towards your annual reading goal, or allows you to set one. You don\'t have to set a reading goal if that\'s not your thing!' %}", title: "{% trans 'Reading Goal' %}", attachTo: { element: "#reading_goal_tab", @@ -50,52 +50,6 @@ tour.addSteps([ }, ], }, - { - text: "{% trans 'The Books tab shows your books, on various shelves.' %}", - title: "{% trans 'Books' %}", - attachTo: { - element: "#shelves_tab", - on: "right", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "{% trans 'Back' %}", - }, - { - action() { - return this.next(); - }, - text: "{% trans 'Next' %}", - }, - ], - }, - { - text: "{% trans 'Here you can see your lists, or create a new one. A list is a collection of books that have something in common.' %}", - title: "{% trans 'Lists' %}", - attachTo: { - element: "#lists_tab", - on: "right", - }, - buttons: [ - { - action() { - return this.back(); - }, - secondary: true, - text: "{% trans 'Back' %}", - }, - { - action() { - return this.next(); - }, - text: "{% trans 'Next' %}", - }, - ], - }, { text: "{% trans 'Here you can see your groups, or create a new one. A group brings together Bookwyrm users and allows them to curate lists together.' %}", title: "{% trans 'Groups' %}", @@ -120,8 +74,58 @@ tour.addSteps([ ], }, { - text: "{% trans 'Now that you have seen the basics of your profile page, we\'re going to explore some of these concepts. Start by clicking on' %}{% trans 'Groups' %}.", - title: "{% trans 'Groups' %}", + text: "{% trans 'You can see your lists, or create a new one, here. A list is a collection of books that have something in common.' %}", + title: "{% trans 'Lists' %}", + attachTo: { + element: "#lists_tab", + on: "right", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + return this.next(); + }, + text: "{% trans 'Next' %}", + }, + ], + }, + { + text: "{% trans 'The Books tab shows your book shelves. We\'ll explore this next.' %}", + title: "{% trans 'Books' %}", + attachTo: { + element: "#shelves_tab", + on: "right", + }, + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: "{% trans 'Back' %}", + }, + { + action() { + return this.next(); + }, + text: "{% trans 'Next' %}", + }, + ], + }, + { + text: "{% trans 'Now you understand the basics of your profile page, let\s add a book to your shelves. Search for a title or author here to continue the tour.' %}", + title: "{% trans 'Find a book' %}", + attachTo: { + element: "#search_input", + on: "right", + }, buttons: [ { action() { From 3f67bc3b614a5607a175d60fafd1574dcf2c88ea Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 3 Jul 2022 15:57:10 +1000 Subject: [PATCH 036/426] standardise ids for tour anchors To make it harder to accidentally mess up the tour when making changes to pages, this commit adds ids with 'tour' prefixes to (nearly) all elements used by the guided tour as anchor points. The exception is where an element already had an id that is being used by something else in Bookwyrm. Some minor changes also made to clean up the wording of the tour. --- bookwyrm/templates/book/book.html | 6 +++--- bookwyrm/templates/feed/suggested_books.html | 2 +- bookwyrm/templates/groups/form.html | 2 +- bookwyrm/templates/groups/group.html | 2 +- bookwyrm/templates/groups/members.html | 4 ++-- bookwyrm/templates/guided_tour/book.html | 16 ++++++++++------ bookwyrm/templates/guided_tour/group.html | 6 +++--- bookwyrm/templates/guided_tour/home.html | 12 ++++++------ bookwyrm/templates/guided_tour/lists.html | 6 +++--- bookwyrm/templates/guided_tour/search.html | 14 +++++++------- bookwyrm/templates/guided_tour/user_books.html | 8 ++++---- bookwyrm/templates/guided_tour/user_groups.html | 14 +++++--------- bookwyrm/templates/guided_tour/user_profile.html | 12 ++++++------ bookwyrm/templates/layout.html | 10 +++++----- bookwyrm/templates/lists/form.html | 4 ++-- bookwyrm/templates/lists/lists.html | 2 +- bookwyrm/templates/search/book.html | 8 ++++---- bookwyrm/templates/search/layout.html | 2 +- bookwyrm/templates/shelf/shelf.html | 6 +++--- .../snippets/privacy_select_no_followers.html | 2 +- .../snippets/shelve_button/shelve_button.html | 2 +- bookwyrm/templates/user/groups.html | 2 +- bookwyrm/templates/user/layout.html | 8 ++++---- 23 files changed, 75 insertions(+), 75 deletions(-) diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index fe0b87e7e..c73e2920a 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -210,7 +210,7 @@ {% with work=book.parent_work %}

- + {% blocktrans trimmed count counter=work.editions.count with count=work.editions.count|intcomma %} {{ count }} edition {% plural %} @@ -254,7 +254,7 @@

{% trans "Your reading activity" %}

-
{% endif %} -