Sidebar refactor
This commit is contained in:
parent
03b5847641
commit
fbb6405da9
113 changed files with 1321 additions and 637 deletions
|
|
@ -12,6 +12,7 @@ dependencies = [
|
||||||
'django-celery-beat~=2.7.0',
|
'django-celery-beat~=2.7.0',
|
||||||
'django-registration-redux~=2.7',
|
'django-registration-redux~=2.7',
|
||||||
'django-rest-framework',
|
'django-rest-framework',
|
||||||
|
'djangorestframework-camel-case',
|
||||||
'pymemcache',
|
'pymemcache',
|
||||||
'python-dotenv~=1.0.1',
|
'python-dotenv~=1.0.1',
|
||||||
'ftfy~=6.2',
|
'ftfy~=6.2',
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,47 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="integrations--page" class="main">
|
<main id="integrations--page" class="main" data-render-sidebar=true>
|
||||||
<section class="section">
|
<div class="main__container">
|
||||||
{% include "components/header/header.html" with title="Integrations" only %}
|
<section class="section">
|
||||||
|
{% include "components/header/header.html" with title="Integrations" only %}
|
||||||
|
|
||||||
<div class="integrations">
|
<div class="integrations">
|
||||||
<h3 class="integrations__title">Reddit</h3>
|
<h3 class="integrations__title">Reddit</h3>
|
||||||
<div class="integrations__controls">
|
<div class="integrations__controls">
|
||||||
{% if reddit_authorization_url %}
|
{% if reddit_authorization_url %}
|
||||||
<a class="link button button--reddit" href="{{ reddit_authorization_url }}">
|
<a class="link button button--reddit" href="{{ reddit_authorization_url }}">
|
||||||
{% trans "Authorize account" %}
|
{% trans "Authorize account" %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="button button--reddit button--disabled" disabled>
|
<button class="button button--reddit button--disabled" disabled>
|
||||||
{% trans "Authorize account" %}
|
{% trans "Authorize account" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if reddit_refresh_url %}
|
{% if reddit_refresh_url %}
|
||||||
<a class="link button button--reddit" href="{{ reddit_refresh_url }}">
|
<a class="link button button--reddit" href="{{ reddit_refresh_url }}">
|
||||||
{% trans "Refresh token" %}
|
{% trans "Refresh token" %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="button button--reddit button--disabled" disabled>
|
<button class="button button--reddit button--disabled" disabled>
|
||||||
{% trans "Refresh token" %}
|
{% trans "Refresh token" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if reddit_revoke_url %}
|
{% if reddit_revoke_url %}
|
||||||
<a class="link button button--reddit" href="{{ reddit_revoke_url }}">
|
<a class="link button button--reddit" href="{{ reddit_revoke_url }}">
|
||||||
{% trans "Deauthorize account" %}
|
{% trans "Deauthorize account" %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="button button--reddit button--disabled" disabled>
|
<button class="button button--reddit button--disabled" disabled>
|
||||||
{% trans "Deauthorize account" %}
|
{% trans "Deauthorize account" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="login--page" class="main">
|
<main id="login--page" class="main" data-render-sidebar=true>
|
||||||
{% include "accounts/components/login-form.html" with form=form title="Login" confirm_text="Login" %}
|
<div class="main__container">
|
||||||
|
{% include "accounts/components/login-form.html" with form=form title="Login" confirm_text="Login" %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="password-change--page" class="main">
|
{% url 'accounts:settings:home' as cancel_url %}
|
||||||
{% url 'accounts:settings:home' as cancel_url %}
|
|
||||||
{% include "components/form/form.html" with form=form title="Change password" confirm_text="Change password" cancel_url=cancel_url %}
|
<main id="password-change--page" class="main" data-render-sidebar=true>
|
||||||
|
<div class="main__container">
|
||||||
|
{% include "components/form/form.html" with form=form title="Change password" confirm_text="Change password" cancel_url=cancel_url %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="reddit--page" class="main">
|
<main id="reddit--page" class="main" data-render-sidebar=true>
|
||||||
<section class="section text-section">
|
<div class="main__container">
|
||||||
{% if error %}
|
<section class="section text-section">
|
||||||
<h1 class="h1">{% trans "Reddit authorization failed" %}</h1>
|
{% if error %}
|
||||||
<p>{{ error }}</p>
|
<h1 class="h1">{% trans "Reddit authorization failed" %}</h1>
|
||||||
{% elif access_token and refresh_token %}
|
<p>{{ error }}</p>
|
||||||
<h1 class="h1">{% trans "Reddit account is linked" %}</h1>
|
{% elif access_token and refresh_token %}
|
||||||
<p>{% trans "Your reddit account was successfully linked." %}</p>
|
<h1 class="h1">{% trans "Reddit account is linked" %}</h1>
|
||||||
{% endif %}
|
<p>{% trans "Your reddit account was successfully linked." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a class="link" href="{% url 'accounts:settings:integrations' %}">{% trans "Return to integrations page" %}</a>
|
<a class="link" href="{% url 'accounts:settings:integrations' %}">{% trans "Return to integrations page" %}</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="settings--page" class="main">
|
<main id="settings--page" class="main" data-render-sidebar=true>
|
||||||
{% include "accounts/components/settings-form.html" with form=form title="User profile" confirm_text="Save" %}
|
<div class="main__container">
|
||||||
|
{% include "accounts/components/settings-form.html" with form=form title="User profile" confirm_text="Save" %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
from django.contrib.auth import views as django_views
|
from django.contrib.auth import views as django_views
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
class LoginView(django_views.LoginView):
|
|
||||||
|
class LoginView(NavListMixin, django_views.LoginView):
|
||||||
template_name = "accounts/views/login.html"
|
template_name = "accounts/views/login.html"
|
||||||
success_url = reverse_lazy("index")
|
success_url = reverse_lazy("index")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,13 @@ from newsreader.news.collection.reddit import (
|
||||||
revoke_reddit_token,
|
revoke_reddit_token,
|
||||||
)
|
)
|
||||||
from newsreader.news.collection.tasks import RedditTokenTask
|
from newsreader.news.collection.tasks import RedditTokenTask
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IntegrationsView(TemplateView):
|
class IntegrationsView(NavListMixin, TemplateView):
|
||||||
template_name = "accounts/views/integrations.html"
|
template_name = "accounts/views/integrations.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
@ -55,7 +56,7 @@ class IntegrationsView(TemplateView):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RedditTemplateView(TemplateView):
|
class RedditTemplateView(NavListMixin, TemplateView):
|
||||||
template_name = "accounts/views/reddit.html"
|
template_name = "accounts/views/reddit.html"
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,34 @@
|
||||||
from django.contrib.auth import views as django_views
|
from django.contrib.auth import views as django_views
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
# PasswordResetView sends the mail
|
# PasswordResetView sends the mail
|
||||||
# PasswordResetDoneView shows a success message for the above
|
# PasswordResetDoneView shows a success message for the above
|
||||||
# PasswordResetConfirmView checks the link the user clicked and
|
# PasswordResetConfirmView checks the link the user clicked and
|
||||||
# prompts for a new password
|
# prompts for a new password
|
||||||
# PasswordResetCompleteView shows a success message for the above
|
# PasswordResetCompleteView shows a success message for the above
|
||||||
class PasswordResetView(django_views.PasswordResetView):
|
class PasswordResetView(NavListMixin, django_views.PasswordResetView):
|
||||||
template_name = "password-reset/password-reset.html"
|
template_name = "password-reset/password-reset.html"
|
||||||
subject_template_name = "password-reset/password-reset-subject.txt"
|
subject_template_name = "password-reset/password-reset-subject.txt"
|
||||||
email_template_name = "password-reset/password-reset-email.html"
|
email_template_name = "password-reset/password-reset-email.html"
|
||||||
success_url = reverse_lazy("accounts:password-reset-done")
|
success_url = reverse_lazy("accounts:password-reset-done")
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetDoneView(django_views.PasswordResetDoneView):
|
class PasswordResetDoneView(NavListMixin, django_views.PasswordResetDoneView):
|
||||||
template_name = "password-reset/password-reset-done.html"
|
template_name = "password-reset/password-reset-done.html"
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetConfirmView(django_views.PasswordResetConfirmView):
|
class PasswordResetConfirmView(NavListMixin, django_views.PasswordResetConfirmView):
|
||||||
template_name = "password-reset/password-reset-confirm.html"
|
template_name = "password-reset/password-reset-confirm.html"
|
||||||
success_url = reverse_lazy("accounts:password-reset-complete")
|
success_url = reverse_lazy("accounts:password-reset-complete")
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetCompleteView(django_views.PasswordResetCompleteView):
|
class PasswordResetCompleteView(NavListMixin, django_views.PasswordResetCompleteView):
|
||||||
template_name = "password-reset/password-reset-complete.html"
|
template_name = "password-reset/password-reset-complete.html"
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeView(django_views.PasswordChangeView):
|
class PasswordChangeView(NavListMixin, django_views.PasswordChangeView):
|
||||||
template_name = "accounts/views/password-change.html"
|
template_name = "accounts/views/password-change.html"
|
||||||
success_url = reverse_lazy("accounts:settings")
|
success_url = reverse_lazy("accounts:settings")
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ from django.views.generic import TemplateView
|
||||||
|
|
||||||
from registration.backends.default import views as registration_views
|
from registration.backends.default import views as registration_views
|
||||||
|
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
# RegistrationView shows a registration form and sends the email
|
# RegistrationView shows a registration form and sends the email
|
||||||
# RegistrationCompleteView shows after filling in the registration form
|
# RegistrationCompleteView shows after filling in the registration form
|
||||||
|
|
@ -11,34 +13,34 @@ from registration.backends.default import views as registration_views
|
||||||
# ActivationCompleteView shows the success screen when activation was succesful
|
# ActivationCompleteView shows the success screen when activation was succesful
|
||||||
# ActivationResendView can be used when activation links are expired
|
# ActivationResendView can be used when activation links are expired
|
||||||
# RegistrationClosedView shows when registration is disabled
|
# RegistrationClosedView shows when registration is disabled
|
||||||
class RegistrationView(registration_views.RegistrationView):
|
class RegistrationView(NavListMixin, registration_views.RegistrationView):
|
||||||
disallowed_url = reverse_lazy("accounts:register-closed")
|
disallowed_url = reverse_lazy("accounts:register-closed")
|
||||||
template_name = "registration/registration_form.html"
|
template_name = "registration/registration_form.html"
|
||||||
success_url = reverse_lazy("accounts:register-complete")
|
success_url = reverse_lazy("accounts:register-complete")
|
||||||
|
|
||||||
|
|
||||||
class RegistrationCompleteView(TemplateView):
|
class RegistrationCompleteView(NavListMixin, TemplateView):
|
||||||
template_name = "registration/registration_complete.html"
|
template_name = "registration/registration_complete.html"
|
||||||
|
|
||||||
|
|
||||||
class RegistrationClosedView(TemplateView):
|
class RegistrationClosedView(NavListMixin, TemplateView):
|
||||||
template_name = "registration/registration_closed.html"
|
template_name = "registration/registration_closed.html"
|
||||||
|
|
||||||
|
|
||||||
# Redirects or renders failed activation template
|
# Redirects or renders failed activation template
|
||||||
class ActivationView(registration_views.ActivationView):
|
class ActivationView(NavListMixin, registration_views.ActivationView):
|
||||||
template_name = "registration/activation_failure.html"
|
template_name = "registration/activation_failure.html"
|
||||||
|
|
||||||
def get_success_url(self, user):
|
def get_success_url(self, user):
|
||||||
return ("accounts:activate-complete", (), {})
|
return ("accounts:activate-complete", (), {})
|
||||||
|
|
||||||
|
|
||||||
class ActivationCompleteView(TemplateView):
|
class ActivationCompleteView(NavListMixin, TemplateView):
|
||||||
template_name = "registration/activation_complete.html"
|
template_name = "registration/activation_complete.html"
|
||||||
|
|
||||||
|
|
||||||
# Renders activation form resend or resend_activation_complete
|
# Renders activation form resend or resend_activation_complete
|
||||||
class ActivationResendView(registration_views.ResendActivationView):
|
class ActivationResendView(NavListMixin, registration_views.ResendActivationView):
|
||||||
template_name = "registration/activation_resend_form.html"
|
template_name = "registration/activation_resend_form.html"
|
||||||
|
|
||||||
def render_form_submitted_template(self, form):
|
def render_form_submitted_template(self, form):
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ from django.views.generic.edit import FormView, ModelFormMixin
|
||||||
|
|
||||||
from newsreader.accounts.forms import UserSettingsForm
|
from newsreader.accounts.forms import UserSettingsForm
|
||||||
from newsreader.accounts.models import User
|
from newsreader.accounts.models import User
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
class SettingsView(ModelFormMixin, FormView):
|
class SettingsView(NavListMixin, ModelFormMixin, FormView):
|
||||||
template_name = "accounts/views/settings.html"
|
template_name = "accounts/views/settings.html"
|
||||||
success_url = reverse_lazy("accounts:settings:home")
|
success_url = reverse_lazy("accounts:settings:home")
|
||||||
form_class = UserSettingsForm
|
form_class = UserSettingsForm
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
src/newsreader/assets/fonts/Inter-VariableFont_opsz,wght.ttf
Normal file
BIN
src/newsreader/assets/fonts/Inter-VariableFont_opsz,wght.ttf
Normal file
Binary file not shown.
|
|
@ -1,101 +0,0 @@
|
||||||
name: "Rubik"
|
|
||||||
designer: "Hubert and Fischer, Meir Sadan, Cyreal"
|
|
||||||
license: "OFL"
|
|
||||||
category: "SANS_SERIF"
|
|
||||||
date_added: "2015-07-22"
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "normal"
|
|
||||||
weight: 300
|
|
||||||
filename: "Rubik-Light.ttf"
|
|
||||||
post_script_name: "Rubik-Light"
|
|
||||||
full_name: "Rubik Light"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "italic"
|
|
||||||
weight: 300
|
|
||||||
filename: "Rubik-LightItalic.ttf"
|
|
||||||
post_script_name: "Rubik-LightItalic"
|
|
||||||
full_name: "Rubik Light Italic"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "normal"
|
|
||||||
weight: 400
|
|
||||||
filename: "Rubik-Regular.ttf"
|
|
||||||
post_script_name: "Rubik-Regular"
|
|
||||||
full_name: "Rubik Regular"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "italic"
|
|
||||||
weight: 400
|
|
||||||
filename: "Rubik-Italic.ttf"
|
|
||||||
post_script_name: "Rubik-Italic"
|
|
||||||
full_name: "Rubik Italic"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "normal"
|
|
||||||
weight: 500
|
|
||||||
filename: "Rubik-Medium.ttf"
|
|
||||||
post_script_name: "Rubik-Medium"
|
|
||||||
full_name: "Rubik Medium"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "italic"
|
|
||||||
weight: 500
|
|
||||||
filename: "Rubik-MediumItalic.ttf"
|
|
||||||
post_script_name: "Rubik-MediumItalic"
|
|
||||||
full_name: "Rubik Medium Italic"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "normal"
|
|
||||||
weight: 700
|
|
||||||
filename: "Rubik-Bold.ttf"
|
|
||||||
post_script_name: "Rubik-Bold"
|
|
||||||
full_name: "Rubik Bold"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "italic"
|
|
||||||
weight: 700
|
|
||||||
filename: "Rubik-BoldItalic.ttf"
|
|
||||||
post_script_name: "Rubik-BoldItalic"
|
|
||||||
full_name: "Rubik Bold Italic"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "normal"
|
|
||||||
weight: 900
|
|
||||||
filename: "Rubik-Black.ttf"
|
|
||||||
post_script_name: "Rubik-Black"
|
|
||||||
full_name: "Rubik Black"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
fonts {
|
|
||||||
name: "Rubik"
|
|
||||||
style: "italic"
|
|
||||||
weight: 900
|
|
||||||
filename: "Rubik-BlackItalic.ttf"
|
|
||||||
post_script_name: "Rubik-BlackItalic"
|
|
||||||
full_name: "Rubik Black Italic"
|
|
||||||
copyright: "Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)"
|
|
||||||
}
|
|
||||||
subsets: "cyrillic"
|
|
||||||
subsets: "cyrillic-ext"
|
|
||||||
subsets: "hebrew"
|
|
||||||
subsets: "latin"
|
|
||||||
subsets: "latin-ext"
|
|
||||||
subsets: "menu"
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -227,6 +227,7 @@ AXES_FAILURE_LIMIT = 5
|
||||||
AXES_COOLOFF_TIME = 3 # in hours
|
AXES_COOLOFF_TIME = 3 # in hours
|
||||||
AXES_RESET_ON_SUCCESS = True
|
AXES_RESET_ON_SUCCESS = True
|
||||||
|
|
||||||
|
# TODO: verify parser works correctly
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
|
|
@ -235,7 +236,13 @@ REST_FRAMEWORK = {
|
||||||
"rest_framework.permissions.IsAuthenticated",
|
"rest_framework.permissions.IsAuthenticated",
|
||||||
"newsreader.accounts.permissions.IsOwner",
|
"newsreader.accounts.permissions.IsOwner",
|
||||||
),
|
),
|
||||||
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
|
"DEFAULT_RENDERER_CLASSES": (
|
||||||
|
"djangorestframework_camel_case.render.CamelCaseJSONRenderer",
|
||||||
|
),
|
||||||
|
|
||||||
|
"DEFAULT_PARSER_CLASSES": (
|
||||||
|
"djangorestframework_camel_case.parser.CamelCaseJSONParser",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
SWAGGER_SETTINGS = {
|
SWAGGER_SETTINGS = {
|
||||||
|
|
|
||||||
20
src/newsreader/js/components/NavList.js
Normal file
20
src/newsreader/js/components/NavList.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
class NavList extends React.Component {
|
||||||
|
render() {
|
||||||
|
const entries = Object.entries(this.props.navLinks);
|
||||||
|
const links = entries.map(([name, link], index) => {
|
||||||
|
return (
|
||||||
|
<li key={index} className="nav-list__item">
|
||||||
|
<a href={link}>{name}</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const className = this.props.includeBorder ? "nav-list nav-list--bordered": "nav-list";
|
||||||
|
|
||||||
|
return <ol className={className}>{links}</ol>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavList;
|
||||||
23
src/newsreader/js/components/Sidebar.js
Normal file
23
src/newsreader/js/components/Sidebar.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import NavList from './NavList.js';
|
||||||
|
|
||||||
|
// TODO: show empty category message
|
||||||
|
class Sidebar extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="sidebar">
|
||||||
|
<div className="sidebar__nav">
|
||||||
|
|
||||||
|
<NavList navLinks={this.props.navLinks} includeBorder={this.props.includeBorder} />
|
||||||
|
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label htmlFor="menu-input" className="sidebar__close" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
|
|
@ -2,3 +2,4 @@ import './lib/index.js';
|
||||||
import './pages/homepage/index.js';
|
import './pages/homepage/index.js';
|
||||||
import './pages/categories/index.js';
|
import './pages/categories/index.js';
|
||||||
import './pages/rules/index.js';
|
import './pages/rules/index.js';
|
||||||
|
import './pages/default/index.js';
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import Card from '../../components/Card.js';
|
||||||
import CategoryCard from './components/CategoryCard.js';
|
import CategoryCard from './components/CategoryCard.js';
|
||||||
import CategoryModal from './components/CategoryModal.js';
|
import CategoryModal from './components/CategoryModal.js';
|
||||||
import Messages from '../../components/Messages.js';
|
import Messages from '../../components/Messages.js';
|
||||||
|
import Sidebar from '../../components/Sidebar.js';
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
selectCategory = ::this.selectCategory;
|
selectCategory = ::this.selectCategory;
|
||||||
|
|
@ -90,15 +91,19 @@ class App extends React.Component {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.state.message && <Messages messages={[this.state.message]} />}
|
{this.state.message && <Messages messages={[this.state.message]} />}
|
||||||
<Card header={pageHeader} />
|
<Sidebar navLinks={this.props.navLinks} />
|
||||||
{cards}
|
|
||||||
{selectedCategory && (
|
<div className="main__container">
|
||||||
<CategoryModal
|
<Card header={pageHeader} />
|
||||||
category={selectedCategory}
|
{cards}
|
||||||
handleCancel={this.deselectCategory}
|
{selectedCategory && (
|
||||||
handleDelete={this.deleteCategory}
|
<CategoryModal
|
||||||
/>
|
category={selectedCategory}
|
||||||
)}
|
handleCancel={this.deselectCategory}
|
||||||
|
handleDelete={this.deleteCategory}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,15 @@ if (page) {
|
||||||
let createUrl = document.getElementById('createUrl').textContent;
|
let createUrl = document.getElementById('createUrl').textContent;
|
||||||
let updateUrl = document.getElementById('updateUrl').textContent;
|
let updateUrl = document.getElementById('updateUrl').textContent;
|
||||||
|
|
||||||
|
let linkScript = document.getElementById('Links');
|
||||||
|
let navLinks = JSON.parse(linkScript.textContent);
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<App
|
<App
|
||||||
categories={categories}
|
categories={categories}
|
||||||
createUrl={createUrl.substring(1, createUrl.length - 2)}
|
createUrl={createUrl.substring(1, createUrl.length - 2)}
|
||||||
updateUrl={updateUrl.substring(1, updateUrl.length - 4)}
|
updateUrl={updateUrl.substring(1, updateUrl.length - 4)}
|
||||||
|
navLinks={navLinks}
|
||||||
/>,
|
/>,
|
||||||
page
|
page
|
||||||
);
|
);
|
||||||
|
|
|
||||||
20
src/newsreader/js/pages/default/index.js
Normal file
20
src/newsreader/js/pages/default/index.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
|
import Sidebar from "../../components/Sidebar";
|
||||||
|
|
||||||
|
const mainElements = [...document.getElementsByClassName('main')];
|
||||||
|
const mainElement = mainElements.find(element => element.dataset.renderSidebar);
|
||||||
|
|
||||||
|
if (mainElement) {
|
||||||
|
let linkScript = document.getElementById('Links');
|
||||||
|
let navLinks = JSON.parse(linkScript.textContent);
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
ReactDOM.createPortal(
|
||||||
|
<Sidebar navLinks={navLinks} />,
|
||||||
|
mainElement
|
||||||
|
),
|
||||||
|
document.createElement('div')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -4,14 +4,25 @@ import { connect } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import { fetchCategories } from './actions/categories';
|
import { fetchCategories } from './actions/categories';
|
||||||
|
import { filterPosts } from './components/postlist/filters.js';
|
||||||
|
|
||||||
import ScrollTop from './components/ScrollTop.js';
|
import ScrollTop from './components/ScrollTop.js';
|
||||||
import Sidebar from './components/sidebar/Sidebar.js';
|
import HomepageSidebar from './components/sidebar/Sidebar.js';
|
||||||
import PostList from './components/postlist/PostList.js';
|
import PostList from './components/postlist/PostList.js';
|
||||||
import PostModal from './components/PostModal.js';
|
import PostModal from './components/PostModal.js';
|
||||||
import Messages from '../../components/Messages.js';
|
import Messages from '../../components/Messages.js';
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
|
state = { postListNode: null }
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.postListRef = node => {
|
||||||
|
this.setState({ postListNode: node });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchCategories();
|
this.props.fetchCategories();
|
||||||
}
|
}
|
||||||
|
|
@ -19,11 +30,13 @@ class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sidebar />
|
<HomepageSidebar navLinks={this.props.navLinks} />
|
||||||
<PostList
|
<PostList
|
||||||
feedUrl={this.props.feedUrl}
|
feedUrl={this.props.feedUrl}
|
||||||
subredditUrl={this.props.subredditUrl}
|
subredditUrl={this.props.subredditUrl}
|
||||||
timezone={this.props.timezone}
|
timezone={this.props.timezone}
|
||||||
|
forwardedRef={this.postListRef}
|
||||||
|
postsByType={this.props.postsByType}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isEqual(this.props.post, {}) && (
|
{!isEqual(this.props.post, {}) && (
|
||||||
|
|
@ -40,7 +53,7 @@ class App extends React.Component {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ScrollTop />
|
<ScrollTop postListNode={this.state.postListNode} />
|
||||||
|
|
||||||
{this.props.error && (
|
{this.props.error && (
|
||||||
<Messages messages={[{ type: 'error', text: this.props.error.message }]} />
|
<Messages messages={[{ type: 'error', text: this.props.error.message }]} />
|
||||||
|
|
@ -52,6 +65,7 @@ class App extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { error } = state.error;
|
const { error } = state.error;
|
||||||
|
const postsByType = filterPosts(state)
|
||||||
|
|
||||||
if (!isEqual(state.selected.post, {})) {
|
if (!isEqual(state.selected.post, {})) {
|
||||||
const ruleId = state.selected.post.rule.id;
|
const ruleId = state.selected.post.rule.id;
|
||||||
|
|
@ -65,10 +79,11 @@ const mapStateToProps = state => {
|
||||||
rule,
|
rule,
|
||||||
post: state.selected.post,
|
post: state.selected.post,
|
||||||
selectedType: state.selected.item.type,
|
selectedType: state.selected.item.type,
|
||||||
|
postsByType: postsByType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { error, post: state.selected.post };
|
return { error, post: state.selected.post, postsByType: postsByType, };
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class PostModal extends React.Component {
|
||||||
const titleClassName = post.read ? 'post__title post__title--read' : 'post__title';
|
const titleClassName = post.read ? 'post__title post__title--read' : 'post__title';
|
||||||
const readButtonDisabled =
|
const readButtonDisabled =
|
||||||
post.read || this.props.isUpdating || this.props.selectedType === SAVED_TYPE;
|
post.read || this.props.isUpdating || this.props.selectedType === SAVED_TYPE;
|
||||||
const savedIconClass = post.saved ? 'saved-icon saved-icon--saved' : 'saved-icon';
|
const savedIconClass = post.saved ? 'post__save post__save--saved saved-icon saved-icon--saved' : 'post__save saved-icon';
|
||||||
|
|
||||||
let ruleUrl = '';
|
let ruleUrl = '';
|
||||||
|
|
||||||
|
|
@ -63,59 +63,64 @@ class PostModal extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className="modal post-modal">
|
<div className="modal post-modal">
|
||||||
<div className="post">
|
<div className="post">
|
||||||
<div className="post__header">
|
<div className="post__container">
|
||||||
<div className="post__actions">
|
<div className="post__header">
|
||||||
<button
|
<div className="post__actions">
|
||||||
className={`button read-button ${readButtonDisabled &&
|
<button
|
||||||
'button--disabled'}`}
|
className={`button read-button ${readButtonDisabled &&
|
||||||
onClick={() =>
|
'button--disabled'}`}
|
||||||
!readButtonDisabled && this.props.markPostRead(post, token)
|
onClick={() =>
|
||||||
}
|
!readButtonDisabled && this.props.markPostRead(post, token)
|
||||||
>
|
}
|
||||||
<i className="fas fa-check" /> Mark as read
|
>
|
||||||
</button>
|
<i className="fas fa-check" /> Mark as read
|
||||||
<button
|
</button>
|
||||||
className="button post__close-button"
|
<button
|
||||||
onClick={() => this.props.unSelectPost()}
|
className="button post__close-button"
|
||||||
>
|
onClick={() => this.props.unSelectPost()}
|
||||||
<i className="fas fa-times"></i> Close
|
>
|
||||||
</button>
|
<i className="fas fa-times"></i> Close
|
||||||
</div>
|
</button>
|
||||||
<div className="post__heading">
|
</div>
|
||||||
<h2 className={titleClassName}>{`${post.title} `}</h2>
|
<div className="post__heading">
|
||||||
<div className="post__meta-info">
|
<h2 className={titleClassName}>{`${post.title} `}</h2>
|
||||||
<span className="post__date">
|
<div className="post__meta">
|
||||||
{publicationDate} {this.props.timezone}
|
<div className="post__text">
|
||||||
</span>
|
<span className="post__date">{publicationDate}</span>
|
||||||
{post.author && <span className="post__author">{post.author}</span>}
|
{post.author && <span className="post__author">{post.author}</span>}
|
||||||
{this.props.category && (
|
</div>
|
||||||
<span className="badge post__category" title={this.props.category.name}>
|
|
||||||
|
<div className="post__buttons">
|
||||||
|
{this.props.category && (
|
||||||
|
<span className="badge post__category" title={this.props.category.name}>
|
||||||
|
<a
|
||||||
|
href={`${this.props.categoriesUrl}/${this.props.category.id}/`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{this.props.category.name}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="badge post__rule" title={this.props.rule.name}>
|
||||||
|
<a href={ruleUrl} target="_blank" rel="noopener noreferrer">
|
||||||
|
{this.props.rule.name}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
<a
|
<a
|
||||||
href={`${this.props.categoriesUrl}/${this.props.category.id}/`}
|
className="post__link"
|
||||||
|
href={post.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{this.props.category.name}
|
<i className="fas fa-external-link-alt" />
|
||||||
</a>
|
</a>
|
||||||
</span>
|
<span
|
||||||
)}
|
className={savedIconClass}
|
||||||
<span className="badge post__rule" title={this.props.rule.name}>
|
onClick={() => this.props.toggleSaved(post, token)}
|
||||||
<a href={ruleUrl} target="_blank" rel="noopener noreferrer">
|
/>
|
||||||
{this.props.rule.name}
|
</div>
|
||||||
</a>
|
</div>
|
||||||
</span>
|
|
||||||
<a
|
|
||||||
className="post__link"
|
|
||||||
href={post.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<i className="fas fa-external-link-alt" />
|
|
||||||
</a>
|
|
||||||
<span
|
|
||||||
className={savedIconClass}
|
|
||||||
onClick={() => this.props.toggleSaved(post, token)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,35 +3,48 @@ import React from 'react';
|
||||||
export default class ScrollTop extends React.Component {
|
export default class ScrollTop extends React.Component {
|
||||||
scrollListener = ::this.scrollListener;
|
scrollListener = ::this.scrollListener;
|
||||||
|
|
||||||
state = { showTop: false, showBottom: false };
|
state = {
|
||||||
|
listenerAttached: false,
|
||||||
|
showTop: false,
|
||||||
|
showBottom: false
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidUpdate() {
|
||||||
window.addEventListener('scroll', this.scrollListener);
|
if (this.props.postListNode && !this.state.listenerAttached) {
|
||||||
|
this.props.postListNode.addEventListener('scroll', this.scrollListener);
|
||||||
|
|
||||||
|
this.setState({ listenerAttached: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollListener() {
|
scrollListener() {
|
||||||
const showBottom = window.innerHeight + window.scrollY < document.body.offsetHeight;
|
const postList = this.props.postListNode;
|
||||||
|
const elementEnd = (
|
||||||
|
postList.scrollTop + postList.offsetHeight>= postList.scrollHeight
|
||||||
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showTop: window.pageYOffset > 0 ? true : false,
|
showTop: postList.scrollTop > window.innerHeight,
|
||||||
showBottom: showBottom,
|
showBottom: !elementEnd,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
const postList = this.props.postListNode;
|
||||||
|
|
||||||
|
return postList && (
|
||||||
<div className="scroll-to-top">
|
<div className="scroll-to-top">
|
||||||
{this.state.showTop && (
|
{this.state.showTop && (
|
||||||
<i
|
<i
|
||||||
className="scroll-to-top__icon scroll-to-top__icon--top"
|
className="scroll-to-top__icon scroll-to-top__icon--top"
|
||||||
onClick={() => window.scrollTo(0, 0)}
|
onClick={() => postList.scroll({ top: 0 })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.showBottom && (
|
{this.state.showBottom && (
|
||||||
<i
|
<i
|
||||||
className="scroll-to-top__icon scroll-to-top__icon--bottom"
|
className="scroll-to-top__icon scroll-to-top__icon--bottom"
|
||||||
onClick={() => window.scrollTo(0, document.body.scrollHeight)}
|
onClick={() => postList.scroll({ top: postList.scrollHeight })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class PostItem extends React.Component {
|
||||||
|
|
||||||
<div className="posts-info">
|
<div className="posts-info">
|
||||||
<span className="posts-info__date" title={publicationDate}>
|
<span className="posts-info__date" title={publicationDate}>
|
||||||
{publicationDate} {this.props.timezone} {post.author && `By ${post.author}`}
|
{publicationDate} {post.author && `By ${post.author}`}
|
||||||
</span>
|
</span>
|
||||||
{[CATEGORY_TYPE, SAVED_TYPE].includes(this.props.selected.type) && (
|
{[CATEGORY_TYPE, SAVED_TYPE].includes(this.props.selected.type) && (
|
||||||
<span className="badge">
|
<span className="badge">
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ class PostList extends React.Component {
|
||||||
selected: this.props.selected,
|
selected: this.props.selected,
|
||||||
feedUrl: this.props.feedUrl,
|
feedUrl: this.props.feedUrl,
|
||||||
subredditUrl: this.props.subredditUrl,
|
subredditUrl: this.props.subredditUrl,
|
||||||
timezone: this.props.timezone,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLastItem?.id === item.id) {
|
if (isLastItem?.id === item.id) {
|
||||||
|
|
@ -96,7 +95,7 @@ class PostList extends React.Component {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="posts">
|
<div className="posts" ref={this.props.forwardedRef}>
|
||||||
<ul className="posts__list">{postItems}</ul>
|
<ul className="posts__list">{postItems}</ul>
|
||||||
{this.props.isFetching && <LoadingIndicator />}
|
{this.props.isFetching && <LoadingIndicator />}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -107,7 +106,6 @@ class PostList extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
isFetching: state.posts.isFetching,
|
isFetching: state.posts.isFetching,
|
||||||
postsByType: filterPosts(state),
|
|
||||||
next: state.selected.next,
|
next: state.selected.next,
|
||||||
lastReached: state.selected.lastReached,
|
lastReached: state.selected.lastReached,
|
||||||
selected: state.selected.item,
|
selected: state.selected.item,
|
||||||
|
|
@ -118,4 +116,4 @@ const mapDispatchToProps = dispatch => ({
|
||||||
fetchSavedPosts: (next = false) => dispatch(fetchSavedPosts(next)),
|
fetchSavedPosts: (next = false) => dispatch(fetchSavedPosts(next)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
|
export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(PostList);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
|
|
||||||
import { CATEGORY_TYPE } from '../../constants.js';
|
import { CATEGORY_TYPE } from '../../constants.js';
|
||||||
import { selectCategory, fetchCategory } from '../../actions/categories.js';
|
import { selectCategory, fetchCategory } from '../../actions/categories.js';
|
||||||
import { fetchPostsBySection } from '../../actions/posts.js';
|
import { fetchPostsBySection } from '../../actions/posts.js';
|
||||||
|
|
||||||
import { isSelected } from './functions.js';
|
import { isSelected } from './functions.js';
|
||||||
import RuleItem from './RuleItem.js';
|
import RuleItem from './RuleItem.js';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
|
|
||||||
import { RULE_TYPE } from '../../constants.js';
|
import { RULE_TYPE } from '../../constants.js';
|
||||||
import { selectRule, fetchRule } from '../../actions/rules.js';
|
import { selectRule, fetchRule } from '../../actions/rules.js';
|
||||||
import { fetchPostsBySection } from '../../actions/posts.js';
|
import { fetchPostsBySection } from '../../actions/posts.js';
|
||||||
|
|
||||||
import { isSelected } from './functions.js';
|
import { isSelected } from './functions.js';
|
||||||
|
|
||||||
class RuleItem extends React.Component {
|
class RuleItem extends React.Component {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
|
|
||||||
import { filterCategories, filterRules } from './filters.js';
|
import Sidebar from "../../../../components/Sidebar.js";
|
||||||
|
|
||||||
import { CATEGORY_TYPE, RULE_TYPE } from '../../constants.js';
|
|
||||||
import LoadingIndicator from '../../../../components/LoadingIndicator.js';
|
import LoadingIndicator from '../../../../components/LoadingIndicator.js';
|
||||||
|
import { CATEGORY_TYPE, RULE_TYPE } from '../../constants.js';
|
||||||
|
|
||||||
import CategoryItem from './CategoryItem.js';
|
import CategoryItem from './CategoryItem.js';
|
||||||
import SavedItem from './SavedItem.js';
|
import SavedItem from './SavedItem.js';
|
||||||
import ReadButton from './ReadButton.js';
|
import ReadButton from './ReadButton.js';
|
||||||
|
|
||||||
// TODO: show empty category message
|
import { filterCategories, filterRules } from './filters.js';
|
||||||
class Sidebar extends React.Component {
|
|
||||||
|
|
||||||
|
class HomepageSidebar extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const categoryItems = this.props.categories.items.map(category => {
|
const categoryItems = this.props.categories.items.map(category => { const rules = this.props.rules.items.filter(rule => {
|
||||||
const rules = this.props.rules.items.filter(rule => {
|
|
||||||
return rule.category === category.id;
|
return rule.category === category.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -32,22 +32,24 @@ class Sidebar extends React.Component {
|
||||||
this.props.selected.item &&
|
this.props.selected.item &&
|
||||||
[CATEGORY_TYPE, RULE_TYPE].includes(this.props.selected.item.type);
|
[CATEGORY_TYPE, RULE_TYPE].includes(this.props.selected.item.type);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar">
|
<Sidebar navLinks={this.props.navLinks} includeBorder={true} >
|
||||||
{(this.props.categories.isFetching || this.props.rules.isFetching) && (
|
{(this.props.categories.isFetching || this.props.rules.isFetching) && (
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ul className="sidebar__nav">
|
<ul className="sidebar__list">
|
||||||
<SavedItem selected={this.props.selected.item} />
|
<SavedItem selected={this.props.selected.item} />
|
||||||
{categoryItems}
|
{categoryItems}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{showReadButton && <ReadButton />}
|
{showReadButton && <ReadButton />}
|
||||||
</div>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
categories: { ...state.categories, items: filterCategories(state.categories.items) },
|
categories: { ...state.categories, items: filterCategories(state.categories.items) },
|
||||||
|
|
@ -55,4 +57,4 @@ const mapStateToProps = state => ({
|
||||||
selected: state.selected,
|
selected: state.selected,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(Sidebar);
|
export default connect(mapStateToProps)(HomepageSidebar);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ if (page) {
|
||||||
const settings = JSON.parse(document.getElementById('homepageSettings').textContent);
|
const settings = JSON.parse(document.getElementById('homepageSettings').textContent);
|
||||||
const { feedUrl, subredditUrl, categoriesUrl } = settings;
|
const { feedUrl, subredditUrl, categoriesUrl } = settings;
|
||||||
|
|
||||||
|
const navLinks = JSON.parse(document.getElementById('Links').textContent);
|
||||||
|
|
||||||
const app = (
|
const app = (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App
|
<App
|
||||||
|
|
@ -22,6 +24,7 @@ if (page) {
|
||||||
categoriesUrl={categoriesUrl.substring(1, categoriesUrl.length - 3)}
|
categoriesUrl={categoriesUrl.substring(1, categoriesUrl.length - 3)}
|
||||||
timezone={settings.timezone}
|
timezone={settings.timezone}
|
||||||
autoMarking={settings.autoMarking}
|
autoMarking={settings.autoMarking}
|
||||||
|
navLinks={navLinks}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="rule--page" class="main">
|
{% url "news:collection:rules" as cancel_url %}
|
||||||
{% url "news:collection:rules" as cancel_url %}
|
|
||||||
{% include "components/form/form.html" with form=form title="Add a feed" cancel_url=cancel_url confirm_text="Add feed" %}
|
<main id="rule--page" class="main" data-render-sidebar=true>
|
||||||
|
<div class="main__container">
|
||||||
|
{% include "components/form/form.html" with form=form title="Add a feed" cancel_url=cancel_url confirm_text="Add feed" %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="rule--page" class="main">
|
{% url "news:collection:rules" as cancel_url %}
|
||||||
{% if feed.error %}
|
{% trans "Failed to retrieve posts" as title %}
|
||||||
{% trans "Failed to retrieve posts" as title %}
|
|
||||||
{% include "components/textbox/textbox.html" with title=title body=feed.error class="text-section--error" only %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% url "news:collection:rules" as cancel_url %}
|
<main id="rule--page" class="main" data-render-sidebar=true>
|
||||||
{% include "components/form/form.html" with form=form title="Update feed" cancel_url=cancel_url confirm_text="Save feed" %}
|
<div class="main__container">
|
||||||
|
{% if feed.error %}
|
||||||
|
{% include "components/textbox/textbox.html" with title=title body=feed.error class="text-section--error" only %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include "components/form/form.html" with form=form title="Update feed" cancel_url=cancel_url confirm_text="Save feed" %}
|
||||||
|
<div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="import--page" class="main">
|
{% url "news:collection:rules" as cancel_url %}
|
||||||
{% url "news:collection:rules" as cancel_url %}
|
|
||||||
{% include "components/form/form.html" with form=form title="Import an OPML file" cancel_url=cancel_url confirm_text="Import feeds" %}
|
<main id="import--page" class="main" data-render-sidebar=true>
|
||||||
|
<div class="main__container">
|
||||||
|
{% include "components/form/form.html" with form=form title="Import an OPML file" cancel_url=cancel_url confirm_text="Import feeds" %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,102 +1,139 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load i18n static filters %}
|
{% load i18n static filters %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="rules--page" class="main">
|
<main id="rules--page" class="main" data-render-sidebar=true>
|
||||||
<form class="form rules-form">
|
<div class="main__container">
|
||||||
{% csrf_token %}
|
<form class="form rules-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
<section class="section form__section form__section--actions">
|
<section class="section form__section form__section--actions">
|
||||||
<div class="form__actions">
|
<div class="form__actions">
|
||||||
<a class="link button button--confirm" href="{% url "news:collection:feed-create" %}">{% trans "Add a feed" %}</a>
|
<a class="link button button--confirm" href="{% url "news:collection:feed-create" %}">{% trans "Add a feed" %}</a>
|
||||||
<a class="link button button--confirm" href="{% url "news:collection:import" %}">{% trans "Import feeds" %}</a>
|
<a class="link button button--confirm" href="{% url "news:collection:import" %}">{% trans "Import feeds" %}</a>
|
||||||
<a class="link button button--reddit" href="{% url "news:collection:subreddit-create" %}">{% trans "Add a subreddit" %}</a>
|
<a class="link button button--reddit" href="{% url "news:collection:subreddit-create" %}">{% trans "Add a subreddit" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section form__section form__section--actions">
|
<section class="section form__section form__section--actions">
|
||||||
<fieldset class="fieldset form__fieldset">
|
<fieldset class="fieldset form__fieldset">
|
||||||
<input type="submit" class="button button--primary" formaction="{% url "news:collection:rules-enable" %}" formmethod="post" value="{% trans "Enable" %}" />
|
<input type="submit" class="button button--primary" formaction="{% url "news:collection:rules-enable" %}" formmethod="post" value="{% trans "Enable" %}" />
|
||||||
<input type="submit" class="button button--primary" formaction="{% url "news:collection:rules-disable" %}" formmethod="post" value="{% trans "Disable" %}" />
|
<input type="submit" class="button button--primary" formaction="{% url "news:collection:rules-disable" %}" formmethod="post" value="{% trans "Disable" %}" />
|
||||||
<input type="submit" class="button button--error" formaction="{% url "news:collection:rules-delete" %}" formmethod="post" value="{% trans "Delete" %}"/>
|
<input type="submit" class="button button--error" formaction="{% url "news:collection:rules-delete" %}" formmethod="post" value="{% trans "Delete" %}"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section form__section">
|
<section class="section form__section">
|
||||||
<table class="table rules-table" border="0" cellspacing="0">
|
<table class="table rules-table" border="0" cellspacing="0">
|
||||||
<thead class="table__header rules-table__header">
|
<thead class="table__header rules-table__header">
|
||||||
<tr class="table__row rules-table__row">
|
<tr class="table__row rules-table__row">
|
||||||
<th class="table__heading rules-table__heading--select">
|
<th class="table__heading rules-table__heading--select">
|
||||||
{% include "components/form/checkbox.html" with id="select-all" data_input="rules" id_for_label="select-all" %}
|
{% include "components/form/checkbox.html" with id="select-all" data_input="rules" id_for_label="select-all" %}
|
||||||
</th>
|
</th>
|
||||||
<th class="table__heading rules-table__heading--name">{% trans "Name" %}</th>
|
<th class="table__heading rules-table__heading rules-table__heading--name">{% trans "Name" %}</th>
|
||||||
<th class="table__heading rules-table__heading--category">{% trans "Category" %}</th>
|
<th class="table__heading rules-table__heading rules-table__heading--category">{% trans "Category" %}</th>
|
||||||
<th class="table__heading rules-table__heading--url">{% trans "URL" %}</th>
|
<th class="table__heading rules-table__heading rules-table__heading--url">{% trans "URL" %}</th>
|
||||||
<th class="table__heading rules-table__heading--succeeded">{% trans "Successfuly ran" %}</th>
|
<th class="table__heading rules-table__heading rules-table__heading--succeeded">{% trans "Successfuly ran" %}</th>
|
||||||
<th class="table__heading rules-table__heading--enabled">{% trans "Enabled" %}</th>
|
<th class="table__heading rules-table__heading rules-table__heading--enabled">{% trans "Enabled" %}</th>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="table__body">
|
|
||||||
{% for rule in rules %}
|
|
||||||
<tr class="table__row {% if rule.failed %}table__row--error {% endif %}rules-table__row">
|
|
||||||
<td class="table__item rules-table__item">
|
|
||||||
{% with rule|id_for_label:"rules" as id_for_label %}
|
|
||||||
{% include "components/form/checkbox.html" with name="rules" value=rule.pk id=id_for_label id_for_label=id_for_label %}
|
|
||||||
{% endwith %}
|
|
||||||
</td>
|
|
||||||
<td class="table__item rules-table__item" title="{{ rule.name }}">
|
|
||||||
<a class="link" href="{{ rule.update_url }}">{{ rule.name }}</a>
|
|
||||||
</td>
|
|
||||||
<td class="table__item rules-table__item" title="{{ rule.category.name }}">
|
|
||||||
{% if rule.category %}
|
|
||||||
<a class="link" href="{% url 'news:core:category-update' pk=rule.category.pk %}">{{ rule.category.name }}</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="table__item rules-table__item" title="{{ rule.source_url }}">
|
|
||||||
<a class="link" href="{{ rule.source_url }}" target="_blank" rel="noopener noreferrer">{{ rule.source_url }}</a>
|
|
||||||
</td>
|
|
||||||
<td class="table__item rules-table__item">
|
|
||||||
{% if rule.failed %}
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="fas fa-check"></i>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="table__item rules-table__item">
|
|
||||||
{% if rule.enabled %}
|
|
||||||
<i class="fas fa-check"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="fas fa-pause"></i>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
</thead>
|
||||||
</tbody>
|
<tbody class="table__body rules-table__body">
|
||||||
</table>
|
{% for rule in rules %}
|
||||||
</section>
|
<tr class="table__row {% if rule.failed %}table__row--error{% endif %} rules-table__row">
|
||||||
</form>
|
<td class="table__item rules-table__item--select">
|
||||||
|
{% with rule|id_for_label:"rules" as id_for_label %}
|
||||||
|
{% include "components/form/checkbox.html" with name="rules" value=rule.pk id=id_for_label id_for_label=id_for_label %}
|
||||||
|
{% endwith %}
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="table__footer">
|
<td
|
||||||
<div class="pagination">
|
class="table__item rules-table__item rules-table__item--name"
|
||||||
<span class="pagination__previous">
|
title="{{ rule.name }}"
|
||||||
{% if page_obj.has_previous %}
|
>
|
||||||
<a class="link button" href="?page=1">{% trans "first" %}</a>
|
<a class="link" href="{{ rule.update_url }}">
|
||||||
<a class="link button" href="?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a>
|
{{ rule.name }}
|
||||||
{% endif %}
|
</a>
|
||||||
</span>
|
</td>
|
||||||
|
|
||||||
<span class="pagination__current">
|
<td
|
||||||
{% blocktrans with current_number=page_obj.number total_count=page_obj.paginator.num_pages %}
|
class="table__item rules-table__item rules-table__item--category"
|
||||||
Page {{ current_number }} of {{ total_count }}
|
title="{{ rule.category.name }}"
|
||||||
{% endblocktrans %}
|
>
|
||||||
</span>
|
{% if rule.category %}
|
||||||
|
<a
|
||||||
|
class="link"
|
||||||
|
href="{% url 'news:core:category-update' pk=rule.category.pk %}"
|
||||||
|
>
|
||||||
|
{{ rule.category.name }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
<span class="pagination__next">
|
<td
|
||||||
{% if page_obj.has_next %}
|
class="table__item rules-table__item rules-table__item--url"
|
||||||
<a class="link button" href="?page={{ page_obj.next_page_number }}">{% trans "next" %}</a>
|
title="{{ rule.source_url }}"
|
||||||
<a class="link button" href="?page={{ page_obj.paginator.num_pages }}">{% trans "last" %}</a>
|
>
|
||||||
{% endif %}
|
<a
|
||||||
</span>
|
class="link"
|
||||||
|
href="{{ rule.source_url }}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{{ rule.source_url }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="table__item rules-table__item rules-table__item--failed">
|
||||||
|
{% if rule.failed %}
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="table__item rules-table__item rules-table__item--enabled">
|
||||||
|
{% if rule.enabled %}
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-pause"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="table__footer">
|
||||||
|
<div class="pagination">
|
||||||
|
<span class="pagination__previous">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a class="link button" href="?page=1">{% trans "first" %}</a>
|
||||||
|
<a class="link button" href="?page={{ page_obj.previous_page_number }}">
|
||||||
|
{% trans "previous" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="pagination__current">
|
||||||
|
{% blocktrans with current_number=page_obj.number total_count=page_obj.paginator.num_pages %}
|
||||||
|
Page {{ current_number }} of {{ total_count }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="pagination__next">
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a class="link button" href="?page={{ page_obj.next_page_number }}">
|
||||||
|
{% trans "next" %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="link button" href="?page={{ page_obj.paginator.num_pages }}">
|
||||||
|
{% trans "last" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="subreddit--page" class="main">
|
{% url "news:collection:rules" as cancel_url %}
|
||||||
{% url "news:collection:rules" as cancel_url %}
|
|
||||||
{% include "components/form/form.html" with form=form title="Add a subreddit" cancel_url=cancel_url confirm_text="Add subrredit" %}
|
<main id="subreddit--page" class="main" data-render-sidebar=true>
|
||||||
|
<div class="main__container">
|
||||||
|
{% include "components/form/form.html" with form=form title="Add a subreddit" cancel_url=cancel_url confirm_text="Add subrredit" %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
||||||
|
|
||||||
from newsreader.news.collection.models import CollectionRule
|
from newsreader.news.collection.models import CollectionRule
|
||||||
from newsreader.news.core.models import Category
|
from newsreader.news.core.models import Category
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
class CollectionRuleViewMixin:
|
class CollectionRuleViewMixin(NavListMixin):
|
||||||
queryset = CollectionRule.objects.order_by("name")
|
queryset = CollectionRule.objects.order_by("name")
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from newsreader.news.collection.views.base import (
|
||||||
TaskCreationMixin,
|
TaskCreationMixin,
|
||||||
)
|
)
|
||||||
from newsreader.utils.opml import parse_opml
|
from newsreader.utils.opml import parse_opml
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
class FeedUpdateView(CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView):
|
class FeedUpdateView(CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView):
|
||||||
|
|
@ -36,7 +37,7 @@ class FeedCreateView(
|
||||||
form_class = FeedForm
|
form_class = FeedForm
|
||||||
|
|
||||||
|
|
||||||
class OPMLImportView(FormView):
|
class OPMLImportView(NavListMixin, FormView):
|
||||||
form_class = OPMLImportForm
|
form_class = OPMLImportForm
|
||||||
template_name = "news/collection/views/import.html"
|
template_name = "news/collection/views/import.html"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,20 @@ from django.views.generic.list import ListView
|
||||||
|
|
||||||
from newsreader.news.collection.forms import CollectionRuleBulkForm
|
from newsreader.news.collection.forms import CollectionRuleBulkForm
|
||||||
from newsreader.news.collection.views.base import CollectionRuleViewMixin
|
from newsreader.news.collection.views.base import CollectionRuleViewMixin
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
class CollectionRuleListView(CollectionRuleViewMixin, ListView):
|
class CollectionRuleListView(
|
||||||
|
CollectionRuleViewMixin,
|
||||||
|
NavListMixin,
|
||||||
|
ListView
|
||||||
|
):
|
||||||
paginate_by = 50
|
paginate_by = 50
|
||||||
template_name = "news/collection/views/rules.html"
|
template_name = "news/collection/views/rules.html"
|
||||||
context_object_name = "rules"
|
context_object_name = "rules"
|
||||||
|
|
||||||
|
|
||||||
class CollectionRuleBulkView(FormView):
|
class CollectionRuleBulkView(NavListMixin, FormView):
|
||||||
form_class = CollectionRuleBulkForm
|
form_class = CollectionRuleBulkForm
|
||||||
|
|
||||||
def get_redirect_url(self):
|
def get_redirect_url(self):
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
{{ categories_update_url|json_script:"updateUrl" }}
|
{{ categories_update_url|json_script:"updateUrl" }}
|
||||||
{{ categories_create_url|json_script:"createUrl" }}
|
{{ categories_create_url|json_script:"createUrl" }}
|
||||||
|
{{ sidebar_links|json_script:"Links" }}
|
||||||
|
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="category--page" class="main">
|
{% url "news:core:categories" as cancel_url %}
|
||||||
{% url "news:core:categories" as cancel_url %}
|
|
||||||
{% include "components/form/form.html" with form=form title="Create category" cancel_url=cancel_url confirm_text="Create category" %}
|
<main id="category--page" class="main" data-render-sidebar=true>
|
||||||
|
<div class="main__container">
|
||||||
|
{% include "components/form/form.html" with form=form title="Create category" cancel_url=cancel_url confirm_text="Create category" %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
{% extends "base.html" %}
|
{% extends "sidebar.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="category--page" class="main">
|
{% url "news:core:categories" as cancel_url %}
|
||||||
{% url "news:core:categories" as cancel_url %}
|
|
||||||
{% include "components/form/form.html" with form=form title="Update category" cancel_url=cancel_url confirm_text="Save category" %}
|
<main id="category--page" class="main" data-render-sidebar=true>
|
||||||
|
<div class="main__container">
|
||||||
|
{% include "components/form/form.html" with form=form title="Update category" cancel_url=cancel_url confirm_text="Save category" %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ homepageSettings|json_script:"homepageSettings" }}
|
{{ homepageSettings|json_script:"homepageSettings" }}
|
||||||
|
{{ sidebar_links|json_script:"Links" }}
|
||||||
|
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock scripts %}
|
{% endblock scripts %}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ from django.views.generic.list import ListView
|
||||||
from newsreader.news.collection.models import CollectionRule
|
from newsreader.news.collection.models import CollectionRule
|
||||||
from newsreader.news.core.forms import CategoryForm
|
from newsreader.news.core.forms import CategoryForm
|
||||||
from newsreader.news.core.models import Category
|
from newsreader.news.core.models import Category
|
||||||
|
from newsreader.utils.views import NavListMixin
|
||||||
|
|
||||||
|
|
||||||
class NewsView(TemplateView):
|
class NewsView(NavListMixin, TemplateView):
|
||||||
template_name = "news/core/views/homepage.html"
|
template_name = "news/core/views/homepage.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
@ -29,7 +30,7 @@ class NewsView(TemplateView):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CategoryViewMixin:
|
class CategoryViewMixin(NavListMixin):
|
||||||
queryset = Category.objects.prefetch_related("rules").order_by("name")
|
queryset = Category.objects.prefetch_related("rules").order_by("name")
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: Rubik, sans-serif;
|
font-family: Inter;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
@import '../../lib/mixins';
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -9,6 +12,10 @@
|
||||||
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -16,17 +23,17 @@
|
||||||
|
|
||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
|
|
||||||
border-bottom: 2px var(--lightest-accent-color) solid;
|
border-bottom: 2px var(--border-color) solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .favicon {
|
& .favicon {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
.checkbox-list {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/newsreader/scss/components/checkbox-list/index.scss
Normal file
1
src/newsreader/scss/components/checkbox-list/index.scss
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import './checkbox-list';
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
.fieldset {
|
.fieldset {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 0;
|
||||||
|
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -6,6 +8,10 @@
|
||||||
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&__section {
|
&__section {
|
||||||
&--last {
|
&--last {
|
||||||
& .form__fieldset {
|
& .form__fieldset {
|
||||||
|
|
@ -44,6 +50,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
@include block-padding;
|
@include block-padding;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,21 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
|
|
||||||
.rules-form {
|
.rules-form {
|
||||||
@extend .form;
|
@extend .form;
|
||||||
|
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
||||||
|
@media (max-width: $wqhd-breakpoint) {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
& .form__fieldset {
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,6 @@
|
||||||
@import './posts/index';
|
@import './posts/index';
|
||||||
@import './posts-info/index';
|
@import './posts-info/index';
|
||||||
@import './scroll-to-top/index';
|
@import './scroll-to-top/index';
|
||||||
|
@import './menu/index';
|
||||||
|
@import './nav-list/index';
|
||||||
|
@import './checkbox-list/index';
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
&__controls {
|
&__controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
|
|
||||||
& > * {
|
gap: 15px;
|
||||||
margin: 0 15px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 6px;
|
left: 6px;
|
||||||
width: 13px;
|
width: 13px;
|
||||||
background-color: $lavendal-pink;
|
background-color: var(--font-color);
|
||||||
animation: loading-indicator 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite;
|
animation: loading-indicator 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite;
|
||||||
|
|
||||||
&:nth-child(1){
|
&:nth-child(1){
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,22 @@
|
||||||
.main {
|
@import '../../partials/variables';
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin: 20px 0;
|
.main {
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: grid;
|
||||||
|
grid: [stack] 1fr / min-content [stack] 1fr;
|
||||||
|
|
||||||
|
& .sidebar, .post-message, .posts, #{&}__container {
|
||||||
|
grid-area: stack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
src/newsreader/scss/components/menu/_menu.scss
Normal file
26
src/newsreader/scss/components/menu/_menu.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
user-select: none;
|
||||||
|
touch-action: manipulation;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
color: var(--font-color);
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-input {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:checked ~ * .menu__icon::before {
|
||||||
|
@include font-awesome;
|
||||||
|
content: "\f410";
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/newsreader/scss/components/menu/index.scss
Normal file
1
src/newsreader/scss/components/menu/index.scss
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import './menu';
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
@import '../../partials/colors';
|
||||||
|
|
||||||
.messages {
|
.messages {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -6,7 +9,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 5px 0 20px 0;
|
margin: 5px 0 20px 0;
|
||||||
|
|
||||||
color: $font-color;
|
color: var(--font-color);
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
|
@ -17,6 +20,10 @@
|
||||||
|
|
||||||
background-color: $transparant-blue;
|
background-color: $transparant-blue;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
&--error {
|
&--error {
|
||||||
background-color: $transparant-red;
|
background-color: $transparant-red;
|
||||||
}
|
}
|
||||||
|
|
@ -42,12 +49,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--fixed &__item {
|
&--fixed &__item {
|
||||||
color: $white;
|
|
||||||
background-color: $blue;
|
background-color: $blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--fixed &__item--error {
|
&--fixed &__item--error {
|
||||||
color: $white;
|
|
||||||
background-color: $red;
|
background-color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +61,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--fixed &__item--success {
|
&--fixed &__item--success {
|
||||||
color: $white;
|
|
||||||
background-color: $green;
|
background-color: $green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -8,7 +10,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
background-color: $dark;
|
background-color: var(--background-color);
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -20,15 +22,17 @@
|
||||||
|
|
||||||
width: 60%;
|
width: 60%;
|
||||||
|
|
||||||
background-color: var(--accent-color);
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
padding: 5px 20px;
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
padding: 10px 30px;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
|
|
@ -36,6 +40,6 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
padding: 10px;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
.post-modal {
|
.post-modal {
|
||||||
@extend .modal;
|
@extend .modal;
|
||||||
|
|
||||||
|
|
@ -6,5 +8,9 @@
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (min-width: $tablet-breakpoint) {
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
src/newsreader/scss/components/nav-list/_nav-list.scss
Normal file
16
src/newsreader/scss/components/nav-list/_nav-list.scss
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
.nav-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
margin: 0px 10px;
|
||||||
|
|
||||||
|
& a {
|
||||||
|
@extend .button;
|
||||||
|
|
||||||
|
color: var(--font-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/newsreader/scss/components/nav-list/index.scss
Normal file
1
src/newsreader/scss/components/nav-list/index.scss
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import './nav-list';
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
@import '../../lib/functions';
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -5,34 +8,43 @@
|
||||||
|
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: map-deep-get($nav, height);
|
||||||
|
|
||||||
position: sticky;
|
background-color: var(--background-color);
|
||||||
top: 0;
|
|
||||||
|
|
||||||
background-color: var(--lightest-accent-color);
|
border-bottom: 2px var(--border-color) solid;
|
||||||
|
|
||||||
ol {
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
justify-content: space-between;
|
||||||
|
height: map-deep-get($nav, mobile, height);
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
font-size: map-deep-get($nav, mobile, font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
width: 80%;
|
width: 80%;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
@media (max-width: $mobile-breakpoint) {
|
||||||
margin: 0px 10px;
|
display: none;
|
||||||
|
|
||||||
& a {
|
|
||||||
@extend .button;
|
|
||||||
|
|
||||||
font-size: 0.9em !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item:last-child {
|
& .nav-list {
|
||||||
margin: 0 10px 0 auto;
|
width: 80%;
|
||||||
|
|
||||||
border-right: 2px solid var(--lighter-accent-color);
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item:last-child {
|
||||||
|
margin: 0 10px 0 auto;
|
||||||
|
|
||||||
|
border-right: 2px solid var(--border-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
width: 60%;
|
height: max-content;
|
||||||
height: 80vh;
|
margin: 20px 0 0 0;
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,44 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
@import '../../partials/colors';
|
||||||
|
@import '../../lib/functions';
|
||||||
|
@import '../../elements/button/';
|
||||||
|
|
||||||
.post {
|
.post {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
width: 80%;
|
width: 35%;
|
||||||
height: 90%;
|
|
||||||
|
@media (max-width: $wqhd-breakpoint) {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
height: max-content;
|
||||||
|
|
||||||
margin: 2% auto 5% auto;
|
margin: 2% auto 5% auto;
|
||||||
|
padding: 0 0 20px 0;
|
||||||
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
|
|
||||||
|
border-radius: 0.25em;
|
||||||
cursor: initial;
|
cursor: initial;
|
||||||
|
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -23,8 +47,6 @@
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,8 +55,15 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
padding: 20px 50px 0;
|
padding: 20px 0;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__heading {
|
&__heading {
|
||||||
|
|
@ -42,11 +71,18 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20px 0 10px 0;
|
padding: 20px 0 10px 0;
|
||||||
|
|
||||||
width: 75%;
|
@media (min-width: $hd-breakpoint) {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $hd-breakpoint) {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: $header-size;
|
font-size: map-deep-get($post, "header-font-size");
|
||||||
|
|
||||||
&--read {
|
&--read {
|
||||||
color: var(--read-color);
|
color: var(--read-color);
|
||||||
|
|
@ -62,8 +98,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__rule, &__category {
|
&__rule, &__category {
|
||||||
background-color: var(--lightest-accent-color) !important;
|
|
||||||
|
|
||||||
& a {
|
& a {
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +108,15 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
padding: 10px 0 30px 0;
|
padding: 10px 0 30px 0;
|
||||||
width: 75%;
|
|
||||||
|
@media (min-width: $hd-breakpoint) {
|
||||||
|
width: 72%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $hd-breakpoint) {
|
||||||
|
width: 90%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
& p {
|
& p {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
|
|
@ -98,19 +140,66 @@
|
||||||
|
|
||||||
&__close-button {
|
&__close-button {
|
||||||
background-color: var(--info-color);
|
background-color: var(--info-color);
|
||||||
color: var(--font-color);
|
color: $white;
|
||||||
|
|
||||||
& i {
|
& i {
|
||||||
padding: 0 $fa-padding 0 0;
|
padding: 0 $fa-padding 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__meta-info {
|
&__meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
|
@media (max-width: $hd-breakpoint) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text, &__buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: inherit;
|
||||||
|
align-items: inherit;
|
||||||
|
gap: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
@media (max-width: $hd-breakpoint) {
|
||||||
|
flex: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
@media (max-width: $hd-breakpoint) {
|
||||||
|
flex-wrap: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
@media (max-width: $tablet-breakpoint) {
|
||||||
|
@include button;
|
||||||
|
|
||||||
|
background-color: var(--info-color);
|
||||||
|
color: $white;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__save {
|
||||||
|
@media (max-width: $tablet-breakpoint) {
|
||||||
|
@include button;
|
||||||
|
|
||||||
|
background-color: var(--confirm-color);
|
||||||
|
color: $white;
|
||||||
|
width: 100px;
|
||||||
|
|
||||||
|
&--saved {
|
||||||
|
color: var(--read-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,27 @@
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint){
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link, .saved-icon {
|
||||||
|
@media (max-width: $mobile-breakpoint){
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .badge {
|
& .badge {
|
||||||
& .link {
|
& .link {
|
||||||
color: var(--font-color);
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint){
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
.posts {
|
@import '../../partials/variables';
|
||||||
margin: 0 0 2% 20px;
|
@import '../../lib/functions';
|
||||||
|
|
||||||
width: 70%;
|
.posts {
|
||||||
|
height: calc(100vh - map-deep-get($nav, height));
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 0 0 0 10px;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
height: calc(100vh - map-deep-get($nav, mobile, height));
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__list {
|
&__list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -11,6 +19,10 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
|
|
@ -21,12 +33,22 @@
|
||||||
|
|
||||||
max-width: max-content;
|
max-width: max-content;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
padding: 0 10px 10px 10px;
|
padding: 0 10px 10px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .badge {
|
& .badge {
|
||||||
background-color: var(--lightest-accent-color);
|
background-color: var(--background-color-secondary);
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint){
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
|
@ -38,7 +60,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: $header-size;
|
font-size: map-deep-get($post, header-font-size);
|
||||||
|
|
||||||
&--read {
|
&--read {
|
||||||
color: var(--read-color);
|
color: var(--read-color);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
.rules {
|
.rules {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
|
@ -6,19 +8,27 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
border-bottom-right-radius: .25em;
|
||||||
|
border-top-right-radius: .25em;
|
||||||
|
|
||||||
padding: 5px 5px 5px 20px;
|
padding: 5px 5px 5px 20px;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 25px 5px 20px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
& * {
|
& * {
|
||||||
padding: 0 2px 0 2px;
|
padding: 0 2px 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--lighter-accent-color);
|
background-color: var(--selected-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
background-color: var(--lighter-accent-color);
|
background-color: var(--selected-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
.scroll-to-top {
|
.scroll-to-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
@ -8,6 +10,10 @@
|
||||||
|
|
||||||
margin: 0 0 20px 0;
|
margin: 0 0 20px 0;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +22,7 @@
|
||||||
font-style: initial;
|
font-style: initial;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
background-color: var(--lightest-accent-color);
|
background-color: var(--background-color-secondary);
|
||||||
|
|
||||||
&--top:before {
|
&--top:before {
|
||||||
@include font-awesome;
|
@include font-awesome;
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,77 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
@import '../../lib/functions';
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
display: flex;
|
display: none; // hide the sidebar by default, homepage enables it by default
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
align-self: start;
|
|
||||||
|
|
||||||
position: sticky;
|
--easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
top: 50px;
|
--duration: .6s;
|
||||||
|
|
||||||
width: 20%;
|
height: calc(100vh - map-deep-get($nav, height));
|
||||||
|
|
||||||
|
font-size: map-deep-get($nav, font-size);
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: [nav] 5fr [escape] 1fr;
|
||||||
|
height: calc(100vh - map-deep-get($nav, mobile, height));
|
||||||
|
|
||||||
|
font-size: map-deep-get($sidebar, mobile, font-size);
|
||||||
|
|
||||||
|
overflow: hidden auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateX(-110vw);
|
||||||
|
will-change: transform;
|
||||||
|
transition:
|
||||||
|
transform var(--duration) var(--easeOutExpo),
|
||||||
|
visibility 0s linear var(--duration);
|
||||||
|
}
|
||||||
|
|
||||||
&__nav {
|
&__nav {
|
||||||
width: 100%;
|
display: flex;
|
||||||
max-height: 80vh;
|
flex-direction: column;
|
||||||
overflow: auto;
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
background-color: var(--background-color);
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__container {
|
&__container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
border-bottom-right-radius: .25em;
|
||||||
|
border-top-right-radius: .25em;
|
||||||
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
padding: 25px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
&--selected, &:hover {
|
&--selected, &:hover {
|
||||||
background-color: var(--lighter-accent-color);
|
background-color: var(--selected-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +97,60 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.read-button {
|
.read-button {
|
||||||
margin: 20px 0 0 0;
|
margin: 20px 0 0 10px;
|
||||||
|
width: max-content;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
margin: auto 0 20px 10px;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .nav-list {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&--bordered {
|
||||||
|
border-bottom: 2px var(--border-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
padding: 25px 15px;
|
||||||
|
border-bottom-right-radius: 0.25em;
|
||||||
|
border-bottom-left-radius: 0.25em;
|
||||||
|
|
||||||
|
& a {
|
||||||
|
|
||||||
|
color: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
|
||||||
|
align-items: initial;
|
||||||
|
justify-content: initial;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
@include font-awesome;
|
||||||
|
|
||||||
|
content: "\f35d";
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
#menu-input:checked ~ * .sidebar {
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform var(--duration) var(--easeOutExpo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
|
||||||
.rules-table {
|
.rules-table {
|
||||||
&__heading {
|
padding: 15px;
|
||||||
|
|
||||||
|
&__heading, &__item {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
&--select {
|
&--select {
|
||||||
width: 5%;
|
width: 5%;
|
||||||
|
|
||||||
|
& .checkbox {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--name {
|
&--name {
|
||||||
|
|
@ -10,10 +20,18 @@
|
||||||
|
|
||||||
&--category {
|
&--category {
|
||||||
width: 15%;
|
width: 15%;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--url {
|
&--url {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--succeeded {
|
&--succeeded {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
width: 90%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
@ -10,6 +10,10 @@
|
||||||
|
|
||||||
&__heading {
|
&__heading {
|
||||||
@extend .h1;
|
@extend .h1;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row {
|
&__row {
|
||||||
|
|
@ -27,7 +31,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
width: 80%;
|
width: 100%;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
@import '../../partials/colors';
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
|
|
@ -6,7 +9,12 @@
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
background-color: var(--lighter-accent-color);
|
background-color: var(--background-color-secondary);
|
||||||
|
|
||||||
font-size: small;
|
font-size: small;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.button {
|
@mixin button {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -7,16 +7,15 @@
|
||||||
@include button-padding;
|
@include button-padding;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
|
border-radius: 0.25em;
|
||||||
font-size: 16px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--success, &--confirm {
|
&--success, &--confirm {
|
||||||
color: var(--confirm-button-font-color) !important;
|
|
||||||
background-color: var(--confirm-color);
|
background-color: var(--confirm-color);
|
||||||
|
color: $white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--error, &--cancel {
|
&--error, &--cancel {
|
||||||
|
|
@ -40,6 +39,15 @@
|
||||||
|
|
||||||
&--disabled {
|
&--disabled {
|
||||||
color: var(--font-color) !important;
|
color: var(--font-color) !important;
|
||||||
background-color: $gray !important;
|
background-color: var(--background-color-secondary) !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
@include button;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
|
@import '../../partials/variables';
|
||||||
|
@import '../../partials/colors';
|
||||||
|
|
||||||
.read-button {
|
.read-button {
|
||||||
@extend .button;
|
@extend .button;
|
||||||
|
|
||||||
color: var(--confirm-button-font-color);
|
color: var(--confirm-font-color);
|
||||||
|
|
||||||
background-color: var(--confirm-color);
|
background-color: var(--confirm-color);
|
||||||
|
|
||||||
& i {
|
& i {
|
||||||
padding: 0 $fa-padding 0 0;
|
padding: 0 $fa-padding 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
display: block;
|
display: block;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
margin: 0 0 0 20px;
|
|
||||||
|
|
||||||
|
|
||||||
& input[type=checkbox] {
|
& input[type=checkbox] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -14,7 +12,7 @@
|
||||||
|
|
||||||
&:checked + .checkbox__label {
|
&:checked + .checkbox__label {
|
||||||
.checkbox__box {
|
.checkbox__box {
|
||||||
background-color: var(--lightest-accent-color);
|
background-color: var(--info-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +27,7 @@
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 2px solid var(--lighter-accent-color);
|
border: 1.5px solid var(--border-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
.help-text {
|
.help-text {
|
||||||
@extend .small;
|
@extend .small;
|
||||||
|
|
||||||
padding: 5px 15px;
|
padding: 10px 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.helptext {
|
.helptext {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,8 @@
|
||||||
@include text-padding;
|
@include text-padding;
|
||||||
|
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
background-color: var(--accent-color);
|
background-color: var(--background-color-secondary);
|
||||||
border: 1px var(--lighter-accent-color) solid;
|
border: 1px var(--border-color) solid;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border: 1px var(--lightest-accent-color) solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[type="file"] {
|
&[type="file"] {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
|
@ -15,7 +11,6 @@
|
||||||
|
|
||||||
&[type="checkbox"] {
|
&[type="checkbox"] {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
margin: 0 0 0 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.label {
|
.label {
|
||||||
@include text-padding;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
|
|
||||||
7
src/newsreader/scss/lib/_functions.scss
Normal file
7
src/newsreader/scss/lib/_functions.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
@function map-deep-get($map, $keys...) {
|
||||||
|
@each $key in $keys {
|
||||||
|
$map: map-get($map, $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@return $map;
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,26 @@
|
||||||
#homepage--page {
|
#homepage--page {
|
||||||
display: flex;
|
background-color: initial;
|
||||||
flex-direction: row;
|
|
||||||
align-items: initial;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
margin: 20px 0 0 0;
|
display: grid;
|
||||||
background-color: initial;
|
|
||||||
|
@media (min-width: $mobile-breakpoint) {
|
||||||
|
grid: [stack] 1fr/20% [stack] auto; // TODO: remove this line?
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $hd-breakpoint) {
|
||||||
|
grid: [stack] 1fr/15% [stack] auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $wqhd-breakpoint) {
|
||||||
|
grid: [stack] 1fr/12% [stack] auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $uhd-breakpoint) {
|
||||||
|
grid: [stack] 1fr/10% [stack] auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .sidebar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: [nav] 100% [escape] 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,24 @@
|
||||||
#login--page {
|
#login--page {
|
||||||
margin: 5% auto;
|
|
||||||
width: 50%;
|
|
||||||
|
|
||||||
& .form {
|
& .form {
|
||||||
@extend .form;
|
@extend .form;
|
||||||
|
|
||||||
width: 100%;
|
width: 20%;
|
||||||
|
|
||||||
|
@media (max-width: $wqhd-breakpoint) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $hd-breakpoint) {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $tablet-breakpoint) {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobile-breakpoint) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -21,9 +34,6 @@
|
||||||
|
|
||||||
&__fieldset {
|
&__fieldset {
|
||||||
@extend .form__fieldset;
|
@extend .form__fieldset;
|
||||||
|
|
||||||
&--last {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
#rules--page {
|
#rules--page {
|
||||||
& .table {
|
// TODO: remove scss
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
&__section {
|
&__section {
|
||||||
&--last {
|
&--last {
|
||||||
& .fieldset {
|
& .fieldset {
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,64 @@
|
||||||
$orange: rgba(255, 212, 153, 1);
|
$orange: #ff2a51;
|
||||||
$green: rgba(89, 181, 128, 1);
|
$green: #007936;
|
||||||
$red: lighten(rgba(231, 76, 60, 1), 10%);
|
$red: #d30038;
|
||||||
$gray: rgba(227, 227, 227, 1);
|
$blue: #0085f2;
|
||||||
$blue: rgba(111, 164, 196, 1);
|
|
||||||
|
|
||||||
$white: rgba(255, 255, 255, 1);
|
$white: #fff;
|
||||||
$black: rgba(0, 0, 0, 1);
|
$black: #000;
|
||||||
$dark: rgba(0, 0, 0, 0.4);
|
|
||||||
|
|
||||||
$reddit-orange: rgba(255, 69, 0, 1);
|
|
||||||
|
|
||||||
$transparant-red: transparentize($red, 0.8);
|
$transparant-red: transparentize($red, 0.8);
|
||||||
$transparant-blue: transparentize($blue, 0.8);
|
$transparant-blue: transparentize($blue, 0.8);
|
||||||
$transparant-orange: transparentize($orange, 0.4);
|
$transparant-orange: transparentize($orange, 0.4);
|
||||||
$transparant-green: transparentize($green, 0.4);
|
$transparant-green: transparentize($green, 0.4);
|
||||||
|
|
||||||
$azureish-white: rgba(205, 230, 245, 1);
|
|
||||||
$gainsboro: rgba(238, 238, 238, 1);
|
|
||||||
$nickel: rgba(112, 112, 120, 1);
|
|
||||||
$lavendal-pink: rgba(162, 155, 254, 1);
|
|
||||||
|
|
||||||
$focus-blue: darken($azureish-white, +10%);
|
|
||||||
$checkbox-blue: rgba(34, 170, 253, 1);
|
|
||||||
|
|
||||||
// White theme
|
// White theme
|
||||||
$background-color: rgba(255, 249, 176, 1);
|
$background-color: $white;
|
||||||
|
$background-color-secondary: #f9f9fb;
|
||||||
|
|
||||||
$font-color: rgba(83, 87, 91, 1);
|
$font-color: #1b1b1b;
|
||||||
$link-color: rgba(45, 142, 202, 1);
|
|
||||||
$read-color: darken($gainsboro, 10%);
|
|
||||||
$confirm-button-font-color: rgba(255, 255, 255, 1);
|
|
||||||
|
|
||||||
$accent-color: rgba(255, 171, 115, 1);
|
$link-color: #0069c2;
|
||||||
$lighter-accent-color: rgba(255, 211, 132, 1);
|
$selected-color: #0085f230;
|
||||||
$lightest-accent-color: rgba(255, 174, 192, 1);
|
$read-color: darken($font-color, 10%);
|
||||||
|
|
||||||
$confirm-color: rgba(117, 207, 184, 1);
|
$confirm-color: $green;
|
||||||
$danger-color: rgba(237, 118, 105, 1);
|
$confirm-font-color: $white;
|
||||||
$warning-color: rgba(255, 218, 119, 1);
|
|
||||||
$info-color: rgba(162, 213, 242, 1);
|
$danger-color: $red;
|
||||||
|
$danger-font-color: $white;
|
||||||
|
|
||||||
|
$warning-color: $orange;
|
||||||
|
$warning-font-color: $white;
|
||||||
|
|
||||||
|
$info-color: $blue;
|
||||||
|
$info-font-color: $white;
|
||||||
|
|
||||||
|
$sidebar-background-color: $background-color-secondary;
|
||||||
|
|
||||||
|
$border-color: #cdcdcd;
|
||||||
|
|
||||||
// Dark theme
|
// Dark theme
|
||||||
$dark-background-color: rgba(29, 45, 80, 1);
|
$dark-background-color: #1b1b1b;
|
||||||
|
$dark-background-color-secondary: #313131;
|
||||||
|
|
||||||
|
$dark-font-color: #cccccc;
|
||||||
|
|
||||||
$dark-font-color: darken($gray, 10%);
|
|
||||||
$dark-link-color: $link-color;
|
$dark-link-color: $link-color;
|
||||||
$dark-read-color: darken($dark-font-color, 20%);
|
$dark-read-color: darken($dark-font-color, 5%);
|
||||||
$dark-confirm-button-font-color: $dark-font-color;
|
|
||||||
|
|
||||||
$dark-accent-color: rgba(19, 59, 92, 1);
|
$dark-confirm-color: $green;
|
||||||
$dark-lighter-accent-color: rgba(30, 95, 116, 1);
|
$dark-confirm-font-color: $white;
|
||||||
$dark-lightest-accent-color: rgba(88, 61, 114, 1);
|
|
||||||
|
|
||||||
$dark-confirm-color: rgba(0, 121, 101, 1);
|
$dark-danger-color: $red;
|
||||||
$dark-danger-color: rgba(175, 45, 45, 1);
|
$dark-danger-font-color: $white;
|
||||||
$dark-warning-color: rgba(238, 187, 77, 1);
|
|
||||||
$dark-info-color: rgba(31, 111, 139, 1);
|
$dark-warning-color: $orange;
|
||||||
|
$dark-warning-font-color: $white;
|
||||||
|
|
||||||
|
$dark-info-color: $blue;
|
||||||
|
$dark-info-font-color: $white;
|
||||||
|
|
||||||
|
$dark-sidebar-background-color: $dark-background-color-secondary;
|
||||||
|
|
||||||
|
// Third party
|
||||||
|
$reddit-orange: rgba(255, 69, 0, 1);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Rubik;
|
font-family: Inter;
|
||||||
src: url('../assets/fonts/Rubik-Regular.ttf');
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('../assets/fonts/Inter-VariableFont_opsz,wght.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Rubik;
|
font-family: Inter;
|
||||||
src: url('../assets/fonts/Rubik-Bold.ttf');
|
font-style: italic;
|
||||||
font-weight: bold;
|
font-display: swap;
|
||||||
|
src: url('../assets/fonts/Inter-Italic-VariableFont_opsz,wght.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,37 @@
|
||||||
:root {
|
:root {
|
||||||
--background-color: #{$background-color};
|
--background-color: #{$background-color};
|
||||||
|
--background-color-secondary: #{$background-color-secondary};
|
||||||
|
|
||||||
--font-color: #{$font-color};
|
--font-color: #{$font-color};
|
||||||
--link-color: #{$link-color};
|
--link-color: #{$link-color};
|
||||||
|
--selected-color: #{$selected-color};
|
||||||
--read-color: #{$read-color};
|
--read-color: #{$read-color};
|
||||||
--confirm-button-font-color: #{$confirm-button-font-color};
|
|
||||||
|
|
||||||
--accent-color: #{$accent-color};
|
|
||||||
--lighter-accent-color: #{$lighter-accent-color};
|
|
||||||
--lightest-accent-color: #{$lightest-accent-color};
|
|
||||||
|
|
||||||
--confirm-color: #{$confirm-color};
|
--confirm-color: #{$confirm-color};
|
||||||
|
--confirm-font-color: #{$confirm-font-color};
|
||||||
|
|
||||||
--danger-color: #{$danger-color};
|
--danger-color: #{$danger-color};
|
||||||
|
--danger-font-color: #{$danger-color};
|
||||||
|
|
||||||
--warning-color: #{$warning-color};
|
--warning-color: #{$warning-color};
|
||||||
|
--warning-font-color: #{$warning-color};
|
||||||
|
|
||||||
--info-color: #{$info-color};
|
--info-color: #{$info-color};
|
||||||
|
--info-font-color: #{$info-color};
|
||||||
|
|
||||||
|
--border-color: #{$border-color};
|
||||||
|
|
||||||
&.dark-theme {
|
&.dark-theme {
|
||||||
--background-color: #{$dark-background-color};
|
--background-color: #{$dark-background-color};
|
||||||
|
--background-color-secondary: #{$dark-background-color-secondary};
|
||||||
|
|
||||||
--font-color: #{$dark-font-color};
|
--font-color: #{$dark-font-color};
|
||||||
--link-color: #{$dark-link-color};
|
--link-color: #{$dark-link-color};
|
||||||
--read-color: #{$dark-read-color};
|
--read-color: #{$dark-read-color};
|
||||||
--confirm-button-font-color: #{$dark-confirm-button-font-color};
|
|
||||||
|
|
||||||
--accent-color: #{$dark-accent-color};
|
|
||||||
--lighter-accent-color: #{$dark-lighter-accent-color};
|
|
||||||
--lightest-accent-color: #{$dark-lightest-accent-color};
|
|
||||||
|
|
||||||
--confirm-color: #{$dark-confirm-color};
|
--confirm-color: #{$dark-confirm-color};
|
||||||
|
--confirm-font-color: #{$dark-confirm-font-color};
|
||||||
|
|
||||||
--danger-color: #{$dark-danger-color};
|
--danger-color: #{$dark-danger-color};
|
||||||
--warning-color: #{$dark-warning-color};
|
--warning-color: #{$dark-warning-color};
|
||||||
--info-color: #{$dark-info-color};
|
--info-color: #{$dark-info-color};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,36 @@
|
||||||
$fa-padding: 7px;
|
$fa-padding: 7px;
|
||||||
|
|
||||||
$header-size: 1.2em;
|
// Fonts
|
||||||
$font-size: 1.1em;
|
$font-size: 1.1em;
|
||||||
|
$font-size-small: 0.833em;
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
$mobile-breakpoint: 540px;
|
||||||
|
$tablet-breakpoint: 1280px;
|
||||||
|
$hd-breakpoint: 1920px;
|
||||||
|
$wqhd-breakpoint: 2560px;
|
||||||
|
$uhd-breakpoint: 3840px;
|
||||||
|
|
||||||
|
$nav: (
|
||||||
|
height: 50px,
|
||||||
|
font-size: $font-size-small,
|
||||||
|
|
||||||
|
mobile: (
|
||||||
|
height: 75px,
|
||||||
|
font-size: 1.5em
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Post
|
||||||
|
$post: (
|
||||||
|
header-font-size: 1.2em
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sidebar
|
||||||
|
$sidebar: (
|
||||||
|
font-size: $font-size,
|
||||||
|
|
||||||
|
mobile: (
|
||||||
|
font-size: 1.2em,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Newreader</title>
|
<title>Newreader</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link type="image/png" href="{% static 'favicon.png' %}" rel="shortcut icon" />
|
<link type="image/png" href="{% static 'favicon.png' %}" rel="shortcut icon" />
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<link href="{% static 'css/main.css' %}" rel="stylesheet" />
|
<link href="{% static 'css/main.css' %}" rel="stylesheet" />
|
||||||
|
|
@ -11,22 +12,14 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="body">
|
<body class="body">
|
||||||
|
<input id="menu-input" type="checkbox" />
|
||||||
|
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<ol>
|
<div class="menu">
|
||||||
{% if request.user.is_authenticated %}
|
<label class="menu__icon fas fa-bars" for="menu-input" />
|
||||||
<li class="nav__item"><a href="{% url 'index' %}">Home</a></li>
|
</div>
|
||||||
<li class="nav__item"><a href="{% url 'news:core:categories' %}">Categories</a></li>
|
|
||||||
<li class="nav__item"><a href="{% url 'news:collection:rules' %}">Sources</a></li>
|
{% include "components/nav-list/nav-list.html" with request=request only %}
|
||||||
<li class="nav__item"><a href="{% url 'accounts:settings:home' %}">Settings</a></li>
|
|
||||||
{% if request.user.is_superuser %}
|
|
||||||
<li class="nav__item"><a href="{% url 'admin:index' %}">Admin</a></li>
|
|
||||||
{% endif %}
|
|
||||||
<li class="nav__item"><a href="{% url 'accounts:logout' %}">Logout</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li class="nav__item"><a href="{% url 'accounts:login' %}">Login</a></li>
|
|
||||||
<li class="nav__item"><a href="{% url 'accounts:register' %}">Register</a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<i class="theme-switcher fas fa-adjust"></i>
|
<i class="theme-switcher fas fa-adjust"></i>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
17
src/newsreader/templates/components/nav-list/nav-list.html
Normal file
17
src/newsreader/templates/components/nav-list/nav-list.html
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<ol class="nav-list">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<li class="nav-list__item"><a href="{% url 'index' %}">Home</a></li>
|
||||||
|
<li class="nav-list__item"><a href="{% url 'news:core:categories' %}">Categories</a></li>
|
||||||
|
<li class="nav-list__item"><a href="{% url 'news:collection:rules' %}">Sources</a></li>
|
||||||
|
<li class="nav-list__item"><a href="{% url 'accounts:settings:home' %}">Settings</a></li>
|
||||||
|
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
<li class="nav-list__item"><a href="{% url 'admin:index' %}">Admin</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="nav-list__item"><a href="{% url 'accounts:logout' %}">Logout</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-list__item"><a href="{% url 'accounts:login' %}">Login</a></li>
|
||||||
|
<li class="nav-list__item"><a href="{% url 'accounts:register' %}">Register</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ol>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue